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
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Beryllium.Camera" Version="1.5.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Beryllium.Camera" Version="1.5.1" />
                    
Directory.Packages.props
<PackageReference Include="Beryllium.Camera" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Beryllium.Camera --version 1.5.1
                    
#r "nuget: Beryllium.Camera, 1.5.1"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Beryllium.Camera@1.5.1
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Beryllium.Camera&version=1.5.1
                    
Install as a Cake Addin
#tool nuget:?package=Beryllium.Camera&version=1.5.1
                    
Install as a Cake Tool

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 along worldUp still yields a valid orientation.
  • ScreenPointToRay(pixel, viewport) returns a world-space Ray from the near plane through the pixel. It is computed from the current pose and projection parameters, so it needs no Update after they change.
  • Validation. FieldOfViewDegrees must lie strictly between 0 and 180, and AspectRatio, NearPlane and FarPlane must be positive and finite; each setter throws ArgumentOutOfRangeException otherwise. A near plane not less than the far plane throws InvalidOperationException from Update. Orientation rejects 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.

  • Closed loops the path. It takes effect with at least three waypoints, and IsClosed reports the effective state.
  • WorldUp is the up reference for look-at and tangent-follow orientations.
  • Length and SegmentCount describe 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:

  • AnimatedCameraController in Stop mode no longer stops itself when a zero-length frame follows Play() at the start of the path.
  • The end of a closed path, as sampled by SampleByPathDistance(Length), SampleByPathDistanceNormalized(1) or a paused Seek(Length), has the first waypoint's orientation instead of the second's.
  • Path tangents, and with them TangentFollow orientations, no longer fall back to Vector3.Forward on segments shorter than about half a unit.
  • ScreenPointToRay builds 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.Waypoints can no longer be cast back to the underlying list and edited without a rebuild.
  • CameraWaypoint.CreateFixedFromCamera throws ArgumentNullException on 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. Length is 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:

  • CameraPath no longer throws when Rebuild was not called after an edit or after toggling Closed; it rebuilds itself.
  • A lone tangent-follow waypoint no longer throws when visualized, and a lone look-at waypoint shows its resolved orientation.
  • ClosestDistance refines 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 = null detaches instead of throwing, and ApplyTo and the property setter both sync the controller exactly once.
  • LookAt towards 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.Waypoints is read-only; use the editing methods.
  • CameraPath.VisualOptions is a property; assign a copy.
  • AnimatedCameraController.Path cannot be null, and AddWaypoint throws on a null camera.
  • Projection parameters and Orientation are validated in their setters.
  • FreeCameraController.Move caps the summed input at unit length instead of normalizing it.
  • Nullable reference types are enabled; Camera.Controller is BaseCameraController?.

Also: cached basis vectors and path samples, a cheaper view matrix and picking path, and XML documentation shipped in the package.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0-windows7.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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