/*
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.
*/
/*
 * $Source: f:/miner/source/main/rcs/morph.c $
 * $Revision: 2.1 $
 * $Author: john $
 * $Date: 1995/02/27 18:26:33 $
 * 
 * Morphing code
 * 
 * $Log: morph.c $
 * Revision 2.1  1995/02/27  18:26:33  john
 * Fixed bug that was caused by externing Polygon_models, and I had
 * changed the type of it in polyobj.c, thus causing page faults.
 * 
 * Revision 2.0  1995/02/27  11:27:44  john
 * New version 2.0, which has no anonymous unions, builds with
 * Watcom 10.0, and doesn't require parsing BITMAPS.TBL.
 * 
 * Revision 1.35  1995/02/22  14:45:37  allender
 * remove anonymous unions from object structure
 * 
 * Revision 1.34  1995/01/14  19:16:52  john
 * First version of new bitmap paging code.
 * 
 * Revision 1.33  1995/01/03  20:38:36  john
 * Externed MAX_MORPH_OBJECTS
 * 
 * Revision 1.32  1994/11/17  15:34:04  matt
 * Attempt #4 to fix morph bug
 * 
 * Revision 1.31  1994/11/15  10:57:14  matt
 * Tried again to fix morph
 * 
 * Revision 1.30  1994/11/14  14:06:45  matt
 * Fixed stupid bug
 * 
 * Revision 1.29  1994/11/14  11:55:13  matt
 * Added divide overflow check
 * 
 * Revision 1.28  1994/09/26  17:28:14  matt
 * Made new multiple-object morph code work with the demo system
 * 
 * Revision 1.27  1994/09/26  15:39:56  matt
 * Allow multiple simultaneous morphing objects
 * 
 * Revision 1.26  1994/09/11  22:44:59  mike
 * quick on vecmat function.
 * 
 * Revision 1.25  1994/08/26  15:36:00  matt
 * Made eclips usable on more than one object at a time
 * 
 * Revision 1.24  1994/07/25  00:02:46  matt
 * Various changes to accomodate new 3d, which no longer takes point numbers
 * as parms, and now only takes pointers to points.
 * 
 * Revision 1.23  1994/07/12  12:39:58  matt
 * Revamped physics system
 * 
 * Revision 1.22  1994/06/28  11:54:51  john
 * Made newdemo system record/play directly to/from disk, so
 * we don't need the 4 MB buffer anymore.
 * 
 * Revision 1.21  1994/06/27  15:53:01  john
 * #define'd out the newdemo stuff
 * 
 * 
 * Revision 1.20  1994/06/16  14:30:19  matt
 * Moved morph record data call to reder routine
 * 
 * Revision 1.19  1994/06/16  13:57:23  matt
 * Added support for morphing objects in demos
 * 
 * Revision 1.18  1994/06/16  12:24:23  matt
 * Made robot lighting not mess with Lighting_on so robots now night
 * according to this variable.
 * 
 * Revision 1.17  1994/06/14  16:55:01  matt
 * Got rid of physics_object speed field
 * 
 * Revision 1.16  1994/06/08  21:16:29  matt
 * Made objects spin while morphing
 * 
 * Revision 1.15  1994/06/08  18:21:53  matt
 * Made morphing objects light correctly
 * 
 * Revision 1.14  1994/06/07  16:50:49  matt
 * Made object lighting work correctly; changed name of Ambient_light to
 * Dynamic_light; cleaned up polygobj object rendering a little.
 * 
 * Revision 1.13  1994/06/01  16:33:59  yuan
 * Fixed bug.
 * 
 * 
 * Revision 1.12  1994/06/01  16:29:08  matt
 * If morph_frame called on object this isn't the morph object, kill it.
 * 
 * Revision 1.11  1994/06/01  12:46:34  matt
 * Added needed include
 * 
 * Revision 1.10  1994/05/31  22:12:41  matt
 * Set lighting for morph objects
 * Don't let another object start morph while one is morphing, unless
 * that one dies.
 * 
 * Revision 1.9  1994/05/31  18:49:53  john
 * Took out debugging printf's that Matt left in.
 * 
 * Revision 1.8  1994/05/30  22:50:22  matt
 * Added morph effect for robots
 * 
 * 
 * 
 */


