//////////////////////////////////////////////////////////////////////
//
// Project  : BRender Pick Demo
//            Copyright (c) 1996 Argonaut Technologies Ltd.
//            All rights reserved.
//
// Author        : Sam Littlewood 
// Modifications : Steve Williams - Modified to work with new demo
//                 framework.
//
// File     : PICKDEMO.C
//
// Requires : BRender V1.3
//            Microsoft DirectX 3.0, Win95/NT or DOS
//                         
// Comments : Pick demo specific source.
//
//////////////////////////////////////////////////////////////////////

#include "..\misc\br_demo.h"
#include "pickdemo.h"

#define PIXEL_COLOUR 232

// Actors for world, camera, and an object

static br_actor *observer, *world, *a;

static br_material *mats[100];

static br_pick_nearest pickNearest;

// BRender pixelmaps

static br_pixelmap *screen, *depth_buffer ;
static br_pixelmap *back_buffer, *palette ;

// Information abount the cursor

static br_boolean CursorFlag = BR_FALSE;
static br_int_32 CursorX = 0, CursorY = 0;
static br_uint_32 CursorColour ;

// Picking material

static br_material *PickMaterial;



br_error BrDemo_Render_Loop()
{
   // Clear the buffers

   if ( !back_buffer )
	   return BRE_OK ;
     
   BrPixelmapFill(back_buffer,0);
   BrPixelmapFill(depth_buffer,0xFFFFFFFF);

   // Render 3D world

   BrZbSceneRender(world,observer,back_buffer,depth_buffer);

   // Render Cursor if visible

   if ( CursorFlag ) 
   {
       br_int_32 x, y ;
	   br_int_32 x_pos, y_pos ;
	   
       x = CursorX ;
       y = CursorY;               
	 
       MouseReadAbsolute( screen, &x, &y );

	   x = (br_uint_32) ( (float) x * ( (float) back_buffer->width / (float) screen->width ) );
	   y = (br_uint_32) ( (float) y * ( (float) back_buffer->height / (float) screen->height ) );
	   
	   x_pos = (x - back_buffer->origin_x) ;
	   y_pos = (y - back_buffer->origin_y) ;

       BrPixelmapLine(back_buffer, x_pos+2, y_pos,   x_pos+6, y_pos,   CursorColour );
       BrPixelmapLine(back_buffer, x_pos-2, y_pos,   x_pos-6, y_pos,   CursorColour );
       BrPixelmapLine(back_buffer, x_pos,   y_pos+2, x_pos,   y_pos+6, CursorColour );
       BrPixelmapLine(back_buffer, x_pos,   y_pos-2, x_pos,   y_pos-6, CursorColour );

       if(pickNearest.model) 
       {
	  dprintf(0,0,"Actor:    %s",pickNearest.actor->identifier?pickNearest.actor->identifier:"<NULL>");
	  dprintf(0,1,"Model:    %s",pickNearest.model->identifier?pickNearest.model->identifier:"<NULL>");
	  dprintf(0,2,"Material: %s",pickNearest.material->identifier?pickNearest.material->identifier:"<NULL>");
	  dprintf(0,3,"t = %f",BrScalarToFloat(pickNearest.t));
      }
   }

   // Update display
   
   BrPixelmapDoubleBuffer(screen,back_buffer);

   // User controls

   if ( Control(world, observer, a) )
      return BRE_FAIL ;
      
   return BRE_OK ;
}
   


