From 527a1ef0c7083d591b17737aad784318f01c4fbc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thea=20Sch=C3=B6bl?= Date: Mon, 29 May 2023 19:32:10 +0200 Subject: [PATCH] cleanup --- godot/kart/entry.tscn | 12 +- godot/main.tscn | 80 +++++----- godot/project.godot | 1 - godot/starforce/starforce.gdshader | 105 +++++++++++++ godot/starforce/test.tscn | 4 +- rust/.gitignore | 1 + rust/mhgd/src/lwo/clips.rs | 9 +- rust/mhgd/src/lwo/intermediate_layer.rs | 92 +++++++++++ rust/mhgd/src/lwo/material.rs | 75 +++++---- rust/mhgd/src/lwo/mod.rs | 4 +- rust/mhgd/src/lwo/object.rs | 56 +++++-- rust/mhgd/src/lwo/surface.rs | 193 ------------------------ rust/mhgd/src/lwo/surface_info.rs | 144 ++++++++++++++++++ rust/mhgd/src/lwo/unique_vertex.rs | 32 ++++ 14 files changed, 516 insertions(+), 292 deletions(-) create mode 100644 godot/starforce/starforce.gdshader create mode 100644 rust/mhgd/src/lwo/intermediate_layer.rs delete mode 100644 rust/mhgd/src/lwo/surface.rs create mode 100644 rust/mhgd/src/lwo/surface_info.rs create mode 100644 rust/mhgd/src/lwo/unique_vertex.rs diff --git a/godot/kart/entry.tscn b/godot/kart/entry.tscn index 2bbe62f..8585e39 100644 --- a/godot/kart/entry.tscn +++ b/godot/kart/entry.tscn @@ -1,10 +1,10 @@ [gd_scene load_steps=5 format=3 uid="uid://4n26dt3e4pv3"] -[ext_resource type="Shader" path="res://kart/flag.gdshader" id="1_lbvg8"] -[ext_resource type="FontFile" uid="uid://b50bdb32aerbb" path="res://remakes/font/LondrinaSolid-Regular.otf" id="2_fj37l"] +[ext_resource type="Shader" path="res://kart/flag.gdshader" id="1_mtrao"] +[ext_resource type="FontFile" uid="uid://b50bdb32aerbb" path="res://remakes/font/LondrinaSolid-Regular.otf" id="2_vriwa"] [sub_resource type="ShaderMaterial" id="ShaderMaterial_y6o8u"] -shader = ExtResource("1_lbvg8") +shader = ExtResource("1_mtrao") shader_parameter/rotation = 2.2 shader_parameter/size = 160.0 shader_parameter/time_scale = 0.125 @@ -40,7 +40,7 @@ grow_horizontal = 2 theme_override_colors/font_color = Color(0.996078, 0.92549, 0.14902, 1) theme_override_colors/font_outline_color = Color(0.776471, 0.215686, 0.14902, 1) theme_override_constants/outline_size = 23 -theme_override_fonts/font = ExtResource("2_fj37l") +theme_override_fonts/font = ExtResource("2_vriwa") theme_override_font_sizes/font_size = 60 text = "Moorhuhn" horizontal_alignment = 1 @@ -55,7 +55,7 @@ theme_override_colors/font_color = Color(0.894118, 0.133333, 0.0705882, 1) theme_override_colors/font_outline_color = Color(1, 1, 1, 1) theme_override_constants/outline_size = 15 theme_override_constants/line_spacing = -140 -theme_override_fonts/font = ExtResource("2_fj37l") +theme_override_fonts/font = ExtResource("2_vriwa") theme_override_font_sizes/font_size = 97 text = "»KART«" horizontal_alignment = 1 @@ -71,7 +71,7 @@ rotation = -0.338594 theme_override_colors/font_color = Color(0.913725, 0.945098, 0.952941, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) theme_override_constants/outline_size = 14 -theme_override_fonts/font = ExtResource("2_fj37l") +theme_override_fonts/font = ExtResource("2_vriwa") theme_override_font_sizes/font_size = 48 text = "ULTIMATE" horizontal_alignment = 1 diff --git a/godot/main.tscn b/godot/main.tscn index 76f5be9..8085ab3 100644 --- a/godot/main.tscn +++ b/godot/main.tscn @@ -1,25 +1,25 @@ [gd_scene load_steps=21 format=3 uid="uid://cmqfu6cc780h4"] -[ext_resource type="Theme" uid="uid://ks2uyxqg6u4k" path="res://mhjnr/theme.tres" id="1_4qyey"] -[ext_resource type="Texture2D" uid="uid://wetnflcj1b0w" path="res://cover_art/mh1.jpg" id="2_epsv5"] -[ext_resource type="Texture2D" uid="uid://dyga5qn124307" path="res://cover_art/schatzjaeger.webp" id="2_f5uhf"] -[ext_resource type="Script" path="res://main.gd" id="2_j1xqk"] -[ext_resource type="Texture2D" uid="uid://baqjofchj6yaw" path="res://cover_art/mhw.webp" id="3_wgtv5"] -[ext_resource type="Texture2D" uid="uid://brovl1cel1uiw" path="res://cover_art/mh2.jpg" id="4_fb5aq"] -[ext_resource type="Texture2D" uid="uid://ix132vewff0h" path="res://cover_art/mh3.jpg" id="5_q44ta"] -[ext_resource type="Texture2D" uid="uid://k8i84cvnuun" path="res://cover_art/mhx.jpg" id="6_b76vr"] -[ext_resource type="Texture2D" uid="uid://bst48atkwn7r1" path="res://cover_art/mhrem.png" id="7_ios37"] -[ext_resource type="Texture2D" uid="uid://chcjw51coc53r" path="res://cover_art/mhinv.jpg" id="8_1fm4r"] -[ext_resource type="Texture2D" uid="uid://l37kev18676o" path="res://cover_art/mhwant.jpg" id="8_yb215"] -[ext_resource type="Texture2D" uid="uid://bk5a3xob0raqj" path="res://cover_art/mh_kart.jpg" id="9_b3ixn"] -[ext_resource type="Texture2D" uid="uid://cpigwqlll0kbf" path="res://cover_art/mhpir.jpg" id="9_v7fae"] -[ext_resource type="Texture2D" uid="uid://dkhygfyylbjov" path="res://cover_art/mh_kart2.jpg" id="10_1woe1"] -[ext_resource type="Texture2D" uid="uid://l8groudaf385" path="res://cover_art/mh_kart3.webp" id="11_7me0v"] -[ext_resource type="Texture2D" uid="uid://bmybnhi2i2ep" path="res://cover_art/mhdir.jpg" id="11_fweyu"] -[ext_resource type="Texture2D" uid="uid://cjp0ilsodyu4j" path="res://cover_art/mh_kart4.jpg" id="12_hv57k"] -[ext_resource type="Texture2D" uid="uid://by4wug5r7311q" path="res://cover_art/schatzjaeger_2.jpg" id="14_tnyyh"] -[ext_resource type="Texture2D" uid="uid://bkn3cdrm1fj8b" path="res://cover_art/schatzjaeger_3.jpg" id="15_kbsip"] -[ext_resource type="Texture2D" uid="uid://8vn1dpq37mve" path="res://cover_art/atlantis.webp" id="16_xvpbi"] +[ext_resource type="Theme" uid="uid://ks2uyxqg6u4k" path="res://mhjnr/theme.tres" id="1_24af1"] +[ext_resource type="Script" path="res://main.gd" id="2_3ufyn"] +[ext_resource type="Texture2D" uid="uid://dyga5qn124307" path="res://cover_art/schatzjaeger.webp" id="3_dq726"] +[ext_resource type="Texture2D" uid="uid://by4wug5r7311q" path="res://cover_art/schatzjaeger_2.jpg" id="4_qurcc"] +[ext_resource type="Texture2D" uid="uid://bkn3cdrm1fj8b" path="res://cover_art/schatzjaeger_3.jpg" id="5_wceoy"] +[ext_resource type="Texture2D" uid="uid://8vn1dpq37mve" path="res://cover_art/atlantis.webp" id="6_mwmag"] +[ext_resource type="Texture2D" uid="uid://bk5a3xob0raqj" path="res://cover_art/mh_kart.jpg" id="7_kndjp"] +[ext_resource type="Texture2D" uid="uid://dkhygfyylbjov" path="res://cover_art/mh_kart2.jpg" id="8_r2mlu"] +[ext_resource type="Texture2D" uid="uid://l8groudaf385" path="res://cover_art/mh_kart3.webp" id="9_yfa8e"] +[ext_resource type="Texture2D" uid="uid://cjp0ilsodyu4j" path="res://cover_art/mh_kart4.jpg" id="10_s4ouh"] +[ext_resource type="Texture2D" uid="uid://wetnflcj1b0w" path="res://cover_art/mh1.jpg" id="11_0icjw"] +[ext_resource type="Texture2D" uid="uid://baqjofchj6yaw" path="res://cover_art/mhw.webp" id="12_8qgke"] +[ext_resource type="Texture2D" uid="uid://brovl1cel1uiw" path="res://cover_art/mh2.jpg" id="13_dypyq"] +[ext_resource type="Texture2D" uid="uid://ix132vewff0h" path="res://cover_art/mh3.jpg" id="14_tc2rn"] +[ext_resource type="Texture2D" uid="uid://k8i84cvnuun" path="res://cover_art/mhx.jpg" id="15_gxg0f"] +[ext_resource type="Texture2D" uid="uid://bst48atkwn7r1" path="res://cover_art/mhrem.png" id="16_dtmhv"] +[ext_resource type="Texture2D" uid="uid://l37kev18676o" path="res://cover_art/mhwant.jpg" id="17_pcn4f"] +[ext_resource type="Texture2D" uid="uid://cpigwqlll0kbf" path="res://cover_art/mhpir.jpg" id="18_w72rq"] +[ext_resource type="Texture2D" uid="uid://chcjw51coc53r" path="res://cover_art/mhinv.jpg" id="19_31858"] +[ext_resource type="Texture2D" uid="uid://bmybnhi2i2ep" path="res://cover_art/mhdir.jpg" id="20_b2doc"] [node name="main" type="ScrollContainer"] anchors_preset = 15 @@ -29,8 +29,8 @@ offset_right = 20.0 offset_bottom = 154.0 grow_horizontal = 2 grow_vertical = 2 -theme = ExtResource("1_4qyey") -script = ExtResource("2_j1xqk") +theme = ExtResource("1_24af1") +script = ExtResource("2_3ufyn") [node name="margins" type="MarginContainer" parent="."] layout_mode = 2 @@ -55,7 +55,7 @@ layout_mode = 2 [node name="Schatzjaeger1" type="Button" parent="margins/VBoxContainer/JNR/JNR"] custom_minimum_size = Vector2(200, 256) layout_mode = 2 -icon = ExtResource("2_f5uhf") +icon = ExtResource("3_dq726") flat = true icon_alignment = 1 expand_icon = true @@ -63,7 +63,7 @@ expand_icon = true [node name="Schatzjaeger2" type="Button" parent="margins/VBoxContainer/JNR/JNR"] custom_minimum_size = Vector2(200, 256) layout_mode = 2 -icon = ExtResource("14_tnyyh") +icon = ExtResource("4_qurcc") flat = true icon_alignment = 1 expand_icon = true @@ -71,7 +71,7 @@ expand_icon = true [node name="Schatzjaeger3" type="Button" parent="margins/VBoxContainer/JNR/JNR"] custom_minimum_size = Vector2(200, 256) layout_mode = 2 -icon = ExtResource("15_kbsip") +icon = ExtResource("5_wceoy") flat = true icon_alignment = 1 expand_icon = true @@ -80,7 +80,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("16_xvpbi") +icon = ExtResource("6_mwmag") flat = true icon_alignment = 1 expand_icon = true @@ -100,7 +100,7 @@ layout_mode = 2 custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("9_b3ixn") +icon = ExtResource("7_kndjp") flat = true icon_alignment = 1 expand_icon = true @@ -109,7 +109,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("10_1woe1") +icon = ExtResource("8_r2mlu") flat = true icon_alignment = 1 expand_icon = true @@ -118,7 +118,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("11_7me0v") +icon = ExtResource("9_yfa8e") flat = true icon_alignment = 1 expand_icon = true @@ -127,7 +127,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("12_hv57k") +icon = ExtResource("10_s4ouh") flat = true icon_alignment = 1 expand_icon = true @@ -147,7 +147,7 @@ layout_mode = 2 custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("2_epsv5") +icon = ExtResource("11_0icjw") flat = true icon_alignment = 1 expand_icon = true @@ -156,7 +156,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("3_wgtv5") +icon = ExtResource("12_8qgke") flat = true icon_alignment = 1 expand_icon = true @@ -165,7 +165,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("4_fb5aq") +icon = ExtResource("13_dypyq") flat = true icon_alignment = 1 expand_icon = true @@ -174,7 +174,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("5_q44ta") +icon = ExtResource("14_tc2rn") flat = true icon_alignment = 1 expand_icon = true @@ -183,7 +183,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("6_b76vr") +icon = ExtResource("15_gxg0f") flat = true icon_alignment = 1 expand_icon = true @@ -192,7 +192,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("7_ios37") +icon = ExtResource("16_dtmhv") flat = true icon_alignment = 1 expand_icon = true @@ -201,7 +201,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("8_yb215") +icon = ExtResource("17_pcn4f") flat = true icon_alignment = 1 expand_icon = true @@ -210,7 +210,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("9_v7fae") +icon = ExtResource("18_w72rq") flat = true icon_alignment = 1 expand_icon = true @@ -219,7 +219,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("8_1fm4r") +icon = ExtResource("19_31858") flat = true icon_alignment = 1 expand_icon = true @@ -228,7 +228,7 @@ expand_icon = true custom_minimum_size = Vector2(200, 256) layout_mode = 2 disabled = true -icon = ExtResource("11_fweyu") +icon = ExtResource("20_b2doc") flat = true icon_alignment = 1 expand_icon = true diff --git a/godot/project.godot b/godot/project.godot index 5510c93..3d4baab 100644 --- a/godot/project.godot +++ b/godot/project.godot @@ -48,5 +48,4 @@ config/icon="res://icon.png" [rendering] -renderer/rendering_method="gl_compatibility" renderer/rendering_method.mobile="gl_compatibility" diff --git a/godot/starforce/starforce.gdshader b/godot/starforce/starforce.gdshader new file mode 100644 index 0000000..cb297ae --- /dev/null +++ b/godot/starforce/starforce.gdshader @@ -0,0 +1,105 @@ +shader_type spatial; +render_mode skip_vertex_transform; + +uniform vec4 color: source_color; + +uniform float diffuse; +uniform float diffuse_envelope; +uniform float specular; +uniform float specular_envelope; +uniform float luminosity; +uniform float luminosity_envelope; +uniform float reflectivity; +uniform float reflectivity_envelope; +uniform float translucency; +uniform float translucency_envelope; +uniform float transparency; +uniform float transparency_envelope; + +uniform sampler2D tex_color: source_color; +uniform int tex_color_axis; +uniform int tex_color_projection; +uniform mat4 tex_color_projection_transform; +uniform vec3 tex_color_projection_falloff; +uniform int tex_color_projection_falloff_type; +uniform bool tex_color_projection_world_coords; + +uniform sampler2D tex_diffuse: source_color; +uniform int tex_diffuse_axis; +uniform int tex_diffuse_projection; +uniform mat4 tex_diffuse_projection_transform; +uniform vec3 tex_diffuse_projection_falloff; +uniform int tex_diffuse_projection_falloff_type; +uniform bool tex_diffuse_projection_world_coords; + +varying vec3 position; +varying vec3 normal; + +vec3 project( + sampler2D tex, + int mode, + mat4 transform, + vec3 falloff, + int falloff_type, + bool world_coords, + vec2 uv, +) { + switch (mode) { + case 5: // UV + return texture(tex, uv).rgb; + case 4: // Front Projection + return vec3(0.0, 1.0, 0.0); + case 3: + vec3 p = (transform * vec4(position, 1.0)).xyz / 10.0; + vec3 n = normalize(abs(mat3(transform) * normal)); + vec2 uv2 = (n.x > n.y && n.x > n.z) ? vec2(p.z, p.y) + : ((n.y > n.x && n.y > n.z) ? vec2(p.x, p.z) : vec2(p.x, p.y)); + return texture(tex, uv2).rgb; + case 2: // Spherical + return vec3(0.0, 0.0, 1.0); + case 1: // Cylindrical + return vec3(1.0, 1.0, 0.0); + case 0: // Planar + return texture(tex, (transform * vec4(position, 1.0)).xz / 10.0).rgb; + default: + return vec3(0.0); + } +} + +void vertex() { + position = VERTEX; + VERTEX = (MODELVIEW_MATRIX * vec4(VERTEX, 1.0)).xyz; + NORMAL = normalize((MODELVIEW_MATRIX * vec4(NORMAL, 0.0)).xyz); +} + +void fragment() { + vec3 rot = vec3(PI / 4.0, PI / -2.0, PI / -4.0); + mat3 x = mat3(vec3(1.0, 0.0, 0.0), vec3(0.0, cos(rot.x), -sin(rot.x)), vec3(0.0, sin(rot.x), cos(rot.x))); + mat3 y = mat3(vec3(cos(rot.y), 0.0, sin(rot.y)), vec3(0.0, 1.0, 0.0), vec3(-sin(rot.y), 0.0, cos(rot.y))); + mat3 z = mat3(vec3(cos(rot.z), -sin(rot.z), 0.0), vec3(sin(rot.z), cos(rot.z), 0.0), vec3(0.0, 0.0, 1.0)); + mat3 mat = x * y * z; + + normal = (INV_VIEW_MATRIX * vec4(NORMAL, 0.0)).xyz; + + ALBEDO = project( + tex_color, + tex_color_projection, + tex_color_projection_transform, + tex_color_projection_falloff, + tex_color_projection_falloff_type, + tex_color_projection_world_coords, + UV2 + ); +} + +void light() { + DIFFUSE_LIGHT =project( + tex_diffuse, + tex_diffuse_projection, + tex_diffuse_projection_transform, + tex_diffuse_projection_falloff, + tex_diffuse_projection_falloff_type, + tex_diffuse_projection_world_coords, + UV + ); +} diff --git a/godot/starforce/test.tscn b/godot/starforce/test.tscn index 1453a1b..c08ec58 100644 --- a/godot/starforce/test.tscn +++ b/godot/starforce/test.tscn @@ -6,8 +6,8 @@ script/source = "@tool extends Node3D func _ready(): - var scene = load(\"sar://D:/Moorhuhnkart/3dobjects_tracks/track07_ufo/boden.lwo\") - add_child(scene.instantiate()) + var scene = load(\"sar://D:/Moorhuhnkart/3dobjects_tracks/track07_ufo/waende.lwo\").instantiate() + add_child(scene) " [node name="test" type="Node3D"] diff --git a/rust/.gitignore b/rust/.gitignore index 2ab338f..f4031d2 100644 --- a/rust/.gitignore +++ b/rust/.gitignore @@ -1,3 +1,4 @@ /target .idea +.cargo mhjnr.iml diff --git a/rust/mhgd/src/lwo/clips.rs b/rust/mhgd/src/lwo/clips.rs index 805d168..b21fd50 100644 --- a/rust/mhgd/src/lwo/clips.rs +++ b/rust/mhgd/src/lwo/clips.rs @@ -8,11 +8,10 @@ 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)); + let path = format!("sar://{}", still.name.replace('\\', "/").replace(':', ":/")); + let mut image: Gd = load(path); + image.set_name(still.name.clone().into()); + target.insert(clip.index, image); } x => { godot_error!("TODO: Clip chunk {:?}", x) diff --git a/rust/mhgd/src/lwo/intermediate_layer.rs b/rust/mhgd/src/lwo/intermediate_layer.rs new file mode 100644 index 0000000..2da6600 --- /dev/null +++ b/rust/mhgd/src/lwo/intermediate_layer.rs @@ -0,0 +1,92 @@ +use crate::lwo::material::MaterialUvInfo; +use crate::lwo::surface_info::SurfaceInfo; +use godot::builtin::{Array, Dictionary, Vector2, Vector3}; +use godot::engine::mesh::{ArrayFormat, PrimitiveType}; +use godot::engine::{ArrayMesh, SurfaceTool}; +use godot::obj::{Gd, Share}; +use itertools::Itertools; +use lightwave_3d::lwo2::tags::polygon_list::PolygonList; +use std::collections::HashMap; + +pub type SurfaceMapping = HashMap>; + +#[derive(Default)] +pub struct IntermediateLayer { + pub name: String, + pub pivot: Vector3, + pub parent: Option, + pub id: u16, + pub points: Vec, + pub uv_mappings: Vec<(String, SurfaceMapping)>, + pub weight_mappings: SurfaceMapping, + pub polygons: Vec, + pub material_mappings: HashMap, +} + +impl IntermediateLayer { + pub fn commit(mut self, materials: &HashMap) -> Gd { + let mut mesh = ArrayMesh::new(); + mesh.set_name(self.name.clone().into()); + let mut surface_material_ids = Vec::::new(); + + self.uv_mappings.sort_by(|a, b| a.0.cmp(&b.0)); + + for material_id in self.material_mappings.values().unique() { + let material = &materials[material_id]; + let surface_info = SurfaceInfo::collect_from_layer(&self, material); + + if !surface_info.is_empty() { + mesh.add_surface_from_arrays( + PrimitiveType::PRIMITIVE_TRIANGLES, + surface_info.commit_to_arrays(), + Array::new(), + Dictionary::new(), + ArrayFormat::ARRAY_FORMAT_NORMAL, + ); + surface_material_ids.push(*material_id); + } + } + + let mut final_mesh = post_process_mesh( + mesh, + materials, + surface_material_ids, + &self.material_mappings, + ); + final_mesh.set_name(self.name.into()); + final_mesh + } +} + +fn post_process_mesh( + mesh: Gd, + materials: &HashMap, + material_ids: Vec, + material_mappings: &HashMap, +) -> Gd { + let mut out_mesh = ArrayMesh::new(); + + debug_assert_eq!(mesh.get_surface_count() as usize, material_ids.len()); + + for (surface_idx, surface_id) in material_ids.into_iter().enumerate() { + let mut tool = SurfaceTool::new(); + + tool.create_from(mesh.share().upcast(), surface_idx as i64); + tool.generate_normals(false); + tool.generate_tangents(); + + 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) = materials.get(&material_mappings[&(surface_id as i32)]) { + out_mesh.surface_set_material(surface_idx as i64, mat.material.share().upcast()) + } + } + + out_mesh +} diff --git a/rust/mhgd/src/lwo/material.rs b/rust/mhgd/src/lwo/material.rs index bd03d6b..a795a1c 100644 --- a/rust/mhgd/src/lwo/material.rs +++ b/rust/mhgd/src/lwo/material.rs @@ -1,6 +1,4 @@ -use godot::builtin::{ - Basis, Color, EulerOrder, Quaternion, ToVariant, Transform3D, Variant, Vector3, -}; +use godot::builtin::{Basis, Color, EulerOrder, ToVariant, Transform3D, Variant, Vector3}; use godot::engine::{load, Image, ImageTexture, ShaderMaterial}; use godot::log::{godot_error, godot_print}; use godot::obj::{Gd, Share}; @@ -17,20 +15,23 @@ use lightwave_3d::lwo2::sub_tags::surface_parameters::SurfaceParameterSubChunk; use lightwave_3d::lwo2::tags::surface_definition::SurfaceDefinition; use std::collections::HashMap; +#[derive(Debug)] pub struct MaterialUvInfo { pub diffuse_channel: Option, pub color_channel: Option, pub material: Gd, + pub id: u16, } impl MaterialUvInfo { - pub fn collect(surface: SurfaceDefinition, images: &HashMap>) -> Self { + pub fn collect(surface: SurfaceDefinition, images: &HashMap>, id: u16) -> Self { let mut m = MaterialUvInfo { diffuse_channel: None, color_channel: None, material: ShaderMaterial::new(), + id, }; - m.material.set_name(surface.name.to_string().into()); + m.material.set_name(surface.name.into()); m.material .set_shader(load("res://starforce/starforce.gdshader")); @@ -41,6 +42,7 @@ impl MaterialUvInfo { let mut texture = ImageTexture::new(); let mut chan = TextureChannel::Color; let mut uv_channel = None; + let mut major_axis = 0; let mut projection_mode = ProjectionMode::UV; let mut mapping_info = Vec::<(&str, Variant)>::new(); for attr in header.data.block_attributes { @@ -66,6 +68,7 @@ impl MaterialUvInfo { match attr { SurfaceBlockImageTextureSubChunk::ImageMap(r) => { if let Some(i) = images.get(&r.texture_image) { + godot_print!("{}", i.get_name()); texture.set_image(i.share()); } else { godot_error!("Missing texture {:?}", r); @@ -75,7 +78,7 @@ impl MaterialUvInfo { projection_mode = projection.data; } SurfaceBlockImageTextureSubChunk::UvVertexMap(map) => { - uv_channel = Some(map.txuv_map_name.to_string()); + uv_channel = Some(map.txuv_map_name.clone()); } SurfaceBlockImageTextureSubChunk::TextureMapping(mapping) => { let mut pos = Vector3::default(); @@ -85,33 +88,33 @@ impl MaterialUvInfo { match mapping_param { TextureMappingSubChunk::Center(it) => { pos = Vector3 { - x: it.base_color[0], + z: it.base_color[0], y: it.base_color[1], - z: it.base_color[2], + x: it.base_color[2], }; } TextureMappingSubChunk::Rotation(it) => { rot = Vector3 { - x: it.base_color[0], + z: it.base_color[0], y: it.base_color[1], - z: it.base_color[2], + x: it.base_color[2], } .normalized(); } TextureMappingSubChunk::Size(it) => { size = Vector3 { - x: it.base_color[0], + z: it.base_color[0], y: it.base_color[1], - z: it.base_color[2], + x: it.base_color[2], }; } TextureMappingSubChunk::Falloff(it) => { mapping_info.push(( "falloff", Vector3 { - x: it.vector[0], + z: it.vector[0], y: it.vector[1], - z: it.vector[2], + x: it.vector[2], } .to_variant(), )); @@ -138,10 +141,8 @@ impl MaterialUvInfo { )); } TextureMappingSubChunk::ReferenceObject(it) => { - if !matches!( - it.object_name.to_string().as_str(), - "" | "(none)" - ) { + if !matches!(it.object_name.as_str(), "" | "(none)") + { godot_error!("Reference object '{}': not supported for texture mapping", it.object_name) } } @@ -151,15 +152,15 @@ impl MaterialUvInfo { mapping_info.push(( "transform", Transform3D { - basis: Basis::from_euler(EulerOrder::XYZ, rot) + basis: Basis::from_euler(EulerOrder::ZYX, rot) .scaled(size), origin: pos, } .to_variant(), )); } - SurfaceBlockImageTextureSubChunk::MajorAxis(_) => { - // TODO; + SurfaceBlockImageTextureSubChunk::MajorAxis(axis) => { + major_axis = axis.data.texture_axis; } SurfaceBlockImageTextureSubChunk::ImageWrapOptions(_) => { // TODO; @@ -181,19 +182,31 @@ impl MaterialUvInfo { } } } - godot_print!("TX: {:?} @ UV{:?}", chan, uv_channel); - let channel_name = match chan { + /*godot_print!( + "TX: {:?} ({:?}) @ UV{:?}", + chan, + projection_mode, + uv_channel + );*/ + let channel_name = match &chan { TextureChannel::Color => "color", TextureChannel::Diffuse => "diffuse", - TextureChannel::Luminosity => "luminosity", - TextureChannel::Specular => "specular", - TextureChannel::Glossy => "glossy", - TextureChannel::Reflectivity => "reflectivity", - TextureChannel::Transparency => "transparency", - TextureChannel::RefractiveIndex => "refractive_index", - TextureChannel::Translucency => "translucency", - TextureChannel::Bump => "bump", + x => { + godot_error!("TODO: Texture channel {:?} is not supported", x); + "color" + } /*TextureChannel::Luminosity => "luminosity", + TextureChannel::Specular => "specular", + TextureChannel::Glossy => "glossy", + TextureChannel::Reflectivity => "reflectivity", + TextureChannel::Transparency => "transparency", + TextureChannel::RefractiveIndex => "refractive_index", + TextureChannel::Translucency => "translucency", + TextureChannel::Bump => "bump",*/ }; + m.material.set_shader_parameter( + format!("tex_{}_axis", channel_name).into(), + major_axis.to_variant(), + ); m.material.set_shader_parameter( format!("tex_{}_projection", channel_name).into(), match projection_mode { diff --git a/rust/mhgd/src/lwo/mod.rs b/rust/mhgd/src/lwo/mod.rs index a0b3f47..a707b2c 100644 --- a/rust/mhgd/src/lwo/mod.rs +++ b/rust/mhgd/src/lwo/mod.rs @@ -6,10 +6,12 @@ use godot::obj::Gd; use lightwave_3d::LightWaveObject; pub(crate) mod clips; +pub(crate) mod intermediate_layer; pub(crate) mod mapping; pub(crate) mod material; pub(crate) mod object; -pub(crate) mod surface; +pub(crate) mod surface_info; +pub(crate) mod unique_vertex; #[derive(GodotClass)] #[class(init)] diff --git a/rust/mhgd/src/lwo/object.rs b/rust/mhgd/src/lwo/object.rs index 15aedc6..98d093e 100644 --- a/rust/mhgd/src/lwo/object.rs +++ b/rust/mhgd/src/lwo/object.rs @@ -1,52 +1,59 @@ use crate::lwo::clips::collect_clip; +use crate::lwo::intermediate_layer::IntermediateLayer; use crate::lwo::mapping::{collect_discontinuous_mappings, collect_mappings}; use crate::lwo::material::MaterialUvInfo; -use crate::lwo::surface::IntermediateLayer; use godot::builtin::{Vector2, Vector3}; use godot::engine::node::InternalMode; use godot::engine::{Image, MeshInstance3D, Node3D, PackedScene}; use godot::log::{godot_error, godot_print, godot_warn}; use godot::obj::{Gd, Share}; +use itertools::Itertools; use lightwave_3d::lwo2::tags::Tag; use lightwave_3d::LightWaveObject; use std::collections::HashMap; pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { - let mut materials = vec![]; + let mut surfaces = HashMap::::new(); let mut images = HashMap::>::new(); let mut layers = vec![]; + let mut tag_strings = vec![]; for tag in lightwave.data { match tag { + Tag::TagStrings(it) => { + tag_strings = it.data.tag_strings.into_iter().collect(); + godot_print!("{:?}", tag_strings); + } Tag::Layer(layer_tag) => { layers.push(IntermediateLayer { - name: layer_tag.name.to_string(), + name: layer_tag.name.clone(), parent: layer_tag.parent, id: layer_tag.number, pivot: Vector3 { - x: layer_tag.pivot[0], + z: layer_tag.pivot[0], y: layer_tag.pivot[1], - z: layer_tag.pivot[2], + x: layer_tag.pivot[2], }, ..IntermediateLayer::default() }); } Tag::PointList(points_chunk) => { + debug_assert_eq!(layers.last().unwrap().points.len(), 0); layers.last_mut().unwrap().points = points_chunk .data .point_location .into_iter() .map(|p| Vector3 { - x: p[0], + z: p[0], y: p[1], - z: p[2], + x: p[2], }) .collect(); } Tag::DiscontinuousVertexMapping(vmad) => match &vmad.kind { b"TXUV" => { debug_assert!(vmad.data.mappings[0].values.len() == 2); - let name = vmad.name.to_string(); + let name = vmad.name.clone(); let layer = layers.last_mut().unwrap(); let map = if let Some(mappings) = @@ -76,7 +83,7 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { Tag::VertexMapping(vmap) => match &vmap.kind { b"TXUV" => { debug_assert!(vmap.data.mapping[0].value.len() == 2); - let name = vmap.name.to_string(); + let name = vmap.name.clone(); let layer = layers.last_mut().unwrap(); let map = if let Some(mappings) = @@ -109,7 +116,7 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { layers .last_mut() .unwrap() - .surfaces + .material_mappings .insert(surf.poly as i32, surf.tag); } } @@ -120,14 +127,24 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { }, Tag::PolygonList(polygon_lists) => match &polygon_lists.kind { b"FACE" => { + debug_assert_eq!(layers.last().unwrap().polygons.len(), 0); layers.last_mut().unwrap().polygons = polygon_lists.data.polygons; } x => godot_warn!("{}", String::from_utf8(x.to_vec()).unwrap()), }, Tag::ImageClip(clip) => collect_clip(&mut images, clip.data), Tag::SurfaceDefinition(surf) => { - godot_print!("Def: '{}' -> '{}'", surf.source, surf.name); - materials.push(MaterialUvInfo::collect(surf.data, &images)); + let surf_name = surf.name.clone(); + let (tag_index, _) = tag_strings + .iter() + .find_position(|name| name == &&surf_name) + .expect("Invalid File"); + godot_print!("'{}': {}", surf_name, tag_index); + + surfaces.insert( + tag_index as u16, + MaterialUvInfo::collect(surf.data, &images, tag_index as u16), + ); } Tag::BoundingBox(_) => (), x => { @@ -136,12 +153,25 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd { } } + /*godot_print!( + "{:?}", + surfaces + .iter() + .map(|(k, v)| ( + k, + tag_strings[*k as usize].clone(), + v.material.get_shader_parameter("tex_diffuse".into()), + v.material.get_shader_parameter("tex_color".into()) + )) + .collect_vec() + );*/ + let mut root_node = Node3D::new_alloc(); for layer in layers { let mut instance = MeshInstance3D::new_alloc(); instance.set_name(layer.name.clone().into()); - instance.set_mesh(layer.commit(&mut materials).upcast()); + instance.set_mesh(layer.commit(&surfaces).upcast()); root_node.add_child( instance.share().upcast(), diff --git a/rust/mhgd/src/lwo/surface.rs b/rust/mhgd/src/lwo/surface.rs deleted file mode 100644 index 3b576cf..0000000 --- a/rust/mhgd/src/lwo/surface.rs +++ /dev/null @@ -1,193 +0,0 @@ -use crate::lwo::mapping::find_mapping; -use crate::lwo::material::MaterialUvInfo; -use godot::builtin::{ - Array, Dictionary, PackedFloat32Array, PackedInt32Array, PackedVector2Array, - PackedVector3Array, ToVariant, VariantArray, Vector2, Vector3, -}; -use godot::engine::mesh::{ArrayFormat, ArrayType, PrimitiveType}; -use godot::engine::{ArrayMesh, SurfaceTool}; -use godot::obj::{EngineEnum, Gd, Share}; -use godot::prelude::godot_error; -use itertools::Itertools; -use lightwave_3d::lwo2::tags::polygon_list::PolygonList; -use std::collections::HashMap; - -#[derive(Hash, Eq, PartialEq)] -struct UniqueVertex { - vert: i32, - uv: Vec>, - weight: [u8; 4], -} - -pub type SurfaceMapping = HashMap>; - -#[derive(Default)] -pub struct IntermediateLayer { - pub name: String, - pub pivot: Vector3, - pub parent: Option, - pub id: u16, - pub points: Vec, - pub uv_mappings: Vec<(String, SurfaceMapping)>, - pub weight_mappings: SurfaceMapping, - pub polygons: Vec, - pub surfaces: HashMap, -} - -impl IntermediateLayer { - pub fn commit(mut self, materials: &[MaterialUvInfo]) -> Gd { - let mut mesh = ArrayMesh::new(); - mesh.set_name(self.name.clone().into()); - let mut surface_materials = Vec::::new(); - - self.uv_mappings.sort_by(|a, b| a.0.cmp(&b.0)); - - for surface_id in self.surfaces.values().unique() { - let material = &materials[*surface_id as usize]; - let material_uv_names = [ - material.diffuse_channel.as_ref(), - material.color_channel.as_ref(), - ]; - let mut material_incomplete = material_uv_names.iter().map(|_| 0).collect_vec(); - let materials_subset = material_uv_names - .iter() - .map(|it| it.and_then(|it| self.uv_mappings.iter().find(|(name, _)| name == it))) - .collect_vec(); - - let mut vertex_map = HashMap::::new(); - let mut vertices = PackedVector3Array::new(); - let mut uvs = materials_subset - .iter() - .map(|it| it.map(|_| PackedVector2Array::new())) - .collect_vec(); - let mut weights = PackedFloat32Array::new(); - let mut indices = PackedInt32Array::new(); - - for (id, poly) in self - .polygons - .iter_mut() - .enumerate() - .filter(|(id, _)| self.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 = materials_subset - .iter() - .map(|it| { - it.and_then(|(name, it)| { - find_mapping(it, id, vert).map(|it| (name, it)) - }) - }) - .collect_vec(); - // let weight = find_mapping(weight_mappings, id, vert); - - indices.push( - *vertex_map - .entry(UniqueVertex { - vert, - uv: uv - .iter() - .map(|it| { - it.map(|(_, uv)| [uv.x.to_ne_bytes(), uv.y.to_ne_bytes()]) - }) - .collect(), - weight: (0.0f32).to_ne_bytes(), - }) - .or_insert_with(|| { - vertices.push(*self.points.get(vert as usize).unwrap()); - for (i, uv) in uv.iter().enumerate() { - if let Some(uvs) = &mut uvs[i] { - uvs.push(uv.map(|(_, it)| it).unwrap_or_else(|| { - material_incomplete[i] += 1; - Vector2::ZERO - })); - } - } - 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(), - ); - for (i, uv) in uvs.iter().enumerate() { - if let Some(uv) = uv { - surface.set( - match i { - 0 => ArrayType::ARRAY_TEX_UV, - 1 => ArrayType::ARRAY_TEX_UV2, - _ => todo!(), - } - .ord() as usize, - uv.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()); - - for (i, inc) in material_incomplete.into_iter().enumerate() { - if inc != 0 { - godot_error!( - "{}:{}'s UV map '{}' has {} ({}%) incomplete UVs", - self.name, - surface_id, - material_uv_names[i].unwrap(), - inc, - (inc as f32 / vertices.len() as f32) * 100.0 - ); - } - } - - 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 out_mesh = ArrayMesh::new(); - out_mesh.set_name(self.name.into()); - 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(); - - 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) = materials.get(self.surfaces[&(i as i32)] as usize) { - out_mesh.surface_set_material(i, mat.material.share().upcast()) - } - } - - out_mesh - } -} diff --git a/rust/mhgd/src/lwo/surface_info.rs b/rust/mhgd/src/lwo/surface_info.rs new file mode 100644 index 0000000..b508359 --- /dev/null +++ b/rust/mhgd/src/lwo/surface_info.rs @@ -0,0 +1,144 @@ +use crate::lwo::intermediate_layer::IntermediateLayer; +use crate::lwo::mapping::find_mapping; +use crate::lwo::material::MaterialUvInfo; +use crate::lwo::unique_vertex::UniqueVertex; +use godot::builtin::{ + PackedFloat32Array, PackedInt32Array, PackedVector2Array, PackedVector3Array, ToVariant, + VariantArray, Vector2, Vector3, +}; +use godot::engine::mesh::ArrayType; +use godot::log::godot_error; +use godot::obj::EngineEnum; +use itertools::Itertools; +use std::collections::HashMap; + +fn triangulate(poly: &[u32]) -> Vec { + let mut poly = poly.iter().collect_vec(); + + let fan_v = *poly.pop().unwrap() as i32; + poly.reverse(); + poly.windows(2) + .flat_map(move |w| [*w[1] as i32, *w[0] as i32, fan_v]) + .collect() +} + +#[derive(Default)] +pub struct SurfaceInfo { + vertex_map: HashMap, + material_incomplete: Vec, + pub material_id: u16, + pub vertices: PackedVector3Array, + pub uv_sets: Vec>, + pub weights: PackedFloat32Array, + pub indices: PackedInt32Array, +} + +impl SurfaceInfo { + pub fn is_empty(&self) -> bool { + self.vertices.is_empty() + } + + pub fn commit_to_arrays(self) -> VariantArray { + let mut arrays = VariantArray::new(); + arrays.resize(ArrayType::ARRAY_MAX.ord() as usize); + arrays.set( + ArrayType::ARRAY_VERTEX.ord() as usize, + self.vertices.to_variant(), + ); + for (i, uv) in self.uv_sets.into_iter().enumerate() { + if let Some(uv) = uv { + arrays.set( + match i { + 0 => ArrayType::ARRAY_TEX_UV, + 1 => ArrayType::ARRAY_TEX_UV2, + _ => todo!(), + } + .ord() as usize, + uv.to_variant(), + ) + } + } + /*TODO: surface.set( + ArrayType::ARRAY_WEIGHTS.ord() as usize, + weights.to_variant(), + );*/ + arrays.set( + ArrayType::ARRAY_INDEX.ord() as usize, + self.indices.to_variant(), + ); + + #[cfg(debug_assertions)] + for (i, inc) in self.material_incomplete.into_iter().enumerate() { + if inc != 0 { + godot_error!( + "{} ({}%) incomplete UVs", + inc, + (inc as f32 / self.vertices.len() as f32) * 100.0 + ); + } + } + + arrays + } + + pub fn collect_from_layer(layer: &IntermediateLayer, material: &MaterialUvInfo) -> Self { + let material_uv_names = [ + material.diffuse_channel.as_ref(), + material.color_channel.as_ref(), + ]; + + let materials_subset = material_uv_names + .iter() + .map(|it| it.and_then(|it| layer.uv_mappings.iter().find(|(name, _)| name == it))) + .collect_vec(); + + let mut surface_info = SurfaceInfo { + uv_sets: materials_subset + .iter() + .map(|it| it.map(|_| PackedVector2Array::new())) + .collect(), + material_incomplete: material_uv_names.iter().map(|_| 0).collect_vec(), + ..SurfaceInfo::default() + }; + + let mut surface_polygons = layer.polygons.iter().enumerate().filter(|(id, _)| { + layer.material_mappings.get(&(*id as i32)).unwrap_or(&0) == &material.id + }); + + for (id, poly) in surface_polygons { + for index in triangulate(&poly.vert) { + let uv = materials_subset + .iter() + .map(|it| it.and_then(|(_, it)| find_mapping(it, id, index))) + .collect_vec(); + // TODO: let weight = find_mapping(weight_mappings, id, vert); + let vert = layer.points.get(index as usize).unwrap(); + + surface_info.push_index(vert, &uv, 0f32); + } + } + + surface_info + } + + fn push_index(&mut self, vert: &Vector3, uvs: &[Option], weight: f32) { + let index = *self + .vertex_map + .entry(UniqueVertex::from_point(vert, uvs, 0f32, self.material_id)) + .or_insert_with(|| { + self.vertices.push(*vert); + for (i, uv) in uvs.iter().enumerate() { + if let Some(uv_set) = &mut self.uv_sets[i] { + uv_set.push(uv.unwrap_or_else(|| { + self.material_incomplete[i] += 1; + Vector2::ZERO + })); + } + } + self.weights.push(weight); + self.vertices.len() as i32 - 1 + }); + + self.indices.push(index); + } +} diff --git a/rust/mhgd/src/lwo/unique_vertex.rs b/rust/mhgd/src/lwo/unique_vertex.rs new file mode 100644 index 0000000..9f4824f --- /dev/null +++ b/rust/mhgd/src/lwo/unique_vertex.rs @@ -0,0 +1,32 @@ +use godot::builtin::{Vector2, Vector3}; + +#[derive(Hash, Eq, PartialEq)] +pub struct UniqueVertex { + vert: [[u8; 4]; 3], + material_id: u16, + uv: Vec>, + weight: [u8; 4], +} + +impl UniqueVertex { + pub fn from_point( + vert: &Vector3, + uvs: &[Option], + weight: f32, + material_id: u16, + ) -> Self { + Self { + vert: [ + vert.x.to_ne_bytes(), + vert.y.to_ne_bytes(), + vert.z.to_ne_bytes(), + ], + material_id, + uv: uvs + .iter() + .map(|it| it.map(|uv| [uv.x.to_ne_bytes(), uv.y.to_ne_bytes()])) + .collect(), + weight: weight.to_ne_bytes(), + } + } +}