#pragma off (unreferenced)
static char rcsid[] = "$Id: morph.c 2.1 1995/02/27 18:26:33 john Exp $";
#pragma on (unreferenced)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "texmap.h"
#include "error.h"

#include "inferno.h"
#include "morph.h"
#include "polyobj.h"
#include "game.h"
#include "lighting.h"
#include "newdemo.h"
#include "piggy.h"

#include "mono.h"
#include "bm.h"

morph_data morph_objects[MAX_MORPH_OBJECTS];

//returns ptr to data for this object, or NULL if none
morph_data *find_morph_data(object *obj)
{
	int i;

	#ifdef NEWDEMO
	if (Newdemo_state == ND_STATE_PLAYBACK) {
		morph_objects[0].obj = obj;
		return &morph_objects[0];
	}
	#endif

	for (i=0;i<MAX_MORPH_OBJECTS;i++)
		if (morph_objects[i].obj == obj)
			return &morph_objects[i];

	return NULL;
}


//takes pm, fills in min & max
find_min_max(polymodel *pm,int submodel_num,vms_vector *minv,vms_vector *maxv)
{
	ushort nverts;
	vms_vector *vp;
	ushort *data,type;

	data = (ushort *) &pm->model_data[pm->submodel_ptrs[submodel_num]];

	type = *data++;

	Assert(type == 7 || type == 1);

	nverts = *data++;

	if (type==7)
		data+=2;		//skip start & pad

	vp = (vms_vector *) data;

	*minv = *maxv = *vp++; nverts--;

	while (nverts--) {
		if (vp->x > maxv->x) maxv->x = vp->x;
		if (vp->y > maxv->y) maxv->y = vp->y;
		if (vp->z > maxv->z) maxv->z = vp->z;

		if (vp->x < minv->x) minv->x = vp->x;
		if (vp->y < minv->y) minv->y = vp->y;
		if (vp->z < minv->z) minv->z = vp->z;

		vp++;
	}

}

#define MORPH_RATE (f1_0*3)

fix morph_rate = MORPH_RATE;

init_points(polymodel *pm,vms_vector *box_size,int submodel_num,morph_data *md)
{
	ushort nverts;
	vms_vector *vp;
	ushort *data,type;
	int i;

	//printf("initing %d ",submodel_num);

	data = (ushort *) &pm->model_data[pm->submodel_ptrs[submodel_num]];

	type = *data++;

	Assert(type == 7 || type == 1);

	nverts = *data++;

	md->n_morphing_points[submodel_num] = 0;

	if (type==7) {
		i = *data++;		//get start point number
		data++;				//skip pad
	}
	else
		i = 0;				//start at zero

	Assert(i+nverts < MAX_VECS);

	md->submodel_startpoints[submodel_num] = i;

	vp = (vms_vector *) data;

	while (nverts--) {
		fix k,dist;

		if (box_size) {
			fix t;

			k = 0x7fffffff;

			if (vp->x && f2i(box_size->x)<abs(vp->x)/2 && (t = fixdiv(box_size->x,abs(vp->x))) < k) k=t;
			if (vp->y && f2i(box_size->y)<abs(vp->y)/2 && (t = fixdiv(box_size->y,abs(vp->y))) < k) k=t;
			if (vp->z && f2i(box_size->z)<abs(vp->z)/2 && (t = fixdiv(box_size->z,abs(vp->z))) < k) k=t;

			if (k==0x7fffffff) k=0;

		}
		else
			k=0;

		vm_vec_copy_scale(&md->morph_vecs[i],vp,k);

		dist = vm_vec_normalized_dir_quick(&md->morph_deltas[i],vp,&md->morph_vecs[i]);

		md->morph_times[i] = fixdiv(dist,morph_rate);

		if (md->morph_times[i] != 0)
			md->n_morphing_points[submodel_num]++;

		vm_vec_scale(&md->morph_deltas[i],morph_rate);

		vp++; i++;

	}

	//printf("npoints = %d\n",n_morphing_points[submodel_num]);

}

