mirror of
https://codeberg.org/icewind/vmdl.git
synced 2026-06-03 16:44:11 +02:00
fully switch to bytemuck for reading
This commit is contained in:
parent
473d6eecc5
commit
bb4dfddde2
11 changed files with 38 additions and 130 deletions
|
|
@ -6,7 +6,6 @@ exclude = ["data"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
arrayvec = "0.7.2"
|
arrayvec = "0.7.2"
|
||||||
binrw = "0.8.0"
|
|
||||||
thiserror = "1.0.30"
|
thiserror = "1.0.30"
|
||||||
static_assertions = "1.1.0"
|
static_assertions = "1.1.0"
|
||||||
bitflags = "1.0.4"
|
bitflags = "1.0.4"
|
||||||
|
|
|
||||||
21
src/error.rs
21
src/error.rs
|
|
@ -7,31 +7,10 @@ pub enum ModelError {
|
||||||
IO(#[from] std::io::Error),
|
IO(#[from] std::io::Error),
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
String(#[from] StringError),
|
String(#[from] StringError),
|
||||||
#[error("Malformed field found while parsing: {0:#}")]
|
|
||||||
MalformedData(binrw::Error),
|
|
||||||
#[error("referenced data to {data} is out of bounds at {offset}")]
|
#[error("referenced data to {data} is out of bounds at {offset}")]
|
||||||
OutOfBounds { data: &'static str, offset: usize },
|
OutOfBounds { data: &'static str, offset: usize },
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<binrw::Error> for ModelError {
|
|
||||||
fn from(e: binrw::Error) -> Self {
|
|
||||||
use binrw::Error;
|
|
||||||
|
|
||||||
// only a few error types should be generated by our code
|
|
||||||
match e {
|
|
||||||
Error::Io(e) => ModelError::IO(e),
|
|
||||||
Error::Custom { err, .. } => {
|
|
||||||
if err.is::<StringError>() {
|
|
||||||
ModelError::String(*err.downcast::<StringError>().unwrap())
|
|
||||||
} else {
|
|
||||||
panic!("unexpected custom error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
e => ModelError::MalformedData(e),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub enum StringError {
|
pub enum StringError {
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
|
|
|
||||||
16
src/lib.rs
16
src/lib.rs
|
|
@ -8,13 +8,11 @@ pub mod vvd;
|
||||||
use crate::mdl::Mdl;
|
use crate::mdl::Mdl;
|
||||||
use crate::vtx::Vtx;
|
use crate::vtx::Vtx;
|
||||||
use crate::vvd::{Vertex, Vvd};
|
use crate::vvd::{Vertex, Vvd};
|
||||||
use binrw::{BinRead, BinReaderExt};
|
|
||||||
use bytemuck::{pod_read_unaligned, Pod};
|
use bytemuck::{pod_read_unaligned, Pod};
|
||||||
pub use error::*;
|
pub use error::*;
|
||||||
pub use handle::Handle;
|
pub use handle::Handle;
|
||||||
pub use shared::*;
|
pub use shared::*;
|
||||||
use std::any::type_name;
|
use std::any::type_name;
|
||||||
use std::io::Cursor;
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
pub struct Model {
|
pub struct Model {
|
||||||
|
|
@ -82,13 +80,10 @@ impl Model {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_indexes<'a, I: Iterator<Item = usize> + 'static, T: BinRead>(
|
fn read_indexes<'a, I: Iterator<Item = usize> + 'static, T: Readable>(
|
||||||
indexes: I,
|
indexes: I,
|
||||||
data: &'a [u8],
|
data: &'a [u8],
|
||||||
) -> impl Iterator<Item = Result<T, ModelError>> + 'a
|
) -> impl Iterator<Item = Result<T, ModelError>> + 'a {
|
||||||
where
|
|
||||||
T::Args: Default,
|
|
||||||
{
|
|
||||||
indexes
|
indexes
|
||||||
.map(|index| {
|
.map(|index| {
|
||||||
data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
data.get(index..).ok_or_else(|| ModelError::OutOfBounds {
|
||||||
|
|
@ -96,12 +91,7 @@ where
|
||||||
offset: index,
|
offset: index,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.map(|data| {
|
.map(|data| data.and_then(|data| T::read(data)))
|
||||||
data.and_then(|data| {
|
|
||||||
let mut cursor = Cursor::new(data);
|
|
||||||
cursor.read_le().map_err(ModelError::from)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index_range(index: i32, count: i32, size: usize) -> impl Iterator<Item = usize> {
|
fn index_range(index: i32, count: i32, size: usize) -> impl Iterator<Item = usize> {
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ pub use raw::header2::*;
|
||||||
|
|
||||||
use crate::mdl::raw::{BodyPartHeader, Bone, MeshHeader, ModelHeader};
|
use crate::mdl::raw::{BodyPartHeader, Bone, MeshHeader, ModelHeader};
|
||||||
use crate::{read_indexes, read_relative, FixedString, ModelError, ReadRelative, Readable};
|
use crate::{read_indexes, read_relative, FixedString, ModelError, ReadRelative, Readable};
|
||||||
use binrw::BinReaderExt;
|
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, ModelError>;
|
type Result<T> = std::result::Result<T, ModelError>;
|
||||||
|
|
||||||
|
|
@ -19,8 +17,7 @@ pub struct Mdl {
|
||||||
|
|
||||||
impl Mdl {
|
impl Mdl {
|
||||||
pub fn read(data: &[u8]) -> Result<Self> {
|
pub fn read(data: &[u8]) -> Result<Self> {
|
||||||
let mut reader = Cursor::new(data);
|
let header = <StudioHeader as Readable>::read(data)?;
|
||||||
let header: StudioHeader = reader.read_le()?;
|
|
||||||
let bones = read_indexes(header.bone_indexes(), data).collect::<Result<_>>()?;
|
let bones = read_indexes(header.bone_indexes(), data).collect::<Result<_>>()?;
|
||||||
Ok(Mdl {
|
Ok(Mdl {
|
||||||
bones,
|
bones,
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,18 @@
|
||||||
use crate::mdl::raw::*;
|
use crate::mdl::raw::*;
|
||||||
use crate::mdl::Bone;
|
use crate::mdl::Bone;
|
||||||
use crate::{index_range, FixedString, Vector};
|
use crate::{index_range, Vector};
|
||||||
use binrw::BinRead;
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
pub const FILETYPE_ID: i32 = i32::from_be_bytes(*b"IDST");
|
pub const FILETYPE_ID: i32 = i32::from_be_bytes(*b"IDST");
|
||||||
pub const MDL_VERSION: i32 = 48;
|
pub const MDL_VERSION: i32 = 48;
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct StudioHeader {
|
pub struct StudioHeader {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub version: i32,
|
pub version: i32,
|
||||||
pub checksum: [u8; 4], // This has to be the same in the phy and vtx files to load!
|
pub checksum: [u8; 4], // This has to be the same in the phy and vtx files to load!
|
||||||
pub name: FixedString<64>,
|
pub name: [u8; 64],
|
||||||
pub data_length: i32,
|
pub data_length: i32,
|
||||||
|
|
||||||
pub eye_position: Vector, // Position of player viewpoint relative to model origin
|
pub eye_position: Vector, // Position of player viewpoint relative to model origin
|
||||||
|
|
@ -175,7 +175,8 @@ pub struct StudioHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(BinRead)]
|
#[derive(Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct ModelFlags: u32 {
|
pub struct ModelFlags: u32 {
|
||||||
const AUTOGENERATED_HITBOX = 0x00000001;
|
const AUTOGENERATED_HITBOX = 0x00000001;
|
||||||
const USES_ENV_CUBEMAP = 0x00000002;
|
const USES_ENV_CUBEMAP = 0x00000002;
|
||||||
|
|
@ -308,4 +309,4 @@ impl StudioHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static_assertions::const_assert_eq!(size_of::<StudioHeader>() - size_of::<FixedString<0>>(), 408);
|
static_assertions::const_assert_eq!(size_of::<StudioHeader>(), 408);
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
use crate::{index_range, FixedString, Pod};
|
use crate::{index_range, FixedString};
|
||||||
use crate::{Quaternion, RadianEuler, Vector};
|
use crate::{Quaternion, RadianEuler, Vector};
|
||||||
use binrw::BinRead;
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use bytemuck::Zeroable;
|
use bytemuck::{Pod, Zeroable};
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
pub mod header;
|
pub mod header;
|
||||||
pub mod header2;
|
pub mod header2;
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct Bone {
|
pub struct Bone {
|
||||||
pub sz_name_index: i32,
|
pub sz_name_index: i32,
|
||||||
pub parent: i32, // parent bone
|
pub parent: i32, // parent bone
|
||||||
|
|
@ -34,7 +34,8 @@ pub struct Bone {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(BinRead)]
|
#[derive(Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct BoneFlags: u32 {
|
pub struct BoneFlags: u32 {
|
||||||
const BONE_PHYSICALLY_SIMULATED = 0x00000001;
|
const BONE_PHYSICALLY_SIMULATED = 0x00000001;
|
||||||
const BONE_PHYSICS_PROCEDURAL = 0x00000002;
|
const BONE_PHYSICS_PROCEDURAL = 0x00000002;
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
use crate::{BinRead, ModelError, StringError};
|
use crate::{ModelError, StringError};
|
||||||
use arrayvec::ArrayString;
|
use arrayvec::ArrayString;
|
||||||
use binrw::{BinResult, ReadOptions};
|
|
||||||
use bytemuck::{Pod, Zeroable};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::{Display, Formatter};
|
use std::fmt::{Display, Formatter};
|
||||||
use std::ops::Add;
|
use std::ops::Add;
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, BinRead, Zeroable, Pod)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Vector {
|
pub struct Vector {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
|
|
@ -54,7 +53,8 @@ impl Add<Vector> for Vector {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct Quaternion {
|
pub struct Quaternion {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
|
|
@ -62,7 +62,8 @@ pub struct Quaternion {
|
||||||
pub w: f32,
|
pub w: f32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct RadianEuler {
|
pub struct RadianEuler {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
pub y: f32,
|
pub y: f32,
|
||||||
|
|
@ -107,38 +108,3 @@ impl<const LEN: usize> Display for FixedString<LEN> {
|
||||||
Display::fmt(&self.0, f)
|
Display::fmt(&self.0, f)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const LEN: usize> BinRead for FixedString<LEN> {
|
|
||||||
type Args = ();
|
|
||||||
|
|
||||||
fn read_options<R: binrw::io::Read + binrw::io::Seek>(
|
|
||||||
reader: &mut R,
|
|
||||||
options: &ReadOptions,
|
|
||||||
args: Self::Args,
|
|
||||||
) -> BinResult<Self> {
|
|
||||||
use std::str;
|
|
||||||
|
|
||||||
let name_buf = <[u8; LEN]>::read_options(reader, options, args)?;
|
|
||||||
|
|
||||||
let zero_pos =
|
|
||||||
name_buf
|
|
||||||
.iter()
|
|
||||||
.position(|c| *c == 0)
|
|
||||||
.ok_or_else(|| binrw::Error::Custom {
|
|
||||||
pos: reader.stream_position().unwrap(),
|
|
||||||
err: Box::new(StringError::NotNullTerminated),
|
|
||||||
})?;
|
|
||||||
let name = &name_buf[..zero_pos];
|
|
||||||
Ok(FixedString(
|
|
||||||
ArrayString::from(
|
|
||||||
str::from_utf8(name)
|
|
||||||
.map_err(StringError::NonUTF8)
|
|
||||||
.map_err(|e| binrw::Error::Custom {
|
|
||||||
pos: reader.stream_position().unwrap(),
|
|
||||||
err: Box::new(e),
|
|
||||||
})?,
|
|
||||||
)
|
|
||||||
.unwrap(),
|
|
||||||
))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
mod raw;
|
mod raw;
|
||||||
|
|
||||||
use crate::{read_relative, ModelError, ReadRelative};
|
use crate::{read_relative, ModelError, ReadRelative, Readable};
|
||||||
use binrw::BinReaderExt;
|
|
||||||
use itertools::Either;
|
use itertools::Either;
|
||||||
use raw::*;
|
use raw::*;
|
||||||
pub use raw::{MeshFlags, StripFlags, StripGroupFlags, Vertex};
|
pub use raw::{MeshFlags, StripFlags, StripGroupFlags, Vertex};
|
||||||
use std::io::Cursor;
|
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
pub const MDL_VERSION: i32 = 7;
|
pub const MDL_VERSION: i32 = 7;
|
||||||
|
|
@ -20,8 +18,7 @@ pub struct Vtx {
|
||||||
|
|
||||||
impl Vtx {
|
impl Vtx {
|
||||||
pub fn read(data: &[u8]) -> Result<Self> {
|
pub fn read(data: &[u8]) -> Result<Self> {
|
||||||
let mut reader = Cursor::new(data);
|
let header = <VtxHeader as Readable>::read(data)?;
|
||||||
let header: VtxHeader = reader.read_le()?;
|
|
||||||
Ok(Vtx {
|
Ok(Vtx {
|
||||||
body_parts: read_relative(data, header.body_indexes())?,
|
body_parts: read_relative(data, header.body_indexes())?,
|
||||||
header,
|
header,
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
use crate::{index_range, Pod};
|
use crate::{index_range, Pod};
|
||||||
use binrw::BinRead;
|
|
||||||
use bitflags::bitflags;
|
use bitflags::bitflags;
|
||||||
use bytemuck::Zeroable;
|
use bytemuck::Zeroable;
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct VtxHeader {
|
pub struct VtxHeader {
|
||||||
pub version: i32,
|
pub version: i32,
|
||||||
pub vertex_cache_size: i32,
|
pub vertex_cache_size: i32,
|
||||||
|
|
@ -103,7 +103,7 @@ impl MeshHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(BinRead, Zeroable, Pod)]
|
#[derive(Zeroable, Pod)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct MeshFlags: u8 {
|
pub struct MeshFlags: u8 {
|
||||||
const IS_TEETH = 0x01;
|
const IS_TEETH = 0x01;
|
||||||
|
|
@ -150,7 +150,7 @@ impl StripGroupHeader {
|
||||||
}
|
}
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(BinRead, Zeroable, Pod)]
|
#[derive(Zeroable, Pod)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct StripGroupFlags: u8 {
|
pub struct StripGroupFlags: u8 {
|
||||||
const IS_FLEXED = 0x01;
|
const IS_FLEXED = 0x01;
|
||||||
|
|
@ -177,7 +177,7 @@ pub struct StripHeader {
|
||||||
static_assertions::const_assert_eq!(size_of::<StripHeader>(), 27);
|
static_assertions::const_assert_eq!(size_of::<StripHeader>(), 27);
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
#[derive(BinRead, Zeroable, Pod)]
|
#[derive(Zeroable, Pod)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct StripFlags: u8 {
|
pub struct StripFlags: u8 {
|
||||||
const IS_TRI_LIST = 0x01;
|
const IS_TRI_LIST = 0x01;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,8 @@
|
||||||
mod raw;
|
mod raw;
|
||||||
|
|
||||||
use crate::vvd::raw::{VertexFileFixup, VvdHeader};
|
use crate::vvd::raw::{VertexFileFixup, VvdHeader};
|
||||||
use crate::{read_relative, read_relative_iter, ModelError};
|
use crate::{read_relative, read_relative_iter, ModelError, Readable};
|
||||||
use binrw::BinReaderExt;
|
|
||||||
pub use raw::{BoneWeight, Tangent, Vertex};
|
pub use raw::{BoneWeight, Tangent, Vertex};
|
||||||
use std::io::Cursor;
|
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, ModelError>;
|
type Result<T> = std::result::Result<T, ModelError>;
|
||||||
|
|
||||||
|
|
@ -16,8 +14,7 @@ pub struct Vvd {
|
||||||
|
|
||||||
impl Vvd {
|
impl Vvd {
|
||||||
pub fn read(data: &[u8]) -> Result<Self> {
|
pub fn read(data: &[u8]) -> Result<Self> {
|
||||||
let mut reader = Cursor::new(data);
|
let header = <VvdHeader as Readable>::read(data)?;
|
||||||
let header: VvdHeader = reader.read_le()?;
|
|
||||||
let source_vertices = read_relative(data, header.vertex_indexes(0).unwrap())?;
|
let source_vertices = read_relative(data, header.vertex_indexes(0).unwrap())?;
|
||||||
let vertices = if !header.has_fixups() {
|
let vertices = if !header.has_fixups() {
|
||||||
source_vertices
|
source_vertices
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
use crate::{index_range, Vector};
|
use crate::{index_range, Vector};
|
||||||
use binrw::{BinRead, BinResult, ReadOptions};
|
use bytemuck::{Pod, Zeroable};
|
||||||
use bytemuck::{cast, Pod, Zeroable};
|
|
||||||
use std::io::{Read, Seek};
|
|
||||||
use std::mem::size_of;
|
use std::mem::size_of;
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead)]
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
pub struct VvdHeader {
|
pub struct VvdHeader {
|
||||||
pub id: i32,
|
pub id: i32,
|
||||||
pub version: i32,
|
pub version: i32,
|
||||||
|
|
@ -44,7 +43,7 @@ impl VvdHeader {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead, Zeroable, Pod, Copy)]
|
#[derive(Debug, Clone, Zeroable, Pod, Copy)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct VertexFileFixup {
|
pub struct VertexFileFixup {
|
||||||
pub lod: i32,
|
pub lod: i32,
|
||||||
|
|
@ -62,26 +61,8 @@ pub struct Vertex {
|
||||||
}
|
}
|
||||||
|
|
||||||
static_assertions::const_assert_eq!(size_of::<Vertex>(), 48);
|
static_assertions::const_assert_eq!(size_of::<Vertex>(), 48);
|
||||||
// binread_for_pod!(Vertex);
|
|
||||||
|
|
||||||
impl BinRead for Vertex {
|
#[derive(Debug, Clone, Zeroable, Pod, Copy)]
|
||||||
type Args = ();
|
|
||||||
|
|
||||||
fn read_options<R: Read + Seek>(
|
|
||||||
reader: &mut R,
|
|
||||||
_options: &ReadOptions,
|
|
||||||
_args: Self::Args,
|
|
||||||
) -> BinResult<Self> {
|
|
||||||
let mut bytes = unsafe {
|
|
||||||
std::mem::MaybeUninit::<[u8; std::mem::size_of::<Self>()]>::uninit().assume_init()
|
|
||||||
};
|
|
||||||
|
|
||||||
reader.read(&mut bytes)?;
|
|
||||||
Ok(cast(bytes))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead, Zeroable, Pod, Copy)]
|
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct BoneWeight {
|
pub struct BoneWeight {
|
||||||
pub weight: [f32; 3],
|
pub weight: [f32; 3],
|
||||||
|
|
@ -91,7 +72,7 @@ pub struct BoneWeight {
|
||||||
|
|
||||||
static_assertions::const_assert_eq!(size_of::<BoneWeight>(), 16);
|
static_assertions::const_assert_eq!(size_of::<BoneWeight>(), 16);
|
||||||
|
|
||||||
#[derive(Debug, Clone, BinRead, Zeroable, Pod, Copy)]
|
#[derive(Debug, Clone, Zeroable, Pod, Copy)]
|
||||||
#[repr(C)]
|
#[repr(C)]
|
||||||
pub struct Tangent {
|
pub struct Tangent {
|
||||||
pub x: f32,
|
pub x: f32,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue