#[cfg(feature = "app_utils")]
use bincode::{Decode, Encode};
use lin_alg::f32::{Mat4, Quaternion, Vec3, Vec4};
use wgpu::{VertexAttribute, VertexBufferLayout, VertexFormat};
use crate::{
EntityUpdate, camera::Camera, gauss::Gaussian, lighting::Lighting, text_overlay::TextOverlay,
viewport_rect,
};
pub const F32_SIZE: usize = 4;
pub const VEC3_SIZE: usize = 3 * F32_SIZE;
pub const VEC4_SIZE: usize = 4 * F32_SIZE;
pub const VEC3_UNIFORM_SIZE: usize = 4 * F32_SIZE;
pub const MAT4_SIZE: usize = 16 * F32_SIZE;
pub const MAT3_SIZE: usize = 9 * F32_SIZE;
pub const VERTEX_SIZE: usize = 14 * F32_SIZE + 4;
pub const INSTANCE_SIZE: usize = MAT4_SIZE + MAT3_SIZE + VEC4_SIZE + F32_SIZE;
pub(crate) const VERTEX_LAYOUT: VertexBufferLayout<'static> = VertexBufferLayout {
array_stride: VERTEX_SIZE as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Vertex,
attributes: &[
VertexAttribute {
offset: 0,
shader_location: 0,
format: VertexFormat::Float32x3,
},
VertexAttribute {
offset: VEC3_SIZE as wgpu::BufferAddress,
shader_location: 1,
format: VertexFormat::Float32x2,
},
VertexAttribute {
offset: (2 * F32_SIZE + VEC3_SIZE) as wgpu::BufferAddress,
shader_location: 2,
format: VertexFormat::Float32x3,
},
VertexAttribute {
offset: (2 * F32_SIZE + 2 * VEC3_SIZE) as wgpu::BufferAddress,
shader_location: 3,
format: VertexFormat::Float32x3,
},
VertexAttribute {
offset: (2 * F32_SIZE + 3 * VEC3_SIZE) as wgpu::BufferAddress,
shader_location: 4,
format: VertexFormat::Float32x3,
},
VertexAttribute {
offset: (2 * F32_SIZE + 4 * VEC3_SIZE) as wgpu::BufferAddress,
shader_location: 5,
format: VertexFormat::Unorm8x4,
},
],
};
pub(crate) const INSTANCE_LAYOUT: VertexBufferLayout<'static> = VertexBufferLayout {
array_stride: INSTANCE_SIZE as wgpu::BufferAddress,
step_mode: wgpu::VertexStepMode::Instance,
attributes: &[
VertexAttribute {
offset: 0,
shader_location: 6,
format: VertexFormat::Float32x4,
},
VertexAttribute {
offset: (F32_SIZE * 4) as wgpu::BufferAddress,
shader_location: 7,
format: VertexFormat::Float32x4,
},
VertexAttribute {
offset: (F32_SIZE * 8) as wgpu::BufferAddress,
shader_location: 8,
format: VertexFormat::Float32x4,
},
VertexAttribute {
offset: (F32_SIZE * 12) as wgpu::BufferAddress,
shader_location: 9,
format: VertexFormat::Float32x4,
},
VertexAttribute {
offset: (MAT4_SIZE) as wgpu::BufferAddress,
shader_location: 10,
format: VertexFormat::Float32x3,
},
VertexAttribute {
offset: (MAT4_SIZE + VEC3_SIZE) as wgpu::BufferAddress,
shader_location: 11,
format: VertexFormat::Float32x3,
},
VertexAttribute {
offset: (MAT4_SIZE + VEC3_SIZE * 2) as wgpu::BufferAddress,
shader_location: 12,
format: VertexFormat::Float32x3,
},
VertexAttribute {
offset: (MAT4_SIZE + MAT3_SIZE) as wgpu::BufferAddress,
shader_location: 13,
format: VertexFormat::Float32x4,
},
VertexAttribute {
offset: (MAT4_SIZE + MAT3_SIZE + VEC4_SIZE) as wgpu::BufferAddress,
shader_location: 14,
format: VertexFormat::Float32,
},
],
};
#[cfg_attr(feature = "app_utils", derive(Encode, Decode))]
#[derive(Clone, Copy, Debug)]
pub struct Vertex {
pub position: [f32; 3],
pub tex_coords: [f32; 2],
pub normal: Vec3,
pub tangent: Vec3,
pub bitangent: Vec3,
pub color: Option<(u8, u8, u8, u8)>,
}
impl Vertex {
pub fn new(position: [f32; 3], normal: Vec3) -> Self {
Self {
position,
tex_coords: [0., 0.],
normal,
tangent: Vec3::new_zero(),
bitangent: Vec3::new_zero(),
color: None,
}
}
pub fn to_bytes(&self) -> [u8; VERTEX_SIZE] {
let mut result = [0; VERTEX_SIZE];
result[0..4].clone_from_slice(&self.position[0].to_ne_bytes());
result[4..8].clone_from_slice(&self.position[1].to_ne_bytes());
result[8..12].clone_from_slice(&self.position[2].to_ne_bytes());
result[12..16].clone_from_slice(&self.tex_coords[0].to_ne_bytes());
result[16..20].clone_from_slice(&self.tex_coords[1].to_ne_bytes());
result[20..32].clone_from_slice(&self.normal.to_bytes());
result[32..44].clone_from_slice(&self.tangent.to_bytes());
result[44..56].clone_from_slice(&self.bitangent.to_bytes());
if let Some(color) = self.color {
result[56..60].copy_from_slice(&[color.0, color.1, color.2, color.3]);
}
result
}
}
pub struct Instance {
pub position: Vec3,
pub orientation: Quaternion,
pub pivot: Option<Vec3>,
pub scale: Vec3,
pub color: Vec3,
pub opacity: f32,
pub shinyness: f32,
}
impl Instance {
pub fn to_bytes(&self) -> [u8; INSTANCE_SIZE] {
let mut result = [0; INSTANCE_SIZE];
let model_mat = match self.pivot {
Some(p) => {
Mat4::new_translation(self.position)
* Mat4::new_translation(p)
* self.orientation.to_matrix()
* Mat4::new_translation(-p)
* Mat4::new_scaler_partial(self.scale)
}
None => {
Mat4::new_translation(self.position)
* self.orientation.to_matrix()
* Mat4::new_scaler_partial(self.scale)
}
};
let normal_mat = self.orientation.to_matrix3();
result[0..MAT4_SIZE].clone_from_slice(&model_mat.to_bytes());
result[MAT4_SIZE..MAT4_SIZE + MAT3_SIZE].clone_from_slice(&normal_mat.to_bytes());
let mut color_buf = [0; VEC4_SIZE];
color_buf[0..F32_SIZE].clone_from_slice(&self.color.x.to_ne_bytes());
color_buf[F32_SIZE..2 * F32_SIZE].clone_from_slice(&self.color.y.to_ne_bytes());
color_buf[2 * F32_SIZE..3 * F32_SIZE].clone_from_slice(&self.color.z.to_ne_bytes());
color_buf[3 * F32_SIZE..4 * F32_SIZE].clone_from_slice(&self.opacity.to_ne_bytes());
result[MAT4_SIZE + MAT3_SIZE..INSTANCE_SIZE - F32_SIZE].clone_from_slice(&color_buf);
result[INSTANCE_SIZE - F32_SIZE..INSTANCE_SIZE]
.clone_from_slice(&self.shinyness.to_ne_bytes());
result
}
}
impl From<&Entity> for Instance {
fn from(entity: &Entity) -> Self {
let scale = match entity.scale_partial {
Some(s) => s,
None => Vec3::new(entity.scale, entity.scale, entity.scale),
};
Self {
position: entity.position,
orientation: entity.orientation,
pivot: entity.pivot,
scale,
color: Vec3::new(entity.color.0, entity.color.1, entity.color.2),
opacity: entity.opacity,
shinyness: entity.shinyness,
}
}
}
#[cfg_attr(feature = "app_utils", derive(Encode, Decode))]
#[derive(Clone, Debug, Default)]
pub struct Mesh {
pub vertices: Vec<Vertex>,
pub indices: Vec<usize>,
pub material: usize,
}
#[derive(Clone, Debug)]
pub struct Entity {
pub id: u32,
pub class: u32,
pub mesh: usize,
pub position: Vec3,
pub orientation: Quaternion,
pub pivot: Option<Vec3>,
pub scale: f32, pub scale_partial: Option<Vec3>,
pub color: (f32, f32, f32),
pub opacity: f32,
pub shinyness: f32, pub buf_i: Option<usize>,
pub buf_is_transparent: bool,
pub overlay_text: Option<TextOverlay>,
}
impl Default for Entity {
fn default() -> Self {
Self {
id: 0,
class: 0,
mesh: 0,
position: Vec3::new_zero(),
orientation: Quaternion::new_identity(),
pivot: None,
scale: 1.,
scale_partial: None,
color: (1., 1., 1.),
opacity: 1.,
shinyness: 0.,
buf_i: None,
buf_is_transparent: false,
overlay_text: None,
}
}
}
impl Entity {
pub fn new(
mesh: usize,
position: Vec3,
orientation: Quaternion,
scale: f32,
color: (f32, f32, f32),
shinyness: f32,
) -> Self {
Self {
mesh,
position,
orientation,
scale,
color,
shinyness,
..Default::default()
}
}
}
#[cfg_attr(feature = "app_utils", derive(Encode, Decode))]
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub enum ControlScheme {
None,
#[default]
FreeCamera,
Fps,
Arc { center: Vec3 },
}
#[derive(Clone, Debug)]
pub struct Scene {
pub meshes: Vec<Mesh>,
pub gaussians: Vec<Gaussian>,
pub entities: Vec<Entity>,
pub camera: Camera,
pub lighting: Lighting,
pub input_settings: InputSettings,
pub background_color: (f32, f32, f32),
pub window_title: String,
pub window_size: (f32, f32),
pub gui_size: (f32, f32),
}
impl Default for Scene {
fn default() -> Self {
Self {
meshes: Vec::new(),
gaussians: Vec::new(),
entities: Vec::new(),
camera: Default::default(),
lighting: Default::default(),
input_settings: Default::default(),
background_color: (0.7, 0.7, 0.7),
window_title: "(Window title here)".to_owned(),
window_size: (900., 600.),
gui_size: (0., 0.),
}
}
}
impl Scene {
pub fn screen_to_render(&self, mut screen_pos: (f32, f32)) -> (Vec3, Vec3) {
let proj_view = self.camera.proj_mat.clone() * self.camera.view_mat();
let proj_view_inv = match proj_view.inverse() {
Some(p) => p,
None => {
eprintln!("Error inverting the projection matrix.");
return (Vec3::new_zero(), Vec3::new_zero());
}
};
let (x, y, eff_width, eff_height) = viewport_rect(
self.gui_size,
self.window_size.0 as u32,
self.window_size.1 as u32,
&UiSettings::default(), 0., );
screen_pos.0 -= x;
screen_pos.1 -= y;
let sx = screen_pos.0 / eff_width;
let sy = screen_pos.1 / eff_height;
let clip_x = 2.0 * sx - 1.0;
let clip_y = 1.0 - 2.0 * sy;
let near_clip = Vec4::new(clip_x, clip_y, 0.0, 1.0);
let far_clip = Vec4::new(clip_x, clip_y, 1.0, 1.0);
let near_world_h = proj_view_inv.clone() * near_clip;
let far_world_h = proj_view_inv * far_clip;
let near_world = near_world_h.xyz() / near_world_h.w;
let far_world = far_world_h.xyz() / far_world_h.w;
(near_world, far_world)
}
}
#[derive(Clone, Copy, Debug, Default)]
pub enum ScrollBehavior {
#[default]
None,
MoveRoll { move_amt: f32, rotate_amt: f32 },
}
#[derive(Clone, Debug)]
pub struct InputSettings {
pub move_sens: f32,
pub rotate_sens: f32,
pub rotate_key_sens: f32,
pub run_factor: f32,
pub control_scheme: ControlScheme,
pub scroll_behavior: ScrollBehavior,
pub middle_click_pan: bool,
pub device_events_for_cam_controls: bool,
}
impl Default for InputSettings {
fn default() -> Self {
Self {
control_scheme: Default::default(),
move_sens: 1.5,
rotate_sens: 0.45,
rotate_key_sens: 1.0,
run_factor: 5.,
scroll_behavior: Default::default(),
middle_click_pan: true,
device_events_for_cam_controls: false,
}
}
}
#[cfg_attr(feature = "app_utils", derive(Encode, Decode))]
#[derive(Clone, Copy, PartialEq, Debug, Default)]
pub enum AmbientOcclusion {
None,
#[default]
Ssao,
Gtao,
}
#[derive(Clone, Debug)]
pub struct GraphicsSettings {
pub msaa_samples: u32,
pub ambient_occlusion: AmbientOcclusion,
pub self_shadowing: bool,
pub edge_cueing: Option<f32>,
pub depth_aware_halos: Option<f32>,
pub depth_revealing_contour_lines: Option<f32>,
pub intersection_revealing_contour_lines: Option<f32>,
}
impl Default for GraphicsSettings {
fn default() -> Self {
Self {
msaa_samples: 4,
self_shadowing: true,
edge_cueing: None,
ambient_occlusion: AmbientOcclusion::Ssao,
depth_aware_halos: None,
depth_revealing_contour_lines: None,
intersection_revealing_contour_lines: None,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UiLayoutSides {
Left,
Right,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum UiLayoutTopBottom {
Top,
Bottom,
}
#[derive(Clone, Debug)]
pub struct UiSettings {
pub layout_sides: UiLayoutSides,
pub layout_top_bottom: UiLayoutTopBottom,
pub icon_path: Option<String>,
}
impl Default for UiSettings {
fn default() -> Self {
Self {
layout_sides: UiLayoutSides::Left,
layout_top_bottom: UiLayoutTopBottom::Top,
icon_path: None,
}
}
}
#[derive(Default)]
pub struct EngineUpdates {
pub meshes: bool,
pub entities: EntityUpdate,
pub camera: bool,
pub lighting: bool,
pub ui_reserved_px: (f32, f32),
pub graphics_settings: Option<GraphicsSettings>,
}