304 lines
7.8 KiB
Rust
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, ¶ms)
|
|
.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);
|
|
}
|
|
}
|