Jelajahi Sumber

Cleaned up triangle clipping code, added separate versions for 2D and 3D

BearishSun 9 tahun lalu
induk
melakukan
1ab3082c73

+ 31 - 0
Source/BansheeCore/Include/BsMeshUtility.h

@@ -82,6 +82,37 @@ namespace BansheeEngine
 		 */
 		 */
 		static void calculateTangentSpace(Vector3* vertices, Vector2* uv, UINT8* indices, UINT32 numVertices, 
 		static void calculateTangentSpace(Vector3* vertices, Vector2* uv, UINT8* indices, UINT32 numVertices, 
 			UINT32 numIndices, Vector3* normals, Vector3* tangents, Vector3* bitangents, UINT32 indexSize = 4);
 			UINT32 numIndices, Vector3* normals, Vector3* tangents, Vector3* bitangents, UINT32 indexSize = 4);
+
+		/**
+		 * Clips a set of two-dimensional vertices and uv coordinates against a set of arbitrary planes.
+		 *
+		 * @param[in]	vertices			A set of vertices in Vector2 format. Each vertex should be @p vertexStride bytes
+		 *									from each other.
+		 * @param[in]	uvs					A set of UV coordinates in Vector2 format. Each coordinate should be 
+		 *									@p vertexStride bytes from each other.
+		 * @param[in]	clipPlanes			A set of planes to clip the vertices against. Since the vertices are 
+		 *									two-dimensional the plane's Z coordinate should be zero.
+		 * @param[in]	writeCallback		Callback that will be triggered when clipped vertices and UV coordinates are
+		 *									generated and need to be stored. Vertices are always generate in tuples of
+		 *									three, forming a single triangle.
+		 */
+		static void clip2D(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Vector<Plane>& clipPlanes,
+			const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback);
+
+		/**
+		 * Clips a set of three-dimensional vertices and uv coordinates against a set of arbitrary planes.
+		 *
+		 * @param[in]	vertices			A set of vertices in Vector3 format. Each vertex should be @p vertexStride bytes
+		 *									from each other.
+		 * @param[in]	uvs					A set of UV coordinates in Vector2 format. Each coordinate should be 
+		 *									@p vertexStride bytes from each other.
+		 * @param[in]	clipPlanes			A set of planes to clip the vertices against. 
+		 * @param[in]	writeCallback		Callback that will be triggered when clipped vertices and UV coordinates are
+		 *									generated and need to be stored. Vertices are always generate in tuples of
+		 *									three, forming a single triangle.
+		 */
+		static void clip3D(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Vector<Plane>& clipPlanes,
+			const std::function<void(Vector3*, Vector2*, UINT32)>& writeCallback);
 	};
 	};
 
 
 	/** @} */
 	/** @} */

+ 631 - 0
Source/BansheeCore/Source/BsMeshUtility.cpp

@@ -3,6 +3,7 @@
 #include "BsMeshUtility.h"
 #include "BsMeshUtility.h"
 #include "BsVector3.h"
 #include "BsVector3.h"
 #include "BsVector2.h"
 #include "BsVector2.h"
+#include "BsPlane.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -76,6 +77,622 @@ namespace BansheeEngine
 		UINT32* mFaces;
 		UINT32* mFaces;
 	};
 	};
 
 
