// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//  * Redistributions of source code must retain the above copyright
//    notice, this list of conditions and the following disclaimer.
//  * Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.
//  * Neither the name of NVIDIA CORPORATION nor the names of its
//    contributors may be used to endorse or promote products derived
//    from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ''AS IS'' AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
// OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Copyright (c) 2008-2025 NVIDIA Corporation. All rights reserved.
// Copyright (c) 2004-2008 AGEIA Technologies, Inc. All rights reserved.
// Copyright (c) 2001-2004 NovodeX AG. All rights reserved.  

#include "extensions/PxCustomGeometryExt.h"

#include <geometry/PxGeometryHelpers.h>
#include <geometry/PxGeometryQuery.h>
#include <geometry/PxMeshQuery.h>
#include <geometry/PxTriangle.h>
#include <geometry/PxTriangleMesh.h>
#include <geometry/PxTriangleMeshGeometry.h>
#include <geomutils/PxContactBuffer.h>
#include <common/PxRenderOutput.h>
#include <extensions/PxGjkQueryExt.h>
#include <extensions/PxMassProperties.h>
#include <PxImmediateMode.h>

#include "omnipvd/ExtOmniPvdSetData.h"

using namespace physx;
#if PX_SUPPORT_OMNI_PVD
using namespace Ext;
#endif

static const PxU32 gCollisionShapeColor = PxU32(PxDebugColor::eARGB_MAGENTA);

///////////////////////////////////////////////////////////////////////////////

static const PxU32 MAX_TRIANGLES = 512;
static const PxU32 MAX_TRIANGLE_CONTACTS = 6;
const PxReal FACE_CONTACT_THRESHOLD = 0.99999f;

struct TrimeshContactFilter
{
	PxU32 triCount;
	PxU32 triIndices[MAX_TRIANGLES];
	PxU32 triAdjacencies[MAX_TRIANGLES][3];
	PxU32 triContactCounts[MAX_TRIANGLES][2];
	PxContactPoint triContacts[MAX_TRIANGLES][MAX_TRIANGLE_CONTACTS];

	TrimeshContactFilter() : triCount(0) {}

	void addTriangleContacts(const PxContactPoint* points, PxU32 count, const PxU32 triIndex, const PxVec3 triVerts[3], const PxU32 triAdjacency[3])
	{
		if (triCount == MAX_TRIANGLES)
			return;

		triIndices[triCount] = triIndex;
		PxU32& faceContactCount = triContactCounts[triCount][0];
		PxU32& edgeContactCount = triContactCounts[triCount][1];
		faceContactCount = edgeContactCount = 0;

		for (PxU32 i = 0; i < 3; ++i)
			triAdjacencies[triCount][i] = triAdjacency[i];

		PxVec3 triNormal = (triVerts[1] - triVerts[0]).cross(triVerts[2] - triVerts[0]).getNormalized();

		for (PxU32 i = 0; i < count; ++i)
		{
			const PxContactPoint& point = points[i];
			bool faceContact = fabsf(point.normal.dot(triNormal)) > FACE_CONTACT_THRESHOLD;

			if (faceContactCount + edgeContactCount < MAX_TRIANGLE_CONTACTS)
			{
				if (faceContact) triContacts[triCount][faceContactCount++] = point;
				else triContacts[triCount][MAX_TRIANGLE_CONTACTS - 1 - edgeContactCount++] = point;
			}
		}

		++triCount;
	}

	void writeToBuffer(PxContactBuffer& buffer)
	{
		for (PxU32 i = 0; i < triCount; ++i)
		{
			if (triContactCounts[i][1] > 0)
			{
				for (PxU32 j = 0; j < triCount; ++j)
				{
					if (triIndices[j] == triAdjacencies[i][0] || triIndices[j] == triAdjacencies[i][1] || triIndices[j] == triAdjacencies[i][2])
					{
						if (triContactCounts[j][0] > 0)
						{
							triContactCounts[i][1] = 0;
							break;
						}
					}
				}
			}

			for (PxU32 j = 0; j < triContactCounts[i][0]; ++j)
				buffer.contact(triContacts[i][j]);

			for (PxU32 j = 0; j < triContactCounts[i][1]; ++j)
				buffer.contact(triContacts[i][MAX_TRIANGLE_CONTACTS - 1 - j]);
		}
	}

	PX_NOCOPY(TrimeshContactFilter)
};

///////////////////////////////////////////////////////////////////////////////

struct TriangleSupport : PxGjkQuery::Support
{
	PxVec3 v0, v1, v2;
	PxReal margin;

	TriangleSupport(const PxVec3& _v0, const PxVec3& _v1, const PxVec3& _v2, PxReal _margin)
		:
		v0(_v0), v1(_v1), v2(_v2), margin(_margin)
	{}

	virtual PxReal getMargin() const
	{
		return margin;
	}
	virtual PxVec3 supportLocal(const PxVec3& dir) const
	{
		float d0 = dir.dot(v0), d1 = dir.dot(v1), d2 = dir.dot(v2);
		return (d0 > d1 && d0 > d2) ? v0 : (d1 > d2) ? v1 : v2;
	}
};

///////////////////////////////////////////////////////////////////////////////

static void drawArc(const PxVec3& center, const PxVec3& radius, const PxVec3& axis, PxReal angle, PxReal error, PxRenderOutput& out)
{
	int sides = int(ceilf(angle / (2 * acosf(1.0f - error))));
	float step = angle / sides;
	out << PxRenderOutput::LINESTRIP;
	for (int i = 0; i <= sides; ++i)
		out << center + PxQuat(step * i, axis).rotate(radius);
}
static void drawCircle(const PxVec3& center, const PxVec3& radius, const PxVec3& axis, PxReal error, PxRenderOutput& out)
{
	drawArc(center, radius, axis, PxTwoPi, error, out);
}
static void drawQuarterCircle(const PxVec3& center, const PxVec3& radius, const PxVec3& axis, PxReal error, PxRenderOutput& out)
{
	drawArc(center, radius, axis, PxPiDivTwo, error, out);
}
static void drawLine(const PxVec3& s, const PxVec3& e, PxRenderOutput& out)
{
	out << PxRenderOutput::LINES << s << e;
}

