Boids/Wren

From Rosetta Code

Wren version of Boids.

Code

Translation of: C
Library: FreeGLUT
Library: Wren-dynamic
Library: Wren-fmt


As it's not currently possible for Wren-cli to access OpenGL directly, we embed a Wren script in a C application to complete this task. See the OpenGL#Wren task for some of the issues involved here.

/* Boids.wren */

import "random" for Random
import "./dynamic" for Struct
import "./fmt" for Fmt

var MIN_MOUNTAIN_RADIUS = 3
var MAX_MOUNTAIN_RADIUS = 5
var MAX_MOUNTAIN_HEIGHT = 25
var MOUNTAIN_RATIO      = 500

var IDEAL_HEIGHT   = 1  // how high a boid prefers to stay above ground
var IDEAL_DISTANCE = 5  // how far boids prefer to stay away from each other
var MOVE_SPEED     = 1e-2
var N              = 100
var WORLD_SIZE     = 40
var START          = System.clock

// 3D vector
class Vec {
    static zero { new([0, 0, 0]) }

    construct new(x) {
        _x = x
    }

    x { _x }

    [i]     { _x[i] }
    [i]=(v) { _x[i] = v }

    scale(r) {
        for (i in 0..2) _x[i] = _x[i] * r
    }

    mulAddTo(y, r) {
        for (i in 0..2) _x[i] = _x[i] + r * y[i]
    }

    addTo(y) {
        mulAddTo(y, 1)
    }

    +(y) {
        var z = List.filled(3, 0)
        for (i in 0..2) z[i] = _x[i] + y[i]
        return Vec.new(z)
    }

    -(y) {
        var z = List.filled(3, 0)
        for (i in 0..2) z[i] = _x[i] - y[i]
        return Vec.new(z)
    }

    len2 { _x[0]*_x[0] + _x[1]*_x[1] + _x[2]*_x[2] }

    dist2(y) { len2(this - y) }

    cross(y) {
        return Vec.new(
            _x[1]*y[2] - _x[2]*y[1],
		    _x[2]*y[0] - _x[0]*y[2],
		    _x[0]*y[1] - _x[1]*y[0]
        )
    }

    normalize() {
        var r = len2.sqrt
        if (r == 0) return
        scale(1/r)
    }
}

var Boid = Struct.create("Boid", ["position", "heading", "newHeading", "speed"])

var Boids = List.filled(N, null)
for (i in 0...N) Boids[i] = Boid.new(Vec.zero, Vec.zero, Vec.zero, 0)

var Mountain = Struct.create("Mountain", ["x", "y", "h", "r", "next"])

var World = Struct.create("World", ["x", "y", "ground", "groundNormal", "hills"])

var WorldV = World.new([0, 0], [0, 0], null, null, null)

var Camera = Struct.create("Camera", ["pitch", "yaw", "distance", "target"])

var CameraV = Camera.new(-Num.pi/4, 0, 100, Vec.zero)

var Rand = Random.new()

var UpdateTime = 0

var GetMsec = Fn.new { ((System.clock - START) * 1000).round }

var HashXY = Fn.new { |x, y|
    var ror = Fn.new { |a, d| (a << d) | (a >> (32 - d)) }
    var h = 0x12345678
    h = h + ror.call(h, 15) ^ ror.call(x, 5)
    h = h + ror.call(h, 15) ^ ror.call(y, 5)
    h = h ^ ror.call(h,  7)
    h = h + ror.call(h, 23)
    h = h ^ ror.call(h, 19)
    h = h + ror.call(h, 11)
    return h
}

var HillHeight = Fn.new { |m, x, y|
    x = x - m.x
    y = y - m.y
    return m.h * (-(x * x + y * y) / (m.r * m.r)).exp
}

var GroundHeight = Fn.new { |x, y|
    var m = WorldV.hills
    var h = 0
    while (m) {
        h = h + HillHeight.call(m, x, y)
        m = m.next
    }
    return h
}

var CalcNormal = Fn.new { |x, y|
    var v = Vec.zero
    var m = WorldV.hills
    while (m) {
        var h = HillHeight.call(m, x, y)
        var t = 2 / (m.r * m.r)
        v[0] = v[0] + (x - m.x) * t * h
        v[1] = v[1] + (y - m.y) * t * h
        m = m.next
    }
    v[2] = 1
    v.normalize()
    return v
}