br_error BrDemo_ShutDown()
{
   // Close down

   BrPixelmapFree(depth_buffer);
   BrPixelmapFree(back_buffer);
   
   depth_buffer = NULL ;
   back_buffer = NULL ;

   BrZbEnd();

   return BRE_OK ;
}

   
br_error BrDemo_InitApp()
{
   br_error r ;
   br_pixelmap *pShade ;
   br_camera *camera;
   int n;

   // Initialise BRender

   r = BrDemo_Allocate_Pixelmaps( "Pick Demo", &screen, &back_buffer,
				       &depth_buffer, &palette, "winstd.pal" );

   if ( r != BRE_OK )
	   return r ;

   screen->origin_x = screen->width/2;
   screen->origin_y = screen->height/2;
   back_buffer->origin_x = back_buffer->width/2;
   back_buffer->origin_y = back_buffer->height/2;

   // Make tables, maps and models autoload
   
   BrMapFindHook(BrMapFindFailedLoad);
   BrModelFindHook(BrModelFindFailedLoad);
   
   pShade = NULL ;

   switch ( screen->type )
   {
       case BR_PMT_INDEX_8 : pShade = BrPixelmapLoad("winsh8.tab");
                             CursorColour = 228 ;
			                    break ;

       case BR_PMT_RGB_555 : pShade = BrPixelmapLoad("winsh15.tab");
                             CursorColour = 0xFFFF ;
                             break ;

       case BR_PMT_RGB_565 : pShade = BrPixelmapLoad("winsh16.tab");
                             CursorColour = 0xFFFF ;
                             break ;
   }

   if ( !pShade )
   {
	   BR_ERROR0("Could not load shade table");
	   return BRE_FAIL ;
   }

   BrTableAdd(pShade);

   BrZbBegin(screen->type,BR_PMT_DEPTH_16);

   // Load materials from pickdemo.mat

   n = BrFmtScriptMaterialLoadMany( PICK_MAT_FILE, mats, BR_ASIZE(mats) );
   
   if ( n == 0 )
   {
	   BR_ERROR("Could not load any materials");
	   return BRE_FAIL ;
   }

   BrMaterialAddMany(mats,n);

   // Remember a pointer to a material for testing pick2d

   PickMaterial = BrMaterialFind("green");

   // Build the world
   
   world = BrActorAllocate(BR_ACTOR_NONE,NULL);

   // BrDemo_SetRootActor lets the demo framework know which 
   // actor to enumerate when turning on/off actor based properties.
	
   BrDemo_SetRootActor(world);

   observer = BrActorAdd(world,BrActorAllocate(BR_ACTOR_CAMERA,NULL));
   camera = (br_camera *)(observer->type_data);

   camera->type = BR_CAMERA_PERSPECTIVE ;
   camera->field_of_view = BR_ANGLE_DEG(55.0);
   camera->hither_z = BR_SCALAR(0.01); 
   camera->yon_z = BR_SCALAR(50.0); 
   camera->aspect = BR_SCALAR(1.6); 

   BrMatrix34Translate(&observer->t.t.mat,BR_SCALAR(0.0),BR_SCALAR(0.0),BR_SCALAR(3.0));

   BrLightEnable(BrActorAdd(world,BrActorAllocate(BR_ACTOR_LIGHT,NULL)));

   a = BrActorAdd(world,BrActorAllocate(BR_ACTOR_MODEL,NULL));

   //a->model = BrModelLoad("torus.dat");
   a->model = BrModelLoad("teapot.dat");

   a->material = BrMaterialFind("Earth"); 
   
   if ( a->model )
   {
      a->model->flags |= BR_MODF_UPDATEABLE;
      BrModelAdd(a->model);
   }

   return BRE_OK ;
}


/*
 * Read mouse and keyboard for control info.
 *
 * Return true if user wants to quit
 */

int Control(br_actor *world, br_actor *observer, br_actor *target)
{
   if(CursorFlag)
      ControlCursor(world, observer, target);
   else
      ControlObject(target);
   
   if(DOSKeyTest(SC_Q,0,REPT_FIRST_DOWN))
      return 1;

   if(DOSKeyTest(SC_SPACE,0,REPT_FIRST_DOWN))
   {
      CursorFlag = !CursorFlag;
   }

   return 0;
}



/*
 * Read the mouse to control an actor's translation and rotation
 */
