/*		
 * $Source: f:/miner/source/main/editor/rcs/group.c $
 * $Revision: 2.0 $
 * $Author: john $
 * $Date: 1995/02/27 11:35:05 $
 * 
 * group functions
 * 
 * $Log: group.c $
 * Revision 2.0  1995/02/27  11:35:05  john
 * Version 2.0! No anonymous unions, Watcom 10.0, with no need
 * for bitmaps.tbl.
 * 
 * Revision 1.65  1994/11/27  23:17:21  matt
 * Made changes for new mprintf calling convention
 * 
 * Revision 1.64  1994/11/17  14:48:08  mike
 * validation functions moved from editor to game.
 * 
 * Revision 1.63  1994/11/17  11:38:56  matt
 * Ripped out code to load old mines
 * 
 * Revision 1.62  1994/10/27  10:06:20  mike
 * adapt to no inverse table.
 * 
 * Revision 1.61  1994/10/03  23:40:08  mike
 * New fuelcen_activate parameters.
 * 
 * Revision 1.60  1994/09/28  17:32:01  mike
 * Make group copying work for copying a group's walls.
 * 
 * Revision 1.59  1994/09/20  14:35:28  mike
 * Fix bugs in group subtraction code.  Don't allow to attach a group if the attach side is unfree.
 * 
 * Revision 1.58  1994/08/25  21:58:07  mike
 * IS_CHILD stuff.
 * 
 * Revision 1.57  1994/08/04  19:12:58  matt
 * Changed a bunch of vecmat calls to use multiple-function routines, and to
 * allow the use of C macros for some functions
 * 
 * Revision 1.56  1994/08/03  15:40:01  mike
 * Enable calls to compress_mine to get rid of bugs in group
 * copying -- was creating invalid segments.
 * 
 * Revision 1.55  1994/06/30  10:59:13  yuan
 * Fixed texture translations.
 * 
 * Revision 1.54  1994/06/22  17:36:00  mike
 * Fix bug in group creation, was stuffing first two group segs over number
 * of segments in group (then number would overwrite them), so there would
 * be two bogus segments in group, one of which was always 0, the other
 * would be a small number.
 * 
 * Revision 1.53  1994/06/14  17:07:15  john
 * *** empty log message ***
 * 
 * Revision 1.52  1994/06/14  16:59:09  mike
 * Fix references to tmap_num2, must strip off orientation bits.
 * 
 * Revision 1.51  1994/05/23  14:56:37  mike
 * make current segment be add segment.
 * 
 * Revision 1.50  1994/05/19  12:10:01  matt
 * Use new vecmat macros and globals
 * 
 * Revision 1.49  1994/05/17  10:33:59  matt
 * Deleted unused get_free_object_num() func.
 * 
 * Revision 1.48  1994/05/09  23:34:17  mike
 * Punch all sloppy sides in a group, speed up segment rotation.
 * 
 * Revision 1.47  1994/05/06  14:39:56  mike
 * Make objects move and copy with groups.
 * 
 * Revision 1.46  1994/05/05  16:05:54  yuan
 * Added fuelcen/repaircens to groups...
 * 
 * Eventually, walls will be added too...
 * 
 * Revision 1.45  1994/05/05  12:56:25  yuan
 * Fixed a bunch of group bugs.
 * 
 * Revision 1.44  1994/05/04  14:10:04  mike
 * Assert added to prevent bombing out when current_group = -1
 * 
 * Revision 1.43  1994/05/02  17:59:18  yuan
 * Changed undo_status into an array rather than malloced pointers.
 * 
 * Revision 1.42  1994/05/02  15:23:19  mike
 * Call med_combine_duplicate_vertices in med_copy_group and med_move_group.
 * 
 * Revision 1.41  1994/04/27  12:11:23  mike
 * Fix bug in group rotation.
 * 
 * Revision 1.40  1994/04/22  10:07:37  yuan
 * Make sure we don't get obj->next equal itself error.
 * 
 * Revision 1.39  1994/04/18  17:15:13  yuan
 * Added error checking for select prev, and next group.
 * 
 */


#pragma off (unreferenced)
static char rcsid[] = "$Id: group.c 2.0 1995/02/27 11:35:05 john Exp $";
#pragma on (unreferenced)


#include <stdio.h>
#include <string.h>

#include "mono.h"
#include "gr.h"
#include "cflib.h"
#include "nocfile.h"
#include "ui.h"

#include "inferno.h"
#include "segment.h"
#include	"editor.h"
#include "error.h"
#include "gamemine.h"
#include "gameseg.h"

#include "bm.h"				// For MAX_TEXTURES.
#include "textures.h"
#include "hash.h"
#include "fuelcen.h"

#include "medwall.h"

struct {
	int     fileinfo_version;
	int     fileinfo_sizeof;
} group_top_fileinfo;    // Should be same as first two fields below...

struct {
	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;
	int     texture_howmany;
	int     texture_sizeof;
} group_fileinfo;

struct {
	int     num_vertices;
	int     num_segments;
} group_header;

struct {
	int     current_seg;
	int     newsegment_offset;
	int     newsegment_size;
	int     Groupsegp;
	int     Groupside;
} group_editor;

group		GroupList[MAX_GROUPS+1];
segment  *Groupsegp[MAX_GROUPS+1];
int		Groupside[MAX_GROUPS+1];
int		Group_orientation[MAX_GROUPS+1];
int 		Current_group=-1;
int 		Num_groups=0; 

extern void validate_segment_side(segment *sp, int sidenum);

// -- 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;
// -- 	}
// -- 
// -- }


// ------------------------------------------------------------------------------------------------
//	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
void med_create_group_rotation_matrix(vms_matrix *result_mat, int delta_flag, segment *first_seg, int first_side, segment *base_seg, int base_side, vms_matrix *orient_matrix, 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
		set_matrix_based_on_side(&rotmat4, first_side);
		rotmat3 = *orient_matrix;
		vm_transpose_matrix(&rotmat3);
	 	vm_matrix_x_matrix(&rotmat,&rotmat4,&rotmat3);			// this is the desired orientation of the new segment
		vm_transpose_matrix(&rotmat4);
	 	vm_matrix_x_matrix(&rotmat2,&rotmat,&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
	 	set_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);
		vm_matrix_x_matrix(&rotmat, &rotmat4, &rotmat3);
		rotmat4 = rotmat;

	 	rotmat = rotmat4;

	 	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].
	 	set_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
	 	vm_matrix_x_matrix(&rotmat2,&rotmat,&rotmat3);			// now rotmat2 takes the current segment to the desired orientation
	 	vm_transpose_matrix(&rotmat2);
	}

	*result_mat = rotmat2;

}

