# How Pyrite64’s Object Hierarchy and Scene Graph Structure Drive World Transforms and Parent-Child Relationships

> Discover how Pyrite64s object hierarchy and scene graph define world transforms and parent-child relationships. Understand its unique entity structure for transform calculations.

- Repository: [Max Bebök/pyrite64](https://github.com/hailtododongo/pyrite64)
- Tags: internals
- Published: 2026-02-19

---

**Pyrite64 represents every entity as a `Project::Object` containing local transform data and bidirectional parent-child pointers, calculating world transforms by recursively multiplying local matrices up the hierarchy chain.**

The object hierarchy and scene graph structure in Pyrite64 form the spatial backbone of the engine, determining how entities relate to one another and how local transformations convert into world-space coordinates. By storing raw parent pointers and shared pointer collections in every `Object`, the engine creates a directed acyclic graph where ancestor modifications automatically propagate to all descendants. This architecture, implemented across [`src/project/scene/object.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/object.h) and [`src/project/scene/scene.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.h), ensures consistent spatial calculations for rendering, physics, and editor tooling.

## Core Architecture: The Object Class and Bidirectional Links

Every element appearing in a Pyrite64 level is stored as a **`Project::Object`**. The class definition in [`src/project/scene/object.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/object.h) declares three essential local transform properties—**position**, **rotation**, and **scale**—alongside the structural links that build the scene graph:

- A raw pointer to its **parent** (`Object* parent{nullptr};`)
- A container of its **children** (`std::vector<std::shared_ptr<Object>> children{}`)

These members appear at lines 24–35 of [`src/project/scene/object.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/object.h) and establish the bidirectional relationships necessary for transform propagation.

### Local Transform Storage

Each `Object` maintains its spatial data in local coordinate space relative to its parent. The `pos`, `rot`, and `scale` properties describe where the object sits in relation to its immediate ancestor rather than the world origin. This design allows artists and developers to move parent objects without manually recalculating every child position, as the hierarchy handles the mathematical conversion automatically.

## World Transform Calculation Through the Scene Graph

The scene graph determines world transforms through recursive matrix multiplication. When the engine requires a world-space transformation—for rendering, collision detection, or script queries—it walks up the parent chain defined by the `parent` pointers.

### Recursive Matrix Multiplication

The calculation follows this deterministic rule:

```

WorldMatrix(obj) = WorldMatrix(obj.parent) * LocalMatrix(obj)

```

If `obj.parent` is `nullptr`, the recursion terminates and the world matrix equals the object's local matrix. This multiplication order ensures that child objects inherit all transformations (translation, rotation, and scaling) from their ancestors. Because the hierarchy is strictly a directed acyclic graph, the engine guarantees no circular dependencies exist that would cause infinite recursion during traversal.

### Automatic Propagation Behavior

Any modification to an ancestor's local transform immediately affects all descendants without requiring manual updates. When the editor adjusts an object's `pos`, `rot`, or `scale`—such as through the gizmo interface in [`viewport3D.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/viewport3D.cpp)—the next render pass queries the world matrix via `computeWorldMatrix`. This routine walks the `parent` chain and multiplies each local matrix, producing updated world-space coordinates for the entire subtree.

## Scene Graph Management via the Scene Class

The **`Project::Scene`** class, defined in [`src/project/scene/scene.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.h) (lines 30–48), serves as the root container and primary API for object manipulation. It maintains the top-level objects—those whose `parent` pointer remains `nullptr`—and provides the interface that the editor, build system, and runtime scripts use to modify the hierarchy.

### Root Object Container

`Scene` tracks all objects within a level, distinguishing between root nodes and nested children. Root objects represent the entry points for graph traversal, while their descendants form subtrees that move as cohesive units. This container relationship ensures that serialization and memory management operations occur at the scene level while preserving the hierarchical integrity of individual object chains.

### Object Lifecycle and UUID Assignment

When creating entities, `Scene::addObject` generates a new `Object` instance, assigns it a unique UUID, and optionally links it to a parent. The implementation sets `newObj.parent = &parent` and pushes the new object into the parent's `children` vector using `std::shared_ptr` ownership. This dual-link approach—raw pointer upward, shared pointer downward—enables efficient upward traversal for transform calculations while ensuring proper memory management for child collections.

## Practical Impact on Editor and Runtime Systems

The object hierarchy influences multiple subsystems beyond pure mathematics, affecting how data persists and how components receive spatial information.

### Serialization and Persistence

During scene saving operations (`Scene::serialize`), Pyrite64 writes the object tree using depth-first traversal, preserving parent UUID references for each child. On load (`Scene::deserialize`), the system reconstructs objects and re-establishes parent-child links by resolving these UUIDs before any world-matrix calculations occur. This serialization strategy ensures that hierarchical relationships survive between editor sessions and runtime deployments.

### Component System Integration

Components such as `compTransform`, `compCamera`, and `compLight` receive world transform data through the scene graph API rather than maintaining independent spatial calculations. When a render pass queries a camera's view matrix or a light's position, it accesses the owning `Object`'s computed world transform, guaranteeing that all subsystems operate on a consistent spatial representation derived from the same parent-child hierarchy.

## Code Example: Building a Hierarchical Object Tree

The following implementation demonstrates constructing a parent-child relationship and querying the resulting world transforms:

```cpp
// 1️⃣ Access the active scene
Project::Scene *scene = project->getScenes().getLoadedScene();

// 2️⃣ Create a root object (a "ship")
auto ship = scene->addObject(/*json descriptor*/, /*parent UUID = 0*/);

// 3️⃣ Attach a child object (a "cannon")
auto cannon = scene->addObject(/*json descriptor*/, ship->uuid);
cannon->parent = ship.get();            // establish upward link
ship->children.emplace_back(cannon);    // establish downward link

// 4️⃣ Configure local transforms
ship->pos   = {0.0f, 0.0f, 0.0f};
ship->rot   = glm::quat{1,0,0,0};
ship->scale = {1.0f, 1.0f, 1.0f};

cannon->pos   = {2.0f, 0.0f, 0.0f};   // 2 units forward from ship center
cannon->rot   = glm::quat{1,0,0,0};
cannon->scale = {0.5f, 0.5f, 0.5f};

// 5️⃣ Query world matrices for rendering
glm::mat4 shipWorld   = scene->computeWorldMatrix(*ship);
glm::mat4 cannonWorld = scene->computeWorldMatrix(*cannon); 
// cannonWorld == shipWorld * cannonLocalMatrix

```

The `computeWorldMatrix` function recursively traverses the `parent` pointer chain, multiplying each local matrix to produce the final world-space transformation. This mechanism powers both the visual representation in the editor viewport and the physics simulation calculations at runtime.

## Summary

- **Bidirectional linking**: `Project::Object` stores a raw `parent` pointer and a `std::vector<std::shared_ptr<Object>>` of children in [`src/project/scene/object.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/object.h), enabling both upward and downward graph traversal.
- **Recursive world transforms**: World matrices calculate as `WorldMatrix(parent) * LocalMatrix(object)`, automatically propagating ancestor transformations to all descendants.
- **Scene-level management**: `Project::Scene` in [`src/project/scene/scene.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.h) maintains root objects and provides `addObject`, `serialize`, and `deserialize` APIs that preserve hierarchical integrity.
- **Subsystem consistency**: Components receive world transforms through the scene graph API, ensuring rendering, physics, and editor tools share identical spatial data.

## Frequently Asked Questions

### How does Pyrite64 calculate world transforms from local coordinates?

Pyrite64 uses recursive matrix multiplication defined in the scene graph traversal logic. The engine multiplies an object's local matrix by its parent's world matrix, continuing up the chain until reaching a root node with no parent. This calculation occurs in methods like `computeWorldMatrix`, which walks the `parent` pointer chain defined in [`src/project/scene/object.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/object.h).

### What data structure does Pyrite64 use to store parent-child relationships?

Each `Object` maintains a raw pointer to its parent (`Object* parent`) and a `std::vector<std::shared_ptr<Object>>` containing its children. This dual-link structure appears in [`src/project/scene/object.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/object.h) and supports both efficient upward traversal for transform calculations and safe memory management for child collections through shared ownership.

### How does the Scene class manage object hierarchies?

`Project::Scene`, defined in [`src/project/scene/scene.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/project/scene/scene.h), tracks top-level objects (those with null parent pointers) and provides the `addObject` method for creating new entities. When adding objects, the scene handles UUID generation and establishes parent-child links by setting raw pointers and populating the `children` vector, ensuring the directed acyclic graph structure remains valid.

### How does serialization preserve the scene graph structure?

During `Scene::serialize`, Pyrite64 writes objects using depth-first traversal while recording parent UUID references. The `Scene::deserialize` method reconstructs objects and resolves these UUIDs to re-establish parent-child links before any world-matrix calculations occur. This approach ensures that hierarchical relationships persist across save and load operations.