Homebrew 4D Game Engine In Rust, inspired by Engine4D
Rust4D docs getting-started.md
16 kB · 574 lines
Markdown
at main

Getting Started with Rust4D #

Welcome to Rust4D, a 4D rendering engine written in Rust. This guide will take you from installation to creating your first 4D scene.

Table of Contents #

  1. Introduction
  2. Prerequisites
  3. Installation
  4. Building and Running
  5. Understanding 4D Space
  6. Running the Examples
  7. Your First 4D Scene
  8. Next Steps

Introduction #

What is Rust4D? #

Rust4D displays real-time 3D cross-sections of 4D geometry. Just as a 3D object passing through a 2D plane creates a 2D cross-section, Rust4D slices 4D objects with a 3D hyperplane to produce the 3D shapes you see on screen.

The engine includes:

  • True 4D geometry: Mathematical primitives defined in 4-dimensional space
  • GPU-accelerated slicing: Compute shaders that slice 4D tetrahedra in parallel
  • 4D physics: Gravity, collision detection, and rigid body dynamics in 4D
  • FPS-style controls: Navigate 4D space with intuitive keyboard and mouse controls

What You Will Learn #

By the end of this guide, you will understand:

  • How to set up your development environment
  • The basic concepts of 4D space and 4D rendering
  • How to run the included examples
  • How to create a simple 4D scene from scratch

Prerequisites #

Rust Knowledge #

This guide assumes basic familiarity with Rust and Cargo. If you are new to Rust, work through The Rust Book first.

Hardware Requirements #

Rust4D uses wgpu for GPU access, requiring support for:

  • Vulkan (Linux, Windows)
  • Metal (macOS)
  • DirectX 12 (Windows)

Most GPUs from the last 10 years will work.

Software Requirements #

  • Rust 1.70 or later
  • A C compiler: Visual Studio Build Tools (Windows), Xcode CLI tools (macOS), or GCC/Clang (Linux)

Installation #

Step 1: Install Rust #

If Rust is not installed, use rustup:

curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

Verify installation:

rustc --version
cargo --version

Step 2: Clone the Repository #

git clone https://github.com/yourusername/rust4d
cd rust4d

Step 3: Verify GPU Support #

cargo run --example 01_hello_tesseract --release

If you see a window with an orange shape, your environment is ready.

Linux-Specific Setup #

On Linux, install Vulkan support:

# Debian/Ubuntu
sudo apt install libvulkan1 vulkan-tools

# Fedora
sudo dnf install vulkan-loader vulkan-tools

# Arch Linux
sudo pacman -S vulkan-icd-loader vulkan-tools

On NixOS (or any system with Nix), skip manual setup entirely — the repo's dev shell provides the toolchain, Vulkan wiring, and verification tools:

nix develop

Building and Running #

Running the Main Application #

cargo run --release

Always use --release mode for interactive use.

What You Should See #

A window displays geometric shapes - these are 3D cross-sections of 4D objects. As you move along the W-axis, shapes change in ways impossible for true 3D objects.

Controls Overview #

Input Action
WASD Move in XZ plane
Q / E Move along W-axis (4th dimension)
Space / Shift Move up/down
Mouse Look around
Right-click drag Rotate through W
Scroll wheel Adjust slice offset
R Reset camera
F Fullscreen
Escape Release cursor / Quit

Click to capture the mouse cursor; press Escape to release.

Configuration #

Configuration loads from TOML files with environment variable overrides:

R4D_WINDOW__TITLE="My 4D Game" cargo run --release
R4D_PHYSICS__GRAVITY="-10.0" cargo run --release

Troubleshooting #

"No suitable GPU adapter found": Update graphics drivers or ensure Vulkan/Metal/DX12 support.

Compilation errors: Install C development tools (build-essential on Linux, Xcode CLI on macOS).

Low frame rate: Use --release mode and ensure your discrete GPU is being used.


Understanding 4D Space #

What is the 4th Dimension? #

In Rust4D, the 4th dimension is a spatial dimension, not time. We add a fourth perpendicular axis called W to the familiar X, Y, Z.

The Flatland Analogy #

Imagine a 2D being living on a flat plane. If a 3D sphere passes through their plane, they see a circle that appears, grows, shrinks, and vanishes - 2D cross-sections of a 3D object.

3D Sphere passing through 2D planePointSmall CircleLarge CircleSmall CirclePoint

Rust4D does this one dimension higher: we see 3D cross-sections of 4D objects.

4D Tesseract passing through 3D hyperplanePointSmall CubeFull CubeSmall CubePoint

The W-Axis #

The W-axis is perpendicular to X, Y, and Z. In Rust4D:

  • Q key moves toward +W (called "ana")
  • E key moves toward -W (called "kata")

Why Objects "Morph" #

