From 17e33d4881bd7bf7a0fc4f4a2850d6e5d01281c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Sun, 28 May 2023 19:30:32 +0200 Subject: [PATCH] improved lwo loading --- .gitignore | 2 + godot/project.godot | 2 +- godot/starforce/test.tscn | 2 +- rust/mhgd/src/lightwave_object.rs | 324 ++++++++++++++++--------- rust/mhgd/src/pr3d/pro.rs | 2 +- rust/mhgd/src/sproing/datafile.rs | 2 +- rust/mhgd/src/starforce/sar_archive.rs | 4 +- rust/powerrender-3d/src/main.rs | 2 +- 8 files changed, 220 insertions(+), 120 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..333c5f7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +games/ +.idea/ diff --git a/godot/project.godot b/godot/project.godot index e3d6e87..5510c93 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -12,7 +12,7 @@ config_version=5 config/name="MHJNR" run/main_scene="res://mhjnr/level.tscn" -config/features=PackedStringArray("4.0", "GL Compatibility") +config/features=PackedStringArray("4.1", "GL Compatibility") boot_splash/bg_color=Color(0, 0, 0, 0) boot_splash/image="res://icon.png" boot_splash/fullsize=false diff --git a/godot/starforce/test.tscn b/godot/starforce/test.tscn index 5b84daa..2ea30f0 100644 --- a/godot/starforce/test.tscn +++ b/godot/starforce/test.tscn @@ -1,6 +1,6 @@ [gd_scene load_steps=2 format=3 uid="uid://kyw4wuusc33g"] -[ext_resource type="ArrayMesh" path="sar://D:/Moorhuhnkart/3dobjects_cars/affe.lwo" id="2_ab234"] +[ext_resource type="ArrayMesh" path="sar://D:/Moorhuhnkart/3dobjects_cars/moorhuhn.lwo" id="2_ab234"] [node name="test" type="Node3D"] diff --git a/rust/mhgd/src/lightwave_object.rs b/rust/mhgd/src/lightwave_object.rs index a583ff8..1888d7f 100644 --- a/rust/mhgd/src/lightwave_object.rs +++ b/rust/mhgd/src/lightwave_object.rs @@ -6,21 +6,23 @@ use godot::builtin::{ use godot::engine::base_material_3d::TextureParam; use godot::engine::mesh::{ArrayFormat, ArrayType, PrimitiveType}; use godot::engine::{load, ArrayMesh, Image, ImageTexture, StandardMaterial3D, SurfaceTool}; -use godot::log::godot_print; +use godot::log::{godot_error, godot_print}; use godot::obj::{EngineEnum, Gd}; -use godot::prelude::{godot_warn, Array, GodotClass, Share, ToVariant}; +use godot::prelude::{godot_warn, Array, GodotClass, PackedFloat32Array, Share, ToVariant}; use lightwave_3d::iff::Chunk; use lightwave_3d::lwo2::sub_tags::blocks::image_texture::SurfaceBlockImageTextureSubChunk; use lightwave_3d::lwo2::sub_tags::blocks::{ SurfaceBlockHeaderSubChunk, SurfaceBlocks, TextureChannel, }; use lightwave_3d::lwo2::sub_tags::surface_parameters::SurfaceParameterSubChunk; +use lightwave_3d::lwo2::tags::discontinuous_vertex_mapping::DiscontinuousVertexMappings; use lightwave_3d::lwo2::tags::image_clip::{ImageClip, ImageClipSubChunk}; -use lightwave_3d::lwo2::tags::point_list::PointList; -use lightwave_3d::lwo2::tags::polygon_list::PolygonLists; +use lightwave_3d::lwo2::tags::polygon_list::PolygonList; use lightwave_3d::lwo2::tags::surface_definition::SurfaceDefinition; +use lightwave_3d::lwo2::tags::vertex_mapping::VertexMappings; use lightwave_3d::lwo2::tags::Tag; use lightwave_3d::LightWaveObject; +use std::collections::{HashMap, HashSet}; #[derive(GodotClass)] #[class(init)] @@ -37,98 +39,130 @@ impl Lwo { pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { let mut mesh = ArrayMesh::new(); - let mut arrays: Option = None; let mut materials = vec![]; let mut images: Option = None; - let mut vert_count = 0; + let mut points = Vec::::new(); + let mut uv_mappings = HashMap::>::new(); + let mut weight_mappings = HashMap::>::new(); + let mut polygons = Vec::::new(); for tag in lightwave.data { match tag { - Tag::PointList(points) => { - try_commit(&mut mesh, &arrays); - vert_count = points.point_location.len(); - - let mut ars = Array::new(); - ars.resize(ArrayType::ARRAY_MAX.ord() as usize); - - ars.set( - ArrayType::ARRAY_VERTEX.ord() as usize, - collect_points(points).to_variant(), + Tag::Layer(layer) => { + try_commit( + &mut mesh, + &mut points, + &mut uv_mappings, + &mut weight_mappings, + &mut polygons, ); - arrays = Some(ars); + } + Tag::PointList(points_chunk) => { + points = points_chunk + .data + .point_location + .into_iter() + .map(|p| Vector3 { + x: p[0], + y: p[1], + z: p[2], + }) + .collect(); } Tag::DiscontinuousVertexMapping(vmad) => match &vmad.kind { b"TXUV" => { - collect_uvs( - vmad.data.mappings.into_iter().map(|it| { - ( - it.vert as usize, - Vector2 { - x: it.values[0], - y: it.values[1], - }, - ) - }), - vert_count, - arrays.as_mut().unwrap(), - ); + debug_assert!(vmad.data.mappings[0].values.len() == 2); + collect_discontinuous_mappings(&mut uv_mappings, vmad, |uv| Vector2 { + x: uv[0], + y: uv[1], + }) } - x => godot_warn!( - "Discontinuous Vertex Mapping: {}", + b"WGHT" => collect_discontinuous_mappings(&mut weight_mappings, vmad, |it| it[0]), + x => godot_error!( + "Not Implemented: Discontinuous Vertex Mapping: {}", String::from_utf8(x.to_vec()).unwrap() ), }, Tag::VertexMapping(vmap) => match &vmap.kind { b"TXUV" => { - /*collect_uvs( - vmap.data.mapping.into_iter().map(|it| { - ( - it.vert as usize, - Vector2 { - x: it.value[0], - y: it.value[1], - }, - ) - }), - vert_count, - arrays.as_mut().unwrap(), - );*/ + debug_assert!(vmap.data.mapping[0].value.len() == 2); + collect_mappings(&mut uv_mappings, vmap, |uv| Vector2 { x: uv[0], y: uv[1] }) } - x => godot_warn!("Vertex Mapping: {}", String::from_utf8(x.to_vec()).unwrap()), + b"WGHT" => collect_mappings(&mut weight_mappings, vmap, |it| it[0]), + x => godot_error!( + "Not Implemented: Vertex Mapping: {}", + String::from_utf8(x.to_vec()).unwrap() + ), }, - Tag::PolygonList(polygons) => match &polygons.kind { - b"FACE" => { - if let Some(arrays) = &mut arrays { - let indices = collect_polygons(polygons); - arrays.set(ArrayType::ARRAY_INDEX.ord() as usize, indices.to_variant()); + Tag::PolygonTagMapping(ptag) => match &ptag.kind { + /*b"COLR" => { + todo!(); + },*/ + b"SURF" => { + let surfaces = ptag + .data + .mappings + .iter() + .map(|map| map.tag) + .collect::>(); + if surfaces.len() > 1 { + godot_error!("Too many surfaces: {:?}", surfaces) } } + x => godot_warn!( + "Polygon Tag Mapping: {}", + String::from_utf8(x.to_vec()).unwrap() + ), + }, + Tag::PolygonList(polygon_lists) => match &polygon_lists.kind { + b"FACE" => { + polygons = polygon_lists.data.polygons; + } x => godot_warn!("{}", String::from_utf8(x.to_vec()).unwrap()), }, Tag::ImageClip(clip) => { images = Some(clip.data); } Tag::SurfaceDefinition(surf) => { - let mat = collect_material(surf.data, images.unwrap()); - images = None; - materials.push(mat); + if let Some(img) = images { + let mat = collect_material(surf.data, img); + images = None; + materials.push(mat); + } else { + godot_error!("Missing images for surface {}", surf.name) + } } x => { - godot_warn!("Invalid chunk {:?}", x); + godot_error!("Invalid chunk {:?}", x); } } } - try_commit(&mut mesh, &arrays); + try_commit( + &mut mesh, + &mut points, + &mut uv_mappings, + &mut weight_mappings, + &mut polygons, + ); let mut out_mesh = ArrayMesh::new(); let mut mats = materials.into_iter(); for i in 0..mesh.get_surface_count() { let mut tool = SurfaceTool::new(); + tool.create_from(mesh.share().upcast(), i); tool.generate_normals(false); tool.generate_tangents(); - try_commit(&mut out_mesh, &Some(tool.commit_to_arrays())); + + out_mesh.add_surface_from_arrays( + PrimitiveType::PRIMITIVE_TRIANGLES, + tool.commit_to_arrays(), + Array::new(), + Dictionary::new(), + ArrayFormat::ARRAY_FORMAT_NORMAL, + ); + if let Some(mat) = mats.next() { out_mesh.surface_set_material(i, mat.upcast()) } @@ -136,49 +170,128 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { out_mesh } -fn try_commit(mesh: &mut ArrayMesh, arrays: &Option) { - if let Some(arrays) = arrays { - mesh.add_surface_from_arrays( - PrimitiveType::PRIMITIVE_TRIANGLES, - arrays.share(), - Array::new(), - Dictionary::new(), - ArrayFormat::ARRAY_FORMAT_NORMAL, - ); - } +#[derive(Hash, Eq, PartialEq)] +struct UniqueVertex { + vert: i32, + uv: [[u8; 4]; 2], + weight: [u8; 4], } -fn collect_uvs>( - mapping: I, - vert_count: usize, - arrays: &mut VariantArray, +fn try_commit( + mesh: &mut ArrayMesh, + points: &mut [Vector3], + uv_mappings: &mut HashMap>, + weight_mappings: &mut HashMap>, + polygons: &mut Vec, ) { - let mut arr = arrays - .get(ArrayType::ARRAY_TEX_UV.ord() as usize) - .try_to::() - .unwrap_or_else(|_| { - let mut new_uvs = PackedVector2Array::new(); - new_uvs.resize(vert_count); - new_uvs - }); - - for (index, uv) in mapping { - arr.set(index, uv); + if polygons.is_empty() { + return; } - arrays.set(ArrayType::ARRAY_TEX_UV.ord() as usize, arr.to_variant()); + let mut vertex_map = HashMap::::new(); + let mut vertices = PackedVector3Array::new(); + let mut uvs = PackedVector2Array::new(); + let mut weights = PackedFloat32Array::new(); + let mut indices = PackedInt32Array::new(); + + for (id, poly) in polygons.iter_mut().enumerate() { + let fan_v = poly.vert.remove(0) as i32; + let fan = poly + .vert + .windows(2) + .flat_map(|w| [w[1] as i32, w[0] as i32, fan_v]); + + for vert in fan { + let uv = find_mapping(uv_mappings, id, vert); + // let weight = find_mapping(weight_mappings, id, vert); + + indices.push( + *vertex_map + .entry(UniqueVertex { + vert, + uv: [uv.x.to_ne_bytes(), uv.y.to_ne_bytes()], + weight: (0.0f32).to_ne_bytes(), + }) + .or_insert_with(|| { + vertices.push(*points.get(vert as usize).unwrap()); + uvs.push(uv); + weights.push(0.0); + vertices.len() as i32 - 1 + }), + ); + } + } + + let mut surface = VariantArray::new(); + surface.resize(ArrayType::ARRAY_MAX.ord() as usize); + surface.set( + ArrayType::ARRAY_VERTEX.ord() as usize, + vertices.to_variant(), + ); + surface.set(ArrayType::ARRAY_TEX_UV.ord() as usize, uvs.to_variant()); + /*TODO: surface.set( + ArrayType::ARRAY_WEIGHTS.ord() as usize, + weights.to_variant(), + );*/ + surface.set(ArrayType::ARRAY_INDEX.ord() as usize, indices.to_variant()); + + mesh.add_surface_from_arrays( + PrimitiveType::PRIMITIVE_TRIANGLES, + surface, + Array::new(), + Dictionary::new(), + ArrayFormat::ARRAY_FORMAT_NORMAL, + ); } -fn collect_points(chunk: Chunk) -> PackedVector3Array { - PackedVector3Array::from( - chunk - .data - .point_location - .into_iter() - .map(|[x, y, z]| Vector3 { x, y, z }) - .collect::>() - .as_slice(), - ) +fn find_mapping( + target: &HashMap>, + poly: usize, + vert: i32, +) -> T { + target + .get(&(poly as i32)) + .and_then(|mapping| mapping.get(&vert).copied()) + .or_else(|| { + target + .get(&-1) + .and_then(|mapping| mapping.get(&vert).copied()) + }) + .unwrap_or_else(|| { + godot_error!( + "Missing VX Mapping for {{vert: {}, poly: {}}}; {:?}", + vert, + poly, + target + .get(&(poly as i32)) + .map(|p| p.keys().collect::>()) + ); + T::default() + }) +} + +fn collect_discontinuous_mappings( + target: &mut HashMap>, + vmap: Chunk, + map_fn: fn(Vec) -> T, +) { + for mapping in vmap.data.mappings { + target + .entry(mapping.poly as i32) + .or_insert_with(|| HashMap::new()) + .insert(mapping.vert as i32, map_fn(mapping.values)); + } +} + +fn collect_mappings( + target: &mut HashMap>, + vmap: Chunk, + map_fn: fn(Vec) -> T, +) { + let entry = target.entry(-1).or_insert_with(|| HashMap::new()); + for mapping in vmap.data.mapping { + entry.insert(mapping.vert as i32, map_fn(mapping.value)); + } } fn collect_material(surface: SurfaceDefinition, clip: ImageClip) -> Gd { @@ -234,7 +347,11 @@ fn collect_material(surface: SurfaceDefinition, clip: ImageClip) -> Gd { - texture.set_image(i.unwrap()); + if let Some(i) = i { + texture.set_image(i); + } else { + godot_error!("Missing texture {:?}", r); + } i = None; } x => { @@ -253,22 +370,3 @@ fn collect_material(surface: SurfaceDefinition, clip: ImageClip) -> Gd) -> PackedInt32Array { - debug_assert!(chunk.polygons.len() >= 3, "{:?}", chunk); - PackedInt32Array::from( - chunk - .data - .polygons - .into_iter() - .flat_map(|mut it| { - let fan_v = it.vert.remove(0) as i32; - it.vert - .windows(2) - .flat_map(|w| [w[1] as i32, w[0] as i32, fan_v]) - .collect::>() - }) - .collect::>() - .as_slice(), - ) -} diff --git a/rust/mhgd/src/pr3d/pro.rs b/rust/mhgd/src/pr3d/pro.rs index d705b90..3dcbf1e 100644 --- a/rust/mhgd/src/pr3d/pro.rs +++ b/rust/mhgd/src/pr3d/pro.rs @@ -27,7 +27,7 @@ pub fn pro_to_gd(pro: PowerRenderObject) -> Gd { let pr_tex = &pro.textures[m.texture_index]; let mut image_file = - File::open(format!("E:\\Games\\Moorhuhn Kart\\data\\{}", pr_tex.name)).unwrap(); + File::open(format!("../games/Moorhuhn Kart/data/{}", pr_tex.name)).unwrap(); let mut image = Image::new(); let mut buffer = vec![]; image_file.read_to_end(&mut buffer).unwrap(); diff --git a/rust/mhgd/src/sproing/datafile.rs b/rust/mhgd/src/sproing/datafile.rs index 122efde..7af6563 100644 --- a/rust/mhgd/src/sproing/datafile.rs +++ b/rust/mhgd/src/sproing/datafile.rs @@ -18,7 +18,7 @@ use springylib::DatafileFile; use std::fs::File; use std::str::FromStr; -const DAT_PATH: &str = "E:\\Games\\Schatzjäger\\data\\datafile.dat"; +const DAT_PATH: &str = "../games/Schatzjäger/data/datafile.dat"; #[derive(GodotClass)] #[class(base=ResourceFormatLoader)] diff --git a/rust/mhgd/src/starforce/sar_archive.rs b/rust/mhgd/src/starforce/sar_archive.rs index dc13a69..91d78c5 100644 --- a/rust/mhgd/src/starforce/sar_archive.rs +++ b/rust/mhgd/src/starforce/sar_archive.rs @@ -11,7 +11,7 @@ use starforcelib::sarc::SarcArchive; use std::fs::File; use std::io::Cursor; -const SAR_PATH: &str = r#"E:\Games\mhall\Moorhuhn Kart 3\data.sar"#; +const SAR_PATH: &str = r#"../games/Moorhuhn Kart 3/data.sar"#; #[derive(GodotClass)] #[class(base=ResourceFormatLoader)] @@ -61,7 +61,7 @@ impl ResourceFormatLoaderVirtual for SarLoader { let obj = LightWaveObject::read(&mut Cursor::new(data)).unwrap(); lightwave_to_gd(obj).to_variant() } - Some((_, "bmp")) => { + Some((_, "bmp" | "dds")) => { let mut f = File::open(SAR_PATH).unwrap(); let mut image = Image::new(); diff --git a/rust/powerrender-3d/src/main.rs b/rust/powerrender-3d/src/main.rs index 99b397f..f6bfc3a 100644 --- a/rust/powerrender-3d/src/main.rs +++ b/rust/powerrender-3d/src/main.rs @@ -6,7 +6,7 @@ pub mod pro; pub mod trk; fn main() { - let mut file = File::open(r#"E:\Games\Moorhuhn Kart\data\alk.pro"#).unwrap(); + let mut file = File::open(r#"../games/Moorhuhn Kart/data/alk.pro"#).unwrap(); let result = PowerRenderObject::read(&mut file).unwrap(); println!("{:#?}", result); }