What Is the Role of the Scene Graph in ArmorPaint?

The scene graph in ArmorPaint serves as the central hierarchical data structure that stores all 3D objects, cameras, materials, and environmental resources, enabling transform propagation, rendering coordination, scene editing, and project serialization.

ArmorPaint relies on Armory3D's core engine to manage its 3D painting environment through a robust scene graph architecture. Defined primarily in base/sources/engine.h, this system organizes every drawable element into a tree structure that drives real-time rendering and editing operations. Understanding the scene_t implementation reveals how the software maintains coherent spatial relationships and resource management during texture painting workflows.

Core Architecture of the Scene Graph

At the heart of ArmorPaint's engine lies the scene_t structure, which acts as the root container for all scene data.

The scene_t Container

According to the armory3d/armorpaint source code, the scene_t structure (located in base/sources/engine.h) maintains arrays of every asset type within a project:

  • Objects (obj_t[]) – Each entry represents a node in the hierarchy
  • Meshes, cameras, materials, shaders – Referenced resources attached to objects
  • World data – Environmental and lighting information

Hierarchical Object Relationships

Individual scene nodes are represented by object_t structures that contain a transform_t component, a parent reference, and a list of children. These links form a tree hierarchy where child objects inherit their parent's coordinate space. The engine provides dedicated functions such as object_set_parent() and object_get_child() to manipulate these relationships programmatically.

Key Responsibilities of the Scene Graph

The hierarchical structure enables five critical architectural functions throughout the application.

Transform Propagation

Child objects automatically inherit parent transformations through the transform_update() and transform_build_matrix() functions. When object_set_parent() re-links an object into a new branch of the tree, calling transform_update() recalculates the world matrix for that node and its descendants. This ensures that moving a parent container (like an empty group) correctly repositions all attached meshes without requiring manual updates to each child.

Rendering Order and Culling

The main render loop in paint/sources/viewport.c traverses scene->objects to submit geometry to the GPU. Before drawing, the engine applies frustum culling to eliminate objects outside the camera's view, improving performance in complex scenes. The active camera reference (scene_camera) and world data (scene_world) are singletons stored directly in the scene structure, ensuring consistent rendering parameters across all objects.

Scene Editing and Tool Integration

UI tools and editing features interact with the graph through helper functions like scene_get_child(".Gizmo") or scene_spawn_object(".Sphere", NULL, true). These calls retrieve existing nodes or instantiate new ones within the hierarchy, allowing gizmos, selection outlines, and procedural primitives to coexist with imported models. The naming convention using dot-prefixed strings (e.g., .Gizmo) identifies special engine objects versus user content.

Resource Sharing

The scene graph centralizes shared resources through reference counting. The camera_ref and world_ref fields point to active environment data, while materials and shaders reside in arrays accessible to all objects. This design allows multiple mesh objects to reference the same material instance while maintaining independent transforms.

Serialization and Import/Export

When saving projects, util_encode_scene() in paint/sources/util/util_encode.c serializes the entire graph structure to disk. Import plugins for GLTF (io_gltf/cgltf.h) and FBX (io_fbx/ufbx/ufbx.h) parse external files into temporary scene_t structures before merging them into the active scene, preserving the original hierarchy from 3D modeling software.

Practical Examples

The following C patterns demonstrate common scene graph operations in ArmorPaint's codebase.

Finding and manipulating the transform gizmo:

object_t *gizmo = scene_get_child(".Gizmo");
if (gizmo) {
    transform_set_matrix(gizmo->transform, mat4_identity());
}

Re-parenting objects to establish new hierarchical relationships:

object_t *child = scene_get_child(".Sphere");
object_t *new_parent = scene_get_child(".Plane");
if (child && new_parent) {
    object_set_parent(child, new_parent);
    transform_update(child->transform);
}

Iterating over all objects for game logic or culling:

for (i32 i = 0; i < scene->objects->length; ++i) {
    object_t *obj = ((object_t **)scene->objects->buffer)[i];
    /* Perform frustum culling or UI highlighting */
}

Spawning new mesh objects at runtime:

object_t *new_obj = scene_spawn_object(".Sphere", NULL, true);
if (new_obj) {
    new_obj->material = find_material("Default");
    transform_set_matrix(new_obj->transform, mat4_translation(vec3(0, 0, -2)));
}

Summary

  • The scene_t structure in base/sources/engine.h acts as the root container for all 3D data in ArmorPaint, organizing objects into a hierarchical tree via object_t nodes.
  • Transform propagation relies on parent-child links updated through object_set_parent() and transform_update(), ensuring inherited coordinate spaces.
  • The rendering pipeline in paint/sources/viewport.c traverses the object array to submit meshes to the GPU while applying frustum culling for performance optimization.
  • Editing tools utilize scene_get_child() and scene_spawn_object() to manipulate the graph directly, supporting gizmos, selection, and procedural generation.
  • Serialization through util_encode.c and import plugins (GLTF/FBX) enables persistent project storage and interoperability with external 3D applications.

Frequently Asked Questions

What data structure defines the scene graph in ArmorPaint?

The scene_t structure defined in base/sources/engine.h serves as the primary container. It holds arrays of objects, meshes, cameras, materials, and world data, along with references to the active camera (scene_camera) and environment (scene_world).

How does ArmorPaint handle parent-child transform relationships?

Each object_t maintains a transform_t component and pointers to its parent and children. When object_set_parent() modifies the hierarchy, transform_update() recalculates the object's world matrix by combining its local transform with the parent's transformation, propagating changes down the tree.

Where does the engine traverse the scene graph during rendering?

The main rendering loop resides in paint/sources/viewport.c, where the code iterates over scene->objects to process each object_t. Before submission to the GPU, the engine performs frustum culling against the active scene_camera to determine visibility.

Can ArmorPaint import scene graphs from external 3D formats?

Yes. Import plugins located in paint/plugins/io_gltf/ and paint/plugins/io_fbx/ parse external files into temporary scene_t structures. These plugins preserve hierarchical relationships from the source files, allowing complex models with existing parent-child groupings to be used directly within ArmorPaint's painting environment.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →