Lysa  0.0
Lysa 3D Engine
Camera

Camera.ixx and Camera.cpp

Camera implements a first-person fly camera. It wires its own input and process event handlers internally, so the application code only needs to construct it and set its initial transform.

lysa::Camera struct

At the engine level a camera is just a plain data struct:

struct lysa::Camera {
float4x4 transform; // world-space view matrix (camera-to-world)
float4x4 projection; // perspective or orthographic projection
float near;
float far;
};

The renderer reads transform and projection each frame to build the view-projection matrix used for culling and shading. It is your responsibility to keep these fields up to date.

The application-level Camera class manages the lysa::Camera through a two-node SceneInstance hierarchy and updates camera.transform from pivot->globalTransform every PROCESS tick.

Node structure

The camera is built from two SceneInstance nodes:

  • attachment : holds the world-space position and the horizontal rotation (yaw). Camera::setTransform writes to this node.
  • pivot : a child of attachment that stores the vertical rotation (pitch). Its pitch is clamped to [maxCameraAngleDown, maxCameraAngleUp] ([-45°, 60°]) to prevent gimbal flip.

Separating yaw (on attachment) from pitch (on pivot) avoids the gimbal lock that occurs when both rotations are composed in a single matrix: since yaw is always applied in world space and pitch is always applied in the camera's local frame, the two degrees of freedom remain independent.

The lysa::Camera view matrix is updated from pivot->globalTransform every process tick:

camera.transform = lysa::mul(lysa::float4x4::identity(), pivot->globalTransform);

Projection setup

The projection matrix is created with lysa::perspective and re-created on window resize so the field of view is always correct for the current aspect ratio:

lysa::float4x4::identity(),
lysa::perspective(fov, window.getRenderTarget().getAspectRatio(), near, far),
near, far
};

Controls

The camera captures and releases the mouse on click. While the mouse is captured the cursor is hidden and locked to the window centre (lysa::MouseMode::HIDDEN_CAPTURED). Pressing Escape releases it.

Input Effect
Click Toggle mouse capture
W / A / S / D Move forward / left / backward / right
Q / Z Move up / down
Mouse Yaw and pitch
Arrow keys Yaw and pitch (keyboard fallback)
Escape Release mouse
Left gamepad stick Move
Right gamepad stick Look

Movement accelerates from minMovementsSpeed to maxMovementsSpeed while a key is held, and resets to zero immediately on release. The acceleration is applied in PHYSICS_PROCESS (fixed timestep) so movement speed is frame-rate independent.

Event wiring

The Camera constructor subscribes to three events and stores the returned handler IDs so they can be unregistered cleanly in the destructor:

Event Handler Role
MainLoopEvent::PHYSICS_PROCESS onPhysicsProcess Keyboard/gamepad input and movement
MainLoopEvent::PROCESS onProcess Mouse delta and final camera.transform push
RenderingWindowEvent::INPUT onInput Mouse-button capture toggle
RenderTargetEvent::RESIZED onResize Rebuild projection matrix

Storing the handler IDs (lysa::unique_id) returned by subscribe is the standard pattern for event ownership. Always call ctx().events.unsubscribe in the destructor to avoid dangling callbacks:

Camera::~Camera() {
lysa::ctx().events.unsubscribe(onPhysicsProcess);
}

Next : Loading and displaying an asset