file save and loading with expandable backwards compat layer
This commit is contained in:
parent
87dacb619e
commit
f764cd14c3
|
@ -1,6 +1,9 @@
|
|||
version 1
|
||||
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
|
||||
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
|
|
@ -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 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.meshes.insert(forward, each.clone());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -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,31 +47,94 @@ pub enum OctLoadError {
|
|||
FileNotFound(String),
|
||||
}
|
||||
|
||||
impl AssetLoader for OctLoader {
|
||||
type Asset = OctTreeAsset;
|
||||
type Settings = ();
|
||||
type Error = OctLoadError;
|
||||
pub mod serialization {
|
||||
|
||||
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<Self::Asset, Self::Error>> {
|
||||
Box::pin(async move {
|
||||
let mut bytes = Vec::<u8>::new();
|
||||
let result = reader.read_to_end(&mut bytes).await;
|
||||
if result.is_err() {
|
||||
return Err(OctLoadError::FileNotFound("".to_string()));
|
||||
}
|
||||
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 = data.lines();
|
||||
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 lines {
|
||||
for line in data {
|
||||
//
|
||||
let line: String = line.into();
|
||||
let mut sides = line.split("|");
|
||||
|
@ -172,6 +235,61 @@ impl AssetLoader for OctLoader {
|
|||
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 = ();
|
||||
type Error = OctLoadError;
|
||||
|
||||
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<Self::Asset, Self::Error>> {
|
||||
Box::pin(async move {
|
||||
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(
|
||||
"could not read_to_end the bytes".to_string(),
|
||||
));
|
||||
}
|
||||
return serialization::meshes::load_detect_version(bytes);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue