diff --git a/rust/Cargo.lock b/rust/Cargo.lock index e939b73..ecf4184 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -318,6 +318,13 @@ version = "0.2.144" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1" +[[package]] +name = "lightwave" +version = "0.1.0" +dependencies = [ + "binrw", +] + [[package]] name = "lock_api" version = "0.4.9" @@ -350,6 +357,7 @@ dependencies = [ name = "mhex" version = "0.1.0" dependencies = [ + "lightwave", "starforcelib", ] diff --git a/rust/Cargo.toml b/rust/Cargo.toml index 3a39371..aaf5114 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,3 +1,3 @@ [workspace] -members = ["renderwarelib", "springylib", "starforcelib", "mhex"] +members = ["renderwarelib", "springylib", "starforcelib", "mhex", "lightwave"] diff --git a/rust/lightwave/README.md b/rust/lightwave/README.md new file mode 100644 index 0000000..4210063 --- /dev/null +++ b/rust/lightwave/README.md @@ -0,0 +1,3 @@ +# LightWave 3D Rust Parser + +* [LWO2 Spec](http://static.lightwave3d.com/sdk/2015/html/filefmts/lwo2.html) \ No newline at end of file diff --git a/rust/lightwave/src/binrw_helpers.rs b/rust/lightwave/src/binrw_helpers.rs new file mode 100644 index 0000000..664c6c6 --- /dev/null +++ b/rust/lightwave/src/binrw_helpers.rs @@ -0,0 +1,64 @@ +use crate::lwo2::vx; +use binrw::{BinRead, BinResult, Endian}; +use std::io::{Read, Seek}; +use std::iter::from_fn; + +pub fn until_size_limit( + limit: u64, +) -> impl Fn(&mut R, Endian, Arg) -> BinResult +where + T: for<'a> BinRead = Arg>, + R: Read + Seek, + Arg: Clone, + Ret: FromIterator, +{ + until_size_limit_with(limit, default_reader) +} + +/// Reads data until total size reaches a limit +pub fn until_size_limit_with( + limit: u64, + reader_fn: ReadFn, +) -> impl Fn(&mut R, Endian, Arg) -> BinResult +where + T: for<'a> BinRead = Arg>, + R: Read + Seek, + Arg: Clone, + ReadFn: Fn(&mut R, Endian, Arg) -> BinResult, + Ret: FromIterator, +{ + move |reader, endian, args| { + let pos = reader.stream_position()?; + from_fn(|| match reader.stream_position() { + Ok(now) if now - pos < limit => Some(reader_fn(reader, endian, args.clone())), + Ok(_) => None, + Err(err) => Some(Err(binrw::Error::Io(err))), + }) + .fuse() + .collect() + } +} + +pub fn count_with_vx(n: usize) -> impl Fn(&mut R, Endian, ()) -> BinResult> +where + R: Read + Seek, +{ + move |reader, endian, _args: ()| { + core::iter::repeat_with(|| vx(reader, endian, ())) + .take(n) + .collect() + } +} + +fn default_reader<'a, T: BinRead, R: Read + Seek>( + reader: &mut R, + endian: Endian, + args: T::Args<'a>, +) -> BinResult +where + T::Args<'a>: Clone, +{ + let mut value = T::read_options(reader, endian, args.clone())?; + value.after_parse(reader, endian, args)?; + Ok(value) +} diff --git a/rust/lightwave/src/iff/mod.rs b/rust/lightwave/src/iff/mod.rs new file mode 100644 index 0000000..218903a --- /dev/null +++ b/rust/lightwave/src/iff/mod.rs @@ -0,0 +1,46 @@ +use binrw::{binread, BinRead}; +use std::ops::Deref; + +#[binread] +#[derive(Debug)] +pub struct Chunk +where + for<'a> D: BinRead = (u32,)>, +{ + pub length: u32, + #[br(pad_size_to = length, align_after = 2, args(length))] + pub data: D, +} + +impl Deref for Chunk +where + for<'a> D: BinRead = (u32,)>, +{ + type Target = D; + + fn deref(&self) -> &Self::Target { + &self.data + } +} + +#[binread] +#[derive(Debug)] +pub struct SubChunk +where + for<'a> D: BinRead = (u32,)>, +{ + pub length: u16, + #[br(pad_size_to = length, align_after = 2, args(length as u32))] + pub data: D, +} + +impl Deref for SubChunk +where + for<'a> D: BinRead = (u32,)>, +{ + type Target = D; + + fn deref(&self) -> &Self::Target { + &self.data + } +} diff --git a/rust/lightwave/src/lib.rs b/rust/lightwave/src/lib.rs index 7d12d9a..1696499 100644 --- a/rust/lightwave/src/lib.rs +++ b/rust/lightwave/src/lib.rs @@ -1,14 +1,60 @@ -pub fn add(left: usize, right: usize) -> usize { - left + right +use crate::lwo2::tags::Tag; +use binrw::{binread, until_eof, BinRead, BinResult}; +use std::fs::File; +use std::io::{Read, Seek}; +use std::path::Path; + +mod binrw_helpers; +pub mod iff; +pub mod lwo2; + +/// The data in LightWave 3D® object files comprise the points, polygons and surfaces that describe +/// the geometry and appearance of an object. "Polygons" here means any of several geometric +/// elements (faces, curves or patches, for example) defined by an ordered list of points, and +/// "surfaces" refers to the collection of attributes, sometimes called materials, that define +/// the visual surface properties of polygons. +/// +/// Object files can contain multiple layers, or parts, and each part can be a single connected mesh +/// or several disjoint meshes. They may also contain one or more surface definitions with no points +/// or polygons at all. Surface definitions can include references to other files (images, for +/// example), plug-ins, and envelopes containing parameter values that vary over time. +/// +/// This document outlines the object file format and provides a detailed reference for each of the +/// components. The component descriptions include both a regular expression defining the syntax and +/// a discussion of the contents. See also the Examples supplement, a more conversational +/// introduction to the format that includes annotated listings of file contents as well as +/// several sample files. +/// Informally, object files start with the four bytes "FORM" followed by a four-byte integer giving +/// the length of the file (minus 8) and the four byte ID "LWO2". The remainder of the data is a +/// collection of chunks, some of which will contain subchunks. +/// +/// To be read, IFF files must be parsed. The order in which chunks can occur in a file isn't fixed. +/// Some chunks, however, contain data that depends on the contents of other chunks, and this fixes +/// a relative order for the chunks involved. Chunks and subchunks also depend on context for their +/// meaning. The CHAN subchunk in an envelope chunk isn't the same thing as the CHAN subchunk in a +/// surface block. And you may encounter chunks that aren't defined here, which you should be +/// prepared to skip gracefully if you don't understand them. You can do this by using the chunk +/// size to seek to the next chunk. +#[binread] +#[br(big, magic(b"FORM"))] +#[derive(Debug)] +pub struct LightWaveObject { + pub file_size: u32, + #[br(magic(b"LWO2"), parse_with = until_eof)] + pub data: Vec, } -#[cfg(test)] -mod tests { - use super::*; +impl LightWaveObject { + pub fn read_file>(path: P) -> std::io::Result { + let mut reader = File::open(path)?; + Self::read(&mut reader) + .map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err)) + } - #[test] - fn it_works() { - let result = add(2, 2); - assert_eq!(result, 4); + pub fn read(reader: &mut R) -> BinResult + where + R: Read + Seek, + { + BinRead::read(reader) } } diff --git a/rust/lightwave/src/lwo2/mod.rs b/rust/lightwave/src/lwo2/mod.rs new file mode 100644 index 0000000..203132b --- /dev/null +++ b/rust/lightwave/src/lwo2/mod.rs @@ -0,0 +1,24 @@ +use binrw::{BinReaderExt, BinResult, Endian}; +use std::io::{Read, Seek}; + +pub mod sub_tags; +pub mod tags; + +/// This is an index into an array of items (points or polygons), or a collection of items +/// each uniquely identified by an integer (clips or envelopes). A VX is written as a variable +/// length 2- or 4-byte element. If the index value is less than 65,280 (0xFF00), then the +/// index is written as an unsigned two-byte integer. Otherwise the index is written as an +/// unsigned four byte integer with bits 24-31 set. When reading an index, if the first byte +/// encountered is 255 (0xFF), then the four-byte form is being used and the first byte should +/// be discarded or masked out. +pub fn vx(reader: &mut R, endian: Endian, _args: ()) -> BinResult +where + R: Read + Seek, +{ + let kind: u16 = reader.read_type(endian)?; + Ok(if (kind & 0xff) != 0xff { + kind as u32 + } else { + (((kind as u32) & 0xff) << 16) | (reader.read_type::(endian)? as u32) + }) +} diff --git a/rust/lightwave/src/lwo2/sub_tags/envelope_type.rs b/rust/lightwave/src/lwo2/sub_tags/envelope_type.rs new file mode 100644 index 0000000..b9e3996 --- /dev/null +++ b/rust/lightwave/src/lwo2/sub_tags/envelope_type.rs @@ -0,0 +1,43 @@ +use binrw::binread; + +/// The type subchunk records the format in which the envelope is displayed to the user and a type +/// code that identifies the components of certain predefined envelope triples. The user format has +/// no effect on the actual values, only the way they're presented in LightWave®'s interface. +#[binread] +#[br(import(_length: u32))] +#[derive(Debug)] +pub struct EnvelopeType { + pub user_format: UserFormat, + pub kind: EnvelopeKind, +} + +#[binread] +#[br(repr = u8)] +#[derive(Debug)] +pub enum UserFormat { + Float = 2, + Distance = 3, + Percent = 4, + Angle = 5, +} + +#[binread] +#[br(repr = u8)] +#[derive(Debug)] +pub enum EnvelopeKind { + PositionX = 0x1, + PositionY = 0x2, + PositionZ = 0x3, + RotHeading = 0x4, + RotPitch = 0x5, + RotBank = 0x6, + ScaleX = 0x7, + ScaleY = 0x8, + ScaleZ = 0x9, + ColorR = 0xa, + ColorG = 0xb, + ColorB = 0xc, + FalloffX = 0xd, + FalloffY = 0xe, + FalloffZ = 0xf, +} diff --git a/rust/lightwave/src/lwo2/sub_tags/mod.rs b/rust/lightwave/src/lwo2/sub_tags/mod.rs new file mode 100644 index 0000000..f779d4f --- /dev/null +++ b/rust/lightwave/src/lwo2/sub_tags/mod.rs @@ -0,0 +1,11 @@ +use binrw::binread; +use crate::lwo2::sub_tags::envelope_type::EnvelopeType; + +pub mod envelope_type; + +#[binread] +#[derive(Debug)] +pub enum SubTag { + #[br(magic(b"TYPE"))] + EnvelopeType(EnvelopeType) +} diff --git a/rust/lightwave/src/lwo2/tags/bounding_box.rs b/rust/lightwave/src/lwo2/tags/bounding_box.rs new file mode 100644 index 0000000..f3a9412 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/bounding_box.rs @@ -0,0 +1,11 @@ +use binrw::binread; + +///Store the bounding box for the vertex data in a layer. Optional. The min and max vectors are +/// the lower and upper corners of the bounding box. +#[binread] +#[br(import(_length: u32))] +#[derive(Debug)] +pub struct BoundingBox { + pub min: [f32; 3], + pub max: [f32; 3], +} diff --git a/rust/lightwave/src/lwo2/tags/discontinuous_vertex_mapping.rs b/rust/lightwave/src/lwo2/tags/discontinuous_vertex_mapping.rs new file mode 100644 index 0000000..ca20e2f --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/discontinuous_vertex_mapping.rs @@ -0,0 +1,53 @@ +use crate::binrw_helpers::until_size_limit; +use crate::lwo2::vx; +use binrw::{binread, NullString, PosValue}; + +/// (Introduced with LightWave® 6.5.) Associates a set of floating-point vectors with the vertices +/// of specific polygons. VMADs are similar to VMAPs, but they assign vectors to polygon vertices +/// rather than points. For a given mapping, a VMAP always assigns only one vector to a point, while +/// a VMAD can assign as many vectors to a point as there are polygons sharing the point. +/// +/// The motivation for VMADs is the problem of seams in UV texture mapping. If a UV map is +/// topologically equivalent to a cylinder or a sphere, a seam is formed where the opposite edges of +/// the map meet. Interpolation of UV coordinates across this discontinuity is aesthetically and +/// mathematically incorrect. The VMAD substitutes an equivalent mapping that interpolates +/// correctly. It only needs to do this for polygons in which the seam lies. +/// +/// VMAD chunks are paired with VMAPs of the same name, if they exist. The vector values in the VMAD +/// will then replace those in the corresponding VMAP, but only for calculations involving the +/// specified polygons. When the same points are used for calculations on polygons not specified in +/// the VMAD, the VMAP values are used. +/// +/// VMADs need not be associated with a VMAP. They can also be used simply to define a +/// (discontinuous) per-polygon mapping. But not all mapping types are valid for VMADs, since for +/// some types it makes no sense for points to have more than one map value. TXUV, RGB, RGBA and +/// WGHT types are supported for VMADs, for example, while MORF and SPOT are not. VMADs of +/// unsupported types are preserved but never evaluated. +#[binread] +#[br(import(length: u32))] +#[derive(Debug)] +pub struct DiscontinuousVertexMappings { + #[br(temp)] + pub start_pos: PosValue<()>, + pub kind: [u8; 4], + #[br(temp)] + pub dimension: u16, + #[br(align_after = 2)] + pub name: NullString, + #[br(temp)] + pub end_pos: PosValue<()>, + #[br(parse_with = |reader, endian, _: ()| until_size_limit(length as u64 - (end_pos.pos - start_pos.pos))(reader, endian, (dimension, )))] + pub mappings: Vec, +} + +#[binread] +#[br(import(dimension: u16))] +#[derive(Debug)] +pub struct DiscontinuousVertexMapping { + #[br(parse_with = vx)] + pub vert: u32, + #[br(parse_with = vx)] + pub poly: u32, + #[br(count = dimension)] + pub values: Vec, +} diff --git a/rust/lightwave/src/lwo2/tags/layer.rs b/rust/lightwave/src/lwo2/tags/layer.rs new file mode 100644 index 0000000..afeb81b --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/layer.rs @@ -0,0 +1,20 @@ +use binrw::{binread, NullString, PosValue}; + +/// Signals the start of a new layer. All the data chunks which follow will be included in this +/// layer until another layer chunk is encountered. If data is encountered before a layer chunk, +/// it goes into an arbitrary layer. If the least significant bit of flags is set, the layer is +/// hidden. The parent index indicates the default parent for this layer and can be -1 or missing +/// to indicate no parent. +#[binread] +#[br(import(length: u32))] +#[derive(Debug)] +pub struct Layer { + pub number: u16, + pub flags: u16, + pub pivot: [f32; 3], + #[br(align_after = 2)] + pub name: NullString, + pub status: PosValue<()>, + #[br(if(status.pos < length as u64))] + pub parent: Option, +} diff --git a/rust/lightwave/src/lwo2/tags/mod.rs b/rust/lightwave/src/lwo2/tags/mod.rs new file mode 100644 index 0000000..4efc163 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/mod.rs @@ -0,0 +1,48 @@ +use crate::iff::Chunk; +use crate::lwo2::tags::bounding_box::BoundingBox; +use crate::lwo2::tags::discontinuous_vertex_mapping::DiscontinuousVertexMappings; +use crate::lwo2::tags::layer::Layer; +use crate::lwo2::tags::point_list::PointList; +use crate::lwo2::tags::polygon_list::PolygonLists; +use crate::lwo2::tags::polygon_tag_mapping::PolygonTagMappings; +use crate::lwo2::tags::surface_definition::SurfaceDefinition; +use crate::lwo2::tags::tag_strings::TagStrings; +use crate::lwo2::tags::vertex_map_parameter::VertexMapParameter; +use crate::lwo2::tags::vertex_mapping::VertexMappings; +use binrw::binread; + +pub mod bounding_box; +pub mod discontinuous_vertex_mapping; +pub mod layer; +pub mod point_list; +pub mod polygon_list; +pub mod polygon_tag_mapping; +pub mod surface_definition; +pub mod tag_strings; +pub mod vertex_map_parameter; +pub mod vertex_mapping; + +#[binread] +#[derive(Debug)] +pub enum Tag { + #[br(magic(b"LAYR"))] + Layer(Chunk), + #[br(magic(b"PNTS"))] + PointList(Chunk), + #[br(magic(b"VMAP"))] + VertexMapping(Chunk), + #[br(magic(b"TAGS"))] + TagStrings(Chunk), + #[br(magic(b"PTAG"))] + PolygonTagMapping(Chunk), + #[br(magic(b"VMAD"))] + DiscontinuousVertexMapping(Chunk), + #[br(magic(b"VMPA"))] + VertexMapParameter(Chunk), + #[br(magic(b"BBOX"))] + BoundingBox(Chunk), + #[br(magic(b"POLS"))] + PolygonList(Chunk), + #[br(magic(b"SURF"))] + SurfaceDefinition(Chunk), +} diff --git a/rust/lightwave/src/lwo2/tags/point_list.rs b/rust/lightwave/src/lwo2/tags/point_list.rs new file mode 100644 index 0000000..9b72628 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/point_list.rs @@ -0,0 +1,16 @@ +use binrw::binread; + +///Lists (x, y, z) coordinate triples for a set of points. The number of points in the chunk is +/// just the chunk size divided by 12. The PNTS chunk must precede the POLS, VMAP and VMAD chunks +/// that refer to it. These chunks list points using a 0-based index into PNTS. +// +// The LightWave® coordinate system is left-handed, with +X to the right or east, +Y upward, +// and +Z forward or north. Object files don't contain explicit units, but by convention the +// unit is meters. Coordinates in PNTS are relative to the pivot point of the layer. +#[binread] +#[br(import(length: u32))] +#[derive(Debug)] +pub struct PointList { + #[br(count = length / 12, assert(length % 12 == 0))] + pub point_location: Vec<[f32; 3]>, +} diff --git a/rust/lightwave/src/lwo2/tags/polygon_list.rs b/rust/lightwave/src/lwo2/tags/polygon_list.rs new file mode 100644 index 0000000..bc1acc8 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/polygon_list.rs @@ -0,0 +1,54 @@ +use crate::binrw_helpers::{count_with_vx, until_size_limit}; +use binrw::binread; + +/// A list of polygons for the current layer. Possible polygon types include: +/// +/// * **FACE** +///
    "Regular" polygons, the most common.
