This commit is contained in:
2023-05-10 17:04:23 +02:00
parent 4b6c4e1af8
commit 25787c8b65
47 changed files with 234 additions and 2348 deletions

124
rust/Cargo.lock generated
View File

@@ -242,12 +242,82 @@ dependencies = [
"weezl",
]
[[package]]
name = "glam"
version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e4afd9ad95555081e109fe1d21f2a30c691b5f0919c67dfa690a2e1eb6bd51c"
[[package]]
name = "glob"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]]
name = "godot"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#c0935ac122dd94983f0f790cd07d6c7629a7429e"
dependencies = [
"godot-core",
"godot-macros",
]
[[package]]
name = "godot-bindings"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#c0935ac122dd94983f0f790cd07d6c7629a7429e"
dependencies = [
"godot4-prebuilt",
]
[[package]]
name = "godot-codegen"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#c0935ac122dd94983f0f790cd07d6c7629a7429e"
dependencies = [
"godot-bindings",
"heck",
"nanoserde",
"proc-macro2",
"quote",
]
[[package]]
name = "godot-core"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#c0935ac122dd94983f0f790cd07d6c7629a7429e"
dependencies = [
"glam",
"godot-codegen",
"godot-ffi",
]
[[package]]
name = "godot-ffi"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#c0935ac122dd94983f0f790cd07d6c7629a7429e"
dependencies = [
"godot-bindings",
"godot-codegen",
"paste",
]
[[package]]
name = "godot-macros"
version = "0.1.0"
source = "git+https://github.com/godot-rust/gdext?branch=master#c0935ac122dd94983f0f790cd07d6c7629a7429e"
dependencies = [
"proc-macro2",
"quote",
"venial",
]
[[package]]
name = "godot4-prebuilt"
version = "0.0.0"
source = "git+https://github.com/godot-rust/godot4-prebuilt?branch=4.0.1#f9e8cfec0ec565201801b02160ef836800746617"
[[package]]
name = "half"
version = "2.2.1"
@@ -257,6 +327,12 @@ dependencies = [
"crunchy",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
@@ -325,8 +401,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b00cc1c228a6782d0f076e7b232802e0c5689d41bb5df366f2a6b6621cfdfe1"
[[package]]
name = "lightwave"
name = "lightwave-3d"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9e57b794415e79e3446e057c2c2cc9922c7a83261b2ac6be00b15191e9ed07e"
dependencies = [
"binrw",
]
@@ -364,7 +442,18 @@ name = "mhex"
version = "0.1.0"
dependencies = [
"glob",
"lightwave",
"lightwave-3d",
"starforcelib",
]
[[package]]
name = "mhgd"
version = "0.1.0"
dependencies = [
"godot",
"itertools",
"lightwave-3d",
"springylib",
"starforcelib",
]
@@ -396,6 +485,21 @@ dependencies = [
"getrandom",
]
[[package]]
name = "nanoserde"
version = "0.1.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "755e7965536bc54d7c9fba2df5ada5bf835b0443fd613f0a53fa199a301839d3"
dependencies = [
"nanoserde-derive",
]
[[package]]
name = "nanoserde-derive"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed7a94da6c6181c35d043fc61c43ac96d3a5d739e7b8027f77650ba41504d6ab"
[[package]]
name = "num-integer"
version = "0.1.45"
@@ -448,6 +552,12 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "paste"
version = "1.0.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
[[package]]
name = "pin-project"
version = "1.0.12"
@@ -671,6 +781,16 @@ version = "1.0.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5464a87b239f13a63a501f2701565754bae92d243d4bb7eb12f6d57d2269bf4"
[[package]]
name = "venial"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "61584a325b16f97b5b25fcc852eb9550843a251057a5e3e5992d2376f3df4bb2"
dependencies = [
"proc-macro2",
"quote",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

View File

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

View File

@@ -1,9 +0,0 @@
[package]
name = "lightwave"
version = "1.0.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
binrw = "0.11.1"

View File

@@ -1,175 +0,0 @@
# LightWave 3D Rust Parser
Complete LWO2 parser for Rust.
Basic Usage
```rust
use lightwave::LightWaveObject;
fn main() {
LightWaveObject::read_file("path/to/file.lwo");
// or
LightWaveObject::read(Cursor::new(vec![0x00, 0x01, ...]))
}
```
## LightWave Object (LWO2)
Fully feature complete following the [LWO2 Spec](http://static.lightwave3d.com/sdk/2015/html/filefmts/lwo2.html).
| Chunk | Tag | Status |
|--------------------------------------------|--------|--------|
| Layer | `LAYR` | ✅ |
| Point List | `PNTS` | ✅ |
| Vertex Mapping | `VMAP` | ✅ |
| Polygon List | `POLS` | ✅ |
| Tag Strings | `TAGS` | ✅ |
| Polygon Tag Mapping | `PTAG` | ✅ |
| Discontinuous Vertex Mapping | `VMAD` | ✅ |
| Vertex Map Parameter | `VMPA` | ✅ |
| [Envelope Definition](#envelope-subchunks) | `ENVL` | ✅ |
| [Image (-Sequence)](#clip-subchunks) | `CLIP` | ✅ |
| [Surface Definition](#surface-subchunks) | `SURF` | ✅ |
| Bounding Box | `BBOX` | ✅ |
| Description Line | `DESC` | ✅ |
| Commentary Text | `TEXT` | ✅ |
| Thumbnail Icon Image | `ICON` | ✅ |
### Envelope Subchunks
| Chunk | Tag | Status |
|--------------------------|--------|--------|
| Envelope Type | `TYPE` | ✅ |
| Pre-Behavior | `PRE` | ✅ |
| Post-Behavior | `POST` | ✅ |
| Keyframe Time and Value | `KEY` | ✅ |
| Interval Interpolation | `SPAN` | ✅ |
| Plugin Channel Modifiers | `CHAN` | ✅ |
| Channel Name | `NAME` | ✅ |
### Clip Subchunks
| Chunk | Tag | Status |
|----------------------|--------|--------|
| Still Image | `STIL` | ✅ |
| Image Sequence | `ISEQ` | ✅ |
| Plugin Animation | `ANIM` | ✅ |
| Reference (Clone) | `XREF` | ✅ |
| Flag (Undocumented) | `FLAG` | ⚠️ |
| Color-cycling Still | `STCC` | ✅ |
| Time | `TIME` | ✅ |
| Color Space RGB | `CLRS` | ✅ |
| Color Space Alpha | `CLRA` | ✅ |
| Image Filtering | `FILT` | ✅ |
| Image Dithering | `DITH` | ✅ |
| Contrast | `CONT` | ✅ |
| Brightness | `BRIT` | ✅ |
| Saturation | `SATR` | ✅ |
| Hue | `HUE` | ✅ |
| Gamma Correction | `GAMM` | ✅ |
| Negative | `NEGA` | ✅ |
| Plugin Image Filters | `IFLT` | ✅ |
| Plugin Pixel Filters | `PFLT` | ✅ |
### Surface Subchunks
### Basic Surface Parameters
| Chunk | Tag | Status |
|-----------------------------------|----------------------------------------------------------|--------|
| Base Color | `COLR` | ✅ |
| Base Shading Values | `DIFF`<br>`LUMI`<br>`SPEC`<br>`REFL`<br>`TRAN`<br>`TRNL` | ✅ |
| Specular Glossiness | `GLOS` | ✅ |
| Diffuse Sharpness | `SHRP` | ✅ |
| Bump Intensity | `BUMP` | ✅ |
| Polygon Sidedness | `SIDE` | ✅ |
| Max Smoothing Angle | `SMAN` | ✅ |
| Reflection Options | `RFOP` | ✅ |
| Reflection Map Image | `RIMG` | ✅ |
| Reflection Map Image Seam Angle | `RSAN` | ✅ |
| Reflection Blurring | `RBLR` | ✅ |
| Refractive Index | `RIND` | ✅ |
| Transparency Options | `TROP` | ✅ |
| Refraction Map Image | `TIMG` | ✅ |
| Refraction Blurring | `TBLR` | ✅ |
| Color Highlights | `CLRH` | ✅ |
| Color Filter | `CLRF` | ✅ |
| Additive Transparency | `ADRT` | ✅ |
| Glow Effect | `GLOW` | ✅ |
| Render Outlines | `LINE` | ✅ |
| Alpha Mode | `ALPH` | ✅ |
| Vertex Color Map | `VCOL` | ✅ |
| [Surface Blocks](#surface-blocks) | `BLOK` | 🚧 |
### Surface Blocks
Ordinal Strings:
* ✅ [Image Texture Map](#image-texture-map) `IMAP`
* ✅ [Procedural Texture](#procedural-texture) `PROC`
* ✅ [Gradient Texture](#gradient-texture) `GRAD`
* ✅ [Shader Plugin](#shaders) `SHDR`
#### Shared
| Chunk | Tag | Status |
|-------------------------|--------|--------|
| Texture Channel | `CHAN` | ✅ |
| Enable State | `ENAB` | ✅ |
| Opacity | `OPAC` | ✅ |
| Displacement Axis | `AXIS` | ✅ |
| Negative (Undocumented) | `NEGA` | ⚠️ |
#### Texture Mapping
| Chunk | Tag | Status |
|---------------------|----------------------------|--------|
| Positioning | `CNTR`<br>`SIZE`<br>`ROTA` | ✅ |
| Reference Object | `OREF` | ✅ |
| Falloff | `FALL` | ✅ |
| Coordinate System | `CSYS` | ✅ |
#### Image Texture Map
| Chunk | Tag | Status |
|-------------------------------------|------------------|--------|
| [Texture Mapping](#texture-mapping) | `TMAP` | ✅ |
| Projection Mode | `PROJ` | ✅ |
| Major Axis | `AXIS` | ✅ |
| Image Map | `IMAG` | ✅ |
| Image Wrap Options | `WRAP` | ✅ |
| Image Wrap Amount | `WRPW`<br>`WRPH` | ✅ |
| UV Vertex Map | `VMAP` | ✅ |
| Antialiasing Strength | `AAST` | ✅ |
| Pixel Blending | `PIXB` | ✅ |
| Sticky Projection | `STCK` | ✅ |
| Texture Ampliture | `TAMP` | ✅ |
#### Procedural Texture
| Chunk | Tag | Status |
|--------------------------|--------|--------|
| Axis | `AXIS` | ✅ |
| Basic Value | `VALU` | ✅ |
| Algorithm and Parameters | `FUNC` | ✅ |
#### Gradient Texture
| Chunk | Tag | Status |
|----------------|-------------------|--------|
| Parameter Name | `PNAM` | ✅ |
| Item Name | `INAM` | ✅ |
| Gradient Range | `GRST`<br>`GREN` | ✅ |
| Repeat Mode | `GRPT` | ✅ |
| Key Values | `FKEY` | ✅ |
| Key Parameters | `IKEY` | ✅ |
#### Shaders
| Chunk | Tag | Status |
|------------------|--------|--------|
| Shader Algorithm | `FUNC` | ✅ |

View File

@@ -1,69 +0,0 @@
use crate::lwo2::vx;
use binrw::{binread, BinRead, BinResult, Endian};
use std::io::{Read, Seek};
use std::iter::from_fn;
#[binread]
#[br(assert(false, "Not implemented yet"))]
#[derive(Debug)]
pub struct BinReadTodo();
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

@@ -1,46 +0,0 @@
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,60 +0,0 @@
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>,
}
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))
}
pub fn read<R>(reader: &mut R) -> BinResult<LightWaveObject>
where
R: Read + Seek,
{
BinRead::read(reader)
}
}

View File

@@ -1,24 +0,0 @@
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 < 0xff00 {
kind as u32
} else {
(((kind as u32) & 0xff) << 16) | (reader.read_type::<u16>(endian)? as u32)
})
}

View File

@@ -1,68 +0,0 @@
use crate::iff::SubChunk;
use crate::lwo2::sub_tags::Name;
use binrw::binread;
#[binread]
#[derive(Debug)]
pub enum GradientTextureSubChunk {
#[br(magic(b"PNAM"))]
ParameterName(SubChunk<Name>),
#[br(magic(b"INAM"))]
ItemName(SubChunk<Name>),
#[br(magic(b"GRST"))]
GradientRangeStart(SubChunk<GradientRange>),
#[br(magic(b"GREN"))]
GradientRangeEnd(SubChunk<GradientRange>),
#[br(magic(b"GRPT"))]
RepeatMode(SubChunk<RepeatMode>),
#[br(magic(b"FKEY"))]
KeyValues(SubChunk<KeyValues>),
#[br(magic(b"IKEY"))]
KeyParameters(SubChunk<KeyParameters>),
}
/// The repeat mode. This is currently undefined.
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct KeyParameters {
#[br(count = length / 2)]
pub repeat_mode: Vec<u16>,
}
/// The transfer function is defined by an array of keys, each with an input value and an RGBA
/// output vector. Given an input value, the gradient can be evaluated by selecting the keys whose
/// positions bracket the value and interpolating between their outputs. If the input value is lower
/// than the first key or higher than the last key, the gradient value is the value of the closest
/// key.
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct KeyValues {
#[br(count = length / 18)]
pub key_values: Vec<KeyValue>,
}
#[binread]
#[derive(Debug)]
pub struct KeyValue {
pub input: f32,
pub output: [f32; 4],
}
/// The start and end of the input range. These values only affect the display of the gradient
/// in the user interface. They don't affect rendering.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct GradientRange {
pub name: f32,
}
/// The repeat mode. This is currently undefined.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct RepeatMode {
pub repeat_mode: u16,
}

View File

@@ -1,121 +0,0 @@
use crate::iff::SubChunk;
use crate::lwo2::sub_tags::blocks::texture_mapping::TextureMapping;
use crate::lwo2::sub_tags::{ValueEnvelope, VxReference};
use crate::lwo2::vx;
use binrw::{binread, NullString};
#[binread]
#[derive(Debug)]
pub enum SurfaceBlockImageTextureSubChunk {
#[br(magic(b"TMAP"))]
TextureMapping(SubChunk<TextureMapping>),
#[br(magic(b"PROJ"))]
ProjectionMode(SubChunk<ProjectionMode>),
#[br(magic(b"AXIS"))]
MajorAxis(SubChunk<MajorAxis>),
#[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"STICK"))]
StickyProjection(SubChunk<ValueEnvelope>),
#[br(magic(b"TAMP"))]
TextureAmplitude(SubChunk<ValueEnvelope>),
}
/// The major axis used for planar, cylindrical and spherical projections. The value is 0, 1 or 2
/// for the X, Y or Z axis.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct MajorAxis {
pub texture_axis: u16,
}
/// 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,
}
/// For UV projection, which depends on texture coordinates at each vertex, this selects the name of
/// the TXUV vertex map that contains those coordinates.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct UvMap {
#[br(align_after = 2)]
pub txuv_map_name: NullString,
}
/// For cylindrical and spherical projections, these parameters control how many times the image
/// repeats over each full interval.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct ImageWrapAmount {
pub cycles: f32,
#[br(parse_with = vx)]
pub envelope: u32,
}
/// Specifies how the color of the texture is derived for areas outside the image.
#[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 {
/// Areas outside the image are assumed to be black. The ultimate effect of this depends on
/// the opacity settings. For an additive texture layer on the color channel, the final color
/// will come from the preceding layers or from the base color of the surface.
Reset = 0,
/// The image is repeated or tiled.
Repeat = 1,
/// Like repeat, but alternate tiles are mirror-reversed.
Mirror = 2,
/// The color is taken from the image's nearest edge pixel.
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,
}

