Files
wsg/docs/user/quickstart.md
T
Jérôme Bousquié d4c2d93fc5 eng doc
2026-09-25 20:06:10 +02:00

4.2 KiB

Quickstart

Get a window with a rotating cube on screen in ~30 lines. The full version with comments is in the simple example (2D quad, unlit) and cube (3D cube, lit).

1. Add the dependency

# Cargo.toml
[dependencies]
wsg-lib = { path = "../lib" }
glam = "0.29"        # Vec3/Quat — re-exported but you need it in your own code
winit = "0.30"       # KeyCode/MouseButton for the input (only if you use app.input)

The workspace pins glam 0.29 and winit 0.30; match these versions to avoid type mismatches.

2. Implement AppHandler

Three mandatory methods (setup, update, render) and an optional event hook.

use glam::{Quat, Vec3};
use winit::event::MouseButton;
use winit::keyboard::KeyCode;
use wsg_lib::camera::CameraController;
use wsg_lib::prelude::*;

struct MyHandler {
    camera: CameraController,
}

impl AppHandler for MyHandler {
    fn new() -> Self {
        Self { camera: CameraController::default() }
    }

    fn setup(&mut self, app: &mut App) -> Result<(), String> {
        // A cube (primitive) + the standard material.
        app.scene.create_mesh("cube_mesh", cube(1.0), Some("cube_mat"))?;
        app.scene.add_entity("cube", "cube_mesh")?;

        // A warm directional light + shadows on it.
        app.scene
            .add_directional_light(Vec3::new(1.0, 1.2, 1.0).normalize(), [1.0, 0.98, 0.92], 1.5)?;
        app.scene.set_shadow_caster(Some(0));
        Ok(())
    }

    fn update(&mut self, app: &mut App) {
        // Spin the cube.
        let mut tf = *app.scene.entity_transform("cube").unwrap();
        tf.rotation = Quat::from_rotation_y(self.t) * tf.rotation;
        app.scene.set_entity_transform("cube", tf);
        self.t += 0.02;

        // Camera: orbit (left-drag), zoom (wheel), reset (R), presets (1/2/3).
        let (dx, dy) = app.input.mouse_delta();
        if app.input.mouse_button_held(MouseButton::Left) {
            self.camera.orbit(dx, dy);
        }
        let (_, sy) = app.input.scroll_delta();
        self.camera.zoom(sy);
        if app.input.key_pressed(KeyCode::KeyR) {
            self.camera.yaw = 0.6;
            self.camera.pitch = 0.35;
            self.camera.distance = 6.5;
        }
        self.camera.apply_to(app.scene.camera_mut());
    }

    fn render(&mut self, app: &mut App) -> Result<(), String> {
        // Default implementation: renders the whole scene. Override only for custom passes.
        app.renderer().render_scene(app.context())
    }
}

3. Build the app

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let app = AppBuilder::new()
        .title("My WSG app")
        .size(1024, 768)
        .with_culling(true)      // optional: skip off-screen entities
        .with_shadows()          // optional: enable shadow mapping
        .build()
        .await?;

    let mut handler = MyHandler::new();
    app.run(&mut handler).await?;
    Ok(())
}

AppBuilder methods you will use early:

Method Purpose
.title(…) / .size(w, h) Window
.with_vsync(false) / .with_frame_limit(n) Frame pacing (vsync off + 144 fps cap in the demo)
.with_culling(true) Opt-in frustum culling (see GPU-driven)
.with_shadows() Opt-in shadow mapping (see Shadows)
.with_hdr(ToneMapper::Aces) Opt-in HDR + tone mapping (see HDR)

4. Run it

cargo run -p wsg-lib --example cube   # the reference "hello world" of the engine

Controls (in the cube/demo examples): left-drag orbit, wheel zoom, R reset camera, 1/2/3 view presets, H help overlay, Esc quit.

5. Where to go next