//-
// ==========================================================================
// Copyright 1995,2006,2008 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.
// ==========================================================================
//+

#include "meshOpFty.h"

// General Includes
//
#include <maya/MGlobal.h>
#include <maya/MIOStream.h>
#include <maya/MFloatPointArray.h>

// Function Sets
//
#include <maya/MFnMesh.h>

// Iterators
//
#include <maya/MItMeshPolygon.h>
#include <maya/MItMeshEdge.h>

#define CHECK_STATUS(st) if ((st) != MS::kSuccess) { break; }

MStatus meshOpFty::doIt()
//
//	Description:
//		Performs the operation on the selected mesh and components
//
{
	MStatus status;
	unsigned int i, j;

	// Get access to the mesh's function set
	//
	MFnMesh meshFn(fMesh);

	// The division count argument is used in many of the operations
	// to execute the operation multiple subsequent times. For example,
	// with a division count of 2 in subdivide face, the given faces will be
	// divide once and then the resulting inner faces will be divided again.
	//
	int divisionCount = 2;

	MFloatVector translation;
	if (fOperationType == kExtrudeEdges
		|| fOperationType == kExtrudeFaces
		|| fOperationType == kDuplicateFaces
		|| fOperationType == kExtractFaces)
	{
		// The translation vector is used for the extrude, extract and 
		// duplicate operations to move the result to a new position. For 
		// example, if you extrude an edge on a mesh without a subsequent 
		// translation, the extruded edge will be on at the position of the 
		// orignal edge and the created faces will have no area.
		// 
		// Here, we provide a translation that is in the same direction as the
		// average normal of the given components.
		//
		MFn::Type componentType = getExpectedComponentType(fOperationType);
		MIntArray adjacentVertexList;
		switch (componentType)
		{
		case MFn::kMeshEdgeComponent:
			for (i = 0; i < fComponentIDs.length(); ++i)
			{
				int2 vertices;
				meshFn.getEdgeVertices(fComponentIDs[i], vertices);
				adjacentVertexList.append(vertices[0]);
				adjacentVertexList.append(vertices[1]);
			}
			break;

		case MFn::kMeshPolygonComponent:
			for (i = 0; i < fComponentIDs.length(); ++i)
			{
				MIntArray vertices;
				meshFn.getPolygonVertices(fComponentIDs[i], vertices);
				for (j = 0; j < vertices.length(); ++j)
					adjacentVertexList.append(vertices[j]);
			}
			break;
		default:	
			break;
		}
		MVector averageNormal(0, 0, 0);
		for (i = 0; i < adjacentVertexList.length(); ++i)
		{
			MVector vertexNormal;
			meshFn.getVertexNormal(adjacentVertexList[i], vertexNormal,
				MSpace::kWorld);
			averageNormal += vertexNormal;
		}
		if (averageNormal.length() < 0.001)
			averageNormal = MVector(0.0, 1.0, 0.0);
		else averageNormal.normalize();
		translation = averageNormal;
	}

	// When doing an extrude operation, there is a choice of extrude the
	// faces/edges individually or together. If extrudeTogether is true and 
	// multiple adjacent components are selected, they will be extruded as if
	// it were one more complex component.
	//
	// The following variable sets that option.
	//
	bool extrudeTogether = true;

	// Execute the requested operation
	//
	switch (fOperationType)
	{
	case kSubdivideEdges: {
		status = meshFn.subdivideEdges(fComponentIDs, divisionCount);
		CHECK_STATUS(status);
		break; }

	case kSubdivideFaces: {
		status = meshFn.subdivideFaces(fComponentIDs, divisionCount);
		CHECK_STATUS(status);
		break; }

	case kExtrudeEdges: {
		status = meshFn.extrudeEdges(fComponentIDs, divisionCount,
			&translation, extrudeTogether);
		CHECK_STATUS(status);
		break; }

	case kExtrudeFaces: {
		status = meshFn.extrudeFaces(fComponentIDs, divisionCount,
			&translation, extrudeTogether);
		CHECK_STATUS(status);
		break; }

	case kCollapseEdges: {
		status = meshFn.collapseEdges(fComponentIDs);
		CHECK_STATUS(status);
		break; }

	case kCollapseFaces: {
		status = meshFn.collapseFaces(fComponentIDs);
		CHECK_STATUS(status);
		break; }

	case kDuplicateFaces: {
		status = meshFn.duplicateFaces(fComponentIDs, &translation);
		CHECK_STATUS(status);
		break; }

	case kExtractFaces: {
		status = meshFn.extractFaces(fComponentIDs, &translation);
		CHECK_STATUS(status);
		break; }

	case kSplitLightning: {
		status = doLightningSplit(meshFn);
		CHECK_STATUS(status);
		break; }

	default:
		status = MS::kFailure;
		break;
	}

	return status;
}

