/**********************************************************************
This file is part of Crack dot Com's free source code release of Golgotha.
for information about compiling & licensing issues visit this URL
 If that doesn't help, contact Jonathan Clark at 
  golgotha_source@usa.net (Subject should have "GOLG" in it) 
***********************************************************************/

#include "objs/explode_model.hh"
#include "objs/model_id.hh"
#include "math/pi.hh"
#include "math/trig.hh"
#include "math/angle.hh"
#include "g1_rand.hh"
#include "resources.hh"
#include "saver.hh"
#include "g1_render.hh"
#include "r1_clip.hh"
#include "r1_api.hh"
#include "g1_tint.hh"
#include "object_definer.hh"
#include "time/profile.hh"
#include "map_man.hh"
#include "map.hh"
#include "objs/particle_emitter.hh"
#include "tick_count.hh"
#include "draw_context.hh"

i4_profile_class pf_exp_model_setup("explode_model setup");
i4_profile_class pf_exp_model_think("explode_model think");
i4_profile_class pf_exp_model_draw("explode_model draw");

g1_object_definer
g1_explode_model_def("explode_model");

inline void check_on_map(const i4_3d_vector &v)
{
  int vx=i4_f_to_i(v.x), vy=i4_f_to_i(v.y);
  if (vx<0 || vy<0 || vx>=g1_get_map()->width() || vy>=g1_get_map()->height())
    i4_error("off map");
}



g1_explode_model_class::g1_explode_model_class(g1_object_type id,
                                               g1_loader_class *fp)
  : g1_object_class(id,fp)
{
  //does not save or load. too complicated and not really necessary
  vertices = 0;
  num_vertices = 0;

  faces = 0;
  num_faces = 0;
}

void g1_explode_model_class::save(g1_saver_class *fp)
{
  g1_object_class::save(fp);
}

g1_explode_model_class::~g1_explode_model_class()
{
  if (vertices)
    i4_free(vertices);
  
  if (faces)
    i4_free(faces);
}  


