Skip to content

leftger/embedded-3dgfx

 
 

Repository files navigation

embedded-3dgfx

crates.io CI

A no_std 3D graphics and physics engine for embedded systems, optimized for resource-constrained devices. Features real-time rendering, rigid body dynamics, soft body physics, skeletal animation, and visual effects.

This is a fork of embedded-gfx by Kezii. This fork adds texture mapping, fog/dithering effects, DMA rendering, Z-buffer improvements, anti-aliasing, a complete physics engine, and Quake-style environmental effects.

What's new in 0.3.0

  • Perspective-correct textures — clip-space W is propagated through the rasterizer; UV coordinates are now divided by W per pixel, eliminating the affine swim on non-axis-aligned surfaces
  • Sector lighting — Doom-style per-sector brightness scaling applied at mesh level, zero per-vertex cost
  • UV ray-castingmesh_ray_cast now returns barycentric-interpolated UV at the hit point alongside distance, world position, and face normal
  • HUD overlayhud module provides a fixed-capacity overlay for text and icon elements drawn after the scene pass
  • record/execute pipeline (introduced in 0.2, stabilized in 0.3) — scene traversal and rasterization are explicit separate phases; command buffers can be replayed without re-traversing the scene graph

Recent additions

  • Particle system — fixed-capacity, no-alloc billboard emitter (ParticleSystem<N>). Supports color and size interpolation over particle lifetime, gravity/acceleration, and camera-facing billboards. Integrates with the record/execute pipeline via sys.record(&engine, &mut cmd_buf).
  • Runtime depth fogK3dengine::set_fog(FogConfig) enables per-pixel linear depth fog applied during rasterization across both mesh and BSP paths.
  • Dynamic point lightsK3dengine::add_point_light(PointLight) registers runtime point lights with squared-distance falloff that are composited as an additive RGB565 tint on top of baked/directional lighting at face granularity.
  • BSP room-strip builder (std)bsp::builder::build_room_strip converts high-level room specs into valid BSP lumps for quick tooling/tests/examples.

Features

3D Rendering

  • Full MVP pipeline with perspective projection and frustum/backface culling
  • Z-buffering with 16.16 fixed-point depth testing
  • Flat and Gouraud shading, directional lighting, Blinn-Phong specular
  • Perspective-correct UV texture mapping with multi-texture support (RGB565)
  • Sector lighting (Doom-style per-mesh brightness scaling)
  • Runtime depth fog (K3dengine::set_fog)
  • Dynamic point lights with additive tint (K3dengine::add_point_light)
  • Particle system — no-alloc billboard emitters with color/size over lifetime (ParticleSystem<N>)
  • Bayer 4×4 dithering, billboards, vertex animation
  • Anti-aliased lines and triangles (heuristic and per-pixel coverage modes)
  • LOD system with distance-based mesh switching
  • DMA double-buffer rendering via swapchain
  • HUD overlay with text and icon elements

Record/Execute Pipeline

  • engine.record(meshes, &mut cmd_buf, telemetry) — traverse scene graph, emit draw commands
  • engine.execute(fb, &mut frame, &cmd_buf, telemetry) — rasterize commands to framebuffer
  • engine.execute_tiled(...) — tile-binned execution for partial display updates

Physics Engine (feature: physics, opt-in)

  • Rigid body dynamics — linear/angular motion, forces, torques
  • Sphere, AABB, and capsule colliders with impulse-based collision response
  • Distance, ball-socket, and fixed joint constraints
  • Ray casting with hit point, face normal, and UV coordinate

Skeletal Animation

  • Parent-child bone hierarchies with linear blend skinning (LBS/SSD)
  • Up to 4 bone influences per vertex

Soft Body Physics (feature: physics, opt-in)

  • Mass-spring systems for cloth, jelly, and deformable objects
  • Volume preservation via pressure simulation

UI Animation

  • Rigid transform animation tracks for splash screens and menus
  • Tweening with easing functions (Tween3, Easing)

Screenshots

