Scene
This module provides functions for interacting with scene nodes and components. It allows you to create, read, delete, and modify nodes and components within the scene.
Nodes serve as the fundamental building blocks of the scene, organizing its hierarchy and storing the position, rotation, and scale of objects. Components, on the other hand, are attached to nodes and define their behavior. For instance, a mesh component defines the geometry of a node, a camera component specifies camera properties, and a light component defines light properties.
The node hierarchy forms a tree structure where each node can have one parent and multiple children. The root node is the top-level node in the hierarchy and has no parent.
Note
Child nodes inherit their parent node’s transformation, meaning that moving the parent node will also move its child nodes.
The scene consists of nodes and components that collectively define the 3D environment. It accommodates multiple nodes and components, with nodes capable of hosting multiple attached components. The scene is used for rendering, physics simulation updates, and input event handling.
To work with the scene, you must create a new scene, add nodes and components to it, and update the scene continuously.
Note
The scene will not be rendered until a camera node is added to it.
Let’s build a simple scene with a cube rotating around the origin:
require engine.core
var parentNode : NodeId
[export]
def on_initialize() {
// add render settings with shadows and tonemapping
let renderSettings = create_render_settings()
get_component(renderSettings) $(var tonemap : Tonemap?) {
tonemap.active = true
}
// create fixed camera
add_component(create_node(NodeData(name="camera", position=float3(0, 0, -7))), new Camera())
// create light
add_component(create_node(NodeData(name="sun")), new DirectionLight())
// create the parent node for the cube at the origin
parentNode = create_node(NodeData(name="parent", position=float3(0)))
// create the cube
let cubeNode = create_node(NodeData(name="child", position=float3(-1, 0, 0), parent=parentNode))
add_component(cubeNode, new Mesh(meshId=CUBE_MESH_ID))
}
[export]
def on_update() {
// rotate the parent node. The cube is a child of the parent node,
// so it will rotate around the parent node
let dt = get_delta_time()
parentNode.localRotation *= quat4(0, 0.5 * dt, 0)
}
You can find more examples in the sample projects.
To use this module, include the following line in your project file:
require engine.scene_core // or require engine.core
Enumerations
- ChildrenPolicy
The policy to include children nodes in the bounding box calculation.
- Values:
Exclude = 0 - Exclude children nodes from the bounding box calculation.
IncludeActive = 1 - Include only active children nodes in the bounding box calculation.
IncludeAll = 2 - Include all children nodes in the bounding box calculation.
- AddChildStrategy
Enumeration for strategies to add a child node.
- Values:
KeepWorldTransform = 0 - Keeps the world transform unchanged. Suitable for 3D objects. Default strategy if not specified.
KeepLocalTransform = 1 - Keeps the local transform unchanged. Suitable for 2D/UI objects.
- BlendingMethod
Blending Methods for Animation Layers used in sample_animation_and_overlay()
Animation layers can be combined in two ways, depending on the blending method:
Overlay – The layer replaces subsequent layers (order matters).
BlendSublayer – Multiple sublayers are combined into a single layer by normalizing and scaling their weights (order within a layer does not matter).
The last sublayer should be with Overlay method, and its weight should be the weight of the whole layer, not the sublayer (for technical reasons).
See overlay methods descriptions for further details.
For example, consider this layer stack:
Top Layer (weight 50%)
Middle Layer (weight 80%)
Sublayer 1 (weight 20%)
Sublayer 2 (weight 30%)
Sublayer 3 (weight 50%)
Bottom Layer (weight 100%)
This is represented as:
BlendingMethod.Overlay, weight 0.5 // Top Layer
BlendingMethod.BlendSublayer, weight 0.2 // Middle Layer, Sublayer 1
BlendingMethod.BlendSublayer, weight 0.3 // Middle Layer, Sublayer 2
BlendingMethod.Overlay, weight 0.8 // Middle Layer, Sublayer 3, Last Sublayer
BlendingMethod.Overlay, weight 1.0 // Bottom Layer
which in the end will result in that weights:
Top Layer = 50%
Middle Layer, Sublayer 1 = 20% * 80% * (100 - 50%) = 8%
Middle Layer, Sublayer 2 = 30% * 80% * (100 - 50%) = 12%
Middle Layer, Sublayer 3 = 50% * 80% * (100 - 50%) = 20%
Bottom Layer = (100 - 50%) * (100 - 80%) = 10%
Total = 100% (always normalized).
- Values:
Overlay = 0 -
result = layerA * weight + layerB * (1 - weight). With 100% weight, the layer completely replaces all subsequent layers.BlendSublayer = 1 -
result = (Σ sublayer_i * weight_i) * layer_weight. Sublayers are combined into one layer. The last sublayer must be with Overlay method, with its weight set to the weight of the whole group (not just that sublayer). This ensures the total weight is preserved.
- AttachmentMode
The attachment mode of a scene node.
- Values:
Parent = 0x0u8 - The scene node is attached to the parent node as a child.
World = 0x1u8 - The scene node is still a child of the parent node, but is attached to the world. The local transformation of the parent node is ignored.
Structures
- NodeData
A structure for initialization of a scene instance (single scene node or prefab).
- Fields:
name : string - The name of the node
- NodeData.has_position(): bool
Checks if the position component has been explicitly set
- NodeData.has_rotation(): bool
Checks if the rotation component has been explicitly set
- NodeData.has_scale(): bool
Checks if the scale component has been explicitly set
- NodeData.has_parent(): bool
Checks if the parent component has been explicitly set
- NodeData.has_name(): bool
Checks if the name component has been explicitly set (empty string counts as an invalid name)
- Properties:
- NodeData.position: float3
- NodeData.position =(position: float3)
Local position, relative to its parent
- Arguments:
position : float3
- NodeData.rotation: quat4
Returns the current local rotation as a quaternion (quat4)
- NodeData.rotation =(rotation: quat4)
Local rotation, relative to its parent
- Arguments:
rotation : quat4
- NodeData.scale: float3
Returns the current local scale vector (float3)
- NodeData.scale =(scale: float3)
Local scale, relative to its parent
- Arguments:
scale : float3
- NodeData.parent: NodeId
- NodeData.parent =(parent: NodeId)
The parent node, can be invalid NodeId if the node does not have a parent
- Arguments:
parent : NodeId
Handled types
- NodeId
A reference (uint64) to the real scene node. It is used to uniquely identify a specific node in the scene.
NodeId is considered invalid if its initialized with NodeId(). Consider these examples:
// invalid
let node2 = NodeId()
// valid, constructed via create_node()
let node2 = create_node(NodeData(name="node2"))
So, if you want to check NodeId for validity, you can do it like this:
if (node == NodeId()) {
print("Node is invalid!")
}
Functions
get_children (nodeId: NodeId; var children: array<NodeId>) : int
add_child (nodeId: NodeId; child: NodeId; strategy: AddChildStrategy) : bool
add_child (nodeId: NodeId; child: NodeId; at: int; strategy: AddChildStrategy) : bool
find_child_recursive (nodeId: NodeId; child_name: string) : NodeId
find_children (nodeId: NodeId; child_name: string) : array<NodeId>
find_children (nodeId: NodeId; child_name: string; var res: array<NodeId>) : int
find_children_recursive (nodeId: NodeId; child_name: string) : array<NodeId>
find_children_recursive (nodeId: NodeId; child_name: string; var res: array<NodeId>) : int
find_children_recursive (child_name: string) : array<NodeId>
find_children_recursive (child_name: string; var res: array<NodeId>) : int
duplicate_node (nodeId: NodeId; data: NodeData = NodeData()) : NodeId
NodeId.attachmentMode = (nodeId: NodeId; attachment_type: AttachmentMode)
set_parent (nodeId: NodeId; parent: NodeId; strategy: AddChildStrategy)
NodeId.localPosition = (nodeId: NodeId; local_position: float3)
NodeId.localRotation = (nodeId: NodeId; local_rotation: quat4)
NodeId.worldPosition = (nodeId: NodeId; world_position: float3)
NodeId.worldRotation = (nodeId: NodeId; world_rotation: quat4)
- clear_scene()
clear all scene nodes and components
- create_node(req: NodeData = NodeData()): NodeId
creates a scene node with specified parameters
Usage example:
let node = create_node(NodeData(
name="MyNode", parent=parent_node,
position=float3(0, 4, 0), rotation=quat4(), scale=float3(0.5)
))
- Arguments:
req : NodeData
- get_children(nodeId: NodeId; children: array<NodeId>): int
Gets all children of the node, and appends them to the children array
- Arguments:
- Returns:
int - the amount of children of the node
Usage example:
var inscope children : array<NodeId>
get_children(nodeId, children)
print("{children}")
- get_children(nodeId: NodeId): array<NodeId>
Gets all children of the node, that is, nodes for which nodeId is a parent. If nodeId is zero, gets all root nodes of the scene (i.e. nodes with no parent).
Usage example:
for (child in get_children(nodeId)) {
print("{child}")
}
- NodeId.childCount(nodeId: NodeId): int
- Arguments:
nodeId : NodeId
- Returns:
int - the number of children of the node.
Usage example:
print("{nodeId.childCount}")
- get_child(nodeId: NodeId; idx: int): NodeId
- Returns:
NodeId - idx’th child of the node.
- Arguments:
nodeId : NodeId - the parent node
idx : int - the index of the child in the parent’s children list. Values < 0 are treated as an offset from the end of the children list
Usage example:
for (i in 0..nodeId.childCount) {
print("{get_child(nodeId, i)}")
}
- remove_node(nodeId: NodeId): bool
removes the scene node with all components
- Arguments:
nodeId : NodeId - the node to remove
- Returns:
bool - true if the node was removed, false if the node was not found
- add_child(nodeId: NodeId; child: NodeId): bool
adds a child node in the end of the children list
- Arguments:
- Returns:
bool - true if the child was added, false if the child wasn’t added
- add_child(nodeId: NodeId; child: NodeId; strategy: AddChildStrategy): bool
adds a child node in the end of the children list
- Arguments:
nodeId : NodeId - the parent node
child : NodeId - the node to add as a new child
strategy : AddChildStrategy - the strategy to apply when adding the child
- Returns:
bool - true if the child was added, false if the child wasn’t added
- add_child(nodeId: NodeId; child: NodeId; at: int): bool
adds a child node to the position with a specified index. If node is already a child of nodeId, it will be moved to the at position.
- Arguments:
- Returns:
bool - true if the child was added, false if the child wasn’t added
- add_child(nodeId: NodeId; child: NodeId; at: int; strategy: AddChildStrategy): bool
adds a child node to the position with a specified index
- Arguments:
nodeId : NodeId - the parent node
child : NodeId - the node to add as a new child
at : int - the index in the children list to where the child should be inserted. Values < 0 are treated as an offset from the end of the children list
strategy : AddChildStrategy - the strategy to apply when adding the child
- Returns:
bool - true if the child was added, false if the child wasn’t added
- get_child_index(nodeId: NodeId; child: NodeId): int
- Arguments:
- Returns:
int - the index of the child in the parent’s children list. If the child is not actually a child of nodeId, returns -1
- get_sibling_index(nodeId: NodeId): int
- Arguments:
nodeId : NodeId
- Returns:
int - the index of the node in its parent’s children list. Equivalent to get_child_index(node.parent, node)
- find_child(nodeId: NodeId; child_name: string): NodeId
finds the child node by name, looking only in the list of direct children of nodeId. If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
let child = find_child(nodeId, "MyChild")
- Arguments:
nodeId : NodeId
child_name : string
- find_child_recursive(nodeId: NodeId; child_name: string): NodeId
finds the child node by name, looking through all children of nodeId (including grandchildren etc.) recursively. If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
// finds the "Hand" node on any level of the hierarchy
let child = find_child_recursive(nodeId, "Hand")
// finds the "Hand" node that is a child of the "LeftArm" node
let child = find_child_recursive(nodeId, "LeftArm/Hand")
- Arguments:
nodeId : NodeId
child_name : string
- find_children(nodeId: NodeId; child_name: string): array<NodeId>
Finds all child nodes by name, looking only in the list of direct children of nodeId. If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
let children = find_children(nodeId, "MyChild")
- Arguments:
nodeId : NodeId
child_name : string
- find_children(nodeId: NodeId; child_name: string; res: array<NodeId>): int
Finds all child nodes by name, looking only in the list of direct children of nodeId. If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
var children : array<NodeId>
find_children(nodeId, "MyChild", children)
- find_children_recursive(nodeId: NodeId; child_name: string): array<NodeId>
Finds all child nodes by name, looking through all children of nodeId (including grandchildren etc.) recursively. If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
let children = find_children_recursive(nodeId, "MyChild")
- Arguments:
nodeId : NodeId
child_name : string
- find_children_recursive(nodeId: NodeId; child_name: string; res: array<NodeId>): int
Finds all child nodes by name, looking through all children of nodeId (including grandchildren etc.) recursively. If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
var children : array<NodeId>
find_children_recursive(nodeId, "MyChild", children)
- look_at(nodeId: NodeId; pos: float3; up: float3 = UP)
rotates the node to look at the position
- Arguments:
nodeId : NodeId - the node to rotate
pos : float3 - the position to look at
up : float3 - the upward direction for the new rotation
- look_at(nodeId: NodeId; target: NodeId; up: float3 = UP)
rotates the node to look at the target node
- Arguments:
- get_scene_root(): NodeId
- Returns:
NodeId - the root node of the scene, returns invalid NodeId if the scene is empty
- get_scene_roots(): array<NodeId>
- Returns:
array< NodeId > - all root nodes of the scene. Same as get_children(NodeId())
- get_scene_roots(out_nodes: array<NodeId>)
writes all root nodes of the scene to the out_nodes array. Same as get_children(NodeId(), out_nodes) :Arguments: * out_nodes : array< NodeId > - content of the array will be replaced with the root nodes
- find_child(child_name: string): NodeId
finds the child node by name, looking through all scene nodes If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
let child = find_child("MyChild")
- Arguments:
child_name : string
- find_child_recursive(child_name: string): NodeId
finds the child node by name, looking through all scene nodes If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
// finds the "Hand" node on any level of the hierarchy
let child = find_child_recursive("Hand")
- Arguments:
child_name : string
- find_children_recursive(child_name: string): array<NodeId>
Finds all child nodes by name, looking through all children of nodeId (including grandchildren etc.) recursively. If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
let children = find_children_recursive("MyChild")
- Arguments:
child_name : string
- find_children_recursive(child_name: string; res: array<NodeId>): int
Finds all child nodes by name, looking through all children of nodeId (including grandchildren etc.) recursively. If name contains a path separator ‘/’, the function will search for the child recursively
Usage example:
var children : array<NodeId>
find_children_recursive("MyChild", children)
- Arguments:
child_name : string
res : array< NodeId >
- duplicate_node(nodeId: NodeId; data: NodeData = NodeData()): NodeId
Creates a copy of the node, including all of its children and components. Note that duplication will be performed as if the target node was a prefab root, so all on_initialize functions of components will be called. You can pass NodeData to additionaly parameterize the copy, just like in create_node or instantiate_prefab functions.
- Arguments:
- Returns:
NodeId - the resulting copy of of the input node
Usage example:
let copy = duplicate_node(duplicateMe, NodeData(name = "{duplicateMe}_copy"))
- NodeId.isAlive(nodeId: NodeId): bool
- Arguments:
nodeId : NodeId
- Returns:
bool - true if nodeId is valid and is not removed from the scene. Otherwise, false
Usage example:
if (node.isAlive) {
print("Node {node.name} is alive")
}
- NodeId.isActive(nodeId: NodeId): bool
- Arguments:
nodeId : NodeId
- Returns:
bool - an active state of nodeId, not taking into account parent’s active state
When the node is not active, all components attached to the node are also disabled (e.g. meshes do not rendering, physics for the node is not calculated, etc.) and it does not receive on_update events.
- NodeId.isActive =(nodeId: NodeId; is_active: bool)
sets the node active state. It will also change the enabled state of all components attached to the node. Note that the render related components will be affected only in the next frame
- Arguments:
nodeId : NodeId
is_active : bool
- NodeId.isActiveInHierarchy(nodeId: NodeId): bool
- Arguments:
nodeId : NodeId
- Returns:
bool - true if nodeId and all its parents are active
- NodeId.attachmentMode(nodeId: NodeId): AttachmentMode
- Arguments:
nodeId : NodeId
- Returns:
AttachmentMode - the attachment mode of the node
- NodeId.attachmentMode =(nodeId: NodeId; attachment_type: AttachmentMode)
sets the attachment mode of the node
- Arguments:
nodeId : NodeId
attachment_type : AttachmentMode
- NodeId.parent(nodeId: NodeId): NodeId
- NodeId.parent =(nodeId: NodeId; parent: NodeId)
sets the parent for this node
- set_parent(nodeId: NodeId; parent: NodeId; strategy: AddChildStrategy)
sets the parent for this node
- Arguments:
nodeId : NodeId - the node to set the parent for
parent : NodeId - the new parent node
strategy : AddChildStrategy - the strategy to apply when adding the child
- set_parent(nodeId: NodeId; parent: NodeId)
sets the parent for this node, using AddChildStrategy.KeepWorldTransform
- NodeId.name(nodeId: NodeId): string
- Arguments:
nodeId : NodeId
- Returns:
string - the node name
- NodeId.name =(nodeId: NodeId; name: string)
sets the node name
- Arguments:
nodeId : NodeId
name : string
- NodeId.hierarchyName(nodeId: NodeId): string
- Arguments:
nodeId : NodeId
- Returns:
string - the node full hierarchy name, e.g. “root/parent/current”
- NodeId.localPosition(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the local position of the node
- NodeId.localPosition =(nodeId: NodeId; local_position: float3)
sets the local position of the node
- Arguments:
nodeId : NodeId
local_position : float3
- NodeId.localPosition +=(nodeId: NodeId; delta: float3)
increments the local position of the node
- Arguments:
nodeId : NodeId
delta : float3
- NodeId.localPosition -=(nodeId: NodeId; delta: float3)
decrements the local position of the node
- Arguments:
nodeId : NodeId
delta : float3
- NodeId.localRotation(nodeId: NodeId): quat4
- NodeId.localRotation =(nodeId: NodeId; local_rotation: quat4)
sets the local rotation of the node
- NodeId.localRotation *=(nodeId: NodeId; delta: quat4)
multiplies the local rotation of the node by the delta quaternion
- NodeId.localScale(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the local scale of the node
- NodeId.localScale =(nodeId: NodeId; local_scale: float3)
sets the local scale of the node
- Arguments:
nodeId : NodeId
local_scale : float3
- NodeId.worldTransform(nodeId: NodeId): float3x4
Usage example:
var particleWorldPos = node.worldTransform * particle.localPosition
- NodeId.invWorldTransform(nodeId: NodeId): float3x4
- Arguments:
nodeId : NodeId
- Returns:
float3x4 - the inverse world transform of the node (world to local)
Usage example:
var bulletLocalPos = node.invWorldTransform * bullet.worldPosition
- NodeId.localTransform(nodeId: NodeId): float3x4
- NodeId.worldPosition(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the world position of the node
- NodeId.worldPosition =(nodeId: NodeId; world_position: float3)
sets the world position of the node
- Arguments:
nodeId : NodeId
world_position : float3
- NodeId.worldPosition +=(nodeId: NodeId; delta: float3)
increments the world position of the node
- Arguments:
nodeId : NodeId
delta : float3
- NodeId.worldPosition -=(nodeId: NodeId; delta: float3)
decrements the world position of the node
- Arguments:
nodeId : NodeId
delta : float3
- NodeId.worldRotation(nodeId: NodeId): quat4
- NodeId.worldRotation =(nodeId: NodeId; world_rotation: quat4)
sets the world rotation of the node
- NodeId.worldRotation *=(nodeId: NodeId; delta: quat4)
multiplies the world rotation of the node by the delta quaternion
- NodeId.worldScale(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the world scale of the node
- NodeId.worldScale =(nodeId: NodeId; world_scale: float3)
sets the world scale of the node
- Arguments:
nodeId : NodeId
world_scale : float3
- NodeId.localRight(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the local right vector of the node
- NodeId.localUp(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the local up vector of the node
- NodeId.localForward(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the local forward vector of the node
- NodeId.worldRight(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the world right vector of the node
- NodeId.worldUp(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the world up vector of the node
- NodeId.worldForward(nodeId: NodeId): float3
- Arguments:
nodeId : NodeId
- Returns:
float3 - the world forward vector of the node
- sample_animation_and_blend(nodeId: NodeId; animations: array<AnimationId>; ratios: array<float>; weights: array<float>)
samples given animations and blends them into the node’s animator
- Arguments:
nodeId : NodeId
animations : array< AnimationId >
ratios : array<float>
weights : array<float>
- sample_animation_and_blend(nodeId: NodeId; animations: array<AnimationId>; ratios: array<float>; weights: array<float>; masks: array<AnimationMaskId>)
samples given animations and blends them into the node’s animator
- Arguments:
nodeId : NodeId
animations : array< AnimationId >
ratios : array<float>
weights : array<float>
masks : array< AnimationMaskId >
- sample_animation_and_overlay(nodeId: NodeId; animations: array<AnimationId>; ratios: array<float>; weights: array<float>; masks: array<AnimationMaskId>; blendingMethod: array<BlendingMethod>)
samples given animations and overlays them into the node’s animator
- Arguments:
nodeId : NodeId
animations : array< AnimationId >
ratios : array<float>
weights : array<float>
masks : array< AnimationMaskId >
blendingMethod : array< BlendingMethod >
- apply_aim_ik_to_animation(nodeId: NodeId; names: array<string>; target: float3; offset: float3; chain_weight: float; joint_weight: float)
samples given animations and blends them into the node’s animator
- Arguments:
nodeId : NodeId
names : array<string>
target : float3
offset : float3
chain_weight : float
joint_weight : float
- get_node_bounding_box(nodeId: NodeId; policy: ChildrenPolicy = scene_core::ChildrenPolicy.Exclude): BBox3D
Calculate the bounding box of a scene node including mesh bounding boxes and optionally the bounding boxes of the children.
- Arguments:
nodeId : NodeId - the id of the scene node
policy : ChildrenPolicy - the policy to include children nodes in the bounding box calculation
- Returns:
BBox3D - the bounding box of the node
Usage example:
let bbox = get_node_bounding_box(node, /*with children*/true)
- NodeId(): NodeId
- Returns:
NodeId - an empty NodeId object
- NodeId(id: uint64): NodeId
- Arguments:
id : uint64
- Returns:
NodeId - a constructed NodeId object from id
- NodeId(id: NodeId): NodeId
- uint64(id: NodeId): uint64
- Arguments:
id : NodeId
- Returns:
uint64 - the id of a scene node as uint64
- NodeId!(a: NodeId): bool
- Arguments:
a : NodeId
- Returns:
bool - whether the NodeId is invalid
- NodeId==(a: NodeId; b: NodeId): bool
- Arguments:
- Returns:
bool - whether two scene nodes are equal (or both invalid)
- NodeId!=(a: NodeId; b: NodeId): bool
- valid(id: NodeId): bool
- Arguments:
id : NodeId
- Returns:
bool - whether the NodeId is valid