mirror of
https://github.com/Theaninova/mhlib.git
synced 2025-12-11 03:56:18 +00:00
mhk3
This commit is contained in:
48
rust/Cargo.lock
generated
48
rust/Cargo.lock
generated
@@ -20,6 +20,17 @@ version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
|
||||
|
||||
[[package]]
|
||||
name = "bincode"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e103c8b299b28a9c6990458b7013dc4a8356a9b854c51b9883241f5866fac36e"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
"num-traits 0.1.43",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "binrw"
|
||||
version = "0.11.1"
|
||||
@@ -144,6 +155,18 @@ version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7"
|
||||
|
||||
[[package]]
|
||||
name = "dds-rs"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4c293f2e4ae9760641b1b5faf1c05c63d2ff1755763f87853217c8774afa55a1"
|
||||
dependencies = [
|
||||
"bincode",
|
||||
"rgb",
|
||||
"serde",
|
||||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.8.1"
|
||||
@@ -355,7 +378,7 @@ dependencies = [
|
||||
"gif",
|
||||
"jpeg-decoder",
|
||||
"num-rational",
|
||||
"num-traits",
|
||||
"num-traits 0.2.15",
|
||||
"png",
|
||||
"qoi",
|
||||
"tiff",
|
||||
@@ -450,6 +473,7 @@ dependencies = [
|
||||
name = "mhgd"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"dds-rs",
|
||||
"godot",
|
||||
"itertools",
|
||||
"lightwave-3d",
|
||||
@@ -507,7 +531,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-traits",
|
||||
"num-traits 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -518,7 +542,16 @@ checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"num-integer",
|
||||
"num-traits",
|
||||
"num-traits 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.1.43"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
|
||||
dependencies = [
|
||||
"num-traits 0.2.15",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -644,6 +677,15 @@ dependencies = [
|
||||
name = "renderwarelib"
|
||||
version = "0.1.0"
|
||||
|
||||
[[package]]
|
||||
name = "rgb"
|
||||
version = "0.8.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "20ec2d3e3fc7a92ced357df9cebd5a10b6fb2aa1ee797bf7e9ce2f17dffc8f59"
|
||||
dependencies = [
|
||||
"bytemuck",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
[package]
|
||||
name = "mhjnr"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[[bin]]
|
||||
name = "mhjnr-viewer"
|
||||
path = "src/main.rs"
|
||||
|
||||
[lib]
|
||||
name = "mhjnr"
|
||||
crate-type = ["cdylib", "lib"]
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.10.5"
|
||||
unicode-segmentation = "1.10.1"
|
||||
encoding_rs = "0.8.32"
|
||||
binrw = "0.11.1"
|
||||
serde = {version = "1.0.160", features = ["derive"]}
|
||||
serde-xml-rs = "0.6.0"
|
||||
image = "0.24.6"
|
||||
base64 = "0.21.0"
|
||||
godot = { git = "https://github.com/godot-rust/gdext", branch = "master" }
|
||||
@@ -11,5 +11,6 @@ crate-type = ["cdylib"]
|
||||
godot = { git = "https://github.com/godot-rust/gdext", branch = "master" }
|
||||
lightwave-3d = "1.0.0"
|
||||
itertools = "0.10.5"
|
||||
dds-rs = "0.7.0"
|
||||
starforcelib = { path = "../starforcelib" }
|
||||
springylib = { path = "../springylib" }
|
||||
|
||||
0
rust/mhgd/src/data_installer.rs
Normal file
0
rust/mhgd/src/data_installer.rs
Normal file
@@ -1,24 +1,34 @@
|
||||
use crate::sproing::datafile::DatafileLoader;
|
||||
use crate::starforce::sar_archive::SarLoader;
|
||||
use ::godot::engine::class_macros::auto_register_classes;
|
||||
use ::godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader};
|
||||
use ::godot::init::{gdextension, ExtensionLayer};
|
||||
use ::godot::prelude::{ExtensionLibrary, Gd, InitHandle, InitLevel, Share};
|
||||
|
||||
pub mod sproing;
|
||||
pub mod data_installer;
|
||||
pub mod lightwave_object;
|
||||
pub mod sproing;
|
||||
pub mod starforce;
|
||||
|
||||
struct Main {}
|
||||
|
||||
#[gdextension]
|
||||
unsafe impl ExtensionLibrary for Main {
|
||||
fn load_library(handle: &mut InitHandle) -> bool {
|
||||
handle.register_layer(InitLevel::Editor, ResourceLoaderLayer { datafile: None });
|
||||
handle.register_layer(
|
||||
InitLevel::Editor,
|
||||
ResourceLoaderLayer {
|
||||
datafile: None,
|
||||
sarc: None,
|
||||
},
|
||||
);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
struct ResourceLoaderLayer {
|
||||
pub datafile: Option<Gd<DatafileLoader>>,
|
||||
pub sarc: Option<Gd<SarLoader>>,
|
||||
}
|
||||
|
||||
impl ExtensionLayer for ResourceLoaderLayer {
|
||||
@@ -26,9 +36,12 @@ impl ExtensionLayer for ResourceLoaderLayer {
|
||||
auto_register_classes();
|
||||
|
||||
self.datafile = Some(Gd::<DatafileLoader>::with_base(DatafileLoader::init));
|
||||
self.sarc = Some(Gd::<SarLoader>::with_base(SarLoader::init));
|
||||
|
||||
ResourceLoader::singleton()
|
||||
.add_resource_format_loader(self.datafile.as_ref().unwrap().share().upcast(), true);
|
||||
ResourceLoader::singleton()
|
||||
.add_resource_format_loader(self.sarc.as_ref().unwrap().share().upcast(), true);
|
||||
}
|
||||
|
||||
fn deinitialize(&mut self) {
|
||||
@@ -36,5 +49,9 @@ impl ExtensionLayer for ResourceLoaderLayer {
|
||||
ResourceLoader::singleton().remove_resource_format_loader(datafile.share().upcast());
|
||||
self.datafile = None;
|
||||
}
|
||||
if let Some(sarc) = &self.sarc {
|
||||
ResourceLoader::singleton().remove_resource_format_loader(sarc.share().upcast());
|
||||
self.sarc = None;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,22 @@ use godot::builtin::{
|
||||
Dictionary, GodotString, PackedInt32Array, PackedVector2Array, PackedVector3Array,
|
||||
VariantArray, Vector2, Vector3,
|
||||
};
|
||||
use godot::engine::base_material_3d::TextureParam;
|
||||
use godot::engine::mesh::{ArrayFormat, ArrayType, PrimitiveType};
|
||||
use godot::engine::{ArrayMesh, SurfaceTool};
|
||||
use godot::engine::{load, ArrayMesh, Image, ImageTexture, StandardMaterial3D, SurfaceTool};
|
||||
use godot::log::godot_print;
|
||||
use godot::obj::{EngineEnum, Gd};
|
||||
use godot::prelude::{godot_warn, Array, GodotClass, 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::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::surface_definition::SurfaceDefinition;
|
||||
use lightwave_3d::lwo2::tags::Tag;
|
||||
use lightwave_3d::LightWaveObject;
|
||||
|
||||
@@ -27,7 +36,10 @@ 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;
|
||||
|
||||
@@ -46,26 +58,44 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd<ArrayMesh> {
|
||||
);
|
||||
arrays = Some(ars);
|
||||
}
|
||||
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(),
|
||||
);
|
||||
}
|
||||
x => godot_warn!(
|
||||
"Discontinuous Vertex Mapping: {}",
|
||||
String::from_utf8(x.to_vec()).unwrap()
|
||||
),
|
||||
},
|
||||
Tag::VertexMapping(vmap) => match &vmap.kind {
|
||||
b"TXUV" => {
|
||||
if let Some(arrays) = &mut arrays {
|
||||
let mut arr = PackedVector2Array::new();
|
||||
arr.resize(vert_count);
|
||||
|
||||
for uv in vmap.data.mapping {
|
||||
arr.set(
|
||||
uv.vert as usize,
|
||||
/*collect_uvs(
|
||||
vmap.data.mapping.into_iter().map(|it| {
|
||||
(
|
||||
it.vert as usize,
|
||||
Vector2 {
|
||||
x: uv.value[0],
|
||||
y: uv.value[1],
|
||||
x: it.value[0],
|
||||
y: it.value[1],
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
arrays.set(ArrayType::ARRAY_TEX_UV.ord() as usize, arr.to_variant());
|
||||
}
|
||||
)
|
||||
}),
|
||||
vert_count,
|
||||
arrays.as_mut().unwrap(),
|
||||
);*/
|
||||
}
|
||||
x => godot_warn!("{}", String::from_utf8(x.to_vec()).unwrap()),
|
||||
x => godot_warn!("Vertex Mapping: {}", String::from_utf8(x.to_vec()).unwrap()),
|
||||
},
|
||||
Tag::PolygonList(polygons) => match &polygons.kind {
|
||||
b"FACE" => {
|
||||
@@ -76,18 +106,32 @@ pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd<ArrayMesh> {
|
||||
}
|
||||
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);
|
||||
}
|
||||
x => {
|
||||
godot_warn!("Invalid chunk {:?}", x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
try_commit(&mut mesh, &arrays);
|
||||
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()));
|
||||
if let Some(mat) = mats.next() {
|
||||
out_mesh.surface_set_material(i, mat.upcast())
|
||||
}
|
||||
}
|
||||
out_mesh
|
||||
}
|
||||
@@ -104,6 +148,27 @@ fn try_commit(mesh: &mut ArrayMesh, arrays: &Option<VariantArray>) {
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_uvs<I: Iterator<Item = (usize, Vector2)>>(
|
||||
mapping: I,
|
||||
vert_count: usize,
|
||||
arrays: &mut VariantArray,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
|
||||
arrays.set(ArrayType::ARRAY_TEX_UV.ord() as usize, arr.to_variant());
|
||||
}
|
||||
|
||||
fn collect_points(chunk: Chunk<PointList>) -> PackedVector3Array {
|
||||
PackedVector3Array::from(
|
||||
chunk
|
||||
@@ -116,6 +181,79 @@ fn collect_points(chunk: Chunk<PointList>) -> PackedVector3Array {
|
||||
)
|
||||
}
|
||||
|
||||
fn collect_material(surface: SurfaceDefinition, clip: ImageClip) -> Gd<StandardMaterial3D> {
|
||||
let mut material = StandardMaterial3D::new();
|
||||
material.set_name(surface.name.to_string().into());
|
||||
|
||||
let mut i: Option<Gd<Image>> = 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for attr in surface.attributes {
|
||||
match attr {
|
||||
SurfaceParameterSubChunk::Blocks(blocks) => {
|
||||
if let SurfaceBlocks::ImageMapTexture { header, attributes } = blocks.data {
|
||||
let mut texture = ImageTexture::new();
|
||||
let mut chan = TextureParam::TEXTURE_ALBEDO;
|
||||
for attr in header.data.block_attributes {
|
||||
match attr {
|
||||
SurfaceBlockHeaderSubChunk::Channel(c) => {
|
||||
chan = match c.data.texture_channel {
|
||||
TextureChannel::Color => TextureParam::TEXTURE_ALBEDO,
|
||||
TextureChannel::Diffuse => TextureParam::TEXTURE_ALBEDO,
|
||||
TextureChannel::Bump => TextureParam::TEXTURE_HEIGHTMAP,
|
||||
TextureChannel::RefractiveIndex => {
|
||||
TextureParam::TEXTURE_REFRACTION
|
||||
}
|
||||
TextureChannel::Specular => TextureParam::TEXTURE_METALLIC,
|
||||
TextureChannel::Glossy => TextureParam::TEXTURE_ROUGHNESS,
|
||||
x => {
|
||||
godot_warn!("Invalid channel {:?}", x);
|
||||
TextureParam::TEXTURE_ORM
|
||||
}
|
||||
}
|
||||
}
|
||||
x => {
|
||||
godot_warn!("Invalid surface header chunk {:?}", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
for attr in attributes {
|
||||
match attr {
|
||||
SurfaceBlockImageTextureSubChunk::ImageMap(r) => {
|
||||
texture.set_image(i.unwrap());
|
||||
i = None;
|
||||
}
|
||||
x => {
|
||||
godot_warn!("Invalid image texture chunk {:?}", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
material.set_texture(chan, texture.upcast());
|
||||
}
|
||||
}
|
||||
x => {
|
||||
godot_warn!("Invalid Surface Chunk {:?}", x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
material
|
||||
}
|
||||
|
||||
fn collect_polygons(chunk: Chunk<PolygonLists>) -> PackedInt32Array {
|
||||
debug_assert!(chunk.polygons.len() >= 3, "{:?}", chunk);
|
||||
PackedInt32Array::from(
|
||||
|
||||
1
rust/mhgd/src/starforce/mod.rs
Normal file
1
rust/mhgd/src/starforce/mod.rs
Normal file
@@ -0,0 +1 @@
|
||||
pub mod sar_archive;
|
||||
92
rust/mhgd/src/starforce/sar_archive.rs
Normal file
92
rust/mhgd/src/starforce/sar_archive.rs
Normal file
@@ -0,0 +1,92 @@
|
||||
use crate::lightwave_object::lightwave_to_gd;
|
||||
use dds::{Compression, PixelFormat, DDS};
|
||||
use godot::bind::{godot_api, GodotClass};
|
||||
use godot::builtin::{GodotString, PackedByteArray, StringName, ToVariant, Variant};
|
||||
use godot::engine::global::Error;
|
||||
use godot::engine::image::Format;
|
||||
use godot::engine::{Image, ImageTexture, ResourceFormatLoader, ResourceFormatLoaderVirtual};
|
||||
use godot::obj::{Base, Gd};
|
||||
use lightwave_3d::LightWaveObject;
|
||||
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"#;
|
||||
|
||||
#[derive(GodotClass)]
|
||||
#[class(base=ResourceFormatLoader)]
|
||||
pub struct SarLoader {
|
||||
pub archive: SarcArchive,
|
||||
|
||||
#[base]
|
||||
pub base: Base<ResourceFormatLoader>,
|
||||
}
|
||||
|
||||
#[godot_api]
|
||||
impl SarLoader {}
|
||||
|
||||
#[godot_api]
|
||||
impl ResourceFormatLoaderVirtual for SarLoader {
|
||||
fn init(base: Base<Self::Base>) -> Self {
|
||||
let mut file = File::open(SAR_PATH).unwrap();
|
||||
let archive = SarcArchive::read(&mut file).unwrap();
|
||||
|
||||
Self { base, archive }
|
||||
}
|
||||
|
||||
fn recognize_path(&self, path: GodotString, type_: StringName) -> bool {
|
||||
path.to_string().starts_with("sar://")
|
||||
}
|
||||
|
||||
fn load(
|
||||
&self,
|
||||
path: GodotString,
|
||||
original_path: GodotString,
|
||||
use_sub_threads: bool,
|
||||
cache_mode: i64,
|
||||
) -> Variant {
|
||||
let internal_path = original_path
|
||||
.to_string()
|
||||
.strip_prefix("sar://")
|
||||
.unwrap()
|
||||
.replace('/', "\\");
|
||||
|
||||
match path.to_string().rsplit_once('.') {
|
||||
Some((_, "lwo")) => {
|
||||
let mut f = File::open(SAR_PATH).unwrap();
|
||||
let data = self
|
||||
.archive
|
||||
.extract(&mut f, internal_path.as_str())
|
||||
.unwrap();
|
||||
let obj = LightWaveObject::read(&mut Cursor::new(data)).unwrap();
|
||||
lightwave_to_gd(obj).to_variant()
|
||||
}
|
||||
Some((_, "bmp")) => {
|
||||
let mut f = File::open(SAR_PATH).unwrap();
|
||||
|
||||
let mut image = Image::new();
|
||||
if let Ok(bmp) = self.archive.extract(&mut f, internal_path.as_str()) {
|
||||
image.load_bmp_from_buffer(PackedByteArray::from(bmp.as_slice()));
|
||||
} else if let Ok(dds) = self
|
||||
.archive
|
||||
.extract(&mut f, format!("{}.dds", internal_path).as_str())
|
||||
{
|
||||
let data = DDS::decode(&mut Cursor::new(dds)).unwrap();
|
||||
image.set_data(
|
||||
data.header.width as i64,
|
||||
data.header.height as i64,
|
||||
false,
|
||||
Format::FORMAT_RGBA8,
|
||||
data.layers[0]
|
||||
.iter()
|
||||
.flat_map(|px| [px.r, px.g, px.b, px.a])
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
image.to_variant()
|
||||
}
|
||||
None => Error::ERR_FILE_UNRECOGNIZED.to_variant(),
|
||||
_ => Error::ERR_BUG.to_variant(),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user