View File

@@ -1,140 +0,0 @@
use crate::binrw_helpers::until_size_limit;
use crate::iff::SubChunk;
use crate::lwo2::sub_tags::blocks::gradient_texture::GradientTextureSubChunk;
use crate::lwo2::sub_tags::blocks::image_texture::SurfaceBlockImageTextureSubChunk;
use crate::lwo2::sub_tags::blocks::procedural_texture::ProceduralTextureSubChunk;
use crate::lwo2::sub_tags::EnableState;
use crate::lwo2::vx;
use binrw::{binread, NullString};
pub mod gradient_texture;
pub mod image_texture;
pub mod procedural_texture;
pub mod texture_mapping;
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub enum SurfaceBlocks {
#[br(magic(b"IMAP"))]
ImageMapTexture {
header: SubChunk<SurfaceBlockHeader>,
#[br(parse_with = until_size_limit(length as u64 - (header.length as u64 + 2 + 4)))]
attributes: Vec<SurfaceBlockImageTextureSubChunk>,
},
#[br(magic(b"PROC"))]
ProceduralTexture {
header: SubChunk<SurfaceBlockHeader>,
#[br(parse_with = until_size_limit(length as u64 - (header.length as u64 + 2 + 4)))]
attributes: Vec<ProceduralTextureSubChunk>,
},
#[br(magic(b"GRAD"))]
GradientTexture {
header: SubChunk<SurfaceBlockHeader>,
#[br(parse_with = until_size_limit(length as u64 - (header.length as u64 + 2 + 4)))]
attributes: Vec<GradientTextureSubChunk>,
},
#[br(magic(b"SHDR"))]
ShaderPlugin {
header: SubChunk<SurfaceBlockHeader>,
#[br(magic(b"FUNC"))]
algorithm: SubChunk<Algorithm>,
},
}
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct Algorithm {
#[br(align_after = 2)]
pub algorithm_name: NullString,
#[br(count = length - (algorithm_name.len() as u32 + 1))]
pub data: Vec<u8>,
}
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct SurfaceBlockHeader {
#[br(pad_before = 2)]
#[br(parse_with = until_size_limit(length as u64 - 4))]
pub block_attributes: Vec<SurfaceBlockHeaderSubChunk>,
}
#[binread]
#[derive(Debug)]
pub enum SurfaceBlockHeaderSubChunk {
#[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"NEGA"))]
Negative(SubChunk<EnableState>),
}
#[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 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