var MakeTerrain = Fn.new { |cx, cy|
    if (cx*2 == WorldV.x[0] + WorldV.x[1] && cy*2 == WorldV.y[0] + WorldV.y[1]) return
    WorldV.x[0] = cx - WORLD_SIZE
	WorldV.x[1] = cx + WORLD_SIZE
	WorldV.y[0] = cy - WORLD_SIZE
	WorldV.y[1] = cy + WORLD_SIZE

    var nx = WorldV.x[1] - WorldV.x[0] + 1
	var ny = WorldV.y[1] - WorldV.y[0] + 1

    while (WorldV.hills) {
        var m = WorldV.hills.next
        WorldV.hills = m
    }

    for (x in WorldV.x[0]..WorldV.x[1]) {
        for (y in WorldV.y[0]..WorldV.y[1]) {
            var h = HashXY.call(x, y) % MOUNTAIN_RATIO
            if (h != 0) continue
            var m = Mountain.new(
                x, y,
                HashXY.call(((y + x)/2).floor, ((y - x)/2).floor) % MAX_MOUNTAIN_HEIGHT,
                MIN_MOUNTAIN_RADIUS + (HashXY.call(y, x) % 100) / 100 *
                (MAX_MOUNTAIN_RADIUS -  MIN_MOUNTAIN_RADIUS),
                WorldV.hills
            )
            WorldV.hills = m
        }
    }
    if (!WorldV.ground) WorldV.ground = List.filled(nx * ny, 0)
    if (!WorldV.groundNormal) WorldV.groundNormal = List.filled(nx * ny, null)
    for (x in 0...nx) {
		var xx = x + WorldV.x[0]
		for (y in 0...ny) {
			var yy = y + WorldV.y[0]
            var ix = x * ny + y
			WorldV.ground[ix] = GroundHeight.call(xx, yy)
			WorldV.groundNormal[ix] = CalcNormal.call(xx, yy)
		}
	}
}

var BoidThink = Fn.new { |b|
    var g = GroundHeight.call(b.position[0], b.position[1])
    var migrationDrive = Vec.new([0, 0.5, 0])
    var heightDrive = Vec.zero
    heightDrive[2] = (IDEAL_HEIGHT + g - b.position[2]) * 0.3

    // follow the ground surface normal
    var terrainDrive = CalcNormal.call(b.position[0], b.position[1])
    var crowdingDrive = Vec.zero
    var groupingDrive = Vec.zero
    var totalWeight = 0
    for (i in 0...N) {
        var other = Boids[i]
        if (other == b) continue
        var diff = other.position - b.position
        var d2 = diff.len2
        var weight = 1 / (d2 * d2)
        diff.normalize()
        if (d2 > IDEAL_DISTANCE * IDEAL_DISTANCE) {
		    crowdingDrive.mulAddTo(diff, weight)
	    } else {
		    crowdingDrive.mulAddTo(diff, -weight)
        }
	    groupingDrive.mulAddTo(other.heading, weight)
	    totalWeight = totalWeight + weight
	}
    groupingDrive.scale(1/totalWeight)
    b.newHeading = migrationDrive
    b.newHeading.addTo(heightDrive)
    b.newHeading.addTo(terrainDrive)
    b.newHeading.addTo(crowdingDrive)
    b.newHeading.addTo(groupingDrive)
    b.newHeading.scale(0.2)
    b.newHeading.normalize()

    var cx = (WorldV.x[0] + WorldV.x[1]) / 2
	var cy = (WorldV.y[0] + WorldV.y[1]) / 2
	b.newHeading[0] = b.newHeading[0] + (cx - b.position[0]) / 400
	b.newHeading[1] = b.newHeading[1] + (cy - b.position[1]) / 400
}

var RunBoids = Fn.new { |msec|
    for (i in 0...N) Boids[i].position.mulAddTo(Boids[i].heading, msec * Boids[i].speed)
    var average = Vec.zero
    for (i in 0...N) average.addTo(Boids[i].position)
    average.scale(1/N)
    CameraV.target = average
    MakeTerrain.call(average[0].truncate, average[1].truncate)
    for (i in 0...N) BoidThink.call(Boids[i])
    for (i in 0...N) Boids[i].heading = Boids[i].newHeading
}

class CUtils {
    foreign static usleep(usec)

    foreign static flushStdout()
}

// GL stuff

var GL_PROJECTION = 0x1701
var GL_QUAD_STRIP = 0x0008
var GL_TRIANGLES  = 0x0004
var GL_LIGHTING   = 0x0b50
var GL_LIGHT1     = 0x4001
var GL_AMBIENT    = 0x1200
var GL_DIFFUSE    = 0x1201
var GL_POSITION   = 0x1203
var GL_FLAT       = 0x1d00
var GL_MODELVIEW  = 0x1700

