v2026.9.4
All Bundles
Bundle OpenGL 3.3 core rendering on top of SDL2. Builds on sdl2.obl for the window and context; this bundle is the GL side. Compile with -lib sdl2. Targets the **3.3 core profile**, forward-compatible. That is the highest common denominator across the platforms Objeck ships: Windows, Linux and macOS desktop all reach it, and macOS caps at 4.1 core so nothing above 4.1 is portable there. GLES-only hardware (Raspberry Pi and similar) is out of scope by construction. Layers, from thinnest to friendliest -- all public, so nothing is capped: * GLWindow -- a window and a 3.3 core context, opened and paced for you. * GL -- static state operations. Thin, but never per-vertex. * Shader -- a linked program; Flat/Textured/TexturedFog/Normals built in, so the common cases need no GLSL at all. * Mesh -- geometry uploaded once into buffer objects, drawn many times, with Cube/Plane/Quad/Sphere built in, OBJ loading, and instancing. * Texture2D -- a texture, from a file or generated; Solid/Checker built in. * Light -- one directional light plus ambient, written into a shader. * RenderTarget -- draw into a texture instead of the window, then sample it. * Material -- a surface: its texture, tint and shininess. * Frustum -- the six planes of a view, for skipping what is off screen. * ShadowMap -- shadows from a directional or spot light, depth pass and all. * PointShadow -- shadows from a point light, in every direction, via a cube map. * Overlay -- text and rectangles over the scene, positioned in pixels. * Transform -- position, rotation and scale, with a cached matrix. * Box, Scene -- a world of boxes: draws itself, and answers collision. ## Why the API is shaped this way The native call boundary is expensive: the VM resolves each native symbol by string on EVERY call (GetProcAddress/dlsym) and boxes every argument into a fresh holder. A 1:1 mapping of OpenGL onto that would be thousands of lookups and allocations per frame. So each call here does real work -- 'compile a program from two sources' is one call, not the five GL calls it decomposes into -- and bulk data crosses as whole arrays, never element by element. This happens to be exactly what GL 3.3 core wants anyway: upload geometry once, then draw with few calls. ## Adding a call One `void fn(VMContext&)` in the OpenGL section of core/lib/sdl/sdl.cpp, and one method here that names it. See that file's header for the two rules that matter (positional slot indices, and keeping each call coarse).

Quaternion

A rotation, as a quaternion: four floats, [x, y, z, w]. ## Why this exists alongside RotationYXZ Transform stores rotation as three Euler angles and builds a matrix with Matrix4->RotationYXZ. That is fine to author with and fine to store, and it is wrong for two jobs: interpolation Blending between two orientations by lerping their Euler angles does not travel the short way round, wobbles on the way, and can stop dead when two axes line up. Track can already sample a Float along an eased curve; it had no way to sample an ORIENTATION. Slerp is that. accumulation Applying one rotation after another by adding angles is only correct while the axes stay independent. Multiplying quaternions is correct always, which is what a turret that turns relative to a hull that is already turning needs. Follows Matrix4's convention rather than Vector3's: static functions over a Float[4] the caller owns, each with an Into twin that writes rather than allocates. A rotation per object per frame is the same allocation problem a matrix per object per frame is, and for the same reason. Conventions here: right-handed, the same as Matrix4; the scalar is LAST (w at index 3), which is what OpenGL literature and GLM use; and every function assumes unit length except Normalize, Length and FromAxisAngle.

Operations

Conjugate # function

function : Conjugate(q:Float[]) ~ Float[]

Parameters

NameTypeDescription
qFloatthe rotation to reverse

Return

TypeDescription
Floata new opposite rotation

ConjugateInto # function

The opposite rotation, written into a buffer the caller owns. For a unit rotation this is also the inverse, which is why there is no separate Inverse: negating the vector part is correct and cheap, and an inverse that silently assumed unit length would be a trap.

function : ConjugateInto(out:Float[], q:Float[]) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 4 elements
qFloatthe rotation to reverse

Dot # function

function : Dot(a:Float[], b:Float[]) ~ Float

Parameters

NameTypeDescription
aFloatfirst rotation
bFloatsecond rotation

Return

TypeDescription
Floatthe dot product. Negative when they are more than half a turn apart, which is what Slerp uses to decide which way round to go.

FromAxisAngle # function

A rotation of 'angle' radians about an axis. The axis is normalised here, so it does not have to arrive that way -- passing (0, 2, 0) and (0, 1, 0) give the same rotation. A zero-length axis has no direction to turn about and yields identity rather than a quaternion full of NaN.

function : FromAxisAngle(x:Float, y:Float, z:Float, angle:Float) ~ Float[]

Parameters

