1use crate::format::{CubeDef, ModelFile};
6use crate::math::{Mat4, Vec3, rad};
7
8#[derive(Debug, Clone, Copy, PartialEq)]
10pub struct Quad {
11 pub pos: [Vec3; 4],
12 pub uv: [[f32; 2]; 4],
13 pub normal: Vec3,
14}
15
16impl Quad {
17 pub fn transformed(&self, m: &Mat4) -> Quad {
19 Quad {
20 pos: self.pos.map(|p| m.transform_point(p)),
21 uv: self.uv,
22 normal: m.transform_normal(self.normal),
23 }
24 }
25
26 pub fn center(&self) -> Vec3 {
28 self.pos.iter().fold(Vec3::ZERO, |sum, &p| sum + p) * (1.0 / self.pos.len() as f32)
29 }
30}
31
32#[derive(Debug, Clone)]
34pub struct Bone {
35 pub name: String,
36 pub parent: Option<usize>,
38 pub pivot: Vec3,
40 pub rotation: Vec3,
42 pub quads: Vec<Quad>,
44 pub locators: Vec<(String, Vec3)>,
45}
46
47#[derive(Debug, Clone)]
49pub struct Model {
50 pub texture: String,
52 pub bones: Vec<Bone>,
53}
54
55impl Model {
56 pub fn new(file: &ModelFile) -> Result<Model, String> {
59 let texture = texture_resource(&file.texture);
60 let [tw, th] = file.texture_size.map(|n| n.max(1) as f32);
61 let mut bones: Vec<Bone> = Vec::with_capacity(file.bones.len());
62 for b in &file.bones {
63 if bones.iter().any(|o| o.name == b.name) {
64 return Err(format!("bone `{}` comes twice", b.name));
65 }
66 let parent = match &b.parent {
67 None => None,
68 Some(p) => Some(bones.iter().position(|o| &o.name == p).ok_or_else(|| {
69 format!("bone `{}`: its parent `{p}` must come before it", b.name)
70 })?),
71 };
72 bones.push(Bone {
73 name: b.name.clone(),
74 parent,
75 pivot: Vec3::from_array(b.pivot),
76 rotation: rad(Vec3::from_array(b.rotation)),
77 quads: b.cubes.iter().flat_map(|c| cube(c, tw, th)).collect(),
78 locators: b
79 .locators
80 .0
81 .iter()
82 .map(|(n, p)| (n.clone(), Vec3::from_array(*p)))
83 .collect(),
84 });
85 }
86 Ok(Model { texture, bones })
87 }
88
89 pub fn bone(&self, name: &str) -> Option<usize> {
90 self.bones.iter().position(|b| b.name == name)
91 }
92}
93
94pub fn texture_resource(id: &str) -> String {
96 let (ns, path) = yog_ids::split(id);
97 yog_ids::join(ns, &yog_ids::texture_file(path))
98}
99
100pub fn cube(c: &CubeDef, tex_w: f32, tex_h: f32) -> [Quad; 6] {
102 let [x, y, z] = c.origin;
103 let [w, h, d] = c.size;
104 let [u, v] = c.uv;
105 let g = c.inflate;
106 let (mut x0, y0, z0) = (x - g, y - g, z - g);
107 let (mut x1, y1, z1) = (x + w + g, y + h + g, z + d + g);
108 if c.mirror {
109 std::mem::swap(&mut x0, &mut x1);
110 }
111 let p = |x, y, z| Vec3::new(x, y, z);
112 let (v7, v0, v1, v2) = (p(x0, y0, z0), p(x1, y0, z0), p(x1, y1, z0), p(x0, y1, z0));
113 let (v3, v4, v5, v6) = (p(x0, y0, z1), p(x1, y0, z1), p(x1, y1, z1), p(x0, y1, z1));
114 let (u0, u1, u2, u3, u4, u5) = (
115 u,
116 u + d,
117 u + d + w,
118 u + d + w + w,
119 u + d + w + d,
120 u + d + w + d + w,
121 );
122 let (t0, t1, t2) = (v, v + d, v + d + h);
123 let face = |corners: [Vec3; 4], ua: f32, va: f32, ub: f32, vb: f32, normal: Vec3| {
124 let mut pos = corners;
125 let mut uv = [
126 [ub / tex_w, va / tex_h],
127 [ua / tex_w, va / tex_h],
128 [ua / tex_w, vb / tex_h],
129 [ub / tex_w, vb / tex_h],
130 ];
131 let mut n = normal;
132 if c.mirror {
133 pos.reverse();
134 uv.reverse();
135 n.x = -n.x;
136 }
137 Quad { pos, uv, normal: n }
138 };
139 [
140 face([v4, v3, v7, v0], u1, t0, u2, t1, Vec3::new(0.0, -1.0, 0.0)),
141 face([v1, v2, v6, v5], u2, t1, u3, t0, Vec3::new(0.0, 1.0, 0.0)),
142 face([v7, v3, v6, v2], u0, t1, u1, t2, Vec3::new(-1.0, 0.0, 0.0)),
143 face([v0, v7, v2, v1], u1, t1, u2, t2, Vec3::new(0.0, 0.0, -1.0)),
144 face([v4, v0, v1, v5], u2, t1, u4, t2, Vec3::new(1.0, 0.0, 0.0)),
145 face([v3, v4, v5, v6], u4, t1, u5, t2, Vec3::new(0.0, 0.0, 1.0)),
146 ]
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 fn wing() -> CubeDef {
154 CubeDef {
155 origin: [-10.0, 0.0, 0.0],
156 size: [10.0, 20.0, 2.0],
157 uv: [22.0, 0.0],
158 inflate: 1.0,
159 mirror: false,
160 }
161 }
162
163 #[test]
164 fn a_cube_is_vanillas() {
165 let q = cube(&wing(), 64.0, 32.0);
166 assert_eq!(q[0].pos[0], Vec3::new(1.0, -1.0, 3.0));
168 assert_eq!(q[0].uv[0], [34.0 / 64.0, 0.0]);
169 assert_eq!(q[0].uv[1], [24.0 / 64.0, 0.0]);
170 assert_eq!(q[3].normal, Vec3::new(0.0, 0.0, -1.0));
171 }
172
173 #[test]
174 fn a_mirrored_cube_turns_its_faces() {
175 let mut c = wing();
176 c.mirror = true;
177 let q = cube(&c, 64.0, 32.0);
178 assert_eq!(q[2].normal, Vec3::new(1.0, 0.0, 0.0));
179 assert_eq!(q[0].pos[3], Vec3::new(-11.0, -1.0, 3.0));
180 }
181
182 #[test]
183 fn a_texture_is_loaded_from_its_resource() {
184 assert_eq!(
185 texture_resource("minecraft:entity/elytra"),
186 "minecraft:textures/entity/elytra.png"
187 );
188 }
189}