var GL_COLOR_MATERIAL   = 0x0b57
var GL_COLOR_BUFFER_BIT = 0x4000
var GL_DEPTH_BUFFER_BIT = 0x0100
var GL_DEPTH_TEST       = 0x0b71

var GLUT_UP     = 0x0001
var GLUT_DEPTH  = 0x0010
var GLUT_RGB    = 0x0000
var GLUT_DOUBLE = 0x0002

var GLUT_ACTION_ON_WINDOW_CLOSE      = 0x01f9
var GLUT_ACTION_GLUTMAINLOOP_RETURNS = 0x0001

class GL {
    foreign static clear(mask)

    foreign static shadeModel(mode)

    foreign static loadIdentity()

    foreign static frustum(left, right, bottom, top, nearVal, farVal)

    foreign static pushMatrix()

    foreign static normal3f(nx, ny, nz)

    foreign static normal3fv(v)

    foreign static rotatef(angle, x, y, z)

    foreign static translatef(x, y, z)

    foreign static begin(mode)

    foreign static color3f(red, green, blue)

    foreign static vertex3f(x, y, z)

    foreign static popMatrix()

    foreign static end()

    foreign static flush()

    foreign static lightfv(light, pname, params)

    foreign static enable(cap)

    foreign static viewport(x, y, width, height)

    foreign static matrixMode(mode)
}

class GLU {
    foreign static lookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ)
}

class Glut {
    foreign static initDisplayMode(mode)

    foreign static initWindowSize(width, height)

    foreign static createWindow(name)

    foreign static swapBuffers()

    foreign static ignoreKeyRepeat(ignore)

    foreign static keyboardFunc(clazz, method)

    foreign static keyboardUpFunc(clazz, method)

    foreign static reshapeFunc(clazz, method)

    foreign static idleFunc(clazz, method)

    foreign static mouseFunc(clazz, method)

    foreign static motionFunc(clazz, method)

    foreign static setOption(eWhat, value)

    foreign static leaveMainLoop()
}

var WinWidth = 600
var WinHeight = 400
var CursorX = 0
var CursorY = 0

var DrawTerrain = Fn.new {
	var nx = (WorldV.x[1] - WorldV.x[0] + 1).round
	var ny = (WorldV.y[1] - WorldV.y[0] + 1).round

	GL.color3f(0.1, 0.25, 0.35)

	for (x in 0...nx - 1) {
		var xx = x + WorldV.x[0]
		GL.begin(GL_QUAD_STRIP)

		for (y in 0...ny) {
			var yy = y + WorldV.y[0]
            var ix = x * ny + y
			GL.normal3fv(WorldV.groundNormal[ix].x)
			GL.vertex3f(xx, yy, WorldV.ground[ix])
            ix = ix + ny
			GL.normal3fv(WorldV.groundNormal[ix].x)
			GL.vertex3f(xx + 1, yy, WorldV.ground[ix])
		}

		GL.end()
	}
}

var DrawBoid = Fn.new { |b|
	GL.color3f(0.6, 0.3, 0.3)
	GL.pushMatrix()
	GL.translatef(b.position[0], b.position[1], b.position[2])

	var x = b.heading.x
	var yaw = x[1].atan(x[0]) / Num.pi * 180 - 90
	GL.rotatef(yaw, 0, 0, 1)

	var rxy = (x[0] * x[0] + x[1] * x[1]).sqrt
	var pitch = x[2].atan(rxy) / Num.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()
}

var SetProjection = Fn.new { |w, h|
    var hor
    var ver
    if (w > h) {
        hor = 0.05
        ver = hor * h / w
    } else {
        ver = 0.05
        hor = ver * w / h
    }
    GL.matrixMode(GL_PROJECTION)
    GL.loadIdentity()
    GL.frustum(-hor, hor, -ver, ver, 0.1, 1000)
}

var SetLighting = Fn.new {
	var lightAmbient  = [0.3, 0.3, 0.3, 1]
	var lightDiffuse  = [1, 1, 1, 1]
	var lightPosition = [0, 1, 2, 1]

	GL.enable(GL_LIGHTING)
	GL.lightfv(GL_LIGHT1, GL_AMBIENT,  lightAmbient)
	GL.lightfv(GL_LIGHT1, GL_DIFFUSE,  lightDiffuse)
	GL.lightfv(GL_LIGHT1, GL_POSITION, lightPosition)
	GL.enable(GL_LIGHT1)
	GL.shadeModel(GL_FLAT)
	GL.enable(GL_COLOR_MATERIAL)
}

