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}