void g1_explode_model_class::grab_model(g1_object_class *o, i4_3d_vector &obj_center)
{
  g1_quad_object_class *base_m = o->draw_params.model;

  //face/vertex setup. count all the faces and vertices in base and sub-objects
    
  num_faces    = base_m->num_quad;
  num_vertices = 0;

  obj_center=i4_3d_vector(0,0,0);
  
  int i,j;
  for (i=0; inum_quad; i++)
    num_vertices += base_m->quad[i].num_verts();

  //allocate space
  faces    = (g1_explode_face_class *)   i4_malloc(sizeof(g1_explode_face_class)   * num_faces,"");
  vertices = (g1_explode_vertex_class *)i4_malloc(sizeof(g1_explode_vertex_class)*num_vertices,"");

  //copy information. this is ugly, has to use transforms
  //to get everything into world space, etc..  
  
  i4_transform_class base_transform,mini_transform,spare_transform,*cur_transform;

  //make sure there is a transform ready for the base model
  o->calc_world_transform(g1_render.frame_ratio, &base_transform);

  cur_transform = &base_transform;      
  
  g1_quad_object_class    *m = base_m;
  g1_explode_face_class   *f = faces;
  g1_explode_vertex_class *v = vertices;
  
  sw32 vertex_count = 0;  
  sw32 k = 0;

  sw32 total_face_count=0;

  
  //  while (m)
  {
    g1_vert_class *src_verts=m->get_verts(o->draw_params.animation, o->draw_params.frame);
    
    for (i=0; inum_quad; i++, f++)
    {
      memcpy(f->indices, m->quad[i].vertex_ref, sizeof(f->indices));

      i4_3d_vector center(0,0,0);
      int tv=f->num_verts();
      for (j=0; jtransform(src_verts[m->quad[i].vertex_ref[j]].v, t_vert);

        center += t_vert;
        v[j].v          = t_vert;
        v[j].s          = m->quad[i].u[j];
        v[j].t          = m->quad[i].v[j];
        
        f->indices[j] = vertex_count++;
      }
     
      center/=(float)tv;                 // center is the average position of verts
      f->pos = f->l_pos = center;        // set 'position' of face to center location
      for (j=0; jnum_quad)
      obj_center/=(float)m->num_quad;
  }
}

void g1_explode_model_class::setup(g1_object_class *source_obj,
                                   const i4_3d_vector ¢er,
                                   g1_explode_params &_params)
{
  x=lx=center.x;
  y=ly=center.y;
  h=lh=center.z;

  if (x<0 || y<0 || x>=g1_get_map()->width() || y>=g1_get_map()->height())
    request_remove();
  else
  {
    i4_3d_vector c;
    grab_model(source_obj, c);
    params=_params;
    
    if (center.x==0)
    {
      x=lx=c.x;
      y=ly=c.y;
      h=lh=c.z;
    }  

      
   
    int i;
    for (i=0; i=params.t_stages)
    {
      unoccupy_location();
      request_remove();
      return ;
    }
  }

  params.stages[params.current_stage].ticks--;
  float force=params.stages[params.current_stage].force;
  float air_friction=params.stages[params.current_stage].air_friction;
  float f;
  i4_3d_vector d;
  
  for (int i=0; ipos.x-x, face->pos.y-y, face->pos.z-h);
      float d_len=d.length();
      d/=d_len;
    
      f=force/(d_len*d_len);
      d*=f;
      d.z-=params.gravity;
    }

    if (params.stages[params.current_stage].type==G1_APPLY_SING || (((g1_tick_counter+i)&31)==0))
    {
      
      float xr=(g1_float_rand(9)-0.5) *0.2;
      float yr=(g1_float_rand(3)-0.5) *0.2;
      float zr=(g1_float_rand(4)-0.5) *0.2;



      faces[i].angular_vel.x = xr;
      faces[i].angular_vel.y = yr;
      faces[i].angular_vel.z = zr;
    }
    

    face->vel *= air_friction;
    face->vel += d;
    face->l_pos = face->pos;

    i4_3d_vector ray=face->vel;
    
    g1_object_class *hit;
//     if (g1_get_map()->check_non_player_collision(0xff, face->pos, ray, hit))
//       face->vel.z *=-0.5;      // not accurate, but who cares :)
  
    face->pos += ray;
    
    face->l_angles = face->angles;
    face->angles += face->angular_vel;   
    i4_normalize_angle(face->angles.x);
    i4_normalize_angle(face->angles.y);
    i4_normalize_angle(face->angles.z);
  }
  request_think();
}


i4_profile_class pf_exp_model_draw_transform("explode_model_draw::transform");
i4_profile_class pf_exp_model_draw_get_ambient("explode_model_draw::get_ambient");

r1_texture_ref g1_burnt_texture("solid_black");

void fast_transform(i4_transform_class *t, const i4_3d_vector &src, r1_3d_point_class &dst);

