vmf_forge/vmf_file/
visgroup_ops.rs

1use std::collections::HashSet;
2
3use super::VmfFile;
4use crate::prelude::{Entity, Solid, VisGroup};
5
6impl VmfFile {
7    /// Returns an iterator over entities (including hidden ones) belonging to the specified VisGroup ID.
8    ///
9    /// # Arguments
10    ///
11    /// * `group_id` - The ID of the target VisGroup.
12    /// * `include_children` - If true, includes entities from all child VisGroups recursively.
13    ///
14    /// # Returns
15    ///
16    /// An `Option` containing an iterator yielding references to the matching `Entity` objects.
17    /// Returns `None` if no VisGroup with the given `group_id` is found.
18    pub fn get_entities_in_visgroup(
19        &self,
20        group_id: i32,
21        include_children: bool,
22    ) -> Option<impl Iterator<Item = &Entity> + '_> {
23        // 1. Find the starting VisGroup by ID. Returns None if not found.
24        let start_group = self.visgroups.find_by_id(group_id)?;
25
26        // 2. Collect all relevant VisGroup IDs.
27        let ids_to_check: HashSet<i32> = if include_children {
28            let mut ids = HashSet::new();
29            collect_child_visgroup_ids(start_group, &mut ids);
30            ids
31        } else {
32            // Only the ID of the found group.
33            HashSet::from([start_group.id])
34        };
35
36        // 3. Create the filtered iterator over entities and hidden entities.
37        let iterator = self
38            .entities
39            .iter()
40            .chain(self.hiddens.iter())
41            .filter(move |entity| {
42                // Check if the entity belongs to one of the target VisGroup IDs.
43                entity
44                    .editor
45                    .visgroup_id.is_some_and(|ent_group_id| ids_to_check.contains(&ent_group_id))
46            });
47
48        // 4. Return the iterator wrapped in Some.
49        Some(iterator)
50    }
51
52    /// Returns a mutable iterator over entities (including hidden ones) belonging to the specified VisGroup ID.
53    ///
54    /// # Arguments
55    ///
56    /// * `group_id` - The ID of the target VisGroup.
57    /// * `include_children` - If true, includes entities from all child VisGroups recursively.
58    ///
59    /// # Returns
60    ///
61    /// An `Option` containing an iterator yielding mutable references to the matching `Entity` objects.
62    /// Returns `None` if no VisGroup with the given `group_id` is found.
63    pub fn get_entities_in_visgroup_mut(
64        &mut self,
65        group_id: i32,
66        include_children: bool,
67    ) -> Option<impl Iterator<Item = &mut Entity> + '_> {
68        // Note: returns mutable references
69
70        // 1. Find the starting VisGroup by ID (immutable lookup is sufficient to get IDs).
71        let start_group = self.visgroups.find_by_id(group_id)?;
72
73        // 2. Collect all relevant VisGroup IDs (immutable collection is fine).
74        let ids_to_check: HashSet<i32> = if include_children {
75            let mut ids = HashSet::new();
76            collect_child_visgroup_ids(start_group, &mut ids);
77            ids
78        } else {
79            HashSet::from([start_group.id])
80        };
81
82        // 3. Create the filtered *mutable* iterator.
83        let iterator = self
84            .entities
85            .iter_mut() // Get mutable iterator
86            .chain(self.hiddens.iter_mut()) // Chain mutable iterator
87            .filter(move |entity| {
88                entity
89                    .editor
90                    .visgroup_id.is_some_and(|ent_group_id| ids_to_check.contains(&ent_group_id))
91            });
92
93        // 4. Return the mutable iterator wrapped in Some.
94        Some(iterator)
95    }
96
97    /// Returns an iterator over world solids (visible and hidden) belonging to the specified VisGroup ID.
98    ///
99    /// # Arguments
100    ///
101    /// * `group_id` - The ID of the target VisGroup.
102    /// * `include_children` - If true, includes solids from all child VisGroups recursively.
103    ///
104    /// # Returns
105    ///
106    /// An `Option` containing an iterator yielding references to the matching `Solid` objects.
107    /// Returns `None` if no VisGroup with the given `group_id` is found.
108    pub fn get_solids_in_visgroup(
109        &self,
110        group_id: i32,
111        include_children: bool,
112    ) -> Option<impl Iterator<Item = &Solid> + '_> {
113        // 1. Find the starting VisGroup.
114        let start_group = self.visgroups.find_by_id(group_id)?;
115
116        // 2. Collect relevant IDs.
117        let ids_to_check: HashSet<i32> = if include_children {
118            let mut ids = HashSet::new();
119            collect_child_visgroup_ids(start_group, &mut ids);
120            ids
121        } else {
122            HashSet::from([start_group.id])
123        };
124
125        // 3. Create filtered iterator over world solids.
126        let iterator = self
127            .world
128            .solids
129            .iter()
130            .chain(self.world.hidden.iter())
131            .filter(move |solid| {
132                solid.editor.visgroup_id.is_some_and(|solid_group_id| {
133                    ids_to_check.contains(&solid_group_id)
134                })
135            });
136
137        // 4. Return iterator.
138        Some(iterator)
139    }
140
141    /// Returns a mutable iterator over world solids (visible and hidden) belonging to the specified VisGroup ID.
142    ///
143    /// # Arguments
144    ///
145    /// * `group_id` - The ID of the target VisGroup.
146    /// * `include_children` - If true, includes solids from all child VisGroups recursively.
147    ///
148    /// # Returns
149    ///
150    /// An `Option` containing an iterator yielding mutable references to the matching `Solid` objects.
151    /// Returns `None` if no VisGroup with the given `group_id` is found.
152    pub fn get_solids_in_visgroup_mut(
153        &mut self,
154        group_id: i32,
155        include_children: bool,
156    ) -> Option<impl Iterator<Item = &mut Solid> + '_> {
157        // 1. Find the starting VisGroup.
158        let start_group = self.visgroups.find_by_id(group_id)?;
159
160        // 2. Collect relevant IDs.
161        let ids_to_check: HashSet<i32> = if include_children {
162            let mut ids = HashSet::new();
163            collect_child_visgroup_ids(start_group, &mut ids);
164            ids
165        } else {
166            HashSet::from([start_group.id])
167        };
168
169        // 3. Create filtered *mutable* iterator over world solids.
170        let iterator = self
171            .world
172            .solids
173            .iter_mut() // Mutable iterator
174            .chain(self.world.hidden.iter_mut()) // Chain mutable iterator
175            .filter(move |solid| {
176                solid.editor.visgroup_id.is_some_and(|solid_group_id| {
177                    ids_to_check.contains(&solid_group_id)
178                })
179            });
180
181        // 4. Return mutable iterator.
182        Some(iterator)
183    }
184}
185
186/// Recursively collects the IDs of a VisGroup and all its children into a HashSet.
187/// The passed `group` must be the one found by ID/Name previously.
188/// Uses the `collected_ids` set to avoid infinite loops in case of (unlikely) cycles.
189fn collect_child_visgroup_ids(group: &VisGroup, collected_ids: &mut HashSet<i32>) {
190    // Insert the current group's ID. If it was already present, stop to prevent cycles.
191    if !collected_ids.insert(group.id) {
192        return;
193    }
194
195    // Recursively collect IDs from children
196    if let Some(ref children) = group.children {
197        for child in children {
198            collect_child_visgroup_ids(child, collected_ids);
199        }
200    }
201}