+/// * **CURV** +///
    Catmull-Rom splines. These are used during modeling and are currently ignored by the +/// renderer.
+/// * **PTCH** +///
    Subdivision patches. The POLS chunk contains the definition of the control cage +/// polygons, and the patch is created by subdividing these polygons. The renderable geometry +/// that results from subdivision is determined interactively by the user through settings +/// within LightWave®. The subdivision method is undocumented.
+/// * **MBAL** +///
    Metaballs. These are single-point polygons. The points are associated with a VMAP of +/// type MBAL that contains the radius of influence of each metaball. The renderable polygonal +/// surface constructed from a set of metaballs is inferred as an isosurface on a scalar field +/// derived from the sum of the influences of all of the metaball points.
+/// * **BONE** +///
    Line segments representing the object's skeleton. These are converted to bones for +/// deformation during rendering.
+/// +/// Each polygon is defined by a vertex count followed by a list of indexes into the most recent +/// PNTS chunk. The maximum number of vertices is 1023. The 6 high-order bits of the vertex count +/// are flag bits with different meanings for each polygon type. When reading POLS, remember to mask +/// out the flags to obtain numverts. (For CURV polygon: The two low order flags are for continuity +/// control point toggles. The four remaining high order flag bits are additional vertex count bits; +/// this brings the maximum number of vertices for CURV polygons to 2^14 = 16383.) +/// +/// When writing POLS, the vertex list for each polygon should begin at a convex vertex and proceed +/// clockwise as seen from the visible side of the polygon. LightWave® polygons are single-sided +/// (although double-sidedness is a possible surface property), and the normal is defined as the +/// cross product of the first and last edges. +#[binread] +#[br(import(length: u32))] +#[derive(Debug)] +pub struct PolygonLists { + pub kind: [u8; 4], + #[br(parse_with = until_size_limit(length as u64 - 4))] + pub polygons: Vec, +} + +#[binread] +#[derive(Debug)] +pub struct PolygonList { + #[br(temp)] + pub numvert_and_flags: u16, + #[br(calc = (numvert_and_flags >> 10) as u8)] + pub flags: u8, + #[br(parse_with = count_with_vx((numvert_and_flags & 0x3ff) as usize))] + pub vert: Vec, +} diff --git a/rust/lightwave/src/lwo2/tags/polygon_tag_mapping.rs b/rust/lightwave/src/lwo2/tags/polygon_tag_mapping.rs new file mode 100644 index 0000000..4dcf167 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/polygon_tag_mapping.rs @@ -0,0 +1,36 @@ +use crate::binrw_helpers::until_size_limit; +use crate::lwo2::vx; +use binrw::binread; + +/// Associates tags of a given type with polygons in the most recent POLS chunk. The most common +/// polygon tag types are +/// +/// * **SURF**: +///
    The surface assigned to the polygon. The actual surface attributes are found by matching +/// the name in the TAGS chunk with the name in a SURF chunk.
