Skip to main content

Crate finite_light_bevy

Crate finite_light_bevy 

Source
Expand description

§Finite Light Project

Pipeline status crates.io docs.rs License: MIT or Apache 2.0 Bevy 0.18 Web Demo

Tools for enabling relativistic rendering for games and visualizations in Rust. Currently comprises a plugin for the Bevy game engine, finite_light_bevy, along with other supporting crates.

When enabled, objects appear as they actually would to an observer in a universe with a finite speed of light: light-travel delay, Lorentz contraction, relativistic aberration, and Penrose-Terrell rotation are all computed per-vertex on the GPU.

Check out the web demo here.

§Can I add relativistic rendering to my Bevy game/app?

Yes! The biggest hurdle might be velocity tracking: once enabled, the RelativisticPlugin must be continually informed about the correct velocity of every moving object in a scene, and velocities can never exceed the configured speed of light (otherwise there can be visual glitches). See below.

§Quick start

use bevy::prelude::*;
use finite_light_bevy::{RelativisticPlugin, Relativistic, RelativisticMetric};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(RelativisticPlugin::with_speed_of_light(30.))
        .add_systems(Startup, set_up)
        .add_systems(Update, move_cube)
        .run();
}

#[derive(Component)]
struct MovingCube;

fn set_up(
    mut commands: Commands,
    mut meshes: ResMut<Assets<Mesh>>,
    mut materials: ResMut<Assets<StandardMaterial>>,
    metric: Res<RelativisticMetric>,
) {
    // Manually enroll a cube in relativistic rendering and give it a constant initial
    // velocity. `Transform` can be used to seed initial position/pose, but subsequent
    // changes must use the `PoincareTransform` contained in the `Relativistic` component
    // instead.
    commands.spawn((
        Mesh3d(meshes.add(Cuboid::default())),
        MeshMaterial3d(materials.add(Color::srgb(0.8, 0.2, 0.2))),
        Transform::from_xyz(50., 0., 0.),
        Relativistic::default().with_velocity(**metric, Vec3::new(-10., 0., 0.)),
        MovingCube,
    ));

    // Add a stationary cube. `Relativistic` will be added automatically by the plugin.
    commands.spawn((
        Mesh3d(meshes.add(Cuboid::default())),
        MeshMaterial3d(materials.add(Color::srgb(0.8, 0.8, 0.8))),
        Transform::from_xyz(-50., 0., 0.),
    ));

    // A camera. Non-mesh entities need explicit `Relativistic::default()`.
    commands.spawn((
        Camera3d::default(),
        Relativistic::default(),
    ));
}

/// Advance the cube's position each frame according to its constant velocity.
fn move_cube(
    mut query: Query<&mut Relativistic, With<MovingCube>>,
    metric: Res<RelativisticMetric>,
    time: Res<Time>,
) {
    let Ok(mut relativistic) = query.single_mut() else { return };
    relativistic.update_position(**metric, time.delta_secs());
}

§How it works

Each frame, the plugin records each entity’s spacetime pose as a keyframe on its world line. A compute shader then solves, for every mesh vertex, the intersection of the camera’s past light cone with the object’s world line. (See impl_guide.typ.) Each vertex is then placed at the retarded position – where it was when the light now reaching the camera was emitted – and the displacement is Lorentz-transformed into the camera’s rest frame to produce correct relativistic aberration. Normals are rotated to match the object’s orientation at retarded time.

The result is written directly into Bevy’s mesh buffers, so standard materials and lighting work without modification.

§What it doesn’t do

  • Lighting is not treated relativistically: relativistic Doppler shifts and beaming are currently not included, nor are time retardation effects for lighting (e.g. a light turns on at t = 0, object becomes illuminated later when light first hits it).
  • Relativistic dynamics are not automatically enabled, e.g. relativistic collisions. This needs to be accounted for in game systems manually if desired.

§Examples

See examples/! Run e.g. the Mars demo with cargo run --bin mars.

§Caveats

Each Relativistic entity with a Mesh3d must have a unique Mesh3d. The compute shader writes transformed vertices directly into the vertex buffer keyed by mesh asset ID, so entities sharing a handle would overwrite each other. Call meshes.add(...) per entity rather than cloning a single handle.

§Core concepts

§PoincareTransform

An inhomogeneous Lorentz transformation – the relativistic equivalent of Bevy’s Transform. It combines a spatial rotation, a Lorentz boost (velocity), and a spacetime translation (position in space and time). This is the fundamental state for each relativistic entity, which the plugin tracks.

§Relativistic