class GLCallbacks {
    static keyDown(key, x, y) {
        System.print("Key down: %(key) (%(x) %(y))")
        if (key == 113) Glut.leaveMainLoop() // q pressed
    }

    static keyUp(key, x, y) {
        System.print("Key up: %(key) (%(x) %(y))")
    }

    static resize(w, h) {
        WinWidth = w
        WinHeight = h
        GL.viewport(0, 0, w, h)
    }

    static render() {
        var msec = GetMsec.call()
	    if (msec < UpdateTime + 16) {
		    CUtils.usleep((UpdateTime + 16 - msec) * 1000)
		    return
	    }
	    RunBoids.call(msec - UpdateTime)
	    UpdateTime = msec

	    GL.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
	    GL.enable(GL_DEPTH_TEST)

	    SetProjection.call(WinWidth, WinHeight)

	    CameraV.distance = CameraV.distance.clamp(1, 1000)
	    CameraV.pitch = CameraV.pitch.clamp(-Num.pi / 2.1, Num.pi / 2.1)

	    var rz  = CameraV.distance * (CameraV.pitch.sin)
	    var rxy = CameraV.distance * (CameraV.pitch.cos)

	    GL.matrixMode(GL_MODELVIEW)
	    GL.loadIdentity()

	    SetLighting.call()
	    Fmt.write("$0.5f $0.5f\r", CameraV.target[0], CameraV.target[1])
	    CUtils.flushStdout()

	    GLU.lookAt(
            CameraV.target[0] - rxy * (CameraV.yaw.cos),
		    CameraV.target[1] - rxy * (CameraV.yaw.sin),
		    CameraV.target[2] - rz,
		    CameraV.target[0],
		    CameraV.target[1],
		    CameraV.target[2],
		    0, 0, 1
        )

	    DrawTerrain.call()
	    for (i in 0...N) DrawBoid.call(Boids[i])
        GL.flush()
	    Glut.swapBuffers()
    }

    static mouseButton(button, state, x, y) {
        if (state == GLUT_UP) return
        if (button == 3) {
            CameraV.distance = CameraV.distance / 2
        } else if (button == 4) {
            CameraV.distance = CameraV.distance * 2
        }
        CursorX = x
        CursorY = y
    }

    static mouseMove(x, y) {
        var ext = WinWidth
        if (ext < WinHeight) ext = WinHeight
        ext = (ext / 4).floor
        CameraV.yaw   = CameraV.yaw   - (x - CursorX)/ext
        CameraV.pitch = CameraV.pitch - (y - CursorY)/ext
        CursorX = x
        CursorY = y
    }
}

var InitGL = Fn.new {
    UpdateTime = GetMsec.call()
	Glut.initDisplayMode(GLUT_DEPTH | GLUT_RGB | GLUT_DOUBLE)
	Glut.initWindowSize(600, 400) 
	Glut.createWindow("Boids")
    Glut.ignoreKeyRepeat(1)
	Glut.keyboardFunc("GLCallbacks", "keyDown(_,_,_)")
	Glut.keyboardUpFunc("GLCallbacks", "keyUp(_,_,_)")
	Glut.reshapeFunc("GLCallbacks", "resize(_,_)")
	Glut.idleFunc("GLCallbacks", "render()")
	Glut.mouseFunc("GLCallbacks", "mouseButton(_,_,_,_)")
	Glut.motionFunc("GLCallbacks", "mouseMove(_,_)")
    Glut.setOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS)
	SetLighting.call()
}

MakeTerrain.call(0, 1)
for (i in 0...N) {
    var x = Rand.float() * 10 - 5
    var y = Rand.float() * 10 - 5
    var z = (Rand.float() + 0.5) * IDEAL_HEIGHT + GroundHeight.call(x, y)
    Boids[i].position = Vec.new([x, y, z])
    Boids[i].speed = (0.98 + 0.04 * Rand.float()) * MOVE_SPEED
}
InitGL.call()


We now embed this Wren script in the following C program, compile and run it.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <GL/gl.h>
#include <GL/freeglut.h>
#include <GL/glu.h>
#include <unistd.h>
#include "wren.h"

/* C <=> Wren interface functions */

WrenVM *vm;

const char *keyboardClass, *keyboardMethod, *keyboardUpClass, *keyboardUpMethod, 
           *reshapeClass, *reshapeMethod, *idleClass, *idleMethod,
           *mouseClass, *mouseMethod, *motionClass, *motionMethod;

