Skip to main content

rusterix/map/
mod.rs

1pub mod bbox;
2pub mod geometry;
3pub mod light;
4pub mod linedef;
5pub mod meta;
6pub mod mini;
7pub mod particle;
8pub mod pixelsource;
9pub mod sector;
10pub mod softrig;
11pub mod surface;
12pub mod tile;
13pub mod vertex;
14
15use crate::{
16    BBox, Keyform, MapMini, PixelSource, ShapeFXGraph, SoftRig, SoftRigAnimator, Surface, Terrain,
17    Value, ValueContainer,
18};
19use codegridfx::Module;
20use indexmap::IndexMap;
21use std::collections::VecDeque;
22use theframework::prelude::{FxHashMap, FxHashSet};
23
24use linedef::*;
25use sector::*;
26use serde::{Deserialize, Serialize};
27use uuid::Uuid;
28use vek::{Vec2, Vec3, Vec4};
29use vertex::*;
30
31use crate::{Entity, Item, Light};
32
33#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Copy)]
34pub enum MapCamera {
35    TwoD,
36    ThreeDIso,
37    ThreeDFirstPerson,
38}
39
40#[derive(Serialize, Deserialize, PartialEq, Clone, Debug, Copy)]
41pub enum MapToolType {
42    General,
43    Selection,
44    Vertex,
45    Linedef,
46    Sector,
47    Effects,
48    Rect,
49    Game,
50    MiniMap,
51    World,
52}
53
54#[derive(Serialize, Deserialize, Clone, Debug)]
55pub struct Map {
56    #[serde(default)]
57    pub id: Uuid,
58    pub name: String,
59
60    pub offset: Vec2<f32>,
61    pub grid_size: f32,
62    pub subdivisions: f32,
63
64    #[serde(default)]
65    pub terrain: Terrain,
66
67    // When adding linedefs we keep track of them to check if we have a closed polygon
68    #[serde(skip)]
69    pub possible_polygon: Vec<u32>,
70
71    // For temporary line previews
72    #[serde(skip)]
73    pub curr_grid_pos: Option<Vec2<f32>>,
74    #[serde(skip)]
75    pub curr_mouse_pos: Option<Vec2<f32>>,
76    #[serde(skip)]
77    pub curr_rectangle: Option<(Vec2<f32>, Vec2<f32>)>,
78
79    pub vertices: Vec<Vertex>,
80    pub linedefs: Vec<Linedef>,
81    pub sectors: Vec<Sector>,
82
83    #[serde(default)]
84    pub shapefx_graphs: IndexMap<Uuid, ShapeFXGraph>,
85
86    pub sky_texture: Option<Uuid>,
87
88    // Camera Mode
89    pub camera: MapCamera,
90    #[serde(skip)]
91    pub camera_xz: Option<Vec2<f32>>,
92    #[serde(skip)]
93    pub look_at_xz: Option<Vec2<f32>>,
94
95    // Lights
96    pub lights: Vec<Light>,
97
98    // Entities
99    pub entities: Vec<Entity>,
100
101    // Items
102    pub items: Vec<Item>,
103
104    // Selection
105    pub selected_vertices: Vec<u32>,
106    pub selected_linedefs: Vec<u32>,
107    pub selected_sectors: Vec<u32>,
108
109    pub selected_entity_item: Option<Uuid>,
110
111    // Meta Data
112    #[serde(default)]
113    pub properties: ValueContainer,
114
115    /// All SoftRigs in the map, each defining vertex-based keyforms
116    #[serde(default)]
117    pub softrigs: IndexMap<Uuid, SoftRig>,
118
119    /// Currently edited SoftRig, or None for base geometry
120    #[serde(skip)]
121    pub editing_rig: Option<Uuid>,
122
123    /// Vertex animation
124    #[serde(skip)]
125    pub soft_animator: Option<SoftRigAnimator>,
126
127    /// The surfaces of the 3D meshes.
128    #[serde(default)]
129    pub surfaces: IndexMap<Uuid, Surface>,
130
131    /// The optional profile of surfaces.
132    #[serde(default)]
133    pub profiles: FxHashMap<Uuid, Map>,
134
135    /// The shaders used in the map.
136    #[serde(default)]
137    pub shaders: IndexMap<Uuid, Module>,
138
139    // Change counter, right now only used for materials
140    // to indicate when to refresh live updates
141    #[serde(default)]
142    pub changed: u32,
143}
144
145impl Default for Map {
146    fn default() -> Self {
147        Self::new()
148    }
149}
150
151impl Map {
152    pub fn new() -> Self {
153        Self {
154            id: Uuid::new_v4(),
155            name: "New Map".to_string(),
156
157            offset: Vec2::zero(),
158            grid_size: 30.0,
159            subdivisions: 1.0,
160
161            terrain: Terrain::default(),
162
163            possible_polygon: vec![],
164            curr_grid_pos: None,
165            curr_mouse_pos: None,
166            curr_rectangle: None,
167
168            vertices: vec![],
169            linedefs: vec![],
170            sectors: vec![],
171
172            shapefx_graphs: IndexMap::default(),
173            sky_texture: None,
174
175            camera: MapCamera::TwoD,
176            camera_xz: None,
177            look_at_xz: None,
178
179            lights: vec![],
180            entities: vec![],
181            items: vec![],
182
183            selected_vertices: vec![],
184            selected_linedefs: vec![],
185            selected_sectors: vec![],
186
187            selected_entity_item: None,
188
189            properties: ValueContainer::default(),
190            softrigs: IndexMap::default(),
191            editing_rig: None,
192            soft_animator: None,
193
194            surfaces: IndexMap::default(),
195            profiles: FxHashMap::default(),
196            shaders: IndexMap::default(),
197
198            changed: 0,
199        }
200    }
201
202    /// Clear temporary data
203    pub fn clear_temp(&mut self) {
204        self.possible_polygon = vec![];
205        self.curr_grid_pos = None;
206        self.curr_rectangle = None;
207    }
208
209    /// Clear the selection
210    pub fn clear_selection(&mut self) {
211        self.selected_vertices = vec![];
212        self.selected_linedefs = vec![];
213        self.selected_sectors = vec![];
214        self.selected_entity_item = None;
215    }
216
217    /// Returns the surface for the given sector_id
218    pub fn get_surface_for_sector_id(&self, sector_id: u32) -> Option<&Surface> {
219        self.surfaces
220            .values()
221            .find(|surface| surface.sector_id == sector_id)
222    }
223
224    /// Returns the mutable surface for the given sector_id
225    pub fn get_surface_for_sector_id_mut(&mut self, sector_id: u32) -> Option<&mut Surface> {
226        self.surfaces
227            .values_mut()
228            .find(|surface| surface.sector_id == sector_id)
229    }
230
231    /// Updates the geometry of all surfaces
232    pub fn update_surfaces(&mut self) {
233        let mut surfaces = std::mem::take(&mut self.surfaces);
234        for (_id, surface) in surfaces.iter_mut() {
235            surface.calculate_geometry(self);
236        }
237        self.surfaces = surfaces;
238    }
239
240    /// Return the Map as MapMini
241    pub fn as_mini(&self, blocking_tiles: &FxHashSet<Uuid>) -> MapMini {
242        let mut linedefs: Vec<CompiledLinedef> = vec![];
243        let mut occluded_sectors: Vec<(BBox, f32)> = vec![];
244
245        let mut blocked_tiles = FxHashSet::default();
246
247        for sector in self.sectors.iter() {
248            let mut add_it = false;
249
250            // We collect occluded sectors
251            let occlusion = sector.properties.get_float_default("occlusion", 1.0);
252            if occlusion < 1.0 {
253                let mut bbox = sector.bounding_box(self);
254                bbox.expand(Vec2::new(0.1, 0.1));
255                occluded_sectors.push((bbox, occlusion));
256            }
257
258            if sector.layer.is_some() {
259                let render_mode = sector.properties.contains("rect");
260                if render_mode {
261                    add_it = false;
262                }
263                // If the tile is explicitly set to blocking we have to add the geometry
264                match sector.properties.get_default_source() {
265                    Some(PixelSource::TileId(id)) => {
266                        if blocking_tiles.contains(id) {
267                            add_it = true;
268                            if let Some(center) = sector.center(self) {
269                                blocked_tiles.insert(center.map(|c| (c.floor()) as i32));
270                            }
271                        }
272                    }
273                    Some(PixelSource::MaterialId(id)) => {
274                        if blocking_tiles.contains(id) {
275                            add_it = true;
276                        }
277                    }
278                    _ => {}
279                }
280            }
281
282            if add_it {
283                for linedef_id in sector.linedefs.iter() {
284                    if let Some(linedef) = self.find_linedef(*linedef_id) {
285                        if let Some(start) = self.find_vertex(linedef.start_vertex) {
286                            if let Some(end) = self.find_vertex(linedef.end_vertex) {
287                                let sy = start.as_vec3_world().y;
288                                let ey = end.as_vec3_world().y;
289                                if sy == 0.0 && ey == 0.0 {
290                                    let cl = CompiledLinedef::new(
291                                        start.as_vec2(),
292                                        end.as_vec2(),
293                                        linedef.properties.get_float_default("wall_width", 0.0),
294                                        linedef.properties.get_float_default("wall_height", 0.0),
295                                    );
296                                    linedefs.push(cl);
297                                }
298                            }
299                        }
300                    }
301                }
302            }
303        }
304
305        for l in self.linedefs.iter() {
306            if l.sector_ids.is_empty() {
307                let wall_height = l.properties.get_float_default("wall_height", 0.0);
308                let mut add_it = false;
309
310                // If the tile is explicitly set to blocking we have to add the geometry
311                match l.properties.get("source") {
312                    Some(Value::Source(PixelSource::TileId(id))) => {
313                        if blocking_tiles.contains(id) {
314                            add_it = true;
315                        }
316                    }
317                    Some(Value::Source(PixelSource::MaterialId(id))) => {
318                        if blocking_tiles.contains(id) {
319                            add_it = true;
320                        }
321                    }
322                    _ => {}
323                }
324
325                if add_it {
326                    if let Some(start) = self.find_vertex(l.start_vertex) {
327                        if let Some(end) = self.find_vertex(l.end_vertex) {
328                            let sy = start.as_vec3_world().y;
329                            let ey = end.as_vec3_world().y;
330                            if sy == 0.0 && ey == 0.0 {
331                                let cl = CompiledLinedef::new(
332                                    start.as_vec2(),
333                                    end.as_vec2(),
334                                    l.properties.get_float_default("wall_width", 0.0),
335                                    wall_height,
336                                );
337                                linedefs.push(cl);
338                            }
339                        }
340                    }
341                }
342            }
343        }
344
345        let mut mini = MapMini::new(self.offset, self.grid_size, linedefs, occluded_sectors);
346        mini.blocked_tiles = blocked_tiles;
347        mini
348    }
349
350    /// Generate a bounding box for all vertices in the map
351    pub fn bbox(&self) -> BBox {
352        // Find min and max coordinates among all vertices
353        let min_x = self
354            .vertices
355            .iter()
356            .map(|v| v.x)
357            .fold(f32::INFINITY, f32::min);
358        let max_x = self
359            .vertices
360            .iter()
361            .map(|v| v.x)
362            .fold(f32::NEG_INFINITY, f32::max);
363        let min_y = self
364            .vertices
365            .iter()
366            .map(|v| v.y)
367            .fold(f32::INFINITY, f32::min);
368        let max_y = self
369            .vertices
370            .iter()
371            .map(|v| v.y)
372            .fold(f32::NEG_INFINITY, f32::max);
373        BBox {
374            min: Vec2::new(min_x, min_y),
375            max: Vec2::new(max_x, max_y),
376        }
377    }
378
379    /// Generate a bounding box for all vertices in the map
380    pub fn bounding_box(&self) -> Option<Vec4<f32>> {
381        if self.vertices.is_empty() {
382            return None; // No vertices in the map
383        }
384
385        // Find min and max coordinates among all vertices
386        let min_x = self
387            .vertices
388            .iter()
389            .map(|v| v.x)
390            .fold(f32::INFINITY, f32::min);
391        let max_x = self
392            .vertices
393            .iter()
394            .map(|v| v.x)
395            .fold(f32::NEG_INFINITY, f32::max);
396        let min_y = self
397            .vertices
398            .iter()
399            .map(|v| v.y)
400            .fold(f32::INFINITY, f32::min);
401        let max_y = self
402            .vertices
403            .iter()
404            .map(|v| v.y)
405            .fold(f32::NEG_INFINITY, f32::max);
406
407        // Calculate width and height
408        let width = max_x - min_x;
409        let height = max_y - min_y;
410
411        // Return the bounding box as Vec4f (x, y, width, height)
412        Some(Vec4::new(min_x, min_y, width, height))
413    }
414
415    /// Tick the soft animator.
416    pub fn tick(&mut self, delta_time: f32) {
417        if let Some(anim) = &mut self.soft_animator {
418            anim.tick(delta_time);
419        }
420    }
421
422    /// Get the current position of a vertex, using any keyform override in the current SoftRig.
423    pub fn get_vertex(&self, vertex_id: u32) -> Option<Vec2<f32>> {
424        // Base vertex lookup
425        let base = self.vertices.iter().find(|v| v.id == vertex_id)?;
426        let base_pos = Vec2::new(base.x, base.y);
427
428        // 1. Try runtime animation
429        if let Some(animator) = &self.soft_animator {
430            if let Some(rig) = animator.get_blended_rig(self) {
431                if let Some((_, pos)) = rig
432                    .keyforms
433                    .first()
434                    .and_then(|key| key.vertex_positions.iter().find(|(id, _)| *id == vertex_id))
435                {
436                    return Some(*pos);
437                }
438            }
439        }
440
441        // 2. Try editing override (if not currently animating)
442        if self.soft_animator.is_none() {
443            if let Some(rig_id) = self.editing_rig {
444                if let Some(rig) = self.softrigs.get(&rig_id) {
445                    for keyform in &rig.keyforms {
446                        if let Some((_, pos)) = keyform
447                            .vertex_positions
448                            .iter()
449                            .find(|(id, _)| *id == vertex_id)
450                        {
451                            return Some(*pos);
452                        }
453                    }
454                }
455            }
456        }
457
458        // 3. Fallback to base
459        Some(base_pos)
460    }
461
462    /// Get the current position of a vertex, using any keyform override in the current SoftRig.
463    pub fn get_vertex_3d(&self, vertex_id: u32) -> Option<Vec3<f32>> {
464        // Base vertex lookup
465        let base = self.vertices.iter().find(|v| v.id == vertex_id)?;
466        let base_pos = Vec3::new(base.x, base.z, base.y);
467
468        // 1. Try runtime animation
469        // if let Some(animator) = &self.soft_animator {
470        //     if let Some(rig) = animator.get_blended_rig(self) {
471        //         if let Some((_, pos)) = rig
472        //             .keyforms
473        //             .first()
474        //             .and_then(|key| key.vertex_positions.iter().find(|(id, _)| *id == vertex_id))
475        //         {
476        //             return Some(*pos);
477        //         }
478        //     }
479        // }
480
481        // 2. Try editing override (if not currently animating)
482        // if self.soft_animator.is_none() {
483        //     if let Some(rig_id) = self.editing_rig {
484        //         if let Some(rig) = self.softrigs.get(&rig_id) {
485        //             for keyform in &rig.keyforms {
486        //                 if let Some((_, pos)) = keyform
487        //                     .vertex_positions
488        //                     .iter()
489        //                     .find(|(id, _)| *id == vertex_id)
490        //                 {
491        //                     return Some(*pos);
492        //                 }
493        //             }
494        //         }
495        //     }
496        // }
497
498        // 3. Fallback to base
499        Some(base_pos)
500    }
501
502    /// Update the vertex position. If a keyform in the selected rig contains this vertex, update it.
503    /// Otherwise, create a new keyform for this single vertex.
504    pub fn update_vertex(&mut self, vertex_id: u32, new_position: Vec2<f32>) {
505        // Update in active SoftRig
506        if let Some(rig_id) = self.editing_rig {
507            if let Some(rig) = self.softrigs.get_mut(&rig_id) {
508                // Try to find a keyform that already contains this vertex
509                for keyform in &mut rig.keyforms {
510                    if let Some(entry) = keyform
511                        .vertex_positions
512                        .iter_mut()
513                        .find(|(id, _)| *id == vertex_id)
514                    {
515                        entry.1 = new_position;
516                        return;
517                    }
518                }
519
520                // No existing keyform contains this vertex → create new keyform
521                let new_keyform = Keyform {
522                    vertex_positions: vec![(vertex_id, new_position)],
523                };
524
525                rig.keyforms.push(new_keyform);
526                return;
527            }
528        }
529
530        // Otherwise update base geometry
531        if let Some(v) = self.vertices.iter_mut().find(|v| v.id == vertex_id) {
532            v.x = new_position.x;
533            v.y = new_position.y;
534        }
535    }
536
537    // Add the vertex (and snap it to the subdivsion grid)
538    pub fn add_vertex_at(&mut self, mut x: f32, mut y: f32) -> u32 {
539        let subdivisions = 1.0 / self.subdivisions;
540
541        x = (x / subdivisions).round() * subdivisions;
542        y = (y / subdivisions).round() * subdivisions;
543
544        // Check if the vertex already exists
545        if let Some(id) = self.find_vertex_at(x, y) {
546            return id;
547        }
548
549        if let Some(id) = self.find_free_vertex_id() {
550            let vertex = Vertex::new(id, x, y);
551            self.vertices.push(vertex);
552            id
553        } else {
554            println!("No free vertex ID available");
555            0
556        }
557    }
558
559    /// Add a 3D vertex (x,y on the 2D grid; z is up).
560    pub fn add_vertex_at_3d(&mut self, mut x: f32, mut y: f32, mut z: f32, snap: bool) -> u32 {
561        // Snap X/Y using the same 2D grid/subdivision logic as add_vertex_at
562        if snap {
563            let subdivisions = 1.0 / self.subdivisions;
564            x = (x / subdivisions).round() * subdivisions;
565            y = (y / subdivisions).round() * subdivisions;
566            z = (z / subdivisions).round() * subdivisions;
567        }
568
569        // Check if a vertex at (x,y,z) already exists
570        if let Some(id) = self.find_vertex_at_3d(x, y, z) {
571            return id;
572        }
573
574        // Allocate a new vertex id and insert
575        if let Some(id) = self.find_free_vertex_id() {
576            let vertex = Vertex::new_3d(id, x, y, z);
577            self.vertices.push(vertex);
578            id
579        } else {
580            println!("No free vertex ID available");
581            0
582        }
583    }
584
585    /// Finds a vertex exactly at (x,y,z) and returns its ID if it exists
586    pub fn find_vertex_at_3d(&self, x: f32, y: f32, z: f32) -> Option<u32> {
587        self.vertices
588            .iter()
589            .find(|v| v.x == x && v.y == y && v.z == z)
590            .map(|v| v.id)
591    }
592
593    /// Finds a vertex at the given position and returns its ID if it exists
594    pub fn find_vertex_at(&self, x: f32, y: f32) -> Option<u32> {
595        self.vertices
596            .iter()
597            .find(|v| v.x == x && v.y == y)
598            .map(|v| v.id)
599    }
600
601    /// Finds a reference to a vertex by its ID
602    pub fn find_vertex(&self, id: u32) -> Option<&Vertex> {
603        self.vertices.iter().find(|vertex| vertex.id == id)
604    }
605
606    /// Finds a mutable reference to a vertex by its ID
607    pub fn find_vertex_mut(&mut self, id: u32) -> Option<&mut Vertex> {
608        self.vertices.iter_mut().find(|vertex| vertex.id == id)
609    }
610
611    /// Finds a reference to a linedef by its ID
612    pub fn find_linedef(&self, id: u32) -> Option<&Linedef> {
613        self.linedefs.iter().find(|linedef| linedef.id == id)
614    }
615
616    /// Finds a reference to a linedef by its ID
617    pub fn find_linedef_mut(&mut self, id: u32) -> Option<&mut Linedef> {
618        self.linedefs.iter_mut().find(|linedef| linedef.id == id)
619    }
620
621    /// Finds a mutable reference to a sector by its ID
622    pub fn find_sector(&self, id: u32) -> Option<&Sector> {
623        self.sectors.iter().find(|sector| sector.id == id)
624    }
625
626    /// Finds a mutable reference to a sector by its ID
627    pub fn find_sector_mut(&mut self, id: u32) -> Option<&mut Sector> {
628        self.sectors.iter_mut().find(|sector| sector.id == id)
629    }
630
631    // Create a new (or use an existing) linedef for the given vertices and closes a polygon sector if it detects a loop.
632    pub fn create_linedef(&mut self, start_vertex: u32, end_vertex: u32) -> (u32, Option<u32>) {
633        let mut sector_id: Option<u32> = None;
634
635        // Reuse an existing linedef only if it matches the requested winding direction exactly.
636        if let Some(existing) = self
637            .linedefs
638            .iter()
639            .find(|l| l.start_vertex == start_vertex && l.end_vertex == end_vertex)
640        {
641            let id = existing.id;
642            if let Some(polygon) = self.find_directed_cycle_from_edge(id) {
643                self.possible_polygon = polygon;
644                sector_id = self.create_sector_from_polygon();
645            }
646            return (id, sector_id);
647        }
648
649        // Create a new linedef as before and try to close a sector.
650        if let Some(id) = self.find_free_linedef_id() {
651            let linedef = Linedef::new(id, start_vertex, end_vertex);
652            self.linedefs.push(linedef);
653
654            if let Some(polygon) = self.find_directed_cycle_from_edge(id) {
655                self.possible_polygon = polygon;
656
657                if let Some(sid) = self.create_sector_from_polygon() {
658                    if let Some(linedef) = self.find_linedef_mut(id) {
659                        // Assign the new sector on the freshly created edge if possible; the full
660                        // assignment across the ring is handled inside create_sector_from_polygon.
661                        if !linedef.sector_ids.contains(&sid) {
662                            linedef.sector_ids.push(sid);
663                        }
664                    }
665                    sector_id = Some(sid);
666                }
667            }
668            (id, sector_id)
669        } else {
670            println!("No free linedef ID available");
671            (0, None)
672        }
673    }
674    // Create a new (or use an existing) linedef for the given vertices WITHOUT auto-creating sectors.
675    // This is useful for manual polygon creation where the user is drawing a sequence of lines.
676    // The linedef ID is added to possible_polygon for later manual sector creation.
677    pub fn create_linedef_manual(&mut self, start_vertex: u32, end_vertex: u32) -> u32 {
678        // Reuse an existing linedef if it matches the requested winding direction exactly.
679        if let Some(existing) = self
680            .linedefs
681            .iter()
682            .find(|l| l.start_vertex == start_vertex && l.end_vertex == end_vertex)
683        {
684            let id = existing.id;
685            // Add to possible_polygon for manual tracking
686            if !self.possible_polygon.contains(&id) {
687                self.possible_polygon.push(id);
688            }
689            return id;
690        }
691
692        // Create a new linedef
693        if let Some(id) = self.find_free_linedef_id() {
694            let linedef = Linedef::new(id, start_vertex, end_vertex);
695            self.linedefs.push(linedef);
696
697            // Add to possible_polygon for manual tracking
698            self.possible_polygon.push(id);
699            id
700        } else {
701            println!("No free linedef ID available");
702            0
703        }
704    }
705
706    // Manually close the current polygon tracked in possible_polygon if it forms a closed loop.
707    // Returns the sector ID if a sector was created, None otherwise.
708    pub fn close_polygon_manual(&mut self) -> Option<u32> {
709        if self.test_for_closed_polygon() {
710            self.create_sector_from_polygon()
711        } else {
712            None
713        }
714    }
715
716    // Check if a vertex is used by any sector with the "rect" property
717    pub fn is_vertex_in_rect_sector(&self, vertex_id: u32) -> bool {
718        self.sectors.iter().any(|sector| {
719            if sector.properties.contains("rect") {
720                sector.linedefs.iter().any(|&line_id| {
721                    if let Some(line) = self.find_linedef(line_id) {
722                        line.start_vertex == vertex_id || line.end_vertex == vertex_id
723                    } else {
724                        false
725                    }
726                })
727            } else {
728                false
729            }
730        })
731    }
732
733    // Duplicate a vertex at the same position and return the new vertex ID
734    pub fn duplicate_vertex(&mut self, vertex_id: u32) -> Option<u32> {
735        if let Some(vertex) = self.find_vertex(vertex_id) {
736            let new_id = self.find_free_vertex_id()?;
737            let mut new_vertex = vertex.clone();
738            new_vertex.id = new_id;
739            self.vertices.push(new_vertex);
740            Some(new_id)
741        } else {
742            None
743        }
744    }
745
746    // Replace a vertex in a sector's linedefs with a new vertex
747    pub fn replace_vertex_in_sector(
748        &mut self,
749        sector_id: u32,
750        old_vertex_id: u32,
751        new_vertex_id: u32,
752    ) {
753        if let Some(sector) = self.find_sector(sector_id) {
754            let linedef_ids: Vec<u32> = sector.linedefs.clone();
755            for linedef_id in linedef_ids {
756                if let Some(linedef) = self.find_linedef(linedef_id) {
757                    // Check if this linedef is shared with other sectors
758                    let is_shared = linedef.sector_ids.len() > 1
759                        || self
760                            .sectors
761                            .iter()
762                            .any(|s| s.id != sector_id && s.linedefs.contains(&linedef_id));
763
764                    let needs_vertex_change = linedef.start_vertex == old_vertex_id
765                        || linedef.end_vertex == old_vertex_id;
766
767                    if is_shared && needs_vertex_change {
768                        // Linedef is shared - create a new linedef for this sector
769                        let new_start = if linedef.start_vertex == old_vertex_id {
770                            new_vertex_id
771                        } else {
772                            linedef.start_vertex
773                        };
774                        let new_end = if linedef.end_vertex == old_vertex_id {
775                            new_vertex_id
776                        } else {
777                            linedef.end_vertex
778                        };
779
780                        if let Some(new_linedef_id) = self.find_free_linedef_id() {
781                            let mut new_linedef = linedef.clone();
782                            new_linedef.id = new_linedef_id;
783                            new_linedef.start_vertex = new_start;
784                            new_linedef.end_vertex = new_end;
785                            new_linedef.sector_ids = vec![sector_id];
786                            self.linedefs.push(new_linedef);
787
788                            // Remove sector_id from old linedef's sector_ids
789                            if let Some(old_linedef) = self.find_linedef_mut(linedef_id) {
790                                old_linedef.sector_ids.retain(|&sid| sid != sector_id);
791                            }
792
793                            // Replace the linedef in sector's linedefs list
794                            if let Some(sector) = self.find_sector_mut(sector_id) {
795                                if let Some(pos) =
796                                    sector.linedefs.iter().position(|&id| id == linedef_id)
797                                {
798                                    sector.linedefs[pos] = new_linedef_id;
799                                }
800                            }
801                        }
802                    } else if needs_vertex_change {
803                        // Linedef is not shared - modify it directly
804                        if let Some(linedef) = self.find_linedef_mut(linedef_id) {
805                            if linedef.start_vertex == old_vertex_id {
806                                linedef.start_vertex = new_vertex_id;
807                            }
808                            if linedef.end_vertex == old_vertex_id {
809                                linedef.end_vertex = new_vertex_id;
810                            }
811                        }
812                    }
813                }
814            }
815        }
816    }
817    /// Attempts to find a closed directed cycle that uses the provided linedef ID.
818    /// The traversal walks forward along linedef winding to keep sector orientation deterministic.
819    fn find_directed_cycle_from_edge(&self, edge_id: u32) -> Option<Vec<u32>> {
820        let edge = self.find_linedef(edge_id)?;
821
822        // We look for a directed path from the end of the new edge back to its start.
823        let path = self.find_directed_path(edge.end_vertex, edge.start_vertex, edge_id)?;
824
825        // A polygon needs at least three edges: path (>=1) + the new edge.
826        if path.len() + 1 < 3 {
827            return None;
828        }
829
830        let mut cycle = path;
831        cycle.push(edge_id);
832        Some(cycle)
833    }
834
835    /// Breadth-first search for a directed path from `from` to `to`, following linedef winding.
836    /// The search is iterative (no recursion) and skips the provided `skip_edge` ID.
837    fn find_directed_path(&self, from: u32, to: u32, skip_edge: u32) -> Option<Vec<u32>> {
838        let mut queue = VecDeque::new();
839        let mut visited = FxHashSet::default();
840        let mut parent: FxHashMap<u32, (u32, u32)> = FxHashMap::default(); // vertex -> (prev_vertex, edge_id)
841
842        queue.push_back(from);
843        visited.insert(from);
844
845        while let Some(v) = queue.pop_front() {
846            // Collect all outgoing edges that respect winding (start == v)
847            for edge in self.linedefs.iter().filter(|e| e.start_vertex == v) {
848                if edge.id == skip_edge {
849                    continue;
850                }
851                let next = edge.end_vertex;
852
853                if visited.contains(&next) {
854                    continue;
855                }
856
857                parent.insert(next, (v, edge.id));
858
859                if next == to {
860                    // Reconstruct edge list from `from` -> ... -> `to`
861                    let mut path = Vec::new();
862                    let mut current = to;
863                    while let Some((prev_vertex, edge_id)) = parent.get(&current) {
864                        path.push(*edge_id);
865                        if *prev_vertex == from {
866                            break;
867                        }
868                        current = *prev_vertex;
869                    }
870                    path.reverse();
871                    return Some(path);
872                }
873
874                visited.insert(next);
875                queue.push_back(next);
876            }
877        }
878
879        None
880    }
881
882    /// Check if the `possible_polygon` forms a closed loop
883    pub fn test_for_closed_polygon(&self) -> bool {
884        if self.possible_polygon.len() < 3 {
885            return false; // A polygon needs at least 3 edges
886        }
887
888        if let Some(first_linedef) = self.find_linedef(self.possible_polygon[0]) {
889            if let Some(last_linedef) =
890                self.find_linedef(self.possible_polygon[self.possible_polygon.len() - 1])
891            {
892                // Check if the last linedef's end_vertex matches the first linedef's start_vertex
893                return last_linedef.end_vertex == first_linedef.start_vertex;
894            }
895        }
896        false
897    }
898
899    /// Tries to create a polyon from the tracked vertices in possible_polygon
900    pub fn create_sector_from_polygon(&mut self) -> Option<u32> {
901        if !self.test_for_closed_polygon() {
902            // println!("Polygon is not closed. Cannot create sector.");
903            return None;
904        }
905
906        // Check for duplicate sector
907        if self
908            .find_sector_by_linedefs(&self.possible_polygon)
909            .is_some()
910        {
911            // println!(
912            //     "Polygon already exists",
913            // );
914            self.possible_polygon.clear();
915            return None;
916        }
917
918        // Create a new sector
919        if let Some(sector_id) = self.find_free_sector_id() {
920            for &id in &self.possible_polygon {
921                if let Some(linedef) = self.linedefs.iter_mut().find(|l| l.id == id) {
922                    // Add sector to the sector_ids list
923                    if !linedef.sector_ids.contains(&sector_id) {
924                        linedef.sector_ids.push(sector_id);
925                    }
926                }
927            }
928
929            let sector = Sector::new(sector_id, self.possible_polygon.clone());
930            self.sectors.push(sector);
931
932            self.possible_polygon.clear();
933            Some(sector_id)
934        } else {
935            None
936        }
937    }
938
939    /// Check if a set of linedefs matches any existing sector
940    fn find_sector_by_linedefs(&self, linedefs: &[u32]) -> Option<u32> {
941        for sector in &self.sectors {
942            if sector.linedefs.len() == linedefs.len()
943                && sector.linedefs.iter().all(|id| linedefs.contains(id))
944            {
945                return Some(sector.id);
946            }
947        }
948        None
949    }
950
951    /// Deletes the specified vertices, linedefs, and sectors.
952    pub fn delete_elements(&mut self, vertex_ids: &[u32], linedef_ids: &[u32], sector_ids: &[u32]) {
953        let mut all_linedef_ids = linedef_ids.to_vec();
954        let all_vertex_ids = vertex_ids.to_vec();
955
956        // Step 1: Collect linedefs (and eventually vertices) from sectors to delete
957        if !sector_ids.is_empty() {
958            for sector in &self.sectors {
959                if sector_ids.contains(&sector.id) {
960                    for &linedef_id in &sector.linedefs {
961                        // Check if this linedef is used by other sectors (that are not also being deleted)
962                        // We check both: sector.linedefs lists AND linedef.sector_ids
963                        let used_in_sector_linedefs = self.sectors.iter().any(|s| {
964                            s.id != sector.id
965                                && !sector_ids.contains(&s.id)
966                                && s.linedefs.contains(&linedef_id)
967                        });
968
969                        let used_in_linedef_sector_ids =
970                            if let Some(linedef) = self.find_linedef(linedef_id) {
971                                linedef
972                                    .sector_ids
973                                    .iter()
974                                    .any(|&sid| sid != sector.id && !sector_ids.contains(&sid))
975                            } else {
976                                false
977                            };
978
979                        let used_elsewhere = used_in_sector_linedefs || used_in_linedef_sector_ids;
980
981                        if !used_elsewhere && !all_linedef_ids.contains(&linedef_id) {
982                            all_linedef_ids.push(linedef_id);
983                        }
984                    }
985                }
986            }
987        }
988        /*
989        // Do not delete vertices from deleted linedefs / sectors, only the selected ones.
990        // Step 2: Collect vertices used *only* by linedefs being deleted
991        for &linedef_id in &all_linedef_ids {
992            if let Some(linedef) = self.find_linedef(linedef_id) {
993                for &vertex_id in &[linedef.start_vertex, linedef.end_vertex] {
994                    let used_elsewhere = self.linedefs.iter().any(|l| {
995                        l.id != linedef_id
996                            && (l.start_vertex == vertex_id || l.end_vertex == vertex_id)
997                            && !all_linedef_ids.contains(&l.id)
998                    });
999
1000                    if !used_elsewhere && !all_vertex_ids.contains(&vertex_id) {
1001                        all_vertex_ids.push(vertex_id);
1002                    }
1003                }
1004            }
1005        }*/
1006
1007        // Step 3: Delete sectors
1008        if !sector_ids.is_empty() {
1009            self.sectors
1010                .retain(|sector| !sector_ids.contains(&sector.id));
1011
1012            // Collect surfaces and remove them
1013            let mut surfaces_to_remove: Vec<uuid::Uuid> = Vec::new();
1014            for (surf_id, surf) in self.surfaces.iter() {
1015                if sector_ids.contains(&surf.sector_id) {
1016                    if let Some(profile_id) = surf.profile {
1017                        self.profiles.remove(&profile_id);
1018                    }
1019                    surfaces_to_remove.push(*surf_id);
1020                }
1021            }
1022
1023            for sid in surfaces_to_remove {
1024                let _ = self.surfaces.shift_remove(&sid);
1025            }
1026
1027            // --
1028
1029            for linedef in &mut self.linedefs {
1030                // Remove deleted sectors from sector_ids list
1031                linedef.sector_ids.retain(|sid| !sector_ids.contains(sid));
1032            }
1033        }
1034
1035        // Step 3.5: Before deleting vertices, collect linedefs that reference them
1036        for &vertex_id in &all_vertex_ids {
1037            for linedef in &self.linedefs {
1038                if (linedef.start_vertex == vertex_id || linedef.end_vertex == vertex_id)
1039                    && !all_linedef_ids.contains(&linedef.id)
1040                {
1041                    all_linedef_ids.push(linedef.id);
1042                }
1043            }
1044        }
1045
1046        // Step 4: Delete linedefs
1047        if !all_linedef_ids.is_empty() {
1048            self.linedefs
1049                .retain(|linedef| !all_linedef_ids.contains(&linedef.id));
1050        }
1051
1052        self.cleanup_sectors();
1053
1054        // Step 5: Delete vertices (only if explicitly requested or now truly orphaned)
1055        if !all_vertex_ids.is_empty() {
1056            self.vertices
1057                .retain(|vertex| !all_vertex_ids.contains(&vertex.id));
1058        }
1059        // Step 6: Sanitize to ensure consistency (rebuild surfaces, clean up any remaining issues)
1060        self.sanitize();
1061    }
1062
1063    /// Cleans up sectors to ensure no references to deleted linedefs remain.
1064    fn cleanup_sectors(&mut self) {
1065        let valid_linedef_ids: std::collections::HashSet<u32> =
1066            self.linedefs.iter().map(|l| l.id).collect();
1067
1068        for sector in &mut self.sectors {
1069            sector
1070                .linedefs
1071                .retain(|linedef_id| valid_linedef_ids.contains(linedef_id));
1072        }
1073
1074        // Remove empty sectors
1075        self.sectors.retain(|sector| !sector.linedefs.is_empty());
1076    }
1077
1078    /// Check if a given linedef ID is part of any sector.
1079    pub fn is_linedef_in_closed_polygon(&self, linedef_id: u32) -> bool {
1080        self.sectors
1081            .iter()
1082            .any(|sector| sector.linedefs.contains(&linedef_id))
1083    }
1084
1085    /// Add the given geometry to the selection.
1086    pub fn add_to_selection(&mut self, vertices: Vec<u32>, linedefs: Vec<u32>, sectors: Vec<u32>) {
1087        for v in &vertices {
1088            if !self.selected_vertices.contains(v) {
1089                self.selected_vertices.push(*v);
1090            }
1091        }
1092        for l in &linedefs {
1093            if !self.selected_linedefs.contains(l) {
1094                self.selected_linedefs.push(*l);
1095            }
1096        }
1097        for s in &sectors {
1098            if !self.selected_sectors.contains(s) {
1099                self.selected_sectors.push(*s);
1100            }
1101        }
1102    }
1103
1104    /// Remove the given geometry from the selection.
1105    pub fn remove_from_selection(
1106        &mut self,
1107        vertices: Vec<u32>,
1108        linedefs: Vec<u32>,
1109        sectors: Vec<u32>,
1110    ) {
1111        for v in &vertices {
1112            self.selected_vertices.retain(|&selected| selected != *v);
1113        }
1114        for l in &linedefs {
1115            self.selected_linedefs.retain(|&selected| selected != *l);
1116        }
1117        for s in &sectors {
1118            self.selected_sectors.retain(|&selected| selected != *s);
1119        }
1120    }
1121
1122    /// Returns the sectors sorted from largest to smallest by area
1123    pub fn sorted_sectors_by_area(&self) -> Vec<&Sector> {
1124        let mut sectors_with_areas: Vec<(&Sector, f32)> = self
1125            .sectors
1126            .iter()
1127            .map(|sector| (sector, sector.area(self))) // Calculate the area for each sector
1128            .collect();
1129
1130        // Sort by area in descending order
1131        sectors_with_areas
1132            .sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
1133
1134        // Return the sorted sectors
1135        sectors_with_areas
1136            .into_iter()
1137            .map(|(sector, _)| sector)
1138            .collect()
1139    }
1140
1141    /// Adds a midpoint to a specified linedef, updates the geometry, and returns the new vertex ID.
1142    pub fn add_midpoint(&mut self, linedef_id: u32) -> Option<u32> {
1143        // Step 1: Find the linedef
1144        let linedef = self.find_linedef(linedef_id)?.clone(); // Clone to avoid borrow issues
1145        let start_vertex = self.find_vertex(linedef.start_vertex)?.clone();
1146        let end_vertex = self.find_vertex(linedef.end_vertex)?.clone();
1147
1148        // Step 2: Calculate the midpoint
1149        let midpoint = Vec3::new(
1150            (start_vertex.x + end_vertex.x) / 2.0,
1151            (start_vertex.y + end_vertex.y) / 2.0,
1152            (start_vertex.z + end_vertex.z) / 2.0,
1153        );
1154
1155        // Step 3: Add the midpoint as a new vertex
1156        let new_vertex_id = self.add_vertex_at_3d(midpoint.x, midpoint.y, midpoint.z, false);
1157
1158        // Step 4: Create new linedefs
1159        let mut new_linedef_1 = Linedef::new(
1160            linedef_id, // Use the same ID as the old linedef for the first new linedef
1161            linedef.start_vertex,
1162            new_vertex_id,
1163        );
1164        let mut new_linedef_2 = Linedef::new(
1165            self.linedefs.len() as u32, // New unique ID for the second linedef
1166            new_vertex_id,
1167            linedef.end_vertex,
1168        );
1169
1170        // Assign the old properties of the linedef to the two new ones.
1171        new_linedef_1.properties = linedef.properties.clone();
1172        new_linedef_2.properties = linedef.properties.clone();
1173
1174        // Step 5: Replace the old linedef in all sectors
1175        for sector in self.sectors.iter_mut() {
1176            if let Some(position) = sector.linedefs.iter().position(|&id| id == linedef_id) {
1177                // Replace the old linedef with the new ones in the correct order
1178                sector.linedefs.splice(
1179                    position..=position, // Replace the single linedef
1180                    [new_linedef_1.id, new_linedef_2.id].iter().cloned(), // Insert the new linedefs
1181                );
1182            }
1183        }
1184
1185        // Step 6: Update the global linedef list
1186        if let Some(index) = self.linedefs.iter().position(|l| l.id == linedef_id) {
1187            self.linedefs[index] = new_linedef_1; // Replace the old linedef with the first new one
1188        }
1189        self.linedefs.push(new_linedef_2); // Add the second new linedef at the end
1190
1191        // Return the ID of the new vertex
1192        Some(new_vertex_id)
1193    }
1194
1195    /// Find sectors which consist of exactly the same 4 vertices and return them.
1196    /// This is used for stacking tiles / layering via the RECT tool.
1197    pub fn find_sectors_with_vertex_indices(&self, vertex_indices: &[u32; 4]) -> Vec<u32> {
1198        let mut matching_sectors = Vec::new();
1199
1200        let mut new_vertex_set = vertex_indices.to_vec();
1201        new_vertex_set.sort();
1202
1203        for sector in &self.sectors {
1204            let mut sector_vertex_indices = Vec::new();
1205
1206            for &linedef_id in &sector.linedefs {
1207                if let Some(linedef) = self.find_linedef(linedef_id) {
1208                    sector_vertex_indices.push(linedef.start_vertex);
1209                    sector_vertex_indices.push(linedef.end_vertex);
1210                }
1211            }
1212
1213            // Deduplicate and sort for consistent comparison
1214            sector_vertex_indices.sort();
1215            sector_vertex_indices.dedup();
1216
1217            // If the sector contains exactly these 4 vertices, it's a match
1218            if sector_vertex_indices == new_vertex_set {
1219                matching_sectors.push(sector.id);
1220            }
1221        }
1222
1223        matching_sectors
1224    }
1225
1226    /// Returns the sector at the given position (if any).
1227    pub fn find_sector_at(&self, position: Vec2<f32>) -> Option<&Sector> {
1228        self.sectors
1229            .iter()
1230            .find(|s| s.is_inside(self, position) && s.layer.is_none())
1231    }
1232
1233    /// Debug: Print all vertices with their current animated positions
1234    pub fn debug_print_vertices(&self) {
1235        for vertex in &self.vertices {
1236            let current_position = self
1237                .get_vertex(vertex.id)
1238                .unwrap_or(Vec2::new(vertex.x, vertex.y));
1239            println!(
1240                "Vertex ID: {}, Base: ({}, {}), Animated: ({}, {})",
1241                vertex.id, vertex.x, vertex.y, current_position.x, current_position.y
1242            );
1243        }
1244    }
1245
1246    /// Returns information about the Map
1247    pub fn info(&self) -> String {
1248        format!(
1249            "V {}, L {}, S {}",
1250            self.vertices.len(),
1251            self.linedefs.len(),
1252            self.sectors.len()
1253        )
1254    }
1255
1256    /// Sanitizes and associates linedefs with their sectors by populating the sector_ids vector.
1257    /// Removes orphaned linedefs that reference non-existent vertices.
1258    /// This should be called after loading a map or when sectors are modified.
1259    pub fn sanitize(&mut self) {
1260        // First, sanitize: remove linedefs that reference non-existent vertices
1261        let valid_vertex_ids: std::collections::HashSet<u32> =
1262            self.vertices.iter().map(|v| v.id).collect();
1263
1264        let mut orphaned_linedef_ids = Vec::new();
1265
1266        for linedef in &self.linedefs {
1267            if !valid_vertex_ids.contains(&linedef.start_vertex)
1268                || !valid_vertex_ids.contains(&linedef.end_vertex)
1269            {
1270                println!(
1271                    "Sanitizing: removing orphaned linedef {} (references vertices {} -> {})",
1272                    linedef.id, linedef.start_vertex, linedef.end_vertex
1273                );
1274                orphaned_linedef_ids.push(linedef.id);
1275            }
1276        }
1277
1278        if !orphaned_linedef_ids.is_empty() {
1279            self.linedefs
1280                .retain(|linedef| !orphaned_linedef_ids.contains(&linedef.id));
1281
1282            // Collect sectors before cleanup to remove their surfaces
1283            let sectors_before: std::collections::HashSet<u32> =
1284                self.sectors.iter().map(|s| s.id).collect();
1285
1286            // Clean up sectors that reference these linedefs (removes invalid refs and empty sectors)
1287            self.cleanup_sectors();
1288
1289            // Find which sectors were removed
1290            let sectors_after: std::collections::HashSet<u32> =
1291                self.sectors.iter().map(|s| s.id).collect();
1292            let removed_sector_ids: Vec<u32> =
1293                sectors_before.difference(&sectors_after).copied().collect();
1294
1295            // Remove surfaces for deleted sectors
1296            if !removed_sector_ids.is_empty() {
1297                let mut surfaces_to_remove: Vec<uuid::Uuid> = Vec::new();
1298                for (surf_id, surf) in self.surfaces.iter() {
1299                    if removed_sector_ids.contains(&surf.sector_id) {
1300                        if let Some(profile_id) = surf.profile {
1301                            self.profiles.remove(&profile_id);
1302                        }
1303                        surfaces_to_remove.push(*surf_id);
1304                    }
1305                }
1306
1307                for sid in surfaces_to_remove {
1308                    let _ = self.surfaces.shift_remove(&sid);
1309                }
1310
1311                println!(
1312                    "Sanitized: removed {} sector(s) and their surfaces",
1313                    removed_sector_ids.len()
1314                );
1315            }
1316
1317            println!(
1318                "Sanitized: removed {} orphaned linedef(s)",
1319                orphaned_linedef_ids.len()
1320            );
1321        }
1322
1323        // Additional validation: check for sectors with invalid geometry
1324        let mut invalid_sectors = Vec::new();
1325        for sector in &self.sectors {
1326            // Check if sector has enough linedefs to form a polygon
1327            if sector.linedefs.len() < 3 {
1328                println!(
1329                    "Sanitizing: sector {} has only {} linedef(s), need at least 3",
1330                    sector.id,
1331                    sector.linedefs.len()
1332                );
1333                invalid_sectors.push(sector.id);
1334                continue;
1335            }
1336
1337            // Check if all linedefs in the sector reference valid vertices
1338            let mut has_invalid_linedef = false;
1339            for &linedef_id in &sector.linedefs {
1340                if let Some(linedef) = self.find_linedef(linedef_id) {
1341                    if self.find_vertex(linedef.start_vertex).is_none()
1342                        || self.find_vertex(linedef.end_vertex).is_none()
1343                    {
1344                        println!(
1345                            "Sanitizing: sector {} has linedef {} with invalid vertices",
1346                            sector.id, linedef_id
1347                        );
1348                        has_invalid_linedef = true;
1349                        break;
1350                    }
1351                } else {
1352                    println!(
1353                        "Sanitizing: sector {} references non-existent linedef {}",
1354                        sector.id, linedef_id
1355                    );
1356                    has_invalid_linedef = true;
1357                    break;
1358                }
1359            }
1360            if has_invalid_linedef {
1361                invalid_sectors.push(sector.id);
1362                continue;
1363            }
1364
1365            // Check if the sector forms a closed loop
1366            if let Some(first_linedef) = self.find_linedef(sector.linedefs[0]) {
1367                if let Some(last_linedef) =
1368                    self.find_linedef(sector.linedefs[sector.linedefs.len() - 1])
1369                {
1370                    if last_linedef.end_vertex != first_linedef.start_vertex {
1371                        println!(
1372                            "Sanitizing: sector {} does not form a closed loop (last vertex {} != first vertex {})",
1373                            sector.id, last_linedef.end_vertex, first_linedef.start_vertex
1374                        );
1375                        invalid_sectors.push(sector.id);
1376                        continue;
1377                    }
1378                }
1379            }
1380
1381            // Check for consecutive duplicate vertices (zero-length edges)
1382            let mut has_zero_length = false;
1383            for &linedef_id in &sector.linedefs {
1384                if let Some(linedef) = self.find_linedef(linedef_id) {
1385                    if linedef.start_vertex == linedef.end_vertex {
1386                        println!(
1387                            "Sanitizing: sector {} has linedef {} with same start and end vertex {}",
1388                            sector.id, linedef_id, linedef.start_vertex
1389                        );
1390                        has_zero_length = true;
1391                        break;
1392                    }
1393                }
1394            }
1395            if has_zero_length {
1396                invalid_sectors.push(sector.id);
1397                continue;
1398            }
1399
1400            // Check for NaN or infinite vertex coordinates
1401            let mut has_invalid_coords = false;
1402            for &linedef_id in &sector.linedefs {
1403                if let Some(linedef) = self.find_linedef(linedef_id) {
1404                    if let Some(start_v) = self.find_vertex(linedef.start_vertex) {
1405                        if !start_v.x.is_finite()
1406                            || !start_v.y.is_finite()
1407                            || !start_v.z.is_finite()
1408                        {
1409                            println!(
1410                                "Sanitizing: sector {} has vertex {} with invalid coordinates: ({}, {}, {})",
1411                                sector.id, linedef.start_vertex, start_v.x, start_v.y, start_v.z
1412                            );
1413                            has_invalid_coords = true;
1414                            break;
1415                        }
1416                    }
1417                    if let Some(end_v) = self.find_vertex(linedef.end_vertex) {
1418                        if !end_v.x.is_finite() || !end_v.y.is_finite() || !end_v.z.is_finite() {
1419                            println!(
1420                                "Sanitizing: sector {} has vertex {} with invalid coordinates: ({}, {}, {})",
1421                                sector.id, linedef.end_vertex, end_v.x, end_v.y, end_v.z
1422                            );
1423                            has_invalid_coords = true;
1424                            break;
1425                        }
1426                    }
1427                }
1428            }
1429            if has_invalid_coords {
1430                invalid_sectors.push(sector.id);
1431            }
1432        }
1433
1434        // Remove invalid sectors
1435        if !invalid_sectors.is_empty() {
1436            // Remove surfaces for these sectors
1437            let mut surfaces_to_remove: Vec<uuid::Uuid> = Vec::new();
1438            for (surf_id, surf) in self.surfaces.iter() {
1439                if invalid_sectors.contains(&surf.sector_id) {
1440                    if let Some(profile_id) = surf.profile {
1441                        self.profiles.remove(&profile_id);
1442                    }
1443                    surfaces_to_remove.push(*surf_id);
1444                }
1445            }
1446
1447            for sid in surfaces_to_remove {
1448                let _ = self.surfaces.shift_remove(&sid);
1449            }
1450
1451            self.sectors.retain(|s| !invalid_sectors.contains(&s.id));
1452
1453            println!(
1454                "Sanitized: removed {} invalid sector(s)",
1455                invalid_sectors.len()
1456            );
1457        }
1458
1459        // Rebuild surfaces for remaining sectors and check for invalid transforms
1460        self.update_surfaces();
1461
1462        let mut invalid_surface_sectors = Vec::new();
1463        for (_surface_id, surface) in &self.surfaces {
1464            if !surface.is_valid() {
1465                println!(
1466                    "Sanitizing: sector {} has surface with invalid transform (NaN/Inf)",
1467                    surface.sector_id
1468                );
1469                invalid_surface_sectors.push(surface.sector_id);
1470            }
1471        }
1472
1473        // Remove sectors with invalid surfaces
1474        if !invalid_surface_sectors.is_empty() {
1475            // Remove the invalid surfaces and profiles
1476            let mut surfaces_to_remove: Vec<uuid::Uuid> = Vec::new();
1477            for (surf_id, surf) in self.surfaces.iter() {
1478                if invalid_surface_sectors.contains(&surf.sector_id) {
1479                    if let Some(profile_id) = surf.profile {
1480                        self.profiles.remove(&profile_id);
1481                    }
1482                    surfaces_to_remove.push(*surf_id);
1483                }
1484            }
1485
1486            for sid in surfaces_to_remove {
1487                let _ = self.surfaces.shift_remove(&sid);
1488            }
1489
1490            // Remove the sectors themselves
1491            self.sectors
1492                .retain(|s| !invalid_surface_sectors.contains(&s.id));
1493
1494            println!(
1495                "Sanitized: removed {} sector(s) with invalid surfaces",
1496                invalid_surface_sectors.len()
1497            );
1498        }
1499
1500        // Now, clear all existing sector_ids
1501        for linedef in &mut self.linedefs {
1502            linedef.sector_ids.clear();
1503        }
1504
1505        // Collect all linedef-to-sector associations
1506        let mut associations: FxHashMap<u32, Vec<u32>> = FxHashMap::default();
1507        for sector in &self.sectors {
1508            let sector_id = sector.id;
1509            for &linedef_id in &sector.linedefs {
1510                associations
1511                    .entry(linedef_id)
1512                    .or_insert_with(Vec::new)
1513                    .push(sector_id);
1514            }
1515        }
1516
1517        // Apply the associations to linedefs
1518        for (linedef_id, sector_ids) in associations {
1519            if let Some(linedef) = self.linedefs.iter_mut().find(|l| l.id == linedef_id) {
1520                linedef.sector_ids = sector_ids;
1521            }
1522        }
1523    }
1524
1525    /// Alias for sanitize() to maintain backward compatibility.
1526    pub fn associate_linedefs_with_sectors(&mut self) {
1527        self.sanitize();
1528    }
1529
1530    // /// Returns true if the given vertex is part of a sector with rect rendering enabled.
1531    // pub fn is_vertex_in_rect(&self, vertex_id: u32) -> bool {
1532    //     for sector in &self.sectors {
1533    //         if sector.layer.is_none() {
1534    //             continue;
1535    //         }
1536    //         for linedef_id in sector.linedefs.iter() {
1537    //             if let Some(linedef) = self.find_linedef(*linedef_id) {
1538    //                 if linedef.start_vertex == vertex_id || linedef.end_vertex == vertex_id {
1539    //                     return true;
1540    //                 }
1541    //             }
1542    //         }
1543    //     }
1544    //     false
1545    // }
1546
1547    /// Returns true if the given linedef is part of a sector with rect rendering enabled.
1548    pub fn is_linedef_in_rect(&self, linedef_id: u32) -> bool {
1549        for sector in &self.sectors {
1550            if sector.layer.is_none() {
1551                continue;
1552            }
1553
1554            if sector.linedefs.contains(&linedef_id) {
1555                return true;
1556            }
1557        }
1558        false
1559    }
1560
1561    /// Finds a free vertex ID that can be used for creating a new vertex.
1562    pub fn find_free_vertex_id(&self) -> Option<u32> {
1563        (0..).find(|&id| !self.vertices.iter().any(|v| v.id == id))
1564    }
1565
1566    /// Finds a free linedef ID that can be used for creating a new linedef.
1567    pub fn find_free_linedef_id(&self) -> Option<u32> {
1568        (0..).find(|&id| !self.linedefs.iter().any(|l| l.id == id))
1569    }
1570
1571    /// Finds a free sector ID that can be used for creating a new sector.
1572    pub fn find_free_sector_id(&self) -> Option<u32> {
1573        (0..).find(|&id| !self.sectors.iter().any(|s| s.id == id))
1574    }
1575
1576    /// Check if the map has selected geometry.
1577    pub fn has_selection(&self) -> bool {
1578        !self.selected_vertices.is_empty()
1579            || !self.selected_linedefs.is_empty()
1580            || !self.selected_sectors.is_empty()
1581    }
1582
1583    /// Check if the map is empty.
1584    pub fn is_empty(&self) -> bool {
1585        self.vertices.is_empty() && self.linedefs.is_empty() && self.sectors.is_empty()
1586    }
1587
1588    /// Copy selected geometry into a new map
1589    pub fn copy_selected(&mut self, cut: bool) -> Map {
1590        let mut clipboard = Map::new();
1591
1592        let mut old_to_new_vertex: FxHashMap<u32, u32> = FxHashMap::default();
1593        let mut old_to_new_linedef: FxHashMap<u32, u32> = FxHashMap::default();
1594        // let mut old_to_new_sector: FxHashMap<u32, u32> = FxHashMap::default();
1595
1596        let mut vertex_ids: FxHashSet<u32> = FxHashSet::default();
1597        let mut linedef_ids: FxHashSet<u32> = self.selected_linedefs.iter().copied().collect();
1598        let sector_ids: FxHashSet<u32> = self.selected_sectors.iter().copied().collect();
1599
1600        // Add linedefs from selected sectors
1601        for sid in &sector_ids {
1602            if let Some(sector) = self.find_sector(*sid) {
1603                for &lid in &sector.linedefs {
1604                    linedef_ids.insert(lid);
1605                }
1606            }
1607        }
1608
1609        // Add vertices from selected linedefs
1610        for lid in &linedef_ids {
1611            if let Some(ld) = self.find_linedef(*lid) {
1612                vertex_ids.insert(ld.start_vertex);
1613                vertex_ids.insert(ld.end_vertex);
1614            }
1615        }
1616
1617        // Add standalone selected vertices
1618        for &vid in &self.selected_vertices {
1619            vertex_ids.insert(vid);
1620        }
1621
1622        // Normalize vertex positions
1623        let copied_vertices: Vec<Vertex> = vertex_ids
1624            .iter()
1625            .filter_map(|id| self.find_vertex(*id).cloned())
1626            .collect();
1627
1628        if copied_vertices.is_empty() {
1629            return clipboard;
1630        }
1631
1632        let min_x = copied_vertices
1633            .iter()
1634            .map(|v| v.x)
1635            .fold(f32::INFINITY, f32::min);
1636        let min_y = copied_vertices
1637            .iter()
1638            .map(|v| v.y)
1639            .fold(f32::INFINITY, f32::min);
1640        let offset = Vec2::new(min_x, min_y);
1641
1642        // Remap and store vertices
1643        for old in copied_vertices {
1644            if let Some(new_id) = clipboard.find_free_vertex_id() {
1645                let mut new_v = old.clone();
1646                new_v.id = new_id;
1647                new_v.x -= offset.x;
1648                new_v.y -= offset.y;
1649                old_to_new_vertex.insert(old.id, new_id);
1650                clipboard.vertices.push(new_v);
1651            }
1652        }
1653
1654        // Remap and store linedefs
1655        for old_id in &linedef_ids {
1656            if let Some(ld) = self.find_linedef(*old_id).cloned() {
1657                if let Some(new_id) = clipboard.find_free_linedef_id() {
1658                    let mut new_ld = ld.clone();
1659                    new_ld.id = new_id;
1660                    new_ld.start_vertex = *old_to_new_vertex.get(&ld.start_vertex).unwrap();
1661                    new_ld.end_vertex = *old_to_new_vertex.get(&ld.end_vertex).unwrap();
1662                    new_ld.sector_ids.clear();
1663                    old_to_new_linedef.insert(ld.id, new_id);
1664                    clipboard.linedefs.push(new_ld);
1665                }
1666            }
1667        }
1668
1669        // Remap and store sectors (only those whose linedefs were copied)
1670        for sid in &sector_ids {
1671            if let Some(s) = self.find_sector(*sid).cloned() {
1672                if s.linedefs.iter().all(|id| linedef_ids.contains(id)) {
1673                    if let Some(new_id) = clipboard.find_free_sector_id() {
1674                        let mut new_s = s.clone();
1675                        new_s.id = new_id;
1676                        new_s.linedefs = s
1677                            .linedefs
1678                            .iter()
1679                            .map(|id| *old_to_new_linedef.get(id).unwrap())
1680                            .collect();
1681
1682                        // Update sector_ids in the linedefs
1683                        for &old_lid in &s.linedefs {
1684                            if let Some(&new_lid) = old_to_new_linedef.get(&old_lid) {
1685                                if let Some(ld) =
1686                                    clipboard.linedefs.iter_mut().find(|l| l.id == new_lid)
1687                                {
1688                                    if !ld.sector_ids.contains(&new_id) {
1689                                        ld.sector_ids.push(new_id);
1690                                    }
1691                                }
1692                            }
1693                        }
1694
1695                        clipboard.sectors.push(new_s);
1696                    }
1697                }
1698            }
1699        }
1700
1701        // Delete source geometry if cutting
1702        if cut {
1703            self.delete_elements(
1704                &vertex_ids.iter().copied().collect::<Vec<_>>(),
1705                &linedef_ids.iter().copied().collect::<Vec<_>>(),
1706                &sector_ids.iter().copied().collect::<Vec<_>>(),
1707            );
1708            self.clear_selection();
1709        }
1710
1711        clipboard
1712    }
1713
1714    /// Inserts the given map at the given position.
1715    pub fn paste_at_position(&mut self, local_map: &Map, position: Vec2<f32>) {
1716        let mut vertex_map = FxHashMap::default();
1717        let mut linedef_map = FxHashMap::default();
1718        let mut pasted_sector_ids: Vec<u32> = Vec::new();
1719
1720        self.clear_selection();
1721
1722        // Vertices
1723        for v in &local_map.vertices {
1724            if let Some(new_id) = self.find_free_vertex_id() {
1725                let mut new_v = v.clone();
1726                new_v.id = new_id;
1727                new_v.x += position.x;
1728                new_v.y += position.y;
1729                self.vertices.push(new_v);
1730                self.selected_vertices.push(new_id);
1731                vertex_map.insert(v.id, new_id);
1732            }
1733        }
1734
1735        // Linedefs
1736        for l in &local_map.linedefs {
1737            if let Some(new_id) = self.find_free_linedef_id() {
1738                let mut new_l = l.clone();
1739                new_l.id = new_id;
1740                new_l.creator_id = Uuid::new_v4();
1741                new_l.start_vertex = *vertex_map.get(&l.start_vertex).unwrap();
1742                new_l.end_vertex = *vertex_map.get(&l.end_vertex).unwrap();
1743                // Reset front/back sector and sector_ids
1744                new_l.sector_ids.clear();
1745                self.linedefs.push(new_l);
1746                self.selected_linedefs.push(new_id);
1747                linedef_map.insert(l.id, new_id);
1748            }
1749        }
1750
1751        // Sectors
1752        for s in &local_map.sectors {
1753            if let Some(new_id) = self.find_free_sector_id() {
1754                let mut new_s = s.clone();
1755                new_s.id = new_id;
1756                new_s.creator_id = Uuid::new_v4();
1757                new_s.linedefs = s
1758                    .linedefs
1759                    .iter()
1760                    .map(|id| *linedef_map.get(id).unwrap())
1761                    .collect();
1762
1763                // Assign sector to each of its linedefs
1764                for old_lid in &s.linedefs {
1765                    if let Some(&new_lid) = linedef_map.get(old_lid) {
1766                        if let Some(ld) = self.linedefs.iter_mut().find(|l| l.id == new_lid) {
1767                            // Add sector to sector_ids list
1768                            if !ld.sector_ids.contains(&new_id) {
1769                                ld.sector_ids.push(new_id);
1770                            }
1771                        }
1772                    }
1773                }
1774
1775                self.sectors.push(new_s);
1776                self.selected_sectors.push(new_id);
1777                pasted_sector_ids.push(new_id);
1778            }
1779        }
1780
1781        // Pasted sectors need matching surfaces so 3D chunk building can render them.
1782        for sector_id in pasted_sector_ids {
1783            if self.get_surface_for_sector_id(sector_id).is_none() {
1784                let mut surface = Surface::new(sector_id);
1785                surface.calculate_geometry(self);
1786                self.surfaces.insert(surface.id, surface);
1787            }
1788        }
1789    }
1790
1791    /// Creates a geometry_clone clone of the map containing only vertices, linedefs, and sectors.
1792    pub fn geometry_clone(&self) -> Map {
1793        Map {
1794            id: Uuid::new_v4(),
1795            name: format!("{} (geometry_clone)", self.name),
1796
1797            offset: self.offset,
1798            grid_size: self.grid_size,
1799            subdivisions: self.subdivisions,
1800
1801            terrain: Terrain::default(),
1802
1803            possible_polygon: vec![],
1804            curr_grid_pos: None,
1805            curr_mouse_pos: None,
1806            curr_rectangle: None,
1807
1808            vertices: self.vertices.clone(),
1809            linedefs: self.linedefs.clone(),
1810            sectors: self.sectors.clone(),
1811
1812            shapefx_graphs: self.shapefx_graphs.clone(),
1813            sky_texture: None,
1814
1815            camera: self.camera,
1816            camera_xz: None,
1817            look_at_xz: None,
1818
1819            lights: vec![],
1820            entities: vec![],
1821            items: vec![],
1822
1823            selected_vertices: vec![],
1824            selected_linedefs: vec![],
1825            selected_sectors: vec![],
1826
1827            selected_entity_item: None,
1828
1829            properties: ValueContainer::default(),
1830            softrigs: IndexMap::default(),
1831            editing_rig: None,
1832            soft_animator: None,
1833
1834            surfaces: IndexMap::default(),
1835            profiles: FxHashMap::default(),
1836            shaders: IndexMap::default(),
1837
1838            changed: 0,
1839        }
1840    }
1841
1842    /// Extracts all geometry into a new Map which intersects with the given chunk bbox.
1843    pub fn extract_chunk_geometry(&self, bbox: BBox) -> Map {
1844        let mut result = Map::new();
1845
1846        let mut vertex_map: FxHashMap<u32, u32> = FxHashMap::default();
1847        let mut linedef_map: FxHashMap<u32, u32> = FxHashMap::default();
1848
1849        // Step 1: Find all linedefs that intersect the BBox
1850        for l in &self.linedefs {
1851            if let (Some(start), Some(end)) = (
1852                self.get_vertex(l.start_vertex),
1853                self.get_vertex(l.end_vertex),
1854            ) {
1855                // Check if either endpoint is inside or the segment intersects bbox
1856                if bbox.contains(start) || bbox.contains(end) || bbox.line_intersects(start, end) {
1857                    let new_id = result.find_free_linedef_id().unwrap_or(l.id);
1858                    let mut l_clone = l.clone();
1859                    l_clone.id = new_id;
1860                    l_clone.sector_ids.clear();
1861                    result.linedefs.push(l_clone);
1862                    linedef_map.insert(l.id, new_id);
1863
1864                    // Ensure both vertices are marked for inclusion
1865                    for vid in &[l.start_vertex, l.end_vertex] {
1866                        if !vertex_map.contains_key(vid) {
1867                            if let Some(v) = self.find_vertex(*vid) {
1868                                let new_vid = result.find_free_vertex_id().unwrap_or(v.id);
1869                                let mut v_clone = v.clone();
1870                                v_clone.id = new_vid;
1871                                result.vertices.push(v_clone);
1872                                vertex_map.insert(*vid, new_vid);
1873                            }
1874                        }
1875                    }
1876
1877                    // Reassign the vertex IDs
1878                    if let Some(ld) = result.linedefs.last_mut() {
1879                        ld.start_vertex = vertex_map[&l.start_vertex];
1880                        ld.end_vertex = vertex_map[&l.end_vertex];
1881                    }
1882                }
1883            }
1884        }
1885
1886        // Step 2: Add sectors that reference any included linedef
1887        for s in &self.sectors {
1888            if s.linedefs.iter().any(|lid| linedef_map.contains_key(lid)) {
1889                let new_id = result.find_free_sector_id().unwrap_or(s.id);
1890                let mut s_clone = s.clone();
1891                s_clone.id = new_id;
1892                s_clone.linedefs = s
1893                    .linedefs
1894                    .iter()
1895                    .filter_map(|lid| linedef_map.get(lid).copied())
1896                    .collect();
1897
1898                // Re-link sector ID into included linedefs
1899                for lid in &s.linedefs {
1900                    if let Some(&new_lid) = linedef_map.get(lid) {
1901                        if let Some(ld) = result.linedefs.iter_mut().find(|l| l.id == new_lid) {
1902                            // Add sector to sector_ids list
1903                            if !ld.sector_ids.contains(&new_id) {
1904                                ld.sector_ids.push(new_id);
1905                            }
1906                        }
1907                    }
1908                }
1909
1910                result.sectors.push(s_clone);
1911            }
1912        }
1913
1914        result
1915    }
1916
1917    // Check if a point is inside a sector (using ray casting algorithm)
1918    fn is_point_in_sector(&self, point: Vec2<f32>, sector_id: u32) -> bool {
1919        if let Some(sector) = self.find_sector(sector_id) {
1920            let mut vertices = Vec::new();
1921            for &linedef_id in &sector.linedefs {
1922                if let Some(linedef) = self.find_linedef(linedef_id) {
1923                    if let Some(vertex) = self.find_vertex(linedef.start_vertex) {
1924                        vertices.push(Vec2::new(vertex.x, vertex.y));
1925                    }
1926                }
1927            }
1928
1929            // Ray casting algorithm
1930            let mut inside = false;
1931            let mut j = vertices.len() - 1;
1932            for i in 0..vertices.len() {
1933                if ((vertices[i].y > point.y) != (vertices[j].y > point.y))
1934                    && (point.x
1935                        < (vertices[j].x - vertices[i].x) * (point.y - vertices[i].y)
1936                            / (vertices[j].y - vertices[i].y)
1937                            + vertices[i].x)
1938                {
1939                    inside = !inside;
1940                }
1941                j = i;
1942            }
1943            inside
1944        } else {
1945            false
1946        }
1947    }
1948
1949    // Find all sectors that are completely embedded within a given sector
1950    pub fn find_embedded_sectors(&self, container_sector_id: u32) -> Vec<u32> {
1951        let mut embedded = Vec::new();
1952
1953        for sector in &self.sectors {
1954            if sector.id == container_sector_id {
1955                continue; // Skip the container itself
1956            }
1957
1958            if sector.linedefs.is_empty() {
1959                continue;
1960            }
1961
1962            // Collect all unique vertices from the sector's linedefs
1963            let mut vertices = Vec::new();
1964            for &linedef_id in &sector.linedefs {
1965                if let Some(linedef) = self.find_linedef(linedef_id) {
1966                    if let Some(vertex) = self.find_vertex(linedef.start_vertex) {
1967                        vertices.push(Vec2::new(vertex.x, vertex.y));
1968                    }
1969                    if let Some(vertex) = self.find_vertex(linedef.end_vertex) {
1970                        vertices.push(Vec2::new(vertex.x, vertex.y));
1971                    }
1972                }
1973            }
1974
1975            if vertices.is_empty() {
1976                continue;
1977            }
1978
1979            // Calculate the centroid (center point) of the sector
1980            let mut centroid = Vec2::new(0.0, 0.0);
1981            for vertex in &vertices {
1982                centroid.x += vertex.x;
1983                centroid.y += vertex.y;
1984            }
1985            centroid.x /= vertices.len() as f32;
1986            centroid.y /= vertices.len() as f32;
1987
1988            // Check if the centroid is inside the container sector
1989            if self.is_point_in_sector(centroid, container_sector_id) {
1990                embedded.push(sector.id);
1991            }
1992        }
1993
1994        embedded
1995    }
1996}