//
// Copyright (c) 2000 by Tech Soft 3D, LLC.
// The information contained herein is confidential and proprietary to
// Tech Soft 3D, LLC., and considered a trade secret as defined under
// civil and criminal statutes.  Tech Soft 3D shall pursue its civil
// and criminal remedies in the event of unauthorized use or misappropriation
// of its trade secrets.  Use of this information by anyone other than
// authorized employees of Tech Soft 3D, LLC. is granted only under a
// written non-disclosure agreement, expressly prescribing the scope and
// manner of such use.
//
// $Header: /files/homes/master/cvs/hoops_master/docs_hoops/Hoops3DGS/prog_guide/examples/HOpObjectClash.cpp,v 1.2 2006-08-07 20:39:02 stage Exp $
//

#include <math.h>
#include <string.h>
#include <stdio.h>

#include "HTools.h"

#include "HBaseModel.h"
#include "HBaseView.h"
#include "HBaseOperator.h"
#include "HEventInfo.h"
#include "HOpObjectClash.h"
#include "HSelectionSet.h"

/* unofficial backdoor for getting profiling results */
HC_EXTERNAL void HC_CDECL HI_Show_Shell_Selection_Profile HC_PROTO (( char *results ));


/////////////////////////////////////////////////////////////////////////////
// HOpObjectClash
//
// Translate items in the selection list in direction of mouse movement
// parallel to camera target plane.
//
// Currently supports translation if item if it is a segment


HOpObjectClash::HOpObjectClash(HBaseView* view, int DoRepeat,int DoCapture) : 
	HOpObjectTranslate(view, DoRepeat, DoCapture)
{
	m_pClashSelection = NULL;
	m_TempSegKey = INVALID_KEY;
	m_SelectMode = HOOC_Shell;
}


HOpObjectClash::~HOpObjectClash()
{}


void HOpObjectClash::GrabSelectionSet()
{
	int i, size;
	HC_KEY object;
	char type[64];
	HSelectionSet *selection;

  	m_pClashSelection = m_pView->GetSelection();
	selection = new HSelectionSet(m_pView);
	selection->Init();
	m_pView->SetSelection( selection );
	HC_Open_Segment_By_Key( m_pView->GetSceneKey() );
		m_TempSegKey = HC_KOpen_Segment( "HOpObjectClash_temp" );
			HC_Set_Window( -1, 1, -1, 1 );
			HC_Set_Camera_By_Volume( "stretched", -1, 1, -1, 1 );
			HC_Set_Window_Pattern( "clear" );
		HC_Close_Segment();
	HC_Close_Segment();
	SetSelectMode( m_SelectMode );

	//turn off selectability of the selected objects
	size = m_pClashSelection->GetSize();
	for( i = 0 ; i < size ; i++ ) {
		object = m_pClashSelection->GetAt(i);
		HC_Show_Key_Type( object, type );
		if( streq( type, "segment" ) ) {
			HC_Open_Segment_By_Key( object );
				HC_Set_Selectability( "everything = off" );
			HC_Close_Segment();
		}
		else {
			m_pClashSelection->DeSelect( object );
		}
	}
}

void HOpObjectClash::RestoreSelectionSet()
{
	HC_KEY object;
	int i, size;
	HSelectionSet* selection;

	// tjh: it shouldn't be necessary to do this check, but I hit a null pointer 
	// here once (perhaps caused by two consecutive LButtonUp events?). I haven't 
	// seen it since, but we'll just be extra cautious.
	if( m_pClashSelection != NULL ) {
		//unset the selectability setting that we set during GrabSelectionSet
		size = m_pClashSelection->GetSize();
		for( i = 0 ; i < size ; i++ ) {
			object = m_pClashSelection->GetAt(i);
			HC_Open_Segment_By_Key( object );
				HC_UnSet_Selectability( );
			HC_Close_Segment();
		}
		selection = m_pView->GetSelection();
		selection->DeSelectAll();
		delete selection;
		m_pView->SetSelection( m_pClashSelection );
		m_pClashSelection = NULL;
		HC_Delete_By_Key( m_TempSegKey );
		m_TempSegKey = INVALID_KEY;
	}
	else {
		m_pView->GetSelection()->DeSelectAll();
	}
	m_pView->Update();
}




