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)

WebGL rotating F

WebGL rotating F is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Using WebGL or OpenGL ES 2.0 compatible routines draw a rotating F shape.
Specifically avoid glBegin, glVertex, glPush/PopMatrix, or glu(t/fw), however all functions needed are available in (eg) opengl32.dll.
While the Phix example below uses a somewhat fudged "Hello!" graphic, any texture or even just plain colours is perfectly acceptable.

A detailed tutorial (if code below does not suffice) can be found at https://webglfundamentals.org - specifically 2nd 3D and 1st textures sections.
Be warned that site can and probably will stress your CPU, but closing the page returns things to normal pretty quickly. The OpenGL task entries for JavaScript, Phix, and Rust could also be used as starting points, as can OpenGL_pixel_shader entries for JavaScript and Phix.

Optional extra

Allow toggling between the F shape and a classic Utah teapot.

Julia

Makie can use OpenGL as a backend for plotting (GLMakie). The code plots an 'F' as three rectangular solid-shaped scatter plots with overlapping markers, then rotates the resulting graph.

`using Colorsusing GeometryBasics: Rect3fusing GLMakie topbar = vec([Point3f(i / 5, j / 5, k / 5) for i in 1:7, j in 8:3:28, k in 45:50])stem = vec([Point3f(i / 5, j / 5, k / 5) for i in 1:7, j in 1:7, k in 1:8:56])midbar = vec([Point3f(i / 5, j / 5, k / 5) for i in 1:7, j in 8:2:21, k in 25:29])fig = Figure(resolution = (800, 400))pos = fig[1, 1]meshscatter(pos, topbar; marker = Rect3f(Vec3f(-0.5), Vec3f(16)), transparency = true,   color = [RGBA(topbar[i]..., 0.5) for i in 1:length(topbar)])meshscatter!(pos, stem; marker = Rect3f(Vec3f(-0.5), Vec3f(16)), transparency = true,   color = [RGBA(stem[i]..., 0.5) for i in 1:length(stem)])meshscatter!(pos, midbar; marker = Rect3f(Vec3f(-0.5), Vec3f(16)), transparency = true,   color = [RGBA(midbar[i]..., 0.5) for i in 1:length(midbar)]) for _ in 1:28    display(fig.scene)    rotate!(Accum, fig.scene, 0.25)    sleep(0.25)end `

Phix

You can run this online here (which gives me around 10% CPU load, with space toggling the timer for no extra load). No teapot, sorry.

