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/Nim

Translation of: C
Library: OpenGL
`import math, os, random, strformat, timesimport opengl, opengl/glut, opengl/glu const  MinMountainRadius = 3  MaxMountainRadius = 5  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  ##################################################################################################### Vector. type Vec = tuple[x, y, z: float32]  func `*=`(a: var Vec; r: float32) =  a.x *= r  a.y *= r  a.z *= r func muladd(a: var Vec; b: Vec; r: float32) =  a.x += r * b.x  a.y += r * b.y  a.z += r * b.z func `+=`(a: var Vec; b: Vec) =  a.x += b.x  a.y += b.y  a.z += b.z func `+`(a, b: Vec): Vec =  (a.x + b.x, a.y + b.y, a.z + b.z) func `-`(a, b: Vec): Vec =  (a.x - b.x, a.y - b.y, a.z - b.z) func len2(a: Vec): float32 =  a.x * a.x + a.y * a.y + a.z * a.z func normalize(a: var Vec) =  let r = sqrt(a.len2)  if r == 0: return  a.x /= r  a.y /= r  a.z /= r  #################################################################################################### type   Boid = ref object    position: Vec    heading: Vec    newHeading: Vec    speed: float32   Mountain = ref object    x, y, h: int    r: float64    next: Mountain   World = object    x, y: array[2, int]   # mgroupingDrivein/max coords of world.    ground: seq[float32]    groundNormal: seq[Vec]    hills: Mountain   Camera = object    pitch: float    yaw: float    distance: float64    target: Vec  var  world: World  boids: array[N, Boid]  winWidth, winHeight: int  camera = Camera(pitch: -PI / 4, yaw: 0, distance: 100, target: (0f32, 0f32, 0f32))  cursorX, cursorY: int  updateTime: int  func hashXY(x, y: int): uint32 =  func ror(a: uint32; d: int): uint32 = a shl d or a shr (32 - d)   var h = 0x12345678u32  h += ror(h, 15) xor ror(x.uint32, 5)  h += ror(h, 15) xor ror(y.uint32, 5)   h = h xor ror(h, 7)  h += ror(h, 23)   h = h xor ror(h, 19)  h += ror(h, 11)  func hillHeight[T: SomeFloat](m: Mountain; x, y: T): T =    let x = x - T(m.x)    let y = y - T(m.y)    result = T(m.h) * exp(-(x * x + y * y) / (m.r * m.r))  proc groundHeight(x, y: float32): float32 =  var p = world.hills  while not p.isNil:    result += hillHeight(p, x, y)    p = p.next  proc calcNormal(x, y: float32): Vec =  var p = world.hills  while not p.isNil:    let h = hillHeight(p, float64(x), float64(y))    let t: float32 = 2 / (p.r * p.r)    result.x += (x - float32(p.x)) * t * h    result.y += (y - float32(p.y)) * t * h    p = p.next  result.z = 1  result.normalize()  proc resize(w, h: cint) {.cdecl.} =  (winWidth, winHeight) = (w, h)  glViewport(0, 0, w, h)  proc 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)   glMatrixMode(GL_PROJECTION)  glLoadIdentity()  glFrustum(-hor, hor, -ver, ver, 0.1, 1000)  func clamp(x: var float64; min, max: float64) =  if x < min: x = min  elif x > max: x = max  proc makeTerrain(cx, cy: int) =  if cx * 2 == world.x[0] + world.x[1] and 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  let nx = world.x[1] - world.x[0] + 1  let ny = world.y[1] - world.y[0] + 1  world.hills = nil  for x in world.x[0]..world.x[1]:    for y in world.y[0]..world.y[1]:      let h = hashXY(x, y) mod MountainRatio      if h != 0: continue      let m = Mountain(x: x,                       y: y,                       r: MinMountainRadius + float64(hashXY(y, x) mod 100) / 100 *                                              (MaxMountainRadius - MinMountainRadius),                       h: int(hashXY((y + x) div 2, (y - x) div 2) mod MaxMountainHeight),                       next: world.hills)      world.hills = m   if world.ground.len == 0:    world.ground = newSeq[float32](nx * ny)  if world.groundNormal.len == 0:    world.groundNormal = newSeq[Vec](nx * ny)   for x in 0..<nx:    let xx = x + world.x[0]    for y in 0..<ny:      let 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))  proc setLighting() =  var    lightAmbient = [float32 0.3, 0.3, 0.3, 1]    lightDiffuse = [float32 1, 1, 1, 1]    lightPosition = [float32 0, 1, 2, 1]   glEnable(GL_LIGHTING)  glLightfv(GL_LIGHT1, GL_AMBIENT, addr(lightAmbient[0]))  glLightfv(GL_LIGHT1, GL_DIFFUSE, addr(lightDiffuse[0]))  glLightfv(GL_LIGHT1, GL_POSITION, addr(lightPosition[0]))  glEnable(GL_LIGHT1)  glShadeModel(GL_FLAT)  glEnable(GL_COLOR_MATERIAL)  proc boidThink(boid: Boid) =  let g = groundHeight(boid.position.x, boid.position.y)  let migrationDrive: Vec = (0f32, 0.5f32, 0f32)  var heightDrive, crowdingDrive, groupingDrive: Vec  heightDrive.z = (IdealHeight + g - boid.position.z) * 0.3   # Follow the ground surface normal.  let terrainDrive = calcNormal(boid.position.x, boid.position.y)  var totalWeight = 0f32  for other in boids:    if other == boid: continue    var diff = other.position - boid.position    let d2 = diff.len2    let weight = 1 / (d2 * d2)    diff.normalize()    crowdingDrive.muladd(diff, if d2 > IdealDistance * IdealDistance: weight else: -weight)    groupingDrive.muladd(other.heading, weight)    totalWeight += weight   groupingDrive *= 1 / totalWeight  boid.newheading = migrationDrive + heightDrive + terrainDrive + crowdingDrive + groupingDrive  boid.newHeading *= 0.2  boid.newheading.normalize()   let cx = float32(world.x[0] + world.x[1]) / 2  let cy = float32(world.y[0] + world.y[1]) / 2  boid.newheading.x += (cx - boid.position.x) / 400  boid.newheading.y += (cy - boid.position.y) / 400  proc runBoids(msec: int) =  for boid in boids:    boid.position.muladd(boid.heading, float32(msec) * boid.speed)  var average: Vec  for boid in boids:    average += boid.position  average *= 1 / N  camera.target = average  makeTerrain(average.x.toInt, average.y.toInt)  for boid in boids:    boidThink(boid)  for boid in boids:    boid.heading = boid.newheading  proc drawTerrain() =  let nx = world.x[1] - world.x[0] + 1  let ny = world.y[1] - world.y[0] + 1  glColor3f(0.1, 0.25, 0.35)  for x in 0..<(nx-1):    let xx = x + world.x[0]    glBegin(GL_QUAD_STRIP)    for y in 0..<ny:      let yy = y + world.y[0]      glNormal3fv(addr(world.groundNormal[x*ny+y].x))      glVertex3f(float32(xx), float32(yy), world.ground[x*ny+y])      glNormal3fv(addr(world.groundNormal[(1+x)*ny+y].x))      glVertex3f(float32(xx+1), float32(yy), world.ground[(1+x)*ny+y])    glEnd()  proc draw(boid: Boid) =  glColor3f(0.6, 0.3, 0.3)  glPushMatrix()  glTranslatef(boid.position.x, boid.position.y, boid.position.z)  let (x, y, z) = boid.heading  let yaw: float32 = arctan2(float64(y), float64(x)) / PI * 180 - 90  glRotatef(yaw, 0, 0, 1)  let rxy = sqrt(float64(x * x + y * y))  let pitch: float32 = arctan2(float64(z), rxy) / PI * 180  glRotatef(pitch, 1, 0, 0)   glBegin(GL_TRIANGLES)   glNormal3f(-0.8, 0, 0.6)  glVertex3f(0, 0.5, 0)  glVertex3f(-0.5, -0.5, 0)  glVertex3f(0, 0, 0.1)   glNormal3f(0.8, 0, 0.6)  glVertex3f(0, 0.5, 0)  glVertex3f(0.5, -0.5, 0)  glVertex3f(0, 0, 0.1)   glNormal3f(-0.8, 0, -0.6)  glVertex3f(0, 0.5, 0)  glVertex3f(-0.5, -0.5, 0)  glVertex3f(0, 0, -0.1)   glNormal3f(0.8, 0, -0.6)  glVertex3f(0, 0.5, 0)  glVertex3f(0.5, -0.5, 0)  glVertex3f(0, 0, -0.1)   glNormal3f(1, -1, 0)  glVertex3f(-0.5, -0.5, 0)  glVertex3f(0, 0, 0.1)  glVertex3f(0, 0, -0.1)   glNormal3f(-1, -1, 0)  glVertex3f(0.5, -0.5, 0)  glVertex3f(0, 0, 0.1)  glVertex3f(0, 0, -0.1)   glEnd()  glPopMatrix()  proc render() {.cdecl.} =  let msec = (epochTime() * 1000).toInt  if msec < updateTime + 16:    sleep updateTime + 16 - msec   runBoids(msec - updateTime)  updateTime = msec  glClear(GL_COLOR_BUFFER_BIT or GL_DEPTH_BUFFER_BIT)  glEnable(GL_DEPTH_TEST)  setProjection(winWidth, winHeight)  clamp(camera.distance, 1, 1000)  clamp(camera.pitch, -PI / 2.1, PI / 2.1)  let rz = camera.distance * sin(camera.pitch)  let rxy = camera.distance * cos(camera.pitch)  glMatrixMode(GL_MODELVIEW)  glLoadIdentity()  setLighting()  stdout.write &"{camera.target.x:.5f} {camera.target.y:.5f}\r"  stdout.flushFile()  gluLookAt(camera.target.x - rxy * cos(camera.yaw),            camera.target.y - rxy * sin(camera.yaw),            camera.target.z - rz,            camera.target.x,            camera.target.y,            camera.target.z,            0, 0, 1)  drawTerrain()  for boid in boids:    boid.draw()  glFlush()  glutSwapBuffers()  proc keyDown(key: int8; x, y: cint) {.cdecl.} =  echo &"key down: {chr(key)} ({x} {y})"  if key == ord('q'):    quit QuitSuccess  proc mouseButton(button, state, x, y: cint) {.cdecl.} =  if state == GLUT_UP: return  if button == 3:    camera.distance /= 1.2  elif button == 4:    camera.distance *= 1.2  cursorX = x  cursorY = y  proc mouseMove(x, y: cint) {.cdecl.} =  let ext = max(winWidth, winHeight) div 4  camera.yaw -= (x - cursorX) / ext  camera.pitch -= (y - cursorY) / ext  cursorX = x  cursorY = y  proc initGL() =  updateTime = (epochTime() * 1000).toInt  glutInit()  glutInitDisplayMode(GLUT_RGB or GLUT_DOUBLE)  glutInitWindowSize(600, 400)  discard glutCreateWindow("Boids")  glutIgnoreKeyRepeat(1)  glutKeyboardFunc(keyDown)  glutReshapeFunc(resize)  glutIdleFunc(render)  glutDisplayFunc(render)  glutMouseFunc(mouseButton)  glutMotionFunc(mouseMove)  loadExtensions()  setLighting()  randomize()makeTerrain(0, 1)for i in 0..<N:  new(boids[i])  let x, y: float32 = rand(-5.0..5.0)  let z: float32 = rand(0.5..1.5) * IdealHeight + groundHeight(x, y)  boids[i].position = (x, y, z)  boids[i].speed = rand(0.98..1.02) * MoveSpeedinitGL()glutMainLoop()`