refraction/src/bin/wireframe/main.rs
2024-09-23 00:04:16 +03:00

304 lines
7.8 KiB
Rust

use std::{collections::HashSet, time::Instant};
use glam::{mat4, vec2, vec3, vec4, Mat4, Quat, Vec2, Vec3};
use glium::{
backend::{glutin::SimpleWindowBuilder, Facade},
glutin::config::ConfigTemplateBuilder,
implement_vertex,
index::PrimitiveType,
uniform,
winit::event::{Event, WindowEvent},
DrawParameters, Program, Surface, VertexBuffer,
};
use winit::{
event::ElementState,
event_loop::EventLoop,
keyboard::{KeyCode, PhysicalKey},
};
mod scene;
// The coordinate system:
// * X: forward
// * Y: left
// * Z: up
#[derive(Copy, Clone)]
struct Vertex {
position: [f32; 3],
}
implement_vertex!(Vertex, position);
struct DragCtl<S> {
state: S,
ctl: Option<(Vec2, S)>,
}
impl<S: Copy> DragCtl<S> {
pub fn new(state: S) -> Self {
Self { state, ctl: None }
}
pub fn state(&self) -> S {
self.state
}
pub fn on_button(&mut self, pos: Vec2, state: ElementState) {
match state {
ElementState::Pressed => {
if self.ctl.is_none() {
self.ctl = Some((pos, self.state));
}
}
ElementState::Released => {
self.ctl = None;
}
}
}
pub fn on_move(&mut self, pos: Vec2, f: impl FnOnce(S, Vec2) -> S) {
if let Some((old_pos, old_state)) = self.ctl {
self.state = f(old_state, pos - old_pos);
}
}
}
struct Wireframe {
color: Vec3,
mode: PrimitiveType,
data: VertexBuffer<Vertex>,
}
fn prepare_scene(display: &impl Facade) -> Vec<Wireframe> {
scene::build()
.into_iter()
.map(|line| {
let color = line.color;
let mode;
let data: Vec<Vertex>;
match line.line {
scene::Line::Lines(_) => todo!(),
scene::Line::Strip(pts) => {
mode = PrimitiveType::LineStrip;
data = pts
.into_iter()
.map(|p| Vertex {
position: p.to_array(),
})
.collect();
}
scene::Line::Loop(pts) => {
mode = PrimitiveType::LineLoop;
data = pts
.into_iter()
.map(|p| Vertex {
position: p.to_array(),
})
.collect();
}
};
let data = VertexBuffer::new(display, &data).unwrap();
Wireframe { color, mode, data }
})
.collect()
}
fn main() {
let event_loop = EventLoop::builder().build().unwrap();
let cfg = ConfigTemplateBuilder::new().with_multisampling(8);
let (window, display) = SimpleWindowBuilder::new()
.with_config_template_builder(cfg)
.with_title("Refraction: Wireframe")
.build(&event_loop);
let mut keys_pressed = HashSet::<PhysicalKey>::new();
let vs_src = include_str!("ray.v.glsl");
let fs_src = include_str!("ray.f.glsl");
let program = Program::from_source(&display, vs_src, fs_src, None).unwrap();
let scene = prepare_scene(&display);
let rot = Quat::from_euler(glam::EulerRot::ZYX, std::f32::consts::FRAC_PI_4, 0., 0.);
let mut cur_pos = vec2(0., 0.);
let mut cam_pos = rot * vec3(-200., 0., 50.);
let mut cam_rot = DragCtl::new(rot);
let mut t1 = Instant::now();
#[allow(deprecated)]
event_loop
.run(move |ev, window_target| match ev {
Event::WindowEvent { event, .. } => match event {
WindowEvent::RedrawRequested => {
let dt = {
let t2 = Instant::now();
let dt = t2 - t1;
t1 = t2;
dt.as_secs_f32()
};
let v: Vec3 = {
let ctl_left = keys_pressed.contains(&PhysicalKey::Code(KeyCode::KeyA));
let ctl_right = keys_pressed.contains(&PhysicalKey::Code(KeyCode::KeyD));
let ctl_fwd = keys_pressed.contains(&PhysicalKey::Code(KeyCode::KeyW));
let ctl_bwd = keys_pressed.contains(&PhysicalKey::Code(KeyCode::KeyS));
let ctl_up = keys_pressed.contains(&PhysicalKey::Code(KeyCode::Space));
let ctl_down =
keys_pressed.contains(&PhysicalKey::Code(KeyCode::ShiftLeft));
[
(ctl_left, vec3(0., 1., 0.)),
(ctl_right, vec3(0., -1., 0.)),
(ctl_fwd, vec3(1., 0., 0.)),
(ctl_bwd, vec3(-1., 0., 0.)),
(ctl_up, vec3(0., 0., 1.)),
(ctl_down, vec3(0., 0., -1.)),
]
.into_iter()
.filter_map(|(ctl, dir)| ctl.then_some(dir))
.sum()
};
cam_pos += 100. * dt * (cam_rot.state() * v);
let size = window.inner_size();
let size = vec2(size.width as f32, size.height as f32).normalize()
* std::f32::consts::SQRT_2;
let proj = make_proj_matrix(vec3(size.x, size.y, 2.), (1., 4096.));
let my_to_gl = mat4(
vec4(0., 0., 1., 0.),
vec4(-1., 0., 0., 0.),
vec4(0., 1., 0., 0.),
vec4(0., 0., 0., 1.),
);
let view = my_to_gl
* Mat4::from_quat(cam_rot.state().inverse())
* Mat4::from_translation(-cam_pos);
let mut target = display.draw();
target.clear_color(0.0, 0.0, 0.0, 0.0);
let mvp = proj * view;
let params = DrawParameters {
blend: glium::Blend {
color: glium::BlendingFunction::Addition {
source: glium::LinearBlendingFactor::One,
destination: glium::LinearBlendingFactor::OneMinusSourceAlpha,
},
alpha: glium::BlendingFunction::Addition {
source: glium::LinearBlendingFactor::One,
destination: glium::LinearBlendingFactor::OneMinusSourceAlpha,
},
constant_value: (0., 0., 0., 0.),
},
line_width: Some(3.),
smooth: Some(glium::Smooth::Nicest),
..Default::default()
};
for mesh in &scene {
let uniforms = uniform! {
mvp: mvp.to_cols_array_2d(),
color: mesh.color.to_array(),
};
let indices = glium::index::NoIndices(mesh.mode);
target
.draw(&mesh.data, &indices, &program, &uniforms, &params)
.unwrap();
}
target.finish().unwrap();
}
WindowEvent::MouseInput {
device_id: _,
state,
button,
} => match button {
winit::event::MouseButton::Right => cam_rot.on_button(cur_pos, state),
_ => {}
},
WindowEvent::CursorMoved {
device_id: _,
position,
} => {
let size = window.inner_size();
let size = vec2(size.width as f32, size.height as f32);
cur_pos = vec2(position.x as f32, position.y as f32) / size.length();
cam_rot.on_move(cur_pos, |init, off| {
window.request_redraw();
init * Quat::from_euler(glam::EulerRot::ZYX, 2. * off.x, -2. * off.y, 0.)
});
}
WindowEvent::KeyboardInput {
device_id: _,
event,
is_synthetic: _,
} => {
match event.state {
ElementState::Pressed => keys_pressed.insert(event.physical_key),
ElementState::Released => keys_pressed.remove(&event.physical_key),
};
}
WindowEvent::Resized(window_size) => {
display.resize(window_size.into());
}
WindowEvent::CloseRequested => {
window_target.exit();
}
_ => (),
},
Event::AboutToWait => {
window.request_redraw();
}
_ => (),
})
.unwrap();
}
/// Make a projection matrix, assuming input coordinates are (right, up, forward).
///
/// `corner` is a vector that will be mapped to (x=1, y=1) after the perspective division.
/// `zrange` is the Z range that will be mapped to z∈[-1, 1]. It has no other effect. Both ends have to be positive though.
fn make_proj_matrix(corner: Vec3, zrange: (f32, f32)) -> Mat4 {
let scale = 1.0 / corner;
let zspan = zrange.1 - zrange.0;
mat4(
scale.x * vec4(1., 0., 0., 0.),
scale.y * vec4(0., 1., 0., 0.),
scale.z * vec4(0., 0., (zrange.0 + zrange.1) / zspan, 1.),
scale.z * vec4(0., 0., -2. * zrange.0 * zrange.1 / zspan, 0.),
)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_abs_diff_eq;
use glam::vec3;
#[test]
fn test_proj_matrix() {
let m = make_proj_matrix(vec3(2., 3., 4.), (0.5, 20.0));
let v = m * vec4(2., 3., 4., 1.);
assert_abs_diff_eq!(v.x / v.w, 1.0);
assert_abs_diff_eq!(v.y / v.w, 1.0);
assert!(-v.w < v.z && v.z < v.w, "z out of range in {v}");
let v = m * vec4(2., 3., 0.5, 1.);
assert_abs_diff_eq!(v.x / v.w, 8.0);
assert_abs_diff_eq!(v.y / v.w, 8.0);
assert_abs_diff_eq!(v.z / v.w, -1.0);
let v = m * vec4(2., 3., 20.0, 1.);
assert_abs_diff_eq!(v.x / v.w, 0.2);
assert_abs_diff_eq!(v.y / v.w, 0.2);
assert_abs_diff_eq!(v.z / v.w, 1.0);
}
}