void keyboard(unsigned char key, int x, int y) {
    wrenEnsureSlots(vm, 4);
    wrenGetVariable(vm, "main", keyboardClass, 0);
    wrenSetSlotDouble(vm, 1, (double)key);
    wrenSetSlotDouble(vm, 2, (double)x);
    wrenSetSlotDouble(vm, 3, (double)y);
    WrenHandle *method = wrenMakeCallHandle(vm, keyboardMethod);
    wrenCall(vm, method);
    wrenReleaseHandle(vm, method);
}

void keyboardUp(unsigned char key, int x, int y) {
    wrenEnsureSlots(vm, 4);
    wrenGetVariable(vm, "main", keyboardUpClass, 0);
    wrenSetSlotDouble(vm, 1, (double)key);
    wrenSetSlotDouble(vm, 2, (double)x);
    wrenSetSlotDouble(vm, 3, (double)y);
    WrenHandle *method = wrenMakeCallHandle(vm, keyboardUpMethod);
    wrenCall(vm, method);
    wrenReleaseHandle(vm, method);
}

void reshape(int width, int height) {
    wrenEnsureSlots(vm, 3);
    wrenGetVariable(vm, "main", reshapeClass, 0);
    wrenSetSlotDouble(vm, 1, (double)width);
    wrenSetSlotDouble(vm, 2, (double)height);
    WrenHandle *method = wrenMakeCallHandle(vm, reshapeMethod);
    wrenCall(vm, method);
    wrenReleaseHandle(vm, method);
}

void idle() {
    wrenEnsureSlots(vm, 1);
    wrenGetVariable(vm, "main", idleClass, 0);
    WrenHandle *method = wrenMakeCallHandle(vm, idleMethod);
    wrenCall(vm, method);
    wrenReleaseHandle(vm, method);
}

void mouse(int button, int state, int x, int y) {
    wrenEnsureSlots(vm, 5);
    wrenGetVariable(vm, "main", mouseClass, 0);
    wrenSetSlotDouble(vm, 1, (double)button);
    wrenSetSlotDouble(vm, 2, (double)state);
    wrenSetSlotDouble(vm, 3, (double)x);
    wrenSetSlotDouble(vm, 4, (double)y);
    WrenHandle *method = wrenMakeCallHandle(vm, mouseMethod);
    wrenCall(vm, method);
    wrenReleaseHandle(vm, method);
}

void motion(int x, int y) {
    wrenEnsureSlots(vm, 3);
    wrenGetVariable(vm, "main", motionClass, 0);
    wrenSetSlotDouble(vm, 1, (double)x);
    wrenSetSlotDouble(vm, 2, (double)y);
    WrenHandle *method = wrenMakeCallHandle(vm, motionMethod);
    wrenCall(vm, method);
    wrenReleaseHandle(vm, method);
}

void C_clear(WrenVM* vm) {
    GLbitfield mask = (GLbitfield)wrenGetSlotDouble(vm, 1);
    glClear(mask);
}

void C_shadeModel(WrenVM* vm) {
    GLenum mode = (GLenum)wrenGetSlotDouble(vm, 1);
    glShadeModel(mode);
}

void C_loadIdentity(WrenVM* vm) {
    glLoadIdentity();
}

void C_frustum(WrenVM* vm) {
    GLdouble left    = (GLdouble)wrenGetSlotDouble(vm, 1);
    GLdouble right   = (GLdouble)wrenGetSlotDouble(vm, 2);
    GLdouble bottom  = (GLdouble)wrenGetSlotDouble(vm, 3);    
    GLdouble top     = (GLdouble)wrenGetSlotDouble(vm, 4);
    GLdouble nearVal = (GLdouble)wrenGetSlotDouble(vm, 5);    
    GLdouble farVal  = (GLdouble)wrenGetSlotDouble(vm, 6);
    glFrustum(left, right, bottom, top, nearVal, farVal);
}

void C_pushMatrix(WrenVM* vm) {
    glPushMatrix();
}

void C_normal3f(WrenVM* vm) {
    GLfloat nx = (GLfloat)wrenGetSlotDouble(vm, 1);
    GLfloat ny = (GLfloat)wrenGetSlotDouble(vm, 2);
    GLfloat nz = (GLfloat)wrenGetSlotDouble(vm, 3);
    glNormal3f(nx, ny, nz);
}

void C_normal3fv(WrenVM* vm) {
    GLfloat v[3];
    int i;
    for (i = 0; i < 3; ++i) {
        wrenGetListElement(vm, 1, i, 2);
        v[i] = (GLfloat)wrenGetSlotDouble(vm, 2);
    }
    glNormal3fv((const GLfloat*)v);
}

