diff --git a/.vscode/settings.json b/.vscode/settings.json index 86d8b31..1834c2e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -8,6 +8,7 @@ "./Cargo.toml", "./Cargo.toml", "./Cargo.toml", + "./Cargo.toml", "./Cargo.toml" ], } \ No newline at end of file diff --git a/src/vvedit/mod.rs b/src/vvedit/mod.rs index afa4e13..65fc486 100644 --- a/src/vvedit/mod.rs +++ b/src/vvedit/mod.rs @@ -10,6 +10,7 @@ mod s_orbit_camera; pub mod s_string_tree; pub mod s_ui_details; + pub fn setup(app: &mut App) -> &mut App { app.add_plugins(DefaultPlugins.set(WindowPlugin { primary_window: Some(Window { diff --git a/src/vvedit/s_editor_ui.rs b/src/vvedit/s_editor_ui.rs index c1fdc7c..ed91c1a 100644 --- a/src/vvedit/s_editor_ui.rs +++ b/src/vvedit/s_editor_ui.rs @@ -69,7 +69,7 @@ impl Default for EditWindowUIState { pub enum PopupWindowMode { #[default] None, - StructureElement(String), + EditStructureElement(String, String), //rename from and to, otherwise second string is unused } impl PopupWindowMode { pub fn is_none(&self) -> bool { @@ -394,7 +394,7 @@ pub fn edit_window_ui( for window_entity in &windows { let ctx = contexts.ctx_for_window_mut(window_entity); let pos = ctx.memory(|memory| { - if let PopupWindowMode::StructureElement(_) = shared_ui_state.popup { + if let PopupWindowMode::EditStructureElement(_, _) = shared_ui_state.popup { memory.area_rect(shared_ui_state.egui.popup_identifier.unwrap()) } else { None diff --git a/src/vvedit/s_string_tree.rs b/src/vvedit/s_string_tree.rs index 9cb2afe..64326ba 100644 --- a/src/vvedit/s_string_tree.rs +++ b/src/vvedit/s_string_tree.rs @@ -46,6 +46,33 @@ impl StringTree { pub fn new() -> Self { StringTree { root: Vec::new() } } + pub fn rename(&mut self, subject: &String, new_name: &String) -> bool { + for each in &mut self.root { + if Self::recursive_search_rename(each, subject, new_name) { + return true; + } + } + false + } + fn recursive_search_rename( + element: &mut StringTreeElement, + subject: &String, + new_name: &String, + ) -> bool { + if let StringTreeElement::Element(title, children) = element { + if title == subject { + *title = new_name.clone(); + return true; + } else { + for each in children.as_mut() { + if Self::recursive_search_rename(each, subject, new_name) { + return true; + } + } + } + } + false + } pub fn add_root_level(&mut self, element: &String) -> bool { for each in &self.root { if Self::check_present(each, element) { diff --git a/src/vvedit/s_ui_details.rs b/src/vvedit/s_ui_details.rs index 0381117..7a5ec83 100644 --- a/src/vvedit/s_ui_details.rs +++ b/src/vvedit/s_ui_details.rs @@ -8,6 +8,8 @@ use bevy_egui::{ EguiContexts, }; +use crate::vvlib::s_identifier_validation; + use super::{ s_editor_ui::{EditWindowUIState, PopupWindowMode}, s_string_tree::StringTreeElement, @@ -60,9 +62,10 @@ pub fn round_trip_color() { pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) { let mut change_state = None; let mut reparent = None; - match &state.popup { + let mut rename_complete = false; + match &mut state.popup { PopupWindowMode::None => return, - PopupWindowMode::StructureElement(name) => { + PopupWindowMode::EditStructureElement(name, rename) => { //formatting comment let response = egui::Window::new("edit structure element ".to_owned() + &name.clone()) .id(state.egui.popup_identifier.unwrap()); @@ -70,6 +73,19 @@ pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) { //formatting comment let cur = name.clone(); ui.label("what do you want to do with ".to_owned() + &name + "?"); + ui.horizontal(|ui| { + //. + ui.label("rename element: "); + let mut rename_string = rename.clone(); + ui.text_edit_singleline(&mut rename_string); + if s_identifier_validation::is_valid(rename_string.clone()) { + *rename = rename_string; + } + if ui.button("Confirm and change name").clicked() { + //. + rename_complete = true; + } + }); enum Tasks { MoveElement, RemoveElement, @@ -113,6 +129,16 @@ pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) { } else { state.structure.deparent(&cur); } + return; + } + if rename_complete { + if let PopupWindowMode::EditStructureElement(old, new) = &state.popup { + state.structure.rename(old, new); + if &state.selected_structure_element == old { + state.selected_structure_element = new.clone(); + } + } + state.popup = PopupWindowMode::None; } } @@ -120,6 +146,13 @@ pub fn show_editable_stringtree(ui: &mut egui::Ui, state: &mut EditWindowUIState let mut string_selected = state.selected_structure_element.clone(); ui.collapsing("Model Structure", |ui| { ui.vertical(|ui| { + if state.selected_structure_element != "" { + ui.label("Tree Element Selection: ".to_owned() + &state.selected_structure_element); + if ui.button("deselect").clicked() { + state.selected_structure_element = "".into(); + } + ui.separator(); + } for each in &state.structure.root { match each { super::s_string_tree::StringTreeElement::None => {} @@ -132,7 +165,8 @@ pub fn show_editable_stringtree(ui: &mut egui::Ui, state: &mut EditWindowUIState if ui.add_enabled(true, but).clicked() { let mut should_close = false; - if let PopupWindowMode::StructureElement(val) = &state.popup { + if let PopupWindowMode::EditStructureElement(val, _) = &state.popup + { if val == name { should_close = true; } @@ -140,7 +174,10 @@ pub fn show_editable_stringtree(ui: &mut egui::Ui, state: &mut EditWindowUIState if should_close { state.popup = PopupWindowMode::None; } else { - state.popup = PopupWindowMode::StructureElement(name.clone()) + state.popup = PopupWindowMode::EditStructureElement( + name.clone(), + name.clone(), + ) } } }); @@ -179,7 +216,7 @@ pub fn show_child( ui.radio_value(selection, name.clone(), name.clone()); if ui.button("...").clicked() { let mut should_close = false; - if let PopupWindowMode::StructureElement(val) = state { + if let PopupWindowMode::EditStructureElement(val, _) = state { if val == name { should_close = true; } @@ -187,7 +224,7 @@ pub fn show_child( if should_close { *state = PopupWindowMode::None; } else { - *state = PopupWindowMode::StructureElement(name.clone()) + *state = PopupWindowMode::EditStructureElement(name.clone(), name.clone()) } } }); diff --git a/src/vvlib/mod.rs b/src/vvlib/mod.rs index 6a70ef1..844a4d8 100644 --- a/src/vvlib/mod.rs +++ b/src/vvlib/mod.rs @@ -8,6 +8,7 @@ pub mod s_oct_asset; pub mod s_octtree; pub mod s_structure_asset; pub mod s_true_generic_octtree; +pub mod s_identifier_validation; pub const RANDOM_SPACE: f32 = 5.; // size of the random possibility space. pub const INTERVAL: f32 = 0.3; // how often to add ^ that many and remove ^ that many random locations diff --git a/src/vvlib/s_structure_asset.rs b/src/vvlib/s_structure_asset.rs index 0d6b0c1..a27216a 100644 --- a/src/vvlib/s_structure_asset.rs +++ b/src/vvlib/s_structure_asset.rs @@ -1,92 +1,145 @@ -use bevy::{ - math::{Quat, Vec3}, - transform::components::Transform, - utils::hashbrown::HashMap, -}; -use crate::vvedit::s_string_tree::StringTree; +use s_string_tree::StringTree; +use bevy::{asset::{Asset, AssetLoader, AsyncReadExt}, reflect::TypePath, transform::components::Transform, utils::{hashbrown::HashMap, thiserror}}; -pub struct ChildElPose { - euler: Vec3, - translation: Vec3, - scale: Vec3, -} -pub enum StructureOptions { - None, -} -impl ChildElPose { - fn quat_from(&self) -> Quat { - Quat::from_rotation_x(self.euler.x) - + Quat::from_rotation_y(self.euler.y) - + Quat::from_rotation_z(self.euler.z) - } - pub fn into_transform(&self) -> Transform { - Transform::from_rotation(self.quat_from()) - * Transform::from_scale(self.scale) - * Transform::from_translation(self.translation) - } - pub fn interpolate_toward(&self, other: &ChildElPose, t: f32) -> ChildElPose { - let t = t.clamp(0., 1.); - let rot = self.quat_from().slerp(other.quat_from(), t); - let translation = self.translation.lerp(other.translation, t); - let scale = self.scale.lerp(other.scale, t); - Self::new_quat(rot, translation, scale) - } - pub fn interpolate(lhs: &ChildElPose, rhs: &ChildElPose, t: f32) -> ChildElPose { - lhs.interpolate_toward(rhs, t) - } - pub fn combine_with(&self, other: &ChildElPose) -> ChildElPose { - let rot = self.quat_from() + other.quat_from(); - let translation = self.translation + other.translation; - let scale = self.scale * other.scale; - Self::new_quat(rot, translation, scale) - } - pub fn combine(lhs: &ChildElPose, rhs: &ChildElPose) -> ChildElPose { - lhs.combine_with(rhs) - } - pub fn new_euler(euler: Vec3, translation: Vec3, scale: Vec3) -> ChildElPose { - ChildElPose { - euler, - translation, - scale, - } - } - pub fn new_quat(rot: Quat, translation: Vec3, scale: Vec3) -> ChildElPose { - let int = rot.to_euler(bevy::math::EulerRot::XYZ); - ChildElPose { - euler: Vec3::new(int.0.to_degrees(), int.1.to_degrees(), int.2.to_degrees()), - translation, - scale, - } - } -} +use thiserror::Error; +use crate::vvedit::s_string_tree; + +#[derive(Asset, TypePath)] pub struct StructureAsset { - shape: StringTree, - base_pose: Pose, + layout: StringTree, + positions: HashMap, } -pub struct Pose { - elements: HashMap, +#[derive(Default)] +pub struct StructureLoader { + //can anything even be put in here? } -#[test] -fn test_quaternion_euler_convs() { - let start = Vec3::new(0., f32::to_radians(90.), f32::to_radians(45.)); - let quat = dbg!(Quat::from_euler( - bevy::math::EulerRot::XYZ, - start.x, - start.y, - start.z - )); - let int = quat.normalize().to_euler(bevy::math::EulerRot::XYZ); - let out = [int.0.to_degrees(), int.1.to_degrees(), int.2.to_degrees()]; - dbg!(&out); - let quat2 = Quat::from_euler( - bevy::math::EulerRot::XYZ, - out[0].to_radians(), - out[1].to_radians(), - out[2].to_radians(), - ); - assert!(quat2.normalize() == quat) +#[non_exhaustive] +#[derive(Debug, Error)] +pub enum StructureLoadError { + #[error("Invalid File Data: {0}")] + FileDataInvalid(String), + #[error("FileNotFound: {0}")] + FileNotFound(String), } + +pub mod serialization { + + pub mod meshes { + use std::path::Path; + + use bevy::{ + asset::io::{AssetSource, AssetWriter}, + tasks::block_on, + }; + + use super::super::{StructureLoadError, StructureAsset}; + + pub fn load_detect_version(bytes: Vec) -> Result { + return Err(StructureLoadError::FileDataInvalid("".to_string())); + } + + pub fn write_latest_version(path: &String, asset: &StructureAsset) -> bool { + write_any_version(path, asset, Default::default()) + } + + pub fn write_any_version( + path: &String, + asset: &StructureAsset, + version: StructureVersions, + ) -> bool { + if let Some(writer) = get_writer() { + let mut bytes: Vec = Vec::new(); + if let Some(mut header) = version_header(version) { + bytes.append(&mut header); + } else { + return false; + } + match version { + StructureVersions::Error => return false, + StructureVersions::Version1 => { + if let Some(mut contents) = versions::version_1::save(&asset) { + bytes.append(&mut contents); + } else { + return false; + } + } + } + + let res = + block_on(async { writer.write_bytes(Path::new(path), bytes.as_slice()).await }); + return res.is_ok(); + } + false + } + + pub fn get_writer() -> Option> { + AssetSource::get_default_writer("assets".into())(true) + } + + #[derive(Copy, Clone, Default)] + pub enum StructureVersions { + #[default] + Version1, + Error, + } + + mod versions { + + pub mod version_1 { + + use super::super::super::super::{StructureLoadError, StructureAsset}; + pub fn load(data: std::str::Lines<'_>) -> Result { + + Err(StructureLoadError::FileDataInvalid("".to_string())) + } + pub fn save(asset: &StructureAsset) -> Option> { + None + } + } + } + const VERSION1: &str = "version 1"; + pub fn determine_version(line: Option<&str>) -> StructureVersions { + if line.is_some() { + let line: String = line.unwrap().into(); + if line.to_lowercase() == String::from(VERSION1) { + return StructureVersions::Version1; + } + } + StructureVersions::Error + } + pub fn version_header(version: StructureVersions) -> Option> { + match version { + StructureVersions::Error => None, + StructureVersions::Version1 => Some((VERSION1.to_string()).as_bytes().to_vec()), + } + } + } +} + +impl AssetLoader for StructureLoader { + type Asset = StructureAsset; + type Settings = (); + type Error = StructureLoadError; + + fn load<'a>( + &'a self, + reader: &'a mut bevy::asset::io::Reader, + _settings: &'a Self::Settings, + _load_context: &'a mut bevy::asset::LoadContext, + ) -> bevy::utils::BoxedFuture<'a, Result> { + Box::pin(async move { + let mut bytes: Vec = Vec::::new(); + let result = reader.read_to_end(&mut bytes).await; + if result.is_err() { + return Err(StructureLoadError::FileNotFound( + "could not read_to_end the bytes".to_string(), + )); + } + return serialization::meshes::load_detect_version(bytes); + }) + } +} \ No newline at end of file