+	/** Provides base methods required for clipping of arbitrary triangles. */
+	class TriangleClipperBase // Implementation from: http://www.geometrictools.com/Documentation/ClipMesh.pdf
+	{
+	protected:
+		/** Single vertex in the clipped mesh. */
+		struct ClipVert
+		{
+			ClipVert() { }
+
+			Vector3 point;
+			Vector2 uv;
+			float distance = 0.0f;
+			UINT32 occurs = 0;
+			bool visible = true;
+		};
+
+		/** Single edge in the clipped mesh. */
+		struct ClipEdge
+		{
+			ClipEdge() { }
+
+			UINT32 verts[2];
+			Vector<UINT32> faces;
+			bool visible = true;
+		};
+
+		/** Single polygon in the clipped mesh. */
+		struct ClipFace
+		{
+			ClipFace() { }
+
+			Vector<UINT32> edges;
+			bool visible = true;
+			Vector3 normal;
+		};
+
+		/** Contains vertices, edges and faces of the clipped mesh. */
+		struct ClipMesh
+		{
+			ClipMesh() { }
+
+			Vector<ClipVert> verts;
+			Vector<ClipEdge> edges;
+			Vector<ClipFace> faces;
+		};
+
+	protected:
+		/** 
+		 * Register all edges and faces, using the mesh vertices as a basis. Assumes vertices are not indexed and that
+		 * every three vertices form a face
+		 */
+		void addEdgesAndFaces();
+
+		/** Clips the current mesh with the provided plane. */
+		INT32 clipByPlane(const Plane& plane);
+
+		/** Clips vertices of the current mesh by the provided plane. */
+		INT32 processVertices(const Plane& plane);
+
+		/** Clips edges of the current mesh. processVertices() must be called beforehand. */
+		void processEdges();
+
+		/** Clips the faces (polygons) of the current mesh. processEdges() must be called beforehand. */
+		void processFaces();
+
+		/** 
+		 * Returns a set of non-culled vertex indices for every visible face in the mesh. This should be called after
+		 * clipping operation is complete to retrieve valid vertices.		 
+		 */
+		void getOrderedFaces(FrameVector<FrameVector<UINT32>>& sortedFaces);
+
+		/** Returns a set of ordered and non-culled vertices for the provided face of the mesh */
+		void getOrderedVertices(const ClipFace& face, UINT32* vertices);
+
+		/** Calculates the normal for vertices related to the provided vertex indices. */
+		Vector3 getNormal(UINT32* sortedVertices, UINT32 numVertices);
+
+		/** 
+		 * Checks is the polygon shape of the provided face open or closed. If open, returns true and outputs endpoints of
+		 * the polyline.
+		 */
+		bool getOpenPolyline(ClipFace& face, UINT32& start, UINT32& end);
+
+		ClipMesh mesh;
+	};
+
+	void TriangleClipperBase::addEdgesAndFaces()
+	{
+		UINT32 numTris = (UINT32)mesh.verts.size() / 3;
+
+		UINT32 numEdges = numTris * 3;
+		mesh.edges.resize(numEdges);
+		mesh.faces.resize(numTris);
+
+		for (UINT32 i = 0; i < numTris; i++)
+		{
+			UINT32 idx0 = i * 3 + 0;
+			UINT32 idx1 = i * 3 + 1;
+			UINT32 idx2 = i * 3 + 2;
+
+			ClipEdge& clipEdge0 = mesh.edges[idx0];
+			clipEdge0.verts[0] = idx0;
+			clipEdge0.verts[1] = idx1;
+
+			ClipEdge& clipEdge1 = mesh.edges[idx1];
+			clipEdge0.verts[0] = idx1;
+			clipEdge0.verts[1] = idx2;
+
+			ClipEdge& clipEdge2 = mesh.edges[idx2];
+			clipEdge0.verts[0] = idx2;
+			clipEdge0.verts[1] = idx0;
+
+			ClipFace& clipFace = mesh.faces[i];
+
+			clipFace.edges.push_back(idx0);
+			clipFace.edges.push_back(idx1);
+			clipFace.edges.push_back(idx2);
+
+			clipEdge0.faces.push_back(i);
+			clipEdge1.faces.push_back(i);
+			clipEdge2.faces.push_back(i);
+
+			UINT32 verts[] = { idx0, idx1, idx2, idx0 };
+			for (UINT32 j = 0; j < 3; j++)
+				clipFace.normal += Vector3::cross(mesh.verts[verts[j]].point, mesh.verts[verts[j + 1]].point);
+
+			clipFace.normal.normalize();
+
+			mesh.faces.push_back(clipFace);
+		}
+	}
+
+	INT32 TriangleClipperBase::clipByPlane(const Plane& plane)
+	{
+		int state = processVertices(plane);
+
+		if (state == 1)
+			return +1; // Nothing is clipped
+		else if (state == -1)
+			return -1; // Everything is clipped
+
+		processEdges();
+		processFaces();
+
+		return 0;
+	}
+
+	INT32 TriangleClipperBase::processVertices(const Plane& plane)
+	{
+		static const float EPSILON = 0.00001f;
+
+		// Compute signed distances from vertices to plane
+		int positive = 0, negative = 0;
+		for (UINT32 i = 0; i < (UINT32)mesh.verts.size(); i++)
+		{
+			ClipVert& vertex = mesh.verts[i];
+
+			if (vertex.visible)
+			{
+				vertex.distance = Vector3::dot(plane.normal, vertex.point) - plane.d;
+				if (vertex.distance >= EPSILON)
+				{
+					positive++;
+				}
+				else if (vertex.distance <= -EPSILON)
+				{
+					negative++;
+					vertex.visible = false;
+				}
+				else
+				{
+					// Point on the plane within floating point tolerance
+					vertex.distance = 0;
+				}
+			}
+		}
+		if (negative == 0)
+		{
+			// All vertices on nonnegative side, no clipping
+			return +1;
+		}
+		if (positive == 0)
+		{
+			// All vertices on nonpositive side, everything clipped
+			return -1;
+		}
+
+		return 0;
+	}
+
+	void TriangleClipperBase::processEdges()
+	{
+		for (UINT32 i = 0; i < (UINT32)mesh.edges.size(); i++)
+		{
+			ClipEdge& edge = mesh.edges[i];
+
+			if (edge.visible)
+			{
+				const ClipVert& v0 = mesh.verts[edge.verts[0]];
+				const ClipVert& v1 = mesh.verts[edge.verts[1]];
+
+				float d0 = v0.distance;
+				float d1 = v1.distance;
+
+				if (d0 <= 0 && d1 <= 0)
+				{
+					// Edge is culled, remove edge from faces sharing it
+					for (UINT32 j = 0; j < (UINT32)edge.faces.size(); j++)
+					{
+						ClipFace& face = mesh.faces[edge.faces[j]];
+
+						auto iterFind = std::find(face.edges.begin(), face.edges.end(), i);
+						if (iterFind != face.edges.end())
+						{
+							face.edges.erase(iterFind);
+
+							if (face.edges.empty())
+								face.visible = false;
+						}
+					}
+
+					edge.visible = false;
+					continue;
+				}
+
+				if (d0 >= 0 && d1 >= 0)
+				{
+					// Edge is on nonnegative side, faces retain the edge
+					continue;
+				}
+
+				// The edge is split by the plane. Compute the point of intersection.
+				// If the old edge is <V0,V1> and I is the intersection point, the new
+				// edge is <V0,I> when d0 > 0 or <I,V1> when d1 > 0.
+				float t = d0 / (d0 - d1);
+				Vector3 intersectPt = (1 - t)*v0.point + t*v1.point;
+				Vector2 intersectUv = (1 - t)*v0.uv + t*v1.uv;
+
+				UINT32 newVertIdx = (UINT32)mesh.verts.size();
+				mesh.verts.push_back(ClipVert());
+
+				ClipVert& newVert = mesh.verts.back();
+				newVert.point = intersectPt;
+				newVert.uv = intersectUv;
+
+				if (d0 > 0)
+					mesh.edges[i].verts[1] = newVertIdx;
+				else
+					mesh.edges[i].verts[0] = newVertIdx;
+			}
+		}
+	}
+
+	void TriangleClipperBase::processFaces()
+	{
+		for (UINT32 i = 0; i < (UINT32)mesh.faces.size(); i++)
+		{
+			ClipFace& face = mesh.faces[i];
+
+			if (face.visible)
+			{
+				// The edge is culled. If the edge is exactly on the clip
+				// plane, it is possible that a visible triangle shares it.
+				// The edge will be re-added during the face loop.
+
+				for (UINT32 j = 0; j < (UINT32)face.edges.size(); j++)
+				{
+					ClipEdge& edge = mesh.edges[face.edges[j]];
+					ClipVert& v0 = mesh.verts[edge.verts[0]];
+					ClipVert& v1 = mesh.verts[edge.verts[0]];
+
+					v0.occurs = 0;
+					v1.occurs = 0;
+				}
+			}
+
+			UINT32 start, end;
+			if (getOpenPolyline(mesh.faces[i], start, end))
+			{
+				// Polyline is open, close it
+				UINT32 closeEdgeIdx = (UINT32)mesh.edges.size();
+				mesh.edges.push_back(ClipEdge());
+				ClipEdge& closeEdge = mesh.edges.back();
+
+				closeEdge.verts[0] = start;
+				closeEdge.verts[1] = end;
+
+				closeEdge.faces.push_back(i);
+				face.edges.push_back(closeEdgeIdx);
+			}
+		}
+	}
+
+	bool TriangleClipperBase::getOpenPolyline(ClipFace& face, UINT32& start, UINT32& end)
+	{
+		// Count the number of occurrences of each vertex in the polyline. The
+		// resulting "occurs" values must be 1 or 2.
+		for (UINT32 i = 0; i < (UINT32)face.edges.size(); i++)
+		{
+			ClipEdge& edge = mesh.edges[face.edges[i]];
+
+			if (edge.visible)
+			{
+				ClipVert& v0 = mesh.verts[edge.verts[0]];
+				ClipVert& v1 = mesh.verts[edge.verts[0]];
+
+				v0.occurs++;
+				v1.occurs++;
+			}
+		}
+
+		// Determine if the polyline is open
+		bool gotStart = false;
+		bool gotEnd = false;
+		for (UINT32 i = 0; i < (UINT32)face.edges.size(); i++)
+		{
+			const ClipEdge& edge = mesh.edges[face.edges[i]];
+
+			const ClipVert& v0 = mesh.verts[edge.verts[0]];
+			const ClipVert& v1 = mesh.verts[edge.verts[1]];
+
+			if (v0.occurs == 1)
+			{
+				if (!gotStart)
+				{
+					start = edge.verts[0];
+					gotStart = true;
+				}
+				else if (!gotEnd)
+				{
+					end = edge.verts[0];
+					gotEnd = true;
+				}
+			}
+
+			if (v1.occurs == 1)
+			{
+				if (!gotStart)
+				{
+					start = edge.verts[1];
+					gotStart = true;
+				}
+				else if (!gotEnd)
+				{
+					end = edge.verts[1];
+					gotEnd = true;
+				}
+			}
+		}
+
+		return gotStart;
+	}
+
+	void TriangleClipperBase::getOrderedFaces(FrameVector<FrameVector<UINT32>>& sortedFaces)
+	{
+		for (UINT32 i = 0; i < (UINT32)mesh.faces.size(); i++)
+		{
+			const ClipFace& face = mesh.faces[i];
+
+			if (face.visible)
+			{
+				// Get the ordered vertices of the face. The first and last
+				// element of the array are the same since the polyline is
+				// closed.
+				UINT32 numSortedVerts = (UINT32)face.edges.size() + 1;
+				UINT32* sortedVerts = (UINT32*)bs_stack_alloc(sizeof(UINT32) * numSortedVerts);
+
+				getOrderedVertices(face, sortedVerts);
+
+				FrameVector<UINT32> faceVerts;
+
+				// The convention is that the vertices should be counterclockwise
+				// ordered when viewed from the negative side of the plane of the
+				// face. If you need the opposite convention, switch the
+				// inequality in the if-else statement.
+				Vector3 normal = getNormal(sortedVerts, numSortedVerts);
+				if (Vector3::dot(mesh.faces[i].normal, normal) < 0)
+				{
+					// Clockwise, need to swap
+					for (INT32 j = (INT32)numSortedVerts - 2; j >= 0; j--)
+						faceVerts.push_back(sortedVerts[j]);
+				}
+				else
+				{
+					// Counterclockwise
+					for (int j = 0; j <= (INT32)numSortedVerts - 2; j++)
+						faceVerts.push_back(sortedVerts[j]);
+				}
+
+				sortedFaces.push_back(faceVerts);
+				bs_stack_free(sortedVerts);
+			}
+		}
+	}
+
+	void TriangleClipperBase::getOrderedVertices(const ClipFace& face, UINT32* sortedVerts)
+	{
+		UINT32 numEdges = (UINT32)face.edges.size();
+		UINT32* sortedEdges = (UINT32*)bs_stack_alloc(sizeof(UINT32) * numEdges);
+		for (UINT32 i = 0; i < numEdges; i++)
+			sortedEdges[i] = i;
+
+		// Bubble sort to arrange edges in contiguous order
+		for (UINT32 i0 = 0, i1 = 1, choice = 1; i1 < numEdges - 1; i0 = i1, i1++)
+		{
+			const ClipEdge& edge0 = mesh.edges[sortedEdges[i0]];
+
+			UINT32 current = edge0.verts[choice];
+			for (UINT32 j = i1; j < numEdges; j++)
+			{
+				const ClipEdge& edge1 = mesh.edges[sortedEdges[j]];
+
+				if (edge1.verts[0] == current || edge1.verts[1] == current)
+				{
+					std::swap(sortedEdges[i1], sortedEdges[j]);
+					choice = 1;
+					break;
+				}
+			}
+		}
+
+		// Add the first two vertices
+		sortedVerts[0] = mesh.edges[sortedEdges[0]].verts[0];
+		sortedVerts[1] = mesh.edges[sortedEdges[0]].verts[1];
+
+		// Add the remaining vertices
+		for (UINT32 i = 1; i < numEdges; i++)
+		{
+			const ClipEdge& edge = mesh.edges[sortedEdges[i]];
+
+			if (edge.verts[0] == sortedVerts[i])
+				sortedVerts[i + 1] = edge.verts[1];
+			else
+				sortedVerts[i + 1] = edge.verts[0];
+		}
+
+		bs_stack_free(sortedEdges);
+	}
+
+	Vector3 TriangleClipperBase::getNormal(UINT32* sortedVertices, UINT32 numVertices)
+	{
+		Vector3 normal;
+		for (UINT32 i = 0; i <= numVertices - 2; i++)
+			normal += Vector3::cross(mesh.verts[sortedVertices[i]].point, mesh.verts[sortedVertices[i + 1]].point);
+
+		normal.normalize();
+		return normal;
+	}
+
+	/** Clips two-dimensional triangles against a set of provided planes. */
+	class TriangleClipper2D : public TriangleClipperBase
+	{
+	public:
+		/** @copydoc MeshUtility::clip2D */
+		void clip(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Vector<Plane>& clipPlanes,
+			const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback);
+
+	private:
+		/** Converts clipped vertices back into triangles and outputs them via the provided callback. */
+		void convertToMesh(const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback);
+
+		static const int BUFFER_SIZE = 64 * 3; // Must be a multiple of three
+		Vector2 vertexBuffer[BUFFER_SIZE];
+		Vector2 uvBuffer[BUFFER_SIZE];
+	};
+
+	void TriangleClipper2D::clip(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Vector<Plane>& clipPlanes,
+		const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback)
+	{
+		// Add vertices
+		UINT32 numVertices = numTris * 3;
+		mesh.verts.resize(numVertices);
+
+		for (UINT32 i = 0; i < numVertices; i++)
+		{
+			ClipVert& clipVert = mesh.verts[i];
+			Vector2 vector2D = *(Vector2*)(vertices + vertexStride * i);
+
+			clipVert.point = Vector3(vector2D.x, vector2D.y, 0.0f);
+			clipVert.uv = *(Vector2*)(uvs + vertexStride * i);
+		}
+
+		addEdgesAndFaces();
+
+		for (int i = 0; i < 4; i++)
+			clipByPlane(clipPlanes[i]);
+
+		convertToMesh(writeCallback);
+	}
+
+	void TriangleClipper2D::convertToMesh(const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback)
+	{
+		bs_frame_mark();
+		{
+			FrameVector<FrameVector<UINT32>> allFaces;
+			getOrderedFaces(allFaces);
+
+			// Note: Consider using Delaunay triangulation to avoid skinny triangles
+			UINT32 numWritten = 0;
+			assert(BUFFER_SIZE % 3 == 0);
+			for (auto& face : allFaces)
+			{
+				for (UINT32 i = 0; i < (UINT32)face.size() - 2; i++)
+				{
+					const Vector3& v0 = mesh.verts[face[0]].point;
+					const Vector3& v1 = mesh.verts[face[i + 1]].point;
+					const Vector3& v2 = mesh.verts[face[i + 2]].point;
+
+					vertexBuffer[numWritten] = Vector2(v0.x, v0.y);
+					uvBuffer[numWritten] = mesh.verts[face[0]].uv;
+					numWritten++;
+
+					vertexBuffer[numWritten] = Vector2(v1.x, v1.y);
+					uvBuffer[numWritten] = mesh.verts[face[i + 1]].uv;
+					numWritten++;
+
+					vertexBuffer[numWritten] = Vector2(v2.x, v2.y);
+					uvBuffer[numWritten] = mesh.verts[face[i + 2]].uv;
+					numWritten++;
+
+					// Only need to check this here since we guarantee the buffer is in multiples of three
+					if (numWritten >= BUFFER_SIZE)
+					{
+						writeCallback(vertexBuffer, uvBuffer, numWritten);
+						numWritten = 0;
+					}
+				}
+			}
+
+			if (numWritten > 0)
+				writeCallback(vertexBuffer, uvBuffer, numWritten);
+		}
+		bs_frame_clear();
+	}
+
+	/** Clips three-dimensional triangles against a set of provided planes. */
+	class TriangleClipper3D : public TriangleClipperBase
+	{
+	public:
+		/** @copydoc MeshUtility::clip3D */
+		void clip(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Vector<Plane>& clipPlanes,
+			const std::function<void(Vector3*, Vector2*, UINT32)>& writeCallback);
+
+	private:
+		/** Converts clipped vertices back into triangles and outputs them via the provided callback. */
+		void convertToMesh(const std::function<void(Vector3*, Vector2*, UINT32)>& writeCallback);
+
+		static const int BUFFER_SIZE = 64 * 3; // Must be a multiple of three
+		Vector3 vertexBuffer[BUFFER_SIZE];
+		Vector2 uvBuffer[BUFFER_SIZE];
+	};
+
+	void TriangleClipper3D::clip(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Vector<Plane>& clipPlanes,
+		const std::function<void(Vector3*, Vector2*, UINT32)>& writeCallback)
+	{
+		// Add vertices
+		UINT32 numVertices = numTris * 3;
+		mesh.verts.resize(numVertices);
+
+		for (UINT32 i = 0; i < numVertices; i++)
+		{
+			ClipVert& clipVert = mesh.verts[i];
+
+			clipVert.point = *(Vector3*)(vertices + vertexStride * i);
+			clipVert.uv = *(Vector2*)(uvs + vertexStride * i);
+		}
+
+		addEdgesAndFaces();
+
+		for (int i = 0; i < 4; i++)
+			clipByPlane(clipPlanes[i]);
+
+		convertToMesh(writeCallback);
+	}
+
+	void TriangleClipper3D::convertToMesh(const std::function<void(Vector3*, Vector2*, UINT32)>& writeCallback)
+	{
+		bs_frame_mark();
+		{
+			FrameVector<FrameVector<UINT32>> allFaces;
+			getOrderedFaces(allFaces);
+
+			// Note: Consider using Delaunay triangulation to avoid skinny triangles
+			UINT32 numWritten = 0;
+			assert(BUFFER_SIZE % 3 == 0);
+			for (auto& face : allFaces)
+			{
+				for (UINT32 i = 0; i < (UINT32)face.size() - 2; i++)
+				{
+					vertexBuffer[numWritten] = mesh.verts[face[0]].point;
+					uvBuffer[numWritten] = mesh.verts[face[0]].uv;
+					numWritten++;
+
+					vertexBuffer[numWritten] = mesh.verts[face[i + 1]].point;
+					uvBuffer[numWritten] = mesh.verts[face[i + 1]].uv;
+					numWritten++;
+
+					vertexBuffer[numWritten] = mesh.verts[face[i + 2]].point;
+					uvBuffer[numWritten] = mesh.verts[face[i + 2]].uv;
+					numWritten++;
+
+					// Only need to check this here since we guarantee the buffer is in multiples of three
+					if (numWritten >= BUFFER_SIZE)
+					{
+						writeCallback(vertexBuffer, uvBuffer, numWritten);
+						numWritten = 0;
+					}
+				}
+			}
+
+			if (numWritten > 0)
+				writeCallback(vertexBuffer, uvBuffer, numWritten);
+		}
+		bs_frame_clear();
+	}
+
 	void MeshUtility::calculateNormals(Vector3* vertices, UINT8* indices, UINT32 numVertices,
 	void MeshUtility::calculateNormals(Vector3* vertices, UINT8* indices, UINT32 numVertices,
 		UINT32 numIndices, Vector3* normals, UINT32 indexSize)
 		UINT32 numIndices, Vector3* normals, UINT32 indexSize)
 	{
 	{
@@ -203,4 +820,18 @@ namespace BansheeEngine
 		calculateNormals(vertices, indices, numVertices, numIndices, normals, indexSize);
 		calculateNormals(vertices, indices, numVertices, numIndices, normals, indexSize);
 		calculateTangents(vertices, normals, uv, indices, numVertices, numIndices, tangents, bitangents, indexSize);
 		calculateTangents(vertices, normals, uv, indices, numVertices, numIndices, tangents, bitangents, indexSize);
 	}
 	}