void C_rotatef(WrenVM* vm) {
    GLdouble angle = (GLdouble)wrenGetSlotDouble(vm, 1);
    GLdouble x     = (GLdouble)wrenGetSlotDouble(vm, 2);
    GLdouble y     = (GLdouble)wrenGetSlotDouble(vm, 3);
    GLdouble z     = (GLdouble)wrenGetSlotDouble(vm, 4);
    glRotatef(angle, x, y, z);
}

void C_translatef(WrenVM* vm) {
    GLfloat x = (GLfloat)wrenGetSlotDouble(vm, 1);
    GLfloat y = (GLfloat)wrenGetSlotDouble(vm, 2);
    GLfloat z = (GLfloat)wrenGetSlotDouble(vm, 3);
    glTranslatef(x, y, z);
}

void C_begin(WrenVM* vm) {
    GLenum mode = (GLenum)wrenGetSlotDouble(vm, 1);
    glBegin(mode);
}

void C_color3f(WrenVM* vm) {
    GLfloat red   = (GLfloat)wrenGetSlotDouble(vm, 1);
    GLfloat green = (GLfloat)wrenGetSlotDouble(vm, 2);
    GLfloat blue  = (GLfloat)wrenGetSlotDouble(vm, 3);
    glColor3f(red, green, blue);
}

void C_vertex3f(WrenVM* vm) {
    GLfloat x = (GLfloat)wrenGetSlotDouble(vm, 1);
    GLfloat y = (GLfloat)wrenGetSlotDouble(vm, 2);
    GLfloat z = (GLfloat)wrenGetSlotDouble(vm, 3);
    glVertex3f(x, y, z);
}

void C_popMatrix(WrenVM* vm) {
    glPopMatrix();
}

void C_end(WrenVM* vm) {
    glEnd();
}

void C_flush(WrenVM* vm) {
    glFlush();
}

void C_lightfv(WrenVM* vm) {
    GLenum light  = (GLenum)wrenGetSlotDouble(vm, 1);
    GLenum pname  = (GLenum)wrenGetSlotDouble(vm, 2);
    int count = wrenGetListCount(vm, 3);
    GLfloat params[count];
    int i;
    for (i = 0; i < count; ++i) {
        wrenGetListElement(vm, 3, i, 1);
        params[i] = (GLfloat)wrenGetSlotDouble(vm, 1);
    }
    glLightfv(light, pname, (const GLfloat*)params);
}

void C_enable(WrenVM* vm) {
    GLenum cap = (GLenum)wrenGetSlotDouble(vm, 1);
    glEnable(cap);
}

void C_viewport(WrenVM* vm) {
    GLint x        = (GLint)  wrenGetSlotDouble(vm, 1);
    GLint y        = (GLint)  wrenGetSlotDouble(vm, 2);
    GLsizei width  = (GLsizei)wrenGetSlotDouble(vm, 3);
    GLsizei height = (GLsizei)wrenGetSlotDouble(vm, 4);
    glViewport(x, y, width, height);
}

void C_matrixMode(WrenVM* vm) {
    GLenum mode = (GLenum)wrenGetSlotDouble(vm, 1);
    glMatrixMode(mode);
}

void C_lookAt(WrenVM* vm) {
    GLdouble eyeX    = (GLdouble)wrenGetSlotDouble(vm, 1);
    GLdouble eyeY    = (GLdouble)wrenGetSlotDouble(vm, 2);
    GLdouble eyeZ    = (GLdouble)wrenGetSlotDouble(vm, 3);    
    GLdouble centerX = (GLdouble)wrenGetSlotDouble(vm, 4);
    GLdouble centerY = (GLdouble)wrenGetSlotDouble(vm, 5);
    GLdouble centerZ = (GLdouble)wrenGetSlotDouble(vm, 6);
    GLdouble upX     = (GLdouble)wrenGetSlotDouble(vm, 7);
    GLdouble upY     = (GLdouble)wrenGetSlotDouble(vm, 8);
    GLdouble upZ     = (GLdouble)wrenGetSlotDouble(vm, 9);
    gluLookAt(eyeX, eyeY, eyeZ, centerX, centerY, centerZ, upX, upY, upZ);
}

void C_initDisplayMode(WrenVM* vm) {
    unsigned int mode = (unsigned int)wrenGetSlotDouble(vm, 1);
    glutInitDisplayMode(mode);
}

void C_initWindowSize(WrenVM* vm) {
    int width  = (int)wrenGetSlotDouble(vm, 1);
    int height = (int)wrenGetSlotDouble(vm, 2);
    glutInitWindowSize(width, height);
}

