Skip to main content

rusterix/map/
sector.rs

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    // For editors
11    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    /// The rect tool layer for this sector (if created by the rect tool).
22    #[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    /// Returns the sector's vertices in world space as Vec<Vec3<f32>>.
44    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    /// Returns the vertical span (min_y, max_y) of this sector in world space.
59    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    /// Checks whether this sector intersects a vertical slice centered at `slice_y` with thickness `thickness`.
75    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    // Generate a bounding box for the sector
90    pub fn bounding_box(&self, map: &Map) -> BBox {
91        // Collect all vertices for the sector
92        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        // Find min and max coordinates
105        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    /// Calculate the center of the sector
120    pub fn center(&self, map: &Map) -> Option<Vec2<f32>> {
121        // Collect all vertices for the sector
122        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        // Ensure we have vertices to calculate the center
135        if vertices.is_empty() {
136            return None;
137        }
138
139        // Calculate the average x and y coordinates
140        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    /// Calculate the center of the sector using 3D coords.
146    pub fn center_3d(&self, map: &Map) -> Option<Vec3<f32>> {
147        // Collect all vertices for the sector
148        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        // Ensure we have vertices to calculate the center
161        if vertices.is_empty() {
162            return None;
163        }
164
165        // Calculate the average x and y coordinates
166        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    /// Calculate the area of the sector (for sorting).
172    pub fn area(&self, map: &Map) -> f32 {
173        // Generate geometry for the sector
174        if let Some((vertices, indices)) = self.generate_geometry(map) {
175            // Calculate the area by summing up the areas of individual triangles
176            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                // Calculate the area of the triangle using the shoelace formula
182                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 // Return 0 if the geometry couldn't be generated
189        }
190    }
191
192    /// Generate geometry (vertices and indices) for the polygon using earcutr
193    #[allow(clippy::type_complexity)]
194    pub fn generate_geometry(
195        &self,
196        map: &Map,
197    ) -> Option<(Vec<[f32; 2]>, Vec<(usize, usize, usize)>)> {
198        // Collect unique vertices from the Linedefs in order
199        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            // Add the vertex to the list if it isn't already there
206            // if vertices.last() != Some(&vertex) {
207            //     vertices.push(vertex);
208            // }
209            //
210            if !vertices.contains(&vertex) {
211                vertices.push(vertex);
212            }
213        }
214
215        // Flatten the vertices for earcutr
216        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        // No holes in this example, so pass an empty holes array
222        let holes: Vec<usize> = Vec::new();
223
224        // Perform triangulation
225        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    // Returns a random position inside the sector.
237    // pub fn get_random_position(&self, map: &Map) -> Option<Vec2<f32>> {
238    //     // Generate geometry for the sector
239    //     if let Some((vertices, indices)) = self.generate_geometry(map) {
240    //         // Create a random number generator
241    //         let mut rng = rand::rng();
242
243    //         // Randomly select a triangle from the indices
244    //         if let Some(&(i1, i2, i3)) = indices.choose(&mut rng) {
245    //             let v1 = vertices[i1];
246    //             let v2 = vertices[i2];
247    //             let v3 = vertices[i3];
248
249    //             // Generate random barycentric coordinates
250    //             let r1: f32 = rng.random();
251    //             let r2: f32 = rng.random();
252
253    //             // Ensure they are constrained to the triangle
254    //             let sqrt_r1 = r1.sqrt();
255    //             let u = 1.0 - sqrt_r1;
256    //             let v = r2 * sqrt_r1;
257
258    //             // Compute the random position as a weighted sum of the triangle's vertices
259    //             let x = u * v1[0] + v * v2[0] + (1.0 - u - v) * v3[0];
260    //             let y = u * v1[1] + v * v2[1] + (1.0 - u - v) * v3[1];
261
262    //             Some(Vec2::new(x, y))
263    //         } else {
264    //             None // Return None if no triangles are available
265    //         }
266    //     } else {
267    //         None // Return None if geometry couldn't be generated
268    //     }
269    // }
270
271    /// Checks if a point is inside the sector polygon using the ray-casting algorithm.
272    pub fn is_inside(&self, map: &Map, point: Vec2<f32>) -> bool {
273        // Collect the polygon vertices
274        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        // Early exit if the polygon is invalid
284        if polygon.len() < 3 {
285            return false; // A polygon must have at least 3 vertices
286        }
287
288        // Ray-casting algorithm
289        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    /// Generate the signed distance to the
308    pub fn signed_distance(&self, map: &Map, point: Vec2<f32>) -> Option<f32> {
309        let mut min_dist = f32::MAX;
310
311        // Distance to nearest edge
312        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        // Check if point is inside
329        let inside = self.is_inside(map, point);
330
331        // Return signed distance
332        Some(if inside { -min_dist } else { min_dist })
333    }
334
335    /// Generate 2D walls with uniform thickness for the sector using mitered joins.
336    #[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        // Collect ordered unique vertices of the sector
346        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        // Ensure the polygon is closed
357        if let (Some(&first), Some(&last)) = (polygon.first(), polygon.last()) {
358            if first != last {
359                polygon.push(first);
360            }
361        }
362
363        // Remove the artificially duplicated last point
364        polygon.pop();
365
366        let len = polygon.len();
367        if len < 3 {
368            return None;
369        }
370
371        // We'll store our "inner" and "outer" offset points
372        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            // Compute segment direction and normal for edges (prev->curr) and (curr->next)
381            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            // Compute the bisector and how much to offset
388            let bisector = (normal1 + normal2).normalized();
389            let angle = dir1.angle_between(dir2) / 2.0;
390
391            // Prevent near-zero or negative cosines from blowing up offset_length
392            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        // Convert all offset points into 'vertices' in one big buffer
402        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        // Now generate triangles connecting outer/inner rings
416        for i in 0..len {
417            let next = (i + 1) % len;
418
419            // Outer ring indices
420            let o1 = outer_indices[i];
421            let o2 = outer_indices[next];
422            // Inner ring indices
423            let i1 = inner_indices[i];
424            let i2 = inner_indices[next];
425
426            // Two triangles per quad:
427            //  1) outer[i], outer[i+1], inner[i]
428            //  2) outer[i+1], inner[i+1], inner[i]
429            indices.push((o1, o2, i1));
430            indices.push((o2, i2, i1));
431        }
432
433        Some((vertices, indices))
434    }
435
436    /// For each linedef, we produce its own (vertices, indices).
437    #[allow(clippy::type_complexity)]
438    pub fn generate_wall_geometry_by_linedef(
439        &self,
440        map: &Map,
441        // thickness: f32,
442    ) -> 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, return None
461        if all_walls_have_no_width {
462            return None;
463        }
464
465        // Ensure the polygon is closed
466        if let (Some(&first), Some(&last)) = (polygon.first(), polygon.last()) {
467            if first != last {
468                polygon.push(first);
469            }
470        }
471        // Remove the artificially duplicated last point (avoid zero-length edge)
472        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        /*
483        for i in 0..len {
484            let prev = polygon[(i + len - 1) % len];
485            let curr = polygon[i];
486            let next = polygon[(i + 1) % len];
487
488            // Directions and normals
489            let dir1 = (curr - prev).normalized();
490            let dir2 = (next - curr).normalized();
491
492            let normal1 = Vec2::new(-dir1.y, dir1.x);
493            let normal2 = Vec2::new(-dir2.y, dir2.x);
494
495            // Bisector for the corner
496            let bisector = (normal1 + normal2).normalized();
497
498            // Half the angle between edges
499            let angle = dir1.angle_between(dir2) / 2.0;
500            // Avoid blow-ups for near-180° angles
501            let offset_length = thickness / (2.0 * angle.cos()).max(0.1);
502
503            let outer = curr + bisector * offset_length;
504            let inner = curr - bisector * offset_length;
505
506            outer_points.push(outer);
507            inner_points.push(inner);
508        }*/
509
510        // For each corner i in [0..len)
511        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            // Directions, normals, angle as before...
517            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            // Look up each linedef's thickness:
525            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            // For a "smooth" approach, average them. (Or pick whichever logic you prefer.)
540            let corner_thickness = (t_prev + t_curr) * 0.5;
541
542            // The final offset distance at corner i
543            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        // The "wall" between corner i and i+1 is a quad -> 2 triangles
553        // We'll store them in the HashMap keyed by linedef_id.
554        for i in 0..len {
555            let next = (i + 1) % len;
556            let linedef_id = self.linedefs[i];
557
558            // 4 local, duplicated vertices for this linedef
559            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            // We'll store them in order: [o1, o2, i2, i1]
565            // so the winding of the first triangle is (0->1->3), etc.
566            let local_verts = vec![
567                [o1.x, o1.y], // index 0
568                [o2.x, o2.y], // index 1
569                [i2.x, i2.y], // index 2
570                [i1.x, i1.y], // index 3
571            ];
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        // Compare sectors by their geometry: same set of linedef IDs,
584        // ignoring order (and ignoring duplicate IDs if any).
585        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 {}