+
+	void MeshUtility::clip2D(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Vector<Plane>& clipPlanes,
+		const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback)
+	{
+		TriangleClipper2D clipper;
+		clipper.clip(vertices, uvs, numTris, vertexStride, clipPlanes, writeCallback);
+	}
+
+	void MeshUtility::clip3D(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Vector<Plane>& clipPlanes,
+		const std::function<void(Vector3*, Vector2*, UINT32)>& writeCallback)
+	{
+		TriangleClipper3D clipper;
+		clipper.clip(vertices, uvs, numTris, vertexStride, clipPlanes, writeCallback);
+	}
 }
 }

+ 13 - 9
Source/BansheeEngine/Include/BsSprite.h

@@ -142,7 +142,7 @@ namespace BansheeEngine
 			const Rect2I& clipRect, bool clip = true) const;
 			const Rect2I& clipRect, bool clip = true) const;
 
 
 		/**
 		/**
-		 * Clips the provided vertices to the provided clip rectangle. The vertices must for axis aligned quads.
+		 * Clips the provided 2D vertices to the provided clip rectangle. The vertices must form axis aligned quads.
 		 *
 		 *
 		 * @param[in, out]	vertices	Pointer to the start of the buffer containing vertex positions.
 		 * @param[in, out]	vertices	Pointer to the start of the buffer containing vertex positions.
 		 * @param[in, out]	uv			Pointer to the start of the buffer containing UV coordinates.
 		 * @param[in, out]	uv			Pointer to the start of the buffer containing UV coordinates.
@@ -155,17 +155,21 @@ namespace BansheeEngine
 		static void clipQuadsToRect(UINT8* vertices, UINT8* uv, UINT32 numQuads, UINT32 vertStride, const Rect2I& clipRect);
 		static void clipQuadsToRect(UINT8* vertices, UINT8* uv, UINT32 numQuads, UINT32 vertStride, const Rect2I& clipRect);
 
 
 		/**
 		/**
-		 * Clips the provided triangles vertices to the provided clip rectangle.
+		 * Clips the provided 2D vertices to the provided clip rectangle. The vertices can be arbitrary triangles.
 		 *
 		 *
-		 * @param[in, out]	vertices	Pointer to the start of the buffer containing vertex positions.
-		 * @param[in, out]	uv			Pointer to the start of the buffer containing UV coordinates.
-		 * @param[in]		numTris		Number of triangles in the provided buffer pointers.
-		 * @param[in]		vertStride	Number of bytes to skip when going to the next vertex. This assumes both position
-		 *								and uv coordinates have the same stride (as they are likely pointing to the same 
+		 * @param[in]	vertices		Pointer to the start of the buffer containing vertex positions.
+		 * @param[in]	uv				Pointer to the start of the buffer containing UV coordinates.
+		 * @param[in]	numTris			Number of triangles in the provided buffer pointers.
+		 * @param[in]	vertStride		Number of bytes to skip when going to the next vertex. This assumes both position
+		 *								and uv coordinates have the same stride (as they are likely pointing to the same
 		 *								buffer).
 		 *								buffer).
-		 * @param[in]		clipRect	Rectangle to clip the geometry to.
+		 * @param[in]	clipRect		Rectangle to clip the geometry to.
+		 * @param[in]	writeCallback	Callback that will be triggered when clipped vertices and UV coordinates are
+		 *								generated and need to be stored. Vertices are always generate in tuples of three,
+		 *								forming a single triangle.
 		 */
 		 */
