finished proof of file load and save. port to multi-grid and multi-structure modality. remaining: selector for structure, selector for grid, exclusivity between the two. transform editor and node-data-editor (grid or light or structure nesting (test for infiniite loop!!))

This commit is contained in:
Lillian Vixe 2024-06-03 15:13:47 -07:00
parent 51805dcbcb
commit a67beb77bd
5 changed files with 155 additions and 365 deletions

View file

@ -2,14 +2,16 @@
use bevy::{diagnostic::FrameTimeDiagnosticsPlugin, prelude::*, window::WindowResolution};
use crate::vvlib::s_oct_asset::{self, OctAssetMarker, OctTreeAsset};
use crate::vvlib::{
s_oct_asset::{self, OctAssetMarker, OctTreeAsset},
s_structure_asset::StructureAssetPlugin,
};
mod s_editor_bevy_input_shim;
pub mod s_editor_ui;
mod s_orbit_camera;
pub mod s_string_tree;
pub mod s_ui_details;
pub mod s_ui_helpers_and_popup;
pub fn setup(app: &mut App) -> &mut App {
app.add_plugins(DefaultPlugins.set(WindowPlugin {
@ -21,7 +23,6 @@ pub fn setup(app: &mut App) -> &mut App {
..default()
}))
.add_plugins(s_oct_asset::OctAssetPlugin)
.init_asset::<s_oct_asset::OctTreeAsset>()
.add_systems(Startup, crate::vvlib::s_fps_display::setup_fps_counter)
.add_systems(
Update,
@ -31,6 +32,7 @@ pub fn setup(app: &mut App) -> &mut App {
),
)
.add_plugins(FrameTimeDiagnosticsPlugin::default())
.add_plugins(StructureAssetPlugin)
.add_systems(Startup, startup);
s_editor_ui::register_edit_ui(app)
}

View file

@ -9,8 +9,7 @@ use bevy::{
math::Vec3,
prelude::default,
render::{camera::Camera, color::Color},
transform::components::{GlobalTransform, Transform},
utils::hashbrown::HashMap,
transform::components::GlobalTransform,
window::{PrimaryWindow, Window},
};
use bevy_egui::{
@ -21,11 +20,8 @@ use bevy_egui::{
use crate::{
vvedit::s_editor_bevy_input_shim::keybind_codes::REMOVE_VOXEL,
vvlib::{
s_inputs::InputRegister,
s_intersections::octtree_ray_overlap,
s_obb::OBB,
s_oct_asset::OctAssetMarker,
s_structure_asset::{self, StructureAsset},
s_inputs::InputRegister, s_intersections::octtree_ray_overlap, s_obb::OBB,
s_oct_asset::OctAssetMarker, s_structure_asset::StructureAsset,
},
};
use crate::{
@ -36,36 +32,44 @@ use crate::{
use super::{
s_editor_bevy_input_shim::setup_inputs,
s_orbit_camera::orbit_camera::{pan_orbit_camera, spawn_camera},
s_string_tree::StringTree,
s_ui_details::*,
s_ui_helpers_and_popup::*,
};
use std::fmt::Write;
pub struct OctAssetEditData {
path: String,
id: Handle<OctTreeAsset>,
has_changed_since_last_save: bool,
pub struct GridEditData {
pub path: String,
pub id: Handle<OctTreeAsset>,
pub has_changed_since_last_save: bool,
}
pub struct StructureEditData {
pub path: String,
pub id: Handle<StructureAsset>,
pub has_changed_since_last_save: bool,
}
#[derive(Resource)]
pub struct EditWindowUIState {
brush_color: Hsva,
meshes: Vec<OctAssetEditData>,
pub structure: StringTree,
pub grids: Vec<GridEditData>,
pub structures: Vec<StructureEditData>,
pub egui: EditWindowEguiState,
pub popup: PopupWindowMode,
pub selected_structure_element: String,
pub selected_structure: String,
pub selected_grid: String,
}
impl Default for EditWindowUIState {
fn default() -> Self {
Self {
brush_color: Default::default(),
meshes: Default::default(),
structure: Default::default(),
grids: Default::default(),
structures: Default::default(),
egui: Default::default(),
popup: Default::default(),
selected_structure_element: "".into(),
selected_structure: "".into(),
selected_grid: "".into(),
}
}
}
@ -73,8 +77,9 @@ impl Default for EditWindowUIState {
pub enum PopupWindowMode {
#[default]
None,
EditStructureElement(String, String), // rename from and to, otherwise second string is unused
CreateStructureElement(String, String), // node name, parent node
EditStructureElement(String, String, String), // structure subject, rename from and to, otherwise second string is unused
CreateStructureElement(String, String, String), // structure subject, node name, parent node
LoadFile(String, i32, Vec<String>), // string is current selection from list discovered from disk, number is a timer for refreshing asset list via disk scan
}
impl PopupWindowMode {
pub fn is_none(&self) -> bool {
@ -103,7 +108,7 @@ impl Default for EditWindowEguiState {
// refactor into hashmaps of the AssetId
impl EditWindowUIState {
pub fn name_from_handle(&self, id: Handle<OctTreeAsset>) -> Option<String> {
for each in &self.meshes {
for each in &self.grids {
if &each.id == &id {
return Some(each.path.clone());
}
@ -111,7 +116,7 @@ impl EditWindowUIState {
None
}
pub fn has_changed_from_name(&self, name: &String) -> Option<bool> {
for each in &self.meshes {
for each in &self.grids {
if &each.path == name {
return Some(each.has_changed_since_last_save);
}
@ -119,7 +124,7 @@ impl EditWindowUIState {
None
}
pub fn has_changed_from_handle(&self, id: Handle<OctTreeAsset>) -> Option<bool> {
for each in &self.meshes {
for each in &self.grids {
if &each.id == &id {
return Some(each.has_changed_since_last_save);
}
@ -127,7 +132,7 @@ impl EditWindowUIState {
None
}
pub fn handle_from_name(&self, name: &String) -> Option<Handle<OctTreeAsset>> {
for each in &self.meshes {
for each in &self.grids {
if &each.path == name {
return Some(each.id.clone());
}
@ -141,7 +146,7 @@ impl EditWindowUIState {
has_changed: bool,
) -> bool {
let mut match_count: usize = 0;
for each in &self.meshes {
for each in &self.grids {
if &each.path == path {
match_count += 1;
} else if &each.id == &id {
@ -149,7 +154,7 @@ impl EditWindowUIState {
}
}
if match_count == 1 {
for each in &mut self.meshes {
for each in &mut self.grids {
if &each.path == path {
each.id = id.clone();
each.has_changed_since_last_save = has_changed;
@ -160,7 +165,7 @@ impl EditWindowUIState {
}
return true;
} else if match_count == 0 {
self.meshes.push(OctAssetEditData {
self.grids.push(GridEditData {
path: path.clone(),
id,
has_changed_since_last_save: has_changed,
@ -178,8 +183,7 @@ pub fn register_edit_ui(
) -> &mut bevy::prelude::App {
app.insert_resource(EditWindowUIState {
brush_color: egui_color_from(Color::RED),
meshes: Vec::new(),
structure: StringTree::new(),
grids: Vec::new(),
..default()
});
app.add_plugins(EguiPlugin);
@ -196,29 +200,17 @@ pub fn startup_system_edit_ui(
// formatting comment
commands: Commands,
mut shared_ui_state: ResMut<EditWindowUIState>,
asset_server: ResMut<AssetServer>,
) {
spawn_camera(commands);
shared_ui_state
.structure
.add_root_level(&String::from("example"));
shared_ui_state
.structure
.add_root_level(&String::from("example2"));
shared_ui_state
.structure
.add(&String::from("c1"), Some(&String::from("example")));
shared_ui_state
.structure
.add(&String::from("c2"), Some(&String::from("c1")));
let asset_test = StructureAsset {
layout: shared_ui_state.structure.clone(),
default_pose_positions: HashMap::<String, Transform>::new(),
element_data: Default::default(),
let str = "test.vvs";
let handle = asset_server.load::<StructureAsset>(str);
let data = StructureEditData {
path: str.into(),
id: handle,
has_changed_since_last_save: false,
};
s_structure_asset::serialization::structures::write_latest_version(
&"test.vvs".to_string(),
&asset_test,
);
shared_ui_state.structures.push(data);
}
fn update_ui_scale_factor(
mut egui_settings: ResMut<EguiSettings>,
@ -234,7 +226,7 @@ pub fn edit_click_events(
camera_query: Query<(&Camera, &GlobalTransform)>,
oct_tree: Query<(&GlobalTransform, &OctAssetMarker)>,
windows: Query<&Window>,
mut oct_assets: ResMut<Assets<OctTreeAsset>>,
mut grid_assets: ResMut<Assets<OctTreeAsset>>,
mut shared_ui_state: ResMut<EditWindowUIState>,
egui_settings: Res<EguiSettings>,
) {
@ -252,7 +244,7 @@ pub fn edit_click_events(
continue;
};
let obb = OBB::from_transform(trans.compute_transform());
let octtree = oct_assets.get_mut(oct.oct_handle.clone());
let octtree = grid_assets.get_mut(oct.oct_handle.clone());
if octtree.is_none() {
continue;
}
@ -373,10 +365,12 @@ pub fn update_asset_mesh_list(
pub fn edit_window_ui(
mut shared_ui_state: ResMut<EditWindowUIState>,
mut contexts: EguiContexts,
mut oct_assets: ResMut<Assets<OctTreeAsset>>,
mut grid_assets: ResMut<Assets<OctTreeAsset>>,
mut structure_assets: ResMut<Assets<StructureAsset>>,
mut pairs: ResMut<MeshingOctTreePairs>,
windows: Query<Entity, With<Window>>,
window: Query<&Window>,
mut asset_server: ResMut<AssetServer>,
) {
shared_ui_state.egui.windows.clear();
let title = String::from("VVEdit");
@ -407,12 +401,9 @@ pub fn edit_window_ui(
} else {
for window_entity in &windows {
let ctx = contexts.ctx_for_window_mut(window_entity);
let pos = ctx.memory(|memory| {
if let PopupWindowMode::EditStructureElement(_, _) = shared_ui_state.popup {
memory.area_rect(shared_ui_state.egui.popup_identifier.unwrap())
} else {
None
}
let pos = ctx.memory(|memory| match &shared_ui_state.popup {
PopupWindowMode::None => None,
_ => memory.area_rect(shared_ui_state.egui.popup_identifier.unwrap()),
});
if let Some(pos) = pos {
shared_ui_state.egui.windows.push(pos);
@ -430,6 +421,9 @@ pub fn edit_window_ui(
}
response.show(contexts.ctx_mut(), |ui: &mut egui::Ui| {
if ui.button("test directory list popup").clicked() {
shared_ui_state.popup = PopupWindowMode::LoadFile("".into(), 0, Vec::new());
}
ui.label("This tool is in early development.");
ScrollArea::vertical().show(ui, |ui| {
ui.collapsing("Mesh Tools", |ui| {
@ -445,11 +439,11 @@ pub fn edit_window_ui(
);
ui.label(label);
let mut color_to_set: Option<Color> = None;
for each in &shared_ui_state.meshes {
for each in &shared_ui_state.grids {
ui.vertical(|ui| {
//color palette clickables.
let handle = each.id.clone();
let asset = oct_assets.get(handle);
let asset = grid_assets.get(handle);
if asset.is_some() {
let list = {
let eoct = asset.unwrap().model.read().unwrap();
@ -496,7 +490,7 @@ pub fn edit_window_ui(
//
for pair in &mut pairs.handles {
let id = pair.oct_handle.clone();
let tree = oct_assets.get_mut(id);
let tree = grid_assets.get_mut(id);
if tree.is_some() {
let tree = tree.unwrap();
{
@ -510,59 +504,94 @@ pub fn edit_window_ui(
}
}
});
ui.collapsing("Model Structure", |ui| {
ui.vertical(|ui| {
if ui.button("add element").clicked() {
shared_ui_state.popup = PopupWindowMode::CreateStructureElement(
"unnamed".to_owned(),
"".to_owned(),
);
let mut selected_structure = false;
let mut selection = None;
if shared_ui_state.selected_structure != "" {
for structure in &shared_ui_state.structures {
if structure.path == shared_ui_state.selected_structure {
let asset = structure_assets.get_mut(structure.id.clone());
if asset.is_some() {
selection = Some(&mut asset.unwrap().layout);
selected_structure = true;
}
break;
}
ui.separator();
if shared_ui_state.selected_structure_element != "" {
if ui.button("Edit Structure of Node").clicked() {
let selection = shared_ui_state.selected_structure_element.clone();
let mut should_close = false;
if let PopupWindowMode::EditStructureElement(val, _) =
&shared_ui_state.popup
{
if val == &selection {
should_close = true;
}
}
if should_close {
shared_ui_state.popup = PopupWindowMode::None;
} else {
shared_ui_state.popup = PopupWindowMode::EditStructureElement(
shared_ui_state.selected_structure_element.clone(),
shared_ui_state.selected_structure_element.clone(),
)
}
ui.label(
"Tree Element Selection: ".to_owned()
+ &shared_ui_state.selected_structure_element,
}
}
if selected_structure {
let selection = selection.unwrap();
ui.collapsing("Model Structure", |ui| {
ui.vertical(|ui| {
if ui.button("add element").clicked() {
shared_ui_state.popup = PopupWindowMode::CreateStructureElement(
shared_ui_state.selected_structure.clone(),
"unnamed".to_owned(),
"".to_owned(),
);
}
if ui.button("deselect").clicked() {
shared_ui_state.selected_structure_element = "".into();
}
ui.separator();
if shared_ui_state.selected_structure_element != "" {
if ui.button("Edit Structure of Node").clicked() {
let selection = shared_ui_state.selected_structure_element.clone();
let mut should_close = false;
if let PopupWindowMode::EditStructureElement(_, val, _) =
&shared_ui_state.popup
{
if val == &selection {
should_close = true;
}
}
if should_close {
shared_ui_state.popup = PopupWindowMode::None;
} else {
shared_ui_state.popup = PopupWindowMode::EditStructureElement(
shared_ui_state.selected_structure.clone(),
shared_ui_state.selected_structure_element.clone(),
shared_ui_state.selected_structure_element.clone(),
)
}
ui.label(
"Tree Element Selection: ".to_owned()
+ &shared_ui_state.selected_structure_element,
);
}
if ui.button("deselect").clicked() {
shared_ui_state.selected_structure_element = "".into();
}
ui.separator();
}
let mut string_selection =
shared_ui_state.selected_structure_element.clone();
show_stringtree_selector(ui, selection, &mut string_selection);
shared_ui_state.selected_structure_element = string_selection;
})
});
}
ui.collapsing("grid assets", |ui| {
//
for (id, _asset) in (&grid_assets).iter() {
let path = asset_server.get_path(id.untyped());
if let Some(path) = path {
ui.label(path.to_string().as_str());
}
let mut string_selection = shared_ui_state.selected_structure_element.clone();
show_stringtree_selector(
ui,
&mut shared_ui_state.structure,
&mut string_selection,
);
shared_ui_state.selected_structure_element = string_selection;
})
}
});
ui.collapsing("structure assets", |ui| {
//
for (id, _asset) in (&structure_assets).iter() {
let path = asset_server.get_path(id.untyped());
if let Some(path) = path {
ui.label(path.to_string().as_str());
}
}
});
if ui.button("Save Any Changes").clicked() {
for each in &mut shared_ui_state.meshes {
for each in &mut shared_ui_state.grids {
if each.has_changed_since_last_save {
let handle = each.id.clone();
let path = each.path.clone() + ".vvg";
let asset = oct_assets.get(handle);
let asset = grid_assets.get(handle);
if asset.is_some() {
let result =
crate::vvlib::s_oct_asset::serialization::meshes::write_latest_version(
@ -578,5 +607,10 @@ pub fn edit_window_ui(
}
});
});
show_popup(&mut shared_ui_state, &mut contexts);
show_popup(
&mut shared_ui_state,
&mut contexts,
&mut asset_server,
&mut structure_assets,
);
}

View file

@ -1,255 +0,0 @@
use std::mem;
use bevy::render::color::Color;
use bevy_egui::{
egui::{
self,
ecolor::{hsv_from_rgb, rgb_from_hsv},
epaint::Hsva,
},
EguiContexts,
};
use crate::vvlib::s_identifier_validation::{self, is_valid};
use super::{
s_editor_ui::{EditWindowUIState, PopupWindowMode},
s_string_tree::{StringTree, StringTreeElement},
};
pub fn color_from(egui_color: Hsva) -> Color {
let rgb = rgb_from_hsv((egui_color.h, egui_color.s, egui_color.v));
return Color::rgb_from_array(rgb);
}
pub fn egui_color_from(color: Color) -> Hsva {
let x = hsv_from_rgb([color.r(), color.g(), color.b()]);
return Hsva::new(x.0, x.1, x.2, 1.);
}
#[test]
pub fn round_trip_color() {
use rand::Rng;
let mut rng = rand::thread_rng();
let min = 0.;
let max = 1.;
for _ in 0..10000 {
let r = rng.gen_range(min..max);
let g = rng.gen_range(min..max);
let b = rng.gen_range(min..max);
let mut color = Color::rgb(r, g, b);
let mut egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
egui_color = egui_color_from(color);
color = color_from(egui_color);
assert_eq!((color.r() * 255.).floor(), (r * 255.).floor());
assert_eq!((color.g() * 255.).floor(), (g * 255.).floor());
assert_eq!((color.b() * 255.).floor(), (b * 255.).floor());
}
}
pub fn show_popup(state: &mut EditWindowUIState, contexts: &mut EguiContexts) {
enum PopupStateChange {
None,
RemoveStructureElement(bool), //keep children(reparenting upward) or drop
}
let mut state_change = PopupStateChange::None;
let mut change_state = None;
let mut reparent = None;
let mut rename_complete = false;
let mut added_valid_name = false;
match &mut state.popup {
PopupWindowMode::None => return,
PopupWindowMode::EditStructureElement(name, rename) => {
//formatting comment
let response = egui::Window::new("edit structure element ".to_owned() + &name.clone())
.id(state.egui.popup_identifier.unwrap());
response.show(contexts.ctx_mut(), |ui| {
//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;
}
});
ui.horizontal(|ui| {
if ui.button("remove and reparent to parent").clicked() {
state_change = PopupStateChange::RemoveStructureElement(true);
}
if ui.button("remove and drop children").clicked() {
state_change = PopupStateChange::RemoveStructureElement(false);
}
});
enum Tasks {
MoveElement,
RemoveElement,
DeleteElementWithChildren,
DeleteElementAndReparent,
}
ui.collapsing("Reparent to child of", |ui| {
ui.vertical(|ui| {
for each in state.structure.list_of_elements() {
if each != cur
&& Some(&each) != state.structure.parent_of(&cur.clone())
&& !state.structure.is_upstream_of(&cur, &each)
{
if ui.button(&each).clicked() {
reparent = Some((cur.clone(), each));
change_state = Some(PopupWindowMode::None);
}
}
}
if state.structure.parent_of(&cur).is_some() {
if ui.button("None").clicked() {
reparent = Some((cur.clone(), "".into()));
change_state = Some(PopupWindowMode::None);
}
}
});
});
// todo: rename action. edit subject action (mesh, light), remove action (with, without reparenting)
if ui.button("Cancel").clicked() {
change_state = Some(PopupWindowMode::None);
}
});
}
PopupWindowMode::CreateStructureElement(name, parent) => {
//
let response = egui::Window::new("new structure element ".to_owned() + &name.clone())
.id(state.egui.popup_identifier.unwrap());
response.show(contexts.ctx_mut(), |ui| {
ui.label("please name the new structure node.");
let mut newname = name.clone();
ui.text_edit_singleline(&mut newname);
if is_valid(newname.clone()) && !state.structure.is_present(&newname) {
*name = newname;
}
if ui.button("Confirm and add.").clicked() {
added_valid_name = true;
}
ui.collapsing(
"parent status: ".to_owned() + parent.clone().as_str(),
|ui| {
ui.vertical(|ui| {
ui.radio_value(parent, "".into(), "Root level - no parent");
for each in state.structure.list_of_elements() {
ui.radio_value(parent, each.clone(), each.clone());
}
});
},
);
});
}
}
if change_state.is_some() {
state.popup = change_state.unwrap();
}
if let Some((cur, next)) = reparent {
if next != "" {
state.structure.move_branch(&cur, &next);
} 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;
}
if added_valid_name {
if let PopupWindowMode::CreateStructureElement(name, parent) = &state.popup {
let par = if parent == "" { None } else { Some(parent) };
state.structure.add(name, par);
} else {
return;
}
state.popup = PopupWindowMode::None;
}
match state_change {
PopupStateChange::None => {} // nothing to do if state does not change
PopupStateChange::RemoveStructureElement(keep_children) => {
let mut consumed = PopupWindowMode::None;
mem::swap(&mut state.popup, &mut consumed);
if let PopupWindowMode::EditStructureElement(subject, _) = consumed {
if keep_children {
state.structure.remove_element_preserve_branch(&subject);
} else {
state.structure.remove_branch(&subject);
}
}
}
}
}
pub fn show_stringtree_selector(ui: &mut egui::Ui, tree: &StringTree, selection: &mut String) {
for each in &tree.root {
match each {
super::s_string_tree::StringTreeElement::None => {}
super::s_string_tree::StringTreeElement::Element(name, children) => {
ui.horizontal(|ui| {
ui.label(">".to_owned());
//ui.label(name.clone());
ui.radio_value(selection, name.clone(), name.clone());
});
for each in children.as_ref() {
show_child(ui, each, 1, selection);
}
}
}
}
}
pub fn show_child(
ui: &mut egui::Ui,
subject: &StringTreeElement,
depth: usize,
selection: &mut String,
) {
match subject {
StringTreeElement::None => {}
StringTreeElement::Element(name, children) => {
let mut prec = String::from("");
for i in 0..depth {
if i == 0 {
prec = prec + "+";
} else {
prec = prec + "+";
}
}
prec = prec + ">";
ui.horizontal(|ui| {
ui.label(prec);
ui.radio_value(selection, name.clone(), name.clone());
});
for each in children.as_ref() {
show_child(ui, each, depth + 1, selection);
}
}
}
}

View file

@ -384,6 +384,7 @@ pub struct OctAssetPlugin;
impl Plugin for OctAssetPlugin {
fn build(&self, app: &mut App) {
app.init_asset_loader::<OctLoader>();
app.init_asset::<OctTreeAsset>();
app.add_systems(Update, (attach_mesh_handle, oct_asset_to_mesh));
app.insert_resource(MeshingOctTreePairs {
handles: Vec::new(),

View file

@ -1,5 +1,6 @@
use bevy::{
asset::{Asset, AssetLoader, AssetServer, AsyncReadExt, Handle},
app::{App, Plugin},
asset::{Asset, AssetApp, AssetLoader, AssetServer, AsyncReadExt, Handle},
reflect::TypePath,
transform::components::Transform,
utils::{hashbrown::HashMap, thiserror},
@ -76,6 +77,13 @@ impl ElementData {
}
}
pub struct StructureAssetPlugin;
impl Plugin for StructureAssetPlugin {
fn build(&self, app: &mut App) {
app.init_asset_loader::<StructureLoader>();
app.init_asset::<StructureAsset>();
}
}
#[derive(Default)]
pub struct StructureLoader {
//can anything even be put in here?
@ -454,7 +462,7 @@ pub mod serialization {
}
}
// ^ NODE DATA SUCH AS RELIANT GRIDS (optional)
Err(StructureLoadError::FileDataInvalid("".to_string()))
return Ok(result);
}
pub fn save(asset: &StructureAsset) -> Option<Vec<u8>> {
const NEWLINE: &str = "\n";