MStatus meshOpFty::doLightningSplit(MFnMesh& meshFn)
//
//	Description:
//		Performs the kSplitLightning operation on the selected mesh
//      and components. It may not split all the selected components.
//
{
	unsigned int i, j;

	// These are the input arrays to the split function. The following
	// algorithm fills them in with the arguments for a continuous
	// split that goes through some of the selected faces.
	//
	MIntArray placements;
	MIntArray edgeIDs;
	MFloatArray edgeFactors;
	MFloatPointArray internalPoints;
	
	// The following array is going to be used to determine which faces
	// have been split. Since the split function can only split faces
	// which are adjacent to the earlier face, we may not split
	// all the faces
	//
	bool* faceTouched = new bool[fComponentIDs.length()];
	for (i = 0; i < fComponentIDs.length(); ++i)
		faceTouched[i] = false;
	
	// We need a starting point. For this example, the first face in
	// the component list is picked. Also get a polygon iterator
	// to this face.
	// 
	MItMeshPolygon itPoly(fMesh);
	for (; !itPoly.isDone(); itPoly.next())
	{
		if (fComponentIDs[0] == (int)itPoly.index()) break;
	}
	if (itPoly.isDone())
	{
		// Should never happen.
		//
		delete [] faceTouched;
		return MS::kFailure;
	}
	
	// In this example, edge0 is called the starting edge and
	// edge1 is called the destination edge. This algorithm will split
	// each face from the starting edge to the destination edge
	// while going through two inner points inside each face.
	//
	int edge0, edge1;
	MPoint innerVert0, innerVert1;
	int nextFaceIndex = 0;
	
	// We need a starting edge. For this example, the first edge in the
	// edge list is used.
	//
	MIntArray edgeList;
	itPoly.getEdges(edgeList);
	edge0 = edgeList[0];
	
	bool done = false;
	while (!done)
	{
		// Set this face as touched so that we don't try to split it twice
		//
		faceTouched[nextFaceIndex] = true;
		
		// Get the current face's center. It is used later in the
		// algorithm to calculate inner vertices.
		//
		MPoint faceCenter = itPoly.center();
			
		// Iterate through the connected faces to find an untouched,
		// selected face and get the ID of the shared edge. That face
		// will become the next face to be split.
		//
		MIntArray faceList;
		itPoly.getConnectedFaces(faceList);
		nextFaceIndex = -1;
		for (i = 0; i < fComponentIDs.length(); ++i)
		{
			for (j = 0; j < faceList.length(); ++j)
			{
				if (fComponentIDs[i] == faceList[j] && !faceTouched[i])
				{
					nextFaceIndex = i;
					break;
				}
			}
			if (nextFaceIndex != -1) break;
		}
		
		if (nextFaceIndex == -1)
		{
			// There is no selected and untouched face adjacent to this
			// face, so this algorithm is done. Pick the first edge that
			// is not the starting edge as the destination edge.
			//
			done = true;
			edge1 = -1;
			for (i = 0; i < edgeList.length(); ++i)
			{
				if (edgeList[i] != edge0)
				{
					edge1 = edgeList[i];
					break;
				}
			}
			if (edge1 == -1)
			{
				// This should not happen, since there should be more than
				// one edge for each face
				//
				delete [] faceTouched;
				return MS::kFailure;
			}
		}
		else
		{
			// The next step is to find out which edge is shared between
			// the two faces and use it as the destination edge. To do
			// that, we need to iterate through the faces and get the
			// next face's list of edges.
			//
			itPoly.reset();
			for (; !itPoly.isDone(); itPoly.next())
			{
				if (fComponentIDs[nextFaceIndex] == (int)itPoly.index()) break;
			}
			if (itPoly.isDone()) 
			{
				// Should never happen.
				//
				delete [] faceTouched;
				return MS::kFailure;
			}
			
			// Look for a common edge ID in the two faces edge lists
			//
			MIntArray nextFaceEdgeList;
			itPoly.getEdges(nextFaceEdgeList);
			edge1 = -1;
			for (i = 0; i < edgeList.length(); ++i)
			{
				for (j = 0; j < nextFaceEdgeList.length(); ++j)
				{
					if (edgeList[i] == nextFaceEdgeList[j])
					{
						edge1 = edgeList[i];
						break;
					}
				}
				if (edge1 != -1) break;
			}
			if (edge1 == -1)
			{
				// Should never happen.
				//
				delete [] faceTouched;
				return MS::kFailure;
			}
			
			// Save the edge list for the next iteration
			//
			edgeList = nextFaceEdgeList;
		}
		
		// Calculate the two inner points that the split will go through.
		// For this example, the midpoints between the center and the two
		// farthest vertices of the edges are used.
		//
		// Find the 3D positions of the edges' vertices
		//
		MPoint edge0vert0, edge0vert1, edge1vert0, edge1vert1;
		MItMeshEdge itEdge(fMesh, MObject::kNullObj );
		for (; !itEdge.isDone(); itEdge.next())
		{
			if (itEdge.index() == edge0)
			{
				edge0vert0 = itEdge.point(0);
				edge0vert1 = itEdge.point(1);
			}
			if (itEdge.index() == edge1)
			{
				edge1vert0 = itEdge.point(0);
				edge1vert1 = itEdge.point(1);
			}
		}
		
		// Figure out which are the farthest from each other
		//
		double distMax = edge0vert0.distanceTo(edge1vert0);
		MPoint max0, max1;
		max0 = edge0vert0;
		max1 = edge1vert0;
		double newDist = edge0vert1.distanceTo(edge1vert0);
		if (newDist > distMax)
		{
			max0 = edge0vert1;
			max1 = edge1vert0;
			distMax = newDist;
		}
		newDist = edge0vert0.distanceTo(edge1vert1);
		if (newDist > distMax)
		{
			max0 = edge0vert0;
			max1 = edge1vert1;
			distMax = newDist;
		}
		newDist = edge0vert1.distanceTo(edge1vert1);
		if (newDist > distMax)
		{
			max0 = edge0vert1;
			max1 = edge1vert1;
		}
		
		// Calculate the two inner points
		//
		innerVert0 = (faceCenter + max0) / 2.0;
		innerVert1 = (faceCenter + max1) / 2.0;
		
		// Add this split's information to the input arrays. If this is
		// the last split, also add the destination edge's split information.
		//
		placements.append((int) MFnMesh::kOnEdge);
		placements.append((int) MFnMesh::kInternalPoint);
		placements.append((int) MFnMesh::kInternalPoint);
		if (done) placements.append((int) MFnMesh::kOnEdge);
		
		edgeIDs.append(edge0);
		if (done) edgeIDs.append(edge1);
		
		edgeFactors.append(0.5f);
		if (done) edgeFactors.append(0.5f);
		
		MFloatPoint point1((float)innerVert0[0], (float)innerVert0[1],
			(float)innerVert0[2], (float)innerVert0[3]);
		MFloatPoint point2((float)innerVert1[0], (float)innerVert1[1],
			(float)innerVert1[2], (float)innerVert1[3]);
		internalPoints.append(point1);
		internalPoints.append(point2);
		
		// For the next iteration, the current destination
		// edge becomes the start edge.
		//
		edge0 = edge1;
	}

	// Release the dynamically-allocated memory and do the actual split
	//
	delete [] faceTouched;
	return meshFn.split(placements, edgeIDs, edgeFactors, internalPoints);
}