///////////////////////////////////////////////////////////////////////////////

PxBounds3 PxCustomGeometryExt::BaseConvexCallbacks::getLocalBounds(const PxGeometry&) const
{
	const PxVec3 min(supportLocal(PxVec3(-1, 0, 0)).x, supportLocal(PxVec3(0, -1, 0)).y, supportLocal(PxVec3(0, 0, -1)).z);
	const PxVec3 max(supportLocal(PxVec3(1, 0, 0)).x, supportLocal(PxVec3(0, 1, 0)).y, supportLocal(PxVec3(0, 0, 1)).z);
	PxBounds3 bounds(min, max);
	bounds.fattenSafe(getMargin());
	return bounds;
}

bool PxCustomGeometryExt::BaseConvexCallbacks::generateContacts(const PxGeometry& geom0, const PxGeometry& geom1,
	const PxTransform& pose0, const PxTransform& pose1, const PxReal contactDistance, const PxReal meshContactMargin,
	const PxReal toleranceLength, PxContactBuffer& contactBuffer) const
{
	struct ContactRecorder : immediate::PxContactRecorder
	{
		PxContactBuffer* contactBuffer;
		ContactRecorder(PxContactBuffer& _contactBuffer) : contactBuffer(&_contactBuffer) {}
		virtual bool recordContacts(const PxContactPoint* contactPoints, PxU32 nbContacts, PxU32 /*index*/)
		{
			for (PxU32 i = 0; i < nbContacts; ++i)
				contactBuffer->contact(contactPoints[i]);
			return true;
		}
	}
	contactRecorder(contactBuffer);

	PxCache contactCache;

	struct ContactCacheAllocator : PxCacheAllocator
	{
		PxU8 buffer[1024];
		ContactCacheAllocator() { PxMemSet(buffer, 0, sizeof(buffer)); }
		virtual PxU8* allocateCacheData(const PxU32 /*byteSize*/) { return reinterpret_cast<PxU8*>(size_t(buffer + 0xf) & ~0xf); }
	}
	contactCacheAllocator;

	const PxTransform identityPose(PxIdentity);

	switch (geom1.getType())
	{
	case PxGeometryType::eSPHERE:
	case PxGeometryType::eCAPSULE:
	case PxGeometryType::eBOX:
	case PxGeometryType::eCONVEXCORE:
	case PxGeometryType::eCONVEXMESH:
	{
		PxGjkQueryExt::ConvexGeomSupport geomSupport(geom1);
		if (PxGjkQueryExt::generateContacts(*this, geomSupport, pose0, pose1, contactDistance, toleranceLength, contactBuffer))
		{
			PxGeometryHolder substituteGeom; PxTransform preTransform;
			if (useSubstituteGeometry(substituteGeom, preTransform, contactBuffer.contacts[contactBuffer.count - 1], pose0))
			{
				contactBuffer.count--;
				const PxGeometry* pGeom0 = &substituteGeom.any();
				const PxGeometry* pGeom1 = &geom1;
				// PT:: tag: scalar transform*transform
				PxTransform pose = pose0.transform(preTransform);
				immediate::PxGenerateContacts(&pGeom0, &pGeom1, &pose, &pose1, &contactCache, 1, contactRecorder,
					contactDistance, meshContactMargin, toleranceLength, contactCacheAllocator);
			}
		}
		break;
	}
	case PxGeometryType::ePLANE:
	{
		const PxPlane plane = PxPlane(1.0f, 0.0f, 0.0f, 0.0f).transform(pose1);
		const PxPlane localPlane = plane.inverseTransform(pose0);
		const PxVec3 point = supportLocal(-localPlane.n) - localPlane.n * margin;
		const float dist = localPlane.distance(point);
		if (dist < contactDistance)
		{
			const PxVec3 n = localPlane.n;
			const PxVec3 p = point - n * dist * 0.5f;
			PxContactPoint contact;
			contact.point = pose0.transform(p);
			contact.normal = pose0.rotate(n);
			contact.separation = dist;
			contactBuffer.contact(contact);
			PxGeometryHolder substituteGeom; PxTransform preTransform;
			if (useSubstituteGeometry(substituteGeom, preTransform, contactBuffer.contacts[contactBuffer.count - 1], pose0))
			{
				contactBuffer.count--;
				const PxGeometry* pGeom0 = &substituteGeom.any();
				const PxGeometry* pGeom1 = &geom1;
				// PT:: tag: scalar transform*transform
				PxTransform pose = pose0.transform(preTransform);
				immediate::PxGenerateContacts(&pGeom0, &pGeom1, &pose, &pose1, &contactCache, 1, contactRecorder,
					contactDistance, meshContactMargin, toleranceLength, contactCacheAllocator);
			}
		}
		break;
	}
	case PxGeometryType::eTRIANGLEMESH:
	case PxGeometryType::eHEIGHTFIELD:
	{
		// As a triangle has no thickness, we need to choose some collision margin - the distance
		// considered a touching contact. I set it as 1% of the custom shape minimum dimension.
		PxReal meshMargin = getLocalBounds(geom0).getDimensions().minElement() * 0.01f;
		TrimeshContactFilter contactFilter;
		bool hasAdjacency = (geom1.getType() != PxGeometryType::eTRIANGLEMESH || (static_cast<const PxTriangleMeshGeometry&>(geom1).triangleMesh->getTriangleMeshFlags() & PxTriangleMeshFlag::eADJACENCY_INFO));
		PxBoxGeometry boxGeom(getLocalBounds(geom0).getExtents() + PxVec3(contactDistance + meshMargin));
		PxU32 triangles[MAX_TRIANGLES];
		bool overflow = false;
		PxU32 triangleCount = (geom1.getType() == PxGeometryType::eTRIANGLEMESH) ?
			PxMeshQuery::findOverlapTriangleMesh(boxGeom, pose0, static_cast<const PxTriangleMeshGeometry&>(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow) :
			PxMeshQuery::findOverlapHeightField(boxGeom, pose0, static_cast<const PxHeightFieldGeometry&>(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow);
		if (overflow)
			PxGetFoundation().error(PxErrorCode::eDEBUG_INFO, PX_FL, "PxCustomGeometryExt::BaseConvexCallbacks::generateContacts() Too many triangles.\n");
		for (PxU32 i = 0; i < triangleCount; ++i)
		{
			PxTriangle tri; PxU32 adjacent[3];
			if (geom1.getType() == PxGeometryType::eTRIANGLEMESH)
				PxMeshQuery::getTriangle(static_cast<const PxTriangleMeshGeometry&>(geom1), pose1, triangles[i], tri, NULL, hasAdjacency ? adjacent : NULL);
			else
				PxMeshQuery::getTriangle(static_cast<const PxHeightFieldGeometry&>(geom1), pose1, triangles[i], tri, NULL, adjacent);
			TriangleSupport triSupport(tri.verts[0], tri.verts[1], tri.verts[2], meshMargin);
			if (PxGjkQueryExt::generateContacts(*this, triSupport, pose0, identityPose, contactDistance, toleranceLength, contactBuffer))
			{
				contactBuffer.contacts[contactBuffer.count - 1].internalFaceIndex1 = triangles[i];
				PxGeometryHolder substituteGeom; PxTransform preTransform;
				if (useSubstituteGeometry(substituteGeom, preTransform, contactBuffer.contacts[contactBuffer.count - 1], pose0))
				{
					contactBuffer.count--;
					const PxGeometry& geom = substituteGeom.any();
					// PT:: tag: scalar transform*transform
					PxTransform pose = pose0.transform(preTransform);
					PxGeometryQuery::generateTriangleContacts(geom, pose, tri.verts, triangles[i], contactDistance, meshMargin, toleranceLength, contactBuffer);
				}
			}
			if (hasAdjacency)
			{
				contactFilter.addTriangleContacts(contactBuffer.contacts, contactBuffer.count, triangles[i], tri.verts, adjacent);
				contactBuffer.count = 0;
			}
		}
		if (hasAdjacency)
			contactFilter.writeToBuffer(contactBuffer);
		break;
	}
	case PxGeometryType::eCUSTOM:
	{
		const PxCustomGeometry& customGeom1 = static_cast<const PxCustomGeometry&>(geom1);
		if (customGeom1.getCustomType() == CylinderCallbacks::TYPE() ||
			customGeom1.getCustomType() == ConeCallbacks::TYPE()) // It's a CustomConvex
		{
			BaseConvexCallbacks* custom1 = static_cast<BaseConvexCallbacks*>(customGeom1.callbacks);
			if (PxGjkQueryExt::generateContacts(*this, *custom1, pose0, pose1, contactDistance, toleranceLength, contactBuffer))
			{
				PxGeometryHolder substituteGeom; PxTransform preTransform;
				if (useSubstituteGeometry(substituteGeom, preTransform, contactBuffer.contacts[contactBuffer.count - 1], pose0))
				{
					contactBuffer.count--;

					PxU32 oldCount = contactBuffer.count;

					const PxGeometry* pGeom0 = &substituteGeom.any();
					const PxGeometry* pGeom1 = &geom1;
					// PT:: tag: scalar transform*transform
					PxTransform pose = pose0.transform(preTransform);
					immediate::PxGenerateContacts(&pGeom1, &pGeom0, &pose1, &pose, &contactCache, 1, contactRecorder,
						contactDistance, meshContactMargin, toleranceLength, contactCacheAllocator);

					for (int i = oldCount; i < int(contactBuffer.count); ++i)
						contactBuffer.contacts[i].normal = -contactBuffer.contacts[i].normal;
				}
			}
		}
		else
		{
			const PxGeometry* pGeom0 = &geom0;
			const PxGeometry* pGeom1 = &geom1;
			immediate::PxGenerateContacts(&pGeom1, &pGeom0, &pose1, &pose0, &contactCache, 1, contactRecorder,
				contactDistance, meshContactMargin, toleranceLength, contactCacheAllocator);

			for (int i = 0; i < int(contactBuffer.count); ++i)
				contactBuffer.contacts[i].normal = -contactBuffer.contacts[i].normal;
		}
		break;
	}
	default:
		break;
	}

	return contactBuffer.count > 0;
}

PxU32 PxCustomGeometryExt::BaseConvexCallbacks::raycast(const PxVec3& origin, const PxVec3& unitDir, const PxGeometry& geom, const PxTransform& pose,
	PxReal maxDist, PxHitFlags /*hitFlags*/, PxU32 /*maxHits*/, PxGeomRaycastHit* rayHits, PxU32 /*stride*/, PxRaycastThreadContext*) const
{
	// When FLT_MAX is used as maxDist, it works bad with GJK algorithm.
	// Here I compute the maximum needed distance (wiseDist) as the diagonal
	// of the bounding box of both the geometry and the ray origin.
	PxBounds3 bounds = PxBounds3::transformFast(pose, getLocalBounds(geom));
	bounds.include(origin);
	PxReal wiseDist = PxMin(maxDist, bounds.getDimensions().magnitude());
	PxReal t;
	PxVec3 n, p;
	if (PxGjkQuery::raycast(*this, pose, origin, unitDir, wiseDist, t, n, p))
	{
		PxGeomRaycastHit& hit = *rayHits;
		hit.distance = t;
		hit.position = p;
		hit.normal = n;
		hit.flags |= PxHitFlag::ePOSITION | PxHitFlag::eNORMAL;
		return 1;
	}

	return 0;
}

bool PxCustomGeometryExt::BaseConvexCallbacks::overlap(const PxGeometry& /*geom0*/, const PxTransform& pose0, const PxGeometry& geom1, const PxTransform& pose1, PxOverlapThreadContext*) const
{
	switch (geom1.getType())
	{
	case PxGeometryType::eSPHERE:
	case PxGeometryType::eCAPSULE:
	case PxGeometryType::eBOX:
	case PxGeometryType::eCONVEXMESH:
	{
		PxGjkQueryExt::ConvexGeomSupport geomSupport(geom1);
		if (PxGjkQuery::overlap(*this, geomSupport, pose0, pose1))
			return true;
		break;
	}
	default:
		break;
	}

	return false;
}

bool PxCustomGeometryExt::BaseConvexCallbacks::sweep(const PxVec3& unitDir, const PxReal maxDist,
	const PxGeometry& geom0, const PxTransform& pose0, const PxGeometry& geom1, const PxTransform& pose1,
	PxGeomSweepHit& sweepHit, PxHitFlags /*hitFlags*/, const PxReal inflation, PxSweepThreadContext*) const
{
	PxBounds3 bounds0 = PxBounds3::transformFast(pose0, getLocalBounds(geom0));

	switch (geom1.getType())
	{
	case PxGeometryType::eSPHERE:
	case PxGeometryType::eCAPSULE:
	case PxGeometryType::eBOX:
	case PxGeometryType::eCONVEXMESH:
	{
		// See comment in BaseConvexCallbacks::raycast
		PxBounds3 bounds; PxGeometryQuery::computeGeomBounds(bounds, geom1, pose1, 0, inflation);
		bounds.include(bounds0);
		PxReal wiseDist = PxMin(maxDist, bounds.getDimensions().magnitude());
		PxGjkQueryExt::ConvexGeomSupport geomSupport(geom1, inflation);
		PxReal t;
		PxVec3 n, p;
		if (PxGjkQuery::sweep(*this, geomSupport, pose0, pose1, unitDir, wiseDist, t, n, p))
		{
			sweepHit.distance = t;
			sweepHit.position = p;
			sweepHit.normal = n;
			sweepHit.flags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL;
			return true;
		}
		break;
	}
	// sweep against triangle meshes and height fields for CCD support
	case PxGeometryType::eTRIANGLEMESH:
	case PxGeometryType::eHEIGHTFIELD:
	{
		PxReal radius = getLocalBounds(geom0).getExtents().magnitude();
		PxGeometryHolder sweepGeom = PxSphereGeometry(radius + inflation);
		PxTransform sweepGeomPose = pose0;
		// for some reason, capsule can't have near zero height
		// [assert @ source\geomutils\src\intersection\GuIntersectionCapsuleTriangle.cpp(37)]
		// so I use capsule only if height is larger than 0.1% of radius
		if (maxDist > radius * 0.001f)
		{
			sweepGeom = PxCapsuleGeometry(radius + inflation, maxDist * 0.5f);
			sweepGeomPose = PxTransform(pose0.p - unitDir * maxDist * 0.5f, PxShortestRotation(PxVec3(1, 0, 0), unitDir));
		}
		PxU32 triangles[MAX_TRIANGLES];
		bool overflow = false;
		PxU32 triangleCount = (geom1.getType() == PxGeometryType::eTRIANGLEMESH) ?
			PxMeshQuery::findOverlapTriangleMesh(sweepGeom.any(), sweepGeomPose, static_cast<const PxTriangleMeshGeometry&>(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow) :
			PxMeshQuery::findOverlapHeightField(sweepGeom.any(), sweepGeomPose, static_cast<const PxHeightFieldGeometry&>(geom1), pose1, triangles, MAX_TRIANGLES, 0, overflow);
		if(overflow)
			PxGetFoundation().error(PxErrorCode::eDEBUG_INFO, PX_FL, "PxCustomGeometryExt::BaseConvexCallbacks::sweep() Too many triangles.\n");
		sweepHit.distance = PX_MAX_F32;
		const PxTransform identityPose(PxIdentity);
		for (PxU32 i = 0; i < triangleCount; ++i)
		{
			PxTriangle tri;
			if (geom1.getType() == PxGeometryType::eTRIANGLEMESH)
				PxMeshQuery::getTriangle(static_cast<const PxTriangleMeshGeometry&>(geom1), pose1, triangles[i], tri);
			else
				PxMeshQuery::getTriangle(static_cast<const PxHeightFieldGeometry&>(geom1), pose1, triangles[i], tri);
			TriangleSupport triSupport(tri.verts[0], tri.verts[1], tri.verts[2], inflation);
			PxBounds3 bounds = bounds0;
			for (PxU32 j = 0; j < 3; ++j)
				bounds.include(tri.verts[j]);
			bounds.fattenFast(inflation);
			// See comment in BaseConvexCallbacks::raycast
			PxReal wiseDist = PxMin(maxDist, bounds.getDimensions().magnitude());
			PxReal t;
			PxVec3 n, p;
			if (PxGjkQuery::sweep(*this, triSupport, pose0, identityPose, unitDir, wiseDist, t, n, p))
			{
				if (sweepHit.distance > t)
				{
					sweepHit.faceIndex = triangles[i];
					sweepHit.distance = t;
					sweepHit.position = p;
					sweepHit.normal = n;
					sweepHit.flags = PxHitFlag::ePOSITION | PxHitFlag::eNORMAL | PxHitFlag::eFACE_INDEX;
				}
			}
		}
		if (sweepHit.distance < PX_MAX_F32)
			return true;
		break;
	}
	default:
		break;
	}

	return false;
}

bool PxCustomGeometryExt::BaseConvexCallbacks::usePersistentContactManifold(const PxGeometry& /*geometry*/, PxReal& breakingThreshold) const
{
	// Even if we don't use persistent manifold, we still need to set proper breakingThreshold
	// because the other geometry still can force the PCM usage. FLT_EPSILON ensures that
	// the contacts will be discarded and recomputed every frame if actor moves.
	breakingThreshold = FLT_EPSILON;
	return false;
}

void PxCustomGeometryExt::BaseConvexCallbacks::setMargin(float m)
{
	margin = m;
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtBaseConvexCallbacks, margin, *this, margin);
#endif
}

///////////////////////////////////////////////////////////////////////////////

IMPLEMENT_CUSTOM_GEOMETRY_TYPE(PxCustomGeometryExt::CylinderCallbacks)

PxCustomGeometryExt::CylinderCallbacks::CylinderCallbacks(float _height, float _radius, int _axis, float _margin)
	:
	BaseConvexCallbacks(_margin), height(_height), radius(_radius), axis(_axis)
{
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_WRITE_SCOPE_BEGIN(pvdWriter, pvdRegData)
	OMNI_PVD_CREATE_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, *this);
	OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtBaseConvexCallbacks, margin, *static_cast<BaseConvexCallbacks*>(this), margin);
	OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, height, *this, height);
	OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, radius, *this, radius);
	OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, axis, *this, axis);
	OMNI_PVD_WRITE_SCOPE_END