Tracks an entity’s spacetime state and history. The plugin automatically adds this to every Mesh3d entity it discovers, seeding the PoincareTransform from the entity’s Transform. For non-mesh entities (cameras, player controllers), add Relativistic::default() yourself; the Poincare is seeded from the Transform on the first frame.

Access the PoincareTransform via transform()/transform_mut(). Convenience methods set_velocity(), with_velocity(), and update_position() cover the common case of setting a velocity and advancing position each frame.

§NonRelativistic

Opt-out marker. Add this to an entity to prevent the plugin from auto-adding Relativistic to it or any of its Mesh3d descendants. Useful for UI elements or other geometry that should bypass the relativistic pipeline.

§RelativisticChild

Added automatically by init_relativistic for Mesh3d descendants of a Relativistic entity. Each frame, the entity’s PoincareTransform is composed from the source entity’s transform plus a stored local offset (position and rotation). Can also be added manually for custom follow behavior.

§ProperTime

Accumulated proper time along the camera’s world line. Each frame, delta_secs() is set to dt_game / gamma (where gamma is the camera’s Lorentz factor) and added to the running total.

To make the game tick at proper-time rate, call Time<Virtual>::set_relative_speed(gamma). Game systems that apply forces or consume resources should read Res<Time<Real>> to avoid a positive feedback loop where acceleration scales with gamma.

§The metric

RelativisticPlugin takes a math::Metric that sets the speed of light. The metric is available as the RelativisticMetric resource.

RelativisticPlugin::with_speed_of_light(30.)

§Debug mode

Chain .with_debug(true) on the plugin to visualize entity world lines with gizmos. Add HideWorldLine to any entity you want to exclude from the visualization.

§Public API/ontology

ItemKindDescription
RelativisticPluginPluginRegisters all systems and the GPU pipeline.
RelativisticComponentTracks spacetime state and world-line history.
NonRelativisticComponentOpt-out: suppresses auto-enrollment of meshes.
RelativisticChildComponentLinks an entity’s Poincare to a source + offset.
RelativisticSkyboxComponentCubemap skybox rendered as a mesh with aberration.
HideWorldLineComponentExcludes an entity from debug world-line drawing.
RelativisticMetricResourceWraps math::Metric; derefs to &Metric.
ProperTimeResourceAccumulated proper time along the camera world line.
mathRe-exportThe underlying spacetime math library.

Key types re-exported through math:

  • PoincareTransform: Inhomogeneous Lorentz transform (rotation + boost + spacetime translation).
  • Boost: Lorentz boost parameterized by rapidity vector. Collinear boosts compose by adding rapidities.
  • SpacetimeEvent: A point in Minkowski spacetime (x, y, z, t).
  • Metric: Minkowski metric with configurable speed of light.
  • WorldLine: Chronologically ordered history of spacetime poses.

§System schedule

All systems run in PostUpdate, chained after TransformSystems::Propagate:

  1. init_relativistic: For each Mesh3d without Relativistic, walk up the hierarchy looking for an existing Relativistic ancestor (wire RelativisticChild) or a NonRelativistic ancestor (skip). If neither is found, add Relativistic to the hierarchy root (or directly to a standalone mesh).
  2. update_poincare: Seed newly-added entities’ Poincare from their Transform, then stamp the current wall time on all entities.
  3. advance_proper_time: Increment ProperTime by dt / gamma using the camera’s Lorentz factor.
  4. sync_children: Compose source Poincare + local offset for RelativisticChild entities.
  5. record_and_gc: Push a keyframe onto each entity’s world line and discard keyframes that can no longer affect any vertex.
  6. sync_transforms: Reset Transform and GlobalTransform to identity for GPU-transformed entities; sync PoincareTransform to Transform for others.
  7. init_mesh_data: Lazily prepare vertex data for new entities (bakes scale into vertices). Runs last because it adds GpuTransformed via deferred commands.

Re-exports§

pub use finite_light_math as math;

Structs§

HideWorldLine
NonRelativistic
Opt-out: prevents the plugin from auto-adding Relativistic to this entity or any Mesh3d descendant.
ProperTime
Accumulated proper time along the camera’s worldline.
Relativistic
Tracks an entity’s spacetime state and history.
RelativisticChild
Link a Relativistic entity to a source. Each frame the entity’s PoincareTransform is composed from the source’s transform plus the stored local offset. Added automatically by init_relativistic for Mesh3d descendants of a Relativistic entity.
RelativisticMetric
Bevy resource wrapping math::Metric.
RelativisticPlugin
RelativisticSkybox
A skybox that deforms under relativistic aberration.