```--
-- demo\pGUI\HelloF.exw
-- ====================
--
--  Translation of https://webglfundamentals.org/webgl/webgl-3d-textures-good-npot.html
--          but using a manually generated texture rather than one loaded from an image.
--
--  Converted to pGUI by Pete Lomax
--
--  This can be run online here, and also works on desktop/Phix
--
with javascript_semantics   -- (OpenGL ES 2.0 compatible) [YEAH!!]
requires("1.0.1")
include pGUI.e
include opengl.e
--
-- Pete's super quick intro to OpenGL/WebGL		(copied from the docs)
-- ========================================
--
--  First and foremost you need to define all the points/vertices and faces of the model.
--  For a 2D 'F' draw three rectangles, one upright and two rungs. We will treat that as 3 faces.
--  There are four distinct x (left/right) values and five distinct y (up/down) values, and 12 vertices,
--  two of which are the same point (where the upright and the top rung meet).
--  For a 3D 'F' replicate that with a different z (front/back), now we have 24 vertices.
--  The right hand side of the upright needs (splitting into) two faces and the left hand side of both rungs have none, plus
--  the top of the upright and the top of the top rung are in practice merged together into a single face.
--  In total we have 16 faces[1], each of which we are going to specify as two triangles[2].
--  [1] Just to clarify and not of any great significance, 6 of those faces are flat and 5 each are horizonal/vertical edge-on.
--  [2] While OpenGL 1.0's glBegin() supported quads and polygons, OpenGL ES 2.0 and WebGL stop at triangles.
--
--  Let that all sink in. If you cannot imagine or arrange a few suitable things on the desktop in front of you to resemble
--  a 3D 'F', and verify the numbers above (esp 16), then I'm afraid to say you are a lost cause.
--  Don't worry about the units: internally OpenGL maps everything to 0.0..1.0 or -1.0..+1.0 as needed anyway (I think),
--  along with the "camera" position, so just use the numbers you find easiest. Do however bear in mind that we are
--  (probably) going to be rotating about {0,0,0}, so making that the "centre of gravity" usually helps, though of
--  course you can specify things more naturally and then add/subtract some {dx,dy,dz} to/from every point.
--  See sequence F_positions below.
--
--  It is important to note that each face can be drawn using two triangles in 144 different ways, which could have a significant
--  impact when it comes to applying textures. There are two diagonals that split a rectangle into two triangles, but you can start
--  on any one of those four, and draw by starting on any of the three corners of a triangle and go clockwise or anticlockwise, so
--  4*3*2*3*2=144. On top of that we have different pixel coordinates for Canvas Draw and OpenGL, as well as low-level differences
--  between OpenGL (on the desktop) and WebGL (as in a browser). We need to take the simplest possible route here (and we do).
--
--  Next we want to apply a texture to each of those faces. This is a simple matter of matching each and every vertex previously
--  specified to the corresponding position on the texture. We can make things much simpler by declaring the faces in a consistent
--  manner and applying textures using a standard platform-specific approach, which means opengl.e vs opengl.js in this context.
--  Applying a world map to a sphere needs a bit more math and probably more triangles, but it is basically the same.
--
--  The rest is fairly standard: store it with glBufferData(), and specify what it all means first by calling glVertexAttribPointer()
--  with the right size, type, and possibly stride settings, and finally glDrawArrays() with the desired mode, here GL_TRIANGLES, not
--  forgetting the total length. Of course there are all sorts of other things to achieve smoothing, rotation, lighting effects, and
--  so on, but this quick little intro endeth here. The following notes are all very specific to this particular program.
--
-- First define a 2D 'F' as follows
--
--      [1] {0,150}  {30,150}  {100,150}
--            +---------+----------+
--            |         |          |
--      [2]   | {30,120}+----------+{100,120}
--            |         |
--            |         |
--      [3]   |  {30,90}+------+{67,90}
--            |         |  X   |
--      [4]   |  {30,60}+------+{67,60}
--            |         |
--            |         |
--            |         |
--            +---------+
--      [5] {0,0}    {30,0}
--
-- Use twelve 3-letter contants named (up|tr|mr)((l|r)|(t|b)),
--              eg upl is the x co-ord for the upright's left.
--
-- [1] aka {upl,upt}, {upr,upt}=={trl,trt}, {trr,trt}
-- [2] aka                       {trl,trb}, {trr,trb}
-- [3] aka                       {mrl,mrt}, {mrr,mrt}
-- [4] aka                       {mrl,mrb}, {mrr,mrb}
-- [5] aka {upl,upb}, {upr,upb}
--
-- The translations simply shift {x,y,z} such that {0,0,0} is the
--  "centre of gravity" (X) rather than front upright botton left,
--  because rotating about that X point looks so very much better.
--
-- Obviously replicate that with z of 0 and 30 for the front and
--  back panes, then out of that mess define all of the 16 faces
--  that we need to draw the whole thing properly.
--
constant tx = -50, ty = -75, tz = -15, -- translations
f = 0+tz, b = 30+tz,    -- front and back planes
-- name the left/right/top/bottom on the 3 front faces:
upl = 0+tx, upr =  30+tx, upt = 150+ty, upb =   0+ty,  -- upright
trl =  upr, trr = 100+tx, trt =    upt, trb = 120+ty,  -- top rung
mrl =  upr, mrr =  67+tx, mrt =  90+ty, mrb =  60+ty   -- middle rung

-- Define all 24 vertices:- (up|tr|mr)(t|b)(l|r)(f|b)
constant uptlf = {upl,upt,f},   uptrf = {upr,upt,f},
upblf = {upl,upb,f},   upbrf = {upr,upb,f},
trtlf = {trl,trt,f},   trtrf = {trr,trt,f},
trblf = {trl,trb,f},   trbrf = {trr,trb,f},
mrtlf = {mrl,mrt,f},   mrtrf = {mrr,mrt,f},
mrblf = {mrl,mrb,f},   mrbrf = {mrr,mrb,f},

uptlb = {upl,upt,b},   uptrb = {upr,upt,b},
upblb = {upl,upb,b},   upbrb = {upr,upb,b},
trtlb = {trl,trt,b},   trtrb = {trr,trt,b},
trblb = {trl,trb,b},   trbrb = {trr,trb,b},
mrtlb = {mrl,mrt,b},   mrtrb = {mrr,mrt,b},
mrblb = {mrl,mrb,b},   mrbrb = {mrr,mrb,b}
--
-- Referring back to the above diagram, we now have:
-- [1] aka uptlf/b, uptrf/b==trtlf/b, trtrf/b
-- [2] aka                   trblf/b, trbrf/b
-- [3] aka                   mrtlf/b, mrtrf/b
-- [4] aka                   mrblf/b, mrbrf/b
-- [5] aka upblf/b, upbrf/b
--
-- Now define the 16 faces.
--
-- Note that while the 24 vertices were named corresponding
-- to two upright 'F's, when we define each face we will
-- use the corners in an order suitable for texture mapping.
-- I hereby define the "A7" format as {bl,tl,br,tl,tr,br},
-- ie the six points (aka two triangles) needed to draw it,
-- when looking at it from the texture side and orientated
-- the right way up for that, as a way to simplify things.
-- The two front faces of the rungs are orientated the same
-- as the vertex naming, the rest all [up]side[down]ways.
--
-- While the following is fairly difficult to type in, and
-- the polar opposite of flexible, the hope is it will be
-- less daunting than the more usual "wall of 288 numbers".
-- Trust me I had a furrowed brow and used lots of pencil
-- and paper and rotated my notepad in order to pen these.
-- Of course you can generate all the vertices and faces
-- programmatically in a much more abstract fashion, too.
--
constant faces = {{upbrf,upblf,uptrf,upblf,uptlf,uptrf},    -- left column front
{trblf,trtlf,trbrf,trtlf,trtrf,trbrf},    -- top rung front
{mrblf,mrtlf,mrbrf,mrtlf,mrtrf,mrbrf},    -- middle rung front
{upblb,upbrb,uptlb,upbrb,uptrb,uptlb},    -- left column back
{trbrb,trtrb,trblb,trtrb,trtlb,trblb},    -- top rung back
{mrbrb,mrtrb,mrblb,mrtrb,mrtlb,mrblb},    -- middle rung back
{uptlf,uptlb,trtrf,uptlb,trtrb,trtrf},    -- joined up top
{trbrf,trtrf,trbrb,trtrf,trtrb,trbrb},    -- top rung right
{trblb,trblf,trbrb,trblf,trbrf,trbrb},    -- under top rung
{mrtlb,mrtlf,trblb,mrtlf,trblf,trblb},    -- between rungs right
{mrtlf,mrtlb,mrtrf,mrtlb,mrtrb,mrtrf},    -- middle rung top
{mrbrf,mrtrf,mrbrb,mrtrf,mrtrb,mrbrb},    -- middle rung right
{mrblb,mrblf,mrbrb,mrblf,mrbrf,mrbrb},    -- under middle rung
{upbrb,upbrf,mrblb,upbrf,mrblf,mrblb},    -- below rungs right
{upblb,upblf,upbrb,upblf,upbrf,upbrb},    -- upright bottom (match under rungs)
--        {upbrb,upblb,upbrf,upblb,upblf,upbrf},    -- upright bottom (match the uprights)
{upblf,upblb,uptlf,upblb,uptlb,uptlf}},   -- left hand side
--
-- And sqidge together into the 32 triangles / 96 points that OpenGL likes:
--
F_positions = flatten(faces,{}), -- (nb be sure to ask for a dword-sequence)
--
-- Now for the final flourish: since we defined our faces in a consistent manner
-- we can reach out to opengl.e/js for a standard texture mapping for all faces.
-- More details can be found in the documentation for glSimpleA7texcoords().
--
F_texture_coordinates = glSimpleA7texcoords(length(faces))

Ihandln dlg
Ihandle canvas, timer
cdCanvas cd_canvas

precision mediump float;

// Passed in from the vertex shader.
varying vec2 v_texcoord;

// The texture.
uniform sampler2D u_texture;

void main() {
gl_FragColor = texture2D(u_texture, v_texcoord);
}`

