add lightwave parser

This commit is contained in:
2023-05-09 19:04:50 +02:00
parent 09a1e9ac0b
commit 48d3004c35
22 changed files with 667 additions and 13 deletions

8
rust/Cargo.lock generated
View File

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

View File

@@ -1,3 +1,3 @@
[workspace]
members = ["renderwarelib", "springylib", "starforcelib", "mhex"]
members = ["renderwarelib", "springylib", "starforcelib", "mhex", "lightwave"]

3
rust/lightwave/README.md Normal file
View File

@@ -0,0 +1,3 @@
# LightWave 3D Rust Parser
* [LWO2 Spec](http://static.lightwave3d.com/sdk/2015/html/filefmts/lwo2.html)

View File

@@ -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<R, Arg, T, Ret>(
limit: u64,
) -> impl Fn(&mut R, Endian, Arg) -> BinResult<Ret>
where
T: for<'a> BinRead<Args<'a> = Arg>,
R: Read + Seek,
Arg: Clone,
Ret: FromIterator<T>,
{
until_size_limit_with(limit, default_reader)
}
/// Reads data until total size reaches a limit
pub fn until_size_limit_with<R, Arg, T, ReadFn, Ret>(
limit: u64,
reader_fn: ReadFn,
) -> impl Fn(&mut R, Endian, Arg) -> BinResult<Ret>
where
T: for<'a> BinRead<Args<'a> = Arg>,
R: Read + Seek,
Arg: Clone,
ReadFn: Fn(&mut R, Endian, Arg) -> BinResult<T>,
Ret: FromIterator<T>,
{
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<R>(n: usize) -> impl Fn(&mut R, Endian, ()) -> BinResult<Vec<u32>>
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<T>
where
T::Args<'a>: Clone,
{
let mut value = T::read_options(reader, endian, args.clone())?;
value.after_parse(reader, endian, args)?;
Ok(value)
}

View File

@@ -0,0 +1,46 @@
use binrw::{binread, BinRead};
use std::ops::Deref;
#[binread]
#[derive(Debug)]
pub struct Chunk<D>
where
for<'a> D: BinRead<Args<'a> = (u32,)>,
{
pub length: u32,
#[br(pad_size_to = length, align_after = 2, args(length))]
pub data: D,
}
impl<D> Deref for Chunk<D>
where
for<'a> D: BinRead<Args<'a> = (u32,)>,
{
type Target = D;
fn deref(&self) -> &Self::Target {
&self.data
}
}
#[binread]
#[derive(Debug)]
pub struct SubChunk<D>
where
for<'a> D: BinRead<Args<'a> = (u32,)>,
{
pub length: u16,
#[br(pad_size_to = length, align_after = 2, args(length as u32))]
pub data: D,
}
impl<D> Deref for SubChunk<D>
where
for<'a> D: BinRead<Args<'a> = (u32,)>,
{
type Target = D;
fn deref(&self) -> &Self::Target {
&self.data
}
}

View File

@@ -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<Tag>,
}
#[cfg(test)]
mod tests {
use super::*;
impl LightWaveObject {
pub fn read_file<P: AsRef<Path>>(path: P) -> std::io::Result<LightWaveObject> {
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<R>(reader: &mut R) -> BinResult<LightWaveObject>
where
R: Read + Seek,
{
BinRead::read(reader)
}
}

View File

@@ -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<R>(reader: &mut R, endian: Endian, _args: ()) -> BinResult<u32>
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::<u16>(endian)? as u32)
})
}

View File

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

View File

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

View File

@@ -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],
}

View File

@@ -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<DiscontinuousVertexMapping>,
}
#[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<f32>,
}

View File

@@ -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<u16>,
}

View File

@@ -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<Layer>),
#[br(magic(b"PNTS"))]
PointList(Chunk<PointList>),
#[br(magic(b"VMAP"))]
VertexMapping(Chunk<VertexMappings>),
#[br(magic(b"TAGS"))]
TagStrings(Chunk<TagStrings>),
#[br(magic(b"PTAG"))]
PolygonTagMapping(Chunk<PolygonTagMappings>),
#[br(magic(b"VMAD"))]
DiscontinuousVertexMapping(Chunk<DiscontinuousVertexMappings>),
#[br(magic(b"VMPA"))]
VertexMapParameter(Chunk<VertexMapParameter>),
#[br(magic(b"BBOX"))]
BoundingBox(Chunk<BoundingBox>),
#[br(magic(b"POLS"))]
PolygonList(Chunk<PolygonLists>),
#[br(magic(b"SURF"))]
SurfaceDefinition(Chunk<SurfaceDefinition>),
}