@@ -1,32 +0,0 @@
use crate::iff::SubChunk;
use crate::lwo2::sub_tags::blocks::Algorithm;
use binrw::binread;
#[binread]
#[derive(Debug)]
pub enum ProceduralTextureSubChunk {
#[br(magic(b"AXIS"))]
Axis(SubChunk<Axis>),
#[br(magic(b"VALU"))]
BasicValue(SubChunk<BasicValue>),
#[br(magic(b"FUNC"))]
AlgorithmAndParameters(SubChunk<Algorithm>),
}
/// Procedurals are often modulations between the current channel value and another value, given
/// here. This may be a scalar or a vector.
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct BasicValue {
#[br(count = length / 4)]
pub value: Vec<f32>,
}
/// If the procedural has an axis, it may be defined with this chunk using a value of 0, 1 or 2.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct Axis {
pub axis: u16,
}

View File

@@ -1,67 +0,0 @@
use crate::binrw_helpers::until_size_limit;
use crate::iff::SubChunk;
use crate::lwo2::sub_tags::VectorEnvelope;
use crate::lwo2::vx;
use binrw::{binread, NullString};
#[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,
}

View File

@@ -1,47 +0,0 @@
use crate::lwo2::vx;
use binrw::{binread, NullString};
pub mod blocks;
pub mod plugin;
pub mod surface_parameters;
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct VectorEnvelope {
pub base_color: [f32; 3],
#[br(parse_with = vx)]
pub envelope: u32,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct Name {
#[br(align_after = 2)]
pub name: NullString,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct ValueEnvelope {
pub value: f32,
#[br(parse_with = vx)]
pub envelope: u32,
}
#[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 EnableState {
pub enable: u16,
}

View File

@@ -1,16 +0,0 @@
use binrw::{binread, NullString, PosValue};
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct PluginServerNameAndData {
#[br(temp)]
start_pos: PosValue<()>,
#[br(align_after = 2)]
pub server_name: NullString,
pub flags: u16,
#[br(temp)]
end_pos: PosValue<()>,
#[br(count = length as u64 - (end_pos.pos - start_pos.pos))]
pub parameters: Vec<u8>,
}

View File

@@ -1,177 +0,0 @@
use crate::iff::SubChunk;
use crate::lwo2::sub_tags::blocks::SurfaceBlocks;
use crate::lwo2::sub_tags::{ValueEnvelope, VectorEnvelope, VxReference};
use crate::lwo2::vx;
use binrw::{binread, NullString};
#[binread]
#[derive(Debug)]
pub enum SurfaceParameterSubChunk {
#[br(magic(b"COLR"))]
BaseColor(SubChunk<VectorEnvelope>),
#[br(magic(b"DIFF"))]
BaseShadingValueDiffuse(SubChunk<ValueEnvelope>),
#[br(magic(b"LUMI"))]
BaseShadingValueLuminosity(SubChunk<ValueEnvelope>),
#[br(magic(b"SPEC"))]
BaseShadingValueSpecular(SubChunk<ValueEnvelope>),
#[br(magic(b"REFL"))]
BaseShadingValueReflectivity(SubChunk<ValueEnvelope>),
#[br(magic(b"TRAN"))]
BaseShadingValueTransparency(SubChunk<ValueEnvelope>),
#[br(magic(b"TRNL"))]
BaseShadingValueTranslucency(SubChunk<ValueEnvelope>),
#[br(magic(b"GLOS"))]
SpecularGlossiness(SubChunk<ValueEnvelope>),
#[br(magic(b"SHRP"))]
DiffuseSharpness(SubChunk<ValueEnvelope>),
#[br(magic(b"BUMP"))]
BumpIntensity(SubChunk<ValueEnvelope>),
#[br(magic(b"SIDE"))]
PolygonSidedness(SubChunk<PolygonSidedness>),
#[br(magic(b"SMAN"))]
MaxSmoothingAngle(SubChunk<MaxSmoothingAngle>),
#[br(magic(b"RFOP"))]
ReflectionOptions(SubChunk<ReflectionOptions>),
#[br(magic(b"RIMG"))]
ReflectionMapImage(SubChunk<VxReference>),
#[br(magic(b"RSAN"))]
ReflectionMapSeamAngle(SubChunk<VectorEnvelope>),
#[br(magic(b"RBLR"))]
ReflectionBlurring(SubChunk<ValueEnvelope>),
#[br(magic(b"RIND"))]
RefractiveIndex(SubChunk<ValueEnvelope>),
#[br(magic(b"TROP"))]
TransparencyOptions(SubChunk<ReflectionOptions>),
#[br(magic(b"TIMG"))]
RefractionMapImage(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>),
#[br(magic(b"GLOW"))]
GlowEffect(SubChunk<GlowEffect>),
#[br(magic(b"LINE"))]
RenderOutlines(SubChunk<RenderOutlines>),
#[br(magic(b"ALPH"))]
AlphaMode(SubChunk<AlphaMode>),
#[br(magic(b"VCOL"))]
VertexColorMap(SubChunk<VertexColorMap>),
#[br(magic(b"BLOK"))]
Blocks(SubChunk<SurfaceBlocks>),
}
/// The vertex color map subchunk identifies an RGB or RGBA VMAP that will be used to color the surface.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct VertexColorMap {
pub intensity: f32,
#[br(parse_with = vx)]
pub envelope: u32,
pub vmap_type: [u8; 4],
#[br(align_after = 2)]
pub name: NullString,
}
/// The alpha mode defines the alpha channel output options for the surface.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct AlphaMode {
pub mode: AlphaModeMode,
pub value: f32,
}
#[binread]
#[br(repr = u16)]
#[derive(Debug)]
pub enum AlphaModeMode {
/// The surface has no effect on the alpha channel when rendered.
UnaffectedBySurface = 0,
/// The alpha channel will be written with the constant value following the mode in the subchunk.
ConstantValue = 1,
/// The alpha value is derived from surface opacity, which is the default if the ALPH chunk is missing.
SurfaceOpacity = 2,
/// The alpha value comes from the shadow density.
ShadowDensity = 3,
}
/// The line effect draws the surface as a wireframe of the polygon edges. Currently the only flag
/// defined is an enable switch in the low bit. The size is the thickness of the lines in pixels,
/// and the color, if not given, is the base color of the surface. Note that you may encounter
/// LINE subchunks with no color information (these will have a subchunk length of 8 bytes) and
/// possibly without size information (subchunk length 2).
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct RenderOutlines {
pub flags: u16,
#[br(if(length > 2))]
pub size: f32,
#[br(if(length > 2))]
#[br(parse_with = vx)]
pub size_envelope: u32,
#[br(if(length > 8))]
pub color: [f32; 3],
#[br(if(length > 8))]
#[br(parse_with = vx)]
pub color_envelope: u32,
}
/// The glow effect causes a surface to spread and affect neighboring areas of the image. The type
/// can be 0 for Hastings glow, and 1 for image convolution. The size and intensity define how large
/// and how strong the effect is.
///
/// You may also encounter glow information written in a GVAL subchunk containing only the intensity
/// and its envelope (the subchunk length is 6).
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct GlowEffect {
pub kind: GlowType,
pub intensity: f32,
#[br(parse_with = vx)]
pub intensity_envelope: u32,
#[br(if(length > 6))]
pub size: f32,
#[br(if(length > 6))]
#[br(parse_with = vx)]
pub size_envelope: u32,
}
#[binread]
#[br(repr = u16)]
#[derive(Debug)]
pub enum GlowType {
HastingsGlow = 0,
ImageConvolution = 1,
}
#[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 PolygonSidedness {
pub sidedness: u16,
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct MaxSmoothingAngle {
pub max_smoothing_angle: f32,
}