-		static void clipTrianglesToRect(UINT8* vertices, UINT8* uv, UINT32 numTris, UINT32 vertStride, const Rect2I& clipRect);
+		static void clipTrianglesToRect(UINT8* vertices, UINT8* uv, UINT32 numTris, UINT32 vertStride, 
+			const Rect2I& clipRect, const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback);
 	protected:
 	protected:
 		/**	Returns the offset needed to move the sprite in order for it to respect the provided anchor. */
 		/**	Returns the offset needed to move the sprite in order for it to respect the provided anchor. */
 		static Vector2I getAnchorOffset(SpriteAnchor anchor, UINT32 width, UINT32 height);
 		static Vector2I getAnchorOffset(SpriteAnchor anchor, UINT32 width, UINT32 height);

+ 9 - 472
Source/BansheeEngine/Source/BsSprite.cpp

@@ -4,7 +4,7 @@
 #include "BsVector2.h"
 #include "BsVector2.h"
 #include "BsTexture.h"
 #include "BsTexture.h"
 #include "BsPlane.h"
 #include "BsPlane.h"
-#include "BsRect2.h"
+#include "BsMeshUtility.h"
 
 
 namespace BansheeEngine
 namespace BansheeEngine
 {
 {
@@ -318,481 +318,18 @@ namespace BansheeEngine
 		}
 		}
 	}
 	}
 
 
