mirror of
https://github.com/Theaninova/mhlib.git
synced 2026-01-22 18:02:41 +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"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lightwave"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"binrw",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lock_api"
|
name = "lock_api"
|
||||||
version = "0.4.9"
|
version = "0.4.9"
|
||||||
@@ -350,6 +357,7 @@ dependencies = [
|
|||||||
name = "mhex"
|
name = "mhex"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"lightwave",
|
||||||
"starforcelib",
|
"starforcelib",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
[workspace]
|
[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 {
|
use crate::lwo2::tags::Tag;
|
||||||
left + right
|
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)]
|
impl LightWaveObject {
|
||||||
mod tests {
|
pub fn read_file<P: AsRef<Path>>(path: P) -> std::io::Result<LightWaveObject> {
|
||||||
use super::*;
|
let mut reader = File::open(path)?;
|
||||||
|
Self::read(&mut reader)
|
||||||
|
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err))
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
pub fn read<R>(reader: &mut R) -> BinResult<LightWaveObject>
|
||||||
fn it_works() {
|
where
|
||||||
let result = add(2, 2);
|
R: Read + Seek,
|
||||||
assert_eq!(result, 4);
|
{
|
||||||
|
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]
|
[dependencies]
|
||||||
starforcelib = {path = "../starforcelib"}
|
starforcelib = {path = "../starforcelib"}
|
||||||
|
lightwave = {path = "../lightwave"}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use starforcelib::sarc::SarcArchive;
|
use lightwave::LightWaveObject;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let path = "E:\\Games\\Moorhuhn Kart 3\\data.sar";
|
let obj = LightWaveObject::read_file("E:\\Games\\Moorhuhn Kart 3\\extract\\D\\Moorhuhnkart\\3dobjects_tracks\\track04_robinhood\\colreset.lwo").unwrap();
|
||||||
SarcArchive::extract_all(path).unwrap();
|
println!("{:#?}", obj);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user