#endif
}

void PxCustomGeometryExt::CylinderCallbacks::setHeight(float h)
{
	height = h;
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, height, *this, height);
#endif
}

void PxCustomGeometryExt::CylinderCallbacks::setRadius(float r)
{
	radius = r;
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, radius, *this, radius);
#endif
}

void PxCustomGeometryExt::CylinderCallbacks::setAxis(int a)
{
	axis = a;
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtCylinderCallbacks, axis, *this, axis);
#endif
}

void PxCustomGeometryExt::CylinderCallbacks::visualize(const PxGeometry&, PxRenderOutput& out, const PxTransform& transform, const PxBounds3&) const
{
	const float ERR = 0.001f;

	out << gCollisionShapeColor;
	out << transform;

	int axis1 = (axis + 1) % 3;
	int axis2 = (axis + 2) % 3;

	PxVec3 zr(PxZero), rd(PxZero), ax(PxZero), ax1(PxZero), ax2(PxZero), r0(PxZero), r1(PxZero);
	ax[axis] = ax1[axis1] = ax2[axis2] = 1.0f;
	r0[axis1] = r1[axis2] = radius;

	rd[axis1] = radius;
	rd[axis] = -(height * 0.5f + margin);
	drawCircle(zr, rd, ax, ERR, out);
	rd[axis] = (height * 0.5f + margin);
	drawCircle(zr, rd, ax, ERR, out);
	rd[axis1] = radius + margin;
	rd[axis] = -(height * 0.5f);
	drawCircle(zr, rd, ax, ERR, out);
	rd[axis] = (height * 0.5f);
	drawCircle(zr, rd, ax, ERR, out);

	drawLine(-ax * height * 0.5f + ax1 * (radius + margin), ax * height * 0.5f + ax1 * (radius + margin), out);
	drawLine(-ax * height * 0.5f - ax1 * (radius + margin), ax * height * 0.5f - ax1 * (radius + margin), out);
	drawLine(-ax * height * 0.5f + ax2 * (radius + margin), ax * height * 0.5f + ax2 * (radius + margin), out);
	drawLine(-ax * height * 0.5f - ax2 * (radius + margin), ax * height * 0.5f - ax2 * (radius + margin), out);

	drawQuarterCircle(-ax * height * 0.5f + ax1 * radius, -ax * margin, -ax2, ERR, out);
	drawQuarterCircle(-ax * height * 0.5f - ax1 * radius, -ax * margin, ax2, ERR, out);
	drawQuarterCircle(-ax * height * 0.5f + ax2 * radius, -ax * margin, ax1, ERR, out);
	drawQuarterCircle(-ax * height * 0.5f - ax2 * radius, -ax * margin, -ax1, ERR, out);

	drawQuarterCircle(ax * height * 0.5f + ax1 * radius, ax * margin, ax2, ERR, out);
	drawQuarterCircle(ax * height * 0.5f - ax1 * radius, ax * margin, -ax2, ERR, out);
	drawQuarterCircle(ax * height * 0.5f + ax2 * radius, ax * margin, -ax1, ERR, out);
	drawQuarterCircle(ax * height * 0.5f - ax2 * radius, ax * margin, ax1, ERR, out);
}

