//-
// ==========================================================================
// Copyright 2012 Autodesk, Inc. All rights reserved.
//
// Use of this software is subject to the terms of the Autodesk
// license agreement provided at the time of installation or download,
// or which otherwise accompanies this software in either electronic
// or hard copy form.
// ==========================================================================
//+

//////////////////////////////////////////////////////////////////////////////
//
// apiMeshSubSceneOverride.cpp
//
//////////////////////////////////////////////////////////////////////////////

#include <apiMeshSubSceneOverride.h>
#include <apiMeshShape.h>
#include <apiMeshGeom.h>

#include <maya/MObject.h>
#include <maya/MFnDependencyNode.h>
#include <maya/MFnDagNode.h>
#include <maya/MDagPathArray.h>
#include <maya/MGlobal.h>
#include <maya/MSelectionList.h>
#include <maya/MHWGeometry.h>
#include <maya/MShaderManager.h>
#include <maya/MViewport2Renderer.h>


static bool useSelectHighlight(const MSelectionList& selectedList, const MDagPath& path);
static bool floatApproxEqual(float left, float right);


apiMeshSubSceneOverride::apiMeshSubSceneOverride(const MObject& obj)
: MPxSubSceneOverride(obj)
, fObject(obj)
, fMesh(NULL)
, fWireShader(NULL)
, fThickWireShader(NULL)
, fSelectShader(NULL)
, fThickSelectShader(NULL)
, fShadedShader(NULL)
, fPositionBuffer(NULL)
, fNormalBuffer(NULL)
, fBoxPositionBuffer(NULL)
, fWireIndexBuffer(NULL)
, fBoxIndexBuffer(NULL)
, fShadedIndexBuffer(NULL)
, fThickLineWidth(-1.0)
, fQueuedLineWidth(-1.0)
, fNumInstances(0)
, fIsInstanceMode(false)
, fQueueUpdate(false)
, fUseQueuedLineUpdate(false) // Set to true to run sample line width update code
{
	// get the real apiMesh object from the MObject
	MStatus status;
	MFnDependencyNode node(obj, &status);
	if (status)
	{
		fMesh = dynamic_cast<apiMesh*>(node.userNode());
	}
}

apiMeshSubSceneOverride::~apiMeshSubSceneOverride()
{
	fMesh = NULL;

	MHWRender::MRenderer* renderer = MHWRender::MRenderer::theRenderer();
	if (renderer)
	{
		const MHWRender::MShaderManager* shaderMgr =
			renderer->getShaderManager();
		if (shaderMgr)
		{
			if (fWireShader) shaderMgr->releaseShader(fWireShader);
			fWireShader = NULL;
			if (fThickWireShader) shaderMgr->releaseShader(fThickWireShader);
			fThickWireShader = NULL;
			if (fSelectShader) shaderMgr->releaseShader(fSelectShader);
			fSelectShader = NULL;
			if (fThickSelectShader) shaderMgr->releaseShader(fThickSelectShader);
			fThickSelectShader = NULL;
			if (fShadedShader) shaderMgr->releaseShader(fShadedShader);
			fShadedShader = NULL;
		}
	}

	deleteBuffers();
}

MHWRender::DrawAPI apiMeshSubSceneOverride::supportedDrawAPIs() const
{
	// this plugin supports both GL and DX
	return (MHWRender::kOpenGL | MHWRender::kDirectX11);
}

bool apiMeshSubSceneOverride::requiresUpdate(
	const MHWRender::MSubSceneContainer& container,
	const MHWRender::MFrameContext& frameContext) const
{
	// Nothing in the container, definitely need to update
	if (container.count() == 0)
	{
		return true;
	}

	// Update always. This could be optimized to only update when the
	// actual shape node detects a change.
	return true;
}

void apiMeshSubSceneOverride::update(
	MHWRender::MSubSceneContainer& container,
	const MHWRender::MFrameContext& frameContext)
{
	// Update render items based on current set of instances
	manageRenderItems(container, frameContext,
		fMesh->shapeDirty() || container.count() == 0);

	// Always reset shape dirty flag
	fMesh->resetShapeDirty();	
}

bool apiMeshSubSceneOverride::furtherUpdateRequired(
		const MHWRender::MFrameContext& frameContext)
{
	if (fUseQueuedLineUpdate)
	{
		if (!frameContext.inUserInteraction()
			&& !frameContext.userChangingViewContext())
		{
			return fQueueUpdate;
		}
	}
	return false;
}

