DatumHueEnterprise v0.0.24datumhue.com
Contents

handle22 methods, 20 fields

SceneNode

A node (mesh / light / model) within a 3D scene.

Obtained from
datumhue.physics.raycast, datumhue.physics.raycast_all, datumhue.physics.point_query, datumhue.physics.shapecast, Scene:cube, Scene:sphere, Scene:plane, Scene:cylinder and 16 more
Accepted by
datumhue.physics.add_joint, datumhue.physics.raycast, datumhue.physics.raycast_all, datumhue.physics.point_query, datumhue.physics.shapecast, Scene:group, Scene:camera_relative_yaw, Scene:camera_yaw_bucket and 6 more

The examples on this page start from:

lua
local vec3 = datumhue.math.vec3
local scene = datumhue.scene.new({ width = 320, height = 240 })
scene:mount({ width = 320, height = 240 })
scene:directional_light({ direction = vec3(-1, -2, -1) })
local node = scene:cube({ pos = vec3(0, 0.5, 0) })

Fields 20

FieldTypeAccessDescription
velocityVec3?read/writeLinear velocity (assign a vec2/vec3), or nil if the handle has no physics body.
angular_velocityVec3?read/writeAngular velocity per axis (radians/sec), or nil if the handle has no physics body. Assign a vec3.
posVec3?read/writePosition relative to the parent (assign a vec2/vec3); a physics body follows at the next physics step. nil once removed.
rotationQuat?read/writeRotation quaternion relative to the parent, or nil once the node is removed. Assign a quat; a physics body follows at the next physics step.
restitutionnumber?read/writeBounciness, 0 or more (0 absorbs an impact, 1 keeps all its energy); nil if the handle has no physics body. Assign a number to change it at runtime.
frictionnumber?read/writeSurface friction coefficient; nil if the handle has no physics body. Assign a number to change it at runtime.
ccdbooleanread/writeWhether this body, when moving fast, also sweeps moving (kinematic and dynamic) bodies so it never tunnels through them; fast bodies always sweep static geometry. Assign a boolean to toggle.
visiblebooleanread/writeWhether the node is shown. Setting false hides it and every node parented under it; true defers to the parent's visibility.
parentSceneNode?read/writeThe node this node is parented to (its transform composes with the parent's), or nil if it sits at the scene root. Assign a node to reparent (the local transform is kept, and a physics body moves with the node), or nil to detach; reparenting across scenes, or under the node itself or one of its descendants, raises.
billboardscene.BillboardModeread/writeCamera-facing mode. While not none, turning to face the camera sets the node's rotation every frame, replacing any rotation assigned to it, and a physics body on the sprite keeps its own.
size_modescene.SpriteSizeModeread/writeWhether size is world units or constant on-screen pixels. In pixels, a physics body on the sprite keeps its unscaled collider.
sizeVec2read/writeQuad extent (units per size_mode).
pivotVec2read/writeNormalized anchor in 0..1; the node position sits at this point.
alphascene.SpriteAlphaModeread/writeTransparency handling.
cutoffnumberread/writeAlpha-test threshold for the cutout mode (0..1).
colorColorread/writeBase color. On sprites, a tint multiplied with the texture; on mesh primitives, the material's base color. Writing it changes this node alone: a node sharing a material takes its own copy, which later edits to the shared material no longer reach.
flip_xbooleanread/writeMirror horizontally.
flip_ybooleanread/writeMirror vertically.
shadedbooleanread/writeLit by scene lights when true; full-bright (unlit) when false.
cellintegerread/writeCurrent atlas cell index (0-based, row-major). Raises if the sprite draws a whole image.

Methods 22

SceneNode:update

method
SceneNode:update(opts: scene.NodeUpdateOptions)

Update node transform / material / light properties.

Example
-- Only the keys given change; the others keep their values.
node:update({
    pos = vec3(2, 0.5, 0),
    color = datumhue.color("#e67e80"),
})
print(node.pos) --> vec3(2.0, 0.5, 0.0)

opts scene.NodeUpdateOptions options 11 keys, 3 inherited Omitted keys keep their current value.

KeyTypeDescription
colorColorA light's color, or a mesh's base color: a standard material of this color in place of the mesh's material or shader.
materialMaterial | ShaderMaterial handle to apply: a standard Material, or a Shader, whose color the mesh shows unlit (a Material's shader lights it).
intensitynumberLight intensity.
rangenumberLight falloff range.
shadowsbooleanCast shadows.
directionVec3Light direction.
inner_anglenumberSpot inner cone half-angle in radians.
outer_anglenumberSpot outer cone half-angle in radians.
From scene.Transform3dOptions
posVec3World-space position.
rotationQuatRotation quaternion. Build with datumhue.math.quat.from_euler etc.
scaleVec3Per-axis scale.

SceneNode:remove

method
SceneNode:remove()

Remove this node.

SceneNode:animate

method
SceneNode:animate(opts: scene.AnimateOptions)

Tween the node's transform (position, rotation, scale), color, and opacity over duration seconds with the given easing curve.

Example
-- Rise 2 units and turn half way round, over 1.5 seconds.
node:animate({
    pos = vec3(0, 2.5, 0),
    rotation = datumhue.math.quat.from_rotation_y(math.pi),
    duration = 1.5,
    easing = "sine_in_out",
    looping = true,
})

opts scene.AnimateOptions options 8 keys

KeyTypeDefaultDescription
posVec3the position is not tweenedTarget world-space position.
rotationQuatthe rotation is not tweenedTarget rotation quaternion. Build with datumhue.math.quat.from_euler etc.
scaleVec3the scale is not tweenedTarget per-axis scale.
colorColorthe color is not tweenedTarget color, blended perceptually (Oklab, like Color:lerp). The node gets its own copy of its material, so other nodes sharing it are unaffected; a light tweens its light color instead. Raises on a node that shows a shader unlit, whose colors are its uniforms.
opacitynumberthe opacity is not tweenedTarget alpha 0.0-1.0 for the material's base color. The node's material copy switches to alpha blending if it was opaque. Raises on a node that shows a shader unlit.
durationnumber1Tween duration in seconds.
easingEasing"linear"Easing curve.
loopingbooleanfalseLoop the tween.

SceneNode:play_animation

method
SceneNode:play_animation(opts: scene.PlayAnimationOptions)

Play one of the model's animation clips, crossfading from whatever is currently playing. Only meaningful on nodes created with scene:model.

Example
local asset = datumhue.package:dir():read("robot.glb"):scene()
local robot = scene:model({ asset = asset })
-- Crossfade from the current clip over 0.3 seconds.
robot:play_animation({ clip = "Animation1", fade = 0.3, speed = 1.5 })

opts scene.PlayAnimationOptions options 4 keys

KeyTypeDefaultDescription
clipstring"Animation0"Animation clip label, e.g. Animation0, Animation1.
speednumber1Playback speed multiplier.
loopingbooleantrueLoop the clip.
fadenumber0Seconds to crossfade from the currently-playing clip; 0 is an instant cut.

SceneNode:stop_animation

method
SceneNode:stop_animation()

Stop all animation playback on the node, freezing it at the current pose.

SceneNode:blend

method
SceneNode:blend(clips: scene.BlendClip[])

Play several of the model's clips at once at the given weights (a blend tree), replacing whatever was playing. Each clip loops. Only meaningful on nodes created with scene:model.

Example
local asset = datumhue.package:dir():read("robot.glb"):scene()
local robot = scene:model({ asset = asset })
-- Weights are relative: these play at 75 % and 25 %.
robot:blend({
    { clip = "Animation0", weight = 3 },
    { clip = "Animation1", weight = 1 },
})

clips scene.BlendClip options 2 keys

KeyTypeDefaultDescription
clip requiredstringno defaultAnimation clip label, e.g. Animation0.
weight requirednumberno defaultRelative blend weight (normalized across the set).

SceneNode:bounds

method
SceneNode:bounds() -> scene.MeshBounds?

The mesh's local-space bounding box (intrinsic dimensions, before the node's transform). nil for nodes without a direct mesh (loaded models, lights, groups).

returns scene.MeshBounds record 4 keys

KeyTypeDescription
minVec3Minimum corner.
maxVec3Maximum corner.
sizeVec3Extent along each axis (max - min).
centerVec3Center of the box.

SceneNode:mesh

method
SceneNode:mesh() -> Mesh

This node's mutable mesh. Only primitive mesh nodes (cube, sphere, plane, ...) qualify; raises for voxel volumes, glTF models, lights, cameras, and groups.

SceneNode:deform

method
SceneNode:deform(opts: scene.DeformOptions) -> Deformer

Attach a deformer to this node's mesh — it displaces the mesh every frame without running any Lua. One deformer per node: a second call changes the kind and the parameters it gives, and keeps the original base geometry. mesh:update with new positions re-bases an attached deformer. scene:raycast hits the displaced mesh; a physics body keeps the primitive's own shape.

Example
local water = scene:plane({ scale = vec3(6, 1, 6), subdivisions = 32 })
-- Lengths are in the plane's own units, before its scale.
local waves = water:deform({
    kind = "wave",
    amplitude = 0.1,
    frequency = 2,
    speed = 2,
    direction = datumhue.math.vec2(1, 0),
})

opts scene.DeformOptions options 7 keys

KeyTypeDefaultDescription
kind required"wave" | "ripple" | "sway" | "noise"no defaultThe deformation shape.
amplitudenumber0.1 on a new deformer; an attached one keeps its amplitudePeak displacement in local units.
frequencynumber1 on a new deformer; an attached one keeps its frequencySpatial frequency in cycles per local unit.
speednumber1 on a new deformer; an attached one keeps its speedAnimation speed in radians per second.
directionVec2+X on a new deformer; an attached one keeps its directionTravel / bend direction in the local XZ plane.
originVec2the local origin on a new deformer; an attached one keeps its originRipple center in the local XZ plane.
seedinteger0 on a new deformer; an attached one keeps its seedNoise seed; same seed, same field.

SceneNode:add_body

method
SceneNode:add_body(options?: physics.BodyOptions)

Attach a physics body. A DrawPrimitive's body simulates in the app's 2D world, a SceneNode's in its 3D world. See physics.BodyOptions for the shape + material fields. With no shape, the collider fits what the handle draws, measured when add_body runs: a draw primitive's drawing (a circle for a round one, a text's laid-out block, a group's children combined), or a scene node's mesh (a plane is a thin slab under it, a torus the disc it spans, a billboard sprite an upright cylinder standing on its pivot, anything else a unit box). A draw primitive's named shape also takes omitted sizes from its drawing; a scene node's takes width, height, and depth 1 and radius 0.5. The collider, unless built from points, is centered on the drawing; refit_body measures again. Sizes and points are in the handle's own units: the collider scales and mirrors with the handle and every group or node above it, except that a sprite sized in pixels keeps its unscaled collider. A size or point, or a collider reaching, past 1000000 units raises. The body follows whatever moves, turns, or scales the handle or anything above it at the next physics step: a static or kinematic body goes where its handle goes; a dynamic body simulates on its own and jumps, keeping its velocity, when your code moves it or something above it. Replacing a body keeps its joints. Raises, keeping any previous body, for a text span or a voxel volume, a negative or non-finite size, a non-finite point, a triangle naming a point that does not exist, or a world other than the jointed body's own.

Example
local floor =
    scene:cube({ pos = vec3(0, -0.5, 0), scale = vec3(10, 1, 10) })
floor:add_body({ type = "static" })
-- With no shape, the collider fits the mesh as drawn.
node:add_body({ restitution = 0.8 })
local ball = scene:sphere({ pos = vec3(1, 3, 0) })
ball:add_body({ shape = "sphere", radius = 0.5, mass = 2 })

options physics.BodyOptions options 18 keys

KeyTypeDefaultDescription
type"dynamic" | "static" | "kinematic""dynamic"Body dynamics.
shapephysics.Shapethe collider fits what the handle drawsCollider shape.
widthnumbertaken from what the handle draws, or 1Box width, in the handle's own units.
heightnumbertaken from what the handle draws, or 1Box, cylinder, or cone height, or a capsule's straight length between its caps, in the handle's own units.
depthnumbertaken from what the handle draws, or 1Box depth in the handle's own units, 3D only.
radiusnumbertaken from what the handle draws, or 0.5Circle, sphere, capsule, cylinder, or cone radius, in the handle's own units.
restitutionnumber0.3Bounciness, 0 or more: 0 absorbs an impact, 1 keeps all its energy.
frictionnumber0.5Surface friction, 0 or more.
massnumberderived from the collider's sizeMass override, more than 0.
sensorbooleanfalseMake the body a non-colliding trigger volume that reports the bodies overlapping it: dynamic and kinematic ones, and static ones unless the sensor is static itself.
pointsVec3[]a convex_hull, polyline or trimesh shape is a boxVertices for convex_hull / polyline / trimesh in the handle's own frame and units (vec3; vec2 accepted, z = 0).
indicesinteger[][]a trimesh shape is a boxTriangles for the trimesh shape: each three 1-based positions in points.
layersinteger[]every layerCollision membership: layer bit indices 0..=31 the body belongs to.
collides_withinteger[]every layerCollision filter: layer bit indices 0..=31 the body collides with; an empty list collides with nothing.
lock_translation"x" | "y" | "z"[]no axis beyond those constraintsAxes to lock translation on, on top of a 2D body's in-plane constraints.
lock_rotation"x" | "y" | "z"[]no axis beyond those constraintsAxes to lock rotation on, on top of a 2D body's in-plane constraints.
ccdbooleanfalseSweep this body against moving (kinematic and dynamic) bodies too when it moves fast; fast bodies always sweep static geometry so they never tunnel through it.
worldPhysicsWorldthe app's default worldSimulate in this world, made by datumhue.physics.world(), instead of the app's default one.

SceneNode:character_controller

method
SceneNode:character_controller(options?: physics.CharacterControllerOptions) -> CharacterController

Attach a kinematic character controller to this body and return its CharacterController handle. The body must be kinematic. The controller solves walking/sliding/stair-stepping geometry only — gravity, jump, input, and camera stay in your code. Lengths, absolute or relative, are 0 to 1000000. Raises if the body is missing, not kinematic, or already has a controller (remove() to rebind).

Example
-- The body must be kinematic; lengths are in scene units.
node:add_body({ type = "kinematic" })
local walker = node:character_controller({
    max_slope_climb_angle = math.rad(50),
    snap_to_ground = 0.2,
    autostep = { max_height = 0.3, min_width = 0.2 },
})

options physics.CharacterControllerOptions options 8 keys

KeyTypeDefaultDescription
upVec2 | Vec3datumhue.math.vec3(0, 1, 0)Floor-defining up direction; selects which contacts count as ground. 2D uses the XY components.
offsetnumber | physics.RelativeLength{relative = 0.01}Small gap kept between the character and surroundings for stability; must be > 0.
slidebooleantrueSlide along blocking walls instead of stopping dead.
max_slope_climb_anglenumbermath.pi / 4Steepest slope, in radians, the character will climb; steeper surfaces act as walls.
min_slope_slide_anglenumbermath.pi / 4Slope angle, in radians, at/above which the character auto-slides down.
snap_to_groundnumber | physics.RelativeLength | boolean{relative = 0.2}Distance to snap down onto ground after a move, keeping the character glued over dips; false disables.
autostepphysics.AutostepConfig | booleanfalseStep-climbing config, or false to disable; stepping is expensive. Needs a flat-bottomed body (cube or cylinder); a capsule's rounded bottom slides back off the ledge. The body also needs head room of its own height plus max_height above where it stands, so a low ceiling stops it stepping.
normal_nudge_factornumber0.0001Tiny anti-stick push along contact normals while sliding, 0 to 1.

SceneNode:apply_impulse

method
SceneNode:apply_impulse(vec: Vec2 | Vec3)

Apply an instantaneous, mass-correct velocity change of impulse / mass. Accepts vec2 or vec3.

Example
node:add_body()
-- One kick: the velocity changes by impulse / mass at once.
node:apply_impulse(vec3(0, 5, 0))

SceneNode:apply_angular_impulse

method
SceneNode:apply_angular_impulse(impulse: number | Vec3)

Apply an instantaneous, inertia-correct spin change. A number for 2D bodies (about Z), a vec3 for 3D.

Example
node:add_body()
-- A 3D body takes a vec3: a spin kick about each axis.
node:apply_angular_impulse(vec3(0, 2, 0))

SceneNode:apply_force

method
SceneNode:apply_force(vec: Vec2 | Vec3)

Set the continuous force applied every step until changed. Pass a zero vector to stop. Accepts vec2 or vec3.

Example
node:add_body()
-- Pushes every step until changed; a zero vector stops it.
node:apply_force(vec3(0, 15, 0))

SceneNode:apply_torque

method
SceneNode:apply_torque(torque: number | Vec3)

Set the continuous torque applied every step until changed. Pass zero to stop. A number for 2D bodies (about Z), a vec3 for 3D.

Example
node:add_body()
-- Turns every step until changed; a 3D body takes a vec3.
node:apply_torque(vec3(0, 1, 0))

SceneNode:lock_rotation

method
SceneNode:lock_rotation(locked: boolean)

Lock or unlock rotation.

SceneNode:set_collision_layers

method
SceneNode:set_collision_layers(options: physics.CollisionLayerOptions)

Replace this body's collision filtering at runtime.

Example
node:add_body()
-- Layer bits 0..31: belong to layer 2, collide with layers 0 and 1.
node:set_collision_layers({ layers = { 2 }, collides_with = { 0, 1 } })

options physics.CollisionLayerOptions options 2 keys

KeyTypeDefaultDescription
layersinteger[]every layerMembership layer bit indices 0..=31.
collides_withinteger[]every layerFilter layer bit indices 0..=31; an empty list collides with nothing.

SceneNode:set_locked_axes

method
SceneNode:set_locked_axes(options: physics.LockedAxesOptions)

Lock or unlock individual translation/rotation axes at runtime. The 2D in-plane constraints are always preserved.

Example
node:add_body()
-- Slide in the XZ plane only, and turn only about Y.
node:set_locked_axes({
    lock_translation = { "y" },
    lock_rotation = { "x", "z" },
})

options physics.LockedAxesOptions options 2 keys

KeyTypeDefaultDescription
lock_translation"x" | "y" | "z"[]translation is unlockedAxes to lock translation on; the 2D in-plane locks always remain.
lock_rotation"x" | "y" | "z"[]rotation is unlockedAxes to lock rotation on; the 2D in-plane locks always remain.

SceneNode:on_collision_start

method
SceneNode:on_collision_start(callback?: fun(other: SceneNode | Voxel, contact?: physics.Contact))

Set, replace, or clear (pass nil) the handler fired when this body starts colliding. contact is nil when either body is a sensor. Not fired once this body's own handle has been removed, even by an earlier collision handler in the same step; other may have been removed that way, and its handle still compares equal to one you hold.

SceneNode:on_collision_end

method
SceneNode:on_collision_end(callback?: fun(other: SceneNode | Voxel))

Set, replace, or clear (pass nil) the handler fired when this body stops colliding. Removing either body of a touching pair (its handle, or only its body, as remove_body does) ends the contact on the next physics step; other may be the removed body, and its handle still compares equal to one you hold. Not fired once this body's own handle has been removed, even by an earlier collision handler in the same step.

SceneNode:remove_body

method
SceneNode:remove_body()

Remove all physics from this handle.

SceneNode:refit_body

method
SceneNode:refit_body()

Measure what the handle draws again and resize its collider to fit, in place: velocity, joints, and contacts carry on. Sizes given to add_body stay; the ones it took from the drawing, and the collider's center, follow the drawing as it is now (a text laid out again, a group whose children moved). Raises, keeping the collider, for a handle with no body, a voxel volume (its body follows its cells), or a collider that would reach more than 1000000 units from the body.

datumhue.com

(c) 2026 Shuusei AB. Quote excerpts of this manual, with attribution to DatumHue, in your own documentation, tutorials, course materials, reviews, and other writing or video. Republishing it in whole or in substantial part needs written permission: support@datumhue.com.

Victor Mono is licensed under the SIL Open Font License 1.1
Copyright (c) 2024, Rune Bjørnerås (https://github.com/rubjo)

This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL


-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------

PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.

The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.

DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.

"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).

"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).

"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.

"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.

PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:

1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.

2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.

3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.

4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.

5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.

TERMINATION
This license becomes null and void if any of the above conditions are
not met.

DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Try

    ↑↓ selectEnter openTab filter

    Settings

    Theme
    Text
    Print

    Keyboard shortcuts

    / or Ctrl KSearch
    ↑ ↓ EnterPick a result
    TabNext result filter
    j kNext or previous entry
    [ ]Previous or next page
    yCopy a link to the current entry
    ?These shortcuts
    EscClose

    On this page