-	// Implementation from: http://www.geometrictools.com/Documentation/ClipMesh.pdf
-	class TriangleClipper
+	void Sprite::clipTrianglesToRect(UINT8* vertices, UINT8* uv, UINT32 numTris, UINT32 vertStride, const Rect2I& clipRect, 
+		const std::function<void(Vector2*, Vector2*, UINT32)>& writeCallback)
 	{
 	{
-	private:
-		struct ClipVert
+		Vector<Plane> clipPlanes =
 		{
 		{
-			ClipVert() { }
-
-			Vector3 point;
-			Vector2 uv;
-			float distance = 0.0f;
-			UINT32 occurs = 0;
-			bool visible = true;
-		};
-
-		struct ClipEdge
-		{
-			ClipEdge() { }
-
-			UINT32 verts[2];
-			Vector<UINT32> faces;
-			bool visible = true;
-		};
-
-		struct ClipFace
-		{
-			ClipFace() { }
-
-			Vector<UINT32> edges;
-			bool visible = true;
-			Vector3 normal;
-		};
-
-		struct ClipMesh
-		{
-			ClipMesh() { }
-
-			Vector<ClipVert> verts;
-			Vector<ClipEdge> edges;
-			Vector<ClipFace> faces;
-		};
-
-	public:
-		void clip(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Rect2& clipRect, 
-			Vector<Vector3>& clippedVertices, Vector<Vector2>& clippedUvs); // TODO - Use write callback instead of vectors for output
-
-	private:
-		INT32 clipByPlane(const Plane& plane);
-		INT32 processVertices(const Plane& plane);
-		void processEdges();
-		void processFaces();
-		void convertToMesh(Vector<Vector3>& vertices, Vector<Vector2>& uvs);
-		void getOrderedFaces(FrameVector<FrameVector<UINT32>>& sortedFaces);
-		void getOrderedVertices(ClipFace face, UINT32* vertices);
-		Vector3 getNormal(UINT32* sortedVertices, UINT32 numVertices);
-		bool getOpenPolyline(ClipFace& face, UINT32& start, UINT32& end);
-
-		ClipMesh mesh;
-	};
-
-	void TriangleClipper::clip(UINT8* vertices, UINT8* uvs, UINT32 numTris, UINT32 vertexStride, const Rect2& clipRect,
-		Vector<Vector3>& clippedVertices, Vector<Vector2>& clippedUvs)
-	{
-		// Add vertices
-		UINT32 numVertices = numTris * 3;
-		mesh.verts.resize(numVertices);
-
-		for (UINT32 i = 0; i < numVertices; i++)
-		{
-			ClipVert& clipVert = mesh.verts[i];
-			clipVert.point = *(Vector3*)(vertices + vertexStride * i);
-			clipVert.uv = *(Vector2*)(uvs + vertexStride * i);
-		}
-
-		// Add edges & faces
-		UINT32 numEdges = numTris * 3;
-		mesh.edges.resize(numEdges);
-		mesh.faces.resize(numTris);
-
-		for (UINT32 i = 0; i < numTris; i++)
-		{
-			UINT32 idx0 = i * 3 + 0;
-			UINT32 idx1 = i * 3 + 1;
-			UINT32 idx2 = i * 3 + 2;
-
-			ClipEdge& clipEdge0 = mesh.edges[idx0];
-			clipEdge0.verts[0] = idx0;
-			clipEdge0.verts[1] = idx1;
-
-			ClipEdge& clipEdge1 = mesh.edges[idx1];
-			clipEdge0.verts[0] = idx1;
-			clipEdge0.verts[1] = idx2;
-
-			ClipEdge& clipEdge2 = mesh.edges[idx2];
-			clipEdge0.verts[0] = idx2;
-			clipEdge0.verts[1] = idx0;
-
-			ClipFace& clipFace = mesh.faces[i];
-
-			clipFace.edges.push_back(idx0);
-			clipFace.edges.push_back(idx1);
-			clipFace.edges.push_back(idx2);
-
-			clipEdge0.faces.push_back(i);
-			clipEdge1.faces.push_back(i);
-			clipEdge2.faces.push_back(i);
-
-			UINT32 verts[] = { idx0, idx1, idx2, idx0 };
-			for (UINT32 j = 0; j < 3; j++)
-				clipFace.normal += Vector3::cross(mesh.verts[verts[j]].point, mesh.verts[verts[j + 1]].point);
-
-			clipFace.normal.normalize();
-
-			mesh.faces.push_back(clipFace);
-		}
-
-		Plane clipPlanes[] =
-		{
-			{ Vector3(1, 0, 0), clipRect.x },
-			{ Vector3(-1, 0, 0), -(clipRect.x + clipRect.width) },
-			{ Vector3(0, 1, 0), (clipRect.y + clipRect.height) },
-			{ Vector3(0, -1, 0), -clipRect.y }
+			Plane(Vector3(1.0f, 0.0f, 0.0f), (float)clipRect.x),
+			Plane(Vector3(-1.0f, 0.0f, 0.0f), (float)-(clipRect.x + clipRect.width)),
+			Plane(Vector3(0, 1.0f, 0), (float)(clipRect.y + clipRect.height)),
+			Plane(Vector3(0.0f, -1.0f, 0.0f), (float)-clipRect.y)
 		};
 		};
 
 
-		for (int i = 0; i < 4; i++)
-			clipByPlane(clipPlanes[i]);
-
-		convertToMesh(clippedVertices, clippedUvs);
-	}
-
-	INT32 TriangleClipper::clipByPlane(const Plane& plane)
-	{
-		int state = processVertices(plane);
-
-		if (state == 1)
-			return +1; // Nothing is clipped
-		else if (state == -1)
-			return -1; // Everything is clipped
-
-		processEdges();
-		processFaces();
-
-		return 0;
-	}
-
-	INT32 TriangleClipper::processVertices(const Plane& plane)
-	{
-		static const float EPSILON = 0.00001f;
-
-		// Compute signed distances from vertices to plane
-		int positive = 0, negative = 0;
-		for (UINT32 i = 0; i < (UINT32)mesh.verts.size(); i++)
-		{
-			ClipVert& vertex = mesh.verts[i];
-
-			if (vertex.visible)
-			{
-				vertex.distance = Vector3::dot(plane.normal, vertex.point) - plane.d;
-				if (vertex.distance >= EPSILON)
-				{
-					positive++;
-				}
-				else if (vertex.distance <= -EPSILON)
-				{
-					negative++;
-					vertex.visible = false;
-				}
-				else
-				{
-					// Point on the plane within floating point tolerance
-					vertex.distance = 0;
-				}
-			}
-		}
-		if (negative == 0)
-		{
-			// All vertices on nonnegative side, no clipping
-			return +1;
-		}
-		if (positive == 0)
-		{
-			// All vertices on nonpositive side, everything clipped
-			return -1;
-		}
-
-		return 0;
-	}
-
-	void TriangleClipper::processEdges()
-	{
-		for (INT32 i = 0; i < (UINT32)mesh.edges.size(); i++)
-		{
-			ClipEdge& edge = mesh.edges[i];
-
-			if (edge.visible)
-			{
-				const ClipVert& v0 = mesh.verts[edge.verts[0]];
-				const ClipVert& v1 = mesh.verts[edge.verts[1]];
-
-				float d0 = v0.distance;
-				float d1 = v1.distance;
-
-				if (d0 <= 0 && d1 <= 0)
-				{
-					// Edge is culled, remove edge from faces sharing it
-					for (UINT32 j = 0; j < (UINT32)edge.faces.size(); j++)
-					{
-						ClipFace& face = mesh.faces[edge.faces[j]];
-
-						auto iterFind = std::find(face.edges.begin(), face.edges.end(), i);
-						if (iterFind != face.edges.end())
-						{
-							face.edges.erase(iterFind);
-
-							if (face.edges.empty())
-								face.visible = false;
-						}
-					}
-
-					edge.visible = false;
-					continue;
-				}
-
-				if (d0 >= 0 && d1 >= 0)
-				{
-					// Edge is on nonnegative side, faces retain the edge
-					continue;
-				}
-
-				// The edge is split by the plane. Compute the point of intersection.
-				// If the old edge is <V0,V1> and I is the intersection point, the new
-				// edge is <V0,I> when d0 > 0 or <I,V1> when d1 > 0.
-				float t = d0 / (d0 - d1);
-				Vector3 intersectPt = (1 - t)*v0.point + t*v1.point;
-				Vector2 intersectUv = (1 - t)*v0.uv + t*v1.uv;
-
-				UINT32 newVertIdx = (UINT32)mesh.verts.size();
-				mesh.verts.push_back(ClipVert());
-
-				ClipVert& newVert = mesh.verts.back();
-				newVert.point = intersectPt;
-				newVert.uv = intersectUv;
-
-				if (d0 > 0)
-					mesh.edges[i].verts[1] = newVertIdx;
-				else
-					mesh.edges[i].verts[0] = newVertIdx;
-			}
-		}
-	}
-
-	void TriangleClipper::processFaces()
-	{
-		for (UINT32 i = 0; i < (UINT32)mesh.faces.size(); i++)
-		{
-			ClipFace& face = mesh.faces[i];
-
-			if (face.visible)
-			{
-				// The edge is culled. If the edge is exactly on the clip
-				// plane, it is possible that a visible triangle shares it.
-				// The edge will be re-added during the face loop.
-
-				for (UINT32 j = 0; j < (UINT32)face.edges.size(); j++)
-				{
-					ClipEdge& edge = mesh.edges[face.edges[j]];
-					ClipVert& v0 = mesh.verts[edge.verts[0]];
-					ClipVert& v1 = mesh.verts[edge.verts[0]];
-
-					v0.occurs = 0;
-					v1.occurs = 0;
-				}
-			}
-
-			UINT32 start, end;
-			if (getOpenPolyline(mesh.faces[i], start, end))
-			{
-				// Polyline is open, close it
-				UINT32 closeEdgeIdx = (UINT32)mesh.edges.size();
-				mesh.edges.push_back(ClipEdge());
-				ClipEdge& closeEdge = mesh.edges.back();
-
-				closeEdge.verts[0] = start;
-				closeEdge.verts[1] = end;
-
-				closeEdge.faces.push_back(i);
-				face.edges.push_back(closeEdgeIdx);
-			}
-		}
-	}
-
-	bool TriangleClipper::getOpenPolyline(ClipFace& face, UINT32& start, UINT32& end)
-	{
-		// Count the number of occurrences of each vertex in the polyline. The
-		// resulting "occurs" values must be 1 or 2.
-		for (UINT32 i = 0; i < (UINT32)face.edges.size(); i++)
-		{
-			ClipEdge& edge = mesh.edges[face.edges[i]];
-	
-			if (edge.visible)
-			{
-				ClipVert& v0 = mesh.verts[edge.verts[0]];
-				ClipVert& v1 = mesh.verts[edge.verts[0]];
-
-				v0.occurs++;
-				v1.occurs++;
-			}
-		}
-
-		// Determine if the polyline is open
-		bool gotStart = false;
-		bool gotEnd = false;
-		for (UINT32 i = 0; i < (UINT32)face.edges.size(); i++)
-		{
-			const ClipEdge& edge = mesh.edges[face.edges[i]];
-
-			const ClipVert& v0 = mesh.verts[edge.verts[0]];
-			const ClipVert& v1 = mesh.verts[edge.verts[1]];
-
-			if (v0.occurs == 1)
-			{
-				if (!gotStart)
-				{
-					start = edge.verts[0];
-					gotStart = true;
-				}
-				else if (!gotEnd)
-				{
-					end = edge.verts[0];
-					gotEnd = true;
-				}
-			}
-
-			if (v1.occurs == 1)
-			{
-				if (!gotStart)
-				{
-					start = edge.verts[1];
-					gotStart = true;
-				}
-				else if (!gotEnd)
-				{
-					end = edge.verts[1];
-					gotEnd = true;
-				}
-			}
-		}
-
-		return gotStart;
-	}
-
-	void TriangleClipper::convertToMesh(Vector<Vector3>& vertices, Vector<Vector2>& uvs)
-	{
-		bs_frame_mark();
-		{
-			FrameVector<FrameVector<UINT32>> allFaces;
-			getOrderedFaces(allFaces);
-
-			// Note: Consider using Delaunay triangulation to avoid skinny triangles
-			for (auto& face : allFaces)
-			{
-				for (int i = 0; i < (UINT32)face.size() - 2; i++)
-				{
-					vertices.push_back(mesh.verts[face[0]].point);
-					vertices.push_back(mesh.verts[face[i + 1]].point);
-					vertices.push_back(mesh.verts[face[i + 2]].point);
-
-					uvs.push_back(mesh.verts[face[0]].uv);
-					uvs.push_back(mesh.verts[face[i + 1]].uv);
-					uvs.push_back(mesh.verts[face[i + 2]].uv);
-				}
-			}
-		}
-		bs_frame_clear();
-	}
-
-	void TriangleClipper::getOrderedFaces(FrameVector<FrameVector<UINT32>>& sortedFaces)
-	{
-		for (UINT32 i = 0; i < (UINT32)mesh.faces.size(); i++)
-		{
-			const ClipFace& face = mesh.faces[i];
-
-			if (face.visible)
-			{
-				// Get the ordered vertices of the face. The first and last
-				// element of the array are the same since the polyline is
-				// closed.
-				UINT32 numSortedVerts = (UINT32)face.edges.size() + 1;
-				UINT32* sortedVerts = (UINT32*)bs_stack_alloc(sizeof(UINT32) * numSortedVerts);
-
-				getOrderedVertices(face, sortedVerts);
-
-				FrameVector<UINT32> faceVerts;
-
-				// The convention is that the vertices should be counterclockwise
-				// ordered when viewed from the negative side of the plane of the
-				// face. If you need the opposite convention, switch the
-				// inequality in the if-else statement.
-				Vector3 normal = getNormal(sortedVerts, numSortedVerts);
-				if (Vector3::dot(mesh.faces[i].normal, normal) < 0)
-				{
-					// Clockwise, need to swap
-					for (INT32 j = (INT32)numSortedVerts - 2; j >= 0; j--)
-						faceVerts.push_back(sortedVerts[j]);
-				}
-				else
-				{
-					// Counterclockwise
-					for (int j = 0; j <= (INT32)numSortedVerts - 2; j++)
-						faceVerts.push_back(sortedVerts[j]);
-				}
-
-				sortedFaces.push_back(faceVerts);
-				bs_stack_free(sortedVerts);
-			}
-		}
-	}
-
-	void TriangleClipper::getOrderedVertices(ClipFace face, UINT32* sortedVerts)
-	{
-		UINT32 numEdges = (UINT32)face.edges.size();
-		UINT32* sortedEdges = (UINT32*)bs_stack_alloc(sizeof(UINT32) * numEdges);
-		for (UINT32 i = 0; i < numEdges; i++)
-			sortedEdges[i] = i;
-
-		// Bubble sort to arrange edges in contiguous order
-		for (UINT32 i0 = 0, i1 = 1, choice = 1; i1 < numEdges - 1; i0 = i1, i1++)
-		{
-			const ClipEdge& edge0 = mesh.edges[sortedEdges[i0]];
-
-			UINT32 current = edge0.verts[choice];
-			for (UINT32 j = i1; j < numEdges; j++)
-			{
-				const ClipEdge& edge1 = mesh.edges[sortedEdges[j]];
-
-				if (edge1.verts[0] == current || edge1.verts[1] == current)
-				{
-					std::swap(sortedEdges[i1], sortedEdges[j]);
-					choice = 1;
-					break;
-				}
-			}
-		}
-
-		// Add the first two vertices
-		sortedVerts[0] = mesh.edges[sortedEdges[0]].verts[0];
-		sortedVerts[1] = mesh.edges[sortedEdges[0]].verts[1];
-
-		// Add the remaining vertices
-		for (UINT32 i = 1; i < numEdges; i++)
-		{
-			const ClipEdge& edge = mesh.edges[sortedEdges[i]];
-
-			if (edge.verts[0] == sortedVerts[i])
-				sortedVerts[i + 1] = edge.verts[1];
-			else
-				sortedVerts[i + 1] = edge.verts[0];
-		}
-
-		bs_stack_free(sortedEdges);
-	}
-
-	Vector3 TriangleClipper::getNormal(UINT32* sortedVertices, UINT32 numVertices)
-	{
-		Vector3 normal;
-		for (UINT32 i = 0; i <= numVertices - 2; i++)
-			normal += Vector3::cross(mesh.verts[sortedVertices[i]].point, mesh.verts[sortedVertices[i + 1]].point);
-
-		normal.normalize();
-		return normal;
-	}
-
-	void Sprite::clipTrianglesToRect(UINT8* vertices, UINT8* uv, UINT32 numTris, UINT32 vertStride, const Rect2I& clipRect)
-	{
-		
+		MeshUtility::clip2D(vertices, uv, numTris, vertStride, clipPlanes, writeCallback);
 	}
 	}
 
 
 	UINT64 SpriteMaterialInfo::generateHash() const
 	UINT64 SpriteMaterialInfo::generateHash() const