Moving along W shows different cross-sections. A tesseract (4D cube) appears as a cube, then complex polyhedra, then another cube as you move through it.

Objects Can Pass "Through" Each Other #

Two objects at the same X, Y, Z but different W positions do not collide - they are separated in the 4th dimension.


Running the Examples #

Example List #

Example Description Key Concepts
01_hello_tesseract Minimal 4D rendering World, Entity, Camera4D
02_multiple_shapes Multiple objects Transform4D, Material, input
03_physics_demo Physics simulation PhysicsConfig, RigidBody4D
04_camera_exploration Full controls CameraController, mouse look

Running Examples #

cargo run --example 01_hello_tesseract --release
cargo run --example 02_multiple_shapes --release
cargo run --example 03_physics_demo --release
cargo run --example 04_camera_exploration --release

Learning Path #

  1. 01_hello_tesseract - Basic render pipeline
  2. 02_multiple_shapes - Transforms and materials
  3. 03_physics_demo - Physics integration
  4. 04_camera_exploration - Full control scheme

Example Walkthrough: 01_hello_tesseract #

The minimal example creates a single tesseract:

use rust4d_core::{Entity, Material, ShapeRef, Tesseract4D, World};
use rust4d_render::{camera4d::Camera4D, RenderableGeometry};
use rust4d_math::Vec4;

// Create a world
let mut world = World::new();

// Create a tesseract with size 2.0
let tesseract = Tesseract4D::new(2.0);

// Add an entity with orange material
let entity = Entity::with_material(
    ShapeRef::shared(tesseract),
    Material::from_rgb(0.8, 0.4, 0.2),
);
world.add_entity(entity);

// Convert to GPU geometry
let geometry = RenderableGeometry::from_world(&world);

// Set up camera
let mut camera = Camera4D::new();
camera.position = Vec4::new(0.0, 2.0, 6.0, 0.0);

Your First 4D Scene #

Let us create a scene with tesseracts at different W positions.

Step 1: Imports #

use std::sync::Arc;
use winit::{
    application::ApplicationHandler,
    event::WindowEvent,
    event_loop::{ActiveEventLoop, ControlFlow, EventLoop},
    window::{Window, WindowId},
};

use rust4d_core::{Entity, Material, ShapeRef, Tesseract4D, Transform4D, World};
use rust4d_render::{
    camera4d::Camera4D,
    context::RenderContext,
    pipeline::{perspective_matrix, RenderPipeline, RenderUniforms, SliceParams, SlicePipeline},
    RenderableGeometry,
};
use rust4d_math::Vec4;

Step 2: Application State #

struct MyApp {
    window: Option<Arc<Window>>,
    render_context: Option<RenderContext>,
    slice_pipeline: Option<SlicePipeline>,
    render_pipeline: Option<RenderPipeline>,
    world: World,
    geometry: RenderableGeometry,
    camera: Camera4D,
}

Step 3: Initialize the World #

impl MyApp {
    fn new() -> Self {
        let mut world = World::new();

        // Tesseract at origin (visible at W=0)
        let origin = Tesseract4D::new(1.5);
        world.add_entity(Entity::with_material(
            ShapeRef::shared(origin),
            Material::from_rgb(0.9, 0.3, 0.2),
        ));

        // Tesseract at W=+2 (press Q to find it)
        let w_positive = Tesseract4D::new(1.5);
        let transform_wp = Transform4D::from_position(Vec4::new(3.0, 0.0, 0.0, 2.0));
        world.add_entity(Entity::with_transform(
            ShapeRef::shared(w_positive),
            transform_wp,
            Material::from_rgb(0.2, 0.9, 0.3),
        ));

        // Tesseract at W=-2 (press E to find it)
        let w_negative = Tesseract4D::new(1.5);
        let transform_wn = Transform4D::from_position(Vec4::new(-3.0, 0.0, 0.0, -2.0));
        world.add_entity(Entity::with_transform(
            ShapeRef::shared(w_negative),
            transform_wn,
            Material::from_rgb(0.2, 0.3, 0.9),
        ));

        let geometry = RenderableGeometry::from_world(&world);

        let mut camera = Camera4D::new();
        camera.position = Vec4::new(0.0, 2.0, 8.0, 0.0);

        Self {
            window: None,
            render_context: None,
            slice_pipeline: None,
            render_pipeline: None,
            world,
            geometry,
            camera,
        }
    }
}

Step 4: Window Setup #

