text: /*
  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: Smoke:miner:source:main::RCS:morph.c $
   * $Revision: 1.5 $
   * $Author: allender $
   * $Date: 1995/08/23 21:36:10 $
   * 
   * Morphing code
   * 
   * $Log: morph.c $
   * Revision 1.5  1995/08/23  21:36:10  allender
   * mcc compiler warnings fixed
   *
   * Revision 1.4  1995/08/12  11:34:19  allender
   * removed #ifdef NEWDEMO -- always in
   *
   * Revision 1.3  1995/07/28  15:39:51  allender
   * removed fixdiv thing
   *
   * Revision 1.2  1995/07/28  15:21:23  allender
   * inverse magnitude fixup thing
   *
   * Revision 1.1  1995/05/16  15:28:05  allender
   * Initial revision
   *
   * 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 1.5 1995/08/23 21:36:10 allender 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;
  
  	if (Newdemo_state == ND_STATE_PLAYBACK) {
  		morph_objects[0].obj = obj;
  		return &morph_objects[0];
  	}
  
  	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
  void 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};
  
  void 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();
  
  	if (Newdemo_state == ND_STATE_RECORDING)
  		newdemo_record_morph_frame(md);
  
  }
  