update_points(polymodel *pm,int submodel_num,morph_data *md)
{
	ushort nverts;
	vms_vector *vp;
	ushort *data,type;
	int i;

	//printf("updating %d ",submodel_num);

	data = (ushort *) &pm->model_data[pm->submodel_ptrs[submodel_num]];

	type = *data++;

	Assert(type == 7 || type == 1);

	nverts = *data++;

	if (type==7) {
		i = *data++;		//get start point number
		data++;				//skip pad
	}
	else
		i = 0;				//start at zero

	vp = (vms_vector *) data;

	while (nverts--) {

		if (md->morph_times[i])		//not done yet

			if ((md->morph_times[i] -= FrameTime) <= 0) {
				md->morph_vecs[i] = *vp;
				md->morph_times[i] = 0;
				md->n_morphing_points[submodel_num]--;
			}
			else
				vm_vec_scale_add2(&md->morph_vecs[i],&md->morph_deltas[i],FrameTime);

		vp++; i++;
	}

	//printf("npoints = %d\n",n_morphing_points[submodel_num]);
}


//process the morphing object for one frame
do_morph_frame(object *obj)
{
	int i;
	polymodel *pm;
	morph_data *md;

	md = find_morph_data(obj);

	if (md == NULL) {					//maybe loaded half-morphed from disk
		obj->flags |= OF_SHOULD_BE_DEAD;		//..so kill it
		return;
	}

	pm = &Polygon_models[md->obj->rtype.pobj_info.model_num];

	//printf("morph_frame active = ");
	//for (i=0;i<pm->n_models;i++)
	//	printf("%d ",submodel_active[i]);
	//printf("\n");


	for (i=0;i<pm->n_models;i++)
		if (md->submodel_active[i]==1) {

			update_points(pm,i,md);

			if (md->n_morphing_points[i] == 0) {		//maybe start submodel
				int t;

				md->submodel_active[i] = 2;		//not animating, just visible

				md->n_submodels_active--;		//this one done animating

				for (t=0;t<pm->n_models;t++)
					if (pm->submodel_parents[t] == i) {		//start this one

						init_points(pm,NULL,t,md);
						md->n_submodels_active++;
						md->submodel_active[t] = 1;

					}
			}

		}

	if (!md->n_submodels_active) {			//done morphing!

		md->obj->control_type = md->morph_save_control_type;
		md->obj->movement_type = md->morph_save_movement_type;

		md->obj->render_type = RT_POLYOBJ;

		md->obj->mtype.phys_info = md->morph_save_phys_info;

		md->obj = NULL;
	}

}

vms_vector morph_rotvel = {0x4000,0x2000,0x1000};

init_morphs()
{
	int i;

	for (i=0;i<MAX_MORPH_OBJECTS;i++)
		morph_objects[i].obj = NULL;
}


//make the object morph
void morph_start(object *obj)
{
	polymodel *pm;
	vms_vector pmmin,pmmax;
	vms_vector box_size;
	int i;
	morph_data *md;

	for (i=0;i<MAX_MORPH_OBJECTS;i++)
		if (morph_objects[i].obj == NULL || morph_objects[i].obj->type==OBJ_NONE  || morph_objects[i].obj->signature!=morph_objects[i].Morph_sig)
			break;

	if (i==MAX_MORPH_OBJECTS)		//no free slots
		return;

	md = &morph_objects[i];

	Assert(obj->render_type == RT_POLYOBJ);

	md->obj = obj;
	md->Morph_sig = obj->signature;

	md->morph_save_control_type = obj->control_type;
	md->morph_save_movement_type = obj->movement_type;
	md->morph_save_phys_info = obj->mtype.phys_info;

	Assert(obj->control_type == CT_AI);		//morph objects are also AI objects

	obj->control_type = CT_MORPH;
	obj->render_type = RT_MORPH;
	obj->movement_type = MT_PHYSICS;		//RT_NONE;

	obj->mtype.phys_info.rotvel = morph_rotvel;

	pm = &Polygon_models[obj->rtype.pobj_info.model_num];

	find_min_max(pm,0,&pmmin,&pmmax);

	box_size.x = max(-pmmin.x,pmmax.x) / 2;
	box_size.y = max(-pmmin.y,pmmax.y) / 2;
	box_size.z = max(-pmmin.z,pmmax.z) / 2;

	for (i=0;i<MAX_VECS;i++)		//clear all points
		md->morph_times[i] = 0;

	for (i=1;i<MAX_SUBMODELS;i++)		//clear all parts
		md->submodel_active[i] = 0;

	md->submodel_active[0] = 1;		//1 means visible & animating

	md->n_submodels_active = 1;

	//now, project points onto surface of box

	init_points(pm,&box_size,0,md);

}

