rustapi/actions/
duplicate.rs1use crate::prelude::*;
2use rusterix::{Linedef, Sector, Surface};
3
4pub const DUPLICATE_ACTION_ID: &str = "1468f85f-ef66-49f9-8c3f-54fbde6e3d9c";
5
6pub struct Duplicate {
7 id: TheId,
8 nodeui: TheNodeUI,
9}
10
11impl Action for Duplicate {
12 fn new() -> Self
13 where
14 Self: Sized,
15 {
16 let mut nodeui: TheNodeUI = TheNodeUI::default();
17
18 nodeui.add_item(TheNodeUIItem::FloatEditSlider(
19 "actionDuplicateX".into(),
20 "".into(),
21 "".into(),
22 0.0,
23 -1000.0..=1000.0,
24 false,
25 ));
26 nodeui.add_item(TheNodeUIItem::FloatEditSlider(
27 "actionDuplicateY".into(),
28 "".into(),
29 "".into(),
30 1.0,
31 -1000.0..=1000.0,
32 false,
33 ));
34 nodeui.add_item(TheNodeUIItem::FloatEditSlider(
35 "actionDuplicateZ".into(),
36 "".into(),
37 "".into(),
38 0.0,
39 -1000.0..=1000.0,
40 false,
41 ));
42 nodeui.add_item(TheNodeUIItem::OpenTree("sector".into()));
43 nodeui.add_item(TheNodeUIItem::Checkbox(
44 "actionSectorConnect".into(),
45 "".into(),
46 "".into(),
47 false,
48 ));
49 nodeui.add_item(TheNodeUIItem::CloseTree);
50
51 Self {
52 id: TheId::named_with_id(
53 &fl!("action_duplicate"),
54 Uuid::parse_str(DUPLICATE_ACTION_ID).unwrap(),
55 ),
56 nodeui,
57 }
58 }
59
60 fn id(&self) -> TheId {
61 self.id.clone()
62 }
63
64 fn info(&self) -> String {
65 fl!("action_duplicate_desc")
66 }
67
68 fn role(&self) -> ActionRole {
69 ActionRole::Editor
70 }
71
72 fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, _server_ctx: &ServerContext) -> bool {
73 !map.selected_vertices.is_empty()
74 || !map.selected_linedefs.is_empty()
75 || !map.selected_sectors.is_empty()
76 }
77
78 fn apply(
79 &self,
80 map: &mut Map,
81 _ui: &mut TheUI,
82 _ctx: &mut TheContext,
83 server_ctx: &mut ServerContext,
84 ) -> Option<ProjectUndoAtom> {
85 if map.selected_vertices.is_empty()
86 && map.selected_linedefs.is_empty()
87 && map.selected_sectors.is_empty()
88 {
89 return None;
90 }
91
92 let prev = map.clone();
93
94 let offset_x = self.nodeui.get_f32_value("actionDuplicateX").unwrap_or(0.0);
96 let offset_y = self.nodeui.get_f32_value("actionDuplicateY").unwrap_or(0.0);
97 let offset_z = self.nodeui.get_f32_value("actionDuplicateZ").unwrap_or(1.0);
98 let connect_sectors = self
99 .nodeui
100 .get_bool_value("actionSectorConnect")
101 .unwrap_or(false);
102
103 let mut selected_sector_ids = map.selected_sectors.clone();
104 selected_sector_ids.sort_unstable();
105 let mut selected_linedef_ids = map.selected_linedefs.clone();
106 selected_linedef_ids.sort_unstable();
107 let mut selected_vertex_ids = map.selected_vertices.clone();
108 selected_vertex_ids.sort_unstable();
109 selected_vertex_ids.dedup();
110
111 let mut old_linedef_ids: FxHashSet<u32> = FxHashSet::default();
112 for linedef_id in &selected_linedef_ids {
113 old_linedef_ids.insert(*linedef_id);
114 }
115 for sector_id in &selected_sector_ids {
116 if let Some(sector) = map.find_sector(*sector_id) {
117 for linedef_id in §or.linedefs {
118 old_linedef_ids.insert(*linedef_id);
119 }
120 }
121 }
122
123 let mut old_vertex_ids: FxHashSet<u32> = FxHashSet::default();
124 for vertex_id in &selected_vertex_ids {
125 old_vertex_ids.insert(*vertex_id);
126 }
127 for linedef_id in &old_linedef_ids {
128 if let Some(linedef) = map.find_linedef(*linedef_id) {
129 old_vertex_ids.insert(linedef.start_vertex);
130 old_vertex_ids.insert(linedef.end_vertex);
131 }
132 }
133
134 let mut sorted_vertex_ids: Vec<u32> = old_vertex_ids.into_iter().collect();
135 sorted_vertex_ids.sort_unstable();
136
137 let mut sorted_linedef_ids: Vec<u32> = old_linedef_ids.into_iter().collect();
138 sorted_linedef_ids.sort_unstable();
139
140 let mut next_vertex_id = map.vertices.iter().map(|v| v.id).max().unwrap_or(0);
141 let mut next_linedef_id = map.linedefs.iter().map(|l| l.id).max().unwrap_or(0);
142 let mut next_sector_id = map.sectors.iter().map(|s| s.id).max().unwrap_or(0);
143
144 let mut vertex_map: FxHashMap<u32, u32> = FxHashMap::default();
145 let mut linedef_map: FxHashMap<u32, u32> = FxHashMap::default();
146
147 let mut new_vertices = Vec::new();
148 let mut new_linedefs = Vec::new();
149 let mut new_sectors = Vec::new();
150 let mut sector_map: FxHashMap<u32, u32> = FxHashMap::default();
151
152 for old_vid in sorted_vertex_ids {
153 if let Some(old_vertex) = map.find_vertex(old_vid).cloned() {
154 next_vertex_id = next_vertex_id.saturating_add(1);
155 let new_id = next_vertex_id;
156 let mut new_vertex = old_vertex;
157 new_vertex.id = new_id;
158 new_vertex.x += offset_x;
159 new_vertex.y += offset_z;
160 new_vertex.z += offset_y;
161 vertex_map.insert(old_vid, new_id);
162 new_vertices.push(new_vertex);
163 }
164 }
165
166 for old_lid in sorted_linedef_ids {
167 if let Some(old_linedef) = map.find_linedef(old_lid).cloned()
168 && let (Some(&new_start), Some(&new_end)) = (
169 vertex_map.get(&old_linedef.start_vertex),
170 vertex_map.get(&old_linedef.end_vertex),
171 )
172 {
173 next_linedef_id = next_linedef_id.saturating_add(1);
174 let new_id = next_linedef_id;
175 let mut new_linedef = old_linedef;
176 new_linedef.id = new_id;
177 new_linedef.start_vertex = new_start;
178 new_linedef.end_vertex = new_end;
179 new_linedef.sector_ids.clear();
180 linedef_map.insert(old_lid, new_id);
181 new_linedefs.push(new_linedef);
182 }
183 }
184
185 for old_sid in &selected_sector_ids {
186 if let Some(old_sector) = map.find_sector(*old_sid).cloned() {
187 next_sector_id = next_sector_id.saturating_add(1);
188 let new_id = next_sector_id;
189 let mut new_sector = old_sector;
190 new_sector.id = new_id;
191 new_sector.linedefs = new_sector
192 .linedefs
193 .iter()
194 .filter_map(|id| linedef_map.get(id).copied())
195 .collect();
196 new_sectors.push(new_sector);
197 sector_map.insert(*old_sid, new_id);
198 }
199 }
200
201 if connect_sectors {
202 let selected_sector_set: FxHashSet<u32> = selected_sector_ids.iter().copied().collect();
203 let mut connector_linedefs = Vec::new();
204 let mut connector_sectors = Vec::new();
205
206 for old_sid in &selected_sector_ids {
207 let Some(old_sector) = map.find_sector(*old_sid).cloned() else {
208 continue;
209 };
210 if !sector_map.contains_key(old_sid) {
211 continue;
212 }
213
214 for old_linedef_id in old_sector.linedefs {
215 let Some(old_linedef) = map.find_linedef(old_linedef_id) else {
216 continue;
217 };
218 let is_internal = old_linedef.sector_ids.len() > 1
220 && old_linedef
221 .sector_ids
222 .iter()
223 .all(|sid| selected_sector_set.contains(sid));
224 if is_internal {
225 continue;
226 }
227
228 let Some(&new_start) = vertex_map.get(&old_linedef.start_vertex) else {
229 continue;
230 };
231 let Some(&new_end) = vertex_map.get(&old_linedef.end_vertex) else {
232 continue;
233 };
234
235 next_linedef_id = next_linedef_id.saturating_add(1);
236 let bridge_side_a_id = next_linedef_id;
237 let mut bridge_side_a =
238 Linedef::new(bridge_side_a_id, old_linedef.end_vertex, new_end);
239
240 next_linedef_id = next_linedef_id.saturating_add(1);
241 let bridge_side_b_id = next_linedef_id;
242 let mut bridge_side_b =
243 Linedef::new(bridge_side_b_id, new_start, old_linedef.start_vertex);
244
245 next_linedef_id = next_linedef_id.saturating_add(1);
248 let bridge_top_id = next_linedef_id;
249 let mut bridge_top = Linedef::new(bridge_top_id, new_end, new_start);
250
251 next_sector_id = next_sector_id.saturating_add(1);
252 let connector_sector_id = next_sector_id;
253 bridge_side_a.sector_ids.push(connector_sector_id);
254 bridge_side_b.sector_ids.push(connector_sector_id);
255 bridge_top.sector_ids.push(connector_sector_id);
256
257 let connector_sector = Sector::new(
258 connector_sector_id,
259 vec![
260 old_linedef_id,
261 bridge_side_a_id,
262 bridge_top_id,
263 bridge_side_b_id,
264 ],
265 );
266
267 connector_linedefs.push(bridge_side_a);
268 connector_linedefs.push(bridge_side_b);
269 connector_linedefs.push(bridge_top);
270 connector_sectors.push(connector_sector);
271 }
272 }
273
274 new_linedefs.extend(connector_linedefs);
275 new_sectors.extend(connector_sectors);
276 }
277
278 for new_sector in &new_sectors {
279 for new_linedef_id in &new_sector.linedefs {
280 if let Some(new_linedef) = new_linedefs.iter_mut().find(|l| l.id == *new_linedef_id)
281 && !new_linedef.sector_ids.contains(&new_sector.id)
282 {
283 new_linedef.sector_ids.push(new_sector.id);
284 } else if let Some(existing_linedef) = map.find_linedef_mut(*new_linedef_id)
285 && !existing_linedef.sector_ids.contains(&new_sector.id)
286 {
287 existing_linedef.sector_ids.push(new_sector.id);
288 }
289 }
290 }
291
292 if new_vertices.is_empty() && new_linedefs.is_empty() && new_sectors.is_empty() {
293 return None;
294 }
295
296 map.vertices.extend(new_vertices.clone());
297 map.linedefs.extend(new_linedefs.clone());
298 map.sectors.extend(new_sectors.clone());
299
300 for sector in &new_sectors {
302 if map.get_surface_for_sector_id(sector.id).is_none() {
303 let mut surface = if let Some((&old_sid, _)) = sector_map
304 .iter()
305 .find(|(_, new_sid)| **new_sid == sector.id)
306 {
307 if let Some(src_surface) = map.get_surface_for_sector_id(old_sid) {
308 let mut cloned = src_surface.clone();
309 cloned.id = Uuid::new_v4();
310 cloned.sector_id = sector.id;
311 cloned
312 } else {
313 Surface::new(sector.id)
314 }
315 } else {
316 Surface::new(sector.id)
317 };
318 surface.calculate_geometry(map);
319 map.surfaces.insert(surface.id, surface);
320 }
321 }
322
323 map.selected_vertices = selected_vertex_ids
324 .iter()
325 .filter_map(|id| vertex_map.get(id).copied())
326 .collect();
327
328 map.selected_linedefs = selected_linedef_ids
329 .iter()
330 .filter_map(|id| linedef_map.get(id).copied())
331 .collect();
332
333 map.selected_sectors = new_sectors.iter().map(|s| s.id).collect();
335
336 Some(ProjectUndoAtom::MapEdit(
337 server_ctx.pc,
338 Box::new(prev),
339 Box::new(map.clone()),
340 ))
341 }
342
343 fn params(&self) -> TheNodeUI {
344 self.nodeui.clone()
345 }
346
347 fn handle_event(
348 &mut self,
349 event: &TheEvent,
350 _project: &mut Project,
351 _ui: &mut TheUI,
352 _ctx: &mut TheContext,
353 _server_ctx: &mut ServerContext,
354 ) -> bool {
355 self.nodeui.handle_event(event)
356 }
357}