impl ApplicationHandler for MyApp {
    fn resumed(&mut self, event_loop: &ActiveEventLoop) {
        if self.window.is_none() {
            let window = Arc::new(
                event_loop
                    .create_window(
                        Window::default_attributes()
                            .with_title("My First 4D Scene")
                            .with_inner_size(winit::dpi::LogicalSize::new(1024, 768)),
                    )
                    .expect("Failed to create window"),
            );

            let render_context = pollster::block_on(RenderContext::new(window.clone()));
            let mut slice_pipeline = SlicePipeline::new(&render_context.device);
            let mut render_pipeline = RenderPipeline::new(
                &render_context.device,
                render_context.config.format,
            );

            render_pipeline.ensure_depth_texture(
                &render_context.device,
                render_context.size.width,
                render_context.size.height,
            );

            slice_pipeline.upload_tetrahedra(
                &render_context.device,
                &self.geometry.vertices,
                &self.geometry.tetrahedra,
            );

            self.window = Some(window);
            self.render_context = Some(render_context);
            self.slice_pipeline = Some(slice_pipeline);
            self.render_pipeline = Some(render_pipeline);
        }
    }

    fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
        match event {
            WindowEvent::CloseRequested => event_loop.exit(),
            WindowEvent::Resized(size) => {
                if let Some(ctx) = &mut self.render_context {
                    ctx.resize(size);
                }
                if let (Some(ctx), Some(rp)) = (&self.render_context, &mut self.render_pipeline) {
                    rp.ensure_depth_texture(&ctx.device, size.width, size.height);
                }
            }
            WindowEvent::RedrawRequested => {
                self.render_frame();
                if let Some(w) = &self.window {
                    w.request_redraw();
                }
            }
            _ => {}
        }
    }
}

Step 5: Render Loop #

impl MyApp {
    fn render_frame(&self) {
        let (Some(ctx), Some(sp), Some(rp)) = (
            &self.render_context,
            &self.slice_pipeline,
            &self.render_pipeline,
        ) else { return };

        let pos = self.camera.position;
        let slice_params = SliceParams {
            slice_w: self.camera.get_slice_w(),
            tetrahedron_count: self.geometry.tetrahedron_count() as u32,
            _padding: [0.0; 2],
            camera_matrix: self.camera.rotation_matrix(),
            camera_eye: [pos.x, pos.y, pos.z],
            _padding2: 0.0,
            camera_position: [pos.x, pos.y, pos.z, pos.w],
        };
        sp.update_params(&ctx.queue, &slice_params);

        let render_uniforms = RenderUniforms {
            view_matrix: [[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0],
                          [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0]],
            projection_matrix: perspective_matrix(
                std::f32::consts::FRAC_PI_4, ctx.aspect_ratio(), 0.1, 100.0,
            ),
            light_dir: [0.5, 1.0, 0.3],
            _padding: 0.0,
            ambient_strength: 0.3,
            diffuse_strength: 0.7,
            w_color_strength: 0.5,
            w_range: 2.0,
        };
        rp.update_uniforms(&ctx.queue, &render_uniforms);

        let output = match ctx.surface.get_current_texture() {
            Ok(o) => o,
            Err(_) => return,
        };
        let view = output.texture.create_view(&wgpu::TextureViewDescriptor::default());
        let mut encoder = ctx.device.create_command_encoder(
            &wgpu::CommandEncoderDescriptor { label: None },
        );

        sp.reset_counter(&ctx.queue);
        sp.run_slice_pass(&mut encoder);
        rp.prepare_indirect_draw(&mut encoder, sp.counter_buffer());
        rp.render(&mut encoder, &view, sp.output_buffer(),
            wgpu::Color { r: 0.02, g: 0.02, b: 0.08, a: 1.0 });

        ctx.queue.submit(std::iter::once(encoder.finish()));
        output.present();
    }
}

Step 6: Main Function #

fn main() {
    env_logger::init();
    let event_loop = EventLoop::new().expect("Failed to create event loop");
    event_loop.set_control_flow(ControlFlow::Poll);
    let mut app = MyApp::new();
    event_loop.run_app(&mut app).expect("Event loop error");
}

Running Your Scene #

cargo run --example my_first_scene --release

You will see a red tesseract at W=0. The green (W=+2) and blue (W=-2) tesseracts are invisible until you add input handling to move along the W-axis.


Next Steps #

Read the Documentation #

Experiment #

  • Work through all four examples in order
  • Modify example 03 to experiment with physics
  • Add keyboard input to your scene (see example 02)

Inspiration #


Quick Reference #

Common Types #

Type Description
World Container for all entities
Entity Object with shape, transform, material
Transform4D Position, rotation, scale in 4D
Vec4 4D vector (x, y, z, w)
Camera4D Viewpoint for rendering
Tesseract4D 4D hypercube primitive
Hyperplane4D 4D floor/surface primitive

Crates #

Crate Purpose
rust4d_math 4D vector and matrix math
rust4d_core World, entities, transforms, shapes
rust4d_render GPU rendering pipeline
rust4d_physics 4D physics simulation
rust4d_input Input handling and camera

Happy exploring in 4D space!