void ControlObject(br_actor *target)
{
   static br_int_32 mouse_x,mouse_y;
   br_uint_32 mouse_buttons;
   br_matrix34 mat_target, mat_roll;
   br_scalar tx,ty,tz;

   DOSMouseRead(&mouse_x,&mouse_y,&mouse_buttons);

   // Mouse controls
   
   if ( ( mouse_buttons & BR_MSM_BUTTONL ) &&
       ( mouse_buttons & BR_MSM_BUTTONR ) )
   {
      // Drag object around in x/y plane

      tx = BR_MUL(BR_SCALAR(mouse_x),MSCALE);
      tz = BR_MUL(BR_SCALAR(mouse_y),MSCALE);

      BrMatrix34PostTranslate(&target->t.t.mat,tx,BR_SCALAR(0.0),tz);
      
   } 
   else if( mouse_buttons & BR_MSM_BUTTONL ) 
   {
      // Move object along z axis

      tx =  BR_MUL(BR_SCALAR(mouse_x),MSCALE);
      ty = -BR_MUL(BR_SCALAR(mouse_y),MSCALE);

      BrMatrix34PostTranslate(&target->t.t.mat,tx,ty,BR_SCALAR(0.0));
   } 
   else if ( mouse_buttons & BR_MSM_BUTTONR ) 
   {
      // Rotate object via rolling ball interface

      BrMatrix34RollingBall(&mat_roll,mouse_x,-mouse_y, ROLLING_BALL_SENSITIVITY );

      // Only modify the top 3x3

      BrMatrix34Copy(&mat_target, &target->t.t.mat);
      Matrix33Mul(&target->t.t.mat,&mat_target, &mat_roll);
   }

   mouse_x = 0;
   mouse_y = 0;
}



int BR_CALLBACK BrPickNearestModelCallback(
      br_model *model,
      br_material *material,
      br_vector3 *ray_pos, br_vector3 *ray_dir,
      br_scalar t,
      int f,
      int e,
      int v,
      br_vector3 *p,
      br_vector2 *map,
      br_pick_nearest *pn)
{
   if ( t < pn->t ) 
   {
      pn->t = t;
      pn->actor = pn->temp_actor;
      pn->model = model;
      pn->material = material;
      pn->point = *p;
      pn->face = f;
      pn->edge = e;
      pn->vertex = v;
      pn->map = *map;
   }

   return 0;
}


// Test callback

int BR_CALLBACK BrPickNearestCallback(
      br_actor *actor,
      br_model *model,
      br_material *material,
      br_vector3 *ray_pos, br_vector3 *ray_dir,
      br_scalar t_near, br_scalar t_far,
      br_pick_nearest *pn)
{
   pn->temp_actor = actor;

   BrModelPick2D(model, material, ray_pos, ray_dir, t_near, t_far,
      (br_modelpick2d_cbfn *)BrPickNearestModelCallback, pn);

   return 0;
}



// Read mouse to control cursor position

