diff --git a/rust/mhgd/src/lwo/clips.rs b/rust/mhgd/src/lwo/clips.rs new file mode 100644 index 0000000..805d168 --- /dev/null +++ b/rust/mhgd/src/lwo/clips.rs @@ -0,0 +1,22 @@ +use godot::engine::{load, Image}; +use godot::log::godot_error; +use godot::obj::Gd; +use lightwave_3d::lwo2::tags::image_clip::{ImageClip, ImageClipSubChunk}; +use std::collections::HashMap; + +pub fn collect_clip(target: &mut HashMap>, clip: ImageClip) { + for img in clip.attributes.iter() { + match img { + ImageClipSubChunk::StillImage(still) => { + let path = format!( + "sar://{}", + still.name.to_string().replace('\\', "/").replace(':', ":/") + ); + target.insert(clip.index, load(path)); + } + x => { + godot_error!("TODO: Clip chunk {:?}", x) + } + } + } +} diff --git a/rust/mhgd/src/lwo/material.rs b/rust/mhgd/src/lwo/material.rs index b909c20..5eb1ca3 100644 --- a/rust/mhgd/src/lwo/material.rs +++ b/rust/mhgd/src/lwo/material.rs @@ -1,36 +1,25 @@ use godot::builtin::Color; -use godot::engine::base_material_3d::TextureParam; -use godot::engine::{load, Image, ImageTexture, StandardMaterial3D}; -use godot::log::{godot_error, godot_print, godot_warn}; -use godot::obj::Gd; -use lightwave_3d::lwo2::sub_tags::blocks::image_texture::SurfaceBlockImageTextureSubChunk; +use godot::engine::base_material_3d::{DiffuseMode, TextureParam}; +use godot::engine::{Image, ImageTexture, StandardMaterial3D}; +use godot::log::{godot_error, godot_warn}; +use godot::obj::{Gd, Share}; +use lightwave_3d::lwo2::sub_tags::blocks::image_texture::{ + ProjectionMode, 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::image_clip::{ImageClip, ImageClipSubChunk}; use lightwave_3d::lwo2::tags::surface_definition::SurfaceDefinition; +use std::collections::HashMap; -pub fn collect_material(surface: SurfaceDefinition, clip: ImageClip) -> Gd { +pub fn collect_material( + surface: SurfaceDefinition, + images: &HashMap>, +) -> Gd { let mut material = StandardMaterial3D::new(); material.set_name(surface.name.to_string().into()); - - let mut i: Option> = None; - for img in clip.attributes { - match img { - ImageClipSubChunk::StillImage(still) => { - let path = format!( - "sar://{}", - still.name.to_string().replace('\\', "/").replace(':', ":/") - ); - godot_print!("Loading {}", &path); - i = Some(load(path)); - } - x => { - godot_warn!("Invalid clip chunk {:?}", x) - } - } - } + material.set_diffuse_mode(DiffuseMode::DIFFUSE_TOON); for attr in surface.attributes { match attr { @@ -64,15 +53,28 @@ pub fn collect_material(surface: SurfaceDefinition, clip: ImageClip) -> Gd { - if let Some(i) = i { - texture.set_image(i); + if let Some(i) = images.get(&r.texture_image) { + texture.set_image(i.share()); } else { godot_error!("Missing texture {:?}", r); } - i = None; + } + SurfaceBlockImageTextureSubChunk::ProjectionMode(projection) => { + match projection.data { + ProjectionMode::UV => {} + _ => { + godot_error!("TODO: Projection mode {:?}", projection) + } + } + } + SurfaceBlockImageTextureSubChunk::UvVertexMap(map) => { + godot_error!( + "TODO: UV maps (this one is using '{}')", + map.txuv_map_name + ) } x => { - godot_warn!("Invalid image texture chunk {:?}", x) + godot_error!("TODO: Image texture chunk {:?}", x) } } } @@ -85,17 +87,23 @@ pub fn collect_material(surface: SurfaceDefinition, clip: ImageClip) -> Gd { - material.set_specular(base_specular.value as f64); - if base_specular.envelope != 0 { + SurfaceParameterSubChunk::BaseShadingValueDiffuse(base_diffuse) => { + if base_diffuse.envelope != 0 || base_diffuse.value != 1.0 { godot_error!( - "Not implemented: Specular envelope {}", - base_specular.envelope + "TODO: Diffuse {{envelope: {}, value: {}}}", + base_diffuse.envelope, + base_diffuse.value ); } } + SurfaceParameterSubChunk::BaseShadingValueSpecular(base_specular) => { + material.set_specular(base_specular.value as f64); + if base_specular.envelope != 0 { + godot_error!("TODO: Specular envelope {}", base_specular.envelope); + } + } x => { - godot_error!("Invalid Surface Chunk {:?}", x) + godot_error!("TODO: Surface Chunk {:?}", x) } } } diff --git a/rust/mhgd/src/lwo/mod.rs b/rust/mhgd/src/lwo/mod.rs index 9d70b1d..afa2fdc 100644 --- a/rust/mhgd/src/lwo/mod.rs +++ b/rust/mhgd/src/lwo/mod.rs @@ -5,6 +5,7 @@ use godot::engine::ArrayMesh; use godot::obj::Gd; use lightwave_3d::LightWaveObject; +pub(crate) mod clips; pub(crate) mod mapping; pub(crate) mod material; pub(crate) mod object; diff --git a/rust/mhgd/src/lwo/object.rs b/rust/mhgd/src/lwo/object.rs index 0f97397..9ee8731 100644 --- a/rust/mhgd/src/lwo/object.rs +++ b/rust/mhgd/src/lwo/object.rs @@ -1,12 +1,12 @@ +use crate::lwo::clips::collect_clip; use crate::lwo::mapping::{collect_discontinuous_mappings, collect_mappings}; use crate::lwo::material::collect_material; use crate::lwo::surface::try_commit; use godot::builtin::{Array, Dictionary, Vector2, Vector3}; use godot::engine::mesh::{ArrayFormat, PrimitiveType}; -use godot::engine::{ArrayMesh, SurfaceTool}; +use godot::engine::{ArrayMesh, Image, SurfaceTool}; use godot::log::{godot_error, godot_warn}; use godot::obj::{Gd, Share}; -use lightwave_3d::lwo2::tags::image_clip::ImageClip; use lightwave_3d::lwo2::tags::polygon_list::PolygonList; use lightwave_3d::lwo2::tags::Tag; use lightwave_3d::LightWaveObject; @@ -16,12 +16,15 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { let mut mesh = ArrayMesh::new(); let mut materials = vec![]; - let mut images: Option = None; + let mut images = HashMap::>::new(); let mut points = Vec::::new(); let mut uv_mappings = HashMap::>::new(); let mut weight_mappings = HashMap::>::new(); let mut polygons = Vec::::new(); + let mut surfaces = HashMap::::new(); + + let mut surface_materials = Vec::::new(); for tag in lightwave.data { match tag { @@ -32,6 +35,8 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { &mut uv_mappings, &mut weight_mappings, &mut polygons, + &mut surfaces, + &mut surface_materials, ); } Tag::PointList(points_chunk) => { @@ -76,14 +81,8 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { todo!(); },*/ b"SURF" => { - let surfaces = ptag - .data - .mappings - .iter() - .map(|map| map.tag) - .collect::>(); - if surfaces.len() > 1 { - godot_error!("Too many surfaces: {:?}", surfaces) + for surf in ptag.data.mappings { + surfaces.insert(surf.poly as i32, surf.tag); } } x => godot_warn!( @@ -97,17 +96,10 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { } x => godot_warn!("{}", String::from_utf8(x.to_vec()).unwrap()), }, - Tag::ImageClip(clip) => { - images = Some(clip.data); - } + Tag::ImageClip(clip) => collect_clip(&mut images, clip.data), Tag::SurfaceDefinition(surf) => { - 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) - } + let mat = collect_material(surf.data, &images); + materials.push(mat); } Tag::BoundingBox(_) => (), x => { @@ -122,9 +114,10 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { &mut uv_mappings, &mut weight_mappings, &mut polygons, + &mut surfaces, + &mut surface_materials, ); 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(); @@ -140,8 +133,8 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { ArrayFormat::ARRAY_FORMAT_NORMAL, ); - if let Some(mat) = mats.next() { - out_mesh.surface_set_material(i, mat.upcast()) + if let Some(mat) = materials.get(surfaces[&(i as i32)] as usize) { + out_mesh.surface_set_material(i, mat.share().upcast()) } } out_mesh diff --git a/rust/mhgd/src/lwo/surface.rs b/rust/mhgd/src/lwo/surface.rs index 0bd44bc..50d8484 100644 --- a/rust/mhgd/src/lwo/surface.rs +++ b/rust/mhgd/src/lwo/surface.rs @@ -6,6 +6,7 @@ use godot::builtin::{ use godot::engine::mesh::{ArrayFormat, ArrayType, PrimitiveType}; use godot::engine::ArrayMesh; use godot::obj::EngineEnum; +use itertools::Itertools; use lightwave_3d::lwo2::tags::polygon_list::PolygonList; use std::collections::HashMap; @@ -22,63 +23,75 @@ pub fn try_commit( uv_mappings: &mut HashMap>, weight_mappings: &mut HashMap>, polygons: &mut Vec, + surfaces: &mut HashMap, + surface_materials: &mut Vec, ) { if polygons.is_empty() { return; } - 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 surface_id in surfaces.values().unique() { + 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 (id, poly) in polygons + .iter_mut() + .enumerate() + .filter(|(id, _)| surfaces.get(&(*id as i32)).unwrap_or(&0) == surface_id) + { + 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); + 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 - }), - ); + 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()); + + if vertices.is_empty() { + continue; + } + mesh.add_surface_from_arrays( + PrimitiveType::PRIMITIVE_TRIANGLES, + surface, + Array::new(), + Dictionary::new(), + ArrayFormat::ARRAY_FORMAT_NORMAL, + ); + surface_materials.push(*surface_id); } - - 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, - ); }