void apiMeshSubSceneOverride::manageRenderItems(
	MHWRender::MSubSceneContainer& container,
	const MHWRender::MFrameContext& frameContext,
	bool updateGeometry)
{
	// Preamble
	using namespace MHWRender;
	if (!fMesh || fObject.isNull()) return;
	MRenderer* renderer = MRenderer::theRenderer();
	if (!renderer) return;
	const MShaderManager* shaderMgr = renderer->getShaderManager();
	if (!shaderMgr) return;
	MStatus status;
	MFnDagNode node(fObject, &status);
	if (!status) return;
	MDagPathArray instances;
	if (!node.getAllPaths(instances) || instances.length() == 0) return;

	// Constants
	static const MString sWireName("apiMeshWire");
	static const MString sSelectName("apiMeshSelection");
	static const MString sBoxName("apiMeshBox");
	static const MString sSelectedBoxName("apiMeshBoxSelection");
	static const MString sShadedName("apiMeshShaded");
	static const float sRed[] = {1.0f, 0.0f, 0.0f, 1.0f};
	static const float sGreen[] = {0.0f, 1.0f, 0.0f, 1.0f};
	static const float sBlue[] = {0.0f, 0.0f, 1.0f};

	// Set up shared shaders if needed
	if (!fWireShader)
	{
		fWireShader = shaderMgr->getStockShader(
			MShaderManager::k3dSolidShader);
		fWireShader->setParameter("solidColor", sRed);
	}
	if (!fThickWireShader)
	{
		fThickWireShader = shaderMgr->getStockShader(
			MShaderManager::k3dThickLineShader);
		fThickWireShader->setParameter("solidColor", sRed);
	}
	if (!fSelectShader)
	{
		fSelectShader = shaderMgr->getStockShader(
			MShaderManager::k3dSolidShader);
		fSelectShader->setParameter("solidColor", sGreen);
	}
	if (!fThickSelectShader)
	{
		fThickSelectShader = shaderMgr->getStockShader(
			MShaderManager::k3dThickLineShader);
		fThickSelectShader->setParameter("solidColor", sGreen);
	}
	if (!fShadedShader)
	{
		fShadedShader = shaderMgr->getFragmentShader(
			"mayaLambertSurface", "outSurfaceFinal", true);
		fShadedShader->setParameter("color", sBlue);
		fShadedShader->setIsTransparent(false);
	}

	// Set up shared geometry if necessary
	if (updateGeometry) rebuildGeometryBuffers();
	if (!fPositionBuffer || !fNormalBuffer || !fBoxPositionBuffer ||
		!fWireIndexBuffer || !fBoxIndexBuffer || !fShadedIndexBuffer)
	{
		return;
	}
    
	MSelectionList selectedList;
	MGlobal::getActiveSelectionList(selectedList);

    bool anyMatrixChanged = false;
    bool itemsChanged = false;
	unsigned int instanceArrayLength = instances.length();
    unsigned int numSelected = 0;
    unsigned int numUnselected = 0;
    unsigned int numInstances = 0;
    MMatrixArray instanceMatrixArray(instanceArrayLength);
    MMatrixArray selectedInstanceMatrixArray(instanceArrayLength);
    MMatrixArray unselectedInstanceMatrixArray(instanceArrayLength);
	for (unsigned int instIdx=0; instIdx<instanceArrayLength; instIdx++)
    {
        // If expecting large numbers of instances, then walking through the whole
        // list of instances every time to look for changes is not efficient
        // enough.  Watching for change events and changing only the required
        // instances should be done instead.  This method of checking for selection
        // status is also not fast.
        if (!instances[instIdx].isValid()  || !instances[instIdx].isVisible())
            continue;
	    unsigned int instanceNum = instances[instIdx].instanceNumber();

        InstanceInfo instanceInfo(instances[instIdx].inclusiveMatrix(),
                                  useSelectHighlight(selectedList, instances[instIdx]));

        InstanceInfoMap::iterator iter = fInstanceInfoCache.find(instanceNum);
        if (iter == fInstanceInfoCache.end() ||
            iter->second.fIsSelected != instanceInfo.fIsSelected ||
            !iter->second.fTransform.isEquivalent(instanceInfo.fTransform))
        {
            fInstanceInfoCache[instanceNum] = instanceInfo;
            anyMatrixChanged = true;
        }

        instanceMatrixArray[numInstances++] = instanceInfo.fTransform;
        if (instanceInfo.fIsSelected)
        {
            selectedInstanceMatrixArray[numSelected++] = instanceInfo.fTransform;
        }
        else
        {
            unselectedInstanceMatrixArray[numUnselected++] = instanceInfo.fTransform;
        }
    }

    instanceMatrixArray.setLength(numInstances);  //collapse to correct length
    selectedInstanceMatrixArray.setLength(numSelected);
    unselectedInstanceMatrixArray.setLength(numUnselected);
    if (fNumInstances != numInstances)
    {
        anyMatrixChanged = true;
        fNumInstances = numInstances;
    }

    bool anySelected = numSelected > 0;
    bool anyUnselected = numUnselected > 0;

	// Add render items if necessary.  Remove any pre-existing render items
    // that are no longer needed.
	MRenderItem* wireItem = container.find(sWireName);
	if (!wireItem && anyUnselected)
	{
		wireItem = MRenderItem::Create(
			sWireName,
			MRenderItem::DecorationItem,
			MGeometry::kLines);
		wireItem->setDrawMode(MGeometry::kWireframe);
		wireItem->depthPriority(MRenderItem::sActiveWireDepthPriority);
		wireItem->setShader(fWireShader);
		container.add(wireItem);
		itemsChanged = true;
	}
    else if (wireItem && !anyUnselected)
    {
        container.remove(sWireName);
        wireItem = NULL;
		itemsChanged = true;
    }
	MRenderItem* selectItem = container.find(sSelectName);
	if (!selectItem && anySelected)
	{
		selectItem = MRenderItem::Create(
			sSelectName,
			MRenderItem::DecorationItem,
			MGeometry::kLines);
		selectItem->setDrawMode((MGeometry::DrawMode)(MGeometry::kWireframe | MGeometry::kShaded | MGeometry::kTextured));
		selectItem->depthPriority(MRenderItem::sActiveWireDepthPriority);
		selectItem->setShader(fSelectShader);
		container.add(selectItem);
		itemsChanged = true;
	}
    else if (selectItem && !anySelected)
    {
        container.remove(sSelectName);
        selectItem = NULL;
		itemsChanged = true;
    }

	MRenderItem* boxItem = container.find(sBoxName);
	if (!boxItem && anyUnselected)
	{
		boxItem = MRenderItem::Create(
			sBoxName,
			MRenderItem::NonMaterialSceneItem,
			MGeometry::kLines);
		boxItem->setDrawMode(MGeometry::kBoundingBox);
		boxItem->setShader(fWireShader);
		container.add(boxItem);
		itemsChanged = true;
	}
    else if (boxItem && !anyUnselected)
    {
        container.remove(sBoxName);
        boxItem = NULL;
		itemsChanged = true;
    }
	MRenderItem* selectedBoxItem = container.find(sSelectedBoxName);
	if (!selectedBoxItem && anySelected)
	{
		selectedBoxItem = MRenderItem::Create(
			sSelectedBoxName,
			MRenderItem::NonMaterialSceneItem,
			MGeometry::kLines);
		selectedBoxItem->setDrawMode(MGeometry::kBoundingBox);
		selectedBoxItem->setShader(fSelectShader);
		container.add(selectedBoxItem);
		itemsChanged = true;
	}
    else if (selectedBoxItem && !anySelected)
    {
        container.remove(sSelectedBoxName);
        selectedBoxItem = NULL;
		itemsChanged = true;
    }

	MRenderItem* shadedItem = container.find(sShadedName);
	if (!shadedItem)
	{
        // We always want a shaded item
		shadedItem = MRenderItem::Create(
			sShadedName,
			MRenderItem::MaterialSceneItem,
			MGeometry::kTriangles);
		shadedItem->setDrawMode((MGeometry::DrawMode)(MGeometry::kShaded | MGeometry::kTextured));
		shadedItem->setShader(fShadedShader);
		shadedItem->setExcludedFromPostEffects(false);
		shadedItem->castsShadows(true);
		shadedItem->receivesShadows(true);
		container.add(shadedItem);
		itemsChanged = true;
	}

    // update the line width on the shader for our wire items if it changed
	float lineWidth = frameContext.getGlobalLineWidth();
	bool userWidthChange = !floatApproxEqual(lineWidth, fThickLineWidth);

	bool doUpdate = false;
	const float targetRefinedLineWidth = 50.0f; 
	if (userWidthChange)
	{
        fThickLineWidth = lineWidth;
		doUpdate = true;

		// First user change will trigger an update requirement
		if (fUseQueuedLineUpdate)
		{
			fQueuedLineWidth = lineWidth;
			if (fQueuedLineWidth < targetRefinedLineWidth)
			{
				fQueueUpdate = true;
			}
		}
	}
	else 
	{
		// Increment by 1 until we reach the target width.
		// If we haven't reached it yet then queue another update
		// so we can increment and retest against the target width.
		if (fUseQueuedLineUpdate && fQueueUpdate)
		{
			if (fQueuedLineWidth < targetRefinedLineWidth)
			{
				lineWidth = fQueuedLineWidth;
				fQueuedLineWidth++;
				fQueueUpdate = true;
				doUpdate = true;
			}
			else
			{
				// Reached target. Stop asking for more refinement
				fQueueUpdate = false;
			}
		}
	}
	
	if (doUpdate)
    {	
		if (!floatApproxEqual(lineWidth, 1.0f))
	    {
            // Only set the shader if the line width changes (or the first time)
		    float lineWidthArray[2] = {0};
		    lineWidthArray[0] = lineWidth;
		    lineWidthArray[1] = lineWidth;
		    fThickWireShader->setParameter("lineWidth", lineWidthArray);
		    fThickSelectShader->setParameter("lineWidth", lineWidthArray);
            if (wireItem)
		        wireItem->setShader(fThickWireShader);
            if (selectItem)
		        selectItem->setShader(fThickSelectShader);
	    }
	    else
	    {
            if (wireItem)
		        wireItem->setShader(fWireShader);
            if (selectItem)
		        selectItem->setShader(fSelectShader);
	    }
    }

	// Update geometry if required
	if (itemsChanged || updateGeometry)
	{
		MBoundingBox bounds = fMesh->boundingBox();

		MVertexBufferArray wireBuffers;
		wireBuffers.addBuffer("positions", fPositionBuffer);
        if (wireItem)
		    setGeometryForRenderItem(*wireItem, wireBuffers, *fWireIndexBuffer, &bounds);
        if (selectItem)
		    setGeometryForRenderItem(*selectItem, wireBuffers, *fWireIndexBuffer, &bounds);

		MVertexBufferArray boxBuffers;
		boxBuffers.addBuffer("positions", fBoxPositionBuffer);
        if (boxItem)
		    setGeometryForRenderItem(*boxItem, boxBuffers, *fBoxIndexBuffer, &bounds);
        if (selectedBoxItem)
		    setGeometryForRenderItem(*selectedBoxItem, boxBuffers, *fBoxIndexBuffer, &bounds);

		MVertexBufferArray shadedBuffers;
		shadedBuffers.addBuffer("positions", fPositionBuffer);
		shadedBuffers.addBuffer("normals", fNormalBuffer);
		setGeometryForRenderItem(*shadedItem, shadedBuffers, *fShadedIndexBuffer, &bounds);
	}
        
	// Update render item matrices if necessary
    if (itemsChanged || anyMatrixChanged)
    {
        if (!fIsInstanceMode && numInstances == 1)
        {
            // When not dealing with multiple instances, don't convert the render items into instanced
            // mode.  Set the matrices on them directly.
            MMatrix& objToWorld = instanceMatrixArray[0];

            if (wireItem)
			    wireItem->setMatrix(&objToWorld);
            if (selectItem)
			    selectItem->setMatrix(&objToWorld);
            if (boxItem)
			    boxItem->setMatrix(&objToWorld);
            if (selectedBoxItem)
			    selectedBoxItem->setMatrix(&objToWorld);
			shadedItem->setMatrix(&objToWorld);

        }
        else
        {
            // If we have DAG instances of this shape then use the MPxSubSceneOverride instance
            // transform API to set up instance copies of the render items.  This will be faster
            // to render than creating render items for each instance, especially for large numbers
            // of instances.
            // Note: for simplicity, this code is not tracking the actual shaded material binding
            // of the shape.  MPxSubSceneOverride render item instances require that the shader
            // and other properties of the instances all match.  So if we were to bind a shader based
            // on the DAG shape material binding, then we would need one render item per material. We
            // could then group up the instance transforms based matching materials.

            // Note this has to happen after the geometry and shaders are set, otherwise it will fail.
            if (wireItem)
                setInstanceTransformArray(*wireItem, unselectedInstanceMatrixArray);
            if (selectItem)
                setInstanceTransformArray(*selectItem, selectedInstanceMatrixArray);
            if (boxItem)
                setInstanceTransformArray(*boxItem, unselectedInstanceMatrixArray);
            if (selectedBoxItem)
                setInstanceTransformArray(*selectedBoxItem, selectedInstanceMatrixArray);
            setInstanceTransformArray(*shadedItem, instanceMatrixArray);

            // Once we change the render items into instance rendering they can't be changed back without
            // being deleted and re-created.  So if instances are deleted to leave only one remaining,
            // just keep treating them the instance way.
            fIsInstanceMode = true;
        }
    }

	if (itemsChanged || anyMatrixChanged || updateGeometry)
    {
	    // On transform or geometry change, force recalculation of shadow maps
	    MRenderer::setLightsAndShadowsDirty();
    }
}