// -----------------------------------------------------------------------------------------
// Rotate all vertices and objects in group.
void med_rotate_group(vms_matrix *rotmat, short *group_seglist, int group_size, segment *first_seg, int first_side)
{
	int			v,s, objnum;
	byte			vertex_list[MAX_VERTICES];
	vms_vector	rotate_center;

	compute_center_point_on_side(&rotate_center, first_seg, first_side);

	//	Create list of points to rotate.
	for (v=0; v<=Highest_vertex_index; v++)
		vertex_list[v] = 0;

	for (s=0; s<group_size; s++) {
		segment *sp = &Segments[group_seglist[s]];

		for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++)
			vertex_list[sp->verts[v]] = 1;

		//	Rotate center of all objects in group.
		objnum = sp->objects;
		while (objnum != -1) {
			vms_vector	tv, tv1;

			mprintf((0, "%2i ", objnum));
			vm_vec_sub(&tv1,&Objects[objnum].pos,&rotate_center);
			vm_vec_rotate(&tv,&tv1,rotmat);
			vm_vec_add(&Objects[objnum].pos, &tv, &rotate_center);

			objnum = Objects[objnum].next;
		}			
	}

	// Do the pre-rotation xlate, do the rotation, do the post-rotation xlate
	for (v=0; v<=Highest_vertex_index; v++)
		if (vertex_list[v]) {
			vms_vector	tv,tv1;

			vm_vec_sub(&tv1,&Vertices[v],&rotate_center);
			vm_vec_rotate(&tv,&tv1,rotmat);
			vm_vec_add(&Vertices[v],&tv,&rotate_center);

		}

}


// ------------------------------------------------------------------------------------------------
void cgl_aux(segment *segp, short *seglistp, int *num_segs, short *ignore_list, int num_ignore_segs)
{
	int	i, side;
	int	curseg = segp-Segments;

	for (i=0; i<num_ignore_segs; i++)
		if (curseg == ignore_list[i])
			return;

	if ((segp-Segments < 0) || (segp-Segments >= MAX_SEGMENTS)) {
		mprintf((0,"Warning -- invalid segment index = %i, max = %i\n",segp-Segments,MAX_SEGMENTS));
		Int3();
	}

	if (!Been_visited[segp-Segments]) {
		seglistp[(*num_segs)++] = segp-Segments;
		Been_visited[segp-Segments] = 1;

		for (side=0; side<MAX_SIDES_PER_SEGMENT; side++)
			if (IS_CHILD(segp->children[side]))
				cgl_aux(&Segments[segp->children[side]], seglistp, num_segs, ignore_list, num_ignore_segs);
	}
}

// ------------------------------------------------------------------------------------------------
//	Sets Been_visited[n] if n is reachable from segp
void create_group_list(segment *segp, short *seglistp, int *num_segs, short *ignore_list, int num_ignore_segs)
{
	int	i;

	for (i=0; i<MAX_SEGMENTS; i++)
		Been_visited[i] = 0;

	cgl_aux(segp, seglistp, num_segs, ignore_list, num_ignore_segs);
}


#define MXS MAX_SEGMENTS
#define MXV MAX_VERTICES

// ------------------------------------------------------------------------------------------------
void duplicate_group(byte *vertex_ids, short *segment_ids, int num_segments)
{
	int	v,s,ss,new_vertex_id,new_segment_id,sidenum;
	short	new_segment_ids[MAX_SEGMENTS];
	short	new_vertex_ids[MAX_VERTICES];		// If new_vertex_ids[v] != -1, then vertex v has been remapped to new_vertex_ids[v]
	short	new_object_ids[MAX_OBJECTS];

	//	duplicate vertices
	for (v=0; v<MXV; v++)
		new_vertex_ids[v] = -1;

	for (v=0; v<MAX_OBJECTS; v++)
		new_object_ids[v] = -1;

	//	duplicate vertices
	for (v=0; v<=Highest_vertex_index; v++) {
		if (vertex_ids[v]) {
			new_vertex_id = med_create_duplicate_vertex(&Vertices[v]);
			new_vertex_ids[v] = new_vertex_id;
		}
	}

	//	duplicate segments
	for (s=0; s<num_segments; s++) {
		int	objnum;

		new_segment_id = med_create_duplicate_segment(&Segments[segment_ids[s]]);
		new_segment_ids[s] = new_segment_id;
		objnum = Segments[new_segment_id].objects;
		Segments[new_segment_id].objects = -1;
		while (objnum != -1) {
			if (Objects[objnum].type != OBJ_PLAYER) {
				int new_obj_id;
				new_obj_id = obj_create_copy(objnum, &Objects[objnum].pos, new_segment_id);
				mprintf((0, "Object #%i in segment #%i copied to object #%i, segment #%i: new_obj->segnum = %i\n", objnum, Objects[objnum].segnum, new_obj_id, new_segment_id, Objects[new_obj_id].segnum));
			}
			objnum = Objects[objnum].next;
		}
	}

	//	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
	for (s=0; s<num_segments; s++) {
		segment *sp = &Segments[new_segment_ids[s]];
		for (sidenum=0; sidenum<MAX_SIDES_PER_SEGMENT; sidenum++) {
			int seg = sp->children[sidenum];
			if (IS_CHILD(seg)) {
				for (ss=0; ss<num_segments; ss++) {
					if (seg == segment_ids[ss])
						Segments[new_segment_ids[s]].children[sidenum] = new_segment_ids[ss];
				}
			}
		}	// end for (sidenum=0...

		//	Now fixup vertex ids
		for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++) {
			if (vertex_ids[sp->verts[v]]) {
				sp->verts[v] = new_vertex_ids[sp->verts[v]];
			}
		}
	}	// end for (s=0...

	//	Now, copy new_segment_ids into segment_ids
	for (s=0; s<num_segments; s++) {
		segment_ids[s] = new_segment_ids[s];
	}

	//	Now, copy new_vertex_ids into vertex_ids
	for (v=0; v<MXV; v++)
		vertex_ids[v] = 0;

	for (v=0; v<MXV; v++) {
		if (new_vertex_ids[v] != -1)
			vertex_ids[new_vertex_ids[v]] = 1;

	}
}


