mirror of
https://github.com/Theaninova/mhlib.git
synced 2026-01-04 15:52:52 +00:00
add lightwave parser
This commit is contained in:
8
rust/Cargo.lock
generated
8
rust/Cargo.lock
generated
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
[workspace]
|
||||
|
||||
members = ["renderwarelib", "springylib", "starforcelib", "mhex"]
|
||||
members = ["renderwarelib", "springylib", "starforcelib", "mhex", "lightwave"]
|
||||
|
||||
3
rust/lightwave/README.md
Normal file
3
rust/lightwave/README.md
Normal file
@@ -0,0 +1,3 @@
|
||||
# LightWave 3D Rust Parser
|
||||
|
||||
* [LWO2 Spec](http://static.lightwave3d.com/sdk/2015/html/filefmts/lwo2.html)
|
||||
64
rust/lightwave/src/binrw_helpers.rs
Normal file
64
rust/lightwave/src/binrw_helpers.rs
Normal 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)
|
||||
}
|
||||
46
rust/lightwave/src/iff/mod.rs
Normal file
46
rust/lightwave/src/iff/mod.rs
Normal 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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
24
rust/lightwave/src/lwo2/mod.rs
Normal file
24
rust/lightwave/src/lwo2/mod.rs
Normal 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)
|
||||
})
|
||||
}
|
||||
43
rust/lightwave/src/lwo2/sub_tags/envelope_type.rs
Normal file
43
rust/lightwave/src/lwo2/sub_tags/envelope_type.rs
Normal 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,
|
||||
}
|
||||
11
rust/lightwave/src/lwo2/sub_tags/mod.rs
Normal file
11
rust/lightwave/src/lwo2/sub_tags/mod.rs
Normal 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)
|
||||
}
|
||||
11
rust/lightwave/src/lwo2/tags/bounding_box.rs
Normal file
11
rust/lightwave/src/lwo2/tags/bounding_box.rs
Normal 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],
|
||||
}
|
||||
53
rust/lightwave/src/lwo2/tags/discontinuous_vertex_mapping.rs
Normal file
53
rust/lightwave/src/lwo2/tags/discontinuous_vertex_mapping.rs
Normal 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>,
|
||||
}
|
||||
20
rust/lightwave/src/lwo2/tags/layer.rs
Normal file
20
rust/lightwave/src/lwo2/tags/layer.rs
Normal 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>,
|
||||
}
|
||||
48
rust/lightwave/src/lwo2/tags/mod.rs
Normal file
48
rust/lightwave/src/lwo2/tags/mod.rs
Normal 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>),
|
||||
}
|
||||
16
rust/lightwave/src/lwo2/tags/point_list.rs
Normal file
16
rust/lightwave/src/lwo2/tags/point_list.rs
Normal 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]>,
|
||||
}
|
||||
54
rust/lightwave/src/lwo2/tags/polygon_list.rs
Normal file
54
rust/lightwave/src/lwo2/tags/polygon_list.rs
Normal 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>,
|
||||
}
|
||||
36
rust/lightwave/src/lwo2/tags/polygon_tag_mapping.rs
Normal file
36
rust/lightwave/src/lwo2/tags/polygon_tag_mapping.rs
Normal 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,
|
||||
}
|
||||
81
rust/lightwave/src/lwo2/tags/surface_definition.rs
Normal file
81
rust/lightwave/src/lwo2/tags/surface_definition.rs
Normal 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,
|
||||
}
|
||||
11
rust/lightwave/src/lwo2/tags/tag_strings.rs
Normal file
11
rust/lightwave/src/lwo2/tags/tag_strings.rs
Normal 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>,
|
||||
}
|
||||
21
rust/lightwave/src/lwo2/tags/vertex_map_parameter.rs
Normal file
21
rust/lightwave/src/lwo2/tags/vertex_map_parameter.rs
Normal 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,
|
||||
}
|
||||
57
rust/lightwave/src/lwo2/tags/vertex_mapping.rs
Normal file
57
rust/lightwave/src/lwo2/tags/vertex_mapping.rs
Normal 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>,
|
||||
}
|
||||
@@ -7,3 +7,4 @@ edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
starforcelib = {path = "../starforcelib"}
|
||||
lightwave = {path = "../lightwave"}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user