18 3D Algebra and Opaque Rendering
| (require animate/3d) | package: animate |
This module provides the pure spatial algebra kernel. Its values describe right-handed mathematical coordinates; they neither render nor depend on a renderer. All coordinate and matrix fields reject NaN and infinite values.
18.1 Vectors
struct
(struct vec3 (x y z) #:transparent) x : finite-real? y : finite-real? z : finite-real?
procedure
(vec3-scale scalar value) → vec3?
scalar : finite-real? value : vec3?
procedure
(vec3-dot first second) → finite-real?
first : vec3? second : vec3?
procedure
(vec3-cross first second) → vec3?
first : vec3? second : vec3?
procedure
(vec3-length value) → nonnegative-real?
value : vec3?
procedure
(vec3-distance first second) → nonnegative-real?
first : vec3? second : vec3?
procedure
(vec3-normalize value) → vec3?
value : vec3?
procedure
first : vec3? second : vec3? progress : finite-real?
procedure
(vec3-finite? value) → boolean?
value : any/c
18.2 Linear maps and rotations
struct
(struct linear3 (m00 m01 m02 m10 m11 m12 m20 m21 m22) #:transparent) m00 : finite-real? m01 : finite-real? m02 : finite-real? m10 : finite-real? m11 : finite-real? m12 : finite-real? m20 : finite-real? m21 : finite-real? m22 : finite-real?
[a b c] [d e f] [g h i]
value
procedure
(linear3-compose outer inner) → linear3?
outer : linear3? inner : linear3?
procedure
(linear3-invert map) → linear3?
map : linear3?
procedure
(linear3-determinant map) → finite-real?
map : linear3?
procedure
(linear3-transpose map) → linear3?
map : linear3?
procedure
(linear3-apply-vector map value) → vec3?
map : linear3? value : vec3?
procedure
(linear3-normal-transform map) → linear3?
map : linear3?
procedure
(rotation3? value) → boolean?
value : any/c
procedure
(rotation3-components rotation) → vector?
rotation : rotation3?
value
procedure
(axis-angle axis angle) → rotation3?
axis : vec3? angle : finite-real?
procedure
(rotation3-from-to from-direction to-direction) → rotation3? from-direction : vec3? to-direction : vec3?
procedure
(rotation3-look-at forward [#:up up]) → rotation3?
forward : vec3? up : vec3? = y-axis3
procedure
(rotation3-compose outer inner) → rotation3?
outer : rotation3? inner : rotation3?
procedure
(rotation3-invert rotation) → rotation3?
rotation : rotation3?
procedure
(rotation3-apply rotation value) → vec3?
rotation : rotation3? value : vec3?
procedure
(rotation3->linear3 rotation) → linear3?
rotation : rotation3?
procedure
(rotation3-slerp from to progress) → rotation3?
from : rotation3? to : rotation3? progress : (and/c finite-real? (>=/c 0) (<=/c 1))
18.3 Affine and decomposed transforms
procedure
(affine3-linear map) → linear3?
map : affine3?
procedure
(affine3-translation map) → vec3?
map : affine3?
value
procedure
(affine3-compose outer inner) → affine3?
outer : affine3? inner : affine3?
procedure
(affine3-invert map) → affine3?
map : affine3?
procedure
(affine3-apply-point map point) → vec3?
map : affine3? point : vec3?
procedure
(affine3-apply-vector map value) → vec3?
map : affine3? value : vec3?
procedure
(affine3-normal-transform map) → linear3?
map : affine3?
procedure
(affine3-lerp from to progress) → affine3?
from : affine3? to : affine3? progress : (and/c finite-real? (>=/c 0) (<=/c 1))
struct
(struct transform3 (translation rotation scale) #:transparent) translation : vec3? rotation : rotation3? scale : vec3?
procedure
(make-transform3 [ #:translation translation #:rotation rotation #:scale scale]) → transform3? translation : vec3? = origin3 rotation : rotation3? = identity-rotation3 scale : vec3? = (vec3 1 1 1)
value
procedure
(transform3->affine3 transform) → affine3?
transform : transform3?
procedure
(transform3-compose outer inner) → affine3?
outer : transform3? inner : transform3?
procedure
(transform3-apply-point transform point) → vec3?
transform : transform3? point : vec3?
procedure
(transform3-lerp from to progress) → transform3?
from : transform3? to : transform3? progress : (and/c finite-real? (>=/c 0) (<=/c 1))
18.4 Bounds, rays, and planes
procedure
(aabb3-minimum bounds) → (or/c #f vec3?)
bounds : aabb3?
procedure
(aabb3-maximum bounds) → (or/c #f vec3?)
bounds : aabb3?
value
procedure
(aabb3-empty? bounds) → boolean?
bounds : aabb3?
procedure
(aabb3-union first second) → aabb3?
first : aabb3? second : aabb3?
procedure
(aabb3-from-points points) → aabb3?
points : (listof vec3?)
procedure
(aabb3-transform bounds map) → aabb3?
bounds : aabb3? map : affine3?
procedure
(aabb3-center bounds) → vec3?
bounds : aabb3?
procedure
(aabb3-size bounds) → vec3?
bounds : aabb3?
procedure
(aabb3-contains? bounds point) → boolean?
bounds : aabb3? point : vec3?
procedure
(plane3-point plane) → vec3?
plane : plane3?
procedure
(plane3-normal plane) → vec3?
plane : plane3?
struct
(struct ray3-plane-hit (point distance) #:transparent) point : vec3? distance : nonnegative-real?
struct
(struct ray3-aabb-hit (entry exit) #:transparent) entry : nonnegative-real? exit : nonnegative-real?
struct
(struct ray3-triangle-hit (point distance barycentric normal) #:transparent) point : vec3? distance : nonnegative-real? barycentric : vec3? normal : vec3?
procedure
ray : ray3? distance : finite-real?
procedure
(ray3-intersect-plane ray plane) → (or/c #f ray3-plane-hit?)
ray : ray3? plane : plane3?
procedure
(ray3-intersect-aabb ray bounds) → (or/c #f ray3-aabb-hit?)
ray : ray3? bounds : aabb3?
procedure
(ray3-intersect-triangle ray first second third) → (or/c #f ray3-triangle-hit?) ray : ray3? first : vec3? second : vec3? third : vec3?
18.5 Spatial Visuals and paths
SCENE-3D-B keeps spatial content in a protocol distinct from ordinary two-dimensional visual? values. This prevents an ordinary scene path or two-dimensional animation request from silently treating a mesh as a Pict. Only its enclosing view3d is an ordinary two-dimensional Visual.
procedure
(spatial-visual? value) → boolean?
value : any/c
procedure
(spatial-container? value) → boolean?
value : any/c
procedure
(spatial-id object) → symbol?
object : spatial-visual?
procedure
(spatial-transform object) → transform3?
object : spatial-visual?
procedure
(spatial-with-transform object transform) → spatial-visual?
object : spatial-visual? transform : transform3?
procedure
(spatial-opacity object) → (and/c real? (between/c 0 1))
object : spatial-visual?
procedure
(spatial-with-opacity object opacity) → spatial-visual?
object : spatial-visual? opacity : (and/c real? (between/c 0 1))
procedure
(spatial-local-bounds object) → aabb3?
object : spatial-visual?
procedure
(spatial-position object) → vec3?
object : spatial-visual?
procedure
(spatial-with-position object position) → spatial-visual?
object : spatial-visual? position : vec3?
procedure
(spatial-rotation object) → rotation3?
object : spatial-visual?
procedure
(spatial-with-rotation object rotation) → spatial-visual?
object : spatial-visual? rotation : rotation3?
procedure
(spatial-scale object) → vec3?
object : spatial-visual?
procedure
(spatial-with-scale object scale) → spatial-visual?
object : spatial-visual? scale : vec3?
struct
(struct spatial-child (id visual) #:transparent) id : symbol? visual : spatial-visual?
procedure
(group3d children #:id id [ #:transform transform #:opacity opacity]) → group3d? children : (listof spatial-visual?) id : symbol? transform : transform3? = identity-transform3 opacity : (and/c real? (between/c 0 1)) = 1
procedure
(group3d-children group) → (listof spatial-visual?)
group : group3d?
procedure
(group3d-with-children group children) → group3d?
group : group3d? children : (listof spatial-visual?)
procedure
(spatial-path? value) → boolean?
value : any/c
procedure
(spatial-relative-ref container path) → spatial-visual?
container : spatial-container? path : spatial-path?
procedure
(spatial-relative-replace container path replacement) → spatial-container? container : spatial-container? path : spatial-path? replacement : spatial-visual?
18.6 Meshes
procedure
(mesh3d #:id id #:vertices vertices [ #:triangles triangles #:edges edges #:normals normals #:colors colors] #:material material [ #:transform transform #:opacity opacity #:wireframe-color color #:wireframe-width width]) → mesh3d? id : symbol? vertices : vector? triangles : vector? = #() edges : (or/c #f vector?) = #f normals : (or/c #f vector?) = #f colors : (or/c #f vector?) = #f material : material3d? transform : transform3? = identity-transform3 opacity : (and/c real? (between/c 0 1)) = 1 color : any/c = "steelblue" width : positive-real? = 2
procedure
(mesh3d-vertices mesh) → vector?
mesh : mesh3d?
procedure
(mesh3d-triangles mesh) → vector?
mesh : mesh3d?
procedure
(mesh3d-edges mesh) → vector?
mesh : mesh3d?
procedure
(mesh3d-normals mesh) → (or/c #f vector?)
mesh : mesh3d?
procedure
(mesh3d-colors mesh) → (or/c #f vector?)
mesh : mesh3d?
procedure
(mesh3d-material mesh) → material3d?
mesh : mesh3d?
procedure
(mesh3d-wireframe-color mesh) → any/c
mesh : mesh3d?
procedure
(mesh3d-wireframe-width mesh) → positive-real?
mesh : mesh3d?
procedure
(mesh3d-local-bounds mesh) → aabb3?
mesh : mesh3d?
18.6.1 Topology diagnostics and explicit orientation repair
struct
(struct mesh3d-duplicate-triangle ( first-triangle-index duplicate-triangle-index winding) #:transparent) first-triangle-index : exact-nonnegative-integer? duplicate-triangle-index : exact-nonnegative-integer? winding : (or/c 'same 'reversed)
struct
(struct mesh3d-analysis ( vertex-count triangle-count edge-count degenerate-triangles duplicate-triangles boundary-edges boundary-loops nonmanifold-edges inconsistent-winding-edges connected-components isolated-vertices signed-component-volumes watertight? orientable? consistently-wound?) #:transparent) vertex-count : exact-nonnegative-integer? triangle-count : exact-nonnegative-integer? edge-count : exact-nonnegative-integer? degenerate-triangles : vector? duplicate-triangles : vector? boundary-edges : vector? boundary-loops : vector? nonmanifold-edges : vector? inconsistent-winding-edges : vector? connected-components : vector? isolated-vertices : vector? signed-component-volumes : vector? watertight? : boolean? orientable? : boolean? consistently-wound? : boolean?
procedure
(analyze-mesh3d mesh) → mesh3d-analysis?
mesh : mesh3d?
procedure
(mesh3d-validate mesh) → mesh3d-analysis?
mesh : mesh3d?
struct
(struct mesh3d-orientation-report ( initial-analysis final-analysis flipped-triangle-indices outward?) #:transparent) initial-analysis : mesh3d-analysis? final-analysis : mesh3d-analysis? flipped-triangle-indices : vector? outward? : boolean?
procedure
(mesh3d-orient-consistently mesh)
→
mesh3d? mesh3d-orientation-report? mesh : mesh3d?
procedure
(mesh3d-orient-outward mesh)
→
mesh3d? mesh3d-orientation-report? mesh : mesh3d?
procedure
mesh : mesh3d?
18.7 Materials and lights
procedure
(material3d [ #:color color #:shading shading #:ambient ambient #:diffuse diffuse #:specular specular #:roughness roughness #:double-sided? double-sided? #:wireframe? wireframe?]) → material3d? color : any/c = "cornflowerblue" shading : (or/c 'unlit 'flat 'smooth) = 'flat ambient : nonnegative-real? = 1 diffuse : nonnegative-real? = 1 specular : nonnegative-real? = 0 roughness : positive-real? = 1 double-sided? : boolean? = #f wireframe? : boolean? = #f
procedure
(material3d? value) → boolean?
value : any/c
procedure
(material3d-color material) → rgba-color?
material : material3d?
procedure
(material3d-shading material) → (or/c 'unlit 'flat 'smooth)
material : material3d?
procedure
(material3d-ambient material) → nonnegative-real?
material : material3d?
procedure
(material3d-diffuse material) → nonnegative-real?
material : material3d?
procedure
(material3d-specular material) → nonnegative-real?
material : material3d?
procedure
(material3d-roughness material) → positive-real?
material : material3d?
procedure
(material3d-double-sided? material) → boolean?
material : material3d?
procedure
(material3d-wireframe? material) → boolean?
material : material3d?
procedure
(ambient-light3d [ #:intensity intensity #:color color]) → ambient-light3d? intensity : nonnegative-real? = 1 color : any/c = "white"
procedure
(directional-light3d direction [ #:intensity intensity #:color color]) → directional-light3d? direction : vec3? intensity : nonnegative-real? = 1 color : any/c = "white"
procedure
(ambient-light3d? value) → boolean?
value : any/c
procedure
(ambient-light3d-intensity light) → nonnegative-real?
light : ambient-light3d?
procedure
(ambient-light3d-color light) → rgba-color?
light : ambient-light3d?
procedure
(directional-light3d? value) → boolean?
value : any/c
procedure
(directional-light3d-direction light) → vec3?
light : directional-light3d?
procedure
(directional-light3d-intensity light) → nonnegative-real?
light : directional-light3d?
procedure
(directional-light3d-color light) → rgba-color?
light : directional-light3d?
18.8 Cameras and projection
A camera’s local x axis is screen-right, local y is screen-up, and local negative z is forward. A default camera is at (vec3 0 0 8) and looks at origin3. The camera is reusable: viewport aspect is supplied when projecting or rendering, rather than stored in the camera.
struct
(struct perspective-projection3d (vertical-field-of-view) #:transparent) vertical-field-of-view : finite-real?
struct
(struct orthographic-projection3d (vertical-size) #:transparent) vertical-size : positive-real?
procedure
(perspective-camera3d [ #:position position #:look-at target #:up up #:rotation rotation #:near near #:far far #:vertical-field-of-view field-of-view]) → camera3d? position : vec3? = (vec3 0 0 8) target : vec3? = origin3 up : vec3? = y-axis3 rotation : (or/c #f rotation3?) = #f near : positive-real? = 1/10 far : positive-real? = 100 field-of-view : finite-real? = (/ pi 4)
procedure
(orthographic-camera3d [ #:position position #:look-at target #:up up #:rotation rotation #:near near #:far far #:vertical-size size]) → camera3d? position : vec3? = (vec3 0 0 8) target : vec3? = origin3 up : vec3? = y-axis3 rotation : (or/c #f rotation3?) = #f near : positive-real? = 1/10 far : positive-real? = 100 size : positive-real? = 6
procedure
(camera3d-position camera) → vec3?
camera : camera3d?
procedure
(camera3d-rotation camera) → rotation3?
camera : camera3d?
procedure
(camera3d-near camera) → positive-real?
camera : camera3d?
procedure
(camera3d-far camera) → positive-real?
camera : camera3d?
procedure
(camera3d-projection camera)
→ (or/c perspective-projection3d? orthographic-projection3d?) camera : camera3d?
procedure
(camera3d-forward camera) → vec3?
camera : camera3d?
procedure
(camera3d-right camera) → vec3?
camera : camera3d?
procedure
(camera3d-up camera) → vec3?
camera : camera3d?
procedure
(camera3d-look-at camera target [#:up up]) → camera3d?
camera : camera3d? target : vec3? up : vec3? = y-axis3
procedure
(camera3d-world->view camera point) → vec3?
camera : camera3d? point : vec3?
procedure
(camera3d-view-depth camera point) → finite-real?
camera : camera3d? point : vec3?
procedure
(camera3d-project camera point [ #:aspect aspect]) → (or/c #f vec2?) camera : camera3d? point : vec3? aspect : positive-real? = 1
procedure
(camera3d-pixel-ray camera pixel-x pixel-y #:width width #:height height) → ray3? camera : camera3d? pixel-x : finite-real? pixel-y : finite-real? width : exact-positive-integer? height : exact-positive-integer?
procedure
(camera3d-frustum camera [#:aspect aspect]) → vector?
camera : camera3d? aspect : positive-real? = 1
18.9 Spatial viewports
procedure
(view3d children #:id id [ #:center center #:width width #:height height #:rotation rotation #:scale scale #:opacity opacity #:camera camera #:lights lights #:background background #:render-mode render-mode #:transparency-mode transparency-mode]) → view3d? children : (listof spatial-visual?) id : symbol? center : vec2? = origin width : positive-real? = 12 height : positive-real? = 27/4 rotation : finite-real? = 0 scale : (or/c positive-real? vec2?) = 1 opacity : (and/c real? (between/c 0 1)) = 1 camera : camera3d? = (perspective-camera3d)
lights : (listof (or/c ambient-light3d? directional-light3d?)) = null background : any/c = "white" render-mode : (or/c 'wireframe 'opaque) = 'wireframe
transparency-mode : (or/c 'object-sorted 'triangle-sorted) = 'triangle-sorted
procedure
(view3d-children view) → (listof spatial-visual?)
view : view3d?
procedure
(view3d-width view) → positive-real?
view : view3d?
procedure
(view3d-height view) → positive-real?
view : view3d?
procedure
(view3d-camera view) → camera3d?
view : view3d?
procedure
(view3d-lights view) → list?
view : view3d?
procedure
(view3d-background view) → any/c
view : view3d?
procedure
(view3d-render-mode view) → (or/c 'wireframe 'opaque)
view : view3d?
procedure
(view3d-transparency-mode view)
→ (or/c 'object-sorted 'triangle-sorted) view : view3d?
procedure
(view3d-spatial-ref view path) → spatial-visual?
view : view3d? path : spatial-path?
procedure
(view3d-spatial-has? view path) → boolean?
view : view3d? path : any/c
procedure
(view3d-spatial-replace view path replacement) → view3d? view : view3d? path : spatial-path? replacement : spatial-visual?
procedure
(view3d-spatial-update view path update) → view3d?
view : view3d? path : spatial-path? update : procedure?
18.10 Spatial animation and camera authoring
SCENE-3D-D keeps three-dimensional motion in the ordinary immutable scene timeline. A spatial target is a path beginning with its owning view3d identifier, for example '(world cube). A camera target is the owning view identifier alone, for example 'world. Every request captures its endpoint from the clip-start state. Sampling at a time does not depend on having sampled an earlier frame.
(scene-play (scene-add (make-scene) world matrix) (rotate3d-by '(world cube) (axis-angle y-axis3 pi)) (camera3d-orbit-by 'world #:azimuth (/ pi 2)) #:duration 2)
procedure
path : spatial-path? position : vec3?
procedure
path : spatial-path? delta : vec3?
procedure
(rotate3d-to path rotation) → any/c
path : spatial-path? rotation : rotation3?
procedure
(rotate3d-by path rotation) → any/c
path : spatial-path? rotation : rotation3?
procedure
(scale3d-to path scale) → any/c
path : spatial-path? scale : vec3?
procedure
(scale3d-by path factor) → any/c
path : spatial-path? factor : vec3?
procedure
(transform3d-to path transform) → any/c
path : spatial-path? transform : transform3?
procedure
(camera3d-move-to view-id position) → any/c
view-id : symbol? position : vec3?
procedure
(camera3d-look-at-to view-id target [#:up up]) → any/c
view-id : symbol? target : vec3? up : vec3? = y-axis3
procedure
(camera3d-orbit-by view-id [ #:center center #:azimuth azimuth #:elevation elevation]) → any/c view-id : symbol? center : vec3? = origin3 azimuth : finite-real? = 0 elevation : finite-real? = 0
procedure
(camera3d-roll-to view-id angle) → any/c
view-id : symbol? angle : finite-real?
procedure
(camera3d-field-of-view-to view-id field-of-view) → any/c view-id : symbol? field-of-view : finite-real?
procedure
(camera3d-orthographic-height-to view-id height) → any/c view-id : symbol? height : positive-real?
procedure
(camera3d-dolly-by view-id distance) → any/c
view-id : symbol? distance : finite-real?
procedure
(camera3d-fit view-id [#:padding padding]) → any/c
view-id : symbol? padding : positive-real? = 11/10
procedure
(camera3d-follow view-id path) → any/c
view-id : symbol? path : spatial-path?
All these forms work as leaves of timed, succession, animation-group, and lagged-start. A 3D camera remains separate from the ordinary two-dimensional render camera, so a formula or caption stays fixed while the view’s spatial camera moves.
In interactive preview, an inspection camera is an overlay on the authored camera: drag in a spatial viewport to orbit, shift-drag to pan its target, use the mouse wheel to dolly (or orthographic zoom), and press R to return to the authored view. The override is part of the immutable preview render request—including a subprocess worker request—and is never written into source or the Scene. The Animate menu can reset it or copy an expression/animation scratch form for authoring.
For runnable examples, see "examples/3d/wireframe-cube.rkt", "examples/3d/opaque-cube.rkt", and "examples/3d/depth-test.rkt"; "examples/3d/camera-orbit.rkt" is the canonical motion probe.
Current limitation: opaque mode is a software rasterizer for filled triangles. It has flat, unlit, and smooth shading, depth-aware transparency, spatial relations, and projected labels, but no texture mapping, no specular response, no shadows, order-independent transparency, or 3D picking. An ordinary two-dimensional traversal of a spatial child is rejected: use rooted 3D animation paths or view3d-spatial-*.
18.11 Semantic spatial relations and projected labels
SCENE-3D-E adds derived spatial geometry without a mutable per-frame updater. A spatial-relation is a spatial Visual declaration with a concrete template, an explicit list of inputs, and a resolver. During a regular scene sample, Animate first samples ordinary spatial and camera animation, then resolves spatial relations inside each view3d, then renders the spatial viewport, and finally resolves projected labels as ordinary two-dimensional Visuals. This order means a label may use the resolved position of a relation result, while ordinary 2D layout can still use the label.
The three dependency declarations make a resolver’s inputs inspectable:
procedure
(spatial-dependency? value) → boolean?
value : any/c
procedure
(spatial-visual-dependency target) → spatial-dependency?
target : spatial-path?
procedure
(spatial-value-dependency target) → spatial-dependency?
target : symbol?
procedure
(spatial-camera-dependency view-id) → spatial-dependency?
view-id : symbol?
procedure
(spatial-relation template [ #:depends-on dependencies #:structure structure #:cache-key cache-key] resolver) → spatial-relation? template : spatial-visual? dependencies : (listof spatial-dependency?) = null structure : (or/c 'root-only 'fixed) = 'root-only cache-key : any/c = #f resolver : procedure?
A 'root-only relation may change all of its internal structure and can only be addressed at its own root path. A 'fixed relation must return the template’s exact child-ID tree, so a descendant is addressable. Generic resolver procedures are intentionally not cross-process-cacheable; supply a stable #:cache-key to declare an explicit cache identity.
procedure
(spatial-relation? value) → boolean?
value : any/c
procedure
(spatial-relation-dependencies relation)
→ (listof spatial-dependency?) relation : spatial-relation?
procedure
(spatial-relation-structure relation)
→ (or/c 'root-only 'fixed) relation : spatial-relation?
procedure
(spatial-relation-cacheability relation)
→ (or/c 'explicit-key 'disabled) relation : spatial-relation?
procedure
(spatial-relation-context? value) → boolean?
value : any/c
procedure
(spatial-relation-context-spatial-ref context target) → spatial-visual? context : spatial-relation-context? target : spatial-path?
procedure
(spatial-relation-context-spatial-world-transform context target) → affine3? context : spatial-relation-context? target : spatial-path?
procedure
(spatial-relation-context-spatial-position context target) → vec3? context : spatial-relation-context? target : spatial-path?
procedure
(spatial-relation-context-value-ref context target) → any/c context : spatial-relation-context? target : symbol?
procedure
(spatial-relation-context-camera context) → camera3d?
context : spatial-relation-context?
An undeclared access is an authoring error. Relations resolve lazily with one cache for the sampled viewport; a cycle reports complete paths rooted at the owning view, such as '(world links ab).
The initial relation vocabulary is intentionally small:
procedure
(segment-between3d from to #:id id [ #:color color #:width width #:opacity opacity]) → spatial-relation? from : spatial-path? to : spatial-path? id : symbol? color : any/c = "slategray" width : positive-real? = 2 opacity : real? = 1
procedure
(line-between3d from to #:id id [ #:padding padding #:color color #:width width #:opacity opacity]) → spatial-relation? from : spatial-path? to : spatial-path? id : symbol? padding : nonnegative-real? = 10 color : any/c = "slategray" width : positive-real? = 2 opacity : real? = 1
procedure
(arrow-between3d from to #:id id [ #:color color #:width width #:tip-size tip-size #:opacity opacity]) → spatial-relation? from : spatial-path? to : spatial-path? id : symbol? color : any/c = "slategray" width : positive-real? = 2 tip-size : positive-real? = 1/4 opacity : real? = 1
procedure
(plane-through3d first second third #:id id [ #:color color #:opacity opacity]) → spatial-relation? first : spatial-path? second : spatial-path? third : spatial-path? id : symbol? color : any/c = "lightskyblue" opacity : real? = 1
procedure
(normal-at3d target normal #:id id [ #:length length #:color color #:width width #:tip-size tip-size #:opacity opacity]) → spatial-relation? target : spatial-path? normal : vec3? id : symbol? length : positive-real? = 1 color : any/c = "darkmagenta" width : positive-real? = 2 tip-size : positive-real? = 1/4 opacity : real? = 1
procedure
(distance-segment3d from to #:id id [ #:color color #:width width #:opacity opacity]) → spatial-relation? from : spatial-path? to : spatial-path? id : symbol? color : any/c = "darkgoldenrod" width : positive-real? = 2 opacity : real? = 1
procedure
(projected-label template #:view view-id #:target target [ #:offset offset #:occlusion occlusion]) → projected-label? template : visual? view-id : symbol? target : (or/c vec3? spatial-path?) offset : vec2? = origin
occlusion : (or/c 'always-visible 'hide 'fade) = 'always-visible
procedure
(projected-label? value) → boolean?
value : any/c
procedure
(follow-projected-point template #:view view-id #:point point [ #:offset offset #:occlusion occlusion]) → projected-label? template : visual? view-id : symbol? point : vec3? offset : vec2? = origin
occlusion : (or/c 'always-visible 'hide 'fade) = 'always-visible
procedure
(follow-projected-spatial template #:view view-id #:target target [ #:offset offset #:occlusion occlusion]) → projected-label? template : visual? view-id : symbol? target : spatial-path? offset : vec2? = origin
occlusion : (or/c 'always-visible 'hide 'fade) = 'always-visible
The canonical example uses all of this without a second timeline:
(define label-a (follow-projected-spatial (math-tex #:id 'label-a "A") #:view 'world #:target '(tetrahedron A) #:offset (vec2 -20 -18))) (scene-play (scene-add (make-scene) world label-a) (rotate3d-by '(world tetrahedron) (axis-angle y-axis3 pi)) (camera3d-orbit-by 'world #:azimuth pi) #:duration 2)
For the complete moving tetrahedron with labels A–D, see "examples/3d/projected-labels.rkt".
18.12 Spatial curves and vector diagrams
SCENE-3D-F added finite spatial diagram geometry; SCENE-3D-O separates a sampled curve’s centreline from its rendering style. A mathematical stroke3d is resolved after projection and can retain a constant pixel width. A tube-style3d creates explicit physical tube geometry whose apparent width changes with the camera. Partial curves, reveals, and curve-following are sampled directly from the complete immutable centreline at the requested scene time; they never use a mutable updater or the preceding frame.
procedure
(stroke3d [ #:color color #:width width #:width-mode width-mode #:cap cap #:join join #:miter-limit miter-limit #:dash dash #:dash-offset dash-offset #:dash-space dash-space #:opacity opacity #:depth-mode depth-mode #:depth-bias depth-bias]) → stroke3d? color : color-spec? = "steelblue" width : positive? = 2 width-mode : (or/c 'screen 'world) = 'screen cap : (or/c 'butt 'square 'round) = 'round join : (or/c 'miter 'bevel 'round) = 'round miter-limit : positive? = 4 dash : (or/c #f list? vector?) = #f dash-offset : finite-real? = 0 dash-space : (or/c 'screen 'world) = width-mode opacity : (real-in 0 1) = 1 depth-mode : (or/c 'test 'always 'hidden) = 'test depth-bias : nonnegative-real? = 1e-5
procedure
(stroke3d-color style) → color-spec?
style : stroke3d?
procedure
(stroke3d-width style) → positive?
style : stroke3d?
procedure
(stroke3d-width-mode style) → (or/c 'screen 'world)
style : stroke3d?
procedure
(stroke3d-cap style) → (or/c 'butt 'square 'round)
style : stroke3d?
procedure
(stroke3d-join style) → (or/c 'miter 'bevel 'round)
style : stroke3d?
procedure
(stroke3d-miter-limit style) → positive?
style : stroke3d?
procedure
(stroke3d-dash style) → (or/c #f vector?)
style : stroke3d?
procedure
(stroke3d-dash-offset style) → finite-real?
style : stroke3d?
procedure
(stroke3d-dash-space style) → (or/c 'screen 'world)
style : stroke3d?
procedure
(stroke3d-opacity style) → (real-in 0 1)
style : stroke3d?
procedure
(stroke3d-depth-mode style) → (or/c 'test 'always 'hidden)
style : stroke3d?
procedure
(stroke3d-depth-bias style) → nonnegative-real?
style : stroke3d?
procedure
(stroke3d-with-color style color) → stroke3d?
style : stroke3d? color : color-spec?
procedure
(stroke3d-with-opacity style opacity) → stroke3d?
style : stroke3d? opacity : (real-in 0 1)
procedure
(tube-style3d [ #:radius radius #:sides sides #:color color]) → tube-style3d? radius : positive? = 1/20 sides : exact-positive-integer? = 8 color : color-spec? = "steelblue"
procedure
(tube-style3d? value) → boolean?
value : any/c
procedure
(tube-style3d-radius style) → positive?
style : tube-style3d?
procedure
(tube-style3d-sides style) → exact-positive-integer?
style : tube-style3d?
procedure
(tube-style3d-color style) → color-spec?
style : tube-style3d?
procedure
(point-style3d [ #:size size #:size-mode size-mode #:color color #:opacity opacity #:depth-mode depth-mode #:depth-bias depth-bias]) → point-style3d? size : positive? = 8 size-mode : (or/c 'screen 'world) = 'screen color : color-spec? = "cornflowerblue" opacity : (real-in 0 1) = 1 depth-mode : (or/c 'test 'always 'hidden) = 'test depth-bias : nonnegative-real? = 1e-5
procedure
(point-style3d? value) → boolean?
value : any/c
procedure
(arrow-style3d [ #:length length #:length-mode length-mode #:width width #:color color #:opacity opacity #:depth-mode depth-mode #:depth-bias depth-bias]) → arrow-style3d? length : positive? = 12 length-mode : (or/c 'screen 'world) = 'screen width : (or/c #f positive?) = #f color : color-spec? = "tomato" opacity : (real-in 0 1) = 1 depth-mode : (or/c 'test 'always 'hidden) = 'test depth-bias : nonnegative-real? = 1e-5
procedure
(arrow-style3d? value) → boolean?
value : any/c
procedure
(point3d position #:id id [#:style style]) → spatial-visual?
position : vec3? id : symbol? style : point-style3d? = (point-style3d)
procedure
(polyline3d points #:id id [ #:style style #:closed? closed? #:transform transform #:opacity opacity]) → curve3d? points : (or/c list? vector?) id : symbol? style : (or/c stroke3d? tube-style3d?) = (stroke3d) closed? : boolean? = #f transform : transform3? = identity-transform3 opacity : (real-in 0 1) = 1
procedure
(parametric-curve3d procedure [ #:range range #:samples samples] #:id id [ #:style style #:closed? closed?]) → curve3d? procedure : procedure? range : (list/c finite-real? finite-real?) = (list 0 1) samples : exact-positive-integer? = 64 id : symbol? style : (or/c stroke3d? tube-style3d?) = (stroke3d) closed? : boolean? = #f
procedure
(tube3d points #:id id [ #:radius radius #:sides sides #:closed? closed?] #:width-mode width-mode) → mesh3d? points : (or/c list? vector?) id : symbol? radius : positive? = 1/20 sides : exact-positive-integer? = 8 closed? : boolean? = #f width-mode : 'world
procedure
(arrow3d from to #:id id [ #:shaft-style shaft-style #:tip-style tip-style]) → group3d? from : vec3? to : vec3? id : symbol? shaft-style : stroke3d? = (stroke3d #:color "tomato") tip-style : arrow-style3d? = (arrow-style3d #:color "tomato")
procedure
(double-arrow3d from to #:id id [ #:shaft-style shaft-style #:tip-style tip-style]) → group3d? from : vec3? to : vec3? id : symbol? shaft-style : stroke3d? = (stroke3d #:color "tomato") tip-style : arrow-style3d? = (arrow-style3d #:color "tomato")
procedure
(with-edges3d mesh [ #:edges edges #:visible visible #:hidden hidden #:crease-angle crease-angle #:surface surface]) → edge-overlay3d? mesh : mesh3d?
edges : (or/c 'explicit 'all 'boundary 'crease 'silhouette 'feature) = 'feature
visible : (or/c #f stroke3d?) = (stroke3d #:color "black" #:width 2) hidden : (or/c #f stroke3d?) = #f crease-angle : finite-real? = (/ pi 6) surface : (or/c 'visible 'depth-only 'none) = 'visible
procedure
(edge-style3d? value) → boolean?
value : any/c
procedure
(edge-overlay3d? value) → boolean?
value : any/c
procedure
(axes3d #:id id [ #:x-range x-range #:y-range y-range #:z-range z-range]) → group3d? id : symbol? x-range : list? = (list -3 3) y-range : list? = (list -3 3) z-range : list? = (list -3 3)
procedure
(coordinate-plane3d plane #:id id) → mesh3d?
plane : (or/c 'xy 'xz 'yz) id : symbol?
procedure
(grid-plane3d plane #:id id) → group3d?
plane : (or/c 'xy 'xz 'yz) id : symbol?
procedure
(basis-vectors3d #:id id) → group3d?
id : symbol?
procedure
(vector-arrow3d vector #:id id) → group3d?
vector : vec3? id : symbol?
procedure
(vector-components3d vector #:id id) → group3d?
vector : vec3? id : symbol?
procedure
(move-along-curve3d target curve [ #:start start #:end end]) → any/c target : spatial-path? curve : spatial-path? start : finite-real? = 0 end : finite-real? = 1
procedure
(orient-along-curve3d target curve [ #:start start #:end end]) → any/c target : spatial-path? curve : spatial-path? start : finite-real? = 0 end : finite-real? = 1
For an already-present curve path, (create '(world curve)), (uncreate '(world curve)), and (show-passing-flash '(world curve)) use the same direct curve sampling. A passing flash adds a temporary coloured tube sliver over its unchanged source curve; it disappears at each clip endpoint. The canonical vector-camera-orbit example is "examples/3d/vector-components.rkt".
18.13 Parametric surfaces and calculus
SCENE-3D-G adds fixed-topology rectangular parametric surfaces. A surface is sampled once at inclusive parameter-grid sites and therefore has stable vertex and triangle identities. Its normals use declared analytic derivatives when both are supplied; otherwise they use deterministic centred/one-sided finite differences, adjacent-face fallback, and explicit unresolved-index reporting.
procedure
(parametric-surface3d procedure [ #:u-range u-range #:v-range v-range #:resolution resolution] #:id id) → surface3d? procedure : procedure? u-range : list? = (list -1 1) v-range : list? = (list -1 1) resolution : list? = (list 33 33) id : symbol?
procedure
(function-surface3d function [ #:x-range x-range #:y-range y-range #:resolution resolution] #:id id) → surface3d? function : procedure? x-range : list? = (list -1 1) y-range : list? = (list -1 1) resolution : list? = (list 33 33) id : symbol?
procedure
(surface3d? value) → boolean?
value : any/c
procedure
(surface3d-resolution surface) → list?
surface : surface3d?
procedure
(surface3d-position-at surface u v) → vec3?
surface : surface3d? u : finite-real? v : finite-real?
procedure
(surface3d-normal-at surface u v) → vec3?
surface : surface3d? u : finite-real? v : finite-real?
procedure
(surface-color surface color) → surface3d?
surface : surface3d? color : color-spec?
procedure
(surface-color-by-height surface) → surface3d?
surface : surface3d?
procedure
(surface-color-by-scalar surface scalar) → surface3d?
surface : surface3d? scalar : procedure?
procedure
(surface-checkerboard surface) → surface3d?
surface : surface3d?
procedure
(surface-point surface u v #:id id) → mesh3d?
surface : surface3d? u : finite-real? v : finite-real? id : symbol?
procedure
(surface-tangent-u surface u v #:id id) → group3d?
surface : surface3d? u : finite-real? v : finite-real? id : symbol?
procedure
(surface-tangent-v surface u v #:id id) → group3d?
surface : surface3d? u : finite-real? v : finite-real? id : symbol?
procedure
(surface-normal surface u v #:id id) → group3d?
surface : surface3d? u : finite-real? v : finite-real? id : symbol?
procedure
(surface-tangent-plane surface u v #:id id) → mesh3d?
surface : surface3d? u : finite-real? v : finite-real? id : symbol?
procedure
(surface-coordinate-curve surface #:id id) → curve3d?
surface : surface3d? id : symbol?
procedure
(surface-gradient-arrow surface x y #:id id) → group3d?
surface : surface3d? x : finite-real? y : finite-real? id : symbol?
18.14 Adaptive, trimmed, and implicit surfaces
SCENE-3D-Q adds three producers which all lower to the same immutable indexed surface record returned by surface3d-mesh. The record preserves vertex and triangle provenance as well as a topology key, while renderer caches remain outside authored values.
adaptive-parametric-surface3d accepts the same parameterization shape as parametric-surface3d, but samples it on a deterministic dyadic quadtree. Its position, normal-angle, and maximum-edge-length tolerances are camera independent. Neighbouring cells are conformed before lowering, so their shared edge vertices are identical rather than merely close.
trimmed-parametric-surface3d accepts signed surface-trim fields in parameter space. It clips the retained adaptive triangles and shares edge/trim intersections by canonical keys. Consequently surface3d-domain-contains? and surface3d-position-at? can distinguish a point in the original parameter box from one outside its trim.
implicit-surface3d samples a finite scalar field within a declared axis-aligned box and extracts one level set with deterministic marching tetrahedra. It shares lattice-edge intersections, estimates normals from central field differences, and records whether the surface touches the box boundary. The initial extractor is a fixed-resolution algorithm; it does not yet adapt its 3D cells. view3d-surface-pick uses the ordinary CPU BVH hit and attaches the retained triangle provenance: barycentrically interpolated (vector u v) parameters for parametric surfaces and the source cube/ tetrahedron record for implicit ones. It does not claim a separate analytic implicit intersection solver.
procedure
(reveal-surface-u target) → any/c
target : spatial-path?
procedure
(reveal-surface-v target) → any/c
target : spatial-path?
procedure
(transform-surface3d target destination) → any/c
target : spatial-path? destination : surface3d?
The canonical acceptance scene is "examples/3d/tangent-plane.rkt".
18.15 Constructive solids
SCENE-3D-H adds deterministic indexed solid meshes. The standard constructors and regular-polyhedron constructors return ordinary mesh3d values. Their tessellation parameters are part of the authored immutable value, not a renderer decision.
procedure
side : finite-real? id : symbol?
procedure
width : finite-real? height : finite-real? depth : finite-real? id : symbol?
procedure
sides : exact-positive-integer? id : symbol?
procedure
radius : finite-real? id : symbol?
procedure
(cylinder3d radius height #:id id) → mesh3d?
radius : finite-real? height : finite-real? id : symbol?
procedure
radius : finite-real? height : finite-real? id : symbol?
procedure
major-radius : finite-real? minor-radius : finite-real? id : symbol?
procedure
(extrude3d contour #:id id #:vector direction) → mesh3d? contour : (listof vec2?) id : symbol? direction : vec3?
procedure
(mesh3d-smooth-normals mesh) → mesh3d?
mesh : mesh3d?
procedure
(mesh3d-flat-normals mesh) → mesh3d?
mesh : mesh3d?
procedure
(mesh3d-boundary-edges mesh) → vector?
mesh : mesh3d?
The canonical acceptance scene is "examples/3d/solid-of-revolution.rkt".
18.16 Clipping, sections, and transparency
SCENE-3D-I distinguishes a render instruction from a geometric operation. clip3d keeps its source subtree intact and clips only the triangles submitted to a view3d renderer. slice-mesh3d instead returns new half-space mesh geometry; it deliberately does not invent a cap. Use section-by-plane3d for the actual plane intersection and section-curve3d to draw every loop or open chain as ordinary spatial curves.
procedure
(clip-plane3d plane [#:keep keep]) → clip-plane3d?
plane : plane3? keep : (or/c 'positive 'negative) = 'positive
procedure
(clip-plane3d? value) → boolean?
value : any/c
procedure
content : spatial-visual? clip : (or/c plane3? clip-plane3d?) id : symbol?
procedure
(slice-mesh3d mesh clip) → mesh3d?
mesh : mesh3d? clip : (or/c plane3? clip-plane3d?)
procedure
(section-by-plane3d mesh clip) → section3d?
mesh : mesh3d? clip : (or/c plane3? clip-plane3d?)
procedure
(section3d? value) → boolean?
value : any/c
procedure
(section3d-loops section) → (listof (listof vec3?))
section : section3d?
procedure
(section3d-chains section) → (listof (listof vec3?))
section : section3d?
procedure
(section-curve3d mesh clip #:id id) → group3d?
mesh : mesh3d? clip : (or/c plane3? clip-plane3d?) id : symbol?
material3d accepts an alpha-bearing semantic colour. In an opaque view3d, fully opaque geometry writes the depth buffer first; transparent geometry is then rendered far-to-near with 'object-sorted or 'triangle-sorted #:transparency-mode. Transparent triangles depth-test against opaque geometry but do not write depth. A projected-label accepts #:occlusion 'always-visible, 'hide, or 'fade; its occlusion test uses that opaque depth target, preserving the label as a crisp 2D Visual.
18.17 Cuts, caps, and section measurements
SCENE-3D-R extends a plane section with an explicit local numerical policy, plane basis, and component records. cut-mesh3d returns both clipped halves plus their shared section3d and optional separate cap meshes; it does not mutate the source mesh. section3d-area, section3d-centroid, and section3d-perimeter operate on the same preserved section topology. clip-planes3d and clip-box3d build ordered render-only half-space sequences.
section-fill3d exposes a separate cap-style mesh for a section; section-hatch3d creates deterministic even/odd stroke intervals in the plane-local basis, including empty intervals for nested holes. slice-stack3d retains stable section-group paths as planes advance along a normal. prepare-cross-section-function3d makes an immutable table of sections, areas, centroids, and diagnostics, which volume-by-slices3d evaluates with a declared midpoint, trapezoid, or Simpson rule. The sampling convention is part of the table and is checked by the chosen numerical rule. riemann-volume3d provides a separate midpoint-column construction for graph-volume explanations; each row and cell is an ordinary stable spatial child. washer-sum3d creates stable midpoint annular slabs about the x axis, and shell-sum3d creates stable midpoint cylindrical shells about the z axis. These are explanatory geometry groups; numerical volume estimation remains explicit.
The cap triangulator handles simple concave, hole-free section loops with deterministic ear clipping. Nested loops (cap holes) remain explicitly rejected rather than silently filling the wrong region. Multi-plane render clipping is semantic and ordered, but the optional OpenGL backend has not yet received its corresponding multi-plane uniform path. See "examples/3d/capped-cube-cutaway.rkt".
procedure
(riemann-volume3d function [ #:x-range x-range #:y-range y-range #:resolution resolution #:base base] #:id id) → group3d? function : procedure? x-range : list? = (list -1 1) y-range : list? = (list -1 1) resolution : list? = (list 8 8) base : finite-real? = 0 id : symbol?
procedure
(washer-sum3d outer inner [ #:x-range x-range #:count count] #:id id) → group3d? outer : procedure? inner : procedure? x-range : list? = (list -1 1) count : exact-positive-integer? = 8 id : symbol?
procedure
(shell-sum3d height [ #:radius-range radius-range #:count count #:base base] #:id id) → group3d? height : procedure? radius-range : list? = (list 0 1) count : exact-positive-integer? = 8 base : finite-real? = 0 id : symbol?
18.18 Spatial anchors and label layout
SCENE-3D-S begins the annotation layer with immutable anchor3d? descriptors. vertex-anchor3d, edge-anchor3d, face-anchor3d, curve-anchor3d, surface-anchor3d, and bounds/origin anchors resolve after every spatial transformation into a resolved-anchor3d world point, normal/tangent when available, source path, and stable provenance identity. A regular parametric surface anchor also exposes its evaluated normal and u-tangent after its complete world transform. Generated adaptive/trimmed frames remain a separately documented provenance-boundary limitation.
label3d uses such an anchor while retaining its content as a crisp ordinary 2D Visual. label-placement3d and layout-labels3d provide a deterministic, pure direct-mode candidate layout in output pixels. prepare-label-layout3d optionally computes an immutable dynamic-programming candidate table for a declared finite frame grid, applying explicit movement and switching penalties without relying on the previously displayed frame. Equal-priority labels retain declaration order, and equal-cost candidates retain the declared preferred-direction order. 2D leader rendering, mathematical dimensions, and textured/camera-facing billboards remain later work; they are intentionally not simulated by mutable callbacks. The executable anchor probe is "examples/3d/anchor-aware-labels.rkt".
The canonical acceptance scene is "examples/3d/sphere-plane-section.rkt".
18.19 Spatial maps and homotopies
SCENE-3D-J adds map requests to the ordinary immutable scene timeline. Every target is a rooted spatial path, and every map procedure is authored in world coordinates. This makes a map applied to a nested child mean the same thing as applying it to an equivalent top-level child. A surrounding parent map must therefore be invertible when the result is rebased into that parent’s local coordinate system.
apply-linear3 and apply-affine3 retain the original spatial subtree and attach a full affine map to it. They consequently preserve the indexed topology exactly, even for a shear, reflection, or singular map. A named child of a transformed group3d remains addressable. The canonical linear-transformation-diagram3d groups coordinate planes, a unit cube, basis arrows, and an arbitrary vector so one map applies to all of them coherently.
(scene-play (scene-add (make-scene) world) (apply-linear3 '(world diagram) (linear3 1 0 1 0 1 0 0 0 1)) (apply-homotopy3 '(world sheet) (lambda (point phase) (vec3 (vec3-x point) (* (cos phase) (vec3-y point)) (* (sin phase) (vec3-y point))))) #:duration 2)
procedure
(linear-transformation-diagram3d #:id id [ #:vector vector #:cube-side cube-side #:plane-size plane-size]) → group3d? id : symbol? vector : vec3? = (vec3 3/2 1 1/2) cube-side : positive-real? = 1 plane-size : positive-real? = 3
procedure
(apply-linear3 path map) → any/c
path : spatial-path? map : linear3?
procedure
(apply-affine3 path map) → any/c
path : spatial-path? map : affine3?
procedure
(apply-pointwise3 path map-point [ #:on-failure on-failure #:recompute-normals? recompute-normals?]) → any/c path : spatial-path? map-point : procedure? on-failure : (or/c 'error 'drop-triangle) = 'error recompute-normals? : boolean? = #t
procedure
(apply-homotopy3 path homotopy [ #:on-failure on-failure #:recompute-normals? recompute-normals?]) → any/c path : spatial-path? homotopy : procedure? on-failure : (or/c 'error 'drop-triangle) = 'error recompute-normals? : boolean? = #t
The canonical acceptance scene is "examples/3d/spatial-maps-and-homotopies.rkt".
18.20 Prepared spatial ODE trajectories and vector fields
SCENE-3D-K adds direct-time flow geometry without a mutable per-frame updater. prepare-ode-trajectory3d records an immutable numerical path once; subsequent position lookup accepts any supported time in any order. A field accepts either (field x y z) or (field time x y z) and must return exactly one finite vec3. The fixed default is checkpointed RK4. With adaptive-rk45, accepted Dormand–Prince nodes and endpoint derivatives are stored, so lookup uses cubic Hermite dense output and never calls the author field.
flow-particle3d is a semantic spatial relation. Before an image or preview worker resolves it, Animate samples its requested phase values into an immutable table. Thus worker rendering reads positions and tangents only; it does not evaluate the field procedure. Direct lookup of a fixed RK4 trajectory may still take the bounded suffix after its nearest checkpoint.
(define lorenz-path (prepare-ode-trajectory3d (lambda (x y z) (vec3 (* 10 (- y x)) (- (* x (- 28 z)) y) (- (* x y) (* 8/3 z)))) (vec3 0 1 21/20) #:time-range (cons 0 20) #:solver (adaptive-rk45 #:relative-tolerance 1e-6))) (define phase (parameter 'time 0)) (flow-particle3d lorenz-path phase #:id 'particle #:tangent-length 1)
procedure
(prepare-ode-trajectory3d field seed #:time-range time-range [ #:step-size step-size #:checkpoint-every checkpoint-every #:solver solver]) → ode-trajectory3d?
field :
(or/c (procedure-arity-includes/c 3) (procedure-arity-includes/c 4)) seed : vec3? time-range : (cons/c finite-real? finite-real?) step-size : (and/c finite-real? positive?) = 1/20 checkpoint-every : exact-positive-integer? = 16 solver : (or/c false/c adaptive-rk45?) = #f
procedure
(ode-trajectory3d? value) → boolean?
value : any/c
procedure
(ode-trajectory3d-position trajectory time) → vec3?
trajectory : ode-trajectory3d? time : finite-real?
procedure
(ode-trajectory3d-time-range trajectory)
→ (cons/c finite-real? finite-real?) trajectory : ode-trajectory3d?
procedure
(ode-trajectory3d-step-size trajectory)
→ (or/c positive? false/c) trajectory : ode-trajectory3d?
procedure
(ode-trajectory3d-checkpoint-every trajectory)
→ (or/c exact-positive-integer? false/c) trajectory : ode-trajectory3d?
procedure
(ode-trajectory3d-solver trajectory) → any/c
trajectory : ode-trajectory3d?
procedure
(ode-trajectory3d-diagnostics trajectory) → any/c
trajectory : ode-trajectory3d?
procedure
(vector-field3d field #:id id [ #:x-range x-range #:y-range y-range #:z-range z-range #:x-count x-count #:y-count y-count #:z-count z-count #:normalize? normalize? #:length-range length-range #:color-by-magnitude? color-by-magnitude? #:seed-order seed-order]) → group3d?
field :
(or/c (procedure-arity-includes/c 3) (procedure-arity-includes/c 4)) id : symbol? x-range : list? = (list -2 2) y-range : list? = (list -2 2) z-range : list? = (list -2 2) x-count : exact-positive-integer? = 5 y-count : exact-positive-integer? = 5 z-count : exact-positive-integer? = 5 normalize? : boolean? = #f length-range : (or/c false/c list? pair?) = #f color-by-magnitude? : boolean? = #f seed-order : symbol? = 'xyz
procedure
(streamline3d field seed #:id id) → curve3d?
field : procedure? seed : vec3? id : symbol?
procedure
(streamlines3d field seeds #:id id) → group3d?
field : procedure? seeds : (listof vec3?) id : symbol?
procedure
(flow-particle3d trajectory phase #:id id [ #:tangent-length tangent-length]) → spatial-relation? trajectory : ode-trajectory3d? phase : scene-parameter? id : symbol? tangent-length : (or/c false/c positive?) = #f
procedure
(flow-cloud3d trajectories phase #:id id) → group3d?
trajectories : (listof ode-trajectory3d?) phase : scene-parameter? id : symbol?
The canonical acceptance scene is "examples/3d/prepared-lorenz-flow.rkt".
18.21 Spatial inspection and exact picking
SCENE-3D-L exposes the spatial hierarchy that an already sampled view3d submits to its renderer. Inspection is immutable query data; it never adds a wireframe, selection flag, acceleration structure, or other hidden state to an authored Scene. The preview uses the same query data after a viewport click and paints its AABB, exact triangle, normal, local frame, and ray-pixel marker only after the cached bitmap has been drawn.
struct
(struct spatial-inspection ( path kind local-transform world-transform local-bounds world-bounds material triangle-count vertex-count camera-position view-position projected-position view-depth metadata) #:transparent) path : (listof symbol?) kind : symbol? local-transform : transform3? world-transform : affine3? local-bounds : aabb3? world-bounds : aabb3? material : any/c triangle-count : exact-nonnegative-integer? vertex-count : exact-nonnegative-integer? camera-position : vec3? view-position : (or/c #f vec3?) projected-position : (or/c #f vec2?) view-depth : (or/c #f nonnegative-real?) metadata : immutable-hash?
struct
(struct spatial-pick ( inspection path triangle-index point distance barycentric normal ray metadata) #:transparent) inspection : spatial-inspection? path : (listof symbol?) triangle-index : (or/c #f exact-nonnegative-integer?) point : vec3? distance : nonnegative-real? barycentric : vec3? normal : vec3? ray : ray3? metadata : immutable-hash?
struct
(struct surface-pick3d ( spatial-pick surface-kind parameter trim-boundary source-cell interpolated-normal) #:transparent) spatial-pick : spatial-pick? surface-kind : symbol? parameter : (or/c #f vector?) trim-boundary : any/c source-cell : any/c interpolated-normal : (or/c #f vec3?)
procedure
(spatial-pick-kind pick)
→ (or/c 'mesh-triangle 'stroke-segment 'point-marker 'arrow-marker) pick : spatial-pick?
procedure
(view3d-spatial-inspections view)
→ (listof spatial-inspection?) view : view3d?
procedure
→ (listof spatial-inspection?) view : view3d?
procedure
(view3d-spatial-inspection-at view path)
→ (or/c #f spatial-inspection?) view : view3d? path : (listof symbol?)
procedure
(view3d-pick view ray) → (or/c #f spatial-pick?)
view : view3d? ray : ray3?
procedure
(view3d-surface-pick view ray) → (or/c #f surface-pick3d?)
view : view3d? ray : ray3?
procedure
(view3d-pixel-pick view pixel-x pixel-y #:width width #:height height) → (or/c #f spatial-pick?) view : view3d? pixel-x : finite-real? pixel-y : finite-real? width : exact-positive-integer? height : exact-positive-integer?
procedure
(mesh3d-bvh mesh) → mesh3d-bvh?
mesh : mesh3d?
procedure
(mesh3d-bvh? value) → boolean?
value : any/c
procedure
(bvh3d-node? value) → boolean?
value : any/c
procedure
(bvh3d-leaf? value) → boolean?
value : any/c
procedure
(bvh3d-bounds tree) → aabb3?
tree : mesh3d-bvh?
procedure
(bvh3d-triangle-indices tree)
→ (listof exact-nonnegative-integer?) tree : mesh3d-bvh?
procedure
(bvh3d-ray-candidates tree ray)
→ (listof exact-nonnegative-integer?) tree : mesh3d-bvh? ray : ray3?
The canonical preview probe is "examples/3d/spatial-inspector-picking.rkt". Open it with animate/preview, click a visible facet, then use the Animate → 3D selection menu to copy the spatial path, hit point, or normal; its scratch action also supplies a clipping plane. Focusing the inspection camera changes only the preview override, never the authored camera or timeline.
18.22 Retained renderer backends
SCENE-3D-N keeps animate/3d pure and places effectful implementation choice in animate/3d/render. SCENE-3D-O extends that compiled view with ordered, renderer-neutral centreline strokes and screen markers. A backend receives an immutable render3d-request containing a camera-independent compiled view plus a frame specification, may retain geometry and preparation resources that it owns, and returns copied ARGB bytes. Thus changing a backend, releasing its cache, or recovering from a failed optional native renderer cannot mutate a view3d or any of its spatial children.
| (require animate/3d/render) | package: animate |
procedure
(renderer3d? value) → boolean?
value : any/c
procedure
(renderer3d-id renderer) → symbol?
renderer : renderer3d?
procedure
(renderer3d-capabilities renderer) → renderer3d-capability-set?
renderer : renderer3d?
procedure
(renderer3d-fingerprint renderer request) → any/c
renderer : renderer3d? request : render3d-request?
procedure
(renderer3d-prepare renderer request) → any/c
renderer : renderer3d? request : render3d-request?
procedure
(renderer3d-render renderer preparation request) → renderer3d-render-result? renderer : renderer3d? preparation : any/c request : render3d-request?
procedure
(renderer3d-release renderer) → void?
renderer : renderer3d?
struct
(struct renderer3d-capability-set ( wireframe opaque-triangles perspective orthographic depth-buffer flat-shading smooth-shading transparency clipping-planes) #:transparent) wireframe : boolean? opaque-triangles : boolean? perspective : boolean? orthographic : boolean? depth-buffer : boolean? flat-shading : boolean? smooth-shading : boolean? transparency : boolean? clipping-planes : boolean?
struct
(struct compiled-geometry3d ( key mesh local-bounds face-normals edge-adjacency analysis) #:transparent) key : any/c mesh : mesh3d? local-bounds : aabb3? face-normals : vector? edge-adjacency : vector? analysis : mesh3d-analysis?
struct
(struct compiled-instance3d ( path geometry-key world-transform normal-transform material opacity clip-planes drawing-index surface-mode) #:transparent) path : (listof symbol?) geometry-key : any/c world-transform : affine3? normal-transform : linear3? material : material3d? opacity : real? clip-planes : list? drawing-index : exact-nonnegative-integer? surface-mode : (or/c 'visible 'depth-only 'none)
struct
(struct compiled-stroke3d ( path points closed? world-transform style opacity clip-planes drawing-index source-kind source-metadata) #:transparent) path : (listof symbol?) points : vector? closed? : boolean? world-transform : affine3? style : stroke3d? opacity : real? clip-planes : list? drawing-index : exact-nonnegative-integer? source-kind : symbol? source-metadata : immutable-hash?
struct
(struct compiled-point-marker3d ( path position world-transform style opacity clip-planes drawing-index) #:transparent) path : (listof symbol?) position : vec3? world-transform : affine3? style : point-style3d? opacity : real? clip-planes : list? drawing-index : exact-nonnegative-integer?
struct
(struct compiled-arrow-marker3d ( path from to world-transform style opacity clip-planes drawing-index) #:transparent) path : (listof symbol?) from : vec3? to : vec3? world-transform : affine3? style : arrow-style3d? opacity : real? clip-planes : list? drawing-index : exact-nonnegative-integer?
struct
(struct compiled-edge-overlay3d ( path geometry-key world-transform normal-transform style opacity clip-planes drawing-index) #:transparent) path : (listof symbol?) geometry-key : any/c world-transform : affine3? normal-transform : linear3? style : edge-style3d? opacity : real? clip-planes : list? drawing-index : exact-nonnegative-integer?
struct
(struct compiled-view3d ( geometries instances strokes point-markers arrow-markers edge-overlays background render-mode transparency-mode) #:transparent) geometries : vector? instances : vector? strokes : vector? point-markers : vector? arrow-markers : vector? edge-overlays : vector? background : any/c render-mode : symbol? transparency-mode : symbol?
struct
(struct frame3d-spec (camera lights width height) #:transparent) camera : camera3d? lights : list? width : exact-positive-integer? height : exact-positive-integer?
procedure
(compile-view3d view) → compiled-view3d?
view : view3d?
procedure
(compiled-view3d-primitives view) → vector?
view : compiled-view3d?
procedure
(view3d->frame3d-spec view width height) → frame3d-spec?
view : view3d? width : exact-positive-integer? height : exact-positive-integer?
procedure
(view3d->render3d-request view width height [ #:cancellation-token cancellation-token]) → render3d-request? view : view3d? width : exact-positive-integer? height : exact-positive-integer? cancellation-token : any/c = #f
struct
(struct render3d-request ( compiled-view frame-spec cancellation-token) #:transparent) compiled-view : compiled-view3d? frame-spec : frame3d-spec? cancellation-token : any/c
struct
(struct renderer3d-render-result ( width height argb-bytes diagnostics) #:transparent) width : exact-positive-integer? height : exact-positive-integer? argb-bytes : bytes? diagnostics : any/c
procedure
(renderer3d-render-result->bitmap result) → bitmap?
result : renderer3d-render-result?
procedure
procedure
(retained-software-renderer3d [#:capacity capacity])
→ renderer3d? capacity : exact-positive-integer? = 32
parameter
(current-view3d-renderer3d renderer) → void? renderer : renderer3d?
procedure
(retained-software-renderer3d-cache-hits renderer)
→ exact-nonnegative-integer? renderer : renderer3d?
procedure
(retained-software-renderer3d-cache-misses renderer)
→ exact-nonnegative-integer? renderer : renderer3d?
procedure
(retained-software-renderer3d-cache-size renderer)
→ exact-nonnegative-integer? renderer : renderer3d?
struct
(struct renderer3d-statistics ( spatial-compilations geometry-fingerprints geometry-cache-hits geometry-cache-misses geometry-cache-bytes instance-count source-triangle-count clipped-triangle-count raster-triangle-count pixel-count bitmap-conversion-count preparation-milliseconds raster-milliseconds readback-milliseconds) #:transparent) spatial-compilations : exact-nonnegative-integer? geometry-fingerprints : exact-nonnegative-integer? geometry-cache-hits : exact-nonnegative-integer? geometry-cache-misses : exact-nonnegative-integer? geometry-cache-bytes : integer? instance-count : exact-nonnegative-integer? source-triangle-count : exact-nonnegative-integer? clipped-triangle-count : exact-nonnegative-integer? raster-triangle-count : exact-nonnegative-integer? pixel-count : exact-nonnegative-integer? bitmap-conversion-count : exact-nonnegative-integer? preparation-milliseconds : real? raster-milliseconds : real? readback-milliseconds : real?
procedure
(renderer3d-statistics-reset! renderer) → void?
renderer : renderer3d?
procedure
(renderer3d-statistics-snapshot renderer)
→ renderer3d-statistics? renderer : renderer3d?
18.23 Optional Racket/OpenGL backend
| (require animate/3d/opengl) | package: animate |
SCENE-3D-P adds an explicit GPU implementation of the same renderer3d protocol. Requiring this module is the opt-in boundary for opengl and racket/gui/base; neither animate, animate/3d, nor animate/3d/render loads it. The renderer owns a hidden canvas only to obtain a context, renders each view3d to an owned RGBA8/depth framebuffer, reads it back, flips the rows, and returns the ordinary top-down straight-alpha ARGB result used by the Pict compositor.
procedure
(opengl-renderer3d-spec [ #:samples samples #:cache-megabytes cache-megabytes #:fallback fallback]) → opengl-renderer3d-spec? samples : exact-positive-integer? = 4 cache-megabytes : exact-positive-integer? = 512 fallback : (or/c 'error 'software) = 'error
procedure
(opengl-renderer3d-spec? value) → boolean?
value : any/c
procedure
(opengl-renderer3d [spec]) → renderer3d?
spec : opengl-renderer3d-spec? = (opengl-renderer3d-spec)
procedure
(opengl-renderer3d? value) → boolean?
value : any/c
procedure
procedure
(opengl-renderer3d-info renderer) → immutable-hash?
renderer : opengl-renderer3d?
procedure
(opengl-renderer3d-statistics renderer) → immutable-hash?
renderer : opengl-renderer3d?
procedure
(opengl-renderer3d-reset-statistics! renderer) → void?
renderer : opengl-renderer3d?
procedure
(opengl-renderer3d-release! renderer) → void?
renderer : opengl-renderer3d?
For final project output, use an explicit declaration:
(render-spec #:renderer3d (opengl-renderer3d-spec #:samples 4 #:cache-megabytes 512 #:fallback 'error) #:workers 1)
An OpenGL project must run in Racket 9.3 gracket; a plain racket process produces an actionable project diagnostic instead of selecting software implicitly. Project preview inherits this declaration and owns the retained renderer for the lifetime of its window. Camera motion changes uniforms and a viewport-size change only reallocates the FBO; neither reuploads immutable geometry.
OpenGL limitations: The first backend has one serialized context and therefore requires #:workers 1; it does not create threaded GPU workers. It uses FBO readback rather than direct OpenGL preview-canvas composition. There is no GPU picking, textures, shadows, specular/roughness lighting, persistent mapped buffers, PBO pipelining, compute/geometry shaders, or order-independent transparency. The software backend remains the portable default and conformance reference. Compare GPU/software pixels by tolerance: opaque interiors, antialiased edges, and transparent regions need different thresholds and must not be expected to be bit-identical.
The reference/retained conformance tests compare projected output at exact endpoints and in nonmonotonic camera-frame order. The canonical probe is "examples/3d/retained-renderer.rkt"; evaluating (retained-renderer-summary) there demonstrates a cache hit without changing the visible scene.
The repository tool "tools/run-3d-probes.rkt" renders the canonical visual probes for any visible stage from 3D-B through 3D-P. For example, gracket tools/run-3d-probes.rkt –stage 3D-P –renderer opengl –output rendered-examples/3d-p-opengl writes frame PNGs plus manifest.rktd and a diagnostics.rktd file per probe. The manifest records Animate and Racket versions, renderer ID, output dimensions, sample times, sampled 3D cameras, renderer-fingerprint digests, compiled geometry keys/counts, frame hashes, and for O, compiled stroke/marker/outline counts and requested screen/world width modes. –compare-renderers software,opengl writes side-by-side software and OpenGL trees, per-frame absolute-difference PNGs, and channel-difference metrics in comparison.rktd. Probe images are human review evidence; semantic and small-raster tests remain the correctness oracle. "tools/benchmark-3d.rkt" measures the same ten named workloads through either backend. Its OpenGL run reports first context/shader/allocation work separately from warm frames, plus retained geometry, framebuffer, readback, and renderer counters. It has no CI timing threshold; the acceptance checks are zero geometry uploads for warm camera/object-transform work and zero framebuffer reallocations at an unchanged viewport.
Current limitation: Screen strokes use deterministic software coverage, not analytic antialiasing. Curve centreline detail is limited by authored samples. Hidden-line classification uses opaque and 'depth-only surfaces only: transparent surfaces are not reliable hidden-line occluders. Screen points and arrowheads are camera-facing marks, not lit mesh spheres or cones. Surface topology is a fixed rectangular grid. Solid construction currently supports only simple single contours (no holes or self-intersections), and revolution accepts only nonnegative-radius profiles around a cardinal axis. There is no adaptive tessellation, trimmed domain, texture mapping, arbitrary implicit surface, or cap generation for arbitrary sliced meshes. Transparent intersections are not order-independent: triangle sorting is a useful deterministic approximation, not OIT. Section joining does not repair pathological nonmanifold meshes. Projected labels are crisp 2D overlays and may overlap; only opaque depth is considered for their hide/fade policy. Linear and affine map requests do not resample geometry; singular maps use a deterministic authored-normal shading fallback. Pointwise and homotopy maps currently accept only an unwrapped mesh3d, not curves, surfaces, or arbitrary containers. They use the source’s fixed authored vertices without adaptive remeshing, so non-injective maps can create degenerate or self-intersecting triangles. 'drop-triangle leaves open holes and does not cap or repair them; it is deliberately not the default. Turning off normal recomputation preserves source normals and can make nonlinear shading misleading. Spatial ODE fields require finite vec3 results. Vector fields and streamlines have finite explicit author samples; they do not offer adaptive field-line topology, event detection, or adaptive stopping. There is no 3D ODE source inspector. Spatial picking accelerates indexed mesh triangles (including generated tube/surface meshes) with object bounds and a local BVH, and uses the same prepared screen footprints for strokes and markers. It does not yet expose texture UVs, interpolate supplied vertex normals for a pick, support analytic implicit geometry, or perform a GPU-backed selection pass. Preview overlays and selection scratch values are diagnostic-only and are intentionally absent from normal frame/video renders. The retained software backend caches immutable geometry separately from the reference renderer’s camera-space preparations; a camera or viewport change may therefore miss that software preparation cache. The optional OpenGL backend retains immutable geometry separately and uses tolerant, not bit-exact, image comparison.