poe_data_tools 1.0.0

A library for working with Path of Exile game data
Documentation
use winnow::{
    Parser,
    binary::{le_f32, le_i8, le_u8, le_u16, le_u32},
    combinator::{cond, dispatch, empty, repeat, seq},
    token::literal,
};

use super::types::*;
use crate::file_parsers::shared::winnow::{WinnowParser, le_f16, repeat_array, take_array};

struct Header {
    c0h: u16,
    num_lods: u8,
    num_shapes: u16,
    vertex_format: u32,
}

fn header<'a>() -> impl WinnowParser<&'a [u8], Header> {
    let parser = seq!(Header {
        _: literal(b"DOLm"),
        c0h: le_u16,
        num_lods: le_u8,
        num_shapes: le_u16,
        vertex_format: le_u32,
    });

    winnow::trace!("dolm_header", parser)
}

pub fn index_buffer<'a>(
    num_vertices: u32,
    num_triangles: u32,
) -> impl WinnowParser<&'a [u8], IndexBuffer> {
    dispatch! {
        empty.value(num_vertices);
        ..0x10000 => repeat(num_triangles as usize * 3, le_u16).map(IndexBuffer::U16),
        0x10000.. => repeat(num_triangles as usize * 3, le_u32).map(IndexBuffer::U32),
    }
}

fn vertex<'a>(vertex_format: u32) -> impl WinnowParser<&'a [u8], DolmVertex> {
    let parser = seq!(DolmVertex {
        pos: repeat_array(le_f32),
        normal: repeat_array(le_i8),
        tangent: repeat_array(le_i8),
        tex_coord0: cond((vertex_format >> 3) & 1 == 1, repeat_array(le_f16)),
        tail_float: cond(false, repeat_array(le_f16)),
        skin_bones: cond((vertex_format >> 2) & 1 == 1, take_array()),
        skin_weights: cond((vertex_format >> 2) & 1 == 1, take_array()),
        skin_extra: cond((vertex_format >> 1) & 1 == 1, take_array()),
        tex_coord1: cond(vertex_format & 1 == 1, repeat_array(le_f16)),
        extra_vformat_6: cond((vertex_format >> 6) & 1 == 1, take_array()),
    });

    winnow::trace!("vertex", parser)
}

fn mesh<'a>(
    num_shapes: usize,
    num_triangles: u32,
    num_vertices: u32,
    vertex_format: u32,
) -> impl WinnowParser<&'a [u8], Mesh> {
    let shape_extents = repeat(
        num_shapes,
        repeat_array(le_u32).map(|[start_index, count_index]| DolmShapeExtents {
            start_index,
            count_index,
        }),
    );

    let index_buffer = index_buffer(num_vertices, num_triangles);

    let vertex_buffer = repeat(num_vertices as usize, vertex(vertex_format));

    let parser = (shape_extents, index_buffer, vertex_buffer).map(|(e, i, v)| Mesh {
        shape_extents: e,
        indices: i,
        vertices: v,
    });

    winnow::trace!("mesh", parser)
}

pub fn dolm<'a>() -> impl WinnowParser<&'a [u8], Dolm> {
    let parser = |input: &mut &[u8]| {
        let header = header().parse_next(input)?;

        let lod_extents: Vec<_> =
            repeat(header.num_lods as usize, repeat_array(le_u32)).parse_next(input)?;

        let lods = lod_extents
            .iter()
            .map(|[num_triangles, num_vertices]| {
                mesh(
                    header.num_shapes as usize,
                    *num_triangles,
                    *num_vertices,
                    header.vertex_format,
                )
                .parse_next(input)
            })
            .collect::<winnow::Result<Vec<_>>>()?;

        let (extra_vformat_6, extra_vformat_6_c0h_2, extra_c0h_4) = (
            cond(
                (header.vertex_format >> 6) & 1 == 1,
                repeat(header.num_shapes as usize, take_array::<36, _>()),
            ),
            cond(
                header.c0h == 2 && (header.vertex_format >> 6) & 1 == 1,
                repeat(header.num_shapes as usize, take_array::<4, _>()),
            ),
            cond(header.c0h == 4 && header.num_lods > 0, take_array::<4, _>()),
        )
            .parse_next(input)?;

        Ok(Dolm {
            c0h: header.c0h,
            vertex_format: header.vertex_format,
            lod_extents,
            lods,
            extra_vformat_6,
            extra_vformat_6_c0h_2,
            extra_c0h_4,
        })
    };

    winnow::trace!("dolm", parser)
}