PxVec3 PxCustomGeometryExt::CylinderCallbacks::supportLocal(const PxVec3& dir) const
{
	float halfHeight = height * 0.5f;
	PxVec3 d = dir.getNormalized();

	switch (axis)
	{
	case 0: // X
	{
		if (PxSign2(d.x) != 0 && PxSign2(d.y) == 0 && PxSign2(d.z) == 0) return PxVec3(PxSign2(d.x) * halfHeight, 0, 0);
		return PxVec3(PxSign2(d.x) * halfHeight, 0, 0) + PxVec3(0, d.y, d.z).getNormalized() * radius;
	}
	case 1: // Y
	{
		if (PxSign2(d.x) == 0 && PxSign2(d.y) != 0 && PxSign2(d.z) == 0) return PxVec3(0, PxSign2(d.y) * halfHeight, 0);
		return PxVec3(0, PxSign2(d.y) * halfHeight, 0) + PxVec3(d.x, 0, d.z).getNormalized() * radius;
	}
	case 2: // Z
	{
		if (PxSign2(d.x) == 0 && PxSign2(d.y) == 0 && PxSign2(d.z) != 0) return PxVec3(0, 0, PxSign2(d.z) * halfHeight);
		return PxVec3(0, 0, PxSign2(d.z) * halfHeight) + PxVec3(d.x, d.y, 0).getNormalized() * radius;
	}
	}

	return PxVec3(0);
}

