1use crate::{Map, Sector};
2use serde::{Deserialize, Serialize};
3use uuid::Uuid;
4use vek::{Vec2, Vec3};
5
6use earcutr::earcut;
7
8#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Default)]
10pub enum BillboardAnimation {
11 #[default]
12 None,
13 OpenUp, OpenRight, OpenDown, OpenLeft, Fade, }
19
20#[derive(Serialize, Deserialize, Clone, Debug)]
22pub enum LoopOp {
23 None,
24 Relief {
25 height: f32,
26 }, Recess {
28 depth: f32,
29 }, Billboard {
31 tile_id: Option<Uuid>, animation: BillboardAnimation, inset: f32, },
35 Window {
36 frame_tile_id: Option<Uuid>,
37 glass_tile_id: Option<Uuid>,
38 inset: f32,
39 },
40}
41
42impl LoopOp {
43 pub fn to_action_properties(
45 &self,
46 target_side: i32,
47 ) -> crate::chunkbuilder::action::ActionProperties {
48 use crate::chunkbuilder::action::ActionProperties;
49
50 match self {
51 LoopOp::None => ActionProperties::default().with_target_side(target_side),
52 LoopOp::Relief { height } => ActionProperties::default()
53 .with_height(*height)
54 .with_target_side(target_side),
55 LoopOp::Recess { depth } => ActionProperties::default()
56 .with_depth(*depth)
57 .with_target_side(target_side),
58 LoopOp::Billboard {
59 tile_id,
60 animation,
61 inset,
62 } => ActionProperties::default()
63 .with_depth(*inset)
64 .with_target_side(target_side)
65 .with_tile_id(*tile_id)
66 .with_animation(*animation),
67 LoopOp::Window {
68 frame_tile_id,
69 inset,
70 ..
71 } => ActionProperties::default()
72 .with_depth(*inset)
73 .with_target_side(target_side)
74 .with_tile_id(*frame_tile_id),
75 }
76 }
77
78 pub fn get_action(&self) -> Option<Box<dyn crate::chunkbuilder::action::SurfaceAction>> {
80 use crate::chunkbuilder::action::{
81 BillboardAction, HoleAction, RecessAction, ReliefAction,
82 };
83
84 match self {
85 LoopOp::None => Some(Box::new(HoleAction)),
86 LoopOp::Relief { .. } => Some(Box::new(ReliefAction)),
87 LoopOp::Recess { .. } => Some(Box::new(RecessAction)),
88 LoopOp::Billboard { .. } => Some(Box::new(BillboardAction)),
89 LoopOp::Window { .. } => None,
90 }
91 }
92}
93
94#[derive(Serialize, Deserialize, Clone, Debug)]
96pub struct ProfileLoop {
97 pub path: Vec<Vec2<f32>>, pub op: LoopOp, pub origin_profile_sector: Option<u32>,
101}
102
103#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default)]
105pub struct Plane {
106 pub origin: Vec3<f32>,
107 pub normal: Vec3<f32>,
108}
109
110#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default)]
112pub struct Basis3 {
113 pub right: Vec3<f32>,
114 pub up: Vec3<f32>,
115 pub normal: Vec3<f32>,
116}
117
118#[derive(Serialize, Deserialize, Clone, Copy, Debug, Default)]
120pub struct EditPlane {
121 pub origin: Vec3<f32>,
122 pub right: Vec3<f32>,
123 pub up: Vec3<f32>,
124 pub scale: f32,
125}
126
127#[derive(Serialize, Deserialize, Clone, Debug)]
129pub struct Attachment {
130 pub id: Uuid,
131 pub surface_id: Uuid,
132 pub transform: [[f32; 4]; 4],
133 pub mesh_ref: Option<Uuid>,
134 pub proc_ref: Option<Uuid>,
135}
136
137#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
139pub enum ExtrudeUV {
140 Stretch { scale_u: f32, scale_v: f32 },
142 PlanarFront { scale: f32 },
144}
145
146impl Default for ExtrudeUV {
147 fn default() -> Self {
148 Self::Stretch {
149 scale_u: 1.0,
150 scale_v: 1.0,
151 }
152 }
153}
154
155#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
157pub struct ExtrusionSpec {
158 pub enabled: bool, pub depth: f32, pub cap_front: bool, pub cap_back: bool, pub flip_normal: bool, pub uv: ExtrudeUV, }
165
166impl Default for ExtrusionSpec {
167 fn default() -> Self {
168 Self {
169 enabled: false,
170 depth: 0.0,
171 cap_front: true,
172 cap_back: false,
173 flip_normal: false,
174 uv: ExtrudeUV::default(),
175 }
176 }
177}
178
179#[derive(Serialize, Deserialize, Clone, Debug)]
181pub struct Surface {
182 pub id: Uuid,
183 pub sector_id: u32,
184
185 pub plane: Plane,
187 pub frame: Basis3,
188 pub edit_uv: EditPlane,
189
190 #[serde(default)]
192 pub extrusion: ExtrusionSpec,
193
194 pub profile: Option<Uuid>,
196
197 #[serde(skip)]
199 pub world_vertices: Vec<Vec3<f32>>,
200}
201
202impl Surface {
203 pub fn new(sector_id: u32) -> Surface {
204 Surface {
205 id: Uuid::new_v4(),
206 sector_id,
207 plane: Plane::default(),
208 frame: Basis3::default(),
209 edit_uv: EditPlane::default(),
210 extrusion: ExtrusionSpec::default(),
211 profile: None,
212 world_vertices: vec![],
213 }
214 }
215
216 pub fn is_valid(&self) -> bool {
218 self.plane.origin.x.is_finite()
219 && self.plane.origin.y.is_finite()
220 && self.plane.origin.z.is_finite()
221 && self.plane.normal.x.is_finite()
222 && self.plane.normal.y.is_finite()
223 && self.plane.normal.z.is_finite()
224 && self.frame.right.x.is_finite()
225 && self.frame.right.y.is_finite()
226 && self.frame.right.z.is_finite()
227 && self.frame.up.x.is_finite()
228 && self.frame.up.y.is_finite()
229 && self.frame.up.z.is_finite()
230 && self.frame.normal.x.is_finite()
231 && self.frame.normal.y.is_finite()
232 && self.frame.normal.z.is_finite()
233 }
234
235 pub fn calculate_geometry(&mut self, map: &Map) {
237 if let Some(sector) = map.find_sector(self.sector_id) {
238 if let Some(points) = sector.vertices_world(map) {
239 let (centroid, mut normal) = newell_plane(&points);
241 if normal.magnitude() < 1e-6 {
242 normal = Vec3::new(0.0, 1.0, 0.0);
243 }
244 let mut right = stable_right(&points, normal);
245 let mut up = normalize_or_zero(normal.cross(right));
246
247 if up.magnitude() < 1e-6 {
248 right = normalize_or_zero(normal.cross(Vec3::new(0.0, 1.0, 0.0)));
250 up = normalize_or_zero(normal.cross(right));
251 }
252
253 if up.magnitude() < 1e-6 {
254 right = Vec3::new(1.0, 0.0, 0.0);
256 up = normalize_or_zero(normal.cross(right));
257 }
258
259 let test_up = normalize_or_zero(normal.cross(right));
261 if test_up.magnitude() > 1e-6 && (test_up - up).magnitude() > 1e-6 {
262 right = -right;
263 up = normalize_or_zero(normal.cross(right));
264 }
265
266 self.plane.origin = centroid;
267 self.plane.normal = normal;
268
269 self.frame.right = right;
270 self.frame.up = up;
271 self.frame.normal = self.plane.normal;
272
273 self.edit_uv.origin = self.plane.origin;
274 self.edit_uv.right = self.frame.right;
275 self.edit_uv.up = self.frame.up;
276 self.edit_uv.scale = 1.0;
277 return;
278 } else {
279 self.plane = Default::default();
280 self.frame = Default::default();
281 self.edit_uv = Default::default();
282 return;
283 }
284 }
285 self.plane = Default::default();
286 self.frame = Default::default();
287 self.edit_uv = Default::default();
288 }
289
290 pub fn uv_to_world(&self, uv: Vec2<f32>) -> Vec3<f32> {
292 self.edit_uv.origin
293 + self.edit_uv.right * uv.x * self.edit_uv.scale
294 + self.edit_uv.up * uv.y * self.edit_uv.scale
295 }
296
297 pub fn uvw_to_world(&self, uv: Vec2<f32>, w: f32) -> Vec3<f32> {
299 self.uv_to_world(uv) + self.frame.normal * w
300 }
301
302 pub fn world_to_uv(&self, p: Vec3<f32>) -> Vec2<f32> {
303 let rel = p - self.edit_uv.origin;
304 Vec2::new(rel.dot(self.edit_uv.right), rel.dot(self.edit_uv.up)) / self.edit_uv.scale
305 }
306
307 pub fn sector_uv_min(&self, map: &Map) -> Option<Vec2<f32>> {
310 let loop_uv = self.sector_loop_uv(map)?;
311 if loop_uv.is_empty() {
312 return None;
313 }
314 let mut min = Vec2::new(f32::INFINITY, f32::INFINITY);
315 for p in &loop_uv {
316 min.x = min.x.min(p.x);
317 min.y = min.y.min(p.y);
318 }
319 Some(min)
320 }
321
322 pub fn tile_local_anchor_uv(&self, map: &Map) -> Vec2<f32> {
327 if self.plane.normal.y.abs() < 0.25 {
328 self.world_to_uv(Vec3::zero())
330 } else {
331 self.sector_uv_min(map).unwrap_or(Vec2::zero())
332 }
333 }
334
335 pub fn tile_local_flip_x(&self) -> bool {
338 if self.plane.normal.y.abs() >= 0.25 {
339 return false;
340 }
341 let rx = self.edit_uv.right.x.abs();
342 let rz = self.edit_uv.right.z.abs();
343 let dominant = if rx >= rz {
344 self.edit_uv.right.x
345 } else {
346 self.edit_uv.right.z
347 };
348 dominant < 0.0
349 }
350
351 pub fn uv_to_tile_local(&self, uv: Vec2<f32>, map: &Map) -> Vec2<f32> {
353 let anchor = self.tile_local_anchor_uv(map);
354 let x = if self.tile_local_flip_x() {
355 anchor.x - uv.x
356 } else {
357 uv.x - anchor.x
358 };
359 let y = uv.y - anchor.y;
360 Vec2::new(x, y)
361 }
362
363 pub fn tile_local_to_uv(&self, local: Vec2<f32>, map: &Map) -> Vec2<f32> {
365 let anchor = self.tile_local_anchor_uv(map);
366 let x = if self.tile_local_flip_x() {
367 anchor.x - local.x
368 } else {
369 anchor.x + local.x
370 };
371 let y = anchor.y + local.y;
372 Vec2::new(x, y)
373 }
374
375 pub fn world_to_tile(&self, p: Vec3<f32>) -> (i32, i32) {
379 let uv = self.world_to_uv(p);
380 (uv.x.floor() as i32, uv.y.floor() as i32)
381 }
382
383 pub fn world_to_tile_local(&self, p: Vec3<f32>, map: &Map) -> (i32, i32) {
386 let uv = self.world_to_uv(p);
387 let local = self.uv_to_tile_local(uv, map);
388 (local.x.floor() as i32, local.y.floor() as i32)
389 }
390
391 pub fn tile_outline_world(&self, tile: (i32, i32)) -> [Vec3<f32>; 4] {
394 let (tx, ty) = tile;
395 let corners_uv = [
396 Vec2::new(tx as f32, ty as f32),
397 Vec2::new(tx as f32 + 1.0, ty as f32),
398 Vec2::new(tx as f32 + 1.0, ty as f32 + 1.0),
399 Vec2::new(tx as f32, ty as f32 + 1.0),
400 ];
401 corners_uv.map(|uv| self.uv_to_world(uv))
402 }
403
404 pub fn tile_outline_world_local(&self, tile: (i32, i32), map: &Map) -> [Vec3<f32>; 4] {
407 let (tx, ty) = tile;
408 let corners_local = [
409 Vec2::new(tx as f32, ty as f32),
410 Vec2::new(tx as f32 + 1.0, ty as f32),
411 Vec2::new(tx as f32 + 1.0, ty as f32 + 1.0),
412 Vec2::new(tx as f32, ty as f32 + 1.0),
413 ];
414 let corners_uv = corners_local.map(|local| self.tile_local_to_uv(local, map));
415 corners_uv.map(|uv| self.uv_to_world(uv))
416 }
417
418 pub fn sector_loop_uv(&self, map: &Map) -> Option<Vec<Vec2<f32>>> {
420 let sector = map.find_sector(self.sector_id)?;
421 let pts3 = sector.vertices_world(map)?;
422 if pts3.len() < 3 {
423 return None;
424 }
425 let mut uv: Vec<Vec2<f32>> = pts3.iter().map(|p| self.world_to_uv(*p)).collect();
426 if polygon_signed_area_uv(&uv) < 0.0 {
427 uv.reverse();
428 }
429 Some(uv)
430 }
431
432 pub fn triangulate_cap_with_holes(
435 &self,
436 outer_uv: &[Vec2<f32>],
437 holes_uv: &[Vec<Vec2<f32>>],
438 ) -> Option<(Vec<[f32; 4]>, Vec<(usize, usize, usize)>, Vec<[f32; 2]>)> {
439 if outer_uv.len() < 3 {
440 return None;
441 }
442 let mut verts: Vec<Vec2<f32>> =
444 Vec::with_capacity(outer_uv.len() + holes_uv.iter().map(|h| h.len()).sum::<usize>());
445 let mut holes_idx: Vec<usize> = Vec::with_capacity(holes_uv.len());
446
447 if polygon_signed_area_uv(outer_uv) < 0.0 {
449 let mut ccw = outer_uv.to_vec();
450 ccw.reverse();
451 verts.extend(ccw);
452 } else {
453 verts.extend_from_slice(outer_uv);
454 }
455 let mut offset = outer_uv.len();
457 for h in holes_uv {
458 holes_idx.push(offset);
459 if polygon_signed_area_uv(h) > 0.0 {
460 let mut cw = h.clone();
462 cw.reverse();
463 verts.extend(cw);
464 } else {
465 verts.extend_from_slice(h);
466 }
467 offset += h.len();
468 }
469
470 let flat: Vec<f64> = verts
472 .iter()
473 .flat_map(|v| [v.x as f64, v.y as f64])
474 .collect();
475 let idx = earcut(&flat, &holes_idx, 2).ok()?;
476 let indices: Vec<(usize, usize, usize)> =
477 idx.chunks_exact(3).map(|c| (c[2], c[1], c[0])).collect();
478
479 let verts_uv: Vec<[f32; 2]> = verts.iter().map(|v| [v.x, v.y]).collect();
480 let world_vertices: Vec<[f32; 4]> = verts
481 .iter()
482 .map(|uv| {
483 let p = self.uv_to_world(*uv);
484 [p.x, p.y, p.z, 1.0]
485 })
486 .collect();
487
488 Some((world_vertices, indices, verts_uv))
489 }
490
491 pub fn normal(&self) -> Vec3<f32> {
493 let n = self.plane.normal;
494 let m = n.magnitude();
495 if m > 1e-6 {
496 n / m
497 } else {
498 Vec3::new(0.0, 1.0, 0.0)
499 }
500 }
501
502 pub fn triangulate(
505 &self,
506 sector: &Sector,
507 map: &Map,
508 ) -> Option<(Vec<[f32; 4]>, Vec<(usize, usize, usize)>, Vec<[f32; 2]>)> {
509 let points3 = sector.vertices_world(map)?;
511 if points3.len() < 3 {
512 return None;
513 }
514
515 let verts_uv: Vec<[f32; 2]> = points3
517 .iter()
518 .map(|p| {
519 let uv = self.world_to_uv(*p);
520 [uv.x, uv.y]
521 })
522 .collect();
523
524 let flattened: Vec<f64> = verts_uv
526 .iter()
527 .flat_map(|v| [v[0] as f64, v[1] as f64])
528 .collect();
529 let holes: Vec<usize> = Vec::new();
530 let idx = earcut(&flattened, &holes, 2).ok()?; let indices: Vec<(usize, usize, usize)> =
534 idx.chunks_exact(3).map(|c| (c[2], c[1], c[0])).collect();
535
536 let world_vertices: Vec<[f32; 4]> = verts_uv
538 .iter()
539 .map(|v| {
540 let p = self.uv_to_world(vek::Vec2::new(v[0], v[1]));
541 [p.x, p.y, p.z, 1.0]
542 })
543 .collect();
544
545 Some((world_vertices, indices, verts_uv))
546 }
547}
548
549fn normalize_or_zero(v: Vec3<f32>) -> Vec3<f32> {
550 let m = v.magnitude();
551 if m > 1e-6 { v / m } else { Vec3::zero() }
552}
553
554fn newell_plane(points: &[Vec3<f32>]) -> (Vec3<f32>, Vec3<f32>) {
555 let mut centroid = Vec3::zero();
556 let mut normal = Vec3::zero();
557 let n = points.len();
558 for i in 0..n {
559 let current = points[i];
560 let next = points[(i + 1) % n];
561 centroid += current;
562 normal.x += (current.y - next.y) * (current.z + next.z);
563 normal.y += (current.z - next.z) * (current.x + next.x);
564 normal.z += (current.x - next.x) * (current.y + next.y);
565 }
566 centroid /= n as f32;
567 let m = normal.magnitude();
568 if m > 1e-6 {
569 normal /= m;
570 } else {
571 normal = Vec3::zero();
572 }
573 (centroid, normal)
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579 use crate::Map;
580 use uuid::Uuid;
581
582 fn make_wall_surface(right: Vec3<f32>, up: Vec3<f32>, normal: Vec3<f32>) -> Surface {
583 Surface {
584 id: Uuid::new_v4(),
585 sector_id: 1,
586 plane: Plane {
587 origin: Vec3::zero(),
588 normal,
589 },
590 frame: Basis3 { right, up, normal },
591 edit_uv: EditPlane {
592 origin: Vec3::zero(),
593 right,
594 up,
595 scale: 1.0,
596 },
597 extrusion: ExtrusionSpec::default(),
598 profile: None,
599 world_vertices: vec![],
600 }
601 }
602
603 #[test]
604 fn wall_tile_local_x_is_consistent_for_opposite_orientations() {
605 let map = Map::new();
606 let inside = make_wall_surface(
607 Vec3::new(1.0, 0.0, 0.0),
608 Vec3::new(0.0, 1.0, 0.0),
609 Vec3::new(0.0, 0.0, 1.0),
610 );
611 let outside = make_wall_surface(
612 Vec3::new(-1.0, 0.0, 0.0),
613 Vec3::new(0.0, 1.0, 0.0),
614 Vec3::new(0.0, 0.0, -1.0),
615 );
616
617 let p = Vec3::new(0.2, 0.4, 0.0);
618 let a = inside.world_to_tile_local(p, &map);
619 let b = outside.world_to_tile_local(p, &map);
620 assert_eq!(a.0, b.0);
621 }
622
623 #[test]
624 fn wall_tile_local_y_uses_integer_bands() {
625 let map = Map::new();
626 let surface = make_wall_surface(
627 Vec3::new(1.0, 0.0, 0.0),
628 Vec3::new(0.0, 1.0, 0.0),
629 Vec3::new(0.0, 0.0, 1.0),
630 );
631
632 let low = surface.world_to_tile_local(Vec3::new(0.0, 0.2, 0.0), &map);
633 let high = surface.world_to_tile_local(Vec3::new(0.0, 1.2, 0.0), &map);
634 assert_eq!(low.1, 0);
635 assert_eq!(high.1, 1);
636 }
637
638 #[test]
639 fn tile_outline_local_roundtrips_back_to_same_tile_cell_center() {
640 let map = Map::new();
641 let surface = make_wall_surface(
642 Vec3::new(-1.0, 0.0, 0.0),
643 Vec3::new(0.0, 1.0, 0.0),
644 Vec3::new(0.0, 0.0, -1.0),
645 );
646
647 let center_local = Vec2::new(1.5, 2.5);
648 let center_world = surface.uv_to_world(surface.tile_local_to_uv(center_local, &map));
649 let cell = surface.world_to_tile_local(center_world, &map);
650 assert_eq!(cell, (1, 2));
651 }
652
653 #[test]
654 fn tile_local_helpers_do_not_change_world_to_uv_mapping() {
655 let map = Map::new();
656 let surface = make_wall_surface(
657 Vec3::new(-1.0, 0.0, 0.0),
658 Vec3::new(0.0, 1.0, 0.0),
659 Vec3::new(0.0, 0.0, -1.0),
660 );
661 let p = Vec3::new(0.7, 1.3, 0.0);
662 let before = surface.world_to_uv(p);
663
664 let _ = surface.world_to_tile_local(p, &map);
665 let _ = surface.tile_outline_world_local((2, 1), &map);
666
667 let after = surface.world_to_uv(p);
668 assert!((before.x - after.x).abs() < 1e-6);
669 assert!((before.y - after.y).abs() < 1e-6);
670 }
671}
672
673fn stable_right(points: &[Vec3<f32>], normal: Vec3<f32>) -> Vec3<f32> {
674 let n = points.len();
675 let mut max_len = 0.0;
676 let mut right = Vec3::zero();
677 for i in 0..n {
678 let edge = points[(i + 1) % n] - points[i];
679 let proj = edge - normal * normal.dot(edge);
680 let len = proj.magnitude();
681 if len > max_len {
682 max_len = len;
683 right = proj;
684 }
685 }
686 if max_len < 1e-6 {
687 if normal.x.abs() < normal.y.abs() && normal.x.abs() < normal.z.abs() {
689 right = Vec3::new(0.0, -normal.z, normal.y);
690 } else if normal.y.abs() < normal.z.abs() {
691 right = Vec3::new(-normal.z, 0.0, normal.x);
692 } else {
693 right = Vec3::new(-normal.y, normal.x, 0.0);
694 }
695 }
696 normalize_or_zero(right)
697}
698
699fn polygon_signed_area_uv(poly: &[Vec2<f32>]) -> f32 {
700 if poly.len() < 3 {
701 return 0.0;
702 }
703 let mut a = 0.0f32;
704 for i in 0..poly.len() {
705 let p = poly[i];
706 let q = poly[(i + 1) % poly.len()];
707 a += p.x * q.y - q.x * p.y;
708 }
709 0.5 * a
710}