/* * Portions of this file are copyright Rebirth contributors and licensed as * described in COPYING.txt. * Portions of this file are copyright Parallax Software and licensed * according to the Parallax license below. * See COPYING.txt for license details. THE COMPUTER CODE CONTAINED HEREIN IS THE SOLE PROPERTY OF PARALLAX SOFTWARE CORPORATION ("PARALLAX"). PARALLAX, IN DISTRIBUTING THE CODE TO END-USERS, AND SUBJECT TO ALL OF THE TERMS AND CONDITIONS HEREIN, GRANTS A ROYALTY-FREE, PERPETUAL LICENSE TO SUCH END-USERS FOR USE BY SUCH END-USERS IN USING, DISPLAYING, AND CREATING DERIVATIVE WORKS THEREOF, SO LONG AS SUCH USE, DISPLAY OR CREATION IS FOR NON-COMMERCIAL, ROYALTY OR REVENUE FREE PURPOSES. IN NO EVENT SHALL THE END-USER USE THE COMPUTER CODE CONTAINED HEREIN FOR REVENUE-BEARING PURPOSES. THE END-USER UNDERSTANDS AND AGREES TO THE TERMS HEREIN AND ACCEPTS THE SAME BY USE OF THIS FILE. COPYRIGHT 1993-1998 PARALLAX SOFTWARE CORPORATION. ALL RIGHTS RESERVED. */ /* * * group functions * */ #include #include #include "gr.h" #include "ui.h" #include "inferno.h" #include "segment.h" #include "editor/editor.h" #include "editor/esegment.h" #include "editor/medmisc.h" #include "dxxerror.h" #include "physfsx.h" #include "gameseg.h" #include "bm.h" // For MAX_TEXTURES. #include "textures.h" #include "hash.h" #include "kdefs.h" #include "fwd-wall.h" #include "medwall.h" #include "compiler-range_for.h" #include "d_bitset.h" #include "d_enumerate.h" #include "d_levelstate.h" #include "d_zip.h" #include "partial_range.h" #include "segiter.h" namespace { static void validate_selected_segments(void); #if 0 struct group_top_fileinfo { int fileinfo_version; int fileinfo_sizeof; } group_top_fileinfo; // Should be same as first two fields below... struct group_fileinfo { int fileinfo_version; int fileinfo_sizeof; int header_offset; // Stuff common to game & editor int header_size; int editor_offset; // Editor specific stuff int editor_size; int vertex_offset; int vertex_howmany; int vertex_sizeof; int segment_offset; int segment_howmany; int segment_sizeof; int texture_offset; uint32_t texture_howmany; int texture_sizeof; } group_fileinfo; struct group_header { int num_vertices; int num_segments; } group_header; struct group_editor { int current_seg; int newsegment_offset; int newsegment_size; int Groupsegp; int Groupside; } group_editor; #endif } std::array GroupList; std::array Groupsegp; std::array Groupside; std::array Group_orientation; int current_group=-1; unsigned num_groups; // -- void swap_negate_columns(vms_matrix *rotmat, int col1, int col2) // -- { // -- fix col1_1,col1_2,col1_3; // -- fix col2_1,col2_2,col2_3; // -- // -- switch (col1) { // -- case 0: // -- col1_1 = rotmat->m1; // -- col1_2 = rotmat->m2; // -- col1_3 = rotmat->m3; // -- break; // -- // -- case 1: // -- col1_1 = rotmat->m4; // -- col1_2 = rotmat->m5; // -- col1_3 = rotmat->m6; // -- break; // -- // -- case 2: // -- col1_1 = rotmat->m7; // -- col1_2 = rotmat->m8; // -- col1_3 = rotmat->m9; // -- break; // -- } // -- // -- switch (col2) { // -- case 0: // -- col2_1 = rotmat->m1; // -- col2_2 = rotmat->m2; // -- col2_3 = rotmat->m3; // -- break; // -- // -- case 1: // -- col2_1 = rotmat->m4; // -- col2_2 = rotmat->m5; // -- col2_3 = rotmat->m6; // -- break; // -- // -- case 2: // -- col2_1 = rotmat->m7; // -- col2_2 = rotmat->m8; // -- col2_3 = rotmat->m9; // -- break; // -- } // -- // -- switch (col2) { // -- case 0: // -- rotmat->m1 = -col1_1; // -- rotmat->m2 = -col1_2; // -- rotmat->m3 = -col1_3; // -- break; // -- // -- case 1: // -- rotmat->m4 = -col1_1; // -- rotmat->m5 = -col1_2; // -- rotmat->m6 = -col1_3; // -- break; // -- // -- case 2: // -- rotmat->m7 = -col1_1; // -- rotmat->m8 = -col1_2; // -- rotmat->m9 = -col1_3; // -- break; // -- } // -- // -- switch (col1) { // -- case 0: // -- rotmat->m1 = -col2_1; // -- rotmat->m2 = -col2_2; // -- rotmat->m3 = -col2_3; // -- break; // -- // -- case 1: // -- rotmat->m4 = -col2_1; // -- rotmat->m5 = -col2_2; // -- rotmat->m6 = -col2_3; // -- break; // -- // -- case 2: // -- rotmat->m7 = -col2_1; // -- rotmat->m8 = -col2_2; // -- rotmat->m9 = -col2_3; // -- break; // -- } // -- // -- } // -- // -- void swap_negate_rows(vms_matrix *rotmat, int row1, int row2) // -- { // -- fix row1_1,row1_2,row1_3; // -- fix row2_1,row2_2,row2_3; // -- // -- switch (row1) { // -- case 0: // -- row1_1 = rotmat->m1; // -- row1_2 = rotmat->m4; // -- row1_3 = rotmat->m7; // -- break; // -- // -- case 1: // -- row1_1 = rotmat->m2; // -- row1_2 = rotmat->m5; // -- row1_3 = rotmat->m8; // -- break; // -- // -- case 2: // -- row1_1 = rotmat->m3; // -- row1_2 = rotmat->m6; // -- row1_3 = rotmat->m9; // -- break; // -- } // -- // -- switch (row2) { // -- case 0: // -- row2_1 = rotmat->m1; // -- row2_2 = rotmat->m4; // -- row2_3 = rotmat->m7; // -- break; // -- // -- case 1: // -- row2_1 = rotmat->m2; // -- row2_2 = rotmat->m5; // -- row2_3 = rotmat->m8; // -- break; // -- // -- case 2: // -- row2_1 = rotmat->m3; // -- row2_2 = rotmat->m6; // -- row2_3 = rotmat->m9; // -- break; // -- } // -- // -- switch (row2) { // -- case 0: // -- rotmat->m1 = -row1_1; // -- rotmat->m4 = -row1_2; // -- rotmat->m7 = -row1_3; // -- break; // -- // -- case 1: // -- rotmat->m2 = -row1_1; // -- rotmat->m5 = -row1_2; // -- rotmat->m8 = -row1_3; // -- break; // -- // -- case 2: // -- rotmat->m3 = -row1_1; // -- rotmat->m6 = -row1_2; // -- rotmat->m9 = -row1_3; // -- break; // -- } // -- // -- switch (row1) { // -- case 0: // -- rotmat->m1 = -row2_1; // -- rotmat->m4 = -row2_2; // -- rotmat->m7 = -row2_3; // -- break; // -- // -- case 1: // -- rotmat->m2 = -row2_1; // -- rotmat->m5 = -row2_2; // -- rotmat->m8 = -row2_3; // -- break; // -- // -- case 2: // -- rotmat->m3 = -row2_1; // -- rotmat->m6 = -row2_2; // -- rotmat->m9 = -row2_3; // -- break; // -- } // -- // -- } // -- // -- // ------------------------------------------------------------------------------------------------ // -- void side_based_matrix(vms_matrix *rotmat,int destside) // -- { // -- vms_angvec rotvec; // -- vms_matrix r1,rtemp; // -- // -- switch (destside) { // -- case WLEFT: // -- // swap_negate_columns(rotmat,1,2); // -- // swap_negate_rows(rotmat,1,2); // -- break; // -- // -- case WTOP: // -- break; // -- // -- case WRIGHT: // -- // swap_negate_columns(rotmat,1,2); // -- // swap_negate_rows(rotmat,1,2); // -- break; // -- // -- case WBOTTOM: // -- break; // -- // -- case WFRONT: // -- break; // -- // -- case WBACK: // -- break; // -- } // -- // -- } namespace { // ------------------------------------------------------------------------------------------------ // Rotate a group about a point. // The segments in the group are indicated (by segment number) in group_seglist. There are group_size segments. // The point about which the groups is rotated is the center of first_seg:first_side. // delta_flag: // 0 absolute rotation, destination specified in terms of base_seg:base_side, used in moving or copying a group // 1 relative rotation, destination specified relative to current orientation of first_seg:first_side // Note: The group must exist in the mine, consisting of actual points in the world. If any points in the // segments in the group are shared by segments not in the group, those points will get rotated and the // segments not in the group will have their shapes modified. // Return value: // 0 group rotated // 1 unable to rotate group static void med_create_group_rotation_matrix(vms_matrix &result_mat, const unsigned delta_flag, const shared_segment &first_seg, const sidenum_t first_side, const shared_segment &base_seg, const sidenum_t base_side, const vms_matrix &orient_matrix, const int orientation) { vms_matrix rotmat2,rotmat,rotmat3,rotmat4; vms_angvec pbh = {0,0,0}; // Determine whether this rotation is a delta rotation, meaning to just rotate in place, or an absolute rotation, // which means that the destination rotation is specified, not as a delta, but as an absolute if (delta_flag) { // Create rotation matrix describing rotation. med_extract_matrix_from_segment(first_seg, rotmat4); // get rotation matrix describing current orientation of first seg update_matrix_based_on_side(rotmat4, first_side); rotmat3 = vm_transposed_matrix(orient_matrix); const auto vm_desired_orientation = vm_matrix_x_matrix(rotmat4,rotmat3); // this is the desired orientation of the new segment vm_transpose_matrix(rotmat4); vm_matrix_x_matrix(rotmat2,vm_desired_orientation,rotmat4); // this is the desired orientation of the new segment } else { // Create rotation matrix describing rotation. med_extract_matrix_from_segment(base_seg, rotmat); // get rotation matrix describing desired orientation update_matrix_based_on_side(rotmat, base_side); // modify rotation matrix for desired side // If the new segment is to be attached without rotation, then its orientation is the same as the base_segment vm_matrix_x_matrix(rotmat4,rotmat,orient_matrix); // this is the desired orientation of the new segment pbh.b = orientation*16384; vm_angles_2_matrix(rotmat3,pbh); rotmat4 = rotmat = vm_matrix_x_matrix(rotmat4, rotmat3); med_extract_matrix_from_segment(first_seg, rotmat3); // get rotation matrix describing current orientation of first seg // It is curious that the following statement has no analogue in the med_attach_segment_rotated code. // Perhaps it is because segments are always attached at their front side. If the back side is the side // passed to the function, then the matrix is not modified, which might suggest that what you need to do below // is use Side_opposite[first_side]. update_matrix_based_on_side(rotmat3, Side_opposite[first_side]); // modify rotation matrix for desired side vm_transpose_matrix(rotmat3); // get the inverse of the current orientation matrix rotmat2 = vm_transposed_matrix(vm_matrix_x_matrix(rotmat,rotmat3)); // now rotmat2 takes the current segment to the desired orientation } result_mat = rotmat2; } static inline vms_matrix med_create_group_rotation_matrix(const unsigned delta_flag, const shared_segment &first_seg, const sidenum_t first_side, const shared_segment &base_seg, const sidenum_t base_side, const vms_matrix &orient_matrix, const int orientation) { vms_matrix result_mat; return med_create_group_rotation_matrix(result_mat, delta_flag, first_seg, first_side, base_seg, base_side, orient_matrix, orientation), result_mat; } // ----------------------------------------------------------------------------------------- // Rotate all vertices and objects in group. static void med_rotate_group(const vms_matrix &rotmat, group::segment_array_type_t &group_seglist, const shared_segment &first_seg, const sidenum_t first_side) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Objects = LevelUniqueObjectState.Objects; auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vmobjptridx = Objects.vmptridx; auto &vcvertptr = Vertices.vcptr; auto &vmvertptridx = Vertices.vmptridx; const auto &&rotate_center = compute_center_point_on_side(vcvertptr, first_seg, first_side); // Create list of points to rotate. enumerated_bitset vertex_list{}; range_for (const auto &gs, group_seglist) { auto &sp = *vmsegptr(gs); range_for (const auto v, sp.verts) vertex_list[v] = true; // Rotate center of all objects in group. range_for (const auto objp, objects_in(sp, vmobjptridx, vcsegptr)) { const auto tv1 = vm_vec_sub(objp->pos,rotate_center); const auto tv = vm_vec_rotate(tv1,rotmat); vm_vec_add(objp->pos, tv, rotate_center); } } // Do the pre-rotation xlate, do the rotation, do the post-rotation xlate range_for (auto &&v, vmvertptridx) if (vertex_list[v]) { const auto &&tv1 = vm_vec_sub(*v, rotate_center); const auto tv = vm_vec_rotate(tv1,rotmat); vm_vec_add(*v, tv, rotate_center); } } // ------------------------------------------------------------------------------------------------ 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) { if (ignore_list) if (ignore_list->contains(segp)) return; if (auto &&v = visited[segp]; !v) { v = true; seglistp.emplace_back(segp); for (const auto c : segp->shared_segment::children) if (IS_CHILD(c)) cgl_aux(segp.absolute_sibling(c), seglistp, ignore_list, visited); } } // ------------------------------------------------------------------------------------------------ // Sets Been_visited[n] if n is reachable from segp static void create_group_list(const vmsegptridx_t segp, group::segment_array_type_t &seglistp, selected_segment_array_t *ignore_list) { visited_segment_bitarray_t visited; cgl_aux(segp, seglistp, ignore_list, visited); } #define MXS MAX_SEGMENTS #define MXV MAX_VERTICES // ------------------------------------------------------------------------------------------------ static void duplicate_group(enumerated_array &vertex_ids, group::segment_array_type_t &segments) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Objects = LevelUniqueObjectState.Objects; auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vmobjptridx = Objects.vmptridx; group::segment_array_type_t new_segments; enumerated_array new_vertex_ids; // If new_vertex_ids[v] != -1, then vertex v has been remapped to new_vertex_ids[v] constexpr vertnum_t undefined_vertex_id{UINT32_MAX}; // duplicate vertices new_vertex_ids.fill(undefined_vertex_id); // duplicate vertices auto &vcvertptridx = Vertices.vcptridx; range_for (auto &&v, vcvertptridx) { if (const vertnum_t vn{v}; vertex_ids[vn]) new_vertex_ids[vn] = med_create_duplicate_vertex(*v); } // duplicate segments range_for(const auto &gs, segments) { const auto &&segp = vmsegptr(gs); const auto &&new_segment_id = med_create_duplicate_segment(Segments, segp); new_segments.emplace_back(new_segment_id); range_for (const auto objp, objects_in(segp, vmobjptridx, vmsegptr)) { if (objp->type != OBJ_PLAYER) { const auto &&new_obj_id = obj_create_copy(objp, vmsegptridx(new_segment_id)); (void)new_obj_id; // FIXME! } } } // Now, for each segment in segment_ids, correct its children numbers by translating through new_segment_ids // and correct its vertex numbers by translating through new_vertex_ids range_for(const auto &gs, new_segments) { shared_segment &sp = vmsegptr(gs); range_for (auto &seg, sp.children) { if (IS_CHILD(seg)) { group::segment_array_type_t::iterator inew = new_segments.begin(); range_for (const auto i, segments) { if (seg == i) { seg = *inew; break; } ++inew; } } } // end for (sidenum=0... // Now fixup vertex ids range_for (auto &v, sp.verts) { if (const vertnum_t vn{v}; vertex_ids[vn]) v = new_vertex_ids[vn]; } } // end for (s=0... // Now, copy new_segment_ids into segment_ids segments = new_segments; // Now, copy new_vertex_ids into vertex_ids vertex_ids = {}; range_for (auto &v, new_vertex_ids) if (v != undefined_vertex_id) vertex_ids[v] = 1; } // ------------------------------------------------------------------------------------------------ static int in_group(segnum_t segnum, int group_num) { range_for(const auto& s, GroupList[group_num].segments) if (segnum == s) return 1; return 0; } // ------------------------------------------------------------------------------------------------ // Copy a group of segments. // The group is defined as all segments accessible from group_seg. // The group is copied so group_seg:group_side is incident upon base_seg:base_side. // group_seg and its vertices are bashed to coincide with base_seg. // If any vertex of base_seg is contained in a segment that is reachable from group_seg, then errror. static int med_copy_group(const unsigned delta_flag, const vmsegptridx_t base_seg, const sidenum_t base_side, vcsegptr_t group_seg, const sidenum_t group_side, const vms_matrix &orient_matrix) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Objects = LevelUniqueObjectState.Objects; auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vmobjptridx = Objects.vmptridx; int x; int new_current_group; if (IS_CHILD(base_seg->shared_segment::children[base_side])) { editor_status("Error -- unable to copy group, base_seg:base_side must be free."); return 1; } if (num_groups == MAX_GROUPS) { x = ui_messagebox( -2, -2, 2, "Warning: You have reached the MAXIMUM group number limit. Continue?", "No", "Yes" ); if (x==1) return 0; } if (num_groups < MAX_GROUPS) { num_groups++; new_current_group = num_groups-1; } else new_current_group = 0; Assert(current_group >= 0); // Find groupsegp index auto gb = GroupList[current_group].segments.begin(); auto ge = GroupList[current_group].segments.end(); auto gp = Groupsegp[current_group]; const auto &&gi = ranges::find_if(gb, ge, [gp](const segnum_t segnum) { return vcsegptr(segnum) == gp; }); int gs_index = (gi == ge) ? 0 : std::distance(gb, gi); GroupList[new_current_group] = GroupList[current_group]; // Make a list of all vertices in group. enumerated_array in_vertex_list{}; if (group_seg == &New_segment) range_for (auto &v, group_seg->verts) in_vertex_list[v] = 1; else { range_for(const auto &gs, GroupList[new_current_group].segments) range_for (auto &v, vmsegptr(gs)->verts) in_vertex_list[v] = 1; } // Given a list of vertex indices (indicated by !0 in in_vertex_list) and segment indices (in list GroupList[current_group].segments, there // are GroupList[current_group].num_segments segments), copy all segments and vertices // Return updated lists of vertices and segments in in_vertex_list and GroupList[current_group].segments duplicate_group(in_vertex_list, GroupList[new_current_group].segments); //group_seg = &Segments[GroupList[new_current_group].segments[0]]; // connecting segment in group has been changed, so update group_seg { const auto &&gs = vmsegptr(GroupList[new_current_group].segments[gs_index]); group_seg = gs; Groupsegp[new_current_group] = gs; } Groupside[new_current_group] = Groupside[current_group]; range_for(const auto &gs, GroupList[new_current_group].segments) { shared_segment &s = *vmsegptr(gs); s.group = new_current_group; s.special = segment_special::nothing; s.matcen_num = materialization_center_number::None; } auto &vcvertptr = Vertices.vcptr; // Breaking connections between segments in the current group and segments not in the group. range_for(const auto &gs, GroupList[new_current_group].segments) { const auto &&segp = base_seg.absolute_sibling(gs); for (auto &&[sidenum, child_segnum] : enumerate(segp->shared_segment::children)) if (IS_CHILD(child_segnum)) { if (!in_group(child_segnum, new_current_group)) { child_segnum = segment_none; validate_segment_side(vcvertptr, segp, sidenum); // we have converted a connection to a side so validate the segment } } } copy_uvs_seg_to_seg(vmsegptr(&New_segment), group_seg); // Now do the copy // First, xlate all vertices so center of group_seg:group_side is at origin const auto &&srcv = compute_center_point_on_side(vcvertptr, group_seg, group_side); auto &vmvertptridx = Vertices.vmptridx; range_for (auto &&v, vmvertptridx) if (in_vertex_list[v]) vm_vec_sub2(*v, srcv); // Now, translate all object positions. range_for(const auto &segnum, GroupList[new_current_group].segments) { range_for (const auto objp, objects_in(vmsegptr(segnum), vmobjptridx, vmsegptr)) vm_vec_sub2(objp->pos, srcv); } // Now, rotate segments in group so orientation of group_seg is same as base_seg. const auto rotmat = med_create_group_rotation_matrix(delta_flag, group_seg, group_side, base_seg, base_side, orient_matrix, 0); med_rotate_group(rotmat, GroupList[new_current_group].segments, group_seg, group_side); // Now xlate all vertices so group_seg:group_side shares center point with base_seg:base_side const auto &&destv = compute_center_point_on_side(vcvertptr, base_seg, base_side); range_for (auto &&v, vmvertptridx) if (in_vertex_list[v]) vm_vec_add2(*v, destv); // Now, xlate all object positions. range_for(const auto &segnum, GroupList[new_current_group].segments) { range_for (const auto objp, objects_in(vmsegptr(segnum), vmobjptridx, vmsegptr)) vm_vec_add2(objp->pos, destv); } // Now, copy all walls (ie, doors, illusionary, etc.) into the new group. copy_group_walls(current_group, new_current_group); current_group = new_current_group; // Now, form joint on connecting sides. med_form_joint(base_seg,base_side,vmsegptridx(Groupsegp[current_group]),Groupside[new_current_group]); validate_selected_segments(); med_combine_duplicate_vertices(in_vertex_list); return 0; } // ------------------------------------------------------------------------------------------------ // Move a group of segments. // The group is defined as all segments accessible from group_seg. // The group is moved so group_seg:group_side is incident upon base_seg:base_side. // group_seg and its vertices are bashed to coincide with base_seg. // If any vertex of base_seg is contained in a segment that is reachable from group_seg, then errror. static int med_move_group(int delta_flag, const vmsegptridx_t base_seg, const sidenum_t base_side, const vmsegptridx_t group_seg, const sidenum_t group_side, const vms_matrix &orient_matrix, int orientation) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Objects = LevelUniqueObjectState.Objects; auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vmobjptridx = Objects.vmptridx; if (IS_CHILD(base_seg->children[base_side])) if (base_seg->children[base_side] != group_seg) { editor_status("Error -- unable to move group, base_seg:base_side must be free or point to group_seg."); return 1; } // // See if any vertices in base_seg are contained in any vertex in group_list // for (v=0; vverts[v]) { // editor_status("Error -- unable to move group, it shares a vertex with destination segment."); // return 1; // } enumerated_array in_vertex_list{}; enumerated_array out_vertex_list{}; // Make a list of all vertices in group. range_for(const auto &gs, GroupList[current_group].segments) range_for (auto &v, vmsegptr(gs)->verts) in_vertex_list[v] = 1; // For all segments which are not in GroupList[current_group].segments, mark all their vertices in the out list. range_for (const auto &&segp, vmsegptridx) { if (!GroupList[current_group].segments.contains(segp)) { range_for (auto &v, segp->verts) out_vertex_list[v] = 1; } } // Now, for all vertices present in both the in (part of group segment) and out (part of non-group segment) // create an extra copy of the vertex so we can just move the ones in the in list. // Can't use Highest_vertex_index as loop termination because it gets increased by med_create_duplicate_vertex. auto &vcvertptr = Vertices.vcptr; auto &vmvertptridx = Vertices.vmptridx; range_for (auto &&v, vmvertptridx) if (in_vertex_list[v]) if (out_vertex_list[v]) { const auto new_vertex_id = med_create_duplicate_vertex(*v); in_vertex_list[v] = 0; in_vertex_list[new_vertex_id] = 1; // Create a new vertex and assign all occurrences of vertex v in IN list to new vertex number. range_for(const auto &gs, GroupList[current_group].segments) { auto &sp = *vmsegptr(gs); range_for (auto &vv, sp.verts) if (vv == v) vv = new_vertex_id; } } range_for(const auto &gs, GroupList[current_group].segments) vmsegptr(gs)->group = current_group; // Breaking connections between segments in the group and segments not in the group. range_for(const auto &gs, GroupList[current_group].segments) { const auto &&segp = base_seg.absolute_sibling(gs); for (const auto &&[idx0, value0] : enumerate(segp->children)) if (IS_CHILD(value0)) { const auto &&csegp = base_seg.absolute_sibling(value0); if (csegp->group != current_group) { for (const auto &&[idx1, value1] : enumerate(csegp->children)) if (IS_CHILD(value1)) { auto &dsegp = *vmsegptr(value1); if (dsegp.group == current_group) { value1 = segment_none; validate_segment_side(vcvertptr, csegp, static_cast(idx1)); // we have converted a connection to a side so validate the segment } } value0 = segment_none; validate_segment_side(vcvertptr, segp, static_cast(idx0)); // we have converted a connection to a side so validate the segment } } } copy_uvs_seg_to_seg(vmsegptr(&New_segment), vcsegptr(Groupsegp[current_group])); // Now do the move // First, xlate all vertices so center of group_seg:group_side is at origin const auto &&srcv = compute_center_point_on_side(vcvertptr, group_seg, group_side); range_for (auto &&v, vmvertptridx) if (in_vertex_list[v]) vm_vec_sub2(*v, srcv); // Now, move all object positions. range_for(const auto &segnum, GroupList[current_group].segments) { range_for (const auto objp, objects_in(vmsegptr(segnum), vmobjptridx, vmsegptr)) vm_vec_sub2(objp->pos, srcv); } // Now, rotate segments in group so orientation of group_seg is same as base_seg. const auto rotmat = med_create_group_rotation_matrix(delta_flag, group_seg, group_side, base_seg, base_side, orient_matrix, orientation); med_rotate_group(rotmat, GroupList[current_group].segments, group_seg, group_side); // Now xlate all vertices so group_seg:group_side shares center point with base_seg:base_side const auto &&destv = compute_center_point_on_side(vcvertptr, base_seg, base_side); range_for (auto &&v, vmvertptridx) if (in_vertex_list[v]) vm_vec_add2(*v, destv); // Now, rotate all object positions. range_for(const auto &segnum, GroupList[current_group].segments) { range_for (const auto objp, objects_in(vmsegptr(segnum), vmobjptridx, vmsegptr)) vm_vec_add2(objp->pos, destv); } // Now, form joint on connecting sides. med_form_joint(base_seg,base_side,group_seg,group_side); validate_selected_segments(); med_combine_duplicate_vertices(in_vertex_list); return 0; } // ----------------------------------------------------------------------------- static segnum_t place_new_segment_in_world(void) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Vertices = LevelSharedVertexState.get_vertices(); const auto &&segnum = Segments.vmptridx(get_free_segment_number(Segments)); auto &seg = *segnum; seg = New_segment; auto &vcvertptr = Vertices.vcptr; for (auto &&[w, r] : zip(seg.verts, New_segment.verts)) w = med_create_duplicate_vertex(vcvertptr(r)); return segnum; } // ----------------------------------------------------------------------------- // Attach segment in the new-fangled way, which is by using the CopyGroup code. static int AttachSegmentNewAng(const vms_angvec &pbh) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Vertices = LevelSharedVertexState.get_vertices(); GroupList[current_group].segments.clear(); const auto newseg = place_new_segment_in_world(); GroupList[current_group].segments.emplace_back(newseg); const auto &&nsegp = vmsegptridx(newseg); if (!med_move_group(1, Cursegp, Curside, nsegp, AttachSide, vm_angles_2_matrix(pbh),0)) { autosave_mine(mine_filename); med_propagate_tmaps_to_segments(Cursegp,nsegp,0); med_propagate_tmaps_to_back_side(nsegp, Side_opposite[AttachSide],0); copy_uvs_seg_to_seg(vmsegptr(&New_segment),nsegp); Cursegp = nsegp; Curside = Side_opposite[AttachSide]; med_create_new_segment_from_cursegp(); if (Lock_view_to_cursegp) { auto &vcvertptr = Vertices.vcptr; set_view_target_from_segment(vcvertptr, Cursegp); } Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; warn_if_concave_segment(Cursegp); } return 1; } } int AttachSegmentNew(void) { vms_angvec pbh; pbh.p = 0; pbh.b = 0; pbh.h = 0; AttachSegmentNewAng(pbh); return 1; } namespace { // ----------------------------------------------------------------------------- void validate_selected_segments(void) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Vertices = LevelSharedVertexState.get_vertices(); auto &vcvertptr = Vertices.vcptr; range_for (const auto &gs, GroupList[current_group].segments) validate_segment(vcvertptr, vmsegptridx(gs)); } } // ===================================================================================== // ----------------------------------------------------------------------------- namespace dsx { void delete_segment_from_group(const vmsegptridx_t segment_num, unsigned group_num) { segment_num->group = -1; GroupList[group_num].segments.erase(segment_num); } } // ===================================================================================== // ----------------------------------------------------------------------------- void add_segment_to_group(segnum_t segment_num, int group_num) { GroupList[group_num].segments.emplace_back(segment_num); } // ===================================================================================== // ----------------------------------------------------------------------------- int rotate_segment_new(const vms_angvec &pbh) { vms_matrix tm1; group::segment_array_type_t selected_segs_save; int current_group_save; if (!IS_CHILD(Cursegp->children[Side_opposite[Curside]])) // -- I don't understand this, MK, 01/25/94: if (Cursegp->children[Curside] != group_seg-Segments) { editor_status("Error -- unable to rotate group, Cursegp:Side_opposite[Curside] cannot be free."); return 1; } current_group_save = current_group; current_group = ROT_GROUP; Groupsegp[ROT_GROUP] = Cursegp; selected_segs_save = GroupList[current_group].segments; GroupList[ROT_GROUP].segments.clear(); const auto newseg = Cursegp; const auto newseg_side = Side_opposite[Curside]; // Create list of segments to rotate. // Sever connection between first seg to rotate and its connection on Side_opposite[Curside]. const auto child_save = Cursegp->children[newseg_side]; // save connection we are about to sever Cursegp->children[newseg_side] = segment_none; // sever connection create_group_list(Cursegp, GroupList[ROT_GROUP].segments, NULL); // create list of segments in group Cursegp->children[newseg_side] = child_save; // restore severed connection GroupList[ROT_GROUP].segments.emplace_back(newseg); const auto baseseg = newseg->children[newseg_side]; if (!IS_CHILD(baseseg)) { editor_status("Error -- unable to rotate segment, side opposite curside is not attached."); GroupList[current_group].segments = selected_segs_save; current_group = current_group_save; return 1; } const auto &&basesegp = vmsegptridx(baseseg); const auto &&baseseg_side = find_connect_side(newseg, basesegp); med_extract_matrix_from_segment(newseg, tm1); tm1 = vmd_identity_matrix; const auto tm2 = vm_angles_2_matrix(pbh); const auto orient_matrix = vm_matrix_x_matrix(tm1,tm2); basesegp->children[baseseg_side] = segment_none; newseg->children[newseg_side] = segment_none; if (!med_move_group(1, basesegp, baseseg_side, newseg, newseg_side, orient_matrix, 0)) { Cursegp = newseg; med_create_new_segment_from_cursegp(); // validate_selected_segments(); med_propagate_tmaps_to_segments(basesegp, newseg, 1); med_propagate_tmaps_to_back_side(newseg, Curside, 1); } GroupList[current_group].segments = selected_segs_save; current_group = current_group_save; return 1; } // ----------------------------------------------------------------------------- // Attach segment in the new-fangled way, which is by using the CopyGroup code. int RotateSegmentNew(vms_angvec *pbh) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Vertices = LevelSharedVertexState.get_vertices(); int rval; autosave_mine(mine_filename); rval = rotate_segment_new(*pbh); if (Lock_view_to_cursegp) { auto &vcvertptr = Vertices.vcptr; set_view_target_from_segment(vcvertptr, Cursegp); } Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; warn_if_concave_segment(Cursegp); return rval; } #if 0 static std::array current_tmap_list; // ----------------------------------------------------------------------------- // Save mine will: // 1. Write file info, header info, editor info, vertex data, segment data, // and new_segment in that order, marking their file offset. // 2. Go through all the fields and fill in the offset, size, and sizeof // values in the headers. static int med_save_group( const char *filename, const group::vertex_array_type_t &vertex_ids, const group::segment_array_type_t &segment_ids) { int header_offset, editor_offset, vertex_offset, segment_offset, texture_offset; char ErrorMessage[100]; int j; auto SaveFile = PHYSFSX_openWriteBuffered(filename); if (!SaveFile) { snprintf(ErrorMessage, sizeof(ErrorMessage), "ERROR: Unable to open %s\n", filename); ui_messagebox( -2, -2, 1, ErrorMessage, "Ok" ); return 1; } //===================== SAVE FILE INFO ======================== group_fileinfo.fileinfo_version = MINE_VERSION; group_fileinfo.fileinfo_sizeof = sizeof(group_fileinfo); group_fileinfo.header_offset = -1; group_fileinfo.header_size = sizeof(group_header); group_fileinfo.editor_offset = -1; group_fileinfo.editor_size = sizeof(group_editor); group_fileinfo.vertex_offset = -1; group_fileinfo.vertex_howmany = vertex_ids.size(); group_fileinfo.vertex_sizeof = sizeof(vms_vector); group_fileinfo.segment_offset = -1; group_fileinfo.segment_howmany = segment_ids.size(); group_fileinfo.segment_sizeof = sizeof(segment); group_fileinfo.texture_offset = -1; group_fileinfo.texture_howmany = 0; group_fileinfo.texture_sizeof = 13; // num characters in a name // Write the fileinfo PHYSFS_write( SaveFile, &group_fileinfo, sizeof(group_fileinfo), 1); //===================== SAVE HEADER INFO ======================== group_header.num_vertices = vertex_ids.size(); group_header.num_segments = segment_ids.size(); // Write the editor info header_offset = PHYSFS_tell(SaveFile); PHYSFS_write( SaveFile, &group_header, sizeof(group_header), 1); //===================== SAVE EDITOR INFO ========================== group_editor.newsegment_offset = -1; // To be written group_editor.newsegment_size = sizeof(segment); // Next 3 vars added 10/07 by JAS group_editor.Groupsegp = 0; if (Groupsegp[current_group]) { const auto i = segment_ids.find(vmsegptridx(Groupsegp[current_group])); if (i != segment_ids.end()) group_editor.Groupsegp = std::distance(segment_ids.begin(), i); } group_editor.Groupside = Groupside[current_group]; editor_offset = PHYSFS_tell(SaveFile); PHYSFS_write( SaveFile, &group_editor, sizeof(group_editor), 1); //===================== SAVE VERTEX INFO ========================== vertex_offset = PHYSFS_tell(SaveFile); range_for (const auto &gv, vertex_ids) { const vertex tvert = *vcvertptr(gv); PHYSFS_write(SaveFile, &tvert, sizeof(tvert), 1); } //===================== SAVE SEGMENT INFO ========================= segment_offset = PHYSFS_tell(SaveFile); range_for (const auto &gs, segment_ids) { auto &&tseg = *vmsegptr(gs); for (j=0;j<6;j++) { group::segment_array_type_t::const_iterator i = segment_ids.find(tseg.children[j]); tseg.children[j] = (i == segment_ids.end()) ? segment_none : std::distance(segment_ids.begin(), i); } for (j=0;j<8;j++) { group::vertex_array_type_t::const_iterator i = vertex_ids.find(tseg.verts[j]); if (i != vertex_ids.end()) tseg.verts[j] = std::distance(vertex_ids.begin(), i); } PHYSFS_write( SaveFile, &tseg, sizeof(tseg), 1); } //===================== SAVE TEXTURE INFO ========================== texture_offset = PHYSFS_tell(SaveFile); for (unsigned i = 0, n = NumTextures; i < n; ++i) { current_tmap_list[i] = TmapInfo[i].filename; PHYSFS_write(SaveFile, current_tmap_list[i].data(), current_tmap_list[i].size(), 1); } //============= REWRITE FILE INFO, TO SAVE OFFSETS =============== // Update the offset fields group_fileinfo.header_offset = header_offset; group_fileinfo.editor_offset = editor_offset; group_fileinfo.vertex_offset = vertex_offset; group_fileinfo.segment_offset = segment_offset; group_fileinfo.texture_offset = texture_offset; // Write the fileinfo PHYSFSX_fseek( SaveFile, 0, SEEK_SET ); // Move to TOF PHYSFS_write( SaveFile, &group_fileinfo, sizeof(group_fileinfo), 1); //==================== CLOSE THE FILE ============================= return 0; } static std::array old_tmap_list; // static short tmap_xlate_table[MAX_TEXTURES]; // ZICO - FIXME // ----------------------------------------------------------------------------- // Load group will: //int med_load_group(char * filename) static int med_load_group( const char *filename, group::vertex_array_type_t &vertex_ids, group::segment_array_type_t &segment_ids) { int vertnum; char ErrorMessage[200]; int translate=0; char *temptr; segment tseg; auto &&[LoadFile, physfserr] = PHYSFSX_openReadBuffered(filename); if (!LoadFile) { snprintf(ErrorMessage, sizeof(ErrorMessage), "ERROR: Failed to open %s\n%s\n", filename, PHYSFS_getErrorByCode(physfserr)); ui_messagebox( -2, -2, 1, ErrorMessage, "Ok" ); return 1; } //===================== READ FILE INFO ======================== // These are the default values... version and fileinfo_sizeof // don't have defaults. group_fileinfo.header_offset = -1; group_fileinfo.header_size = sizeof(group_header); group_fileinfo.editor_offset = -1; group_fileinfo.editor_size = sizeof(group_editor); group_fileinfo.vertex_offset = -1; group_fileinfo.vertex_howmany = 0; group_fileinfo.vertex_sizeof = sizeof(vms_vector); group_fileinfo.segment_offset = -1; group_fileinfo.segment_howmany = 0; group_fileinfo.segment_sizeof = sizeof(segment); group_fileinfo.texture_offset = -1; group_fileinfo.texture_howmany = 0; group_fileinfo.texture_sizeof = 13; // num characters in a name // Read in group_top_fileinfo to get size of saved fileinfo. if (PHYSFSX_fseek( LoadFile, 0, SEEK_SET )) Error( "Error seeking to 0 in group.c" ); if (PHYSFS_read( LoadFile, &group_top_fileinfo, sizeof(group_top_fileinfo),1 )!=1) Error( "Error reading top_fileinfo in group.c" ); // Check version number if (group_top_fileinfo.fileinfo_version < COMPATIBLE_VERSION ) { snprintf(ErrorMessage, sizeof(ErrorMessage), "You are trying to load %s\n" \ "a version %d group, which is known to be incompatible\n" \ "with the current expected version %d groups.", \ filename, group_top_fileinfo.fileinfo_version, MINE_VERSION ); if (ui_messagebox( -2, -2, 2, ErrorMessage, "Forget it", "Try anyway" )==1) { return 1; } ui_messagebox( -2, -2, 1, "Good luck!", "I need it" ); } // Now, Read in the fileinfo if (PHYSFSX_fseek( LoadFile, 0, SEEK_SET )) Error( "Error seeking to 0b in group.c" ); if (PHYSFS_read( LoadFile, &group_fileinfo, group_top_fileinfo.fileinfo_sizeof,1 )!=1) Error( "Error reading group_fileinfo in group.c" ); //===================== READ HEADER INFO ======================== // Set default values. group_header.num_vertices = 0; group_header.num_segments = 0; if (group_fileinfo.header_offset > -1 ) { if (PHYSFSX_fseek( LoadFile,group_fileinfo.header_offset, SEEK_SET )) Error( "Error seeking to header_offset in group.c" ); if (PHYSFS_read( LoadFile, &group_header, group_fileinfo.header_size,1 )!=1) Error( "Error reading group_header in group.c" ); } //===================== READ EDITOR INFO ========================== // Set default values group_editor.current_seg = 0; group_editor.newsegment_offset = -1; // To be written group_editor.newsegment_size = sizeof(segment); group_editor.Groupsegp = -1; group_editor.Groupside = 0; if (group_fileinfo.editor_offset > -1 ) { if (PHYSFSX_fseek( LoadFile,group_fileinfo.editor_offset, SEEK_SET )) Error( "Error seeking to editor_offset in group.c" ); if (PHYSFS_read( LoadFile, &group_editor, group_fileinfo.editor_size,1 )!=1) Error( "Error reading group_editor in group.c" ); } //===================== READ VERTEX INFO ========================== if ( (group_fileinfo.vertex_offset > -1) && (group_fileinfo.vertex_howmany > 0)) { if (PHYSFSX_fseek( LoadFile,group_fileinfo.vertex_offset, SEEK_SET )) Error( "Error seeking to vertex_offset in group.c" ); vertex_ids.clear(); for (unsigned i = 0; i< group_header.num_vertices; ++i) { vertex tvert; if (PHYSFS_read( LoadFile, &tvert, sizeof(tvert),1 )!=1) Error( "Error reading tvert in group.c" ); vertex_ids.emplace_back(med_create_duplicate_vertex(tvert)); } } //==================== READ SEGMENT INFO =========================== if ( (group_fileinfo.segment_offset > -1) && (group_fileinfo.segment_howmany > 0)) { if (PHYSFSX_fseek( LoadFile,group_fileinfo.segment_offset, SEEK_SET )) Error( "Error seeking to segment_offset in group.c" ); segment_ids.clear(); for (unsigned i = 0; i < group_header.num_segments; ++i) { if (PHYSFS_read( LoadFile, &tseg, sizeof(segment),1 )!=1) Error( "Error reading tseg in group.c" ); group::segment_array_type_t::value_type s = get_free_segment_number(Segments); segment_ids.emplace_back(s); const auto &&segp = vmsegptridx(s); *segp = tseg; segp->objects = object_none; fuelcen_activate(segp); } range_for (const auto &gs, segment_ids) { auto &segp = *vmsegptr(gs); // Fix vertices range_for (auto &j, segp.verts) { vertnum = vertex_ids[j]; j = vertnum; } // Fix children and walls. for (const auto j : MAX_SIDES_PER_SEGMENT) { auto &seg = Segments[gs]; shared_segment &useg = seg; unique_segment &useg = seg; sseg.sides[j].wall_num = wall_none; if (IS_CHILD(Segments[gs].children[j])) { segnum_t segnum; segnum = segment_ids[Segments[gs].children[j]]; Segments[gs].children[j] = segnum; } //Translate textures. if (translate == 1) { int temp; useg.sides[j].tmap_num = tmap_xlate_table[useg.sides[j].tmap_num]; temp = useg.sides[j].tmap_num2; // strip off orientation bits if (const auto tmap_xlate = get_texture_index(temp); tmap_xlate != 0) useg.sides[j].tmap_num2 = build_texture2_value(tmap_xlate_table[tmap_xlate], get_texture_rotation_high(temp)); // mask on original orientation bits } } } } //===================== READ TEXTURE INFO ========================== if ( (group_fileinfo.texture_offset > -1) && (group_fileinfo.texture_howmany > 0)) { if (PHYSFSX_fseek( LoadFile, group_fileinfo.texture_offset, SEEK_SET )) Error( "Error seeking to texture_offset in gamemine.c" ); range_for (auto &i, partial_range(old_tmap_list, group_fileinfo.texture_howmany)) { std::array a; if (PHYSFS_read(LoadFile, a.data(), std::min(static_cast(group_fileinfo.texture_sizeof), a.size()), 1) != 1) Error( "Error reading old_tmap_list[i] in gamemine.c" ); i.copy_if(a); } } //=============== GENERATE TEXTURE TRANSLATION TABLE =============== translate = 0; Assert (NumTextures < MAX_TEXTURES); { hashtable ht; // Remove all the file extensions in the textures list for (unsigned i = 0; i < NumTextures; ++i) { temptr = strchr(&TmapInfo[i].filename[0u], '.'); if (temptr) *temptr = '\0'; hashtable_insert( &ht, &TmapInfo[i].filename[0u], i ); } // For every texture, search through the texture list // to find a matching name. for (unsigned j = 0; j < group_fileinfo.texture_howmany; ++j) { // Remove this texture name's extension temptr = strchr(&old_tmap_list[j][0u], '.'); if (temptr) *temptr = '\0'; tmap_xlate_table[j] = hashtable_search( &ht, static_cast(old_tmap_list[j])); if (tmap_xlate_table[j] < 0 ) tmap_xlate_table[j] = 0; if (tmap_xlate_table[j] != j ) translate = 1; } } //======================== CLOSE FILE ============================== LoadFile.reset(); //========================= UPDATE VARIABLES ====================== if (group_editor.Groupsegp != -1 ) Groupsegp[current_group] = &Segments[segment_ids[group_editor.Groupsegp]]; else Groupsegp[current_group] = NULL; Groupside[current_group] = group_editor.Groupside; warn_if_concave_segments(); return 0; } static char group_filename[PATH_MAX] = "*.GRP"; static void checkforgrpext( char * f ) { int i; for (i=1; f[i]; i++ ) { if (f[i]=='.') return; if ((f[i]==' '||f[i]==0) ) { f[i]='.'; f[i+1]='G'; f[i+2]= 'R'; f[i+3]= 'P'; f[i+4]=0; return; } } if (i < 123) { f[i]='.'; f[i+1]='G'; f[i+2]= 'R'; f[i+3]= 'P'; f[i+4]=0; return; } } #endif //short vertex_list[MAX_VERTICES]; int SaveGroup() { ui_messagebox(-2, -2, 1, "ERROR: Groups are broken.", "Ok"); return 0; #if 0 // Save group int i; if (current_group == -1) { ui_messagebox(-2, -2, 1, "ERROR: No current group.", "Ok"); return 0; } std::array vertex_list{}; // Make a list of all vertices in group. range_for (const auto &gs, GroupList[current_group].segments) range_for (auto &v, Segments[gs].verts) { vertex_list[v] = 1; } GroupList[current_group].vertices.clear(); for (i=0; i<=Highest_vertex_index; i++) if (vertex_list[i] == 1) { GroupList[current_group].vertices.emplace_back(i); } med_save_group("TEMP.GRP", GroupList[current_group].vertices, GroupList[current_group].segments); if (ui_get_filename( group_filename, "*.GRP", "SAVE GROUP" )) { checkforgrpext(group_filename); if (med_save_group(group_filename, GroupList[current_group].vertices, GroupList[current_group].segments)) return 0; mine_changed = 0; } return 1; #endif } int LoadGroup() { ui_messagebox(-2, -2, 1, "ERROR: Groups are broken.", "Ok"); return 0; #if 0 int x; if (num_groups == MAX_GROUPS) { x = ui_messagebox( -2, -2, 2, "Warning: You are about to wipe out a group.", "ARGH! NO!", "No problemo." ); if (x==1) return 0; } if (num_groups < MAX_GROUPS) { num_groups++; current_group = num_groups-1; } else current_group = 0; if (ui_get_filename( group_filename, "*.GRP", "LOAD GROUP" )) { checkforgrpext(group_filename); med_load_group(group_filename, GroupList[current_group].vertices, GroupList[current_group].segments); if (!med_move_group(0, Cursegp, Curside, vmsegptridx(Groupsegp[current_group]), Groupside[current_group], vmd_identity_matrix, 0)) { autosave_mine(mine_filename); set_view_target_from_segment(Cursegp); Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message("Group moved."); return 0; } else return 1; } else return 1; #endif } int UngroupSegment( void ) { if (Cursegp->group == current_group) { Cursegp->group = -1; delete_segment_from_group(Cursegp, current_group); Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message_fmt("Segment Ungrouped from Group %d.", current_group); return 1; } else return 0; } int GroupSegment( void ) { if (Cursegp->group == -1) { Cursegp->group = current_group; add_segment_to_group(Cursegp, current_group); Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message_fmt("Segment Added to Group %d.", current_group); return 1; } else return 0; } int Degroup( void ) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Vertices = LevelSharedVertexState.get_vertices(); int i; // GroupList[current_group].num_segments = 0; // Groupsegp[current_group] = 0; if (num_groups==0) return 0; range_for (const auto &gs, GroupList[current_group].segments) delete_segment_from_group(vmsegptridx(gs), current_group); // delete_segment_from_group( &Segments[GroupList[current_group].segments[i]]-Segments, current_group ); for (i=current_group;i num_groups-1) current_group--; if (num_groups == 0) current_group = -1; if (Lock_view_to_cursegp) { auto &vcvertptr = Vertices.vcptr; set_view_target_from_segment(vcvertptr, Cursegp); } Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message("Group UNgrouped."); return 1; } int NextGroup( void ) { if (num_groups > 0) { current_group++; if (current_group >= num_groups ) current_group = 0; Update_flags |= UF_ED_STATE_CHANGED; mine_changed = 1; } else editor_status("No Next Group\n"); return 0; } int PrevGroup( void ) { if (num_groups > 0) { current_group--; if (current_group < 0 ) current_group = num_groups-1; Update_flags |= UF_ED_STATE_CHANGED; mine_changed = 1; } else editor_status("No Previous Group\n"); return 0; } // ----------------------------------------------------------------------------- int MoveGroup(void) { if (!Groupsegp[current_group]) { editor_status("Error -- Cannot move group, no group segment."); return 1; } med_compress_mine(); if (!med_move_group(0, Cursegp, Curside, vmsegptridx(Groupsegp[current_group]), Groupside[current_group], vmd_identity_matrix, 0)) { autosave_mine(mine_filename); Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message("Group moved."); return 0; } else return 1; } // ----------------------------------------------------------------------------- int CopyGroup(void) { segnum_t attach_seg; if (!Groupsegp[current_group]) { editor_status("Error -- Cannot copy group, no group segment."); return 1; } // See if the attach side in the group is attached to another segment. // If so, it must not be in the group for group copy to be legal. attach_seg = Groupsegp[current_group]->children[Groupside[current_group]]; if (attach_seg != segment_none) { if (GroupList[current_group].segments.contains(attach_seg)) { editor_status_fmt("Error -- Cannot copy group, attach side has a child (segment %i) attached.", attach_seg); return 1; } } med_compress_mine(); if (!med_copy_group(0, Cursegp, Curside, vcsegptr(Groupsegp[current_group]), Groupside[current_group], vmd_identity_matrix)) { autosave_mine(mine_filename); Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message("Group copied."); return 0; } else return 1; } // ----------------------------------------------------------------------------- int RotateGroup(void) { if (!Groupsegp[current_group]) { editor_status("Error -- Cannot rotate group, no group segment."); return 1; } Group_orientation[current_group]++; if ((Group_orientation[current_group] <0) || (Group_orientation[current_group] >4)) Group_orientation[current_group]=0; med_compress_mine(); if (!med_move_group(0, Cursegp, Curside, vmsegptridx(Groupsegp[current_group]), Groupside[current_group], vmd_identity_matrix, Group_orientation[current_group])) { Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message("Group rotated."); return 0; } else return 1; } // ----------------------------------------------------------------------------- // Creates a group from all segments connected to marked segment. int SubtractFromGroup(void) { int x, original_group; if (!Markedsegp) { editor_status("Error -- Cannot create group, no marked segment."); return 1; } med_compress_mine(); autosave_mine(mine_filename); if (num_groups == MAX_GROUPS) { x = ui_messagebox( -2, -2, 2, "Warning: You are about to wipe out a group.", "ARGH! NO!", "No problemo." ); if (x==1) return 0; } if (current_group == -1) { editor_status("Error -- No current group. Cannot subtract."); return 1; } original_group = current_group; current_group = (current_group + 1) % MAX_GROUPS; // Create a list of segments to copy. GroupList[current_group].segments.clear(); create_group_list(Markedsegp, GroupList[current_group].segments, &Selected_segs); // Now, scan the two groups, forming a group which consists of only those segments common to the two groups. auto intersects = [original_group](group::segment_array_type_t::const_reference r) -> bool { bool contains = GroupList[original_group].segments.contains(r); if (!contains) Segments[r].group = -1; return !contains; }; GroupList[current_group].segments.erase_if(intersects); // Replace Marked segment with Group Segment. Groupsegp[current_group] = Markedsegp; Groupside[current_group] = Markedside; range_for (const auto &gs, GroupList[current_group].segments) Segments[gs].group = current_group; Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message("Group created."); return 1; } // ----------------------------------------------------------------------------- // Creates a group from all segments already in CurrentGroup which can be reached from marked segment // without passing through current segment. int CreateGroup(void) { int x; if (!Markedsegp) { editor_status("Error -- Cannot create group, no marked segment."); return 1; } med_compress_mine(); autosave_mine(mine_filename); if (num_groups == MAX_GROUPS) { x = ui_messagebox( -2, -2, 2, "Warning: You are about to wipe out a group.", "ARGH! NO!", "No problemo." ); if (x==1) return 0; // Aborting at user's request. } if (num_groups < MAX_GROUPS) { num_groups++; current_group = num_groups-1; } else current_group = 0; // Create a list of segments to copy. GroupList[current_group].clear(); create_group_list(Markedsegp, GroupList[current_group].segments, NULL); // Replace Marked segment with Group Segment. Groupsegp[current_group] = Markedsegp; Groupside[current_group] = Markedside; // Markedsegp = 0; // Markedside = WBACK; range_for (const auto &gs, GroupList[current_group].segments) Segments[gs].group = current_group; Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message("Group created."); return 1; } // ----------------------------------------------------------------------------- // Deletes current group. int DeleteGroup( void ) { auto &LevelSharedVertexState = LevelSharedSegmentState.get_vertex_state(); auto &Vertices = LevelSharedVertexState.get_vertices(); int i; autosave_mine(mine_filename); if (num_groups==0) return 0; range_for (const auto &gs, GroupList[current_group].segments) { const auto &&segp = vmsegptridx(gs); segp->group = -1; med_delete_segment(segp); } for (i=current_group;i num_groups-1) current_group--; if (num_groups==0) current_group = -1; undo_status[Autosave_count] = "Delete Group UNDONE."; if (Lock_view_to_cursegp) { auto &vcvertptr = Vertices.vcptr; set_view_target_from_segment(vcvertptr, Cursegp); } Update_flags |= UF_WORLD_CHANGED; mine_changed = 1; diagnostic_message("Group deleted."); // warn_if_concave_segments(); // This could be faster -- just check if deleted segment was concave, warn accordingly return 1; } int MarkGroupSegment( void ) { if ((Cursegp->group != -1) && (Cursegp->group == current_group)) { autosave_mine(mine_filename); Groupsegp[current_group] = Cursegp; Groupside[current_group] = Curside; editor_status("Group Segment Marked."); Update_flags |= UF_ED_STATE_CHANGED; undo_status[Autosave_count] = "Mark Group Segment UNDONE."; mine_changed = 1; return 1; } else return 0; }