void ControlCursor(br_actor *world, br_actor *observer, br_actor *target)
{
   br_uint_32 mouse_buttons;
   br_int_32 x_pos, y_pos ;

   DOSMouseRead(&CursorX,&CursorY,&mouse_buttons);
   MouseReadAbsolute( screen, &CursorX, &CursorY );

   CursorX = (br_uint_32) ( (float) CursorX * ( (float) back_buffer->width / (float) screen->width ) );
   CursorY = (br_uint_32) ( (float) CursorY * ( (float) back_buffer->height / (float) screen->height ) );
   
   x_pos = CursorX - back_buffer->origin_x ;
   y_pos = CursorY - back_buffer->origin_y ;

   if ( mouse_buttons ) 
   {
      pickNearest.model = NULL;
      pickNearest.t = BR_SCALAR_MAX;

      BrScenePick2D( world, observer, back_buffer, x_pos, y_pos,
	 (br_pick2d_cbfn *) BrPickNearestCallback,&pickNearest);

      if ( pickNearest.model ) 
      {
	 if ( ( mouse_buttons & BR_MSM_BUTTONL ) &&
			  ( mouse_buttons & BR_MSM_BUTTONR ) )
	 {
	    if ( pickNearest.actor->render_style == BR_RSTYLE_EDGES )
				pickNearest.actor->render_style = BR_RSTYLE_FACES ;
			else
				pickNearest.actor->render_style = BR_RSTYLE_EDGES ;
	 }

		 else if( mouse_buttons & BR_MSM_BUTTONL ) 
	 {
	    pickNearest.model->faces[pickNearest.face].material = PickMaterial;
	    BrModelUpdate(pickNearest.model,BR_MODU_ALL);
	 }

	 // Scribble in the texture ( if one is present )

	 else if ( mouse_buttons & BR_MSM_BUTTONR ) 
	 {
	    if(pickNearest.material && pickNearest.material->colour_map) {
	       br_vector2 t;
	       int u,v;

	       // Push point through map_transform

	       BrMatrix23ApplyP(&t,&pickNearest.map,&pickNearest.material->map_transform);
	       
			   t.v[0] = t.v[0] - BrScalarToInt(t.v[0]);
	       t.v[1] = t.v[1] - BrScalarToInt(t.v[1]);
	       
			   u = BrScalarToInt(BR_MUL(t.v[0],BrIntToScalar(pickNearest.material->colour_map->width)));
	       v = BrScalarToInt(BR_MUL(t.v[1],BrIntToScalar(pickNearest.material->colour_map->height)));

	       // Set the pixel (texture mappper ignores origin)

	       //BR_WARNING1( "Colour_map=%p",pickNearest.material->colour_map );
			   
			   BrPixelmapPlot(pickNearest.material->colour_map,
		     u-pickNearest.material->colour_map->origin_x,
		     v-pickNearest.material->colour_map->origin_y, PIXEL_COLOUR );

			   BrMapUpdate( pickNearest.material->colour_map, BR_MAPU_ALL );
	    }
	 }
      }
   }
}



// Matrix multiply for top 3x3 only

#define A(x,y) A->m[x][y]
#define B(x,y) B->m[x][y]
#define C(x,y) C->m[x][y]

void Matrix33Mul(br_matrix34 *A, br_matrix34 *B, br_matrix34 *C)
{
   A(0,0) = BR_MAC3(B(0,0),C(0,0), B(0,1),C(1,0), B(0,2),C(2,0));
   A(0,1) = BR_MAC3(B(0,0),C(0,1), B(0,1),C(1,1), B(0,2),C(2,1));
   A(0,2) = BR_MAC3(B(0,0),C(0,2), B(0,1),C(1,2), B(0,2),C(2,2));

   A(1,0) = BR_MAC3(B(1,0),C(0,0), B(1,1),C(1,0), B(1,2),C(2,0));
   A(1,1) = BR_MAC3(B(1,0),C(0,1), B(1,1),C(1,1), B(1,2),C(2,1));
   A(1,2) = BR_MAC3(B(1,0),C(0,2), B(1,1),C(1,2), B(1,2),C(2,2));

   A(2,0) = BR_MAC3(B(2,0),C(0,0), B(2,1),C(1,0), B(2,2),C(2,0));
   A(2,1) = BR_MAC3(B(2,0),C(0,1), B(2,1),C(1,1), B(2,2),C(2,1));
   A(2,2) = BR_MAC3(B(2,0),C(0,2), B(2,1),C(1,2), B(2,2),C(2,2));
}


br_error BrDemo_OnResize()
{
	BrDemo_Refresh_Pointers( &screen, &back_buffer, &depth_buffer );
    return BRE_OK ;
}

void dprintf(int x, int y, char *fmt,...)
{
   char temp[256];
   va_list args;

   // Build output string

   va_start(args,fmt);
   BrVSprintf(temp,fmt,args);
   va_end(args);
   
   BrPixelmapText(back_buffer, 100 + (x * 4)-back_buffer->origin_x, 
		 (y * 6)-back_buffer->origin_y, 197, BrFontFixed3x5,  temp);
}