HBaseOperator * HOpObjectClash::Clone()
{
	return (HBaseOperator *)new HOpObjectClash(m_pView);
}


const char * HOpObjectClash::GetName() { return "Clash Detect Object"; }


int HOpObjectClash::GetScreenVolume( HC_KEY seg, HPoint &min, HPoint &max ) {
	int i;
	HPoint	bbx[2], bbox[8];
	float matrix[16];

	// get min/max and enumerate the 8 corner points around seg's bounding cuboid
	HC_Open_Segment_By_Key( seg );
		HC_Compute_Circumcuboid(".", &bbx[0], &bbx[1]);
		HUtility::GenerateCuboidPoints (&bbx[0], &bbx[1], bbox);
		HC_Show_Net_Modelling_Matrix( matrix );
	HC_Close_Segment();

	// get the screen space min/max 
	HC_Open_Segment_By_Key(m_pView->GetSceneKey());
		HC_Open_Segment("dummy");
			HC_Set_Modelling_Matrix (matrix);
			for( i = 0 ; i < 8 ; i++ ) {
				HC_Compute_Coordinates(".", "object", &bbox[i], "local window", &bbox[i]);
				HUtility::ClampPointToWindow(&bbox[i]);
			}
		HC_Close_Segment();
	HC_Close_Segment();
	min = bbox[0];
	max = bbox[0];
	for( i = 1 ; i < 8 ; i++ )
	{
		if (min.x > bbox[i].x) min.x = bbox[i].x;
		if (min.y > bbox[i].y) min.y = bbox[i].y;
		if (min.z > bbox[i].z) min.z = bbox[i].z;
		if (max.x < bbox[i].x) max.x = bbox[i].x;
		if (max.y < bbox[i].y) max.y = bbox[i].y;
		if (max.z < bbox[i].z) max.z = bbox[i].z;
	}
	return HOP_OK;
}


void HOpObjectClash::ProcessSelectionResults()
{
	HC_KEY	hit_object;
	int		offset1, offset2, offset3;
	char	hit_path[512];

	do {
		HC_Show_Selection_Element( &hit_object, &offset1, &offset2, &offset3 );
		if( m_pView->GetSelection()->IsSelected( hit_object ) )
			continue;
		HC_Show_Selection_Pathname( hit_path );
		//now simply select the clashed item.  
		//Ignore multi-instancing effects for now
		m_pView->GetSelection()->Select( hit_object, hit_path, -1, -1 );
	}
	while( HC_Find_Related_Selection() );
}



int HOpObjectClash::SelectByScreenVolume( HC_KEY seg )
{
	int		hit_found = 0;
	HPoint	min, max;

	GetScreenVolume( seg, min, max );
	// compute the selection
	HC_Open_Segment_By_Key(m_pView->GetViewKey());
		hit_found = HC_Compute_Selection_By_Volume(
					".", 
					".", 
					"v", 
					min.x, max.x, min.y, 
					max.y, min.z, max.z );
	HC_Close_Segment();
	if( hit_found )
		ProcessSelectionResults();
	return HOP_OK;
}


int HOpObjectClash::GetWorldVolume( HC_KEY seg, HPoint &min, HPoint &max ) {
	int i;
	HPoint	bbx[2], bbox[8];
	float matrix[16];

	// get min/max and enumerate the 8 corner points around seg's bounding cuboid
	HC_Open_Segment_By_Key( seg );
		HC_Compute_Circumcuboid(".", &bbx[0], &bbx[1]);
		HUtility::GenerateCuboidPoints (&bbx[0], &bbx[1], bbox);
		HC_Show_Net_Modelling_Matrix( matrix );
	HC_Close_Segment();

	// get the screen space min/max 
	HC_Open_Segment_By_Key(m_pView->GetSceneKey());
		HC_Open_Segment("dummy");
			HC_Set_Modelling_Matrix (matrix);
			for( i = 0 ; i < 8 ; i++ ) {
				HC_Compute_Coordinates(".", "object", &bbox[i], "world", &bbox[i]);
			}
		HC_Close_Segment();
	HC_Close_Segment();
	min = bbox[0];
	max = bbox[0];
	for( i = 1 ; i < 8 ; i++ )
	{
		if (min.x > bbox[i].x) min.x = bbox[i].x;
		if (min.y > bbox[i].y) min.y = bbox[i].y;
		if (min.z > bbox[i].z) min.z = bbox[i].z;
		if (max.x < bbox[i].x) max.x = bbox[i].x;
		if (max.y < bbox[i].y) max.y = bbox[i].y;
		if (max.z < bbox[i].z) max.z = bbox[i].z;
	}
	return HOP_OK;
}


