Skip to main content

banished_gfx/
model.rs

1//! CPU-side description of a `GraphicsMesh`'s draw ranges, shared by the viewer and the game:
2//! which material each range uses, which shader kind that is, its textures and its rigid node
3//! transform. GPU upload and skinning strategy stay with each app.
4
5use banished_formats::skeleton::{local_bind, world_transforms, Transform};
6use banished_formats::types::{DrawRange, GraphicsMesh, MaterialInstance, MeshPart};
7
8use crate::material::MaterialKind;
9
10/// Everything about one draw range except its geometry bytes.
11#[derive(Debug, Clone)]
12pub struct RangeMaterial {
13    /// The shader the range is drawn with.
14    pub kind: MaterialKind,
15    /// The reference of the `diffuse` texture.
16    pub diffuse: Option<String>,
17    /// The reference of the `snow` texture, for materials that hold snow.
18    pub snow: Option<String>,
19    /// The damaged materials' `damage` texture (`OpaqueSnowDamageMaterial` and the like).
20    pub damage: Option<String>,
21    /// Skinned ranges (`bone < 0`) go through the skin palette; rigid ones use `rigid`.
22    pub skinned: bool,
23    /// Rigid node index (`bone >= 0`).
24    pub rigid_bone: Option<usize>,
25    /// The rigid node's bind world transform (identity for skinned ranges).
26    pub rigid: Transform,
27}
28
29/// The material set a mesh is drawn with (falls back to the first).
30pub fn material_set(mesh: &GraphicsMesh, set: usize) -> Option<&[Option<String>]> {
31    mesh.material_instances
32        .get(set)
33        .or_else(|| mesh.material_instances.first())
34        .map(Vec::as_slice)
35}
36
37/// Describe a range: resolve its material instance (`resolve` reads a reference) and pick the
38/// shader kind and texture references the ported shaders sample.
39pub fn range_material(
40    world: &[Transform],
41    set: Option<&[Option<String>]>,
42    range: &DrawRange,
43    resolve: &mut impl FnMut(&str) -> Option<MaterialInstance>,
44) -> RangeMaterial {
45    let instance = set
46        .and_then(|s| s.get(range.material_index as usize).cloned().flatten())
47        .and_then(|r| resolve(&r));
48    let texture = |slot: &str| {
49        instance
50            .as_ref()
51            .and_then(|mi| mi.texture(slot).map(str::to_owned))
52    };
53    let skinned = range.bone < 0;
54    let rigid_bone = usize::try_from(range.bone).ok();
55    RangeMaterial {
56        kind: MaterialKind::of(instance.as_ref().and_then(|mi| mi.material.as_deref())),
57        diffuse: texture("diffuse"),
58        snow: texture("snow"),
59        damage: texture("damage"),
60        skinned,
61        rigid_bone,
62        rigid: rigid_bone
63            .and_then(|b| world.get(b).copied())
64            .unwrap_or(Transform::IDENTITY),
65    }
66}
67
68/// Bind-pose world transforms of a mesh's nodes.
69pub fn bind_world(mesh: &GraphicsMesh) -> Vec<Transform> {
70    let locals: Vec<Transform> = mesh.bones.iter().map(local_bind).collect();
71    world_transforms(&mesh.bones, &locals)
72}
73
74/// The single rigid node all of a part's ranges hang from, if any: such meshes (trees) animate
75/// by moving the whole instance with that node.
76pub fn animation_bone(part: &MeshPart) -> Option<usize> {
77    let mut nodes = part
78        .ranges
79        .iter()
80        .filter(|r| r.index_count > 0)
81        .map(|r| r.bone);
82    nodes
83        .next()
84        .filter(|&first| first >= 0 && nodes.all(|bone| bone == first))
85        .map(|bone| bone as usize)
86}