9eead719b0
Full project snapshot migrated to new Gitea remote without history: engine, editor, physics, script, examples, tests, docs, and assets. Relicensed from GPLv3 to MIT and updated repo URLs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
125 lines
4.1 KiB
Rust
125 lines
4.1 KiB
Rust
//! Stage 4 example: load primitive meshes and render them lit, in 3D.
|
|
//!
|
|
//! Run with:
|
|
//! cargo run -p oxide-examples --bin hello_mesh
|
|
//!
|
|
//! Shows a spinning cube, a sphere, and a ground plane drawn through the
|
|
//! [`ForwardRenderer`] with a single directional light. Esc quits.
|
|
|
|
#![deny(warnings)]
|
|
|
|
use oxide_engine::math::Quat;
|
|
use oxide_engine::prelude::*;
|
|
use oxide_engine::window::event::{ElementState, Key, NamedKey, WindowEvent};
|
|
use oxide_engine::window::RenderCtx;
|
|
|
|
/// GPU resources, built lazily on the first frame (once the surface format is
|
|
/// known) and reused thereafter.
|
|
struct Gpu3d {
|
|
pipeline: RenderPipeline,
|
|
cube: GpuMesh,
|
|
sphere: GpuMesh,
|
|
plane: GpuMesh,
|
|
}
|
|
|
|
#[derive(Default)]
|
|
struct HelloMesh {
|
|
angle: f32,
|
|
gpu: Option<Gpu3d>,
|
|
}
|
|
|
|
impl WindowApp for HelloMesh {
|
|
fn init(&mut self, ctx: &mut AppCtx<'_>) {
|
|
ctx.set_clear_color(Color::rgb(0.05, 0.06, 0.09));
|
|
log::info!("hello_mesh: spinning cube + sphere + ground plane (Esc quits)");
|
|
}
|
|
|
|
fn event(&mut self, ctx: &mut AppCtx<'_>, event: &WindowEvent) {
|
|
if let WindowEvent::KeyboardInput { event: key, .. } = event {
|
|
if key.state == ElementState::Pressed && key.logical_key == Key::Named(NamedKey::Escape)
|
|
{
|
|
ctx.request_exit();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn update(&mut self, ctx: &mut AppCtx<'_>) {
|
|
self.angle += ctx.dt;
|
|
}
|
|
|
|
fn render(&mut self, ctx: &RenderCtx<'_>) {
|
|
let device = ctx.gpu.device();
|
|
let queue = ctx.gpu.queue();
|
|
|
|
let gpu = self.gpu.get_or_insert_with(|| {
|
|
// The window runner already clears the surface to the configured
|
|
// clear color before `render`, so the viewport pipeline is just the
|
|
// forward pass (no clear pass needed here).
|
|
let mut pipeline = RenderPipeline::new();
|
|
pipeline.add_pass("forward", ForwardPass::new(device, ctx.surface_format));
|
|
Gpu3d {
|
|
pipeline,
|
|
cube: Mesh::cube().upload(device, "cube"),
|
|
sphere: Mesh::uv_sphere(0.8, 32, 16).upload(device, "sphere"),
|
|
plane: Mesh::plane(12.0).upload(device, "plane"),
|
|
}
|
|
});
|
|
|
|
// Orbit the camera slowly around the scene.
|
|
let eye = Vec3::new(
|
|
4.0 * (self.angle * 0.3).cos(),
|
|
2.6,
|
|
4.0 * (self.angle * 0.3).sin(),
|
|
);
|
|
let view = Transform::looking_at(eye, Vec3::new(0.0, 0.2, 0.0), Vec3::Y);
|
|
let camera = Camera::default();
|
|
|
|
let objects = [
|
|
RenderObject {
|
|
mesh: &gpu.plane,
|
|
material: Material::diffuse(Color::rgb(0.25, 0.27, 0.30)),
|
|
transform: Transform::from_translation(Vec3::new(0.0, -1.0, 0.0)),
|
|
},
|
|
RenderObject {
|
|
mesh: &gpu.cube,
|
|
material: Material::diffuse(Color::rgb(0.85, 0.20, 0.15)),
|
|
transform: Transform::from_trs(
|
|
Vec3::new(-1.3, 0.0, 0.0),
|
|
Quat::from_euler(oxide_engine::math::EulerRot::YXZ, self.angle, 0.4, 0.0),
|
|
Vec3::ONE,
|
|
),
|
|
},
|
|
RenderObject {
|
|
mesh: &gpu.sphere,
|
|
material: Material::metal(Color::rgb(0.55, 0.65, 0.95), 0.35),
|
|
transform: Transform::from_translation(Vec3::new(1.3, 0.2, 0.0)),
|
|
},
|
|
];
|
|
|
|
let lighting = Lighting::default();
|
|
gpu.pipeline.render(&mut FrameContext {
|
|
device,
|
|
queue,
|
|
color: ctx.view,
|
|
size: ctx.size,
|
|
viewport_rect: None,
|
|
clear_color: Color::rgb(0.05, 0.06, 0.09),
|
|
camera: &camera,
|
|
view_transform: &view,
|
|
lighting: &lighting,
|
|
objects: &objects,
|
|
});
|
|
}
|
|
}
|
|
|
|
fn main() -> anyhow::Result<()> {
|
|
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
|
|
let config = WindowConfig {
|
|
title: "Oxide — hello_mesh".to_string(),
|
|
width: 960,
|
|
height: 540,
|
|
..Default::default()
|
|
};
|
|
run(config, HelloMesh::default())
|
|
}
|