# How the Constraint Component in Pyrite64 Enables Complex Physics Interactions and Joint Behaviors

> Discover how Pyrite64s constraint component enables complex physics interactions and joint behaviors. Achieve linked object movement without a full physics engine.

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

---

**The Constraint component in Pyrite64 acts as lightweight glue that drives scene objects to follow, copy, or maintain fixed offsets relative to other objects through a flag-driven update cycle, enabling joint-like behaviors without a full physics engine.**

Pyrite64 implements a flexible, component-based architecture for Nintendo 64 development, allowing developers to model sophisticated object relationships without the overhead of a traditional rigid-body physics engine. The **Constraint** component serves as the primary mechanism for creating these interactions, enabling one scene object to dynamically follow, mirror, or maintain a fixed spatial relationship with another object or the active camera. By storing only a reference object ID, type flag, and behavior flags, this system facilitates complex physics interactions while remaining frame-rate friendly on constrained hardware.

## Core Architecture and Update Cycle

The constraint system operates through three distinct phases defined in [`n64/engine/src/scene/components/constraint.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/n64/engine/src/scene/components/constraint.cpp). Each phase handles specific aspects of the relationship between the constrained object and its target.

### Initialization and Local Space Caching

During setup, the `Constraint::initDelete` method processes an optional `InitData` struct containing `refObjId`, `type`, and `flags`. When the type is set to `TYPE_REL_OFFSET`, the component computes the initial offset by transforming the object's world position into the reference object's local space using `refObj->intoLocalSpace(obj.pos)`. This calculation establishes a persistent local offset that remains constant regardless of how the reference object moves or rotates.

### Per-Frame Update Logic

The `Constraint::update` method executes every frame to enforce the constraint relationship. For `TYPE_COPY_OBJ`, it retrieves the referenced object via `sc.getObjectById(data->refObjId)` and selectively copies position, scale, and rotation based on the `FLAG_USE_POS`, `FLAG_USE_SCALE`, and `FLAG_USE_ROT` flags. For `TYPE_REL_OFFSET`, it converts the cached local position back to world space using `refObj->outOfLocalSpace(data->localRefPos)` and writes the result directly to `obj.pos`. This continuous update cycle creates the joint behavior, driving the constrained object to match or follow its reference.

### Camera-Following Behaviors

The `Constraint::draw` hook handles `TYPE_COPY_CAM` constraints by copying the active camera's position into the object (`obj.pos = cam.getPos()`). This approach bypasses the standard update loop, instead synchronizing the object during the draw phase, which is ideal for editor gizmos, UI elements, or head-tracking effects that must stay locked to the viewer's perspective.

## Constraint Types and Joint Behaviors

Pyrite64 supports three primary constraint types that facilitate different physics interaction patterns. Each type uses the same minimal data footprint but produces distinct behavioral outcomes through flag combinations.

### Relative Offset Joints

The `TYPE_REL_OFFSET` mode creates distance-maintaining links similar to rigid body joints. By caching the initial local offset during initialization and reapplying it each frame, the constrained object maintains a fixed spatial relationship to its reference. This enables scenarios such as a "hand" object following a "controller" with a constant offset, or attached equipment that must stay positioned relative to a character's bone structure.

### Transform Copying

`TYPE_COPY_OBJ` enables direct mirroring of transform properties. Developers can combine behavior flags to create partial constraints: use `FLAG_USE_POS` for position-only following, `FLAG_USE_ROT` for rotation mirroring (useful for wheels aligned to axles), or `FLAG_USE_SCALE` for proportional sizing. This flexibility allows complex hierarchies where child objects inherit specific transform components from parents without full matrix multiplication overhead.

## Practical Implementation Examples

The following code snippets demonstrate how to configure the **Constraint** component for common physics and joint scenarios.

**Creating a relative-offset constraint for object following:**

```cpp
// Assume `controller` and `hand` are already created Object instances.
auto &handComp = hand.addComponent<Comp::Constraint>();
handComp.type   = Comp::Constraint::TYPE_REL_OFFSET;   // keep a fixed offset
handComp.flags  = Comp::Constraint::FLAG_USE_POS;     // only position matters
handComp.refObjId = controller.id;                    // follow this object

```

In [`constraint.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/constraint.cpp), the `initDelete` routine automatically computes and stores the offset via `data->localRefPos = refObj->intoLocalSpace(obj.pos);` (lines 38–44), establishing the persistent spatial relationship.

**Copying rotation for mechanical joints:**

```cpp
auto &wheel = wheelObj.addComponent<Comp::Constraint>();
wheel.type   = Comp::Constraint::TYPE_COPY_OBJ;
wheel.flags  = Comp::Constraint::FLAG_USE_ROT;   // copy only rotation
wheel.refObjId = axleObj.id;

```