+/// * **PART**: +///
    The part the polygon belongs to. Parts are named groups of polygons analogous to point +/// selection sets (but a polygon can belong to only one part).
+/// * **SMGP** +///
    The smoothing group the polygon belongs to. Shading is only interpolated within a +/// smoothing group, not across groups.
+/// +/// The polygon is identified by an index into the previous POLS chunk, and the tag is given by an +/// index into the previous TAGS chunk. Not all polygons will have a value for every tag type. The +/// behavior for polygons lacking a given tag depends on the type. +#[binread] +#[br(import(length: u32))] +#[derive(Debug)] +pub struct PolygonTagMappings { + pub kind: [u8; 4], + #[br(parse_with = until_size_limit(length as u64 - 4))] + pub mappings: Vec, +} + +#[binread] +#[derive(Debug)] +pub struct PolygonTagMapping { + #[br(parse_with = vx)] + pub poly: u32, + pub tag: u16, +} diff --git a/rust/lightwave/src/lwo2/tags/surface_definition.rs b/rust/lightwave/src/lwo2/tags/surface_definition.rs new file mode 100644 index 0000000..e8f5a63 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/surface_definition.rs @@ -0,0 +1,81 @@ +use crate::binrw_helpers::until_size_limit; +use crate::iff::SubChunk; +use crate::lwo2::vx; +use binrw::{binread, NullString, PosValue}; + +#[binread] +#[br(import(length: u32))] +#[derive(Debug)] +pub struct SurfaceDefinition { + #[br(temp)] + pub start_pos: PosValue<()>, + #[br(align_after = 2)] + pub name: NullString, + #[br(align_after = 2)] + pub source: NullString, + #[br(temp)] + pub end_pos: PosValue<()>, + #[br(parse_with = until_size_limit(length as u64 - (end_pos.pos - start_pos.pos)))] + pub attributes: Vec, +} + +#[binread] +#[derive(Debug)] +pub enum SurfaceSubTag { + #[br(magic(b"COLR"))] + BaseColor(SubChunk), + #[br(magic(b"DIFF"))] + BaseShadingValueDiffuse(SubChunk), + #[br(magic(b"LUMI"))] + BaseShadingValueLuminosity(SubChunk), + #[br(magic(b"SPEC"))] + BaseShadingValueSpecular(SubChunk), + #[br(magic(b"REFL"))] + BaseShadingValueReflectivity(SubChunk), + #[br(magic(b"TRAN"))] + BaseShadingValueTransmission(SubChunk), + #[br(magic(b"TRNL"))] // TODO + BaseShadingValueTrnl(SubChunk), + #[br(magic(b"GLOS"))] + SpecularGlossiness(SubChunk), + #[br(magic(b"SHRP"))] + DiffuseSharpness(SubChunk), + #[br(magic(b"BUMP"))] + BumpIntensity(SubChunk), + #[br(magic(b"SIDE"))] + PolygonSidedness(SubChunk), + #[br(magic(b"SMAN"))] + MaxSmoothingAngle(SubChunk), +} + +#[binread] +#[br(import(_length: u32))] +#[derive(Debug)] +pub struct BaseColor { + pub base_color: [f32; 3], + #[br(parse_with = vx)] + pub envelope: u32, +} + +#[binread] +#[br(import(_length: u32))] +#[derive(Debug)] +pub struct BaseShadingValues { + pub value: f32, + #[br(parse_with = vx)] + pub envelope: u32, +} + +#[binread] +#[br(import(_length: u32))] +#[derive(Debug)] +pub struct PolygonSidedness { + pub sidedness: u16, +} + +#[binread] +#[br(import(_length: u32))] +#[derive(Debug)] +pub struct MaxSmoothingAngle { + pub max_smoothing_angle: f32, +} diff --git a/rust/lightwave/src/lwo2/tags/tag_strings.rs b/rust/lightwave/src/lwo2/tags/tag_strings.rs new file mode 100644 index 0000000..3096c74 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/tag_strings.rs @@ -0,0 +1,11 @@ +use crate::binrw_helpers::until_size_limit; +use binrw::{binread, NullString}; + +/// Lists the tag strings that can be associated with polygons by the PTAG chunk. +#[binread] +#[br(import(length: u32))] +#[derive(Debug)] +pub struct TagStrings { + #[br(parse_with = until_size_limit(length as u64))] + pub tag_strings: Vec, +} diff --git a/rust/lightwave/src/lwo2/tags/vertex_map_parameter.rs b/rust/lightwave/src/lwo2/tags/vertex_map_parameter.rs new file mode 100644 index 0000000..567d270 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/vertex_map_parameter.rs @@ -0,0 +1,21 @@ +use binrw::binread; + +/// Describes special properties of VMAPs. +#[binread] +#[br(import(_length: u32))] +#[derive(Debug)] +pub struct VertexMapParameter { + pub uv_subdivision_type: UvSubdivisionType, + pub sketch_color: i32, +} + +#[binread] +#[br(repr = i32)] +#[derive(Debug)] +pub enum UvSubdivisionType { + Linear = 0, + Subpatch = 1, + SubpatchLinearCorners = 2, + SubpatchLinearEdges = 3, + SubpatchDiscoEdges = 4, +} diff --git a/rust/lightwave/src/lwo2/tags/vertex_mapping.rs b/rust/lightwave/src/lwo2/tags/vertex_mapping.rs new file mode 100644 index 0000000..adf6392 --- /dev/null +++ b/rust/lightwave/src/lwo2/tags/vertex_mapping.rs @@ -0,0 +1,57 @@ +use crate::binrw_helpers::until_size_limit; +use crate::lwo2::vx; +use binrw::{binread, NullString, PosValue}; + +/// Associates a set of floating-point vectors with a set of points. VMAPs begin with a type, +/// a dimension (vector length) and a name. These are followed by a list of vertex/vector pairs. +/// The vertex is given as an index into the most recent PNTS chunk, in VX format. The vector +/// contains dimension floating-point values. There can be any number of these chunks, but they +/// should all have different types or names. +/// +/// Some common type codes are +/// +/// * **PICK** +///
    Selection set. This is a VMAP of dimension 0 that marks points for quick selection by +/// name during modeling. It has no effect on the geometry of the object.
