file save and loading with expandable backwards compat layer

This commit is contained in:
Lillian Vixe 2024-04-18 12:53:11 -07:00
parent 87dacb619e
commit f764cd14c3
3 changed files with 369 additions and 153 deletions

View file

@ -1,6 +1,9 @@
0 0 0|1 0 0
0 -0 1|0.68343294 0.073312916 0.7192233
-0 1 0|0.60703295 0.3218385 0.47832417
0 1 1|0.68343294 0.073312916 0.7192233
1 -0 -0|0.19322336 0.48136216 0.49846292
1 -0 1|0.19322336 0.48136216 0.49846292
version 1
0 0 0|1 0 0
0 0 1|1 0 0.77999973
0 0 2|0.0800004 0 1
0 0 3|0 0.7600002 1
0 0 4|0 1 0.3399999
0 0 5|0.5 1 0
0 0 6|1 0.8399999 0
0 0 7|1 0.4200003 0

View file

@ -1,16 +1,14 @@
use bevy::{
app::{Startup, Update},
asset::{AssetId, AssetServer, Assets},
asset::{AssetServer, Assets, Handle},
ecs::{
entity::Entity,
query::With,
system::{Commands, Query, Res, ResMut, Resource},
},
math::Vec3,
reflect::Reflect,
render::{camera::Camera, color::Color},
transform::components::GlobalTransform,
utils::HashMap,
window::Window,
};
use bevy_egui::{
@ -18,7 +16,7 @@ use bevy_egui::{
self, color_picker,
ecolor::{hsv_from_rgb, rgb_from_hsv},
epaint::Hsva,
Id,
Id, Rect,
},
EguiContexts, EguiPlugin,
};
@ -40,34 +38,97 @@ use super::{
orbit_camera::orbit_camera::{pan_orbit_camera, spawn_camera},
};
pub struct AssetEditData {
path: String,
id: Handle<OctTreeAsset>,
has_changed_since_last_save: bool,
}
#[derive(Default, Resource)]
pub struct EditWindowUIState {
brush_color: Hsva,
window_identifier: Option<Id>,
meshes: HashMap<String, AssetId<OctTreeAsset>>,
meshes: Vec<AssetEditData>,
window: Option<Rect>,
}
// refactor into hashmaps of the AssetId
impl EditWindowUIState {
pub fn name_for(&self, id: AssetId<OctTreeAsset>) -> Option<String> {
for (each_str, each_id) in &self.meshes {
if each_id == &id {
return Some(each_str.clone());
pub fn name_from_handle(&self, id: Handle<OctTreeAsset>) -> Option<String> {
for each in &self.meshes {
if &each.id == &id {
return Some(each.path.clone());
}
}
None
}
pub fn id_for(&self, name: &String) -> Option<AssetId<OctTreeAsset>> {
if self.meshes.contains_key(name) {
return Some(self.meshes.get(name).unwrap().clone());
pub fn has_changed_from_name(&self, name: &String) -> Option<bool> {
for each in &self.meshes {
if &each.path == name {
return Some(each.has_changed_since_last_save);
}
}
None
}
pub fn has_changed_from_handle(&self, id: Handle<OctTreeAsset>) -> Option<bool> {
for each in &self.meshes {
if &each.id == &id {
return Some(each.has_changed_since_last_save);
}
}
None
}
pub fn handle_from_name(&self, name: &String) -> Option<Handle<OctTreeAsset>> {
for each in &self.meshes {
if &each.path == name {
return Some(each.id.clone());
}
}
None
}
pub fn set_grid_data(
&mut self,
path: &String,
id: Handle<OctTreeAsset>,
has_changed: bool,
) -> bool {
let mut match_count: usize = 0;
for each in &self.meshes {
if &each.path == path {
match_count += 1;
} else if &each.id == &id {
match_count += 1;
}
}
if match_count == 1 {
for each in &mut self.meshes {
if &each.path == path {
each.id = id.clone();
each.has_changed_since_last_save = has_changed;
} else if &each.id == &id {
each.path = path.clone();
each.has_changed_since_last_save = has_changed;
}
}
return true;
} else if match_count == 0 {
self.meshes.push(AssetEditData {
path: path.clone(),
id,
has_changed_since_last_save: has_changed,
});
return true;
} else {
return false;
}
}
}
pub fn register_edit_ui(app: &mut bevy::prelude::App) -> &mut bevy::prelude::App {
app.insert_resource(EditWindowUIState {
brush_color: egui_color_from(Color::RED),
window_identifier: None,
meshes: HashMap::new(),
meshes: Vec::new(),
window: None,
});
app.add_plugins(EguiPlugin);
app.add_systems(Startup, startup_system_edit_ui);
@ -88,7 +149,7 @@ pub fn edit_click_events(
oct_tree: Query<(&GlobalTransform, &OctAssetMarker)>,
windows: Query<&Window>,
mut oct_assets: ResMut<Assets<OctTreeAsset>>,
shared_ui_state: ResMut<EditWindowUIState>,
mut shared_ui_state: ResMut<EditWindowUIState>,
) {
let paint = input_data.get_binary_input(&PAINT_VOXEL.into());
let remove = input_data.get_binary_input(&REMOVE_VOXEL.into());
@ -119,12 +180,20 @@ pub fn edit_click_events(
col.voxel_index_location,
color_from(shared_ui_state.brush_color),
);
let path = shared_ui_state.name_from_handle(oct.oct_handle.clone());
if let Some(path) = path {
shared_ui_state.set_grid_data(&path, oct.oct_handle.clone(), true);
}
}
} else if let Some(_) = remove.filter(|input| input.pressed()) {
if collision.is_some() {
let col = collision.unwrap();
edit_octtree.remove_voxel(col.voxel_index_location);
let path = shared_ui_state.name_from_handle(oct.oct_handle.clone());
if let Some(path) = path {
shared_ui_state.set_grid_data(&path, oct.oct_handle.clone(), true);
}
}
} else if let Some(_) = insert.filter(|input| input.pressed()) {
if collision.is_some() {
@ -136,13 +205,17 @@ pub fn edit_click_events(
hitvox.round(),
color_from(shared_ui_state.brush_color),
);
let path = shared_ui_state.name_from_handle(oct.oct_handle.clone());
if let Some(path) = path {
shared_ui_state.set_grid_data(&path, oct.oct_handle.clone(), true);
}
}
}
}
}
}
}
// update to use events to respond to
pub fn update_asset_mesh_list(
oct_assets: Res<Assets<OctTreeAsset>>,
mut shared_ui_state: ResMut<EditWindowUIState>,
@ -150,17 +223,22 @@ pub fn update_asset_mesh_list(
) {
for each in oct_assets.ids() {
let asset = oct_assets.get(each);
if asset.is_some() {
let handle = server.get_id_handle(each);
if asset.is_some() && handle.is_some() {
let handle = handle.unwrap();
let path = server.get_path(each);
if path.is_some() && shared_ui_state.name_for(each).is_none() {
let path_str = path.unwrap().to_string();
if path_str.to_lowercase().ends_with(".vvg") {
let reversed: String = path_str.chars().rev().collect();
let reversed_removed = reversed.replacen("gvv.", "", 1);
let forward: String = reversed_removed.chars().rev().collect();
shared_ui_state.meshes.insert(forward, each.clone());
if let Some(path) = path {
if shared_ui_state.name_from_handle(handle.clone()).is_none() {
let path_str = path.to_string();
if path_str.to_lowercase().ends_with(".vvg") {
let reversed: String = path_str.chars().rev().collect();
let reversed_removed = reversed.replacen("gvv.", "", 1);
let forward: String = reversed_removed.chars().rev().collect();
shared_ui_state.set_grid_data(&forward, handle.clone(), false);
}
}
} else if shared_ui_state.name_for(each).is_none() {
}
if shared_ui_state.name_from_handle(handle.clone()).is_none() {
let base_str: String = "unnamed_mesh_".into();
let mut found_name = false;
let mut count: usize = 1;
@ -168,19 +246,20 @@ pub fn update_asset_mesh_list(
while !found_name {
attempt = base_str.clone();
attempt.push_str(count.to_string().as_str());
if shared_ui_state.id_for(&attempt).is_none() {
if shared_ui_state.handle_from_name(&attempt).is_none() {
found_name = true;
} else {
count += 1;
}
}
shared_ui_state.meshes.insert(attempt, each.clone());
let mut has_changed = shared_ui_state.has_changed_from_handle(handle.clone());
if has_changed.is_none() {
has_changed = Some(false);
}
shared_ui_state.set_grid_data(&attempt, handle.clone(), has_changed.unwrap());
}
}
}
for (each_name, each_id) in &shared_ui_state.meshes {
println!("{}", each_name);
}
}
pub fn edit_window_ui(
@ -199,17 +278,14 @@ pub fn edit_window_ui(
shared_ui_state.window_identifier = Some(id);
} else {
response = response.id(shared_ui_state.window_identifier.unwrap());
/*for window_entity in &windows {
for window_entity in &windows {
let ctx = contexts.ctx_for_window_mut(window_entity);
let pos = ctx.memory(|memory| {
// formatting comment
memory.area_rect(shared_ui_state.window_identifier.unwrap())
});
if let Some(pos) = pos {
let (x, y, w, h) = (pos.min.x, pos.min.y, pos.max.x, pos.max.y);
println!("window data: {x}, {y} to {w}, {h}");
}
}*/
shared_ui_state.window = pos;
}
}
response.show(contexts.ctx_mut(), |ui| {
@ -231,13 +307,32 @@ pub fn edit_window_ui(
let id = pair.oct_handle.clone();
let tree = oct_assets.get_mut(id);
if tree.is_some() {
//
let tree = tree.unwrap();
tree.model
.set_voxel_at_location(Vec3::ZERO, color_from(shared_ui_state.brush_color));
}
}
}
if ui.button("Save Any Changes").clicked() {
//save all files.
for each in &shared_ui_state.meshes {
if each.has_changed_since_last_save {
let handle = each.id.clone();
let path = each.path.clone() + ".vvg";
let asset = oct_assets.get(handle);
if asset.is_some() {
let result =
crate::vvlib::oct_asset::serialization::meshes::write_latest_version(
&path,
asset.unwrap(),
);
if result {
println!("{} saved to disk.", &path);
}
}
}
}
}
});
}

View file

@ -24,7 +24,7 @@ use bevy::{
};
use thiserror::Error;
use super::octtree::{self, OctTree, Path};
use super::octtree::{self, Path};
#[derive(Asset, TypePath)]
pub struct OctTreeAsset {
@ -47,6 +47,229 @@ pub enum OctLoadError {
FileNotFound(String),
}
pub mod serialization {
pub mod meshes {
use std::path::Path;
use bevy::{
asset::io::{AssetSource, AssetWriter},
tasks::block_on,
};
use super::super::{OctLoadError, OctTreeAsset};
pub fn load_detect_version(bytes: Vec<u8>) -> Result<OctTreeAsset, OctLoadError> {
let data_str = std::str::from_utf8(bytes.as_slice());
if data_str.is_err() {
return Err(OctLoadError::FileDataInvalid("".to_string()));
}
let data: String = data_str.unwrap().into();
let lines: std::str::Lines<'_> = data.lines();
let mut iter: std::str::Lines<'_> = lines.into_iter();
let first: Option<&str> = iter.next();
let version: MeshFileVersions = determine_version(first);
match version {
MeshFileVersions::Version1 => {
return versions::version_1::load(iter);
}
MeshFileVersions::Error => {
return Err(OctLoadError::FileDataInvalid("invalid version data".into()))
}
}
}
pub fn write_latest_version(path: &String, asset: &OctTreeAsset) -> bool {
write_any_version(path, asset, Default::default())
}
pub fn write_any_version(
path: &String,
asset: &OctTreeAsset,
version: MeshFileVersions,
) -> bool {
if let Some(writer) = get_writer() {
let mut bytes: Vec<u8> = Vec::new();
if let Some(mut header) = version_header(version) {
bytes.append(&mut header);
} else {
return false;
}
match version {
MeshFileVersions::Error => return false,
MeshFileVersions::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<Box<dyn AssetWriter>> {
AssetSource::get_default_writer("assets".into())(true)
}
#[derive(Copy, Clone, Default)]
pub enum MeshFileVersions {
#[default]
Version1,
Error,
}
mod versions {
pub mod version_1 {
use bevy::{math::Vec3, render::color::Color};
use crate::vvlib::octtree::OctTree;
use super::super::super::super::{OctLoadError, OctTreeAsset};
pub fn load(data: std::str::Lines<'_>) -> Result<OctTreeAsset, OctLoadError> {
let mut oct: Option<OctTree<Color>> = None;
for line in data {
//
let line: String = line.into();
let mut sides = line.split("|");
if sides.clone().count() != 2 {
return Err(OctLoadError::FileDataInvalid(line));
}
let left = sides.next();
let right = sides.next();
if !(left.is_some() && right.is_some()) {
return Err(OctLoadError::FileDataInvalid(line));
}
let left: String = left.unwrap().into();
let right: String = right.unwrap().into();
let mut left_ax = left.split_whitespace();
let mut right_ax = right.split_whitespace();
if left_ax.clone().count() != 3 || right_ax.clone().count() != 3 {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_x = left_ax.next();
if loc_x.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_x = loc_x.unwrap().parse::<f32>();
if loc_x.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_x = loc_x.unwrap();
let loc_y = left_ax.next();
if loc_y.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_y = loc_y.unwrap().parse::<f32>();
if loc_y.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_y = loc_y.unwrap();
let loc_z = left_ax.next();
if loc_z.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_z = loc_z.unwrap().parse::<f32>();
if loc_z.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_z = loc_z.unwrap();
let loc = Vec3::new(loc_x, loc_y, loc_z);
let col_r = right_ax.next();
if col_r.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_r = col_r.unwrap();
let col_r = col_r.parse::<f32>();
if col_r.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_r = col_r.unwrap();
let col_g = right_ax.next();
if col_g.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_g = col_g.unwrap();
let col_g = col_g.parse::<f32>();
if col_g.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_g = col_g.unwrap();
let col_b = right_ax.next();
if col_b.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_b = col_b.unwrap();
let col_b = col_b.parse::<f32>();
if col_b.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_b = col_b.unwrap();
let col = Color::rgb(col_r, col_g, col_b);
if oct.is_none() {
oct = Some(OctTree::new(loc, col));
} else {
let mut other = oct.unwrap();
other.set_voxel_at_location(loc, col);
oct = Some(other);
}
}
if oct.is_none() {
return Err(OctLoadError::FileDataInvalid("".to_string()));
}
Ok(OctTreeAsset {
model: oct.unwrap(),
handle: None,
task: None,
})
}
pub fn save(asset: &OctTreeAsset) -> Option<Vec<u8>> {
let mut data: String = "".into();
let voxels = asset.model.collect_voxels();
if voxels.len() == 0 {
return None;
}
for (_, each_pos, each_val) in voxels {
let (x, y, z) = (each_pos.x, each_pos.y, each_pos.z);
let (r, g, b) = (each_val.r(), each_val.g(), each_val.b());
data = format!("{data}\n{x} {y} {z}|{r} {g} {b}");
}
Some(data.as_bytes().to_vec())
}
}
}
const VERSION1: &str = "version 1";
pub fn determine_version(line: Option<&str>) -> MeshFileVersions {
if line.is_some() {
let line: String = line.unwrap().into();
if line.to_lowercase() == String::from(VERSION1) {
return MeshFileVersions::Version1;
}
}
MeshFileVersions::Error
}
pub fn version_header(version: MeshFileVersions) -> Option<Vec<u8>> {
match version {
MeshFileVersions::Error => None,
MeshFileVersions::Version1 => Some((VERSION1.to_string()).as_bytes().to_vec()),
}
}
}
}
impl AssetLoader for OctLoader {
type Asset = OctTreeAsset;
type Settings = ();
@ -59,119 +282,14 @@ impl AssetLoader for OctLoader {
_load_context: &'a mut bevy::asset::LoadContext,
) -> bevy::utils::BoxedFuture<'a, Result<Self::Asset, Self::Error>> {
Box::pin(async move {
let mut bytes = Vec::<u8>::new();
let mut bytes: Vec<u8> = Vec::<u8>::new();
let result = reader.read_to_end(&mut bytes).await;
if result.is_err() {
return Err(OctLoadError::FileNotFound("".to_string()));
return Err(OctLoadError::FileNotFound(
"could not read_to_end the bytes".to_string(),
));
}
let data_str = std::str::from_utf8(bytes.as_slice());
if data_str.is_err() {
return Err(OctLoadError::FileDataInvalid("".to_string()));
}
let data: String = data_str.unwrap().into();
let lines = data.lines();
let mut oct: Option<OctTree<Color>> = None;
for line in lines {
//
let line: String = line.into();
let mut sides = line.split("|");
if sides.clone().count() != 2 {
return Err(OctLoadError::FileDataInvalid(line));
}
let left = sides.next();
let right = sides.next();
if !(left.is_some() && right.is_some()) {
return Err(OctLoadError::FileDataInvalid(line));
}
let left: String = left.unwrap().into();
let right: String = right.unwrap().into();
let mut left_ax = left.split_whitespace();
let mut right_ax = right.split_whitespace();
if left_ax.clone().count() != 3 || right_ax.clone().count() != 3 {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_x = left_ax.next();
if loc_x.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_x = loc_x.unwrap().parse::<f32>();
if loc_x.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_x = loc_x.unwrap();
let loc_y = left_ax.next();
if loc_y.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_y = loc_y.unwrap().parse::<f32>();
if loc_y.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_y = loc_y.unwrap();
let loc_z = left_ax.next();
if loc_z.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_z = loc_z.unwrap().parse::<f32>();
if loc_z.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let loc_z = loc_z.unwrap();
let loc = Vec3::new(loc_x, loc_y, loc_z);
let col_r = right_ax.next();
if col_r.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_r = col_r.unwrap();
let col_r = col_r.parse::<f32>();
if col_r.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_r = col_r.unwrap();
let col_g = right_ax.next();
if col_g.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_g = col_g.unwrap();
let col_g = col_g.parse::<f32>();
if col_g.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_g = col_g.unwrap();
let col_b = right_ax.next();
if col_b.is_none() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_b = col_b.unwrap();
let col_b = col_b.parse::<f32>();
if col_b.is_err() {
return Err(OctLoadError::FileDataInvalid(line));
}
let col_b = col_b.unwrap();
let col = Color::rgb(col_r, col_g, col_b);
if oct.is_none() {
oct = Some(OctTree::new(loc, col));
} else {
let mut other = oct.unwrap();
other.set_voxel_at_location(loc, col);
oct = Some(other);
}
}
if oct.is_none() {
return Err(OctLoadError::FileDataInvalid("".to_string()));
}
Ok(OctTreeAsset {
model: oct.unwrap(),
handle: None,
task: None,
})
return serialization::meshes::load_detect_version(bytes);
})
}
}