void C_createWindow(WrenVM* vm) {
    const char *name = wrenGetSlotString(vm, 1);
    glutCreateWindow(name);
}

void C_swapBuffers(WrenVM* vm) {
    glutSwapBuffers();
}

void C_ignoreKeyRepeat(WrenVM* vm) {
    int ignore = (int)wrenGetSlotDouble(vm, 1);
    glutIgnoreKeyRepeat(ignore);
}

void C_keyboardFunc(WrenVM* vm) {
    keyboardClass  = wrenGetSlotString(vm, 1);
    keyboardMethod = wrenGetSlotString(vm, 2);
    glutKeyboardFunc(&keyboard);
}

void C_keyboardUpFunc(WrenVM* vm) {
    keyboardUpClass  = wrenGetSlotString(vm, 1);
    keyboardUpMethod = wrenGetSlotString(vm, 2);
    glutKeyboardUpFunc(&keyboardUp);
}

void C_reshapeFunc(WrenVM* vm) {
    reshapeClass  = wrenGetSlotString(vm, 1);
    reshapeMethod = wrenGetSlotString(vm, 2);
    glutReshapeFunc(&reshape);
}

void C_idleFunc(WrenVM* vm) {
    idleClass  = wrenGetSlotString(vm, 1);
    idleMethod = wrenGetSlotString(vm, 2);
    glutIdleFunc(&idle);
}

void C_mouseFunc(WrenVM* vm) {
    mouseClass  = wrenGetSlotString(vm, 1);
    mouseMethod = wrenGetSlotString(vm, 2);
    glutMouseFunc(&mouse);
}

void C_motionFunc(WrenVM* vm) {
    motionClass  = wrenGetSlotString(vm, 1);
    motionMethod = wrenGetSlotString(vm, 2);
    glutMotionFunc(&motion);
}

void C_setOption(WrenVM* vm) {
    GLenum eWhat = (GLenum)wrenGetSlotDouble(vm, 1);
    int value    = (int)wrenGetSlotDouble(vm, 2);
    glutSetOption(eWhat, value);
}

void C_leaveMainLoop(WrenVM* vm) {
    glutLeaveMainLoop();
}

void C_usleep(WrenVM* vm) {
    useconds_t usec = (useconds_t)wrenGetSlotDouble(vm, 1);
    usleep(usec);
}

void C_flushStdout(WrenVM* vm) {
    fflush(stdout);
}

WrenForeignMethodFn bindForeignMethod(
    WrenVM* vm,
    const char* module,
    const char* className,
    bool isStatic,
    const char* signature) {
    if (strcmp(module, "main") == 0) {
        if (strcmp(className, "GL") == 0) {
            if (isStatic && strcmp(signature, "clear(_)") == 0)             return C_clear;
            if (isStatic && strcmp(signature, "shadeModel(_)") == 0)        return C_shadeModel;
            if (isStatic && strcmp(signature, "loadIdentity()") == 0)       return C_loadIdentity;
            if (isStatic && strcmp(signature, "frustum(_,_,_,_,_,_)") == 0) return C_frustum;
            if (isStatic && strcmp(signature, "pushMatrix()") == 0)         return C_pushMatrix;
            if (isStatic && strcmp(signature, "normal3f(_,_,_)") == 0)      return C_normal3f;
            if (isStatic && strcmp(signature, "normal3fv(_)") == 0)         return C_normal3fv;
            if (isStatic && strcmp(signature, "rotatef(_,_,_,_)") == 0)     return C_rotatef;
            if (isStatic && strcmp(signature, "translatef(_,_,_)") == 0)    return C_translatef;
            if (isStatic && strcmp(signature, "begin(_)") == 0)             return C_begin;
            if (isStatic && strcmp(signature, "color3f(_,_,_)") == 0)       return C_color3f;
            if (isStatic && strcmp(signature, "vertex3f(_,_,_)") == 0)      return C_vertex3f;
            if (isStatic && strcmp(signature, "popMatrix()") == 0)          return C_popMatrix;
            if (isStatic && strcmp(signature, "end()") == 0)                return C_end;
            if (isStatic && strcmp(signature, "flush()") == 0)              return C_flush;
            if (isStatic && strcmp(signature, "lightfv(_,_,_)") == 0)       return C_lightfv;
            if (isStatic && strcmp(signature, "enable(_)") == 0)            return C_enable;
            if (isStatic && strcmp(signature, "viewport(_,_,_,_)") == 0)    return C_viewport;
            if (isStatic && strcmp(signature, "matrixMode(_)") == 0)        return C_matrixMode;
        } else if (strcmp(className, "GLU") == 0) {
            if (isStatic && strcmp(signature, "lookAt(_,_,_,_,_,_,_,_,_)") == 0) return C_lookAt;
        } else if (strcmp(className, "Glut") == 0) {
            if (isStatic && strcmp(signature, "initDisplayMode(_)") == 0)   return C_initDisplayMode;
            if (isStatic && strcmp(signature, "initWindowSize(_,_)") == 0)  return C_initWindowSize;
            if (isStatic && strcmp(signature, "createWindow(_)") == 0)      return C_createWindow;
            if (isStatic && strcmp(signature, "swapBuffers()") == 0)        return C_swapBuffers; 
            if (isStatic && strcmp(signature, "ignoreKeyRepeat(_)") == 0)   return C_ignoreKeyRepeat; 
            if (isStatic && strcmp(signature, "keyboardFunc(_,_)") == 0)    return C_keyboardFunc;
            if (isStatic && strcmp(signature, "keyboardUpFunc(_,_)") == 0)  return C_keyboardUpFunc;
            if (isStatic && strcmp(signature, "reshapeFunc(_,_)") == 0)     return C_reshapeFunc;
            if (isStatic && strcmp(signature, "idleFunc(_,_)") == 0)        return C_idleFunc;
            if (isStatic && strcmp(signature, "mouseFunc(_,_)") == 0)       return C_mouseFunc;
            if (isStatic && strcmp(signature, "motionFunc(_,_)") == 0)      return C_motionFunc;
            if (isStatic && strcmp(signature, "setOption(_,_)") == 0)       return C_setOption;
            if (isStatic && strcmp(signature, "leaveMainLoop()") == 0)      return C_leaveMainLoop;
        } else if (strcmp(className, "CUtils") == 0) {
            if (isStatic && strcmp(signature, "usleep(_)") == 0)            return C_usleep;
            if (isStatic && strcmp(signature, "flushStdout()") == 0)        return C_flushStdout;
        }
    }
    return NULL;
}