View File

@@ -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]>,
}

View File

@@ -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**
/// <ul>"Regular" polygons, the most common.</ul>
/// * **CURV**
/// <ul>Catmull-Rom splines. These are used during modeling and are currently ignored by the
/// renderer.</ul>
/// * **PTCH**
/// <ul>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.</ul>
/// * **MBAL**
/// <ul>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.</ul>
/// * **BONE**
/// <ul>Line segments representing the object's skeleton. These are converted to bones for
/// deformation during rendering.</ul>
///
/// 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<PolygonList>,
}
#[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<u32>,
}

View File

@@ -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**:
/// <ul>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.</ul>
/// * **PART**:
/// <ul>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).</ul>
/// * **SMGP**
/// <ul>The smoothing group the polygon belongs to. Shading is only interpolated within a
/// smoothing group, not across groups.</ul>
///
/// 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<PolygonTagMapping>,
}
#[binread]
#[derive(Debug)]
pub struct PolygonTagMapping {
#[br(parse_with = vx)]
pub poly: u32,
pub tag: u16,
}

View File

@@ -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<SurfaceSubTag>,
}
#[binread]
#[derive(Debug)]
pub enum SurfaceSubTag {
#[br(magic(b"COLR"))]
BaseColor(SubChunk<BaseColor>),
#[br(magic(b"DIFF"))]
BaseShadingValueDiffuse(SubChunk<BaseShadingValues>),
#[br(magic(b"LUMI"))]
BaseShadingValueLuminosity(SubChunk<BaseShadingValues>),
#[br(magic(b"SPEC"))]
BaseShadingValueSpecular(SubChunk<BaseShadingValues>),
#[br(magic(b"REFL"))]
BaseShadingValueReflectivity(SubChunk<BaseShadingValues>),
#[br(magic(b"TRAN"))]
BaseShadingValueTransmission(SubChunk<BaseShadingValues>),
#[br(magic(b"TRNL"))] // TODO
BaseShadingValueTrnl(SubChunk<BaseShadingValues>),
#[br(magic(b"GLOS"))]
SpecularGlossiness(SubChunk<BaseShadingValues>),
#[br(magic(b"SHRP"))]
DiffuseSharpness(SubChunk<BaseShadingValues>),
#[br(magic(b"BUMP"))]
BumpIntensity(SubChunk<BaseShadingValues>),
#[br(magic(b"SIDE"))]
PolygonSidedness(SubChunk<PolygonSidedness>),
#[br(magic(b"SMAN"))]
MaxSmoothingAngle(SubChunk<MaxSmoothingAngle>),
}
#[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,
}

View File

@@ -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<NullString>,
}

View File

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

View File

@@ -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**
/// <ul>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.</ul>
/// * **WGHT**
/// <ul>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.</ul>
/// * **MNVW**
/// <ul>Subpatch weight maps affect the shape of geometry created by subdivision patching.</ul>
/// * **TXUV**
/// <ul>UV texture maps have a dimension of 2.</ul>
/// * **RGB**, **RGBA**
/// <ul>Color maps, with a dimension of 3 or 4.</ul>
/// * **MORF**
/// <ul>These contain vertex displacement deltas.</ul>
/// * **SPOT**
/// <ul>These contain absolute vertex displacements (alternative vertex positions).</ul>
///
/// 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<VertexMapping>,
}
#[binread]
#[br(import(dimension: u16))]
#[derive(Debug)]
pub struct VertexMapping {
#[br(parse_with = vx)]
pub vert: u32,
#[br(count = dimension)]
pub value: Vec<f32>,
}

View File

@@ -7,3 +7,4 @@ edition = "2021"
[dependencies]
starforcelib = {path = "../starforcelib"}
lightwave = {path = "../lightwave"}

View File

@@ -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);
}