Scene module
Scene module Manual
This manual teaches the Scene module from first sketch to advanced patterns.
The Scene module is a small Processing.org like environment built on top of Metal Shading Language (MSL). You write Metal code, the runtime injects a drawing API around it, and each frame your code emits 2D or 3D geometry.
Unlike shaders, which operate on pixels, the Scene module works with vertices and triangles. It lets you define and manipulate 3D geometry directly, giving you control over the shape, position, and structure of objects before they are rendered to the screen. In other words, shaders determine how surfaces look, while the Scene module determines what geometry exists and where it is placed.
It is entirely code-driven and uses MSL together with custom macros that make defining and constructing objects more convenient. The Scene module also allows you to define custom state that persists between frames. This makes it well suited for stateful compositions, where geometry, motion, simulation data, or other parameters need to evolve continuously over time rather than being recalculated from scratch for every frame.
The most important idea is simple:
- your program is real Metal code
draw()is called every frame- optional
parallelDraw()runs afterward when present - you draw by calling helper functions such as
background(...),line(...),circle(...),push(),translate(...), androtate(...) - those built-in helper names are runtime macros that expand to functions needing
ctxunderneath - any user helper function that calls built-in drawing/runtime helpers should accept
thread RuntimeContext &ctx, and callers should passctx
The Scene module is especially good for:
- simple sketches and studies
- geometric compositions
- animated generative art
- transform-based recursive structures
- particle and swarm-like systems
- procedural 3D scenes and surfaces
If you are new to shader-style code, do not worry about Metal first. You can begin by thinking of the Scene module as “Processing with stricter syntax and a GPU-oriented runtime.” The one extra rule to remember is that drawing helpers secretly depend on the runtime context, so pass ctx through your own drawing helpers.
1. 2D and 3D at a Glance
The Scene module has two main drawing modes:
2dmode for flat drawing, line work, shape composition, particles, and screen-space animation3dmode for lit solids, camera-based scenes, procedural surfaces, and spatial structures
If you do not specify a render mode, the default is 2d.
Smallest 2D sketch:
void draw(thread RuntimeContext &ctx) {
background(0.08, 0.10, 0.14, 1.0);
circle(0.0, 0.0, 0.25);
}
Smallest 3D sketch:
[[render_mode(3d)]]
void draw(thread RuntimeContext &ctx) {
camera(float3(0.0, 0.0, 5.0),
float3(0.0, 0.0, 0.0),
float3(0.0, 1.0, 0.0));
perspective(0.92, 0.1, 50.0);
ambientLight(float3(0.16, 0.16, 0.18));
directionalLight(normalize(float3(-0.6, -1.0, -0.4)),
float3(1.0, 1.0, 1.0),
1.2);
fill(0.30, 0.72, 0.98, 1.0);
box(1.2, 1.2, 1.2);
}
This manual teaches both modes in this order:
- introduce
2dand3dearly - build the full 2D model first
- move into a dedicated 3D part afterward
3D is easier to learn once the core Scene module ideas already feel natural:
- drawing each frame
- style state
- transforms
- helper functions
- repetition and structure
Part I: 2D
2. The Smallest Useful Sketch
Every sketch defines one frame entry:
void draw(thread RuntimeContext &ctx)
This is the smallest useful program:
void draw(thread RuntimeContext &ctx) {
background(0.08, 0.10, 0.14, 1.0);
line(-0.6, 0.0, 0.6, 0.0);
}
What happens here:
draw(...)runs once per framebackground(...)clears the frameline(...)draws a single line segment
The ctx parameter is required even when you do not use it directly.
3. How the Scene module Thinks
The Scene module is not a custom scripting language. It compiles your code together with the runtime as Metal source. That means you can use normal Metal features such as:
structfloat,float2,float3,float4int,uint,bool- helper functions
if,for,while,break,continue- math functions such as
sin,cos,dot,length,normalize,clamp
The drawing facade is implemented with macros. For example, a call such as circle(0.0, 0.0, 0.1) is expanded by the runtime into a lower-level call that uses ctx. This is why draw() and parallelDraw() must receive thread RuntimeContext &ctx, and why user helper functions that call built-ins must receive and pass ctx too.
Example:
float wave(float x, float t) {
return sin(x * 8.0 + t);
}
void marker(thread RuntimeContext &ctx, float x, float y) {
fill(1.0, 0.9, 0.4, 1.0);
circle(x, y, 0.08);
}
void draw(thread RuntimeContext &ctx) {
float y = wave(0.2, time()) * 0.4;
marker(ctx, 0.0, y);
}
wave(...) is pure math, so it does not need ctx. marker(...) calls fill(...) and circle(...), so it must receive thread RuntimeContext &ctx and must be called as marker(ctx, ...).
The runtime also injects a small Processing-style facade, so you can call:
float time()float beatTime()uint frameCount()float width()float height()uint threadId()uint threadCount()
and all drawing helpers directly from draw(), parallelDraw(), or a user helper that has received ctx. You do not pass ctx into the built-in call itself because the macro does that part for you.
4. Coordinates and What You See on Screen
In 2d mode, the Scene module uses an aspect-correct square coordinate system.
(0, 0)is the center of the screen- positive
xgoes right - positive
ygoes downward - the square
[-1, 1] x [-1, 1]is always fully visible
This means:
circle(0.0, 0.0, 0.25)draws a circle in the centerline(-1.0, 0.0, 1.0, 0.0)spans the full visible width of the square view- equal
xandydistances stay visually equal even on wide or tall windows
The coordinate system is one of the most important things to internalize early. The Scene module is not pixel-based by default. Most drawing uses this normalized drawing space.
5. Step 1: Set the Background
The first thing beginners usually want is control over the whole frame:
void draw(thread RuntimeContext &ctx) {
background(0.02, 0.04, 0.08, 1.0);
}
Colors are usually expressed in the 0.0 ... 1.0 range per channel:
- red
- green
- blue
- alpha
So:
background(0.0, 0.0, 0.0, 1.0)is opaque blackbackground(1.0, 1.0, 1.0, 1.0)is opaque whitebackground(0.0, 0.0, 0.0, 0.0)is transparent black
In a parallel sketch, put frame-global setup such as background(...), camera, and lights in draw().
6. Step 2: Draw a Line
Lines use stroke settings:
void draw(thread RuntimeContext &ctx) {
background(0.05, 0.06, 0.08, 1.0);
stroke(1.0, 0.9, 0.7, 1.0);
strokeWeight(0.02);
line(-0.7, -0.4, 0.7, 0.4);
}
Key ideas:
stroke(...)sets the line color and enables strokingstrokeWeight(...)sets thicknessline(x0, y0, x1, y1)draws one segment
Important detail:
strokeWeight(...)uses drawing-space units, not pixels
So 0.02 is a visible drawing thickness, not “2 pixels.”
7. Step 3: Draw Basic Shapes
The core 2D shape helpers are:
rect(float x, float y, float width, float height)triangle(float x0, float y0, float x1, float y1, float x2, float y2)circle(float x, float y, float radius)disc(float x, float y, float radius)glowDisc(float x, float y, float radius)
Example:
void draw(thread RuntimeContext &ctx) {
background(0.06, 0.07, 0.10, 1.0);
stroke(1.0, 1.0, 1.0, 1.0);
strokeWeight(0.01);
fill(0.20, 0.70, 1.0, 0.30);
rect(-0.75, -0.25, 0.45, 0.50);
fill(1.0, 0.55, 0.25, 0.90);
circle(0.0, 0.0, 0.22);
fill(0.35, 1.0, 0.60, 0.85);
triangle(0.35, 0.28,
0.80, 0.20,
0.58, -0.30);
}
Useful facts:
circle(...)takes a radius, not a diameterdisc(...)is a filled hard-edged discglowDisc(...)is a soft radial disc for particles and glowsglowDisc(...)is 2D-only
8. Fill, Stroke, and Default Style
Style state matters a lot in the Scene module.
In 2d mode, the runtime begins each frame with:
- stroke enabled
- fill disabled
- identity transform
So if you call circle(...) at the start of a fresh frame, you get an outlined circle, not a filled one.
Useful style commands:
background(float4 color)background(float r, float g, float b, float a)stroke(float4 color)stroke(float r, float g, float b, float a)fill(float4 color)fill(float r, float g, float b, float a)noStroke()noFill()strokeWeight(float weight)
Example:
void draw(thread RuntimeContext &ctx) {
background(0.04, 0.05, 0.07, 1.0);
fill(0.25, 0.70, 1.0, 0.25);
stroke(1.0, 1.0, 1.0, 1.0);
strokeWeight(0.012);
circle(-0.35, 0.0, 0.20);
noStroke();
fill(1.0, 0.55, 0.30, 1.0);
circle(0.35, 0.0, 0.20);
}
This is worth remembering:
rect(...),triangle(...), andcircle(...)can use both fill and stroke- line strips use stroke only
beginShape(...)geometry uses fill only
9. Motion: Drawing Changes Every Frame
Animation usually starts with time():
void draw(thread RuntimeContext &ctx) {
background(0.04, 0.05, 0.08, 1.0);
noStroke();
fill(1.0, 0.75, 0.25, 1.0);
float t = time();
float x = cos(t) * 0.55;
float y = sin(t * 1.4) * 0.35;
circle(x, y, 0.08);
}
Two things are happening:
time()grows continuouslybeatTime()is a host-supplied musical sequence clock and stays0.0until the host updates itsin(...)andcos(...)turn time into smooth motion
You also have:
frameCount()for a frame indexwidth()andheight()for viewport size in pixels
For most motion studies, time() is the right starting point. For beat-synced visuals, use beatTime().
10. The Transform Stack
This is where the Scene module starts to feel powerful.
The transform commands are:
push()pop()translate(float x, float y)translate(float x, float y, float z)rotate(float angle)rotateX(float angle)rotateY(float angle)rotateZ(float angle)scale(float x, float y)scale(float x, float y, float z)
You can think of them as “move the local drawing space, then draw there.”
Example:
void draw(thread RuntimeContext &ctx) {
background(0.05, 0.05, 0.07, 1.0);
fill(0.30, 0.80, 0.55, 0.55);
stroke(1.0, 1.0, 1.0, 1.0);
strokeWeight(0.01);
push();
translate(0.2, 0.1);
rotate(0.6);
scale(0.7, 0.4);
rect(-0.3, -0.3, 0.6, 0.6);
pop();
}
What this means:
push()saves the current transform and style statetranslate(...)moves the local originrotate(...)rotates future drawingscale(...)scales future drawingpop()restores the previous state
This is the foundation of context-free style drawing: you define a local coordinate system, draw something, then move into a new local system and draw again.
Important notes:
rotate(...)uses radians, not degrees- non-uniform
scale(x, y)can turn circles into ellipses pop()without a matchingpush()causes a stack underflow diagnostic- maximum stack depth is
8
11. Local Coordinates and Repetition
Once you understand transforms, you can repeat a shape in many places without rewriting its geometry.
void petal(thread RuntimeContext &ctx) {
fill(0.90, 0.35, 0.55, 0.40);
rect(-0.18, -0.08, 0.36, 0.16);
}
void draw(thread RuntimeContext &ctx) {
background(0.04, 0.04, 0.06, 1.0);
noStroke();
for (uint i = 0u; i < 12u; i += 1u) {
float a = float(i) / 12.0 * 6.2831853;
push();
rotate(a);
translate(0.35, 0.0);
rotate(a * 0.5);
petal(ctx);
pop();
}
fill(1.0, 0.95, 0.80, 1.0);
circle(0.0, 0.0, 0.10);
}
This pattern appears everywhere in the Scene module:
- define a helper
- use transforms to place it
- repeat it in a loop
12. Drawing Longer Lines and Custom Shapes
Single primitives are not the whole story. You can also build geometry incrementally.
12.1 Polyline Drawing
Polyline helpers:
beginLine(float x, float y)lineTo(float x, float y)endLine()
Example:
void draw(thread RuntimeContext &ctx) {
background(0.05, 0.06, 0.09, 1.0);
noFill();
stroke(0.75, 0.92, 1.0, 1.0);
strokeWeight(0.012);
beginLine(-0.7, 0.0);
lineTo(-0.3, -0.25);
lineTo(0.0, 0.35);
lineTo(0.3, -0.20);
lineTo(0.7, 0.0);
endLine();
}
12.2 Triangle-Based Custom Shapes
Shape helpers:
beginShape()beginShape(TRIANGLE_STRIP)beginShape(TRIANGLE_FAN)vertex(float x, float y)endShape()
Each vertex(...) stores the current transform and current fill color. That means if you change fill(...) between vertices, the result becomes a gradient across the generated triangles.
Example:
void draw(thread RuntimeContext &ctx) {
background(0.05, 0.05, 0.08, 1.0);
noStroke();
beginShape(TRIANGLE_FAN);
fill(1.0, 1.0, 1.0, 1.0);
vertex(0.0, 0.0);
fill(0.35, 0.65, 1.0, 1.0);
vertex(-0.5, -0.2);
fill(0.20, 1.0, 0.65, 1.0);
vertex(-0.1, -0.55);
fill(1.0, 0.85, 0.25, 1.0);
vertex(0.55, -0.10);
fill(1.0, 0.35, 0.45, 1.0);
vertex(0.20, 0.45);
endShape();
}
13. User Parameters
You can expose adjustable parameters directly in draw() or parallelDraw():
void draw(thread RuntimeContext &ctx,
float radius [[param]],
float speed [[param]]) {
float t = time() * (0.2 + speed * 2.0);
float x = cos(t) * 0.5;
float y = sin(t) * 0.5;
noStroke();
fill(0.9, 0.6, 0.2, 1.0);
circle(x, y, 0.04 + radius * 0.2);
}
Rules:
- the first parameter of
draw()andparallelDraw()must bethread RuntimeContext &ctx - argument annotations tell the runtime what kind of entry an argument is
- supported user arguments are
float,Poly,MIDIKeys, andtexture2d<float> float name [[param]]exposes a knob-style user parameterfloat name [[param, bipolar]]exposes a bipolar knob-style user parameterPoly name [[input]]receives a host poly inputMIDIKeys name [[input]]receives host MIDI key statetexture2d<float> name [[input]]receives a host texture input- regular
[[param]]floats are normalized to the0.0 ... 1.0range - bipolar parameters use
[[param, bipolar]]and are normalized to the-1.0 ... 1.0range - input resources must use
[[input]] - textures can be used only as inputs and must be declared as
texture2d<float> name [[input]] - a parameter may be declared only in
draw(), only inparallelDraw(), or in both - if both functions declare the same parameter name with the same type and annotations, they share one UI parameter
- duplicate names with different types or annotations are compile errors
- parameters are exposed in first-seen declaration order
- the current float parameter maximum is
32 - the current Poly input maximum is
32 - the current texture input maximum is
1 - the current MIDIKeys input maximum is
1
Bipolar parameter example:
void draw(thread RuntimeContext &ctx,
float turn [[param, bipolar]]) {
rotate(turn * 3.14159265);
}
Texture inputs can be sampled directly in user code:
void draw(thread RuntimeContext &ctx, texture2d<float> image [[input]]) {
float2 uv = float2(0.5 + 0.25 * cos(time()),
0.5 + 0.25 * sin(time()));
float4 color = sampleTexture(image, uv);
noStroke();
fill(color);
circle(0.0, 0.0, 0.35);
}
Use sampleTexture(image, uv) for normalized coordinates, sampleTexturePx(image, xy) for pixel coordinates, and textureSize(image) when you need the real texture dimensions. Use brightness(color) to convert a sampled float4 color to alpha-weighted perceived luminance. Use gradient(image, uv) or gradientPx(image, xy) to read the local brightness direction.
Texture sampling helper signatures:
uint2 textureSize(texture2d<float> tex)float4 sampleTexture(texture2d<float> tex, float2 uv)float4 sampleTexture(texture2d<float> tex, float u, float v)float4 sampleTexturePx(texture2d<float> tex, float2 xy)float4 sampleTexturePx(texture2d<float> tex, float x, float y)float brightness(float4 color)float2 gradient(texture2d<float> tex, float2 uv)float2 gradient(texture2d<float> tex, float u, float v)float2 gradientPx(texture2d<float> tex, float2 xy)float2 gradientPx(texture2d<float> tex, float x, float y)
When you want to apply the input image as a material instead of sampling it manually, call useInputTexture() or useInputTexture(scale) before drawing. Right now that material path works on built-in 3D solids and surfaces, plus rect(...) in 2d.
Texture material helper signatures:
noTexture()textureUV()textureChecker(float scale)textureStripes(float scale)textureDots(float scale)textureGrid(float scale)textureNoise(float scale)useInputTexture()useInputTexture(float scale)
Parameters are ideal when you want:
- a speed slider
- a size control
- a color bias
- a spacing or density control
14. User Data and Frame-to-Frame Recurrence
By default, your function redraws from scratch each frame. To remember anything from one frame to the next, declare State.
Example:
struct State {
float angle;
float2 position;
float trail[6];
};
void draw(thread RuntimeContext &ctx) {
float angle = state().angle + 0.035;
state().angle = angle;
for (uint i = 0u; i < 5u; i += 1u) {
state().trail[i] = state().trail[i + 1u];
}
state().trail[5] = angle;
float2 position = float2(cos(angle), sin(angle * 1.37)) * 0.46;
state().position = position;
background(0.04, 0.05, 0.07, 1.0);
noStroke();
for (uint i = 0u; i < 6u; i += 1u) {
float t = state().trail[i];
float fade = float(i + 1u) / 6.0;
float2 p = float2(cos(t), sin(t * 1.37)) * 0.46;
fill(0.22 + fade * 0.55, 0.42 + fade * 0.45, 1.0, 0.16 + fade * 0.12);
circle(p.x, p.y, 0.024 + fade * 0.028);
}
fill(1.0, 0.98, 0.82, 0.95);
circle(position.x, position.y, 0.07);
}
The important model is:
state()is this frame’s writable single-frame state- before
draw()starts, the runtime copies the previousStateintostate() - if you do not overwrite a field, it keeps its previous value
State access helper signatures:
state()parallelState()parallelStateAt(uint i)
State is the shared per-frame state for the sketch. If you also use parallelDraw(), that same State can be read there after draw() has updated it for the current frame.
This is the right place to talk about recurrence in the mathematical sense. A stateful sketch often evolves by a recurrence rule:
- new position depends on old position
- new angle depends on old angle
- new velocity depends on old velocity and forces
That is how you build:
- trails
- bouncing systems
- swarms
- particle systems
- simple simulations
Current State and ParallelState fields are intentionally limited. Supported field types are:
floatintuintfloat2float3float4- fixed-size arrays of those types
15. Parallel Sketches
The Scene module can run many logical drawing lanes in parallel:
void draw(thread RuntimeContext &ctx,
float spread [[param]]) {
background(0.03, 0.035, 0.06, 1.0);
}
[[parallel_count(128)]]
void parallelDraw(thread RuntimeContext &ctx,
float spread [[param]]) {
float lane = float(threadId()) / float(max(threadCount() - 1u, 1u));
float angle = lane * 6.2831853 + time() * 0.4;
float radius = 0.2 + spread * 0.6;
noStroke();
fill(0.2 + lane * 0.8, 0.4, 1.0 - lane * 0.5, 0.35);
circle(cos(angle) * radius, sin(angle) * radius, 0.03);
}
Concepts:
draw()runs once first and owns frame-global setupparallelDraw()is optional[[parallel_count(N)]]onparallelDraw()runsNlogical drawing lanes afterwardthreadId()gives the current lane indexthreadCount()gives the active lane count[[parallel_count(N)]]belongs onparallelDraw(), notdraw()
This is useful when one sketch is really “many similar actors”:
- many particles
- many circles
- many branches
- many orbiting objects
In 2d, draw order is deterministic:
- all output from
draw()first - then all output from parallel lane
0 - then all output from parallel lane
1 - and so on
In 3d, both stages contribute to the same scene and visibility is determined by depth.
16. State in Parallel
When ParallelState and parallelDraw() are both active, each parallel lane gets its own state slot.
Declare ParallelState only when each lane needs its own persistent data:
struct ParallelState {
float angle;
float2 position;
};
state()reads the sharedStatewritten bydraw()parallelState()is the current lane’s writable stateparallelStateAt(i)reads laneifrom the previous frame
That distinction is very important:
- write shared state only in
draw() - write lane state only through
parallelState() - use
parallelStateAt(i)when one actor needs to inspect other actors from the previous frame - do not use
threadId() == 0uas a substitute for frame-global setup; that belongs indraw()
This makes patterns such as flocking, networks, and agent interaction possible without exposing partially updated state from the current frame.
17. Recursive Drawing
Recursive drawing is one of the clearest expressions of the transform-stack model.
In recursion, a helper function draws one piece of a structure, changes the local transform, and calls itself again with a smaller size or a deeper level.
Example:
void branch(thread RuntimeContext &ctx,
float length,
uint depth) {
beginLine(0.0, 0.0);
lineTo(0.0, length);
endLine();
if (depth >= 5u) {
return;
}
push();
translate(0.0, length);
push();
rotate(0.42);
branch(ctx, length * 0.72, depth + 1u);
pop();
push();
rotate(-0.42);
branch(ctx, length * 0.72, depth + 1u);
pop();
pop();
}
void draw(thread RuntimeContext &ctx) {
background(0.04, 0.05, 0.07, 1.0);
stroke(0.94, 0.86, 0.65, 1.0);
noFill();
strokeWeight(0.008);
push();
translate(0.0, -0.92);
branch(ctx, 0.22, 0u);
pop();
}
Why this works:
- each call draws in its own local coordinate system
push()andpop()isolate each branch- smaller child calls produce self-similar structure
The runtime tracks recursion depth and reports a diagnostic if the recursion limit is reached.
Part II: 3D
18. 3D Mode
Everything so far has focused on 2d, because that is the best place to learn the model. The Scene module also supports a lit 3d mode:
[[render_mode(3d)]]
void draw(thread RuntimeContext &ctx) {
camera(float3(0.0, 1.5, 6.0),
float3(0.0, 0.0, 0.0),
float3(0.0, 1.0, 0.0));
perspective(0.92, 0.1, 50.0);
ambientLight(float3(0.16, 0.17, 0.20));
directionalLight(normalize(float3(-0.7, -1.0, -0.5)),
float3(1.0, 0.97, 0.92),
1.2);
push();
rotateY(time() * 0.5);
fill(0.30, 0.72, 0.98, 1.0);
specular(0.92, 0.98, 1.0, 1.0);
shininess(24.0);
box(1.2, 1.2, 1.2);
pop();
}
The biggest differences from 2d:
- you enable it with
[[render_mode(3d)]] - the transform system becomes 3D
- you usually set camera and lighting once in
draw() - you do not need leader-thread guards inside
draw() - fill/material state matters more than stroke
Useful 3D transform and camera helpers:
push()pop()translate(float x, float y, float z)scale(float x, float y, float z)rotate(float angle)rotateX(float angle)rotateY(float angle)rotateZ(float angle)camera(float3 eye, float3 target, float3 up)perspective(float fovY, float nearPlane, float farPlane)
Useful 3D material and lighting helpers:
background(float4 color)background(float r, float g, float b, float a)ambientLight(float3 color)directionalLight(float3 direction, float3 color, float intensity)shadows()shadows(float strength)noShadows()fill(float4 color)fill(float r, float g, float b, float a)stroke(float4 color)stroke(float r, float g, float b, float a)noStroke()noFill()strokeWeight(float weight)specular(float4 color)specular(float r, float g, float b, float a)shininess(float value)ambientStrength(float value)diffuseStrength(float value)noTexture()textureUV()textureChecker(float scale)textureStripes(float scale)textureDots(float scale)textureGrid(float scale)textureNoise(float scale)useInputTexture()useInputTexture(float scale)environmentSphere()environmentSphere(float radius)
shadows(float strength) enables a 3D shadow map from the first directional light only. It is ignored in 2d; presets that do not call it stay on the faster non-shadow render path.
environmentSphere(...) creates a camera-centered inverted sphere using the current texture style, so it can show useInputTexture(...) or procedural textures as a sky/environment layer.
Useful 3D solids:
sphere(float radius)capsule(float radius, float height)capsule(float3 start, float3 end, float radius)plane(float width, float depth)pipe(float innerRadius, float outerRadius, float height)box(float width, float height, float depth)cone(float radius, float height)cylinder(float radius, float height)torus(float majorRadius, float minorRadius)
capsule(radius, height) builds a Y-aligned capsule using total end-to-end height, including the hemispherical caps.
capsule(start, end, radius) builds a capsule between two float3(...) spine points. start and end are the centers of the hemispherical caps, so the cylindrical middle section spans exactly between them.
plane(width, depth) builds a centered XZ plane on the local y = 0 surface, with UVs spanning 0..1 across width and depth.
pipe(innerRadius, outerRadius, height) builds a centered Y-aligned hollow cylinder with inner and outer walls plus top and bottom ring caps.
In 3d mode, the default style changes:
- fill enabled
- stroke disabled
That default is sensible because most 3D drawing is about lit surfaces.
19. Advanced 3D Geometry
The Scene module also supports incremental 3D surface generation.
Tube path:
beginTube(float radius, uint segments)tubeVertex(float x, float y, float z)tubeVertex(float x, float y, float z, float radius)endTube()
Grid surface:
beginGridSurface(uint cols, uint rows)surfaceVertex(float x, float y, float z)endGridSurface()
Sphere-like procedural surface:
beginSphereSurface(uint segments)sphereVertex(float radius)endSphereSurface()
These are useful when a built-in solid is not enough and you want:
- ribbons and tubes
- terrain-like surfaces
- blobby or deformed spheres
Part III: General Notes
20. Practical Rules and Limits
The most important practical rules are:
- use radians for all rotation functions
- use
push()andpop()to isolate transforms - use
Statewhen shared data must persist between frames - use
ParallelStateonly when each parallel lane needs its own persistent memory - use
parallelDraw(),threadId(), andthreadCount()when the same sketch logic should run many times - put frame-global setup such as
background(...), camera, and lights indraw() - remember that
2dand3dhelpers are not interchangeable
Current fixed limits include:
- maximum stack depth:
8 - maximum primitives:
131072 - maximum polyline points:
65536 - maximum generated vertices:
1048576 - maximum user parameters:
32
Possible diagnostics include:
- primitive buffer overflow
- point buffer overflow
- vertex buffer overflow
- stack overflow
- stack underflow
- recursion limit reached
If output is unexpectedly truncated, you may be hitting a geometry limit.
21. A Good Learning Path
If you are starting from zero, this order works well:
- background
- line
- circle, rect, triangle
- fill and stroke
- time-based motion
- push, pop, translate, rotate, scale
- loops and repeated forms
- user parameters
Stateand recurrence- parallel sketches
- recursion
- 3D mode
That order matches the way the language grows conceptually:
- first you draw one thing
- then you place many things
- then you make them evolve
- then you make them interact
22. When to Use the Reference
Use this manual when you want to learn, understand patterns, or see complete examples.
Use reference.md when you want the fast technical answer to questions like:
- what is the exact entry-point signature
- what helpers exist in
2dor3d - what are the defaults
- what are the limits