I'm working on modernizing Rosetta Code's infrastructure. Starting with communications. Please accept this time-limited open invite to RC's Slack.. --Michael Mol (talk) 20:59, 30 May 2020 (UTC)

# Boids/Go

Translation of: C
Library: go.gl
Library: glut

Although it's (hopefully) a faithful translation, not as smooth as the original with the boids getting quite spread out at times.

As I couldn't find a Go wrapper for the old 'glu' library, I've had to use cgo to call glu.LookAt directly. Only tested on Ubuntu 18.04.

`package main /*#cgo LDFLAGS: -lGLU#include <GL/glu.h>*/import "C"import (    "fmt"    "github.com/go-gl/gl/v2.1/gl"    "github.com/vbsw/glut"    "log"    "math"    "math/rand"    "os"    "time") const (    minMountainRadius = 3.0    maxMountainRadius = 5.0    maxMountainHeight = 25    mountainRatio     = 500    idealHeight       = 1 // how high a boid prefers to stay above ground    idealDistance     = 5 // how far boids prefer to stay away from each other    moveSpeed         = 1e-2    n                 = 100    worldSize         = 40) var updateTime time.Time // 3D vector stufftype vec struct{ x [3]float32 } func vscale(a *vec, r float32) {    a.x[0] *= r    a.x[1] *= r    a.x[2] *= r} func vmuladdTo(a, b *vec, r float32) {    a.x[0] += r * b.x[0]    a.x[1] += r * b.x[1]    a.x[2] += r * b.x[2]} func vaddTo(a, b *vec) {    a.x[0] += b.x[0]    a.x[1] += b.x[1]    a.x[2] += b.x[2]} func vadd(a, b vec) vec {    return vec{[3]float32{a.x[0] + b.x[0], a.x[1] + b.x[1], a.x[2] + b.x[2]}}} func vsub(a, b vec) vec {    return vec{[3]float32{a.x[0] - b.x[0], a.x[1] - b.x[1], a.x[2] - b.x[2]}}} func vlen2(a vec) float32 {    return a.x[0]*a.x[0] + a.x[1]*a.x[1] + a.x[2]*a.x[2]} func vdist2(a, b vec) float32 {    return vlen2(vsub(a, b))} func vcross(a, b vec) vec {    return vec{[3]float32{        a.x[1]*b.x[2] - a.x[2]*b.x[1],        a.x[2]*b.x[0] - a.x[0]*b.x[2],        a.x[0]*b.x[1] - a.x[1]*b.x[0],    }}} func vnormalize(a *vec) {    r := float32(math.Sqrt(float64(vlen2(*a))))    if r == 0 {        return    }    a.x[0] /= r    a.x[1] /= r    a.x[2] /= r} type boid struct {    position   vec    heading    vec    newheading vec    speed      float32} var boids [n]boid type mountain struct {    x, y, h int    r       float64    next    *mountain} type worldType struct {    x, y         [2]int // min/max coords of world    ground       []float32    groundNormal []vec    hills        *mountain} var world worldType type cameraType struct {    pitch, yaw, distance float64    target               vec} var camera = cameraType{-math.Pi / 4, 0, 100, vec{}} func hashXY(x, y int) uint {    ror := func(a uint, d int) uint {        return (a << d) | (a >> (32 - d))    }    var h, tmp uint = 0x12345678, uint(x)    h += ror(h, 15) ^ ror(tmp, 5)    tmp = uint(y)    h += ror(h, 15) ^ ror(tmp, 5)    h ^= ror(h, 7)    h += ror(h, 23)    h ^= ror(h, 19)    h += ror(h, 11)    return h} func hillHeight(m *mountain, x, y float64) float64 {    x -= float64(m.x)    y -= float64(m.y)    return float64(m.h) * math.Exp(-float64(x*x+y*y)/(m.r*m.r))} func hillHight(m *mountain, x, y float32) float32 {    xx, yy := x-float32(m.x), y-float32(m.y)    return float32(m.h) * float32(math.Exp(-float64(xx*xx+yy*yy)/(m.r*m.r)))} func groundHeight(x, y float32) float32 {    p := world.hills    h := float32(0)    for p != nil {        h += hillHight(p, x, y)        p = p.next    }    return h} func calcNormal(x, y float32) vec {    v := vec{}    p := world.hills    for p != nil {        h := float32(hillHeight(p, float64(x), float64(y)))        t := 2 / float32(p.r*p.r)        v.x[0] += (x - float32(p.x)) * t * h        v.x[1] += (y - float32(p.y)) * t * h        p = p.next    }    v.x[2] = 1    vnormalize(&v)    return v} func makeTerrain(cx, cy int) {    if cx*2 == world.x[0]+world.x[1] &&        cy*2 == world.y[0]+world.y[1] {        return    }    world.x[0] = cx - worldSize    world.x[1] = cx + worldSize    world.y[0] = cy - worldSize    world.y[1] = cy + worldSize    var x, y int    nx := world.x[1] - world.x[0] + 1    ny := world.y[1] - world.y[0] + 1    for world.hills != nil {        world.hills = world.hills.next    }    for x = world.x[0]; x <= world.x[1]; x++ {        for y = world.y[0]; y <= world.y[1]; y++ {            h := hashXY(x, y) % mountainRatio            if h != 0 {                continue            }            m := &mountain{}            m.x, m.y = x, y            m.r = minMountainRadius + float64(hashXY(y, x)%100)/100.0*                (maxMountainRadius-minMountainRadius)            m.h = int(hashXY((y+x)/2, (y-x)/2) % maxMountainHeight)            m.next = world.hills            world.hills = m        }    }    if world.ground == nil {        world.ground = make([]float32, nx*ny)    }    if world.groundNormal == nil {        world.groundNormal = make([]vec, nx*ny)    }    for x = 0; x < nx; x++ {        xx := x + world.x[0]        for y = 0; y < ny; y++ {            yy := y + world.y[0]            world.ground[x*ny+y] = groundHeight(float32(xx), float32(yy))            world.groundNormal[x*ny+y] = calcNormal(float32(xx), float32(yy))        }    }} func boidThink(b *boid) {    g := groundHeight(b.position.x[0], b.position.x[1])    migrationDrive := vec{[3]float32{0, 0.5, 0}}    heightDrive, crowdingDrive, groupingDrive := vec{}, vec{}, vec{}    heightDrive.x[2] = (idealHeight + g - b.position.x[2]) * 0.3     // follow the ground surface normal    terrainDrive := calcNormal(b.position.x[0], b.position.x[1])    totalWeight := float32(0)    for i := 0; i < n; i++ {        other := &boids[i]        if other == b {            continue        }        diff := vsub(other.position, b.position)        d2 := vlen2(diff)        weight := 1 / (d2 * d2)        vnormalize(&diff)        if d2 > idealDistance*idealDistance {            vmuladdTo(&crowdingDrive, &diff, weight)        } else {            vmuladdTo(&crowdingDrive, &diff, -weight)        }        vmuladdTo(&groupingDrive, &other.heading, weight)        totalWeight += weight    }    vscale(&groupingDrive, 1/totalWeight)    b.newheading = migrationDrive    vaddTo(&b.newheading, &heightDrive)    vaddTo(&b.newheading, &terrainDrive)    vaddTo(&b.newheading, &crowdingDrive)    vaddTo(&b.newheading, &groupingDrive)    vscale(&b.newheading, 0.2)    vnormalize(&b.newheading)     cx := float32(world.x[0]+world.x[1]) / 2.0    cy := float32(world.y[0]+world.y[1]) / 2.0    b.newheading.x[0] += (cx - b.position.x[0]) / 400    b.newheading.x[1] += (cy - b.position.x[1]) / 400} func runBoids(msec int) {    for i := 0; i < n; i++ {        vmuladdTo(&boids[i].position, &boids[i].heading, float32(msec)*boids[i].speed)    }    average := vec{}    for i := 0; i < n; i++ {        vaddTo(&average, &boids[i].position)    }    vscale(&average, 1.0/n)    camera.target = average    makeTerrain(int(average.x[0]), int(average.x[1]))    for i := 0; i < n; i++ {        boidThink(&boids[i])    }    for i := 0; i < n; i++ {        boids[i].heading = boids[i].newheading    }} // windowing stuffvar gwin, winWidth, winHeight int func resize(w, h int) {    winWidth, winHeight = w, h    gl.Viewport(0, 0, int32(w), int32(h))} func setProjection(w, h int) {    var hor, ver float64    if w > h {        hor = 0.05        ver = hor * float64(h) / float64(w)    } else {        ver = 0.05        hor = ver * float64(w) / float64(h)    }    gl.MatrixMode(gl.PROJECTION)    gl.LoadIdentity()    gl.Frustum(-hor, hor, -ver, ver, 0.1, 1000)} func clamp(x *float64, min, max float64) {    if *x < min {        *x = min    } else if *x > max {        *x = max    }} func drawTerrain() {    var x, y int    nx := world.x[1] - world.x[0] + 1    ny := world.y[1] - world.y[0] + 1    gl.Color3f(0.1, 0.25, 0.35)    for x = 0; x < nx-1; x++ {        xx := x + world.x[0]        gl.Begin(gl.QUAD_STRIP)        for y = 0; y < ny; y++ {            yy := y + world.y[0]            gl.Normal3fv(&world.groundNormal[x*ny+y].x[0])            gl.Vertex3f(float32(xx), float32(yy), world.ground[x*ny+y])            gl.Normal3fv(&world.groundNormal[(1+x)*ny+y].x[0])            gl.Vertex3f(float32(xx+1), float32(yy), world.ground[(1+x)*ny+y])        }        gl.End()    }} func drawBoid(b *boid) {    gl.Color3f(0.6, 0.3, 0.3)    gl.PushMatrix()    gl.Translatef(b.position.x[0], b.position.x[1], b.position.x[2])    x := b.heading.x    yaw := float32(math.Atan2(float64(x[1]), float64(x[0]))/math.Pi*180 - 90)    gl.Rotatef(yaw, 0, 0, 1)    rxy := float32(math.Sqrt(float64(x[0]*x[0] + x[1]*x[1])))    pitch := float32(math.Atan2(float64(x[2]), float64(rxy)) / math.Pi * 180)    gl.Rotatef(pitch, 1, 0, 0)     gl.Begin(gl.TRIANGLES)     gl.Normal3f(-0.8, 0, 0.6)    gl.Vertex3f(0, 0.5, 0)    gl.Vertex3f(-0.5, -0.5, 0)    gl.Vertex3f(0, 0, 0.1)     gl.Normal3f(0.8, 0, 0.6)    gl.Vertex3f(0, 0.5, 0)    gl.Vertex3f(0.5, -0.5, 0)    gl.Vertex3f(0, 0, 0.1)     gl.Normal3f(-0.8, 0, -0.6)    gl.Vertex3f(0, 0.5, 0)    gl.Vertex3f(-0.5, -0.5, 0)    gl.Vertex3f(0, 0, -0.1)     gl.Normal3f(0.8, 0, -0.6)    gl.Vertex3f(0, 0.5, 0)    gl.Vertex3f(0.5, -0.5, 0)    gl.Vertex3f(0, 0, -0.1)     gl.Normal3f(1, -1, 0)    gl.Vertex3f(-0.5, -0.5, 0)    gl.Vertex3f(0, 0, 0.1)    gl.Vertex3f(0, 0, -0.1)     gl.Normal3f(-1, -1, 0)    gl.Vertex3f(0.5, -0.5, 0)    gl.Vertex3f(0, 0, 0.1)    gl.Vertex3f(0, 0, -0.1)     gl.End()    gl.PopMatrix()} func setLighting() {    lightAmbient := [4]float32{0.3, 0.3, 0.3, 1}    lightDiffuse := [4]float32{1, 1, 1, 1}    lightPosition := [4]float32{0, 1, 2, 1}     gl.Enable(gl.LIGHTING)    gl.Lightfv(gl.LIGHT1, gl.AMBIENT, &lightAmbient[0])    gl.Lightfv(gl.LIGHT1, gl.DIFFUSE, &lightDiffuse[0])    gl.Lightfv(gl.LIGHT1, gl.POSITION, &lightPosition[0])    gl.Enable(gl.LIGHT1)    gl.ShadeModel(gl.FLAT)    gl.Enable(gl.COLOR_MATERIAL)} type dbl = C.GLdouble func render() {    now := time.Now()    msec := now.Sub(updateTime).Milliseconds()    if msec < 16 {        time.Sleep(time.Duration(16-msec) * time.Millisecond)        return    }    runBoids(int(msec))    updateTime = now    gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)    gl.Enable(gl.DEPTH_TEST)    setProjection(winWidth, winHeight)    clamp(&camera.distance, 1, 1000)    clamp(&camera.pitch, -math.Pi/2.1, math.Pi/2.1)    rz := camera.distance * math.Sin(camera.pitch)    rxy := camera.distance * math.Cos(camera.pitch)    gl.MatrixMode(gl.MODELVIEW)    gl.LoadIdentity()    setLighting()    fmt.Printf("%.5f %.5f\r", camera.target.x[0], camera.target.x[1])    C.gluLookAt(dbl(float64(camera.target.x[0])-rxy*math.Cos(camera.yaw)),        dbl(float64(camera.target.x[1])-rxy*math.Sin(camera.yaw)),        dbl(float64(camera.target.x[2])-rz),        dbl(camera.target.x[0]),        dbl(camera.target.x[1]),        dbl(camera.target.x[2]),        0, 0, 1)    drawTerrain()    for i := 0; i < n; i++ {        drawBoid(&boids[i])    }    gl.Flush()    glut.SwapBuffers()} func keydown(key byte, x, y int) {    fmt.Printf("key down: %c (%d %d)\n", key, x, y)    if key == 'q' {        os.Exit(0)    }} func keyup(key byte, x, y int) {    fmt.Printf("key up: %c (%d %d)\n", key, x, y)} // camera movement stuffvar cursorX, cursorY int func mousebutton(button, state, x, y int) {    if state == glut.UP {        return    }    if button == 3 {        camera.distance /= 2    } else if button == 4 {        camera.distance *= 2    }    cursorX, cursorY = x, y} func mousemove(x, y int) {    ext := winWidth    if ext < winHeight {        ext = winHeight    }    ext /= 4    camera.yaw -= float64(x-cursorX) / float64(ext)    camera.pitch -= float64(y-cursorY) / float64(ext)    cursorY, cursorX = y, x} func initGL() {    if err := gl.Init(); err != nil {        log.Fatal(err)    }    updateTime = time.Now()    glut.Init()    glut.InitDisplayMode(glut.RGB | glut.DOUBLE)    glut.InitWindowSize(600, 400)    gwin = glut.CreateWindow("Boids")    glut.IgnoreKeyRepeat(1)    glut.KeyboardFunc(keydown)    glut.KeyboardUpFunc(keyup)    glut.ReshapeFunc(resize)    glut.IdleFunc(render)    glut.MouseFunc(mousebutton)    glut.MotionFunc(mousemove)    setLighting()} func main() {    rand.Seed(time.Now().UnixNano())    makeTerrain(0, 1)    for i := 0; i < n; i++ {        x := float32(rand.Intn(10) - 5)        y := float32(rand.Intn(10) - 5)        z := (rand.Float32()+0.5)*idealHeight + groundHeight(x, y)        boids[i].position = vec{[3]float32{x, y, z}}        boids[i].speed = (0.98 + 0.04*rand.Float32()) * moveSpeed    }    initGL()    glut.MainLoop()}`