1use glam::{Vec2, Vec3};
9use std::collections::HashMap;
10
11use super::font_to_mesh::{GlyphOutline, Contour, OutlineCache, assign_holes_to_outers, signed_area};
12
13#[repr(C)]
16#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
17pub struct Vertex3D {
18 pub position: [f32; 3],
19 pub normal: [f32; 3],
20 pub uv: [f32; 2],
21}
22
23impl Vertex3D {
24 pub fn new(position: Vec3, normal: Vec3, uv: Vec2) -> Self {
25 Self {
26 position: position.to_array(),
27 normal: normal.to_array(),
28 uv: uv.to_array(),
29 }
30 }
31
32 pub fn pos(&self) -> Vec3 { Vec3::from(self.position) }
33 pub fn norm(&self) -> Vec3 { Vec3::from(self.normal) }
34}
35
36#[derive(Clone, Debug)]
39pub struct GlyphMesh {
40 pub vertices: Vec<Vertex3D>,
41 pub indices: Vec<u32>,
42 pub character: char,
43 pub extrusion_depth: f32,
44 pub triangle_count: u32,
45 pub bounds_min: Vec3,
46 pub bounds_max: Vec3,
47}
48
49impl GlyphMesh {
50 pub fn vertex_count(&self) -> usize { self.vertices.len() }
51 pub fn index_count(&self) -> usize { self.indices.len() }
52
53 pub fn vertex_bytes(&self) -> &[u8] {
54 bytemuck::cast_slice(&self.vertices)
55 }
56
57 pub fn index_bytes(&self) -> &[u8] {
58 bytemuck::cast_slice(&self.indices)
59 }
60}
61
62#[derive(Clone, Debug)]
63pub struct MeshStats {
64 pub vertices: usize,
65 pub triangles: usize,
66 pub bytes: usize,
67}
68
69pub fn mesh_stats(mesh: &GlyphMesh) -> MeshStats {
70 MeshStats {
71 vertices: mesh.vertices.len(),
72 triangles: mesh.triangle_count as usize,
73 bytes: mesh.vertices.len() * std::mem::size_of::<Vertex3D>()
74 + mesh.indices.len() * std::mem::size_of::<u32>(),
75 }
76}
77
78pub fn point_in_triangle(p: Vec2, a: Vec2, b: Vec2, c: Vec2) -> bool {
82 let v0 = c - a;
83 let v1 = b - a;
84 let v2 = p - a;
85
86 let dot00 = v0.dot(v0);
87 let dot01 = v0.dot(v1);
88 let dot02 = v0.dot(v2);
89 let dot11 = v1.dot(v1);
90 let dot12 = v1.dot(v2);
91
92 let inv_denom = 1.0 / (dot00 * dot11 - dot01 * dot01);
93 let u = (dot11 * dot02 - dot01 * dot12) * inv_denom;
94 let v = (dot00 * dot12 - dot01 * dot02) * inv_denom;
95
96 u >= 0.0 && v >= 0.0 && u + v <= 1.0
97}
98
99fn cross2d(a: Vec2, b: Vec2) -> f32 {
101 a.x * b.y - a.y * b.x
102}
103
104fn is_convex(polygon: &[Vec2], prev: usize, curr: usize, next: usize) -> bool {
106 let a = polygon[prev];
107 let b = polygon[curr];
108 let c = polygon[next];
109 cross2d(b - a, c - b) > 0.0
110}
111
112fn is_ear(polygon: &[Vec2], indices: &[usize], prev_idx: usize, curr_idx: usize, next_idx: usize) -> bool {
114 let a = polygon[indices[prev_idx]];
115 let b = polygon[indices[curr_idx]];
116 let c = polygon[indices[next_idx]];
117
118 if cross2d(b - a, c - b) <= 0.0 {
120 return false;
121 }
122
123 for (i, &vi) in indices.iter().enumerate() {
125 if i == prev_idx || i == curr_idx || i == next_idx { continue; }
126 if point_in_triangle(polygon[vi], a, b, c) {
127 return false;
128 }
129 }
130
131 true
132}
133
134pub fn bridge_hole(outer: &[Vec2], hole: &[Vec2]) -> Vec<Vec2> {
136 if hole.is_empty() { return outer.to_vec(); }
137
138 let mut rightmost_idx = 0;
140 let mut max_x = f32::MIN;
141 for (i, p) in hole.iter().enumerate() {
142 if p.x > max_x {
143 max_x = p.x;
144 rightmost_idx = i;
145 }
146 }
147
148 let hp = hole[rightmost_idx];
150 let mut best_idx = 0;
151 let mut best_dist = f32::MAX;
152 for (i, p) in outer.iter().enumerate() {
153 let d = (p.x - hp.x).abs() + (p.y - hp.y).abs();
154 if d < best_dist {
155 best_dist = d;
156 best_idx = i;
157 }
158 }
159
160 let mut result = Vec::with_capacity(outer.len() + hole.len() + 2);
162 for i in 0..=best_idx {
163 result.push(outer[i]);
164 }
165 let n_hole = hole.len();
166 for i in 0..=n_hole {
167 result.push(hole[(rightmost_idx + i) % n_hole]);
168 }
169 result.push(outer[best_idx]);
171 for i in (best_idx + 1)..outer.len() {
172 result.push(outer[i]);
173 }
174
175 result
176}
177
178pub fn ear_clip_triangulate(outer: &[Vec2], holes: &[Vec<Vec2>]) -> Vec<[usize; 3]> {
181 let mut polygon = outer.to_vec();
183 for hole in holes {
184 polygon = bridge_hole(&polygon, hole);
185 }
186
187 if polygon.len() < 3 { return Vec::new(); }
188
189 if signed_area(&polygon) < 0.0 {
191 polygon.reverse();
192 }
193
194 let mut indices: Vec<usize> = (0..polygon.len()).collect();
195 let mut triangles = Vec::new();
196 let mut max_iters = polygon.len() * polygon.len();
197
198 while indices.len() > 2 && max_iters > 0 {
199 max_iters -= 1;
200 let n = indices.len();
201 let mut found_ear = false;
202
203 for i in 0..n {
204 let prev = (i + n - 1) % n;
205 let next = (i + 1) % n;
206
207 if is_ear(&polygon, &indices, prev, i, next) {
208 triangles.push([indices[prev], indices[i], indices[next]]);
209 indices.remove(i);
210 found_ear = true;
211 break;
212 }
213 }
214
215 if !found_ear {
216 if indices.len() > 2 {
218 let n = indices.len();
219 triangles.push([indices[0], indices[1], indices[2]]);
220 indices.remove(1);
221 } else {
222 break;
223 }
224 }
225 }
226
227 triangles
228}
229
230pub fn extrude_glyph(outline: &GlyphOutline, depth: f32, ch: char) -> GlyphMesh {
234 let assignments = assign_holes_to_outers(&outline.contours);
235
236 let mut all_vertices = Vec::new();
237 let mut all_indices = Vec::new();
238
239 let bounds = outline.bounds;
240 let bw = (bounds.max.x - bounds.min.x).max(0.001);
241 let bh = (bounds.max.y - bounds.min.y).max(0.001);
242
243 for (outer_idx, hole_indices) in &assignments {
244 let outer = &outline.contours[*outer_idx].points;
245 let holes: Vec<Vec<Vec2>> = hole_indices.iter()
246 .map(|&hi| outline.contours[hi].points.clone())
247 .collect();
248 let hole_refs: Vec<&[Vec2]> = holes.iter().map(|h| h.as_slice()).collect();
249
250 let mut all_points: Vec<Vec2> = outer.clone();
252 for h in &holes {
253 all_points.extend_from_slice(h);
254 }
255
256 let hole_vecs: Vec<Vec<Vec2>> = holes.clone();
258 let tris = ear_clip_triangulate(outer, &hole_vecs);
259
260 let mut merged = outer.clone();
262 for h in &holes {
263 merged = bridge_hole(&merged, h);
264 }
265
266 let base_idx = all_vertices.len() as u32;
267
268 for p in &merged {
270 let u = (p.x - bounds.min.x) / bw;
271 let v = (p.y - bounds.min.y) / bh;
272 all_vertices.push(Vertex3D::new(
273 Vec3::new(p.x, p.y, 0.0),
274 Vec3::Z,
275 Vec2::new(u, v),
276 ));
277 }
278
279 for tri in &tris {
280 all_indices.push(base_idx + tri[0] as u32);
281 all_indices.push(base_idx + tri[1] as u32);
282 all_indices.push(base_idx + tri[2] as u32);
283 }
284
285 let back_base = all_vertices.len() as u32;
287 for p in &merged {
288 let u = (p.x - bounds.min.x) / bw;
289 let v = (p.y - bounds.min.y) / bh;
290 all_vertices.push(Vertex3D::new(
291 Vec3::new(p.x, p.y, -depth),
292 -Vec3::Z,
293 Vec2::new(u, v),
294 ));
295 }
296
297 for tri in &tris {
298 all_indices.push(back_base + tri[2] as u32);
300 all_indices.push(back_base + tri[1] as u32);
301 all_indices.push(back_base + tri[0] as u32);
302 }
303
304 let mut edge_contours: Vec<&Vec<Vec2>> = vec![outer];
307 for h in &holes {
308 edge_contours.push(h);
309 }
310
311 for contour in edge_contours {
312 let n = contour.len();
313 for i in 0..n {
314 let j = (i + 1) % n;
315 let p0 = contour[i];
316 let p1 = contour[j];
317
318 let edge = p1 - p0;
320 let normal_2d = Vec2::new(edge.y, -edge.x).normalize_or_zero();
321 let normal = Vec3::new(normal_2d.x, normal_2d.y, 0.0);
322
323 let edge_len = edge.length();
324 let u0 = 0.0;
325 let u1 = edge_len / bw;
326
327 let side_base = all_vertices.len() as u32;
328
329 all_vertices.push(Vertex3D::new(Vec3::new(p0.x, p0.y, 0.0), normal, Vec2::new(u0, 0.0)));
331 all_vertices.push(Vertex3D::new(Vec3::new(p1.x, p1.y, 0.0), normal, Vec2::new(u1, 0.0)));
332 all_vertices.push(Vertex3D::new(Vec3::new(p1.x, p1.y, -depth), normal, Vec2::new(u1, 1.0)));
333 all_vertices.push(Vertex3D::new(Vec3::new(p0.x, p0.y, -depth), normal, Vec2::new(u0, 1.0)));
334
335 all_indices.push(side_base);
337 all_indices.push(side_base + 1);
338 all_indices.push(side_base + 2);
339 all_indices.push(side_base);
340 all_indices.push(side_base + 2);
341 all_indices.push(side_base + 3);
342 }
343 }
344 }
345
346 if all_vertices.is_empty() {
348 let b = outline.bounds;
349 let min = Vec3::new(b.min.x, b.min.y, -depth);
350 let max = Vec3::new(b.max.x, b.max.y, 0.0);
351 return create_box_mesh(min, max, ch, depth);
352 }
353
354 let mut bmin = Vec3::splat(f32::MAX);
356 let mut bmax = Vec3::splat(f32::MIN);
357 for v in &all_vertices {
358 let p = v.pos();
359 bmin = bmin.min(p);
360 bmax = bmax.max(p);
361 }
362
363 let tri_count = all_indices.len() as u32 / 3;
364
365 GlyphMesh {
366 vertices: all_vertices,
367 indices: all_indices,
368 character: ch,
369 extrusion_depth: depth,
370 triangle_count: tri_count,
371 bounds_min: bmin,
372 bounds_max: bmax,
373 }
374}
375
376pub(crate) fn create_box_mesh(min: Vec3, max: Vec3, ch: char, depth: f32) -> GlyphMesh {
377 let mut vertices = Vec::new();
378 let mut indices = Vec::new();
379
380 let faces: [(Vec3, Vec3, Vec3, Vec3, Vec3); 6] = [
381 (Vec3::new(min.x,min.y,max.z), Vec3::new(max.x,min.y,max.z), Vec3::new(max.x,max.y,max.z), Vec3::new(min.x,max.y,max.z), Vec3::Z),
382 (Vec3::new(max.x,min.y,min.z), Vec3::new(min.x,min.y,min.z), Vec3::new(min.x,max.y,min.z), Vec3::new(max.x,max.y,min.z), -Vec3::Z),
383 (Vec3::new(min.x,max.y,max.z), Vec3::new(max.x,max.y,max.z), Vec3::new(max.x,max.y,min.z), Vec3::new(min.x,max.y,min.z), Vec3::Y),
384 (Vec3::new(min.x,min.y,min.z), Vec3::new(max.x,min.y,min.z), Vec3::new(max.x,min.y,max.z), Vec3::new(min.x,min.y,max.z), -Vec3::Y),
385 (Vec3::new(max.x,min.y,max.z), Vec3::new(max.x,min.y,min.z), Vec3::new(max.x,max.y,min.z), Vec3::new(max.x,max.y,max.z), Vec3::X),
386 (Vec3::new(min.x,min.y,min.z), Vec3::new(min.x,min.y,max.z), Vec3::new(min.x,max.y,max.z), Vec3::new(min.x,max.y,min.z), -Vec3::X),
387 ];
388
389 for (v0, v1, v2, v3, n) in &faces {
390 let base = vertices.len() as u32;
391 vertices.push(Vertex3D::new(*v0, *n, Vec2::new(0.0, 0.0)));
392 vertices.push(Vertex3D::new(*v1, *n, Vec2::new(1.0, 0.0)));
393 vertices.push(Vertex3D::new(*v2, *n, Vec2::new(1.0, 1.0)));
394 vertices.push(Vertex3D::new(*v3, *n, Vec2::new(0.0, 1.0)));
395 indices.extend_from_slice(&[base, base+1, base+2, base, base+2, base+3]);
396 }
397
398 GlyphMesh {
399 vertices, indices, character: ch, extrusion_depth: depth,
400 triangle_count: 12, bounds_min: min, bounds_max: max,
401 }
402}
403
404pub fn generate_bevel(outline_points: &[Vec2], depth: f32, bevel_size: f32) -> Vec<Vertex3D> {
406 let mut verts = Vec::new();
407 let n = outline_points.len();
408 let steps = 3u32;
409
410 for i in 0..n {
411 let j = (i + 1) % n;
412 let p0 = outline_points[i];
413 let p1 = outline_points[j];
414 let edge = p1 - p0;
415 let normal_2d = Vec2::new(edge.y, -edge.x).normalize_or_zero();
416
417 for s in 0..=steps {
418 let t = s as f32 / steps as f32;
419 let angle = t * std::f32::consts::FRAC_PI_2;
420 let offset_xy = normal_2d * bevel_size * angle.cos();
421 let offset_z = bevel_size * (1.0 - angle.sin());
422
423 let pos = Vec3::new(p0.x + offset_xy.x, p0.y + offset_xy.y, offset_z);
425 let norm = Vec3::new(
426 normal_2d.x * angle.cos(),
427 normal_2d.y * angle.cos(),
428 angle.sin(),
429 ).normalize_or_zero();
430 verts.push(Vertex3D::new(pos, norm, Vec2::new(t, 0.0)));
431
432 let pos_b = Vec3::new(p0.x + offset_xy.x, p0.y + offset_xy.y, -depth - offset_z);
434 let norm_b = Vec3::new(
435 normal_2d.x * angle.cos(),
436 normal_2d.y * angle.cos(),
437 -angle.sin(),
438 ).normalize_or_zero();
439 verts.push(Vertex3D::new(pos_b, norm_b, Vec2::new(t, 1.0)));
440 }
441 }
442
443 verts
444}
445
446pub struct GlyphMeshCache {
449 pub meshes: HashMap<char, GlyphMesh>,
450}
451
452impl GlyphMeshCache {
453 pub fn build(outline_cache: &OutlineCache, depth: f32) -> Self {
454 let mut meshes = HashMap::new();
455 for (&ch, outline) in &outline_cache.outlines {
456 let mesh = extrude_glyph(outline, depth, ch);
457 meshes.insert(ch, mesh);
458 }
459 Self { meshes }
460 }
461
462 pub fn get(&self, ch: char) -> Option<&GlyphMesh> {
463 self.meshes.get(&ch)
464 }
465
466 pub fn len(&self) -> usize { self.meshes.len() }
467 pub fn is_empty(&self) -> bool { self.meshes.is_empty() }
468
469 pub fn total_triangles(&self) -> u32 {
470 self.meshes.values().map(|m| m.triangle_count).sum()
471 }
472
473 pub fn total_vertices(&self) -> usize {
474 self.meshes.values().map(|m| m.vertices.len()).sum()
475 }
476}
477
478#[cfg(test)]
481mod tests {
482 use super::*;
483 use super::super::font_to_mesh::GlyphBounds;
484
485 fn square_outline() -> GlyphOutline {
486 GlyphOutline {
487 contours: vec![Contour {
488 points: vec![
489 Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0),
490 Vec2::new(1.0, 1.0), Vec2::new(0.0, 1.0),
491 ],
492 is_hole: false,
493 }],
494 advance_width: 1.0,
495 bounds: GlyphBounds { min: Vec2::ZERO, max: Vec2::ONE },
496 }
497 }
498
499 #[test]
500 fn vertex3d_size() {
501 assert_eq!(std::mem::size_of::<Vertex3D>(), 32);
502 }
503
504 #[test]
505 fn point_in_triangle_basic() {
506 assert!(point_in_triangle(
507 Vec2::new(0.3, 0.3),
508 Vec2::ZERO, Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0),
509 ));
510 assert!(!point_in_triangle(
511 Vec2::new(2.0, 2.0),
512 Vec2::ZERO, Vec2::new(1.0, 0.0), Vec2::new(0.0, 1.0),
513 ));
514 }
515
516 #[test]
517 fn ear_clip_square() {
518 let sq = vec![
519 Vec2::new(0.0, 0.0), Vec2::new(1.0, 0.0),
520 Vec2::new(1.0, 1.0), Vec2::new(0.0, 1.0),
521 ];
522 let tris = ear_clip_triangulate(&sq, &[]);
523 assert_eq!(tris.len(), 2, "Square should produce 2 triangles");
524 }
525
526 #[test]
527 fn extrude_square_produces_geometry() {
528 let outline = square_outline();
529 let mesh = extrude_glyph(&outline, 0.5, '█');
530 assert!(mesh.triangle_count >= 12, "Extruded square: 2 front + 2 back + 8 sides = 12, got {}", mesh.triangle_count);
531 assert!(!mesh.vertices.is_empty());
532 assert!(!mesh.indices.is_empty());
533 }
534
535 #[test]
536 fn extrude_normals_are_unit() {
537 let outline = square_outline();
538 let mesh = extrude_glyph(&outline, 0.5, '█');
539 for v in &mesh.vertices {
540 let n = v.norm();
541 let len = n.length();
542 assert!((len - 1.0).abs() < 0.01 || len < 0.01, "Normal should be unit or zero: {}", len);
543 }
544 }
545
546 #[test]
547 fn extrude_indices_valid() {
548 let outline = square_outline();
549 let mesh = extrude_glyph(&outline, 0.5, '█');
550 let vc = mesh.vertices.len() as u32;
551 for &idx in &mesh.indices {
552 assert!(idx < vc, "Index {} out of range (vertex count = {})", idx, vc);
553 }
554 }
555
556 #[test]
557 fn bridge_hole_increases_count() {
558 let outer = vec![
559 Vec2::new(0.0, 0.0), Vec2::new(10.0, 0.0),
560 Vec2::new(10.0, 10.0), Vec2::new(0.0, 10.0),
561 ];
562 let hole = vec![
563 Vec2::new(3.0, 3.0), Vec2::new(7.0, 3.0),
564 Vec2::new(7.0, 7.0), Vec2::new(3.0, 7.0),
565 ];
566 let merged = bridge_hole(&outer, &hole);
567 assert!(merged.len() > outer.len());
568 assert!(merged.len() > hole.len());
569 }
570
571 #[test]
572 fn box_mesh_fallback() {
573 let mesh = create_box_mesh(Vec3::ZERO, Vec3::ONE, 'X', 1.0);
574 assert_eq!(mesh.triangle_count, 12);
575 assert_eq!(mesh.vertices.len(), 24);
576 }
577}