rustapi/actions/
create_sector.rs1use crate::prelude::*;
2use rusterix::Surface;
3
4pub struct CreateSector {
5 id: TheId,
6 nodeui: TheNodeUI,
7}
8
9impl CreateSector {
10 fn order_vertices_clockwise(map: &Map, verts: &[u32]) -> Option<Vec<u32>> {
12 if verts.len() < 3 {
13 return None;
14 }
15
16 let mut pts: Vec<(u32, f32, f32)> = Vec::new();
18 let mut seen = std::collections::HashSet::new();
19 for &vid in verts {
20 if seen.contains(&vid) {
21 continue;
22 }
23 if let Some(v) = map.get_vertex(vid) {
24 pts.push((vid, v.x, v.y));
26 seen.insert(vid);
27 }
28 }
29 if pts.len() < 3 {
30 return None;
31 }
32
33 let (mut cx, mut cy) = (0.0f32, 0.0f32);
35 for (_, x, y) in &pts {
36 cx += *x;
37 cy += *y;
38 }
39 let n = pts.len() as f32;
40 cx /= n;
41 cy /= n;
42
43 pts.sort_by(|a, b| {
45 let aa = (a.2 - cy).atan2(a.1 - cx);
46 let bb = (b.2 - cy).atan2(b.1 - cx);
47 bb.partial_cmp(&aa).unwrap_or(std::cmp::Ordering::Equal)
48 });
49
50 let eps = 1e-5f32;
52 let mut ordered: Vec<u32> = Vec::with_capacity(pts.len());
53 for (i, (vid, x, y)) in pts.iter().enumerate() {
54 let prev = if i == 0 {
55 pts.last().unwrap()
56 } else {
57 &pts[i - 1]
58 };
59 if (prev.1 - *x).abs() + (prev.2 - *y).abs() < eps {
60 continue;
61 }
62 ordered.push(*vid);
63 }
64 if ordered.len() < 3 {
65 return None;
66 }
67 Some(ordered)
68 }
69
70 fn loop_from_linedefs(map: &Map, lds: &[u32]) -> Option<Vec<u32>> {
73 use std::collections::{HashMap, HashSet};
74 if lds.len() < 3 {
75 return None;
76 }
77
78 let mut adj: HashMap<u32, Vec<(u32, u32)>> = HashMap::new();
80 for &ld_id in lds {
81 let ld = map.find_linedef(ld_id)?;
82 let a = ld.start_vertex;
83 let b = ld.end_vertex;
84 adj.entry(a).or_default().push((ld_id, b));
85 adj.entry(b).or_default().push((ld_id, a));
86 }
87 if adj.values().any(|v| v.len() != 2) {
89 return None;
90 }
91
92 let start_ld = lds[0];
94 let ld0 = map.find_linedef(start_ld)?;
95 let start_v = ld0.start_vertex; let mut ordered: Vec<u32> = Vec::with_capacity(lds.len());
97 let mut used: HashSet<u32> = HashSet::new();
98 let mut curr_v = start_v;
99
100 for _ in 0..lds.len() {
101 let next = adj
103 .get(&curr_v)?
104 .iter()
105 .find(|(cand_ld, _)| !used.contains(cand_ld))
106 .cloned()?;
107 let (next_ld_id, other_v) = next;
108 used.insert(next_ld_id);
109 ordered.push(curr_v);
110 curr_v = other_v;
111 }
112 if curr_v != ordered[0] {
114 return None;
115 }
116 Some(ordered)
117 }
118}
119
120impl Action for CreateSector {
121 fn new() -> Self
122 where
123 Self: Sized,
124 {
125 let mut nodeui: TheNodeUI = TheNodeUI::default();
126 let item = TheNodeUIItem::Markdown("desc".into(), fl!("action_create_sector_desc"));
127 nodeui.add_item(item);
128
129 Self {
130 id: TheId::named(&fl!("action_create_sector")),
131 nodeui,
132 }
133 }
134
135 fn id(&self) -> TheId {
136 self.id.clone()
137 }
138
139 fn info(&self) -> String {
140 fl!("action_create_sector_desc")
141 }
142
143 fn role(&self) -> ActionRole {
144 ActionRole::Editor
145 }
146
147 fn accel(&self) -> Option<TheAccelerator> {
148 None
149 }
150
151 fn is_applicable(&self, map: &Map, _ctx: &mut TheContext, _server_ctx: &ServerContext) -> bool {
152 map.selected_vertices.len() >= 3 || map.selected_linedefs.len() >= 3
153 }
154
155 fn apply(
156 &self,
157 map: &mut Map,
158 _ui: &mut TheUI,
159 _ctx: &mut TheContext,
160 server_ctx: &mut ServerContext,
161 ) -> Option<ProjectUndoAtom> {
162 let mut changed = false;
163
164 let using_linedefs = !map.selected_linedefs.is_empty();
166 if !using_linedefs && map.selected_vertices.len() < 3 {
167 return None;
168 }
169
170 let prev = map.clone();
171 map.possible_polygon.clear();
172
173 let sector_id: Option<u32>;
174
175 if using_linedefs {
176 let ordered = match Self::loop_from_linedefs(map, &map.selected_linedefs) {
178 Some(v) => v,
179 None => {
180 return None;
181 }
182 };
183 for i in 0..ordered.len() {
185 let a = ordered[i];
186 let b = ordered[(i + 1) % ordered.len()];
187 if a == b {
188 continue;
189 }
190 let _ = map.create_linedef_manual(a, b);
191 }
192 sector_id = map.close_polygon_manual();
194 } else {
195 let ordered = match Self::order_vertices_clockwise(map, &map.selected_vertices) {
197 Some(v) => v,
198 None => {
199 return None;
200 }
201 };
202 for i in 0..ordered.len() {
204 let a = ordered[i];
205 let b = ordered[(i + 1) % ordered.len()];
206 if a == b {
207 continue;
208 }
209 let _ = map.create_linedef_manual(a, b);
210 }
211 sector_id = map.close_polygon_manual();
213 }
214
215 if let Some(sector_id) = sector_id {
216 map.selected_sectors.clear();
217 map.selected_sectors.push(sector_id);
218 map.possible_polygon.clear();
219
220 let mut surface = Surface::new(sector_id);
221 surface.calculate_geometry(map);
222 map.surfaces.insert(surface.id, surface);
223
224 changed = true;
225 }
226
227 if changed {
228 Some(ProjectUndoAtom::MapEdit(
229 server_ctx.pc,
230 Box::new(prev),
231 Box::new(map.clone()),
232 ))
233 } else {
234 None
235 }
236 }
237
238 fn params(&self) -> TheNodeUI {
239 self.nodeui.clone()
240 }
241
242 fn handle_event(
243 &mut self,
244 event: &TheEvent,
245 _project: &mut Project,
246 _ui: &mut TheUI,
247 _ctx: &mut TheContext,
248 _server_ctx: &mut ServerContext,
249 ) -> bool {
250 self.nodeui.handle_event(event)
251 }
252}