int HOpObjectClash::SelectByWorldVolume( HC_KEY seg )
{
	int		hit_found = 0;
	HPoint  min, max;

	GetWorldVolume( seg, min, max );
	// compute the selection
	HC_Open_Segment_By_Key(m_pView->GetSceneKey());
		hit_found = HC_Compute_Selection_By_Volume(
					"", 
					".", 
					"v", 
					min.x, max.x, min.y, 
					max.y, min.z, max.z );
	HC_Close_Segment();
	if( hit_found ) {
		ProcessSelectionResults();
	} 
	return HOP_OK;
}

int HOpObjectClash::SelectByShell( HC_KEY seg )
{
	int		hit_found = 0;
	int		point_count, face_list_length;
	float 	*original_points = NULL, *transformed_points = NULL;
	int 	*face_list = NULL;
	HC_KEY	key;
	float	modelling_matrix[16];
	float	start_time, end_time;
	int		bbox_comparisons, face_comparisons, extended_face_comparisons;
	char	profile_results[2048];

	HC_Open_Segment_By_Key( seg );
		HC_Begin_Contents_Search( ".", "shells" );
		while( HC_Find_Contents( NULL, &key ) ) {
			HC_Show_Shell_Size( key, &point_count, &face_list_length );
			if( point_count == 0 )
				continue;
			HC_Show_Geometry_Pointer( key, "points", &original_points);
			HC_Show_Geometry_Pointer( key, "face list", &face_list );
			transformed_points = new float[ 3 * point_count ];
			HC_Show_Net_Modelling_Matrix( modelling_matrix );
			HC_Compute_Transformed_Points( 
					point_count, 
					original_points, 
					modelling_matrix, 
					transformed_points ); 
			break;
		}
		if( face_list == NULL || original_points == NULL )
			goto done;
	HC_Close_Segment( );

	// compute the selection
	HC_Open_Segment_By_Key(m_pView->GetSceneKey());
		HC_Set_Heuristics( "no related selection limit" );
		HC_Show_Time( &start_time );
		hit_found = HC_Compute_Selection_By_Shell( 
					"v",
					".", 
					point_count, 
					transformed_points, 
					face_list_length, 
					face_list );
		/* The following is an unofficial interface provided only for performance tuning.
		 * Its continued existence is not guaranteed */
		HI_Show_Shell_Selection_Profile( profile_results );
		HC_Show_Time( &end_time );
		sscanf( profile_results, 
			"bounding box comparisons: %d, face comparisons: %d, extended face comparisons: %d", 
			&bbox_comparisons, &face_comparisons, &extended_face_comparisons );
		m_selection_time += end_time - start_time;
		m_bbox_comparisons += bbox_comparisons;
		m_face_comparisons += face_comparisons;
		m_extended_face_comparisons += extended_face_comparisons;
	HC_Close_Segment();
	if( hit_found ) {
		ProcessSelectionResults();
	} 
	done:
	delete [] transformed_points;
	return HOP_OK;
}
 

/////////////////////////////////////////////////////////////////////////////
// HOpObjectClash message handlers