View File

@@ -1,11 +0,0 @@
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

@@ -1,53 +0,0 @@
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

@@ -1,148 +0,0 @@
use crate::binrw_helpers::until_size_limit;
use crate::iff::SubChunk;
use crate::lwo2::vx;
use binrw::{binread, NullString, PosValue};
use crate::lwo2::sub_tags::plugin::PluginServerNameAndData;
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct EnvelopeDefinition {
#[br(temp)]
pos_start: PosValue<()>,
#[br(parse_with = vx)]
pub index: u32,
#[br(temp)]
pos_end: PosValue<()>,
#[br(parse_with = until_size_limit(length as u64 - (pos_end.pos - pos_start.pos)))]
pub attributes: Vec<EnvelopeSubChunk>,
}
#[binread]
#[derive(Debug)]
pub enum EnvelopeSubChunk {
#[br(magic(b"TYPE"))]
EnvelopeType(SubChunk<EnvelopeType>),
#[br(magic(b"PRE"))]
PreBehavior(SubChunk<Behavior>),
#[br(magic(b"POST"))]
PostBehavior(SubChunk<Behavior>),
#[br(magic(b"KEY"))]
KeyframeTimeAndValue(SubChunk<KeyframeTimeAndValue>),
#[br(magic(b"SPAN"))]
IntervalInterpolation(SubChunk<IntervalInterpolation>),
#[br(magic(b"CHAN"))]
PluginChannelModifiers(SubChunk<PluginServerNameAndData>),
#[br(magic(b"NAME"))]
ChannelName(SubChunk<PluginChannelName>),
}
/// An optional name for the envelope. LightWave® itself ignores the names of surface envelopes,
/// but plug-ins can browse the envelope database by name.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct PluginChannelName {
#[br(align_after = 2)]
pub channel_name: NullString,
}
/// Defines the interpolation between the most recent KEY chunk and the KEY immediately before it in
/// time. The type identifies the interpolation algorithm and can be STEP, LINE, TCB
/// (Kochanek-Bartels), HERM (Hermite), BEZI (1D Bezier) or BEZ2 (2D Bezier).
/// Different parameters are stored for each of these.
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct IntervalInterpolation {
pub kind: IntervalInterpolationType,
#[br(count = (length - 4) / 4)]
pub parameters: Vec<f32>,
}
#[binread]
#[derive(Debug)]
pub enum IntervalInterpolationType {
#[br(magic(b"STEP"))]
Step,
#[br(magic(b"LINE"))]
Line,
#[br(magic(b"TCB\0"))]
KochanekBartels,
#[br(magic(b"HERM"))]
Hermite,
#[br(magic(b"BEZI"))]
Bezier1D,
#[br(magic(b"BEZ2"))]
Bezier2D,
}
/// The value of the envelope at the specified time in seconds. The signal value between keyframes
/// is interpolated. The time of a keyframe isn't restricted to integer frames.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct KeyframeTimeAndValue {
pub time: f32,
pub value: f32,
}
#[binread]
#[br(repr = u16, import(_length: u32))]
#[derive(Debug)]
pub enum Behavior {
/// Sets the value to 0.0.
Reset = 0,
/// Sets the value to the value at the nearest key.
Constant = 1,
/// Repeats the interval between the first and last keys (the primary interval).
Repeat = 2,
/// Like Repeat, but alternating copies of the primary interval are time-reversed.
Oscillate = 3,
/// Like Repeat, but offset by the difference between the values of the first and last keys.
OffsetRepeat = 4,
/// Linearly extrapolates the value based on the tangent at the nearest key.
Linear = 5,
}
/// 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

@@ -1,172 +0,0 @@
use crate::binrw_helpers::until_size_limit;
use crate::iff::SubChunk;
use crate::lwo2::sub_tags::plugin::PluginServerNameAndData;
use crate::lwo2::sub_tags::{EnableState, ValueEnvelope};
use binrw::{binread, NullString, PosValue};
/// 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"ANIM"))]
PluginAnimation(SubChunk<PluginAnimation>),
#[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\0"))]
Hue(SubChunk<ValueEnvelope>),
#[br(magic(b"GAMM"))]
GammaCorrection(SubChunk<ValueEnvelope>),
#[br(magic(b"NEGA"))]
Negative(SubChunk<EnableState>),
#[br(magic(b"IFLT"))]
PluginImageFilters(SubChunk<PluginServerNameAndData>),
#[br(magic(b"PFLT"))]
PluginPixelFilters(SubChunk<PluginServerNameAndData>),
}
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct PluginAnimation {
#[br(temp)]
start_pos: PosValue<()>,
#[br(align_after = 2)]
pub file_name: NullString,
#[br(align_after = 2)]
pub server_name: NullString,
pub flags: u16,
#[br(temp)]
end_pos: PosValue<()>,
#[br(count = end_pos.pos - start_pos.pos)]
pub data: Vec<u8>,
}
/// 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,20 +0,0 @@
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

