This commit is contained in:
2023-05-12 21:42:47 +02:00
parent 2777d540ba
commit 20b1ccde09
8 changed files with 313 additions and 45 deletions

48
rust/Cargo.lock generated
View File

@@ -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"

View File

@@ -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" }

View File

@@ -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" }

View File

View 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;
}
}
}

View File

@@ -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(

View File

@@ -0,0 +1 @@
pub mod sar_archive;

View 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(),
}
}
}