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:
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:

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

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:
get_children(nodeId: NodeId; children: array<NodeId>): int

Gets all children of the node, and appends them to the children array

Arguments:
  • nodeId : NodeId - the parent node

  • children : array< NodeId > - the output array

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).

Arguments:
  • nodeId : NodeId - the parent node

Returns:
  • array< NodeId > - all children of the node

Usage example:

for (child in get_children(nodeId)) {
     print("{child}")
}
NodeId.childCount(nodeId: NodeId): int
Arguments:
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:
  • nodeId : NodeId - the parent node

  • child : NodeId - the node to add as a new child

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:
  • 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

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:
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)
Arguments:
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)
Arguments:
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:
  • nodeId : NodeId - the node to rotate

  • target : NodeId - the target node to look at

  • up : float3 - the upward direction for the new rotation

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:
  • nodeId : NodeId - the node to duplicate

  • data : NodeData - parameters for the new node

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:
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:
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:
Returns:
  • bool - true if nodeId and all its parents are active

NodeId.attachmentMode(nodeId: NodeId): AttachmentMode
Arguments:
Returns:
NodeId.attachmentMode =(nodeId: NodeId; attachment_type: AttachmentMode)

sets the attachment mode of the node

Arguments:
NodeId.parent(nodeId: NodeId): NodeId
Arguments:
Returns:
  • NodeId - the parent of this node

NodeId.parent =(nodeId: NodeId; parent: NodeId)

sets the parent for this node

Arguments:
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

Arguments:
  • nodeId : NodeId - the node to set the parent for

  • parent : NodeId - the new parent node

NodeId.name(nodeId: NodeId): string
Arguments:
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:
Returns:
  • string - the node full hierarchy name, e.g. “root/parent/current”

NodeId.localPosition(nodeId: NodeId): float3
Arguments:
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
Arguments:
Returns:
  • quat4 - the local rotation of the node

NodeId.localRotation =(nodeId: NodeId; local_rotation: quat4)

sets the local rotation of the node

Arguments:
NodeId.localRotation *=(nodeId: NodeId; delta: quat4)

multiplies the local rotation of the node by the delta quaternion

Arguments:
NodeId.localScale(nodeId: NodeId): float3
Arguments:
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
Arguments:
Returns:
  • float3x4 - the world transform of the node (local to world)

Usage example:

var particleWorldPos = node.worldTransform * particle.localPosition
NodeId.invWorldTransform(nodeId: NodeId): float3x4
Arguments:
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
Arguments:
Returns:
  • float3x4 - the local transform of the node

NodeId.worldPosition(nodeId: NodeId): float3
Arguments:
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
Arguments:
Returns:
  • quat4 - the world rotation of the node

NodeId.worldRotation =(nodeId: NodeId; world_rotation: quat4)

sets the world rotation of the node

Arguments:
NodeId.worldRotation *=(nodeId: NodeId; delta: quat4)

multiplies the world rotation of the node by the delta quaternion

Arguments:
NodeId.worldScale(nodeId: NodeId): float3
Arguments:
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:
Returns:
  • float3 - the local right vector of the node

NodeId.localUp(nodeId: NodeId): float3
Arguments:
Returns:
  • float3 - the local up vector of the node

NodeId.localForward(nodeId: NodeId): float3
Arguments:
Returns:
  • float3 - the local forward vector of the node

NodeId.worldRight(nodeId: NodeId): float3
Arguments:
Returns:
  • float3 - the world right vector of the node

NodeId.worldUp(nodeId: NodeId): float3
Arguments:
Returns:
  • float3 - the world up vector of the node

NodeId.worldForward(nodeId: NodeId): float3
Arguments:
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:
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:
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
Arguments:
Returns:
  • NodeId - a copy of a NodeId object (used as a copy constructor)

uint64(id: NodeId): uint64
Arguments:
Returns:
  • uint64 - the id of a scene node as uint64

NodeId!(a: NodeId): bool
Arguments:
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
Arguments:
Returns:
  • bool - whether two scene nodes are not equal

valid(id: NodeId): bool
Arguments:
Returns:
  • bool - whether the NodeId is valid