draw_model(polymodel *pm,int submodel_num,vms_angvec *anim_angles,fix light,morph_data *md)
{
	int i,mn;
	int facing;
	int sort_list[MAX_SUBMODELS],sort_n;


	//first, sort the submodels

	sort_list[0] = submodel_num;
	sort_n = 1;

	for (i=0;i<pm->n_models;i++)

		if (md->submodel_active[i] && pm->submodel_parents[i]==submodel_num) {

			facing = g3_check_normal_facing(&pm->submodel_pnts[i],&pm->submodel_norms[i]);

			if (!facing)

				sort_list[sort_n++] = i;

			else {		//put at start
				int t;

				for (t=sort_n;t>0;t--)
					sort_list[t] = sort_list[t-1];

				sort_list[0] = i;

				sort_n++;


			}

		}
	

	//now draw everything

	for (i=0;i<sort_n;i++) {

		mn = sort_list[i];

		if (mn == submodel_num) {
 			int i;

			for (i=0;i<pm->n_textures;i++)		{
				texture_list_index[i] = ObjBitmaps[ObjBitmapPtrs[pm->first_texture+i]];
				texture_list[i] = &GameBitmaps[ObjBitmaps[ObjBitmapPtrs[pm->first_texture+i]].index];
			}

#ifdef PIGGY_USE_PAGING			
			// Make sure the textures for this object are paged in...
			piggy_page_flushed = 0;
			for (i=0;i<pm->n_textures;i++)	
				PIGGY_PAGE_IN( texture_list_index[i] );
			// Hmmm... cache got flushed in the middle of paging all these in,
			// so we need to reread them all in.
			if (piggy_page_flushed)	{
				piggy_page_flushed = 0;
				for (i=0;i<pm->n_textures;i++)	
					PIGGY_PAGE_IN( texture_list_index[i] );
			}
			// Make sure that they can all fit in memory.
			Assert( piggy_page_flushed == 0 );
#endif


			g3_draw_morphing_model(&pm->model_data[pm->submodel_ptrs[submodel_num]],texture_list,anim_angles,light,&md->morph_vecs[md->submodel_startpoints[submodel_num]]);

		}
		else {

			vms_matrix orient;

			vm_angles_2_matrix(&orient,&anim_angles[mn]);

			g3_start_instance_matrix(&pm->submodel_offsets[mn],&orient);

			draw_model(pm,mn,anim_angles,light,md);

			g3_done_instance();

		}
	}

}

void draw_morph_object(object *obj)
{
//	int save_light;
	polymodel *po;
	fix light;
	morph_data *md;

	md = find_morph_data(obj);
	Assert(md != NULL);

	Assert(obj->rtype.pobj_info.model_num < N_polygon_models);

	po=&Polygon_models[obj->rtype.pobj_info.model_num];

	light = compute_object_light(obj,NULL);

	g3_start_instance_matrix(&obj->pos,&obj->orient);
	g3_set_interp_points(robot_points);

	draw_model(po,0,obj->rtype.pobj_info.anim_angles,light,md);

	g3_done_instance();

	#ifdef NEWDEMO
	if (Newdemo_state == ND_STATE_RECORDING)
		newdemo_record_morph_frame(md);
	#endif

}