void apiMeshSubSceneOverride::rebuildGeometryBuffers()
{
	// Preamble
	using namespace MHWRender;
	apiMeshGeom* meshGeom = fMesh->meshGeom();
	if (!meshGeom) return;
	MBoundingBox bounds = fMesh->boundingBox();

	// Clear old
	deleteBuffers();

	// Compute mesh data size
	unsigned int numTriangles = 0;
	unsigned int totalVerts = 0;
	for (int i=0; i<meshGeom->faceCount; i++)
	{
		int numVerts = meshGeom->face_counts[i];
		if (numVerts > 2)
		{
			numTriangles += numVerts - 2;
			totalVerts += numVerts;
		}
	}

	// Acquire vertex buffer resources
	const MVertexBufferDescriptor posDesc("", MGeometry::kPosition, MGeometry::kFloat, 3);
	const MVertexBufferDescriptor normalDesc("", MGeometry::kNormal, MGeometry::kFloat, 3);
	fPositionBuffer = new MVertexBuffer(posDesc);
	float* positions = (float*)fPositionBuffer->acquire(totalVerts, true);
	fNormalBuffer = new MVertexBuffer(normalDesc);
	float* normals = (float*)fNormalBuffer->acquire(totalVerts, true);
	fBoxPositionBuffer = new MVertexBuffer(posDesc);
	float* boxPositions = (float*)fBoxPositionBuffer->acquire(8, true);

	// Acquire index buffer resources
	fWireIndexBuffer = new MIndexBuffer(MGeometry::kUnsignedInt32);
	unsigned int* wireBuffer = (unsigned int*)fWireIndexBuffer->acquire(2*totalVerts, true);
	fBoxIndexBuffer = new MIndexBuffer(MGeometry::kUnsignedInt32);
	unsigned int* boxBuffer = (unsigned int*)fBoxIndexBuffer->acquire(24, true);
	fShadedIndexBuffer = new MIndexBuffer(MGeometry::kUnsignedInt32);
	unsigned int* shadedBuffer = (unsigned int*)fShadedIndexBuffer->acquire(3*numTriangles, true);

	// Sanity check
	if (!positions || !boxPositions || !normals || !wireBuffer || !boxBuffer || !shadedBuffer)
	{
		deleteBuffers();
		return; // FAIL
	}

	// Fill vertex data for shaded/wireframe
	int vid = 0;
	int pid = 0;
	int nid = 0;
	for (int i=0; i<meshGeom->faceCount; i++)
	{
		// Ignore degenerate faces
		int numVerts = meshGeom->face_counts[i];
		if (numVerts > 2)
		{
			for (int j=0; j<numVerts; j++)
			{
				MPoint position = meshGeom->vertices[meshGeom->face_connects[vid]];
				positions[pid++] = (float)position[0];
				positions[pid++] = (float)position[1];
				positions[pid++] = (float)position[2];

				MVector normal = meshGeom->normals[meshGeom->face_connects[vid]];
				normals[nid++] = (float)normal[0];
				normals[nid++] = (float)normal[1];
				normals[nid++] = (float)normal[2];

				vid++;
			}
		}
		else if (numVerts > 0)
		{
			vid += numVerts;
		}
	}
	fPositionBuffer->commit(positions); positions = NULL;
	fNormalBuffer->commit(normals); normals = NULL;

	// Fill vertex data for bounding box
	MPoint bbmin = bounds.min();
	MPoint bbmax = bounds.max();
	boxPositions[0]  = (float)bbmin.x; boxPositions[1]  = (float)bbmin.y; boxPositions[2]  = (float)bbmin.z;
	boxPositions[3]  = (float)bbmin.x; boxPositions[4]  = (float)bbmin.y; boxPositions[5]  = (float)bbmax.z;
	boxPositions[6]  = (float)bbmax.x; boxPositions[7]  = (float)bbmin.y; boxPositions[8]  = (float)bbmax.z;
	boxPositions[9]  = (float)bbmax.x; boxPositions[10] = (float)bbmin.y; boxPositions[11] = (float)bbmin.z;
	boxPositions[12] = (float)bbmin.x; boxPositions[13] = (float)bbmax.y; boxPositions[14] = (float)bbmin.z;
	boxPositions[15] = (float)bbmin.x; boxPositions[16] = (float)bbmax.y; boxPositions[17] = (float)bbmax.z;
	boxPositions[18] = (float)bbmax.x; boxPositions[19] = (float)bbmax.y; boxPositions[20] = (float)bbmax.z;
	boxPositions[21] = (float)bbmax.x; boxPositions[22] = (float)bbmax.y; boxPositions[23] = (float)bbmin.z;
	fBoxPositionBuffer->commit(boxPositions); boxPositions = NULL;

	// Fill index data for wireframe
	vid = 0;
	int first = 0;
	unsigned int idx = 0;
	for (int faceIdx=0; faceIdx<meshGeom->faceCount; faceIdx++)
	{
		// ignore degenerate faces
		int numVerts = meshGeom->face_counts[faceIdx];
		if (numVerts > 2)
		{
			first = vid;
			for (int v=0; v<numVerts-1; v++)
			{
				wireBuffer[idx++] = vid++;
				wireBuffer[idx++] = vid;
			}
			wireBuffer[idx++] = vid++;
			wireBuffer[idx++] = first;
		}
		else
		{
			vid += numVerts;
		}
	}
	fWireIndexBuffer->commit(wireBuffer); wireBuffer = NULL;

	// Fill index data for box
	boxBuffer[0]  = 0; boxBuffer[1]  = 1;
	boxBuffer[2]  = 1; boxBuffer[3]  = 2;
	boxBuffer[4]  = 2; boxBuffer[5]  = 3;
	boxBuffer[6]  = 3; boxBuffer[7]  = 0;
	boxBuffer[8]  = 4; boxBuffer[9]  = 5;
	boxBuffer[10] = 5; boxBuffer[11] = 6;
	boxBuffer[12] = 6; boxBuffer[13] = 7;
	boxBuffer[14] = 7; boxBuffer[15] = 4;
	boxBuffer[16] = 0; boxBuffer[17] = 4;
	boxBuffer[18] = 1; boxBuffer[19] = 5;
	boxBuffer[20] = 2; boxBuffer[21] = 6;
	boxBuffer[22] = 3; boxBuffer[23] = 7;
	fBoxIndexBuffer->commit(boxBuffer);

	// Fill index data for shaded
	unsigned int base = 0;
	idx = 0;
	for (int faceIdx=0; faceIdx<meshGeom->faceCount; faceIdx++)
	{
		// Ignore degenerate faces
		int numVerts = meshGeom->face_counts[faceIdx];
		if (numVerts > 2)
		{
			for (int v=1; v<numVerts-1; v++)
			{
				shadedBuffer[idx++] = base;
				shadedBuffer[idx++] = base+v;
				shadedBuffer[idx++] = base+v+1;
			}
			base += numVerts;
		}
	}
	fShadedIndexBuffer->commit(shadedBuffer); shadedBuffer = NULL;
}

void apiMeshSubSceneOverride::deleteBuffers()
{
	delete fPositionBuffer; fPositionBuffer = NULL;
	delete fNormalBuffer; fNormalBuffer = NULL;
	delete fBoxPositionBuffer; fBoxPositionBuffer = NULL;
	delete fWireIndexBuffer; fWireIndexBuffer = NULL;
	delete fBoxIndexBuffer; fBoxIndexBuffer = NULL;
	delete fShadedIndexBuffer; fShadedIndexBuffer = NULL;
}



// helper function
bool useSelectHighlight(const MSelectionList& selectedList, const MDagPath& path)
{
    MStatus status = MStatus::kSuccess;
	MDagPath pathCopy = path;
	do
	{
		if (selectedList.hasItem(pathCopy))
		{
            return true;
		}
		status = pathCopy.pop();
	}
	while (status);

    return false;
}


static bool floatApproxEqual(float left, float right)
{
    return fabs(left - right) < 0.0001f;
}

