/*ScianPick.c
  Picking system for spaces in SciAn
  Eric Pepke
  May 30, 1991
*/

#include "Scian.h"
#include "ScianTypes.h"
#include "ScianLists.h"
#include "ScianWindows.h"
#include "ScianSpaces.h"
#include "ScianDraw.h"
#include "ScianIDs.h"
#include "ScianErrors.h"
#include "ScianPick.h"

#define MAXNPICKS		400	/*Maximum # of pick tuples*/

/*Product of the next two cannot be greater than 32767*/
#define MAXPICKEDOBJECTS	15	/*Maximum # of picked objects*/
#define MAXPICKEDVERTICES	2000	/*Maximum # of picked vertices*/

#ifdef GRAPHICS
static Matrix tempMatrix, pickMatrix;
#endif

short curPickObject;			/*Current object being picked*/
long bestPickVertex;

short pickBuffer[MAXNPICKS * 2 + 1];	/*Pick buffer*/

struct POS
    {
	ObjPtr object;
	long maxIndex;
	Bool anyPicked;
    };

struct POS pickObjects[MAXPICKEDOBJECTS];	/*Pickable objects*/

short hits[MAXNPICKS];

void StartPick()
/*Starts picking mode*/
{
#ifdef GRAPHICS
    getmatrix(tempMatrix);
    pick(pickBuffer, MAXNPICKS);
    initnames();
    loadname(-1);
    curPickObject = -1;
    MATCOPY(pickMatrix, Identity);
#endif
}

#ifdef PROTO
void PickObject(ObjPtr object, int resolveType)
#else
void PickObject(object, resolveType)
ObjPtr object;
int resolveType;
#endif
/*Declares that object is about to be drawn for picking*/
{
#ifdef GRAPHICS
    ++curPickObject;
    if (curPickObject >= MAXPICKEDOBJECTS - 1)
    {
	ReportError("PickObject", "Too many objects to pick");
	return;
    }
    
    pickObjects[curPickObject] . object = object;
    pickObjects[curPickObject] . maxIndex = 0;
    pickObjects[curPickObject] . anyPicked = false;
    loadname(curPickObject * MAXPICKEDVERTICES);
#endif
}

#ifdef PROTO
void PickVertex(long vertex)
#else
void PickVertex(vertex)
long vertex;
#endif
/*Identifies a vertex number for picking*/
{
    if (vertex > pickObjects[curPickObject] . maxIndex)
    {
	pickObjects[curPickObject] . maxIndex = vertex;
    }
    loadname(curPickObject * MAXPICKEDVERTICES +
	     vertex % MAXPICKEDVERTICES);
}

void EndPickObject(object)
ObjPtr object;
/*Ends the picking for object*/
{
#ifdef GRAPHICS
    loadname(-1);
#endif
}

#ifdef PROTO
int CompareShorts(const void *a, const void *b)
#else
int CompareShorts(a, b)
void *a, *b;
#endif
/*Compares two shorts for qsort*/
{
    return ((short *) a)[0] - ((short *) b)[0];
}

ObjPtr StopPick()
/*Stops picking mode and returns a list of objects*/
{
#ifdef GRAPHICS
    int nHits, k, d, c;
    ObjPtr retVal;
    int whichObject;
    long trimmedVertex;
    long hitCounter;
    int currentObject;
    int tryVertex;
    real px, py, pz;
    FuncTyp method;
    Matrix projMatrix, viewMatrix, curMatrix;
    real xnew, ynew, znew, wnew;
    real bestDist;
    int bestVertex;
    real r2;

    bestPickVertex = -1;
    /*Get the current matrix for transformation*/

    mmode(MPROJECTION);
    getmatrix(projMatrix);
    mmode(MVIEWING);
    getmatrix(viewMatrix);
    MULTMATRIX(viewMatrix, projMatrix, curMatrix);

    /*Restore the old matrix*/
    loadmatrix(tempMatrix);
    nHits = endpick(pickBuffer);
    if (nHits < 0)
    {
	ReportError("StopPick", "Pick buffer was too small");
	nHits = -nHits;
    }

    /*Go through the hits and distribute through the buffer*/
    retVal = NULLOBJ;
    if (nHits)
    {
	k = 0;
	hitCounter = 0;
	while (nHits)
	{
	    c = pickBuffer[k];
    
	    ++k;
	    while (c)
	    {
		if (pickBuffer[k] >= 0)
		{
		    hits[hitCounter++] = pickBuffer[k];
		}
		--c;
		++k;
	    }
	    --nHits;
	}
#if 0
		    whichObject = pickBuffer[k] / MAXPICKEDVERTICES;
		    trimmedVertex = pickBuffer[k] % MAXPICKEDVERTICES;
		    printf("%d ", trimmedVertex);
		    retVal = pickObjects[whichObject] . object;
#endif

	/*Sort the hits*/
	qsort(hits, hitCounter, sizeof(short), CompareShorts);

	/*Uniq them*/
	d = 0;
	for (k = 1; k < hitCounter; ++k)
	{
	    if (hits[k] != hits[d])
	    {
		++d;
		hits[d] = hits[k];
	    }
	}
	hitCounter = d + 1;

	/*Go through the hits and see which one is the closest*/
	bestDist = PLUSINF;
	currentObject = -1;
	for (k = 0; k < hitCounter; ++k)
	{
	    whichObject = hits[k] / MAXPICKEDVERTICES;
	    trimmedVertex = hits[k] % MAXPICKEDVERTICES;

	    if (whichObject > currentObject)
	    {
		currentObject = whichObject;
		method = GetMethod(pickObjects[currentObject] . object,
				BEGINSTUFFING);
		if (method)
		{
		    (*method)(pickObjects[currentObject] . object);
		}
		else
		{
		    retVal = pickObjects[currentObject] . object;
		}
		method = GetMethod(pickObjects[currentObject] . object,
			STUFFSELPOINT);
	    }

	    for (tryVertex = trimmedVertex;
		 tryVertex <= pickObjects[currentObject] . maxIndex;
		 tryVertex += MAXPICKEDVERTICES)
	    {
		if (method)
		{
		    (*method)(pickObjects[currentObject] . object,
			tryVertex, &px, &py, &pz);

		    xnew = px * curMatrix[0][0] + py * curMatrix[1][0] +
			   pz * curMatrix[2][0] +      curMatrix[3][0];

		    ynew = px * curMatrix[0][1] + py * curMatrix[1][1] +
			   pz * curMatrix[2][1] +      curMatrix[3][1];

		    znew = px * curMatrix[0][2] + py * curMatrix[1][2] +
			   pz * curMatrix[2][2] +      curMatrix[3][2];

		    wnew = px * curMatrix[0][3] + py * curMatrix[1][3] +
			   pz * curMatrix[2][3] +      curMatrix[3][3];

		    xnew /= wnew;
		    ynew /= wnew;
		    znew /= wnew;

		    r2 = xnew * xnew + ynew * ynew;

		    if (r2 < bestDist)
		    {
			retVal = pickObjects[currentObject] . object;
			bestPickVertex = tryVertex;
			bestDist = r2;
		    }
		}
	    }	    
	}
    }
    return retVal;
#endif
}