The `update` function checks flags and applies rotation when `FLAG_USE_ROT` is set (lines 68–70), creating a mechanical link where the wheel matches the axle's orientation.

**Synchronizing editor gizmos with the camera:**

```cpp
auto &gizmo = gizmoObj.addComponent<Comp::Constraint>();
gizmo.type   = Comp::Constraint::TYPE_COPY_CAM;
gizmo.flags  = Comp::Constraint::FLAG_USE_POS;   // position follows camera

```

This configuration utilizes the `draw` hook to execute `obj.pos = cam.getPos();` (lines 84–87), ensuring the gizmo remains visible relative to the viewport without affecting runtime physics.

## Design Benefits for Physics Simulations

The constraint system in Pyrite64 offers several architectural advantages for implementing complex interactions on resource-constrained platforms.

- **Minimal Data Footprint**: The component stores only integers for the reference ID and type, plus a cached `localRefPos` vector. This keeps memory usage low and updates computationally cheap, critical for maintaining 60 FPS on Nintendo 64 hardware.

- **Flag-Driven Flexibility**: By combining the `type` field with behavior flags (`FLAG_USE_POS`, `FLAG_USE_SCALE`, `FLAG_USE_ROT`), developers can create a wide spectrum of relationships—from full transform copies to single-axis constraints—without writing custom code for each variation.

- **Scene-Wide Lookup**: Constraints resolve target objects through the central `Scene` class using `getObjectById`, allowing any object to constrain any other object regardless of hierarchy or grouping. This facilitates complex physics setups where cross-group interactions are necessary, as defined in [`n64/engine/src/scene/scene.h`](https://github.com/HailToDodongo/pyrite64/blob/main/n64/engine/src/scene/scene.h).

- **Seamless Editor Integration**: The separation of camera-copy logic into the `draw` method enables editor-specific visualization tools. As seen in [`src/editor/undoRedo.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/editor/undoRedo.cpp), constraints integrate with the undo/redo system, allowing designers to manipulate joint relationships in the editor while the runtime engine maintains performance.

## Summary

- The **Constraint** component in Pyrite64 enables joint-like behaviors through a lightweight, flag-driven system that updates object transforms every frame.
- Three constraint types—`TYPE_REL_OFFSET`, `TYPE_COPY_OBJ`, and `TYPE_COPY_CAM`—provide distinct interaction models ranging from fixed offsets to camera-relative positioning.
- The system minimizes overhead by caching local space offsets during initialization (`Constraint::initDelete`) and resolving them during updates (`Constraint::update`).
- Behavior flags (`FLAG_USE_POS`, `FLAG_USE_SCALE`, `FLAG_USE_ROT`) allow granular control over which transform components are constrained.
- Source files including [`n64/engine/src/scene/components/constraint.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/n64/engine/src/scene/components/constraint.cpp), [`n64/engine/src/scene/scene.h`](https://github.com/HailToDodongo/pyrite64/blob/main/n64/engine/src/scene/scene.h), and [`src/editor/undoRedo.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/editor/undoRedo.cpp) demonstrate the integration of constraints into both runtime physics and editor workflows.

## Frequently Asked Questions

### What is the difference between TYPE_REL_OFFSET and TYPE_COPY_OBJ in Pyrite64?

`TYPE_REL_OFFSET` maintains a fixed spatial relationship by caching the initial offset in local space and reapplying it each frame, creating distance-maintaining joints. `TYPE_COPY_OBJ` directly copies transform components (position, rotation, scale) from the reference object based on behavior flags, resulting in exact mirroring rather than offset maintenance.

### How does the Constraint component handle reference objects that might not exist?

During the `update` phase, the component retrieves the reference object via `sc.getObjectById(data->refObjId)`. If the reference object has been deleted or is invalid, the lookup fails silently and the constraint skips processing for that frame, preventing crashes while allowing dynamic object lifecycles.

### Can constraints affect rotation and scale independently of position?

Yes. The component uses discrete behavior flags—`FLAG_USE_POS`, `FLAG_USE_ROT`, and `FLAG_USE_SCALE`—to determine which transform components to copy or maintain. You can create constraints that copy only rotation (for mechanical joints), only position (for camera following), or any combination thereof without affecting other properties.

### Why does TYPE_COPY_CAM use the draw hook instead of the update method?

The `TYPE_COPY_CAM` constraint synchronizes during the `draw` phase to ensure the object matches the camera's final position after all view transformations are calculated. This approach is optimal for editor gizmos and UI elements that must align with the rendered view without participating in the physics update cycle, keeping the runtime update loop lean.