Rotating wireframe cube
Wireframe rendering
Blinn-Phong Suzanne rotating
Blinn-Phong shading (Suzanne)
Bouncing balls physics
Rigid body physics
Cloth soft-body simulation
Cloth soft-body simulation
Particle fountain with fog
Particle system + depth fog
Orbiting dynamic point lights
Dynamic point lights (orbiting)
Newton's cradle — constraint physics
Newton's cradle (distance constraints)
Particle fountain with fog
Particle fountain + depth fog
Dynamic point lights
Point lights — additive tint
Gouraud shading
Gouraud shading (per-vertex color)
Fog and Bayer dithering
Fog + Bayer dithering
Newton's cradle
Newton's cradle (constraints)
Blinn-Phong shading
Blinn-Phong specular
Doom-style retro preset
Retro preset (Doom-style)
BSP room-strip builder output
BSP room-strip builder + textured BSP path

To regenerate all screenshots and GIFs:

cargo run --example screenshots --features std

Installation

[dependencies]
# Embedded (no_std)
embedded-3dgfx = { version = "0.3", default-features = false }

# With physics
embedded-3dgfx = { version = "0.3", features = ["physics"] }

# Desktop / simulator with all features
embedded-3dgfx = { version = "0.3", features = ["std", "physics"] }

Basic Example

use embedded_3dgfx::{K3dengine, mesh::{Geometry, K3dMesh, RenderMode}};
use nalgebra::Vector3;

let mut engine = K3dengine::new(320, 240);
engine.camera.set_position(Vector3::new(0.0, 0.0, 5.0).into());

let geometry = Geometry { vertices: &CUBE_VERTS, faces: &CUBE_FACES, /* ... */ };
let mut mesh = K3dMesh::new(geometry);
mesh.set_render_mode(RenderMode::Lines);

// Record draw commands, then rasterize to framebuffer
let mut commands = embedded_3dgfx::command_buffer::CommandBuffer::<512>::new();
engine.record(core::iter::once(&mesh), &mut commands, None).unwrap();
engine.execute(&mut display, &mut frame_ctx, &commands, None).unwrap();

Particle System

use embedded_3dgfx::particles::{ParticleSpawn, ParticleSystem};
use nalgebra::{Point3, Vector3};

// Fixed capacity — no heap allocation
let mut sys: ParticleSystem<256> = ParticleSystem::new();

// Spawn a burst of sparks from the origin
sys.spawn(ParticleSpawn {
    position: Point3::new(0.0, 0.0, 0.0),
    velocity: Vector3::new(0.5, 3.0, 0.3),
    acceleration: Vector3::zeros(),
    color_start: Rgb565::new(31, 60, 10), // yellow-white
    color_end:   Rgb565::new(28, 5, 0),   // orange-red
    size_start: 0.2,
    size_end:   0.0,
    lifetime:   1.5,
});

// Per-frame: integrate physics, then append billboard quads to the command buffer
let gravity = Vector3::new(0.0, -4.0, 0.0);
sys.update(dt, gravity);
sys.record(&engine, &mut commands);
// engine.execute(...) renders particles alongside meshes

Dynamic Point Lights

use embedded_3dgfx::lights::PointLight;
use nalgebra::Point3;

let mut engine = K3dengine::new(320, 240);

// Register up to MAX_POINT_LIGHTS per frame
engine.add_point_light(
    PointLight::new(
        Point3::new(-3.0, 2.0, 1.0), // world position
        Rgb565::new(31, 0, 0),        // red light
        8.0,                          // radius
    )
    .with_intensity(1.2),
);

// Lights are automatically applied during engine.record()
// as additive RGB565 tint at face granularity — no extra draw pass needed.
engine.record(meshes.iter(), &mut commands, None).unwrap();

// Clear each frame to update light positions
engine.clear_point_lights();

Depth Fog

use embedded_3dgfx::draw::FogConfig;

let fog_color = Rgb565::new(4, 8, 12); // dark blue haze
engine.set_fog(FogConfig::new(fog_color, 5.0, 28.0)); // near, far

// Fog is applied per-pixel during rasterization across all render modes.
// Clear the display to the fog color so distant surfaces blend seamlessly:
display.clear(fog_color).unwrap();

engine.record(meshes.iter(), &mut commands, None).unwrap();
engine.execute(&mut display, &mut frame_ctx, &commands, None).unwrap();

// Disable fog
engine.clear_fog();

Physics Example