+/// * **WGHT** +///
    Weight maps have a dimension of 1 and are generally used to alter the influence of +/// deformers such as bones. Weights can be positive or negative, and the default weight for +/// unmapped vertices is 0.0.
+/// * **MNVW** +///
    Subpatch weight maps affect the shape of geometry created by subdivision patching.
+/// * **TXUV** +///
    UV texture maps have a dimension of 2.
+/// * **RGB**, **RGBA** +///
    Color maps, with a dimension of 3 or 4.
+/// * **MORF** +///
    These contain vertex displacement deltas.
+/// * **SPOT** +///
    These contain absolute vertex displacements (alternative vertex positions).
+/// +/// Other widely used map types will almost certainly appear in the future. +#[binread] +#[br(import(length: u32))] +#[derive(Debug)] +pub struct VertexMappings { + #[br(temp)] + pub begin_pos: PosValue<()>, + pub kind: [u8; 4], + #[br(temp)] + pub dimension: u16, + #[br(align_after = 2)] + pub name: NullString, + #[br(temp)] + pub end_pos: PosValue<()>, + #[br(parse_with = |reader, endian, _: ()| until_size_limit(length as u64 - (end_pos.pos - begin_pos.pos))(reader, endian, (dimension,)))] + pub mapping: Vec, +} + +#[binread] +#[br(import(dimension: u16))] +#[derive(Debug)] +pub struct VertexMapping { + #[br(parse_with = vx)] + pub vert: u32, + #[br(count = dimension)] + pub value: Vec, +} diff --git a/rust/mhex/Cargo.toml b/rust/mhex/Cargo.toml index 22a0f38..da23a3a 100644 --- a/rust/mhex/Cargo.toml +++ b/rust/mhex/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] starforcelib = {path = "../starforcelib"} +lightwave = {path = "../lightwave"} diff --git a/rust/mhex/src/main.rs b/rust/mhex/src/main.rs index a607994..5b5b867 100644 --- a/rust/mhex/src/main.rs +++ b/rust/mhex/src/main.rs @@ -1,6 +1,6 @@ -use starforcelib::sarc::SarcArchive; +use lightwave::LightWaveObject; fn main() { - let path = "E:\\Games\\Moorhuhn Kart 3\\data.sar"; - SarcArchive::extract_all(path).unwrap(); + let obj = LightWaveObject::read_file("E:\\Games\\Moorhuhn Kart 3\\extract\\D\\Moorhuhnkart\\3dobjects_tracks\\track04_robinhood\\colreset.lwo").unwrap(); + println!("{:#?}", obj); }