void PxCustomGeometryExt::CylinderCallbacks::computeMassProperties(const PxGeometry& /*geometry*/, PxMassProperties& massProperties) const
{
	if (margin == 0)
	{
		PxMassProperties& mass = massProperties;
		float R = radius, H = height;

		mass.mass = PxPi * R * R * H;
		mass.inertiaTensor = PxMat33(PxZero);
		mass.centerOfMass = PxVec3(PxZero);

		float I0 = mass.mass * R * R / 2.0f;
		float I1 = mass.mass * (3 * R * R + H * H) / 12.0f;

		mass.inertiaTensor[axis][axis] = I0;
		mass.inertiaTensor[(axis + 1) % 3][(axis + 1) % 3] = mass.inertiaTensor[(axis + 2) % 3][(axis + 2) % 3] = I1;
	}
	else
	{
		const int SLICE_COUNT = 32;
		PxMassProperties sliceMasses[SLICE_COUNT];
		PxTransform slicePoses[SLICE_COUNT];
		float sliceHeight = height / SLICE_COUNT;

		for (int i = 0; i < SLICE_COUNT; ++i)
		{
			float t = -height * 0.5f + i * sliceHeight + sliceHeight * 0.5f;
			float R = getRadiusAtHeight(t), H = sliceHeight;

			PxMassProperties mass;
			mass.mass = PxPi * R * R * H;
			mass.inertiaTensor = PxMat33(PxZero);
			mass.centerOfMass = PxVec3(PxZero);

			float I0 = mass.mass * R * R / 2.0f;
			float I1 = mass.mass * (3 * R * R + H * H) / 12.0f;

			mass.inertiaTensor[axis][axis] = I0;
			mass.inertiaTensor[(axis + 1) % 3][(axis + 1) % 3] = mass.inertiaTensor[(axis + 2) % 3][(axis + 2) % 3] = I1;
			mass.centerOfMass[axis] = t;

			sliceMasses[i] = mass;
			slicePoses[i] = PxTransform(PxIdentity);
		}

		massProperties = PxMassProperties::sum(sliceMasses, slicePoses, SLICE_COUNT);
	}
}

