feo_oop_engine/scene/game_object/obj.rs
1//! A GameObject that can be built from an obj file.
2//!
3//! TODO: explain OOP here
4//!
5use {
6 super::{
7 GameObject,
8 camera::Camera,
9 light::Light,
10 },
11 crate::{
12 registration::{
13 relation::{ParentWrapper, Child, Parent},
14 named::Named,
15 id::{
16 ID
17 }
18 },
19 scripting::{
20 Script,
21 executor::Spawner,
22 Scriptable,
23 globals::{
24 EngineGlobals,
25 Global
26 }
27 },
28 graphics::{
29 Drawable,
30 draw_pass_manager::DrawPassManager,
31 lighting_pass_manager::LightingPassManager,
32 },
33 components::{
34 Vertex,
35 TextureIndex,
36 Normal
37 },
38 term_ui,
39 event::UserEvent,
40 components::{material::Material, triangle_mesh::TriangleMesh}
41 },
42 feo_math::{
43 utils::space::Space,
44 rotation::quaternion::Quaternion,
45 linear_algebra::vector3::Vector3
46 },
47 std::{
48 any::Any,
49 collections::HashMap,
50 fs,
51 io::stdout,
52 mem,
53 sync::{Arc, RwLock}
54 },
55 futures::executor::block_on,
56 vulkano::{
57 descriptor::{
58 descriptor_set::PersistentDescriptorSet,
59 },
60 sync::GpuFuture
61 },
62 winit::event::Event
63};
64
65#[derive(Scriptable, GameObject, Drawable, Child, Parent, Named)] // import
66pub struct Obj {
67 pub id: ID,
68 pub name: String,
69 pub parent: ParentWrapper,
70
71 pub visible: bool,
72
73 pub subspace: Space, // note is the subspace within the parent space
74
75 pub triangle_mesh: Vec<Arc<TriangleMesh>>,
76 // pub material: Option<Material>, // object does not have material triangle mesh does
77
78 pub script: Option<Box<Script<Self>>>,
79
80 pub children: Vec<Arc<RwLock<dyn GameObject>>>,
81}
82
83impl std::fmt::Debug for Obj {
84 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
85 f.debug_struct("Obj")
86 .field("id", &self.id)
87 .field("name", &self.name)
88 .field("parent", &self.parent)
89 .field("visible", &self.visible)
90 .field("subspace", &self.subspace)
91 .field("triangle_mesh", &self.triangle_mesh)
92 .field("script", &self.script)
93 .field("children", &self.children).finish()
94 }
95}
96
97impl Clone for Obj {
98 fn clone(&self) -> Self {
99 let id = self.id.get_system().take();
100 Obj{
101 id,
102 name: self.name.clone(),
103 parent: self.parent.clone(),
104 visible: self.visible,
105 subspace: self.subspace,
106 triangle_mesh: self.triangle_mesh.clone(),
107 script: self.script.clone(),
108 children: self.children.clone().into_iter().map(|_child| {
109 // Dangerous
110 todo!();
111 }).collect::<Vec<Arc<RwLock<dyn GameObject>>>>(),
112 }
113 }
114}
115
116impl PartialEq for Obj { // auto-generate this somehow
117 fn eq(&self, other: &Self) -> bool {
118 self.get_id() == other.get_id()
119 }
120}
121
122// TODO: rename to builders and build gameobjects with them
123impl Obj {
124 #[allow(clippy::too_many_arguments, clippy::or_fun_call)]
125 pub fn new_empty(
126 name: Option<&str>,
127
128 parent: Option<Arc<RwLock<dyn GameObject>>>,
129
130 position: Option<Vector3<f32>>,
131 rotation: Option<Quaternion<f32>>,
132 scale_factor: Option<Vector3<f32>>,
133
134 visible: bool,
135
136 engine_globals: EngineGlobals,
137
138 script: Option<Box<Script<Self>>>) -> Arc<RwLock<Self>> {
139 let id = engine_globals.id_system.take();
140
141 return Arc::new(RwLock::new( Obj {
142 name: name.unwrap_or((String::from("obj_") + id.to_string().as_str()).as_str()).to_owned(),
143 id,
144 parent: match parent {
145 Some(game_object) => {
146 ParentWrapper::GameObject(game_object)
147 },
148 None => {
149 ParentWrapper::Scene(engine_globals.scene)
150 }
151 },
152
153 visible,
154
155 subspace: Space::new(position, rotation, scale_factor),
156
157 triangle_mesh: Vec::new(),
158
159 script,
160
161 children: Vec::new()
162 }));
163 }
164
165 #[allow(clippy::too_many_arguments, clippy::or_fun_call)]
166 pub fn from_triangle_mesh_vec(
167 name: Option<&str>,
168 triangle_mesh_vec: Vec<Arc<TriangleMesh>>,
169
170 parent: Option<Arc<RwLock<dyn GameObject>>>,
171
172 position: Option<Vector3<f32>>,
173 rotation: Option<Quaternion<f32>>,
174 scale_factor: Option<Vector3<f32>>,
175
176 visible: bool,
177
178 engine_globals: EngineGlobals,
179
180 script: Option<Box<Script<Self>>>) -> Arc<RwLock<Self>> {
181 let id = engine_globals.id_system.take();
182 return Arc::new(RwLock::new( Obj{
183 name: name.unwrap_or((String::from("obj_") + id.to_string().as_str()).as_str()).to_owned(),
184 id,
185 parent: match parent {
186 Some(game_object) => {
187 ParentWrapper::GameObject(game_object)
188 },
189 None => {
190 ParentWrapper::Scene(engine_globals.scene)
191 }
192 },
193
194 visible,
195
196 subspace: Space::new(position, rotation, scale_factor),
197
198 triangle_mesh: triangle_mesh_vec,
199
200 script,
201
202 children: Vec::new(),
203 }));
204 }
205
206 #[allow(clippy::too_many_arguments)]
207 pub fn from_obj<'a>( // TODO: cut file into groups pass groups into triangle mesh for parsing
208 name: Option<&str>,
209 path: &str,
210
211 parent: Option<Arc<RwLock<dyn GameObject>>>,
212
213 position: Option<Vector3<f32>>,
214 rotation: Option<Quaternion<f32>>,
215 scale_factor: Option<Vector3<f32>>,
216
217 visible: bool,
218
219 engine_globals: EngineGlobals,
220
221 script: Option<Box<Script<Self>>>) -> Result<Arc<RwLock<Self>>, &'a str>{
222
223 // Data Pools //
224
225 let mut vertex_positions= Vec::new();
226 let mut texture_indices = Vec::new();
227 let mut normals = Vec::new();
228
229 let mut mtls_hashmap: HashMap<String, (Arc<Material>, Box<dyn GpuFuture>)> = HashMap::new();
230
231 // Read In String Data //
232
233 let content = fs::read_to_string(path)
234 .unwrap_or_else(|_| panic!("Something went wrong when trying to read {}.", path));
235 let mut last_block = Box::new(Vec::new());
236 let lines: Vec<(&str, Vec<&str>)> = content.lines().filter_map(|line| {
237 if !line.is_empty() {
238 let mut e = line.split_whitespace();
239 let ty: &str = e.next().unwrap();
240 match &*ty {
241
242 "" | "#" | "s" | "l" => None,
243
244 // Vertex Data //
245
246 "v" => {
247 vertex_positions.push(Box::new(Vertex::new(
248 e.next().unwrap().parse::<f32>().unwrap(),
249 e.next().unwrap().parse::<f32>().unwrap(),
250 e.next().unwrap().parse::<f32>().unwrap()
251 )));
252
253 None
254 },
255 "vt" => {
256 texture_indices.push(Box::new(TextureIndex::new(
257 e.next().unwrap().parse::<f32>().unwrap(),
258 e.next().unwrap().parse::<f32>().unwrap()
259 )));
260
261 None
262 },
263 "vn" => {
264 normals.push(Box::new(Normal::new(
265 e.next().unwrap().parse::<f32>().unwrap(),
266 e.next().unwrap().parse::<f32>().unwrap(),
267 e.next().unwrap().parse::<f32>().unwrap()
268 )));
269
270 None
271 },
272
273 // Materials //
274
275 "mtllib" => {
276 let files = e.fold(String::new(), |mut a, b| {
277 a.reserve(b.len() + 1);
278 a.push_str(b);
279 a.push(' ');
280 a
281 });
282
283 let files = files.trim_end_matches(".mtl ");
284
285 files.split(".mtl ").for_each(|file| {
286 let file = "/".to_owned() + file + ".mtl";
287 let path = path.rsplitn(2, '/').nth(1).unwrap();
288 let path = path.to_owned() + file.as_str();
289
290 mtls_hashmap.extend(Material::from_mtllib(&path, engine_globals.clone().queue));
291 });
292
293 None
294 },
295
296
297 // Faces and Materials //
298
299 "f" | "usemtl" => {
300 last_block.push(line);
301 None
302 },
303
304 // Groupings //
305
306 "g" | "o" => {
307 let tmp_block = *last_block.clone();
308 last_block = Box::new(Vec::new());
309 Some((line, tmp_block))
310 },
311
312 // Other //
313
314 a => {println!("\n\nA: {} \n", a); None}
315 }
316 } else {
317 None
318 }
319 }).collect();
320
321 // Create A New Container For The Model //
322
323 let name = name.unwrap_or_else(|| path.split('/').last().unwrap()).to_string();
324 let this = Obj::new_empty(
325 Some(name.as_str()),
326 parent,
327 position,
328 rotation,
329 scale_factor,
330 visible,
331 engine_globals.clone(),
332 script
333 );
334
335 // Group Data //
336
337 // The current group/container being written to
338 let mut current_group: Option<Arc<RwLock<dyn GameObject>>> = None;
339
340 // Loading Bar //
341
342 // standard output handle
343 let mut stdout = stdout();
344
345 // name of file and current group to be displayed
346 let file_name = path.split('/').last().unwrap();
347 let current_group_name: Option<&str> = None;
348
349 // line number and total number of lines
350 let mut line_n: usize = 0;
351 let file_size = lines.len();
352
353 // how often to update the loading bar
354 let update = file_size / 100; // or just use 500 very small impact
355
356 lines.into_iter().for_each(|line| {
357
358 // Loading Bar //
359
360 line_n += 1;
361
362 // Get the terminal width
363 let terminal_width = terminal_size::terminal_size().unwrap().0.0 as usize;
364
365 // Don't always draw. unless on the final stretch
366 let draw = update == 0 || line_n % update == 4; // || line_n >= file_size - update;
367
368 // store the current_group name so it is 'static
369 let future_s_current_group_name = &*current_group_name.unwrap_or("");
370
371 // create the loading bar future
372 let future = async {
373 if draw {
374 term_ui::progress_bar(&mut stdout, file_name, future_s_current_group_name, line_n, file_size, terminal_width).await
375 }
376 };
377
378 let mut e = line.0.split_whitespace();
379 #[allow(clippy::or_fun_call)]
380 let ty: &str = e.next().ok_or(format!("error on line {} of {}", line_n, path).as_str()).unwrap();
381
382
383 // replaces flush
384 if let Some(group) = current_group.clone() {
385 let mut group_write_lock = group.write().unwrap();
386 // create the triangle mesh and add it to the current group
387 let triangle_mesh = TriangleMesh::from_obj_block(&line.1, &mut mtls_hashmap, (&vertex_positions, &texture_indices, &normals), engine_globals.queue.clone()).unwrap();
388
389 group_write_lock.add_triangle_mesh(
390 Arc::new(triangle_mesh)
391 ).unwrap();
392 }
393
394 match &*ty {
395 "g" => {
396 current_group = Some(this.clone() as Arc<RwLock<dyn GameObject>>);
397 let mut groups = e.collect::<Vec<&str>>();
398 while let Some(group_name) = groups.pop() {
399 let group = current_group.clone().unwrap();
400 let mut wlock_current_group = group.write().unwrap();
401 if let Ok(group) = wlock_current_group.get_child_by_name(group_name) {
402 drop(wlock_current_group);
403 current_group = Some(group);
404 } else {
405 let new_group = Obj::new_empty(
406 Some(group_name),
407 current_group.clone(),
408 None,
409 None,
410 None,
411 true,
412 engine_globals.clone(),
413 None
414 );
415 wlock_current_group.add_child(new_group.clone());
416 drop(wlock_current_group);
417 current_group = Some(new_group);
418 }
419 }
420 },
421 "o" => {
422 current_group = Some(this.clone() as Arc<RwLock<dyn GameObject>>);
423
424 let object_name = e.next().expect("The o tag does not permit default/no names");
425 let new_object = Obj::new_empty(
426 Some(object_name),
427 current_group.clone(),
428 None,
429 None,
430 None,
431 true,
432 engine_globals.clone(),
433 None
434 );
435
436 current_group.clone().unwrap().write().unwrap().add_child(new_object.clone());
437 current_group = Some(new_object);
438 },
439
440 _ => unreachable!()
441 }
442
443 block_on(future);
444 });
445
446 let group = match current_group {
447 Some(group) => group.clone(),
448 None => this.clone()
449 };
450
451 let mut group_write_lock = group.write().unwrap();
452 // create the triangle mesh and add it to the current group
453 let triangle_mesh = TriangleMesh::from_obj_block(&last_block, &mut mtls_hashmap, (&vertex_positions, &texture_indices, &normals), engine_globals.queue.clone()).unwrap();
454
455 group_write_lock.add_triangle_mesh(
456 Arc::new(triangle_mesh)
457 ).unwrap();
458
459 Ok(this)
460 }
461
462 // pub fn from_obj_old<'a>( // couple mistakes I caught are still in here
463 // name: Option<&str>,
464 // path: &str,
465
466 // parent: Option<Arc<RwLock<dyn GameObject>>>,
467
468 // position: Option<Vector3<f32>>,
469 // rotation: Option<Quaternion<f32>>,
470 // scale_factor: Option<Vector3<f32>>,
471
472 // visible: bool,
473
474 // engine_globals: EngineGlobals,
475
476 // script: Option<Box<Script<Self>>>) -> Result<Arc<RwLock<Self>>, &'a str>{
477
478 // // Read In String Data //
479
480 // let content = fs::read_to_string(path)
481 // .expect(&format!("Something went wrong when trying to read {}.", path));
482 // let lines = content.lines();
483
484 // // Create A New Container For The Model //
485
486 // let name = name.unwrap_or(path.split('/').last().unwrap()).to_string();
487 // let this = Obj::new_empty(
488 // Some(name.as_str()),
489 // parent,
490 // position,
491 // rotation,
492 // scale_factor,
493 // visible,
494 // engine_globals.clone(),
495 // script
496 // );
497
498 // // Group Data //
499
500 // // The current group/container being written to
501 // let mut current_group = None;
502
503 // // Ordered mesh data
504 // let mut ordered_vertices = Vec::new();
505 // let mut ordered_normals = Vec::new();
506 // let mut ordered_texture_indices = Vec::new();
507
508 // let mut current_material: Option<Arc<Material>> = None;
509
510 // /// fn that flushes the ordered_vertices, ordered_normals, and ordered_texture_indices buffers
511 // /// and writes the data into the current group
512 // /// Note it does not reset the current group
513 // fn flush(
514 // current_group: Option<Arc<RwLock<dyn GameObject>>>,
515
516 // ordered_vertices: &mut Vec<Vertex>,
517 // ordered_normals: &mut Vec<Normal>,
518 // ordered_texture_indices: &mut Vec<TextureIndex>,
519
520 // current_material: &mut Option<Arc<Material>>,
521
522 // engine_globals: EngineGlobals) {
523 // if let Some(group) = current_group.clone() {
524 // let group = group.clone();
525 // let mut group_write_lock = group.write().unwrap();
526 // // create the triangle mesh and add it to the current group
527 // let triangle_mesh = TriangleMesh::new(
528 // ordered_vertices.clone(),
529 // ordered_normals.clone(),
530 // ordered_texture_indices.clone(),
531 // current_material.clone().unwrap_or(Arc::new(Material::default())),
532 // engine_globals.queue
533 // );
534
535 // group_write_lock.add_triangle_mesh(
536 // Arc::new(triangle_mesh)
537 // ).unwrap();
538
539 // // Reset the ordered vecs
540 // *ordered_vertices = Vec::new();
541 // *ordered_normals = Vec::new();
542 // *ordered_texture_indices = Vec::new();
543
544 // // reset the current material
545 // *current_material = None;
546 // }
547 // }
548
549 // // Loading Bar //
550
551 // // standard output handle
552 // let mut stdout = stdout();
553
554 // // name of file and current group to be displayed
555 // let file_name = path.split('/').last().unwrap();
556 // let current_group_name: Option<&str> = None;
557
558 // // line number and total number of lines
559 // let mut line_n: usize = 0;
560 // let file_size = lines.clone().count();
561
562 // // how often to update the loading bar
563 // let update = file_size / 100; // or just use 500 very small impact
564
565 // // Data Pools //
566
567 // let mut vertex_positions: Vec<Vertex> = Vec::new();
568 // let mut texture_indices: Vec<TextureIndex> = Vec::new();
569 // let mut normals: Vec<Normal> = Vec::new();
570
571 // let mut mtls_hashmap: HashMap<String, (Arc<Material>, Box<dyn GpuFuture>)> = HashMap::new();
572
573
574
575 // lines.for_each(|line| {
576
577 // // Loading Bar //
578
579 // line_n += 1;
580
581 // // Get the terminal width
582 // let terminal_width = terminal_size::terminal_size().unwrap().0.0 as usize;
583
584 // // Don't always draw. unless on the final stretch
585 // let draw = update == 0 || line_n % update == 4; // || line_n >= file_size - update;
586
587 // // store the current_group name so it is 'static
588 // let future_s_current_group_name = &*current_group_name.unwrap_or("");
589
590 // // create the loading bar future
591 // let future = async {
592 // if draw {
593 // term_ui::progress_bar(&mut stdout, file_name, future_s_current_group_name, line_n, file_size, terminal_width).await
594 // }
595 // };
596
597 // if !line.is_empty() {
598 // let mut e = line.split_whitespace();
599 // let ty: &str = e.next().ok_or(format!("error on line {} of {}", line_n, path).as_str()).unwrap();
600 // match &*ty {
601
602 // // Groupings //
603
604 // "g" => {
605 // flush(current_group.clone(), &mut ordered_vertices, &mut ordered_normals, &mut ordered_texture_indices, &mut current_material, engine_globals.clone());
606 // current_group = Some(this.clone() as Arc<RwLock<dyn GameObject>>);
607 // let mut groups = e.collect::<Vec<&str>>();
608 // while let Some(group_name) = groups.pop() {
609 // let group = current_group.clone().unwrap();
610 // let mut wlock_current_group = group.write().unwrap();
611 // if let Ok(group) = wlock_current_group.get_child_by_name(group_name) {
612 // drop(wlock_current_group);
613 // current_group = Some(group);
614 // } else {
615 // let new_group = Obj::new_empty(
616 // Some(group_name),
617 // current_group.clone(),
618 // None,
619 // None,
620 // None,
621 // true,
622 // engine_globals.clone(),
623 // None
624 // );
625 // wlock_current_group.add_child(new_group.clone());
626 // drop(wlock_current_group);
627 // current_group = Some(new_group);
628 // }
629 // }
630 // },
631 // "o" => {
632 // flush(current_group.clone(), &mut ordered_vertices, &mut ordered_normals, &mut ordered_texture_indices, &mut current_material, engine_globals.clone());
633 // current_group = Some(this.clone() as Arc<RwLock<dyn GameObject>>);
634
635 // let object_name = e.next().expect("The o tag does not permit default/no names");
636 // let new_object = Obj::new_empty(
637 // Some(object_name),
638 // current_group.clone(),
639 // None,
640 // None,
641 // None,
642 // true,
643 // engine_globals.clone(),
644 // None
645 // );
646
647 // current_group.clone().unwrap().write().unwrap().add_child(new_object.clone());
648 // current_group = Some(new_object);
649 // },
650
651 // // Vertex Data //
652
653 // "v" => {
654 // vertex_positions.push(Vertex::new(
655 // e.next().unwrap().parse::<f32>().unwrap(),
656 // e.next().unwrap().parse::<f32>().unwrap(),
657 // e.next().unwrap().parse::<f32>().unwrap()
658 // ));
659 // },
660 // "vt" => {
661 // texture_indices.push(TextureIndex::new(
662 // e.next().unwrap().parse::<f32>().unwrap(),
663 // e.next().unwrap().parse::<f32>().unwrap()
664 // ));
665 // },
666 // "vn" => {
667 // normals.push(Normal::new(
668 // e.next().unwrap().parse::<f32>().unwrap(),
669 // e.next().unwrap().parse::<f32>().unwrap(),
670 // e.next().unwrap().parse::<f32>().unwrap()
671 // ));
672 // },
673
674 // // Faces //
675
676 // "f" => {
677 // let mut tris = Vec::new();
678 // let mut i = 0;
679 // for coord in &mut e {
680 // i += 1;
681 // if i > 3{ // not perfect but good enough for now
682 // tris.push(tris[0]);
683 // tris.push(tris[i - 2]);
684 // }
685 // tris.push(coord);
686 // }
687
688 // let mut vertex_fmt: i8 = -1;
689 // let mut developing_normal: Vec<Vertex> = Vec::new();
690 // for raw in tris{
691 // let part = raw.split('/').collect::<Vec<&str>>();
692
693 // if vertex_fmt != part.len() as i8 {
694 // if vertex_fmt == -1 {
695 // vertex_fmt = part.len() as i8;
696 // }else {
697 // panic! ("Inconsistent face vertex format in {}.", path)
698 // }
699 // }
700
701 // let position = vertex_positions[part[0].parse::<usize>().unwrap() - 1_usize];
702
703 // let mut texture_index = TextureIndex::new(0.0, 0.0);
704
705 // if vertex_fmt > 1 && !part[1].is_empty() {
706 // texture_index = texture_indices[part[1].parse::<usize>().unwrap() - 1_usize];
707 // }
708 // // I could have sworn I have separated these multiple times now
709 // if developing_normal.is_empty() && vertex_fmt == 3 && !part[2].is_empty() { // a false second case is a result of improper formatting
710 // ordered_normals.push(normals[part[2].parse::<usize>().unwrap() - 1_usize]);
711 // } else {
712 // developing_normal.push(position);
713 // }
714
715 // ordered_vertices.push(position);
716 // ordered_texture_indices.push(texture_index);
717 // }
718
719 // if developing_normal.len() > 2 {
720 // let normal = Normal::calculate_normal(&developing_normal[0], &developing_normal[1], developing_normal.last().unwrap());
721
722 // for _ in 0..developing_normal.len() {
723 // ordered_normals.push(normal);
724 // }
725 // }
726 // },
727
728 // // Materials //
729
730 // "mtllib" => {
731 // let files = e.fold(String::new(), |mut a, b| {
732 // a.reserve(b.len() + 1);
733 // a.push_str(b);
734 // a.push(' ');
735 // a
736 // });
737
738 // let files = files.trim_end_matches(".mtl ");
739
740 // files.split(".mtl ").for_each(|file| {
741 // let file = "/".to_owned() + file + ".mtl";
742 // let path = path.rsplitn(2, '/').nth(1).unwrap();
743 // let path = path.to_owned() + file.as_str();
744
745 // mtls_hashmap.extend(Material::from_mtllib(&path, engine_globals.clone().queue));
746 // });
747 // },
748 // "usemtl" => {
749 // let key = e.next().expect(format!("formatting error in {}", path).as_str());
750 // let (cm, fut ) = mtls_hashmap.remove(key).unwrap();
751 // current_material = Some(cm.clone());
752 // if fut.queue().is_some() {
753 // let _ = Arc::new(fut.then_signal_fence_and_flush().unwrap()).wait(None); // for now state does not matter
754 // }
755 // mtls_hashmap.insert(key.to_string(), (cm, sync::now(engine_globals.queue.device().clone()).boxed()));
756
757 // },
758
759 // // Other //
760
761 // "#" => (),
762 // "" => (),
763 // &_ => {
764 // //panic!(format!("unsupported type on line {} of {}", line_n, path));
765 // }
766
767 // // TODO: Other Geometry //
768
769 // #[allow(unreachable_patterns)] // fr now just ignore it TODO: fix
770
771 // "line" => {
772 // /* remember that a normal of 0.0, 0.0, 0.0 is perfect because it is visible from any angle */
773 // todo!();
774 // },
775 // };
776 // }
777
778 // block_on(future);
779 // });
780
781 // flush(current_group, &mut ordered_vertices, &mut ordered_normals, &mut ordered_texture_indices, &mut current_material, engine_globals);
782
783 // Ok(this)
784 // }
785}