// ------------------------------------------------------------------------------------------------
int in_group(int segnum, int group_num)
{
	int	i;

	for (i=0; i<GroupList[group_num].num_segments; i++)
		if (segnum == GroupList[group_num].segments[i])
			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.
int med_copy_group(int delta_flag, segment *base_seg, int base_side, segment *group_seg, int group_side, vms_matrix *orient_matrix)
{
	int			v,s;
	vms_vector	srcv,destv;
	int 			x;
	int			new_current_group;
	segment		*segp;
	int 			c;
	int			gs_index;
	byte			in_vertex_list[MAX_VERTICES];
	vms_matrix	rotmat;
	int			objnum;

	if (IS_CHILD(base_seg->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 = 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
	for (s=0;s<GroupList[Current_group].num_segments;s++)
		if (GroupList[Current_group].segments[s] == (Groupsegp[Current_group]-Segments))
			gs_index=s; 

	GroupList[new_current_group] = GroupList[Current_group];

	//	Make a list of all vertices in group.
	if (group_seg == &New_segment)
		for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++)
			in_vertex_list[group_seg->verts[v]] = 1;
	else {
		for (v=0; v<=Highest_vertex_index; v++)
			in_vertex_list[v] = 0;

		for (s=0; s<GroupList[new_current_group].num_segments; s++)
			for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++)
				in_vertex_list[Segments[GroupList[new_current_group].segments[s]].verts[v]] = 1;
	}

	//	Show which objects are in which segments before group copy.
	// for (s=0; s<=Highest_segment_index; s++) {
		// int	objnum = Segments[s].objects;

		// mprintf((0, "Before: Segment #%2i contains objects ", s));

		// while (objnum != -1) {
		// 	mprintf((0, "%2i ",objnum));
		// 	objnum = Objects[objnum].next;
		// }
		// mprintf((0, "\n"));
	// }

	// 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, GroupList[new_current_group].num_segments);

	//group_seg = &Segments[GroupList[new_current_group].segments[0]];					// connecting segment in group has been changed, so update group_seg

   Groupsegp[new_current_group] = group_seg = &Segments[GroupList[new_current_group].segments[gs_index]];
	Groupside[new_current_group] = Groupside[Current_group];

	for (s=0; s<GroupList[new_current_group].num_segments; s++) {
		Segments[GroupList[new_current_group].segments[s]].group = new_current_group;
		Segments[GroupList[new_current_group].segments[s]].special = SEGMENT_IS_NOTHING;
		Segments[GroupList[new_current_group].segments[s]].matcen_num = -1;
	}

	// Breaking connections between segments in the current group and segments not in the group.
	for (s=0; s<GroupList[new_current_group].num_segments; s++) {
		mprintf((0, "[%3i %3i] ", GroupList[new_current_group].segments[s], GroupList[current_group].segments[s]));
		segp = &Segments[GroupList[new_current_group].segments[s]];
		for (c=0; c<MAX_SIDES_PER_SEGMENT; c++) 
			if (IS_CHILD(segp->children[c])) {
				if (!in_group(segp->children[c], new_current_group)) {
					mprintf((0, "2: Breaking connection at seg:side = %i:%i\n", segp-Segments, c));
					segp->children[c] = -1;
					validate_segment_side(segp,c);					// we have converted a connection to a side so validate the segment
				}
			}
	}

	copy_uvs_seg_to_seg(&New_segment, Groupsegp[new_current_group]);
	
	//	Now do the copy
	//	First, xlate all vertices so center of group_seg:group_side is at origin
	compute_center_point_on_side(&srcv,group_seg,group_side);
	for (v=0; v<=Highest_vertex_index; v++)
		if (in_vertex_list[v])
			vm_vec_sub2(&Vertices[v],&srcv);

	//	Now, translate all object positions.
	for (s=0; s<GroupList[new_current_group].num_segments; s++) {
		int	segnum = GroupList[new_current_group].segments[s];

		objnum = Segments[segnum].objects;

		while (objnum != -1) {
			vm_vec_sub2(&Objects[objnum].pos, &srcv);
			objnum = Objects[objnum].next;
		}
	}

	//	Now, rotate segments in group so orientation of group_seg is same as base_seg.
	med_create_group_rotation_matrix(&rotmat, delta_flag, group_seg, group_side, base_seg, base_side, orient_matrix, 0);
	med_rotate_group(&rotmat, GroupList[new_current_group].segments, GroupList[new_current_group].num_segments, group_seg, group_side);

	//	Now xlate all vertices so group_seg:group_side shares center point with base_seg:base_side
	compute_center_point_on_side(&destv,base_seg,base_side);
	for (v=0; v<=Highest_vertex_index; v++)
		if (in_vertex_list[v])
			vm_vec_add2(&Vertices[v],&destv);

	//	Now, xlate all object positions.
	for (s=0; s<GroupList[new_current_group].num_segments; s++) {
		int	segnum = GroupList[new_current_group].segments[s];
		int	objnum = Segments[segnum].objects;

		while (objnum != -1) {
			vm_vec_add2(&Objects[objnum].pos, &destv);
			objnum = Objects[objnum].next;
		}
	}

	//	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,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.
int med_move_group(int delta_flag, segment *base_seg, int base_side, segment *group_seg, int group_side, vms_matrix *orient_matrix, int orientation)
{
	int			v,vv,s,ss,c,d;
	vms_vector	srcv,destv;
	segment		*segp, *csegp, *dsegp;
	byte			in_vertex_list[MAX_VERTICES], out_vertex_list[MAX_VERTICES];
	int			local_hvi;
	vms_matrix	rotmat;

	if (IS_CHILD(base_seg->children[base_side]))
		if (base_seg->children[base_side] != group_seg-Segments) {
			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; v<MAX_VERTICES_PER_SEGMENT; v++)
//		for (s=0; s<GroupList[Current_group].num_segments; s++)
//			for (vv=0; vv<MAX_VERTICES_PER_SEGMENT; vv++)
//				if (Segments[GroupList[Current_group].segments[s]].verts[vv] == base_seg->verts[v]) {
//					editor_status("Error -- unable to move group, it shares a vertex with destination segment.");
//					return 1;
//				}

	for (v=0; v<=Highest_vertex_index; v++) {
		in_vertex_list[v] = 0;
		out_vertex_list[v] = 0;
	}

	//	Make a list of all vertices in group.
	for (s=0; s<GroupList[Current_group].num_segments; s++)
		for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++)
			in_vertex_list[Segments[GroupList[Current_group].segments[s]].verts[v]] = 1;

	//	For all segments which are not in GroupList[Current_group].segments, mark all their vertices in the out list.
	for (s=0; s<=Highest_segment_index; s++) {
		for (ss=0; ss<GroupList[Current_group].num_segments; ss++)
			if (GroupList[Current_group].segments[ss] == s)
				break;
		if (ss == GroupList[Current_group].num_segments) 
			for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++)
				out_vertex_list[Segments[s].verts[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.
	local_hvi = Highest_vertex_index;		//	Can't use Highest_vertex_index as loop termination because it gets increased by med_create_duplicate_vertex.

	for (v=0; v<=local_hvi; v++)
		if (in_vertex_list[v])
			if (out_vertex_list[v]) {
				int new_vertex_id;

				new_vertex_id = med_create_duplicate_vertex(&Vertices[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.
				for (s=0; s<GroupList[Current_group].num_segments; s++) {
					segment *sp = &Segments[GroupList[Current_group].segments[s]];
					for (vv=0; vv<MAX_VERTICES_PER_SEGMENT; vv++)
						if (sp->verts[vv] == v)
							sp->verts[vv] = new_vertex_id;
				}
			}

	for (s=0;s<GroupList[Current_group].num_segments;s++) 
		Segments[GroupList[Current_group].segments[s]].group = Current_group;

	// Breaking connections between segments in the group and segments not in the group.
	for (s=0; s<GroupList[Current_group].num_segments; s++) 
		{
		segp = &Segments[GroupList[Current_group].segments[s]];
		for (c=0; c<MAX_SIDES_PER_SEGMENT; c++) 
			if (IS_CHILD(segp->children[c]))
				{
				csegp = &Segments[segp->children[c]];
				if (csegp->group != Current_group)
					{
					for (d=0; d<MAX_SIDES_PER_SEGMENT; d++)
						if (IS_CHILD(csegp->children[d]))
							{
							dsegp = &Segments[csegp->children[d]];
							if (dsegp->group == Current_group) 
								{
								csegp->children[d] = -1;
								validate_segment_side(csegp,d);					// we have converted a connection to a side so validate the segment
								}
							}
					segp->children[c] = -1;
					validate_segment_side(segp,c);					// we have converted a connection to a side so validate the segment
					}
				}
		}

	copy_uvs_seg_to_seg(&New_segment, Groupsegp[Current_group]);

	//	Now do the move
	//	First, xlate all vertices so center of group_seg:group_side is at origin
	compute_center_point_on_side(&srcv,group_seg,group_side);
	for (v=0; v<=Highest_vertex_index; v++)
		if (in_vertex_list[v])
			vm_vec_sub2(&Vertices[v],&srcv);

	//	Now, move all object positions.
	for (s=0; s<GroupList[Current_group].num_segments; s++) {
		int	segnum = GroupList[Current_group].segments[s];
		int	objnum = Segments[segnum].objects;

		// mprintf((0, "Translating objects in segment #%2i by [%7.3f %7.3f %7.3f]: ", segnum, f2fl(srcv.x), f2fl(srcv.y), f2fl(srcv.z)));

		while (objnum != -1) {
			mprintf((0, "%2i ", objnum));
			vm_vec_sub2(&Objects[objnum].pos, &srcv);
			objnum = Objects[objnum].next;
		}
	}
	// mprintf((0, "\n"));

	//	Now, rotate segments in group so orientation of group_seg is same as base_seg.
	med_create_group_rotation_matrix(&rotmat, delta_flag, group_seg, group_side, base_seg, base_side, orient_matrix, orientation);
	med_rotate_group(&rotmat, GroupList[Current_group].segments, GroupList[Current_group].num_segments, group_seg, group_side);

	//	Now xlate all vertices so group_seg:group_side shares center point with base_seg:base_side
	compute_center_point_on_side(&destv,base_seg,base_side);
	for (v=0; v<=Highest_vertex_index; v++)
		if (in_vertex_list[v])
			vm_vec_add2(&Vertices[v],&destv);

	//	Now, rotate all object positions.
	for (s=0; s<GroupList[Current_group].num_segments; s++) {
		int	segnum = GroupList[Current_group].segments[s];
		int	objnum = Segments[segnum].objects;

		while (objnum != -1) {
			vm_vec_add2(&Objects[objnum].pos, &destv);
			objnum = Objects[objnum].next;
		}
	}

	//	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;
}


//	-----------------------------------------------------------------------------
int place_new_segment_in_world(void)
{
	int	v,segnum;

	segnum = get_free_segment_number();

	Segments[segnum] = New_segment;

	for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++)
		Segments[segnum].verts[v] = med_create_duplicate_vertex(&Vertices[New_segment.verts[v]]);

	return segnum;

}

//	-----------------------------------------------------------------------------
//	Attach segment in the new-fangled way, which is by using the CopyGroup code.
int AttachSegmentNewAng(vms_angvec *pbh)
{
	int			newseg;
	vms_matrix	orient_matrix;

	GroupList[Current_group].num_segments = 1;
	newseg = place_new_segment_in_world();
	GroupList[Current_group].segments[0] = newseg;

	if (!med_move_group(1, Cursegp, Curside, &Segments[newseg], AttachSide, vm_angles_2_matrix(&orient_matrix,pbh),0)) {
		autosave_mine(mine_filename);

		med_propagate_tmaps_to_segments(Cursegp,&Segments[newseg],0);
		med_propagate_tmaps_to_back_side(&Segments[newseg], Side_opposite[AttachSide],0);
		copy_uvs_seg_to_seg(&New_segment,&Segments[newseg]);

		Cursegp = &Segments[newseg];
		Curside = Side_opposite[AttachSide];
		med_create_new_segment_from_cursegp();

		if (Lock_view_to_cursegp)
			set_view_target_from_segment(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;

}

//	-----------------------------------------------------------------------------
void save_selected_segs(int *num, short *segs)
{
	int	i;

	for (i=0; i<GroupList[Current_group].num_segments; i++)
		segs[i] = GroupList[Current_group].segments[i];

	*num = GroupList[Current_group].num_segments;
}

//	-----------------------------------------------------------------------------
void restore_selected_segs(int num, short *segs)
{
	int	i;

	for (i=0; i<GroupList[Current_group].num_segments; i++)
		GroupList[Current_group].segments[i] = segs[i];

	GroupList[Current_group].num_segments = num;
}

//	-----------------------------------------------------------------------------
void validate_selected_segments(void)
{
	int	i;

	for (i=0; i<GroupList[Current_group].num_segments; i++)
		validate_segment(&Segments[GroupList[Current_group].segments[i]]);
}

// =====================================================================================


//	-----------------------------------------------------------------------------
void delete_segment_from_group(int segment_num, int group_num)
{
	int g, del_seg_index;
	
	del_seg_index = -1;
	for (g=0; g<GroupList[group_num].num_segments; g++)
		if (segment_num == GroupList[group_num].segments[g]) {  
			del_seg_index = g;
			break;
		}

	//mprintf((0, "segment_num=%d delseg_index=%d\n", segment_num, del_seg_index)); 
	
	if (IS_CHILD(del_seg_index)) {
		for (g=del_seg_index;g<GroupList[group_num].num_segments-1;g++) { 
			GroupList[group_num].segments[g] = GroupList[group_num].segments[g+1];
			}
		GroupList[group_num].num_segments--;
		//mprintf((0, "num_segments=%d\n\n", GroupList[group_num].num_segments));
		Segments[segment_num].group = -1;		
		}

}
// =====================================================================================


//	-----------------------------------------------------------------------------
void add_segment_to_group(int segment_num, int group_num)
{  
	GroupList[group_num].num_segments++;
	GroupList[group_num].segments[GroupList[group_num].num_segments-1] = segment_num;
}
// =====================================================================================


//	-----------------------------------------------------------------------------
int rotate_segment_new(vms_angvec *pbh)
{
	int			newseg,baseseg,newseg_side,baseseg_side;
	vms_matrix	orient_matrix,tm1,tm2;
	int			n_selected_segs_save;
	short			selected_segs_save[MAX_SEGMENTS];
	int			child_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;
	
	save_selected_segs(&n_selected_segs_save, selected_segs_save);
	GroupList[ROT_GROUP].num_segments = 0;
	newseg = Cursegp - Segments;
	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].
	child_save = Cursegp->children[newseg_side];	// save connection we are about to sever
	Cursegp->children[newseg_side] = -1;			// sever connection
	create_group_list(Cursegp, &GroupList[ROT_GROUP].segments, &GroupList[ROT_GROUP].num_segments, Selected_segs, 0);	// create list of segments in group
	//mprintf((0, "NumSegs = %d\n", GroupList[ROT_GROUP].num_segments));
	Cursegp->children[newseg_side] = child_save;	// restore severed connection
	GroupList[ROT_GROUP].segments[0] = newseg;

	baseseg = Segments[newseg].children[newseg_side];
	if (!IS_CHILD(baseseg)) {
		editor_status("Error -- unable to rotate segment, side opposite curside is not attached.");
		restore_selected_segs(n_selected_segs_save,selected_segs_save);
		Current_group = current_group_save;
		return 1;
	}

	baseseg_side = find_connect_side(&Segments[newseg], &Segments[baseseg]);

	med_extract_matrix_from_segment(&Segments[newseg],&tm1);
	tm1 = vmd_identity_matrix;
	vm_angles_2_matrix(&tm2,pbh);
	vm_matrix_x_matrix(&orient_matrix,&tm1,&tm2);

	Segments[baseseg].children[baseseg_side] = -1;
	Segments[newseg].children[newseg_side] = -1;

	if (!med_move_group(1, &Segments[baseseg], baseseg_side, &Segments[newseg], newseg_side, &orient_matrix, 0)) {
		Cursegp = &Segments[newseg];
		med_create_new_segment_from_cursegp();
//		validate_selected_segments();
		med_propagate_tmaps_to_segments(&Segments[baseseg], &Segments[newseg], 1);
		med_propagate_tmaps_to_back_side(&Segments[newseg], Curside, 1);
	}

	restore_selected_segs(n_selected_segs_save,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)
{
	int	rval;

	autosave_mine(mine_filename);

	rval = rotate_segment_new(pbh);

	if (Lock_view_to_cursegp)
		set_view_target_from_segment(Cursegp);

	Update_flags |= UF_WORLD_CHANGED;
	mine_changed = 1;
	warn_if_concave_segment(Cursegp);

	return rval;
}

static char	 current_tmap_list[MAX_TEXTURES][13];

// -----------------------------------------------------------------------------
// 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.
int med_save_group( char *filename, short *vertex_ids, short *segment_ids, int num_vertices, int num_segments)
{
	FILE * SaveFile;
	int header_offset, editor_offset, vertex_offset, segment_offset, texture_offset;
	char ErrorMessage[100];
	int i, j, k;
	int segnum;
	segment tseg;
   vms_vector tvert;
	int found;

	SaveFile = fopen( filename, "wb" );
	if (!SaveFile)
	{
		sprintf( ErrorMessage, "ERROR: Unable to open %s\n", filename );
		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    =   num_vertices;
	group_fileinfo.vertex_sizeof     =   sizeof(vms_vector);
	group_fileinfo.segment_offset    =   -1;
	group_fileinfo.segment_howmany   =   num_segments;
	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
	fwrite( &group_fileinfo, sizeof(group_fileinfo), 1, SaveFile );

	//===================== SAVE HEADER INFO ========================

	group_header.num_vertices        =   num_vertices;
	group_header.num_segments        =   num_segments;

	// Write the editor info
	header_offset = ftell(SaveFile);
	fwrite( &group_header, sizeof(group_header), 1, SaveFile );

	//===================== 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
	if (Groupsegp[Current_group]) { 
		segnum = Groupsegp[Current_group]-Segments;     	
		for (i=0;i<num_segments;i++)
			if (segnum == segment_ids[i])	
				group_editor.Groupsegp = i;
	} 
	else
		group_editor.Groupsegp      	=   0;
	group_editor.Groupside           =   Groupside[Current_group];

	editor_offset = ftell(SaveFile);
	fwrite( &group_editor, sizeof(group_editor), 1, SaveFile );


	//===================== SAVE VERTEX INFO ==========================

	vertex_offset = ftell(SaveFile);
	for (i=0;i<num_vertices;i++) {
		tvert = Vertices[vertex_ids[i]];	
		fwrite( &tvert, sizeof(tvert), 1, SaveFile ); 
	}

	//===================== SAVE SEGMENT INFO =========================


	segment_offset = ftell(SaveFile);
	for (i=0;i<num_segments;i++) {
		tseg = Segments[segment_ids[i]];
		
		for (j=0;j<6;j++)	{
			found = 0;
			for (k=0;k<num_segments;k++) 
				if (tseg.children[j] == segment_ids[k]) { 
					tseg.children[j] = k;
					found = 1;
					break;
					}	
			if (found==0) tseg.children[j] = -1;
		}

		for (j=0;j<8;j++)
			for (k=0;k<num_vertices;k++)
				if (tseg.verts[j] == vertex_ids[k])	{
					tseg.verts[j] = k;
					break;
					}

		fwrite( &tseg, sizeof(tseg), 1, SaveFile );

	 }

	//===================== SAVE TEXTURE INFO ==========================

	texture_offset = ftell(SaveFile);

	for (i=0;i<NumTextures;i++)
		strncpy(current_tmap_list[i], TmapInfo[i].filename, 13);

	fwrite( current_tmap_list, 13, NumTextures, SaveFile );

	//============= 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
	fseek(  SaveFile, 0, SEEK_SET );  // Move to TOF
	fwrite( &group_fileinfo, sizeof(group_fileinfo), 1, SaveFile );

	//==================== CLOSE THE FILE =============================
	fclose(SaveFile);

	return 0;

}

static char old_tmap_list[MAX_TEXTURES][13];
static short tmap_xlate_table[MAX_TEXTURES];

// -----------------------------------------------------------------------------
// Load group will:
//int med_load_group(char * filename)
int med_load_group( char *filename, short *vertex_ids, short *segment_ids, int *num_vertices, int *num_segments)
{
	int segnum, vertnum;
	char ErrorMessage[200];
	short tmap_xlate;
	int 	translate;
	char 	*temptr;
	int i, j; 
	segment tseg;
   vms_vector tvert;
	CFILE * LoadFile;

	LoadFile = cfopen( filename, CF_READ_MODE );
	if (!LoadFile)
	{
		sprintf( ErrorMessage, "ERROR: Unable to open %s\n", filename );
		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 (cfseek( LoadFile, 0, SEEK_SET ))
		Error( "Error seeking to 0 in group.c" );

	if (cfread( &group_top_fileinfo, sizeof(group_top_fileinfo),1,LoadFile )!=1)
		Error( "Error reading top_fileinfo in group.c" );

	// Check version number
	if (group_top_fileinfo.fileinfo_version < COMPATIBLE_VERSION )
	{
		sprintf( ErrorMessage, "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 (MessageBox( -2, -2, 2, ErrorMessage, "Forget it", "Try anyway" )==1)
		{
			cfclose( LoadFile );
			return 1;
		}

		MessageBox( -2, -2, 1, "Good luck!", "I need it" );
	}

	// Now, Read in the fileinfo

	if (cfseek( LoadFile, 0, SEEK_SET ))
		Error( "Error seeking to 0b in group.c" );

	if (cfread( &group_fileinfo, group_top_fileinfo.fileinfo_sizeof,1,LoadFile )!=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 (cfseek( LoadFile,group_fileinfo.header_offset, SEEK_SET ))
			Error( "Error seeking to header_offset in group.c" );

		if (cfread( &group_header, group_fileinfo.header_size,1,LoadFile )!=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 (cfseek( LoadFile,group_fileinfo.editor_offset, SEEK_SET ))
			Error( "Error seeking to editor_offset in group.c" );

		if (cfread( &group_editor, group_fileinfo.editor_size,1,LoadFile )!=1)
			Error( "Error reading group_editor in group.c" );

	}

	//===================== READ VERTEX INFO ==========================

	if ( (group_fileinfo.vertex_offset > -1) && (group_fileinfo.vertex_howmany > 0))
	{
		if (cfseek( LoadFile,group_fileinfo.vertex_offset, SEEK_SET ))
			Error( "Error seeking to vertex_offset in group.c" );

			for (i=0;i<group_header.num_vertices;i++) {

				if (cfread( &tvert, sizeof(tvert),1,LoadFile )!=1)
					Error( "Error reading tvert in group.c" );
				vertex_ids[i] = med_create_duplicate_vertex( &tvert ); 
				//mprintf((0, "vertex %d created from original %d\n", vertex_ids[i], i));
			}

		}

	//==================== READ SEGMENT INFO ===========================

	if ( (group_fileinfo.segment_offset > -1) && (group_fileinfo.segment_howmany > 0))
	{
		if (cfseek( LoadFile,group_fileinfo.segment_offset, SEEK_SET ))
			Error( "Error seeking to segment_offset in group.c" );

		for (i=0;i<group_header.num_segments;i++) {
			if (cfread( &tseg, sizeof(segment),1,LoadFile )!=1)
				Error( "Error reading tseg in group.c" );
				
			segment_ids[i] = get_free_segment_number();
			Segments[segment_ids[i]] = tseg; 
			Segments[segment_ids[i]].objects = -1;

			fuelcen_activate( &Segments[segment_ids[i]], Segments[segment_ids[i]].special );
			}

		for (i=0;i<group_header.num_segments;i++) {
			// Fix vertices
			for (j=0;j<MAX_VERTICES_PER_SEGMENT;j++) {
				vertnum = vertex_ids[Segments[segment_ids[i]].verts[j]];
				Segments[segment_ids[i]].verts[j] = vertnum;
				}

			// Fix children and walls.
			for (j=0;j<MAX_SIDES_PER_SEGMENT;j++) {
				Segments[segment_ids[i]].sides[j].wall_num = -1;
				if (IS_CHILD(Segments[segment_ids[i]].children[j])) {
					segnum = segment_ids[Segments[segment_ids[i]].children[j]];
					Segments[segment_ids[i]].children[j] = segnum;
					} 
				//Translate textures.
				if (translate == 1) {
					int	temp;
					tmap_xlate = Segments[segment_ids[i]].sides[j].tmap_num;
					Segments[segment_ids[i]].sides[j].tmap_num = tmap_xlate_table[tmap_xlate];
					temp = Segments[segment_ids[i]].sides[j].tmap_num2;
					tmap_xlate = temp & 0x3fff;			// strip off orientation bits
					if (tmap_xlate != 0)
						Segments[segment_ids[i]].sides[j].tmap_num2 = temp & (!0x3fff) | tmap_xlate_table[tmap_xlate];	// mask on original orientation bits
					}
				}
			}
	}
	
	//===================== READ TEXTURE INFO ==========================

	if ( (group_fileinfo.texture_offset > -1) && (group_fileinfo.texture_howmany > 0))
	{
		if (cfseek( LoadFile, group_fileinfo.texture_offset, SEEK_SET ))
			Error( "Error seeking to texture_offset in gamemine.c" );

		for (i=0; i< group_fileinfo.texture_howmany; i++ )
		{
			if (cfread( &old_tmap_list[i], group_fileinfo.texture_sizeof, 1, LoadFile )!=1)
				Error( "Error reading old_tmap_list[i] in gamemine.c" );
		}
	}

	//=============== GENERATE TEXTURE TRANSLATION TABLE ===============

	translate = 0;
	
	Assert (NumTextures < MAX_TEXTURES);
{
	hashtable ht;

	hashtable_init( &ht, NumTextures );

	// Remove all the file extensions in the textures list

	for (i=0;i<NumTextures;i++)	{
		temptr = strchr(TmapInfo[i].filename, '.');
		if (temptr) *temptr = '\0';
//		mprintf( (0, "Texture %d is '%s'\n", i, TmapInfo[i].filename ));
//		key_getch();
		hashtable_insert( &ht, TmapInfo[i].filename, i );
	}

	// For every texture, search through the texture list
	// to find a matching name.
	for (j=0;j<group_fileinfo.texture_howmany;j++) 	{
		// Remove this texture name's extension
		temptr = strchr(old_tmap_list[j], '.');
		if (temptr) *temptr = '\0';

		tmap_xlate_table[j] = hashtable_search( &ht,old_tmap_list[j]);
		if (tmap_xlate_table[j]	< 0 )
			tmap_xlate_table[j] = 0;
		if (tmap_xlate_table[j] != j ) translate = 1;
	}

	hashtable_free( &ht );
}


	//======================== CLOSE FILE ==============================
	cfclose( LoadFile );

	//========================= 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;

	*num_vertices = group_fileinfo.vertex_howmany;
	*num_segments = group_fileinfo.segment_howmany;
	warn_if_concave_segments();
	
	return 0;
}

char group_filename[128] = "*.GRP";

void checkforgrpext( char * f )
{
	int i;

	for (i=1; i<strlen(f); 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;
	}
}

//short vertex_list[MAX_VERTICES];


int SaveGroup()
{
	// Save group
	int i, s, v;
	char  ErrorMessage[200];
	byte	vertex_list[MAX_VERTICES];

	if (Current_group == -1)
		{
		sprintf( ErrorMessage, "ERROR: No current group." );
		MessageBox( -2, -2, 1, ErrorMessage, "Ok" );
 		return 0;
		}

	for (v=0; v<=Highest_vertex_index; v++) {
		vertex_list[v] = 0;
	}

	//	Make a list of all vertices in group.
	for (s=0; s<GroupList[Current_group].num_segments; s++)
		for (v=0; v<MAX_VERTICES_PER_SEGMENT; v++) {
			vertex_list[Segments[GroupList[Current_group].segments[s]].verts[v]] = 1;
		}	

	v=0;
	for (i=0; i<=Highest_vertex_index; i++) 
		if (vertex_list[i] == 1) { 
			GroupList[Current_group].vertices[v++] = i;
		}
	GroupList[Current_group].num_vertices = v;
	//mprintf((0, "Saving %d vertices, %d segments\n", GroupList[Current_group].num_vertices, GroupList[Current_group].num_segments));
	med_save_group("TEMP.GRP", &GroupList[Current_group].vertices, &GroupList[Current_group].segments, 
		GroupList[Current_group].num_vertices, GroupList[Current_group].num_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, 
					GroupList[Current_group].num_vertices, GroupList[Current_group].num_segments))
			return 0;
		mine_changed = 0;
	}
	
	return 1;
}


int LoadGroup()
{
	int x;

	if (Num_groups == MAX_GROUPS)
		{
		x = 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,
					 &GroupList[Current_group].num_vertices, &GroupList[Current_group].num_segments) ;
		//mprintf((0, "Loaded %d vertices, %d segments\n", GroupList[Current_group].num_vertices, GroupList[Current_group].num_segments));
		
	if (!med_move_group(0, Cursegp, Curside, 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;
}


int UngroupSegment( void )
{
	if (Cursegp->group == Current_group) {
	
		Cursegp->group = -1;
		delete_segment_from_group( Cursegp-Segments, Current_group );
	
	   Update_flags |= UF_WORLD_CHANGED;
	   mine_changed = 1;
	   diagnostic_message("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-Segments, Current_group );
	
	   Update_flags |= UF_WORLD_CHANGED;
	   mine_changed = 1;
	   diagnostic_message("Segment Added to Group %d.", Current_group);

		return 1;
	} else
	return 0;
}

int Degroup( void )
{
	int i;

//	GroupList[Current_group].num_segments = 0;
//	Groupsegp[Current_group] = 0;

	if (Num_groups==0) return 0;

	for (i=0; i<GroupList[Current_group].num_segments; i++)  
		delete_segment_from_group( GroupList[Current_group].segments[i], Current_group );

	  //	delete_segment_from_group( &Segments[GroupList[Current_group].segments[i]]-Segments, Current_group );

	for (i=Current_group;i<Num_groups-1;i++) 
		{
		GroupList[i] = GroupList[i+1];
		Groupsegp[i] = Groupsegp[i+1];
		}

	Num_groups--;

	GroupList[Num_groups].num_segments = 0;
	Groupsegp[Num_groups] = 0;
	
	if (Current_group > Num_groups-1) Current_group--;

	if (Num_groups == 0)
		Current_group = -1;

   if (Lock_view_to_cursegp)
       set_view_target_from_segment(Cursegp);
   Update_flags |= UF_WORLD_CHANGED;
   mine_changed = 1;
   diagnostic_message("Group UNgrouped.");

	return 1;
}

void 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");
}

void 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");
}

// Returns:
//	 0 = successfully selected
//  1 = bad group number
int select_group( int num )
{
	if ((num>=0) && (num<Num_groups))
		{
		Current_group = num;
		return 0;
		}
	else return 1;
}


//	-----------------------------------------------------------------------------
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, 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)
{
	int	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 != -1) {
		int	i;

		for (i=0; i<GroupList[Current_group].num_segments; i++)
			if (GroupList[Current_group].segments[i] == attach_seg)
				break;

		if (i != GroupList[Current_group].num_segments) {
			editor_status("Error -- Cannot copy group, attach side has a child (segment %i) attached.", Groupsegp[Current_group]->children[Groupside[Current_group]]);
			return 1;
		}
	}

	med_compress_mine();

	if (!med_copy_group(0, Cursegp, Curside, 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, 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, s, original_group;
	short	*gp;
	int	cur_num_segs;

	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 = 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;

//	if (Num_groups < MAX_GROUPS) {
//		Current_group = Num_groups;
//		Num_groups++;
//	} else
//		Current_group = 0;

	// mprintf((0, "Old group: "));
	// for (s=0; s<GroupList[original_group].num_segments; s++)
	// 	mprintf((0, "%3i ", GroupList[original_group].segments[s]));
	// mprintf((0, "\n"));
	
	//	Create a list of segments to copy.
	GroupList[Current_group].num_segments = 0;
	create_group_list(Markedsegp, &GroupList[Current_group].segments, &GroupList[Current_group].num_segments, Selected_segs, N_selected_segs);

	// mprintf((0, "New group: "));
	// for (s=0; s<GroupList[Current_group].num_segments; s++)
	// 	mprintf((0, "%3i ", GroupList[Current_group].segments[s]));
	// mprintf((0, "\n"));
	
	//	Now, scan the two groups, forming a group which consists of only those segments common to the two groups.
	gp = GroupList[Current_group].segments;
	cur_num_segs = GroupList[Current_group].num_segments;
	for (s=0; s<cur_num_segs; s++) {
		short	*gp1 = GroupList[original_group].segments;
		short	s0 = gp[s];
		int	s1;

		for (s1=0; s1<GroupList[original_group].num_segments; s1++)
			if (gp1[s1] == s0)
				break;				// If break executed, then segment found in both lists.

		//	If current segment was not found in both lists, remove it by copying the last segment over
		//	it and decreasing the number of segments.
		if (s1 == GroupList[original_group].num_segments) {
			gp[s] = gp[cur_num_segs];
			cur_num_segs--;
		}
	}

	//	Go through mine and seg group number of all segments which are in group
	//	All segments which were subtracted from group get group set to -1.
	mprintf((0, "In segments: "));
	for (s=0; s<cur_num_segs; s++) {
		Segments[GroupList[Current_group].segments[s]].group = Current_group;
		mprintf((0, "%2i ", GroupList[Current_group].segments[s]));
	}

	mprintf((0, "\nRemoved segments: "));
	for (s=0; s<=Highest_segment_index; s++) {
		int	t;
		if (Segments[s].group == Current_group) {
			for (t=0; t<cur_num_segs; t++)
				if (GroupList[Current_group].segments[t] == s)
					break;
			if (s == cur_num_segs) {
				Segments[s].group = -1;
				mprintf((0, "%2i ", s));
			}
		}
	}

	// mprintf((0, "Combined group: "));
	// for (s=0; s<GroupList[Current_group].num_segments; s++)
	// 	mprintf((0, "%3i ", GroupList[Current_group].segments[s]));
	// mprintf((0, "\n\n"));

	GroupList[Current_group].num_segments = cur_num_segs;

	// Replace Marked segment with Group Segment.
	Groupsegp[Current_group] = Markedsegp;
	Groupside[Current_group] = Markedside;

	for (x=0;x<GroupList[Current_group].num_segments;x++)
		Segments[GroupList[Current_group].segments[x]].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 = 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].num_segments = 0;
	create_group_list(Markedsegp, &GroupList[Current_group].segments, &GroupList[Current_group].num_segments, Selected_segs, 0);
	
	// Replace Marked segment with Group Segment.
	Groupsegp[Current_group] = Markedsegp;
	Groupside[Current_group] = Markedside;
//	Markedsegp = 0;
//	Markedside = WBACK;

	for (x=0;x<GroupList[Current_group].num_segments;x++)
		Segments[GroupList[Current_group].segments[x]].group = Current_group;
	
	Update_flags |= UF_WORLD_CHANGED;
	mine_changed = 1;
	diagnostic_message("Group created.");

	return 1; 
				  
}

//	-----------------------------------------------------------------------------
// Deletes current group.
int DeleteGroup( void )
{
	int i, numsegs;

	autosave_mine(mine_filename);
		
	if (Num_groups==0) return 0;

	//mprintf((0, "num_segments = %d\n", GroupList[Current_group].num_segments));

	numsegs = GroupList[Current_group].num_segments;
	
	for (i=0; i<numsegs; i++) {
		med_delete_segment(&Segments[GroupList[Current_group].segments[0]]);
	}

	for (i=Current_group;i<Num_groups-1;i++) {
		GroupList[i] = GroupList[i+1];
		Groupsegp[i] = Groupsegp[i+1];
	}

	Num_groups--;
	GroupList[Num_groups].num_segments = 0;
	Groupsegp[Num_groups] = 0;

	if (Current_group > Num_groups-1) Current_group--;

	if (Num_groups==0)
		Current_group = -1;

	strcpy(undo_status[Autosave_count], "Delete Group UNDONE.");
   if (Lock_view_to_cursegp)
       set_view_target_from_segment(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;
	   strcpy(undo_status[Autosave_count], "Mark Group Segment UNDONE.");
		mine_changed = 1;
		return 1;
		}
	else return 0;
}
