# Configuring Camera Components for Various Perspectives and Cinematics in Pyrite64

> Learn best practices for Pyrite64 camera configuration. Master position, offset, and rotation for first-person, orbit, orthographic, and cinematic rigs using the apply method.

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

---

**Pyrite64's lightweight camera system uses position, offset, and rotation quaternions to support first-person, orbit, orthographic, and cinematic rigs through a unified `apply()` method.**

Pyrite64 is an open-source Nintendo 64-style renderer that provides a flexible camera component for building diverse gameplay perspectives and scripted sequences. Understanding how to configure the `Camera` class fields—`pos`, `posOffset`, `rot`, and `isOrtho`—allows developers to switch seamlessly between immersive 3D views and stylized 2D cinematics. The implementation in `src/renderer/camera.{h,cpp}` is deliberately minimal so it can be driven by gameplay code, editor tools, or animation scripts.

## Perspective vs Orthographic Projection

The `Camera` class toggles between projection modes using the boolean `isOrtho` field defined in [`src/renderer/camera.h`](https://github.com/HailToDodongo/pyrite64/blob/main/src/renderer/camera.h).

**Perspective mode** (default, `isOrtho = false`) builds a projection matrix via `glm::perspective` with a 70° field of view and near/far planes set to 10 and 10,000 units. This configuration is ideal for immersive 3D scenes and fly-through cinematics.

**Orthographic mode** (`isOrtho = true`) switches `apply()` to use a fixed orthographic size (`ORTHO_SIZE = 310.0f`) scaled by the current aspect ratio via `glm::ortho`. This produces distortion-free views essential for 2D overlays, map screens, or pixel-art cinematics.

Both modes share the same view matrix construction, calculating the eye position as `pos + posOffset` and looking toward `target = eye + direction`, where direction derives from the rotation quaternion applied to `WORLD_FORWARD`.

## Position and Offset Configuration

The camera rig uses two distinct vector fields to separate the focal point from the eye location:

- **`pos`** – The world-space origin of the camera rig (e.g., the player character's location).
- **`posOffset`** – The distance of the eye from `pos`, defaulting to `{0, 220, 220}` in the constructor.

For a **first-person** perspective, set `posOffset` to `{0, 0, 0}` so the eye sits exactly at the player position. For a **top-down** orthographic view, increase the Y component of `posOffset` to achieve a higher bird's-eye vantage. Changing either value and calling `apply()` immediately updates the view matrix without requiring projection recalculation.

## Rotation and Orientation Control

The `rot` quaternion orients the camera direction. The `rotateDelta()` helper in [`src/renderer/camera.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/renderer/camera.cpp) interprets mouse drag deltas as yaw (around the Y-axis) and pitch (around the X-axis), composing them as:

```cpp
glm::quat qx = glm::angleAxis(angleX, glm::vec3(0,1,0));
glm::quat qy = glm::angleAxis(angleY, glm::vec3(1,0,0));
rot = qx * rotBase * qy;

```

For **first-person** controls, apply yaw and pitch directly to `rot` while locking roll. For **orbit** behaviors, keep `pos` fixed at the target and rotate `posOffset` around it by applying the quaternion to the offset vector: `posOffset = rot * originalOffset`. Cinematic key-framing requires storing quaternions at each frame and interpolating via `glm::slerp` to produce smooth arcs.

## Movement Dynamics and Damping

The camera implements inertia-based movement through the `velocity` vector. In the `update()` method, velocity accumulates each frame and is damped by multiplying by `0.9f`, creating a natural deceleration feel for fly-throughs.

For **snappy** responsive controls, manually zero the velocity after applying movement: `velocity = glm::vec3(0)`. For **cinematic** precision, disable damping entirely or set specific velocity values per frame to control acceleration curves.

## Viewport and Screen-Space Scaling

The `screenSize` field must be updated whenever the viewport resizes (handled in [`src/editor/pages/parts/viewport3D.cpp`](https://github.com/HailToDodongo/pyrite64/blob/main/src/editor/pages/parts/viewport3D.cpp) at line 304). The `moveDelta()` helper uses these dimensions to convert mouse-dragged pixels into world units:

- **Orthographic**: Direct conversion based on orthographic height.
- **Perspective**: Conversion scales with the distance to the focal point (`dist = length(posOffset)`), ensuring consistent drag-to-move sensitivity regardless of zoom level.

## Practical Implementation Examples

### Switch to Orthographic View

```cpp
// src/renderer/camera.h
camera.isOrtho = true;

// Update viewport dimensions (src/editor/pages/parts/viewport3D.cpp)
camera.screenSize = { windowWidth, windowHeight };

// Apply before rendering (src/renderer/camera.cpp)
camera.apply(uniGlobal);

```

### Implement First-Person Mouse Look

```cpp
// Called each frame with mouse delta
void onMouseMove(glm::vec2 delta) {
    camera.rotateDelta(delta);  // src/renderer/camera.cpp
    camera.apply(uniGlobal);
}

```

### Orbit Around a Target Point

```cpp
glm::vec3 focus = selectedEntity.position;
camera.pos = focus;
camera.posOffset = glm::rotate(camera.rot, glm::vec3(0, 220, 220));
camera.apply(uniGlobal);

```

### Cinematic Key-Frame Interpolation

```cpp
float t = currentTime / totalDuration;
camera.rot = glm::slerp(keyRot[i], keyRot[i+1], t);
camera.pos = glm::mix(keyPos[i], keyPos[i+1], t);
camera.velocity = glm::vec3(0);  // Disable inertia for crisp motion
camera.apply(uniGlobal);

```

## Summary

- **Toggle projection modes** by setting `camera.isOrtho` to `true` for 2D/UI views or `false` for 3D perspective.
- **Configure rig positioning** using `pos` for the anchor point and `posOffset` for eye distance, enabling first-person, third-person, or top-down perspectives.
- **Manipulate orientation** via the `rot` quaternion and `rotateDelta()` for mouse control, or apply quaternion math to `posOffset` for orbit cameras.
- **Call `apply()`** before rendering to update `uniGlobal.cameraMat` and `uniGlobal.projMat` with the current view-projection matrix.
- **Adjust `velocity` damping** (default `0.9f`) to tune movement inertia, or zero velocity for immediate response.

## Frequently Asked Questions

### How do I switch between perspective and orthographic views in Pyrite64?

Set `camera.isOrtho = true` for orthographic projection or `false` for perspective. After toggling, ensure `camera.screenSize` reflects the current viewport dimensions, then call `camera.apply(uniGlobal)` to rebuild the projection matrix. Perspective uses a 70° FOV, while orthographic uses a fixed size of 310.0f scaled by aspect ratio.

### What is the difference between `pos` and `posOffset` in the Camera class?

`pos` represents the world-space origin of the camera rig (such as a player character), while `posOffset` defines how far the actual eye position sits from that origin. The final eye location is calculated as `pos + posOffset`. For first-person views, set `posOffset` to zero; for third-person views, use values like `{0, 220, 220}` to place the camera behind and above the target.

### How can I implement a cinematic camera fly-through?

Disable velocity damping by zeroing `camera.velocity` each frame to prevent drift. Store position and rotation key-frames as vectors and quaternions, then interpolate between them using `glm::mix` for positions and `glm::slerp` for rotations. Update `camera.pos` and `camera.rot` each frame before calling `apply()` to generate smooth cinematic motion.

### Why does my camera movement feel sluggish or too fast?

The `update()` method applies a damping factor of `0.9f` to `velocity` each frame, creating inertia. If movement feels sluggish, reduce the damping multiplier closer to `1.0f` or increase the acceleration values added to velocity. If movement feels too fast or floaty, increase the damping (lower the multiplier) or manually reset `velocity` to zero after each frame to create snap-to-position behavior.