attribute vec4 a_position;
attribute vec2 a_texcoord;

uniform mat4 u_matrix;

varying vec2 v_texcoord;

void main() {
// Multiply the position by the matrix.
gl_Position = u_matrix * a_position;

// Pass the texcoord to the fragment shader.
v_texcoord = a_texcoord;
}`

if not compiled then
string fmt = "*** Error compiling shader \'%d\': %s\n",
for i=1 to length(lines) do
lines[i] = sprintf("%d: %s\n",{i,lines[i]})
end for
puts(1,msg & join(lines,"\n"))
end if
end function

integer program = glCreateProgram()
end for
string lastError = glGetProgramInfoLog(program)
puts(1,"Error in program linking:" & lastError)
program = glDeleteProgram(program)
end if
return program
end function

integer program = 0

function canvas_action_cb(Ihandle /*canvas*/)

if program=0 then -- (do this lot once only! but not b4 desktop/Phix is ready)

// Create a buffer for the F_positions and fill it
integer position_buffer = glCreateBuffer()
// Bind to ARRAY_BUFFER (think of it as ARRAY_BUFFER:=position_buffer)
glBindBuffer(GL_ARRAY_BUFFER, position_buffer)
{integer size, atom pData} = glFloat32Array(F_positions)
glBufferData(GL_ARRAY_BUFFER, size, pData, GL_STATIC_DRAW)

// Create a matching buffer for the F_texture_coordinates and fill it
integer texcoord_buffer = glCreateBuffer()
glBindBuffer(GL_ARRAY_BUFFER, texcoord_buffer)
{size, pData} = glFloat32Array(F_texture_coordinates)
glBufferData(GL_ARRAY_BUFFER, size, pData, GL_STATIC_DRAW)

// Put some text in the center of a canvas/pixel array.
// note this is a (bit of a|complete) hack, see docs...
integer w = 100, h = 26
atom cdx = glCanvasSpecialText(cd_canvas,w,h,20,"Hello!")

integer texture = glCreateTexture()
glBindTexture(GL_TEXTURE_2D, texture)
glTexImage2Dc(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, cdx)
// make sure we can render it even if it's not a power of 2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE)

integer position_location = glGetAttribLocation(program, "a_position")
glEnableVertexAttribArray(position_location)    // Turn on the position attribute
glBindBuffer(GL_ARRAY_BUFFER, position_buffer)  // Bind the position buffer.
// Tell the position attribute how to get data out of position_buffer (ARRAY_BUFFER)
atom size3 = 3,         // 3 components per iteration (obvs you can inline all these)
datatype = GL_FLOAT,  // the data is 32bit floats
normalize = false,     // don't normalize the data
stride = 0,         // 0 = move fwd size*sizeof(type) each iteration to get next
offset = 0          // start at the beginning of the buffer
glVertexAttribPointer(position_location, size3, datatype, normalize, stride, offset)

integer texcoord_location = glGetAttribLocation(program, "a_texcoord")
glEnableVertexAttribArray(texcoord_location)    // Turn on the texcoord attribute
glBindBuffer(GL_ARRAY_BUFFER, texcoord_buffer)  // bind the texcoord buffer.
// Tell the texcoord attribute how to get data out of texcoord_buffer (ARRAY_BUFFER)
atom size2 = 2          // 2 components per iteration (the rest are same as above)
glVertexAttribPointer(texcoord_location, size2, datatype, normalize, stride, offset)

glEnable(GL_CULL_FACE)
glEnable(GL_DEPTH_TEST)

// Tell it to use our program (pair of shaders)
glUseProgram(program)
end if
return IUP_DEFAULT
end function

function map_cb(Ihandle /*ih*/)
IupGLMakeCurrent(canvas)
cd_canvas = cdCreateCanvas(CD_IUP, canvas)
return IUP_DEFAULT
end function

last_time = time()

function timer_cb(Ihandle /*ih*/)

atom this_time = time(),
delta_time = this_time-last_time
last_time = this_time   // (remember for the next frame)

// Animate the rotation

// Tell WebGL how to convert from clip space to pixels
integer {w, h} = IupGetIntInt(canvas, "RASTERSIZE")
glViewport(0, 0, w, h)

// Clear the canvas AND the depth buffer.
glClear(GL_COLOR_BUFFER_BIT || GL_DEPTH_BUFFER_BIT)
glClearColor(0.3, 0.3, 0.3, 1.0)

// Compute the projection, camera, and final view matrices.
// that is the core matrix usage, rather than the m4_xxx().
atom aspect = w/h
sequence projection = m4_perspective(field_of_view_radians, aspect, 1, 2000),
camera_position = {0, 0, 200}, up = {0, 1, 0}, target = {0, 0, 0},
camera = m4_lookAt(camera_position, target, up),
view_projection = m4_multiply(projection, m4_inverse(camera)),

// Set the final view matrix.
{integer size, atom pData} = glFloat32Array(final_view_matrix)
glUniformMatrix4fv(glGetUniformLocation(program, "u_matrix"), false, pData)

// Tell the shader to use texture unit 0 for u_texture
glUniform1i(glGetUniformLocation(program, "u_texture"), 0)

integer num_points = length(F_positions)/3,        -- {x,y,z} per
num_coords = length(F_texture_coordinates)/2 -- {x,y} per
assert(num_points==num_coords) -- sanity check

// Draw the geometry.
glDrawArrays(GL_TRIANGLES, 0, num_points)

// Check for errors.
while true do
integer e = glGetError()
if e=GL_NO_ERROR then exit end if
printf(1,"GL error %d\n", e)
end while

glFlush()
return IUP_DEFAULT
end function

function key_cb(Ihandle ih, atom c)
if c=' ' then
IupSetInt(timer,"RUN",not IupGetInt(timer,"RUN"))
last_time = time() -- (avoids sudden jumping)
end if
return iff(c=K_ESC?IUP_CLOSE:IUP_CONTINUE)
end function

procedure main()
IupOpen()
canvas = IupGLCanvas("RASTERSIZE=640x480")
IupSetCallbacks(canvas, {"MAP_CB", Icallback("map_cb"),
"ACTION", Icallback("canvas_action_cb"),
"KEY_CB", Icallback("key_cb")})
dlg = IupDialog(canvas, `TITLE="Hello! F", SHRINK=YES, MINSIZE=120x90`)
IupShow(dlg)
timer = IupTimer(Icallback("timer_cb"), 1000/40)
if platform()!=JS then
IupMainLoop()
dlg = IupDestroy(dlg)
IupClose()
end if
end procedure
main()
```