@@ -1,35 +0,0 @@
use binrw::{binread, NullString};
/// Store an object description. Optional. This should be a simple line of upper and lowercase
/// characters, punctuation and spaces which describes the contents of the object file. There
/// should be no control characters in this text string and it should generally be kept short.
#[binread]
#[br(import(_length: u32))]
#[derive(Debug)]
pub struct DescriptionLine {
#[br(align_after = 2)]
pub description_line: NullString,
}
/// An iconic or thumbnail image for the object which can be used when viewing the file in a
/// browser. Currently the only suported encoding is 0, meaning uncompressed RGB byte triples.
/// The width is the number of pixels in each row of the image, and the height (number of rows)
/// is (chunkSize - 4)/width. This chunk is optional.
#[binread]
#[br(import(length: u32))]
#[derive(Debug)]
pub struct ThumbnailIconImage {
pub encoding: ThumbnailImageEncoding,
pub width: u16,
#[br(calc = (length as u16 - 4) / width)]
pub height: u16,
#[br(count = length - 4)]
pub data: Vec<u8>,
}
#[binread]
#[br(repr = u16)]
#[derive(Debug)]
pub enum ThumbnailImageEncoding {
UncompressedRgb = 0,
}

View File

@@ -1,61 +0,0 @@
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::meta::{DescriptionLine, ThumbnailIconImage};
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 envelope;
pub mod image_clip;
pub mod layer;
pub mod meta;
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"DESC"))]
DescriptionLine(Chunk<DescriptionLine>),
#[br(magic(b"TEXT"))]
CommentaryText(Chunk<DescriptionLine>),
#[br(magic(b"ICON"))]
ThumbnailIconImage(Chunk<ThumbnailIconImage>),
#[br(magic(b"POLS"))]
PolygonList(Chunk<PolygonLists>),
#[br(magic(b"SURF"))]
SurfaceDefinition(Chunk<SurfaceDefinition>),
#[br(magic(b"CLIP"))]
ImageClip(Chunk<ImageClip>),
}

View File

@@ -1,16 +0,0 @@
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

@@ -1,54 +0,0 @@
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

@@ -1,36 +0,0 @@
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

@@ -1,19 +0,0 @@
use crate::binrw_helpers::until_size_limit;
use crate::lwo2::sub_tags::surface_parameters::SurfaceParameterSubChunk;
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<SurfaceParameterSubChunk>,
}

View File

@@ -1,11 +0,0 @@
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

@@ -1,21 +0,0 @@
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

@@ -1,57 +0,0 @@
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,5 +7,5 @@ edition = "2021"
[dependencies]
starforcelib = {path = "../starforcelib"}
lightwave = {path = "../lightwave"}
lightwave-3d = "1.0.0"
glob = "0.3.1"

View File

