From 7f2e7816164c96dfc47b622d426b3095104fac10 Mon Sep 17 00:00:00 2001 From: adia redmoon Date: Tue, 2 Apr 2024 09:25:00 -0700 Subject: [PATCH] half of the architecture for putting oct-tree to mesh on screen --- .vscode/settings.json | 3 + src/vvedit/editor_events.rs | 4 + src/vvedit/mod.rs | 2 + src/vvlib/inputs.rs | 66 ++++++++ src/vvlib/mod.rs | 315 +++++++++++++++++++++++++++++++++++- src/vvlib/octtree.rs | 2 + 6 files changed, 391 insertions(+), 1 deletion(-) create mode 100644 src/vvedit/editor_events.rs create mode 100644 src/vvedit/mod.rs create mode 100644 src/vvlib/inputs.rs diff --git a/.vscode/settings.json b/.vscode/settings.json index ff090ec..51cec23 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,5 +1,8 @@ { "rust-analyzer.linkedProjects": [ + "./Cargo.toml", + "./Cargo.toml", + "./Cargo.toml", "./Cargo.toml", "./Cargo.toml" ], diff --git a/src/vvedit/editor_events.rs b/src/vvedit/editor_events.rs new file mode 100644 index 0000000..70a150b --- /dev/null +++ b/src/vvedit/editor_events.rs @@ -0,0 +1,4 @@ +#![allow(dead_code)] +pub enum InputEvent { + DragMouse(Vec2), +} diff --git a/src/vvedit/mod.rs b/src/vvedit/mod.rs new file mode 100644 index 0000000..9e0f467 --- /dev/null +++ b/src/vvedit/mod.rs @@ -0,0 +1,2 @@ +#![allow(dead_code)] +pub mod editor_events; diff --git a/src/vvlib/inputs.rs b/src/vvlib/inputs.rs new file mode 100644 index 0000000..500bd9d --- /dev/null +++ b/src/vvlib/inputs.rs @@ -0,0 +1,66 @@ +#![allow(dead_code)] + +use bevy::input::{gamepad::*, keyboard::KeyCode, mouse::MouseButton}; +use bevy::prelude::*; + +#[derive(Default)] +pub enum BinaryInputID { + #[default] + Unbound, + KeyCode(KeyCode), + MouseButton(MouseButton), + GamepadButton(GamepadButton, Gamepad), +} + +#[derive(Default)] +pub enum BinaryInputState { + #[default] + Inactive, + JustActivated, + Activated, + JustDeactivated, +} + +#[derive(Default)] +pub struct BinaryInputBinding { + inputs: [BinaryInputID; 5], +} +impl BinaryInputBinding { + pub fn is_activated( + &self, + keycode_buttons: Res>, + mouse_buttons: Res>, + gamepad_buttons: Res>, + ) -> bool { + for input in self.inputs.as_slice() { + match input { + BinaryInputID::KeyCode(code) => { + if !keycode_buttons.pressed(*code) { + return false; + } + } + BinaryInputID::MouseButton(button) => { + if !mouse_buttons.pressed(*button) { + return false; + } + } + BinaryInputID::GamepadButton(button, pad) => { + if !gamepad_buttons.pressed(GamepadButton::new(*pad, button.button_type)) { + return false; + } + } + BinaryInputID::Unbound => {} // ignoring unbound bindings, just means we're not using 5 total keys to do one input. sometimes you Just X to jump. + } + } + true // all are pressed or unbound and thus not mattering. activated + } +} + +#[derive(Default)] +pub enum AxisInputID { + #[default] + Unbound, + MouseMotionX, + MouseMotionY, + GamepadAxis(GamepadAxis, Gamepad), +} diff --git a/src/vvlib/mod.rs b/src/vvlib/mod.rs index 8e0c12e..5fbb69b 100644 --- a/src/vvlib/mod.rs +++ b/src/vvlib/mod.rs @@ -1,11 +1,26 @@ #![allow(dead_code)] + +use bevy::app::App; +use bevy::app::Last; +use bevy::app::Plugin; +use bevy::ecs::component::Component; +use bevy::ecs::system::Query; +use bevy::math::Vec3; +use bevy::render::color::Color; +use bevy::render::mesh::Indices; +use bevy::render::mesh::Mesh; +use bevy::render::mesh::PrimitiveTopology; +use bevy::render::render_asset::RenderAssetUsages; +use bevy::utils::hashbrown::HashMap; + +pub mod inputs; pub mod intersections; pub mod obb; pub mod octtree; pub mod constants { use bevy::math::Vec3; - pub const CORNER: f32 = 0.866025; + pub const CORNER_3D: f32 = 0.866025; pub fn to_region_id(center: Vec3, subject: Vec3) -> i32 { if subject.x == center.x && subject.y == center.y && subject.z == center.z { return -1; @@ -98,3 +113,301 @@ pub mod constants { pub const ORDERED: [Vec3; 6] = [PX, NX, PY, NY, PZ, NZ]; } } + +pub struct OctTreePlugin {} +impl Plugin for OctTreePlugin { + fn build(&self, app: &mut App) { + app.add_systems(Last, oct_tree_edit_updater); + } +} + +#[derive(Component)] +pub struct OctTreeModelComponent { + model: octtree::OctTree, + //TODO: add Option<> handle for remeshing-event for async-computing it - so if it gets edited again we can cancel the old one and let it run a new mesher async compute +} + +/// schedules re-meshing updates of the models used to represent oct-tree data. collisions are instantly updated (as any update to the oct-tree itself updates the data used for collisioning, so we can be lazy about when the visuals kick in (to an extent, of course). as such, we want to kick them into an async updater for the meshes used) +pub fn oct_tree_edit_updater(mut query: Query<&mut OctTreeModelComponent>) { + for mut model in &mut query { + if model.model.needs_update { + // spawn remeshing async task, put in the model + model.model.needs_update = false; + } + } +} + +//TODO: event that takes finished remeshing events and emits an event to be caught with the relevant mesh to update the meshdata + +impl OctTreeModelComponent { + pub fn render_as_mesh(&self) -> Mesh { + let mut vertices = Vec::new(); + let mut normals = Vec::new(); + let mut indices = Vec::new(); + let mut colors = Vec::new(); + + let elements = self.model.collect_voxels(); + let mut lookup = HashMap::new(); + for (_, loc, col) in elements { + lookup.insert(as_ia(loc), col); + } + for (loc, col) in &lookup { + emit_cube_at( + as_v(*loc), + &mut vertices, + &mut indices, + &mut normals, + &mut colors, + *col, + lookup.get(&as_ia(as_v(*loc) + Vec3::X)).is_none(), + lookup.get(&as_ia(as_v(*loc) + Vec3::NEG_X)).is_none(), + lookup.get(&as_ia(as_v(*loc) + Vec3::Y)).is_none(), + lookup.get(&as_ia(as_v(*loc) + Vec3::NEG_Y)).is_none(), + lookup.get(&as_ia(as_v(*loc) + Vec3::Z)).is_none(), + lookup.get(&as_ia(as_v(*loc) + Vec3::NEG_Z)).is_none(), + None, // will let me do custom per-vox normals later. that requires extra calculation im not doing yet + ); + } + return as_mesh(vertices, indices, normals, colors); + } +} +fn as_ia(input: Vec3) -> [i64; 3] { + [ + input.x.round() as i64, + input.y.round() as i64, + input.z.round() as i64, + ] +} +fn as_v(input: [i64; 3]) -> Vec3 { + Vec3::new(input[0] as f32, input[1] as f32, input[2] as f32) +} + +fn as_mesh( + vertices: Vec<[f32; 3]>, + indices: Vec, + normals: Vec<[f32; 3]>, + colors: Vec<[f32; 4]>, +) -> Mesh { + Mesh::new( + PrimitiveTopology::TriangleList, + RenderAssetUsages::RENDER_WORLD, + ) + .with_inserted_attribute(Mesh::ATTRIBUTE_POSITION, vertices) + .with_inserted_attribute(Mesh::ATTRIBUTE_COLOR, colors) + .with_inserted_attribute(Mesh::ATTRIBUTE_NORMAL, normals) + .with_inserted_indices(Indices::U32(indices)) +} + +fn emit_cube_at( + pos: Vec3, + vertices: &mut Vec<[f32; 3]>, + indices: &mut Vec, + normals: &mut Vec<[f32; 3]>, + colors: &mut Vec<[f32; 4]>, + color: Color, + render_px: bool, + render_nx: bool, + render_py: bool, + render_ny: bool, + render_pz: bool, + render_nz: bool, + normal: Option, +) { + if render_px { + let len = vertices.len() as u32; + vertices.extend([ + [pos.x + 0.5, pos.y - 0.5, pos.z - 0.5], + [pos.x + 0.5, pos.y - 0.5, pos.z + 0.5], + [pos.x + 0.5, pos.y + 0.5, pos.z + 0.5], + [pos.x + 0.5, pos.y + 0.5, pos.z - 0.5], + ]); + if normal.is_some() { + let normal = normal.clone().unwrap(); + normals.extend([ + normal.to_array(), + normal.to_array(), + normal.to_array(), + normal.to_array(), + ]); + } else { + normals.extend([ + [1.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + [1.0, 0.0, 0.0], + ]); + } + colors.extend([ + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + ]); + indices.extend([len, len + 3, len + 1, len + 1, len + 3, len + 2]); + } + + if render_nx { + let len = vertices.len() as u32; + vertices.extend([ + [pos.x - 0.5, pos.y - 0.5, pos.z - 0.5], + [pos.x - 0.5, pos.y - 0.5, pos.z + 0.5], + [pos.x - 0.5, pos.y + 0.5, pos.z + 0.5], + [pos.x - 0.5, pos.y + 0.5, pos.z - 0.5], + ]); + if normal.is_some() { + let normal = normal.clone().unwrap(); + normals.extend([ + normal.to_array(), + normal.to_array(), + normal.to_array(), + normal.to_array(), + ]); + } else { + normals.extend([ + [-1.0, 0.0, 0.0], + [-1.0, 0.0, 0.0], + [-1.0, 0.0, 0.0], + [-1.0, 0.0, 0.0], + ]); + } + colors.extend([ + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + ]); + indices.extend([len + 2, len + 3, len + 1, len + 1, len + 3, len]); + } + + if render_py { + let len = vertices.len() as u32; + vertices.extend([ + [pos.x - 0.5, pos.y + 0.5, pos.z - 0.5], + [pos.x + 0.5, pos.y + 0.5, pos.z - 0.5], + [pos.x + 0.5, pos.y + 0.5, pos.z + 0.5], + [pos.x - 0.5, pos.y + 0.5, pos.z + 0.5], + ]); + if normal.is_some() { + let normal = normal.clone().unwrap(); + normals.extend([ + normal.to_array(), + normal.to_array(), + normal.to_array(), + normal.to_array(), + ]); + } else { + normals.extend([ + [0.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + [0.0, 1.0, 0.0], + ]); + } + colors.extend([ + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + ]); + indices.extend([len, len + 3, len + 1, len + 1, len + 3, len + 2]); + } + + if render_ny { + let len = vertices.len() as u32; + vertices.extend([ + [pos.x - 0.5, pos.y - 0.5, pos.z - 0.5], + [pos.x + 0.5, pos.y - 0.5, pos.z - 0.5], + [pos.x + 0.5, pos.y - 0.5, pos.z + 0.5], + [pos.x - 0.5, pos.y - 0.5, pos.z + 0.5], + ]); + if normal.is_some() { + let normal = normal.clone().unwrap(); + normals.extend([ + normal.to_array(), + normal.to_array(), + normal.to_array(), + normal.to_array(), + ]); + } else { + normals.extend([ + [0.0, -1.0, 0.0], + [0.0, -1.0, 0.0], + [0.0, -1.0, 0.0], + [0.0, -1.0, 0.0], + ]); + } + colors.extend([ + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + ]); + indices.extend([len + 2, len + 3, len + 1, len + 1, len + 3, len]); + } + + if render_pz { + let len = vertices.len() as u32; + vertices.extend([ + [pos.x - 0.5, pos.y - 0.5, pos.z + 0.5], + [pos.x - 0.5, pos.y + 0.5, pos.z + 0.5], + [pos.x + 0.5, pos.y + 0.5, pos.z + 0.5], + [pos.x + 0.5, pos.y - 0.5, pos.z + 0.5], + ]); + if normal.is_some() { + let normal = normal.clone().unwrap(); + normals.extend([ + normal.to_array(), + normal.to_array(), + normal.to_array(), + normal.to_array(), + ]); + } else { + normals.extend([ + [0.0, 0.0, 1.0], + [0.0, 0.0, 1.0], + [0.0, 0.0, 1.0], + [0.0, 0.0, 1.0], + ]); + } + colors.extend([ + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + ]); + indices.extend([len, len + 3, len + 1, len + 1, len + 3, len + 2]); + } + + if render_nz { + let len = vertices.len() as u32; + vertices.extend([ + [pos.x - 0.5, pos.y - 0.5, pos.z - 0.5], + [pos.x - 0.5, pos.y + 0.5, pos.z - 0.5], + [pos.x + 0.5, pos.y + 0.5, pos.z - 0.5], + [pos.x + 0.5, pos.y - 0.5, pos.z - 0.5], + ]); + if normal.is_some() { + let normal = normal.clone().unwrap(); + normals.extend([ + normal.to_array(), + normal.to_array(), + normal.to_array(), + normal.to_array(), + ]); + } else { + normals.extend([ + [0.0, 0.0, -1.0], + [0.0, 0.0, -1.0], + [0.0, 0.0, -1.0], + [0.0, 0.0, -1.0], + ]); + } + colors.extend([ + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + color.as_rgba_f32(), + ]); + indices.extend([len + 2, len + 3, len + 1, len + 1, len + 3, len]); + } +} diff --git a/src/vvlib/octtree.rs b/src/vvlib/octtree.rs index 786e173..25c0ee8 100644 --- a/src/vvlib/octtree.rs +++ b/src/vvlib/octtree.rs @@ -23,6 +23,7 @@ pub struct OctTree { pub(crate) size: usize, pub(crate) center: Vec3, pub(crate) root: TreeNode, + pub needs_update: bool, } #[derive(Debug, Default, Clone, PartialEq, Eq)] @@ -119,6 +120,7 @@ impl OctTree { size: 1, center: npos, root: TreeNode::Leaf(value), + needs_update: false, } }