1use super::pixelsource::PixelSource;
2use crate::{BBox, Map, Value, ValueContainer};
3use earcutr::earcut;
4use theframework::prelude::*;
5
6#[derive(Serialize, Deserialize, Clone, Debug)]
7pub struct Sector {
8 pub id: u32,
9
10 pub creator_id: Uuid,
12
13 pub name: String,
14 pub linedefs: Vec<u32>,
15
16 pub properties: ValueContainer,
17
18 #[serde(default)]
19 pub shader: Option<Uuid>,
20
21 #[serde(default)]
23 pub layer: Option<u8>,
24}
25
26impl Sector {
27 pub fn new(id: u32, linedefs: Vec<u32>) -> Self {
28 let mut properties = ValueContainer::default();
29 properties.set("source", Value::Source(PixelSource::Off));
30
31 Self {
32 id,
33 creator_id: Uuid::new_v4(),
34 name: String::new(),
35 linedefs,
36 properties,
37
38 shader: None,
39 layer: None,
40 }
41 }
42
43 pub fn vertices_world(&self, map: &Map) -> Option<Vec<Vec3<f32>>> {
45 let mut verts = Vec::new();
46 for &linedef_id in &self.linedefs {
47 let ld = map.find_linedef(linedef_id)?;
48 let v = map.find_vertex(ld.start_vertex)?;
49 verts.push(Vec3::new(v.x, v.z, v.y));
50 }
51 verts.dedup_by(|a, b| (a.x == b.x) && (a.y == b.y) && (a.z == b.z));
52 if verts.len() < 3 {
53 return None;
54 }
55 Some(verts)
56 }
57
58 pub fn y_span(&self, map: &Map) -> Option<(f32, f32)> {
60 let verts = self.vertices_world(map)?;
61 let mut min_y = f32::INFINITY;
62 let mut max_y = f32::NEG_INFINITY;
63 for p in verts {
64 min_y = min_y.min(p.y);
65 max_y = max_y.max(p.y);
66 }
67 if min_y.is_finite() && max_y.is_finite() {
68 Some((min_y, max_y))
69 } else {
70 None
71 }
72 }
73
74 pub fn intersects_vertical_slice(&self, map: &Map, slice_y: f32, thickness: f32) -> bool {
76 if thickness <= 0.0 {
77 return false;
78 }
79 if let Some((min_y, max_y)) = self.y_span(map) {
80 let half = thickness * 0.5;
81 let y0 = slice_y - half;
82 let y1 = slice_y + half;
83 max_y >= y0 && min_y <= y1
84 } else {
85 false
86 }
87 }
88
89 pub fn bounding_box(&self, map: &Map) -> BBox {
91 let mut vertices = Vec::new();
93 for &linedef_id in &self.linedefs {
94 if let Some(linedef) = map.find_linedef(linedef_id) {
95 if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
96 vertices.push(Vec2::new(start_vertex.x, start_vertex.y));
97 if let Some(end_vertex) = map.find_vertex(linedef.end_vertex) {
98 vertices.push(Vec2::new(end_vertex.x, end_vertex.y));
99 }
100 }
101 }
102 }
103
104 let min_x = vertices.iter().map(|v| v.x).fold(f32::INFINITY, f32::min);
106 let max_x = vertices
107 .iter()
108 .map(|v| v.x)
109 .fold(f32::NEG_INFINITY, f32::max);
110 let min_y = vertices.iter().map(|v| v.y).fold(f32::INFINITY, f32::min);
111 let max_y = vertices
112 .iter()
113 .map(|v| v.y)
114 .fold(f32::NEG_INFINITY, f32::max);
115
116 BBox::new(Vec2::new(min_x, min_y), Vec2::new(max_x, max_y))
117 }
118
119 pub fn center(&self, map: &Map) -> Option<Vec2<f32>> {
121 let mut vertices = Vec::new();
123 for &linedef_id in &self.linedefs {
124 if let Some(linedef) = map.find_linedef(linedef_id) {
125 if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
126 vertices.push(Vec2::new(start_vertex.x, start_vertex.y));
127 if let Some(end_vertex) = map.find_vertex(linedef.end_vertex) {
128 vertices.push(Vec2::new(end_vertex.x, end_vertex.y));
129 }
130 }
131 }
132 }
133
134 if vertices.is_empty() {
136 return None;
137 }
138
139 let sum = vertices.iter().fold(Vec2::new(0.0, 0.0), |acc, v| acc + *v);
141 let count = vertices.len() as f32;
142 Some(sum / count)
143 }
144
145 pub fn center_3d(&self, map: &Map) -> Option<Vec3<f32>> {
147 let mut vertices = Vec::new();
149 for &linedef_id in &self.linedefs {
150 if let Some(linedef) = map.find_linedef(linedef_id) {
151 if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
152 vertices.push(Vec2::new(start_vertex.x, start_vertex.y));
153 if let Some(end_vertex) = map.find_vertex(linedef.end_vertex) {
154 vertices.push(Vec2::new(end_vertex.x, end_vertex.y));
155 }
156 }
157 }
158 }
159
160 if vertices.is_empty() {
162 return None;
163 }
164
165 let sum = vertices.iter().fold(Vec3::zero(), |acc, v| acc + *v);
167 let count = vertices.len() as f32;
168 Some(sum / count)
169 }
170
171 pub fn area(&self, map: &Map) -> f32 {
173 if let Some((vertices, indices)) = self.generate_geometry(map) {
175 indices.iter().fold(0.0, |acc, &(i1, i2, i3)| {
177 let v1 = vertices[i1];
178 let v2 = vertices[i2];
179 let v3 = vertices[i3];
180
181 acc + 0.5
183 * ((v1[0] * v2[1] + v2[0] * v3[1] + v3[0] * v1[1])
184 - (v1[1] * v2[0] + v2[1] * v3[0] + v3[1] * v1[0]))
185 .abs()
186 })
187 } else {
188 0.0 }
190 }
191
192 #[allow(clippy::type_complexity)]
194 pub fn generate_geometry(
195 &self,
196 map: &Map,
197 ) -> Option<(Vec<[f32; 2]>, Vec<(usize, usize, usize)>)> {
198 let mut vertices = Vec::new();
200 for &linedef_id in self.linedefs.iter() {
201 let linedef = map.find_linedef(linedef_id)?;
202 let start_vertex = map.get_vertex(linedef.start_vertex)?;
203 let vertex = [start_vertex.x, start_vertex.y];
204
205 if !vertices.contains(&vertex) {
211 vertices.push(vertex);
212 }
213 }
214
215 let flattened_vertices: Vec<f64> = vertices
217 .iter()
218 .flat_map(|v| vec![v[0] as f64, v[1] as f64])
219 .collect();
220
221 let holes: Vec<usize> = Vec::new();
223
224 if let Ok(indices) = earcut(&flattened_vertices, &holes, 2) {
226 let indices: Vec<(usize, usize, usize)> = indices
227 .chunks_exact(3)
228 .map(|chunk| (chunk[2], chunk[1], chunk[0]))
229 .collect();
230 Some((vertices, indices))
231 } else {
232 None
233 }
234 }
235
236 pub fn is_inside(&self, map: &Map, point: Vec2<f32>) -> bool {
273 let mut polygon = Vec::new();
275 for &linedef_id in &self.linedefs {
276 if let Some(linedef) = map.find_linedef(linedef_id) {
277 if let Some(start_vertex) = map.get_vertex(linedef.start_vertex) {
278 polygon.push(Vec2::new(start_vertex.x, start_vertex.y));
279 }
280 }
281 }
282
283 if polygon.len() < 3 {
285 return false; }
287
288 let mut inside = false;
290 let mut j = polygon.len() - 1;
291
292 for i in 0..polygon.len() {
293 if (polygon[i].y > point.y) != (polygon[j].y > point.y)
294 && point.x
295 < (polygon[j].x - polygon[i].x) * (point.y - polygon[i].y)
296 / (polygon[j].y - polygon[i].y)
297 + polygon[i].x
298 {
299 inside = !inside;
300 }
301 j = i;
302 }
303
304 inside
305 }
306
307 pub fn signed_distance(&self, map: &Map, point: Vec2<f32>) -> Option<f32> {
309 let mut min_dist = f32::MAX;
310
311 for &linedef_id in &self.linedefs {
313 if let Some(ld) = map.find_linedef(linedef_id) {
314 let v0 = map.get_vertex(ld.start_vertex)?;
315 let v1 = map.get_vertex(ld.end_vertex)?;
316 let edge = v1 - v0;
317 let to_point = point - v0;
318
319 let t = to_point.dot(edge) / edge.dot(edge);
320 let t_clamped = t.clamp(0.0, 1.0);
321 let closest = v0 + edge * t_clamped;
322
323 let dist = (point - closest).magnitude();
324 min_dist = min_dist.min(dist);
325 }
326 }
327
328 let inside = self.is_inside(map, point);
330
331 Some(if inside { -min_dist } else { min_dist })
333 }
334
335 #[allow(clippy::type_complexity)]
337 pub fn generate_wall_geometry(
338 &self,
339 map: &Map,
340 thickness: f32,
341 ) -> Option<(Vec<[f32; 2]>, Vec<(usize, usize, usize)>)> {
342 let mut vertices = Vec::new();
343 let mut indices = Vec::new();
344
345 let mut polygon = Vec::new();
347 for &linedef_id in &self.linedefs {
348 if let Some(linedef) = map.find_linedef(linedef_id) {
349 if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
350 let v_start = Vec2::new(start_vertex.x, start_vertex.y);
351 polygon.push(v_start);
352 }
353 }
354 }
355
356 if let (Some(&first), Some(&last)) = (polygon.first(), polygon.last()) {
358 if first != last {
359 polygon.push(first);
360 }
361 }
362
363 polygon.pop();
365
366 let len = polygon.len();
367 if len < 3 {
368 return None;
369 }
370
371 let mut outer_points = Vec::with_capacity(len);
373 let mut inner_points = Vec::with_capacity(len);
374
375 for i in 0..len {
376 let prev = polygon[(i + len - 1) % len];
377 let curr = polygon[i];
378 let next = polygon[(i + 1) % len];
379
380 let dir1 = (curr - prev).normalized();
382 let dir2 = (next - curr).normalized();
383
384 let normal1 = Vec2::new(-dir1.y, dir1.x);
385 let normal2 = Vec2::new(-dir2.y, dir2.x);
386
387 let bisector = (normal1 + normal2).normalized();
389 let angle = dir1.angle_between(dir2) / 2.0;
390
391 let offset_length = thickness / (2.0 * angle.cos()).max(0.1);
393
394 let outer = curr + bisector * offset_length;
395 let inner = curr - bisector * offset_length;
396
397 outer_points.push(outer);
398 inner_points.push(inner);
399 }
400
401 let mut outer_indices = Vec::with_capacity(len);
403 let mut inner_indices = Vec::with_capacity(len);
404
405 for &pt in &outer_points {
406 outer_indices.push(vertices.len());
407 vertices.push([pt.x, pt.y]);
408 }
409
410 for &pt in &inner_points {
411 inner_indices.push(vertices.len());
412 vertices.push([pt.x, pt.y]);
413 }
414
415 for i in 0..len {
417 let next = (i + 1) % len;
418
419 let o1 = outer_indices[i];
421 let o2 = outer_indices[next];
422 let i1 = inner_indices[i];
424 let i2 = inner_indices[next];
425
426 indices.push((o1, o2, i1));
430 indices.push((o2, i2, i1));
431 }
432
433 Some((vertices, indices))
434 }
435
436 #[allow(clippy::type_complexity)]
438 pub fn generate_wall_geometry_by_linedef(
439 &self,
440 map: &Map,
441 ) -> Option<FxHashMap<u32, (Vec<[f32; 2]>, Vec<(usize, usize, usize)>)>> {
443 let mut result: FxHashMap<u32, (Vec<[f32; 2]>, Vec<(usize, usize, usize)>)> =
444 FxHashMap::default();
445
446 let mut all_walls_have_no_width = true;
447 let mut polygon = Vec::new();
448 for &linedef_id in &self.linedefs {
449 if let Some(linedef) = map.find_linedef(linedef_id) {
450 if let Some(start_vertex) = map.find_vertex(linedef.start_vertex) {
451 let v_start = Vec2::new(start_vertex.x, start_vertex.y);
452 polygon.push(v_start);
453 if linedef.properties.get_float_default("wall_width", 0.0) > 0.0 {
454 all_walls_have_no_width = false;
455 }
456 }
457 }
458 }
459
460 if all_walls_have_no_width {
462 return None;
463 }
464
465 if let (Some(&first), Some(&last)) = (polygon.first(), polygon.last()) {
467 if first != last {
468 polygon.push(first);
469 }
470 }
471 polygon.pop();
473
474 let len = polygon.len();
475 if len < 3 {
476 return None;
477 }
478
479 let mut outer_points = Vec::with_capacity(len);
480 let mut inner_points = Vec::with_capacity(len);
481
482 for i in 0..len {
512 let prev = polygon[(i + len - 1) % len];
513 let curr = polygon[i];
514 let next = polygon[(i + 1) % len];
515
516 let dir1 = (curr - prev).normalized();
518 let dir2 = (next - curr).normalized();
519 let normal1 = Vec2::new(-dir1.y, dir1.x);
520 let normal2 = Vec2::new(-dir2.y, dir2.x);
521 let bisector = (normal1 + normal2).normalized();
522 let angle = dir1.angle_between(dir2) / 2.0;
523
524 let prev_line_id = self.linedefs[(i + len - 1) % len];
526 let curr_line_id = self.linedefs[i];
527
528 let mut t_prev = 0.0;
529 let mut t_curr = 0.0;
530
531 if let Some(linedef) = map.find_linedef(prev_line_id) {
532 t_prev = linedef.properties.get_float_default("wall_width", 0.0);
533 }
534
535 if let Some(linedef) = map.find_linedef(curr_line_id) {
536 t_curr = linedef.properties.get_float_default("wall_width", 0.0);
537 }
538
539 let corner_thickness = (t_prev + t_curr) * 0.5;
541
542 let offset_length = corner_thickness / (2.0 * angle.cos()).max(0.1);
544
545 let outer = curr + bisector * offset_length;
546 let inner = curr - bisector * offset_length;
547
548 outer_points.push(outer);
549 inner_points.push(inner);
550 }
551
552 for i in 0..len {
555 let next = (i + 1) % len;
556 let linedef_id = self.linedefs[i];
557
558 let o1 = outer_points[i];
560 let o2 = outer_points[next];
561 let i1 = inner_points[i];
562 let i2 = inner_points[next];
563
564 let local_verts = vec![
567 [o1.x, o1.y], [o2.x, o2.y], [i2.x, i2.y], [i1.x, i1.y], ];
572
573 let local_inds = vec![(0, 1, 3), (1, 2, 3)];
574 result.insert(linedef_id, (local_verts, local_inds));
575 }
576
577 Some(result)
578 }
579}
580
581impl PartialEq for Sector {
582 fn eq(&self, other: &Self) -> bool {
583 let mut a = self.linedefs.clone();
586 let mut b = other.linedefs.clone();
587 a.sort_unstable();
588 a.dedup();
589 b.sort_unstable();
590 b.dedup();
591 a == b
592 }
593}
594
595impl Eq for Sector {}