finite_light_bevy 0.1.2

Bevy plugin for real-time special-relativistic rendering. Part of the Finite Light project.
Documentation
# Finite Light Project

[![Pipeline status](https://gitlab.com/joshburkart/finite-light/badges/main/pipeline.svg)](https://gitlab.com/joshburkart/finite-light/-/pipelines)
[![crates.io](https://img.shields.io/crates/v/finite_light_bevy.svg)](https://crates.io/crates/finite_light_bevy)
[![docs.rs](https://img.shields.io/docsrs/finite_light_bevy)](https://docs.rs/finite_light_bevy)
[![License: MIT or Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](LICENSE-MIT)
[![Bevy 0.18](https://img.shields.io/badge/bevy-0.18-232326)](https://bevyengine.org/)
[![Web Demo](https://img.shields.io/badge/web-demo-orange)](https://joshburkart.gitlab.io/finite-light/)

Tools for enabling relativistic rendering for games and visualizations in Rust. Currently comprises
a plugin for the [Bevy](https://bevyengine.org/) 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](https://en.wikipedia.org/wiki/Light-time_correction), [Lorentz
contraction](https://en.wikipedia.org/wiki/Length_contraction), [relativistic
aberration](https://en.wikipedia.org/wiki/Relativistic_aberration), and [Penrose-Terrell
rotation](https://en.wikipedia.org/wiki/Terrell_rotation) are all computed per-vertex on the GPU.

Check out the web demo [here](https://joshburkart.gitlab.io/finite-light/).

## 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

```rust,no_run
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](https://en.wikipedia.org/wiki/Poincar%C3%A9_group) -- 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.

```rust,ignore
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

| Item                 | Kind      | Description                                          |
| -------------------- | --------- | ---------------------------------------------------- |
| `RelativisticPlugin` | Plugin    | Registers all systems and the GPU pipeline.          |
| `Relativistic`       | Component | Tracks spacetime state and world-line history.       |
| `NonRelativistic`    | Component | Opt-out: suppresses auto-enrollment of meshes.       |
| `RelativisticChild`  | Component | Links an entity's Poincare to a source + offset.     |
| `RelativisticSkybox` | Component | Cubemap skybox rendered as a mesh with aberration.   |
| `HideWorldLine`      | Component | Excludes an entity from debug world-line drawing.    |
| `RelativisticMetric` | Resource  | Wraps `math::Metric`; derefs to `&Metric`.           |
| `ProperTime`         | Resource  | Accumulated proper time along the camera world line. |
| `math`               | Re-export | The 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.