bool PxCustomGeometryExt::CylinderCallbacks::useSubstituteGeometry(PxGeometryHolder& geom, PxTransform& preTransform, const PxContactPoint& p, const PxTransform& pose0) const
{
	// here I check if we contact with the cylender bases or the lateral surface
	// where more than 1 contact point can be generated.
	PxVec3 locN = pose0.rotateInv(p.normal);
	float nAng = acosf(PxClamp(-locN[axis], -1.0f, 1.0f));
	float epsAng = PxPi / 36.0f; // 5 degrees
	if (nAng < epsAng || nAng > PxPi - epsAng)
	{
		// if we contact with the bases
		// make the substitute geometry a box and rotate it so one of
		// the corners matches the contact point
		PxVec3 halfSize;
		halfSize[axis] = height * 0.5f + margin;
		halfSize[(axis + 1) % 3] = halfSize[(axis + 2) % 3] = radius / sqrtf(2.0f);
		geom = PxBoxGeometry(halfSize);
		PxVec3 axisDir(PxZero); axisDir[axis] = 1.0f;
		PxVec3 locP = pose0.transformInv(p.point);
		float s1 = locP[(axis + 1) % 3], s2 = locP[(axis + 2) % 3];
		float ang = ((s1 * s1) + (s2 * s2) > 1e-3f) ? atan2f(s2, s1) : 0;
		preTransform = PxTransform(PxQuat(ang + PxPi * 0.25f, axisDir));
		return true;
	}
	else if (nAng > PxPiDivTwo - epsAng && nAng < PxPiDivTwo + epsAng)
	{
		// if it's the lateral surface
		// make the substitute geometry a capsule and rotate it so it aligns
		// with the cylinder surface
		geom = PxCapsuleGeometry(radius + margin, height * 0.5f);
		switch (axis)
		{
		case 0: preTransform = PxTransform(PxIdentity); break;
		case 1: preTransform = PxTransform(PxQuat(PxPi * 0.5f, PxVec3(0, 0, 1))); break;
		case 2: preTransform = PxTransform(PxQuat(PxPi * 0.5f, PxVec3(0, 1, 0))); break;
		}
		return true;
	}
	return false;
}

float PxCustomGeometryExt::CylinderCallbacks::getRadiusAtHeight(float h) const
{
	if (h >= -height * 0.5f && h <= height * 0.5f)
		return radius + margin;

	if (h < -height * 0.5f)
	{
		float a = -h - height * 0.5f;
		return radius + sqrtf(margin * margin - a * a);
	}

	if (h > height * 0.5f)
	{
		float a = h - height * 0.5f;
		return radius + sqrtf(margin * margin - a * a);
	}

	PX_ASSERT(0);
	return 0;
}

///////////////////////////////////////////////////////////////////////////////

IMPLEMENT_CUSTOM_GEOMETRY_TYPE(PxCustomGeometryExt::ConeCallbacks)

PxCustomGeometryExt::ConeCallbacks::ConeCallbacks(float _height, float _radius, int _axis, float _margin)
	:
	BaseConvexCallbacks(_margin), height(_height), radius(_radius), axis(_axis)
{
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_WRITE_SCOPE_BEGIN(pvdWriter, pvdRegData)
	OMNI_PVD_CREATE_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, *this);
	OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtBaseConvexCallbacks, margin, *static_cast<BaseConvexCallbacks*>(this), margin);
	OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, height, *this, height);
	OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, radius, *this, radius);
	OMNI_PVD_SET_EXPLICIT(pvdWriter, pvdRegData, OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, axis, *this, axis);
	OMNI_PVD_WRITE_SCOPE_END
#endif
}

void PxCustomGeometryExt::ConeCallbacks::setHeight(float h)
{
	height = h;
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, height, *this, height);
#endif
}

