Beryllium.Camera
1.5.1
Prefix Reserved
dotnet add package Beryllium.Camera --version 1.5.1
NuGet\Install-Package Beryllium.Camera -Version 1.5.1
<PackageReference Include="Beryllium.Camera" Version="1.5.1" />
<PackageVersion Include="Beryllium.Camera" Version="1.5.1" />
<PackageReference Include="Beryllium.Camera" />
paket add Beryllium.Camera --version 1.5.1
#r "nuget: Beryllium.Camera, 1.5.1"
#:package Beryllium.Camera@1.5.1
#addin nuget:?package=Beryllium.Camera&version=1.5.1
#tool nuget:?package=Beryllium.Camera&version=1.5.1
BerylliumCamera
A camera for MonoGame: quaternion orientation, a floating origin for large worlds, pluggable controllers for free flight and path animation, and a spline path with arc-length parameterization that also emits the geometry needed to draw itself.
Install
dotnet add package BerylliumCamera
Targets net10.0-windows. Depends on MonoGame.Framework.Compute.WindowsDX.NoMemoryLeak and BerylliumMath.
Quick start
using BerylliumCamera;
using BerylliumCamera.Controllers;
var camera = new Camera(position: new Vector3(0, 5, 20), target: Vector3.Zero, up: Vector3.Up)
{
AspectRatio = GraphicsDevice.Viewport.AspectRatio,
FieldOfViewDegrees = 60f,
NearPlane = 0.1f,
FarPlane = 5000f
};
var free = new FreeCameraController();
camera.Controller = free;
// Update, once per frame:
free.Look(mouseDelta.X, mouseDelta.Y);
if (keyboard.IsKeyDown(Keys.W)) free.Move(Vector3.Forward);
if (keyboard.IsKeyDown(Keys.D)) free.Move(Vector3.Right);
camera.Update((float)gameTime.ElapsedGameTime.TotalSeconds);
// Draw:
effect.View = camera.View;
effect.Projection = camera.Projection;
effect.World = Matrix.CreateTranslation(objectPosition - camera.Offset); // see "Floating origin"
Camera
Camera holds a world Position and a unit Orientation quaternion. Forward, Right and Up are cached
world-space basis vectors, and View and Projection are rebuilt by Update, which runs the attached controller first
and then refreshes whatever changed.
LookAt(worldTarget, worldUp)faces a point. A target at the camera's own position leaves the orientation unchanged, and a target straight alongworldUpstill yields a valid orientation.ScreenPointToRay(pixel, viewport)returns a world-spaceRayfrom the near plane through the pixel. It is computed from the current pose and projection parameters, so it needs noUpdateafter they change.- Validation.
FieldOfViewDegreesmust lie strictly between 0 and 180, andAspectRatio,NearPlaneandFarPlanemust be positive and finite; each setter throwsArgumentOutOfRangeExceptionotherwise. A near plane not less than the far plane throwsInvalidOperationExceptionfromUpdate.Orientationrejects NaN and zero quaternions and stores a normalized copy.
Floating origin
Far from the origin, single-precision world coordinates jitter. The camera keeps an Offset that snaps in whole cells
of 1000 units as the camera moves, and builds View from RebasedPosition, which is Position - Offset. Build your
world matrices relative to the same offset, Matrix.CreateTranslation(worldPosition - camera.Offset), and rendering
stays precise anywhere. Position, LookAt and ScreenPointToRay all use true world coordinates; only rendering needs
the offset.
Controllers
A controller drives one camera at a time. Attach with camera.Controller = controller or controller.ApplyTo(camera);
both take the same path. The camera detaches its previous controller, the new one adopts the camera's current state
once, and a controller assigned to a second camera is detached from the first. Set Controller = null to drive the
camera by hand.
FreeCameraController
Six-degrees-of-freedom flight. The orientation is a quaternion turned about the camera's own axes, so yaw, pitch and roll stay consistent however the camera is banked.
| Member | Meaning |
|---|---|
Look(deltaYaw, deltaPitch) |
Mouse-style deltas scaled by LookSensitivity radians per unit: positive yaw looks right, positive pitch looks down. |
Move(delta) |
Movement in the camera's local frame: X right, Y up, -Z forward, so Vector3.Forward moves forward. Calls within a frame add up and the sum is capped at unit length: a half-deflected stick moves at half MoveSpeedUnitSec, while stacked or diagonal inputs never exceed it. |
Roll(direction) |
Positive rolls clockwise at RollSpeedRadSec. |
SnapTo(forward) |
Smoothly turns to face forward with roll levelled against world up, covering 95% of the turn within AxisSnapConvergenceSec. Any look or roll input cancels the snap. |
Defaults: 20 units per second, 2.5 rad/s roll, 0.0025 look sensitivity, 0.18 s snap.
AnimatedCameraController
Moves the camera along a CameraPath at constant speed.
| Member | Meaning |
|---|---|
Path |
The path to follow. Never null; assign a new instance to replace it. |
DurationSec |
Seconds for one full traversal. Default 8. |
OpenPathAnimationMode |
What an open path does at its ends: Stop (default), Loop or PingPong. Closed paths always wrap. |
EntryBlendDurationSec |
On attachment the camera eases from where it was onto the path over this many seconds; 0 jumps. Default 2. |
SnapToNearestOnEntry |
On attachment, start from the point of the path nearest the camera rather than from its beginning. Default on. |
Play(), Pause(), Seek(distance), SeekNormalized(fraction) |
Transport. Play at the end of a stopped open path restarts from the beginning. |
AddWaypoint(camera, mode, lookAt) |
Appends a waypoint at a camera's current pose, for building a path from a free camera. |
IsPlaying, PathDistance |
Current state. |
var animated = new AnimatedCameraController { DurationSec = 12f, OpenPathAnimationMode = CameraAnimationMode.PingPong };
animated.AddWaypoint(camera); // Fixed: keeps the camera's orientation
animated.AddWaypoint(camera, CameraWaypointOrientationMode.LookAtTarget, Vector3.Zero);
animated.AddWaypoint(camera, CameraWaypointOrientationMode.TangentFollow); // looks down the track
camera.Controller = animated;
animated.Play();
CameraPath
A centripetal Catmull-Rom spline through waypoints, re-parameterized by arc length, so equal distance steps move equal
world distances. At the default resolution a camera's speed stays within 0.15% of constant from frame to frame, even
through a tight turn between long segments, and Length matches the arc length to one part in 100,000. Orientation is
slerped between the resolved orientations of the two waypoints bracketing the sample.
Waypoints
CameraWaypoint factories: CreateFixed(position, orientation), CreateLookAtTarget(position, target),
CreateTangentFollow(position) and CreateFixedFromCamera(camera). Edit the path through AddWaypoint,
InsertWaypoint, SetWaypoint, RemoveWaypointAt and ClearWaypoints; Waypoints is a read-only view.
Every edit, and any change to Closed or WorldUp, marks the path dirty. The lookup tables are rebuilt lazily on the
next query, so there is no Rebuild to forget; call it only to pay the cost at a moment of your choosing.
Closedloops the path. It takes effect with at least three waypoints, andIsClosedreports the effective state.WorldUpis the up reference for look-at and tangent-follow orientations.LengthandSegmentCountdescribe the current path.- The constructor's
samplesPerSegment(default 32) sets the resolution of the arc-length table. Placement error falls more than tenfold each time it doubles, until single-precision rounding takes over at around 64; higher values mostly cost rebuild time.
Sampling
| Method | Returns |
|---|---|
SampleByPathDistance(distance), SampleByPathDistanceNormalized(fraction) |
A CameraPathPoint with Position, unit Tangent, Orientation and DistanceUnits. |
PositionByPathDistance(distance), PositionByPathDistanceNormalized(fraction) |
Position only. |
ClosestDistance(worldPosition) |
Path distance of the nearest point on the path: a coarse scan of the cached samples, then a golden-section refine. |
GetWaypointOrientation(index) |
A waypoint's resolved orientation, also for a lone waypoint. |
Distances are clamped to [0, Length]. A path with a single waypoint samples to that waypoint's position and
orientation.
Drawing a path
Set IsVisualized = true and read VisualGeometryBuffers each frame; it is refreshed lazily whenever the path or the
options changed. The buffers are plain data for your own line renderer:
| Buffer | Content |
|---|---|
Polyline |
Points along the curve, in order. |
Waypoints |
Waypoint positions. |
Gizmos |
GizmoCount orientation frames spaced evenly by arc length, each with Position, Right, Up and Forward. |
WaypointGizmos |
One orientation frame per waypoint. |
Tune with VisualOptions, a struct; assign a modified copy:
path.VisualOptions = path.VisualOptions with { IsAdaptive = true, ChordTolerance = 0.02f, GizmoCount = 24 };
IsAdaptive subdivides where the curve strays from its chord by more than ChordTolerance, bounded by
MaxSubdivisionDepth; otherwise PolylineSamples points are spaced evenly by arc length. Read the buffers through the
property rather than caching the instance, since that read is what triggers the rebuild.
CameraPathVisualizer.Build(path, buffers, options) fills buffers of your own.
Conventions
MonoGame's right-handed frame: +X right, +Y up, and the camera looks down -Z, so Vector3.Forward is (0, 0, -1).
Orientation maps local axes to world axes: camera.Forward == Vector3.Transform(Vector3.Forward, camera.Orientation).
Building and testing
dotnet test
The xUnit suite in Tests/ covers the camera, both controllers, the path and the visualizer, and needs no graphics
device.
dotnet run -c Release --project Benchmarks
The BenchmarkDotNet suite in Benchmarks/ times what a path costs a game: PositionByPathDistance and
SampleByPathDistance per query, ClosestDistance, Rebuild and adaptive visualization. Append
-- --filter *Sample* to run a subset.
Changelog
1.5.1
Fixes:
AnimatedCameraControllerinStopmode no longer stops itself when a zero-length frame followsPlay()at the start of the path.- The end of a closed path, as sampled by
SampleByPathDistance(Length),SampleByPathDistanceNormalized(1)or a pausedSeek(Length), has the first waypoint's orientation instead of the second's. - Path tangents, and with them
TangentFolloworientations, no longer fall back toVector3.Forwardon segments shorter than about half a unit. ScreenPointToRaybuilds the ray from the camera's pose and projection instead of inverting the view-projection matrix. The direction is now accurate to 1e-7 rad; it used to be off by 4e-4 rad with the camera 200 units from the floating-origin offset under the default clip planes, and by more further out. The origin stays on the near plane whatever the viewport's depth range.CameraPath.Waypointscan no longer be cast back to the underlying list and edited without a rebuild.CameraWaypoint.CreateFixedFromCamerathrowsArgumentNullExceptionon a null camera.- Travel along a path is constant-speed. Distance used to map to the spline linearly between table samples, so the
camera's speed rippled with the curve's: by up to 4% from frame to frame around a tight turn at the default
resolution, and 10% for a slow camera. Distance now maps through a cubic Hermite that matches the exact rate at both
ends of each sample interval, keeping speed within 0.15% and position within 2e-4 units of exact arc-length placement
on the same path.
Lengthis integrated rather than summed from chords, and accurate to one part in 100,000 instead of one in 10,000. - Tangents come from the spline's analytic derivative instead of a finite difference.
Also: a test suite, a benchmark suite and a 128×128 package icon. SampleByPathDistance evaluates the spline once
instead of three times and is about 13% faster; Rebuild is about twice as slow (11 µs for an eight-waypoint path) to
build the finer table, and ClosestDistance about 6% slower. Waypoint orientations are resolved once per rebuild rather than on every
GetWaypointOrientation call, adaptive visualization evaluates each seed point once, and gizmo frames come from a
single rotation matrix.
1.5.0
Requires BerylliumMath 0.2.0.
Fixes:
CameraPathno longer throws whenRebuildwas not called after an edit or after togglingClosed; it rebuilds itself.- A lone tangent-follow waypoint no longer throws when visualized, and a lone look-at waypoint shows its resolved orientation.
ClosestDistancerefines within the true neighbouring samples, so it no longer misses the nearest point on long segments, and it handles the seam of a closed path.Camera.Controller = nulldetaches instead of throwing, andApplyToand the property setter both sync the controller exactly once.LookAttowards a target straight along the up vector yields a valid orientation instead of NaN.- Ping-pong no longer leaves a reversed direction behind when the mode changes.
Breaking:
CameraPath.Waypointsis read-only; use the editing methods.CameraPath.VisualOptionsis a property; assign a copy.AnimatedCameraController.Pathcannot be null, andAddWaypointthrows on a null camera.- Projection parameters and
Orientationare validated in their setters. FreeCameraController.Movecaps the summed input at unit length instead of normalizing it.- Nullable reference types are enabled;
Camera.ControllerisBaseCameraController?.
Also: cached basis vectors and path samples, a cheaper view matrix and picking path, and XML documentation shipped in the package.
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-windows7.0 is compatible. |
-
net10.0-windows7.0
- BerylliumMath (>= 0.2.0)
- MonoGame.Framework.Compute.WindowsDX.NoMemoryLeak (>= 3.8.3.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.5.1 | 65 | 10/1/2026 |