#![allow(clippy::many_single_char_names)]
#![allow(
clippy::cast_possible_truncation,
clippy::cast_possible_wrap,
clippy::cast_sign_loss,
clippy::cast_precision_loss
)]
use std::num::NonZeroU32;
use std::rc::Rc;
use std::time::Instant;
use roxlap_cavegen::{pack_dense_grid_to_vxl, MAXZDIM};
use roxlap_core::opticast;
use roxlap_core::rasterizer::ScratchPool;
use roxlap_core::scalar_rasterizer::ScalarRasterizer;
use roxlap_core::{update_lighting, Camera, Engine, GridView, OpticastSettings};
use roxlap_formats::edit::set_rect;
use roxlap_formats::vxl::Vxl;
use winit::application::ApplicationHandler;
use winit::dpi::LogicalSize;
use winit::event::{DeviceEvent, DeviceId, ElementState, KeyEvent, MouseButton, WindowEvent};
use winit::event_loop::{ActiveEventLoop, ControlFlow, EventLoop};
use winit::keyboard::{KeyCode, PhysicalKey};
use winit::window::{CursorGrabMode, Window, WindowId};
const WIDTH: u32 = 800;
const HEIGHT: u32 = 600;
const VSID: u32 = 32;
const GROUND_Z: i32 = 200;
const CUBE_EDGE: i32 = 10;
const GROUND_COL: u32 = 0x80_5a_a0_5a; const CUBE_COL: u32 = 0x80_c0_60_30;
const MOVE_SPEED: f64 = 16.0;
const FAST_MULT: f64 = 4.0;
const MOUSE_SENS: f64 = 0.0025;
const PITCH_LIMIT: f64 = 88.0_f64 * std::f64::consts::PI / 180.0;
fn build_world() -> Vxl {
let vsid_u = VSID as usize;
let maxz_u = MAXZDIM as usize;
let cells = vsid_u * vsid_u * maxz_u;
let mut mask = vec![0u8; cells];
let mut colour = vec![0u32; cells];
let idx = |x: usize, y: usize, z: usize| -> usize { (y * vsid_u + x) * maxz_u + z };
for y in 0..vsid_u {
for x in 0..vsid_u {
let i = idx(x, y, GROUND_Z as usize);
mask[i] = 1;
colour[i] = GROUND_COL;
}
}
let mut world = pack_dense_grid_to_vxl(&mask, &colour, VSID);
world.reserve_edit_capacity(64 * 1024);
let cx = (VSID as i32) / 2;
let cy = (VSID as i32) / 2;
let half = CUBE_EDGE / 2;
let lo = [cx - half, cy - half, GROUND_Z - CUBE_EDGE];
let hi = [cx + half - 1, cy + half - 1, GROUND_Z - 1];
set_rect(&mut world, lo, hi, Some(CUBE_COL));
world
}
struct Cam {
pos: [f64; 3],
yaw: f64,
pitch: f64,
}
impl Cam {
fn camera(&self) -> Camera {
let (sy, cy) = self.yaw.sin_cos();
let (sp, cp) = self.pitch.sin_cos();
let forward = [cy * cp, sy * cp, sp];
let right = [-sy, cy, 0.0];
let down = [
forward[1] * right[2] - forward[2] * right[1],
forward[2] * right[0] - forward[0] * right[2],
forward[0] * right[1] - forward[1] * right[0],
];
Camera {
pos: self.pos,
right,
down,
forward,
}
}
}
#[derive(Default, Clone, Copy)]
struct Keys(u8);
impl Keys {
const FWD: u8 = 1 << 0;
const BACK: u8 = 1 << 1;
const LEFT: u8 = 1 << 2;
const RIGHT: u8 = 1 << 3;
const UP: u8 = 1 << 4;
const DOWN: u8 = 1 << 5;
const FAST: u8 = 1 << 6;
fn set(&mut self, mask: u8, on: bool) {
if on {
self.0 |= mask;
} else {
self.0 &= !mask;
}
}
fn has(self, mask: u8) -> bool {
self.0 & mask != 0
}
}
struct App {
window: Option<Rc<Window>>,
surface: Option<softbuffer::Surface<Rc<Window>, Rc<Window>>>,
engine: Engine,
world: Vxl,
zbuffer: Vec<f32>,
pool: ScratchPool,
cam: Cam,
keys: Keys,
grabbed: bool,
last_tick: Option<Instant>,
}
impl App {
fn new() -> Self {
let mut world = build_world();
let mut engine = Engine::new();
engine.set_side_shades(15, 15, 15, 15, 15, 15);
engine.set_lightmode(1);
update_lighting(
&mut world.data,
&world.column_offset,
world.vsid,
0,
0,
0,
world.vsid as i32,
world.vsid as i32,
MAXZDIM,
engine.lightmode(),
engine.lights(),
);
let cx = f64::from(VSID) * 0.5;
let cy = f64::from(VSID) * 0.5;
let cz = f64::from(GROUND_Z) - f64::from(CUBE_EDGE) - 6.0;
let cam = Cam {
pos: [cx - 16.0, cy, cz],
yaw: 0.0,
pitch: 0.15, };
let pool = ScratchPool::new(WIDTH, HEIGHT, world.vsid);
Self {
window: None,
surface: None,
engine,
world,
zbuffer: Vec::new(),
pool,
cam,
keys: Keys::default(),
grabbed: false,
last_tick: None,
}
}
fn integrate(&mut self, dt: f64) {
if dt <= 0.0 {
return;
}
let speed = MOVE_SPEED
* if self.keys.has(Keys::FAST) {
FAST_MULT
} else {
1.0
};
let cam = self.cam.camera();
let mut d = [0.0; 3];
let add = |d: &mut [f64; 3], v: [f64; 3], s: f64| {
for (di, vi) in d.iter_mut().zip(v.iter()) {
*di += vi * s;
}
};
if self.keys.has(Keys::FWD) {
add(&mut d, cam.forward, 1.0);
}
if self.keys.has(Keys::BACK) {
add(&mut d, cam.forward, -1.0);
}
if self.keys.has(Keys::RIGHT) {
add(&mut d, cam.right, 1.0);
}
if self.keys.has(Keys::LEFT) {
add(&mut d, cam.right, -1.0);
}
if self.keys.has(Keys::UP) {
d[2] -= 1.0;
}
if self.keys.has(Keys::DOWN) {
d[2] += 1.0;
}
let mag = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
if mag <= 1e-6 {
return;
}
let s = speed * dt / mag;
self.cam.pos[0] += d[0] * s;
self.cam.pos[1] += d[1] * s;
self.cam.pos[2] += d[2] * s;
}
fn redraw(&mut self) {
let Some(window) = self.window.as_ref() else {
return;
};
let size = window.inner_size();
let (Some(w_nz), Some(h_nz)) = (NonZeroU32::new(size.width), NonZeroU32::new(size.height))
else {
return;
};
let now = Instant::now();
let dt = self
.last_tick
.map_or(0.0, |t| (now - t).as_secs_f64().min(0.1));
self.last_tick = Some(now);
self.integrate(dt);
let pixel_count = (size.width as usize) * (size.height as usize);
if self.zbuffer.len() < pixel_count {
self.zbuffer.resize(pixel_count, 0.0);
}
if self.pool.slot(0).uurend_half_stride < size.width as usize {
self.pool = ScratchPool::new(size.width, size.height, self.world.vsid);
}
let sky_col_i = i32::from_ne_bytes(self.engine.sky_color().to_ne_bytes());
self.pool.set_skycast(sky_col_i, 0);
let s = self.engine.side_shades();
self.pool
.set_side_shades(s[0], s[1], s[2], s[3], s[4], s[5]);
let cam = self.cam.camera();
let sky = self.engine.sky_color();
let settings = OpticastSettings::for_oracle_framebuffer(size.width, size.height);
let pitch_pixels = size.width as usize;
let Some(surface) = self.surface.as_mut() else {
return;
};
surface.resize(w_nz, h_nz).expect("softbuffer: resize");
let mut buffer = surface.buffer_mut().expect("softbuffer: buffer_mut");
for px in buffer.iter_mut() {
*px = sky;
}
{
let grid = GridView::from_single_vxl(&self.world);
let mut rasterizer =
ScalarRasterizer::new(&mut buffer, &mut self.zbuffer, pitch_pixels, grid);
let _ = opticast(&mut rasterizer, &mut self.pool, &cam, &settings, grid);
}
buffer.present().expect("softbuffer: present");
}
fn set_grabbed(&mut self, grabbed: bool) {
let Some(window) = self.window.as_ref() else {
return;
};
if grabbed {
let r = window
.set_cursor_grab(CursorGrabMode::Locked)
.or_else(|_| window.set_cursor_grab(CursorGrabMode::Confined));
if r.is_ok() {
window.set_cursor_visible(false);
self.grabbed = true;
}
} else {
let _ = window.set_cursor_grab(CursorGrabMode::None);
window.set_cursor_visible(true);
self.grabbed = false;
}
}
}
impl ApplicationHandler for App {
fn resumed(&mut self, event_loop: &ActiveEventLoop) {
let attrs = Window::default_attributes()
.with_title("roxlap — hello")
.with_inner_size(LogicalSize::new(f64::from(WIDTH), f64::from(HEIGHT)));
let window = Rc::new(event_loop.create_window(attrs).expect("create window"));
let context = softbuffer::Context::new(window.clone()).expect("softbuffer: Context::new");
let surface =
softbuffer::Surface::new(&context, window.clone()).expect("softbuffer: Surface::new");
self.window = Some(window);
self.surface = Some(surface);
}
fn window_event(&mut self, event_loop: &ActiveEventLoop, _id: WindowId, event: WindowEvent) {
match event {
WindowEvent::CloseRequested => event_loop.exit(),
WindowEvent::RedrawRequested => self.redraw(),
WindowEvent::MouseInput {
state: ElementState::Pressed,
button: MouseButton::Left,
..
} if !self.grabbed => {
self.set_grabbed(true);
}
WindowEvent::Focused(false) => {
self.keys = Keys::default();
if self.grabbed {
self.set_grabbed(false);
}
}
WindowEvent::KeyboardInput {
event:
KeyEvent {
physical_key: PhysicalKey::Code(code),
state,
repeat,
..
},
..
} => {
if repeat {
return;
}
let pressed = state == ElementState::Pressed;
match code {
KeyCode::Escape if pressed => {
if self.grabbed {
self.set_grabbed(false);
} else {
event_loop.exit();
}
}
KeyCode::KeyW => self.keys.set(Keys::FWD, pressed),
KeyCode::KeyS => self.keys.set(Keys::BACK, pressed),
KeyCode::KeyA => self.keys.set(Keys::LEFT, pressed),
KeyCode::KeyD => self.keys.set(Keys::RIGHT, pressed),
KeyCode::Space => self.keys.set(Keys::UP, pressed),
KeyCode::ShiftLeft | KeyCode::ShiftRight => self.keys.set(Keys::DOWN, pressed),
KeyCode::ControlLeft | KeyCode::ControlRight => {
self.keys.set(Keys::FAST, pressed);
}
_ => {}
}
}
_ => {}
}
}
fn device_event(&mut self, _el: &ActiveEventLoop, _id: DeviceId, event: DeviceEvent) {
if !self.grabbed {
return;
}
if let DeviceEvent::MouseMotion { delta: (dx, dy) } = event {
self.cam.yaw += dx * MOUSE_SENS;
self.cam.pitch = (self.cam.pitch + dy * MOUSE_SENS).clamp(-PITCH_LIMIT, PITCH_LIMIT);
}
}
fn about_to_wait(&mut self, _el: &ActiveEventLoop) {
if let Some(w) = self.window.as_ref() {
w.request_redraw();
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let event_loop = EventLoop::new()?;
event_loop.set_control_flow(ControlFlow::Poll);
let mut app = App::new();
event_loop.run_app(&mut app)?;
Ok(())
}