void g1_explode_model_class::draw(g1_draw_context_class *context)
{
  pf_exp_model_draw.start();

  pf_exp_model_draw_transform.start();

  i4_transform_class out,face_trans;  

  int i,j,k;

  i4_float scale_x = g1_render.scale_x;
  i4_float scale_y = g1_render.scale_y;    
  
  r1_vert t_vertices[2048];
  r1_vert clip_buf_1[64];
  r1_vert clip_buf_2[64];

  g1_explode_vertex_class *src_v = vertices;
  r1_vert *v                     = t_vertices;

  w8 ANDCODE = 0xFF;
  w8 ORCODE  = 0;    

  g1_explode_face_class *f = faces;  

  w8 vertex_count = 0;

  
  
  g1_render.r_api->use_texture(g1_burnt_texture.get(), 0, 0);

  
  for (i=0; il_angles.x, f->angles.x, g1_render.frame_ratio);
    interp_rot.y = i4_interpolate_angle(f->l_angles.y, f->angles.y, g1_render.frame_ratio);
    interp_rot.z = i4_interpolate_angle(f->l_angles.z, f->angles.z, g1_render.frame_ratio);
  
    face_trans.rotate_x_y_z(interp_rot.x,interp_rot.y,interp_rot.z,i4_T);

    face_trans.t.interpolate(f->l_pos, f->pos, g1_render.frame_ratio);
    
    out.multiply(*context->transform,face_trans);
     
    w8 face_and = 0xFF;    
    g1_explode_vertex_class *src_face_v = src_v;
    r1_vert *face_v                     = v;

    int tv=faces[i].num_verts();
    for (j=0; jv, v->v);

      v->v.x *= scale_x;
      v->v.y *= scale_y;
      
      w8 code = r1_calc_outcode(v);
      ANDCODE &= code;
      ORCODE  |= code;
      
      face_and &= code;
      
      if (!code)
      {
        //valid point
        i4_float ooz = 1.f / v->v.z;
      
        v->px = v->v.x * ooz * g1_render.center_x + g1_render.center_x;
        v->py = v->v.y * ooz * g1_render.center_y + g1_render.center_y;
        v->w  = ooz;
      }
    }
    
    if (face_and==0)
    {
      for (j=0; jr = 0;
        face_v->g = 0;
        face_v->b = 0;
        face_v->a = 1.0;
        face_v->s = 0;
        face_v->t = 0;
      }
    }
    else
      vertex_count += tv;
  }

  pf_exp_model_draw_transform.stop();

  if (ANDCODE)
  {
    pf_exp_model_draw.stop();
    return;
  }

  f = faces;

  for (i=0; inum_verts();

    if (ORCODE==0)
    {
      float nearest_w = 0;

      for (j=0; jindices[j]];
        
        float ooz = temp_vert->w;
        
        if (ooz > nearest_w)
          nearest_w=ooz;
      }
        
      g1_render.r_api->render_poly(tv, t_vertices+f->indices[0]);
    }
    else
    {
      sw32 num_poly_verts = tv;
        
      r1_vert temp_buf_1[64];
      r1_vert temp_buf_2[64];
      r1_vert *clipped_poly;
        
      clipped_poly = g1_render.r_api->clip_poly(&num_poly_verts,
                                         t_vertices,
                                         (w16 *)f->indices,
                                         temp_buf_1,
                                         temp_buf_2,
                                         R1_CLIP_NO_CALC_OUTCODE);
        
      if (clipped_poly && num_poly_verts>=3)      
      {
        float nearest_w = 0;

        r1_vert *temp_vert = clipped_poly;

        for (j=0; jv.z;

          temp_vert->w  = ooz;
          temp_vert->px = temp_vert->v.x * ooz * g1_render.center_x + g1_render.center_x;
          temp_vert->py = temp_vert->v.y * ooz * g1_render.center_y + g1_render.center_y;
  
          if (ooz > nearest_w)
            nearest_w=ooz;                            
        }                  
          

        g1_render.r_api->render_poly(num_poly_verts,clipped_poly);
      } 
    }
  }
  

  pf_exp_model_draw.stop();
}  

g1_explode_params::g1_explode_params()
{
  stages[0].setup(1,0.01, G1_APPLY_WHOLE);  // apply force for 2 ticks
  stages[1].setup(3, 0,    G1_APPLY_WHOLE);  // wait 20 ticks with no force to watch results
  stages[2].setup(2, 1.0, G1_APPLY_SING);  // wait 20 ticks with no force to watch results
  stages[3].setup(10, 0,  G1_APPLY_SING);  // wait 20 ticks with no force to watch results
  t_stages=4;
  current_stage=0;
  gravity=0.02;
  initial_vel=i4_3d_vector(0,0,0);
}