add lightwave parser

This commit is contained in:
2023-05-09 21:11:45 +02:00
parent 48d3004c35
commit 8cf1ac2635
9 changed files with 498 additions and 21 deletions

7
rust/Cargo.lock generated
View File

@@ -242,6 +242,12 @@ dependencies = [
"weezl",
]
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "half"
version = "2.2.1"
@@ -357,6 +363,7 @@ dependencies = [
name = "mhex"
version = "0.1.0"
dependencies = [
"glob",
"lightwave",
"starforcelib",
]

View File

@@ -1,3 +1,4 @@
# LightWave 3D Rust Parser
* [LWO2 Spec](http://static.lightwave3d.com/sdk/2015/html/filefmts/lwo2.html)
* [LWO2 Spec](http://static.lightwave3d.com/sdk/2015/html/filefmts/lwo2.html)
* Progress: about 90% (?) complete, so most things should load.

View File

@@ -16,7 +16,7 @@ where
R: Read + Seek,
{
let kind: u16 = reader.read_type(endian)?;
Ok(if (kind & 0xff) != 0xff {
Ok(if kind < 0xff00 {
kind as u32
} else {
(((kind as u32) & 0xff) << 16) | (reader.read_type::<u16>(endian)? as u32)

View File

@@ -0,0 +1,146 @@
use crate::binrw_helpers::until_size_limit;
use crate::iff::SubChunk;
use crate::lwo2::tags::surface_definition::ValueEnvelope;
use binrw::{binread, NullString};
/// Describes an image or a sequence of images. Surface definitions specify images by referring to
/// CLIP chunks. The term "clip" is used to describe these because they can be numbered sequences
/// or animations as well as stills. The index identifies this clip uniquely and may be any non-zero
/// value less than 0x1000000. The filename and any image processing modifiers follow as a variable
/// list of subchunks, which are documented below in the Clip Subchunks section.
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct ImageClip {
pub index: u32,
#[br(parse_with = until_size_limit(length as u64 - 4))]
pub attributes: Vec<ImageClipSubChunk>,
}
#[binread]
#[derive(Debug)]
pub enum ImageClipSubChunk {
#[br(magic(b"STIL"))]
StillImage(SubChunk<StillImage>),
#[br(magic(b"ISEQ"))]
ImageSequence(SubChunk<ImageSequence>),
#[br(magic(b"XREF"))]
Reference(SubChunk<Reference>),
#[br(magic(b"FLAG"))]
Flag(SubChunk<Flags>),
#[br(magic(b"STCC"))]
ColorCyclingStill(SubChunk<ColorCyclingStill>),
#[br(magic(b"TIME"))]
Time(SubChunk<Time>),
#[br(magic(b"CLRS"))]
ColorSpaceRgb(SubChunk<ColorSpace>),
#[br(magic(b"CLRA"))]
ColorSpaceAlpha(SubChunk<ColorSpace>),
#[br(magic(b"FILT"))]
ImageFiltering(SubChunk<Flags>),
#[br(magic(b"DITH"))]
ImageDithering(SubChunk<Flags>),
#[br(magic(b"CONT"))]
Contrast(SubChunk<ValueEnvelope>),
#[br(magic(b"BRIT"))]
Brightness(SubChunk<ValueEnvelope>),
#[br(magic(b"SATR"))]
Saturation(SubChunk<ValueEnvelope>),
#[br(magic(b"HUE"))] // TODO: Typo? Docs say it's just "HUE"
Hue(SubChunk<ValueEnvelope>),
#[br(magic(b"GAMM"))]
GammaCorrection(SubChunk<ValueEnvelope>),
}
/// Contains the color space of the texture. If the flag is 0, then the color space is contained
/// in the following 2 bytes. That color space is defined by the LWCOLORSPACE enum. If the flag
/// is set to 1, then the file name of the color space is save as a local string.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct ColorSpace {
pub flags: u16,
pub color_space: u16,
#[br(align_after = 2)]
pub file_name: NullString,
}
/// A still image with color-cycling is a source defined by a neutral-format name and cycling
/// parameters. lo and hi are indexes into the image's color table. Within this range, the color
/// table entries are shifted over time to cycle the colors in the image. If lo is less than hi,
/// the colors cycle forward, and if hi is less than lo, they go backwards.
///
/// Except for the TIME subchunk, the subchunks after the source subchunk modify the source image
/// and are applied as filters layered on top of the source image.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct ColorCyclingStill {
pub lo: i16,
pub hi: i16,
#[br(align_after = 2)]
pub name: NullString,
}
/// Defines source times for an animated clip.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct Time {
pub start_time: f32,
pub duration: f32,
pub frame_rate: f32,
}
/// TODO: What's this?
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct Flags {
pub flag: u32,
}
/// The source is a single still image referenced by a filename in neutral path format.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct StillImage {
#[br(align_after = 2)]
pub name: NullString,
}
/// The source is a numbered sequence of still image files. Each filename contains a fixed number
/// of decimal digits that specify a frame number, along with a prefix (the part before the frame
/// number, which includes the path) and a suffix (the part after the number, typically a PC-style
/// extension that identifies the file format). The prefix and suffix are the same for all files
/// in the sequence.
///
/// The flags include bits for looping and interlace. The offset is added to the current frame
/// number to obtain the digits of the filename for the current frame. The start and end values
/// define the range of frames in the sequence.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct ImageSequence {
pub num_digits: u8,
pub flags: u8,
pub offset: i16,
pub reserved: u16,
pub start: i16,
pub end: i16,
#[br(align_after = 2)]
pub prefix: NullString,
#[br(align_after = 2)]
pub suffix: NullString,
}
/// The source is a copy, or instance, of another clip, given by the index. The string is a unique
/// name for this instance of the clip.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct Reference {
pub index: u32,
#[br(align_after = 2)]
pub string: NullString,
}

View File

@@ -1,6 +1,7 @@
use crate::iff::Chunk;
use crate::lwo2::tags::bounding_box::BoundingBox;
use crate::lwo2::tags::discontinuous_vertex_mapping::DiscontinuousVertexMappings;
use crate::lwo2::tags::image_clip::ImageClip;
use crate::lwo2::tags::layer::Layer;
use crate::lwo2::tags::point_list::PointList;
use crate::lwo2::tags::polygon_list::PolygonLists;
@@ -13,6 +14,7 @@ use binrw::binread;
pub mod bounding_box;
pub mod discontinuous_vertex_mapping;
pub mod image_clip;
pub mod layer;
pub mod point_list;
pub mod polygon_list;
@@ -45,4 +47,6 @@ pub enum Tag {
PolygonList(Chunk<PolygonLists>),
#[br(magic(b"SURF"))]
SurfaceDefinition(Chunk<SurfaceDefinition>),
#[br(magic(b"CLIP"))]
ImageClip(Chunk<ImageClip>),
}

View File

@@ -3,10 +3,10 @@ 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.
///
/// 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)]

View File

@@ -23,35 +23,59 @@ pub struct SurfaceDefinition {
#[derive(Debug)]
pub enum SurfaceSubTag {
#[br(magic(b"COLR"))]
BaseColor(SubChunk<BaseColor>),
BaseColor(SubChunk<VectorEnvelope>),
#[br(magic(b"DIFF"))]
BaseShadingValueDiffuse(SubChunk<BaseShadingValues>),
BaseShadingValueDiffuse(SubChunk<ValueEnvelope>),
#[br(magic(b"LUMI"))]
BaseShadingValueLuminosity(SubChunk<BaseShadingValues>),
BaseShadingValueLuminosity(SubChunk<ValueEnvelope>),
#[br(magic(b"SPEC"))]
BaseShadingValueSpecular(SubChunk<BaseShadingValues>),
BaseShadingValueSpecular(SubChunk<ValueEnvelope>),
#[br(magic(b"REFL"))]
BaseShadingValueReflectivity(SubChunk<BaseShadingValues>),
BaseShadingValueReflectivity(SubChunk<ValueEnvelope>),
#[br(magic(b"TRAN"))]
BaseShadingValueTransmission(SubChunk<BaseShadingValues>),
#[br(magic(b"TRNL"))] // TODO
BaseShadingValueTrnl(SubChunk<BaseShadingValues>),
BaseShadingValueTransparency(SubChunk<ValueEnvelope>),
#[br(magic(b"TRNL"))]
BaseShadingValueTranslucency(SubChunk<ValueEnvelope>),
#[br(magic(b"GLOS"))]
SpecularGlossiness(SubChunk<BaseShadingValues>),
SpecularGlossiness(SubChunk<ValueEnvelope>),
#[br(magic(b"SHRP"))]
DiffuseSharpness(SubChunk<BaseShadingValues>),
DiffuseSharpness(SubChunk<ValueEnvelope>),
#[br(magic(b"BUMP"))]
BumpIntensity(SubChunk<BaseShadingValues>),
BumpIntensity(SubChunk<ValueEnvelope>),
#[br(magic(b"SIDE"))]
PolygonSidedness(SubChunk<PolygonSidedness>),
#[br(magic(b"SMAN"))]
MaxSmoothingAngle(SubChunk<MaxSmoothingAngle>),
#[br(magic(b"BLOK"))]
Blocks(SubChunk<SurfaceBlocks>),
#[br(magic(b"RFOP"))]
ReflectionOptions(SubChunk<ReflectionOptions>),
#[br(magic(b"RIMG"))]
ReflectionMapImage(SubChunk<VxReference>),
#[br(magic(b"TBLR"))]
RefractionBlurring(SubChunk<ValueEnvelope>),
#[br(magic(b"CLRH"))]
ColorHighlights(SubChunk<ValueEnvelope>),
#[br(magic(b"CLRF"))]
ColorFilter(SubChunk<ValueEnvelope>),
#[br(magic(b"ADTR"))]
AdditiveTransparency(SubChunk<ValueEnvelope>),
}
#[binread]
#[br(repr = u16, import(_length: u32))]
#[derive(Debug)]
pub enum ReflectionOptions {
BackdropOnly = 0,
RaytracingAndBackdrop = 1,
SphericalMap = 2,
RaytracingAndSphericalMap = 3,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct BaseColor {
pub struct VectorEnvelope {
pub base_color: [f32; 3],
#[br(parse_with = vx)]
pub envelope: u32,
@@ -60,7 +84,7 @@ pub struct BaseColor {
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct BaseShadingValues {
pub struct ValueEnvelope {
pub value: f32,
#[br(parse_with = vx)]
pub envelope: u32,
@@ -79,3 +103,279 @@ pub struct PolygonSidedness {
pub struct MaxSmoothingAngle {
pub max_smoothing_angle: f32,
}
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct SurfaceBlocks {
#[br(temp)]
start_pos: PosValue<()>,
pub header: SurfaceBlockHeader,
#[br(temp)]
end_pos: PosValue<()>,
#[br(parse_with = until_size_limit(length as u64 - (end_pos.pos - start_pos.pos)))]
pub attributes: Vec<SurfaceBlockSubChunk>,
}
#[binread]
#[derive(Debug)]
pub enum SurfaceBlockHeader {
#[br(magic(b"IMAP"))]
ImageMapTexture(SubChunk<SurfaceBlockHeaders>),
#[br(magic(b"PROC"))]
ProceduralTexture(SubChunk<SurfaceBlockHeaders>),
#[br(magic(b"GRAD"))]
GradientTexture(SubChunk<SurfaceBlockHeaders>),
#[br(magic(b"SHDR"))]
ShaderPlugin(SubChunk<SurfaceBlockHeaders>),
}
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct SurfaceBlockHeaders {
#[br(pad_before = 2, parse_with = until_size_limit(length as u64))]
pub block_attributes: Vec<SurfaceBlockSubChunk>,
}
#[binread]
#[derive(Debug)]
pub enum SurfaceBlockSubChunk {
#[br(magic(b"CHAN"))]
Channel(SubChunk<Channel>),
#[br(magic(b"ENAB"))]
EnabledState(SubChunk<EnableState>),
#[br(magic(b"OPAC"))]
Opacity(SubChunk<Opacity>),
#[br(magic(b"AXIS"))]
DisplacementAxis(SubChunk<DisplacementAxis>),
#[br(magic(b"TMAP"))]
TextureMapping(SubChunk<TextureMapping>),
#[br(magic(b"NEGA"))]
Negative(SubChunk<EnableState>),
#[br(magic(b"PROJ"))]
ProjectionMode(SubChunk<ProjectionMode>),
#[br(magic(b"IMAG"))]
ImageMap(SubChunk<VxReference>),
#[br(magic(b"WRAP"))]
ImageWrapOptions(SubChunk<ImageWrapOptions>),
#[br(magic(b"WRPW"))]
ImageWrapAmountWidth(SubChunk<ImageWrapAmount>),
#[br(magic(b"WRPH"))]
ImageWrapAmountHeight(SubChunk<ImageWrapAmount>),
#[br(magic(b"VMAP"))]
UvVertexMap(SubChunk<UvMap>),
#[br(magic(b"AAST"))]
AntialiasingStrength(SubChunk<AntialiasingStrength>),
#[br(magic(b"PIXB"))]
PixelBlending(SubChunk<PixelBlending>),
#[br(magic(b"TAMP"))]
TextureAmplitude(SubChunk<ValueEnvelope>),
}
/// Pixel blending enlarges the sample filter when it would otherwise be smaller than a single
/// image map pixel. If the low-order flag bit is set, then pixel blending is enabled.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct PixelBlending {
pub flags: u16,
}
/// The low bit of the flags word is an enable flag for texture antialiasing. The antialiasing
/// strength is proportional to the width of the sample filter, so larger values sample a larger
/// area of the image.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct AntialiasingStrength {
pub flags: u16,
pub strength: f32,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct UvMap {
#[br(align_after = 2)]
pub txuv_map_name: NullString,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct ImageWrapAmount {
pub cycles: f32,
#[br(parse_with = vx)]
pub envelope: u32,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct ImageWrapOptions {
pub width_wrap: ImageWrapType,
pub height_wrap: ImageWrapType,
}
#[binread]
#[br(repr = u16)]
#[derive(Debug)]
pub enum ImageWrapType {
Reset = 0,
Repeat = 1,
Mirror = 2,
Edge = 3,
}
#[binread]
#[br(repr = u16, import(_length: u32))]
#[derive(Debug)]
pub enum ProjectionMode {
Planar = 0,
Cylindrical = 1,
Spherical = 2,
Cubic = 3,
FrontProjection = 4,
UV = 5,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct VxReference {
#[br(parse_with = vx)]
pub texture_image: u32,
}
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct TextureMapping {
#[br(parse_with = until_size_limit(length as u64))]
pub attributes: Vec<TextureMappingSubChunk>,
}
#[binread]
#[derive(Debug)]
pub enum TextureMappingSubChunk {
#[br(magic(b"CNTR"))]
Center(SubChunk<VectorEnvelope>),
#[br(magic(b"SIZE"))]
Size(SubChunk<VectorEnvelope>),
#[br(magic(b"ROTA"))]
Rotation(SubChunk<VectorEnvelope>),
#[br(magic(b"OREF"))]
ReferenceObject(SubChunk<ReferenceObject>),
#[br(magic(b"FALL"))]
Falloff(SubChunk<Falloff>),
#[br(magic(b"CSYS"))]
CoordinateSystem(SubChunk<CoordinateSystem>),
}
#[binread]
#[br(repr = u16, import(_length: u32))]
#[derive(Debug)]
pub enum CoordinateSystem {
ObjectCoordinates = 0,
WorldCoordinates = 1,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct ReferenceObject {
#[br(align_after = 2)]
pub object_name: NullString,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct Falloff {
pub kind: FalloffType,
pub vector: [f32; 3],
#[br(parse_with = vx)]
pub envelope: u32,
}
#[binread]
#[br(repr = u16)]
#[derive(Debug)]
pub enum FalloffType {
Cubic = 0,
Spherical = 1,
LinearX = 2,
LinearY = 3,
LinearZ = 4,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct DisplacementAxis {
pub displacement_axis: u16,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct Opacity {
pub kind: OpacityType,
pub opacity: f32,
#[br(parse_with = vx)]
pub envelope: u32,
}
#[binread]
#[br(repr = u16)]
#[derive(Debug)]
pub enum OpacityType {
Normal = 0,
Subtractive = 1,
Difference = 2,
Multiply = 3,
Divide = 4,
Alpha = 5,
TextureDisplacement = 6,
Additive = 7,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct EnableState {
pub enable: u16,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct Channel {
pub texture_channel: TextureChannel,
}
#[binread]
#[derive(Debug)]
pub enum TextureChannel {
#[br(magic(b"COLR"))]
Color,
#[br(magic(b"DIFF"))]
Diffuse,
#[br(magic(b"LUMI"))]
Luminosity,
#[br(magic(b"SPEC"))]
Specular,
#[br(magic(b"GLOS"))]
Glossy,
#[br(magic(b"REFL"))]
Reflectivity,
#[br(magic(b"TRAN"))]
Transparency,
#[br(magic(b"RIND"))]
RefractiveIndex,
#[br(magic(b"TRNL"))]
Translucency,
#[br(magic(b"BUMP"))]
Bump,
}

View File

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

View File

@@ -1,6 +1,24 @@
use glob::glob;
use lightwave::LightWaveObject;
fn main() {
let obj = LightWaveObject::read_file("E:\\Games\\Moorhuhn Kart 3\\extract\\D\\Moorhuhnkart\\3dobjects_tracks\\track04_robinhood\\colreset.lwo").unwrap();
println!("{:#?}", obj);
let mut successful = 0;
let mut failed = 0;
for entry in glob("E:/Games/Moorhuhn Kart 3/extract/**/*.lwo").unwrap() {
let path = entry.unwrap();
println!("{:?}", path.display());
match LightWaveObject::read_file(path) {
Ok(_) => {
successful += 1;
println!("...Ok")
}
Err(err) => {
failed += 1;
eprintln!("{:?}", err)
}
}
}
println!("Successful: {}\nFailed: {}", successful, failed);
}