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).

Track

How a value changes over time. The other half of animation. Transform->SetParent says how parts move TOGETHER; a Track says how anything moves at all -- a lid opening, a platform rising, a light dimming, a door swinging, a camera pushing in. It holds keys and reads a value back at any time between them: ## Why a track rather than a tween A from-to pair with a duration covers the easy case and then stops: an animation that goes out, waits, and comes back is three of them plus the bookkeeping to sequence them. Keys are that for free, and reduce to the easy case when there are two of them. ## What it does not do One FLOAT. Not a vector, not a colour, not a quaternion -- three tracks make a position and four make a colour, and keeping it scalar is what lets it drive anything at all rather than the specific things somebody thought of. It has no clock of its own either: Sample takes a time, so the caller decides whether that is wall time, a paused time, or one running backwards. Rotation between two ANGLES here is not the same as rotating between two orientations -- interpolating yaw, pitch and roll separately is fine for a hinge or a turntable and wrong for anything tumbling freely, which needs quaternions this bundle does not have.

Example

swing := Track->New();
swing->Add(0.0, 0.0);
swing->Add(1.5, 90.0);
swing->SetEasing(Easing->EASE_IN_OUT);
...
door->SetYaw(swing->Sample(elapsed)->ToRadians());

Operations

Add #

Add a key. Inserted in time order, so keys can be added in any order. Adding a second key at exactly the same time replaces the first rather than creating a step nothing can land between.

method : public : Add(time:Float, value:Float) ~ Nil

Parameters

NameTypeDescription
timeFloatwhen, in seconds from the start
valueFloatwhat the track reads at that moment

GetCount #

method : public : GetCount() ~ Int

Return

TypeDescription
Inthow many keys the track holds

GetDuration #

The time of the last key, which is how long the track runs for.

method : public : GetDuration() ~ Float

Return

TypeDescription
Floatseconds, or 0 for an empty track

GetEasing #

method : public : GetEasing() ~ Int

Return

TypeDescription
Intthe current easing mode

GetKeyTime #

method : public : GetKeyTime(index:Int) ~ Float

Parameters

NameTypeDescription
indexInt0 to GetCount() - 1

Return

TypeDescription
Floatthe time of that key, or 0 if out of range

GetKeyValue #

method : public : GetKeyValue(index:Int) ~ Float

Parameters

NameTypeDescription
indexInt0 to GetCount() - 1

Return

TypeDescription
Floatthe value of that key, or 0 if out of range

IsLoop #

method : public : IsLoop() ~ Bool

Return

TypeDescription
Boolwhether the track loops

New # constructor

An empty track. Sampling one with no keys returns 0.

New()

New # constructor

A track that runs from one value to another -- the common case, spelled directly.

New(duration:Float, )

Parameters

NameTypeDescription
durationFloathow long it takes, in seconds

Sample #

Read the value at a moment. Before the first key returns the first value and after the last returns the last, so a track is safe to sample at any time without the caller clamping -- unless it loops, in which case time wraps.

method : public : Sample(time:Float) ~ Float

Parameters

NameTypeDescription
timeFloatseconds from the start

Return

TypeDescription
Floatthe value there

SetEasing #

How the value moves BETWEEN keys. LINEAR by default. The eased modes are what stop machine-driven motion reading as machine-driven: almost nothing in the world starts and stops at full speed, and EASE_IN_OUT is the difference between a lid opening and a lid teleporting through its arc.

method : public : SetEasing(easing:Int) ~ Nil

Parameters

NameTypeDescription
easingIntan Easing

SetLoop #

Whether time wraps past the last key. Off by default, which HOLDS the last value -- right for a door that opens once. On wraps back to the start, for anything cyclic. Note a looping track jumps at the wrap unless its first and last values match.

method : public : SetLoop(loop:Bool) ~ Nil

Parameters

NameTypeDescription
loopBooltrue to wrap

Shape #

Bend 0..1 according to the easing mode.

method : private : Shape() ~ Float

Parameters

NameTypeDescription