use embedded_3dgfx::physics::{PhysicsWorld, RigidBody, Collider, Ray};
use nalgebra::Vector3;

let mut world = PhysicsWorld::<16, 4>::new();
world.set_gravity(Vector3::new(0.0, -9.81, 0.0));

// Dynamic sphere
let sphere_id = world.add_body(
    RigidBody::new(1.0)
        .with_collider(Collider::Sphere { radius: 0.5 })
        .with_position(Vector3::new(0.0, 10.0, 0.0))
        .with_restitution(0.7)
        .with_inertia_sphere(0.5),
).unwrap();

// Static floor
world.add_body(
    RigidBody::new_static()
        .with_collider(Collider::Aabb { half_extents: Vector3::new(10.0, 0.5, 10.0) })
        .with_friction(0.6),
).unwrap();

// Advance simulation (8 constraint-solver iterations)
world.step::<8>(0.016);

// UV-aware ray cast
let ray = Ray::new(Vector3::zeros(), Vector3::new(0.0, -1.0, 0.0));
if let Some(hit) = world.ray_cast(&ray, 100.0) {
    // hit.distance, hit.point, hit.normal, hit.uv
}

Skeletal Animation

use embedded_3dgfx::skeleton::{Skeleton, Bone, SkinningData, VertexSkinning, apply_skinning};
use nalgebra::Vector3;

let mut skeleton = Skeleton::<8>::new();
let root = skeleton.add_bone(Bone::new("root"), None).unwrap();
let arm  = skeleton.add_bone(
    Bone::new("arm").with_position(Vector3::new(0.0, 1.0, 0.0)),
    Some(root),
).unwrap();

skeleton.update_transforms();
skeleton.compute_inverse_bind_poses();

let mut skinning = SkinningData::new();
skinning.add_vertex(VertexSkinning::two_bones(root.0, 0.7, arm.0, 0.3)).unwrap();

// Animate, then deform mesh
skeleton.get_bone_mut(arm).unwrap().set_rotation(rotation);
skeleton.update_transforms();
apply_skinning(&skeleton, &skinning, &bind_vertices, &mut deformed_vertices);

Soft Body

use embedded_3dgfx::softbody::SoftBody;

// Pre-built cloth pinned at the top edge
let mut cloth = SoftBody::<64, 256>::create_cloth(8, 6, 0.2, 100.0, 0.5).unwrap();
cloth.set_gravity(nalgebra::Vector3::new(0.0, -9.81, 0.0));
cloth.ground_plane = Some(0.0);

cloth.step(0.016);

let mut positions = [[0.0f32; 3]; 64];
cloth.get_vertex_positions(&mut positions);

Available pre-built shapes: create_cloth, create_jelly_cube, create_soft_sphere.

Examples

31 interactive examples — run any with:

cargo run --example <name> --features std

Rendering

Example Description
basic_rendering Render mode cycling: points, lines, solid
rotating_cube Animated 3D transformations
scene_viewer Interactive multi-mesh scene
lighting_demo Directional lighting with ambient
gouraud_demo Smooth per-vertex color interpolation
blinn_phong_demo Specular highlights
fog_dithering_demo Atmospheric fog + Bayer dithering
texture_mapping_demo Perspective-correct UV textures
retro_presets_demo Toggle Doom/PSX/Modern retro rendering presets
bsp_builder_demo Runtime BSP room-strip building + textured BSP render
dma_rendering_demo Double-buffer DMA performance
billboard_demo Camera-facing quads
lod_demo Distance-based mesh LOD switching
vertex_animation_demo Keyframe vertex morphing
painters_algorithm_demo Painter's algorithm ordering
boot_menu Boot splash + menu transitions (96×64, tweens)
stl_viewer Load and view STL models

Physics (requires physics feature)

Example Description
physics_rolling_ball Beginner intro — gravity, friction, ramps
physics_bouncing_balls Restitution coefficients compared
physics_pendulum Constraint-based swinging
physics_newtons_cradle Momentum conservation via distance constraints
physics_stack_tower Stacking stability with friction
physics_domino_chain Chain-reaction angular dynamics
physics_wrecking_ball Heavy ball vs light boxes
physics_demo Comprehensive physics showcase
capsule_physics_demo Capsule collider interactions
skeletal_animation_demo Bone hierarchy and linear blend skinning
cloth_simulation Hanging cloth with wind
jelly_cube_demo Deformable cube with volume preservation
raycast_demo Ray casting, hit detection, UV lookup