Wren

Library: FreeGLUT
Library: Wren-math

The following is based on the WebGL tutorial javascript animation code here but is written as an OpenGL ES 2.0 desktop application. It just uses plain colors, without any textures and looks exactly the same as the WebGL version when run.

Although the task description says to avoid it, I've used Freeglut to create and manage a desktop window (but no drawing nor callbacks) as that's what I'm most familiar with. However, if that's a problem I can try and use something else (such as SDL).

Assuming it's started from a terminal session, the only way to kill the program is by pressing Ctrl-C. The window close button doesn't work, probably due to the use of usleep to create a delay between drawing frames. It does work if I use a freeglut timer function instead but that creates callback issues (the Wren VM is not re-entrant) and I wanted to minimize the use of freeglut in any case.

As with any Open GL aplication, the Wren code needs to be embedded in a host application which can communicate directly with the relevant libraries and which I've written here in C.

`/* webgl_rotating_F.wren */ import "./math" for Math var GL_ARRAY_BUFFER     = 0x8892var GL_STATIC_DRAW      = 0x88E4var GL_VERTEX_SHADER    = 0x8B31var GL_FRAGMENT_SHADER  = 0x8B30var GL_COLOR_BUFFER_BIT = 0x4000var GL_DEPTH_BUFFER_BIT = 0x0100var GL_CULL_FACE        = 0x0B44var GL_DEPTH_TEST       = 0x0B71var GL_UNSIGNED_BYTE    = 0x1403var GL_FLOAT            = 0x1406var GL_TRIANGLES        = 0x0004 var GLUT_ACTION_ON_WINDOW_CLOSE = 0x01F9var GLUT_ACTION_GLUTMAINLOOP_RETURNS = 0x0001var GLUT_SINGLE = 0x0000var GLUT_RGB    = 0x0000var GLUT_DEPTH  = 0x0010 class Gl {    foreign static bufferData(target, size, data, usage)    foreign static createShader(type)    foreign static shaderSource(shader, count, string, length)    foreign static compileShader(shader)    foreign static createProgram()    foreign static attachShader(program, shader)    foreign static linkProgram(program)    foreign static getAttribLocation(program, name)    foreign static getUniformLocation(program, name)    foreign static genBuffers(n, buffers)    foreign static bindBuffer(target, buffer)    foreign static viewport(x, y, width, height)    foreign static clear(mask)    foreign static enable(cap)    foreign static useProgram(program)    foreign static drawArrays(mode, first, count)    foreign static enableVertexAttribArray(index)    foreign static vertexAttribPtr(index, size, type, normalized, stride, ptr)    foreign static uniformMatrix4fv(location, count, transpose, value)    foreign static flush()} class Glut {    foreign static initDisplayMode(mode)    foreign static initWindowSize(width, height)    foreign static createWindow(name)    foreign static setOption(eWhat, value)} class M4 {    static perspective(fieldOfViewInRadians, aspect, near, far, dst) {        if (!dst) dst = List.filled(16, 0)         var f = (Num.pi * 0.5 - 0.5 * fieldOfViewInRadians).tan        var rangeInv = 1 / (near - far)         dst[ 0] = f / aspect        dst[ 1] = 0        dst[ 2] = 0        dst[ 3] = 0        dst[ 4] = 0        dst[ 5] = f        dst[ 6] = 0        dst[ 7] = 0        dst[ 8] = 0        dst[ 9] = 0        dst[10] = (near + far) * rangeInv        dst[11] = -1        dst[12] = 0        dst[13] = 0        dst[14] = near * far * rangeInv * 2        dst[15] = 0         return dst    }     static translate(m, tx, ty, tz, dst) {        if (!dst) dst = List.filled(16, 0)         var m00 = m[0]        var m01 = m[1]        var m02 = m[2]        var m03 = m[3]        var m10 = m[1 * 4 + 0]        var m11 = m[1 * 4 + 1]        var m12 = m[1 * 4 + 2]        var m13 = m[1 * 4 + 3]        var m20 = m[2 * 4 + 0]        var m21 = m[2 * 4 + 1]        var m22 = m[2 * 4 + 2]        var m23 = m[2 * 4 + 3]        var m30 = m[3 * 4 + 0]        var m31 = m[3 * 4 + 1]        var m32 = m[3 * 4 + 2]        var m33 = m[3 * 4 + 3]         if (m != dst) {            dst[ 0] = m00            dst[ 1] = m01            dst[ 2] = m02            dst[ 3] = m03            dst[ 4] = m10            dst[ 5] = m11            dst[ 6] = m12            dst[ 7] = m13            dst[ 8] = m20            dst[ 9] = m21            dst[10] = m22            dst[11] = m23        }         dst[12] = m00 * tx + m10 * ty + m20 * tz + m30        dst[13] = m01 * tx + m11 * ty + m21 * tz + m31        dst[14] = m02 * tx + m12 * ty + m22 * tz + m32        dst[15] = m03 * tx + m13 * ty + m23 * tz + m33         return dst    }     static xRotate(m, angleInRadians, dst) {        if (!dst) dst = List.filled(16, 0)         var m10 = m[4]        var m11 = m[5]        var m12 = m[6]        var m13 = m[7]        var m20 = m[8]        var m21 = m[9]        var m22 = m[10]        var m23 = m[11]        var c = angleInRadians.cos        var s = angleInRadians.sin         dst[4]  = c * m10 + s * m20        dst[5]  = c * m11 + s * m21        dst[6]  = c * m12 + s * m22        dst[7]  = c * m13 + s * m23        dst[8]  = c * m20 - s * m10        dst[9]  = c * m21 - s * m11        dst[10] = c * m22 - s * m12        dst[11] = c * m23 - s * m13         if (m != dst) {            dst[ 0] = m[ 0]            dst[ 1] = m[ 1]            dst[ 2] = m[ 2]            dst[ 3] = m[ 3]            dst[12] = m[12]            dst[13] = m[13]            dst[14] = m[14]            dst[15] = m[15]        }         return dst    }     static yRotate(m, angleInRadians, dst) {        if (!dst) dst = List.filled(16, 0)         var m00 = m[0 * 4 + 0]        var m01 = m[0 * 4 + 1]        var m02 = m[0 * 4 + 2]        var m03 = m[0 * 4 + 3]        var m20 = m[2 * 4 + 0]        var m21 = m[2 * 4 + 1]        var m22 = m[2 * 4 + 2]        var m23 = m[2 * 4 + 3]        var c = angleInRadians.cos        var s = angleInRadians.sin         dst[ 0] = c * m00 - s * m20        dst[ 1] = c * m01 - s * m21        dst[ 2] = c * m02 - s * m22        dst[ 3] = c * m03 - s * m23        dst[ 8] = c * m20 + s * m00        dst[ 9] = c * m21 + s * m01        dst[10] = c * m22 + s * m02        dst[11] = c * m23 + s * m03         if (m != dst) {            dst[ 4] = m[ 4]            dst[ 5] = m[ 5]            dst[ 6] = m[ 6]            dst[ 7] = m[ 7]            dst[12] = m[12]            dst[13] = m[13]            dst[14] = m[14]            dst[15] = m[15]        }         return dst    }     static zRotate(m, angleInRadians, dst) {        if (!dst) dst = List.filled(16, 0)         var m00 = m[0 * 4 + 0]        var m01 = m[0 * 4 + 1]        var m02 = m[0 * 4 + 2]        var m03 = m[0 * 4 + 3]        var m10 = m[1 * 4 + 0]        var m11 = m[1 * 4 + 1]        var m12 = m[1 * 4 + 2]        var m13 = m[1 * 4 + 3]        var c = angleInRadians.cos        var s = angleInRadians.sin         dst[ 0] = c * m00 + s * m10        dst[ 1] = c * m01 + s * m11        dst[ 2] = c * m02 + s * m12        dst[ 3] = c * m03 + s * m13        dst[ 4] = c * m10 - s * m00        dst[ 5] = c * m11 - s * m01        dst[ 6] = c * m12 - s * m02        dst[ 7] = c * m13 - s * m03         if (m != dst) {          dst[ 8] = m[ 8]          dst[ 9] = m[ 9]          dst[10] = m[10]          dst[11] = m[11]          dst[12] = m[12]          dst[13] = m[13]          dst[14] = m[14]          dst[15] = m[15]        }         return dst    }     static scale(m, sx, sy, sz, dst) {        if (!dst) dst = List.filled(16, 0)         dst[ 0] = sx * m[0 * 4 + 0]        dst[ 1] = sx * m[0 * 4 + 1]        dst[ 2] = sx * m[0 * 4 + 2]        dst[ 3] = sx * m[0 * 4 + 3]        dst[ 4] = sy * m[1 * 4 + 0]        dst[ 5] = sy * m[1 * 4 + 1]        dst[ 6] = sy * m[1 * 4 + 2]        dst[ 7] = sy * m[1 * 4 + 3]        dst[ 8] = sz * m[2 * 4 + 0]        dst[ 9] = sz * m[2 * 4 + 1]        dst[10] = sz * m[2 * 4 + 2]        dst[11] = sz * m[2 * 4 + 3]         if (m != dst) {            dst[12] = m[12]            dst[13] = m[13]            dst[14] = m[14]            dst[15] = m[15]        }         return dst    }} class C {    foreign static usleep(usec)} // Fill the buffer with the values that define a letter 'F'.var setGeometry = Fn.new {    Gl.bufferData(        GL_ARRAY_BUFFER,        4, // size of 32 bit float        [            // left column front             0,    0,   0,             0,  150,   0,            30,    0,   0,             0,  150,   0,            30,  150,   0,            30,    0,   0,             // top rung front             30,   0,   0,             30,  30,   0,            100,   0,   0,             30,  30,   0,            100,  30,   0,            100,   0,   0,             // middle rung front             30,  60,   0,             30,  90,   0,             67,  60,   0,             30,  90,   0,             67,  90,   0,             67,  60,   0,             // left column back              0,   0,  30,             30,   0,  30,              0, 150,  30,              0, 150,  30,             30,   0,  30,             30, 150,  30,             // top rung back             30,   0,  30,            100,   0,  30,             30,  30,  30,             30,  30,  30,            100,   0,  30,            100,  30,  30,             // middle rung back             30,  60,  30,             67,  60,  30,             30,  90,  30,             30,  90,  30,             67,  60,  30,             67,  90,  30,             // top              0,   0,   0,            100,   0,   0,            100,   0,  30,              0,   0,   0,            100,   0,  30,              0,   0,  30,             // top rung right            100,   0,   0,            100,  30,   0,            100,  30,  30,            100,   0,   0,            100,  30,  30,            100,   0,  30,             // under top rung             30,  30,   0,             30,  30,  30,            100,  30,  30,             30,  30,   0,            100,  30,  30,            100,  30,   0,             // between top rung and middle             30,  30,   0,             30,  60,  30,             30,  30,  30,             30,  30,   0,             30,  60,   0,             30,  60,  30,             // top of middle rung             30,  60,   0,             67,  60,  30,             30,  60,  30,             30,  60,   0,             67,  60,   0,             67,  60,  30,             // right of middle rung             67,  60,   0,             67,  90,  30,             67,  60,  30,             67,  60,   0,             67,  90,   0,             67,  90,  30,             // bottom of middle rung.             30,  90,   0,             30,  90,  30,             67,  90,  30,             30,  90,   0,             67,  90,  30,             67,  90,   0,             // right of bottom             30,  90,   0,             30, 150,  30,             30,  90,  30,             30,  90,   0,             30, 150,   0,             30, 150,  30,             // bottom              0, 150,   0,              0, 150,  30,             30, 150,  30,              0, 150,   0,             30, 150,  30,             30, 150,   0,             // left side              0,   0,   0,              0,   0,  30,              0, 150,  30,              0,   0,   0,              0, 150,  30,              0, 150,   0        ],        GL_STATIC_DRAW    )} // Fill the buffer with colors for the 'F'.var setColors = Fn.new {    Gl.bufferData(        GL_ARRAY_BUFFER,        1, // size of unsigned byte        [            // left column front            200,  70, 120,            200,  70, 120,            200,  70, 120,            200,  70, 120,            200,  70, 120,            200,  70, 120,             // top rung front            200,  70, 120,            200,  70, 120,            200,  70, 120,            200,  70, 120,            200,  70, 120,            200,  70, 120,             // middle rung front            200,  70, 120,             200,  70, 120,            200,  70, 120,            200,  70, 120,            200,  70, 120,            200,  70, 120,             // left column back             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             // top rung back             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             // middle rung back             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             80,  70, 200,             // top             70, 200, 210,             70, 200, 210,             70, 200, 210,             70, 200, 210,             70, 200, 210,             70, 200, 210,             // top rung right            200, 200,  70,            200, 200,  70,            200, 200,  70,            200, 200,  70,            200, 200,  70,            200, 200,  70,             // under top rung            210, 100,  70,            210, 100,  70,            210, 100,  70,            210, 100,  70,            210, 100,  70,            210, 100,  70,             // between top rung and middle            210, 160,  70,            210, 160,  70,            210, 160,  70,            210, 160,  70,            210, 160,  70,            210, 160,  70,             // top of middle rung             70, 180, 210,             70, 180, 210,             70, 180, 210,             70, 180, 210,             70, 180, 210,             70, 180, 210,             // right of middle rung            100,  70, 210,            100,  70, 210,            100,  70, 210,            100,  70, 210,            100,  70, 210,            100,  70, 210,             // bottom of middle rung.             76, 210, 100,             76, 210, 100,             76, 210, 100,             76, 210, 100,             76, 210, 100,             76, 210, 100,             // right of bottom            140, 210,  80,            140, 210,  80,            140, 210,  80,            140, 210,  80,            140, 210,  80,            140, 210,  80,             // bottom             90, 130, 110,             90, 130, 110,             90, 130, 110,             90, 130, 110,             90, 130, 110,             90, 130, 110,             // left side            160, 160, 220,            160, 160, 220,            160, 160, 220,            160, 160, 220,            160, 160, 220,            160, 160, 220        ],        GL_STATIC_DRAW    )} var Programvar PositionLocationvar ColorLocationvar MatrixLocationvar PositionBuffervar ColorBuffer var init = Fn.new {    // create vertex shader    var vShaderSource = """    #version 100    attribute vec4 a_position;    attribute vec4 a_color;     uniform mat4 u_matrix;     varying vec4 v_color;     void main() {      // Multiply the position by the matrix.      gl_Position = u_matrix * a_position;       // Pass the color to the fragment shader.      v_color = a_color;    }    """    var vShader = Gl.createShader(GL_VERTEX_SHADER)    if (vShader == 0) Fiber.abort("Error creating vertex shader.")    Gl.shaderSource(vShader, 1, vShaderSource, null)    Gl.compileShader(vShader)     // create fragment shader    var fShaderSource = """    #version 100    precision mediump float;     // Passed in from the vertex shader.    varying vec4 v_color;     void main() {       gl_FragColor = v_color;    }    """    var fShader = Gl.createShader(GL_FRAGMENT_SHADER)    if (fShader == 0) Fiber.abort("Error creating fragment shader.")    Gl.shaderSource(fShader, 1, fShaderSource, null)    Gl.compileShader(fShader)     // setup GLSL program    Program = Gl.createProgram()    Gl.attachShader(Program, vShader)    Gl.attachShader(Program, fShader)    Gl.linkProgram(Program)     // look up where the vertex data needs to go    PositionLocation = Gl.getAttribLocation(Program, "a_position")    ColorLocation = Gl.getAttribLocation(Program, "a_color")    // lookup uniforms    MatrixLocation = Gl.getUniformLocation(Program, "u_matrix")    // generate 2 buffer object names    var buffers = List.filled(2, 0)    Gl.genBuffers(2, buffers)     // Create a buffer to put positions in    PositionBuffer = buffers[0]    // Bind it to ARRAY_BUFFER    Gl.bindBuffer(GL_ARRAY_BUFFER, PositionBuffer)    // Put geometry data into buffer    setGeometry.call()     // Create a buffer to put colors in    ColorBuffer = buffers[1]    // Bind it to ARRAY_BUFFER    Gl.bindBuffer(GL_ARRAY_BUFFER, ColorBuffer)    // Put color data into buffer    setColors.call()} var Translation = [0, 0, -360]var Rotation = [Math.radians(190), Math.radians(40), Math.radians(320)]var Scale = [1, 1, 1]var FieldOfViewRadians = Math.radians(60)var WindowWidth = 640var WindowHeight = 480 var drawScene // recursive functiondrawScene = Fn.new {    Rotation[1] = Rotation[1] + 0.01    if (Rotation[1] >= 360) Rotation[1] = 0     // tell GLES how to convert from clip space to pixels    Gl.viewport(0, 0, WindowWidth, WindowHeight)     // clear the window and the depth buffer    Gl.clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)     // turn on culling. By default backfacing triangles will be culled    Gl.enable(GL_CULL_FACE)     // Enable the depth buffer    Gl.enable(GL_DEPTH_TEST)     // Tell it to use our program (pair of shaders)    Gl.useProgram(Program)     // Turn on the position attribute    Gl.enableVertexAttribArray(PositionLocation)     // Bind the position buffer.    Gl.bindBuffer(GL_ARRAY_BUFFER, PositionBuffer)     // tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)    var size = 3           // 3 components per iteration    var type = GL_FLOAT    // the data is 32 bit floats    var normalize = false  // don't normalize the data    var stride = 0         // 0 = move forward size * sizeof(type) each iteration to get the next position    var offset = 0         // start at the beginning of the buffer    Gl.vertexAttribPtr(PositionLocation, size, type, normalize, stride, offset)     // Turn on the color attribute    Gl.enableVertexAttribArray(ColorLocation)     // Bind the color buffer.    Gl.bindBuffer(GL_ARRAY_BUFFER, ColorBuffer)     // Tell the attribute how to get data out of colorBuffer (ARRAY_BUFFER)    Gl.vertexAttribPtr(ColorLocation, size, type, normalize, stride, offset)     // compute the matrices    var aspect = WindowWidth / WindowHeight    var matrix = M4.perspective(FieldOfViewRadians, aspect, 1, 2000, null)    matrix = M4.translate(matrix, Translation[0], Translation[1], Translation[2], null)    matrix = M4.xRotate(matrix, Rotation[0], null)    matrix = M4.yRotate(matrix, Rotation[1], null)    matrix = M4.zRotate(matrix, Rotation[2], null)    matrix = M4.scale(matrix, Scale[0], Scale[1], Scale[2], null)     // set the matrix    Gl.uniformMatrix4fv(MatrixLocation, 1, false, matrix)     // draw the geometry    var primitiveType = GL_TRIANGLES    var count = 16 * 6    Gl.drawArrays(primitiveType, offset, count)    Gl.flush()    C.usleep(10000) // wait for 10 msec    drawScene.call()} Glut.initDisplayMode(GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH)Glut.initWindowSize(640, 480)Glut.createWindow("Rotating F")Glut.setOption(GLUT_ACTION_ON_WINDOW_CLOSE, GLUT_ACTION_GLUTMAINLOOP_RETURNS)init.call()drawScene.call()`