@@ -1,5 +1,5 @@
use glob::glob;
use lightwave::LightWaveObject;
use lightwave_3d::LightWaveObject;
fn main() {
let mut successful = 0;

View File

@@ -0,0 +1,68 @@
use godot::bind::godot_api;
use godot::builtin::{GodotString, PackedInt32Array, PackedVector3Array, Vector3};
use godot::engine::mesh::ArrayType;
use godot::engine::{ArrayMesh, PackedScene};
use godot::obj::{EngineEnum, Gd};
use godot::prelude::{Array, GodotClass, ToVariant};
use lightwave_3d::lwo2::tags::Tag;
use lightwave_3d::LightWaveObject;
use std::fs::File;
#[derive(GodotClass)]
struct Lwo {}
#[godot_api]
impl Lwo {
pub fn get_mesh(path: GodotString) -> Gd<ArrayMesh> {
lightwave_to_gd(LightWaveObject::read_file(path.to_string()).unwrap())
}
}
pub fn lightwave_to_gd(lightwave: LightWaveObject) -> Gd<ArrayMesh> {
let mesh = ArrayMesh::new();
let mut arrays = Array::new();
arrays.resize(ArrayType::ARRAY_MAX.ord() as usize);
for tag in lightwave.data {
match tag {
Tag::PointList(points) => {
arrays.set(
ArrayType::ARRAY_VERTEX.ord() as usize,
PackedVector3Array::from(
points
.point_location
.iter()
.map(|[x, y, z]| Vector3 {
x: *x,
y: *y,
z: *z,
})
.collect::<Vec<Vector3>>()
.as_slice(),
)
.to_variant(),
);
}
Tag::PolygonList(polygons) => match &polygons.kind {
b"FACE" => {
arrays.set(
ArrayType::ARRAY_INDEX.ord() as usize,
PackedInt32Array::from(
polygons
.polygons
.iter()
.flat_map(|it| it.vert.iter().map(|it| *it as i32))
.collect::<Vec<i32>>()
.as_slice(),
)
.to_variant(),
);
}
_ => panic!(),
},
_ => (),
}
}
mesh
}

View File

@@ -1,13 +1,8 @@
use crate::formats;
use crate::formats::datafile::{Datafile, FileEntry};
use crate::formats::{load_data, DatafileFile};
use crate::godot::font::load_bitmap_font;
use crate::godot::game_object::parse_game_object;
use crate::godot::image::{load_bmp_as_image_texture, load_rle_as_sprite_frames};
use crate::godot::sprites::load_sprite_frames;
use crate::godot::tile_map::{create_tile_map, TileCollision};
use crate::godot::ui::convert_ui;
use binrw::BinRead;
use crate::sproing::game_object::parse_game_object;
use crate::sproing::image::{load_bmp_as_image_texture, load_rle_as_sprite_frames};
use crate::sproing::sprites::load_sprite_frames;
use crate::sproing::tile_map::{create_tile_map, TileCollision};
use crate::sproing::ui::convert_ui;
use godot::engine::global::Error;
use godot::engine::resource_loader::CacheMode;
use godot::engine::resource_saver::SaverFlags;
@@ -18,7 +13,8 @@ use godot::engine::{ResourceFormatLoader, ResourceSaver};
use godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader};
use godot::prelude::*;
use itertools::Itertools;
use std::collections::HashMap;
use springylib::archive::Archive;
use springylib::DatafileFile;
use std::fs::File;
use std::str::FromStr;
@@ -27,7 +23,7 @@ const DAT_PATH: &str = "E:\\Games\\Schatzjäger\\data\\datafile.dat";
#[derive(GodotClass)]
#[class(base=ResourceFormatLoader)]
pub struct DatafileLoader {
pub datafile_table: HashMap<String, FileEntry>,
pub datafile_table: Archive,
#[base]
pub base: Base<ResourceFormatLoader>,
@@ -86,7 +82,7 @@ impl DatafileLoader {
impl ResourceFormatLoaderVirtual for DatafileLoader {
fn init(base: Base<Self::Base>) -> Self {
let mut file = File::open(DAT_PATH).unwrap();
let datafile_table = Datafile::read(&mut file).unwrap().into_index();
let datafile_table = Archive::read(&mut file).unwrap();
DatafileLoader {
base,
@@ -151,7 +147,7 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
if let Some(target) = self.datafile_table.get(datafile_path.as_str()) {
let mut file = File::open(DAT_PATH).unwrap();
match load_data(target, &mut file) {
match target.load_from(&mut file) {
Ok(DatafileFile::Level(level)) => {
let level_id = datafile_path
.split_terminator('\\')
@@ -229,13 +225,14 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
};
if datafile_path.contains("\\fonts\\") {
let font = load_bitmap_font(gd_image);
panic!();
/*let font = load_bitmap_font(gd_image);
self.save_to_cache(
font.share().upcast(),
format!("{}.tres", datafile_path),
);
font.to_variant()
font.to_variant()*/
} else {
let mut texture = ImageTexture::new();
texture.set_image(gd_image);
@@ -263,16 +260,22 @@ impl ResourceFormatLoaderVirtual for DatafileLoader {
);*/
tile_collision.to_variant()
}
Err(formats::Error::UnknownFormat(ext)) => {
Err(springylib::error::Error::UnknownFormat(ext)) => {
printerr(format!("Unknown format <{}>", ext).to_variant(), &[]);
Error::ERR_FILE_UNRECOGNIZED.to_variant()
}
Err(formats::Error::Deserialization) => {
printerr("Failed to deserialize".to_variant(), &[]);
Err(springylib::error::Error::InvalidData { info, context }) => {
printerr(
"Failed to deserialize".to_variant(),
&[
info.unwrap_or("".to_string()).to_variant(),
context.to_variant(),
],
);
Error::ERR_FILE_CORRUPT.to_variant()
}
Err(formats::Error::Custom(message)) => {
printerr(message.to_variant(), &[]);
Err(springylib::error::Error::Custom(message)) => {
printerr(message.to_string().to_variant(), &[]);
Error::ERR_BUG.to_variant()
}
_ => {

View File

@@ -1,11 +1,8 @@
use encoding_rs::WINDOWS_1252;
use godot::builtin::{Rect2, Vector2, Vector2i};
use godot::engine::{FontFile, Image};
use godot::prelude::utilities::prints;
use godot::prelude::{Gd, Share, ToVariant};
const CHARSET: &[u8] = include_bytes!("charset.txt");
pub fn load_bitmap_font(image: Gd<Image>) -> Gd<FontFile> {
let mut font_chars = CHARSET.iter();

View File

@@ -1,9 +1,9 @@
use crate::formats::rle::RleImage;
use godot::builtin::{Color, PackedByteArray};
use godot::engine::global::Error;
use godot::engine::image::Format;
use godot::engine::{Image, ImageTexture, SpriteFrames};
use godot::obj::Gd;
use springylib::media::rle::RleImage;
const FPS: f64 = 15.0;

View File

@@ -1,5 +1,5 @@
pub mod datafile;
pub mod font;
// pub mod font;
pub mod game_object;
pub mod image;
pub mod sprites;

View File

@@ -1,4 +1,3 @@
use crate::formats::sprites::{CropMode, RenderMode, Sprites};
use godot::builtin::{GodotString, Rect2, StringName, ToVariant, Vector2};
use godot::engine::utilities::printerr;
use godot::engine::{
@@ -6,6 +5,7 @@ use godot::engine::{
};
use godot::obj::{Gd, Share};
use godot::prelude::GodotClass;
use springylib::media::sprites::{CropMode, RenderMode, Sprites};
const FPS: f64 = 15.0;
const SPRITE_EXTENSIONS: &[&str] = &["bmp", "rle"];

View File

@@ -1,4 +1,3 @@
use crate::formats::level::LevelLayer;
use godot::engine::global::Error;
use godot::engine::utilities::{clampi, printerr};
use godot::engine::{load, PackedScene};
@@ -6,6 +5,7 @@ use godot::engine::{ImageTexture, TileSet};
use godot::engine::{TileMap, TileSetAtlasSource};
use godot::prelude::*;
use godot::prelude::{Gd, PackedByteArray, Share, ToVariant};
use springylib::media::level::LevelLayer;
pub fn create_tile_map(layer: LevelLayer, level_id: u32) -> Gd<PackedScene> {
let mut tile_set = TileSet::new();

View File

@@ -1,4 +1,3 @@
use crate::formats::ui_xml::{HorizontalAlign, UiTag};
use godot::builtin::{Array, Dictionary, GodotString, ToVariant, Vector2};
use godot::engine::control::LayoutPreset;
use godot::engine::global::HorizontalAlignment;
@@ -6,6 +5,7 @@ use godot::engine::node::InternalMode;
use godot::engine::{load, Button, Control, Label, LineEdit, Node, SpinBox, TextureRect};
use godot::obj::{Gd, Inherits, Share};
use itertools::Itertools;
use springylib::media::ui::{HorizontalAlign, UiTag};
const ACTION_META_NAME: &str = "action";
@@ -31,7 +31,7 @@ pub fn convert_ui(ui: UiTag, base_path: &str) -> Gd<Node> {
let mut label = Label::new_alloc();
label.set_anchors_preset(LayoutPreset::PRESET_TOP_WIDE, false);
label.set_position(to_vec2(text.position), false);
label.set_horizontal_alignment(text.horizontal_align.into());
label.set_horizontal_alignment(to_h_alignment(text.horizontal_align));
label.set_text(text.text.into());
label.upcast()
}
@@ -49,7 +49,7 @@ pub fn convert_ui(ui: UiTag, base_path: &str) -> Gd<Node> {
text_field.set_name(name.into());
}
text_field.set_text(field.text.into());
text_field.set_horizontal_alignment(field.horizontal_align.into());
text_field.set_horizontal_alignment(to_h_alignment(field.horizontal_align));
text_field.set_position(to_vec2([field.area[0], field.area[1]]), false);
text_field.set_size(to_vec2([field.area[2], field.area[3]]), false);
text_field.set_meta("buffer_var".into(), field.buffer_var.to_variant());
@@ -76,7 +76,7 @@ pub fn convert_ui(ui: UiTag, base_path: &str) -> Gd<Node> {
gd_button.set_anchors_preset(LayoutPreset::PRESET_TOP_WIDE, false);
gd_button.set_flat(true);
gd_button.set_position(to_vec2(button.position), false);
gd_button.set_text_alignment(button.horizontal_align.into());
gd_button.set_text_alignment(to_h_alignment(button.horizontal_align));
if let Some(name) = button.name {
gd_button.set_name(GodotString::from(name));
}
@@ -87,13 +87,11 @@ pub fn convert_ui(ui: UiTag, base_path: &str) -> Gd<Node> {
}
}
impl Into<HorizontalAlignment> for HorizontalAlign {
fn into(self) -> HorizontalAlignment {
match self {
HorizontalAlign::Center => HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER,
HorizontalAlign::Left => HorizontalAlignment::HORIZONTAL_ALIGNMENT_LEFT,
HorizontalAlign::Right => HorizontalAlignment::HORIZONTAL_ALIGNMENT_RIGHT,
}
fn to_h_alignment(align: HorizontalAlign) -> HorizontalAlignment {
match align {
HorizontalAlign::Center => HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER,
HorizontalAlign::Left => HorizontalAlignment::HORIZONTAL_ALIGNMENT_LEFT,
HorizontalAlign::Right => HorizontalAlignment::HORIZONTAL_ALIGNMENT_RIGHT,
}
}

View File

@@ -25,7 +25,7 @@ impl Display for Error {
Error::InvalidData { info, context } => write!(
f,
"Invalid data: {}; {}",
info.unwrap_or("[no info]".to_string()),
info.clone().unwrap_or("[no info]".to_string()),
context
),
Error::Custom(error) => write!(f, "{}", error),

View File

@@ -55,12 +55,12 @@ impl Default for FadeMode {
impl UiTag {
pub fn post_process(mut self) -> Self {
if let UiTag::Menu(mut menu) = &self {
if let UiTag::Menu(menu) = &mut self {
let children: Vec<UiTag> = menu.children.drain(..).collect();
let mut area_stack: Vec<Vec<UiTag>> = vec![vec![]];
for mut child in children {
child.post_process();
for child in children {
let child = child.post_process();
if let UiTag::TextArea(mut area) = child {
let children = area_stack.pop().unwrap();
let opening_tag = area_stack.last_mut().map(|it| it.last_mut());

View File

@@ -1,121 +0,0 @@
use crate::formats::datafile::FileEntry;
use crate::formats::level::LevelLayer;
use crate::formats::rle::RleImage;
use crate::formats::sprites::Sprites;
use crate::formats::txt::{decrypt_exposed_txt, decrypt_txt, DecryptError};
use crate::formats::ui_xml::UiTag;
use binrw::BinRead;
use encoding_rs::WINDOWS_1252;
use itertools::Itertools;
use std::collections::HashMap;
use std::ffi::OsStr;
use std::fmt::Debug;
use std::io::{Cursor, Read, Seek, SeekFrom};
use std::path::Path;
pub mod datafile;
pub mod level;
pub mod rle;
pub mod sprites;
pub mod txt;
pub mod ui_xml;
pub enum DatafileFile {
Txt(String),
Level(LevelLayer),
Sprites(Vec<Sprites>),
RleSprite(Box<RleImage>),
Bitmap(Vec<u8>),
Vorbis(Vec<u8>),
TileCollision(String),
Ui(UiTag),
Translations(HashMap<String, Vec<String>>),
}
pub enum Error {
Deserialization,
UnknownFormat(String),
UnknownError,
Custom(String),
DecryptError(DecryptError),
}
fn custom_err<T>(e: T) -> Error
where
T: Debug,
{
Error::Custom(format!("{:#?}", e))
}
pub fn load_data<R>(entry: &FileEntry, reader: &mut R) -> Result<DatafileFile, Error>
where
R: Read + Seek,
{
reader
.seek(SeekFrom::Start(entry.pos as u64))
.map_err(custom_err)?;
let mut data = vec![0u8; entry.len as usize];
reader.read_exact(&mut data).map_err(custom_err)?;
let name = entry.name.to_string();
let path = Path::new(&name);
match path
.extension()
.and_then(OsStr::to_str)
.ok_or(Error::Custom("No extension".to_string()))?
{
"dat" => Ok(DatafileFile::Level(
LevelLayer::read(&mut Cursor::new(data)).map_err(custom_err)?,
)),
"rle" => Ok(DatafileFile::RleSprite(Box::new(
RleImage::read(&mut Cursor::new(data)).map_err(custom_err)?,
))),
"bmp" => Ok(DatafileFile::Bitmap(data)),
"ogg" => Ok(DatafileFile::Vorbis(data)),
"xml" => {
serde_xml_rs::from_str::<UiTag>(String::from_utf8(data).map_err(custom_err)?.as_str())
.map_err(custom_err)
.map(|mut it| {
it.post_process();
DatafileFile::Ui(it)
})
}
"txt" => {
let stem = path
.file_stem()
.and_then(OsStr::to_str)
.ok_or(Error::Custom("Stem".to_string()))?;
let decr = decrypt_txt(data.into_iter()).map_err(Error::DecryptError)?;
if stem.starts_with("tile_collision") {
Ok(DatafileFile::TileCollision(decr))
} else if stem == "sprites" {
Ok(DatafileFile::Sprites(
Sprites::parse(decr.as_str()).map_err(custom_err)?,
))
} else if stem.starts_with("profile") || stem.starts_with("highscores") {
Ok(DatafileFile::Txt(
decrypt_exposed_txt(decr).map_err(custom_err)?,
))
} else {
Ok(DatafileFile::Txt(decr))
}
}
"csv" => Ok(DatafileFile::Translations(
WINDOWS_1252
.decode(data.as_slice())
.0
.split('\n')
.map(|l| l.trim())
.filter(|l| !l.is_empty())
.map(|l| {
l.splitn(2, ';')
.map(|s| s.to_string())
.collect_tuple::<(String, String)>()
.expect("Invalid csv")
})
.into_group_map(),
)),
ext => Err(Error::UnknownFormat(ext.to_string())),
}
}

View File

@@ -1,239 +0,0 @@
use serde::de::Error;
use serde::{Deserialize, Deserializer};
#[derive(Debug, Clone, Deserialize)]
pub enum UiTag {
Menu(UiMenu),
Image(UiImage),
TextButton(UiTextButton),
TextArea(UiTextArea),
TextField(UiTextField),
StaticText(UiStaticText),
ToggleButton(UiToggleButton),
}
#[derive(Debug, Clone, Deserialize)]
pub struct UiMenu {
pub selected: String,
#[serde(rename = "OnBack")]
pub on_back: Option<String>,
#[serde(rename = "$value", default)]
pub children: Vec<UiTag>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UiTextField {
pub name: Option<String>,
pub text: String,
#[serde(deserialize_with = "deserialize_vec2")]
pub position: [i32; 2],
#[serde(rename = "bufferVar")]
pub buffer_var: String,
#[serde(deserialize_with = "deserialize_vec4")]
pub area: [i32; 4],
#[serde(rename = "halign", default)]
pub horizontal_align: HorizontalAlign,
#[serde(rename = "fademode")]
pub fade_mode: FadeMode,
#[serde(rename = "OnSelect")]
pub on_select: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UiImage {
pub texture: String,
#[serde(deserialize_with = "deserialize_vec2")]
pub position: [i32; 2],
#[serde(deserialize_with = "deserialize_vec2")]
pub size: [i32; 2],
#[serde(rename = "fademode", default)]
pub fade_mode: FadeMode,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UiTextButton {
pub name: Option<String>,
pub text: String,
#[serde(deserialize_with = "deserialize_vec2")]
pub position: [i32; 2],
#[serde(rename = "halign", default)]
pub horizontal_align: HorizontalAlign,
#[serde(rename = "fademode", default)]
pub fade_mode: FadeMode,
#[serde(rename = "OnSelect")]
pub on_select: String,
}
/// This is a really weird node, sometimes it has children and sometimes, don't ask me why,
/// it appears as a normal tag and then gets closed by an empty tag of this kind.
#[derive(Debug, Clone, Deserialize)]
pub struct UiTextArea {
#[serde(deserialize_with = "deserialize_vec2_opt", default)]
pub position: Option<[i32; 2]>,
#[serde(deserialize_with = "deserialize_vec2_opt", default)]
pub size: Option<[i32; 2]>,
#[serde(rename = "$value", default)]
pub children: Vec<UiTag>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UiStaticText {
pub text: String,
#[serde(deserialize_with = "deserialize_vec2")]
pub position: [i32; 2],
#[serde(rename = "halign", default)]
pub horizontal_align: HorizontalAlign,
#[serde(rename = "fademode", default)]
pub fade_mode: FadeMode,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UiToggleButton {
pub name: Option<String>,
pub text: String,
#[serde(deserialize_with = "deserialize_vec2")]
pub position: [i32; 2],
pub value: String,
#[serde(rename = "minValue")]
pub min_value: i32,
#[serde(rename = "maxValue")]
pub max_value: i32,
#[serde(rename = "valueStep")]
pub value_step: i32,
pub target: String,
#[serde(rename = "targetLOffset", deserialize_with = "deserialize_vec2")]
pub target_l_offset: [i32; 2],
#[serde(rename = "targetROffset", deserialize_with = "deserialize_vec2")]
pub target_r_offset: [i32; 2],
#[serde(rename = "noSound", default)]
pub no_sound: bool,
#[serde(rename = "OnChange")]
pub on_change: String,
#[serde(rename = "OnSelect")]
pub on_select: String,
}
impl UiTag {
pub fn post_process(&mut self) {
if let UiTag::Menu(menu) = self {
let children: Vec<UiTag> = menu.children.drain(..).collect();
let mut area_stack: Vec<Vec<UiTag>> = vec![vec![]];
for mut child in children {
child.post_process();
if let UiTag::TextArea(mut area) = child {
let children = area_stack.pop().unwrap();
let opening_tag = area_stack.last_mut().map(|it| it.last_mut());
if let Some(Some(UiTag::TextArea(opening_tag))) = opening_tag {
opening_tag.children = children;
} else {
area_stack.push(children);
}
if area.position.is_some() && area.size.is_some() {
let children = area.children.drain(..).collect();
area_stack.last_mut().unwrap().push(UiTag::TextArea(area));
area_stack.push(children);
}
} else {
area_stack.last_mut().unwrap().push(child);
}
}
menu.children = area_stack.pop().unwrap();
debug_assert!(area_stack.is_empty());
}
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum HorizontalAlign {
Left,
Center,
Right,
}
impl Default for HorizontalAlign {
fn default() -> HorizontalAlign {
HorizontalAlign::Left
}
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum FadeMode {
None,
}
impl Default for FadeMode {
fn default() -> Self {
FadeMode::None
}
}
fn deserialize_vec2_opt<'de, D>(deserializer: D) -> Result<Option<[i32; 2]>, D::Error>
where
D: Deserializer<'de>,
{
if let Some(buf) = Option::<String>::deserialize(deserializer)? {
to_vec2::<D>(buf).map(Some)
} else {
Ok(None)
}
}
fn deserialize_vec2<'de, D>(deserializer: D) -> Result<[i32; 2], D::Error>
where
D: Deserializer<'de>,
{
to_vec2::<D>(String::deserialize(deserializer)?)
}
fn deserialize_vec4<'de, D>(deserializer: D) -> Result<[i32; 4], D::Error>
where
D: Deserializer<'de>,
{
to_vec4::<D>(String::deserialize(deserializer)?)
}
fn to_vec<'de, D>(buf: String) -> Result<Vec<i32>, D::Error>
where
D: Deserializer<'de>,
{
buf.split(',')
.into_iter()
.map(|value| {
// there's some typos so we have to cover that...
value.split_ascii_whitespace().collect::<Vec<&str>>()[0]
.trim()
.parse::<i32>()
.map_err(|err| Error::custom(err.to_string()))
})
.collect()
}
fn to_vec4<'de, D>(buf: String) -> Result<[i32; 4], D::Error>
where
D: Deserializer<'de>,
{
let mut values = to_vec::<D>(buf)?;
let w = values.pop().ok_or(Error::custom("InvalidField"))?;
let z = values.pop().ok_or(Error::custom("InvalidField"))?;
let y = values.pop().ok_or(Error::custom("InvalidField"))?;
let x = values.pop().ok_or(Error::custom("InvalidField"))?;
Ok([x, y, z, w])
}
fn to_vec2<'de, D>(buf: String) -> Result<[i32; 2], D::Error>
where
D: Deserializer<'de>,
{
let mut values = to_vec::<D>(buf)?;
let y = values.pop().ok_or(Error::custom("InvalidField"))?;
let x = values.pop().ok_or(Error::custom("InvalidField"))?;
Ok([x, y])
}

View File

@@ -1,40 +0,0 @@
use crate::godot::datafile::DatafileLoader;
use ::godot::engine::class_macros::auto_register_classes;
use ::godot::engine::{ResourceFormatLoaderVirtual, ResourceLoader};
use ::godot::init::{gdextension, ExtensionLayer};
use ::godot::prelude::{ExtensionLibrary, Gd, InitHandle, InitLevel, Share};
pub mod formats;
pub mod godot;
struct Main {}
#[gdextension]
unsafe impl ExtensionLibrary for Main {
fn load_library(handle: &mut InitHandle) -> bool {
handle.register_layer(InitLevel::Editor, ResourceLoaderLayer { datafile: None });
true
}
}
struct ResourceLoaderLayer {
pub datafile: Option<Gd<DatafileLoader>>,
}
impl ExtensionLayer for ResourceLoaderLayer {
fn initialize(&mut self) {
auto_register_classes();
self.datafile = Some(Gd::<DatafileLoader>::with_base(DatafileLoader::init));
ResourceLoader::singleton()
.add_resource_format_loader(self.datafile.as_ref().unwrap().share().upcast(), true);
}
fn deinitialize(&mut self) {
if let Some(datafile) = &self.datafile {
ResourceLoader::singleton().remove_resource_format_loader(datafile.share().upcast());
self.datafile = None;
}
}
}

View File

@@ -1,135 +0,0 @@
use binrw::{BinRead, NullString};
use image::codecs::gif::{GifEncoder, Repeat};
use image::{AnimationDecoder, ImageFormat};
use mhjnr::formats::datafile::Datafile;
use mhjnr::formats::level::level_tile_data_to_image;
use mhjnr::formats::rle::RleImage;
use mhjnr::formats::sprites::Sprites;
use mhjnr::formats::txt::{decrypt_exposed_txt, decrypt_txt};
use mhjnr::formats::ui_xml::UiTag;
use serde_xml_rs::from_str;
use std::ffi::OsStr;
use std::fs;
use std::fs::{File, OpenOptions};
use std::io::{Cursor, Read, Seek, SeekFrom, Write};
use std::path::Path;
fn extract(datafile: &Datafile, file: &mut File) {
let target = "E:\\Games\\Schatzjäger\\data3";
for entry in &datafile.files {
let file_name = format!("{}\\{}", target, entry.name);
fs::create_dir_all(file_name.rsplit_once('\\').unwrap().0).unwrap();
file.seek(SeekFrom::Start(entry.pos as u64)).unwrap();
let mut data = vec![0u8; entry.len as usize];
file.read_exact(&mut data).unwrap();
if entry.name.to_string().ends_with(".txt") {
let mut contents = decrypt_txt(data.into_iter()).unwrap();
/*if entry
.name
.to_string()
.split('\\')
.collect::<Vec<&str>>()
.len()
== 1
{
contents = decrypt_exposed_txt(contents).unwrap();
}*/
File::create(file_name)
.unwrap()
.write_all(contents.as_bytes())
.unwrap();
} else if entry.name.to_string().ends_with(".rle") {
let image: RleImage = RleImage::read(&mut Cursor::new(data)).unwrap();
let mut encoder = GifEncoder::new(
OpenOptions::new()
.create(true)
.write(true)
.open(format!(
"{}.{}",
file_name.strip_suffix(".rle").unwrap(),
".gif"
))
.unwrap(),
);
encoder.set_repeat(Repeat::Infinite).unwrap();
encoder.try_encode_frames(image.into_frames()).unwrap();
} else {
File::create(file_name)
.unwrap()
.write_all(data.as_slice())
.unwrap();
}
}
}
fn main() {
let file_name = Some(NullString::from("data\\profile_00.txt"));
let dat_path = "E:\\Games\\Schatzjäger\\data\\datafile.dat";
let mut file = File::open(dat_path).unwrap();
let dat: Datafile = Datafile::read(&mut file).unwrap();
println!("{:#?}", dat);
// extract(&dat, &mut file);
if let Some(file_name) = file_name {
let target = dat.files.iter().find(|it| it.name == file_name).unwrap();
file.seek(SeekFrom::Start(target.pos as u64)).unwrap();
let mut data = vec![0u8; target.len as usize];
file.read_exact(&mut data).unwrap();
match Path::new(&file_name.to_string())
.extension()
.and_then(OsStr::to_str)
{
Some("xml") => {
let mut data =
from_str::<UiTag>(String::from_utf8(data).unwrap().as_str()).unwrap();
data.post_process();
println!("{:#?}", data)
}
Some("txt") => {
if false {
/*let decr = decrypt_txt(&mut data);
let entries: String = decrypt_exposed_txt(decr);*/
let decr = decrypt_txt(data.into_iter()).unwrap();
println!("{}", &decr);
let sprites = Sprites::parse(decr.as_str()).unwrap();
println!("{:#?}", sprites);
} else {
println!(
"{}",
decrypt_exposed_txt(decrypt_txt(data.into_iter()).unwrap()).unwrap()
)
}
}
Some("rle") => {
let image: RleImage = RleImage::read(&mut Cursor::new(data)).unwrap();
let path = Path::new(dat_path).with_file_name("res.gif");
println!("{:?}", path);
let mut encoder = GifEncoder::new(
OpenOptions::new()
.create(true)
.write(true)
.open(path)
.unwrap(),
);
encoder.set_repeat(Repeat::Infinite).unwrap();
encoder.try_encode_frames(image.into_frames()).unwrap();
}
Some("dat") => {
let image = level_tile_data_to_image(&data).unwrap();
let path = Path::new(dat_path).with_file_name("res.png");
println!("{:?}", path);
image.save_with_format(path, ImageFormat::Png).unwrap();
}
Some(ext) => eprintln!("Unknown file extension <{}>", ext),
None => eprintln!("Failed to read"),
}
}
}
// pub fn decr2()