Subversion Repositories Games.Descent

Rev

Blame | Last modification | View Log | Download | RSS feed

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