static void writeFn(WrenVM* vm, const char* text) {
    printf("%s", text);
}

void errorFn(WrenVM* vm, WrenErrorType errorType, const char* module, const int line, const char* msg) {
    switch (errorType) {
        case WREN_ERROR_COMPILE:
            printf("[%s line %d] [Error] %s\n", module, line, msg);
            break;
        case WREN_ERROR_STACK_TRACE:
            printf("[%s line %d] in %s\n", module, line, msg);
            break;
        case WREN_ERROR_RUNTIME:
            printf("[Runtime Error] %s\n", msg);
            break;
    }
}

char *readFile(const char *fileName) {
    FILE *f = fopen(fileName, "r");
    fseek(f, 0, SEEK_END);
    long fsize = ftell(f);
    rewind(f);
    char *script = malloc(fsize + 1);
    fread(script, 1, fsize, f);
    fclose(f);
    script[fsize] = 0;
    return script;
}

static void loadModuleComplete(WrenVM* vm, const char* module, WrenLoadModuleResult result) {
    if( result.source) free((void*)result.source);
}

WrenLoadModuleResult loadModule(WrenVM* vm, const char* name) {
    WrenLoadModuleResult result = {0};
    if (strcmp(name, "random") != 0 && strcmp(name, "meta") != 0) {
        result.onComplete = loadModuleComplete;
        char fullName[strlen(name) + 6];
        strcpy(fullName, name);
        strcat(fullName, ".wren");
        result.source = readFile(fullName);
    }
    return result;
}

int main(int argc, char **argv) {
    glutInit(&argc, argv);
    WrenConfiguration config;
    wrenInitConfiguration(&config);
    config.writeFn = &writeFn;
    config.errorFn = &errorFn;
    config.bindForeignMethodFn = &bindForeignMethod;
    config.loadModuleFn = &loadModule;
    vm = wrenNewVM(&config);
    const char* module = "main";
    const char* fileName = "Boids.wren";
    char *script = readFile(fileName);
    WrenInterpretResult result = wrenInterpret(vm, module, script);
    switch (result) {
        case WREN_RESULT_COMPILE_ERROR:
            printf("Compile Error!\n");
            break;
        case WREN_RESULT_RUNTIME_ERROR:
            printf("Runtime Error!\n");
            break;
        case WREN_RESULT_SUCCESS:
            break;
    }
    glutMainLoop();
    wrenFreeVM(vm);
    free(script);
    return 0;
}
Output:
Similar to C entry.