oxygengine_composite_renderer/
mesh_asset_protocol.rs

1use crate::{component::CompositeTransform, composite_renderer::TriangleFace, math::Vec2};
2use core::{
3    assets::protocol::{AssetLoadResult, AssetProtocol},
4    Ignite, Scalar,
5};
6use serde::{Deserialize, Serialize};
7use std::{collections::HashMap, str::from_utf8};
8
9#[derive(Ignite, Debug, Default, Copy, Clone, Serialize, Deserialize)]
10pub struct MeshFace {
11    pub a: usize,
12    pub b: usize,
13    pub c: usize,
14}
15
16#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
17pub struct MeshVertexBoneInfo {
18    #[serde(default)]
19    pub name: String,
20    #[serde(default)]
21    pub weight: Scalar,
22}
23
24#[derive(Ignite, Debug, Default, Clone, Serialize, Deserialize)]
25pub struct MeshVertex {
26    pub position: Vec2,
27    pub tex_coord: Vec2,
28    #[serde(default)]
29    pub bone_info: Vec<MeshVertexBoneInfo>,
30}
31
32impl MeshVertex {
33    pub fn fix_bone_info(&mut self) {
34        let total_weight = self.bone_info.iter().map(|i| i.weight).sum::<Scalar>();
35        if total_weight > 0.0 {
36            for info in &mut self.bone_info {
37                info.weight /= total_weight;
38            }
39        }
40    }
41}
42
43#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
44pub struct MeshBone {
45    pub transform: CompositeTransform,
46    #[serde(default)]
47    pub children: HashMap<String, MeshBone>,
48}
49
50impl MeshBone {
51    pub fn bones_count(&self) -> usize {
52        self.children
53            .values()
54            .map(|child| child.bones_count())
55            .sum::<usize>()
56            + 1
57    }
58}
59
60#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
61pub struct SubMesh {
62    pub faces: Vec<MeshFace>,
63    #[serde(default)]
64    pub masks: Vec<usize>,
65    #[serde(skip)]
66    #[ignite(ignore)]
67    cached_faces: Vec<TriangleFace>,
68}
69
70impl SubMesh {
71    pub fn cached_faces(&self) -> &[TriangleFace] {
72        &self.cached_faces
73    }
74
75    pub fn cache(&mut self) {
76        self.cached_faces = self
77            .faces
78            .iter()
79            .map(|f| TriangleFace::new(f.a, f.b, f.c))
80            .collect::<Vec<_>>()
81    }
82}
83
84#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
85pub struct MeshMask {
86    pub indices: Vec<usize>,
87    #[serde(default = "MeshMask::default_enabled")]
88    pub enabled: bool,
89}
90
91impl MeshMask {
92    fn default_enabled() -> bool {
93        true
94    }
95}
96
97#[derive(Ignite, Debug, Clone, Serialize, Deserialize)]
98pub struct Mesh {
99    pub vertices: Vec<MeshVertex>,
100    pub submeshes: Vec<SubMesh>,
101    #[serde(default)]
102    pub masks: Vec<MeshMask>,
103    #[serde(default)]
104    pub rig: Option<MeshBone>,
105}
106
107impl Mesh {
108    pub fn initialize(&mut self) {
109        for vertex in &mut self.vertices {
110            vertex.fix_bone_info();
111        }
112        for submesh in &mut self.submeshes {
113            submesh.cache();
114        }
115    }
116}
117
118pub struct MeshAsset(Mesh);
119
120impl MeshAsset {
121    pub fn mesh(&self) -> &Mesh {
122        &self.0
123    }
124}
125
126pub struct MeshAssetProtocol;
127
128impl AssetProtocol for MeshAssetProtocol {
129    fn name(&self) -> &str {
130        "mesh"
131    }
132
133    fn on_load_with_path(&mut self, path: &str, data: Vec<u8>) -> AssetLoadResult {
134        let mut mesh = if path.ends_with(".json") {
135            let data = from_utf8(&data).unwrap();
136            serde_json::from_str::<Mesh>(data).unwrap()
137        } else if path.ends_with(".yaml") {
138            let data = from_utf8(&data).unwrap();
139            serde_yaml::from_str::<Mesh>(data).unwrap()
140        } else {
141            bincode::deserialize::<Mesh>(&data).unwrap()
142        };
143        mesh.initialize();
144        AssetLoadResult::Data(Box::new(MeshAsset(mesh)))
145    }
146
147    // on_load_with_path() handles loading so this is not needed, so we just make it unreachable.
148    fn on_load(&mut self, _data: Vec<u8>) -> AssetLoadResult {
149        unreachable!()
150    }
151}