1use gizmo_core::{EntityName, World};
2use gizmo_core::component::{MeshSource, MaterialSource};
3use serde::{Deserialize, Serialize};
4use std::fs;
5
6use gizmo_core::component::{Children, Parent};
7use std::collections::HashMap;
8
9#[derive(Serialize, Deserialize, Clone)]
11pub struct SceneData {
12 pub entities: Vec<EntityData>,
13 #[serde(default)]
14 pub joints: Vec<gizmo_physics_rigid::joints::Joint>,
15}
16
17#[derive(Serialize, Deserialize, Clone)]
19pub struct PrefabData {
20 pub root_id: u32,
21 pub entities: Vec<EntityData>,
22 #[serde(default)]
23 pub joints: Vec<gizmo_physics_rigid::joints::Joint>,
24}
25
26#[derive(Serialize, Deserialize, Clone)]
28pub struct EntityData {
29 pub original_id: u32,
30 pub name: Option<String>,
31 pub mesh_source: Option<String>,
32 pub material_source: Option<MaterialData>,
33 #[serde(default)]
34 pub parent_id: Option<u32>,
35 #[serde(default)]
36 pub components: std::collections::BTreeMap<String, String>,
37}
38
39#[derive(Serialize, Deserialize, Clone)]
41pub struct MaterialData {
42 pub albedo: [f32; 4],
43 pub roughness: f32,
44 pub metallic: f32,
45 pub unlit: f32,
46 pub texture_source: Option<String>,
47}
48
49impl SceneData {
50 pub fn save(
52 world: &World,
53 file_path: &str,
54 registry: &crate::registry::SceneRegistry,
55 ) -> Result<(), String> {
56 if let Some(parent) = std::path::Path::new(file_path).parent() {
57 let _ = fs::create_dir_all(parent);
58 }
59 let entities_data = Self::serialize_entities(
60 world,
61 world
62 .iter_alive_entities()
63 .into_iter()
64 .map(|e| e.id())
65 .collect(),
66 registry,
67 );
68
69 let mut joints = Vec::new();
70 if let Ok(physics_world) = world.try_get_resource::<gizmo_physics_rigid::world::PhysicsWorld>() {
71 joints = physics_world.joints.clone();
72 }
73
74 let scene = SceneData {
75 entities: entities_data,
76 joints,
77 };
78
79 let string_data = ron::ser::to_string_pretty(&scene, ron::ser::PrettyConfig::default())
80 .map_err(|e| format!("[SceneData::save] Serileştirme hatası: {}", e))?;
81
82 fs::write(file_path, string_data)
83 .map_err(|e| format!("[SceneData::save] Dosya yazma hatası: {}", e))?;
84
85 tracing::info!("✅ Sahne kaydedildi → {}", file_path);
86 Ok(())
87 }
88
89 pub fn serialize_entities(
91 world: &World,
92 entity_ids: Vec<u32>,
93 registry: &crate::registry::SceneRegistry,
94 ) -> Vec<EntityData> {
95 let mut entities_data = Vec::new();
96 let names = world.borrow::<EntityName>();
97 let meshes = world.borrow::<MeshSource>();
98 let materials = world.borrow::<MaterialSource>();
99 let parents = world.borrow::<Parent>();
100
101 for &id in &entity_ids {
102 let name = names.get(id).map(|n| n.0.clone());
103
104 if let Some(ref n) = name {
106 if n.starts_with("Editor ") || n == "Highlight Box" {
107 continue;
108 }
109 }
110
111 let mesh_source = meshes.get(id).map(|m| m.0.clone());
112 let material_source = materials.get(id).map(|m| MaterialData {
113 albedo: m.albedo,
114 roughness: m.roughness,
115 metallic: m.metallic,
116 unlit: m.unlit,
117 texture_source: m.texture_source.clone(),
118 });
119 let parent_id = parents.get(id).map(|p| p.0);
120
121 let mut dynamic_components = std::collections::BTreeMap::new();
122 for comp_name in registry.all_components() {
123 if let Some(serializer) = registry.get_serializer(comp_name) {
124 if let Some(comp_value) = serializer(world, id) {
125 dynamic_components.insert(comp_name.clone(), comp_value);
126 }
127 }
128 }
129
130 if name.is_some()
131 || mesh_source.is_some()
132 || material_source.is_some()
133 || parent_id.is_some()
134 || !dynamic_components.is_empty()
135 {
136 entities_data.push(EntityData {
137 original_id: id,
138 name,
139 mesh_source,
140 material_source,
141 parent_id,
142 components: dynamic_components,
143 });
144 }
145 }
146
147 tracing::info!(">>> serialize_entities: total entities checked: {}, serialized: {}", entity_ids.len(), entities_data.len());
148 entities_data
149 }
150
151 pub fn load_into(
153 file_path: &str,
154 world: &mut World,
155 registry: &crate::registry::SceneRegistry,
156 ) -> bool {
157 let string_data = match fs::read_to_string(file_path) {
158 Ok(content) => content,
159 Err(_) => return false,
160 };
161
162 let scene: SceneData = match ron::from_str(&string_data) {
163 Ok(s) => s,
164 Err(e) => {
165 tracing::error!("❌ Sahne dosyası geçersiz ({}): {}", file_path, e);
166 return false;
167 }
168 };
169
170 let entities = scene.entities;
171 tracing::info!(">>> load_into: Read {} entities from file", entities.len());
172
173 let id_map = Self::instantiate_entities(
174 entities,
175 None,
176 world,
177 registry,
178 );
179
180 if let Ok(mut physics_world) = world.try_get_resource_mut::<gizmo_physics_rigid::world::PhysicsWorld>() {
181 for mut joint in scene.joints {
182 if let (Some(&new_a), Some(&new_b)) = (id_map.get(&joint.entity_a.id()), id_map.get(&joint.entity_b.id())) {
183 joint.entity_a = gizmo_core::entity::Entity::new(new_a, 0);
184 joint.entity_b = gizmo_core::entity::Entity::new(new_b, 0);
185 physics_world.joints.push(joint);
186 }
187 }
188 }
189
190 tracing::info!("✅ Sahne yüklendi ← {}", file_path);
191 true
192 }
193
194 pub fn instantiate_entities(
196 entities: Vec<EntityData>,
197 root_parent: Option<u32>,
198 world: &mut World,
199 registry: &crate::registry::SceneRegistry,
200 ) -> HashMap<u32, u32> {
201 let mut id_map = HashMap::new();
202 let mut entity_structs = HashMap::new();
203
204 for data in &entities {
205 let root_ent = world.spawn();
206 id_map.insert(data.original_id, root_ent.id());
207 entity_structs.insert(root_ent.id(), root_ent);
208 }
209
210 let mut children_map: HashMap<u32, Vec<u32>> = HashMap::new();
211
212 for data in entities {
213 let new_id = id_map[&data.original_id];
214 let entity = entity_structs[&new_id];
215
216 if let Some(n) = data.name {
217 world.add_component(entity, EntityName::new(&n));
218 }
219
220 for (comp_name, comp_val) in &data.components {
221 if let Some(deserializer) = registry.get_deserializer(comp_name) {
222 deserializer(world, new_id, comp_val);
223 }
224 }
225
226 if let Some(mesh_src) = data.mesh_source {
227 world.add_component(entity, MeshSource(mesh_src));
228 }
229
230 if let Some(mat_data) = data.material_source {
231 world.add_component(entity, MaterialSource {
232 albedo: mat_data.albedo,
233 roughness: mat_data.roughness,
234 metallic: mat_data.metallic,
235 unlit: mat_data.unlit,
236 texture_source: mat_data.texture_source,
237 });
238 }
239
240 let mut resolved_parent = None;
241 if let Some(orig_parent) = data.parent_id {
242 if let Some(&p_id) = id_map.get(&orig_parent) {
243 resolved_parent = Some(p_id);
244 }
245 } else {
246 resolved_parent = root_parent;
247 }
248
249 if let Some(p_id) = resolved_parent {
250 world.add_component(entity, Parent(p_id));
251 children_map.entry(p_id).or_default().push(new_id);
252 }
253 }
254
255 for (p_id, mut c_list) in children_map {
256 if let Some(&p_ent) = entity_structs.get(&p_id) {
257 world.add_component(p_ent, Children(c_list));
258 } else if let Some(p_ent) = world.get_entity(p_id) {
259 let existing: Vec<u32> = world
260 .borrow::<Children>()
261 .get(p_id)
262 .map(|c| c.0.clone())
263 .unwrap_or_default();
264 let mut merged = existing;
265 merged.append(&mut c_list);
266 world.add_component(p_ent, Children(merged));
267 }
268 }
269
270 id_map
271 }
272
273 pub fn save_prefab(
275 world: &World,
276 root_entity_id: u32,
277 file_path: &str,
278 registry: &crate::registry::SceneRegistry,
279 ) -> Result<(), String> {
280 if let Some(parent) = std::path::Path::new(file_path).parent() {
281 let _ = fs::create_dir_all(parent);
282 }
283
284 let mut ids_to_save = vec![root_entity_id];
285 let children_storage = world.borrow::<Children>();
286
287 let mut i = 0;
288 while i < ids_to_save.len() {
289 let current = ids_to_save[i];
290 if let Some(children_comp) = children_storage.get(current) {
291 for &child_id in &children_comp.0 {
292 ids_to_save.push(child_id);
293 }
294 }
295 i += 1;
296 }
297
298 let mut entities_data = Self::serialize_entities(world, ids_to_save.clone(), registry);
299
300 if let Some(root_data) = entities_data
301 .iter_mut()
302 .find(|d| d.original_id == root_entity_id)
303 {
304 root_data.parent_id = None;
305 }
306
307 let mut joints = Vec::new();
308 if let Ok(physics_world) = world.try_get_resource::<gizmo_physics_rigid::world::PhysicsWorld>() {
309 for joint in &physics_world.joints {
310 if ids_to_save.contains(&joint.entity_a.id()) && ids_to_save.contains(&joint.entity_b.id()) {
311 joints.push(joint.clone());
312 }
313 }
314 }
315
316 let prefab = PrefabData {
317 root_id: root_entity_id,
318 entities: entities_data,
319 joints,
320 };
321
322 let string_data = ron::ser::to_string_pretty(&prefab, ron::ser::PrettyConfig::default())
323 .map_err(|e| format!("[SceneData::save_prefab] Serileştirme hatası: {}", e))?;
324
325 fs::write(file_path, string_data)
326 .map_err(|e| format!("[SceneData::save_prefab] Dosya yazma hatası: {}", e))?;
327
328 tracing::info!("✅ Prefab kaydedildi → {}", file_path);
329 Ok(())
330 }
331
332 pub fn load_prefab(
334 file_path: &str,
335 parent_entity: Option<u32>,
336 world: &mut World,
337 registry: &crate::registry::SceneRegistry,
338 ) -> Option<u32> {
339 let string_data = match fs::read_to_string(file_path) {
340 Ok(content) => content,
341 Err(_) => return None,
342 };
343
344 let prefab: PrefabData = match ron::from_str(&string_data) {
345 Ok(p) => p,
346 Err(e) => {
347 tracing::info!("❌ Prefab dosyası geçersiz ({}): {}", file_path, e);
348 return None;
349 }
350 };
351
352 let id_map = Self::instantiate_entities(
353 prefab.entities,
354 parent_entity,
355 world,
356 registry,
357 );
358
359 let new_root_id = id_map.get(&prefab.root_id).copied();
360
361 if let (Some(new_r), Some(p_id)) = (new_root_id, parent_entity) {
362 if let Some(p_ent) = world.get_entity(p_id) {
363 let mut children_list = world
364 .borrow::<Children>()
365 .get(p_id)
366 .map(|c| c.0.clone())
367 .unwrap_or_default();
368 children_list.push(new_r);
369 world.add_component(p_ent, Children(children_list));
370 }
371 }
372
373 if let Ok(mut physics_world) = world.try_get_resource_mut::<gizmo_physics_rigid::world::PhysicsWorld>() {
374 for mut joint in prefab.joints {
375 if let (Some(&new_a), Some(&new_b)) = (id_map.get(&joint.entity_a.id()), id_map.get(&joint.entity_b.id())) {
376 joint.entity_a = gizmo_core::entity::Entity::new(new_a, 0);
377 joint.entity_b = gizmo_core::entity::Entity::new(new_b, 0);
378 physics_world.joints.push(joint);
379 }
380 }
381 }
382
383 tracing::info!("✅ Prefab yüklendi ← {}", file_path);
384 new_root_id
385 }
386
387 pub fn get_entity_names(world: &World) -> Vec<(u32, String)> {
389 let mut result = Vec::new();
390 let names = world.borrow::<EntityName>();
391 for (entity_id, _) in names.iter() {
392 if let Some(name) = names.get(entity_id) {
393 result.push((entity_id, name.0.clone()));
394 }
395 }
396 result
397 }
398
399 pub fn find_entity_by_name(world: &World, target_name: &str) -> Option<u32> {
401 let names = world.borrow::<EntityName>();
402 for (entity_id, _) in names.iter() {
403 if let Some(name) = names.get(entity_id) {
404 if name.0 == target_name {
405 return Some(entity_id);
406 }
407 }
408 }
409 None
410 }
411}
412
413#[cfg(test)]
414mod tests {
415 use super::*;
416 use gizmo_core::World;
417 use gizmo_physics_rigid::joints::data::{Joint, JointType};
418
419 #[test]
420 fn test_prefab_joint_serialization() {
421 let mut world = World::new();
422 let ent1 = world.spawn();
423 let ent2 = world.spawn();
424
425 let joint = Joint {
426 entity_a: ent1,
427 entity_b: ent2,
428 local_anchor_a: gizmo_math::Vec3::ZERO,
429 local_anchor_b: gizmo_math::Vec3::ZERO,
430 break_force: 1000.0,
431 break_torque: 1000.0,
432 is_broken: false,
433 collision_enabled: false,
434 data: gizmo_physics_rigid::joints::data::JointData::Fixed,
435 };
436
437 let prefab_data = PrefabData {
438 root_id: ent1.id(),
439 entities: vec![],
440 joints: vec![joint.clone()],
441 };
442
443 let serialized = ron::ser::to_string(&prefab_data).unwrap();
444 assert!(serialized.contains("Fixed"));
445
446 let deserialized: PrefabData = ron::from_str(&serialized).unwrap();
447 assert_eq!(deserialized.joints.len(), 1);
448 assert!(matches!(deserialized.joints[0].data, gizmo_physics_rigid::joints::data::JointData::Fixed));
449 }
450}