#![deny(clippy::disallowed_methods, clippy::disallowed_types)]
use std::collections::BTreeMap;
#[derive(Clone, Debug, PartialEq, Eq, thiserror::Error)]
pub enum Error {
#[error("unsupported DXGI format {id}: expected one of 2, 6, 10, 13, 16, 24, 28, 34, 35, 37")]
UnsupportedFormat {
id: u32,
},
#[error("element of {actual} bytes: expected {expected} for {format:?}")]
ElementSize {
format: Format,
expected: usize,
actual: usize,
},
#[error("{what} is not finite: expected a finite number")]
NonFinite {
what: &'static str,
},
#[error("{what} overflows: expected offsets that fit {} bits", usize::BITS)]
Overflow {
what: &'static str,
},
#[error(
"element at offset {offset} of {size} bytes does not fit a stride of {stride}: expected offset + size <= stride"
)]
ElementBeyondStride {
offset: usize,
size: usize,
stride: usize,
},
#[error(
"stream of {actual} bytes ends before byte {needed}, where vertex {last_vertex} ends: expected at least {needed} bytes"
)]
StreamTooShort {
needed: usize,
actual: usize,
last_vertex: usize,
},
#[error("vertex budget {budget}: expected 3..=65535")]
VertexBudget {
budget: usize,
},
#[error("triangle list of {len} indices: expected a multiple of 3")]
PartialTriangle {
len: usize,
},
#[error("index {index} at position {position}: expected below the vertex count {vertex_count}")]
IndexOutOfRange {
position: usize,
index: u32,
vertex_count: usize,
},
}
pub type Result<T> = std::result::Result<T, Error>;
fn finite(what: &'static str, v: &[f32]) -> Result<()> {
if v.iter().all(|x| x.is_finite()) {
Ok(())
} else {
Err(Error::NonFinite { what })
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(u32)]
pub enum Format {
Float4 = 2,
Float3 = 6,
Half4 = 10,
Snorm16x4 = 13,
Float2 = 16,
Unorm10x3A2 = 24,
Unorm8x4 = 28,
Half2 = 34,
Unorm16x2 = 35,
Snorm16x2 = 37,
}
impl Format {
pub fn from_dxgi(id: u32) -> Result<Self> {
Ok(match id {
2 => Self::Float4,
6 => Self::Float3,
10 => Self::Half4,
13 => Self::Snorm16x4,
16 => Self::Float2,
24 => Self::Unorm10x3A2,
28 => Self::Unorm8x4,
34 => Self::Half2,
35 => Self::Unorm16x2,
37 => Self::Snorm16x2,
_ => return Err(Error::UnsupportedFormat { id }),
})
}
pub fn size(self) -> usize {
match self {
Self::Float4 => 16,
Self::Float3 => 12,
Self::Half4 | Self::Snorm16x4 | Self::Float2 => 8,
_ => 4,
}
}
pub fn decode(self, data: &[u8]) -> Result<[f32; 4]> {
let out = self.decode_raw(data)?;
finite("decoded element", &out)?;
Ok(out)
}
pub fn decode_raw(self, data: &[u8]) -> Result<[f32; 4]> {
if data.len() != self.size() {
return Err(Error::ElementSize {
format: self,
expected: self.size(),
actual: data.len(),
});
}
let mut out = [0.; 4];
match self {
Self::Float2 | Self::Float3 | Self::Float4 => {
for (o, b) in out.iter_mut().zip(data.as_chunks::<4>().0) {
*o = f32::from_le_bytes(*b);
}
}
Self::Half2 | Self::Half4 => {
for (o, b) in out.iter_mut().zip(data.as_chunks::<2>().0) {
*o = half::f16::from_bits(u16::from_le_bytes(*b)).to_f32();
}
}
Self::Snorm16x2 | Self::Snorm16x4 => {
for (o, b) in out.iter_mut().zip(data.as_chunks::<2>().0) {
*o = snorm16(i16::from_le_bytes(*b));
}
}
Self::Unorm16x2 => {
for (o, b) in out.iter_mut().zip(data.as_chunks::<2>().0) {
*o = u16::from_le_bytes(*b) as f32 / 65535.;
}
}
Self::Unorm8x4 => {
for (o, b) in out.iter_mut().zip(data) {
*o = f32::from(*b) / 255.;
}
}
Self::Unorm10x3A2 => {
let p = u32::from_le_bytes(data.first_chunk().copied().unwrap_or_default());
out = [
(p & 1023) as f32 / 1023.,
((p >> 10) & 1023) as f32 / 1023.,
((p >> 20) & 1023) as f32 / 1023.,
(p >> 30) as f32 / 3.,
];
}
}
Ok(out)
}
}
pub fn snorm16(v: i16) -> f32 {
(v as f32 / 32767.).max(-1.)
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Element {
pub semantic: String,
pub index: u32,
pub format: Format,
pub slot: u32,
pub offset: Option<usize>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ResolvedElement {
pub element: Element,
pub offset: usize,
}
pub fn resolve_layout(elements: &[Element]) -> Result<Vec<ResolvedElement>> {
let mut cursors = BTreeMap::<u32, usize>::new();
let mut result = Vec::new();
for e in elements {
let cur = cursors.entry(e.slot).or_default();
let offset = e.offset.unwrap_or(*cur);
*cur = cur.checked_add(e.format.size()).ok_or(Error::Overflow {
what: "slot's append offset",
})?;
offset
.checked_add(e.format.size())
.ok_or(Error::Overflow { what: "element end" })?;
result.push(ResolvedElement {
element: e.clone(),
offset,
});
}
Ok(result)
}
#[derive(Clone, Copy, Debug)]
pub struct Stream<'a> {
pub bytes: &'a [u8],
pub stride: usize,
pub offset: usize,
}
pub fn decode_stream(
element: &ResolvedElement,
stream: Stream<'_>,
first: usize,
count: usize,
) -> Result<Vec<[f32; 4]>> {
stream_with(element, stream, first, count, Format::decode)
}
pub fn decode_stream_raw(
element: &ResolvedElement,
stream: Stream<'_>,
first: usize,
count: usize,
) -> Result<Vec<[f32; 4]>> {
stream_with(element, stream, first, count, Format::decode_raw)
}
fn stream_with(
element: &ResolvedElement,
stream: Stream<'_>,
first: usize,
count: usize,
decode: fn(Format, &[u8]) -> Result<[f32; 4]>,
) -> Result<Vec<[f32; 4]>> {
let size = element.element.format.size();
let end = element
.offset
.checked_add(size)
.ok_or(Error::Overflow { what: "element end" })?;
if stream.stride == 0 || end > stream.stride {
return Err(Error::ElementBeyondStride {
offset: element.offset,
size,
stride: stream.stride,
});
}
let overflow = Error::Overflow {
what: "stream offset",
};
let start = first
.checked_mul(stream.stride)
.and_then(|x| x.checked_add(stream.offset))
.and_then(|x| x.checked_add(element.offset))
.ok_or(overflow.clone())?;
let last = match count.checked_sub(1) {
None => start,
Some(n) => n
.checked_mul(stream.stride)
.and_then(|x| x.checked_add(start))
.and_then(|x| x.checked_add(size))
.ok_or(overflow)?,
};
if last > stream.bytes.len() {
return Err(Error::StreamTooShort {
needed: last,
actual: stream.bytes.len(),
last_vertex: first.saturating_add(count.saturating_sub(1)),
});
}
let too_short = Error::StreamTooShort {
needed: last,
actual: stream.bytes.len(),
last_vertex: first.saturating_add(count.saturating_sub(1)),
};
stream
.bytes
.get(start..)
.unwrap_or_default()
.chunks(stream.stride)
.take(count)
.map(|vertex| match vertex.get(..size) {
Some(bytes) => decode(element.element.format, bytes),
None => Err(too_short.clone()),
})
.collect()
}
pub fn scale_translate(v: [f32; 3], scale: [f32; 3], translate: [f32; 3]) -> Result<[f32; 3]> {
finite("position", &v)?;
finite("scale", &scale)?;
finite("translation", &translate)?;
let ([x, y, z], [sx, sy, sz], [tx, ty, tz]) = (v, scale, translate);
let r = [x * sx + tx, y * sy + ty, z * sz + tz];
finite("scaled position", &r)?;
Ok(r)
}
pub fn transform_uv(uv: [f32; 2], t: [f32; 4]) -> Result<[f32; 2]> {
finite("uv", &uv)?;
finite("uv transform", &t)?;
let ([u, v], [u_offset, u_scale, v_offset, v_scale]) = (uv, t);
let r = [u * u_scale + u_offset, 1. - (v * v_scale + v_offset)];
finite("transformed uv", &r)?;
Ok(r)
}
pub fn normalize(v: [f32; 3]) -> Result<[f32; 3]> {
finite("normal", &v)?;
let [x, y, z] = v;
let len = (x * x + y * y + z * z).sqrt();
if !len.is_finite() {
return Err(Error::Overflow {
what: "normal length",
});
}
Ok(if len > 0. { v.map(|x| x / len) } else { v })
}
pub fn tangent(p: u32) -> Result<[f32; 4]> {
let n = normalize([
(p & 1023) as f32 / 1023. * 2. - 1.,
((p >> 10) & 1023) as f32 / 1023. * 2. - 1.,
((p >> 20) & 1023) as f32 / 1023. * 2. - 1.,
])?;
Ok([n[0], n[1], n[2], if p >> 31 != 0 { 1. } else { -1. }])
}
pub fn split_normal(position_w: f32, yz: [i16; 2]) -> Result<[f32; 3]> {
normalize([position_w, snorm16(yz[0]), snorm16(yz[1])])
}
pub fn packed_position32(p: u32, scale: [f32; 3], offset: [f32; 3]) -> Result<[f32; 3]> {
scale_translate(
[(p & 0x7ff) as f32, ((p >> 11) & 0x7ff) as f32, (p >> 22) as f32],
scale,
offset,
)
}
pub fn packed_position64(p: u64, min: [f32; 3], max: [f32; 3]) -> Result<[f32; 3]> {
finite("box minimum", &min)?;
finite("box maximum", &max)?;
let ([min_x, min_y, min_z], [max_x, max_y, max_z]) = (min, max);
let scale = [
(max_x - min_x) / 2097151.,
(max_y - min_y) / 2097151.,
(max_z - min_z) / 4194303.,
];
scale_translate(
[
(p & 0x1fffff) as f32,
((p >> 21) & 0x1fffff) as f32,
(p >> 42) as f32,
],
scale,
min,
)
}
pub fn strip_to_list(strip: &[u32], reverse: bool) -> Vec<u32> {
let mut out = Vec::new();
for (i, t) in strip.windows(3).enumerate() {
let &[a, b, c] = t else { continue };
if a == b || b == c || a == c {
continue;
}
if (i % 2 == 1) ^ reverse {
out.extend_from_slice(&[b, a, c])
} else {
out.extend_from_slice(&[a, b, c])
}
}
out
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum IndexWidth {
U16,
U32,
}
pub fn index_width(indices: &[u32]) -> IndexWidth {
if indices.iter().all(|&i| i <= u16::MAX as u32) {
IndexWidth::U16
} else {
IndexWidth::U32
}
}
#[derive(Clone, Debug, PartialEq, Eq, Default)]
pub struct Chunk {
pub vertices: Vec<u32>,
pub indices: Vec<u32>,
}
pub fn split_triangles(indices: &[u32], vertex_count: usize) -> Result<Vec<Chunk>> {
split_by_vertex_budget(indices, vertex_count, 65535)
}
pub fn split_by_vertex_budget(
indices: &[u32],
vertex_count: usize,
max_vertices: usize,
) -> Result<Vec<Chunk>> {
if !(3..=65535).contains(&max_vertices) {
return Err(Error::VertexBudget { budget: max_vertices });
}
if !indices.len().is_multiple_of(3) {
return Err(Error::PartialTriangle { len: indices.len() });
}
if let Some((position, &index)) = indices
.iter()
.enumerate()
.find(|&(_, &i)| i as usize >= vertex_count)
{
return Err(Error::IndexOutOfRange {
position,
index,
vertex_count,
});
}
let mut chunks = Vec::new();
let mut chunk = Chunk::default();
let mut map = BTreeMap::new();
for tri in indices.as_chunks::<3>().0 {
if chunk.vertices.len().saturating_add(3) > max_vertices {
chunks.push(std::mem::take(&mut chunk));
map.clear();
}
for &v in tri {
let next = u32::try_from(chunk.vertices.len()).map_err(|_| Error::Overflow {
what: "piece vertex number",
})?;
let n = *map.entry(v).or_insert_with(|| {
chunk.vertices.push(v);
next
});
chunk.indices.push(n);
}
}
if !chunk.indices.is_empty() {
chunks.push(chunk)
}
Ok(chunks)
}
#[allow(
clippy::cast_possible_truncation,
reason = "the documented f64 square root rounded to f32"
)]
pub fn renormalize_stored_normal(n: [f32; 3]) -> Result<[f32; 3]> {
finite("stored normal", &n)?;
let [x, y, z] = n;
let len = ((x * x + y * y + z * z) as f64).sqrt() as f32;
finite("stored normal length", &[len])?;
Ok(if (len - 1.).abs() > 1e-3 {
if len > 1e-12 {
n.map(|v| v / len)
} else {
[0., 0., 1.]
}
} else {
n
})
}