Physics Reference

Creating bodies

// Dynamic body
let id = physics.add_body(
    RigidBody::new(mass)
        .with_position(Vector3::new(x, y, z))
        .with_velocity(Vector3::new(vx, vy, vz))
        .with_collider(Collider::Sphere { radius })
        .with_restitution(0.5)
        .with_friction(0.5)
        .with_inertia_sphere(radius),
).unwrap();

// Static body (floor, wall)
physics.add_body(
    RigidBody::new_static()
        .with_position(Vector3::new(0.0, 0.0, 0.0))
        .with_collider(Collider::Aabb { half_extents: Vector3::new(10.0, 0.1, 10.0) })
        .with_friction(0.6),
).unwrap();

Distance constraints

physics.add_distance_constraint(
    anchor_id,
    Vector3::zeros(), // anchor attachment point
    body_id,
    Vector3::zeros(), // body attachment point
    0.0,              // compliance (0.0 = rigid)
).unwrap();

Stepping and sync

// Step with 8 constraint-solver iterations
physics.step::<8>(1.0 / 60.0);

// Sync physics state to render meshes
for &id in &body_ids {
    let body = physics.body(id).unwrap();
    sync_body_to_mesh(body, &mut mesh);
}

Capacity

PhysicsWorld::<16, 8>::new()  // 16 bodies, 8 constraints (const generics, no heap)

Troubleshooting

Symptom Fix
Bodies fall through floor Increase solver iterations; check collider types match
Constraints are stretchy Increase solver iterations; set compliance to 0.0
Simulation explodes Reduce timestep; add damping; verify inertia tensors
Poor performance Reduce max contacts; fewer substeps; prefer sphere colliders

Feature Flags

Flag Default Description
physics off Rigid body dynamics, soft body, and ray casting (physics + softbody modules)
std on Enables painter's algorithm (Vec) and includes perfcounter
perfcounter off FPS/timing measurements (requires std or embassy-time)
embassy-time off Timing source for embedded targets
aa-heuristic on Heuristic edge AA for triangles, no extra buffer
aa-coverage on Per-pixel coverage AA; eliminates shared-edge seam at cost of a W×H byte buffer
row_width_96 Optimize row buffers for 96 px wide displays
row_width_160 Optimize row buffers for 160 px wide displays
row_width_240 Optimize row buffers for 240 px wide displays (default)
row_width_320 Optimize row buffers for 320 px wide displays
fixed-transform off Fixed-point screen-space projection path
dwt-profiler off DWT cycle-counter profiling hooks
triple-buffering off Triple-buffered swapchain APIs

row_width_* flags are mutually exclusive. aa is an internal flag enabled automatically by either AA feature.

Caps and Telemetry

Technical reference docs in docs/:

Doc Topic
caps-and-telemetry.md Record/execute pipeline, profile caps, telemetry API, CI budget enforcement
backend-integration.md Board bring-up checklist, memory sizing, hardware profiling, smoke tests, compatibility matrix
asset-pipeline.md Offline converter CLI, chunked scene format, cooperative streaming loader, CI budget reporting

System Requirements

Minimum:

  • ARM Cortex-M4F with FPU
  • 128 KB RAM (single buffer, small scenes)
  • 256 KB Flash

Recommended:

  • ARM Cortex-M33 with FPU (e.g. STM32WBA)
  • 512 KB+ RAM (double buffer + Z-buffer + physics)
  • 512 KB+ Flash

Memory at 240×135:

Feature Cost
Single framebuffer 65 KB
Double framebuffer 130 KB
Z-buffer 130 KB
Physics (16 bodies) ~4 KB
Soft body (64 particles) ~2 KB
Skeleton (8 bones) ~1 KB
Particle system (256 particles) ~6 KB
Point light set (8 lights) <1 KB

Budget at 240×135 @ 60 FPS:

  • Rendering: ~10–13 ms/frame
  • Physics (16 bodies): ~2–3 ms/frame
  • DMA display transfer: ~3 ms (parallel)

