use std::sync::Arc;
use std::time::Instant;
use glam::{DVec3, IVec3};
use roxlap_core::opticast::OpticastSettings;
use roxlap_core::Camera;
use roxlap_render::{
Backend, BackendPreference, DitherMode, Feature, FrameParams, Line3, OverlayColor,
PosterizeConfig, RenderOptions, RenderResolution, Rgb, SceneRenderer, VoxColor,
};
use roxlap_scene::{GridTransform, Scene};
use winit::application::ApplicationHandler;
use winit::event::WindowEvent;
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::window::{Window, WindowId};
const GRASS: VoxColor = VoxColor(0x80_4d_8a_3a);
const DOME: VoxColor = VoxColor(0x80_40_60_c0);
const PILLAR: VoxColor = VoxColor(0x80_b0_a0_88);
const SKY: Rgb = Rgb(0x00_8f_bc_d4);
fn build_scene() -> Scene {
let mut scene = Scene::new();
let id = scene.add_grid(GridTransform::at(DVec3::ZERO));
let grid = scene.grid_mut(id).expect("grid just added");
grid.set_rect(
IVec3::new(-320, -320, 210),
IVec3::new(319, 319, 254),
Some(GRASS),
);
grid.set_sphere(IVec3::new(0, 0, 205), 30, Some(DOME));
for i in 0..12 {
let y = -280 + i * 48;
for x in [-90, 90] {
grid.set_rect(
IVec3::new(x - 4, y - 4, 160),
IVec3::new(x + 4, y + 4, 209),
Some(PILLAR),
);
}
}
scene
}
fn wire_box(lo: [f64; 3], hi: [f64; 3], color: OverlayColor) -> Vec<Line3> {
let p = [
[lo[0], lo[1], lo[2]],
[hi[0], lo[1], lo[2]],
[lo[0], hi[1], lo[2]],
[hi[0], hi[1], lo[2]],
[lo[0], lo[1], hi[2]],
[hi[0], lo[1], hi[2]],
[lo[0], hi[1], hi[2]],
[hi[0], hi[1], hi[2]],
];
const EDGES: [(usize, usize); 12] = [
(0, 1),
(1, 3),
(3, 2),
(2, 0), (4, 5),
(5, 7),
(7, 6),
(6, 4), (0, 4),
(1, 5),
(2, 6),
(3, 7), ];
EDGES
.map(|(a, b)| Line3 {
a: p[a],
b: p[b],
color, width_px: 1.5,
depth_test: true, })
.to_vec()
}
#[derive(Default)]
struct App {
renderer: Option<SceneRenderer>,
window: Option<Arc<Window>>,
scene: Option<Scene>,
started: Option<Instant>,
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let window = Arc::new(
event_loop
.create_window(Window::default_attributes().with_title("roxlap book_pipeline"))
.expect("create window"),
);
let size = window.inner_size();
let want_gpu = std::env::var_os("ROXLAP_GPU").is_none_or(|v| v != "0");
let opts = RenderOptions {
backend: if want_gpu {
BackendPreference::PreferGpu
} else {
BackendPreference::Cpu
},
clear_sky: SKY,
..RenderOptions::default()
};
let mut renderer =
match SceneRenderer::try_new(window.clone(), (size.width, size.height), &opts) {
Ok(r) => r,
Err(e) => {
eprintln!("cannot create a render surface: {e}");
event_loop.exit();
return;
}
};
match renderer.backend() {
Backend::Gpu => log::info!(
"GPU backend: {}",
renderer.adapter_info().unwrap_or("unknown adapter")
),
Backend::Cpu => log::info!("CPU backend (software per-pixel DDA)"),
}
if !renderer.supports(Feature::FreePickDepth) {
log::info!("per-frame depth picks are expensive here; use view_ray + raycast");
}
renderer.set_render_resolution(RenderResolution::Fixed { w: 320, h: 180 });
renderer.set_ssaa(2);
renderer.set_posterize(Some(PosterizeConfig::uniform(6, DitherMode::Bayer4x4)));
let (rw, rh) = renderer.render_dims(); let (lw, lh) = renderer.logical_dims(); log::info!("marching {rw}×{rh}, resolving to {lw}×{lh}, upscaling to the window");
self.renderer = Some(renderer);
self.window = Some(window);
self.scene = Some(build_scene());
self.started = Some(Instant::now());
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
let (Some(renderer), Some(scene)) = (self.renderer.as_mut(), self.scene.as_mut()) else {
return;
};
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::Resized(size) => renderer.resize(size.width.max(1), size.height.max(1)),
WindowEvent::RedrawRequested => {
let t = self.started.map_or(0.0, |s| s.elapsed().as_secs_f64());
let camera = Camera::orbit(t * 0.25, 0.3, 260.0, [0.0, 0.0, 195.0]);
let window = self.window.as_ref().expect("window outlives renderer");
let size = window.inner_size();
let settings =
OpticastSettings::for_oracle_framebuffer(size.width.max(1), size.height.max(1));
let mut frame = FrameParams::new(&settings);
frame.sky_color = SKY;
frame.fog_color = SKY;
frame.fog_max_scan_dist = 700;
renderer.render(scene, &camera, &frame);
let gizmo = wire_box(
[-32.0, -32.0, 173.0],
[32.0, 32.0, 237.0],
OverlayColor(0xff_ff_d0_40),
);
renderer.draw_lines(&camera, &gizmo);
renderer.present();
window.request_redraw();
}
_ => {}
}
}
fn exiting(&mut self, _event_loop: &ActiveEventLoop) {
if let Some(renderer) = self.renderer.as_mut() {
renderer.wait_idle();
}
}
}
fn main() {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let event_loop = EventLoop::new().expect("create event loop");
event_loop
.run_app(&mut App::default())
.expect("run event loop");
}