void PxCustomGeometryExt::ConeCallbacks::setRadius(float r)
{
	radius = r;
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, radius, *this, radius);
#endif
}

void PxCustomGeometryExt::ConeCallbacks::setAxis(int a)
{
	axis = a;
#if PX_SUPPORT_OMNI_PVD
	OMNI_PVD_SET(OMNI_PVD_CONTEXT_HANDLE, PxCustomGeometryExtConeCallbacks, axis, *this, axis);
#endif
}

void PxCustomGeometryExt::ConeCallbacks::visualize(const PxGeometry&, PxRenderOutput& out, const PxTransform& transform, const PxBounds3&) const
{
	const float ERR = 0.001f;

	out << gCollisionShapeColor;
	out << transform;

	int axis1 = (axis + 1) % 3;
	int axis2 = (axis + 2) % 3;

	PxVec3 zr(PxZero), rd(PxZero), ax(PxZero), ax1(PxZero), ax2(PxZero), r0(PxZero), r1(PxZero);
	ax[axis] = ax1[axis1] = ax2[axis2] = 1.0f;
	r0[axis1] = r1[axis2] = radius;

	float ang = atan2f(radius, height);
	float aSin = sinf(ang);

	rd[axis1] = radius;
	rd[axis] = -(height * 0.5f + margin);
	drawCircle(zr, rd, ax, ERR, out);
	rd[axis] = -(height * 0.5f) + margin * aSin;
	rd[axis1] = getRadiusAtHeight(rd[axis]);
	drawCircle(zr, rd, ax, ERR, out);
	rd[axis] = height * 0.5f + margin * aSin;
	rd[axis1] = getRadiusAtHeight(rd[axis]);
	drawCircle(zr, rd, ax, ERR, out);

	float h0 = -height * 0.5f + margin * aSin, h1 = height * 0.5f + margin * aSin;
	float s0 = getRadiusAtHeight(h0), s1 = getRadiusAtHeight(h1);
	drawLine(ax * h0 + ax1 * s0, ax * h1 + ax1 * s1, out);
	drawLine(ax * h0 - ax1 * s0, ax * h1 - ax1 * s1, out);
	drawLine(ax * h0 + ax2 * s0, ax * h1 + ax2 * s1, out);
	drawLine(ax * h0 - ax2 * s0, ax * h1 - ax2 * s1, out);

	drawArc(-ax * height * 0.5f + ax1 * radius, -ax * margin, -ax2, PxPiDivTwo + ang, ERR, out);
	drawArc(-ax * height * 0.5f - ax1 * radius, -ax * margin, ax2, PxPiDivTwo + ang, ERR, out);
	drawArc(-ax * height * 0.5f + ax2 * radius, -ax * margin, ax1, PxPiDivTwo + ang, ERR, out);
	drawArc(-ax * height * 0.5f - ax2 * radius, -ax * margin, -ax1, PxPiDivTwo + ang, ERR, out);

	drawArc(ax * height * 0.5f, ax * margin, ax2, PxPiDivTwo - ang, ERR, out);
	drawArc(ax * height * 0.5f, ax * margin, -ax2, PxPiDivTwo - ang, ERR, out);
	drawArc(ax * height * 0.5f, ax * margin, -ax1, PxPiDivTwo - ang, ERR, out);
	drawArc(ax * height * 0.5f, ax * margin, ax1, PxPiDivTwo - ang, ERR, out);
}

PxVec3 PxCustomGeometryExt::ConeCallbacks::supportLocal(const PxVec3& dir) const
{
	float halfHeight = height * 0.5f;
	float cosAlph = radius / sqrtf(height * height + radius * radius);
	PxVec3 d = dir.getNormalized();

	switch (axis)
	{
	case 0: // X
	{
		if (d.x > cosAlph || (PxSign2(d.x) != 0 && PxSign2(d.y) == 0 && PxSign2(d.z) == 0)) return PxVec3(PxSign2(d.x) * halfHeight, 0, 0);
		return PxVec3(-halfHeight, 0, 0) + PxVec3(0, d.y, d.z).getNormalized() * radius;
	}
	case 1: // Y
	{
		if (d.y > cosAlph || (PxSign2(d.y) != 0 && PxSign2(d.x) == 0 && PxSign2(d.z) == 0)) return PxVec3(0, PxSign2(d.y) * halfHeight, 0);
		return PxVec3(0, -halfHeight, 0) + PxVec3(d.x, 0, d.z).getNormalized() * radius;
	}
	case 2: // Z
	{
		if (d.z > cosAlph || (PxSign2(d.z) != 0 && PxSign2(d.x) == 0 && PxSign2(d.y) == 0)) return PxVec3(0, 0, PxSign2(d.z) * halfHeight);
		return PxVec3(0, 0, -halfHeight) + PxVec3(d.x, d.y, 0).getNormalized() * radius;
	}
	}

	return PxVec3(0);
}

