Configuring Camera Components for Various Perspectives and Cinematics in Pyrite64
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.
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 frompos, 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 interprets mouse drag deltas as yaw (around the Y-axis) and pitch (around the X-axis), composing them as:
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 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
// 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
// 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
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
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.isOrthototruefor 2D/UI views orfalsefor 3D perspective. - Configure rig positioning using
posfor the anchor point andposOffsetfor eye distance, enabling first-person, third-person, or top-down perspectives. - Manipulate orientation via the
rotquaternion androtateDelta()for mouse control, or apply quaternion math toposOffsetfor orbit cameras. - Call
apply()before rendering to updateuniGlobal.cameraMatanduniGlobal.projMatwith the current view-projection matrix. - Adjust
velocitydamping (default0.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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →