Skip to content

Showcases

The five examples the gallery lists under Showcases. Each block is what the gallery’s code panel shows beside that example, copied from the example itself. It is the lines that do the work, not the whole file, so it is not compiled here the way the guides’ code is. The file it came from is linked under each one.

Hills from sixteen regions of heights, textured by slope and height, with grass, stones and trees scattered by rule.

// Ground as data: regions of heights, made only where there is ground.
final terrain = Terrain(regionSize: 64, spacing: 2, sets: const [
TerrainSet(name: 'rock', albedo: 'rock.png', normal: 'rock_normal.png',
tileSize: 12, triplanar: true),
TerrainSet(name: 'grass', albedo: 'grass.png', normal: 'grass_normal.png',
tileSize: 4),
]);
for (final key in keys) {
terrain.fillHeights(key, (x, z) => relief * hills.unit(x / 120, z / 120));
}
// New ground is automatic: rock where it is steep or high, grass elsewhere.
terrain.autoCover = const AutoCover(steep: 0, flat: 1, slope: 1.2);
// What grows on it, by rule. A trunk and a crown share a seed, so they stand
// together.
terrain.scatter.addAll(const [
ScatterLayer(name: 'grass', seed: 1, density: 0.5, sets: [1],
size: (0.4, 0.3, 0.4), lean: 0.7, colour: 0x5E8C3A, range: 70),
ScatterLayer(name: 'stones', seed: 2, density: 0.03, sets: [0],
size: (1.2, 0.7, 0.9), lean: 1, lift: -0.25, colour: 0x8A8580),
ScatterLayer(name: 'trunks', seed: 3, density: 0.004, sets: [1],
maxSlope: 22, size: (0.45, 4, 0.45), colour: 0x5A3E28),
ScatterLayer(name: 'crowns', seed: 3, density: 0.004, sets: [1],
maxSlope: 22, size: (2.6, 3.4, 2.6), lift: 3.2, colour: 0x2F5A2A),
]);
// Every frame. The maps are shared, and only a region whose revision moved
// crosses to the renderer; the placer places again only where it moved.
placer.update(terrain);
OrblitScene(
terrain: [
terrainFrom(terrain, key: 1, pixels: (path) => decoded[path]),
],
populations: scatterFrom(placer, key: 100).populations,
...
)
// Standing on it needs no physics: the heights the renderer draws.
final y = terrain.heightAt(x, z);
final up = terrain.normalAt(x, z);

From terrain.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='terrain' flutter run -d macos

A landscape of sixty thousand cubes, generated and sent once.

// The world is a grid of bytes, not a list of what to draw. The moment
// somebody can dig, "what is at this point" is asked constantly — by the
// body falling, by every step, by every ray under the crosshair — and a
// grid answers it in one lookup instead of sixty thousand.
Uint8List blocks; // side * side * tall, nought is air
// Only what can be seen is drawn. A block with six solid neighbours is
// invisible from everywhere, and in a world of hills that is most of them.
if (!buriedOnAllSides) {
transforms.addAll(placed(x, y, z));
colours.addAll(colourOf(kind));
}
// Moved one axis at a time, which is the whole reason it works: move in
// one step and test afterwards and you are inside a wall with no way to
// know which way to come back out. One at a time, a corner stops you
// sideways and lets you keep walking forwards — which is what sliding
// along a wall is.
for (final step in [(dx, 0.0), (0.0, dz)]) { ... }
// And digging is a ray walked a fraction of a block at a time. A proper
// grid traversal is faster; this is called once per click, on a ray six
// blocks long, and being obviously correct is worth more here.

From voxels.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='blocks' flutter run -d macos

A runner you can play: dodge, jump, slide and pick up coins.

// Every model is made in code, written out as a glb, and handed to the
// renderer by name — the same path a downloaded file takes.
final art = RunnerArt.build();
for (final file in art.files.entries) {
await OrblitResources.provide(file.key, file.value);
}
// The world is laid in chunks as the runner reaches them. A chunk that
// falls behind hands its keys to the one being laid ahead, so the
// renderer reuses what it made rather than making more.
final key = 1000 + type * 100 + slot * cap + index;
// Grass is one population per chunk, sent when the chunk is laid and never
// again: the revision only changes when the blades do.
OrblitPopulation(
key: 1 + slot,
transforms: blades,
colours: shades,
minimum: low,
maximum: high,
revision: revisions[slot],
range: 60,
);
// A hurdle is cleared by being above it when you reach it, a bar by being
// below it, a container by not being in its lane.
final blocked = switch (hazard.kind) {
Kind.hurdle => y < 0.92,
Kind.bar => y + (sliding ? 0.8 : 1.3) > 1.05,
Kind.container => true,
};

From runner.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='runner' flutter run -d macos

Somebody else’s street, lit by this engine. A hundred lights at night.

// The fixtures come out of the scene's own emissive geometry, so the lights
// stand where the artist put the lamps.
for (final fixture in fixtures)
OrblitLight(
kind: OrblitLightKind.point,
position: fixture.at,
intensity: 2400, // lumens — a street lamp
falloffRadius: 14, // metres
castShadows: false, // a hundred shadow casters is not a thing
),
// And the sky is still a light, even at night.
OrblitSky(zenith: Color(0xFF0B1224), horizon: Color(0xFF243046), ambient: 120)

From bistro.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='bistro exterior' flutter run -d macos

The room, and the one place where missing bounced light shows.

// Indoors, the ambient is doing the job bounced light would do. Drag it to
// nothing and the shadows go black, which is exactly what a renderer without
// global illumination looks like when nothing stands in for it.
OrblitSky(
zenith: linearOf(const Color(0xFF2A1D18)),
ambient: 400, // lux
drawn: false, // lighting only; there is no sky to see from in here
)
// And occlusion, the cheapest approximation of contact darkening.
OrblitPostProcess(occlusion: OrblitOcclusion(enabled: true))

From bistro.dart. To open the gallery on it:

Terminal window
ORBLIT_EXAMPLE='bistro interior' flutter run -d macos