void PxCustomGeometryExt::ConeCallbacks::computeMassProperties(const PxGeometry& /*geometry*/, PxMassProperties& massProperties) const
{
	if (margin == 0)
	{
		PxMassProperties& mass = massProperties;

		float H = height, R = radius;

		mass.mass = PxPi * R * R * H / 3.0f;
		mass.inertiaTensor = PxMat33(PxZero);
		mass.centerOfMass = PxVec3(PxZero);

		float I0 = mass.mass * R * R * 3.0f / 10.0f;
		float I1 = mass.mass * (R * R * 3.0f / 20.0f + H * H * 3.0f / 80.0f);

		mass.inertiaTensor[axis][axis] = I0;
		mass.inertiaTensor[(axis + 1) % 3][(axis + 1) % 3] = mass.inertiaTensor[(axis + 2) % 3][(axis + 2) % 3] = I1;

		mass.centerOfMass[axis] = -H / 4.0f;

		mass.inertiaTensor = PxMassProperties::translateInertia(mass.inertiaTensor, mass.mass, mass.centerOfMass);
	}
	else
	{
		const int SLICE_COUNT = 32;
		PxMassProperties sliceMasses[SLICE_COUNT];
		PxTransform slicePoses[SLICE_COUNT];
		float sliceHeight = height / SLICE_COUNT;

		for (int i = 0; i < SLICE_COUNT; ++i)
		{
			float t = -height * 0.5f + i * sliceHeight + sliceHeight * 0.5f;
			float R = getRadiusAtHeight(t), H = sliceHeight;

			PxMassProperties mass;
			mass.mass = PxPi * R * R * H;
			mass.inertiaTensor = PxMat33(PxZero);
			mass.centerOfMass = PxVec3(PxZero);

			float I0 = mass.mass * R * R / 2.0f;
			float I1 = mass.mass * (3 * R * R + H * H) / 12.0f;

			mass.inertiaTensor[axis][axis] = I0;
			mass.inertiaTensor[(axis + 1) % 3][(axis + 1) % 3] = mass.inertiaTensor[(axis + 2) % 3][(axis + 2) % 3] = I1;
			mass.centerOfMass[axis] = t;

			sliceMasses[i] = mass;
			slicePoses[i] = PxTransform(PxIdentity);
		}

		massProperties = PxMassProperties::sum(sliceMasses, slicePoses, SLICE_COUNT);

		massProperties.inertiaTensor = PxMassProperties::translateInertia(massProperties.inertiaTensor, massProperties.mass, massProperties.centerOfMass);
	}
}

bool PxCustomGeometryExt::ConeCallbacks::useSubstituteGeometry(PxGeometryHolder& geom, PxTransform& preTransform, const PxContactPoint& p, const PxTransform& pose0) const
{
	// here I check if we contact with the cone base or the lateral surface
	// where more than 1 contact point can be generated.
	PxVec3 locN = pose0.rotateInv(p.normal);
	float nAng = acosf(PxClamp(-locN[axis], -1.0f, 1.0f));
	float epsAng = PxPi / 36.0f; // 5 degrees
	float coneAng = atan2f(radius, height);
	if (nAng > PxPi - epsAng)
	{
		// if we contact with the base
		// make the substitute geometry a box and rotate it so one of
		// the corners matches the contact point
		PxVec3 halfSize;
		halfSize[axis] = height * 0.5f + margin;
		halfSize[(axis + 1) % 3] = halfSize[(axis + 2) % 3] = radius / sqrtf(2.0f);
		geom = PxBoxGeometry(halfSize);
		PxVec3 axisDir(PxZero); axisDir[axis] = 1.0f;
		PxVec3 locP = pose0.transformInv(p.point);
		float s1 = locP[(axis + 1) % 3], s2 = locP[(axis + 2) % 3];
		float ang = ((s1 * s1) + (s2 * s2) > 1e-3f) ? atan2f(s2, s1) : 0;
		preTransform = PxTransform(PxQuat(ang + PxPi * 0.25f, axisDir));
		return true;
	}
	else if (nAng > PxPiDivTwo - coneAng - epsAng && nAng < PxPiDivTwo - coneAng + epsAng)
	{
		// if it's the lateral surface
		// make the substitute geometry a capsule and rotate it so it aligns
		// with the cone surface
		geom = PxCapsuleGeometry(radius * 0.25f + margin, sqrtf(height * height + radius * radius) * 0.5f);
		switch (axis)
		{
		case 0:
		{
			PxVec3 capC = (PxVec3(height * 0.5f, 0, 0) + PxVec3(-height * 0.5f, radius, 0)) * 0.5f - PxVec3(radius, height, 0).getNormalized() * radius * 0.25f;
			preTransform = PxTransform(capC, PxQuat(-coneAng, PxVec3(0, 0, 1)));
			break;
		}
		case 1:
		{
			PxVec3 capC = (PxVec3(0, height * 0.5f, 0) + PxVec3(0, -height * 0.5f, radius)) * 0.5f - PxVec3(0, radius, height).getNormalized() * radius * 0.25f;
			preTransform = PxTransform(capC, PxQuat(-coneAng, PxVec3(1, 0, 0)) * PxQuat(PxPiDivTwo, PxVec3(0, 0, 1)));
			break;
		}
		case 2:
		{
			PxVec3 capC = (PxVec3(0, 0, height * 0.5f) + PxVec3(radius, 0, -height * 0.5f)) * 0.5f - PxVec3(height, 0, radius).getNormalized() * radius * 0.25f;
			preTransform = PxTransform(capC, PxQuat(-coneAng, PxVec3(0, 1, 0)) * PxQuat(PxPiDivTwo, PxVec3(0, 1, 0)));
			break;
		}
		}
		PxVec3 axisDir(PxZero); axisDir[axis] = 1.0f;
		float n1 = -locN[(axis + 1) % 3], n2 = -locN[(axis + 2) % 3];
		float ang = atan2f(n2, n1);
		// PT:: tag: scalar transform*transform
		preTransform = PxTransform(PxQuat(ang, axisDir)) * preTransform;
		return true;
	}
	return false;
}

float PxCustomGeometryExt::ConeCallbacks::getRadiusAtHeight(float h) const
{
	float angle = atan2f(radius, height);
	float aSin = sinf(angle);
	float aCos = cosf(angle);

	if (h >= -height * 0.5f + margin * aSin && h <= height * 0.5f + margin * aSin)
		return radius * (height * 0.5f - h) / height + margin / aCos;

	if (h < -height * 0.5f + margin * aSin)
	{
		float a = -h - height * 0.5f;
		return radius + sqrtf(margin * margin - a * a);
	}

	if (h > height * 0.5f + margin * aSin)
	{
		float a = h - height * 0.5f;
		return sqrtf(margin * margin - a * a);
	}

	PX_ASSERT(0);
	return 0;
}