int HOpObjectClash::OnLButtonDownAndMove(HEventInfo &event)
{
    int n;
    HC_KEY key;
    char keytype[64];
    float seg_matrix[16], tmatrix[16];
    int size;

    if (!m_bOpStarted) 
        return HBaseOperator::OnLButtonDownAndMove(event);

    size = m_pClashSelection->GetSize();
    
	// deselect everything from last time around
	m_pView->GetSelection()->DeSelectAll();
	if( m_SelectMode == HOOC_Shell ) {
		m_selection_time = 0;
		m_bbox_comparisons = 0;
		m_face_comparisons = 0;
		m_extended_face_comparisons = 0;
	}
	// calculate and apply modelling matrices to our local selection list
    for (n = 0; n < size; n++)
    {
        key = m_pClashSelection->GetAt(n);
        HC_Show_Key_Type(key, keytype);
        if ((key != -1) && (streq(keytype, "segment"))) {
            while( IsSpecialKey( key ) )
                key = HC_KShow_Owner_By_Key( key );
            GetMatrix( event, key, tmatrix );
            HC_Open_Segment_By_Key(key);
                HC_Set_Heuristics("quick moves = spriting");
                if (HC_Show_Existence ("modelling matrix")) {
                    HC_Show_Modelling_Matrix (seg_matrix);
                    HC_UnSet_Modelling_Matrix ();
                    HC_Set_Modelling_Matrix( tmatrix );
                    HC_Append_Modelling_Matrix (seg_matrix);
                }
                else {
                    HC_Set_Modelling_Matrix( tmatrix );
                }
            HC_Close_Segment();
        }
		switch( m_SelectMode ) {
			case HOOC_World:
				SelectByWorldVolume( key );
				break;
			case HOOC_Screen:
				SelectByScreenVolume( key );
				break;
			case HOOC_Shell:
				SelectByShell( key );
				break;
		}	
    }
    UpdateMousePos(event);
	if( m_selection_time > m_longest_selection_time )
	    m_longest_selection_time = m_selection_time;
	PostSelectionProfile();

	m_pView->SetGeometryChanged();
	m_pView->Update();
	return (HOP_OK);
}

// Mouse has come down
int HOpObjectClash::OnLButtonDown(HEventInfo &event)
{
	GrabSelectionSet();
	HOpObjectTranslate::OnLButtonDown(event);
	HC_Begin_Shell_Selection();
	m_longest_selection_time = 0;
	return(HOP_READY);
}


// Mouse has come up 
int HOpObjectClash::OnLButtonUp(HEventInfo &event)
{
	HOpObjectTranslate::OnLButtonUp(event);

	HC_End_Shell_Selection();
	HSelectionSet* selection = m_pView->GetSelection();
	if( selection->GetSize() ) {
		selection->DeSelectAll();
		m_pView->Update();
	}
	RestoreSelectionSet();
	return(HOP_READY);
}


void HOpObjectClash::SetSelectMode( int mode )
{
	const char *text;

	switch( mode ) {
		case HOOC_Screen:
			text = "select mode = screen";
			break;
		case HOOC_Shell:
			text = "select mode = shell";
			break;
		case HOOC_World:
			text = "select mode = world";
			break;
		default:
			/* huh? this shouldn't happen */
			return; 
	}
	HC_Open_Segment_By_Key( m_TempSegKey );
		HC_Open_Segment( "select mode" );
			HC_Flush_Contents( ".", "text" );
			HC_Insert_Text( 0, -0.9, 0, text );
		HC_Close_Segment();
	HC_Close_Segment();
	m_SelectMode = mode;
}


void HOpObjectClash::PostSelectionProfile()
{
	HC_Open_Segment_By_Key( m_TempSegKey );
		HC_Open_Segment( "selection profile" );
			HC_Flush_Contents( ".", "text" );
			if( m_SelectMode == HOOC_Shell ) {
				char tempstr[2048];
				sprintf( tempstr, 
						"selection time: %3.2f seconds,\n"
						"longest selection time: %3.2f seconds,\n"
						"bounding box comparisons: %d,\n"
						"face comparisons: %d,\n"
						"extended face comparisons: %d\n",
						m_selection_time,
						m_longest_selection_time, 
						m_bbox_comparisons, 
						m_face_comparisons, 
						m_extended_face_comparisons );
				HC_Set_Text_Alignment( "<^" );
				HC_Set_Text_Font( "size=0.03sru" );
				HC_Insert_Text( -1, 1, 0, tempstr );
			}
		HC_Close_Segment();
	HC_Close_Segment();
}


int HOpObjectClash::OnKeyDown(HEventInfo &event)
{
	char the_char = event.GetChar();

	switch(the_char) {
		case 'd': /* for "driver" */
			m_SelectMode = HOOC_Screen;
			break;

		case 's':
			m_SelectMode = HOOC_Shell;
			break;

		case 'w':
			m_SelectMode = HOOC_World;
			break;

		default:
			return HBaseOperator::OnKeyDown(event);
	}
	return (HOP_OK);
}

