1 /*
2 * Portions of this file are copyright Rebirth contributors and licensed as
3 * described in COPYING.txt.
4 * Portions of this file are copyright Parallax Software and licensed
5 * according to the Parallax license below.
6 * See COPYING.txt for license details.
7
8 THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX
9 SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO
10 END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A
11 ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS
12 IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS
13 SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE
14 FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE
15 CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS
16 AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE.
17 COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED.
18 */
19
20 /*
21 *
22 * group functions
23 *
24 */
25
26 #include <stdio.h>
27 #include <string.h>
28
29 #include "gr.h"
30 #include "ui.h"
31 #include "inferno.h"
32 #include "segment.h"
33 #include "editor/editor.h"
34 #include "editor/esegment.h"
35 #include "editor/medmisc.h"
36 #include "dxxerror.h"
37 #include "physfsx.h"
38 #include "gameseg.h"
39 #include "bm.h" // For MAX_TEXTURES.
40 #include "textures.h"
41 #include "hash.h"
42 #include "kdefs.h"
43 #include "fwd-wall.h"
44 #include "medwall.h"
45 #include "compiler-range_for.h"
46 #include "d_bitset.h"
47 #include "d_enumerate.h"
48 #include "d_levelstate.h"
49 #include "d_zip.h"
50 #include "partial_range.h"
51 #include "segiter.h"
52
53 namespace {
54
55 static void validate_selected_segments(void);
56
57 #if 0
58 struct group_top_fileinfo {
59 int fileinfo_version;
60 int fileinfo_sizeof;
61 } group_top_fileinfo; // Should be same as first two fields below...
62
63 struct group_fileinfo {
64 int fileinfo_version;
65 int fileinfo_sizeof;
66 int header_offset; // Stuff common to game & editor
67 int header_size;
68 int editor_offset; // Editor specific stuff
69 int editor_size;
70 int vertex_offset;
71 int vertex_howmany;
72 int vertex_sizeof;
73 int segment_offset;
74 int segment_howmany;
75 int segment_sizeof;
76 int texture_offset;
77 uint32_t texture_howmany;
78 int texture_sizeof;
79 } group_fileinfo;
80
81 struct group_header {
82 int num_vertices;
83 int num_segments;
84 } group_header;
85
86 struct group_editor {
87 int current_seg;
88 int newsegment_offset;
89 int newsegment_size;
90 int Groupsegp;
91 int Groupside;
92 } group_editor;
93 #endif
94
95 }
96
97 std::array<group, MAX_GROUPS+1> GroupList;
98 std::array<segment *, MAX_GROUPS+1> Groupsegp;
99 std::array<int, MAX_GROUPS+1> Groupside;
100 std::array<int, MAX_GROUPS+1> Group_orientation;
101 int current_group=-1;
102 unsigned num_groups;
103
104 // -- void swap_negate_columns(vms_matrix *rotmat, int col1, int col2)
105 // -- {
106 // -- fix col1_1,col1_2,col1_3;
107 // -- fix col2_1,col2_2,col2_3;
108 // --
109 // -- switch (col1) {
110 // -- case 0:
111 // -- col1_1 = rotmat->m1;
112 // -- col1_2 = rotmat->m2;
113 // -- col1_3 = rotmat->m3;
114 // -- break;
115 // --
116 // -- case 1:
117 // -- col1_1 = rotmat->m4;
118 // -- col1_2 = rotmat->m5;
119 // -- col1_3 = rotmat->m6;
120 // -- break;
121 // --
122 // -- case 2:
123 // -- col1_1 = rotmat->m7;
124 // -- col1_2 = rotmat->m8;
125 // -- col1_3 = rotmat->m9;
126 // -- break;
127 // -- }
128 // --
129 // -- switch (col2) {
130 // -- case 0:
131 // -- col2_1 = rotmat->m1;
132 // -- col2_2 = rotmat->m2;
133 // -- col2_3 = rotmat->m3;
134 // -- break;
135 // --
136 // -- case 1:
137 // -- col2_1 = rotmat->m4;
138 // -- col2_2 = rotmat->m5;
139 // -- col2_3 = rotmat->m6;
140 // -- break;
141 // --
142 // -- case 2:
143 // -- col2_1 = rotmat->m7;
144 // -- col2_2 = rotmat->m8;
145 // -- col2_3 = rotmat->m9;
146 // -- break;
147 // -- }
148 // --
149 // -- switch (col2) {
150 // -- case 0:
151 // -- rotmat->m1 = -col1_1;
152 // -- rotmat->m2 = -col1_2;
153 // -- rotmat->m3 = -col1_3;
154 // -- break;
155 // --
156 // -- case 1:
157 // -- rotmat->m4 = -col1_1;
158 // -- rotmat->m5 = -col1_2;
159 // -- rotmat->m6 = -col1_3;
160 // -- break;
161 // --
162 // -- case 2:
163 // -- rotmat->m7 = -col1_1;
164 // -- rotmat->m8 = -col1_2;
165 // -- rotmat->m9 = -col1_3;
166 // -- break;
167 // -- }
168 // --
169 // -- switch (col1) {
170 // -- case 0:
171 // -- rotmat->m1 = -col2_1;
172 // -- rotmat->m2 = -col2_2;
173 // -- rotmat->m3 = -col2_3;
174 // -- break;
175 // --
176 // -- case 1:
177 // -- rotmat->m4 = -col2_1;
178 // -- rotmat->m5 = -col2_2;
179 // -- rotmat->m6 = -col2_3;
180 // -- break;
181 // --
182 // -- case 2:
183 // -- rotmat->m7 = -col2_1;
184 // -- rotmat->m8 = -col2_2;
185 // -- rotmat->m9 = -col2_3;
186 // -- break;
187 // -- }
188 // --
189 // -- }
190 // --
191 // -- void swap_negate_rows(vms_matrix *rotmat, int row1, int row2)
192 // -- {
193 // -- fix row1_1,row1_2,row1_3;
194 // -- fix row2_1,row2_2,row2_3;
195 // --
196 // -- switch (row1) {
197 // -- case 0:
198 // -- row1_1 = rotmat->m1;
199 // -- row1_2 = rotmat->m4;
200 // -- row1_3 = rotmat->m7;
201 // -- break;
202 // --
203 // -- case 1:
204 // -- row1_1 = rotmat->m2;
205 // -- row1_2 = rotmat->m5;
206 // -- row1_3 = rotmat->m8;
207 // -- break;
208 // --
209 // -- case 2:
210 // -- row1_1 = rotmat->m3;
211 // -- row1_2 = rotmat->m6;
212 // -- row1_3 = rotmat->m9;
213 // -- break;
214 // -- }
215 // --
216 // -- switch (row2) {
217 // -- case 0:
218 // -- row2_1 = rotmat->m1;
219 // -- row2_2 = rotmat->m4;
220 // -- row2_3 = rotmat->m7;
221 // -- break;
222 // --
223 // -- case 1:
224 // -- row2_1 = rotmat->m2;
225 // -- row2_2 = rotmat->m5;
226 // -- row2_3 = rotmat->m8;
227 // -- break;
228 // --
229 // -- case 2:
230 // -- row2_1 = rotmat->m3;
231 // -- row2_2 = rotmat->m6;
232 // -- row2_3 = rotmat->m9;
233 // -- break;
234 // -- }
235 // --
236 // -- switch (row2) {
237 // -- case 0:
238 // -- rotmat->m1 = -row1_1;
239 // -- rotmat->m4 = -row1_2;
240 // -- rotmat->m7 = -row1_3;
241 // -- break;
242 // --
243 // -- case 1:
244 // -- rotmat->m2 = -row1_1;
245 // -- rotmat->m5 = -row1_2;
246 // -- rotmat->m8 = -row1_3;
247 // -- break;
248 // --
249 // -- case 2:
250 // -- rotmat->m3 = -row1_1;
251 // -- rotmat->m6 = -row1_2;
252 // -- rotmat->m9 = -row1_3;
253 // -- break;
254 // -- }
255 // --
256 // -- switch (row1) {
257 // -- case 0:
258 // -- rotmat->m1 = -row2_1;
259 // -- rotmat->m4 = -row2_2;
260 // -- rotmat->m7 = -row2_3;
261 // -- break;
262 // --
263 // -- case 1:
264 // -- rotmat->m2 = -row2_1;
265 // -- rotmat->m5 = -row2_2;
266 // -- rotmat->m8 = -row2_3;
267 // -- break;
268 // --
269 // -- case 2:
270 // -- rotmat->m3 = -row2_1;
271 // -- rotmat->m6 = -row2_2;
272 // -- rotmat->m9 = -row2_3;
273 // -- break;
274 // -- }
275 // --
276 // -- }
277 // --
278 // -- // ------------------------------------------------------------------------------------------------
279 // -- void side_based_matrix(vms_matrix *rotmat,int destside)
280 // -- {
281 // -- vms_angvec rotvec;
282 // -- vms_matrix r1,rtemp;
283 // --
284 // -- switch (destside) {
285 // -- case WLEFT:
286 // -- // swap_negate_columns(rotmat,1,2);
287 // -- // swap_negate_rows(rotmat,1,2);
288 // -- break;
289 // --
290 // -- case WTOP:
291 // -- break;
292 // --
293 // -- case WRIGHT:
294 // -- // swap_negate_columns(rotmat,1,2);
295 // -- // swap_negate_rows(rotmat,1,2);
296 // -- break;
297 // --
298 // -- case WBOTTOM:
299 // -- break;
300 // --
301 // -- case WFRONT:
302 // -- break;
303 // --
304 // -- case WBACK:
305 // -- break;
306 // -- }
307 // --
308 // -- }
309
310 namespace {
311
312 // ------------------------------------------------------------------------------------------------
313 // Rotate a group about a point.
314 // The segments in the group are indicated (by segment number) in group_seglist. There are group_size segments.
315 // The point about which the groups is rotated is the center of first_seg:first_side.
316 // delta_flag:
317 // 0 absolute rotation, destination specified in terms of base_seg:base_side, used in moving or copying a group
318 // 1 relative rotation, destination specified relative to current orientation of first_seg:first_side
319 // Note: The group must exist in the mine, consisting of actual points in the world. If any points in the
320 // segments in the group are shared by segments not in the group, those points will get rotated and the
321 // segments not in the group will have their shapes modified.
322 // Return value:
323 // 0 group rotated
324 // 1 unable to rotate group
med_create_group_rotation_matrix(vms_matrix & result_mat,const unsigned delta_flag,const shared_segment & first_seg,const unsigned first_side,const shared_segment & base_seg,const unsigned base_side,const vms_matrix & orient_matrix,const int orientation)325 static void med_create_group_rotation_matrix(vms_matrix &result_mat, const unsigned delta_flag, const shared_segment &first_seg, const unsigned first_side, const shared_segment &base_seg, const unsigned base_side, const vms_matrix &orient_matrix, const int orientation)
326 {
327 vms_matrix rotmat2,rotmat,rotmat3,rotmat4;
328 vms_angvec pbh = {0,0,0};
329
330 // Determine whether this rotation is a delta rotation, meaning to just rotate in place, or an absolute rotation,
331 // which means that the destination rotation is specified, not as a delta, but as an absolute
332 if (delta_flag) {
333 // Create rotation matrix describing rotation.
334 med_extract_matrix_from_segment(first_seg, rotmat4); // get rotation matrix describing current orientation of first seg
335 update_matrix_based_on_side(rotmat4, first_side);
336 rotmat3 = vm_transposed_matrix(orient_matrix);
337 const auto vm_desired_orientation = vm_matrix_x_matrix(rotmat4,rotmat3); // this is the desired orientation of the new segment
338 vm_transpose_matrix(rotmat4);
339 vm_matrix_x_matrix(rotmat2,vm_desired_orientation,rotmat4); // this is the desired orientation of the new segment
340 } else {
341 // Create rotation matrix describing rotation.
342
343 med_extract_matrix_from_segment(base_seg, rotmat); // get rotation matrix describing desired orientation
344 update_matrix_based_on_side(rotmat, base_side); // modify rotation matrix for desired side
345
346 // If the new segment is to be attached without rotation, then its orientation is the same as the base_segment
347 vm_matrix_x_matrix(rotmat4,rotmat,orient_matrix); // this is the desired orientation of the new segment
348
349 pbh.b = orientation*16384;
350 vm_angles_2_matrix(rotmat3,pbh);
351 rotmat4 = rotmat = vm_matrix_x_matrix(rotmat4, rotmat3);
352 med_extract_matrix_from_segment(first_seg, rotmat3); // get rotation matrix describing current orientation of first seg
353
354 // It is curious that the following statement has no analogue in the med_attach_segment_rotated code.
355 // Perhaps it is because segments are always attached at their front side. If the back side is the side
356 // passed to the function, then the matrix is not modified, which might suggest that what you need to do below
357 // is use Side_opposite[first_side].
358 update_matrix_based_on_side(rotmat3, Side_opposite[first_side]); // modify rotation matrix for desired side
359
360 vm_transpose_matrix(rotmat3); // get the inverse of the current orientation matrix
361 rotmat2 = vm_transposed_matrix(vm_matrix_x_matrix(rotmat,rotmat3)); // now rotmat2 takes the current segment to the desired orientation
362 }
363 result_mat = rotmat2;
364 }
365
med_create_group_rotation_matrix(const unsigned delta_flag,const shared_segment & first_seg,const unsigned first_side,const shared_segment & base_seg,const unsigned base_side,const vms_matrix & orient_matrix,const int orientation)366 static inline vms_matrix med_create_group_rotation_matrix(const unsigned delta_flag, const shared_segment &first_seg, const unsigned first_side, const shared_segment &base_seg, const unsigned base_side, const vms_matrix &orient_matrix, const int orientation)
367 {
368 vms_matrix result_mat;
369 return med_create_group_rotation_matrix(result_mat, delta_flag, first_seg, first_side, base_seg, base_side, orient_matrix, orientation), result_mat;
370 }
371
372 // -----------------------------------------------------------------------------------------
373 // Rotate all vertices and objects in group.
med_rotate_group(const vms_matrix & rotmat,group::segment_array_type_t & group_seglist,const shared_segment & first_seg,const unsigned first_side)374 static void med_rotate_group(const vms_matrix &rotmat, group::segment_array_type_t &group_seglist, const shared_segment &first_seg, const unsigned first_side)
375 {
376 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
377 auto &Objects = LevelUniqueObjectState.Objects;
378 auto &Vertices = LevelSharedVertexState.get_vertices();
379 auto &vmobjptridx = Objects.vmptridx;
380 auto &vcvertptr = Vertices.vcptr;
381 auto &vmvertptridx = Vertices.vmptridx;
382 const auto &&rotate_center = compute_center_point_on_side(vcvertptr, first_seg, first_side);
383
384 // Create list of points to rotate.
385 enumerated_bitset<MAX_VERTICES, vertnum_t> vertex_list{};
386
387 range_for (const auto &gs, group_seglist)
388 {
389 auto &sp = *vmsegptr(gs);
390
391 range_for (const auto v, sp.verts)
392 vertex_list[v] = true;
393
394 // Rotate center of all objects in group.
395 range_for (const auto objp, objects_in(sp, vmobjptridx, vcsegptr))
396 {
397 const auto tv1 = vm_vec_sub(objp->pos,rotate_center);
398 const auto tv = vm_vec_rotate(tv1,rotmat);
399 vm_vec_add(objp->pos, tv, rotate_center);
400 }
401 }
402
403 // Do the pre-rotation xlate, do the rotation, do the post-rotation xlate
404 range_for (auto &&v, vmvertptridx)
405 if (vertex_list[v]) {
406 const auto &&tv1 = vm_vec_sub(*v, rotate_center);
407 const auto tv = vm_vec_rotate(tv1,rotmat);
408 vm_vec_add(*v, tv, rotate_center);
409 }
410 }
411
412 // ------------------------------------------------------------------------------------------------
cgl_aux(const vmsegptridx_t segp,group::segment_array_type_t & seglistp,selected_segment_array_t * ignore_list,visited_segment_bitarray_t & visited)413 static void cgl_aux(const vmsegptridx_t segp, group::segment_array_type_t &seglistp, selected_segment_array_t *ignore_list, visited_segment_bitarray_t &visited)
414 {
415 if (ignore_list)
416 if (ignore_list->contains(segp))
417 return;
418
419 if (auto &&v = visited[segp]; !v) {
420 v = true;
421 seglistp.emplace_back(segp);
422
423 for (const auto c : segp->shared_segment::children)
424 if (IS_CHILD(c))
425 cgl_aux(segp.absolute_sibling(c), seglistp, ignore_list, visited);
426 }
427 }
428
429 // ------------------------------------------------------------------------------------------------
430 // Sets Been_visited[n] if n is reachable from segp
create_group_list(const vmsegptridx_t segp,group::segment_array_type_t & seglistp,selected_segment_array_t * ignore_list)431 static void create_group_list(const vmsegptridx_t segp, group::segment_array_type_t &seglistp, selected_segment_array_t *ignore_list)
432 {
433 visited_segment_bitarray_t visited;
434 cgl_aux(segp, seglistp, ignore_list, visited);
435 }
436
437
438 #define MXS MAX_SEGMENTS
439 #define MXV MAX_VERTICES
440
441 // ------------------------------------------------------------------------------------------------
duplicate_group(enumerated_array<uint8_t,MAX_VERTICES,vertnum_t> & vertex_ids,group::segment_array_type_t & segments)442 static void duplicate_group(enumerated_array<uint8_t, MAX_VERTICES, vertnum_t> &vertex_ids, group::segment_array_type_t &segments)
443 {
444 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
445 auto &Objects = LevelUniqueObjectState.Objects;
446 auto &Vertices = LevelSharedVertexState.get_vertices();
447 auto &vmobjptridx = Objects.vmptridx;
448 group::segment_array_type_t new_segments;
449 enumerated_array<vertnum_t, MAX_VERTICES, vertnum_t> new_vertex_ids; // If new_vertex_ids[v] != -1, then vertex v has been remapped to new_vertex_ids[v]
450 constexpr vertnum_t undefined_vertex_id{UINT32_MAX};
451
452 // duplicate vertices
453 new_vertex_ids.fill(undefined_vertex_id);
454
455 // duplicate vertices
456 auto &vcvertptridx = Vertices.vcptridx;
457 range_for (auto &&v, vcvertptridx)
458 {
459 if (const vertnum_t vn{v}; vertex_ids[vn])
460 new_vertex_ids[vn] = med_create_duplicate_vertex(*v);
461 }
462
463 // duplicate segments
464 range_for(const auto &gs, segments)
465 {
466 const auto &&segp = vmsegptr(gs);
467 const auto &&new_segment_id = med_create_duplicate_segment(Segments, segp);
468 new_segments.emplace_back(new_segment_id);
469 range_for (const auto objp, objects_in(segp, vmobjptridx, vmsegptr))
470 {
471 if (objp->type != OBJ_PLAYER) {
472 const auto &&new_obj_id = obj_create_copy(objp, vmsegptridx(new_segment_id));
473 (void)new_obj_id; // FIXME!
474 }
475 }
476 }
477
478 // Now, for each segment in segment_ids, correct its children numbers by translating through new_segment_ids
479 // and correct its vertex numbers by translating through new_vertex_ids
480 range_for(const auto &gs, new_segments)
481 {
482 shared_segment &sp = vmsegptr(gs);
483 range_for (auto &seg, sp.children)
484 {
485 if (IS_CHILD(seg)) {
486 group::segment_array_type_t::iterator inew = new_segments.begin();
487 range_for (const auto i, segments)
488 {
489 if (seg == i)
490 {
491 seg = *inew;
492 break;
493 }
494 ++inew;
495 }
496 }
497 } // end for (sidenum=0...
498
499 // Now fixup vertex ids
500 range_for (auto &v, sp.verts)
501 {
502 if (const vertnum_t vn{v}; vertex_ids[vn])
503 v = new_vertex_ids[vn];
504 }
505 } // end for (s=0...
506
507 // Now, copy new_segment_ids into segment_ids
508 segments = new_segments;
509
510 // Now, copy new_vertex_ids into vertex_ids
511 vertex_ids = {};
512
513 range_for (auto &v, new_vertex_ids)
514 if (v != undefined_vertex_id)
515 vertex_ids[v] = 1;
516 }
517
518
519 // ------------------------------------------------------------------------------------------------
in_group(segnum_t segnum,int group_num)520 static int in_group(segnum_t segnum, int group_num)
521 {
522 range_for(const auto& s, GroupList[group_num].segments)
523 if (segnum == s)
524 return 1;
525
526 return 0;
527 }
528
529 // ------------------------------------------------------------------------------------------------
530 // Copy a group of segments.
531 // The group is defined as all segments accessible from group_seg.
532 // The group is copied so group_seg:group_side is incident upon base_seg:base_side.
533 // group_seg and its vertices are bashed to coincide with base_seg.
534 // If any vertex of base_seg is contained in a segment that is reachable from group_seg, then errror.
med_copy_group(const unsigned delta_flag,const vmsegptridx_t base_seg,const unsigned base_side,vcsegptr_t group_seg,const unsigned group_side,const vms_matrix & orient_matrix)535 static int med_copy_group(const unsigned delta_flag, const vmsegptridx_t base_seg, const unsigned base_side, vcsegptr_t group_seg, const unsigned group_side, const vms_matrix &orient_matrix)
536 {
537 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
538 auto &Objects = LevelUniqueObjectState.Objects;
539 auto &Vertices = LevelSharedVertexState.get_vertices();
540 auto &vmobjptridx = Objects.vmptridx;
541 int x;
542 int new_current_group;
543
544 if (IS_CHILD(base_seg->shared_segment::children[base_side])) {
545 editor_status("Error -- unable to copy group, base_seg:base_side must be free.");
546 return 1;
547 }
548
549 if (num_groups == MAX_GROUPS) {
550 x = ui_messagebox( -2, -2, 2, "Warning: You have reached the MAXIMUM group number limit. Continue?", "No", "Yes" );
551 if (x==1)
552 return 0;
553 }
554
555 if (num_groups < MAX_GROUPS) {
556 num_groups++;
557 new_current_group = num_groups-1;
558 } else
559 new_current_group = 0;
560
561 Assert(current_group >= 0);
562
563 // Find groupsegp index
564 auto gb = GroupList[current_group].segments.begin();
565 auto ge = GroupList[current_group].segments.end();
566 auto gp = Groupsegp[current_group];
567 auto gi = std::find_if(gb, ge, [gp](const segnum_t segnum){ return vcsegptr(segnum) == gp; });
568 int gs_index = (gi == ge) ? 0 : std::distance(gb, gi);
569
570 GroupList[new_current_group] = GroupList[current_group];
571
572 // Make a list of all vertices in group.
573 enumerated_array<uint8_t, MAX_VERTICES, vertnum_t> in_vertex_list{};
574 if (group_seg == &New_segment)
575 range_for (auto &v, group_seg->verts)
576 in_vertex_list[v] = 1;
577 else {
578 range_for(const auto &gs, GroupList[new_current_group].segments)
579 range_for (auto &v, vmsegptr(gs)->verts)
580 in_vertex_list[v] = 1;
581 }
582
583 // Given a list of vertex indices (indicated by !0 in in_vertex_list) and segment indices (in list GroupList[current_group].segments, there
584 // are GroupList[current_group].num_segments segments), copy all segments and vertices
585 // Return updated lists of vertices and segments in in_vertex_list and GroupList[current_group].segments
586 duplicate_group(in_vertex_list, GroupList[new_current_group].segments);
587
588 //group_seg = &Segments[GroupList[new_current_group].segments[0]]; // connecting segment in group has been changed, so update group_seg
589
590 {
591 const auto &&gs = vmsegptr(GroupList[new_current_group].segments[gs_index]);
592 group_seg = gs;
593 Groupsegp[new_current_group] = gs;
594 }
595 Groupside[new_current_group] = Groupside[current_group];
596
597 range_for(const auto &gs, GroupList[new_current_group].segments)
598 {
599 shared_segment &s = *vmsegptr(gs);
600 s.group = new_current_group;
601 s.special = segment_special::nothing;
602 s.matcen_num = materialization_center_number::None;
603 }
604
605 auto &vcvertptr = Vertices.vcptr;
606 // Breaking connections between segments in the current group and segments not in the group.
607 range_for(const auto &gs, GroupList[new_current_group].segments)
608 {
609 const auto &&segp = base_seg.absolute_sibling(gs);
610 for (auto &&[sidenum, child_segnum] : enumerate(segp->shared_segment::children))
611 if (IS_CHILD(child_segnum))
612 {
613 if (!in_group(child_segnum, new_current_group))
614 {
615 child_segnum = segment_none;
616 validate_segment_side(vcvertptr, segp, sidenum); // we have converted a connection to a side so validate the segment
617 }
618 }
619 }
620
621 copy_uvs_seg_to_seg(vmsegptr(&New_segment), group_seg);
622
623 // Now do the copy
624 // First, xlate all vertices so center of group_seg:group_side is at origin
625 const auto &&srcv = compute_center_point_on_side(vcvertptr, group_seg, group_side);
626 auto &vmvertptridx = Vertices.vmptridx;
627 range_for (auto &&v, vmvertptridx)
628 if (in_vertex_list[v])
629 vm_vec_sub2(*v, srcv);
630
631 // Now, translate all object positions.
632 range_for(const auto &segnum, GroupList[new_current_group].segments)
633 {
634 range_for (const auto objp, objects_in(vmsegptr(segnum), vmobjptridx, vmsegptr))
635 vm_vec_sub2(objp->pos, srcv);
636 }
637
638 // Now, rotate segments in group so orientation of group_seg is same as base_seg.
639 const auto rotmat = med_create_group_rotation_matrix(delta_flag, group_seg, group_side, base_seg, base_side, orient_matrix, 0);
640 med_rotate_group(rotmat, GroupList[new_current_group].segments, group_seg, group_side);
641
642 // Now xlate all vertices so group_seg:group_side shares center point with base_seg:base_side
643 const auto &&destv = compute_center_point_on_side(vcvertptr, base_seg, base_side);
644 range_for (auto &&v, vmvertptridx)
645 if (in_vertex_list[v])
646 vm_vec_add2(*v, destv);
647
648 // Now, xlate all object positions.
649 range_for(const auto &segnum, GroupList[new_current_group].segments)
650 {
651 range_for (const auto objp, objects_in(vmsegptr(segnum), vmobjptridx, vmsegptr))
652 vm_vec_add2(objp->pos, destv);
653 }
654
655 // Now, copy all walls (ie, doors, illusionary, etc.) into the new group.
656 copy_group_walls(current_group, new_current_group);
657
658 current_group = new_current_group;
659
660 // Now, form joint on connecting sides.
661 med_form_joint(base_seg,base_side,vmsegptridx(Groupsegp[current_group]),Groupside[new_current_group]);
662
663 validate_selected_segments();
664 med_combine_duplicate_vertices(in_vertex_list);
665
666 return 0;
667 }
668
669
670 // ------------------------------------------------------------------------------------------------
671 // Move a group of segments.
672 // The group is defined as all segments accessible from group_seg.
673 // The group is moved so group_seg:group_side is incident upon base_seg:base_side.
674 // group_seg and its vertices are bashed to coincide with base_seg.
675 // If any vertex of base_seg is contained in a segment that is reachable from group_seg, then errror.
med_move_group(int delta_flag,const vmsegptridx_t base_seg,int base_side,const vmsegptridx_t group_seg,int group_side,const vms_matrix & orient_matrix,int orientation)676 static int med_move_group(int delta_flag, const vmsegptridx_t base_seg, int base_side, const vmsegptridx_t group_seg, int group_side, const vms_matrix &orient_matrix, int orientation)
677 {
678 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
679 auto &Objects = LevelUniqueObjectState.Objects;
680 auto &Vertices = LevelSharedVertexState.get_vertices();
681 auto &vmobjptridx = Objects.vmptridx;
682 if (IS_CHILD(base_seg->children[base_side]))
683 if (base_seg->children[base_side] != group_seg) {
684 editor_status("Error -- unable to move group, base_seg:base_side must be free or point to group_seg.");
685 return 1;
686 }
687
688 // // See if any vertices in base_seg are contained in any vertex in group_list
689 // for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++)
690 // for (s=0; s<GroupList[current_group].num_segments; s++)
691 // for (vv=0; vv<MAX_VERTICES_PER_SEGMENT; vv++)
692 // if (Segments[GroupList[current_group].segments[s]].verts[vv] == base_seg->verts[v]) {
693 // editor_status("Error -- unable to move group, it shares a vertex with destination segment.");
694 // return 1;
695 // }
696
697 enumerated_array<uint8_t, MAX_VERTICES, vertnum_t> in_vertex_list{};
698 enumerated_array<int8_t, MAX_VERTICES, vertnum_t> out_vertex_list{};
699
700 // Make a list of all vertices in group.
701 range_for(const auto &gs, GroupList[current_group].segments)
702 range_for (auto &v, vmsegptr(gs)->verts)
703 in_vertex_list[v] = 1;
704
705 // For all segments which are not in GroupList[current_group].segments, mark all their vertices in the out list.
706 range_for (const auto &&segp, vmsegptridx)
707 {
708 if (!GroupList[current_group].segments.contains(segp))
709 {
710 range_for (auto &v, segp->verts)
711 out_vertex_list[v] = 1;
712 }
713 }
714
715 // Now, for all vertices present in both the in (part of group segment) and out (part of non-group segment)
716 // create an extra copy of the vertex so we can just move the ones in the in list.
717 // Can't use Highest_vertex_index as loop termination because it gets increased by med_create_duplicate_vertex.
718
719 auto &vcvertptr = Vertices.vcptr;
720 auto &vmvertptridx = Vertices.vmptridx;
721 range_for (auto &&v, vmvertptridx)
722 if (in_vertex_list[v])
723 if (out_vertex_list[v]) {
724 const auto new_vertex_id = med_create_duplicate_vertex(*v);
725 in_vertex_list[v] = 0;
726 in_vertex_list[new_vertex_id] = 1;
727
728 // Create a new vertex and assign all occurrences of vertex v in IN list to new vertex number.
729 range_for(const auto &gs, GroupList[current_group].segments)
730 {
731 auto &sp = *vmsegptr(gs);
732 range_for (auto &vv, sp.verts)
733 if (vv == v)
734 vv = new_vertex_id;
735 }
736 }
737
738 range_for(const auto &gs, GroupList[current_group].segments)
739 vmsegptr(gs)->group = current_group;
740
741 // Breaking connections between segments in the group and segments not in the group.
742 range_for(const auto &gs, GroupList[current_group].segments)
743 {
744 const auto &&segp = base_seg.absolute_sibling(gs);
745 for (const auto &&[idx0, value0] : enumerate(segp->children))
746 if (IS_CHILD(value0))
747 {
748 const auto &&csegp = base_seg.absolute_sibling(value0);
749 if (csegp->group != current_group)
750 {
751 for (const auto &&[idx1, value1] : enumerate(csegp->children))
752 if (IS_CHILD(value1))
753 {
754 auto &dsegp = *vmsegptr(value1);
755 if (dsegp.group == current_group)
756 {
757 value1 = segment_none;
758 validate_segment_side(vcvertptr, csegp, idx1); // we have converted a connection to a side so validate the segment
759 }
760 }
761 value0 = segment_none;
762 validate_segment_side(vcvertptr, segp, idx0); // we have converted a connection to a side so validate the segment
763 }
764 }
765 }
766
767 copy_uvs_seg_to_seg(vmsegptr(&New_segment), vcsegptr(Groupsegp[current_group]));
768
769 // Now do the move
770 // First, xlate all vertices so center of group_seg:group_side is at origin
771 const auto &&srcv = compute_center_point_on_side(vcvertptr, group_seg, group_side);
772 range_for (auto &&v, vmvertptridx)
773 if (in_vertex_list[v])
774 vm_vec_sub2(*v, srcv);
775
776 // Now, move all object positions.
777 range_for(const auto &segnum, GroupList[current_group].segments)
778 {
779 range_for (const auto objp, objects_in(vmsegptr(segnum), vmobjptridx, vmsegptr))
780 vm_vec_sub2(objp->pos, srcv);
781 }
782
783 // Now, rotate segments in group so orientation of group_seg is same as base_seg.
784 const auto rotmat = med_create_group_rotation_matrix(delta_flag, group_seg, group_side, base_seg, base_side, orient_matrix, orientation);
785 med_rotate_group(rotmat, GroupList[current_group].segments, group_seg, group_side);
786
787 // Now xlate all vertices so group_seg:group_side shares center point with base_seg:base_side
788 const auto &&destv = compute_center_point_on_side(vcvertptr, base_seg, base_side);
789 range_for (auto &&v, vmvertptridx)
790 if (in_vertex_list[v])
791 vm_vec_add2(*v, destv);
792
793 // Now, rotate all object positions.
794 range_for(const auto &segnum, GroupList[current_group].segments)
795 {
796 range_for (const auto objp, objects_in(vmsegptr(segnum), vmobjptridx, vmsegptr))
797 vm_vec_add2(objp->pos, destv);
798 }
799
800 // Now, form joint on connecting sides.
801 med_form_joint(base_seg,base_side,group_seg,group_side);
802
803 validate_selected_segments();
804 med_combine_duplicate_vertices(in_vertex_list);
805
806 return 0;
807 }
808
809
810 // -----------------------------------------------------------------------------
place_new_segment_in_world(void)811 static segnum_t place_new_segment_in_world(void)
812 {
813 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
814 auto &Vertices = LevelSharedVertexState.get_vertices();
815 const auto &&segnum = Segments.vmptridx(get_free_segment_number(Segments));
816 auto &seg = *segnum;
817 seg = New_segment;
818
819 auto &vcvertptr = Vertices.vcptr;
820 for (auto &&[w, r] : zip(seg.verts, New_segment.verts))
821 w = med_create_duplicate_vertex(vcvertptr(r));
822
823 return segnum;
824
825 }
826
827 // -----------------------------------------------------------------------------
828 // Attach segment in the new-fangled way, which is by using the CopyGroup code.
AttachSegmentNewAng(const vms_angvec & pbh)829 static int AttachSegmentNewAng(const vms_angvec &pbh)
830 {
831 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
832 auto &Vertices = LevelSharedVertexState.get_vertices();
833 GroupList[current_group].segments.clear();
834 const auto newseg = place_new_segment_in_world();
835 GroupList[current_group].segments.emplace_back(newseg);
836
837 const auto &&nsegp = vmsegptridx(newseg);
838 if (!med_move_group(1, Cursegp, Curside, nsegp, AttachSide, vm_angles_2_matrix(pbh),0))
839 {
840 autosave_mine(mine_filename);
841
842 med_propagate_tmaps_to_segments(Cursegp,nsegp,0);
843 med_propagate_tmaps_to_back_side(nsegp, Side_opposite[AttachSide],0);
844 copy_uvs_seg_to_seg(vmsegptr(&New_segment),nsegp);
845
846 Cursegp = nsegp;
847 Curside = Side_opposite[AttachSide];
848 med_create_new_segment_from_cursegp();
849
850 if (Lock_view_to_cursegp)
851 {
852 auto &vcvertptr = Vertices.vcptr;
853 set_view_target_from_segment(vcvertptr, Cursegp);
854 }
855
856 Update_flags |= UF_WORLD_CHANGED;
857 mine_changed = 1;
858 warn_if_concave_segment(Cursegp);
859 }
860
861 return 1;
862 }
863
864 }
865
AttachSegmentNew(void)866 int AttachSegmentNew(void)
867 {
868 vms_angvec pbh;
869
870 pbh.p = 0;
871 pbh.b = 0;
872 pbh.h = 0;
873
874 AttachSegmentNewAng(pbh);
875 return 1;
876
877 }
878
879 namespace {
880 // -----------------------------------------------------------------------------
validate_selected_segments(void)881 void validate_selected_segments(void)
882 {
883 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
884 auto &Vertices = LevelSharedVertexState.get_vertices();
885 auto &vcvertptr = Vertices.vcptr;
886 range_for (const auto &gs, GroupList[current_group].segments)
887 validate_segment(vcvertptr, vmsegptridx(gs));
888 }
889 }
890
891 // =====================================================================================
892
893
894 // -----------------------------------------------------------------------------
895 namespace dsx {
delete_segment_from_group(const vmsegptridx_t segment_num,unsigned group_num)896 void delete_segment_from_group(const vmsegptridx_t segment_num, unsigned group_num)
897 {
898 segment_num->group = -1;
899 GroupList[group_num].segments.erase(segment_num);
900 }
901 }
902 // =====================================================================================
903
904
905 // -----------------------------------------------------------------------------
add_segment_to_group(segnum_t segment_num,int group_num)906 void add_segment_to_group(segnum_t segment_num, int group_num)
907 {
908 GroupList[group_num].segments.emplace_back(segment_num);
909 }
910 // =====================================================================================
911
912
913 // -----------------------------------------------------------------------------
rotate_segment_new(const vms_angvec & pbh)914 int rotate_segment_new(const vms_angvec &pbh)
915 {
916 int newseg_side;
917 vms_matrix tm1;
918 group::segment_array_type_t selected_segs_save;
919 int child_save;
920 int current_group_save;
921
922 if (!IS_CHILD(Cursegp->children[static_cast<int>(Side_opposite[Curside])]))
923 // -- I don't understand this, MK, 01/25/94: if (Cursegp->children[Curside] != group_seg-Segments)
924 {
925 editor_status("Error -- unable to rotate group, Cursegp:Side_opposite[Curside] cannot be free.");
926 return 1;
927 }
928
929 current_group_save = current_group;
930 current_group = ROT_GROUP;
931 Groupsegp[ROT_GROUP] = Cursegp;
932
933 selected_segs_save = GroupList[current_group].segments;
934 GroupList[ROT_GROUP].segments.clear();
935 const auto newseg = Cursegp;
936 newseg_side = Side_opposite[Curside];
937
938 // Create list of segments to rotate.
939 // Sever connection between first seg to rotate and its connection on Side_opposite[Curside].
940 child_save = Cursegp->children[newseg_side]; // save connection we are about to sever
941 Cursegp->children[newseg_side] = segment_none; // sever connection
942 create_group_list(Cursegp, GroupList[ROT_GROUP].segments, NULL); // create list of segments in group
943 Cursegp->children[newseg_side] = child_save; // restore severed connection
944 GroupList[ROT_GROUP].segments.emplace_back(newseg);
945
946 const auto baseseg = newseg->children[newseg_side];
947 if (!IS_CHILD(baseseg)) {
948 editor_status("Error -- unable to rotate segment, side opposite curside is not attached.");
949 GroupList[current_group].segments = selected_segs_save;
950 current_group = current_group_save;
951 return 1;
952 }
953 const auto &&basesegp = vmsegptridx(baseseg);
954 const auto &&baseseg_side = find_connect_side(newseg, basesegp);
955
956 med_extract_matrix_from_segment(newseg, tm1);
957 tm1 = vmd_identity_matrix;
958 const auto tm2 = vm_angles_2_matrix(pbh);
959 const auto orient_matrix = vm_matrix_x_matrix(tm1,tm2);
960
961 basesegp->children[baseseg_side] = segment_none;
962 newseg->children[newseg_side] = segment_none;
963
964 if (!med_move_group(1, basesegp, baseseg_side, newseg, newseg_side, orient_matrix, 0))
965 {
966 Cursegp = newseg;
967 med_create_new_segment_from_cursegp();
968 // validate_selected_segments();
969 med_propagate_tmaps_to_segments(basesegp, newseg, 1);
970 med_propagate_tmaps_to_back_side(newseg, Curside, 1);
971 }
972
973 GroupList[current_group].segments = selected_segs_save;
974 current_group = current_group_save;
975
976 return 1;
977 }
978
979 // -----------------------------------------------------------------------------
980 // Attach segment in the new-fangled way, which is by using the CopyGroup code.
RotateSegmentNew(vms_angvec * pbh)981 int RotateSegmentNew(vms_angvec *pbh)
982 {
983 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
984 auto &Vertices = LevelSharedVertexState.get_vertices();
985 int rval;
986
987 autosave_mine(mine_filename);
988
989 rval = rotate_segment_new(*pbh);
990
991 if (Lock_view_to_cursegp)
992 {
993 auto &vcvertptr = Vertices.vcptr;
994 set_view_target_from_segment(vcvertptr, Cursegp);
995 }
996
997 Update_flags |= UF_WORLD_CHANGED;
998 mine_changed = 1;
999 warn_if_concave_segment(Cursegp);
1000
1001 return rval;
1002 }
1003
1004 #if 0
1005 static std::array<d_fname, MAX_TEXTURES> current_tmap_list;
1006
1007 // -----------------------------------------------------------------------------
1008 // Save mine will:
1009 // 1. Write file info, header info, editor info, vertex data, segment data,
1010 // and new_segment in that order, marking their file offset.
1011 // 2. Go through all the fields and fill in the offset, size, and sizeof
1012 // values in the headers.
1013 static int med_save_group( const char *filename, const group::vertex_array_type_t &vertex_ids, const group::segment_array_type_t &segment_ids)
1014 {
1015 int header_offset, editor_offset, vertex_offset, segment_offset, texture_offset;
1016 char ErrorMessage[100];
1017 int j;
1018
1019 auto SaveFile = PHYSFSX_openWriteBuffered(filename);
1020 if (!SaveFile)
1021 {
1022 snprintf(ErrorMessage, sizeof(ErrorMessage), "ERROR: Unable to open %s\n", filename);
1023 ui_messagebox( -2, -2, 1, ErrorMessage, "Ok" );
1024 return 1;
1025 }
1026
1027 //===================== SAVE FILE INFO ========================
1028
1029 group_fileinfo.fileinfo_version = MINE_VERSION;
1030 group_fileinfo.fileinfo_sizeof = sizeof(group_fileinfo);
1031 group_fileinfo.header_offset = -1;
1032 group_fileinfo.header_size = sizeof(group_header);
1033 group_fileinfo.editor_offset = -1;
1034 group_fileinfo.editor_size = sizeof(group_editor);
1035 group_fileinfo.vertex_offset = -1;
1036 group_fileinfo.vertex_howmany = vertex_ids.size();
1037 group_fileinfo.vertex_sizeof = sizeof(vms_vector);
1038 group_fileinfo.segment_offset = -1;
1039 group_fileinfo.segment_howmany = segment_ids.size();
1040 group_fileinfo.segment_sizeof = sizeof(segment);
1041 group_fileinfo.texture_offset = -1;
1042 group_fileinfo.texture_howmany = 0;
1043 group_fileinfo.texture_sizeof = 13; // num characters in a name
1044
1045 // Write the fileinfo
1046 PHYSFS_write( SaveFile, &group_fileinfo, sizeof(group_fileinfo), 1);
1047
1048 //===================== SAVE HEADER INFO ========================
1049
1050 group_header.num_vertices = vertex_ids.size();
1051 group_header.num_segments = segment_ids.size();
1052
1053 // Write the editor info
1054 header_offset = PHYSFS_tell(SaveFile);
1055 PHYSFS_write( SaveFile, &group_header, sizeof(group_header), 1);
1056
1057 //===================== SAVE EDITOR INFO ==========================
1058 group_editor.newsegment_offset = -1; // To be written
1059 group_editor.newsegment_size = sizeof(segment);
1060 // Next 3 vars added 10/07 by JAS
1061 group_editor.Groupsegp = 0;
1062 if (Groupsegp[current_group]) {
1063 const auto i = segment_ids.find(vmsegptridx(Groupsegp[current_group]));
1064 if (i != segment_ids.end())
1065 group_editor.Groupsegp = std::distance(segment_ids.begin(), i);
1066 }
1067 group_editor.Groupside = Groupside[current_group];
1068
1069 editor_offset = PHYSFS_tell(SaveFile);
1070 PHYSFS_write( SaveFile, &group_editor, sizeof(group_editor), 1);
1071
1072
1073 //===================== SAVE VERTEX INFO ==========================
1074
1075 vertex_offset = PHYSFS_tell(SaveFile);
1076 range_for (const auto &gv, vertex_ids)
1077 {
1078 const vertex tvert = *vcvertptr(gv);
1079 PHYSFS_write(SaveFile, &tvert, sizeof(tvert), 1);
1080 }
1081
1082 //===================== SAVE SEGMENT INFO =========================
1083
1084
1085 segment_offset = PHYSFS_tell(SaveFile);
1086 range_for (const auto &gs, segment_ids)
1087 {
1088 auto &&tseg = *vmsegptr(gs);
1089
1090 for (j=0;j<6;j++) {
1091 group::segment_array_type_t::const_iterator i = segment_ids.find(tseg.children[j]);
1092 tseg.children[j] = (i == segment_ids.end()) ? segment_none : std::distance(segment_ids.begin(), i);
1093 }
1094
1095 for (j=0;j<8;j++)
1096 {
1097 group::vertex_array_type_t::const_iterator i = vertex_ids.find(tseg.verts[j]);
1098 if (i != vertex_ids.end())
1099 tseg.verts[j] = std::distance(vertex_ids.begin(), i);
1100 }
1101 PHYSFS_write( SaveFile, &tseg, sizeof(tseg), 1);
1102
1103 }
1104
1105 //===================== SAVE TEXTURE INFO ==========================
1106
1107 texture_offset = PHYSFS_tell(SaveFile);
1108
1109 for (unsigned i = 0, n = NumTextures; i < n; ++i)
1110 {
1111 current_tmap_list[i] = TmapInfo[i].filename;
1112 PHYSFS_write(SaveFile, current_tmap_list[i].data(), current_tmap_list[i].size(), 1);
1113 }
1114
1115 //============= REWRITE FILE INFO, TO SAVE OFFSETS ===============
1116
1117 // Update the offset fields
1118 group_fileinfo.header_offset = header_offset;
1119 group_fileinfo.editor_offset = editor_offset;
1120 group_fileinfo.vertex_offset = vertex_offset;
1121 group_fileinfo.segment_offset = segment_offset;
1122 group_fileinfo.texture_offset = texture_offset;
1123
1124 // Write the fileinfo
1125 PHYSFSX_fseek( SaveFile, 0, SEEK_SET ); // Move to TOF
1126 PHYSFS_write( SaveFile, &group_fileinfo, sizeof(group_fileinfo), 1);
1127
1128 //==================== CLOSE THE FILE =============================
1129 return 0;
1130 }
1131
1132 static std::array<d_fname, MAX_TEXTURES> old_tmap_list;
1133 // static short tmap_xlate_table[MAX_TEXTURES]; // ZICO - FIXME
1134
1135 // -----------------------------------------------------------------------------
1136 // Load group will:
1137 //int med_load_group(char * filename)
1138 static int med_load_group( const char *filename, group::vertex_array_type_t &vertex_ids, group::segment_array_type_t &segment_ids)
1139 {
1140 int vertnum;
1141 char ErrorMessage[200];
1142 int translate=0;
1143 char *temptr;
1144 segment tseg;
1145 auto &&[LoadFile, physfserr] = PHYSFSX_openReadBuffered(filename);
1146 if (!LoadFile)
1147 {
1148 snprintf(ErrorMessage, sizeof(ErrorMessage), "ERROR: Failed to open %s\n%s\n", filename, PHYSFS_getErrorByCode(physfserr));
1149 ui_messagebox( -2, -2, 1, ErrorMessage, "Ok" );
1150 return 1;
1151 }
1152
1153 //===================== READ FILE INFO ========================
1154
1155 // These are the default values... version and fileinfo_sizeof
1156 // don't have defaults.
1157 group_fileinfo.header_offset = -1;
1158 group_fileinfo.header_size = sizeof(group_header);
1159 group_fileinfo.editor_offset = -1;
1160 group_fileinfo.editor_size = sizeof(group_editor);
1161 group_fileinfo.vertex_offset = -1;
1162 group_fileinfo.vertex_howmany = 0;
1163 group_fileinfo.vertex_sizeof = sizeof(vms_vector);
1164 group_fileinfo.segment_offset = -1;
1165 group_fileinfo.segment_howmany = 0;
1166 group_fileinfo.segment_sizeof = sizeof(segment);
1167 group_fileinfo.texture_offset = -1;
1168 group_fileinfo.texture_howmany = 0;
1169 group_fileinfo.texture_sizeof = 13; // num characters in a name
1170
1171 // Read in group_top_fileinfo to get size of saved fileinfo.
1172
1173 if (PHYSFSX_fseek( LoadFile, 0, SEEK_SET ))
1174 Error( "Error seeking to 0 in group.c" );
1175
1176 if (PHYSFS_read( LoadFile, &group_top_fileinfo, sizeof(group_top_fileinfo),1 )!=1)
1177 Error( "Error reading top_fileinfo in group.c" );
1178
1179 // Check version number
1180 if (group_top_fileinfo.fileinfo_version < COMPATIBLE_VERSION )
1181 {
1182 snprintf(ErrorMessage, sizeof(ErrorMessage), "You are trying to load %s\n" \
1183 "a version %d group, which is known to be incompatible\n" \
1184 "with the current expected version %d groups.", \
1185 filename, group_top_fileinfo.fileinfo_version, MINE_VERSION );
1186
1187 if (ui_messagebox( -2, -2, 2, ErrorMessage, "Forget it", "Try anyway" )==1)
1188 {
1189 return 1;
1190 }
1191
1192 ui_messagebox( -2, -2, 1, "Good luck!", "I need it" );
1193 }
1194
1195 // Now, Read in the fileinfo
1196
1197 if (PHYSFSX_fseek( LoadFile, 0, SEEK_SET ))
1198 Error( "Error seeking to 0b in group.c" );
1199
1200 if (PHYSFS_read( LoadFile, &group_fileinfo, group_top_fileinfo.fileinfo_sizeof,1 )!=1)
1201 Error( "Error reading group_fileinfo in group.c" );
1202
1203 //===================== READ HEADER INFO ========================
1204
1205 // Set default values.
1206 group_header.num_vertices = 0;
1207 group_header.num_segments = 0;
1208
1209 if (group_fileinfo.header_offset > -1 )
1210 {
1211 if (PHYSFSX_fseek( LoadFile,group_fileinfo.header_offset, SEEK_SET ))
1212 Error( "Error seeking to header_offset in group.c" );
1213
1214 if (PHYSFS_read( LoadFile, &group_header, group_fileinfo.header_size,1 )!=1)
1215 Error( "Error reading group_header in group.c" );
1216 }
1217
1218 //===================== READ EDITOR INFO ==========================
1219
1220 // Set default values
1221 group_editor.current_seg = 0;
1222 group_editor.newsegment_offset = -1; // To be written
1223 group_editor.newsegment_size = sizeof(segment);
1224 group_editor.Groupsegp = -1;
1225 group_editor.Groupside = 0;
1226
1227 if (group_fileinfo.editor_offset > -1 )
1228 {
1229 if (PHYSFSX_fseek( LoadFile,group_fileinfo.editor_offset, SEEK_SET ))
1230 Error( "Error seeking to editor_offset in group.c" );
1231
1232 if (PHYSFS_read( LoadFile, &group_editor, group_fileinfo.editor_size,1 )!=1)
1233 Error( "Error reading group_editor in group.c" );
1234
1235 }
1236
1237 //===================== READ VERTEX INFO ==========================
1238
1239 if ( (group_fileinfo.vertex_offset > -1) && (group_fileinfo.vertex_howmany > 0))
1240 {
1241 if (PHYSFSX_fseek( LoadFile,group_fileinfo.vertex_offset, SEEK_SET ))
1242 Error( "Error seeking to vertex_offset in group.c" );
1243
1244 vertex_ids.clear();
1245 for (unsigned i = 0; i< group_header.num_vertices; ++i)
1246 {
1247 vertex tvert;
1248 if (PHYSFS_read( LoadFile, &tvert, sizeof(tvert),1 )!=1)
1249 Error( "Error reading tvert in group.c" );
1250 vertex_ids.emplace_back(med_create_duplicate_vertex(tvert));
1251 }
1252
1253 }
1254
1255 //==================== READ SEGMENT INFO ===========================
1256
1257 if ( (group_fileinfo.segment_offset > -1) && (group_fileinfo.segment_howmany > 0))
1258 {
1259 if (PHYSFSX_fseek( LoadFile,group_fileinfo.segment_offset, SEEK_SET ))
1260 Error( "Error seeking to segment_offset in group.c" );
1261
1262 segment_ids.clear();
1263 for (unsigned i = 0; i < group_header.num_segments; ++i)
1264 {
1265 if (PHYSFS_read( LoadFile, &tseg, sizeof(segment),1 )!=1)
1266 Error( "Error reading tseg in group.c" );
1267
1268 group::segment_array_type_t::value_type s = get_free_segment_number(Segments);
1269 segment_ids.emplace_back(s);
1270 const auto &&segp = vmsegptridx(s);
1271 *segp = tseg;
1272 segp->objects = object_none;
1273
1274 fuelcen_activate(segp);
1275 }
1276
1277 range_for (const auto &gs, segment_ids)
1278 {
1279 auto &segp = *vmsegptr(gs);
1280 // Fix vertices
1281 range_for (auto &j, segp.verts)
1282 {
1283 vertnum = vertex_ids[j];
1284 j = vertnum;
1285 }
1286
1287 // Fix children and walls.
1288 for (unsigned j = 0; j < MAX_SIDES_PER_SEGMENT; ++j)
1289 {
1290 auto &seg = Segments[gs];
1291 shared_segment &useg = seg;
1292 unique_segment &useg = seg;
1293 sseg.sides[j].wall_num = wall_none;
1294 if (IS_CHILD(Segments[gs].children[j])) {
1295 segnum_t segnum;
1296 segnum = segment_ids[Segments[gs].children[j]];
1297 Segments[gs].children[j] = segnum;
1298 }
1299 //Translate textures.
1300 if (translate == 1) {
1301 int temp;
1302 useg.sides[j].tmap_num = tmap_xlate_table[useg.sides[j].tmap_num];
1303 temp = useg.sides[j].tmap_num2;
1304 // strip off orientation bits
1305 if (const auto tmap_xlate = get_texture_index(temp); tmap_xlate != 0)
1306 useg.sides[j].tmap_num2 = build_texture2_value(tmap_xlate_table[tmap_xlate], get_texture_rotation_high(temp)); // mask on original orientation bits
1307 }
1308 }
1309 }
1310 }
1311
1312 //===================== READ TEXTURE INFO ==========================
1313
1314 if ( (group_fileinfo.texture_offset > -1) && (group_fileinfo.texture_howmany > 0))
1315 {
1316 if (PHYSFSX_fseek( LoadFile, group_fileinfo.texture_offset, SEEK_SET ))
1317 Error( "Error seeking to texture_offset in gamemine.c" );
1318
1319 range_for (auto &i, partial_range(old_tmap_list, group_fileinfo.texture_howmany))
1320 {
1321 std::array<char, FILENAME_LEN> a;
1322 if (PHYSFS_read(LoadFile, a.data(), std::min(static_cast<size_t>(group_fileinfo.texture_sizeof), a.size()), 1) != 1)
1323 Error( "Error reading old_tmap_list[i] in gamemine.c" );
1324 i.copy_if(a);
1325 }
1326 }
1327
1328 //=============== GENERATE TEXTURE TRANSLATION TABLE ===============
1329
1330 translate = 0;
1331
1332 Assert (NumTextures < MAX_TEXTURES);
1333 {
1334 hashtable ht;
1335 // Remove all the file extensions in the textures list
1336
1337 for (unsigned i = 0; i < NumTextures; ++i)
1338 {
1339 temptr = strchr(&TmapInfo[i].filename[0u], '.');
1340 if (temptr) *temptr = '\0';
1341 hashtable_insert( &ht, &TmapInfo[i].filename[0u], i );
1342 }
1343
1344 // For every texture, search through the texture list
1345 // to find a matching name.
1346 for (unsigned j = 0; j < group_fileinfo.texture_howmany; ++j)
1347 {
1348 // Remove this texture name's extension
1349 temptr = strchr(&old_tmap_list[j][0u], '.');
1350 if (temptr) *temptr = '\0';
1351
1352 tmap_xlate_table[j] = hashtable_search( &ht, static_cast<const char *>(old_tmap_list[j]));
1353 if (tmap_xlate_table[j] < 0 )
1354 tmap_xlate_table[j] = 0;
1355 if (tmap_xlate_table[j] != j ) translate = 1;
1356 }
1357 }
1358
1359
1360 //======================== CLOSE FILE ==============================
1361 LoadFile.reset();
1362
1363 //========================= UPDATE VARIABLES ======================
1364
1365 if (group_editor.Groupsegp != -1 )
1366 Groupsegp[current_group] = &Segments[segment_ids[group_editor.Groupsegp]];
1367 else
1368 Groupsegp[current_group] = NULL;
1369
1370 Groupside[current_group] = group_editor.Groupside;
1371
1372 warn_if_concave_segments();
1373
1374 return 0;
1375 }
1376
1377 static char group_filename[PATH_MAX] = "*.GRP";
1378
1379 static void checkforgrpext( char * f )
1380 {
1381 int i;
1382
1383 for (i=1; f[i]; i++ )
1384 {
1385 if (f[i]=='.') return;
1386
1387 if ((f[i]==' '||f[i]==0) )
1388 {
1389 f[i]='.';
1390 f[i+1]='G';
1391 f[i+2]= 'R';
1392 f[i+3]= 'P';
1393 f[i+4]=0;
1394 return;
1395 }
1396 }
1397
1398 if (i < 123)
1399 {
1400 f[i]='.';
1401 f[i+1]='G';
1402 f[i+2]= 'R';
1403 f[i+3]= 'P';
1404 f[i+4]=0;
1405 return;
1406 }
1407 }
1408 #endif
1409
1410 //short vertex_list[MAX_VERTICES];
1411
1412
SaveGroup()1413 int SaveGroup()
1414 {
1415 ui_messagebox(-2, -2, 1, "ERROR: Groups are broken.", "Ok");
1416 return 0;
1417 #if 0
1418 // Save group
1419 int i;
1420
1421 if (current_group == -1)
1422 {
1423 ui_messagebox(-2, -2, 1, "ERROR: No current group.", "Ok");
1424 return 0;
1425 }
1426
1427 std::array<int8_t, MAX_VERTICES> vertex_list{};
1428
1429 // Make a list of all vertices in group.
1430 range_for (const auto &gs, GroupList[current_group].segments)
1431 range_for (auto &v, Segments[gs].verts)
1432 {
1433 vertex_list[v] = 1;
1434 }
1435
1436 GroupList[current_group].vertices.clear();
1437 for (i=0; i<=Highest_vertex_index; i++)
1438 if (vertex_list[i] == 1) {
1439 GroupList[current_group].vertices.emplace_back(i);
1440 }
1441 med_save_group("TEMP.GRP", GroupList[current_group].vertices, GroupList[current_group].segments);
1442 if (ui_get_filename( group_filename, "*.GRP", "SAVE GROUP" ))
1443 {
1444 checkforgrpext(group_filename);
1445 if (med_save_group(group_filename, GroupList[current_group].vertices, GroupList[current_group].segments))
1446 return 0;
1447 mine_changed = 0;
1448 }
1449
1450 return 1;
1451 #endif
1452 }
1453
1454
LoadGroup()1455 int LoadGroup()
1456 {
1457 ui_messagebox(-2, -2, 1, "ERROR: Groups are broken.", "Ok");
1458 return 0;
1459 #if 0
1460 int x;
1461
1462 if (num_groups == MAX_GROUPS)
1463 {
1464 x = ui_messagebox( -2, -2, 2, "Warning: You are about to wipe out a group.", "ARGH! NO!", "No problemo." );
1465 if (x==1) return 0;
1466 }
1467
1468 if (num_groups < MAX_GROUPS)
1469 {
1470 num_groups++;
1471 current_group = num_groups-1;
1472 }
1473 else current_group = 0;
1474
1475 if (ui_get_filename( group_filename, "*.GRP", "LOAD GROUP" ))
1476 {
1477 checkforgrpext(group_filename);
1478 med_load_group(group_filename, GroupList[current_group].vertices, GroupList[current_group].segments);
1479
1480 if (!med_move_group(0, Cursegp, Curside, vmsegptridx(Groupsegp[current_group]), Groupside[current_group], vmd_identity_matrix, 0))
1481 {
1482 autosave_mine(mine_filename);
1483 set_view_target_from_segment(Cursegp);
1484 Update_flags |= UF_WORLD_CHANGED;
1485 mine_changed = 1;
1486 diagnostic_message("Group moved.");
1487 return 0;
1488 } else
1489 return 1;
1490 } else
1491
1492 return 1;
1493 #endif
1494 }
1495
1496
UngroupSegment(void)1497 int UngroupSegment( void )
1498 {
1499 if (Cursegp->group == current_group) {
1500
1501 Cursegp->group = -1;
1502 delete_segment_from_group(Cursegp, current_group);
1503
1504 Update_flags |= UF_WORLD_CHANGED;
1505 mine_changed = 1;
1506 diagnostic_message_fmt("Segment Ungrouped from Group %d.", current_group);
1507
1508 return 1;
1509 } else
1510 return 0;
1511 }
1512
GroupSegment(void)1513 int GroupSegment( void )
1514 {
1515 if (Cursegp->group == -1) {
1516
1517 Cursegp->group = current_group;
1518 add_segment_to_group(Cursegp, current_group);
1519
1520 Update_flags |= UF_WORLD_CHANGED;
1521 mine_changed = 1;
1522 diagnostic_message_fmt("Segment Added to Group %d.", current_group);
1523
1524 return 1;
1525 } else
1526 return 0;
1527 }
1528
Degroup(void)1529 int Degroup( void )
1530 {
1531 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1532 auto &Vertices = LevelSharedVertexState.get_vertices();
1533 int i;
1534
1535 // GroupList[current_group].num_segments = 0;
1536 // Groupsegp[current_group] = 0;
1537
1538 if (num_groups==0) return 0;
1539
1540 range_for (const auto &gs, GroupList[current_group].segments)
1541 delete_segment_from_group(vmsegptridx(gs), current_group);
1542
1543 // delete_segment_from_group( &Segments[GroupList[current_group].segments[i]]-Segments, current_group );
1544
1545 for (i=current_group;i<num_groups-1;i++)
1546 {
1547 GroupList[i] = GroupList[i+1];
1548 Groupsegp[i] = Groupsegp[i+1];
1549 }
1550
1551 num_groups--;
1552
1553 GroupList[num_groups].segments.clear();
1554 Groupsegp[num_groups] = 0;
1555
1556 if (current_group > num_groups-1) current_group--;
1557
1558 if (num_groups == 0)
1559 current_group = -1;
1560
1561 if (Lock_view_to_cursegp)
1562 {
1563 auto &vcvertptr = Vertices.vcptr;
1564 set_view_target_from_segment(vcvertptr, Cursegp);
1565 }
1566 Update_flags |= UF_WORLD_CHANGED;
1567 mine_changed = 1;
1568 diagnostic_message("Group UNgrouped.");
1569
1570 return 1;
1571 }
1572
NextGroup(void)1573 int NextGroup( void )
1574 {
1575
1576 if (num_groups > 0)
1577 {
1578 current_group++;
1579 if (current_group >= num_groups ) current_group = 0;
1580
1581 Update_flags |= UF_ED_STATE_CHANGED;
1582 mine_changed = 1;
1583 }
1584 else editor_status("No Next Group\n");
1585 return 0;
1586 }
1587
PrevGroup(void)1588 int PrevGroup( void )
1589 {
1590 if (num_groups > 0)
1591 {
1592 current_group--;
1593 if (current_group < 0 ) current_group = num_groups-1;
1594
1595 Update_flags |= UF_ED_STATE_CHANGED;
1596 mine_changed = 1;
1597 }
1598 else editor_status("No Previous Group\n");
1599 return 0;
1600 }
1601
1602
1603 // -----------------------------------------------------------------------------
MoveGroup(void)1604 int MoveGroup(void)
1605 {
1606 if (!Groupsegp[current_group]) {
1607 editor_status("Error -- Cannot move group, no group segment.");
1608 return 1;
1609 }
1610
1611 med_compress_mine();
1612
1613 if (!med_move_group(0, Cursegp, Curside, vmsegptridx(Groupsegp[current_group]), Groupside[current_group], vmd_identity_matrix, 0))
1614 {
1615 autosave_mine(mine_filename);
1616 Update_flags |= UF_WORLD_CHANGED;
1617 mine_changed = 1;
1618 diagnostic_message("Group moved.");
1619 return 0;
1620 } else
1621 return 1;
1622 }
1623
1624
1625 // -----------------------------------------------------------------------------
CopyGroup(void)1626 int CopyGroup(void)
1627 {
1628 segnum_t attach_seg;
1629
1630 if (!Groupsegp[current_group]) {
1631 editor_status("Error -- Cannot copy group, no group segment.");
1632 return 1;
1633 }
1634
1635 // See if the attach side in the group is attached to another segment.
1636 // If so, it must not be in the group for group copy to be legal.
1637 attach_seg = Groupsegp[current_group]->children[Groupside[current_group]];
1638 if (attach_seg != segment_none) {
1639 if (GroupList[current_group].segments.contains(attach_seg)) {
1640 editor_status_fmt("Error -- Cannot copy group, attach side has a child (segment %i) attached.", attach_seg);
1641 return 1;
1642 }
1643 }
1644
1645 med_compress_mine();
1646
1647 if (!med_copy_group(0, Cursegp, Curside, vcsegptr(Groupsegp[current_group]), Groupside[current_group], vmd_identity_matrix))
1648 {
1649 autosave_mine(mine_filename);
1650 Update_flags |= UF_WORLD_CHANGED;
1651 mine_changed = 1;
1652 diagnostic_message("Group copied.");
1653 return 0;
1654 } else
1655 return 1;
1656 }
1657
1658
1659 // -----------------------------------------------------------------------------
RotateGroup(void)1660 int RotateGroup(void)
1661 {
1662
1663 if (!Groupsegp[current_group]) {
1664 editor_status("Error -- Cannot rotate group, no group segment.");
1665 return 1;
1666 }
1667
1668 Group_orientation[current_group]++;
1669 if ((Group_orientation[current_group] <0) || (Group_orientation[current_group] >4))
1670 Group_orientation[current_group]=0;
1671
1672 med_compress_mine();
1673
1674 if (!med_move_group(0, Cursegp, Curside, vmsegptridx(Groupsegp[current_group]), Groupside[current_group],
1675 vmd_identity_matrix, Group_orientation[current_group]))
1676 {
1677 Update_flags |= UF_WORLD_CHANGED;
1678 mine_changed = 1;
1679 diagnostic_message("Group rotated.");
1680 return 0;
1681 }
1682 else
1683 return 1;
1684 }
1685
1686
1687 // -----------------------------------------------------------------------------
1688 // Creates a group from all segments connected to marked segment.
SubtractFromGroup(void)1689 int SubtractFromGroup(void)
1690 {
1691 int x, original_group;
1692 if (!Markedsegp) {
1693 editor_status("Error -- Cannot create group, no marked segment.");
1694 return 1;
1695 }
1696
1697 med_compress_mine();
1698 autosave_mine(mine_filename);
1699
1700 if (num_groups == MAX_GROUPS) {
1701 x = ui_messagebox( -2, -2, 2, "Warning: You are about to wipe out a group.", "ARGH! NO!", "No problemo." );
1702 if (x==1) return 0;
1703 }
1704
1705 if (current_group == -1) {
1706 editor_status("Error -- No current group. Cannot subtract.");
1707 return 1;
1708 }
1709
1710 original_group = current_group;
1711
1712 current_group = (current_group + 1) % MAX_GROUPS;
1713
1714 // Create a list of segments to copy.
1715 GroupList[current_group].segments.clear();
1716 create_group_list(Markedsegp, GroupList[current_group].segments, &Selected_segs);
1717
1718 // Now, scan the two groups, forming a group which consists of only those segments common to the two groups.
1719 auto intersects = [original_group](group::segment_array_type_t::const_reference r) -> bool {
1720 bool contains = GroupList[original_group].segments.contains(r);
1721 if (!contains)
1722 Segments[r].group = -1;
1723 return !contains;
1724 };
1725 GroupList[current_group].segments.erase_if(intersects);
1726
1727 // Replace Marked segment with Group Segment.
1728 Groupsegp[current_group] = Markedsegp;
1729 Groupside[current_group] = Markedside;
1730
1731 range_for (const auto &gs, GroupList[current_group].segments)
1732 Segments[gs].group = current_group;
1733
1734 Update_flags |= UF_WORLD_CHANGED;
1735 mine_changed = 1;
1736 diagnostic_message("Group created.");
1737
1738 return 1;
1739
1740 }
1741
1742 // -----------------------------------------------------------------------------
1743 // Creates a group from all segments already in CurrentGroup which can be reached from marked segment
1744 // without passing through current segment.
CreateGroup(void)1745 int CreateGroup(void)
1746 {
1747 int x;
1748
1749 if (!Markedsegp) {
1750 editor_status("Error -- Cannot create group, no marked segment.");
1751 return 1;
1752 }
1753
1754 med_compress_mine();
1755 autosave_mine(mine_filename);
1756
1757 if (num_groups == MAX_GROUPS) {
1758 x = ui_messagebox( -2, -2, 2, "Warning: You are about to wipe out a group.", "ARGH! NO!", "No problemo." );
1759 if (x==1)
1760 return 0; // Aborting at user's request.
1761 }
1762
1763 if (num_groups < MAX_GROUPS) {
1764 num_groups++;
1765 current_group = num_groups-1;
1766 } else
1767 current_group = 0;
1768
1769 // Create a list of segments to copy.
1770 GroupList[current_group].clear();
1771 create_group_list(Markedsegp, GroupList[current_group].segments, NULL);
1772
1773 // Replace Marked segment with Group Segment.
1774 Groupsegp[current_group] = Markedsegp;
1775 Groupside[current_group] = Markedside;
1776 // Markedsegp = 0;
1777 // Markedside = WBACK;
1778
1779 range_for (const auto &gs, GroupList[current_group].segments)
1780 Segments[gs].group = current_group;
1781
1782 Update_flags |= UF_WORLD_CHANGED;
1783 mine_changed = 1;
1784 diagnostic_message("Group created.");
1785
1786 return 1;
1787
1788 }
1789
1790 // -----------------------------------------------------------------------------
1791 // Deletes current group.
DeleteGroup(void)1792 int DeleteGroup( void )
1793 {
1794 auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state();
1795 auto &Vertices = LevelSharedVertexState.get_vertices();
1796 int i;
1797
1798 autosave_mine(mine_filename);
1799
1800 if (num_groups==0) return 0;
1801
1802 range_for (const auto &gs, GroupList[current_group].segments)
1803 {
1804 const auto &&segp = vmsegptridx(gs);
1805 segp->group = -1;
1806 med_delete_segment(segp);
1807 }
1808
1809 for (i=current_group;i<num_groups-1;i++) {
1810 GroupList[i] = GroupList[i+1];
1811 Groupsegp[i] = Groupsegp[i+1];
1812 }
1813
1814 num_groups--;
1815 GroupList[num_groups].clear();
1816 Groupsegp[num_groups] = 0;
1817
1818 if (current_group > num_groups-1) current_group--;
1819
1820 if (num_groups==0)
1821 current_group = -1;
1822
1823 undo_status[Autosave_count] = "Delete Group UNDONE.";
1824 if (Lock_view_to_cursegp)
1825 {
1826 auto &vcvertptr = Vertices.vcptr;
1827 set_view_target_from_segment(vcvertptr, Cursegp);
1828 }
1829
1830 Update_flags |= UF_WORLD_CHANGED;
1831 mine_changed = 1;
1832 diagnostic_message("Group deleted.");
1833 // warn_if_concave_segments(); // This could be faster -- just check if deleted segment was concave, warn accordingly
1834
1835 return 1;
1836
1837 }
1838
1839
MarkGroupSegment(void)1840 int MarkGroupSegment( void )
1841 {
1842 if ((Cursegp->group != -1) && (Cursegp->group == current_group))
1843 {
1844 autosave_mine(mine_filename);
1845 Groupsegp[current_group] = Cursegp;
1846 Groupside[current_group] = Curside;
1847 editor_status("Group Segment Marked.");
1848 Update_flags |= UF_ED_STATE_CHANGED;
1849 undo_status[Autosave_count] = "Mark Group Segment UNDONE.";
1850 mine_changed = 1;
1851 return 1;
1852 }
1853 else return 0;
1854 }
1855