1use super::mesh::HeightGrid;
10
11type Vec3 = [f32; 3];
12
13fn sub(a: Vec3, b: Vec3) -> Vec3 {
14 [a[0] - b[0], a[1] - b[1], a[2] - b[2]]
15}
16
17fn cross(a: Vec3, b: Vec3) -> Vec3 {
19 [
20 a[1] * b[2] - a[2] * b[1],
21 b[0] * a[2] - a[0] * b[2],
22 a[0] * b[1] - b[0] * a[1],
23 ]
24}
25
26fn dot(a: Vec3, b: Vec3) -> f32 {
27 a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
28}
29
30fn normalize(v: Vec3) -> Vec3 {
32 let len = (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]).sqrt();
33 if len == 0.0 {
34 return v;
35 }
36 let inv = 1.0 / len;
37 [v[0] * inv, v[1] * inv, v[2] * inv]
38}
39
40const EPSILON: f32 = -1e-4;
41const RAY_TOP: f32 = 100.0;
42const RAY_LENGTH: f32 = 200.0;
43
44#[allow(clippy::too_many_arguments, clippy::neg_cmp_op_on_partial_ord)]
47fn ray_triangle(
48 origin: Vec3,
49 dir: Vec3,
50 tmax: f32,
51 v0: Vec3,
52 v1: Vec3,
53 v2: Vec3,
54 t: &mut f32,
55 normal: &mut Vec3,
56) -> bool {
57 let e1 = sub(v1, v0);
58 let e2 = sub(v2, v0);
59 let p = cross(dir, e2);
60 let det = dot(e1, p);
61 let s = sub(origin, v0);
62 let q = cross(s, e1);
63 let u = dot(s, p);
64 let v = dot(dir, q);
65 if !(det > 0.0) || EPSILON > u || u > det || EPSILON > v || v + u > det {
67 return false;
68 }
69 let hit = 1.0 / det * dot(e2, q);
70 *t = hit;
71 if hit < 0.0 || tmax < hit {
72 return false;
73 }
74 *normal = normalize(cross(e1, e2));
75 true
76}
77
78impl HeightGrid<'_> {
79 pub fn intersect(&self, origin: Vec3, direction: Vec3, maximum: f32) -> Option<(f32, Vec3)> {
87 let n = self.width as i32 - 1;
88 let half = n >> 1;
89 let end = [
90 origin[0] + direction[0] * maximum,
91 origin[1] + direction[1] * maximum,
92 ];
93 let lo = std::array::from_fn::<_, 2, _>(|k| {
94 ((origin[k].min(end[k]) + half as f32).floor() as i32 - 1).clamp(0, n - 1)
95 });
96 let hi = std::array::from_fn::<_, 2, _>(|k| {
97 ((origin[k].max(end[k]) + half as f32).floor() as i32 + 1).clamp(0, n - 1)
98 });
99 let mut limit = maximum;
100 let mut found = false;
101 let mut normal = [0.; 3];
102 for y in lo[1]..=hi[1] {
103 for x in lo[0]..=hi[0] {
104 let [a, b, c, d] = [
105 self.corner(x, y, half),
106 self.corner(x + 1, y, half),
107 self.corner(x + 1, y + 1, half),
108 self.corner(x, y + 1, half),
109 ];
110 let triangles = if (x + y) & 1 == 0 {
111 [[a, b, d], [b, c, d]]
112 } else {
113 [[a, b, c], [a, c, d]]
114 };
115 for [a, b, c] in triangles {
116 let mut t = limit;
117 if ray_triangle(origin, direction, limit, a, b, c, &mut t, &mut normal) {
118 limit = t;
119 found = true;
120 }
121 }
122 }
123 }
124 found.then(|| {
125 (
126 limit,
127 [
128 origin[0] + direction[0] * limit,
129 origin[1] + direction[1] * limit,
130 origin[2] + direction[2] * limit,
131 ],
132 )
133 })
134 }
135
136 fn corner(&self, x: i32, y: i32, half: i32) -> Vec3 {
137 let hi = self.width as i32 - 1;
138 let h = self.heights[y.clamp(0, hi) as usize * self.width + x.clamp(0, hi) as usize];
139 [
140 (x - half) as f32,
141 (y - half) as f32,
142 h as i32 as f32 * 0.00390625,
143 ]
144 }
145
146 fn raycast_cell(
149 &self,
150 x: i32,
151 y: i32,
152 dir: Vec3,
153 origin: Vec3,
154 tmax: f32,
155 normal: &mut Vec3,
156 ) -> (bool, f32) {
157 let half = (self.width as i32 - 1) >> 1;
158 let a = self.corner(x, y, half);
159 let b = self.corner(x + 1, y, half);
160 let c = self.corner(x + 1, y + 1, half);
161 let d = self.corner(x, y + 1, half);
162 let (first, second) = if (x + y) & 1 == 0 {
163 ([a, b, d], [b, c, d])
164 } else {
165 ([a, b, c], [a, c, d])
166 };
167 let mut limit = tmax;
168 let mut t = 0.0f32;
169 let mut hit = false;
170 if ray_triangle(
171 origin, dir, limit, first[0], first[1], first[2], &mut t, normal,
172 ) {
173 limit = t;
174 hit = true;
175 }
176 if ray_triangle(
177 origin, dir, limit, second[0], second[1], second[2], &mut t, normal,
178 ) {
179 hit = true;
180 }
181 (hit, t)
182 }
183
184 pub fn normal_below(&self, x: f32, y: f32, top: f32, length: f32) -> [f32; 3] {
189 let half = ((self.width as i32 - 1) >> 1) as f32;
190 let mut normal = [0.0; 3];
191 self.raycast_cell(
192 (x + half) as i32,
193 (y + half) as i32,
194 [0.0, 0.0, -1.0],
195 [x, y, top],
196 length,
197 &mut normal,
198 );
199 normal
200 }
201
202 pub fn ground(&self, x: f32, y: f32, normal: &mut [f32; 3]) -> f32 {
207 let half = ((self.width as i32 - 1) >> 1) as f32;
208 let ix = (x + half) as i32;
209 let iy = (y + half) as i32;
210 let dir = [0.0, 0.0, -1.0];
211 let origin = [x, y, RAY_TOP];
212 let (hit, t) = self.raycast_cell(ix, iy, dir, origin, RAY_LENGTH, normal);
213 if hit {
214 t * dir[2] + origin[2]
215 } else {
216 0.0
217 }
218 }
219
220 pub fn natural_cell_type(&self, map_size: usize, index: usize) -> CellType {
225 let size = map_size as i32;
226 let index = index as i32;
227 let offset = (size >> 1) as f32 - 0.5;
228 let x = (index % size) as f32 - offset;
229 let y = (index / size) as f32 - offset;
230 let mut normal = [0.0; 3];
231 let h = self.ground(x, y, &mut normal);
232 CellType::classify(h, normal[2])
233 }
234}
235
236#[derive(Debug, Clone, Copy, PartialEq, Eq)]
240pub struct CellType(pub u8);
241
242impl CellType {
243 pub const GROUND: CellType = CellType(2);
245 pub const HIGH: CellType = CellType(6);
247 pub const SHALLOW_WATER: CellType = CellType(7);
249 pub const DEEP_WATER: CellType = CellType(8);
251 pub const ROUGH: CellType = CellType(10);
253
254 pub fn classify(height: f32, up: f32) -> CellType {
260 if height < -1.4 {
261 return CellType::DEEP_WATER;
262 }
263 if height < -0.25 {
264 return CellType::SHALLOW_WATER;
265 }
266 if (-0.25..=0.75).contains(&height) && 0.9 <= up {
267 return CellType::GROUND;
268 }
269 if height < 2.0 {
270 CellType::ROUGH
271 } else {
272 CellType::HIGH
273 }
274 }
275
276 pub fn blocks_movement(self) -> bool {
278 (1u32 << self.0) & 0x1e0 != 0
279 }
280}