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