Performance

Desktop benchmarks at 320×240 (cargo run --release --example screenshots --features std):

Scene Triangles p50 release p50 debug
Wireframe cube 12 0.05 ms 3.6 ms
Blinn-Phong Suzanne ~960 0.09 ms 6.6 ms
Physics (5 balls + floor) + render ~800 0.08 ms 8.2 ms

Desktop figures are on a modern x86-64 CPU. On ARM Cortex-M33 @ 64 MHz expect roughly 10–13 ms/frame for mid-complexity scenes at 240×135.

Key optimizations in the record/execute pipeline:

  • micromath transcendentalssin/cos/atan2 routed through micromath on no_std, replacing soft-float libm; ~15–25% reduction in per-frame transform cost on Cortex-M4
  • 16.16 fixed-point z-buffer — depth values stored as u32, cutting memory bandwidth on 32-bit bus targets
  • Separate record/execute phases — command-buffer replay avoids re-traversing the scene graph on unchanged frames

Architecture

src/
  lib.rs              # Engine entry point: K3dengine, record/execute API
  camera.rs           # View/projection matrices
  mesh.rs             # Geometry, LOD, render modes
  draw.rs             # Rasterization, shading, fog, effects
  renderer.rs         # FrameCtx, execute_commands, tiled execution
  command_buffer.rs   # Fixed-capacity command buffer
  particles.rs        # No-alloc billboard particle system
  lights.rs           # Dynamic point lights, PointLight, PointLightSet
  config.rs           # ProfileCaps, QualityTier, DegradationPolicy
  error.rs            # RenderError, BudgetKind
  physics.rs          # Rigid body dynamics (feature: physics)
  skeleton.rs         # Skeletal animation, linear blend skinning
  softbody.rs         # Mass-spring soft body physics (feature: physics)
  texture.rs          # Texture management, RGB565
  billboard.rs        # Camera-facing quads
  animation.rs        # Keyframe vertex animation
  transform_anim.rs   # Rigid transform animation tracks
  tween.rs            # Tweening and easing functions
  swapchain.rs        # DMA double/triple buffering
  display_backend.rs  # Display abstraction layer
  bridge.rs           # embedded-graphics bridge
  painters.rs         # Painter's algorithm (std only)
  hud.rs              # HUD overlay elements
  scene_format.rs     # Serialized scene chunk format
  scene_stream.rs     # Cooperative chunk streaming
  hardware_profile.rs # Target hardware profile definitions
  perfcounter.rs      # FPS/timing measurements
  fixed_math.rs       # Fixed-point math helpers
  telemetry.rs        # Record/execute telemetry types
  tilebin.rs          # Tile-bin stats and config
  lut.rs              # Precomputed lookup tables

load_stl/             # STL file embedding macro
examples/             # 31 interactive demos
tests/                # 255 unit tests

Testing

cargo test --lib
cargo test --lib --all-features

Git Hooks (Rustfmt Guardrails)

Versioned hooks live in .githooks/ and can be installed into .git/hooks:

./scripts/install-git-hooks.sh

Installed behavior:

  • pre-commit: runs cargo fmt --all and re-stages formatted staged Rust files.
  • pre-push: runs cargo fmt --all --check and blocks push on formatting drift.

Contributing

Contributions welcome. Priority areas:

  • Hardware-specific display backends (ESP32, STM32, RP2040)
  • Spatial partitioning (octree/BVH for broad-phase collision)
  • Additional joint types (hinge, slider, prismatic)
  • Mesh colliders (convex hulls)

References

Graphics:

Physics:

License

The contents of this repository are dual-licensed under the MIT OR Apache 2.0 License. That means you can choose either the MIT license or the Apache 2.0 license when you re-use this code. See LICENSE-MIT or LICENSE-APACHE for more information on each specific license. Our Apache 2.0 notices can be found in NOTICE.

About

3D graphics library for embedded systems, built around embedded-graphics

Resources

License

Apache-2.0, MIT licenses found

Licenses found

Apache-2.0
LICENSE-APACHE
MIT
LICENSE-MIT

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages

  • Rust 99.7%
  • Shell 0.3%