mirror of
https://github.com/Theaninova/mhlib.git
synced 2025-12-12 20:46:20 +00:00
improved lwo loading
This commit is contained in:
@@ -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<ArrayMesh> {
|
||||
let mut mesh = ArrayMesh::new();
|
||||
|
||||
let mut arrays: Option<VariantArray> = None;
|
||||
let mut materials = vec![];
|
||||
let mut images: Option<ImageClip> = None;
|
||||
|
||||
let mut vert_count = 0;
|
||||
let mut points = Vec::<Vector3>::new();
|
||||
let mut uv_mappings = HashMap::<i32, HashMap<i32, Vector2>>::new();
|
||||
let mut weight_mappings = HashMap::<i32, HashMap<i32, f32>>::new();
|
||||
let mut polygons = Vec::<PolygonList>::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::<HashSet<u16>>();
|
||||
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<ArrayMesh> {
|
||||
out_mesh
|
||||
}
|
||||
|
||||
fn try_commit(mesh: &mut ArrayMesh, arrays: &Option<VariantArray>) {
|
||||
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<I: Iterator<Item = (usize, Vector2)>>(
|
||||
mapping: I,
|
||||
vert_count: usize,
|
||||
arrays: &mut VariantArray,
|
||||
fn try_commit(
|
||||
mesh: &mut ArrayMesh,
|
||||
points: &mut [Vector3],
|
||||
uv_mappings: &mut HashMap<i32, HashMap<i32, Vector2>>,
|
||||
weight_mappings: &mut HashMap<i32, HashMap<i32, f32>>,
|
||||
polygons: &mut Vec<PolygonList>,
|
||||
) {
|
||||
let mut arr = arrays
|
||||
.get(ArrayType::ARRAY_TEX_UV.ord() as usize)
|
||||
.try_to::<PackedVector2Array>()
|
||||
.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::<UniqueVertex, i32>::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<PointList>) -> PackedVector3Array {
|
||||
PackedVector3Array::from(
|
||||
chunk
|
||||
.data
|
||||
.point_location
|
||||
.into_iter()
|
||||
.map(|[x, y, z]| Vector3 { x, y, z })
|
||||
.collect::<Vec<Vector3>>()
|
||||
.as_slice(),
|
||||
)
|
||||
fn find_mapping<T: Default + Copy + std::fmt::Debug>(
|
||||
target: &HashMap<i32, HashMap<i32, T>>,
|
||||
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::<Vec<&i32>>())
|
||||
);
|
||||
T::default()
|
||||
})
|
||||
}
|
||||
|
||||
fn collect_discontinuous_mappings<T>(
|
||||
target: &mut HashMap<i32, HashMap<i32, T>>,
|
||||
vmap: Chunk<DiscontinuousVertexMappings>,
|
||||
map_fn: fn(Vec<f32>) -> 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<T>(
|
||||
target: &mut HashMap<i32, HashMap<i32, T>>,
|
||||
vmap: Chunk<VertexMappings>,
|
||||
map_fn: fn(Vec<f32>) -> 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<StandardMaterial3D> {
|
||||
@@ -234,7 +347,11 @@ fn collect_material(surface: SurfaceDefinition, clip: ImageClip) -> Gd<StandardM
|
||||
for attr in attributes {
|
||||
match attr {
|
||||
SurfaceBlockImageTextureSubChunk::ImageMap(r) => {
|
||||
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<StandardM
|
||||
|
||||
material
|
||||
}
|
||||
|
||||
fn collect_polygons(chunk: Chunk<PolygonLists>) -> 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::<Vec<i32>>()
|
||||
})
|
||||
.collect::<Vec<i32>>()
|
||||
.as_slice(),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ pub fn pro_to_gd(pro: PowerRenderObject) -> Gd<PackedScene> {
|
||||
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();
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user