We now embed the above code in the following C program, compile and run.

`/* gcc webgl_rotating_F.c -o webgl_rotating_F -lglut -lGLESv2 -lm -lwren */ #include <stdlib.h>#include <stdio.h>#include <string.h>#include <GLES2/gl2.h>#include <GL/freeglut.h>#include <unistd.h>#include "wren.h" /* Gl functions */ void C_bufferData(WrenVM* vm) {    GLenum target = (GLenum)wrenGetSlotDouble(vm, 1);    GLsizeiptr size = (GLsizeiptr)wrenGetSlotDouble(vm, 2);    int count = wrenGetListCount(vm, 3);    GLenum usage = (GLenum)wrenGetSlotDouble(vm, 4);    float data[count];    for (int i = 0; i < count; ++i) {        wrenGetListElement(vm, 3, i, 1);        data[i] = (float)wrenGetSlotDouble(vm, 1);        if (size == 1) data[i] /= 255.0;    }    glBufferData(target, 4 * count, data, usage);} void C_createShader(WrenVM* vm) {    GLenum shaderType = (GLenum)wrenGetSlotDouble(vm, 1);    GLuint shader = glCreateShader(shaderType);    wrenSetSlotDouble(vm, 0, (double)shader);} void C_shaderSource(WrenVM* vm) {    GLuint shader = (GLuint)wrenGetSlotDouble(vm, 1);    GLsizei count = (GLsizei)wrenGetSlotDouble(vm, 2);    // assume for simplicity there will always be one shader string and the fourth parameter will be null    const GLchar *string = (const GLchar *)wrenGetSlotString(vm, 3);    glShaderSource(shader, count, &string, 0);} void C_compileShader(WrenVM* vm) {    GLuint shader = (GLuint)wrenGetSlotDouble(vm, 1);    glCompileShader(shader);    // check shader compiled ok    GLint shader_compiled;    glGetShaderiv(shader, GL_COMPILE_STATUS, &shader_compiled);    if (shader_compiled != GL_TRUE) {        GLsizei log_length = 0;        GLchar message[1024];        glGetShaderInfoLog(shader, 1024, &log_length, message);        printf("%s\n", message);    }} void C_createProgram(WrenVM* vm) {    GLuint program = glCreateProgram();    wrenSetSlotDouble(vm, 0, (double)program);} void C_attachShader(WrenVM* vm) {    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);    GLuint shader  = (GLuint)wrenGetSlotDouble(vm, 2);    glAttachShader(program, shader);} void C_linkProgram(WrenVM* vm) {    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);    // check program linked ok    glBindAttribLocation(program, 0, "position");    glBindAttribLocation(program, 1, "texcoord");    glBindAttribLocation(program, 2, "normal");    glBindAttribLocation(program, 3, "color");    glLinkProgram(program);    GLint program_linked;    glGetProgramiv(program, GL_LINK_STATUS, &program_linked);    if (program_linked != GL_TRUE) {        GLsizei log_length = 0;        GLchar message[1024];        glGetProgramInfoLog(program, 1024, &log_length, message);        printf("%s\n", message);    }} void C_getAttribLocation(WrenVM* vm) {    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);    const GLchar *name = (const GLchar *)wrenGetSlotString(vm, 2);    GLint location = glGetAttribLocation(program, name);    wrenSetSlotDouble(vm, 0, (double)location);} void C_getUniformLocation(WrenVM* vm) {    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);    const GLchar *name = (const GLchar *)wrenGetSlotString(vm, 2);    GLint location = glGetUniformLocation(program, name);    wrenSetSlotDouble(vm, 0, (double)location);} void C_genBuffers(WrenVM* vm) {    GLsizei n = (GLsizei)wrenGetSlotDouble(vm, 1);    int count = wrenGetListCount(vm, 2);    GLuint buffers[count];    glGenBuffers(n, buffers);    for (int i = 0; i < count; ++i) {        wrenSetSlotDouble(vm, 1, (double)buffers[i]);        wrenSetListElement(vm, 2, i, 1);    }} void C_bindBuffer(WrenVM* vm) {    GLenum target = (GLenum)wrenGetSlotDouble(vm, 1);    GLuint buffer = (GLuint)wrenGetSlotDouble(vm, 2);    glBindBuffer(target, buffer);} 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_clear(WrenVM* vm) {    glClearColor(1, 1, 1, 1); // always clear to white background    GLbitfield mask = (GLbitfield)wrenGetSlotDouble(vm, 1);    glClear(mask);} void C_enable(WrenVM* vm) {    GLenum cap = (GLenum)wrenGetSlotDouble(vm, 1);    glEnable(cap);} void C_useProgram(WrenVM* vm) {    GLuint program = (GLuint)wrenGetSlotDouble(vm, 1);    glUseProgram(program);} void C_drawArrays(WrenVM* vm) {    GLenum mode = (GLenum)wrenGetSlotDouble(vm, 1);    GLint first = (GLint)wrenGetSlotDouble(vm, 2);    GLsizei count = (GLsizei)wrenGetSlotDouble(vm, 3);    glDrawArrays(mode, first, count);} void C_enableVertexAttribArray(WrenVM* vm) {    GLuint index = (GLuint)wrenGetSlotDouble(vm, 1);    glEnableVertexAttribArray(index);} void C_vertexAttribPtr(WrenVM* vm) {    GLuint index = (GLuint)wrenGetSlotDouble(vm, 1);    GLint size = (GLint)wrenGetSlotDouble(vm, 2);    GLenum type = (GLenum)wrenGetSlotDouble(vm, 3);    GLboolean normalized = (GLboolean)wrenGetSlotBool(vm, 4);    GLsizei stride = (GLsizei)wrenGetSlotDouble(vm, 5);    // assume for simplicity sixth parameter will always be 0    glVertexAttribPointer(index, size, type, normalized, stride, 0);} void C_uniformMatrix4fv(WrenVM* vm) {    GLint location = (GLint)wrenGetSlotDouble(vm, 1);    GLsizei count = (GLsizei)wrenGetSlotDouble(vm, 2);    GLboolean transpose = (GLboolean)wrenGetSlotBool(vm, 3);    int len = wrenGetListCount(vm, 4);    GLfloat value[len];    for (int i = 0; i < len; ++i) {        wrenGetListElement(vm, 4, i, 1);        value[i] = (GLfloat)wrenGetSlotDouble(vm, 1);    }    glUniformMatrix4fv(location, count, transpose, value);} void C_flush(WrenVM* vm) {    glFlush();} /* Glut functions */ 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_setOption(WrenVM* vm) {    GLenum eWhat = (GLenum)wrenGetSlotDouble(vm, 1);    int value    = (int)wrenGetSlotDouble(vm, 2);    glutSetOption(eWhat, value);} /* C function */ void C_usleep(WrenVM* vm) {    useconds_t usec = (useconds_t)wrenGetSlotDouble(vm, 1);    usleep(usec);} 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, "bufferData(_,_,_,_)") == 0)     return C_bufferData;            if (isStatic && strcmp(signature, "createShader(_)") == 0)         return C_createShader;            if (isStatic && strcmp(signature, "shaderSource(_,_,_,_)") == 0)   return C_shaderSource;            if (isStatic && strcmp(signature, "compileShader(_)") == 0)        return C_compileShader;            if (isStatic && strcmp(signature, "createProgram()") == 0)         return C_createProgram;            if (isStatic && strcmp(signature, "attachShader(_,_)") == 0)       return C_attachShader;            if (isStatic && strcmp(signature, "linkProgram(_)") == 0)          return C_linkProgram;            if (isStatic && strcmp(signature, "getAttribLocation(_,_)") == 0)  return C_getAttribLocation;            if (isStatic && strcmp(signature, "getUniformLocation(_,_)") == 0) return C_getUniformLocation;            if (isStatic && strcmp(signature, "genBuffers(_,_)") == 0)         return C_genBuffers;            if (isStatic && strcmp(signature, "bindBuffer(_,_)") == 0)         return C_bindBuffer;            if (isStatic && strcmp(signature, "viewport(_,_,_,_)") == 0)       return C_viewport;            if (isStatic && strcmp(signature, "clear(_)") == 0)                return C_clear;            if (isStatic && strcmp(signature, "enable(_)") == 0)               return C_enable;            if (isStatic && strcmp(signature, "useProgram(_)") == 0)           return C_useProgram;            if (isStatic && strcmp(signature, "drawArrays(_,_,_)") == 0)       return C_drawArrays;            if (isStatic && strcmp(signature, "flush()") == 0)                 return C_flush;             if (isStatic && strcmp(signature, "enableVertexAttribArray(_)") == 0)   return C_enableVertexAttribArray;            if (isStatic && strcmp(signature, "vertexAttribPtr(_,_,_,_,_,_)") == 0) return C_vertexAttribPtr;            if (isStatic && strcmp(signature, "uniformMatrix4fv(_,_,_,_)") == 0)    return C_uniformMatrix4fv;        } 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, "setOption(_,_)") == 0)      return C_setOption;        } else if (strcmp(className, "C") == 0) {            if (isStatic && strcmp(signature, "usleep(_)") == 0)           return C_usleep;        }    }    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);    glutInitContextVersion(2, 0);    WrenConfiguration config;    wrenInitConfiguration(&config);    config.writeFn = &writeFn;    config.errorFn = &errorFn;    config.bindForeignMethodFn = &bindForeignMethod;    config.loadModuleFn = &loadModule;    WrenVM* vm = wrenNewVM(&config);    const char* module = "main";    const char* fileName = "webgl_rotating_F.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;}`