NameTypeDescription
xFloataxis x
yFloataxis y
zFloataxis z
angleFloatradians, counter-clockwise looking down the axis

Return

TypeDescription
Floata new rotation

FromAxisAngleInto # function

Axis-angle, written into a buffer the caller owns.

function : FromAxisAngleInto(out:Float[], x:Float, y:Float, z:Float, angle:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 4 elements
xFloataxis x
yFloataxis y
zFloataxis z
angleFloatradians

FromEuler # function

The rotation Transform already means by (pitch, yaw, roll). Matches Matrix4->RotationYXZ exactly -- yaw, then pitch, then roll -- so converting a Transform's angles here and building a matrix from the result gives the same matrix Transform would have built itself. That equivalence is what makes it safe to move between the two.

function : FromEuler(yaw:Float, pitch:Float, roll:Float) ~ Float[]

Parameters

NameTypeDescription
yawFloatradians about Y, applied first
pitchFloatradians about X
rollFloatradians about Z, applied last

Return

TypeDescription
Floata new rotation

FromEulerInto # function

Euler angles, written into a buffer the caller owns.

function : FromEulerInto(out:Float[], yaw:Float, pitch:Float, roll:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 4 elements
yawFloatradians about Y, applied first
pitchFloatradians about X
rollFloatradians about Z, applied last

Identity # function

function : Identity() ~ Float[]

Return

TypeDescription
Floata new identity rotation -- no rotation at all

IdentityInto # function

Identity, written into a buffer the caller owns.

function : IdentityInto(out:Float[]) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 4 elements

Length # function

function : Length(q:Float[]) ~ Float

Parameters

NameTypeDescription
qFloata rotation

Return

TypeDescription
Floatits length. One, for any rotation that has not drifted.

Multiply # function

Rotation product: rhs first, then lhs. The same argument order as Matrix4->Multiply, deliberately, so the two read alike at a call site.

function : Multiply(lhs:Float[], rhs:Float[]) ~ Float[]

Parameters

NameTypeDescription
lhsFloatapplied second
rhsFloatapplied first

Return

TypeDescription
Floata new rotation

MultiplyInto # function

Rotation product, written into a buffer the caller owns. Safe to alias: reads both inputs into locals before writing, so MultiplyInto(q, q, r) does what it looks like it does.

function : MultiplyInto(out:Float[], lhs:Float[], rhs:Float[]) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 4 elements
lhsFloatapplied second
rhsFloatapplied first

NormalizeInto # function

Unit length, written into a buffer the caller owns. Repeatedly multiplying rotations accumulates floating-point drift, so anything doing that should normalise every so often. A zero-length input yields identity rather than NaN.

function : NormalizeInto(out:Float[], q:Float[]) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 4 elements
qFloatthe rotation to normalise

Normalized # function

function : Normalized(q:Float[]) ~ Float[]

Parameters

NameTypeDescription
qFloatthe rotation to normalise

Return

TypeDescription
Floata new unit-length rotation

Rotate # function

Turn a vector by a rotation. Uses the expanded form rather than q * v * conjugate(q), which would need two quaternion products and a temporary.

function : Rotate(q:Float[], v:Vector3) ~ Vector3

Parameters

NameTypeDescription
qFloata unit rotation
vVector3the vector to turn

Return

TypeDescription
Vector3a new turned vector

Slerp # function

function : Slerp(a:Float[], b:Float[], t:Float) ~ Float[]

Parameters

NameTypeDescription
aFloatrotation at t = 0
bFloatrotation at t = 1
tFloat0..1, clamped

Return

TypeDescription
Floata new rotation between the two

SlerpInto # function

Spherical linear interpolation: the shortest turn from 'a' to 'b'. This is the reason the class exists. It travels the short way round -- if the two are more than half a turn apart, 'b' is negated first, which is the same rotation reached the other way -- and it moves at a constant angular rate, so an object turning under it does not speed up in the middle. Falls back to a straight lerp when the two are very close, because the sine of a tiny angle divides badly.

function : SlerpInto(out:Float[], a:Float[], b:Float[], t:Float) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 4 elements
aFloatrotation at t = 0
bFloatrotation at t = 1
tFloat0..1, clamped

ToMatrix # function

function : ToMatrix(q:Float[]) ~ Float[]

Parameters

NameTypeDescription
qFloata unit rotation

Return

TypeDescription
Floata new 4x4 rotation matrix

ToMatrixInto # function

The rotation as a 4x4 matrix, written into a buffer the caller owns. Column-major and right-handed, the same as everything Matrix4 produces, so the result can be multiplied straight into a model matrix.

function : ToMatrixInto(out:Float[], q:Float[]) ~ Nil

Parameters

NameTypeDescription
outFloatdestination, at least 16 elements
qFloata unit rotation