diff options
| author | git perforce import user <a@b> | 2016-10-25 12:29:14 -0600 |
|---|---|---|
| committer | Sheikh Dawood Abdul Ajees <Sheikh Dawood Abdul Ajees> | 2016-10-25 18:56:37 -0500 |
| commit | 3dfe2108cfab31ba3ee5527e217d0d8e99a51162 (patch) | |
| tree | fa6485c169e50d7415a651bf838f5bcd0fd3bfbd /APEX_1.4/shared/internal/src | |
| download | physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.tar.xz physx-3.4-3dfe2108cfab31ba3ee5527e217d0d8e99a51162.zip | |
Initial commit:
PhysX 3.4.0 Update @ 21294896
APEX 1.4.0 Update @ 21275617
[CL 21300167]
Diffstat (limited to 'APEX_1.4/shared/internal/src')
| -rw-r--r-- | APEX_1.4/shared/internal/src/PvdNxParamSerializer.cpp | 607 | ||||
| -rw-r--r-- | APEX_1.4/shared/internal/src/authoring/ApexCSG.cpp | 3140 | ||||
| -rw-r--r-- | APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp | 1224 | ||||
| -rw-r--r-- | APEX_1.4/shared/internal/src/authoring/ApexCSGMeshCleaning.cpp | 559 | ||||
| -rw-r--r-- | APEX_1.4/shared/internal/src/authoring/Cutout.cpp | 1908 | ||||
| -rw-r--r-- | APEX_1.4/shared/internal/src/authoring/Fracturing.cpp | 7349 | ||||
| -rw-r--r-- | APEX_1.4/shared/internal/src/authoring/Noise.h | 131 | ||||
| -rw-r--r-- | APEX_1.4/shared/internal/src/authoring/NoiseUtils.h | 156 |
8 files changed, 15074 insertions, 0 deletions
diff --git a/APEX_1.4/shared/internal/src/PvdNxParamSerializer.cpp b/APEX_1.4/shared/internal/src/PvdNxParamSerializer.cpp new file mode 100644 index 00000000..54995948 --- /dev/null +++ b/APEX_1.4/shared/internal/src/PvdNxParamSerializer.cpp @@ -0,0 +1,607 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + +#if TODO_PVD_NXPARAM_SERIALIZER + +#include "ApexUsingNamespace.h" +#include "PvdNxParamSerializer.h" +#include "nvparameterized/NvParameterized.h" +#include "PvdConnection.h" +#include "PVDCommLayerDebuggerStream.h" +#include "ApexString.h" +#include "PxMat33.h" +#include "PxMat34Legacy.h" + +using namespace PVD; +using namespace nvidia::apex; +using namespace NvParameterized; + +namespace PvdNxParamSerializer +{ + + +inline void Append(ApexSimpleString& inStr, const char* inAppend) +{ + while (inAppend && *inAppend) + { + inStr += *inAppend; + ++inAppend; + } +} + +inline const char* GetVariableName(ApexSimpleString& inWorkString, const char* inNamePrefix, const char* inVarName) +{ + if (inNamePrefix && *inNamePrefix) + { + Append(inWorkString, inNamePrefix); + Append(inWorkString, "."); + Append(inWorkString, inVarName); + return inWorkString.c_str(); + } + return inVarName; +} + + +/** + * The serialization architecture is complicated by dynamic arrays of information. I made a decision that a dynamic array cannot + * contain another dynamic array in the debugger's world thus if the real world has this limitation then there will be data + * loss when dealing with the debugger. + * + * In general, the TopLevel handler sends across properties as it encounters them giving property names dotted notations + * when they are part of a struct. The debugger UI on the other end has logic create objects based on the dotted names + * and thus even though the database layer doesn't support struct objects the UI makes it appear as though it does. + * Static or non-resizeable-arrays are treated as a struct. + * + * Dynamic arrays require at least two passes and perhaps three. The first pass only needs to check out the first object. + * The next pass creates all ref objects if necessary and needs to pass over every item in the array. + * the third pass collects data values and sends such values over the wire using the array blocks. + * + * An initial test with the APEX integration tests sent 11 megs of data into the database; so I imagine + * most of the information in the APEX system, at least as far as initialization and static + * or asset based information is being sent to the debugger. + * + * Due to time constraints I haven't been able to upgrade the PVD UI to handle arrays well, which is unfortunate because + * it seems that most of the APEX information is in arrays. + */ + +#define HANDLE_PARAM_TYPE( datatype, paramFuncName ) { \ + datatype tmp; \ + handle.paramFuncName(tmp); \ + return HandleDataType( tmp, theVariableName ); } + + +class TopLevelParamTreeHandler +{ +protected: + NvParameterized::Interface* mObj; + PvdDataStream* mRemoteDebugger; + uint64_t mCurPvdObj; + const char* mVariablePrefix; + uint64_t mDynamicArrayHandle; + +public: + TopLevelParamTreeHandler(NvParameterized::Interface* obj, PvdDataStream* remoteDebugger, uint64_t inCurrentObject, const char* inVariablePrefix = NULL) + : mObj(obj) + , mRemoteDebugger(remoteDebugger) + , mCurPvdObj(inCurrentObject) + , mVariablePrefix(inVariablePrefix) + { + mDynamicArrayHandle = mCurPvdObj + 1; + } + + TopLevelParamTreeHandler(const TopLevelParamTreeHandler& inOther, const char* inNewPrefix) + : mObj(inOther.mObj) + , mRemoteDebugger(inOther.mRemoteDebugger) + , mCurPvdObj(inOther.mCurPvdObj) + , mVariablePrefix(inNewPrefix) + , mDynamicArrayHandle(inOther.mDynamicArrayHandle) + {} + + template<typename THandlerType> + inline NvParameterized::ErrorType DoHandleStruct(NvParameterized::Handle& inHandle, const char* inParamName) + { + const NvParameterized::Definition* paramDef = inHandle.parameterDefinition(); + const char* newPrefix = mVariablePrefix; + ApexSimpleString theWorker; + if (inParamName && *inParamName) + { + newPrefix = GetVariableName(theWorker, mVariablePrefix, inParamName); + } + + THandlerType theNewHandler(*this, newPrefix); + for (int i = 0; i < paramDef->numChildren(); ++i) + { + inHandle.set(i); + theNewHandler.TraverseParamDefTree(inHandle); + inHandle.popIndex(); + } + TransferStackInformationBack(theNewHandler); + return(NvParameterized::ERROR_NONE); + } + template<typename THandlerType> + inline NvParameterized::ErrorType DoHandleArray(NvParameterized::Handle& handle, const char* paramName) + { + int arraySize = 0; + + const char* arrayName = paramName; + ApexSimpleString theVariableNamePrefix; + Append(theVariableNamePrefix, arrayName); + + const Definition* theDef = handle.parameterDefinition(); + bool isFixedSize = theDef->arraySizeIsFixed(); + isFixedSize = false; + + if (handle.getArraySize(arraySize) != NvParameterized::ERROR_NONE) + { + return(ERROR_INVALID_ARRAY_SIZE); + } + + ApexSimpleString theWorkString(theVariableNamePrefix); + + + for (int i = 0; i < arraySize; ++i) + { + theWorkString = theVariableNamePrefix; + theWorkString += '['; + char tempBuf[20]; + shdfnd::snprintf(tempBuf, 20, "%7d", i); + Append(theWorkString, tempBuf); + theWorkString += ']'; + handle.set(i); + THandlerType theNewHandler(*this, theWorkString.c_str()); + theNewHandler.TraverseParamDefTree(handle); + handle.popIndex(); + TransferStackInformationBack(theNewHandler); + } + return(NvParameterized::ERROR_NONE); + } + virtual ~TopLevelParamTreeHandler() {} + virtual void TransferStackInformationBack(const TopLevelParamTreeHandler& inOther) + { + mDynamicArrayHandle = inOther.mDynamicArrayHandle; + } + virtual NvParameterized::ErrorType HandleStruct(NvParameterized::Handle& handle, const char* inParamName); + virtual NvParameterized::ErrorType HandleArray(NvParameterized::Handle& handle, const char* paramName); + virtual NvParameterized::ErrorType HandleDynamicArray(NvParameterized::Handle& handle, const char* paramName); + virtual NvParameterized::ErrorType HandleRef(NvParameterized::Handle& handle, const char*); + virtual NvParameterized::ErrorType HandleProperty(const PvdCommLayerValue& inValue, const char* inParamName); + template<typename TDataType> + inline NvParameterized::ErrorType HandleDataType(const TDataType& inDataType, const char* inParamName) + { + return HandleProperty(CreateCommLayerValue(inDataType), inParamName); + } + + virtual NvParameterized::ErrorType TraverseParamDefTree(NvParameterized::Handle& handle); +}; + +//Run through a type define all the properties ignoring dynamic arrays and +//ref objs. +class PropertyDefinitionTreeHandler : public TopLevelParamTreeHandler +{ + physx::Array<uint32_t> mProperties; + physx::Array<PVD::PvdCommLayerDatatype> mDatatypes; + uint32_t mClassKey; + bool hasRefs; +public: + PropertyDefinitionTreeHandler(const TopLevelParamTreeHandler& inOther, uint32_t inClassKey) + : TopLevelParamTreeHandler(inOther, "") + , mClassKey(inClassKey) + , hasRefs(false) + {} + + PropertyDefinitionTreeHandler(const TopLevelParamTreeHandler& inOther, const char* inParamName) + : TopLevelParamTreeHandler(inOther, inParamName) + { + const PropertyDefinitionTreeHandler& realOther = static_cast<const PropertyDefinitionTreeHandler&>(inOther); + mClassKey = realOther.mClassKey; + hasRefs |= realOther.hasRefs; + } + + virtual NvParameterized::ErrorType HandleDynamicArray(NvParameterized::Handle&, const char*) + { + return NvParameterized::ERROR_NONE; + } + virtual NvParameterized::ErrorType HandleArray(NvParameterized::Handle& handle, const char* inParamName) + { + return DoHandleArray<PropertyDefinitionTreeHandler>(handle, inParamName); + } + virtual NvParameterized::ErrorType HandleRef(NvParameterized::Handle&, const char* inParamName) + { + HandleProperty(createInstanceId(0), inParamName); + hasRefs = true; + return NvParameterized::ERROR_NONE; + } + virtual NvParameterized::ErrorType HandleStruct(NvParameterized::Handle& handle, const char* inParamName) + { + return DoHandleStruct<PropertyDefinitionTreeHandler>(handle, inParamName); + } + virtual void TransferStackInformationBack(const TopLevelParamTreeHandler& inOther) + { + const PropertyDefinitionTreeHandler& realOther = static_cast<const PropertyDefinitionTreeHandler&>(inOther); + hasRefs |= realOther.hasRefs; + for (uint32_t idx = 0; idx < realOther.mProperties.size(); ++idx) + { + mProperties.pushBack(realOther.mProperties[idx]); + } + } + + virtual NvParameterized::ErrorType HandleProperty(const PvdCommLayerValue& inValue, const char* inParamName) + { + uint32_t thePropertyKey = HashFunction(inParamName); + mRemoteDebugger->defineProperty(mClassKey, inParamName, NULL, inValue.getDatatype(), thePropertyKey); + mProperties.pushBack(thePropertyKey); + mDatatypes.pushBack(inValue.getDatatype()); + return NvParameterized::ERROR_NONE; + } + uint32_t GetPropertyCount() + { + return mProperties.size(); + } + const uint32_t* GetProperties() + { + return mProperties.begin(); + } + const PVD::PvdCommLayerDatatype* getDatatypes() + { + return mDatatypes.begin(); + } + bool HasRefs() + { + return hasRefs; + } +}; + +//Simply create the parameter ref objects. +class ParamRefTreeHandler : public TopLevelParamTreeHandler +{ +public: + ParamRefTreeHandler(TopLevelParamTreeHandler& inOther) + : TopLevelParamTreeHandler(inOther) + { + } + + ParamRefTreeHandler(TopLevelParamTreeHandler& inOther, const char* inParamName) + : TopLevelParamTreeHandler(inOther, inParamName) + { + } + + virtual NvParameterized::ErrorType HandleStruct(NvParameterized::Handle& handle, const char* inParamName) + { + return DoHandleStruct<ParamRefTreeHandler>(handle, inParamName); + } + + virtual NvParameterized::ErrorType HandleArray(NvParameterized::Handle& handle, const char* inParamName) + { + return DoHandleArray<ParamRefTreeHandler>(handle, inParamName); + } + virtual NvParameterized::ErrorType HandleDynamicArray(NvParameterized::Handle&, const char*) + { + return NvParameterized::ERROR_NONE; + } + virtual NvParameterized::ErrorType HandleProperty(const PvdCommLayerValue& , const char*) + { + return NvParameterized::ERROR_NONE; + } +}; + +class ValueRecorderTreeHandler : public TopLevelParamTreeHandler +{ + physx::Array<PvdCommLayerValue>* mValues; +public: + ValueRecorderTreeHandler(TopLevelParamTreeHandler& inHandler, physx::Array<PvdCommLayerValue>* inValues) + : TopLevelParamTreeHandler(inHandler, "") + , mValues(inValues) + { + } + + ValueRecorderTreeHandler(TopLevelParamTreeHandler& inHandler, const char* inParamName) + : TopLevelParamTreeHandler(inHandler, inParamName) + { + const ValueRecorderTreeHandler& realOther = static_cast< const ValueRecorderTreeHandler& >(inHandler); + mValues = realOther.mValues; + } + + virtual NvParameterized::ErrorType HandleDynamicArray(NvParameterized::Handle&, const char*) + { + return NvParameterized::ERROR_NONE; + } + + virtual NvParameterized::ErrorType HandleStruct(NvParameterized::Handle& handle, const char* inParamName) + { + return DoHandleStruct<ValueRecorderTreeHandler>(handle, inParamName); + } + + virtual NvParameterized::ErrorType HandleArray(NvParameterized::Handle& handle, const char* inParamName) + { + return DoHandleArray<ValueRecorderTreeHandler>(handle, inParamName); + } + + virtual NvParameterized::ErrorType HandleRef(NvParameterized::Handle& handle, const char* inParamName) + { + NvParameterized::Interface* refObj = 0; + if (handle.getParamRef(refObj) != NvParameterized::ERROR_NONE) + { + return(NvParameterized::ERROR_INVALID_PARAMETER_HANDLE); + } + uint64_t refObjId(PtrToPVD(refObj)); + return HandleProperty(createInstanceId(refObjId), inParamName); + } + + virtual NvParameterized::ErrorType HandleProperty(const PvdCommLayerValue& inValue, const char*) + { + mValues->pushBack(inValue); + return NvParameterized::ERROR_NONE; + } + +}; + +class DynamicArrayParamTreeHandler : public TopLevelParamTreeHandler +{ + physx::Array<PvdCommLayerValue> mValues; + ApexSimpleString mTypeName; + uint64_t mInstanceHandle; +public: + DynamicArrayParamTreeHandler(const TopLevelParamTreeHandler& inOther, const char* inNewPrefix, uint64_t inInstanceHandle) + : TopLevelParamTreeHandler(inOther, "") + , mInstanceHandle(inInstanceHandle) + { + Append(mTypeName, mObj->className()); + mTypeName += '.'; + Append(mTypeName, inNewPrefix); + } + + virtual NvParameterized::ErrorType TraverseParamDefTree(NvParameterized::Handle& handle) + { + const NvParameterized::Definition* theDef = handle.parameterDefinition(); + int arraySize = 0; + handle.getArraySize(arraySize); + if (arraySize > 0) + { + uint32_t theClassKey((uint32_t)(size_t)theDef); + mRemoteDebugger->createClass(mTypeName.c_str(), theClassKey); + handle.set(0); + PropertyDefinitionTreeHandler theHandler(*this, theClassKey); + theHandler.TraverseParamDefTree(handle); + handle.popIndex(); + uint32_t thePropertyCount(theHandler.GetPropertyCount()); + if (thePropertyCount) + { + if (theHandler.HasRefs()) + { + for (int idx = 0; idx < arraySize; ++idx) + { + handle.set(idx); + ParamRefTreeHandler refHandler(*this); + refHandler.TraverseParamDefTree(handle); //Create the ref objects + handle.popIndex(); + } + } + mValues.reserve(thePropertyCount); + mRemoteDebugger->beginArrayBlock(theClassKey, mInstanceHandle, theHandler.GetProperties(), theHandler.getDatatypes(), thePropertyCount); + for (int idx = 0; idx < arraySize; ++idx) + { + handle.set(idx); + ValueRecorderTreeHandler valueRecorder(*this, &mValues); + valueRecorder.TraverseParamDefTree(handle); //Set the values in the array. + handle.popIndex(); + uint32_t theValueSize(mValues.size()); + if (theValueSize >= thePropertyCount) + { + mRemoteDebugger->sendArrayObject(&mValues[0]); + } + mValues.clear(); + } + mRemoteDebugger->endArrayBlock(); + } + } + return NvParameterized::ERROR_NONE; + } +}; + +NvParameterized::ErrorType TopLevelParamTreeHandler::HandleStruct(NvParameterized::Handle& handle, const char* inParamName) +{ + return DoHandleStruct<TopLevelParamTreeHandler>(handle, inParamName); +} + +NvParameterized::ErrorType TopLevelParamTreeHandler::HandleArray(NvParameterized::Handle& handle, const char* paramName) +{ + return DoHandleArray<TopLevelParamTreeHandler>(handle, paramName); +} + +NvParameterized::ErrorType TopLevelParamTreeHandler::HandleDynamicArray(NvParameterized::Handle& handle, const char* paramName) +{ + int arraySize = 0; + handle.getArraySize(arraySize); + if (arraySize > 0) + { + uint64_t theArrayHandle = mDynamicArrayHandle; + ++mDynamicArrayHandle; + DynamicArrayParamTreeHandler theHandler(*this, paramName, theArrayHandle); + theHandler.TraverseParamDefTree(handle); + HandleProperty(PVD::createInstanceId(theArrayHandle), paramName); + } + else + { + HandleProperty(PVD::createInstanceId(0), paramName); + } + return NvParameterized::ERROR_NONE; +} + +NvParameterized::ErrorType TopLevelParamTreeHandler::HandleRef(NvParameterized::Handle& handle, const char* inParamName) +{ + const NvParameterized::Definition* paramDef = handle.parameterDefinition(); + bool includedRef = false; + for (int j = 0; j < paramDef->numHints(); j++) + { + const NvParameterized::Hint* hint = paramDef->hint(j); + + if (strcmp("INCLUDED", hint->name()) == 0 && hint->type() == NvParameterized::TYPE_U64) + { + if (hint->asUInt()) + { + includedRef = true; + } + } + } + + NvParameterized::Interface* refObj = 0; + if (handle.getParamRef(refObj) != NvParameterized::ERROR_NONE) + { + return(NvParameterized::ERROR_INVALID_PARAMETER_HANDLE); + } + uint64_t refObjId(PtrToPVD(refObj)); + + if (includedRef) + { + //traversalState state; + if (!refObj) + { + return(NvParameterized::ERROR_INVALID_REFERENCE_VALUE); + } + const char* refName = refObj->className(); + PVD::CreateObject(mRemoteDebugger, refObjId, refName); + TopLevelParamTreeHandler theHandler(refObj, mRemoteDebugger, refObjId); + NvParameterized::Handle refHandle(*refObj); + theHandler.TraverseParamDefTree(refHandle); + HandleProperty(PVD::createInstanceId(refObjId), inParamName); + return NvParameterized::ERROR_NONE; + } + else + { + const char* refName = paramDef->name(); + PVD::CreateObject(mRemoteDebugger, refObjId, refName); + PVD::SetPropertyValue(mRemoteDebugger, refObjId, CreateCommLayerValue(refObj->className()), true, "type"); + PVD::SetPropertyValue(mRemoteDebugger, refObjId, CreateCommLayerValue(refObj->name()), true, "name"); + } + + HandleProperty(createInstanceId(refObjId), inParamName); + //exit here? + return(NvParameterized::ERROR_NONE); +} +NvParameterized::ErrorType TopLevelParamTreeHandler::HandleProperty(const PvdCommLayerValue& inValue, const char* inParamName) +{ + PVD::SetPropertyValue(mRemoteDebugger, mCurPvdObj, inValue, true, inParamName); + return NvParameterized::ERROR_NONE; +} + +NvParameterized::ErrorType TopLevelParamTreeHandler::TraverseParamDefTree(NvParameterized::Handle& handle) +{ + if (handle.numIndexes() < 1) + { + if (mObj->getParameterHandle("", handle) != NvParameterized::ERROR_NONE) + { + return(NvParameterized::ERROR_INVALID_PARAMETER_HANDLE); + } + } + const NvParameterized::Definition* paramDef = handle.parameterDefinition(); + ApexSimpleString tmpStr; + const char* theVariableName(GetVariableName(tmpStr, mVariablePrefix, paramDef->name())); + switch (paramDef->type()) + { + case TYPE_ARRAY: + if (paramDef->arraySizeIsFixed()) + { + return HandleArray(handle, theVariableName); + } + return HandleDynamicArray(handle, theVariableName); + case TYPE_STRUCT: + return HandleStruct(handle, theVariableName); + + case TYPE_BOOL: + HANDLE_PARAM_TYPE(bool, getParamBool); + + case TYPE_STRING: + HANDLE_PARAM_TYPE(const char*, getParamString); + + case TYPE_ENUM: + HANDLE_PARAM_TYPE(const char*, getParamEnum); + + case TYPE_REF: + return HandleRef(handle, theVariableName); + + case TYPE_I8: + HANDLE_PARAM_TYPE(int8_t, getParamI8); + case TYPE_I16: + HANDLE_PARAM_TYPE(int16_t, getParamI16); + case TYPE_I32: + HANDLE_PARAM_TYPE(int32_t, getParamI32); + case TYPE_I64: + HANDLE_PARAM_TYPE(int64_t, getParamI64); + + case TYPE_U8: + HANDLE_PARAM_TYPE(uint8_t, getParamU8); + case TYPE_U16: + HANDLE_PARAM_TYPE(uint16_t, getParamU16); + case TYPE_U32: + HANDLE_PARAM_TYPE(uint32_t, getParamU32); + case TYPE_U64: + HANDLE_PARAM_TYPE(uint64_t, getParamU64); + + case TYPE_F32: + HANDLE_PARAM_TYPE(float, getParamF32); + case TYPE_F64: + HANDLE_PARAM_TYPE(double, getParamF64); + + case TYPE_VEC2: + HANDLE_PARAM_TYPE(physx::PxVec2, getParamVec2); + + case TYPE_VEC3: + HANDLE_PARAM_TYPE(physx::PxVec3, getParamVec3); + + case TYPE_VEC4: + HANDLE_PARAM_TYPE(physx::PxVec4, getParamVec4); + + case TYPE_TRANSFORM: + HANDLE_PARAM_TYPE(physx::PxTransform, getParamTransform); + + case TYPE_QUAT: + HANDLE_PARAM_TYPE(physx::PxQuat, getParamQuat); + + case TYPE_MAT33: + { + physx::PxMat33 tmp; + handle.getParamMat33(tmp); + return HandleDataType(physx::PxMat33(tmp), theVariableName); + } + + case TYPE_MAT34: + { + physx::PxMat44 tmp; + handle.getParamMat34(tmp); + return HandleDataType(PxMat34Legacy(tmp), theVariableName); + } + + case TYPE_BOUNDS3: + HANDLE_PARAM_TYPE(physx::PxBounds3, getParamBounds3); + + case TYPE_POINTER: + case TYPE_MAT44: //mat44 unhandled for now + case TYPE_UNDEFINED: + case TYPE_LAST: + return NvParameterized::ERROR_TYPE_NOT_SUPPORTED; + } + return NvParameterized::ERROR_NONE; +} + + + +NvParameterized::ErrorType +traverseParamDefTree(NvParameterized::Interface& obj, + PVD::PvdDataStream* remoteDebugger, + void* curPvdObj, + NvParameterized::Handle& handle) +{ + TopLevelParamTreeHandler theHandler(&obj, remoteDebugger, PtrToPVD(curPvdObj)); + theHandler.TraverseParamDefTree(handle); + return(NvParameterized::ERROR_NONE); +} +} + +#endif
\ No newline at end of file diff --git a/APEX_1.4/shared/internal/src/authoring/ApexCSG.cpp b/APEX_1.4/shared/internal/src/authoring/ApexCSG.cpp new file mode 100644 index 00000000..50a7b046 --- /dev/null +++ b/APEX_1.4/shared/internal/src/authoring/ApexCSG.cpp @@ -0,0 +1,3140 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + +#include "ApexUsingNamespace.h" +#include "PxSimpleTypes.h" +#include "PxFileBuf.h" + +#include "authoring/ApexCSGDefs.h" +#include "authoring/ApexCSGSerialization.h" +#include "ApexSharedSerialization.h" +#include "RenderDebugInterface.h" + +#include <stdio.h> + +#include "PxErrorCallback.h" + + +#ifndef WITHOUT_APEX_AUTHORING + +using namespace nvidia; +using namespace apex; +using namespace nvidia; + +namespace ApexCSG +{ + +// Tolerances for geometric calculations + +#define CSG_EPS ((Real)1.0e-9) + +BSPTolerances +gDefaultTolerances; + + +// Set to 1 to Measure the stack +#define MEASURE_STACK_USAGE 0 + +#if MEASURE_STACK_USAGE +static size_t +gStackTop = (size_t)-1; +static size_t +gStackBottom = (size_t)-1; + +#define RECORD_STACK_TOP() \ +{ \ + int x; \ + gStackTop = (size_t)&x; \ + gStackBottom = (size_t)-1; \ +} +#define RECORD_STACK_BOTTOM() \ +{ \ + int x; \ + gStackBottom = PxMin(gStackBottom, (size_t)&x); \ +} +#define OUTPUT_STACK_USAGE(fn_name) \ +{ \ + char stackMsg[100]; \ + sprintf(stackMsg, "%s stack usage: %d bytes", #fn_name, gStackTop - gStackBottom); \ + debugWarn(stackMsg); \ +} +#else +#define RECORD_STACK_TOP() +#define RECORD_STACK_BOTTOM() +#define OUTPUT_STACK_USAGE(fn_name) +#endif + + +/* Interpolator */ + +size_t +Interpolator::s_offsets[Interpolator::VertexFieldCount]; + +static InterpolatorBuilder +sInterpolatorBuilder; + +void +Interpolator::serialize(physx::PxFileBuf& stream) const +{ + for (uint32_t i = 0; i < VertexFieldCount; ++i) + { + stream << m_frames[i]; + } +} + +void +Interpolator::deserialize(physx::PxFileBuf& stream, uint32_t version) +{ + if (version < Version::SerializingTriangleFrames) + { + return; + } + + for (uint32_t i = 0; i < VertexFieldCount; ++i) + { + stream >> m_frames[i]; + } +} + + +/* Utilities */ + +#define debugInfo(_msg) GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_INFO, _msg, __FILE__, __LINE__) +#define debugWarn(_msg) GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, _msg, __FILE__, __LINE__) + +static bool +transformsEqual(const physx::PxMat44& a, const physx::PxMat44& b, float eps) +{ + const float eps2 = eps*eps; + const float scaledEps = eps*PxMax(a.getPosition().abs().maxElement(), b.getPosition().abs().maxElement()); + + for (unsigned i = 0; i < 4; ++i) + { + for (unsigned j = 0; j < 4; ++j) + { + const float tol = i == j ? eps2 : (i == 4 ? scaledEps : eps); + if (!physx::PxEquals(a(i,j), b(i,j), tol)) + { + return false; + } + } + } + + return true; +} + +static bool +isZero(const physx::PxMat44& a) +{ + for (unsigned i = 0; i < 4; ++i) + { + if (!a[i].isZero()) + { + return false; + } + } + + return true; +} + +PX_INLINE Mat4Real +CSGFromPx(const physx::PxMat44& a) +{ + Mat4Real r; + for (unsigned i = 0; i < 4; ++i) + { + for (unsigned j = 0; j < 4; ++j) + { + r[i][j] = (Real)a(i,j); + } + } + return r; +} + +PX_INLINE physx::PxMat44 +PxFromCSG(const Mat4Real& a) +{ + physx::PxMat44 r; + for (unsigned i = 0; i < 4; ++i) + { + for (unsigned j = 0; j < 4; ++j) + { + r(i,j) = (float)a[i][j]; + } + } + return r; +} + +class DefaultRandom : public UserRandom +{ +public: + uint32_t getInt() + { + return m_rnd.nextSeed(); + } + float getReal(float min, float max) + { + return m_rnd.getScaled(min, max); + } + + QDSRand m_rnd; +} defaultRnd; + +PX_INLINE int // Returns 0 if the point is on the plane (within tolerance), otherwise +/-1 if the point is above/below the plane +cmpPointToPlane(const Pos& pos, const Plane& plane, Real tol) +{ + const Real dist = plane.distance(pos); + + return dist < -tol ? -1 : (dist < tol ? 0 : 1); +} + +template<typename T> +struct IndexedValue +{ + T value; + uint32_t index; + + static int cmpIncreasing(const void* a, const void* b) + { + return ((IndexedValue*)a)->value == ((IndexedValue*)b)->value ? 0 : (((IndexedValue*)a)->value < ((IndexedValue*)b)->value ? -1 : 1); + } +}; + + +PX_INLINE LinkedVertex* +clipPolygonByPlane(LinkedVertex* poly, const Plane& plane, Pool<LinkedVertex>& pool, Real tol) +{ + LinkedVertex* prev = poly; + int prevSide = cmpPointToPlane(prev->vertex, plane, tol); + bool outsideFound = prevSide == 1; + bool insideFound = prevSide == -1; + LinkedVertex* clip0 = NULL; + LinkedVertex* clip1 = NULL; + LinkedVertex* next = prev->getAdj(1); + if (next != poly) + { + do + { + int nextSide = cmpPointToPlane(next->vertex, plane, tol); + switch (nextSide) + { + case -1: + insideFound = true; + if (prevSide == 1) + { + // Clip + clip1 = pool.borrow(); + const Dir disp = next->vertex - prev->vertex; + const Real dDisp = disp | plane.normal(); + PX_ASSERT(dDisp < 0); + const Real dAbove = plane.distance(prev->vertex); + clip1->vertex = prev->vertex - (dAbove / dDisp) * disp; + next->setAdj(0, clip1); // Insert clip1 between prev and next + } + else if (prevSide == 0) + { + clip1 = prev; + } + break; + case 0: + if (prevSide == -1) + { + clip0 = next; + } + else if (prevSide == 1) + { + clip1 = next; + } + break; + case 1: + outsideFound = true; + if (prevSide == -1) + { + // Clip + clip0 = pool.borrow(); + const Dir disp = next->vertex - prev->vertex; + const Real dDisp = disp | plane.normal(); + PX_ASSERT(dDisp > 0); + const Real dBelow = plane.distance(prev->vertex); + clip0->vertex = prev->vertex - (dBelow / dDisp) * disp; + next->setAdj(0, clip0); // Insert clip0 between prev and next + } + else if (prevSide == 0) + { + clip0 = prev; + } + break; + } + prev = next; + prevSide = nextSide; + next = prev->getAdj(1); + } + while (prev != poly); + } + + PX_ASSERT((clip0 != NULL) == (clip1 != NULL)); + + if (clip0 != NULL && clip1 != NULL && clip0 != clip1) + { + // Get rid of vertices between clip0 and clip1 + LinkedVertex* v = clip0->getAdj(1); + while (v != clip1) + { + LinkedVertex* w = v->getAdj(1); + v->remove(); + pool.replace(v); + v = w; + } + poly = clip1; + } + + if (outsideFound && !insideFound) + { + // Completely outside. Eliminate. + LinkedVertex* v; + do + { + v = poly->getAdj(0); + v->remove(); + pool.replace(v); + } + while (v != poly); + poly = NULL; + } + + return poly; +} + +// If clippedMesh is not NULL, it will be appended with clipped triangles from the leaf. +// Return value is the sum triangle area on the leaf. +PX_INLINE void +clipTriangleToLeaf(physx::Array<Triangle>* clippedMesh, Real& clippedTriangleArea, Real& clippedPyramidVolume, const Pos& origin, + const Triangle& tri, const BSP::Node* leaf, uint32_t edgeTraversalDir, + Pool<LinkedVertex>& vertexPool, Real distanceTol, const physx::Array<Plane>& planes, uint32_t skipPlaneIndex = 0xFFFFFFFF) +{ + clippedTriangleArea = (Real)0; + clippedPyramidVolume = (Real)0; + + // Form a ring of vertices out of the triangle + LinkedVertex* v0 = vertexPool.borrow(); + LinkedVertex* v1 = vertexPool.borrow(); + LinkedVertex* v2 = vertexPool.borrow(); + v0->vertex = tri.vertices[0]; + v1->vertex = tri.vertices[1]; + v2->vertex = tri.vertices[2]; + v0->setAdj(edgeTraversalDir, v1); + v1->setAdj(edgeTraversalDir, v2); + + for (SurfaceIt it(leaf); it.valid(); it.inc()) + { + if (it.surface()->planeIndex == skipPlaneIndex) + { + continue; + } + const Real sign = it.side() ? (Real)1 : -(Real)1; + v0 = clipPolygonByPlane(v0, sign * planes[it.surface()->planeIndex], vertexPool, distanceTol); + if (v0 == NULL) + { + break; // Completely clipped away + } + } + + if (v0 != NULL) + { + // Something remains. Add to clippedMesh if it's not NULL + v1 = v0->getAdj(1); + v2 = v1->getAdj(1); + if (v1 != v0 && v2 != v0) + { + if (clippedMesh != NULL) + { + // Triangluate + do + { + Triangle& newTri = clippedMesh->insert(); + newTri.vertices[0] = v0->vertex; + newTri.vertices[1] = v1->vertex; + newTri.vertices[2] = v2->vertex; + newTri.submeshIndex = tri.submeshIndex; + newTri.smoothingMask = tri.smoothingMask; + newTri.extraDataIndex = tri.extraDataIndex; + newTri.calculateQuantities(); + clippedTriangleArea += newTri.area; + clippedPyramidVolume += newTri.area*((newTri.vertices[0]-origin)|newTri.normal); // 3 * volume + v1 = v2; + v2 = v2->getAdj(1); + } + while (v2 != v0); + } + else + { + // Triangluate + do + { + Dir normal = Dir(v1->vertex - v0->vertex)^Dir(v2->vertex - v0->vertex); + const Real area = (Real)0.5 * normal.normalize(); + clippedTriangleArea += area; + clippedPyramidVolume += area*((v0->vertex-origin)|normal); // 3 * volume + v1 = v2; + v2 = v2->getAdj(1); + } + while (v2 != v0); + } + } + // Return links to pool. + LinkedVertex* v; + do + { + v = v0->getAdj(0); + v->remove(); + vertexPool.replace(v); + } + while (v != v0); + } + + clippedPyramidVolume *= (Real)0.333333333333333333; +} + +PX_INLINE bool +intersectPlanes(Pos& pos, Dir& dir, const Plane& plane0, const Plane& plane1) +{ + const Dir n0 = plane0.normal(); + const Dir n1 = plane1.normal(); + + dir = n0^n1; + const Real dir2 = dir.lengthSquared(); + if (dir2 < square(EPS_REAL)) + { + return false; + } + + const Real recipDir2 = (Real)1/dir2; // DIVIDE + + // Normalize dir + dir *= sqrt(recipDir2); + + // Calculate point in both planes + const Real n0n0RecipDir2 = n0.lengthSquared()*recipDir2; + const Real n1n1RecipDir2 = n1.lengthSquared()*recipDir2; + const Real n0n1RecipDir2 = (n0|n1)*recipDir2; + pos = Pos((Real)0) + (plane1.d()*n0n1RecipDir2 - plane0.d()*n1n1RecipDir2)*n0 + (plane0.d()*n0n1RecipDir2 - plane1.d()*n0n0RecipDir2)*n1; + + // Improve accuracy of solution + const Real error0 = pos|plane0; + const Real error1 = pos|plane1; + pos += (error1*n0n1RecipDir2 - error0*n1n1RecipDir2)*n0 + (error0*n0n1RecipDir2 - error1*n0n0RecipDir2)*n1; + + return true; +} + +PX_INLINE bool +intersectLineWithHalfspace(Real& minS, Real& maxS, const Pos& pos, const Dir& dir, const Plane& plane) +{ + const Real num = -(pos|plane); + const Real den = dir|plane; + if (den < -CSG_EPS) + { + const Real s = num/den; + if (s > minS) + { + minS = s; + } + } + else + if (den > CSG_EPS) + { + const Real s = num/den; + if (s < maxS) + { + maxS = s; + } + } + else + if (num < -CSG_EPS) + { + minS = CSG_EPS; + maxS = -CSG_EPS; + } + + return minS < maxS; +} + +// Returns true if the leaf has finite area and volume, false otherwise +PX_INLINE bool +calculateLeafAreaAndVolume(Real& area, Real& volume, const Plane* planes, uint32_t planeCount, const Mat4Real& cofInternalTransform) +{ + if (planeCount <= 1) + { + area = MAX_REAL; + volume = MAX_REAL; + return false; + } + + area = (Real)0; + volume = (Real)0; + + bool originSet = false; + Pos origin(0.0f); + for (uint32_t i = 0; i < planeCount; ++i) + { + bool p0Set = false; + Pos p0(0.0f); + Real h = (Real)0; + Real faceArea = (Real)0; + for (uint32_t j = 0; j < planeCount; ++j) + { + if (j == i) + { + continue; + } + Pos pos; + Dir dir; + if (!intersectPlanes(pos, dir, planes[i], planes[j])) + { + continue; + } + Pos v1, v2; + Real minS = -MAX_REAL; + Real maxS = MAX_REAL; + for (uint32_t k = 0; k < planeCount; ++k) + { + if (k == j || k == i) + { + continue; + } + if (!intersectLineWithHalfspace(minS, maxS, pos, dir, planes[k])) + { + break; + } + } + + if (minS >= maxS) + { + continue; + } + + if (minS == -MAX_REAL || maxS == MAX_REAL) + { + area = MAX_REAL; + volume = MAX_REAL; + return false; + } + + const Pos p1 = pos + minS*dir; + if (!originSet) + { + origin = p1; + originSet = true; + } + if (!p0Set) + { + p0 = p1; + h = (p0-origin)|planes[i]; + p0Set = true; + continue; // The edge (p1,p2) won't contribute to the area or volume + } + const Pos p2 = pos + maxS*dir; + faceArea += (Dir(p1-p0)^Dir(p2-p0))|planes[i]; + } + area += faceArea*physx::PxSqrt((cofInternalTransform*planes[i].normal()).lengthSquared()); + volume += faceArea*h; + } + + area *= (Real)0.5; + volume *= (Real)0.16666666666666666667*cofInternalTransform[3][3]; + + return true; +} + + +// GSA for a generic plane container +struct PlaneIteratorInit +{ + PlaneIteratorInit() : first(NULL), stop(NULL) {} + + Plane* first; + Plane* stop; +}; + +class PlaneIterator +{ +public: + PlaneIterator(const PlaneIteratorInit& listBounds) : current(listBounds.first), stop(listBounds.stop) {} + + bool valid() const + { + return current != stop; + } + + void inc() + { + ++current; + } + + Plane plane() const + { + return *current; + } + +private: + Plane* current; + Plane* stop; +}; + +class HalfspaceIntersection : public ApexCSG::GSA::StaticConvexPolyhedron<PlaneIterator, PlaneIteratorInit> +{ +public: + void setPlanes(Plane* first, uint32_t count) + { + m_initValues.first = first; + m_initValues.stop = first + count; + } +}; + + +/* BSP */ + +BSP::BSP(IApexBSPMemCache* memCache, const physx::PxMat44& internalTransform) : + m_root(NULL), + m_meshSize(1), + m_meshBounds(physx::PxBounds3::empty()), + m_internalTransform(internalTransform), + m_internalTransformInverse(CSGFromPx(internalTransform).inverse34()), + m_incidentalMesh(false), + m_combined(false), + m_combiningMeshSize(1), + m_combiningIncidentalMesh(false), + m_memCache((BSPMemCache*)memCache), + m_ownsMemCache(false) +{ + if (m_memCache == NULL) + { + m_memCache = (BSPMemCache*)createBSPMemCache(); + m_ownsMemCache = true; + } + + // Always have a node. The trivial (one-leaf) tree is considered "inside". + m_root = m_memCache->m_nodePool.borrow(); +} + +BSP::~BSP() +{ + if (m_ownsMemCache) + { + m_memCache->release(); + } +} + +void +BSP::setTolerances(const BSPTolerances& tolerances) +{ + m_tolerarnces = tolerances; +} + +bool +BSP::fromMesh(const nvidia::ExplicitRenderTriangle* mesh, uint32_t triangleCount, const BSPBuildParameters& params, IProgressListener* progressListener, volatile bool* cancel) +{ + if (triangleCount == 0) + { + return false; + } + + clear(); + + // Shuffle triangle ordering + physx::Array<uint32_t> triangleOrder(triangleCount); + for (uint32_t i = 0; i < triangleCount; ++i) + { + triangleOrder[i] = i; + } + UserRandom* rnd = params.rnd != NULL ? params.rnd : &defaultRnd; + for (uint32_t i = 0; i < triangleCount; ++i) + { + nvidia::swap(triangleOrder[i], triangleOrder[i + (uint32_t)(((uint64_t)rnd->getInt() * (uint64_t)(triangleCount - i)) >> 32)]); + } + + // Collect mesh triangles and find mesh bounds + m_mesh.resize(triangleCount); + m_frames.resize(triangleCount); + m_meshBounds.setEmpty(); + for (uint32_t i = 0; i < m_mesh.size(); ++i) + { + const ExplicitRenderTriangle& inTri = mesh[triangleOrder[i]]; + VertexData vertexData[3]; + m_mesh[i].fromExplicitRenderTriangle(vertexData, inTri); + m_frames[i].setFromTriangle(m_mesh[i], vertexData); + m_meshBounds.include(inTri.vertices[0].position); + m_meshBounds.include(inTri.vertices[1].position); + m_meshBounds.include(inTri.vertices[2].position); + } + + // Size scales + const Dir extents(m_meshBounds.getExtents()); + m_meshSize = PxMax(extents[0], PxMax(extents[1], extents[2])); + + // Scale to unit size and zero offset for BSP building + const Vec4Real recipScale((extents[0] > m_tolerarnces.linear ? extents[0] : (Real)1), (extents[1] > m_tolerarnces.linear ? extents[1] : (Real)1), (extents[2] > m_tolerarnces.linear ? extents[2] : (Real)1), (Real)1); + const Vec4Real scale((Real)1/recipScale[0], (Real)1/recipScale[1], (Real)1/recipScale[2], (Real)1); + const Pos center = m_meshBounds.getCenter(); + const Real gridSize = (Real)params.snapGridSize; + const Real recipGridSize = params.snapGridSize > 0 ? (Real)1/gridSize : (Real)0; + // Rescale + for (physx::PxU32 i = 0; i < m_mesh.size(); ++i) + { + Triangle& tri = m_mesh[i]; + for (physx::PxU32 j = 0; j < 3; ++j) + { + Pos& pos = tri.vertices[j]; + pos = (pos - center) * scale; + } + } + + // Align vertices + if (params.snapGridSize > 0) + { + physx::Array< IndexedValue<Real> > snapValues[3]; // x, y, and z + snapValues[0].resize(3*m_mesh.size()); + snapValues[1].resize(3*m_mesh.size()); + snapValues[2].resize(3*m_mesh.size()); + for (physx::PxU32 i = 0; i < m_mesh.size(); ++i) + { + Triangle& tri = m_mesh[i]; + for (physx::PxU32 j = 0; j < 3; ++j) + { + const Pos& pos = tri.vertices[j]; + for (int e = 0; e < 3; ++e) + { + const physx::PxU32 index = i*3+j; + IndexedValue<Real>& v = snapValues[e][index]; + v.index = index; + v.value = pos[e]; + } + } + } + + for (int e = 0; e < 3; ++e) + { + for (physx::PxU32 valueNum = 0; valueNum < snapValues[e].size(); ++valueNum) + { + const physx::PxU32 index = snapValues[e][valueNum].index; + const physx::PxU32 i = index/3; + const physx::PxU32 j = index-3*i; + m_mesh[i].vertices[j][e] = recipGridSize*floor(gridSize * snapValues[e][valueNum].value + (Real)0.5); + } + } + } + + // Cache triangle quantities + for (physx::PxU32 i = 0; i < m_mesh.size(); ++i) + { + m_mesh[i].calculateQuantities(); + } + + // Initialize surface stack with surfaces formed from mesh triangles + physx::Array<Surface> surfaceStack; + + // Crude estimate, hopefully will reduce re-allocations + surfaceStack.reserve(m_mesh.size() * ((int)physx::PxLog((float)m_mesh.size()) + 1)); + + // Track maximum and total surface triangle area + float maxArea = 0; + Real totalArea = 0; + + // Add mesh triangles + uint32_t triangleIndex = 0; + while (triangleIndex < m_mesh.size()) + { + // Create a surface for the next triangle + const Triangle& tri = m_mesh[triangleIndex]; + surfaceStack.pushBack(Surface()); + Surface* surface = &surfaceStack.back(); + surface->planeIndex = m_planes.size(); + surface->triangleIndexStart = triangleIndex++; + Real surfaceTotalTriangleArea = tri.area; + Plane& plane = m_planes.insert(); + plane.set(tri.normal, (tri.vertices[0] + tri.vertices[1] + tri.vertices[2])/(Real)3); + plane.normalize(); + // See if any of the remaining triangles can fit on this surface. + for (uint32_t testTriangleIndex = triangleIndex; testTriangleIndex < m_mesh.size(); ++testTriangleIndex) + { + Triangle& testTri = m_mesh[testTriangleIndex]; + if ((testTri.normal ^ plane.normal()).lengthSquared() < square(m_tolerarnces.angular) && (testTri.normal | plane.normal()) > 0 && + 0 == cmpPointToPlane(testTri.vertices[0], plane, m_tolerarnces.linear) && + 0 == cmpPointToPlane(testTri.vertices[1], plane, m_tolerarnces.linear) && + 0 == cmpPointToPlane(testTri.vertices[2], plane, m_tolerarnces.linear)) + { + // This triangle fits. Move it next to others in the surface. + if (testTriangleIndex != triangleIndex) + { + nvidia::swap(m_mesh[triangleIndex], m_mesh[testTriangleIndex]); + nvidia::swap(m_frames[triangleIndex], m_frames[testTriangleIndex]); + } + Triangle& newTri = m_mesh[triangleIndex]; + // Add in the new normal, properly weighted + Dir averageNormal = surfaceTotalTriangleArea * plane.normal() + newTri.area * m_mesh[triangleIndex].normal; + averageNormal.normalize(); + surfaceTotalTriangleArea += newTri.area; + ++triangleIndex; + // Calculate the average projection + Real averageProjection = 0; + for (uint32_t i = surface->triangleIndexStart; i < triangleIndex; ++i) + { + for (uint32_t j = 0; j < 3; ++j) + { + averageProjection += averageNormal | m_mesh[i].vertices[j]; + } + } + averageProjection /= 3 * (triangleIndex - surface->triangleIndexStart); + plane.set(averageNormal, -averageProjection); + } + } + surface->triangleIndexStop = triangleIndex; + surface->totalTriangleArea = (float)surfaceTotalTriangleArea; + maxArea = PxMax(maxArea, surface->totalTriangleArea); + totalArea += surfaceTotalTriangleArea; + // Ensure triangles lie on or below surface + Real maxProjection = -MAX_REAL; + for (uint32_t i = surface->triangleIndexStart; i < surface->triangleIndexStop; ++i) + { + Triangle& tri = m_mesh[i]; + for (uint32_t j = 0; j < 3; ++j) + { + maxProjection = PxMax(maxProjection, plane.normal() | tri.vertices[j]); + } + } + plane[3] = -maxProjection; + } + + // Set build process constants + BuildConstants buildConstants; + buildConstants.m_params = params; + buildConstants.m_recipMaxArea = maxArea > 0 ? 1.0f / maxArea : (float)0; + + // Build + m_root = m_memCache->m_nodePool.borrow(); + PX_ASSERT(m_root != NULL); + + QuantityProgressListener quantityListener(totalArea, progressListener); + bool ok = buildTree(m_root, surfaceStack, 0, surfaceStack.size(), buildConstants, &quantityListener, cancel); + if (!ok) + { + return false; + } + + // Bring the mesh back to actual size + Mat4Real tm; + tm.set((Real)1); + for (uint32_t i = 0; i < 3; ++i) + { + tm[i][i] = recipScale[i]; + tm[i][3] = center[i]; + } + + // Currently the BSP is in "unit space", and tm will transform the BSP back to mesh space. + // If params.internalTransform is valid, then the user is asking that: + // (BSP space) = (params.internalTransform)(mesh space) + // But (mesh space) = (tm)(unit space), so (BSP space) = (params.internalTransform)(tm)(unit space), + // and therefore we apply (params.internalTransform)(tm). + // If params.internalTransform is not valid, then the user is asking to keep the BSP in unit space, + // so our effective internalTransform is the inverse of tm. + if (!isZero(params.internalTransform)) + { + m_internalTransform = params.internalTransform; + const Mat4Real internalTransformCSG = CSGFromPx(m_internalTransform); + m_internalTransformInverse = internalTransformCSG.inverse34(); + const Real meshSize = m_meshSize; // Save off mesh size. This gets garbled by scaled transforms. + transform(internalTransformCSG*tm, false); + const Real maxScale = PxMax(internalTransformCSG[0].lengthSquared(), PxMax(internalTransformCSG[1].lengthSquared(), internalTransformCSG[2].lengthSquared())); + m_meshSize = meshSize*maxScale; + } + else + { + m_internalTransformInverse = tm; + m_internalTransform = PxFromCSG(tm.inverse34()); + m_meshSize = (Real)1; + } + + // Delete triangle info if requested. This is done here in case any of the processing above needs this info. + if (!params.keepTriangles) + { + deleteTriangles(); + } + +// performDiagnostics(); + + return true; +} + +bool +BSP::fromConvexPolyhedron(const physx::PxPlane* poly, uint32_t polySize, const physx::PxMat44& internalTransform, const nvidia::ExplicitRenderTriangle* mesh, uint32_t triangleCount) +{ + clear(); + + // Default is all space. If there are no planes, that is the result. + m_root = m_memCache->m_nodePool.borrow(); + PX_ASSERT(m_root != NULL); + + if (polySize == 0) + { + return true; + } + + // Put planes into our format + m_planes.resize(polySize); + for (uint32_t planeIndex = 0; planeIndex < polySize; ++planeIndex) + { + for (unsigned i = 0; i < 3; ++i) + { + m_planes[planeIndex][i] = (Real)poly[planeIndex].n[i]; + } + m_planes[planeIndex][3] = (Real)poly[planeIndex].d; + m_planes[planeIndex].normalize(); + } + + // Build the tree. + Node* node = m_root; + for (uint32_t planeIndex = 0; planeIndex < polySize; ++planeIndex) + { + ApexCSG::Region outside; + outside.side = 0; + Node* child0 = m_memCache->m_nodePool.borrow(); + child0->setLeafData(outside); + Node* child1 = m_memCache->m_nodePool.borrow(); // No need to set inside leaf data, that is the default + Surface surface; + surface.planeIndex = planeIndex; + surface.triangleIndexStart = 0; + surface.triangleIndexStop = 0; + surface.totalTriangleArea = 0.0f; + node->setBranchData(surface); + node->setChild(0, child0); + node->setChild(1, child1); + node = child1; + } + + // See if the planes bound a non-empty set + RegionShape regionShape(m_planes.begin()); + regionShape.set_leaf(node); + regionShape.calculate(); + if (!regionShape.is_nonempty()) + { + clear(); + Region leafData; + leafData.side = 0; + m_root = m_memCache->m_nodePool.borrow(); + m_root->setLeafData(leafData); // Planes define a null intersection. The result is the empty set. + return true; + } + + // Currently there is no internal transform, BSP space = poly space + // With internalTransform is valid, then the user is asking that: + // (BSP space) = (params.internalTransform)(poly space) + // so we simply transform by params.internalTransform. + if (!isZero(internalTransform)) + { + m_internalTransform = internalTransform; + const Mat4Real internalTransformCSG = CSGFromPx(m_internalTransform); + m_internalTransformInverse = internalTransformCSG.inverse34(); + const Real meshSize = m_meshSize; // Save off mesh size. This gets garbled by scaled transforms. + transform(internalTransformCSG, false); + const Real maxScale = PxMax(internalTransformCSG[0].lengthSquared(), PxMax(internalTransformCSG[1].lengthSquared(), internalTransformCSG[2].lengthSquared())); + m_meshSize = meshSize*maxScale; + } + + if (triangleCount > 0) + { + // Collect mesh triangles and find mesh bounds + m_mesh.resize(triangleCount); + m_frames.resize(triangleCount); + m_meshBounds.setEmpty(); + for (uint32_t i = 0; i < m_mesh.size(); ++i) + { + const ExplicitRenderTriangle& inTri = mesh[i]; + VertexData vertexData[3]; + m_mesh[i].fromExplicitRenderTriangle(vertexData, inTri); + m_frames[i].setFromTriangle(m_mesh[i], vertexData); + m_meshBounds.include(inTri.vertices[0].position); + m_meshBounds.include(inTri.vertices[1].position); + m_meshBounds.include(inTri.vertices[2].position); + } + + // Size scales + const Dir extents(m_meshBounds.getExtents()); + m_meshSize = PxMax(extents[0], PxMax(extents[1], extents[2])); + + // Scale to unit size and zero offset for BSP building + const Vec4Real recipScale((extents[0] > m_tolerarnces.linear ? extents[0] : (Real)1), (extents[1] > m_tolerarnces.linear ? extents[1] : (Real)1), (extents[2] > m_tolerarnces.linear ? extents[2] : (Real)1), (Real)1); + const Vec4Real scale((Real)1/recipScale[0], (Real)1/recipScale[1], (Real)1/recipScale[2], (Real)1); + const Pos center = m_meshBounds.getCenter(); + for (uint32_t i = 0; i < m_mesh.size(); ++i) + { + Triangle& tri = m_mesh[i]; + for (uint32_t j = 0; j < 3; ++j) + { + Pos& pos = tri.vertices[j]; + pos = (pos - center) * scale; + } + tri.calculateQuantities(); + } + + m_incidentalMesh = true; + } + + return true; +} + +bool +BSP::combine(const IApexBSP& ibsp) +{ + const BSP& bsp = *(const BSP*)&ibsp; + + if (m_combined || bsp.m_combined) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eINVALID_OPERATION, + "BSP::combine: can only combine two uncombined BSPs. Use op() to merge a combined BSP.", __FILE__, __LINE__); + return false; + } + + if (!transformsEqual(m_internalTransform, bsp.m_internalTransform, 0.001f)) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, + "BSP::combine: Nontrivial BSPs being combined have different internal transformations. Behavior is undefined.", __FILE__, __LINE__); + } + + // Add in other bsp's triangles. + const uint32_t thisTriangleCount = m_mesh.size(); + const uint32_t totalTriangleCount = thisTriangleCount + bsp.m_mesh.size(); + m_mesh.resize(totalTriangleCount); + m_frames.resize(totalTriangleCount); + for (uint32_t i = thisTriangleCount; i < totalTriangleCount; ++i) + { + m_mesh[i] = bsp.m_mesh[i - thisTriangleCount]; + m_frames[i] = bsp.m_frames[i - thisTriangleCount]; + } + + // Add in other bsp's planes. + const uint32_t thisPlaneCount = m_planes.size(); + const uint32_t totalPlaneCount = thisPlaneCount + bsp.m_planes.size(); + m_planes.resize(totalPlaneCount); + for (uint32_t i = thisPlaneCount; i < totalPlaneCount; ++i) + { + m_planes[i] = bsp.m_planes[i - thisPlaneCount]; + } + + combineTrees(m_root, bsp.m_root, thisTriangleCount, thisPlaneCount); + + m_combiningMeshSize = bsp.m_meshSize; + m_combiningIncidentalMesh = bsp.m_incidentalMesh; + + m_meshBounds.include(bsp.m_meshBounds); + + m_combined = true; + + clean(); + + return true; +} + +bool +BSP::op(const IApexBSP& icombinedBSP, Operation::Enum operation) +{ + const BSP& combinedBSP = *(const BSP*)&icombinedBSP; + + if (!combinedBSP.m_combined) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eINVALID_OPERATION, + "BSP::op: can only perform an operation upon a combined BSP. Use combine() with another BSP.", __FILE__, __LINE__); + return false; + } + + if (operation == Operation::NOP) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eINVALID_OPERATION, + "BSP::op: NOP requested. Mesh will remain combined.", __FILE__, __LINE__); + return false; + } + + copy(combinedBSP); // No-ops if this = combinedBSP, so this is safe + + // Combine size tolerances - look at symmetry + switch (operation >> 1) + { + case 1: // From set A + case 5: + // Keep size scales + break; + case 2: // From set B + case 6: + // Replace with other size tolerance + m_meshSize = m_combiningMeshSize; + break; + // Symmetric cases + case 0: // Empty_Set or All_Space, set size scale to unitless value + m_meshSize = 1; + break; + case 3: // Symmetric combinations of sets, use the min scale + case 4: + case 7: + m_meshSize = PxMin(m_meshSize, m_combiningMeshSize); + break; + } + + mergeLeaves(BoolOp(operation), m_root); + + m_incidentalMesh = m_incidentalMesh || m_combiningIncidentalMesh; + + m_combined = false; + + return true; +} + +bool +BSP::complement() +{ + if (m_combined) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eINVALID_OPERATION, + "BSP::complement: can only complement an uncombined BSP. Use op() to merge a combined BSP.", __FILE__, __LINE__); + return false; + } + + complementLeaves(m_root); + + return true; +} + +BSPType::Enum +BSP::getType() const +{ + if (m_combined) + { + return BSPType::Combined; + } + + if (m_root->getType() != Node::Leaf) + { + return BSPType::Nontrivial; + } + + return m_root->getLeafData()->side == 1 ? BSPType::All_Space : BSPType::Empty_Set; +} + +bool +BSP::getSurfaceAreaAndVolume(float& area, float& volume, bool inside, Operation::Enum operation) const +{ + if (m_combined && operation == Operation::NOP) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eINVALID_OPERATION, + "BSP::getSurfaceAreaAndVolume: an operation must be provided for combined BSPs.", __FILE__, __LINE__); + return false; + } + + if (!m_combined && operation != Operation::NOP) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "BSP::getSurfaceAreaAndVolume: warning, operation ignored for non-combined BSPs." , __FILE__, __LINE__); + } + + Real realArea = (Real)0; + Real realVolume = (Real)0; + if (addLeafAreasAndVolumes(realArea, realVolume, m_root, inside, BoolOp(operation))) + { + area = (float)realArea; + volume = (float)realVolume; + return true; + } + + area = PX_MAX_F32; + volume = PX_MAX_F32; + return false; +} + +bool +BSP::pointInside(const PxVec3& point, Operation::Enum operation) const +{ + if (m_combined && operation == Operation::NOP) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eINVALID_OPERATION, + "BSP::pointInside: an operation must be provided for combined BSPs.", __FILE__, __LINE__); + return 0; + } + + if (!m_combined && operation != Operation::NOP) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "BSP::pointInside: warning, operation ignored for non-combined BSPs." , __FILE__, __LINE__); + } + + Node* node = m_root; + + const PxVec3 BSPPoint = m_internalTransform.transform(point); + + while (node->getType() == Node::Branch) + { + const Surface* surface = node->getBranchData(); + node = node->getChild((uint32_t)((m_planes[surface->planeIndex].distance(BSPPoint)) <= 0.0f)); + } + + const Region* region = node->getLeafData(); + + uint32_t side = region->side; + if (m_combined) + { + side = BoolOp(operation)(side & 1, (side >> 1) & 1); + } + + return side != 0; +} + +bool +BSP::toMesh(physx::Array<ExplicitRenderTriangle>& mesh) const +{ + if (m_combined) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eINVALID_OPERATION, + "BSP::toMesh: can only generate a mesh from an uncombined BSP. Use op() to merge a combined BSP.", __FILE__, __LINE__); + return false; + } + + // Clip triangles collected from leaves + physx::Array<Triangle> clippedMesh; + physx::Array<ClippedTriangleInfo> triangleInfo; + clipMeshToLeaves(clippedMesh, triangleInfo, m_root, m_tolerarnces.clip); + + // Clean + if (m_tolerarnces.cleaning > 0 && !m_incidentalMesh) + { + cleanMesh(mesh, clippedMesh, triangleInfo, m_planes, m_mesh, m_frames, (Real)m_tolerarnces.cleaning * m_meshSize, m_internalTransformInverse); + } + else + { + // Copy to render format + mesh.resize(clippedMesh.size()); + for (uint32_t i = 0; i < clippedMesh.size(); ++i) + { + clippedMesh[i].transform(m_internalTransformInverse); + VertexData vertexData[3]; + for (int v = 0; v < 3; ++v) + { + m_frames[triangleInfo[i].originalTriangleIndex].interpolateVertexData(vertexData[v], clippedMesh[i].vertices[v]); + if (!triangleInfo[i].ccw) + { + vertexData[v].normal *= -1.0; + } + } + clippedMesh[i].toExplicitRenderTriangle(mesh[i], vertexData); + } + } + + return true; +} + +void +BSP::copy(const IApexBSP& ibsp, const physx::PxMat44& pxTM, const physx::PxMat44& internalTransform) +{ + const BSP& bsp = *(const BSP*)&ibsp; + + if (this != &bsp) + { + // Copy other bsp + clear(); + + if (bsp.m_root) + { + m_root = m_memCache->m_nodePool.borrow(); + clone(m_root, bsp.m_root); + } + m_tolerarnces = bsp.m_tolerarnces; + m_mesh = bsp.m_mesh; + m_frames = bsp.m_frames; + m_planes = bsp.m_planes; + m_meshSize = bsp.m_meshSize; + m_incidentalMesh = bsp.m_incidentalMesh; + m_internalTransform = bsp.m_internalTransform; + m_internalTransformInverse = bsp.m_internalTransformInverse; + m_combined = bsp.m_combined; + m_combiningMeshSize = bsp.m_combiningMeshSize; + m_combiningIncidentalMesh = bsp.m_combiningIncidentalMesh; + } + + // Take new internal transform if it is valid + if (!isZero(internalTransform)) + { + m_internalTransform = internalTransform; + } + + // Do not calculate new m_internalTransformInverse yet. We need it to transform out of the BSP space. + + // Translate physx::PxMat44 to Mat4Real + // We actually need to apply this transform *before* the internal transform, so we apply: m_internalTransform*pxTM*m_internalTransformInverse + physx::PxMat44 pxTMITM = m_internalTransform*pxTM; + Mat4Real tmITM; + tmITM.setCol(0, Dir((physx::PxF32*)&pxTMITM[0])); + tmITM.setCol(1, Dir((physx::PxF32*)&pxTMITM[1])); + tmITM.setCol(2, Dir((physx::PxF32*)&pxTMITM[2])); + tmITM.setCol(3, Pos((physx::PxF32*)&pxTMITM[3])); + + const Mat4Real netTM = tmITM*m_internalTransformInverse; + + // Do not transform if netTM is the identity + bool isIdentity = true; + for (uint32_t i = 0; i < 4 && isIdentity; ++i) + { + for (uint32_t j = 0; j < 4 && isIdentity; ++j) + { + isIdentity = physx::PxAbs(netTM[i][j] - (Real)(i == j)) < (Real)(10.0f*PX_EPS_F32); + } + } + if (!isIdentity) + { + transform(netTM); + } + + // Now calculate m_internalTransformInverse. + m_internalTransformInverse = CSGFromPx(m_internalTransform).inverse34(); +} + +IApexBSP* +BSP::decomposeIntoIslands() const +{ + // Must be normal BSP + if (m_combined) + { + return NULL; + } + + // First enumerate all inside leaves + uint32_t insideLeafCount = 0; + indexInsideLeaves(insideLeafCount, m_root); + if (insideLeafCount == 0) + { + return NULL; + } + + // Find all leaf neighbors + physx::Array<IntPair> neighbors; + findInsideLeafNeighbors(neighbors, m_root); + + // Find leaf neighbor islands + physx::Array< physx::Array<uint32_t> > islands; + findIslands(islands, neighbors, insideLeafCount); + + // Return this if there is only one island + if (islands.size() == 1) + { + return const_cast<BSP*>(this); + } + + // Otherwise we make a BSP list + physx::Array<Node*> insideLeaves; + insideLeaves.reserve(insideLeafCount); + BSPLink* listRoot = PX_NEW(BSPLink)(); + for (uint32_t islandNum = islands.size(); islandNum--;) + { + // Create new island + BSP* islandBSP = static_cast<BSP*>(createBSP(m_memCache)); + if (islandBSP != NULL) + { + // Copy island BSP from this and add to list + islandBSP->copy(*this); + listRoot->setAdj(1, islandBSP); + // Create a list of the inside leaf pointers + insideLeaves.clear(); + listInsideLeaves(insideLeaves, islandBSP->m_root); + // Set all the leaves' sides to 0 + for (uint32_t leafNum = 0; leafNum < insideLeaves.size(); ++leafNum) + { + insideLeaves[leafNum]->getLeafData()->side = 0; + } + // Set island leaves' sides to 1 + const physx::Array<uint32_t>& island = islands[islandNum]; + for (uint32_t islandLeafNum = 0; islandLeafNum < island.size(); ++islandLeafNum) + { + insideLeaves[island[islandLeafNum]]->getLeafData()->side = 1; + } + // Now merge leaves to consolidate new 0-0 siblings + islandBSP->mergeLeaves(BoolOp(Operation::Set_A), islandBSP->m_root); + } + } + + // Return list head + if (!listRoot->isSolitary()) + { + delete this; + return static_cast<BSP*>(listRoot->getAdj(1)); + } + + delete listRoot; + return const_cast<BSP*>(this); +} + +void +BSP::replaceInteriorSubmeshes(uint32_t frameCount, uint32_t* frameIndices, uint32_t submeshIndex) +{ + // Replace render mesh submesh indices + for (uint32_t triangleIndex = 0; triangleIndex < m_mesh.size(); ++triangleIndex) + { + Triangle& triangle = m_mesh[triangleIndex]; + for (uint32_t frameNum = 0; frameNum < frameCount; ++frameNum) + { + if (triangle.extraDataIndex == frameIndices[frameNum]) + { + triangle.submeshIndex = (int32_t)submeshIndex; + } + } + } +} + +void +BSP::deleteTriangles() +{ + m_mesh.reset(); + m_frames.reset(); + for (Node::It it(m_root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Branch) + { + Surface* surface = node->getBranchData(); + surface->triangleIndexStart = 0; + surface->triangleIndexStop = 0; + surface->totalTriangleArea = 0.0f; + } + } +} + +void +BSP::serialize(physx::PxFileBuf& stream) const +{ + stream << (uint32_t)Version::Current; + + // Tree + serializeNode(m_root, stream); + + // Internal mesh representation + nvidia::serialize(stream, m_mesh); + nvidia::serialize(stream, m_frames); + stream << m_meshSize; + stream << m_incidentalMesh; + stream << m_meshBounds; + stream << m_internalTransform; + stream << m_internalTransformInverse; + + // Unique splitting planes + nvidia::serialize(stream, m_planes); + + // Combination data + stream << m_combined; + stream << m_combiningMeshSize; + stream << m_combiningIncidentalMesh; +} + +void +BSP::deserialize(physx::PxFileBuf& stream) +{ + clear(); + + uint32_t version; + stream >> version; + + // Tree + m_root = deserializeNode(version, stream); + + if (version < Version::RevisedMeshTolerances) + { + stream.readDouble(); // Swallow old m_linearTol + stream.readDouble(); // Swallow old m_angularTol + } + + // Internal mesh representation + if (version >= Version::SerializingTriangleFrames) + { + apex::deserialize(stream, version, m_mesh); + nvidia::deserialize(stream, version, m_frames); + } + else + { + const uint32_t triangleCount = stream.readDword(); + m_mesh.resize(triangleCount); + m_frames.resize(triangleCount); + for (uint32_t triN = 0; triN < triangleCount; ++triN) + { + Triangle& tri = m_mesh[triN]; + if (version < Version::UsingOnlyPositionDataInVertex) + { + VertexData vertexData[3]; + for (uint32_t v = 0; v < 3; ++v) + { + stream >> tri.vertices[v]; + stream >> vertexData[v].normal; + stream >> vertexData[v].binormal; + stream >> vertexData[v].binormal; + for (uint32_t uvN = 0; uvN < VertexFormat::MAX_UV_COUNT; ++uvN) + { + stream >> vertexData[v].uv[uvN]; + } + stream >> vertexData[v].color; + } + m_frames[triN].setFromTriangle(tri, vertexData); + } + else + { + for (uint32_t i = 0; i < 3; ++i) + { + nvidia::deserialize(stream, version, tri); + } + } + stream >> tri.submeshIndex; + stream >> tri.smoothingMask; + stream >> tri.extraDataIndex; + stream >> tri.normal; + stream >> tri.area; + } + } + stream >> m_meshSize; + + if (version >= Version::IncidentalMeshDistinction) + { + stream >> m_incidentalMesh; + } + + if (version >= Version::SerializingMeshBounds) + { + stream >> m_meshBounds; + } + else + { + m_meshBounds.setEmpty(); + for (uint32_t triangleN = 0; triangleN < m_mesh.size(); ++triangleN) + { + Triangle& tri = m_mesh[triangleN]; + for (uint32_t v = 0; v < 3; ++v) + { + Pos& vertex = tri.vertices[v]; + m_meshBounds.include(physx::PxVec3((float)vertex[0], (float)vertex[1], (float)vertex[2])); + } + } + } + + if (version < Version::RevisedMeshTolerances) + { + stream.readDouble(); // Swallow old m_distanceTol + } + + if (version >= Version::AddedInternalTransform) + { + stream >> m_internalTransform; + stream >> m_internalTransformInverse; + } + else + { + m_internalTransform = physx::PxMat44(physx::PxIdentity); + m_internalTransformInverse.set((Real)1); + } + + // Unique splitting planes + apex::deserialize(stream, version, m_planes); + + // Combination data + stream >> m_combined; + stream >> m_combiningMeshSize; + if (version >= Version::IncidentalMeshDistinction) + { + stream >> m_combiningIncidentalMesh; + } + + if (m_root == NULL) + { + // Set to trivial tree + clear(); + m_root = m_memCache->m_nodePool.borrow(); + } +} + +void BSP::visualize(RenderDebugInterface& debugRender, uint32_t flags, uint32_t index) const +{ +#ifdef WITHOUT_DEBUG_VISUALIZE + PX_UNUSED(debugRender); + PX_UNUSED(flags); + PX_UNUSED(index); +#else + const Node* node = m_root; + if (flags & BSPVisualizationFlags::SingleRegion) + { + uint32_t count = 0; + for (Node::It it(m_root); it.valid(); it.inc()) + { + Node* current = it.node(); + if (current->getType() == BSP::Node::Leaf) + { + if (index == count++) + { + node = current; + break; + } + } + } + } + + if (node != NULL) + { + visualizeNode(debugRender, flags, node); + } +#endif +} + +void +BSP::release() +{ + clear(); + delete this; +} + +void +BSP::clear() +{ + if (m_root != NULL) + { + releaseNode(m_root); + m_root = NULL; + } + m_incidentalMesh = false; + m_combiningIncidentalMesh = false; + m_combiningMeshSize = (Real)1; + m_combined = false; + m_mesh.reset(); + m_frames.reset(); + m_planes.reset(); + removeBSPLink(); +} + +void +BSP::clipMeshToLeaf(Real& area, Real& volume, physx::Array<Triangle>* clippedMesh, physx::Array<ClippedTriangleInfo>* triangleInfo, const Node* leaf, float clipTolerance) const +{ + PX_ASSERT(leaf->getType() == BSP::Node::Leaf); + + area = (Real)0; + volume = (Real)0; + + const Pos center(&m_meshBounds.getCenter()[0]); + + // Collect triangles on each surface and clip to other faces + for (SurfaceIt it(leaf); it.valid(); it.inc()) + { + for (uint32_t i = it.surface()->triangleIndexStart; i < it.surface()->triangleIndexStop; ++i) + { + const uint32_t oldClippedMeshSize = clippedMesh != NULL ? clippedMesh->size() : 0; + Real clippedTriangleArea, clippedPyramidVolume; + clipTriangleToLeaf(clippedMesh, clippedTriangleArea, clippedPyramidVolume, center, m_mesh[i], leaf, it.side(), m_memCache->m_linkedVertexPool, + clipTolerance * m_meshSize, m_planes, it.surface()->planeIndex); + area += clippedTriangleArea; + volume += clippedPyramidVolume; + if (triangleInfo != NULL && clippedMesh != NULL) + { + // Fill triangleInfo corresponding to new clipped triangles + const uint32_t newClippedMeshSize = clippedMesh->size(); + for (uint32_t j = oldClippedMeshSize; j < newClippedMeshSize; ++j) + { + ClippedTriangleInfo& info = triangleInfo->insert(); + info.planeIndex = it.surface()->planeIndex; + info.originalTriangleIndex = i; + info.clippedTriangleIndex = j; + info.ccw = it.side(); + } + } + } + } + + if (m_incidentalMesh) + { + for (uint32_t i = 0; i < m_mesh.size(); ++i) + { + const uint32_t oldClippedMeshSize = clippedMesh != NULL ? clippedMesh->size() : 0; + Real clippedTriangleArea, clippedPyramidVolume; + clipTriangleToLeaf(clippedMesh, clippedTriangleArea, clippedPyramidVolume, center, m_mesh[i], leaf, 1, m_memCache->m_linkedVertexPool, clipTolerance * m_meshSize, m_planes); + if (triangleInfo != NULL && clippedMesh != NULL) + { + // Fill triangleInfo corresponding to new clipped triangles + const uint32_t newClippedMeshSize = clippedMesh->size(); + for (uint32_t j = oldClippedMeshSize; j < newClippedMeshSize; ++j) + { + ClippedTriangleInfo& info = triangleInfo->insert(); + info.planeIndex = 0xFFFFFFFF; + info.originalTriangleIndex = i; + info.clippedTriangleIndex = j; + info.ccw = 1; + } + } + } + } +} + +void +BSP::transform(const Mat4Real& tm, bool transformFrames) +{ + // Build cofactor matrix for transformation of normals + const Mat4Real cofTM = tm.cof34(); + const Mat4Real invTransposeTM = cofTM/cofTM[3][3]; + + // Transform mesh + for (uint32_t i = 0; i < m_mesh.size(); ++i) + { + for (int v = 0; v < 3; ++v) + { + m_mesh[i].vertices[v] = tm * m_mesh[i].vertices[v]; + } + m_mesh[i].calculateQuantities(); + if (transformFrames) + { + m_frames[i].transform(m_frames[i], tm, invTransposeTM); + } + } + + // Transform planes + for (uint32_t i = 0; i < m_planes.size(); ++i) + { + m_planes[i] = cofTM * m_planes[i]; // Don't normalize yet - surface areas will be calculated from plane normal lengths in "Transform tree" below + } + + // Transform tree + for (Node::It it(m_root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Branch) + { + // Transform surface quantities + node->getBranchData()->totalTriangleArea *= (float)physx::PxSqrt(m_planes[node->getBranchData()->planeIndex].normal().lengthSquared()); + } + } + + // Now normalize planes + for (uint32_t i = 0; i < m_planes.size(); ++i) + { + m_planes[i].normalize(); + } + + // Adjust sizes + const Real scale = physx::PxPow((float) tm.det3(), (float)0.33333333333333333); + m_meshSize *= scale; + m_combiningMeshSize *= scale; +} + +void +BSP::clean() +{ + /* + 1) Mark planes and triangles that are used in the tree + 2) Remove those that aren't, creating index maps, bounds, and size + 3) Walk tree again and re-index + */ + + physx::Array<uint32_t> planeMap(m_planes.size(), 0); + physx::Array<uint32_t> triangleMap(m_mesh.size()+1, 0); + + // 1) Mark planes and triangles that are used in the tree + for (Node::It it(m_root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Branch) + { + const Surface* surface = node->getBranchData(); + planeMap[surface->planeIndex] = 1; + for (uint32_t triangleIndex = surface->triangleIndexStart; triangleIndex < surface->triangleIndexStop; ++triangleIndex) + { + triangleMap[triangleIndex] = 1; + } + } + } + + if (m_incidentalMesh || (m_combined && m_combiningIncidentalMesh)) + { + // All triangles used + for (uint32_t triangleIndex = 0; triangleIndex < m_mesh.size(); ++triangleIndex) + { + triangleMap[triangleIndex] = 1; + } + } + + // 2) Remove those that aren't, creating index maps and bounds + m_meshBounds.setEmpty(); + + uint32_t newPlaneIndex = 0; + for (uint32_t oldPlaneIndex = 0; oldPlaneIndex < planeMap.size(); ++oldPlaneIndex) + { + const bool planeUsed = planeMap[oldPlaneIndex] != 0; + planeMap[oldPlaneIndex] = newPlaneIndex; + if (planeUsed) + { + m_planes[newPlaneIndex++] = m_planes[oldPlaneIndex]; + } + } + m_planes.resize(newPlaneIndex); + + uint32_t newTriangleIndex = 0; + for (uint32_t oldTriangleIndex = 0; oldTriangleIndex < triangleMap.size(); ++oldTriangleIndex) + { + const bool triangleUsed = triangleMap[oldTriangleIndex] != 0; + triangleMap[oldTriangleIndex] = newTriangleIndex; + if (triangleUsed) + { + Triangle& triangle = m_mesh[newTriangleIndex]; + triangle = m_mesh[oldTriangleIndex]; + for (int v = 0; v < 3; ++v) + { + const Pos& vertex = triangle.vertices[v]; + m_meshBounds.include(physx::PxVec3((float)vertex[0], (float)vertex[1], (float)vertex[3])); + } + m_frames[newTriangleIndex] = m_frames[oldTriangleIndex]; + ++newTriangleIndex; + } + } + m_mesh.resize(newTriangleIndex); + m_frames.resize(newTriangleIndex); + + m_meshSize = (Real)m_meshBounds.getExtents().maxElement(); + + // 3) Walk tree again and re-index + for (Node::It it(m_root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Branch) + { + Surface* surface = const_cast<Surface*>(node->getBranchData()); + surface->planeIndex = planeMap[surface->planeIndex]; + const uint32_t surfaceTriangleCount = surface->triangleIndexStop - surface->triangleIndexStart; + surface->triangleIndexStart = triangleMap[surface->triangleIndexStart]; + surface->triangleIndexStop = surface->triangleIndexStart + surfaceTriangleCount; // Do it this way since the last triangleIndexStop is unmapped + } + } +} + +void +BSP::performDiagnostics() const +{ + debugInfo("BSP diagnostics starting."); + + char msg[10240]; + + debugInfo("Checking for holes..."); + + // This is the "raw result" from toMesh(). It's in our internal (high-precision) format, not cleaned, etc.: + physx::Array<Triangle> clippedMesh; + physx::Array<ClippedTriangleInfo> triangleInfo; + clipMeshToLeaves(clippedMesh, triangleInfo, m_root, m_tolerarnces.clip); + + for (uint32_t triangleIndex = 0; triangleIndex < m_mesh.size(); ++triangleIndex) + { + // Make sure triangle is in a branch somewhere + physx::Array<BSP::Node*> foundInNodes; + for (Node::It it(m_root); it.valid(); it.inc()) + { + BSP::Node* node = static_cast<BSP::Node*>(it.node()); + if (node->getType() == BSP::Node::Branch) + { + const Surface* surface = node->getBranchData(); + if (triangleIndex >= surface->triangleIndexStart && triangleIndex < surface->triangleIndexStop) + { + foundInNodes.pushBack(node); + } + } + } + if (foundInNodes.empty()) + { + sprintf(msg, "Triangle %d not found in any branch.", triangleIndex); + debugWarn(msg); + } + + // Make sure the triangle comes back with no holes + const Triangle& triangle = m_mesh[triangleIndex]; + if (triangle.area > (Real)0) + { + Real area = (Real)0; + for (uint32_t clippedTriangleIndex = 0; clippedTriangleIndex < clippedMesh.size(); ++clippedTriangleIndex) + { + ClippedTriangleInfo& info = triangleInfo[clippedTriangleIndex]; + if (info.originalTriangleIndex != triangleIndex) + { + continue; + } + Triangle& clippedMeshTriangle = clippedMesh[clippedTriangleIndex]; + area += clippedMeshTriangle.area; + } + + const Real areaError = area/triangle.area - (Real)1; + if (physx::PxAbs(areaError) > (Real)0.000001) + { + sprintf(msg, "Triangle %d is reconstructed with a different area: error = %7.4g%%.", triangleIndex, (Real)100*areaError); + debugWarn(msg); + + sprintf(msg, " Triangle %d is found in %d node(s):", triangleIndex, foundInNodes.size()); + debugInfo(msg); + Real totalClippedArea = (Real)0; + for (uint32_t nodeN = 0; nodeN < foundInNodes.size(); ++nodeN) + { + sprintf(msg, " Node #%d:", nodeN); + debugInfo(msg); + Real nodeArea = (Real)0; + for (Node::It it(foundInNodes[nodeN]); it.valid(); it.inc()) + { + Node* subTreeNode = it.node(); + if (subTreeNode->getType() == BSP::Node::Leaf) + { + const uint32_t planeSide = subTreeNode->getIndex(); + if (subTreeNode->getLeafData()->side == 0) + { + continue; + } + Real clipArea, clipVolume; + const Pos origin(&m_meshBounds.getCenter()[0]); + clipTriangleToLeaf(NULL, clipArea, clipVolume, origin, triangle, subTreeNode, planeSide, m_memCache->m_linkedVertexPool, m_tolerarnces.clip*m_meshSize, m_planes, foundInNodes[nodeN]->getBranchData()->planeIndex); + nodeArea += clipArea; +// sprintf(msg, " Subtree leaf area = %15.7f.", clipArea); +// debugInfo(msg); + } + } + totalClippedArea += nodeArea; + sprintf(msg, " Total node area = %15.7g.", nodeArea); + debugInfo(msg); + } + sprintf(msg, " Total clipped area = %15.7g.", totalClippedArea); + debugInfo(msg); + + sprintf(msg, " Attempting brute-force decoposition."); + Real totalClippedArea2ndAttempt[2] = {(Real)0, (Real)0}; + uint32_t leafCount[2] = {0, 0}; + for (Node::It it(m_root); it.valid(); it.inc()) + { + Node* n = it.node(); + if (n->getType() == BSP::Node::Leaf) + { + const uint32_t planeSide = n->getIndex(); + const uint32_t side = n->getLeafData()->side & 1; + Real clipArea, clipVolume; + const Pos origin(&m_meshBounds.getCenter()[0]); + clipTriangleToLeaf(NULL, clipArea, clipVolume, origin, triangle, n, planeSide, m_memCache->m_linkedVertexPool, m_tolerarnces.clip*m_meshSize, m_planes); + totalClippedArea2ndAttempt[side] += clipArea; + if (clipArea != 0) + { + ++leafCount[side]; + sprintf(msg, " Non-zero area found in side(%d) leaf. Parent planes:", side); + const BSP::Node* nn = n; + while((nn = (const BSP::Node*)nn->getParent()) != NULL) + { + char num[32]; + sprintf(num, " %d,", nn->getBranchData()->planeIndex); + strcat(msg, num); + } + debugInfo(msg); + } + } + } + sprintf(msg, " Total outside area from %d leaves = %15.7g.", leafCount[0], totalClippedArea2ndAttempt[0]); + sprintf(msg, " Total inside area from %d leaves = %15.7g.", leafCount[1], totalClippedArea2ndAttempt[1]); + debugInfo(msg); + } + } + else + { + sprintf(msg, "Triangle %d has non-positive area.", triangleIndex); + debugWarn(msg); + } + } + + debugInfo("BSP diagnostics finished."); +} + +PX_INLINE uint32_t +BSP::removeRedundantSurfacesFromStack(physx::Array<Surface>& surfaceStack, uint32_t stackReadStart, uint32_t stackReadStop, Node* leaf) +{ + // Remove surfaces that don't have triangles intersecting this region + const Pos center(&m_meshBounds.getCenter()[0]); + + for (uint32_t i = stackReadStop; i-- > stackReadStart;) + { + Surface* surface = surfaceStack.begin() + i; + bool surfaceIntersectsThisRegion = false; + for (uint32_t j = surface->triangleIndexStart; j < surface->triangleIndexStop; ++j) + { + Real clippedTriangleArea, clippedPyramidVolume; + clipTriangleToLeaf(NULL, clippedTriangleArea, clippedPyramidVolume, center, m_mesh[j], leaf, 1, m_memCache->m_linkedVertexPool, m_tolerarnces.base, m_planes); + if (0 < clippedTriangleArea) + { + surfaceIntersectsThisRegion = true; + break; + } + } + if (!surfaceIntersectsThisRegion) + { + surfaceStack[i] = surfaceStack[--stackReadStop]; + } + } + + return stackReadStop; +} + +PX_INLINE void +BSP::assignLeafSide(Node* leaf, QuantityProgressListener* quantityListener) +{ + const Pos center(&m_meshBounds.getCenter()[0]); + + // See if this leaf is inside or outside + Real sumSignedArea = (Real)0; + for (SurfaceIt it(leaf); it.valid(); it.inc()) + { + const Real sign = it.side() ? (Real)1 : -(Real)1; + for (uint32_t j = it.surface()->triangleIndexStart; j < it.surface()->triangleIndexStop; ++j) + { + Real clippedTriangleArea, clippedPyramidVolume; + clipTriangleToLeaf(NULL, clippedTriangleArea, clippedPyramidVolume, center, m_mesh[j], leaf, it.side(), m_memCache->m_linkedVertexPool, m_tolerarnces.base, m_planes, it.surface()->planeIndex); + sumSignedArea += sign * clippedTriangleArea; + } + } + + if (sumSignedArea != (Real)0) + { + leaf->getLeafData()->side = sumSignedArea > 0 ? 1u : 0u; + quantityListener->add((Real)0.5*physx::PxAbs(sumSignedArea)); + } +} + +PX_INLINE void +BSP::createBranchSurfaceAndSplitStack(uint32_t childReadStart[2], uint32_t childReadStop[2], Node* node, physx::Array<Surface>& surfaceStack, uint32_t stackReadStart, + uint32_t stackReadStop, const BuildConstants& buildConstants) +{ + const uint32_t surfaceListSize = stackReadStop - stackReadStart; + Surface* surfaceList = surfaceStack.begin() + stackReadStart; + + if (m_memCache->m_surfaceFlags.size() < surfaceListSize) + { + m_memCache->m_surfaceFlags.resize(surfaceListSize); + m_memCache->m_surfaceTestFlags.resize(surfaceListSize); + } + + uint32_t branchSurfaceN = 0; // Will be the winning surface - default to 1st surface + + Surface* branchSurface = surfaceList + branchSurfaceN; + + bool splittingCalculated = false; + + if (surfaceListSize > 1) + { + float maxLogArea = -PX_MAX_F32; + float meanLogArea = 0.0f; + float sigma2LogArea = 0.0f; + if (buildConstants.m_params.logAreaSigmaThreshold > 0) + { + uint32_t positiveAreaCount = 0; + for (uint32_t i = 0; i < surfaceListSize; ++i) + { + // Test surface + Surface& testSurface = surfaceList[i]; + if (testSurface.totalTriangleArea <= 0.0f) + { + continue; + } + ++positiveAreaCount; + const float logArea = physx::PxLog(testSurface.totalTriangleArea); + if (logArea > maxLogArea) + { + maxLogArea = logArea; + branchSurfaceN = i; // Candidate + } + meanLogArea += logArea; + } + if (positiveAreaCount > 1) + { + meanLogArea /= (float)positiveAreaCount; + for (uint32_t i = 0; i < surfaceListSize; ++i) + { + // Test surface + Surface& testSurface = surfaceList[i]; + if (testSurface.totalTriangleArea <= 0.0f) + { + continue; + } + const float logArea = physx::PxLog(testSurface.totalTriangleArea); + sigma2LogArea += square<float>(logArea - meanLogArea); + } + sigma2LogArea /= (float)(positiveAreaCount-1); + } + + // Possibly new branchSurfaceN + branchSurface = surfaceList + branchSurfaceN; + } + if (maxLogArea > meanLogArea && square<float>(maxLogArea - meanLogArea) < square(buildConstants.m_params.logAreaSigmaThreshold)*sigma2LogArea) + { + // branchSurface chosen by max area does not have an area that is outside of one standard deviation from the mean surface area. Use another method to determine branchSurface. + + // Pick buildConstants.m_testSetSize surfaces + const uint32_t testSetSize = buildConstants.m_params.testSetSize > 0 ? PxMin(surfaceListSize, buildConstants.m_params.testSetSize) : surfaceListSize; + + // Low score wins + float minScore = PX_MAX_F32; + for (uint32_t i = 0; i < testSetSize; ++i) + { + // Test surface + Surface* testSurface = surfaceList + i; + int32_t counts[4] = {0, 0, 0, 0}; // on, above, below, split + uint32_t triangleCount = 0; + for (uint32_t j = 0; j < surfaceListSize; ++j) + { + uint8_t& flags = m_memCache->m_surfaceTestFlags[j]; // Whether this surface is above or below testSurface (or both) + flags = 0; + + if (j == i) + { + continue; // Don't score testSurface itself + } + + // Surface to contribute to score + Surface* surface = surfaceList + j; + + // Run through all triangles + for (uint32_t k = surface->triangleIndexStart; k < surface->triangleIndexStop; ++k) + { + const Triangle& tri = m_mesh[k]; + uint8_t triFlags = 0; + for (uint32_t v = 0; v < 3; ++v) + { + const int side = cmpPointToPlane(tri.vertices[v], m_planes[testSurface->planeIndex], m_tolerarnces.base); + // triFlags |= (side & 1) << ((1 - side) >> 1); // 0 => 0, 1 => 1, -1 => 2 + triFlags |= (int)(side <= 0) << 1 | (int)(side >= 0); // 0 => 3, 1 => 1, -1 => 2 + } + ++counts[triFlags]; + flags |= triFlags; + } + + triangleCount += surface->triangleIndexStop - surface->triangleIndexStart; + } + + // Compute score = (surface area)/(max area) + (split weight)*(# splits)/(# triangles) + (imbalance weight)*|(# above) - (# below)|/(# triangles) + const float score = testSurface->totalTriangleArea * buildConstants.m_recipMaxArea + + (buildConstants.m_params.splitWeight * counts[3] + buildConstants.m_params.imbalanceWeight * physx::PxAbs(counts[1] - counts[2])) / triangleCount; + + if (score < minScore) + { + // We have a winner + branchSurfaceN = i; + minScore = score; + memcpy(m_memCache->m_surfaceFlags.begin(), m_memCache->m_surfaceTestFlags.begin(), surfaceListSize * sizeof(m_memCache->m_surfaceFlags[0])); + } + } + + // Possibly new branchSurfaceN + branchSurface = surfaceList + branchSurfaceN; + splittingCalculated = true; + } + } + + if (!splittingCalculated) + { + for (uint32_t i = 0; i < surfaceListSize; ++i) + { + uint8_t& flags = m_memCache->m_surfaceFlags[i]; // Whether this surface is above or below branchSurface (or both) + flags = 0; + + if (i == branchSurfaceN) + { + continue; // Don't score branchSurface itself + } + + // Surface to contribute to score + Surface& surface = surfaceList[i]; + + // Run through all triangles + for (uint32_t j = surface.triangleIndexStart; j < surface.triangleIndexStop; ++j) + { + const Triangle& tri = m_mesh[j]; + for (uint32_t v = 0; v < 3; ++v) + { + const int side = cmpPointToPlane(tri.vertices[v], m_planes[branchSurface->planeIndex], m_tolerarnces.base); + // flags |= (side & 1) << ((1 - side) >> 1); // 0 => 0, 1 => 1, -1 => 2 + flags |= (int)(side <= 0) << 1 | (int)(side >= 0); // 0 => 3, 1 => 1, -1 => 2 + } + } + } + } + + // Run through the surface flags and create below/above arrays on the stack. + // These arrays will be contiguous with child[0] surfaces first. + childReadStart[0] = surfaceStack.size(); + childReadStop[0] = childReadStart[0]; + uint32_t targetStackSize = 2*(surfaceStack.size() + 2 * surfaceListSize); + for (;;) + { + const uint32_t newTargetStackSize = targetStackSize&(targetStackSize-1); + if (newTargetStackSize == 0) + { + break; + } + targetStackSize = newTargetStackSize; + } + + surfaceStack.reserve(targetStackSize); + for (uint32_t j = 0; j < surfaceListSize; ++j) + { + uint32_t surfaceJ = j + stackReadStart; + if (j == branchSurfaceN) + { + continue; + } + switch (m_memCache->m_surfaceFlags[j]) + { + case 0: + break; + case 1: + surfaceStack.insert(); + surfaceStack.back() = surfaceStack[childReadStop[0]]; + surfaceStack[childReadStop[0]++] = surfaceStack[surfaceJ]; + break; + case 2: + surfaceStack.pushBack(surfaceStack[surfaceJ]); + break; + case 3: + surfaceStack.insert(); + surfaceStack.back() = surfaceStack[childReadStop[0]]; + surfaceStack[childReadStop[0]++] = surfaceStack[surfaceJ]; + surfaceStack.pushBack(surfaceStack[surfaceJ]); + break; + } + } + childReadStart[1] = childReadStop[0]; + childReadStop[1] = surfaceStack.size(); + + // Set branch data to winning surface + node->setBranchData(surfaceStack[branchSurfaceN + stackReadStart]); +} + +/* Recursive functions */ + +// These can be implemented using a tree iterator or a simple node stack + +void +BSP::releaseNode(Node* node) +{ + PX_ASSERT(node != NULL); + + Node* stop = node->getParent(); + do + { + Node* child0 = node->getChild(0); + if (child0) + { + node = child0; + } + else + { + Node* child1 = node->getChild(1); + if (child1) + { + node = child1; + } + else + { + Node* parent = node->getParent(); + node->detach(); + m_memCache->m_nodePool.replace(node); + node = parent; + } + } + } while (node != stop); +} + +void +BSP::indexInsideLeaves(uint32_t& index, Node* root) const +{ + for (Node::It it(root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Leaf) + { + if (node->getLeafData()->side == 1) + { + node->getLeafData()->tempIndex1 = index++; + } + } + } +} + +void +BSP::listInsideLeaves(physx::Array<Node*>& insideLeaves, Node* root) const +{ + for (Node::It it(root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Leaf) + { + if (node->getLeafData()->side == 1) + { + insideLeaves.pushBack(node); + } + } + } +} + +void +BSP::complementLeaves(Node* root) const +{ + for (Node::It it(root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Leaf) + { + node->getLeafData()->side = node->getLeafData()->side ^ 1; + } + } +} + +void +BSP::clipMeshToLeaves(physx::Array<Triangle>& clippedMesh, physx::Array<ClippedTriangleInfo>& triangleInfo, Node* root, float clipTolerance) const +{ + for (Node::It it(root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Leaf) + { + if (node->getLeafData()->side == 1) + { + Real area, volume; + clipMeshToLeaf(area, volume, &clippedMesh, &triangleInfo, node, clipTolerance); + } + } + } +} + +void BSP::visualizeNode(RenderDebugInterface& debugRender, uint32_t flags, const Node* root) const +{ +#if defined(WITHOUT_DEBUG_VISUALIZE) + PX_UNUSED(debugRender); + PX_UNUSED(flags); + PX_UNUSED(root); +#else + + const physx::PxMat44 BSPToMeshTM = PxFromCSG(m_internalTransformInverse); + + for (Node::It it(root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Leaf) + { + bool showLeaf = false; + uint32_t color = 0; + if (node->getLeafData()->side == 0) + { + if (flags & (BSPVisualizationFlags::OutsideRegions | BSPVisualizationFlags::SingleRegion)) + { + showLeaf = true; + color = 0xFF0000; // JWR: TODO + } + } + else + { + if (flags & (BSPVisualizationFlags::InsideRegions | BSPVisualizationFlags::SingleRegion)) + { + showLeaf = true; + color = 0x00FF00; // JWR: TODO + } + } + + if (showLeaf) + { + RENDER_DEBUG_IFACE(&debugRender)->setCurrentColor(color); + const Real clampSize = m_meshSize * 10; + for (SurfaceIt i(node); i.valid(); i.inc()) + { + const uint32_t planeIndex_i = i.surface()->planeIndex; + const Plane& plane_i = m_planes[planeIndex_i]; + SurfaceIt j = i; + j.inc(); + for (; j.valid(); j.inc()) + { + const uint32_t planeIndex_j = j.surface()->planeIndex; + const Plane& plane_j = m_planes[planeIndex_j]; + // Find potential edge from intersection if plane_i and plane_j + Pos orig; + Dir edgeDir; + if (intersectPlanes(orig, edgeDir, plane_i, plane_j)) + { + Real minS = -clampSize; + Real maxS = clampSize; + bool intersectionFound = true; + for (SurfaceIt k(node); k.valid(); k.inc()) + { + const uint32_t planeIndex_k = k.surface()->planeIndex; + if (planeIndex_k == planeIndex_i || planeIndex_k == planeIndex_j) + { + continue; + } + const Plane& plane_k = (k.side() ? (Real)1 : -(Real)1)*m_planes[planeIndex_k]; + const Real num = -(orig|plane_k); + const Real den = edgeDir|plane_k; + if (physx::PxAbs(den) > 10*EPS_REAL) + { + const Real s = num/den; + if (den > (Real)0) + { + maxS = PxMin(maxS, s); + } + else + { + minS = PxMax(minS, s); + } + if (maxS <= minS) + { + intersectionFound = false; + break; + } + } + else + if (num < -10*EPS_REAL) + { + intersectionFound = false; + break; + } + } + if (intersectionFound) + { + const Pos e0 = orig + minS * edgeDir; + const Pos e1 = orig + maxS * edgeDir; + physx::PxVec3 p0, p1; + p0 = physx::PxVec3(static_cast<float>(e0[0]),static_cast<float>(e0[1]),static_cast<float>(e0[2])); + p1 = physx::PxVec3(static_cast<float>(e1[0]),static_cast<float>(e1[1]),static_cast<float>(e1[2])); + RENDER_DEBUG_IFACE(&debugRender)->debugLine(BSPToMeshTM.transform(p0), BSPToMeshTM.transform(p1)); + } + } + } + } + } + } + } +#endif +} + +void +BSP::serializeNode(const Node* root, physx::PxFileBuf& stream) const +{ + for (Node::It it(root); it.valid(); it.inc()) + { + Node* node = it.node(); + + if (node != NULL) + { + stream << (uint32_t)1; + stream << (uint32_t)node->getType(); + + if (node->getType() == Node::Branch) + { + nvidia::serialize(stream, *node->getBranchData()); + } + else + { + nvidia::serialize(stream, *node->getLeafData()); + } + } + else + { + stream << (uint32_t)0; + } + } +} + +void +BSP::mergeLeaves(const BoolOp& op, Node* node) +{ + PX_ASSERT(node != NULL); + + // Stackless walk of tree + bool up = false; + Node* stop = node->getParent(); + for (;;) + { + if (up) + { + up = (node->getIndex() == 1); + node = node->getParent(); + if (node == stop) + { + break; + } + if (!up) + { + node = node->getChild(1); + } + else + { + // Climbing hierarchy, at a branch + Node* child0 = node->getChild(0); + Node* child1 = node->getChild(1); + + // Can consolidate if the children are both leaves on the same side. + PX_ASSERT(child0 != NULL && child1 != NULL); + if (child0 != NULL && child1 != NULL && child0->getType() == Node::Leaf && child1->getType() == Node::Leaf) + { + Region* region0 = child0->getLeafData(); + Region* region1 = child1->getLeafData(); + + PX_ASSERT(region0 != NULL && region1 != NULL); + PX_ASSERT((region0->side & 1) == region0->side && (region1->side & 1) == region1->side); + if (region0->side == region1->side) + { + // Consolidate + + // Delete children + child0->detach(); + child1->detach(); + + // Turn this node into a leaf + node->setLeafData(*region0); + m_memCache->m_nodePool.replace(child0); + m_memCache->m_nodePool.replace(child1); + } + } + } + } + else + { + up = (node->getType() == Node::Leaf); + if (!up) + { + // Descend to first child + node = node->getChild(0); + } + else + { + // Leaf found + // Perform boolean operation + const uint32_t side = node->getLeafData()->side; + node->getLeafData()->side = op(side & 1, (side >> 1) & 1); + } + } + } +} + +// The following functions take a more complex set of arguments, or call recursively from points within the function + +BSP::Node* +BSP::deserializeNode(uint32_t version, physx::PxFileBuf& stream) +{ + Node* root = NULL; + + physx::Array<Node*> stack; + + Node* parent = NULL; + + uint32_t readChildIndex = 0xFFFFFFFF; + + for (;;) + { + uint32_t createNode; + stream >> createNode; + + if (createNode) + { + Node* node = m_memCache->m_nodePool.borrow(); + + if (parent == NULL) + { + root = node; + } + else + { + parent->setChild(readChildIndex, node); + } + + uint32_t nodeType; + stream >> nodeType; + + if (nodeType != Node::Leaf) + { + Surface surface; + nvidia::deserialize(stream, version, surface); + node->setBranchData(surface); + + // Push child 1 + stack.pushBack(node); + + // Process child 0 + parent = node; + readChildIndex = 0; + } + else + { + Region region; + + // Make compiler happy + region.tempIndex1 = region.tempIndex2 = region.tempIndex3 = 0; + + nvidia::deserialize(stream, version, region); + + node->setLeafData(region); + + if (stack.size() == 0) + { + break; + } + + parent = stack.popBack(); + readChildIndex = 1; + } + } + } + + return root; +} + +struct CombineTreesFrame +{ + BSP::Node* node; + const BSP::Node* combineNode; +}; + +void +BSP::combineTrees(Node* root, const Node* combineRoot, uint32_t triangleIndexOffset, uint32_t planeIndexOffset) +{ + physx::Array<CombineTreesFrame> stack; + stack.reserve(m_planes.size()); // To avoid reallocations + + RegionShape regionShape((const Plane*)m_planes.begin(), (Real)0.0001*m_meshSize); + + CombineTreesFrame localFrame; + localFrame.node = root; + localFrame.combineNode = combineRoot; + + for (;;) + { + if (localFrame.node->getType() != BSP::Node::Leaf) + { + // Push child 1 + CombineTreesFrame& callFrame = stack.insert(); + callFrame.node = localFrame.node->getChild(1); + callFrame.combineNode = localFrame.combineNode; + + // Process child 0 + localFrame.node = localFrame.node->getChild(0); + continue; + } + else + { + if (localFrame.combineNode->getType() != Node::Leaf) + { + // Branch node + + // Copy branch data, and offset the triangle indices + Surface branchSurface; + const Surface* combineBranchSurface = localFrame.combineNode->getBranchData(); + branchSurface.planeIndex = combineBranchSurface->planeIndex + planeIndexOffset; + branchSurface.triangleIndexStart = combineBranchSurface->triangleIndexStart + triangleIndexOffset; + branchSurface.triangleIndexStop = combineBranchSurface->triangleIndexStop + triangleIndexOffset; + branchSurface.totalTriangleArea = combineBranchSurface->totalTriangleArea; + + // Store off old leaf data + Region oldRegion = *localFrame.node->getLeafData(); + + // Turn this leaf into a branch, see which sides are non-empty + localFrame.node->setBranchData(branchSurface); + bool intersects[2]; + for (uint32_t index = 0; index < 2; ++index) + { + Node* child = m_memCache->m_nodePool.borrow(); + child->setLeafData(oldRegion); + localFrame.node->setChild(index, child); + regionShape.set_leaf(child); + regionShape.calculate(); + intersects[index] = regionShape.is_nonempty(); + } + + if (intersects[0] && intersects[1]) + { + // We need both branches + // Push child 1 + CombineTreesFrame& callFrame = stack.insert(); + callFrame.node = localFrame.node->getChild(1); + callFrame.combineNode = localFrame.combineNode->getChild(1); + + // Process child 0 + localFrame.node = localFrame.node->getChild(0); + localFrame.combineNode = localFrame.combineNode->getChild(0); + continue; + } + else + { + // Leaf not split by the combining branch. Return the new branch surface. + for (uint32_t index = 0; index < 2; ++index) + { + Node* child = localFrame.node->getChild(index); + localFrame.node->setChild(index, NULL); + m_memCache->m_nodePool.replace(child); + } + // Turn this branch back into a leaf + localFrame.node->setLeafData(oldRegion); + // Collapse tree by following one branch. + if (intersects[0]) + { + localFrame.combineNode = localFrame.combineNode->getChild(0); + continue; + } + else + if (intersects[1]) + { + localFrame.combineNode = localFrame.combineNode->getChild(1); + continue; + } + // else we drop down into pop stack, below + } + } + else + { + // Leaf node + localFrame.node->getLeafData()->side = localFrame.node->getLeafData()->side | localFrame.combineNode->getLeafData()->side << 1; + } + } + if (stack.size() == 0) + { + break; + } + localFrame = stack.popBack(); + } +} + +void +BSP::findInsideLeafNeighbors(physx::Array<IntPair>& neighbors, Node* root) const +{ + if (root == NULL) + { + return; + } + + const Real tol = m_meshSize*(Real)0.0001; + + physx::Array<Plane> planes; + + HalfspaceIntersection test; + + for (Node::It it(root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Leaf) + { + // Found a leaf. + if (node->getLeafData()->side == 0) + { + continue; // Only want inside leaves + } + + // Iterate up to root and collect planes + planes.resize(0); + for (SurfaceIt it(node); it.valid(); it.inc()) + { + const Real sign = it.side() ? (Real)1 : -(Real)1; + planes.pushBack(sign * m_planes[it.surface()->planeIndex]); + planes.back()[3] -= tol; + } + +#ifdef CULL_PLANES_LIST +#undef CULL_PLANES_LIST +#endif +#define CULL_PLANES_LIST 1 +#if CULL_PLANES_LIST + // Now flip each plane to see if it's necessary + for (uint32_t planeIndex = planes.size(); planeIndex--;) + { + planes[planeIndex] *= -(Real)1; // Invert + test.setPlanes(planes.begin(), planes.size()); + const int result = GSA::vs3d_test(test); + const bool necessary = 1 == result; + const bool testError = result < 0; + planes[planeIndex] *= -(Real)1; // Restore + if (!necessary && !testError) + { + planes.replaceWithLast(planeIndex); // Unnecessary; remove + } + } +#endif + + if (planes.size() > 0) + { + // First half of pair will always be node's index. + IntPair pair; + pair.i0 = (int32_t)node->getLeafData()->tempIndex1; + + const uint32_t currentLeafPlaneCount = planes.size(); + + // Stackless walk of remainder of tree + bool up = true; + while (node != root) + { + if (up) + { + up = (node->getIndex() == 1); + node = node->getParent(); + if (planes.size() > currentLeafPlaneCount) + { + planes.popBack(); + } + if (!up) + { + planes.pushBack(m_planes[node->getBranchData()->planeIndex]); + planes.back()[3] -= tol; + test.setPlanes(planes.begin(), planes.size()); + up = 0 == GSA::vs3d_test(test); // Skip subtree if there is no intersection at this branch + node = node->getChild(1); + } + } + else + { + up = (node->getType() == Node::Leaf); + if (!up) + { + planes.pushBack(-m_planes[node->getBranchData()->planeIndex]); + planes.back()[3] -= tol; + up = 0 == GSA::vs3d_test(test); // Skip subtree if there is no intersection at this branch + node = node->getChild(0); + } + else + { + Region& region = *node->getLeafData(); + if (region.side == 1) + { + // We have found another inside leaf which intersects (at boundary) + pair.i1 = (int32_t)region.tempIndex1; + neighbors.pushBack(pair); + } + } + } + } + } + } + } +} + +PX_INLINE bool +planeIsNotRedundant(const physx::Array<Plane>& planes, const Plane& plane) +{ + for (uint32_t i = 0; i < planes.size(); ++i) + { + if ((planes[i] - plane).lengthSquared() < CSG_EPS) + { + return false; + } + } + return true; +} + +bool +BSP::addLeafAreasAndVolumes(Real& totalArea, Real& totalVolume, const Node* root, bool inside, const BoolOp& op) const +{ + if (root == NULL) + { + return false; + } + + // Build a list of essential planes + physx::Array<Plane> planes; + +#ifdef CULL_PLANES_LIST +#undef CULL_PLANES_LIST +#endif +#define CULL_PLANES_LIST 0 + +#if CULL_PLANES_LIST + HalfspaceIntersection test; +#endif + + const Mat4Real cofInternalTransform = CSGFromPx(m_internalTransform).cof34(); + + for (Node::It it(root); it.valid(); it.inc()) + { + Node* node = it.node(); + if (node->getType() == BSP::Node::Leaf) + { + // Found a leaf. + + // See if it's on the correct side (possibly after combining) + uint32_t side = node->getLeafData()->side; + if (m_combined) + { + side = op(side & 1, (side >> 1) & 1); + } + if ((side != 0) != inside) + { + continue; + } + + // Iterate up to root and collect planes + planes.resize(0); + for (SurfaceIt it(node); it.valid(); it.inc()) + { + const Real sign = it.side() ? (Real)1 : -(Real)1; + planes.pushBack(sign * m_planes[it.surface()->planeIndex]); + } + +#if CULL_PLANES_LIST + // Now flip each plane to see if it's necessary + for (uint32_t planeIndex = planes.size(); planeIndex--;) + { + planes[planeIndex] *= -(Real)1; // Invert + test.setPlanes(planes.begin(), planes.size()); + const bool necessary = test.intersect(); + const bool testError = (test.state() & ApexCSG::GSA::GSA_State::Error_Flag) != 0; + planes[planeIndex] *= -(Real)1; // Restore + if (!necessary && !error) + { + planes.replaceWithLast(planeIndex); // Unnecessary; remove + } + } +#endif + + // Now use this culled plane list to find areas and volumes + if (planes.size() > 0) + { + Real area, volume; + if (!calculateLeafAreaAndVolume(area, volume, planes.begin(), planes.size(), cofInternalTransform)) + { + totalArea = MAX_REAL; + totalVolume = MAX_REAL; + return false; // No need to add anymore + } + totalArea += area; + totalVolume += volume; + } + } + } + + return true; +} + +struct CloneFrame +{ + BSP::Node* node; + const BSP::Node* original; +}; + +void +BSP::clone(Node* root, const Node* originalRoot) +{ + physx::Array<CloneFrame> stack; + + CloneFrame localFrame; + localFrame.node = root; + localFrame.original = originalRoot; + + for (;;) + { + switch (localFrame.original->getType()) + { + case Node::Leaf: + localFrame.node->setLeafData(*localFrame.original->getLeafData()); + break; + case Node::Branch: + localFrame.node->setBranchData(*localFrame.original->getBranchData()); + break; + } + + const Node* originalChild; + Node* child; + + // Push child 1 + originalChild = localFrame.original->getChild(1); + if (originalChild != NULL) + { + child = m_memCache->m_nodePool.borrow(); + localFrame.node->setChild(1, child); + CloneFrame& callFrame = stack.insert(); + callFrame.node = child; + callFrame.original = originalChild; + } + + // Process child 0 + originalChild = localFrame.original->getChild(0); + if (originalChild != NULL) + { + child = m_memCache->m_nodePool.borrow(); + localFrame.node->setChild(0, child); + localFrame.node = child; + localFrame.original = originalChild; + } + else + { + if (stack.size() == 0) + { + break; + } + localFrame = stack.popBack(); + } + } +} + +struct BuildTreeFrame +{ + BSP::Node* node; + uint32_t surfaceStackReadStart; + uint32_t surfaceStackReadStop; + uint32_t inputSurfaceStackSize; +}; + +bool +BSP::buildTree(Node* node, physx::Array<Surface>& surfaceStack, uint32_t stackReadStart, uint32_t stackReadStop, + const BuildConstants& buildConstants, QuantityProgressListener* quantityListener, volatile bool* cancel) +{ + physx::Array<BuildTreeFrame> stack; + stack.reserve(surfaceStack.size()); // To avoid reallocations + + BuildTreeFrame localFrame; + localFrame.node = node; + localFrame.surfaceStackReadStart = stackReadStart; + localFrame.surfaceStackReadStop = stackReadStop; + localFrame.inputSurfaceStackSize = surfaceStack.size(); + + for (;;) + { + if (cancel && *cancel) + { + return false; + } + + localFrame.surfaceStackReadStop = removeRedundantSurfacesFromStack(surfaceStack, localFrame.surfaceStackReadStart, localFrame.surfaceStackReadStop, localFrame.node); + if (localFrame.surfaceStackReadStop == localFrame.surfaceStackReadStart) + { + assignLeafSide(localFrame.node, quantityListener); + if (stack.size() == 0) + { + break; + } + localFrame = stack.popBack(); + surfaceStack.resize(localFrame.inputSurfaceStackSize); + } + else + { + uint32_t childReadStart[2]; + uint32_t childReadStop[2]; + createBranchSurfaceAndSplitStack(childReadStart, childReadStop, localFrame.node, surfaceStack, localFrame.surfaceStackReadStart, localFrame.surfaceStackReadStop, buildConstants); + + Node* child; + + // Push child 1 + child = m_memCache->m_nodePool.borrow(); + child->getLeafData()->side = 1; + localFrame.node->setChild(1, child); + BuildTreeFrame& callFrame = stack.insert(); + callFrame.node = child; + callFrame.surfaceStackReadStart = childReadStart[1]; + callFrame.surfaceStackReadStop = childReadStop[1]; + callFrame.inputSurfaceStackSize = surfaceStack.size(); + + // Process child 0 + child = m_memCache->m_nodePool.borrow(); + child->getLeafData()->side = 0; + localFrame.node->setChild(0, child); + localFrame.node = child; + localFrame.surfaceStackReadStart = childReadStart[0]; + localFrame.surfaceStackReadStop = childReadStop[0]; + localFrame.inputSurfaceStackSize = surfaceStack.size(); + } + } + + return true; +} + + +/* For GSA */ + +GSA::real +BSP::RegionShape::farthest_halfspace(GSA::real plane[4], const GSA::real point[4]) +{ + plane[0] = plane[1] = plane[2] = 0.0f; + plane[3] = 1.0f; + Real greatest_s = -MAX_REAL; + + if (m_leaf && m_planes) + { + for (SurfaceIt it(m_leaf); it.valid(); it.inc()) + { + const Real sign = it.side() ? (Real)1 : -(Real)1; + Plane test = sign * m_planes[it.surface()->planeIndex]; + test[3] -= m_skinWidth; + const Real s = point[0]*test[0] + point[1]*test[1] + point[2]*test[2] + point[3]*test[3]; + if (s > greatest_s) + { + greatest_s = s; + for (int i = 0; i < 4; ++i) + { + plane[i] = (GSA::real)test[i]; + } + } + } + } + + // Return results + return (GSA::real)greatest_s; +} + + +/* BSPMemCache */ + +BSPMemCache::BSPMemCache() : + m_nodePool(10000) +{ +} + +void +BSPMemCache::clearAll() +{ + const int32_t nodesRemaining = m_nodePool.empty(); + + char message[1000]; + if (nodesRemaining != 0) + { + physx::shdfnd::snprintf(message, 1000, "BSPMemCache: %d nodes %sfreed ***", physx::PxAbs(nodesRemaining), nodesRemaining > 0 ? "un" : "over"); + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_INFO, message, __FILE__, __LINE__); + } + + clearTemp(); +} + +void +BSPMemCache::clearTemp() +{ + m_surfaceFlags.reset(); + m_surfaceTestFlags.reset(); + const int32_t linkedVerticesRemaining = m_linkedVertexPool.empty(); + + char message[1000]; + if (linkedVerticesRemaining != 0) + { + physx::shdfnd::snprintf(message, 1000, "BSPMemCache: %d linked vertices %sfreed ***", physx::PxAbs(linkedVerticesRemaining), linkedVerticesRemaining > 0 ? "un" : "over"); + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_INFO, message, __FILE__, __LINE__); + } +} + +void +BSPMemCache::release() +{ + clearAll(); + delete this; +} + + +/* CSG Tools API */ + +IApexBSPMemCache* +createBSPMemCache() +{ + return PX_NEW(BSPMemCache)(); +} + +IApexBSP* +createBSP(IApexBSPMemCache* memCache, const physx::PxMat44& internalTransform) +{ + IApexBSP* bsp = PX_NEW(BSP)(memCache, internalTransform); + + bsp->setTolerances(gDefaultTolerances); + + return bsp; +} + +} +#endif diff --git a/APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp b/APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp new file mode 100644 index 00000000..f8b87cec --- /dev/null +++ b/APEX_1.4/shared/internal/src/authoring/ApexCSGHull.cpp @@ -0,0 +1,1224 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + +#include "ApexUsingNamespace.h" +#include "PxSimpleTypes.h" +#include "PxErrorCallback.h" + +#include "authoring/ApexCSGHull.h" +#include "authoring/ApexCSGSerialization.h" +#include "ApexSharedSerialization.h" +#include <stdio.h> + +#ifndef WITHOUT_APEX_AUTHORING + +#define EPS ((Real)1.0e-6) + +#define SOFT_ASSERT(x) //PX_ASSERT(x) + +using namespace nvidia; + + +/* Local utilities */ + +static int compareEdgeFirstFaceIndices(const void* a, const void* b) +{ + return (int)((const ApexCSG::Hull::Edge*)a)->m_indexF1 - (int)((const ApexCSG::Hull::Edge*)b)->m_indexF1; +} + + +/* Hull */ + +void ApexCSG::Hull::intersect(const ApexCSG::Plane& plane, ApexCSG::Real distanceTol) +{ + if (isEmptySet()) + { + return; + } + + if (isAllSpace()) + { + Plane& newFace = faces.insert(); + newFace = plane; + allSpace = false; + return; + } + + const Dir planeNormal = plane.normal(); + + if (edges.size() == 0) + { + PX_ASSERT(vectors.size() == 0); + PX_ASSERT(faces.size() == 1 || faces.size() == 2); + + bool addFace = true; + const uint32_t newFaceIndex = faces.size(); + + // Nothing but a plane or two. + for (uint32_t i = 0; i < newFaceIndex; ++i) + { + Plane& faceI = faces[i]; + const Dir normalI = faceI.normal(); + Dir edgeDir = normalI ^ planeNormal; + const Real e2 = edgeDir | edgeDir; // = 1-cosAngle*cosAngle + const Real cosAngle = normalI | planeNormal; + if (e2 < EPS * EPS) + { + if (cosAngle > 0) + { + // Parallel case + if (plane.d() <= faceI.d()) + { + SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8)); + return; // Plane is outside of an existing plane + } + faceI = plane; // Plane is inside an existing plane - replace + addFace = false; + } + else + { + // Antiparallel case + if (-plane.d() < faceI.d() + distanceTol) + { + setToEmptySet(); // Halfspaces are mutually exclusive + return; + } + } + } + else + { + // Intersecting case - add an edge + const Real recipE2 = (Real)1 / e2; + edgeDir *= physx::PxSqrt(recipE2); + const Pos orig = Pos((Real)0) + (recipE2 * (faceI.d() * cosAngle - plane.d())) * planeNormal + (recipE2 * (plane.d() * cosAngle - faceI.d())) * normalI; + if (vectors.size() == 0) + { + vectors.resize(newFaceIndex + 1); + vectors[newFaceIndex] = edgeDir; + } + vectors[i] = orig; + Edge& newEdge = edges.insert(); + newEdge.m_indexV0 = i; + newEdge.m_indexV1 = newFaceIndex; // We will have as many edges as old faces. This will be the edge direction vector index. + if (i == 0) + { + newEdge.m_indexF1 = i; + newEdge.m_indexF2 = newFaceIndex; + } + else + { + // Keep the orientation and edge directions the same + newEdge.m_indexF1 = newFaceIndex; + newEdge.m_indexF2 = i; + } + } + } + + if (addFace) + { + Plane& newFace = faces.insert(); + newFace = plane; + } + + SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8)); + return; + } + + // Hull has edges. If they are lines, and parallel to the plane, this will continue to be the case. + if (vertexCount == 0) + { + PX_ASSERT(vectors.size() >= 2); + const Dir edgeDir = getDir(edges[0]); // All line edges will be in the same direction + PX_ASSERT(vectors[vectors.size() - 1][3] == (Real)0); // The last vector should be a direction (the edge direction); + const Real den = edgeDir | planeNormal; + if (physx::PxAbs(den) <= EPS) + { + // Lines are parallel to plane + // This is essentially a 2-d algorithm, edges->vertices, faces->edges + bool negClassEmpty = true; + bool posClassEmpty = true; + int8_t* edgeClass = (int8_t*)PxAlloca((edges.size() + 2) * sizeof(int8_t)); // Adding 2 for "edges at infinity" + struct FaceEdge + { + int32_t e1, e2; + }; + FaceEdge* faceEdges = (FaceEdge*)PxAlloca(sizeof(FaceEdge) * faces.size() + 1);// Adding 1 for possible new face + uint32_t* faceIndices = (uint32_t*)PxAlloca(sizeof(uint32_t) * (faces.size() + 1)); // Adding 1 for possible new face + uint32_t* faceRemap = (uint32_t*)PxAlloca(sizeof(uint32_t) * (faces.size() + 1)); // Adding 1 for possible new face + for (uint32_t i = 0; i < faces.size(); ++i) + { + FaceEdge& faceEdge = faceEdges[i]; + faceEdge.e2 = faceEdge.e1 = 0x80000000; // Indicates unset + faceIndices[i] = i; + faceRemap[i] = i; + } + + // Classify the actual edges + for (uint32_t i = 0; i < edges.size(); ++i) + { + // The inequalities are set up so that when distanceTol = 0, class 0 is empty and classes -1 and 1 are disjoint + Edge& edge = edges[i]; + PX_ASSERT(faceEdges[edge.m_indexF1].e2 == INT32_MIN); + faceEdges[edge.m_indexF1].e2 = (int32_t)i; + PX_ASSERT(faceEdges[edge.m_indexF2].e1 == INT32_MIN); + faceEdges[edge.m_indexF2].e1 = (int32_t)i; + const Real dist = plane.distance(getV0(edge)); + if (dist < -distanceTol) + { + edgeClass[i] = -1; + negClassEmpty = false; + } + else if (dist < distanceTol) + { + edgeClass[i] = 0; + } + else + { + edgeClass[i] = 1; + posClassEmpty = false; + } + } + + // Also classify fictitious "edges at infinity" for a complete description + uint32_t halfInfinitePlaneCount = 0; + for (uint32_t i = 0; i < faces.size(); ++i) + { + Plane& face = faces[i]; + FaceEdge& faceEdge = faceEdges[i]; + PX_ASSERT(faceEdge.e2 != INT32_MIN || faceEdge.e1 != INT32_MIN); + if (faceEdge.e2 == INT32_MIN || faceEdge.e1 == INT32_MIN) + { + PX_ASSERT(halfInfinitePlaneCount < 2); + if (halfInfinitePlaneCount < 2) + { + const int32_t classIndex = (int32_t)(edges.size() + halfInfinitePlaneCount++); + + Real cosTheta = (edgeDir ^ face.normal()) | planeNormal; + uint32_t realEdgeIndex; + if (faceEdge.e2 == INT32_MIN) + { + faceEdge.e2 = -classIndex; // Negating => fictitious edge. + realEdgeIndex = (uint32_t)faceEdge.e1; + } + else + { + faceEdge.e1 = -classIndex; // Negating => fictitious edge. + cosTheta *= -1; + realEdgeIndex = (uint32_t)faceEdge.e2; + } + + if (cosTheta < -EPS) + { + edgeClass[classIndex] = -1; + negClassEmpty = false; + } + else if (cosTheta < EPS) + { + const Real d = plane.distance(getV0(edges[realEdgeIndex])); + if (d < -distanceTol) + { + edgeClass[classIndex] = -1; + negClassEmpty = false; + } + else if (d < distanceTol) + { + edgeClass[classIndex] = 0; + } + else + { + edgeClass[classIndex] = 1; + posClassEmpty = false; + } + } + else + { + edgeClass[classIndex] = 1; + posClassEmpty = false; + } + } + } + } + + if (negClassEmpty) + { + setToEmptySet(); + return; + } + if (posClassEmpty) + { + SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8)); + return; + } + + vectors.popBack(); // We will keep the line origins contiguous, and place the edge direction at the end of the array later + + bool addFace = false; + FaceEdge newFaceEdge = { (int32_t)0x80000000, (int32_t)0x80000000 }; + const uint32_t newFaceIndex = faces.size(); + + for (uint32_t i = faces.size(); i--;) + { + int32_t clippedEdgeIndex = (int32_t)edges.size(); + Plane& face = faces[i]; + FaceEdge& faceEdge = faceEdges[i]; + PX_ASSERT(faceEdge.e2 != INT32_MIN && faceEdge.e1 != INT32_MIN); + uint32_t newEdgeIndex = 0xFFFFFFFF; + int32_t orig1Index = faceEdge.e2; + int32_t orig2Index = faceEdge.e1; + switch (edgeClass[physx::PxAbs(faceEdge.e2)]) + { + case 1: + if (edgeClass[physx::PxAbs(faceEdge.e1)] == -1) + { + // Clip face + PX_ASSERT(((face.normal() ^ planeNormal) | edgeDir) > 0); + PX_ASSERT(newFaceEdge.e1 == INT32_MIN); + newFaceEdge.e1 = clippedEdgeIndex; + newEdgeIndex = edges.size(); + Edge& newEdge = edges.insert(); + newEdge.m_indexF1 = i; + newEdge.m_indexF2 = newFaceIndex; + faceEdge.e2 = clippedEdgeIndex; + addFace = true; + } + else + { + // Eliminate face + faces.replaceWithLast(i); + faceEdges[i] = faceEdges[faces.size()]; + faceIndices[i] = faceIndices[faces.size()]; + faceRemap[faceIndices[faces.size()]] = i; +#ifdef _DEBUG // Should not be necessary, but helps with debugging + faceIndices[faces.size()] = 0xFFFFFFFF; + faceRemap[i] = 0xFFFFFFFF; +#endif + } + break; + case 0: + if (edgeClass[physx::PxAbs(faceEdge.e1)] == -1) + { + // Keep this face, and use this edge on the new face + clippedEdgeIndex = faceEdge.e2; + PX_ASSERT(newFaceEdge.e1 == INT32_MIN); + newFaceEdge.e1 = faceEdge.e2; + edges[(uint32_t)faceEdge.e2].m_indexF2 = newFaceIndex; + addFace = true; + } + else + { + // Eliminate face + faces.replaceWithLast(i); + faceEdges[i] = faceEdges[faces.size()]; + faceIndices[i] = faceIndices[faces.size()]; + faceRemap[faceIndices[faces.size()]] = i; +#ifdef _DEBUG // Should not be necessary, but helps with debugging + faceIndices[faces.size()] = 0xFFFFFFFF; + faceRemap[i] = 0xFFFFFFFF; +#endif + } + break; + case -1: + switch (edgeClass[physx::PxAbs(faceEdge.e1)]) + { + case 1: // Clip face + { + PX_ASSERT(((planeNormal ^ face.normal()) | edgeDir) > 0); + PX_ASSERT(newFaceEdge.e2 == INT32_MIN); + newFaceEdge.e2 = clippedEdgeIndex; + newEdgeIndex = edges.size(); + Edge& newEdge = edges.insert(); + newEdge.m_indexF1 = newFaceIndex; + newEdge.m_indexF2 = i; + faceEdge.e1 = clippedEdgeIndex; + addFace = true; + } + break; + case 0: // Keep this face, and use this edge on the new face + clippedEdgeIndex = faceEdge.e1; + PX_ASSERT(newFaceEdge.e2 == INT32_MIN); + newFaceEdge.e2 = faceEdge.e1; + edges[(uint32_t)faceEdge.e1].m_indexF1 = newFaceIndex; + addFace = true; + break; + } + } + + if (newEdgeIndex < edges.size()) + { + Edge& newEdge = edges[newEdgeIndex]; + newEdge.m_indexV0 = vectors.size(); + newEdge.m_indexV1 = 0xFFFFFFFF; // Will be replaced below + Pos& newOrig = *(Pos*)&vectors.insert(); + if (orig1Index >= 0) + { + const Edge& e1 = edges[(uint32_t)orig1Index]; + const Pos& o1 = getV0(e1); + const Real d1 = plane.distance(o1); + if (orig2Index >= 0) + { + const Edge& e2 = edges[(uint32_t)orig2Index]; + const Pos& o2 = getV0(e2); + const Real d2 = plane.distance(o2); + newOrig = o1 + (d1 / (d1 - d2)) * (o2 - o1); + + } + else + { + const Dir tangent = face.normal() ^ edgeDir; + const Real cosTheta = tangent | planeNormal; + PX_ASSERT(physx::PxAbs(cosTheta) > EPS); + newOrig = o1 - tangent * (d1 / cosTheta); + } + } + else + { + const Dir tangent = edgeDir ^ face.normal(); + const Real cosTheta = tangent | planeNormal; + PX_ASSERT(physx::PxAbs(cosTheta) > EPS); + if (orig2Index >= 0) + { + const Edge& e2 = edges[(uint32_t)orig2Index]; + const Pos& o2 = getV0(e2); + const Real d2 = plane.distance(o2); + newOrig = o2 - tangent * (d2 / cosTheta); + } + else + { + PX_ALWAYS_ASSERT(); // Should not have any full planes + } + } + } + } + + if (addFace) + { + faceRemap[newFaceIndex] = faces.size(); + faceIndices[faces.size()] = newFaceIndex; + faceEdges[faces.size()] = newFaceEdge; + Plane& newFace = faces.insert(); + newFace = plane; + } + + if (faces.size() == 0) + { + setToEmptySet(); + SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8)); + return; + } + + // Replacing edge direction, and re-indexing + const uint32_t edgeDirIndex = vectors.size(); + vectors.pushBack(edgeDir); + for (uint32_t i = 0; i < edges.size(); ++i) + { + edges[i].m_indexV1 = edgeDirIndex; + } + + // Eliminate unused edges (and remap face indices in remaining edges) + uint8_t* edgeMarks = (uint8_t*)PxAlloca(sizeof(uint8_t) * edges.size()); + memset(edgeMarks, 0, sizeof(uint8_t)*edges.size()); + + for (uint32_t i = 0; i < faces.size(); ++i) + { + FaceEdge& faceEdge = faceEdges[i]; + if (faceEdge.e2 >= 0) + { + edgeMarks[faceEdge.e2] = 1; + } + if (faceEdge.e1 >= 0) + { + edgeMarks[faceEdge.e1] = 1; + } + } + + for (uint32_t i = edges.size(); i--;) + { + if (edgeMarks[i] == 0) + { + edges.replaceWithLast(i); + } + else + { + Edge& edge = edges[i]; + PX_ASSERT(faceRemap[edge.m_indexF1] != 0xFFFFFFFF); + if (faceRemap[edge.m_indexF1] != 0xFFFFFFFF) + { + edge.m_indexF1 = faceRemap[edge.m_indexF1]; + } + PX_ASSERT(faceRemap[edge.m_indexF2] != 0xFFFFFFFF); + if (faceRemap[edge.m_indexF2] != 0xFFFFFFFF) + { + edge.m_indexF2 = faceRemap[edge.m_indexF2]; + } + } + } + + // Eliminate unused vectors (and remap vertex indices in edges) + int32_t* vectorOffsets = (int32_t*)PxAlloca(sizeof(int32_t) * vectors.size()); + memset(vectorOffsets, 0, sizeof(int32_t)*vectors.size()); + + for (uint32_t i = 0; i < edges.size(); ++i) + { + Edge& edge = edges[i]; + ++vectorOffsets[edge.m_indexV0]; + ++vectorOffsets[edge.m_indexV1]; + } + + uint32_t vectorCount = 0; + for (uint32_t i = 0; i < vectors.size(); ++i) + { + const bool copy = vectorOffsets[i] > 0; + vectorOffsets[i] = (int32_t)vectorCount - (int32_t)i; + if (copy) + { + vectors[vectorCount++] = vectors[i]; + } + } + vectors.resize(vectorCount); + + for (uint32_t i = 0; i < edges.size(); ++i) + { + Edge& edge = edges[i]; + edge.m_indexV0 += vectorOffsets[edge.m_indexV0]; + edge.m_indexV1 += vectorOffsets[edge.m_indexV1]; + } + + PX_ASSERT(vectors.size() == edges.size() + 1); + + SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8)); + + return; + } + } + + // The hull will have vertices + + // Compare vertex positions to input plane + bool negClassEmpty = true; + bool posClassEmpty = true; + int8_t* vertexClass = (int8_t*)PxAlloca(vertexCount * sizeof(int8_t)); + for (uint32_t i = 0; i < vertexCount; ++i) + { + // The inequalities are set up so that when distanceTol = 0, class 0 is empty and classes -1 and 1 are disjoint + const Real dist = plane.distance(getVertex(i)); + if (dist < -distanceTol) + { + vertexClass[i] = -1; + negClassEmpty = false; + } + else if (dist < distanceTol) + { + vertexClass[i] = 0; + } + else + { + vertexClass[i] = 1; + posClassEmpty = false; + } + } + + if (vertexCount != 0) + { + // Also test "points at infinity" for better culling + for (uint32_t i = vertexCount; i < vectors.size(); ++i) + { + if (vectors[i][3] < (Real)0.5) + { + const Real cosTheta = plane | vectors[i]; + if (cosTheta < -EPS) + { + negClassEmpty = false; + } + else if (cosTheta >= EPS) + { + posClassEmpty = false; + } + } + } + + if (negClassEmpty) + { + setToEmptySet(); + return; + } + if (posClassEmpty) + { + SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8)); + return; + } + } + + // Intersect new plane against edges. + const uint32_t newFaceIndex = faces.size(); + + // Potential number of new edges is the number of faces + Edge* newEdges = (Edge*)PxAlloca((newFaceIndex + 1) * sizeof(Edge)); + memset(newEdges, 0xFF, (newFaceIndex + 1)*sizeof(Edge)); + + bool addFace = false; + + for (uint32_t i = edges.size(); i--;) + { + Edge& edge = edges[i]; + uint32_t clippedVertexIndex = vectors.size(); + bool createNewEdge = false; + switch (getType(edge)) + { + case EdgeType::LineSegment: + switch (vertexClass[edge.m_indexV0]) + { + case -1: + switch (vertexClass[edge.m_indexV1]) + { + case 1: + { + // Clip this edge. + const Pos& v0 = getV0(edge); + const Real d0 = plane.distance(v0); + const Pos& v1 = getV1(edge); + const Real d1 = plane.distance(v1); + const Pos clipVertex = v0 + (d0 / (d0 - d1)) * (v1 - v0); + edge.m_indexV1 = clippedVertexIndex; + vectors.pushBack(clipVertex); + createNewEdge = true; + break; + } + case 0: + createNewEdge = true; + clippedVertexIndex = edge.m_indexV1; + break; + } + break; + case 0: + if (vertexClass[edge.m_indexV1] == -1) + { + createNewEdge = true; + clippedVertexIndex = edge.m_indexV0; + } + else + { + // Eliminate this edge + edges.replaceWithLast(i); + } + break; + case 1: + if (vertexClass[edge.m_indexV1] == -1) + { + // Clip this edge. + const Pos& v0 = getV0(edge); + const Real d0 = plane.distance(v0); + const Pos& v1 = getV1(edge); + const Real d1 = plane.distance(v1); + const Pos clipVertex = v1 + (d1 / (d1 - d0)) * (v0 - v1); + edge.m_indexV0 = clippedVertexIndex; + vectors.pushBack(clipVertex); + createNewEdge = true; + } + else + { + // Eliminate this edge + edges.replaceWithLast(i); + } + break; + } + break; + case EdgeType::Ray: + { + const Dir& edgeDir = getDir(edge); + const Real den = edgeDir | planeNormal; + switch (vertexClass[edge.m_indexV0]) + { + case -1: + if (den > EPS) + { + // Clip this edge. + const Pos& v0 = getV0(edge); + const Real d0 = plane.distance(v0); + const Pos clipVertex = v0 - edgeDir * (d0 / den); + edge.m_indexV1 = clippedVertexIndex; + vectors.pushBack(clipVertex); + createNewEdge = true; + } + break; + case 0: + if (den < -EPS) + { + createNewEdge = true; + clippedVertexIndex = edge.m_indexV0; + } + else + { + // Eliminate this edge + edges.replaceWithLast(i); + } + break; + case 1: + if (den < -EPS) + { + // Clip this edge. + const Pos& v0 = getV0(edge); + const Real d0 = plane.distance(v0); + const Pos clipVertex = v0 - edgeDir * (d0 / den); + edge.m_indexV0 = clippedVertexIndex; + vectors.pushBack(clipVertex); + createNewEdge = true; + } + else + { + // Eliminate this edge + edges.replaceWithLast(i); + } + break; + } + } + break; + case EdgeType::Line: + { + const Pos& point = getV0(edge); + const Dir& edgeDir = getDir(edge); + const Real h = plane.distance(point); + const Real den = edgeDir | planeNormal; + PX_ASSERT(physx::PxAbs(den) >= EPS); + // Clip this edge + clippedVertexIndex = edge.m_indexV0; // Re-use this vector (will become a vertex) + vectors[clippedVertexIndex] = point - edgeDir * (h / den); + if (den > 0) // Make sure the ray points in the correct direction + { + vectors[edge.m_indexV1] *= -(Real)1; + nvidia::swap(edge.m_indexF1, edge.m_indexF2); + } + createNewEdge = true; + } + break; + } + + if (createNewEdge) + { + if (newEdges[edge.m_indexF1].m_indexV0 == 0xFFFFFFFF) + { + newEdges[edge.m_indexF1].m_indexV0 = clippedVertexIndex; + PX_ASSERT(newEdges[edge.m_indexF1].m_indexV1 == 0xFFFFFFFF); + newEdges[edge.m_indexF1].m_indexV1 = vectors.size(); + Dir newDir = planeNormal ^ faces[edge.m_indexF1].normal(); + newDir.normalize(); + if ((newDir | faces[edge.m_indexF2].normal()) > 0) + { + newDir *= -(Real)1; + newEdges[edge.m_indexF1].m_indexF1 = edge.m_indexF1; + newEdges[edge.m_indexF1].m_indexF2 = newFaceIndex; + } + else + { + newEdges[edge.m_indexF1].m_indexF1 = newFaceIndex; + newEdges[edge.m_indexF1].m_indexF2 = edge.m_indexF1; + } + vectors.pushBack(newDir); + addFace = true; + } + else + { + PX_ASSERT(newEdges[edge.m_indexF1].m_indexV1 != 0xFFFFFFFF && vectors[newEdges[edge.m_indexF1].m_indexV1][3] == (Real)0); + newEdges[edge.m_indexF1].m_indexV1 = clippedVertexIndex; + } + if (newEdges[edge.m_indexF2].m_indexV0 == 0xFFFFFFFF) + { + newEdges[edge.m_indexF2].m_indexV0 = clippedVertexIndex; + PX_ASSERT(newEdges[edge.m_indexF2].m_indexV1 == 0xFFFFFFFF); + newEdges[edge.m_indexF2].m_indexV1 = vectors.size(); + Dir newDir = faces[edge.m_indexF2].normal() ^ planeNormal; + newDir.normalize(); + if ((newDir | faces[edge.m_indexF1].normal()) > 0) + { + newDir *= -(Real)1; + newEdges[edge.m_indexF2].m_indexF1 = newFaceIndex; + newEdges[edge.m_indexF2].m_indexF2 = edge.m_indexF2; + } + else + { + newEdges[edge.m_indexF2].m_indexF1 = edge.m_indexF2; + newEdges[edge.m_indexF2].m_indexF2 = newFaceIndex; + } + vectors.pushBack(newDir); + addFace = true; + } + else + { + PX_ASSERT(newEdges[edge.m_indexF2].m_indexV1 != 0xFFFFFFFF && vectors[newEdges[edge.m_indexF2].m_indexV1][3] == (Real)0); + newEdges[edge.m_indexF2].m_indexV1 = clippedVertexIndex; + } + } + } + + if (addFace) + { + Plane& newFace = faces.insert(); + newFace = plane; + } + + for (uint32_t i = 0; i < faces.size(); ++i) + { + Edge& newEdge = newEdges[i]; + if (newEdge.m_indexV0 != 0xFFFFFFFF) + { + if (newEdge.m_indexV0 != newEdge.m_indexV1) // Skip split vertices + { + edges.pushBack(newEdge); + } + } + } + + // Now eliminate unused faces and vectors + int32_t* vectorOffsets = (int32_t*)PxAlloca(sizeof(int32_t) * vectors.size()); + int32_t* faceOffsets = (int32_t*)PxAlloca(sizeof(int32_t) * faces.size()); + memset(vectorOffsets, 0, sizeof(int32_t)*vectors.size()); + memset(faceOffsets, 0, sizeof(int32_t)*faces.size()); + + for (uint32_t i = 0; i < edges.size(); ++i) + { + Edge& edge = edges[i]; + ++vectorOffsets[edge.m_indexV0]; + ++vectorOffsets[edge.m_indexV1]; + ++faceOffsets[edge.m_indexF1]; + ++faceOffsets[edge.m_indexF2]; + } + + uint32_t vectorCount = 0; + for (uint32_t i = 0; i < vectors.size(); ++i) + { + const bool copy = vectorOffsets[i] > 0; + vectorOffsets[i] = (int32_t)vectorCount - (int32_t)i; + if (copy) + { + vectors[vectorCount++] = vectors[i]; + } + } + vectors.resize(vectorCount); + + uint32_t faceCount = 0; + for (uint32_t i = 0; i < faces.size(); ++i) + { + const bool copy = faceOffsets[i] > 0; + faceOffsets[i] = (int32_t)faceCount - (int32_t)i; + if (copy) + { + faces[faceCount++] = faces[i]; + } + } + faces.resize(faceCount); + + PX_ASSERT(faceCount != 1); // Single faces would have been handled above. + + for (uint32_t i = 0; i < edges.size(); ++i) + { + Edge& edge = edges[i]; + edge.m_indexV0 += vectorOffsets[edge.m_indexV0]; + edge.m_indexV1 += vectorOffsets[edge.m_indexV1]; + edge.m_indexF1 += faceOffsets[edge.m_indexF1]; + edge.m_indexF2 += faceOffsets[edge.m_indexF2]; + } + + if (faces.size() == 0) + { + setToEmptySet(); + } + + // Now sort vectors, vertices before directions. We will need to keep track of the mapping to re-index the edges. + uint32_t* vectorIndices = (uint32_t*)PxAlloca(sizeof(uint32_t) * vectors.size()); + uint32_t* vectorRemap = (uint32_t*)PxAlloca(sizeof(uint32_t) * vectors.size()); + for (uint32_t i = 0; i < vectors.size(); ++i) + { + vectorIndices[i] = vectorRemap[i] = i; + } + int32_t firstDirIndex = -1; + int32_t lastPosIndex = (int32_t)vectors.size(); + for (;;) + { + // Correct this + while (++firstDirIndex < (int32_t)vectors.size()) + { + if (vectors[(uint32_t)firstDirIndex][3] < (Real)0.5) // Should be 0 or 1, but in case some f.p. inexactness creeps in... + { + break; + } + } + while (--lastPosIndex >= 0) + { + if (vectors[(uint32_t)lastPosIndex][3] >= (Real)0.5) // Should be 0 or 1, but in case some f.p. inexactness creeps in... + { + break; + } + } + if (firstDirIndex > lastPosIndex) + { + break; // All's good + } + // Fix this - swap vectors, and update map + nvidia::swap(vectors[(uint32_t)firstDirIndex], vectors[(uint32_t)lastPosIndex]); + nvidia::swap(vectorIndices[(uint32_t)firstDirIndex], vectorIndices[(uint32_t)lastPosIndex]); + vectorRemap[vectorIndices[(uint32_t)firstDirIndex]] = (uint32_t)firstDirIndex; + vectorRemap[vectorIndices[(uint32_t)lastPosIndex]] = (uint32_t)lastPosIndex; + } + vertexCount = (uint32_t)firstDirIndex; // Correct vertex count + + // Correct edge indices + for (uint32_t i = 0; i < edges.size(); ++i) + { + Edge& edge = edges[i]; + edge.m_indexV0 = vectorRemap[edge.m_indexV0]; + edge.m_indexV1 = vectorRemap[edge.m_indexV1]; + } + + SOFT_ASSERT(testConsistency(100 * distanceTol, (Real)1.0e-8)); +} + +ApexCSG::Real ApexCSG::Hull::calculateVolume() const +{ + if (isEmptySet()) + { + return (Real)0; + } + + if (edges.size() == 0 || vectors.size() > vertexCount) + { + return MAX_REAL; + } + + // Volume is finite + PX_ASSERT(vertexCount != 0); + + // Work relative to an internal point, for better accuracy + Vec4Real centroid((Real)0); + for (uint32_t i = 0; i < vertexCount; ++i) + { + centroid += vectors[i]; + } + centroid /= (Real)vertexCount; + + // Create a doubled edge list + const uint32_t edgeCount = edges.size(); + const uint32_t edgeListSize = 2 * edgeCount; + Edge* edgeList = (Edge*)PxAlloca(edgeListSize * sizeof(Edge)); + for (uint32_t i = 0; i < edgeCount; ++i) + { + edgeList[i] = edges[i]; + edgeList[i + edgeCount] = edges[i]; + nvidia::swap(edgeList[i + edgeCount].m_indexF1, edgeList[i + edgeCount].m_indexF2); + nvidia::swap(edgeList[i + edgeCount].m_indexV0, edgeList[i + edgeCount].m_indexV1); + } + + // Sort edgeList by first face index + qsort(edgeList, edgeListSize, sizeof(Edge), compareEdgeFirstFaceIndices); + + // A scratchpad for edge vertex locations + uint32_t* vertex0Locations = (uint32_t*)PxAlloca(vertexCount * sizeof(uint32_t)); + memset(vertex0Locations, 0xFF, vertexCount * sizeof(uint32_t)); + + Real volume = 0; + + // Edges are grouped by first face index - each group describes the polygonal face. + uint32_t groupStop = 0; + do + { + const uint32_t groupStart = groupStop; + const uint32_t faceIndex = edgeList[groupStart].m_indexF1; + while (++groupStop < edgeListSize && edgeList[groupStop].m_indexF1 == faceIndex) {} + // Evaluate group + if (groupStop - groupStart >= 3) + { + // Mark first vertex locations within group + for (uint32_t i = groupStart; i < groupStop; ++i) + { + Edge& edge = edgeList[i]; + SOFT_ASSERT(vertex0Locations[edge.m_indexV0] == 0xFFFFFFFF); + vertex0Locations[edge.m_indexV0] = i; + } + const Dir d0 = vectors[edgeList[groupStart].m_indexV0] - centroid; + uint32_t i1 = edgeList[groupStart].m_indexV1; + Dir d1 = vectors[i1] - centroid; + const uint32_t tetCount = groupStop - groupStart - 2; + for (uint32_t i = 0; i < tetCount; ++i) + { + const uint32_t nextEdgeIndex = vertex0Locations[i1]; + SOFT_ASSERT(nextEdgeIndex != 0xFFFFFFFF); + if (nextEdgeIndex == 0xFFFFFFFF) + { + break; + } + const uint32_t i2 = edgeList[nextEdgeIndex].m_indexV1; + const Dir d2 = vectors[i2] - centroid; + const Real tripleProduct = d0 | (d1 ^ d2); + SOFT_ASSERT(tripleProduct > -EPS_REAL); + volume += tripleProduct; // 6 times volume + i1 = i2; + d1 = d2; + } + // Unmark first vertex locations + for (uint32_t i = groupStart; i < groupStop; ++i) + { + Edge& edge = edgeList[i]; + vertex0Locations[edge.m_indexV0] = 0xFFFFFFFF; + } + } + } + while (groupStop < edgeListSize); + + volume *= (Real)0.166666666666666667; + + return volume; +} + +void ApexCSG::Hull::serialize(physx::PxFileBuf& stream) const +{ + apex::serialize(stream, faces); + apex::serialize(stream, edges); + apex::serialize(stream, vectors); + stream << vertexCount; + stream << allSpace; + stream << emptySet; +} + +void ApexCSG::Hull::deserialize(physx::PxFileBuf& stream, uint32_t version) +{ + setToAllSpace(); + + apex::deserialize(stream, version, faces); + apex::deserialize(stream, version, edges); + apex::deserialize(stream, version, vectors); + stream >> vertexCount; + stream >> allSpace; + stream >> emptySet; +} + +bool ApexCSG::Hull::testConsistency(ApexCSG::Real distanceTol, ApexCSG::Real angleTol) const +{ + bool halfInfiniteEdges = false; + for (uint32_t j = 0; j < edges.size(); ++j) + { + if (edges[j].m_indexV0 < vertexCount && edges[j].m_indexV1 >= vertexCount) + { + halfInfiniteEdges = true; + break; + } + } + + for (uint32_t i = 0; i < vertexCount; ++i) + { + uint32_t count = 0; + for (uint32_t j = 0; j < edges.size(); ++j) + { + if (edges[j].m_indexV0 == i) + { + ++count; + } + if (edges[j].m_indexV1 == i) + { + ++count; + } + if (edges[j].m_indexV1 < vertexCount && edges[j].m_indexV0 == edges[j].m_indexV1) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: 0-length edge found.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + if (count < 3) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: vertex connected to fewer than 3 edges.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + + if (edges.size() == 0) + { + if (faces.size() >= 3) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: face count 3 or greater, but with no edges.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + if (vertexCount > 0) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: hull has vertices but no edges.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + return true; + } + + bool halfInfiniteFaces = false; + + for (uint32_t i = 0; i < faces.size(); ++i) + { + uint32_t count = 0; + for (uint32_t j = 0; j < edges.size(); ++j) + { + if (edges[j].m_indexF1 == i) + { + ++count; + } + if (edges[j].m_indexF2 == i) + { + ++count; + } + if (edges[j].m_indexF1 == edges[j].m_indexF2) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge connecting face to itself.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + if (count == 0) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: face has no edges", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + if (count == 1) + { + halfInfiniteFaces = true; + } + } + + for (uint32_t i = 0; i < edges.size(); ++i) + { + Real d; + const Edge& edge = edges[i]; + if (edge.m_indexV1 >= vertexCount) + { + // Edge is a line or ray + d = faces[edge.m_indexF1].distance(getV0(edge)); + if (physx::PxAbs(d) > distanceTol) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge line/ray origin not on face.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + d = faces[edge.m_indexF1].normal() | getDir(edge); + if (physx::PxAbs(d) > angleTol) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge line/ray direction not perpendicular to face.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + d = faces[edge.m_indexF2].distance(getV0(edge)); + if (physx::PxAbs(d) > distanceTol) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge line/ray origin not on face.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + d = faces[edge.m_indexF2].normal() | getDir(edge); + if (physx::PxAbs(d) > angleTol) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge line/ray direction not perpendicular to face.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + else + { + // Edge is a line segment + if (edge.m_indexV0 >= vertexCount) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge has a line/ray origin but a real destination point.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + d = faces[edge.m_indexF1].distance(getV0(edge)); + if (physx::PxAbs(d) > distanceTol) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge vertex 0 not on face.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + d = faces[edge.m_indexF1].distance(getV1(edge)); + if (physx::PxAbs(d) > distanceTol) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge vertex 1 not on face.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + d = faces[edge.m_indexF2].distance(getV0(edge)); + if (physx::PxAbs(d) > distanceTol) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge vertex 0 not on face.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + d = faces[edge.m_indexF2].distance(getV1(edge)); + if (physx::PxAbs(d) > distanceTol) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: edge vertex 1 not on face.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + } + + if (vertexCount == 0) + { + if (!halfInfiniteFaces) + { + if (faces.size() != edges.size()) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: hull has edges but no vertices, with no half-infinite faces. Face count should equal edge count but does not.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + else + { + if (faces.size() != edges.size() + 1) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: hull has edges but no vertices, with half-infinite faces. Face count should be one more than edge count but is not.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + const Edge& edge0 = edges[0]; + for (uint32_t i = 1; i < edges.size(); ++i) + { + const Edge& edge = edges[i]; + Dir e = getDir(edge0) ^ getDir(edge); + if ((e | e) > (Real)EPS * EPS) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: prism edges not all facing the same direction.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + } + else + { + const uint32_t c = faces.size() - edges.size() + vertexCount; + if (!halfInfiniteEdges) + { + if (c != 2) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: bounded hull with faces, vertices and edges does not obey Euler's formula.", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + else + { + if (c != 1) + { + GetApexSDK()->getErrorCallback()->reportError(physx::PxErrorCode::eDEBUG_WARNING, "Hull::testConsistency: unbounded hull with faces, vertices and edges does not obey Euler's formula (with a vertex at infinity).", __FILE__, __LINE__); + PX_ALWAYS_ASSERT(); + return false; + } + } + } + + return true; +} +#endif
\ No newline at end of file diff --git a/APEX_1.4/shared/internal/src/authoring/ApexCSGMeshCleaning.cpp b/APEX_1.4/shared/internal/src/authoring/ApexCSGMeshCleaning.cpp new file mode 100644 index 00000000..1cd80993 --- /dev/null +++ b/APEX_1.4/shared/internal/src/authoring/ApexCSGMeshCleaning.cpp @@ -0,0 +1,559 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + +#include "ApexUsingNamespace.h" +#include "authoring/ApexCSGDefs.h" +#include "PxErrorCallback.h" + +#ifndef WITHOUT_APEX_AUTHORING + +#pragma warning(disable:4505) + +namespace ApexCSG +{ + +PX_INLINE bool +pointOnRay2D(const Vec2Real& point, const Vec2Real& lineOrig, const Vec2Real& lineDisp, Real distanceTol2) +{ + const Vec2Real disp = point - lineOrig; + const Real lineDisp2 = lineDisp | lineDisp; + const Real dispDotLineDisp = disp | lineDisp; + const Real distanceTol2LineDisp2 = distanceTol2 * lineDisp2; + if (dispDotLineDisp < distanceTol2LineDisp2) + { + return false; + } + if ((disp | disp) * lineDisp2 - square(dispDotLineDisp) > distanceTol2LineDisp2) + { + return false; + } + return true; +} + +PX_INLINE bool +diagonalIsValidInLoop(const LinkedEdge2D* edge, const LinkedEdge2D* otherEdge, const LinkedEdge2D* loop) +{ + const Vec2Real& diagonalStart = edge->v[0]; + const Vec2Real& diagonalEnd = otherEdge->v[0]; + const Vec2Real diagonalDisp = diagonalEnd - diagonalStart; + + const LinkedEdge2D* loopEdge = loop; + LinkedEdge2D* loopEdgeNext = loopEdge->getAdj(1); + do + { + loopEdgeNext = loopEdge->getAdj(1); + if (loopEdge == edge || loopEdgeNext == edge || loopEdge == otherEdge || loopEdgeNext == otherEdge) + { + continue; + } + const Vec2Real& loopEdgeStart = loopEdge->v[0]; + const Vec2Real& loopEdgeEnd = loopEdge->v[1]; + const Vec2Real& loopEdgeDisp = loopEdgeEnd - loopEdgeStart; + if ((loopEdgeDisp ^(diagonalStart - loopEdgeStart)) * (loopEdgeDisp ^(diagonalEnd - loopEdgeStart)) <= 0 && + (diagonalDisp ^(loopEdgeStart - diagonalStart)) * (diagonalDisp ^(loopEdgeEnd - diagonalStart)) <= 0) + { + // Edge intersection + return false; + } + } + while ((loopEdge = loopEdgeNext) != loop); + + return true; +} + +PX_INLINE bool +diagonalIsValid(const LinkedEdge2D* edge, const LinkedEdge2D* otherEdge, const physx::Array<LinkedEdge2D*>& loops) +{ + for (uint32_t i = 0; i < loops.size(); ++i) + { + if (!diagonalIsValidInLoop(edge, otherEdge, loops[i])) + { + return false; + } + } + + return true; +} + +PX_INLINE uint32_t +pointCondition(const Vec2Real& point, const Vec2Real& n0, Real d0, const Vec2Real& n1, Real d1, const Vec2Real& n2, Real d2) +{ + return (uint32_t)((point|n0) + d0 >= (Real)0) | ((uint32_t)((point|n1) + d1 >= (Real)0)) << 1 | ((uint32_t)((point|n2) + d2 >= (Real)0)) << 2; +} + +PX_INLINE bool +triangulate(LinkedEdge2D* loop, physx::Array<Vec2Real>& planeVertices, nvidia::Pool<LinkedEdge2D>& linkedEdgePool) +{ + // Trivial cases + LinkedEdge2D* edge0 = loop; + LinkedEdge2D* edge1 = edge0->getAdj(1); + if (edge1 == edge0) + { + // Single vertex !? + linkedEdgePool.replace(edge0); + return true; + } + + LinkedEdge2D* edge2 = edge1->getAdj(1); + if (edge2 == edge0) + { + // Degenerate + linkedEdgePool.replace(edge0); + linkedEdgePool.replace(edge1); + return true; + } + + for(;;) + { + LinkedEdge2D* edge3 = edge2->getAdj(1); + if (edge3 == edge0) + { + // Single triangle - we're done + planeVertices.pushBack(edge0->v[0]); + planeVertices.pushBack(edge1->v[0]); + planeVertices.pushBack(edge2->v[0]); + linkedEdgePool.replace(edge0); + linkedEdgePool.replace(edge1); + linkedEdgePool.replace(edge2); + break; + } + + // Polygon has more than three vertices. Find an ear + bool earFound = false; + do + { + Vec2Real e01 = edge1->v[0] - edge0->v[0]; + Vec2Real e12 = edge2->v[0] - edge1->v[0]; + if ((e01 ^ e12) > (Real)0) + { + // Convex, see if this is an ear + const Vec2Real n01 = e01.perp(); + const Real d01 = -(n01 | edge0->v[0]); + const Vec2Real n12 = e12.perp(); + const Real d12 = -(n12 | edge1->v[0]); + const Vec2Real n20 = Vec2Real(edge0->v[0] - edge2->v[0]).perp(); + const Real d20 = -(n20 | edge2->v[0]); + LinkedEdge2D* eTest = edge3; + bool edgeIntersectsTriangle = false; // Until proven otherwise + do + { + if (pointCondition(eTest->v[0], n01, d01, n12, d12, n20, d20)) + { + // Point is inside the triangle + edgeIntersectsTriangle = true; + break; + } + } while ((eTest = eTest->getAdj(1)) != edge0); + if (!edgeIntersectsTriangle) + { + // Ear found + planeVertices.pushBack(edge0->v[0]); + planeVertices.pushBack(edge1->v[0]); + planeVertices.pushBack(edge2->v[0]); + edge0->v[1] = edge0->v[0]; // So that the next and previous edges will join up to the correct location after remove() is called() + edge0->remove(); + linkedEdgePool.replace(edge0); + if (edge0 == loop) + { + loop = edge1; + } + edge0 = edge1; + edge1 = edge2; + edge2 = edge3; + earFound = true; + break; + } + } + + edge0 = edge1; + edge1 = edge2; + edge2 = edge3; + edge3 = edge3->getAdj(1); + } while (edge0 != loop); + + if (!earFound) + { + // Something went wrong + return false; + } + } + + return true; +} + +static bool +mergeTriangles2D(physx::Array<Vec2Real>& planeVertices, nvidia::Pool<LinkedEdge2D>& linkedEdgePool, Real distanceTol) +{ + // Create a set of linked edges for each triangle. The initial set will consist of nothing but three-edge loops (the triangles). + physx::Array<LinkedEdge2D*> edges; + edges.reserve(planeVertices.size()); + PX_ASSERT((planeVertices.size() % 3) == 0); + for (uint32_t i = 0; i < planeVertices.size(); i += 3) + { + LinkedEdge2D* v0 = linkedEdgePool.borrow(); + edges.pushBack(v0); + LinkedEdge2D* v1 = linkedEdgePool.borrow(); + edges.pushBack(v1); + LinkedEdge2D* v2 = linkedEdgePool.borrow(); + edges.pushBack(v2); + v0->setAdj(1, v1); + v1->setAdj(1, v2); + v0->v[0] = planeVertices[i]; + v0->v[1] = planeVertices[i + 1]; + v1->v[0] = planeVertices[i + 1]; + v1->v[1] = planeVertices[i + 2]; + v2->v[0] = planeVertices[i + 2]; + v2->v[1] = planeVertices[i]; + } + + const Real distanceTol2 = distanceTol * distanceTol; + + // Find all edge overlaps and merge loops + for (uint32_t i = 0; i < edges.size(); ++i) + { + LinkedEdge2D* edge = edges[i]; + const Vec2Real edgeDisp = edge->v[1] - edge->v[0]; + for (uint32_t j = i + 1; j < edges.size(); ++j) + { + LinkedEdge2D* otherEdge = edges[j]; + const Vec2Real otherEdgeDisp = otherEdge->v[1] - otherEdge->v[0]; + if ((otherEdgeDisp | edgeDisp) < 0) + { + if (pointOnRay2D(otherEdge->v[0], edge->v[0], edgeDisp, distanceTol2) && pointOnRay2D(otherEdge->v[1], edge->v[1], -edgeDisp, distanceTol2)) + { + edge->setAdj(0, otherEdge->getAdj(0)); + } + else + if (pointOnRay2D(edge->v[0], otherEdge->v[0], otherEdgeDisp, distanceTol2) && pointOnRay2D(edge->v[1], otherEdge->v[1], -otherEdgeDisp, distanceTol2)) + { + otherEdge->setAdj(0, edge->getAdj(0)); + } + } + } + } + + // Clean further by removing adjacent collinear edges. Also label loops. + physx::Array<LinkedEdge2D*> loops; + int32_t loopID = 0; + for (uint32_t i = edges.size(); i--;) + { + LinkedEdge2D* edge = edges[i]; + if (edge == NULL || edge->isSolitary()) + { + if (edge != NULL) + { + linkedEdgePool.replace(edge); + } + edges.replaceWithLast(i); + continue; + } + if (edge->loopID < 0) + { + do + { + edge->loopID = loopID; + LinkedEdge2D* prev = edge->getAdj(0); + LinkedEdge2D* next = edge->getAdj(1); + const Vec2Real edgeDisp = edge->v[1] - edge->v[0]; + const Vec2Real prevDisp = prev->v[1] - prev->v[0]; + const Real sumDisp2 = (prevDisp + edgeDisp).lengthSquared(); + if (sumDisp2 < distanceTol2) + { + // These two edges cancel. Eliminate both. + next = edge->getAdj(1); + prev->v[1] = prev->v[0]; + edge->v[0] = prev->v[1]; + prev->remove(); + edge->remove(); + if (next == prev) + { + // Loops is empty + edge = NULL; + break; // Done + } + } + else + { + const Real h2 = square(prevDisp ^ edgeDisp) / sumDisp2; + if (h2 < distanceTol2) + { + // Collinear, remove + prev->v[1] = edge->v[0] = edge->v[1]; + edge->remove(); + } + } + edge = next; + } + while (edge->loopID < 0); + if (edge != NULL) + { + ++loopID; + loops.pushBack(edge); + } + } + } + + if (loops.size() == 0) + { + // No loops, done + planeVertices.reset(); + return true; + } + + // The methods employed below are not optimal in time. But the majority of cases will be simple polygons + // with no holes and a small number of vertices. So an optimal algorithm probably isn't worth implementing. + + // Merge all loops into one by finding diagonals to join them + while (loops.size() > 1) + { + LinkedEdge2D* loop = loops.back(); + LinkedEdge2D* bestEdge = NULL; + LinkedEdge2D* bestOtherEdge = NULL; + uint32_t bestOtherLoopIndex = 0xFFFFFFFF; + Real minDist2 = MAX_REAL; + for (uint32_t i = loops.size() - 1; i--;) + { + LinkedEdge2D* otherLoop = loops[i]; + LinkedEdge2D* otherEdge = otherLoop; + do + { + LinkedEdge2D* edge = loop; + do + { + if (diagonalIsValid(edge, otherEdge, loops)) + { + const Real dist2 = (edge->v[0] - otherEdge->v[0]).lengthSquared(); + if (dist2 < minDist2) + { + bestEdge = edge; + bestOtherEdge = otherEdge; + bestOtherLoopIndex = i; + minDist2 = dist2; + } + } + } + while ((edge = edge->getAdj(1)) != loop); + } + while ((otherEdge = otherEdge->getAdj(1)) != otherLoop); + } + + if (bestOtherLoopIndex == 0xFFFFFFFF) + { + // Clean up loops + for (uint32_t i = 0; i < loops.size(); ++i) + { + LinkedEdge2D* edge = loops[i]; + bool done = false; + do + { + done = edge->isSolitary(); + LinkedEdge2D* next = edge->getAdj(1); + linkedEdgePool.replace(edge); // This also removes the link from the loop + edge = next; + } + while (!done); + } + return false; + } + + // Create diagonal loop with correct endpoints + LinkedEdge2D* diagonal = linkedEdgePool.borrow(); + LinkedEdge2D* reciprocal = linkedEdgePool.borrow(); + diagonal->setAdj(1, reciprocal); + diagonal->v[1] = reciprocal->v[0] = bestEdge->v[0]; + diagonal->v[0] = reciprocal->v[1] = bestOtherEdge->v[0]; + + // Insert diagonal loop, merging loops.back() with loops[i] + diagonal->setAdj(1, bestEdge); + reciprocal->setAdj(1, bestOtherEdge); + loops.popBack(); + } + + // Erase planeVertices, will reuse. + planeVertices.reset(); + + // We have one loop. Triangulate. + return triangulate(loops[0], planeVertices, linkedEdgePool); +} + +static void +mergeTriangles(physx::Array<nvidia::ExplicitRenderTriangle>& cleanedMesh, const Triangle* triangles, const Interpolator* frames, ClippedTriangleInfo* info, uint32_t triangleCount, + const physx::Array<Triangle>& originalTriangles, const Plane& plane, nvidia::Pool<LinkedEdge2D>& linkedEdgePool, Real distanceTol, const Mat4Real& BSPToMeshTM) +{ + if (triangleCount == 0) + { + return; + } + + const uint32_t originalTriangleIndexStart = info[0].originalTriangleIndex; + const uint32_t originalTriangleIndexCount = info[triangleCount - 1].originalTriangleIndex - originalTriangleIndexStart + 1; + + physx::Array<uint32_t> originalTriangleGroupStarts; + nvidia::createIndexStartLookup(originalTriangleGroupStarts, (int32_t)originalTriangleIndexStart, originalTriangleIndexCount, + (int32_t*)&info->originalTriangleIndex, triangleCount, sizeof(ClippedTriangleInfo)); + + // Now group equal reference frames, and transform into 2D + physx::Array<Vec2Real> planeVertices; + nvidia::IndexBank<uint32_t> frameIndices; + frameIndices.reserve(originalTriangleIndexCount); + frameIndices.lockCapacity(true); + while (frameIndices.freeCount()) + { + planeVertices.reset(); // Erase, we'll reuse this array + uint32_t seedFrameIndex = 0; + frameIndices.useNextFree(seedFrameIndex); + const Interpolator& seedFrame = frames[seedFrameIndex + originalTriangleIndexStart]; + const Triangle& seedOriginalTri = originalTriangles[originalTriangleIndexStart + seedFrameIndex]; +// const Plane plane(seedOriginalTri.normal, (Real)0.333333333333333333333 * (seedOriginalTri.vertices[0] + seedOriginalTri.vertices[1] + seedOriginalTri.vertices[2])); + const Dir& zAxis = plane.normal(); + const uint32_t maxDir = physx::PxAbs(zAxis[0]) > physx::PxAbs(zAxis[1]) ? + (physx::PxAbs(zAxis[0]) > physx::PxAbs(zAxis[2]) ? 0u : 2u) : + (physx::PxAbs(zAxis[1]) > physx::PxAbs(zAxis[2]) ? 1u : 2u); + Dir xAxis((Real)0); + xAxis[(int32_t)(maxDir + 1) % 3] = (Real)1; + Dir yAxis = zAxis ^ xAxis; + yAxis.normalize(); + xAxis = yAxis ^ zAxis; + Real signedArea = 0; + physx::Array<uint32_t> mergedFrameIndices; + mergedFrameIndices.pushBack(seedFrameIndex); + for (uint32_t i = originalTriangleGroupStarts[seedFrameIndex]; i < originalTriangleGroupStarts[seedFrameIndex + 1]; ++i) + { + const Triangle& triangle = triangles[info[i].clippedTriangleIndex]; + const uint32_t ccw = (uint32_t)((triangle.normal | zAxis) > 0); + const Real sign = ccw ? (Real)1 : -(Real)1; + signedArea += sign * physx::PxAbs(triangle.area); + const uint32_t i1 = 2 - ccw; + const uint32_t i2 = 1 + ccw; + planeVertices.pushBack(Vec2Real(xAxis | triangle.vertices[0], yAxis | triangle.vertices[0])); + planeVertices.pushBack(Vec2Real(xAxis | triangle.vertices[i1], yAxis | triangle.vertices[i1])); + planeVertices.pushBack(Vec2Real(xAxis | triangle.vertices[i2], yAxis | triangle.vertices[i2])); + } +#if 1 + const uint32_t* freeIndexPtrStop = frameIndices.usedIndices() + frameIndices.capacity(); + for (const uint32_t* nextFreeIndexPtr = frameIndices.freeIndices(); nextFreeIndexPtr < freeIndexPtrStop; ++nextFreeIndexPtr) + { + const uint32_t nextFreeIndex = *nextFreeIndexPtr; + const Triangle& nextOriginalTri = originalTriangles[originalTriangleIndexStart + nextFreeIndex]; + if (nextOriginalTri.submeshIndex != seedOriginalTri.submeshIndex) + { + continue; // Different submesh, don't use + } + if (plane.distance(nextOriginalTri.vertices[0]) > distanceTol || + plane.distance(nextOriginalTri.vertices[1]) > distanceTol || + plane.distance(nextOriginalTri.vertices[2]) > distanceTol) + { + continue; // Not coplanar + } + const Interpolator& nextFreeFrame = frames[nextFreeIndex + originalTriangleIndexStart]; + // BRG - Ouch, any way to set these tolerances in a little less of an ad hoc fashion? + if (!nextFreeFrame.equals(seedFrame, (Real)0.001, (Real)0.001, (Real)0.001, (Real)0.01, (Real)0.001)) + { + continue; // Frames different, don't use + } + // We can use this frame + frameIndices.use(nextFreeIndex); + mergedFrameIndices.pushBack(nextFreeIndex); + for (uint32_t i = originalTriangleGroupStarts[nextFreeIndex]; i < originalTriangleGroupStarts[nextFreeIndex + 1]; ++i) + { + const Triangle& triangle = triangles[info[i].clippedTriangleIndex]; + const uint32_t ccw = (uint32_t)((triangle.normal | zAxis) > 0); + const Real sign = ccw ? (Real)1 : -(Real)1; + signedArea += sign * physx::PxAbs(triangle.area); + const uint32_t i1 = 2 - ccw; + const uint32_t i2 = 1 + ccw; + planeVertices.pushBack(Vec2Real(xAxis | triangle.vertices[0], yAxis | triangle.vertices[0])); + planeVertices.pushBack(Vec2Real(xAxis | triangle.vertices[i1], yAxis | triangle.vertices[i1])); + planeVertices.pushBack(Vec2Real(xAxis | triangle.vertices[i2], yAxis | triangle.vertices[i2])); + } + } +#endif + + // We've collected all of the clipped triangles that fit within a single reference frame, and transformed them into the x,y plane. + // Now process this collection. + const uint32_t oldPlaneVertexCount = planeVertices.size(); + const bool success = mergeTriangles2D(planeVertices, linkedEdgePool, distanceTol); + if (success && planeVertices.size() < oldPlaneVertexCount) + { + // Transform back into 3 space and append to cleanedMesh + const uint32_t ccw = (uint32_t)(signedArea >= 0); + const Real sign = ccw ? (Real)1 : (Real) - 1; + const uint32_t vMap[3] = { 0, 2 - ccw, 1 + ccw }; + const Pos planeOffset = Pos((Real)0) + (-plane.d()) * zAxis; + for (uint32_t i = 0; i < planeVertices.size(); i += 3) + { + nvidia::ExplicitRenderTriangle& cleanedTri = cleanedMesh.insert(); + VertexData vertexData[3]; + Triangle tri; + for (uint32_t v = 0; v < 3; ++v) + { + const Vec2Real& planeVertex = planeVertices[i + vMap[v]]; + tri.vertices[v] = planeOffset + planeVertex[0] * xAxis + planeVertex[1] * yAxis; + } + tri.transform(BSPToMeshTM); + for (uint32_t v = 0; v < 3; ++v) + { + seedFrame.interpolateVertexData(vertexData[v], tri.vertices[v]); + vertexData[v].normal *= sign; + } + tri.toExplicitRenderTriangle(cleanedTri, vertexData); + cleanedTri.submeshIndex = seedOriginalTri.submeshIndex; + cleanedTri.smoothingMask = seedOriginalTri.smoothingMask; + cleanedTri.extraDataIndex = seedOriginalTri.extraDataIndex; + } + } + else + { + // An error occurred, or we increased the triangle count. Just use original triangles + for (uint32_t i = 0; i < mergedFrameIndices.size(); ++i) + { + for (uint32_t j = originalTriangleGroupStarts[mergedFrameIndices[i]]; j < originalTriangleGroupStarts[mergedFrameIndices[i] + 1]; ++j) + { + Triangle tri = triangles[info[j].clippedTriangleIndex]; + tri.transform(BSPToMeshTM); + nvidia::ExplicitRenderTriangle& cleanedMeshTri = cleanedMesh.insert(); + VertexData vertexData[3]; + for (int v = 0; v < 3; ++v) + { + frames[info[j].originalTriangleIndex].interpolateVertexData(vertexData[v], tri.vertices[v]); + if (!info[j].ccw) + { + vertexData[v].normal *= -1.0; + } + } + tri.toExplicitRenderTriangle(cleanedMeshTri, vertexData); + } + } + } + } + + return; +} + +void +cleanMesh(physx::Array<nvidia::ExplicitRenderTriangle>& cleanedMesh, const physx::Array<Triangle>& mesh, physx::Array<ClippedTriangleInfo>& triangleInfo, const physx::Array<Plane>& planes, const physx::Array<Triangle>& originalTriangles, const physx::Array<Interpolator>& frames, Real distanceTol, const Mat4Real& BSPToMeshTM) +{ + cleanedMesh.clear(); + + // Sort triangles into splitting plane groups, then original triangle groups + qsort(triangleInfo.begin(), triangleInfo.size(), sizeof(ClippedTriangleInfo), ClippedTriangleInfo::cmp); + + physx::Array<uint32_t> planeGroupStarts; + nvidia::createIndexStartLookup(planeGroupStarts, 0, planes.size(), (int32_t*)&triangleInfo.begin()->planeIndex, triangleInfo.size(), sizeof(ClippedTriangleInfo)); + + nvidia::Pool<LinkedEdge2D> linkedEdgePool; + for (uint32_t i = 0; i < planes.size(); ++i) + { + mergeTriangles(cleanedMesh, mesh.begin(), frames.begin(), triangleInfo.begin() + planeGroupStarts[i], planeGroupStarts[i + 1] - planeGroupStarts[i], originalTriangles, planes[i], linkedEdgePool, distanceTol, BSPToMeshTM); + } +} + +} +#endif diff --git a/APEX_1.4/shared/internal/src/authoring/Cutout.cpp b/APEX_1.4/shared/internal/src/authoring/Cutout.cpp new file mode 100644 index 00000000..5d6ee916 --- /dev/null +++ b/APEX_1.4/shared/internal/src/authoring/Cutout.cpp @@ -0,0 +1,1908 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + +#ifdef _MANAGED +#pragma managed(push, off) +#endif + +#include "Apex.h" +#include "PxMath.h" +#include "ApexUsingNamespace.h" +#include "ApexSharedUtils.h" +#include "FractureTools.h" + +#include "authoring/Fracturing.h" + +#ifndef WITHOUT_APEX_AUTHORING +#include "ApexSharedSerialization.h" + +#define CUTOUT_DISTANCE_THRESHOLD (0.7f) + +#define CUTOUT_DISTANCE_EPS (0.01f) + +struct POINT2D +{ + POINT2D() {} + POINT2D(int32_t _x, int32_t _y) : x(_x), y(_y) {} + + int32_t x; + int32_t y; +}; + +// Unsigned modulus +PX_INLINE uint32_t mod(int32_t n, uint32_t modulus) +{ + const int32_t d = n/(int32_t)modulus; + const int32_t m = n - d*(int32_t)modulus; + return m >= 0 ? (uint32_t)m : (uint32_t)m + modulus; +} + +PX_INLINE float square(float x) +{ + return x * x; +} + +// 2D cross product +PX_INLINE float dotXY(const physx::PxVec3& v, const physx::PxVec3& w) +{ + return v.x * w.x + v.y * w.y; +} + +// Z-component of cross product +PX_INLINE float crossZ(const physx::PxVec3& v, const physx::PxVec3& w) +{ + return v.x * w.y - v.y * w.x; +} + +// z coordinates may be used to store extra info - only deal with x and y +PX_INLINE float perpendicularDistanceSquared(const physx::PxVec3& v0, const physx::PxVec3& v1, const physx::PxVec3& v2) +{ + const physx::PxVec3 base = v2 - v0; + const physx::PxVec3 leg = v1 - v0; + + const float baseLen2 = dotXY(base, base); + + return baseLen2 > PX_EPS_F32 * dotXY(leg, leg) ? square(crossZ(base, leg)) / baseLen2 : 0.0f; +} + +// z coordinates may be used to store extra info - only deal with x and y +PX_INLINE float perpendicularDistanceSquared(const physx::Array< physx::PxVec3 >& cutout, uint32_t index) +{ + const uint32_t size = cutout.size(); + return perpendicularDistanceSquared(cutout[(index + size - 1) % size], cutout[index], cutout[(index + 1) % size]); +} + + +struct CutoutVert +{ + int32_t cutoutIndex; + int32_t vertIndex; + + void set(int32_t _cutoutIndex, int32_t _vertIndex) + { + cutoutIndex = _cutoutIndex; + vertIndex = _vertIndex; + } +}; + +struct NewVertex +{ + CutoutVert vertex; + float edgeProj; +}; + +static int compareNewVertices(const void* a, const void* b) +{ + const int32_t cutoutDiff = ((NewVertex*)a)->vertex.cutoutIndex - ((NewVertex*)b)->vertex.cutoutIndex; + if (cutoutDiff) + { + return cutoutDiff; + } + const int32_t vertDiff = ((NewVertex*)a)->vertex.vertIndex - ((NewVertex*)b)->vertex.vertIndex; + if (vertDiff) + { + return vertDiff; + } + const float projDiff = ((NewVertex*)a)->edgeProj - ((NewVertex*)b)->edgeProj; + return projDiff ? (projDiff < 0.0f ? -1 : 1) : 0; +} + +template<typename T> +class Map2d +{ +public: + Map2d() : mMem(NULL) {} + Map2d(uint32_t width, uint32_t height) : mMem(NULL) + { + create_internal(width, height, NULL); + } + Map2d(uint32_t width, uint32_t height, T fillValue) : mMem(NULL) + { + create_internal(width, height, &fillValue); + } + Map2d(const Map2d& map) + { + *this = map; + } + ~Map2d() + { + delete [] mMem; + } + + Map2d& operator = (const Map2d& map) + { + delete [] mMem; + mMem = NULL; + if (map.mMem) + { + create_internal(map.mWidth, map.mHeight, NULL); + memcpy(mMem, map.mMem, mWidth * mHeight); + } + return *this; + } + + void create(uint32_t width, uint32_t height) + { + return create_internal(width, height, NULL); + } + void create(uint32_t width, uint32_t height, T fillValue) + { + create_internal(width, height, &fillValue); + } + + void clear(const T value) + { + T* mem = mMem; + T* stop = mMem + mWidth * mHeight; + while (mem < stop) + { + *mem++ = value; + } + } + + void setOrigin(uint32_t x, uint32_t y) + { + mOriginX = x; + mOriginY = y; + } + + const T& operator()(int32_t x, int32_t y) const + { + x = (int32_t)mod(x+(int32_t)mOriginX, mWidth); + y = (int32_t)mod(y+(int32_t)mOriginY, mHeight); + return mMem[(uint32_t)(x + y * (int32_t)mWidth)]; + } + T& operator()(int32_t x, int32_t y) + { + x = (int32_t)mod(x+(int32_t)mOriginX, mWidth); + y = (int32_t)mod(y+(int32_t)mOriginY, mHeight); + return mMem[(uint32_t)(x + y * (int32_t)mWidth)]; + } + +private: + + void create_internal(uint32_t width, uint32_t height, T* val) + { + delete [] mMem; + mWidth = width; + mHeight = height; + mMem = new T[mWidth * mHeight]; + mOriginX = 0; + mOriginY = 0; + if (val) + { + clear(*val); + } + } + + T* mMem; + uint32_t mWidth; + uint32_t mHeight; + uint32_t mOriginX; + uint32_t mOriginY; +}; + +class BitMap +{ +public: + BitMap() : mMem(NULL) {} + BitMap(uint32_t width, uint32_t height) : mMem(NULL) + { + create_internal(width, height, NULL); + } + BitMap(uint32_t width, uint32_t height, bool fillValue) : mMem(NULL) + { + create_internal(width, height, &fillValue); + } + BitMap(const BitMap& map) + { + *this = map; + } + ~BitMap() + { + delete [] mMem; + } + + BitMap& operator = (const BitMap& map) + { + delete [] mMem; + mMem = NULL; + if (map.mMem) + { + create_internal(map.mWidth, map.mHeight, NULL); + memcpy(mMem, map.mMem, mHeight * mRowBytes); + } + return *this; + } + + void create(uint32_t width, uint32_t height) + { + return create_internal(width, height, NULL); + } + void create(uint32_t width, uint32_t height, bool fillValue) + { + create_internal(width, height, &fillValue); + } + + void clear(bool value) + { + memset(mMem, value ? 0xFF : 0x00, mRowBytes * mHeight); + } + + void setOrigin(uint32_t x, uint32_t y) + { + mOriginX = x; + mOriginY = y; + } + + bool read(int32_t x, int32_t y) const + { + x = (int32_t)mod(x+(int32_t)mOriginX, mWidth); + y = (int32_t)mod(y+(int32_t)mOriginY, mHeight); + return ((mMem[(x >> 3) + y * mRowBytes] >> (x & 7)) & 1) != 0; + } + void set(int32_t x, int32_t y) + { + x = (int32_t)mod(x+(int32_t)mOriginX, mWidth); + y = (int32_t)mod(y+(int32_t)mOriginY, mHeight); + mMem[(x >> 3) + y * mRowBytes] |= 1 << (x & 7); + } + void reset(int32_t x, int32_t y) + { + x = (int32_t)mod(x+(int32_t)mOriginX, mWidth); + y = (int32_t)mod(y+(int32_t)mOriginY, mHeight); + mMem[(x >> 3) + y * mRowBytes] &= ~(1 << (x & 7)); + } + +private: + + void create_internal(uint32_t width, uint32_t height, bool* val) + { + delete [] mMem; + mRowBytes = (width + 7) >> 3; + const uint32_t bytes = mRowBytes * height; + if (bytes == 0) + { + mWidth = mHeight = 0; + mMem = NULL; + return; + } + mWidth = width; + mHeight = height; + mMem = new uint8_t[bytes]; + mOriginX = 0; + mOriginY = 0; + if (val) + { + clear(*val); + } + } + + uint8_t* mMem; + uint32_t mWidth; + uint32_t mHeight; + uint32_t mRowBytes; + uint32_t mOriginX; + uint32_t mOriginY; +}; + + +PX_INLINE int32_t taxicabSine(int32_t i) +{ + // 0 1 1 1 0 -1 -1 -1 + return (int32_t)((0x01A9 >> ((i & 7) << 1)) & 3) - 1; +} + +// Only looks at x and y components +PX_INLINE bool directionsXYOrderedCCW(const physx::PxVec3& d0, const physx::PxVec3& d1, const physx::PxVec3& d2) +{ + const bool ccw02 = crossZ(d0, d2) > 0.0f; + const bool ccw01 = crossZ(d0, d1) > 0.0f; + const bool ccw21 = crossZ(d2, d1) > 0.0f; + return ccw02 ? ccw01 && ccw21 : ccw01 || ccw21; +} + +PX_INLINE float compareTraceSegmentToLineSegment(const physx::Array<POINT2D>& trace, int _start, int delta, float distThreshold, uint32_t width, uint32_t height, bool hasBorder) +{ + if (delta < 2) + { + return 0.0f; + } + + const uint32_t size = trace.size(); + + uint32_t start = (uint32_t)_start, end = (uint32_t)(_start + delta) % size; + + const bool startIsOnBorder = hasBorder && (trace[start].x == -1 || trace[start].x == (int)width || trace[start].y == -1 || trace[start].y == (int)height); + const bool endIsOnBorder = hasBorder && (trace[end].x == -1 || trace[end].x == (int)width || trace[end].y == -1 || trace[end].y == (int)height); + + if (startIsOnBorder || endIsOnBorder) + { + if ((trace[start].x == -1 && trace[end].x == -1) || + (trace[start].y == -1 && trace[end].y == -1) || + (trace[start].x == (int)width && trace[end].x == (int)width) || + (trace[start].y == (int)height && trace[end].y == (int)height)) + { + return 0.0f; + } + return PX_MAX_F32; + } + + physx::PxVec3 orig((float)trace[start].x, (float)trace[start].y, 0); + physx::PxVec3 dest((float)trace[end].x, (float)trace[end].y, 0); + physx::PxVec3 dir = dest - orig; + + dir.normalize(); + + float aveError = 0.0f; + + for (;;) + { + if (++start >= size) + { + start = 0; + } + if (start == end) + { + break; + } + physx::PxVec3 testDisp((float)trace[start].x, (float)trace[start].y, 0); + testDisp -= orig; + aveError += (float)(physx::PxAbs(testDisp.x * dir.y - testDisp.y * dir.x) >= distThreshold); + } + + aveError /= delta - 1; + + return aveError; +} + +// Segment i starts at vi and ends at vi+ei +// Tests for overlap in segments' projection onto xy plane +// Returns distance between line segments. (Negative value indicates overlap.) +PX_INLINE float segmentsIntersectXY(const physx::PxVec3& v0, const physx::PxVec3& e0, const physx::PxVec3& v1, const physx::PxVec3& e1) +{ + const physx::PxVec3 dv = v1 - v0; + + physx::PxVec3 d0 = e0; + d0.normalize(); + physx::PxVec3 d1 = e1; + d1.normalize(); + + const float c10 = crossZ(dv, d0); + const float d10 = crossZ(e1, d0); + + float a1 = physx::PxAbs(c10); + float b1 = physx::PxAbs(c10 + d10); + + if (c10 * (c10 + d10) < 0.0f) + { + if (a1 < b1) + { + a1 = -a1; + } + else + { + b1 = -b1; + } + } + + const float c01 = crossZ(d1, dv); + const float d01 = crossZ(e0, d1); + + float a2 = physx::PxAbs(c01); + float b2 = physx::PxAbs(c01 + d01); + + if (c01 * (c01 + d01) < 0.0f) + { + if (a2 < b2) + { + a2 = -a2; + } + else + { + b2 = -b2; + } + } + + return physx::PxMax(physx::PxMin(a1, b1), physx::PxMin(a2, b2)); +} + +// If point projects onto segment, returns true and proj is set to a +// value in the range [0,1], indicating where along the segment (from v0 to v1) +// the projection lies, and dist2 is set to the distance squared from point to +// the line segment. Otherwise, returns false. +// Note, if v1 = v0, then the function returns true with proj = 0. +PX_INLINE bool projectOntoSegmentXY(float& proj, float& dist2, const physx::PxVec3& point, const physx::PxVec3& v0, const physx::PxVec3& v1, float margin) +{ + const physx::PxVec3 seg = v1 - v0; + const physx::PxVec3 x = point - v0; + const float seg2 = dotXY(seg, seg); + const float d = dotXY(x, seg); + + if (d < 0.0f || d > seg2) + { + return false; + } + + const float margin2 = margin * margin; + + const float p = seg2 > 0.0f ? d / seg2 : 0.0f; + const float lineDist2 = d * p; + + if (lineDist2 < margin2) + { + return false; + } + + const float pPrime = 1.0f - p; + const float dPrime = seg2 - d; + const float lineDistPrime2 = dPrime * pPrime; + + if (lineDistPrime2 < margin2) + { + return false; + } + + proj = p; + dist2 = dotXY(x, x) - lineDist2; + return true; +} + +PX_INLINE bool isOnBorder(const physx::PxVec3& v, uint32_t width, uint32_t height) +{ + return v.x < -0.5f || v.x >= width - 0.5f || v.y < -0.5f || v.y >= height - 0.5f; +} + +static void createCutout(nvidia::Cutout& cutout, const physx::Array<POINT2D>& trace, float snapThreshold, uint32_t width, uint32_t height, bool hasBorder) +{ + cutout.vertices.reset(); + + const uint32_t traceSize = trace.size(); + + if (traceSize == 0) + { + return; // Nothing to do + } + + uint32_t size = traceSize; + + physx::Array<int> vertexIndices; + + const float errorThreshold = 0.1f; + + const float pixelCenterOffset = hasBorder ? 0.5f : 0.0f; + + // Find best segment + uint32_t start = 0; + uint32_t delta = 0; + for (uint32_t iStart = 0; iStart < size; ++iStart) + { + uint32_t iDelta = (size >> 1) + (size & 1); + for (; iDelta > 1; --iDelta) + { + float fit = compareTraceSegmentToLineSegment(trace, (int32_t)iStart, (int32_t)iDelta, CUTOUT_DISTANCE_THRESHOLD, width, height, hasBorder); + if (fit < errorThreshold) + { + break; + } + } + if (iDelta > delta) + { + start = iStart; + delta = iDelta; + } + } + cutout.vertices.pushBack(physx::PxVec3((float)trace[start].x + pixelCenterOffset, (float)trace[start].y + pixelCenterOffset, 0)); + + // Now complete the loop + while ((size -= delta) > 0) + { + start = (start + delta) % traceSize; + cutout.vertices.pushBack(physx::PxVec3((float)trace[start].x + pixelCenterOffset, (float)trace[start].y + pixelCenterOffset, 0)); + if (size == 1) + { + delta = 1; + break; + } + for (delta = size - 1; delta > 1; --delta) + { + float fit = compareTraceSegmentToLineSegment(trace, (int32_t)start, (int32_t)delta, CUTOUT_DISTANCE_THRESHOLD, width, height, hasBorder); + if (fit < errorThreshold) + { + break; + } + } + } + + const float snapThresh2 = square(snapThreshold); + + // Use the snapThreshold to clean up + while ((size = cutout.vertices.size()) >= 4) + { + bool reduced = false; + for (uint32_t i = 0; i < size; ++i) + { + const uint32_t i1 = (i + 1) % size; + const uint32_t i2 = (i + 2) % size; + const uint32_t i3 = (i + 3) % size; + physx::PxVec3& v0 = cutout.vertices[i]; + physx::PxVec3& v1 = cutout.vertices[i1]; + physx::PxVec3& v2 = cutout.vertices[i2]; + physx::PxVec3& v3 = cutout.vertices[i3]; + const physx::PxVec3 d0 = v1 - v0; + const physx::PxVec3 d1 = v2 - v1; + const physx::PxVec3 d2 = v3 - v2; + const float den = crossZ(d0, d2); + if (den != 0) + { + const float recipDen = 1.0f / den; + const float s0 = crossZ(d1, d2) * recipDen; + const float s2 = crossZ(d0, d1) * recipDen; + if (s0 >= 0 || s2 >= 0) + { + if (d0.magnitudeSquared()*s0* s0 <= snapThresh2 && d2.magnitudeSquared()*s2* s2 <= snapThresh2) + { + v1 += d0 * s0; + + uint32_t index = (uint32_t)(&v2 - cutout.vertices.begin()); + + cutout.vertices.remove(index); + + reduced = true; + break; + } + } + } + } + if (!reduced) + { + break; + } + } +} + +static void splitTJunctions(nvidia::CutoutSetImpl& cutoutSet, float threshold) +{ + // Set bounds reps + physx::Array<nvidia::BoundsRep> bounds; + physx::Array<CutoutVert> cutoutMap; // maps bounds # -> ( cutout #, vertex # ). + physx::Array<nvidia::IntPair> overlaps; + + const float distThreshold2 = threshold * threshold; + + // Split T-junctions + uint32_t edgeCount = 0; + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + edgeCount += cutoutSet.cutouts[i].vertices.size(); + } + + bounds.resize(edgeCount); + cutoutMap.resize(edgeCount); + + edgeCount = 0; + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + nvidia::Cutout& cutout = cutoutSet.cutouts[i]; + const uint32_t cutoutSize = cutout.vertices.size(); + for (uint32_t j = 0; j < cutoutSize; ++j) + { + bounds[edgeCount].aabb.include(cutout.vertices[j]); + bounds[edgeCount].aabb.include(cutout.vertices[(j + 1) % cutoutSize]); + PX_ASSERT(!bounds[edgeCount].aabb.isEmpty()); + bounds[edgeCount].aabb.fattenFast(threshold); + cutoutMap[edgeCount].set((int32_t)i, (int32_t)j); + ++edgeCount; + } + } + + // Find bounds overlaps + if (bounds.size() > 0) + { + boundsCalculateOverlaps(overlaps, nvidia::Bounds3XY, &bounds[0], bounds.size(), sizeof(bounds[0])); + } + + physx::Array<NewVertex> newVertices; + for (uint32_t overlapIndex = 0; overlapIndex < overlaps.size(); ++overlapIndex) + { + const nvidia::IntPair& mapPair = overlaps[overlapIndex]; + const CutoutVert& seg0Map = cutoutMap[(uint32_t)mapPair.i0]; + const CutoutVert& seg1Map = cutoutMap[(uint32_t)mapPair.i1]; + + if (seg0Map.cutoutIndex == seg1Map.cutoutIndex) + { + // Only split based on vertex/segment junctions from different cutouts + continue; + } + + NewVertex newVertex; + float dist2 = 0; + + const nvidia::Cutout& cutout0 = cutoutSet.cutouts[(uint32_t)seg0Map.cutoutIndex]; + const uint32_t cutoutSize0 = cutout0.vertices.size(); + const nvidia::Cutout& cutout1 = cutoutSet.cutouts[(uint32_t)seg1Map.cutoutIndex]; + const uint32_t cutoutSize1 = cutout1.vertices.size(); + + if (projectOntoSegmentXY(newVertex.edgeProj, dist2, cutout0.vertices[(uint32_t)seg0Map.vertIndex], cutout1.vertices[(uint32_t)seg1Map.vertIndex], + cutout1.vertices[(uint32_t)(seg1Map.vertIndex + 1) % cutoutSize1], 0.25f)) + { + if (dist2 <= distThreshold2) + { + newVertex.vertex = seg1Map; + newVertices.pushBack(newVertex); + } + } + + if (projectOntoSegmentXY(newVertex.edgeProj, dist2, cutout1.vertices[(uint32_t)seg1Map.vertIndex], cutout0.vertices[(uint32_t)seg0Map.vertIndex], + cutout0.vertices[(uint32_t)(seg0Map.vertIndex + 1) % cutoutSize0], 0.25f)) + { + if (dist2 <= distThreshold2) + { + newVertex.vertex = seg0Map; + newVertices.pushBack(newVertex); + } + } + } + + if (newVertices.size()) + { + // Sort new vertices + qsort(newVertices.begin(), newVertices.size(), sizeof(NewVertex), compareNewVertices); + + // Insert new vertices + uint32_t lastCutoutIndex = 0xFFFFFFFF; + uint32_t lastVertexIndex = 0xFFFFFFFF; + float lastProj = 1.0f; + for (uint32_t newVertexIndex = newVertices.size(); newVertexIndex--;) + { + const NewVertex& newVertex = newVertices[newVertexIndex]; + if (newVertex.vertex.cutoutIndex != (int32_t)lastCutoutIndex) + { + lastCutoutIndex = (uint32_t)newVertex.vertex.cutoutIndex; + lastVertexIndex = 0xFFFFFFFF; + } + if (newVertex.vertex.vertIndex != (int32_t)lastVertexIndex) + { + lastVertexIndex = (uint32_t)newVertex.vertex.vertIndex; + lastProj = 1.0f; + } + nvidia::Cutout& cutout = cutoutSet.cutouts[(uint32_t)newVertex.vertex.cutoutIndex]; + const float proj = lastProj > 0.0f ? newVertex.edgeProj / lastProj : 0.0f; + const physx::PxVec3 pos = (1.0f - proj) * cutout.vertices[(uint32_t)newVertex.vertex.vertIndex] + + proj * cutout.vertices[(uint32_t)(newVertex.vertex.vertIndex + 1) % cutout.vertices.size()]; + cutout.vertices.insert(); + for (uint32_t n = cutout.vertices.size(); --n > (uint32_t)newVertex.vertex.vertIndex + 1;) + { + cutout.vertices[n] = cutout.vertices[n - 1]; + } + cutout.vertices[(uint32_t)newVertex.vertex.vertIndex + 1] = pos; + lastProj = newVertex.edgeProj; + } + } +} + +#if 0 +static void mergeVertices(CutoutSetImpl& cutoutSet, float threshold, uint32_t width, uint32_t height) +{ + // Set bounds reps + physx::Array<BoundsRep> bounds; + physx::Array<CutoutVert> cutoutMap; // maps bounds # -> ( cutout #, vertex # ). + physx::Array<IntPair> overlaps; + + const float threshold2 = threshold * threshold; + + uint32_t vertexCount = 0; + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + vertexCount += cutoutSet.cutouts[i].vertices.size(); + } + + bounds.resize(vertexCount); + cutoutMap.resize(vertexCount); + + vertexCount = 0; + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + Cutout& cutout = cutoutSet.cutouts[i]; + for (uint32_t j = 0; j < cutout.vertices.size(); ++j) + { + physx::PxVec3& vertex = cutout.vertices[j]; + physx::PxVec3 min(vertex.x - threshold, vertex.y - threshold, 0.0f); + physx::PxVec3 max(vertex.x + threshold, vertex.y + threshold, 0.0f); + bounds[vertexCount].aabb.set(min, max); + cutoutMap[vertexCount].set(i, j); + ++vertexCount; + } + } + + // Find bounds overlaps + overlaps.reset(); + if (bounds.size() > 0) + { + boundsCalculateOverlaps(overlaps, Bounds3XY, &bounds[0], bounds.size(), sizeof(bounds[0])); + } + + const uint32_t overlapCount = overlaps.size(); + if (overlapCount) + { + // Sort overlaps by index0 and index1 + qsort(overlaps.begin(), overlaps.size(), sizeof(IntPair), IntPair::compare); + + // Process overlaps: merge vertices + uint32_t groupStart = 0; + uint32_t groupStop; + do + { + const int32_t groupI0 = overlaps[groupStart].i0; + groupStop = groupStart; + while (++groupStop < overlapCount) + { + const int32_t i0 = overlaps[groupStop].i0; + if (i0 != groupI0) + { + break; + } + } + // Process group + physx::PxVec3 straightV(0.0f); + uint32_t straightCount = 0; + physx::PxVec3 borderV(0.0f); + uint32_t borderCount = 0; + physx::PxVec3 v(0.0f); + float weight = 0.0f; + // Include i0 + const CutoutVert& vertexMap = cutoutMap[overlaps[groupStart].i0]; + Cutout& cutout = cutoutSet.cutouts[vertexMap.cutoutIndex]; + float dist2 = perpendicularDistanceSquared(cutout.vertices, vertexMap.vertIndex); + if (isOnBorder(cutout.vertices[vertexMap.vertIndex], width, height)) + { + borderV += cutout.vertices[vertexMap.vertIndex]; + ++borderCount; + } + else if (dist2 < threshold2) + { + straightV += cutout.vertices[vertexMap.vertIndex]; + ++straightCount; + } + else + { + const float recipDist2 = 1.0f / dist2; + weight += recipDist2; + v += cutout.vertices[vertexMap.vertIndex] * recipDist2; + } + for (uint32_t i = groupStart; i < groupStop; ++i) + { + const CutoutVert& vertexMap = cutoutMap[overlaps[i].i1]; + Cutout& cutout = cutoutSet.cutouts[vertexMap.cutoutIndex]; + dist2 = perpendicularDistanceSquared(cutout.vertices, vertexMap.vertIndex); + if (isOnBorder(cutout.vertices[vertexMap.vertIndex], width, height)) + { + borderV += cutout.vertices[vertexMap.vertIndex]; + ++borderCount; + } + else if (dist2 < threshold2) + { + straightV += cutout.vertices[vertexMap.vertIndex]; + ++straightCount; + } + else + { + const float recipDist2 = 1.0f / dist2; + weight += recipDist2; + v += cutout.vertices[vertexMap.vertIndex] * recipDist2; + } + } + if (borderCount) + { + // If we have any borderVertices, these will be the only ones considered + v = (1.0f / borderCount) * borderV; + } + else if (straightCount) + { + // Otherwise if we have any straight angles, these will be the only ones considered + v = (1.0f / straightCount) * straightV; + } + else + { + v *= 1.0f / weight; + } + // Now replace all group vertices by v + { + const CutoutVert& vertexMap = cutoutMap[overlaps[groupStart].i0]; + cutoutSet.cutouts[vertexMap.cutoutIndex].vertices[vertexMap.vertIndex] = v; + for (uint32_t i = groupStart; i < groupStop; ++i) + { + const CutoutVert& vertexMap = cutoutMap[overlaps[i].i1]; + cutoutSet.cutouts[vertexMap.cutoutIndex].vertices[vertexMap.vertIndex] = v; + } + } + } + while ((groupStart = groupStop) < overlapCount); + } +} +#else +static void mergeVertices(nvidia::CutoutSetImpl& cutoutSet, float threshold, uint32_t width, uint32_t height) +{ + // Set bounds reps + uint32_t vertexCount = 0; + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + vertexCount += cutoutSet.cutouts[i].vertices.size(); + } + + physx::Array<nvidia::BoundsRep> bounds; + physx::Array<CutoutVert> cutoutMap; // maps bounds # -> ( cutout #, vertex # ). + bounds.resize(vertexCount); + cutoutMap.resize(vertexCount); + + vertexCount = 0; + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + nvidia::Cutout& cutout = cutoutSet.cutouts[i]; + for (uint32_t j = 0; j < cutout.vertices.size(); ++j) + { + physx::PxVec3& vertex = cutout.vertices[j]; + physx::PxVec3 min(vertex.x - threshold, vertex.y - threshold, 0.0f); + physx::PxVec3 max(vertex.x + threshold, vertex.y + threshold, 0.0f); + bounds[vertexCount].aabb = physx::PxBounds3(min, max); + cutoutMap[vertexCount].set((int32_t)i, (int32_t)j); + ++vertexCount; + } + } + + // Find bounds overlaps + physx::Array<nvidia::IntPair> overlaps; + if (bounds.size() > 0) + { + boundsCalculateOverlaps(overlaps, nvidia::Bounds3XY, &bounds[0], bounds.size(), sizeof(bounds[0])); + } + uint32_t overlapCount = overlaps.size(); + + if (overlapCount == 0) + { + return; + } + + // Sort by first index + qsort(overlaps.begin(), overlapCount, sizeof(nvidia::IntPair), nvidia::IntPair::compare); + + const float threshold2 = threshold * threshold; + + physx::Array<nvidia::IntPair> pairs; + + // Group by first index + physx::Array<uint32_t> lookup; + nvidia::createIndexStartLookup(lookup, 0, vertexCount, &overlaps.begin()->i0, overlapCount, sizeof(nvidia::IntPair)); + for (uint32_t i = 0; i < vertexCount; ++i) + { + const uint32_t start = lookup[i]; + const uint32_t stop = lookup[i + 1]; + if (start == stop) + { + continue; + } + const CutoutVert& cutoutVert0 = cutoutMap[(uint32_t)overlaps[start].i0]; + const physx::PxVec3& vert0 = cutoutSet.cutouts[(uint32_t)cutoutVert0.cutoutIndex].vertices[(uint32_t)cutoutVert0.vertIndex]; + const bool isOnBorder0 = !cutoutSet.periodic && isOnBorder(vert0, width, height); + for (uint32_t j = start; j < stop; ++j) + { + const CutoutVert& cutoutVert1 = cutoutMap[(uint32_t)overlaps[j].i1]; + if (cutoutVert0.cutoutIndex == cutoutVert1.cutoutIndex) + { + // No pairs from the same cutout + continue; + } + const physx::PxVec3& vert1 = cutoutSet.cutouts[(uint32_t)cutoutVert1.cutoutIndex].vertices[(uint32_t)cutoutVert1.vertIndex]; + const bool isOnBorder1 = !cutoutSet.periodic && isOnBorder(vert1, width, height); + if (isOnBorder0 != isOnBorder1) + { + // No border/non-border pairs + continue; + } + if ((vert0 - vert1).magnitudeSquared() > threshold2) + { + // Distance outside threshold + continue; + } + // A keeper. Keep a symmetric list + nvidia::IntPair overlap = overlaps[j]; + pairs.pushBack(overlap); + const int32_t i0 = overlap.i0; + overlap.i0 = overlap.i1; + overlap.i1 = i0; + pairs.pushBack(overlap); + } + } + + // Sort by first index + qsort(pairs.begin(), pairs.size(), sizeof(nvidia::IntPair), nvidia::IntPair::compare); + + // For every vertex, only keep closest neighbor from each cutout + nvidia::createIndexStartLookup(lookup, 0, vertexCount, &pairs.begin()->i0, pairs.size(), sizeof(nvidia::IntPair)); + for (uint32_t i = 0; i < vertexCount; ++i) + { + const uint32_t start = lookup[i]; + const uint32_t stop = lookup[i + 1]; + if (start == stop) + { + continue; + } + const CutoutVert& cutoutVert0 = cutoutMap[(uint32_t)pairs[start].i0]; + const physx::PxVec3& vert0 = cutoutSet.cutouts[(uint32_t)cutoutVert0.cutoutIndex].vertices[(uint32_t)cutoutVert0.vertIndex]; + uint32_t groupStart = start; + while (groupStart < stop) + { + uint32_t next = groupStart; + const CutoutVert& cutoutVert1 = cutoutMap[(uint32_t)pairs[next].i1]; + int32_t currentOtherCutoutIndex = cutoutVert1.cutoutIndex; + const physx::PxVec3& vert1 = cutoutSet.cutouts[(uint32_t)currentOtherCutoutIndex].vertices[(uint32_t)cutoutVert1.vertIndex]; + uint32_t keep = groupStart; + float minDist2 = (vert0 - vert1).magnitudeSquared(); + while (++next < stop) + { + const CutoutVert& cutoutVertNext = cutoutMap[(uint32_t)pairs[next].i1]; + if (currentOtherCutoutIndex != cutoutVertNext.cutoutIndex) + { + break; + } + const physx::PxVec3& vertNext = cutoutSet.cutouts[(uint32_t)cutoutVertNext.cutoutIndex].vertices[(uint32_t)cutoutVertNext.vertIndex]; + const float dist2 = (vert0 - vertNext).magnitudeSquared(); + if (dist2 < minDist2) + { + pairs[keep].set(-1, -1); // Invalidate + keep = next; + minDist2 = dist2; + } + else + { + pairs[next].set(-1, -1); // Invalidate + } + } + groupStart = next; + } + } + + // Eliminate invalid pairs (compactify) + uint32_t pairCount = 0; + for (uint32_t i = 0; i < pairs.size(); ++i) + { + if (pairs[i].i0 >= 0 && pairs[i].i1 >= 0) + { + pairs[pairCount++] = pairs[i]; + } + } + pairs.resize(pairCount); + + // Snap points together + physx::Array<bool> pinned; + pinned.resize(vertexCount); + memset(pinned.begin(), 0, pinned.size()*sizeof(bool)); + + for (uint32_t i = 0; i < pairCount; ++i) + { + const uint32_t i0 = (uint32_t)pairs[i].i0; + bool& pinned0 = pinned[i0]; + if (pinned0) + { + continue; + } + const CutoutVert& cutoutVert0 = cutoutMap[i0]; + physx::PxVec3& vert0 = cutoutSet.cutouts[(uint32_t)cutoutVert0.cutoutIndex].vertices[(uint32_t)cutoutVert0.vertIndex]; + const uint32_t i1 = (uint32_t)pairs[i].i1; + bool& pinned1 = pinned[i1]; + const CutoutVert& cutoutVert1 = cutoutMap[i1]; + physx::PxVec3& vert1 = cutoutSet.cutouts[(uint32_t)cutoutVert1.cutoutIndex].vertices[(uint32_t)cutoutVert1.vertIndex]; + const physx::PxVec3 disp = vert1 - vert0; + // Move and pin + pinned0 = true; + if (pinned1) + { + vert0 = vert1; + } + else + { + vert0 += 0.5f * disp; + vert1 = vert0; + pinned1 = true; + } + } +} +#endif + +static void eliminateStraightAngles(nvidia::CutoutSetImpl& cutoutSet) +{ + // Eliminate straight angles + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + nvidia::Cutout& cutout = cutoutSet.cutouts[i]; + uint32_t oldSize; + do + { + oldSize = cutout.vertices.size(); + for (uint32_t j = 0; j < cutout.vertices.size();) + { +// if( isOnBorder( cutout.vertices[j], width, height ) ) +// { // Don't eliminate border vertices +// ++j; +// continue; +// } + if (perpendicularDistanceSquared(cutout.vertices, j) < CUTOUT_DISTANCE_EPS * CUTOUT_DISTANCE_EPS) + { + cutout.vertices.remove(j); + } + else + { + ++j; + } + } + } + while (cutout.vertices.size() != oldSize); + } +} + +static void simplifyCutoutSetImpl(nvidia::CutoutSetImpl& cutoutSet, float threshold, uint32_t width, uint32_t height) +{ + splitTJunctions(cutoutSet, 1.0f); + mergeVertices(cutoutSet, threshold, width, height); + eliminateStraightAngles(cutoutSet); +} + +static void cleanCutout(nvidia::Cutout& cutout, uint32_t loopIndex, float tolerance) +{ + nvidia::ConvexLoop& loop = cutout.convexLoops[loopIndex]; + const float tolerance2 = tolerance * tolerance; + uint32_t oldSize; + do + { + oldSize = loop.polyVerts.size(); + uint32_t size = oldSize; + for (uint32_t i = 0; i < size; ++i) + { + nvidia::PolyVert& v0 = loop.polyVerts[(i + size - 1) % size]; + nvidia::PolyVert& v1 = loop.polyVerts[i]; + nvidia::PolyVert& v2 = loop.polyVerts[(i + 1) % size]; + if (perpendicularDistanceSquared(cutout.vertices[v0.index], cutout.vertices[v1.index], cutout.vertices[v2.index]) <= tolerance2) + { + loop.polyVerts.remove(i); + --size; + --i; + } + } + } + while (loop.polyVerts.size() != oldSize); +} + +static bool decomposeCutoutIntoConvexLoops(nvidia::Cutout& cutout, float cleanupTolerance = 0.0f) +{ + const uint32_t size = cutout.vertices.size(); + + if (size < 3) + { + return false; + } + + // Initialize to one loop, which may not be convex + cutout.convexLoops.resize(1); + cutout.convexLoops[0].polyVerts.resize(size); + + // See if the winding is ccw: + + // Scale to normalized size to avoid overflows + physx::PxBounds3 bounds; + bounds.setEmpty(); + for (uint32_t i = 0; i < size; ++i) + { + bounds.include(cutout.vertices[i]); + } + physx::PxVec3 center = bounds.getCenter(); + physx::PxVec3 extent = bounds.getExtents(); + if (extent[0] < PX_EPS_F32 || extent[1] < PX_EPS_F32) + { + return false; + } + const physx::PxVec3 scale(1.0f / extent[0], 1.0f / extent[1], 0.0f); + + // Find "area" (it will only be correct in sign!) + physx::PxVec3 prevV = (cutout.vertices[size - 1] - center).multiply(scale); + float area = 0.0f; + for (uint32_t i = 0; i < size; ++i) + { + const physx::PxVec3 v = (cutout.vertices[i] - center).multiply(scale); + area += crossZ(prevV, v); + prevV = v; + } + + if (physx::PxAbs(area) < PX_EPS_F32 * PX_EPS_F32) + { + return false; + } + + const bool ccw = area > 0.0f; + + for (uint32_t i = 0; i < size; ++i) + { + nvidia::PolyVert& vert = cutout.convexLoops[0].polyVerts[i]; + vert.index = (uint16_t)(ccw ? i : size - i - 1); + vert.flags = 0; + } + + const float cleanupTolerance2 = square(cleanupTolerance); + + // Find reflex vertices + for (uint32_t i = 0; i < cutout.convexLoops.size();) + { + nvidia::ConvexLoop& loop = cutout.convexLoops[i]; + const uint32_t loopSize = loop.polyVerts.size(); + if (loopSize <= 3) + { + ++i; + continue; + } + uint32_t j = 0; + for (; j < loopSize; ++j) + { + const physx::PxVec3& v0 = cutout.vertices[loop.polyVerts[(j + loopSize - 1) % loopSize].index]; + const physx::PxVec3& v1 = cutout.vertices[loop.polyVerts[j].index]; + const physx::PxVec3& v2 = cutout.vertices[loop.polyVerts[(j + 1) % loopSize].index]; + const physx::PxVec3 e0 = v1 - v0; + if (crossZ(e0, v2 - v1) < 0.0f) + { + // reflex + break; + } + } + if (j < loopSize) + { + // Find a vertex + float minLen2 = PX_MAX_F32; + float maxMinDist = -PX_MAX_F32; + uint32_t kToUse = 0; + uint32_t mToUse = 2; + bool cleanSliceFound = false; // A transversal is parallel with an edge + for (uint32_t k = 0; k < loopSize; ++k) + { + const physx::PxVec3& vkPrev = cutout.vertices[loop.polyVerts[(k + loopSize - 1) % loopSize].index]; + const physx::PxVec3& vk = cutout.vertices[loop.polyVerts[k].index]; + const physx::PxVec3& vkNext = cutout.vertices[loop.polyVerts[(k + 1) % loopSize].index]; + const uint32_t mStop = k ? loopSize : loopSize - 1; + for (uint32_t m = k + 2; m < mStop; ++m) + { + const physx::PxVec3& vmPrev = cutout.vertices[loop.polyVerts[(m + loopSize - 1) % loopSize].index]; + const physx::PxVec3& vm = cutout.vertices[loop.polyVerts[m].index]; + const physx::PxVec3& vmNext = cutout.vertices[loop.polyVerts[(m + 1) % loopSize].index]; + const physx::PxVec3 newEdge = vm - vk; + if (!directionsXYOrderedCCW(vk - vkPrev, newEdge, vkNext - vk) || + !directionsXYOrderedCCW(vm - vmPrev, -newEdge, vmNext - vm)) + { + continue; + } + const float len2 = newEdge.magnitudeSquared(); + float minDist = PX_MAX_F32; + for (uint32_t l = 0; l < loopSize; ++l) + { + const uint32_t l1 = (l + 1) % loopSize; + if (l == k || l1 == k || l == m || l1 == m) + { + continue; + } + const physx::PxVec3& vl = cutout.vertices[loop.polyVerts[l].index]; + const physx::PxVec3& vl1 = cutout.vertices[loop.polyVerts[l1].index]; + const float dist = segmentsIntersectXY(vl, vl1 - vl, vk, newEdge); + if (dist < minDist) + { + minDist = dist; + } + } + if (minDist <= 0.0f) + { + if (minDist > maxMinDist) + { + maxMinDist = minDist; + kToUse = k; + mToUse = m; + } + } + else + { + if (perpendicularDistanceSquared(vkPrev, vk, vm) <= cleanupTolerance2 || + perpendicularDistanceSquared(vk, vm, vmNext) <= cleanupTolerance2) + { + if (!cleanSliceFound) + { + minLen2 = len2; + kToUse = k; + mToUse = m; + } + else + { + if (len2 < minLen2) + { + minLen2 = len2; + kToUse = k; + mToUse = m; + } + } + cleanSliceFound = true; + } + else if (!cleanSliceFound && len2 < minLen2) + { + minLen2 = len2; + kToUse = k; + mToUse = m; + } + } + } + } + nvidia::ConvexLoop& newLoop = cutout.convexLoops.insert(); + nvidia::ConvexLoop& oldLoop = cutout.convexLoops[i]; + newLoop.polyVerts.resize(mToUse - kToUse + 1); + for (uint32_t n = 0; n <= mToUse - kToUse; ++n) + { + newLoop.polyVerts[n] = oldLoop.polyVerts[kToUse + n]; + } + newLoop.polyVerts[mToUse - kToUse].flags = 1; // Mark this vertex (and edge that follows) as a split edge + oldLoop.polyVerts[kToUse].flags = 1; // Mark this vertex (and edge that follows) as a split edge + oldLoop.polyVerts.removeRange(kToUse + 1, (mToUse - (kToUse + 1))); + if (cleanupTolerance > 0.0f) + { + cleanCutout(cutout, i, cleanupTolerance); + cleanCutout(cutout, cutout.convexLoops.size() - 1, cleanupTolerance); + } + } + else + { + if (cleanupTolerance > 0.0f) + { + cleanCutout(cutout, i, cleanupTolerance); + } + ++i; + } + } + + return true; +} + +static void traceRegion(physx::Array<POINT2D>& trace, Map2d<uint32_t>& regions, Map2d<uint8_t>& pathCounts, uint32_t regionIndex, const POINT2D& startPoint) +{ + POINT2D t = startPoint; + trace.reset(); + trace.pushBack(t); + ++pathCounts(t.x, t.y); // Increment path count + // Find initial path direction + int32_t dirN; + for (dirN = 1; dirN < 8; ++dirN) + { + const POINT2D t1 = POINT2D(t.x + taxicabSine(dirN + 2), t.y + taxicabSine(dirN)); + if (regions(t1.x, t1.y) != regionIndex) + { + break; + } + } + bool done = false; + do + { + for (int32_t i = 1; i < 8; ++i) // Skip direction we just came from + { + --dirN; + const POINT2D t1 = POINT2D(t.x + taxicabSine(dirN + 2), t.y + taxicabSine(dirN)); + if (regions(t1.x, t1.y) != regionIndex) + { + if (t1.x == trace[0].x && t1.y == trace[0].y) + { + done = true; + break; + } + trace.pushBack(t1); + t = t1; + ++pathCounts(t.x, t.y); // Increment path count + dirN += 4; + break; + } + } + } + while (!done); +} + +static void createCutoutSet(nvidia::CutoutSetImpl& cutoutSet, const uint8_t* pixelBuffer, uint32_t bufferWidth, uint32_t bufferHeight, float snapThreshold, bool periodic) +{ + cutoutSet.cutouts.reset(); + cutoutSet.periodic = periodic; + cutoutSet.dimensions = physx::PxVec2((float)bufferWidth, (float)bufferHeight); + + if (!periodic) + { + cutoutSet.dimensions[0] += 1.0f; + cutoutSet.dimensions[1] += 1.0f; + } + + if (pixelBuffer == NULL || bufferWidth == 0 || bufferHeight == 0) + { + return; + } + + const int borderPad = periodic ? 0 : 2; // Padded for borders if not periodic + const int originCoord = periodic ? 0 : 1; + + BitMap map(bufferWidth + borderPad, bufferHeight + borderPad, 0); + map.setOrigin((uint32_t)originCoord, (uint32_t)originCoord); + + for (uint32_t y = 0; y < bufferHeight; ++y) + { + for (uint32_t x = 0; x < bufferWidth; ++x) + { + const uint32_t pix = 5033165 * (uint32_t)pixelBuffer[0] + 9898557 * (uint32_t)pixelBuffer[1] + 1845494 * (uint32_t)pixelBuffer[2]; + pixelBuffer += 3; + if ((pix >> 28) != 0) + { + map.set((int32_t)x, (int32_t)y); + } + } + } + + // Add borders if not tiling + if (!periodic) + { + for (int32_t x = -1; x <= (int32_t)bufferWidth; ++x) + { + map.set(x, -1); + map.set(x, (int32_t)bufferHeight); + } + for (int32_t y = -1; y <= (int32_t)bufferHeight; ++y) + { + map.set(-1, y); + map.set((int32_t)bufferWidth, y); + } + } + + // Now search for regions + + // Create a region map + Map2d<uint32_t> regions(bufferWidth + borderPad, bufferHeight + borderPad, 0xFFFFFFFF); // Initially an invalid value + regions.setOrigin((uint32_t)originCoord, (uint32_t)originCoord); + + // Create a path counting map + Map2d<uint8_t> pathCounts(bufferWidth + borderPad, bufferHeight + borderPad, 0); + pathCounts.setOrigin((uint32_t)originCoord, (uint32_t)originCoord); + + // Bump path counts on borders + if (!periodic) + { + for (int32_t x = -1; x <= (int32_t)bufferWidth; ++x) + { + pathCounts(x, -1) = 1; + pathCounts(x, (int32_t)bufferHeight) = 1; + } + for (int32_t y = -1; y <= (int32_t)bufferHeight; ++y) + { + pathCounts(-1, y) = 1; + pathCounts((int32_t)bufferWidth, y) = 1; + } + } + + physx::Array<POINT2D> stack; + physx::Array<POINT2D> traceStarts; + physx::Array< physx::Array<POINT2D>* > traces; + + // Initial fill of region maps and path maps + for (int32_t y = 0; y < (int32_t)bufferHeight; ++y) + { + for (int32_t x = 0; x < (int32_t)bufferWidth; ++x) + { + if (map.read(x-1, y) && !map.read(x, y)) + { + // Found an empty spot next to a filled spot + POINT2D t(x - 1, y); + const uint32_t regionIndex = traceStarts.size(); + traceStarts.pushBack(t); // Save off initial point + traces.insert(); // This must be the same size as traceStarts + traces.back() = (physx::Array<POINT2D>*)PX_ALLOC(sizeof(physx::Array<POINT2D>), PX_DEBUG_EXP("CutoutPoint2DSet")); + new(traces.back()) physx::Array<POINT2D>; + // Flood fill region map + stack.pushBack(POINT2D(x, y)); + do + { + const POINT2D s = stack.back(); + stack.popBack(); + map.set(s.x, s.y); + regions(s.x, s.y) = regionIndex; + POINT2D n; + for (int32_t i = 0; i < 4; ++i) + { + const int32_t i0 = i & 1; + const int32_t i1 = (i >> 1) & 1; + n.x = s.x + i0 - i1; + n.y = s.y + i0 + i1 - 1; + if (!map.read(n.x, n.y)) + { + stack.pushBack(n); + } + } + } + while (stack.size()); + // Trace region + PX_ASSERT(map.read(t.x, t.y)); + physx::Array<POINT2D>& trace = *traces[regionIndex]; + traceRegion(trace, regions, pathCounts, regionIndex, t); + } + } + } + + uint32_t cutoutCount = traces.size(); + + // Now expand regions until the paths completely overlap + bool somePathChanged; + int sanityCounter = 1000; + bool abort = false; + do + { + somePathChanged = false; + for (uint32_t i = 0; i < cutoutCount; ++i) + { + bool pathChanged = false; + physx::Array<POINT2D>& trace = *traces[i]; + for (uint32_t j = 0; j < trace.size(); ++j) + { + const POINT2D& t = trace[j]; + if (pathCounts(t.x, t.y) == 1) + { + regions(t.x, t.y) = i; + pathChanged = true; + } + } + if (pathChanged) + { + // Recalculate cutout + // Decrement pathCounts + for (uint32_t j = 0; j < trace.size(); ++j) + { + const POINT2D& t = trace[j]; + --pathCounts(t.x, t.y); + } + // Erase trace + // Calculate new start point + POINT2D& t = traceStarts[i]; + int stop = (int)cutoutSet.dimensions.x; + while (regions(t.x, t.y) == i) + { + --t.x; + if(--stop < 0) + { + // There is an error; abort + break; + } + } + if(stop < 0) + { + // Release traces and abort + abort = true; + somePathChanged = false; + break; + } + traceRegion(trace, regions, pathCounts, i, t); + somePathChanged = true; + } + } + if (--sanityCounter <= 0) + { + abort = true; + break; + } + } + while (somePathChanged); + + if (abort) + { + for (uint32_t i = 0; i < cutoutCount; ++i) + { + traces[i]->~Array<POINT2D>(); + PX_FREE(traces[i]); + } + cutoutCount = 0; + } + + // Create cutouts + cutoutSet.cutouts.resize(cutoutCount); + for (uint32_t i = 0; i < cutoutCount; ++i) + { + createCutout(cutoutSet.cutouts[i], *traces[i], snapThreshold, bufferWidth, bufferHeight, !cutoutSet.periodic); + } + + simplifyCutoutSetImpl(cutoutSet, snapThreshold, bufferWidth, bufferHeight); + + // Release traces + for (uint32_t i = 0; i < cutoutCount; ++i) + { + traces[i]->~Array<POINT2D>(); + PX_FREE(traces[i]); + } + + // Decompose each cutout in the set into convex loops + uint32_t cutoutSetSize = 0; + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + bool success = decomposeCutoutIntoConvexLoops(cutoutSet.cutouts[i]); + if (success) + { + if (cutoutSetSize != i) + { + cutoutSet.cutouts[cutoutSetSize] = cutoutSet.cutouts[i]; + } + ++cutoutSetSize; + } + } + cutoutSet.cutouts.resize(cutoutSetSize); +} + +class Matrix22 +{ +public: + //! Default constructor + Matrix22() + {} + + //! Construct from two base vectors + Matrix22(const physx::PxVec2& col0, const physx::PxVec2& col1) + : column0(col0), column1(col1) + {} + + //! Construct from float[4] + explicit Matrix22(float values[]): + column0(values[0],values[1]), + column1(values[2],values[3]) + { + } + + //! Copy constructor + Matrix22(const Matrix22& other) + : column0(other.column0), column1(other.column1) + {} + + //! Assignment operator + Matrix22& operator=(const Matrix22& other) + { + column0 = other.column0; + column1 = other.column1; + return *this; + } + + //! Set to identity matrix + static Matrix22 createIdentity() + { + return Matrix22(physx::PxVec2(1,0), physx::PxVec2(0,1)); + } + + //! Set to zero matrix + static Matrix22 createZero() + { + return Matrix22(physx::PxVec2(0.0f), physx::PxVec2(0.0f)); + } + + //! Construct from diagonal, off-diagonals are zero. + static Matrix22 createDiagonal(const physx::PxVec2& d) + { + return Matrix22(physx::PxVec2(d.x,0.0f), physx::PxVec2(0.0f,d.y)); + } + + + //! Get transposed matrix + Matrix22 getTranspose() const + { + const physx::PxVec2 v0(column0.x, column1.x); + const physx::PxVec2 v1(column0.y, column1.y); + + return Matrix22(v0,v1); + } + + //! Get the real inverse + Matrix22 getInverse() const + { + const float det = getDeterminant(); + Matrix22 inverse; + + if(det != 0) + { + const float invDet = 1.0f/det; + + inverse.column0[0] = invDet * column1[1]; + inverse.column0[1] = invDet * (-column0[1]); + + inverse.column1[0] = invDet * (-column1[0]); + inverse.column1[1] = invDet * column0[0]; + + return inverse; + } + else + { + return createIdentity(); + } + } + + //! Get determinant + float getDeterminant() const + { + return column0[0] * column1[1] - column0[1] * column1[0]; + } + + //! Unary minus + Matrix22 operator-() const + { + return Matrix22(-column0, -column1); + } + + //! Add + Matrix22 operator+(const Matrix22& other) const + { + return Matrix22( column0+other.column0, + column1+other.column1); + } + + //! Subtract + Matrix22 operator-(const Matrix22& other) const + { + return Matrix22( column0-other.column0, + column1-other.column1); + } + + //! Scalar multiplication + Matrix22 operator*(float scalar) const + { + return Matrix22(column0*scalar, column1*scalar); + } + + //! Matrix vector multiplication (returns 'this->transform(vec)') + physx::PxVec2 operator*(const physx::PxVec2& vec) const + { + return transform(vec); + } + + //! Matrix multiplication + Matrix22 operator*(const Matrix22& other) const + { + //Rows from this <dot> columns from other + //column0 = transform(other.column0) etc + return Matrix22(transform(other.column0), transform(other.column1)); + } + + // a <op>= b operators + + //! Equals-add + Matrix22& operator+=(const Matrix22& other) + { + column0 += other.column0; + column1 += other.column1; + return *this; + } + + //! Equals-sub + Matrix22& operator-=(const Matrix22& other) + { + column0 -= other.column0; + column1 -= other.column1; + return *this; + } + + //! Equals scalar multiplication + Matrix22& operator*=(float scalar) + { + column0 *= scalar; + column1 *= scalar; + return *this; + } + + //! Element access, mathematical way! + float operator()(unsigned int row, unsigned int col) const + { + return (*this)[col][(int)row]; + } + + //! Element access, mathematical way! + float& operator()(unsigned int row, unsigned int col) + { + return (*this)[col][(int)row]; + } + + // Transform etc + + //! Transform vector by matrix, equal to v' = M*v + physx::PxVec2 transform(const physx::PxVec2& other) const + { + return column0*other.x + column1*other.y; + } + + physx::PxVec2& operator[](unsigned int num) {return (&column0)[num];} + const physx::PxVec2& operator[](unsigned int num) const {return (&column0)[num];} + + //Data, see above for format! + + physx::PxVec2 column0, column1; //the two base vectors +}; + +PX_INLINE bool calculateUVMapping(const nvidia::ExplicitRenderTriangle& triangle, physx::PxMat33& theResultMapping) +{ + physx::PxMat33 rMat; + physx::PxMat33 uvMat; + for (unsigned col = 0; col < 3; ++col) + { + rMat[col] = triangle.vertices[col].position; + uvMat[col] = physx::PxVec3(triangle.vertices[col].uv[0][0], triangle.vertices[col].uv[0][1], 1.0f); + } + + if (uvMat.getDeterminant() == 0.0f) + { + return false; + } + + theResultMapping = rMat*uvMat.getInverse(); + + return true; +} + +static bool calculateUVMapping(nvidia::ExplicitHierarchicalMesh& theHMesh, const physx::PxVec3& theDir, physx::PxMat33& theResultMapping) +{ + physx::PxVec3 cutoutDir( theDir ); + cutoutDir.normalize( ); + + const float cosineThreshold = physx::PxCos(3.141593f / 180); // 1 degree + + nvidia::ExplicitRenderTriangle* triangleToUse = NULL; + float greatestCosine = -PX_MAX_F32; + float greatestArea = 0.0f; // for normals within the threshold + for ( uint32_t partIndex = 0; partIndex < theHMesh.partCount(); ++partIndex ) + { + nvidia::ExplicitRenderTriangle* theTriangles = theHMesh.meshTriangles( partIndex ); + uint32_t triangleCount = theHMesh.meshTriangleCount( partIndex ); + for ( uint32_t tIndex = 0; tIndex < triangleCount; ++tIndex ) + { + nvidia::ExplicitRenderTriangle& theTriangle = theTriangles[tIndex]; + physx::PxVec3 theEdge1 = theTriangle.vertices[1].position - theTriangle.vertices[0].position; + physx::PxVec3 theEdge2 = theTriangle.vertices[2].position - theTriangle.vertices[0].position; + physx::PxVec3 theNormal = theEdge1.cross( theEdge2 ); + float theArea = theNormal.normalize(); // twice the area, but that's ok + + if (theArea == 0.0f) + { + continue; + } + + const float cosine = cutoutDir.dot(theNormal); + + if (cosine < cosineThreshold) + { + if (cosine > greatestCosine && greatestArea == 0.0f) + { + greatestCosine = cosine; + triangleToUse = &theTriangle; + } + } + else + { + if (theArea > greatestArea) + { + greatestArea = theArea; + triangleToUse = &theTriangle; + } + } + } + } + + if (triangleToUse == NULL) + { + return false; + } + + return calculateUVMapping(*triangleToUse, theResultMapping); +} + +namespace nvidia +{ +namespace apex +{ + +PX_INLINE void serialize(physx::PxFileBuf& stream, const PolyVert& p) +{ + stream << p.index << p.flags; +} + +PX_INLINE void deserialize(physx::PxFileBuf& stream, uint32_t version, PolyVert& p) +{ + // original version + PX_UNUSED(version); + stream >> p.index >> p.flags; +} + +PX_INLINE void serialize(physx::PxFileBuf& stream, const ConvexLoop& l) +{ + apex::serialize(stream, l.polyVerts); +} + +PX_INLINE void deserialize(physx::PxFileBuf& stream, uint32_t version, ConvexLoop& l) +{ + // original version + apex::deserialize(stream, version, l.polyVerts); +} + +PX_INLINE void serialize(physx::PxFileBuf& stream, const Cutout& c) +{ + apex::serialize(stream, c.vertices); + apex::serialize(stream, c.convexLoops); +} + +PX_INLINE void deserialize(physx::PxFileBuf& stream, uint32_t version, Cutout& c) +{ + // original version + apex::deserialize(stream, version, c.vertices); + apex::deserialize(stream, version, c.convexLoops); +} + +void CutoutSetImpl::serialize(physx::PxFileBuf& stream) const +{ + stream << (uint32_t)Current; + + apex::serialize(stream, cutouts); +} + +void CutoutSetImpl::deserialize(physx::PxFileBuf& stream) +{ + const uint32_t version = stream.readDword(); + + apex::deserialize(stream, version, cutouts); +} + +} +} // end namespace nvidia::apex + +namespace FractureTools +{ +CutoutSet* createCutoutSet() +{ + return new nvidia::CutoutSetImpl(); +} + +void buildCutoutSet(CutoutSet& cutoutSet, const uint8_t* pixelBuffer, uint32_t bufferWidth, uint32_t bufferHeight, float snapThreshold, bool periodic) +{ + ::createCutoutSet(*(nvidia::CutoutSetImpl*)&cutoutSet, pixelBuffer, bufferWidth, bufferHeight, snapThreshold, periodic); +} + +bool calculateCutoutUVMapping(nvidia::ExplicitHierarchicalMesh& hMesh, const physx::PxVec3& targetDirection, physx::PxMat33& theMapping) +{ + return ::calculateUVMapping(hMesh, targetDirection, theMapping); +} + +bool calculateCutoutUVMapping(const nvidia::ExplicitRenderTriangle& targetDirection, physx::PxMat33& theMapping) +{ + return ::calculateUVMapping(targetDirection, theMapping); +} +} // namespace FractureTools + +#endif + +#ifdef _MANAGED +#pragma managed(pop) +#endif diff --git a/APEX_1.4/shared/internal/src/authoring/Fracturing.cpp b/APEX_1.4/shared/internal/src/authoring/Fracturing.cpp new file mode 100644 index 00000000..76a998da --- /dev/null +++ b/APEX_1.4/shared/internal/src/authoring/Fracturing.cpp @@ -0,0 +1,7349 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + +//#ifdef _MANAGED +//#pragma managed(push, off) +//#endif + +#include <stdarg.h> +#include "Apex.h" +#include "ApexSharedUtils.h" + +#include "authoring/Fracturing.h" +#include "authoring/ApexGSA.h" +#include "PsUserAllocated.h" +#include "ApexRand.h" +#include "PxErrorCallback.h" +#include "PsString.h" +#include "ApexUsingNamespace.h" +#include "PxPlane.h" +#include "PsMathUtils.h" +#include "PsAllocator.h" +#include "ConvexDecomposition.h" +#include "Noise.h" +#include "DestructibleAsset.h" +#include "Link.h" +#include "RenderDebugInterface.h" +#include "PsSort.h" +#include "PsInlineArray.h" +#include "PsBitUtils.h" + +#define INCREMENTAL_GSA 0 + +///////////////////////////////////////////////////////////////////////////// + +#include <stdio.h> + +#ifndef WITHOUT_APEX_AUTHORING +#include "ApexSharedSerialization.h" + + +using namespace nvidia; // !? Need to do this for PX_ALLOCA!? + + +#define MAX_ALLOWED_ESTIMATED_CHUNK_TOTAL 10000 + +#define CUTOUT_MAP_BOUNDS_TOLERANCE 0.0001f +#define MESH_INSTANACE_TOLERANCE 0.025f + +static ApexCSG::BSPBuildParameters +gDefaultBuildParameters; + +static bool gIslandGeneration = false; +static unsigned gMicrogridSize = 65536; +static BSPOpenMode::Enum gMeshMode = BSPOpenMode::Automatic; +static int gVerbosity = 0; + + +static CollisionVolumeDesc getVolumeDesc(const CollisionDesc& collisionDesc, unsigned depth) +{ + return collisionDesc.mDepthCount > 0 ? collisionDesc.mVolumeDescs[PxMin(depth, collisionDesc.mDepthCount-1)] : CollisionVolumeDesc(); +} + +PX_INLINE float extentDistance(float min0, float max0, float min1, float max1) +{ + return PxMax(min0 - max1, min1 - max0); +} + + +static physx::PxMat44 randomRotationMatrix(physx::PxVec3 zAxis, nvidia::QDSRand& rnd) +{ + physx::PxMat44 rot; + zAxis.normalize(); + uint32_t maxDir = physx::PxAbs(zAxis.x) > physx::PxAbs(zAxis.y) ? + (physx::PxAbs(zAxis.x) > physx::PxAbs(zAxis.z) ? 0u : 2u) : + (physx::PxAbs(zAxis.y) > physx::PxAbs(zAxis.z) ? 1u : 2u); + physx::PxVec3 xAxis = physx::PxMat33(physx::PxIdentity)[(maxDir + 1) % 3]; + physx::PxVec3 yAxis = zAxis.cross(xAxis); + yAxis.normalize(); + xAxis = yAxis.cross(zAxis); + + const float angle = rnd.getScaled(-physx::PxPi, physx::PxPi); + const float c = physx::PxCos(angle); + const float s = physx::PxSin(angle); + + rot.column0 = physx::PxVec4(c*xAxis + s*yAxis, 0.0f); + rot.column1 = physx::PxVec4(c*yAxis - s*xAxis, 0.0f); + rot.column2 = physx::PxVec4(zAxis, 0.0f); + rot.column3 = physx::PxVec4(0.0f, 0.0f, 0.0f, 1.0f); + + return rot; +} + +PX_INLINE physx::PxVec3 randomPositionInTriangle(const physx::PxVec3& v1, const physx::PxVec3& v2, const physx::PxVec3& v3, nvidia::QDSRand& rnd) +{ + const physx::PxVec3 d1 = v2 - v1; + const physx::PxVec3 d2 = v3 - v1; + float c1 = rnd.getUnit(); + float c2 = rnd.getUnit(); + const float d = 1.0f - (c1+c2); + if (d < 0.0f) + { + c1 += d; + c2 += d; + } + return v1 + c1*d1 + c2*d2; +} + + +// Used by VoronoiCellPlaneIterator +class ReciprocalSitePairLink : public Link +{ +public: + ReciprocalSitePairLink() : Link(), m_recip(NULL) + { + } + ReciprocalSitePairLink(const ReciprocalSitePairLink& other) : Link() + { + index0 = other.index0; + index1 = other.index1; + plane = other.plane; + m_recip = NULL; + } + ~ReciprocalSitePairLink() + { + remove(); + } + + void setRecip(ReciprocalSitePairLink& recip) + { + PX_ASSERT(m_recip == NULL && recip.m_recip == NULL); + m_recip = &recip; + recip.m_recip = this; + } + + ReciprocalSitePairLink* getRecip() const + { + return m_recip; + } + + ReciprocalSitePairLink* getAdj(uint32_t which) const + { + return static_cast<ReciprocalSitePairLink*>(Link::getAdj(which)); + } + + void remove() + { + if (m_recip) + { + m_recip->m_recip = NULL; + m_recip = NULL; + } + Link::remove(); + } + + uint32_t index0, index1; + physx::PxPlane plane; + +private: + ReciprocalSitePairLink* m_recip; +}; + + +struct SiteMidPlaneIteratorInit +{ + SiteMidPlaneIteratorInit() : first(NULL), stop(NULL) {} + + ReciprocalSitePairLink* first; + ReciprocalSitePairLink* stop; +}; + +class SiteMidPlaneIterator +{ +public: + SiteMidPlaneIterator(const SiteMidPlaneIteratorInit& listBounds) : current(listBounds.first), stop(listBounds.stop) {} + + bool valid() const + { + return current != stop; + } + + void inc() + { + current = current->getAdj(1); + } + + ApexCSG::Plane plane() const + { + const physx::PxPlane& midPlane = current->plane; + ApexCSG::Plane plane(ApexCSG::Dir((ApexCSG::Real)midPlane.n.x, (ApexCSG::Real)midPlane.n.y,(ApexCSG::Real)midPlane.n.z), (ApexCSG::Real)midPlane.d); + plane.normalize(); + return plane; + } + +private: + ReciprocalSitePairLink* current; + ReciprocalSitePairLink* stop; +}; + +class SiteMidPlaneIntersection : public ApexCSG::GSA::StaticConvexPolyhedron<SiteMidPlaneIterator, SiteMidPlaneIteratorInit> +{ +public: + void setPlanes(ReciprocalSitePairLink* first, ReciprocalSitePairLink* stop) + { + m_initValues.first = first; + m_initValues.stop = stop; + } + + void replacePlanes(ReciprocalSitePairLink* first, ReciprocalSitePairLink* stop, const physx::PxPlane& oldFlipPlane, const physx::PxPlane& newFlipPlane) + { + m_initValues.first = first; + m_initValues.stop = stop; +#if INCREMENTAL_GSA + const ApexCSG::Plane oldFlipGSAPlane = ApexCSG::Plane(ApexCSG::Dir((ApexCSG::Real)oldFlipPlane.n.x, (ApexCSG::Real)oldFlipPlane.n.y, (ApexCSG::Real)oldFlipPlane.n.z), (ApexCSG::Real)oldFlipPlane.d); + const ApexCSG::Plane newFlipGSAPlane = ApexCSG::Plane(ApexCSG::Dir((ApexCSG::Real)newFlipPlane.n.x, (ApexCSG::Real)newFlipPlane.n.y, (ApexCSG::Real)newFlipPlane.n.z), (ApexCSG::Real)newFlipPlane.d); + for (int i = 0; i < 4; ++i) + { + if (m_S(0,i) == oldFlipGSAPlane(0) && m_S(1,i) == oldFlipGSAPlane(1) && m_S(2,i) == oldFlipGSAPlane(2) && m_S(3,i) == oldFlipGSAPlane(3)) + { + m_S.setCol(i, -oldFlipGSAPlane); + } + if (m_S(0,i) == newFlipGSAPlane(0) && m_S(1,i) == newFlipGSAPlane(1) && m_S(2,i) == newFlipGSAPlane(2) && m_S(3,i) == newFlipGSAPlane(3)) + { + m_S.setCol(i, -newFlipGSAPlane); + } + } +#else + (void)oldFlipPlane; + (void)newFlipPlane; +#endif + } + + void resetPlanes() + { + m_initValues.first = NULL; + m_initValues.stop = NULL; + } +}; + +// Voronoi decomposition utility +class VoronoiCellPlaneIterator +{ +public: + VoronoiCellPlaneIterator(const physx::PxVec3* sites, uint32_t siteCount, const physx::PxPlane* boundPlanes = NULL, uint32_t boundPlaneCount = 0, uint32_t startSiteIndex = 0); + + bool valid() const + { + return m_valid; + } + + uint32_t cellIndex() const + { + return m_valid ? m_cellIndex : 0xFFFFFFFF; + } + + const physx::PxPlane* cellPlanes() const + { + return m_valid ? m_cellPlanes.begin() : NULL; + } + + uint32_t cellPlaneCount() const + { + return m_valid ? m_cellPlanes.size() : 0; + } + + void inc() + { + if (m_valid) + { + if (m_startPair != &m_listRoot) + { + prepareOutput(); + } + else + { + m_valid = false; + } + } + } + +private: + void prepareOutput(); + + // Input + const physx::PxVec3* m_sites; + uint32_t m_siteCount; + uint32_t m_boundPlaneCount; + + // State and intermediate data + physx::Array<ReciprocalSitePairLink> m_sitePairs; // A symmetric array of site pairs and their bisector planes, in order (i major, j minor), with the diagonal removed + SiteMidPlaneIntersection m_test; // Used to see if a plane is necessary for a cell + ReciprocalSitePairLink* m_startPair; // Current start site pair + ReciprocalSitePairLink m_listRoot; // A stopping node + + // Output + bool m_valid; + uint32_t m_cellIndex; + physx::Array<physx::PxPlane> m_cellPlanes; +}; + +VoronoiCellPlaneIterator::VoronoiCellPlaneIterator(const physx::PxVec3* sites, uint32_t siteCount, const physx::PxPlane* boundPlanes, uint32_t boundPlaneCount, uint32_t startSiteIndex) +{ + m_valid = false; + + if (sites == NULL || startSiteIndex >= siteCount) + { + return; + } + + m_valid = true; + + m_sites = sites; + m_siteCount = siteCount; + m_cellIndex = startSiteIndex; + m_boundPlaneCount = boundPlanes != NULL ? boundPlaneCount : 0; + + // Add the bound planes + m_cellPlanes.reserve(m_boundPlaneCount); + for (uint32_t boundPlaneNum = 0; boundPlaneNum < m_boundPlaneCount; ++boundPlaneNum) + { + m_cellPlanes.pushBack(boundPlanes[boundPlaneNum]); + } + + // This should mean m_siteCount = 1. In this case, there are no planes (besides the bound planes) + if (m_siteCount < 2) + { + m_startPair = &m_listRoot; // Causes termination after one iteration + return; + } + + // Fill in the pairs + m_sitePairs.resize(m_siteCount*(m_siteCount-1)); + uint32_t pairIndex = 0; + for (uint32_t i = 0; i < m_siteCount; ++i) + { + for (uint32_t j = 0; j < m_siteCount; ++j) + { + if (j == i) + { + continue; + } + ReciprocalSitePairLink& pair = m_sitePairs[pairIndex]; + if (j > i) + { + pair.setRecip(m_sitePairs[pairIndex+(j-i)*(m_siteCount-2)+1]); + } + pair.index0 = i; + pair.index1 = j; + pair.plane = physx::PxPlane(0.5f*(m_sites[j] + m_sites[i]), (m_sites[j] - m_sites[i]).getNormalized()); + // Link together into a single loop + if (pairIndex > 0) + { + m_sitePairs[pairIndex-1].setAdj(1, &pair); + } + + ++pairIndex; + } + } + + // Start with the first pair in the array + m_startPair = &m_sitePairs[0]; + + // Create a list root + m_listRoot.setAdj(1, m_startPair); + + // Find first pair with the desired index0 + while (m_startPair->index0 != startSiteIndex && m_startPair != &m_listRoot) + { + m_startPair = m_startPair->getAdj(1); + } + + prepareOutput(); +} + +void VoronoiCellPlaneIterator::prepareOutput() +{ + if (!m_valid) + { + return; + } + + m_cellIndex = m_startPair->index0; + + // Find the first pair with a different first site index, which is our end-marker + ReciprocalSitePairLink* stopPair = m_startPair->getAdj(1); + while (stopPair != &m_listRoot && stopPair->index0 == m_cellIndex) + { + stopPair = stopPair->getAdj(1); + } + + PX_ASSERT(stopPair == &m_listRoot || stopPair->index0 == m_startPair->index0+1); + + // Reset planes (keeping bound planes) + m_cellPlanes.resize(m_boundPlaneCount); + + // Now iterate through this subset of the list, flipping one plane each time + ReciprocalSitePairLink* testPlanePair = m_startPair; + bool firstGSAUse = true; + physx::PxPlane lastPlane; + do + { + ReciprocalSitePairLink* nextPlanePair = testPlanePair->getAdj(1); + testPlanePair->plane = physx::PxPlane(-testPlanePair->plane.n, -testPlanePair->plane.d); // Flip + if (firstGSAUse) + { + m_test.setPlanes(m_startPair, stopPair); +#if INCREMENTAL_GSA + firstGSAUse = false; +#endif + } + else + { + m_test.replacePlanes(m_startPair, stopPair, lastPlane, testPlanePair->plane); + } + lastPlane = testPlanePair->plane; + const bool keep = (1 == ApexCSG::GSA::vs3d_test(m_test)); + testPlanePair->plane = physx::PxPlane(-testPlanePair->plane.n, -testPlanePair->plane.d); // Flip back + if (keep) + { + // This is a bounding plane + m_cellPlanes.pushBack(testPlanePair->plane); + } + else + { + // Flipping this plane results in an empty set intersection. It is non-essential, so remove it + // And its reciprocal + if (testPlanePair->getRecip() == stopPair) + { + stopPair = stopPair->getAdj(1); + } + testPlanePair->getRecip()->remove(); + if (testPlanePair == m_startPair) + { + m_startPair = m_startPair->getAdj(1); + } + testPlanePair->remove(); + } + testPlanePair = nextPlanePair; + } while (testPlanePair != stopPair); + + m_startPair = stopPair; +} + + +PX_INLINE bool segmentOnBorder(const physx::PxVec3& v0, const physx::PxVec3& v1, float width, float height) +{ + return + (v0.x < -0.5f && v1.x < -0.5f) || + (v0.y < -0.5f && v1.y < -0.5f) || + (v0.x >= width - 0.5f && v1.x >= width - 0.5f) || + (v0.y >= height - 0.5f && v1.y >= height - 0.5f); +} + +class Random : public ApexCSG::UserRandom +{ +public: + uint32_t getInt() + { + return m_rnd.nextSeed(); + } + float getReal(float min, float max) + { + return m_rnd.getScaled(min, max); + } + + nvidia::QDSRand m_rnd; +} userRnd; + + +typedef ApexCSG::Vec<float,2> Vec2Float; +typedef ApexCSG::Vec<float,3> Vec3Float; + +// TODO: Provide configurable octave parameter +ApexCSG::PerlinNoise<float, 64, 2, Vec2Float > userPerlin2D(userRnd, 2, 1.5f, 2.5f); +ApexCSG::PerlinNoise<float, 64, 3, Vec3Float > userPerlin3D(userRnd, 2, 1.5f, 2.5f); + +PX_INLINE bool edgesOverlap(const physx::PxVec3& pv00, const physx::PxVec3& pv01, const physx::PxVec3& pv10, const physx::PxVec3& pv11, float eps) +{ + physx::PxVec3 e0 = pv01 - pv00; + physx::PxVec3 e1 = pv11 - pv10; + + if (e0.dot(e1) < 0) + { + return false; + } + + float l0 = e0.normalize(); + e1.normalize(); + + const physx::PxVec3 disp0 = pv10 - pv00; + const physx::PxVec3 disp1 = pv11 - pv00; + + const float d10 = disp0.dot(e0); + + const float d11 = disp1.dot(e0); + + if (d11 < -eps) + { + return false; + } + + if (d10 > l0 + eps) + { + return false; + } + + const float disp02 = disp0.dot(disp0); + if (disp02 - d10 * d10 > eps * eps) + { + return false; + } + + const float disp12 = disp1.dot(disp1); + if (disp12 - d11 * d11 > eps * eps) + { + return false; + } + + return true; +} + +PX_INLINE bool trianglesOverlap(const physx::PxVec3& pv00, const physx::PxVec3& pv01, const physx::PxVec3& pv02, const physx::PxVec3& pv10, const physx::PxVec3& pv11, const physx::PxVec3& pv12, float eps) +{ + return edgesOverlap(pv00, pv02, pv10, pv11, eps) || edgesOverlap(pv00, pv02, pv11, pv12, eps) || edgesOverlap(pv00, pv02, pv12, pv10, eps) || + edgesOverlap(pv01, pv00, pv10, pv11, eps) || edgesOverlap(pv01, pv00, pv11, pv12, eps) || edgesOverlap(pv01, pv00, pv12, pv10, eps) || + edgesOverlap(pv02, pv01, pv10, pv11, eps) || edgesOverlap(pv02, pv01, pv11, pv12, eps) || edgesOverlap(pv02, pv01, pv12, pv10, eps); +} + + +// Returns a point uniformly distributed on the "polar cap" in +axisN direction, of azimuthal size range (in radians) +PX_INLINE physx::PxVec3 randomNormal(uint32_t axisN, float range) +{ + physx::PxVec3 result; + const float cosTheta = 1.0f - (1.0f - physx::PxCos(range)) * userRnd.getReal(0.0f, 1.0f); + const float sinTheta = physx::PxSqrt(1.0f - cosTheta * cosTheta); + float cosPhi, sinPhi; + physx::shdfnd::sincos(userRnd.getReal(-physx::PxPi, physx::PxPi), sinPhi, cosPhi); + result[axisN % 3] = cosTheta; + result[(axisN + 1) % 3] = cosPhi * sinTheta; + result[(axisN + 2) % 3] = sinPhi * sinTheta; + return result; +} + +void calculatePartition(int partition[3], const unsigned requestedSplits[3], const physx::PxVec3& extent, const float* targetProportions) +{ + partition[0] = (int32_t)requestedSplits[0] + 1; + partition[1] = (int32_t)requestedSplits[1] + 1; + partition[2] = (int32_t)requestedSplits[2] + 1; + + if (targetProportions != NULL) + { + physx::PxVec3 n(extent[0] / targetProportions[0], extent[1] / targetProportions[1], extent[2] / targetProportions[2]); + n *= physx::PxVec3((float)partition[0], (float)partition[1], (float)partition[2]).dot(n) / n.magnitudeSquared(); + // n now contains the # of partitions per axis closest to the desired # of partitions + // which give the correct target proportions. However, the numbers will not (in general) + // be integers, so round: + partition[0] = PxMax(1, (int)(n[0] + 0.5f)); + partition[1] = PxMax(1, (int)(n[1] + 0.5f)); + partition[2] = PxMax(1, (int)(n[2] + 0.5f)); + } +} + +static void outputMessage(const char* message, physx::PxErrorCode::Enum errorCode = physx::PxErrorCode::eNO_ERROR, int verbosity = 0) // Lower # = higher priority +{ + if (verbosity > gVerbosity) + { + return; + } + + physx::PxErrorCallback* outputStream = nvidia::GetApexSDK()->getErrorCallback(); + if (outputStream) + { + outputStream->reportError(errorCode, message, __FILE__, __LINE__); + } +} + +struct ChunkIndexer +{ + ExplicitHierarchicalMeshImpl::Chunk* chunk; + int32_t parentIndex; + int32_t index; + + static int compareParentIndices(const void* A, const void* B) + { + const int diff = ((const ChunkIndexer*)A)->parentIndex - ((const ChunkIndexer*)B)->parentIndex; + if (diff) + { + return diff; + } + return ((const ChunkIndexer*)A)->index - ((const ChunkIndexer*)B)->index; + } +}; + +static physx::PxBounds3 boundTriangles(const physx::Array<nvidia::ExplicitRenderTriangle>& triangles, const PxMat44& interiorTM) +{ + physx::PxBounds3 bounds; + bounds.setEmpty(); + for (uint32_t triangleN = 0; triangleN < triangles.size(); ++triangleN) + { + for (int v = 0; v < 3; ++v) + { + physx::PxVec3 localVert = interiorTM.inverseRT().transform(triangles[triangleN].vertices[v].position); + bounds.include(localVert); + } + } + return bounds; +} + +PX_INLINE void generateSliceAxes(uint32_t sliceAxes[3], uint32_t sliceAxisNum) +{ + switch (sliceAxisNum) + { + case 0: + sliceAxes[1] = 2; + sliceAxes[0] = 1; + break; + case 1: + sliceAxes[1] = 2; + sliceAxes[0] = 0; + break; + default: + case 2: + sliceAxes[1] = 1; + sliceAxes[0] = 0; + } + sliceAxes[2] = sliceAxisNum; +} + +PX_INLINE physx::PxVec3 createAxis(uint32_t axisNum) +{ + return physx::PxMat33(physx::PxIdentity)[axisNum]; +} + +PX_INLINE void getCutoutSliceAxisAndSign(uint32_t& sliceAxisNum, uint32_t& sliceSignNum, uint32_t sliceDirIndex) +{ + sliceAxisNum = sliceDirIndex >> 1; + sliceSignNum = sliceDirIndex & 1; +} + +typedef float(*NoiseFn)(float x, float y, float z, float& xGrad, float& yGrad, float& zGrad); + +static float planeWave(float x, float y, float, float& xGrad, float& yGrad, float& zGrad) +{ + float c, s; + physx::shdfnd::sincos(x + y, s, c); + xGrad = c; + yGrad = 0.0f; + zGrad = 0.0f; + return s; +} + +static float perlin2D(float x, float y, float, float& xGrad, float& yGrad, float& zGrad) +{ + const float xy[] = {x, y}; + float s = userPerlin2D.sample(Vec2Float(xy)); + // TODO: Implement + xGrad = 0.0f; + yGrad = 0.0f; + zGrad = 0.0f; + return s; +} + +static float perlin3D(float x, float y, float z, float& xGrad, float& yGrad, float& zGrad) +{ + const float xyz[] = {x, y, z}; + float s = userPerlin3D.sample(Vec3Float(xyz)); + // TODO: Implement + xGrad = 0.0f; + yGrad = 0.0f; + zGrad = 0.0f; + return s; +} + +static NoiseFn noiseFns[] = +{ + planeWave, + perlin2D, + perlin3D +}; + +static int noiseFnCount = sizeof(noiseFns) / sizeof(noiseFns[0]); + +static void buildNoise(physx::Array<float>& f, physx::Array<physx::PxVec3>* n, + nvidia::IntersectMesh::GridPattern pattern, float cornerX, float cornerY, float xSpacing, float ySpacing, uint32_t numX, uint32_t numY, + float noiseAmplitude, float relativeFrequency, float xPeriod, float yPeriod, + int noiseType, int noiseDir) +{ + const uint32_t gridSize = (numX + 1) * (numY + 1); + + if( f.size() != gridSize) + f.resize(gridSize, 0.); + + if( n && n->size() != gridSize) + n->resize(gridSize, physx::PxVec3(0,0,0)); + + noiseType = physx::PxClamp(noiseType, 0 , noiseFnCount - 1); + NoiseFn noiseFn = noiseFns[noiseType]; + + // This differentiation between wave planes and perlin is rather arbitrary, but works alright + const uint32_t numModes = noiseType == FractureSliceDesc::NoiseWavePlane ? 20u : 4u; + const float amplitude = noiseAmplitude / physx::PxSqrt((float)numModes); // Scale by frequency? + for (uint32_t i = 0; i < numModes; ++i) + { + float phase = userRnd.getReal(-3.14159265f, 3.14159265f); + float freqShift = userRnd.getReal(0.0f, 3.0f); + float kx, ky; + switch (noiseDir) + { + case 0: + kx = physx::PxPow(2.0f, freqShift) * relativeFrequency / xSpacing; + ky = 0.0f; + break; + case 1: + kx = 0.0f; + ky = physx::PxPow(2.0f, freqShift) * relativeFrequency / ySpacing; + break; + default: + { + const float f = physx::PxPow(2.0f, freqShift) * relativeFrequency; + const float theta = userRnd.getReal(-3.14159265f, 3.14159265f); + const float c = physx::PxCos(theta); + const float s = physx::PxSin(theta); + kx = c * f / xSpacing; + ky = s * f / ySpacing; + } + } + + if (xPeriod != 0.0f) + { + const float cx = (2.0f * 3.14159265f) / xPeriod; + const int nx = (int)physx::PxSign(kx) * (int)(physx::PxAbs(kx) / cx + 0.5f); // round + kx = nx * cx; + } + + if (yPeriod != 0.0f) + { + // Make sure the wavenumbers are integers + const float cy = (2.0f * 3.14159265f) / yPeriod; + const int ny = (int)physx::PxSign(ky) * (int)(physx::PxAbs(ky) / cy + 0.5f); // round + ky = ny * cy; + } + + uint32_t pointN = 0; + float y = cornerY; + for (uint32_t iy = 0; iy <= numY; ++iy, y += ySpacing) + { + float x = cornerX; + for (uint32_t ix = 0; ix <= numX; ++ix, x += xSpacing, ++pointN) + { + if (pattern == nvidia::IntersectMesh::Equilateral && (((iy & 1) == 0 && ix == numX) || ((iy & 1) != 0 && ix == 1))) + { + x -= 0.5f * xSpacing; + } + float xGrad, yGrad, zGrad; + // TODO: Find point in 3D space for use with NoisePerlin3D + f[pointN] += amplitude * noiseFn(x * kx - phase, y * ky - phase, 0, xGrad, yGrad, zGrad); + if (n) (*n)[pointN] += physx::PxVec3(-xGrad * kx * amplitude, -yGrad * ky * amplitude, 0.0f); + } + } + } + +} + +// noiseDir = 0 => X +// noiseDir = 1 => Y +// noiseDir = -1 => userRnd +void nvidia::IntersectMesh::build(GridPattern pattern, const physx::PxPlane& plane, + float cornerX, float cornerY, float xSpacing, float ySpacing, uint32_t numX, uint32_t numY, + const PxMat44& tm, float noiseAmplitude, float relativeFrequency, float xPeriod, float yPeriod, + int noiseType, int noiseDir, uint32_t submeshIndex, uint32_t frameIndex, const nvidia::TriangleFrame& triangleFrame, bool forceGrid) +{ + m_pattern = pattern; + m_plane = plane; + m_cornerX = cornerX; + m_cornerY = cornerY; + m_xSpacing = xSpacing; + m_ySpacing = ySpacing; + m_numX = numX; + m_numY = numY; + m_tm = tm; + + if (relativeFrequency == 0.0f) + { + // 0 frequency only provides a plane offset + m_plane.d += userRnd.getReal(-noiseAmplitude, noiseAmplitude); + noiseAmplitude = 0.0f; + } + + if (!forceGrid && noiseAmplitude == 0.0f) + { + // Without noise, we only need one triangle + m_pattern = Equilateral; + m_vertices.resize(3); + m_triangles.resize(1); + + const float rX = 0.5f * (xSpacing * numX); + const float rY = 0.5f * (ySpacing * numY); + const float centerX = cornerX + rX; + const float centerY = cornerY + rY; + + // Circumscribe rectangle + const float R = physx::PxSqrt(rX * rX + rY * rY); + + // Fit equilateral triangle around circle + const float x = 1.73205081f * R; + m_vertices[0].position = tm.transform(physx::PxVec3(centerX, centerY + 2 * R, 0)); + m_vertices[1].position = tm.transform(physx::PxVec3(centerX - x, centerY - R, 0)); + m_vertices[2].position = tm.transform(physx::PxVec3(centerX + x, centerY - R, 0)); + + for (uint32_t i = 0; i < 3; ++i) + { + m_vertices[i].normal = m_plane.n; + m_vertices[i].tangent = tm.column0.getXYZ(); + m_vertices[i].binormal = tm.column1.getXYZ(); + m_vertices[i].color = ColorRGBA(255, 255, 255, 255); + } + + ExplicitRenderTriangle& triangle = m_triangles[0]; + for (uint32_t v = 0; v < 3; ++v) + { + Vertex& gridVertex = m_vertices[v]; + triangle.vertices[v] = gridVertex; + triangleFrame.interpolateVertexData(triangle.vertices[v]); + // Only really needed to interpolate u,v... replace normals and tangents with proper ones + triangle.vertices[v].normal = gridVertex.normal; + triangle.vertices[v].tangent = gridVertex.tangent; + triangle.vertices[v].binormal = gridVertex.binormal; + } + triangle.extraDataIndex = frameIndex; + triangle.smoothingMask = 0; + triangle.submeshIndex = (int32_t)submeshIndex; + + return; + } + + /////////////////////////////////////////////////////////////////////////// + + physx::PxVec3 corner = m_tm.transform(physx::PxVec3(m_cornerX, m_cornerY, 0)); + const physx::PxVec3 localX = m_tm.column0.getXYZ() * m_xSpacing; + const physx::PxVec3 localY = m_tm.column1.getXYZ() * m_ySpacing; + const physx::PxVec3 localZ = m_tm.column2.getXYZ(); + + // Vertices: + m_vertices.resize((m_numX + 1) * (m_numY + 1)); + const physx::PxVec3 halfLocalX = 0.5f * localX; + uint32_t pointN = 0; + physx::PxVec3 side = corner; + for (uint32_t iy = 0; iy <= m_numY; ++iy, side += localY) + { + physx::PxVec3 point = side; + for (uint32_t ix = 0; ix <= m_numX; ++ix, point += localX) + { + if (m_pattern == nvidia::IntersectMesh::Equilateral && (((iy & 1) == 0 && ix == m_numX) || ((iy & 1) != 0 && ix == 1))) + { + point -= halfLocalX; + } + Vertex& vertex = m_vertices[pointN++]; + vertex.position = point; + vertex.normal = physx::PxVec3(0.0f); + } + } + + // Build noise + physx::Array<float> f(m_vertices.size(), 0.); + physx::Array<physx::PxVec3> n(m_vertices.size(), physx::PxVec3(0,0,0)); + buildNoise(f, &n, pattern, m_cornerX, m_cornerY, m_xSpacing, m_ySpacing, m_numX, m_numY, + noiseAmplitude, relativeFrequency, xPeriod, yPeriod, noiseType, noiseDir); + pointN = 0; + for (uint32_t iy = 0; iy <= m_numY; ++iy) + { + for (uint32_t ix = 0; ix <= m_numX; ++ix, ++pointN) + { + Vertex& vertex = m_vertices[pointN]; + vertex.position += localZ * f[pointN]; + vertex.normal += n[pointN]; + } + } + + // Normalize normals and put in correct frame + for (pointN = 0; pointN < m_vertices.size(); pointN++) + { + Vertex& vertex = m_vertices[pointN]; + vertex.normal.z = 1.0f; + vertex.normal.normalize(); + vertex.normal = m_tm.rotate(vertex.normal); + vertex.tangent = m_tm.column1.getXYZ().cross(vertex.normal); + vertex.tangent.normalize(); + vertex.color = ColorRGBA(255, 255, 255, 255); + vertex.binormal = vertex.normal.cross(vertex.tangent); + } + + m_triangles.resize(2 * m_numX * m_numY); + uint32_t triangleN = 0; + uint32_t index = 0; + const uint32_t tpattern[12] = { 0, m_numX + 2, m_numX + 1, 0, 1, m_numX + 2, 0, 1, m_numX + 1, 1, m_numX + 2, m_numX + 1 }; + for (uint32_t iy = 0; iy < m_numY; ++iy) + { + const uint32_t* yPattern = tpattern + (iy & 1) * 6; + for (uint32_t ix = 0; ix < m_numX; ++ix, ++index) + { + const uint32_t* ytPattern = yPattern; + for (uint32_t it = 0; it < 2; ++it, ytPattern += 3) + { + ExplicitRenderTriangle& triangle = m_triangles[triangleN++]; + for (uint32_t v = 0; v < 3; ++v) + { + Vertex& gridVertex = m_vertices[index + ytPattern[v]]; + triangle.vertices[v] = gridVertex; + triangleFrame.interpolateVertexData(triangle.vertices[v]); + // Only really needed to interpolate u,v... replace normals and tangents with proper ones + triangle.vertices[v].normal = gridVertex.normal; + triangle.vertices[v].tangent = gridVertex.tangent; + triangle.vertices[v].binormal = gridVertex.binormal; + } + triangle.extraDataIndex = frameIndex; + triangle.smoothingMask = 0; + triangle.submeshIndex = (int32_t)submeshIndex; + } + } + ++index; + } +} + +static const int gSliceDirs[6][3] = +{ + {0, 1, 2}, // XYZ + {1, 2, 0}, // YZX + {2, 0, 1}, // ZXY + {2, 1, 0}, // ZYX + {1, 0, 2}, // YXZ + {0, 2, 1} // XZY +}; + +struct GridParameters +{ + GridParameters() : + sizeScale(1.0f), + xPeriod(0.0f), + yPeriod(0.0f), + interiorSubmeshIndex(0xFFFFFFFF), + materialFrameIndex(0xFFFFFFFF), + forceGrid(false) + { + } + + physx::Array< nvidia::ExplicitRenderTriangle >* level0Mesh; + float sizeScale; + nvidia::NoiseParameters noise; + float xPeriod; + float yPeriod; + uint32_t interiorSubmeshIndex; + uint32_t materialFrameIndex; + nvidia::TriangleFrame triangleFrame; + bool forceGrid; +}; + + +////////////////////////////////////////////////////////////////////////////// + +static PX_INLINE uint32_t nearestPowerOf2(uint32_t v) +{ + v = v > 0 ? v - 1 : 0; + + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + ++v; + + return v; +} + +nvidia::DisplacementMapVolumeImpl::DisplacementMapVolumeImpl() +: width(0), + height(0), + depth(0) +{ + +} + +void nvidia::DisplacementMapVolumeImpl::init(const FractureSliceDesc& desc) +{ + PX_UNUSED(desc); + + // Compute the number of slices for each plane + uint32_t slices[3]; + slices[0] = slices[1] = slices[2] = 0; + uint32_t maxGridSize = 0; + for (uint32_t i = 0; i < 3; ++i) + { + for (uint32_t j = 1; j < desc.maxDepth; ++j) + { + if (slices[i] == 0) + slices[i] = desc.sliceParameters[j].splitsPerPass[i]; + else + slices[i] *= desc.sliceParameters[j].splitsPerPass[i]; + } + for (uint32_t j = 0; j < desc.maxDepth; ++j) + { + if (desc.sliceParameters[j].noise[i].gridSize > (int)maxGridSize) + maxGridSize = (uint32_t)desc.sliceParameters[j].noise[i].gridSize; + } + } + + width = 4 * nearestPowerOf2(PxMax(maxGridSize, PxMax(slices[0], slices[1]))); + height = width; + depth = 4 * nearestPowerOf2(PxMax(maxGridSize, slices[2])); +} + + +void nvidia::DisplacementMapVolumeImpl::getData(uint32_t& w, uint32_t& h, uint32_t& d, uint32_t& size, unsigned char const** ppData) const +{ + PX_ASSERT(ppData); + if(data.size() == 0) + buildData(); + + w = width; + h = height; + d = depth; + size = data.size(); + *ppData = data.begin(); +} + +template<typename T, typename U> +class Conversion +{ +public: + static PX_INLINE U convert(T x) + { + return (U)physx::PxClamp(x, (T)-1, (T)1); + } +}; + +template<typename T> +class Conversion<T, unsigned char> +{ +public: + static PX_INLINE unsigned char convert(T x) + { + unsigned char value = (unsigned char)((physx::PxClamp(x, (T)-1, (T)1) + 1) * .5 * 255); + return value; + } +}; + +void nvidia::DisplacementMapVolumeImpl::buildData(physx::PxVec3 scale) const +{ + // For now, we forgo use of the scaling parameter + PX_UNUSED(scale); + + const uint32_t numChannels = 4; // ZYX -> BGRA + const uint32_t channelSize = sizeof(unsigned char); + const uint32_t stride = numChannels * channelSize; + const uint32_t size = width * depth * height * stride; + data.resize(size); + + const float dX = width > 1 ? 1.0f/(width - 1) : 0.0f; + const float dY = height > 1 ? 1.0f/(height- 1) : 0.0f; + const float dZ = depth > 1 ? 1.0f/(depth - 1) : 0.0f; + + uint32_t index = 0; + float z = 0.0f; + for (uint32_t i = 0; i < depth; ++i, z+=dZ) + { + float y = 0.0f; + for (uint32_t j = 0; j < height; ++j, y+=dY) + { + float x = 0.0f; + for (uint32_t k = 0; k < width; ++k, x+=dX, index+=stride) + { + const float xyz[] = {x, y ,z}; + const float yzx[] = {y, z, x}; + + // Random offsets in x and y, with the z offset as a combination of the two + // As long as we're consistent here in our ordering, noise will be a smooth vector function of position + float xOffset = userPerlin3D.sample(Vec3Float(xyz)); + float yOffset = userPerlin3D.sample(Vec3Float(yzx)); + float zOffset = (xOffset + yOffset) * 0.5f; + + // ZXY -> RGB + data[index] = Conversion<float, unsigned char>::convert(zOffset); + data[index+1] = Conversion<float, unsigned char>::convert(yOffset); + data[index+2] = Conversion<float, unsigned char>::convert(xOffset); + } + } + } +} + +////////////////////////////////////////////////////////////////////////////// + +static void buildIntersectMesh(nvidia::IntersectMesh& mesh, + const physx::PxPlane& plane, + const nvidia::MaterialFrame& materialFrame, + int noiseType = FractureSliceDesc::NoiseWavePlane, + const GridParameters* gridParameters = NULL) +{ + if (!gridParameters) + { + mesh.build(plane); + return; + } + + PxMat44 tm = materialFrame.mCoordinateSystem; + + physx::PxBounds3 localPlaneBounds = boundTriangles(*gridParameters->level0Mesh, tm); + + const physx::PxVec3 diameter = localPlaneBounds.maximum - localPlaneBounds.minimum; + const float planeDiameter = PxMax(diameter.x, diameter.y); + // No longer fattening - the BSP does not have side boundaries, so we will not shave off any of the mesh. + // localPlaneBounds.fatten( 0.005f*planeDiameter ); // To ensure we get the whole mesh + const float gridSpacing = planeDiameter / gridParameters->noise.gridSize; + + physx::PxVec3 center = localPlaneBounds.getCenter(); + physx::PxVec3 extent = localPlaneBounds.getExtents(); + +#if 0 // Equilateral + const float offset = 0.5f; + const float yRatio = 0.866025404f; + const nvidia::IntersectMesh::GridPattern pattern = nvidia::IntersectMesh::Equilateral; + const float xSpacing = gridSpacing; + const float numX = physx::PxCeil(2 * extent.x / xSpacing + offset); + const float cornerX = center.x - 0.5f * (numX - offset) * xSpacing; + const float ySpacing = yRatio * gridSpacing; + const float numY = physx::PxCeil(2 * extent.y / ySpacing); + const float cornerY = center.y - 0.5f * numY * ySpacing; +#else // Right + const nvidia::IntersectMesh::GridPattern pattern = nvidia::IntersectMesh::Right; + const float numX = gridParameters->xPeriod != 0.0f ? gridParameters->noise.gridSize : physx::PxCeil(2 * extent.x / gridSpacing); + const float xSpacing = 2 * extent.x / numX; + const float cornerX = center.x - extent.x; + const float numY = gridParameters->yPeriod != 0.0f ? gridParameters->noise.gridSize : physx::PxCeil(2 * extent.y / gridSpacing); + const float ySpacing = 2 * extent.y / numY; + const float cornerY = center.y - extent.y; +#endif + + const float noiseAmplitude = gridParameters->sizeScale * gridParameters->noise.amplitude; + + mesh.build(pattern, plane, cornerX, cornerY, xSpacing, ySpacing, (uint32_t)numX, (uint32_t)numY, tm, + noiseAmplitude, gridParameters->noise.frequency, gridParameters->xPeriod, gridParameters->yPeriod, noiseType, -1, + gridParameters->interiorSubmeshIndex, gridParameters->materialFrameIndex, gridParameters->triangleFrame, gridParameters->forceGrid); +} + +PX_INLINE physx::PxPlane createSlicePlane(const physx::PxVec3& center, const physx::PxVec3& extent, int sliceDir, int sliceDirNum, + const float sliceWidths[3], const float linearNoise[3], const float angularNoise[3]) +{ + // Orient the plane (+apply the angular noise) and compute the d parameter (+apply the linear noise) + physx::PxVec3 slicePoint = center; + slicePoint[(unsigned)sliceDir] += (sliceDirNum + 1) * sliceWidths[(unsigned)sliceDir] - extent[(unsigned)sliceDir]; + const physx::PxVec3 normal = randomNormal((uint32_t)sliceDir, angularNoise[(unsigned)sliceDir]); + return physx::PxPlane(normal, -normal.dot(slicePoint) + sliceWidths[(unsigned)sliceDir] * linearNoise[(unsigned)sliceDir] * userRnd.getReal(-0.5f, 0.5f)); +} + +static void buildSliceBSP(ApexCSG::IApexBSP& sliceBSP, ExplicitHierarchicalMeshImpl& hMesh, const nvidia::NoiseParameters& noise, + const physx::PxVec3& extent, int sliceDir, int sliceDepth, const physx::PxPlane planes[3], + const nvidia::FractureMaterialDesc& materialDesc, int noiseType = FractureSliceDesc::NoiseWavePlane, bool useDisplacementMaps = false) +{ + // Build grid and slice BSP + nvidia::IntersectMesh grid; + GridParameters gridParameters; + gridParameters.interiorSubmeshIndex = materialDesc.interiorSubmeshIndex; + // Defer noise generation if we're using displacement maps + gridParameters.noise = useDisplacementMaps ? nvidia::NoiseParameters() : noise; + gridParameters.level0Mesh = &hMesh.mParts[0]->mMesh; + gridParameters.sizeScale = extent[(unsigned)sliceDir]; + gridParameters.materialFrameIndex = hMesh.addMaterialFrame(); + nvidia::MaterialFrame materialFrame = hMesh.getMaterialFrame(gridParameters.materialFrameIndex); + materialFrame.buildCoordinateSystemFromMaterialDesc(materialDesc, planes[(unsigned)sliceDir]); + materialFrame.mFractureMethod = nvidia::FractureMethod::Slice; + materialFrame.mFractureIndex = sliceDir; + materialFrame.mSliceDepth = (uint32_t)sliceDepth; + hMesh.setMaterialFrame(gridParameters.materialFrameIndex, materialFrame); + gridParameters.triangleFrame.setFlat(materialFrame.mCoordinateSystem, materialDesc.uvScale, materialDesc.uvOffset); + buildIntersectMesh(grid, planes[(unsigned)sliceDir], materialFrame, noiseType, &gridParameters); + + ApexCSG::BSPTolerances bspTolerances = ApexCSG::gDefaultTolerances; + bspTolerances.linear = 1.e-9f; + bspTolerances.angular = 0.00001f; + sliceBSP.setTolerances(bspTolerances); + + ApexCSG::BSPBuildParameters bspBuildParams = gDefaultBuildParameters; + bspBuildParams.rnd = &userRnd; + bspBuildParams.internalTransform = sliceBSP.getInternalTransform(); + + if(useDisplacementMaps) + { + // Displacement map generation is deferred until the end of fracturing + // This used to be where a slice would populate a displacement map with + // offsets along the plane, but no longer + } + + sliceBSP.fromMesh(&grid.m_triangles[0], grid.m_triangles.size(), bspBuildParams); +} + +PX_INLINE ApexCSG::IApexBSP* createFractureBSP(physx::PxPlane slicePlanes[3], ApexCSG::IApexBSP*& sliceBSP, ApexCSG::IApexBSP& sourceBSP, + ExplicitHierarchicalMeshImpl& hMesh, float& childVolume, float minVolume, + const physx::PxVec3& center, const physx::PxVec3& extent, int sliceDir, int sliceDirNum, int sliceDepth, + const float sliceWidths[3], const float linearNoise[3], const float angularNoise[3], + const nvidia::NoiseParameters& noise, const nvidia::FractureMaterialDesc& materialDesc, int noiseType, bool useDisplacementMaps) +{ + const physx::PxPlane oldSlicePlane = slicePlanes[sliceDir]; + slicePlanes[sliceDir] = createSlicePlane(center, extent, sliceDir, sliceDirNum, sliceWidths, linearNoise, angularNoise); + if (sliceBSP == NULL) + { + sliceBSP = createBSP(hMesh.mBSPMemCache, sourceBSP.getInternalTransform()); + buildSliceBSP(*sliceBSP, hMesh, noise, extent, sliceDir, sliceDepth, slicePlanes, materialDesc, noiseType, useDisplacementMaps); + } + sourceBSP.combine(*sliceBSP); + ApexCSG::IApexBSP* bsp = createBSP(hMesh.mBSPMemCache, sourceBSP.getInternalTransform()); + bsp->op(sourceBSP, ApexCSG::Operation::Intersection); +#if 1 // Eliminating volume calculation here, for performance. May introduce it later once the mesh is calculated. + sourceBSP.op(sourceBSP, ApexCSG::Operation::A_Minus_B); + if (minVolume <= 0 || (bsp->getType() != ApexCSG::BSPType::Empty_Set && sourceBSP.getType() != ApexCSG::BSPType::Empty_Set)) + { + childVolume = 1.0f; + } + else + { + // We will ignore this slice + if (sourceBSP.getType() != ApexCSG::BSPType::Empty_Set) + { + // chunk bsp volume too small + slicePlanes[sliceDir] = oldSlicePlane; + bsp->release(); + bsp = NULL; + childVolume = 0.0f; + } + else + { + // remainder is too small. Terminate slicing along this direction + childVolume = 1.0f; + } + } +#else + float bspArea, bspVolume; + bsp->getSurfaceAreaAndVolume(bspArea, bspVolume, true); + float remainingBSPArea, remainingBSPVolume; + sourceBSP.getSurfaceAreaAndVolume(remainingBSPArea, remainingBSPVolume, true, ApexCSG::Operation::A_Minus_B); + if (minVolume <= 0 || (bspVolume >= minVolume && remainingBSPVolume >= minVolume)) + { + sourceBSP.op(sourceBSP, ApexCSG::Operation::A_Minus_B); + childVolume = bspVolume; + } + else + { + // We will ignore this slice + if (remainingBSPVolume >= minVolume) + { + // chunk bsp volume too small + slicePlanes[sliceDir] = oldSlicePlane; + bsp->release(); + bsp = NULL; + sourceBSP.op(sourceBSP, ApexCSG::Operation::Set_A); + childVolume = 0.0f; + } + else + { + // remainder is too small. Terminate slicing along this direction + bsp->op(sourceBSP, ApexCSG::Operation::Set_A); + sourceBSP.op(sourceBSP, ApexCSG::Operation::Empty_Set); + childVolume = bspVolume + remainingBSPVolume; + } + } +#endif + return bsp; +} + +static bool hierarchicallySplitChunkInternal +( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t chunkIndex, + uint32_t relativeSliceDepth, + physx::PxPlane chunkTrailingPlanes[3], + physx::PxPlane chunkLeadingPlanes[3], + const ApexCSG::IApexBSP& chunkBSP, + float chunkVolume, + const nvidia::FractureSliceDesc& desc, + const CollisionDesc& collisionDesc, + nvidia::IProgressListener& progressListener, + volatile bool* cancel + ); + +static bool createChunk +( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t chunkIndex, + uint32_t relativeSliceDepth, + physx::PxPlane trailingPlanes[3], + physx::PxPlane leadingPlanes[3], + float chunkVolume, + const nvidia::FractureSliceDesc& desc, + const ApexCSG::IApexBSP& parentBSP, + const ApexCSG::IApexBSP& fractureBSP, + const nvidia::SliceParameters& sliceParameters, + const CollisionDesc& collisionDesc, + nvidia::IProgressListener& progressListener, + volatile bool* cancel +) +{ + bool canceled = false; + ApexCSG::IApexBSP* chunkBSP = createBSP(hMesh.mBSPMemCache); +#if 0 + chunkBSP->copy(parentBSP); + chunkBSP->combine(fractureBSP); +#else + chunkBSP->copy(fractureBSP); + chunkBSP->combine(parentBSP); +#endif + chunkBSP->op(*chunkBSP, ApexCSG::Operation::Intersection); + + if (chunkBSP->getType() == ApexCSG::BSPType::Empty_Set) + { + return true; + } + + if (gIslandGeneration) + { + chunkBSP = chunkBSP->decomposeIntoIslands(); + } + + CollisionVolumeDesc volumeDesc = getVolumeDesc(collisionDesc, hMesh.depth(chunkIndex)+1); + + const physx::PxVec3 minimumExtents = hMesh.chunkBounds(0).getExtents().multiply(physx::PxVec3(desc.minimumChunkSize[0], desc.minimumChunkSize[1], desc.minimumChunkSize[2])); + + while (chunkBSP != NULL) + { + if (!canceled) + { + // Create a mesh with chunkBSP (or its islands) + const uint32_t newPartIndex = hMesh.addPart(); + const uint32_t newChunkIndex = hMesh.addChunk(); + chunkBSP->toMesh(hMesh.mParts[newPartIndex]->mMesh); + hMesh.buildMeshBounds(newPartIndex); + hMesh.buildCollisionGeometryForPart(newPartIndex, volumeDesc); + hMesh.mChunks[newChunkIndex]->mParentIndex = (int32_t)chunkIndex; + hMesh.mChunks[newChunkIndex]->mPartIndex = (int32_t)newPartIndex; + if (hMesh.mParts[(uint32_t)hMesh.mChunks[chunkIndex]->mPartIndex]->mFlags & ExplicitHierarchicalMeshImpl::Part::MeshOpen) + { + hMesh.mParts[newPartIndex]->mFlags |= ExplicitHierarchicalMeshImpl::Part::MeshOpen; + } + // Trim hull in directions where splitting is noisy + for (uint32_t i = 0; i < 3; ++i) + { + if ((sliceParameters.noise[i].amplitude != 0.0f || volumeDesc.mHullMethod != nvidia::ConvexHullMethod::WRAP_GRAPHICS_MESH) && + volumeDesc.mHullMethod != nvidia::ConvexHullMethod::CONVEX_DECOMPOSITION) + { + for (uint32_t hullIndex = 0; hullIndex < hMesh.mParts[newPartIndex]->mCollision.size(); ++hullIndex) + { + PartConvexHullProxy& hull = *hMesh.mParts[newPartIndex]->mCollision[hullIndex]; + float min, max; + hull.impl.extent(min, max, trailingPlanes[i].n); + if (max > min) + { + physx::PxPlane clipPlane = trailingPlanes[i]; + clipPlane.d = PxMin(clipPlane.d, -(0.8f * (max - min) + min)); // 20% clip bound + hull.impl.intersectPlaneSide(clipPlane); + } + hull.impl.extent(min, max, leadingPlanes[i].n); + if (max > min) + { + physx::PxPlane clipPlane = leadingPlanes[i]; + clipPlane.d = PxMin(clipPlane.d, -(0.8f * (max - min) + min)); // 20% clip bound + hull.impl.intersectPlaneSide(clipPlane); + } + } + } + } + if (hMesh.mParts[newPartIndex]->mMesh.size() > 0 && hMesh.mParts[newPartIndex]->mCollision.size() > 0 && // We have a mesh and collision hulls + (hMesh.chunkBounds(newChunkIndex).getExtents() - minimumExtents).minElement() >= 0.0f) // Chunk is large enough + { + // Proper chunk + hMesh.mParts[newPartIndex]->mMeshBSP->copy(*chunkBSP); + if (relativeSliceDepth < desc.maxDepth) + { + // Recurse + canceled = !hierarchicallySplitChunkInternal(hMesh, newChunkIndex, relativeSliceDepth, trailingPlanes, leadingPlanes, *chunkBSP, chunkVolume, desc, collisionDesc, progressListener, cancel); + } + } + else + { + // No mesh, no colision, or too small. Eliminate. + hMesh.removeChunk(newChunkIndex); + hMesh.removePart(newPartIndex); + } + } + if (chunkBSP == &parentBSP) + { + // No islands were generated; break from loop + break; + } + // Get next bsp in island decomposition + ApexCSG::IApexBSP* nextBSP = chunkBSP->getNext(); + chunkBSP->release(); + chunkBSP = nextBSP; + } + + return !canceled; +} + +static bool hierarchicallySplitChunkInternal +( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t chunkIndex, + uint32_t relativeSliceDepth, + physx::PxPlane chunkTrailingPlanes[3], + physx::PxPlane chunkLeadingPlanes[3], + const ApexCSG::IApexBSP& chunkBSP, + float chunkVolume, + const nvidia::FractureSliceDesc& desc, + const CollisionDesc& collisionDesc, + nvidia::IProgressListener& progressListener, + volatile bool* cancel +) +{ + if (relativeSliceDepth >= desc.maxDepth) + { + return true; // No slice parameters at this depth + } + + const physx::PxBounds3 bounds = hMesh.chunkBounds(chunkIndex); + + if (chunkIndex >= hMesh.chunkCount() || bounds.isEmpty()) + { + return true; // Done, nothing in chunk + } + + bool canceled = false; // our own copy of *cancel + + physx::PxVec3 center = bounds.getCenter(); + physx::PxVec3 extent = bounds.getExtents(); + + if (relativeSliceDepth == 0) + { + chunkTrailingPlanes[0] = physx::PxPlane(-1, 0, 0, bounds.minimum[0]); + chunkTrailingPlanes[1] = physx::PxPlane(0, -1, 0, bounds.minimum[1]); + chunkTrailingPlanes[2] = physx::PxPlane(0, 0, -1, bounds.minimum[2]); + chunkLeadingPlanes[0] = physx::PxPlane(1, 0, 0, -bounds.maximum[0]); + chunkLeadingPlanes[1] = physx::PxPlane(0, 1, 0, -bounds.maximum[1]); + chunkLeadingPlanes[2] = physx::PxPlane(0, 0, 1, -bounds.maximum[2]); + } + + // Get parameters for this depth + const nvidia::SliceParameters& sliceParameters = desc.sliceParameters[relativeSliceDepth++]; + + // Determine slicing at this level + int partition[3]; + calculatePartition(partition, sliceParameters.splitsPerPass, extent, desc.useTargetProportions ? desc.targetProportions : NULL); + + // Slice volume rejection ratio, perhaps should be exposed + const float volumeRejectionRatio = 0.1f; + // Resulting slices must have at least this volume + const float minChildVolume = volumeRejectionRatio * chunkVolume / (partition[0] * partition[1] * partition[2]); + + const bool slicingThrough = sliceParameters.order >= 6; + + const uint32_t sliceDirOrder = slicingThrough ? 0u : (uint32_t)sliceParameters.order; + const uint32_t sliceDir0 = (uint32_t)gSliceDirs[sliceDirOrder][0]; + const uint32_t sliceDir1 = (uint32_t)gSliceDirs[sliceDirOrder][1]; + const uint32_t sliceDir2 = (uint32_t)gSliceDirs[sliceDirOrder][2]; + const float sliceWidths[3] = { 2.0f * extent[0] / partition[0], 2.0f * extent[1] / partition[1], 2.0f * extent[2] / partition[2] }; + + nvidia::HierarchicalProgressListener localProgressListener(PxMax(partition[0]*partition[1]*partition[2], 1), &progressListener); + + // If we are slicing through, then we need to cache the slice BSPs in the 2nd and 3rd directions + physx::Array<ApexCSG::IApexBSP*> sliceBSPs1; + physx::Array<ApexCSG::IApexBSP*> sliceBSPs2; + if (slicingThrough) + { + sliceBSPs1.resize((uint32_t)partition[(uint32_t)gSliceDirs[sliceDirOrder][1]] - 1, NULL); + sliceBSPs2.resize((uint32_t)partition[(uint32_t)gSliceDirs[sliceDirOrder][2]] - 1, NULL); + } + + // If we are not slicingb through, we can re-use this sliceBSP + ApexCSG::IApexBSP* reusedSliceBSP = NULL; + + physx::PxPlane trailingPlanes[3]; + physx::PxPlane leadingPlanes[3]; + + float childVolume = 0.0f; + + ApexCSG::IApexBSP* fractureBSP0 = createBSP(hMesh.mBSPMemCache, chunkBSP.getInternalTransform()); + + const int sliceDepth = (int)hMesh.depth(chunkIndex) + 1; + + trailingPlanes[sliceDir0] = chunkTrailingPlanes[sliceDir0]; + leadingPlanes[sliceDir0] = physx::PxPlane(-trailingPlanes[sliceDir0].n, -trailingPlanes[sliceDir0].d); + for (int sliceDir0Num = 0; sliceDir0Num < partition[sliceDir0] && !canceled; ++sliceDir0Num) + { + ApexCSG::IApexBSP* fractureBSP1 = fractureBSP0; // This is the default; if there is a need to slice it will be replaced below. + if (sliceDir0Num + 1 < partition[sliceDir0]) + { + // Slice off piece in the 0 direction + fractureBSP1 = createFractureBSP(leadingPlanes, reusedSliceBSP, *fractureBSP0, hMesh, childVolume, 0, center, extent, (int32_t)sliceDir0, sliceDir0Num, sliceDepth, sliceWidths, + sliceParameters.linearVariation, sliceParameters.angularVariation, sliceParameters.noise[sliceDir0], + desc.materialDesc[sliceDir0], (int32_t)desc.noiseMode, desc.useDisplacementMaps); + reusedSliceBSP->release(); + reusedSliceBSP = NULL; + } + else + { + leadingPlanes[sliceDir0] = chunkLeadingPlanes[sliceDir0]; + } + trailingPlanes[sliceDir1] = chunkTrailingPlanes[sliceDir1]; + leadingPlanes[sliceDir1] = physx::PxPlane(-trailingPlanes[sliceDir1].n, -trailingPlanes[sliceDir1].d); + for (int sliceDir1Num = 0; sliceDir1Num < partition[sliceDir1] && !canceled; ++sliceDir1Num) + { + ApexCSG::IApexBSP* fractureBSP2 = fractureBSP1; // This is the default; if there is a need to slice it will be replaced below. + if (sliceDir1Num + 1 < partition[sliceDir1]) + { + // Slice off piece in the 1 direction + ApexCSG::IApexBSP*& sliceBSP = !slicingThrough ? reusedSliceBSP : sliceBSPs1[(uint32_t)sliceDir1Num]; + fractureBSP2 = createFractureBSP(leadingPlanes, sliceBSP, *fractureBSP1, hMesh, childVolume, 0, center, extent, (int32_t)sliceDir1, sliceDir1Num, sliceDepth, + sliceWidths, sliceParameters.linearVariation, sliceParameters.angularVariation, sliceParameters.noise[sliceDir1], + desc.materialDesc[sliceDir1], (int32_t)desc.noiseMode, desc.useDisplacementMaps); + if (sliceBSP == reusedSliceBSP) + { + reusedSliceBSP->release(); + reusedSliceBSP = NULL; + } + } + else + { + leadingPlanes[sliceDir1] = chunkLeadingPlanes[sliceDir1]; + } + trailingPlanes[sliceDir2] = chunkTrailingPlanes[sliceDir2]; + leadingPlanes[sliceDir2] = physx::PxPlane(-trailingPlanes[sliceDir2].n, -trailingPlanes[sliceDir2].d); + for (int sliceDir2Num = 0; sliceDir2Num < partition[sliceDir2] && !canceled; ++sliceDir2Num) + { + ApexCSG::IApexBSP* fractureBSP3 = fractureBSP2; // This is the default; if there is a need to slice it will be replaced below. + if (sliceDir2Num + 1 < partition[sliceDir2]) + { + // Slice off piece in the 2 direction + ApexCSG::IApexBSP*& sliceBSP = !slicingThrough ? reusedSliceBSP : sliceBSPs2[(uint32_t)sliceDir2Num]; + fractureBSP3 = createFractureBSP(leadingPlanes, sliceBSP, *fractureBSP2, hMesh, childVolume, minChildVolume, center, extent, (int32_t)sliceDir2, sliceDir2Num, sliceDepth, + sliceWidths, sliceParameters.linearVariation, sliceParameters.angularVariation, sliceParameters.noise[sliceDir2], + desc.materialDesc[sliceDir2], (int32_t)desc.noiseMode, desc.useDisplacementMaps); + if (sliceBSP == reusedSliceBSP) + { + reusedSliceBSP->release(); + reusedSliceBSP = NULL; + } + } + else + { + leadingPlanes[sliceDir2] = chunkLeadingPlanes[sliceDir2]; + } + if (fractureBSP3 != NULL) + { + if (hMesh.mParts[(uint32_t)hMesh.mChunks[chunkIndex]->mPartIndex]->mFlags & ExplicitHierarchicalMeshImpl::Part::MeshOpen) + { + fractureBSP3->deleteTriangles(); // Don't use interior triangles on an open mesh + } + canceled = !createChunk(hMesh, chunkIndex, relativeSliceDepth, trailingPlanes, leadingPlanes, childVolume, desc, chunkBSP, *fractureBSP3, sliceParameters, collisionDesc, localProgressListener, cancel); + } + localProgressListener.completeSubtask(); + // We no longer need fractureBSP3 + if (fractureBSP3 != NULL && fractureBSP3 != fractureBSP2) + { + fractureBSP3->release(); + fractureBSP3 = NULL; + } + trailingPlanes[sliceDir2] = physx::PxPlane(-leadingPlanes[sliceDir2].n, -leadingPlanes[sliceDir2].d); + // Check for cancellation + if (cancel != NULL && *cancel) + { + canceled = true; + } + } + // We no longer need fractureBSP2 + if (fractureBSP2 != NULL && fractureBSP2 != fractureBSP1) + { + fractureBSP2->release(); + fractureBSP2 = NULL; + } + trailingPlanes[sliceDir1] = physx::PxPlane(-leadingPlanes[sliceDir1].n, -leadingPlanes[sliceDir1].d); + // Check for cancellation + if (cancel != NULL && *cancel) + { + canceled = true; + } + } + // We no longer need fractureBSP1 + if (fractureBSP1 != NULL && fractureBSP1 != fractureBSP0) + { + fractureBSP1->release(); + fractureBSP1 = NULL; + } + trailingPlanes[sliceDir0] = physx::PxPlane(-leadingPlanes[sliceDir0].n, -leadingPlanes[sliceDir0].d); + // Check for cancellation + if (cancel != NULL && *cancel) + { + canceled = true; + } + } + fractureBSP0->release(); + + while (sliceBSPs2.size()) + { + if (sliceBSPs2.back() != NULL) + { + sliceBSPs2.back()->release(); + } + sliceBSPs2.popBack(); + } + while (sliceBSPs1.size()) + { + if (sliceBSPs1.back() != NULL) + { + sliceBSPs1.back()->release(); + } + sliceBSPs1.popBack(); + } + + return !canceled; +} + + +struct TriangleLockInfo +{ + TriangleLockInfo() : lockedVertices(0), lockedEdges(0), originalTriangleIndex(0xFFFFFFFF) {} + + uint16_t lockedVertices; // (lockedVertices>>N)&1 => vertex N is locked + uint16_t lockedEdges; // (lockedEdges>>M)&1 => edge M is locked + uint32_t originalTriangleIndex; +}; + +PX_INLINE float square(float x) +{ + return x*x; +} + +// Returns edge of triangle, and position on edge (in pointOnEdge) if an edge is split, otherwise returns -1 +// If a valid edge index is returned, also returns distance squared from the point to the edge in perp2 +PX_INLINE int32_t pointOnAnEdge(physx::PxVec3& pointOnEdge, float& perp2, const physx::PxVec3& point, const nvidia::ExplicitRenderTriangle& triangle, float paraTol2, float perpTol2) +{ + int32_t edgeIndex = -1; + float closestPerp2E2 = 1.0f; + float closestE2 = 0.0f; + + for (uint32_t i = 0; i < 3; ++i) + { + const physx::PxVec3& v0 = triangle.vertices[i].position; + const physx::PxVec3& v1 = triangle.vertices[(i+1)%3].position; + const physx::PxVec3 e = v1 - v0; + const float e2 = e.magnitudeSquared(); + const float perpTol2e2 = perpTol2*e2; + const physx::PxVec3 d0 = point - v0; + const float d02 = d0.magnitudeSquared(); + if (d02 < paraTol2) + { + return -1; + } + if (e2 <= 4.0f*paraTol2) + { + continue; + } + const float d0e = d0.dot(e); + if (d0e < 0.0f || d0e > e2) + { + continue; // point does not project down onto the edge + } + const float perp2e2 = d0.cross(e).magnitudeSquared(); + if (perp2e2 > perpTol2e2) + { + continue; // Point too far from edge + } + // Point is close to an edge. Consider it if it's the closest. + if (perp2e2*closestE2 < closestPerp2E2*e2) + { + closestPerp2E2 = perp2e2; + closestE2 = e2; + edgeIndex = (int32_t)i; + } + } + + if (edgeIndex < 0 || closestE2 == 0.0f) + { + return -1; + } + + const physx::PxVec3& v0 = triangle.vertices[edgeIndex].position; + const physx::PxVec3& v1 = triangle.vertices[(edgeIndex+1)%3].position; + + if ((point-v0).magnitudeSquared() < paraTol2 || (point-v1).magnitudeSquared() < paraTol2) + { + return -1; + } + + pointOnEdge = point; + perp2 = closestE2 > 0.0f ? closestPerp2E2/closestE2 : 0.0f; + + return edgeIndex; +} + +// Returns shared edge of triangleA if an edge is shared, otherwise returns -1 +PX_INLINE int32_t trianglesShareEdge(const nvidia::ExplicitRenderTriangle& triangleA, const nvidia::ExplicitRenderTriangle& triangleB, float tol2) +{ + for (uint32_t i = 0; i < 3; ++i) + { + const physx::PxVec3 eA = triangleA.vertices[(i+1)%3].position - triangleA.vertices[i].position; + const float eA2 = eA.magnitudeSquared(); + const float tol2eA2 = tol2*eA2; + // We will search for edges pointing in the opposite direction only + for (uint32_t j = 0; j < 3; ++j) + { + const physx::PxVec3 d0 = triangleB.vertices[j].position - triangleA.vertices[i].position; + const float d0A = d0.dot(eA); + if (d0A <= 0.0f) + { + continue; // edge on B starts before edge on A + } + const physx::PxVec3 d1 = triangleB.vertices[(j+1)%3].position - triangleA.vertices[i].position; + const float d1A = d1.dot(eA); + if (d1A >= eA2) + { + continue; // edge on B ends after edge on A + } + if (d0A <= d1A) + { + continue; // edges don't point in opposite directions + } + if (d0.cross(eA).magnitudeSquared() > tol2eA2) + { + continue; // one vertex on B is not close enough to the edge of A + } + if (d1.cross(eA).magnitudeSquared() > tol2eA2) + { + continue; // other vertex on B is not close enough to the edge of A + } + // These edges appear to have an overlap, to within tolerance + return (int32_t)i; + } + } + + return -1; +} + +// Positive tol means that interference will be registered even if the triangles are a small distance apart. +// Negative tol means that interference will not be registered even if the triangles have a small overlap. +PX_INLINE bool trianglesInterfere(const nvidia::ExplicitRenderTriangle& triangleA, const nvidia::ExplicitRenderTriangle& triangleB, float tol) +{ + // Check extent of B relative to plane of A + const physx::PxPlane planeA(0.333333333f*(triangleA.vertices[0].position + triangleA.vertices[1].position + triangleA.vertices[2].position), triangleA.calculateNormal().getNormalized()); + physx::PxVec3 dispB(planeA.distance(triangleB.vertices[0].position), planeA.distance(triangleB.vertices[1].position), planeA.distance(triangleB.vertices[2].position)); + if (extentDistance(dispB.minElement(), dispB.maxElement(), 0.0f, 0.0f) > tol) + { + return false; + } + + // Check extent of A relative to plane of B + const physx::PxPlane planeB(0.333333333f*(triangleB.vertices[0].position + triangleB.vertices[1].position + triangleB.vertices[2].position), triangleB.calculateNormal().getNormalized()); + physx::PxVec3 dispA(planeB.distance(triangleA.vertices[0].position), planeB.distance(triangleA.vertices[1].position), planeB.distance(triangleA.vertices[2].position)); + if (extentDistance(dispA.minElement(), dispA.maxElement(), 0.0f, 0.0f) > tol) + { + return false; + } + + for (uint32_t i = 0; i < 3; ++i) + { + physx::PxVec3 eA = triangleA.vertices[(i+1)%3].position - triangleA.vertices[i].position; + eA.normalize(); + for (uint32_t j = 0; j < 3; ++j) + { + physx::PxVec3 eB = triangleB.vertices[(j+1)%3].position - triangleB.vertices[j].position; + eB.normalize(); + physx::PxVec3 n = eA.cross(eB); + if (n.normalize() > 0.00001f) + { + dispA = physx::PxVec3(n.dot(triangleA.vertices[0].position), n.dot(triangleA.vertices[1].position), n.dot(triangleA.vertices[2].position)); + dispB = physx::PxVec3(n.dot(triangleB.vertices[0].position), n.dot(triangleB.vertices[1].position), n.dot(triangleB.vertices[2].position)); + if (extentDistance(dispA.minElement(), dispA.maxElement(), dispB.minElement(), dispB.maxElement()) > tol) + { + return false; + } + } + } + } + + return true; +} + +PX_INLINE bool segmentIntersectsTriangle(const physx::PxVec3 orig, const physx::PxVec3 dest, const nvidia::ExplicitRenderTriangle& triangle, float tol) +{ + // Check extent of segment relative to plane of triangle + const physx::PxPlane plane(0.333333333f*(triangle.vertices[0].position + triangle.vertices[1].position + triangle.vertices[2].position), triangle.calculateNormal().getNormalized()); + const float dist0 = plane.distance(orig); + const float dist1 = plane.distance(dest); + if (extentDistance(PxMin(dist0, dist1), PxMax(dist0, dist1), 0.0f, 0.0f) > tol) + { + return false; + } + + // Test to see if the segment goes through the triangle + const float signDist0 = physx::PxSign(dist0); + const physx::PxVec3 disp = dest-orig; + const physx::PxVec3 relV[3] = {triangle.vertices[0].position - orig, triangle.vertices[1].position - orig, triangle.vertices[2].position - orig}; + for (uint32_t v = 0; v < 3; ++v) + { + if (physx::PxSign(relV[v].cross(relV[(v+1)%3]).dot(disp)) == signDist0) + { + return false; + } + } + + return true; +} + +struct VertexRep +{ + BoundsRep bounds; + physx::PxVec3 position; // Not necessarily the center of bounds, after we snap vertices + physx::PxVec3 normal; +}; + +class MeshProcessor +{ +public: + class FreeVertexIt + { + public: + FreeVertexIt(MeshProcessor& meshProcessor) : mMeshProcessor(meshProcessor), mVertexRep(NULL) + { + mTriangleIndex = meshProcessor.mTrianglePartition; + mVertexIndex = 0; + advanceToNextValidState(); + } + + PX_INLINE bool valid() const + { + return mTriangleIndex < mMeshProcessor.mMesh->size(); + } + + PX_INLINE void inc() + { + if (valid()) + { + ++mVertexIndex; + advanceToNextValidState(); + } + } + + PX_INLINE uint32_t triangleIndex() const + { + return mTriangleIndex; + } + + PX_INLINE uint32_t vertexIndex() const + { + return mVertexIndex; + } + + PX_INLINE VertexRep& vertexRep() const + { + return *mVertexRep; + } + + private: + FreeVertexIt& operator=(const FreeVertexIt&); + + PX_INLINE void advanceToNextValidState() + { + for (; valid(); ++mTriangleIndex, mVertexIndex = 0) + { + for (; mVertexIndex < 3; ++mVertexIndex) + { + const uint32_t relativeTriangleIndex = mTriangleIndex-mMeshProcessor.mTrianglePartition; + if (!((mMeshProcessor.mTriangleInfo[relativeTriangleIndex].lockedVertices >> mVertexIndex)&1)) + { + mVertexRep = &mMeshProcessor.mVertexBounds[3*relativeTriangleIndex + mVertexIndex]; + return; + } + } + } + } + + MeshProcessor& mMeshProcessor; + uint32_t mTriangleIndex; + uint32_t mVertexIndex; + VertexRep* mVertexRep; + }; + + class FreeNeighborVertexIt + { + public: + FreeNeighborVertexIt(MeshProcessor& meshProcessor, uint32_t triangleIndex, uint32_t vertexIndex) + : mMeshProcessor(meshProcessor) + , mTriangleIndex(triangleIndex) + , mVertexIndex(vertexIndex) + , mVertexRep(NULL) + { + const uint32_t vertexRepIndex = 3*(triangleIndex - mMeshProcessor.mTrianglePartition) + vertexIndex; + mNeighbors = meshProcessor.mVertexNeighborhoods.getNeighbors(vertexRepIndex); + mNeighborStop = mNeighbors + meshProcessor.mVertexNeighborhoods.getNeighborCount(vertexRepIndex); + advanceToNextValidState(); + } + + PX_INLINE bool valid() const + { + return mNeighbors < mNeighborStop; + } + + PX_INLINE void inc() + { + if (valid()) + { + ++mNeighbors; + advanceToNextValidState(); + } + } + + PX_INLINE uint32_t triangleIndex() const + { + return mTriangleIndex; + } + + PX_INLINE uint32_t vertexIndex() const + { + return mVertexIndex; + } + + PX_INLINE VertexRep& vertexRep() const + { + return *mVertexRep; + } + + private: + FreeNeighborVertexIt& operator=(const FreeNeighborVertexIt&); + + PX_INLINE void advanceToNextValidState() + { + for (; valid(); ++mNeighbors) + { + const uint32_t neighbor = *mNeighbors; + const uint32_t relativeTriangleIndex = neighbor/3; + mTriangleIndex = mMeshProcessor.mTrianglePartition + relativeTriangleIndex; + mVertexIndex = neighbor - 3*relativeTriangleIndex; + mVertexRep = &mMeshProcessor.mVertexBounds[neighbor]; + if (!((mMeshProcessor.mTriangleInfo[relativeTriangleIndex].lockedVertices >> mVertexIndex)&1)) + { + return; + } + } + } + + MeshProcessor& mMeshProcessor; + const uint32_t* mNeighbors; + const uint32_t* mNeighborStop; + uint32_t mTriangleIndex; + uint32_t mVertexIndex; + VertexRep* mVertexRep; + }; + + MeshProcessor() + { + reset(); + } + + // Removes any triangles with a width less than minWidth + static void removeSlivers(physx::Array<nvidia::ExplicitRenderTriangle>& mesh, float minWidth) + { + const float minWidth2 = minWidth*minWidth; + for (uint32_t i = mesh.size(); i--;) + { + nvidia::ExplicitRenderTriangle& triangle = mesh[i]; + for (uint32_t j = 0; j < 3; ++j) + { + const physx::PxVec3 edge = (triangle.vertices[(j+1)%3].position - triangle.vertices[j].position).getNormalized(); + if ((triangle.vertices[(j+2)%3].position - triangle.vertices[j].position).cross(edge).magnitudeSquared() < minWidth2) + { + mesh.replaceWithLast(i); + break; + } + } + } + } + + // trianglePartition is a point in the part mesh where we want to start processing. We will assume that triangles + // before this index are locked, and vertices in triangles after this index will be locked if they coincide with + // locked triangles + void setMesh(physx::Array<nvidia::ExplicitRenderTriangle>& mesh, physx::Array<nvidia::ExplicitRenderTriangle>* parentMesh, uint32_t trianglePartition, float tol) + { + reset(); + + if (mesh.size() == 0) + { + return; + } + + mMesh = &mesh; + mTrianglePartition = PxMin(trianglePartition, mesh.size()); + mTol = physx::PxAbs(tol); + mPadding = 2*mTol; + + mParentMesh = parentMesh; + + // Find part triangle neighborhoods, expanding triangle bounds by some padding factor + mOriginalTriangleBounds.resize(mesh.size()); + for (uint32_t i = 0; i < mesh.size(); ++i) + { + nvidia::ExplicitRenderTriangle& triangle = mesh[i]; + mOriginalTriangleBounds[i].aabb.setEmpty(); + mOriginalTriangleBounds[i].aabb.include(triangle.vertices[0].position); + mOriginalTriangleBounds[i].aabb.include(triangle.vertices[1].position); + mOriginalTriangleBounds[i].aabb.include(triangle.vertices[2].position); + mOriginalTriangleBounds[i].aabb.fattenFast(mPadding); + } + mOriginalTriangleNeighborhoods.setBounds(&mOriginalTriangleBounds[0], mOriginalTriangleBounds.size(), sizeof(mOriginalTriangleBounds[0])); + + // Create additional triangle info. This will parallel the mesh after the partition + mTriangleInfo.resize(mesh.size()-mTrianglePartition); + // Also create triangle interpolators from the original triangles + mTriangleFrames.resize(mesh.size()-mTrianglePartition); + // As well as child triangle info + mTriangleChildLists.resize(mesh.size()-mTrianglePartition); + for (uint32_t i = mTrianglePartition; i < mesh.size(); ++i) + { + mTriangleInfo[i-mTrianglePartition].originalTriangleIndex = i; // Use the original triangles' neighborhood info + mTriangleFrames[i-mTrianglePartition].setFromTriangle(mesh[i]); + mTriangleChildLists[i-mTrianglePartition].pushBack(i); // These are all of the triangles that use the corresponding triangle frame, and will be represented by the corresponding bounds for spatial lookup + } + } + + void lockBorderVertices() + { + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = *mMesh; + + const float tol2 = mTol*mTol; + + for (uint32_t i = mTrianglePartition; i < mesh.size(); ++i) + { + // Use neighbor info to find out if we should lock any of the vertices + nvidia::ExplicitRenderTriangle& triangle = mesh[i]; + const uint32_t neighborCount = mOriginalTriangleNeighborhoods.getNeighborCount(i); + const uint32_t* neighbors = mOriginalTriangleNeighborhoods.getNeighbors(i); + for (uint32_t j = 0; j < neighborCount; ++j) + { + const uint32_t neighbor = *neighbors++; + if (neighbor < mTrianglePartition) + { + // Neighbor is not a process triangle - if it shares an edge then lock vertices + const int32_t sharedEdge = trianglesShareEdge(triangle, mesh[neighbor], tol2); + if (sharedEdge >= 0) + { + mTriangleInfo[i-mTrianglePartition].lockedVertices |= (1<<sharedEdge) | (1<<((sharedEdge+1)%3)); + mTriangleInfo[i-mTrianglePartition].lockedEdges |= 1<<sharedEdge; + } + // Check triangle vertices against neighbor's edges as well + for (uint32_t v = 0; v < 3; ++v) + { + const physx::PxVec3& point = triangle.vertices[v].position; + physx::PxVec3 pointOnEdge; + float perp2; + if (0 <= pointOnAnEdge(pointOnEdge, perp2, point, mesh[neighbor], 0.0f, tol2)) + { + mTriangleInfo[i-mTrianglePartition].lockedVertices |= 1<<v; + } + } + } + } + } + } + + void removeTJunctions() + { + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = *mMesh; + + const float tol2 = mTol*mTol; + + const uint32_t originalMeshSize = mesh.size(); + for (uint32_t i = mTrianglePartition; i < originalMeshSize; ++i) + { + const uint32_t neighborCount = mOriginalTriangleNeighborhoods.getNeighborCount(i); + for (uint32_t v = 0; v < 3; ++v) + { + const physx::PxVec3& point = mesh[i].vertices[v].position; + int32_t neighborToSplit = -1; + int32_t edgeToSplit = -1; + float leastPerp2 = PX_MAX_F32; + const uint32_t* neighbors = mOriginalTriangleNeighborhoods.getNeighbors(i); + for (uint32_t j = 0; j < neighborCount; ++j) + { + const uint32_t originalNeighbor = *neighbors++; + if (originalNeighbor >= mTrianglePartition) + { + physx::Array<uint32_t>& triangleChildList = mTriangleChildLists[originalNeighbor - mTrianglePartition]; + const uint32_t triangleChildListSize = triangleChildList.size(); + for (uint32_t k = 0; k < triangleChildListSize; ++k) + { + const uint32_t neighbor = triangleChildList[k]; + // Neighbor is a process triangle - split it at this triangle's vertices + const physx::PxVec3& point = mesh[i].vertices[v].position; + physx::PxVec3 pointOnEdge; + float perp2 = 0.0f; + const int32_t edge = pointOnAnEdge(pointOnEdge, perp2, point, mesh[neighbor], tol2, tol2); + if (edge >= 0 && !((mTriangleInfo[neighbor - mTrianglePartition].lockedEdges >> edge)&1)) + { + if (perp2 < leastPerp2) + { + neighborToSplit = (int32_t)neighbor; + edgeToSplit = edge; + leastPerp2 = perp2; + } + } + } + } + } + if (neighborToSplit >= 0 && edgeToSplit >= 0) + { + splitTriangle((uint32_t)neighborToSplit, (uint32_t)edgeToSplit, point); + } + } + } + } + + void subdivide(float maxEdgeLength) + { + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = *mMesh; + const float maxEdgeLength2 = maxEdgeLength*maxEdgeLength; + + const float tol2 = mTol*mTol; + + // Pass through list and split edges that are too long + bool splitDone; + do + { + splitDone = false; + for (uint32_t i = mTrianglePartition; i < mesh.size(); ++i) // this array (as well as info) might grow during the loop + { + nvidia::ExplicitRenderTriangle& triangle = mesh[i]; + // Find the longest edge of this triangle + const float edgeLengthSquared[3] = + { + (triangle.vertices[1].position - triangle.vertices[0].position).magnitudeSquared(), + (triangle.vertices[2].position - triangle.vertices[1].position).magnitudeSquared(), + (triangle.vertices[0].position - triangle.vertices[2].position).magnitudeSquared() + }; + const int longestEdge = edgeLengthSquared[1] > edgeLengthSquared[0] ? (edgeLengthSquared[2] > edgeLengthSquared[1] ? 2 : 1) : (edgeLengthSquared[2] > edgeLengthSquared[0] ? 2 : 0); + if (edgeLengthSquared[longestEdge] > maxEdgeLength2) + { + // Split this edge + const nvidia::ExplicitRenderTriangle oldTriangle = triangle; // Save off old triangle for neighbor edge check + const physx::PxVec3 newVertexPosition = 0.5f*(triangle.vertices[longestEdge].position + triangle.vertices[(longestEdge + 1)%3].position); + splitTriangle(i, (uint32_t)longestEdge, newVertexPosition); + // Now split neighbor edges + const uint32_t neighborCount = mOriginalTriangleNeighborhoods.getNeighborCount(i); + const uint32_t* neighbors = mOriginalTriangleNeighborhoods.getNeighbors(i); + for (uint32_t j = 0; j < neighborCount; ++j) + { + const uint32_t originalNeighbor = *neighbors++; + if (originalNeighbor >= mTrianglePartition) + { + physx::Array<uint32_t>& triangleChildList = mTriangleChildLists[originalNeighbor - mTrianglePartition]; + for (uint32_t k = 0; k < triangleChildList.size(); ++k) + { + const uint32_t neighbor = triangleChildList[k]; + if (neighbor >= mTrianglePartition) + { + // Neighbor is a process triangle - split it too, if the neighbor shares an edge, and the split point is on the shared edge + const int32_t sharedEdge = trianglesShareEdge(oldTriangle, mesh[neighbor], tol2); + if (sharedEdge >= 0) + { + physx::PxVec3 pointOnEdge; + float perp2; + const int32_t edgeToSplit = pointOnAnEdge(pointOnEdge, perp2, newVertexPosition, mesh[neighbor], tol2, tol2); + if (edgeToSplit == sharedEdge && !((mTriangleInfo[neighbor - mTrianglePartition].lockedEdges >> edgeToSplit)&1)) + { + splitTriangle(neighbor, (uint32_t)edgeToSplit, pointOnEdge); + } + } + } + } + } + } + splitDone = true; + } + } + } while (splitDone); + } + + void snapVertices(float snapTol) + { + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = *mMesh; + + // Create a small bounding cube for each vertex + const uint32_t vertexCount = 3*(mesh.size()-mTrianglePartition); + mVertexBounds.resize(vertexCount); + + if (mVertexBounds.size() == 0) + { + return; + } + + VertexRep* vertexRep = &mVertexBounds[0]; + for (uint32_t i = mTrianglePartition; i < mesh.size(); ++i) + { + const physx::PxVec3 normal = mesh[i].calculateNormal(); + for (uint32_t j = 0; j < 3; ++j, ++vertexRep) + { + vertexRep->bounds.aabb.include(mesh[i].vertices[j].position); + vertexRep->bounds.aabb.fattenFast(snapTol); + vertexRep->position = mesh[i].vertices[j].position; + vertexRep->normal = normal; + } + } + + // Generate neighbor info + mVertexNeighborhoods.setBounds(&mVertexBounds[0].bounds, vertexCount, sizeof(VertexRep)); + + const float snapTol2 = snapTol*snapTol; + + // Run through all free vertices, look for neighbors that are free, and snap them together + for (MeshProcessor::FreeVertexIt it(*this); it.valid(); it.inc()) + { + Vertex& vertex = mesh[it.triangleIndex()].vertices[it.vertexIndex()]; + VertexRep& vertexRep = it.vertexRep(); + uint32_t N = 1; + const physx::PxVec3 oldVertexPosition = vertex.position; + for (MeshProcessor::FreeNeighborVertexIt nIt(*this, it.triangleIndex(), it.vertexIndex()); nIt.valid(); nIt.inc()) + { + Vertex& neighborVertex = mesh[nIt.triangleIndex()].vertices[nIt.vertexIndex()]; + if ((neighborVertex.position-oldVertexPosition).magnitudeSquared() < snapTol2) + { + vertex.position += neighborVertex.position; + vertexRep.normal += nIt.vertexRep().normal; + ++N; + } + } + vertex.position *= 1.0f/N; + vertexRep.position = vertex.position; + vertexRep.normal.normalize(); + for (MeshProcessor::FreeNeighborVertexIt nIt(*this, it.triangleIndex(), it.vertexIndex()); nIt.valid(); nIt.inc()) + { + Vertex& neighborVertex = mesh[nIt.triangleIndex()].vertices[nIt.vertexIndex()]; + if ((neighborVertex.position-oldVertexPosition).magnitudeSquared() < snapTol2) + { + neighborVertex.position = vertex.position; + nIt.vertexRep().position = vertex.position; + nIt.vertexRep().normal = vertexRep.normal; + } + } + } + } + + void resolveIntersections(float relaxationFactor = 0.5f, uint32_t maxIterations = 10) + { + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = *mMesh; + + if (mesh.size() == 0 || mParentMesh == NULL) + { + return; + } + + physx::Array<nvidia::ExplicitRenderTriangle>& parentMesh = *mParentMesh; + + // Find neighborhoods for the new active triangles, the inactive triangles, and the face mesh triangles + const uint32_t parentMeshSize = parentMesh.size(); + physx::Array<BoundsRep> triangleBounds; + triangleBounds.resize(parentMeshSize + mesh.size()); + // Triangles from the face mesh + for (uint32_t i = 0; i < parentMeshSize; ++i) + { + nvidia::ExplicitRenderTriangle& triangle = parentMesh[i]; + BoundsRep& boundsRep = triangleBounds[i]; + boundsRep.aabb.setEmpty(); + for (uint32_t v = 0; v < 3; ++v) + { + boundsRep.aabb.include(triangle.vertices[v].position); + } + boundsRep.aabb.fattenFast(mPadding); + } + // Triangles from the part mesh + for (uint32_t i = 0; i < mesh.size(); ++i) + { + nvidia::ExplicitRenderTriangle& triangle = mesh[i]; + BoundsRep& boundsRep = triangleBounds[i+parentMeshSize]; + boundsRep.aabb.setEmpty(); + for (uint32_t v = 0; v < 3; ++v) + { + boundsRep.aabb.include(triangle.vertices[v].position); + // Also include the triangle's original vertices if it's a process triangle, so we can check for tunneling + if (i >= mTrianglePartition) + { + VertexRep& vertexRep = mVertexBounds[3*(i-mTrianglePartition) + v]; + boundsRep.aabb.include(vertexRep.position); + } + } + boundsRep.aabb.fattenFast(mPadding); + } + + NeighborLookup triangleNeighborhoods; + triangleNeighborhoods.setBounds(&triangleBounds[0], triangleBounds.size(), sizeof(triangleBounds[0])); + + const nvidia::ExplicitRenderTriangle* parentMeshStart = parentMeshSize ? &parentMesh[0] : NULL; + + // Find interfering pairs of triangles + physx::Array<IntPair> interferingPairs; + for (uint32_t repIndex = mTrianglePartition+parentMeshSize; repIndex < mesh.size()+parentMeshSize; ++repIndex) + { + const uint32_t neighborCount = triangleNeighborhoods.getNeighborCount(repIndex); + const uint32_t* neighborRepIndices = triangleNeighborhoods.getNeighbors(repIndex); + for (uint32_t j = 0; j < neighborCount; ++j) + { + const uint32_t neighborRepIndex = *neighborRepIndices++; + if (repIndex > neighborRepIndex) // Only count each pair once + { + if (trianglesInterfere(repIndex, neighborRepIndex, parentMeshStart, parentMeshSize, -mTol)) + { + IntPair& pair = interferingPairs.insert(); + pair.set((int32_t)repIndex, (int32_t)neighborRepIndex); + } + } + } + } + + // Now run through the interference list, pulling the vertices in the offending triangles back to + // their original positions. Iterate until there are no more interfering triangles, or the maximum + // number of iterations is reached. + physx::Array<bool> handled; + handled.resize(mesh.size() - mTrianglePartition, false); + for (uint32_t iterN = 0; iterN < maxIterations && interferingPairs.size(); ++iterN) + { + for (uint32_t pairN = 0; pairN < interferingPairs.size(); ++pairN) + { + const IntPair& pair = interferingPairs[pairN]; + const uint32_t i0 = (uint32_t)pair.i0; + const uint32_t i1 = (uint32_t)pair.i1; + if (i0 >= mTrianglePartition + parentMeshSize && !handled[i0 - mTrianglePartition - parentMeshSize]) + { + relaxTriangleFreeVertices(i0 - parentMeshSize, relaxationFactor); + handled[i0 - mTrianglePartition - parentMeshSize] = true; + } + if (i1 >= mTrianglePartition + parentMeshSize && !handled[i1 - mTrianglePartition - parentMeshSize]) + { + relaxTriangleFreeVertices(i1 - parentMeshSize, relaxationFactor); + handled[i1 - mTrianglePartition - parentMeshSize] = true; + } + } + // We've given the vertices a relaxation pass. Reset the handled list, and remove pairs that no longer interfere + for (uint32_t pairN = interferingPairs.size(); pairN--;) + { + const IntPair& pair = interferingPairs[pairN]; + const uint32_t i0 = (uint32_t)pair.i0; + const uint32_t i1 = (uint32_t)pair.i1; + if (i0 >= mTrianglePartition + parentMeshSize) + { + handled[i0 - mTrianglePartition - parentMeshSize] = false; + } + if (i1 >= mTrianglePartition + parentMeshSize) + { + handled[i1 - mTrianglePartition - parentMeshSize] = false; + } + if (!trianglesInterfere(i0, i1, parentMeshStart, parentMeshSize, -mTol)) + { + interferingPairs.replaceWithLast(pairN); + } + } + } + } + +private: + void reset() + { + mMesh = NULL; + mParentMesh = NULL; + mTrianglePartition = 0; + mOriginalTriangleBounds.resize(0); + mTol = 0.0f; + mPadding = 0.0f; + mOriginalTriangleNeighborhoods.setBounds(NULL, 0, 0); + mTriangleInfo.resize(0); + mTriangleFrames.resize(0); + mTriangleChildLists.resize(0); + mVertexBounds.resize(0); + mVertexNeighborhoods.setBounds(NULL, 0, 0); + } + + PX_INLINE void splitTriangle(uint32_t triangleIndex, uint32_t edgeIndex, const physx::PxVec3& newVertexPosition) + { + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = *mMesh; + nvidia::ExplicitRenderTriangle& triangle = mesh[triangleIndex]; + const unsigned nextEdge = (edgeIndex + 1)%3; + nvidia::ExplicitRenderTriangle newTriangle = triangle; + TriangleLockInfo& info = mTriangleInfo[triangleIndex-mTrianglePartition]; + TriangleLockInfo newInfo = info; + const bool splitEdgeIsLocked = ((info.lockedEdges>>edgeIndex)&1) != 0; + info.lockedEdges &= ~(uint16_t)(1<<((edgeIndex + 2)%3)); // New edge is not locked + if (!splitEdgeIsLocked) + { + info.lockedVertices &= ~(uint16_t)(1<<edgeIndex); // New vertex is not locked if split edge is not locked + } + newInfo.lockedEdges &= ~(uint16_t)(1<<nextEdge); // New edge is not locked + if (!splitEdgeIsLocked) + { + newInfo.lockedVertices &= ~(uint16_t)(1<<nextEdge); // New vertex is not locked if split edge is not locked + } + const nvidia::TriangleFrame& triangleFrame = mTriangleFrames[newInfo.originalTriangleIndex-mTrianglePartition]; + triangle.vertices[edgeIndex].position = newVertexPosition; + triangleFrame.interpolateVertexData(triangle.vertices[edgeIndex]); + newTriangle.vertices[nextEdge]= triangle.vertices[edgeIndex]; + const uint32_t newTriangleIndex = mesh.size(); + mesh.pushBack(newTriangle); + mTriangleInfo.pushBack(newInfo); + mTriangleChildLists[newInfo.originalTriangleIndex-mTrianglePartition].pushBack(newTriangleIndex); + } + + PX_INLINE void relaxTriangleFreeVertices(uint32_t triangleIndex, float relaxationFactor) + { + const float tol2 = mTol*mTol; + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = *mMesh; + const uint32_t relativeIndex = triangleIndex - mTrianglePartition; + TriangleLockInfo& info = mTriangleInfo[relativeIndex]; + for (uint32_t v = 0; v < 3; ++v) + { + if (!((info.lockedVertices >> v)&1)) + { + Vertex& vertex = mesh[triangleIndex].vertices[v]; + VertexRep& vertexRep = mVertexBounds[3*relativeIndex + v]; + const physx::PxVec3 oldVertexPosition = vertex.position; + vertex.position = (1.0f - relaxationFactor)*vertex.position + relaxationFactor*vertexRep.position; + for (MeshProcessor::FreeNeighborVertexIt nIt(*this, triangleIndex, v); nIt.valid(); nIt.inc()) + { + Vertex& neighborVertex = mesh[nIt.triangleIndex()].vertices[nIt.vertexIndex()]; + if ((neighborVertex.position-oldVertexPosition).magnitudeSquared() < tol2) + { + neighborVertex.position = vertex.position; + } + } + } + } + } + + PX_INLINE bool trianglesInterfere(uint32_t indexA, uint32_t indexB, const nvidia::ExplicitRenderTriangle* parentMeshStart, uint32_t parentMeshSize, float tol) + { + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = *mMesh; + const nvidia::ExplicitRenderTriangle& triangleA = indexA >= parentMeshSize ? mesh[indexA - parentMeshSize] : parentMeshStart[indexA]; + const nvidia::ExplicitRenderTriangle& triangleB = indexB >= parentMeshSize ? mesh[indexB - parentMeshSize] : parentMeshStart[indexB]; + + // Check for static interference + if (::trianglesInterfere(triangleA, triangleB, tol)) + { + return true; + } + + // See if one of the vertices of A swept through B + if (indexA >= mTrianglePartition + parentMeshSize) + { + for (uint32_t v = 0; v < 3; ++v) + { + VertexRep& vertexRep = mVertexBounds[3*(indexA-mTrianglePartition-parentMeshSize) + v]; + if (segmentIntersectsTriangle(vertexRep.position, triangleA.vertices[v].position, triangleB, tol)) + { + return true; + } + } + } + + // See if one of the vertices of B swept through A + if (indexB >= mTrianglePartition + parentMeshSize) + { + for (uint32_t v = 0; v < 3; ++v) + { + VertexRep& vertexRep = mVertexBounds[3*(indexB-mTrianglePartition-parentMeshSize) + v]; + if (segmentIntersectsTriangle(vertexRep.position, triangleB.vertices[v].position, triangleA, tol)) + { + return true; + } + } + } + + // No interference found + return false; + } + + + physx::Array<nvidia::ExplicitRenderTriangle>* mMesh; + physx::Array<nvidia::ExplicitRenderTriangle>* mParentMesh; + uint32_t mTrianglePartition; + physx::Array<BoundsRep> mOriginalTriangleBounds; + float mTol; + float mPadding; + NeighborLookup mOriginalTriangleNeighborhoods; + physx::Array<TriangleLockInfo> mTriangleInfo; + physx::Array<nvidia::TriangleFrame> mTriangleFrames; + physx::Array< physx::Array<uint32_t> > mTriangleChildLists; + physx::Array<VertexRep> mVertexBounds; + NeighborLookup mVertexNeighborhoods; + + friend class FreeVertexIt; + friend class FreeNeighborVertexIt; +}; + + +PX_INLINE bool triangleIsPartOfActiveSet(const nvidia::ExplicitRenderTriangle& triangle, const ExplicitHierarchicalMeshImpl& hMesh) +{ + if (triangle.extraDataIndex >= hMesh.mMaterialFrames.size()) + { + return false; + } + + const nvidia::MaterialFrame& materialFrame = hMesh.mMaterialFrames[triangle.extraDataIndex]; + + return materialFrame.mFractureIndex == -1; +} + + +static void applyNoiseToChunk +( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t parentPartIndex, + uint32_t partIndex, + const nvidia::NoiseParameters& noise, + float gridScale + ) +{ + if (partIndex >= hMesh.mParts.size() || parentPartIndex >= hMesh.mParts.size()) + { + return; + } + + // Mesh and mesh size + float level0Size = hMesh.mParts[(uint32_t)hMesh.mChunks[0]->mPartIndex]->mBounds.getExtents().magnitude(); + physx::Array<nvidia::ExplicitRenderTriangle>& partMesh = hMesh.mParts[partIndex]->mMesh; + physx::Array<nvidia::ExplicitRenderTriangle>& parentPartMesh = hMesh.mParts[parentPartIndex]->mMesh; + + // Grid parameters + const float gridSize = physx::PxAbs(gridScale) / PxMax(2, noise.gridSize); + if (gridSize == 0.0f) + { + return; + } + + const float tol = 0.0001f*gridSize; + + // MeshProcessor::removeSlivers(partMesh, 0.5f*tol); + + // Sort triangles based upon whether or not they are part of the active group. + // Put the active triangles last in the list, so we only need traverse them when splitting + uint32_t inactiveTriangleCount = 0; + uint32_t highMark = partMesh.size(); + while (inactiveTriangleCount < highMark) + { + if (!triangleIsPartOfActiveSet(partMesh[inactiveTriangleCount], hMesh)) + { + ++inactiveTriangleCount; + } + else + if (triangleIsPartOfActiveSet(partMesh[highMark-1], hMesh)) + { + --highMark; + } + else + { + nvidia::swap(partMesh[inactiveTriangleCount++], partMesh[--highMark]); + } + } + PX_ASSERT(inactiveTriangleCount == highMark); + + MeshProcessor chunkMeshProcessor; + chunkMeshProcessor.setMesh(partMesh, &parentPartMesh, inactiveTriangleCount, tol); + chunkMeshProcessor.lockBorderVertices(); + chunkMeshProcessor.removeTJunctions(); + chunkMeshProcessor.subdivide(gridSize); + chunkMeshProcessor.snapVertices(4*tol); + + // Now create and apply noise field + const uint32_t rndSeedSave = userRnd.m_rnd.seed(); + userRnd.m_rnd.setSeed(0); + const float scaledAmplitude = noise.amplitude*level0Size; + const uint32_t numModes = 10; + const float amplitude = scaledAmplitude / physx::PxSqrt((float)numModes); // Scale by frequency? + const float spatialFrequency = noise.frequency*(physx::PxTwoPi/gridSize); + float phase[numModes][3]; + physx::PxVec3 k[numModes][3]; + for (uint32_t n = 0; n < numModes; ++n) + { + for (uint32_t i = 0; i < 3; ++i) + { + phase[n][i] = userRnd.getReal(-physx::PxPi, physx::PxPi); + k[n][i] = physx::PxVec3(userRnd.getReal(-1.0f, 1.0f), userRnd.getReal(-1.0f, 1.0f), userRnd.getReal(-1.0f, 1.0f)); + k[n][i].normalize(); // Not a uniform spherical distribution, but it's ok + k[n][i] *= spatialFrequency; + } + } + userRnd.m_rnd.setSeed(rndSeedSave); + + for (MeshProcessor::FreeVertexIt it(chunkMeshProcessor); it.valid(); it.inc()) + { + physx::PxVec3& r = partMesh[it.triangleIndex()].vertices[it.vertexIndex()].position; + physx::PxVec3 field(0.0f); + physx::PxMat33 gradient(physx::PxVec3(0.0f), physx::PxVec3(0.0f), physx::PxVec3(0.0f)); + for (uint32_t n = 0; n < numModes; ++n) + { + for (uint32_t i = 0; i < 3; ++i) + { + const float phi = k[n][i].dot(r) + phase[n][i]; + field[i] += amplitude*physx::PxSin(phi); + for (uint32_t j = 0; j < 3; ++j) + { + gradient(i,j) += amplitude*k[n][i][j]*physx::PxCos(phi); + } + } + } + r += field.dot(it.vertexRep().normal)*it.vertexRep().normal; + physx::PxVec3 g = gradient.transformTranspose(it.vertexRep().normal); + physx::PxVec3& n = partMesh[it.triangleIndex()].vertices[it.vertexIndex()].normal; + n += g.dot(n)*n - g; + n.normalize(); + physx::PxVec3& t = partMesh[it.triangleIndex()].vertices[it.vertexIndex()].tangent; + t -= t.dot(n)*n; + t.normalize(); + partMesh[it.triangleIndex()].vertices[it.vertexIndex()].binormal = n.cross(t); + } + + // Fix up any mesh intersections that may have resulted from the application of noise + chunkMeshProcessor.resolveIntersections(); +} + + +static bool voronoiSplitChunkInternal +( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t chunkIndex, + const ApexCSG::IApexBSP& chunkBSP, + const nvidia::FractureVoronoiDesc& desc, + const CollisionDesc& collisionDesc, + nvidia::IProgressListener& progressListener, + volatile bool* cancel +) +{ + bool canceled = false; + + physx::Array<physx::PxVec3> sitesForChunk; + const physx::PxVec3* sites = desc.sites; + uint32_t siteCount = desc.siteCount; + if (desc.chunkIndices != NULL) + { + for (uint32_t siteN = 0; siteN < desc.siteCount; ++siteN) + { + if (desc.chunkIndices[siteN] == chunkIndex) + { + sitesForChunk.pushBack(desc.sites[siteN]); + } + } + siteCount = sitesForChunk.size(); + sites = siteCount > 0 ? &sitesForChunk[0] : NULL; + } + + if (siteCount < 2) + { + return !canceled; // Don't want to generate a single child which is a duplicate of the parent, when siteCount == 1 + } + + HierarchicalProgressListener progress((int32_t)PxMax(siteCount, 1u), &progressListener); + + const float minimumRadius2 = hMesh.chunkBounds(0).getExtents().magnitudeSquared()*desc.minimumChunkSize*desc.minimumChunkSize; + + for (VoronoiCellPlaneIterator i(sites, siteCount); i.valid(); i.inc()) + { + // Create a voronoi cell for this site + ApexCSG::IApexBSP* cellBSP = createBSP(hMesh.mBSPMemCache, chunkBSP.getInternalTransform()); // BSPs start off representing all space + ApexCSG::IApexBSP* planeBSP = createBSP(hMesh.mBSPMemCache, chunkBSP.getInternalTransform()); + const physx::PxPlane* planes = i.cellPlanes(); + for (uint32_t planeN = 0; planeN < i.cellPlaneCount(); ++planeN) + { + const physx::PxPlane& plane = planes[planeN]; + + // Create single-plane slice BSP + nvidia::IntersectMesh grid; + GridParameters gridParameters; + gridParameters.interiorSubmeshIndex = desc.materialDesc.interiorSubmeshIndex; + // Defer noise generation if we're using displacement maps + gridParameters.noise = nvidia::NoiseParameters(); + gridParameters.level0Mesh = &hMesh.mParts[0]->mMesh; + gridParameters.materialFrameIndex = hMesh.addMaterialFrame(); + nvidia::MaterialFrame materialFrame = hMesh.getMaterialFrame(gridParameters.materialFrameIndex); + materialFrame.buildCoordinateSystemFromMaterialDesc(desc.materialDesc, plane); + materialFrame.mFractureMethod = nvidia::FractureMethod::Voronoi; + materialFrame.mSliceDepth = hMesh.depth(chunkIndex) + 1; + // Leaving the materialFrame.mFractureMethod at the default of -1, since voronoi cutout faces are not be associated with a direction index + hMesh.setMaterialFrame(gridParameters.materialFrameIndex, materialFrame); + gridParameters.triangleFrame.setFlat(materialFrame.mCoordinateSystem, desc.materialDesc.uvScale, desc.materialDesc.uvOffset); + buildIntersectMesh(grid, plane, materialFrame, (int32_t)desc.noiseMode, &gridParameters); + + ApexCSG::BSPBuildParameters bspBuildParams = gDefaultBuildParameters; + bspBuildParams.internalTransform = chunkBSP.getInternalTransform(); + bspBuildParams.rnd = &userRnd; + + if(desc.useDisplacementMaps) + { + // Displacement map generation is deferred until the end of fracturing + // This used to be where a slice would populate a displacement map with + // offsets along the plane, but no longer + } + + planeBSP->fromMesh(&grid.m_triangles[0], grid.m_triangles.size(), bspBuildParams); + cellBSP->combine(*planeBSP); + cellBSP->op(*cellBSP, ApexCSG::Operation::Intersection); + } + planeBSP->release(); + + if (hMesh.mParts[(uint32_t)hMesh.mChunks[chunkIndex]->mPartIndex]->mFlags & ExplicitHierarchicalMeshImpl::Part::MeshOpen) + { + cellBSP->deleteTriangles(); // Don't use interior triangles on an open mesh + } + + ApexCSG::IApexBSP* bsp = createBSP(hMesh.mBSPMemCache); + bsp->copy(*cellBSP); + bsp->combine(chunkBSP); + bsp->op(*bsp, ApexCSG::Operation::Intersection); + cellBSP->release(); + + if (gIslandGeneration) + { + bsp = bsp->decomposeIntoIslands(); + } + + while (bsp != NULL) + { + if (cancel != NULL && *cancel) + { + canceled = true; + } + + if (!canceled) + { + // Create a mesh with chunkBSP (or its islands) + const uint32_t newPartIndex = hMesh.addPart(); + const uint32_t newChunkIndex = hMesh.addChunk(); + bsp->toMesh(hMesh.mParts[newPartIndex]->mMesh); + hMesh.mParts[newPartIndex]->mMeshBSP->copy(*bsp); + hMesh.buildMeshBounds(newPartIndex); + hMesh.buildCollisionGeometryForPart(newPartIndex, getVolumeDesc(collisionDesc, hMesh.depth(chunkIndex)+1)); + hMesh.mChunks[newChunkIndex]->mParentIndex = (int32_t)chunkIndex; + hMesh.mChunks[newChunkIndex]->mPartIndex = (int32_t)newPartIndex; + if (hMesh.mParts[(uint32_t)hMesh.mChunks[chunkIndex]->mPartIndex]->mFlags & ExplicitHierarchicalMeshImpl::Part::MeshOpen) + { + hMesh.mParts[newPartIndex]->mFlags |= ExplicitHierarchicalMeshImpl::Part::MeshOpen; + } + if (hMesh.mParts[newPartIndex]->mMesh.size() == 0 || hMesh.mParts[newPartIndex]->mCollision.size() == 0 || + hMesh.chunkBounds(newChunkIndex).getExtents().magnitudeSquared() < minimumRadius2) + { + // Either no mesh, no collision, or too small. Eliminate. + hMesh.removeChunk(newChunkIndex); + hMesh.removePart(newPartIndex); + } + else + { + // Apply graphical noise to new part, if requested + if (desc.faceNoise.amplitude > 0.0f){ + const uint32_t parentPartIndex = (uint32_t)*hMesh.partIndex(chunkIndex); + applyNoiseToChunk(hMesh, parentPartIndex, newPartIndex, desc.faceNoise, hMesh.meshBounds(newPartIndex).getExtents().magnitude()); + } + } + } + // Get next bsp in island decomposition + ApexCSG::IApexBSP* nextBSP = bsp->getNext(); + bsp->release(); + bsp = nextBSP; + } + + progress.completeSubtask(); + } + + return !canceled; +} + +namespace FractureTools +{ + +void setBSPTolerances +( + float linearTolerance, + float angularTolerance, + float baseTolerance, + float clipTolerance, + float cleaningTolerance +) +{ + ApexCSG::gDefaultTolerances.linear = linearTolerance; + ApexCSG::gDefaultTolerances.angular = angularTolerance; + ApexCSG::gDefaultTolerances.base = baseTolerance; + ApexCSG::gDefaultTolerances.clip = clipTolerance; + ApexCSG::gDefaultTolerances.cleaning = cleaningTolerance; +} + +void setBSPBuildParameters +( + float logAreaSigmaThreshold, + uint32_t testSetSize, + float splitWeight, + float imbalanceWeight +) +{ + gDefaultBuildParameters.logAreaSigmaThreshold = logAreaSigmaThreshold; + gDefaultBuildParameters.testSetSize = testSetSize; + gDefaultBuildParameters.splitWeight = splitWeight; + gDefaultBuildParameters.imbalanceWeight = imbalanceWeight; +} + +static void trimChunkHulls(ExplicitHierarchicalMeshImpl& hMesh, uint32_t* chunkIndexArray, uint32_t chunkIndexArraySize, float maxTrimFraction) +{ + // Outer array is indexed by chunk #, and is of size chunkIndexArraySize + // Middle array is indexed by hull # for chunkIndexArray[chunk #], is of the same size as the part mCollision array associated with the chunk + // Inner array is a list of trim planes to be applied to each hull + physx::Array< physx::Array< physx::Array<physx::PxPlane> > > chunkHullTrimPlanes; + + // Initialize arrays + chunkHullTrimPlanes.resize(chunkIndexArraySize); + for (uint32_t chunkNum = 0; chunkNum < chunkIndexArraySize; ++chunkNum) + { + physx::Array< physx::Array<physx::PxPlane> >& hullTrimPlanes = chunkHullTrimPlanes[chunkNum]; + const uint32_t chunkIndex = chunkIndexArray[chunkNum]; + const uint32_t partIndex = (uint32_t)hMesh.mChunks[chunkIndex]->mPartIndex; + const uint32_t hullCount = hMesh.mParts[partIndex]->mCollision.size(); + hullTrimPlanes.resize(hullCount); + } + + const physx::PxVec3 identityScale(1.0f); + + // Compare each chunk's hulls against other chunk hulls, building up list of trim planes. O(N^2), but so far this is only used for multi-fbx level 1 chunks, so N shouldn't be too large. + for (uint32_t chunkNum0 = 0; chunkNum0 < chunkIndexArraySize; ++chunkNum0) + { + const uint32_t chunkIndex0 = chunkIndexArray[chunkNum0]; + const uint32_t partIndex0 = (uint32_t)hMesh.mChunks[chunkIndex0]->mPartIndex; + const physx::PxTransform tm0(hMesh.mChunks[chunkIndex0]->mInstancedPositionOffset); + const uint32_t hullCount0 = hMesh.mParts[partIndex0]->mCollision.size(); + physx::Array< physx::Array<physx::PxPlane> >& hullTrimPlanes0 = chunkHullTrimPlanes[chunkNum0]; + for (uint32_t hullIndex0 = 0; hullIndex0 < hullCount0; ++hullIndex0) + { + PartConvexHullProxy* hull0 = hMesh.mParts[partIndex0]->mCollision[hullIndex0]; + physx::Array<physx::PxPlane>& trimPlanes0 = hullTrimPlanes0[hullIndex0]; + + // Inner set of loops for other chunks + for (uint32_t chunkNum1 = chunkNum0+1; chunkNum1 < chunkIndexArraySize; ++chunkNum1) + { + const uint32_t chunkIndex1 = chunkIndexArray[chunkNum1]; + const uint32_t partIndex1 = (uint32_t)hMesh.mChunks[chunkIndex1]->mPartIndex; + const physx::PxTransform tm1(hMesh.mChunks[chunkIndex1]->mInstancedPositionOffset); + const uint32_t hullCount1 = hMesh.mParts[partIndex1]->mCollision.size(); + physx::Array< physx::Array<physx::PxPlane> >& hullTrimPlanes1 = chunkHullTrimPlanes[chunkNum1]; + for (uint32_t hullIndex1 = 0; hullIndex1 < hullCount1; ++hullIndex1) + { + PartConvexHullProxy* hull1 = hMesh.mParts[partIndex1]->mCollision[hullIndex1]; + physx::Array<physx::PxPlane>& trimPlanes1 = hullTrimPlanes1[hullIndex1]; + + // Test overlap + ConvexHullImpl::Separation separation; + if (ConvexHullImpl::hullsInProximity(hull0->impl, tm0, identityScale, hull1->impl, tm1, identityScale, 0.0f, &separation)) + { + // Add trim planes if there's an overlap + physx::PxPlane& trimPlane0 = trimPlanes0.insert(); + trimPlane0 = separation.plane; + trimPlane0.d = PxMin(trimPlane0.d, maxTrimFraction*(separation.max0-separation.min0) - separation.max0); // Bound clip distance + trimPlane0.d += trimPlane0.n.dot(tm0.p); // Transform back into part local space + physx::PxPlane& trimPlane1 = trimPlanes1.insert(); + trimPlane1 = physx::PxPlane(-separation.plane.n, -separation.plane.d); + trimPlane1.d = PxMin(trimPlane1.d, maxTrimFraction*(separation.max1-separation.min1) + separation.min1); // Bound clip distance + trimPlane1.d += trimPlane1.n.dot(tm1.p); // Transform back into part local space + } + } + } + } + } + + // Now traverse trim plane list and apply it to the chunks's hulls + for (uint32_t chunkNum = 0; chunkNum < chunkIndexArraySize; ++chunkNum) + { + const uint32_t chunkIndex = chunkIndexArray[chunkNum]; + const uint32_t partIndex = (uint32_t)hMesh.mChunks[chunkIndex]->mPartIndex; + const uint32_t hullCount = hMesh.mParts[partIndex]->mCollision.size(); + physx::Array< physx::Array<physx::PxPlane> >& hullTrimPlanes = chunkHullTrimPlanes[chunkNum]; + for (uint32_t hullIndex = hullCount; hullIndex--;) // Traverse backwards, in case we need to delete empty hulls + { + PartConvexHullProxy* hull = hMesh.mParts[partIndex]->mCollision[hullIndex]; + physx::Array<physx::PxPlane>& trimPlanes = hullTrimPlanes[hullIndex]; + for (uint32_t planeIndex = 0; planeIndex < trimPlanes.size(); ++planeIndex) + { + hull->impl.intersectPlaneSide(trimPlanes[planeIndex]); + if (hull->impl.isEmpty()) + { + PX_DELETE(hMesh.mParts[partIndex]->mCollision[hullIndex]); + hMesh.mParts[partIndex]->mCollision.replaceWithLast(hullIndex); + break; + } + } + } + // Make sure we haven't deleted every collision hull + if (hMesh.mParts[partIndex]->mCollision.size() == 0) + { + CollisionVolumeDesc collisionVolumeDesc; + collisionVolumeDesc.mHullMethod = ConvexHullMethod::WRAP_GRAPHICS_MESH; // Should we use something simpler, like a box? + hMesh.buildCollisionGeometryForPart(partIndex, collisionVolumeDesc); + } + } +} + +bool buildExplicitHierarchicalMesh +( + ExplicitHierarchicalMesh& iHMesh, + const nvidia::ExplicitRenderTriangle* meshTriangles, + uint32_t meshTriangleCount, + const nvidia::ExplicitSubmeshData* submeshData, + uint32_t submeshCount, + uint32_t* meshPartition, + uint32_t meshPartitionCount, + int32_t* parentIndices, + uint32_t parentIndexCount +) +{ + bool flatDepthOne = parentIndexCount == 0; + + const bool havePartition = meshPartition != NULL && meshPartitionCount > 1; + + if (!havePartition) + { + flatDepthOne = true; // This only makes sense if we have a partition + } + + if (parentIndices == NULL) + { + parentIndexCount = 0; + } + + ExplicitHierarchicalMeshImpl& hMesh = *(ExplicitHierarchicalMeshImpl*)&iHMesh; + hMesh.clear(); + hMesh.addPart(); + hMesh.mParts[0]->mMesh.reset(); + const uint32_t part0Size = !flatDepthOne ? meshPartition[0] : meshTriangleCount; // Build level 0 part out of all of the triangles if flatDepthOne = true + hMesh.mParts[0]->mMesh.reserve(part0Size); + uint32_t nextTriangle = 0; + for (uint32_t i = 0; i < part0Size; ++i) + { + hMesh.mParts[0]->mMesh.pushBack(meshTriangles[nextTriangle++]); + } + hMesh.buildMeshBounds(0); + hMesh.addChunk(); + hMesh.mChunks[0]->mParentIndex = -1; + hMesh.mChunks[0]->mPartIndex = 0; + + if (flatDepthOne) + { + nextTriangle = 0; // reset + } + + physx::Array<bool> hasChildren(meshPartitionCount+1, false); + + if (havePartition) + { + // We have a partition - build hierarchy + uint32_t partIndex = 1; + const uint32_t firstLevel1Part = !flatDepthOne ? 1u : 0u; + for (uint32_t i = firstLevel1Part; i < meshPartitionCount; ++i, ++partIndex) + { + hMesh.addPart(); + hMesh.mParts[partIndex]->mMesh.reset(); + hMesh.mParts[partIndex]->mMesh.reserve(meshPartition[i] - nextTriangle); + while (nextTriangle < meshPartition[i]) + { + hMesh.mParts[partIndex]->mMesh.pushBack(meshTriangles[nextTriangle++]); + } + hMesh.buildMeshBounds(partIndex); + hMesh.addChunk(); + hMesh.mChunks[partIndex]->mParentIndex = partIndex < parentIndexCount ? parentIndices[partIndex] : 0; // partIndex = chunkIndex here + if (hMesh.mChunks[partIndex]->mParentIndex >= 0) + { + hasChildren[(uint32_t)hMesh.mChunks[partIndex]->mParentIndex] = true; + } + hMesh.mChunks[partIndex]->mPartIndex = (int32_t)partIndex; // partIndex = chunkIndex here + } + } + + // Submesh data + hMesh.mSubmeshData.reset(); + hMesh.mSubmeshData.reserve(submeshCount); + for (uint32_t i = 0; i < submeshCount; ++i) + { + hMesh.mSubmeshData.pushBack(submeshData[i]); + } + + for (uint32_t i = 0; i < hMesh.mChunks.size(); ++i) + { + hMesh.mChunks[i]->mPrivateFlags |= ExplicitHierarchicalMeshImpl::Chunk::Root; + if (!hasChildren[i]) + { + hMesh.mChunks[i]->mPrivateFlags |= ExplicitHierarchicalMeshImpl::Chunk::RootLeaf; + } + } + + hMesh.mRootSubmeshCount = submeshCount; + + hMesh.sortChunks(); + + return true; +} + +// If destructibleAsset == NULL, no hierarchy is assumed and we must have only one part in the render mesh. +static bool buildExplicitHierarchicalMeshFromApexAssetsInternal(ExplicitHierarchicalMeshImpl& hMesh, const nvidia::apex::RenderMeshAsset& renderMeshAsset, + const nvidia::apex::DestructibleAsset* destructibleAsset, uint32_t maxRootDepth = UINT32_MAX) +{ + if (renderMeshAsset.getPartCount() == 0) + { + return false; + } + + if (destructibleAsset == NULL && renderMeshAsset.getPartCount() != 1) + { + return false; + } + + hMesh.clear(); + + // Create parts + for (uint32_t partIndex = 0; partIndex < renderMeshAsset.getPartCount(); ++partIndex) + { + const uint32_t newPartIndex = hMesh.addPart(); + PX_ASSERT(newPartIndex == partIndex); + ExplicitHierarchicalMeshImpl::Part* part = hMesh.mParts[newPartIndex]; + // Fill in fields except for mesh (will be done in submesh loop below) + // part->mMeshBSP is NULL, that's OK + part->mBounds = renderMeshAsset.getBounds(partIndex); + if (destructibleAsset != NULL) + { + // Get collision data from destructible asset + part->mCollision.reserve(destructibleAsset->getPartConvexHullCount(partIndex)); + for (uint32_t hullIndex = 0; hullIndex < destructibleAsset->getPartConvexHullCount(partIndex); ++hullIndex) + { + NvParameterized::Interface* hullParams = destructibleAsset->getPartConvexHullArray(partIndex)[hullIndex]; + if (hullParams != NULL) + { + PartConvexHullProxy* newHull = PX_NEW(PartConvexHullProxy)(); + part->mCollision.pushBack(newHull); + newHull->impl.mParams->copy(*hullParams); + } + } + } + } + + // Deduce root and interior submesh info + hMesh.mRootSubmeshCount = 0; // Incremented below + + // Fill in mesh and get submesh data + hMesh.mSubmeshData.reset(); + hMesh.mSubmeshData.reserve(renderMeshAsset.getSubmeshCount()); + for (uint32_t submeshIndex = 0; submeshIndex < renderMeshAsset.getSubmeshCount(); ++submeshIndex) + { + const nvidia::RenderSubmesh& submesh = renderMeshAsset.getSubmesh(submeshIndex); + + // Submesh data + nvidia::ExplicitSubmeshData& submeshData = hMesh.mSubmeshData.pushBack(nvidia::ExplicitSubmeshData()); + nvidia::strlcpy(submeshData.mMaterialName, nvidia::ExplicitSubmeshData::MaterialNameBufferSize, renderMeshAsset.getMaterialName(submeshIndex)); + submeshData.mVertexFormat.mBonesPerVertex = 1; + + // Mesh + const nvidia::VertexBuffer& vb = submesh.getVertexBuffer(); + const nvidia::VertexFormat& vbFormat = vb.getFormat(); + const uint32_t submeshVertexCount = vb.getVertexCount(); + if (submeshVertexCount == 0) + { + continue; + } + + // Get vb data: + physx::Array<physx::PxVec3> positions; + physx::Array<physx::PxVec3> normals; + physx::Array<physx::PxVec4> tangents; // Handle vec4 tangents + physx::Array<physx::PxVec3> binormals; + physx::Array<nvidia::ColorRGBA> colors; + physx::Array<nvidia::VertexUV> uvs[VertexFormat::MAX_UV_COUNT]; + + // Positions + const int32_t positionBufferIndex = vbFormat.getBufferIndexFromID(vbFormat.getSemanticID(RenderVertexSemantic::POSITION)); + positions.resize(submeshVertexCount); + submeshData.mVertexFormat.mHasStaticPositions = vb.getBufferData(&positions[0], nvidia::RenderDataFormat::FLOAT3, sizeof(physx::PxVec3), + (uint32_t)positionBufferIndex, 0, submeshVertexCount); + if (!submeshData.mVertexFormat.mHasStaticPositions) + { + return false; // Need a position buffer! + } + + // Normals + const int32_t normalBufferIndex = vbFormat.getBufferIndexFromID(vbFormat.getSemanticID(RenderVertexSemantic::NORMAL)); + normals.resize(submeshVertexCount); + submeshData.mVertexFormat.mHasStaticNormals = vb.getBufferData(&normals[0], nvidia::RenderDataFormat::FLOAT3, sizeof(physx::PxVec3), + (uint32_t)normalBufferIndex, 0, submeshVertexCount); + if (!submeshData.mVertexFormat.mHasStaticNormals) + { + ::memset(&normals[0], 0, submeshVertexCount*sizeof(physx::PxVec3)); // Fill with zeros + } + + // Tangents + const int32_t tangentBufferIndex = vbFormat.getBufferIndexFromID(vbFormat.getSemanticID(RenderVertexSemantic::TANGENT)); + tangents.resize(submeshVertexCount, physx::PxVec4(physx::PxVec3(0.0f), 1.0f)); // Fill with (0,0,0,1), in case we read 3-component tangents + switch (vbFormat.getBufferFormat((uint32_t)tangentBufferIndex)) + { + case nvidia::RenderDataFormat::BYTE_SNORM3: + case nvidia::RenderDataFormat::SHORT_SNORM3: + case nvidia::RenderDataFormat::FLOAT3: + submeshData.mVertexFormat.mHasStaticTangents = vb.getBufferData(&tangents[0], nvidia::RenderDataFormat::FLOAT3, sizeof(physx::PxVec4), (uint32_t)tangentBufferIndex, 0, submeshVertexCount); + break; + case nvidia::RenderDataFormat::BYTE_SNORM4: + case nvidia::RenderDataFormat::SHORT_SNORM4: + case nvidia::RenderDataFormat::FLOAT4: + submeshData.mVertexFormat.mHasStaticTangents = vb.getBufferData(&tangents[0], nvidia::RenderDataFormat::FLOAT4, sizeof(physx::PxVec4), (uint32_t)tangentBufferIndex, 0, submeshVertexCount); + break; + default: + submeshData.mVertexFormat.mHasStaticTangents = false; + break; + } + + // Binormals + const int32_t binormalBufferIndex = vbFormat.getBufferIndexFromID(vbFormat.getSemanticID(RenderVertexSemantic::BINORMAL)); + binormals.resize(submeshVertexCount); + submeshData.mVertexFormat.mHasStaticBinormals = vb.getBufferData(&binormals[0], nvidia::RenderDataFormat::FLOAT3, sizeof(physx::PxVec3), + (uint32_t)binormalBufferIndex, 0, submeshVertexCount); + if (!submeshData.mVertexFormat.mHasStaticBinormals) + { + submeshData.mVertexFormat.mHasStaticBinormals = submeshData.mVertexFormat.mHasStaticNormals && submeshData.mVertexFormat.mHasStaticTangents; + for (uint32_t i = 0; i < submeshVertexCount; ++i) + { + binormals[i] = physx::PxSign(tangents[i][3])*normals[i].cross(tangents[i].getXYZ()); // Build from normals and tangents. If one of these doesn't exist we'll get (0,0,0)'s + } + } + + // Colors + const int32_t colorBufferIndex = vbFormat.getBufferIndexFromID(vbFormat.getSemanticID(RenderVertexSemantic::COLOR)); + colors.resize(submeshVertexCount); + submeshData.mVertexFormat.mHasStaticColors = vb.getBufferData(&colors[0], nvidia::RenderDataFormat::B8G8R8A8, sizeof(nvidia::ColorRGBA), + (uint32_t)colorBufferIndex, 0, submeshVertexCount); + if (!submeshData.mVertexFormat.mHasStaticColors) + { + ::memset(&colors[0], 0xFF, submeshVertexCount*sizeof(nvidia::ColorRGBA)); // Fill with 0xFF + } + + // UVs + submeshData.mVertexFormat.mUVCount = 0; + uint32_t uvNum = 0; + for (; uvNum < VertexFormat::MAX_UV_COUNT; ++uvNum) + { + uvs[uvNum].resize(submeshVertexCount); + const int32_t uvBufferIndex = vbFormat.getBufferIndexFromID(vbFormat.getSemanticID((RenderVertexSemantic::Enum)(RenderVertexSemantic::TEXCOORD0 + uvNum))); + if (!vb.getBufferData(&uvs[uvNum][0], nvidia::RenderDataFormat::FLOAT2, sizeof(nvidia::VertexUV), + (uint32_t)uvBufferIndex, 0, submeshVertexCount)) + { + break; + } + } + submeshData.mVertexFormat.mUVCount = uvNum; + for (; uvNum < VertexFormat::MAX_UV_COUNT; ++uvNum) + { + uvs[uvNum].resize(submeshVertexCount); + ::memset(&uvs[uvNum][0], 0, submeshVertexCount*sizeof(nvidia::VertexUV)); // Fill with zeros + } + + // Now create triangles + bool rootChunkHasTrianglesWithThisSubmesh = false; + for (uint32_t partIndex = 0; partIndex < renderMeshAsset.getPartCount(); ++partIndex) + { + ExplicitHierarchicalMeshImpl::Part* part = hMesh.mParts[partIndex]; + physx::Array<nvidia::ExplicitRenderTriangle>& triangles = part->mMesh; + const uint32_t* indexBuffer = submesh.getIndexBuffer(partIndex); + const uint32_t* smoothingGroups = submesh.getSmoothingGroups(partIndex); + const uint32_t indexCount = submesh.getIndexCount(partIndex); + PX_ASSERT((indexCount%3) == 0); + const uint32_t triangleCount = indexCount/3; + triangles.reserve(triangles.size() + triangleCount); + if (triangleCount > 0 && destructibleAsset != NULL) + { + for (uint32_t chunkIndex = 0; chunkIndex < destructibleAsset->getChunkCount(); ++chunkIndex) + { + if (destructibleAsset->getPartIndex(chunkIndex) == partIndex) + { + // This part is in a root chunk. Make sure we've accounted for all of its submeshes + rootChunkHasTrianglesWithThisSubmesh = true; + break; + } + } + } + for (uint32_t triangleNum = 0; triangleNum < triangleCount; ++triangleNum) + { + nvidia::ExplicitRenderTriangle& triangle = triangles.pushBack(nvidia::ExplicitRenderTriangle()); + triangle.extraDataIndex = 0xFFFFFFFF; + triangle.smoothingMask = smoothingGroups != NULL ? smoothingGroups[triangleNum] : 0; + triangle.submeshIndex = (int32_t)submeshIndex; + for (unsigned v = 0; v < 3; ++v) + { + const uint32_t index = *indexBuffer++; + nvidia::Vertex& vertex = triangle.vertices[v]; + vertex.position = positions[index]; + vertex.normal = normals[index]; + vertex.tangent = tangents[index].getXYZ(); + vertex.binormal = binormals[index]; + vertex.color = VertexColor(ColorRGBA(colors[index])); + for (uint32_t uvNum = 0; uvNum < VertexFormat::MAX_UV_COUNT; ++uvNum) + { + vertex.uv[uvNum] = uvs[uvNum][index]; + } + vertex.boneIndices[0] = (uint16_t)partIndex; + } + } + } + + if (rootChunkHasTrianglesWithThisSubmesh) + { + hMesh.mRootSubmeshCount = submeshIndex+1; + } + } + + // Create chunks + if (destructibleAsset != NULL) + { + physx::Array<bool> hasRootChildren(destructibleAsset->getChunkCount(), false); + for (uint32_t chunkIndex = 0; chunkIndex < destructibleAsset->getChunkCount(); ++chunkIndex) + { + const uint32_t newChunkIndex = hMesh.addChunk(); + PX_ASSERT(newChunkIndex == chunkIndex); + ExplicitHierarchicalMeshImpl::Chunk* chunk = hMesh.mChunks[newChunkIndex]; + // Fill in fields of chunk + chunk->mParentIndex = destructibleAsset->getChunkParentIndex(chunkIndex); + chunk->mFlags = destructibleAsset->getChunkFlags(chunkIndex); + chunk->mPartIndex = (int32_t)destructibleAsset->getPartIndex(chunkIndex); + chunk->mInstancedPositionOffset = destructibleAsset->getChunkPositionOffset(chunkIndex); + chunk->mInstancedUVOffset = destructibleAsset->getChunkUVOffset(chunkIndex); + if (destructibleAsset->getChunkDepth(chunkIndex) <= maxRootDepth) + { + chunk->mPrivateFlags |= ExplicitHierarchicalMeshImpl::Chunk::Root; // We will assume every chunk is a root chunk + if (chunk->mParentIndex >= 0 && chunk->mParentIndex < (physx::PxI32)destructibleAsset->getChunkCount()) + { + hasRootChildren[(physx::PxU32)chunk->mParentIndex] = true; + } + } + } + + // See which root chunks have no children; these are root leaves + for (uint32_t chunkIndex = 0; chunkIndex < destructibleAsset->getChunkCount(); ++chunkIndex) + { + ExplicitHierarchicalMeshImpl::Chunk* chunk = hMesh.mChunks[chunkIndex]; + if (chunk->isRootChunk() && !hasRootChildren[chunkIndex]) + { + chunk->mPrivateFlags |= ExplicitHierarchicalMeshImpl::Chunk::RootLeaf; + } + } + } + else + { + // No destructible asset, there's just one chunk + const uint32_t newChunkIndex = hMesh.addChunk(); + PX_ASSERT(newChunkIndex == 0); + ExplicitHierarchicalMeshImpl::Chunk* chunk = hMesh.mChunks[newChunkIndex]; + // Fill in fields of chunk + chunk->mParentIndex = -1; + chunk->mFlags = 0; // Can't retrieve this + chunk->mPartIndex = 0; + chunk->mInstancedPositionOffset = physx::PxVec3(0.0f); + chunk->mInstancedUVOffset = physx::PxVec2(0.0f); + chunk->mPrivateFlags |= (ExplicitHierarchicalMeshImpl::Chunk::Root | ExplicitHierarchicalMeshImpl::Chunk::RootLeaf); + } + + return true; +} + +PX_INLINE bool trianglesTouch(const nvidia::ExplicitRenderTriangle& t1, const nvidia::ExplicitRenderTriangle& t2) +{ + PX_UNUSED(t1); + PX_UNUSED(t2); + return true; // For now, just keep AABB test. May want to do better. +} + +static void partitionMesh(physx::Array<uint32_t>& partition, nvidia::ExplicitRenderTriangle* mesh, uint32_t meshTriangleCount, float padding) +{ + // Find triangle neighbors + physx::Array<nvidia::BoundsRep> triangleBounds; + triangleBounds.reserve(meshTriangleCount); + for (uint32_t i = 0; i < meshTriangleCount; ++i) + { + nvidia::ExplicitRenderTriangle& triangle = mesh[i]; + nvidia::BoundsRep& rep = triangleBounds.insert(); + for (int j = 0; j < 3; ++j) + { + rep.aabb.include(triangle.vertices[j].position); + } + rep.aabb.fattenFast(padding); + } + + NeighborLookup triangleNeighborhoods; + triangleNeighborhoods.setBounds(&triangleBounds[0], triangleBounds.size(), sizeof(triangleBounds[0])); + + // Re-ordering the mesh in-place will make the neighborhoods invalid, so we re-map + physx::Array<uint32_t> triangleRemap(meshTriangleCount); + physx::Array<uint32_t> triangleRemapInv(meshTriangleCount); + for (uint32_t i = 0; i < meshTriangleCount; ++i) + { + triangleRemap[i] = i; + triangleRemapInv[i] = i; + } + + partition.resize(0); + uint32_t nextTriangle = 0; + while (nextTriangle < meshTriangleCount) + { + uint32_t partitionStop = nextTriangle+1; + do + { + const uint32_t r = triangleRemap[nextTriangle]; + const uint32_t neighborCount = triangleNeighborhoods.getNeighborCount(r); + const uint32_t* neighbors = triangleNeighborhoods.getNeighbors(r); + for (uint32_t n = 0; n < neighborCount; ++n) + { + const uint32_t s = triangleRemapInv[neighbors[n]]; + if (s <= partitionStop || !trianglesTouch(mesh[nextTriangle], mesh[s])) + { + continue; + } + nvidia::swap(triangleRemapInv[triangleRemap[partitionStop]], triangleRemapInv[triangleRemap[s]]); + nvidia::swap(triangleRemap[partitionStop], triangleRemap[s]); + nvidia::swap(mesh[partitionStop], mesh[s]); + ++partitionStop; + } + } while(nextTriangle++ < partitionStop); + partition.pushBack(nextTriangle); + } +} + +uint32_t partitionMeshByIslands +( + nvidia::ExplicitRenderTriangle* mesh, + uint32_t meshTriangleCount, + uint32_t* meshPartition, + uint32_t meshPartitionMaxCount, + float padding +) +{ + // Adjust padding for mesh size + physx::PxBounds3 bounds; + bounds.setEmpty(); + for (uint32_t i = 0; i < meshTriangleCount; ++i) + { + for (int j = 0; j < 3; ++j) + { + bounds.include(mesh[i].vertices[j].position); + } + } + padding *= bounds.getExtents().magnitude(); + + physx::Array<uint32_t> partition; + partitionMesh(partition, mesh, meshTriangleCount, padding); + + for (uint32_t i = 0; i < meshPartitionMaxCount && i < partition.size(); ++i) + { + meshPartition[i] = partition[i]; + } + + return partition.size(); +} + +bool buildExplicitHierarchicalMeshFromRenderMeshAsset(ExplicitHierarchicalMesh& iHMesh, const nvidia::apex::RenderMeshAsset& renderMeshAsset, uint32_t maxRootDepth) +{ + return buildExplicitHierarchicalMeshFromApexAssetsInternal(*(ExplicitHierarchicalMeshImpl*)&iHMesh, renderMeshAsset, NULL, maxRootDepth); +} + +bool buildExplicitHierarchicalMeshFromDestructibleAsset(ExplicitHierarchicalMesh& iHMesh, const nvidia::apex::DestructibleAsset& destructibleAsset, uint32_t maxRootDepth) +{ + if (destructibleAsset.getChunkCount() == 0) + { + return false; + } + + const nvidia::RenderMeshAsset* renderMeshAsset = destructibleAsset.getRenderMeshAsset(); + if (renderMeshAsset == NULL) + { + return false; + } + + return buildExplicitHierarchicalMeshFromApexAssetsInternal(*(ExplicitHierarchicalMeshImpl*)&iHMesh, *renderMeshAsset, &destructibleAsset, maxRootDepth); +} + + +class MeshSplitter +{ +public: + virtual bool validate(ExplicitHierarchicalMeshImpl& hMesh) = 0; + + virtual void initialize(ExplicitHierarchicalMeshImpl& hMesh) = 0; + + virtual bool process + ( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t chunkIndex, + const ApexCSG::IApexBSP& chunkBSP, + const CollisionDesc& collisionDesc, + nvidia::IProgressListener& progressListener, + volatile bool* cancel + ) = 0; + + virtual bool finalize(ExplicitHierarchicalMeshImpl& hMesh) = 0; +}; + +static bool splitMeshInternal +( + ExplicitHierarchicalMesh& iHMesh, + ExplicitHierarchicalMesh& iHMeshCore, + bool exportCoreMesh, + int32_t coreMeshImprintSubmeshIndex, + const MeshProcessingParameters& meshProcessingParams, + MeshSplitter& splitter, + const CollisionDesc& collisionDesc, + uint32_t randomSeed, + nvidia::IProgressListener& progressListener, + volatile bool* cancel +) +{ + ExplicitHierarchicalMeshImpl& hMesh = *(ExplicitHierarchicalMeshImpl*)&iHMesh; + ExplicitHierarchicalMeshImpl& hMeshCore = *(ExplicitHierarchicalMeshImpl*)&iHMeshCore; + + if (hMesh.partCount() == 0) + { + return false; + } + + bool rootDepthIsZero = hMesh.mChunks[0]->isRootChunk(); // Until proven otherwise + for (uint32_t chunkIndex = 1; rootDepthIsZero && chunkIndex < hMesh.chunkCount(); ++chunkIndex) + { + rootDepthIsZero = !hMesh.mChunks[chunkIndex]->isRootChunk(); + } + + if (!rootDepthIsZero && hMeshCore.partCount() > 0 && exportCoreMesh) + { + char message[1000]; + sprintf(message, "Warning: cannot export core mesh with multiple-mesh root mesh. Will not export core."); + outputMessage(message, physx::PxErrorCode::eDEBUG_WARNING); + exportCoreMesh = false; + } + + if (!splitter.validate(hMesh)) + { + return false; + } + + // Save state if cancel != NULL + physx::PxFileBuf* save = NULL; + class NullEmbedding : public ExplicitHierarchicalMesh::Embedding + { + void serialize(physx::PxFileBuf& stream, Embedding::DataType type) const + { + (void)stream; + (void)type; + } + void deserialize(physx::PxFileBuf& stream, Embedding::DataType type, uint32_t version) + { + (void)stream; + (void)type; + (void)version; + } + } embedding; + if (cancel != NULL) + { + save = nvidia::GetApexSDK()->createMemoryWriteStream(); + if (save != NULL) + { + hMesh.serialize(*save, embedding); + } + } + bool canceled = false; + + hMesh.buildCollisionGeometryForPart(0, getVolumeDesc(collisionDesc, 0)); + + userRnd.m_rnd.setSeed(randomSeed); + + // Call initialization callback + splitter.initialize(hMesh); + + // Make sure we've got BSPs at root depth + for (uint32_t i = 0; i < hMesh.chunkCount(); ++i) + { + if (!hMesh.mChunks[i]->isRootLeafChunk()) + { + continue; + } + uint32_t partIndex = (uint32_t)*hMesh.partIndex(i); + if (hMesh.mParts[partIndex]->mMeshBSP->getType() != ApexCSG::BSPType::Nontrivial) + { + outputMessage("Building mesh BSP..."); + progressListener.setProgress(0); + if (hMesh.calculatePartBSP(partIndex, randomSeed, meshProcessingParams.microgridSize, meshProcessingParams.meshMode, &progressListener, cancel)) + { + outputMessage("Mesh BSP completed."); + } + else + { + outputMessage("Mesh BSP failed."); + canceled = true; + } + userRnd.m_rnd.setSeed(randomSeed); + } + } + +#if 0 // Debugging aid - uses BSP mesh generation to replace level 0 mesh + hMesh.mParts[*hMesh.partIndex(0)]->mMeshBSP->toMesh(hMesh.mParts[0]->mMesh); +#endif + + hMesh.clear(true); + + ExplicitHierarchicalMeshImpl tempCoreMesh; + + uint32_t coreChunkIndex = 0xFFFFFFFF; + uint32_t corePartIndex = 0xFFFFFFFF; + if (hMeshCore.partCount() > 0 && !canceled) + { + // We have a core mesh. + tempCoreMesh.set(iHMeshCore); + + if (exportCoreMesh) + { + // Use it as our first split + // Core starts as original mesh + coreChunkIndex = hMesh.addChunk(); + corePartIndex = hMesh.addPart(); + hMesh.mChunks[coreChunkIndex]->mPartIndex = (int32_t)corePartIndex; + hMesh.mParts[corePartIndex]->mMesh = hMeshCore.mParts[0]->mMesh; + hMesh.buildMeshBounds(corePartIndex); + hMesh.buildCollisionGeometryForPart(corePartIndex, getVolumeDesc(collisionDesc, 1)); + hMesh.mChunks[coreChunkIndex]->mParentIndex = 0; + } + + // Add necessary submesh data to hMesh from core. + physx::Array<uint32_t> submeshMap(tempCoreMesh.mSubmeshData.size()); + if (exportCoreMesh || coreMeshImprintSubmeshIndex < 0) + { + for (uint32_t i = 0; i < tempCoreMesh.mSubmeshData.size(); ++i) + { + nvidia::ExplicitSubmeshData& coreSubmeshData = tempCoreMesh.mSubmeshData[i]; + submeshMap[i] = hMesh.mSubmeshData.size(); + for (uint32_t j = 0; j < hMesh.mSubmeshData.size(); ++j) + { + nvidia::ExplicitSubmeshData& submeshData = hMesh.mSubmeshData[j]; + if (0 == nvidia::strcmp(submeshData.mMaterialName, coreSubmeshData.mMaterialName)) + { + submeshMap[i] = j; + break; + } + } + if (submeshMap[i] == hMesh.mSubmeshData.size()) + { + hMesh.mSubmeshData.pushBack(coreSubmeshData); + } + } + } + + if (coreMeshImprintSubmeshIndex >= (int32_t)hMesh.mSubmeshData.size()) + { + coreMeshImprintSubmeshIndex = 0; + } + + for (uint32_t i = 0; i < tempCoreMesh.chunkCount(); ++i) + { + if (!tempCoreMesh.mChunks[i]->isRootChunk()) + { + continue; + } + + // Remap materials + uint32_t partIndex = (uint32_t)*tempCoreMesh.partIndex(i); + for (uint32_t j = 0; j < tempCoreMesh.mParts[partIndex]->mMesh.size(); ++j) + { + nvidia::ExplicitRenderTriangle& tri = tempCoreMesh.mParts[partIndex]->mMesh[j]; + if (tri.submeshIndex >= 0 && tri.submeshIndex < (int32_t)submeshMap.size()) + { + tri.submeshIndex = coreMeshImprintSubmeshIndex < 0 ? (int32_t)submeshMap[(uint32_t)tri.submeshIndex] : coreMeshImprintSubmeshIndex; + if (exportCoreMesh && i == 0) + { + hMesh.mParts[corePartIndex]->mMesh[j].submeshIndex = (int32_t)submeshMap[(uint32_t)hMesh.mParts[corePartIndex]->mMesh[j].submeshIndex]; + } + } + else + { + tri.submeshIndex = coreMeshImprintSubmeshIndex; + } + } + + // Make sure we've got BSPs up to hMesh.mRootDepth + if (tempCoreMesh.mParts[partIndex]->mMeshBSP->getType() != ApexCSG::BSPType::Nontrivial) + { + outputMessage("Building core mesh BSP..."); + progressListener.setProgress(0); + if(tempCoreMesh.calculatePartBSP(partIndex, randomSeed, meshProcessingParams.microgridSize, meshProcessingParams.meshMode, &progressListener, cancel)) + { + outputMessage("Core mesh BSP completed."); + } + else + { + outputMessage("Core mesh BSP calculation failed."); + canceled = true; + } + userRnd.m_rnd.setSeed(randomSeed); + } + } + } + + gIslandGeneration = meshProcessingParams.islandGeneration; + gMicrogridSize = meshProcessingParams.microgridSize; + gVerbosity = meshProcessingParams.verbosity; + + for (uint32_t chunkIndex = 0; chunkIndex < hMesh.mChunks.size() && !canceled; ++chunkIndex) + { + const uint32_t depth = hMesh.depth(chunkIndex); + + if (!hMesh.mChunks[chunkIndex]->isRootLeafChunk()) + { + continue; // Only process core leaf chunk + } + + if (chunkIndex == coreChunkIndex) + { + continue; // Ignore core chunk + } + + uint32_t partIndex = (uint32_t)*hMesh.partIndex(chunkIndex); + + ApexCSG::IApexBSP* seedBSP = createBSP(hMesh.mBSPMemCache); + seedBSP->copy(*hMesh.mParts[partIndex]->mMeshBSP); + + // Subtract out core + bool partModified = false; + for (uint32_t i = 0; i < tempCoreMesh.chunkCount(); ++i) + { + if (!tempCoreMesh.mChunks[i]->isRootLeafChunk()) + { + continue; + } + uint32_t corePartIndex = (uint32_t)*tempCoreMesh.partIndex(i); + if (tempCoreMesh.mParts[corePartIndex]->mMeshBSP != NULL) + { + ApexCSG::IApexBSP* rescaledCoreMeshBSP = createBSP(hMesh.mBSPMemCache); + rescaledCoreMeshBSP->copy(*tempCoreMesh.mParts[corePartIndex]->mMeshBSP, physx::PxMat44(physx::PxIdentity), seedBSP->getInternalTransform()); + seedBSP->combine(*rescaledCoreMeshBSP); + rescaledCoreMeshBSP->release(); + seedBSP->op(*seedBSP, ApexCSG::Operation::A_Minus_B); + partModified = true; + } + } + + if (partModified && depth > 0) + { + // Create part from modified seedBSP (unless it's at level 0) + seedBSP->toMesh(hMesh.mParts[partIndex]->mMesh); + if (hMesh.mParts[partIndex]->mMesh.size() != 0) + { + hMesh.mParts[partIndex]->mMeshBSP->copy(*seedBSP); + hMesh.buildCollisionGeometryForPart(partIndex, getVolumeDesc(collisionDesc, depth)); + } + } + +#if 0 // Should always have been true + if (depth == hMesh.mRootDepth) +#endif + { + // At hMesh.mRootDepth - split + outputMessage("Splitting..."); + progressListener.setProgress(0); + canceled = !splitter.process(hMesh, chunkIndex, *seedBSP, collisionDesc, progressListener, cancel); + outputMessage("splitting completed."); + } + + seedBSP->release(); + } + + // Restore if canceled + if (canceled && save != NULL) + { + uint32_t len; + const void* mem = nvidia::GetApexSDK()->getMemoryWriteBuffer(*save, len); + physx::PxFileBuf* load = nvidia::GetApexSDK()->createMemoryReadStream(mem, len); + if (load != NULL) + { + hMesh.deserialize(*load, embedding); + nvidia::GetApexSDK()->releaseMemoryReadStream(*load); + } + } + + if (save != NULL) + { + nvidia::GetApexSDK()->releaseMemoryReadStream(*save); + } + + if (canceled) + { + return false; + } + + if (meshProcessingParams.removeTJunctions && hMesh.mParts.size()) + { + MeshProcessor meshProcessor; + const float size = hMesh.mParts[0]->mBounds.getExtents().magnitude(); + for (uint32_t i = 0; i < hMesh.partCount(); ++i) + { + meshProcessor.setMesh(hMesh.mParts[i]->mMesh, NULL, 0, 0.0001f*size); + meshProcessor.removeTJunctions(); + } + } + + physx::Array<uint32_t> remap; + hMesh.sortChunks(&remap); + + hMesh.createPartSurfaceNormals(); + + if (corePartIndex < hMesh.partCount()) + { + // Create reasonable collision hulls when there is a core mesh + coreChunkIndex = remap[coreChunkIndex]; + const PxTransform idTM(PxIdentity); + const physx::PxVec3 idScale(1.0f); + for (uint32_t coreHullIndex = 0; coreHullIndex < hMesh.mParts[corePartIndex]->mCollision.size(); ++coreHullIndex) + { + const PartConvexHullProxy& coreHull = *hMesh.mParts[corePartIndex]->mCollision[coreHullIndex]; + for (uint32_t i = 1; i < hMesh.partCount(); ++i) + { + if (i == coreChunkIndex) + { + continue; + } + for (uint32_t hullIndex = 0; hullIndex < hMesh.mParts[i]->mCollision.size(); ++hullIndex) + { + PartConvexHullProxy& hull = *hMesh.mParts[i]->mCollision[hullIndex]; + ConvexHullImpl::Separation separation; + if (ConvexHullImpl::hullsInProximity(coreHull.impl, idTM, idScale, hull.impl, idTM, idScale, 0.0f, &separation)) + { + const float hullWidth = separation.max1 - separation.min1; + const float overlap = separation.max0 - separation.min1; + if (overlap < 0.25f * hullWidth) + { + // Trim the hull + hull.impl.intersectPlaneSide(physx::PxPlane(-separation.plane.n, -separation.max0)); + } + } + } + } + } + } + + return splitter.finalize(hMesh); +} + +// Note: chunks must be in breadth-first order +static void deleteChunkChildren +( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t chunkIndex, + bool deleteChunk = false + ) +{ + for (uint32_t index = hMesh.chunkCount(); index-- > chunkIndex+1;) + { + if (hMesh.mChunks[index]->mParentIndex == (int32_t)chunkIndex) + { + deleteChunkChildren(hMesh, index, true); + } + } + + if (deleteChunk) + { + const int32_t partIndex = hMesh.mChunks[chunkIndex]->mPartIndex; + hMesh.removeChunk(chunkIndex); + bool partIndexUsed = false; + for (uint32_t index = 0; index < hMesh.chunkCount(); ++index) + { + if (hMesh.mChunks[index]->mPartIndex == partIndex) + { + partIndexUsed = true; + break; + } + } + if (!partIndexUsed) + { + hMesh.removePart((uint32_t)partIndex); + } + } +} + +static bool splitChunkInternal +( + ExplicitHierarchicalMesh& iHMesh, + uint32_t chunkIndex, + const FractureTools::MeshProcessingParameters& meshProcessingParams, + MeshSplitter& splitter, + const CollisionDesc& collisionDesc, + uint32_t* randomSeed, + IProgressListener& progressListener, + volatile bool* cancel +) +{ + const int32_t* partIndexPtr = iHMesh.partIndex(chunkIndex); + if (partIndexPtr == NULL) + { + return true; + } + const uint32_t partIndex = (uint32_t)*partIndexPtr; + + gIslandGeneration = meshProcessingParams.islandGeneration; + gMicrogridSize = meshProcessingParams.microgridSize; + gVerbosity = meshProcessingParams.verbosity; + + outputMessage("Splitting..."); + + // Save state if cancel != NULL + physx::PxFileBuf* save = NULL; + class NullEmbedding : public ExplicitHierarchicalMesh::Embedding + { + void serialize(physx::PxFileBuf& stream, Embedding::DataType type) const + { + (void)stream; + (void)type; + } + void deserialize(physx::PxFileBuf& stream, Embedding::DataType type, uint32_t version) + { + (void)stream; + (void)type; + (void)version; + } + } embedding; + + ExplicitHierarchicalMeshImpl& hMesh = *(ExplicitHierarchicalMeshImpl*)&iHMesh; + + if (cancel != NULL) + { + save = nvidia::GetApexSDK()->createMemoryWriteStream(); + if (save != NULL) + { + hMesh.serialize(*save, embedding); + } + } + bool canceled = false; + + progressListener.setProgress(0); + + // Delete chunk children + deleteChunkChildren(hMesh, chunkIndex); + + // Reseed if requested + if (randomSeed != NULL) + { + userRnd.m_rnd.setSeed(*randomSeed); + } + const uint32_t seed = userRnd.m_rnd.seed(); + + // Fracture chunk + ApexCSG::IApexBSP* chunkMeshBSP = hMesh.mParts[partIndex]->mMeshBSP; + + // Make sure we've got a BSP. If this is a root chunk, it may not have been created yet. + if (chunkMeshBSP->getType() != ApexCSG::BSPType::Nontrivial) + { + if (!hMesh.mChunks[chunkIndex]->isRootChunk()) + { + outputMessage("Warning: Building a BSP for a non-root mesh. This should have been created by a splitting process."); + } + outputMessage("Building mesh BSP..."); + progressListener.setProgress(0); + hMesh.calculatePartBSP(partIndex, seed, meshProcessingParams.microgridSize, meshProcessingParams.meshMode, &progressListener); + outputMessage("Mesh BSP completed."); + userRnd.m_rnd.setSeed(seed); + } + + const uint32_t oldPartCount = hMesh.mParts.size(); + + canceled = !splitter.process(hMesh, chunkIndex, *chunkMeshBSP, collisionDesc, progressListener, cancel); + + // Restore if canceled + if (canceled && save != NULL) + { + uint32_t len; + const void* mem = nvidia::GetApexSDK()->getMemoryWriteBuffer(*save, len); + physx::PxFileBuf* load = nvidia::GetApexSDK()->createMemoryReadStream(mem, len); + if (load != NULL) + { + hMesh.deserialize(*load, embedding); + nvidia::GetApexSDK()->releaseMemoryReadStream(*load); + } + } + + if (save != NULL) + { + nvidia::GetApexSDK()->releaseMemoryReadStream(*save); + } + + if (canceled) + { + return false; + } + + if (meshProcessingParams.removeTJunctions) + { + MeshProcessor meshProcessor; + const float size = hMesh.mParts[partIndex]->mBounds.getExtents().magnitude(); + for (uint32_t i = oldPartCount; i < hMesh.partCount(); ++i) + { + meshProcessor.setMesh(hMesh.mParts[i]->mMesh, NULL, 0, 0.0001f*size); + meshProcessor.removeTJunctions(); + } + } + + hMesh.sortChunks(); + + hMesh.createPartSurfaceNormals(); + + return true; +} + + +static uint32_t createVoronoiSitesInsideMeshInternal +( + ExplicitHierarchicalMeshImpl& hMesh, + const uint32_t* chunkIndices, + uint32_t chunkCount, + physx::PxVec3* siteBuffer, + uint32_t* siteChunkIndices, + uint32_t siteCount, + uint32_t* randomSeed, + uint32_t* microgridSize, + BSPOpenMode::Enum meshMode, + nvidia::IProgressListener& progressListener +) +{ + if (randomSeed != NULL) + { + userRnd.m_rnd.setSeed(*randomSeed); + } + + const uint32_t microgridSizeToUse = microgridSize != NULL ? *microgridSize : gMicrogridSize; + + // Make sure we've got BSPs for all chunks + for (uint32_t chunkNum = 0; chunkNum < chunkCount; ++chunkNum) + { + const uint32_t chunkIndex = chunkIndices[chunkNum]; + uint32_t partIndex = (uint32_t)*hMesh.partIndex(chunkIndex); + if (hMesh.mParts[partIndex]->mMeshBSP->getType() != ApexCSG::BSPType::Nontrivial) + { + outputMessage("Building mesh BSP..."); + progressListener.setProgress(0); + if (randomSeed == NULL) + { + outputMessage("Warning: no random seed given in createVoronoiSitesInsideMeshInternal but BSP must be built. Using seed = 0.", physx::PxErrorCode::eDEBUG_WARNING); + } + hMesh.calculatePartBSP(partIndex, (randomSeed != NULL ? *randomSeed : 0), microgridSizeToUse, meshMode, &progressListener); + outputMessage("Mesh BSP completed."); + if (randomSeed != NULL) + { + userRnd.m_rnd.setSeed(*randomSeed); + } + } + } + + // Come up with distribution that is weighted by chunk volume, but also tries to ensure each chunk gets at least one site. + float totalVolume = 0.0f; + physx::Array<float> volumes(chunkCount); + physx::Array<uint32_t> siteCounts(chunkCount); + for (uint32_t chunkNum = 0; chunkNum < chunkCount; ++chunkNum) + { + const uint32_t chunkIndex = chunkIndices[chunkNum]; + const physx::PxBounds3 bounds = hMesh.chunkBounds(chunkIndex); + const physx::PxVec3 extents = bounds.getExtents(); + volumes[chunkNum] = extents.x*extents.y*extents.z; + totalVolume += volumes[chunkNum]; + siteCounts[chunkNum] = 0; + } + + // Now fill in site counts + if (totalVolume <= 0.0f) + { + totalVolume = 1.0f; // To avoid divide-by-zero + } + + // Make site count proportional to volume, within error due to quantization. Ensure at least one site per chunk, even if "zero volume" + // is recorded (it might be an open-meshed chunk). We distinguish between zero and one sites per chunk, even though they have the same + // effect on a chunk, since using one site per chunk will reduce the number of sites available for other chunks. The aim is to have + // control over the number of chunks generated, so we will avoid using zero sites per chunk. + uint32_t totalSiteCount = 0; + for (uint32_t chunkNum = 0; chunkNum < chunkCount; ++chunkNum) + { + siteCounts[chunkNum] = PxMax(1U, (uint32_t)(siteCount*volumes[chunkNum]/totalVolume)); + totalSiteCount += siteCounts[chunkNum]; + } + + // Add sites if we need to. This can happen due to the rounding. + while (totalSiteCount < siteCount) + { + uint32_t chunkToAddSite = 0; + float greatestDeficit = -PX_MAX_F32; + for (uint32_t chunkNum = 0; chunkNum < chunkCount; ++chunkNum) + { + const float defecit = siteCount*volumes[chunkNum]/totalVolume - (float)siteCounts[chunkNum]; + if (defecit > greatestDeficit) + { + greatestDeficit = defecit; + chunkToAddSite = chunkNum; + } + } + ++siteCounts[chunkToAddSite]; + ++totalSiteCount; + } + + // Remove sites if necessary. This is much more likely. + while (totalSiteCount > siteCount) + { + uint32_t chunkToRemoveSite = 0; + float greatestSurplus = -PX_MAX_F32; + for (uint32_t chunkNum = 0; chunkNum < chunkCount; ++chunkNum) + { + const float surplus = (float)siteCounts[chunkNum] - siteCount*volumes[chunkNum]/totalVolume; + if (surplus > greatestSurplus) + { + greatestSurplus = surplus; + chunkToRemoveSite = chunkNum; + } + } + --siteCounts[chunkToRemoveSite]; + --totalSiteCount; + } + + // Now generate the actual sites + uint32_t totalSitesGenerated = 0; + for (uint32_t chunkNum = 0; chunkNum < chunkCount; ++chunkNum) + { + const uint32_t chunkIndex = chunkIndices[chunkNum]; + uint32_t partIndex = (uint32_t)*hMesh.partIndex(chunkIndex); + ApexCSG::IApexBSP* meshBSP = hMesh.mParts[partIndex]->mMeshBSP; + const physx::PxBounds3 bounds = hMesh.chunkBounds(chunkIndex); + uint32_t sitesGenerated = 0; + uint32_t attemptsLeft = 100000; + while ( sitesGenerated < siteCounts[chunkNum]) + { + const physx::PxVec3 site(userRnd.getReal(bounds.minimum.x, bounds.maximum.x), userRnd.getReal(bounds.minimum.y, bounds.maximum.y), userRnd.getReal(bounds.minimum.z, bounds.maximum.z)); + if (!attemptsLeft || meshBSP->pointInside(site - *hMesh.instancedPositionOffset(chunkIndex))) + { + siteBuffer[totalSitesGenerated] = site; + if (siteChunkIndices != NULL) + { + siteChunkIndices[totalSitesGenerated] = chunkIndex; + } + ++sitesGenerated; + ++totalSitesGenerated; + } + if (attemptsLeft) + { + --attemptsLeft; + } + } + } + + return totalSitesGenerated; +} + +class HierarchicalMeshSplitter : public MeshSplitter +{ +private: + HierarchicalMeshSplitter& operator=(const HierarchicalMeshSplitter&); + +public: + HierarchicalMeshSplitter(const FractureSliceDesc& desc) : mDesc(desc) + { + } + + bool validate(ExplicitHierarchicalMeshImpl& hMesh) + { + // Try to see if we're going to generate too many chunks + uint32_t estimatedTotalChunkCount = 0; + for (uint32_t chunkIndex = 0; chunkIndex < hMesh.chunkCount(); ++chunkIndex) + { + if (!hMesh.mChunks[chunkIndex]->isRootLeafChunk()) + { + continue; + } + uint32_t partIndex = (uint32_t)*hMesh.partIndex(chunkIndex); + uint32_t estimatedLevelChunkCount = 1; + physx::PxVec3 estimatedExtent = hMesh.mParts[partIndex]->mBounds.getExtents(); + for (uint32_t chunkDepth = 0; chunkDepth < mDesc.maxDepth; ++chunkDepth) + { + // Get parameters for this depth + const nvidia::SliceParameters& sliceParameters = mDesc.sliceParameters[chunkDepth]; + int partition[3]; + calculatePartition(partition, sliceParameters.splitsPerPass, estimatedExtent, mDesc.useTargetProportions ? mDesc.targetProportions : NULL); + estimatedLevelChunkCount *= partition[0] * partition[1] * partition[2]; + estimatedTotalChunkCount += estimatedLevelChunkCount; + if (estimatedTotalChunkCount > MAX_ALLOWED_ESTIMATED_CHUNK_TOTAL) + { + char message[1000]; + shdfnd::snprintf(message,1000, "Slicing chunk count is estimated to be %d chunks, exceeding the maximum allowed estimated total of %d chunks. Aborting. Try using fewer slices, or a smaller fracture depth.", + estimatedTotalChunkCount, (int)MAX_ALLOWED_ESTIMATED_CHUNK_TOTAL); + outputMessage(message, physx::PxErrorCode::eINTERNAL_ERROR); + return false; + } + estimatedExtent[0] /= partition[0]; + estimatedExtent[1] /= partition[1]; + estimatedExtent[2] /= partition[2]; + } + } + + return true; + } + + void initialize(ExplicitHierarchicalMeshImpl& hMesh) + { + if (mDesc.useDisplacementMaps) + { + hMesh.initializeDisplacementMapVolume(mDesc); + } + + for (int i = 0; i < 3; ++i) + { + hMesh.mSubmeshData.resize(PxMax(hMesh.mRootSubmeshCount, mDesc.materialDesc[i].interiorSubmeshIndex + 1)); + } + } + + bool process + ( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t chunkIndex, + const ApexCSG::IApexBSP& chunkBSP, + const CollisionDesc& collisionDesc, + nvidia::IProgressListener& progressListener, + volatile bool* cancel + ) + { + physx::PxPlane trailingPlanes[3]; // passing in depth = 0 will initialize these + physx::PxPlane leadingPlanes[3]; +#if 1 // Eliminating volume calculation here, for performance. May introduce it later once the mesh is calculated. + const float chunkVolume = 1.0f; +#else + float chunkArea, chunkVolume; + chunkBSP.getSurfaceAreaAndVolume(chunkArea, chunkVolume, true); +#endif + return hierarchicallySplitChunkInternal(hMesh, chunkIndex, 0, trailingPlanes, leadingPlanes, chunkBSP, chunkVolume, mDesc, collisionDesc, progressListener, cancel); + } + + bool finalize(ExplicitHierarchicalMeshImpl& hMesh) + { + if (mDesc.instanceChunks) + { + for (uint32_t i = 0; i < hMesh.partCount(); ++i) + { + hMesh.mChunks[i]->mFlags |= nvidia::apex::DestructibleAsset::ChunkIsInstanced; + } + } + + return true; + } + +protected: + const FractureSliceDesc& mDesc; +}; + +bool createHierarchicallySplitMesh +( + ExplicitHierarchicalMesh& iHMesh, + ExplicitHierarchicalMesh& iHMeshCore, + bool exportCoreMesh, + int32_t coreMeshImprintSubmeshIndex, // If this is < 0, use the core mesh materials (was applyCoreMeshMaterialToNeighborChunks). Otherwise, use the given submesh. + const MeshProcessingParameters& meshProcessingParams, + const FractureSliceDesc& desc, + const CollisionDesc& collisionDesc, + uint32_t randomSeed, + nvidia::IProgressListener& progressListener, + volatile bool* cancel +) +{ + HierarchicalMeshSplitter splitter(desc); + + return splitMeshInternal( + iHMesh, + iHMeshCore, + exportCoreMesh, + coreMeshImprintSubmeshIndex, + meshProcessingParams, + splitter, + collisionDesc, + randomSeed, + progressListener, + cancel); +} + +bool hierarchicallySplitChunk +( + ExplicitHierarchicalMesh& iHMesh, + uint32_t chunkIndex, + const FractureTools::MeshProcessingParameters& meshProcessingParams, + const FractureTools::FractureSliceDesc& desc, + const CollisionDesc& collisionDesc, + uint32_t* randomSeed, + IProgressListener& progressListener, + volatile bool* cancel +) +{ + HierarchicalMeshSplitter splitter(desc); + + return splitChunkInternal(iHMesh, chunkIndex, meshProcessingParams, splitter, collisionDesc, randomSeed, progressListener, cancel); +} + +PX_INLINE PxMat44 createCutoutFrame(const physx::PxVec3& center, const physx::PxVec3& extents, uint32_t sliceAxes[3], uint32_t sliceSignNum, const FractureCutoutDesc& desc, bool& invertX) +{ + const uint32_t sliceDirIndex = sliceAxes[2] * 2 + sliceSignNum; + const float sliceSign = sliceSignNum ? -1.0f : 1.0f; + physx::PxVec3 n = createAxis(sliceAxes[2]) * sliceSign; + PxMat44 cutoutTM; + cutoutTM.column2 = PxVec4(n, 0.f); + float applySign; + switch (sliceAxes[2]) + { + case 0: + applySign = 1.0f; + break; + case 1: + applySign = -1.0f; + break; + default: + case 2: + applySign = 1.0f; + } + const physx::PxVec3 p = createAxis(sliceAxes[1]); + const float cutoutPadding = desc.tileFractureMap ? 0.0f : 0.0001f; + cutoutTM.column1 = PxVec4(p, 0.f); + cutoutTM.column0 = PxVec4(p.cross(n), 0.f); + float cutoutWidth = 2 * extents[sliceAxes[0]] * (1.0f + cutoutPadding); + float cutoutHeight = 2 * extents[sliceAxes[1]] * (1.0f + cutoutPadding); + cutoutWidth *= (desc.cutoutWidthInvert[sliceDirIndex] ? -1.0f : 1.0f) * desc.cutoutWidthScale[sliceDirIndex]; + cutoutHeight *= (desc.cutoutHeightInvert[sliceDirIndex] ? -1.0f : 1.0f) * desc.cutoutHeightScale[sliceDirIndex]; + cutoutTM.scale(physx::PxVec4(cutoutWidth / desc.cutoutSizeX, cutoutHeight / desc.cutoutSizeY, 1.0f, 1.0f)); + cutoutTM.setPosition(physx::PxVec3(0.0f)); + float sign = applySign * sliceSign; + invertX = sign < 0.0f; + + PxVec3 cutoutPosition(0.0, 0.0, 0.0); + + cutoutPosition[sliceAxes[0]] = center[sliceAxes[0]] - sign * (0.5f * cutoutWidth + desc.cutoutWidthOffset[sliceDirIndex] * extents[sliceAxes[0]]); + cutoutPosition[sliceAxes[1]] = center[sliceAxes[1]] - 0.5f * cutoutHeight + desc.cutoutHeightOffset[sliceDirIndex] * extents[sliceAxes[1]]; + + cutoutTM.setPosition(cutoutPosition); + return cutoutTM; +} + +static bool createCutoutChunk(ExplicitHierarchicalMeshImpl& hMesh, ApexCSG::IApexBSP& cutoutBSP, /*IApexBSP& remainderBSP,*/ + const ApexCSG::IApexBSP& sourceBSP, uint32_t sourceIndex, + const CollisionVolumeDesc& volumeDesc, volatile bool* cancel) +{ +// remainderBSP.combine( cutoutBSP ); +// remainderBSP.op( remainderBSP, Operation::A_Minus_B ); + cutoutBSP.combine(sourceBSP); + cutoutBSP.op(cutoutBSP, ApexCSG::Operation::Intersection); + // BRG - should apply island generation here +// if( gIslandGeneration ) +// { +// } + const uint32_t newPartIndex = hMesh.addPart(); + hMesh.mParts[newPartIndex]->mMeshBSP->release(); + hMesh.mParts[newPartIndex]->mMeshBSP = &cutoutBSP; // Save off and own this + cutoutBSP.toMesh(hMesh.mParts[newPartIndex]->mMesh); + if (hMesh.mParts[newPartIndex]->mMesh.size() > 0) + { + hMesh.mParts[newPartIndex]->mMeshBSP->copy(cutoutBSP); + hMesh.buildMeshBounds(newPartIndex); + hMesh.buildCollisionGeometryForPart(newPartIndex, volumeDesc); + const uint32_t newChunkIndex = hMesh.addChunk(); + hMesh.mChunks[newChunkIndex]->mParentIndex = (int32_t)sourceIndex; + hMesh.mChunks[newChunkIndex]->mPartIndex = (int32_t)newPartIndex; + } + else + { + hMesh.removePart(newPartIndex); + } + + return cancel == NULL || !(*cancel); +} + +static void addQuad(ExplicitHierarchicalMeshImpl& hMesh, physx::Array<nvidia::ExplicitRenderTriangle>& mesh, uint32_t sliceDepth, uint32_t submeshIndex, + const physx::PxVec2& interiorUVScale, const physx::PxVec3& v0, const physx::PxVec3& v1, const physx::PxVec3& v2, const physx::PxVec3& v3) +{ + // Create material frame TM + const uint32_t materialIndex = hMesh.addMaterialFrame(); + nvidia::MaterialFrame materialFrame = hMesh.getMaterialFrame(materialIndex); + + nvidia::FractureMaterialDesc materialDesc; + + /* BRG: these should be obtained from an alternative set of material descs (one for each cutout direction), which describe the UV layout around the chunk cutout. */ + materialDesc.uAngle = 0.0f; + materialDesc.uvOffset = physx::PxVec2(0.0f); + materialDesc.uvScale = interiorUVScale; + + materialDesc.tangent = v1 - v0; + materialDesc.tangent.normalize(); + physx::PxVec3 normal = materialDesc.tangent.cross(v3 - v0); + normal.normalize(); + const physx::PxPlane plane(v0, normal); + + materialFrame.buildCoordinateSystemFromMaterialDesc(materialDesc, plane); + materialFrame.mFractureMethod = nvidia::FractureMethod::Cutout; + materialFrame.mFractureIndex = -1; // Signifying that it's a cutout around the chunk, so we shouldn't make assumptions about the face direction + materialFrame.mSliceDepth = sliceDepth; + + hMesh.setMaterialFrame(materialIndex, materialFrame); + + // Create interpolator for triangle quantities + + nvidia::TriangleFrame triangleFrame(materialFrame.mCoordinateSystem, interiorUVScale, physx::PxVec2(0.0f)); + + // Fill one triangle + nvidia::ExplicitRenderTriangle& tri0 = mesh.insert(); + memset(&tri0, 0, sizeof(nvidia::ExplicitRenderTriangle)); + tri0.submeshIndex = (int32_t)submeshIndex; + tri0.extraDataIndex = materialIndex; + tri0.smoothingMask = 0; + tri0.vertices[0].position = v0; + tri0.vertices[1].position = v1; + tri0.vertices[2].position = v2; + for (int i = 0; i < 3; ++i) + { + triangleFrame.interpolateVertexData(tri0.vertices[i]); + } + + // ... and another + nvidia::ExplicitRenderTriangle& tri1 = mesh.insert(); + memset(&tri1, 0, sizeof(nvidia::ExplicitRenderTriangle)); + tri1.submeshIndex = (int32_t)submeshIndex; + tri1.extraDataIndex = materialIndex; + tri1.smoothingMask = 0; + tri1.vertices[0].position = v2; + tri1.vertices[1].position = v3; + tri1.vertices[2].position = v0; + for (int i = 0; i < 3; ++i) + { + triangleFrame.interpolateVertexData(tri1.vertices[i]); + } +} + +float getDeterminant(const physx::PxMat44& mt) +{ + return mt.column0.getXYZ().dot(mt.column1.getXYZ().cross(mt.column2.getXYZ())); +} + +static bool createCutout( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t faceChunkIndex, + ApexCSG::IApexBSP& faceBSP, // May be modified + const nvidia::Cutout& cutout, + const PxMat44& cutoutTM, + const nvidia::FractureCutoutDesc& desc, + const nvidia::NoiseParameters& edgeNoise, + float cutoutDepth, + const nvidia::FractureMaterialDesc& materialDesc, + const CollisionVolumeDesc& volumeDesc, + const physx::PxPlane& minPlane, + const physx::PxPlane& maxPlane, + nvidia::HierarchicalProgressListener& localProgressListener, + volatile bool* cancel) +{ + bool canceled = false; + + const float cosSmoothingThresholdAngle = physx::PxCos(desc.facetNormalMergeThresholdAngle * physx::PxPi / 180.0f); + + physx::Array<physx::PxPlane> trimPlanes; + const uint32_t oldPartCount = hMesh.partCount(); + localProgressListener.setSubtaskWork(1); + const uint32_t loopCount = desc.splitNonconvexRegions ? cutout.convexLoops.size() : 1; + + const bool ccw = getDeterminant(cutoutTM) > (float)0; + + const uint32_t facePartIndex = (uint32_t)*hMesh.partIndex(faceChunkIndex); + + const uint32_t sliceDepth = hMesh.depth(faceChunkIndex) + 1; + + for (uint32_t j = 0; j < loopCount && !canceled; ++j) + { + const uint32_t loopSize = desc.splitNonconvexRegions ? cutout.convexLoops[j].polyVerts.size() : cutout.vertices.size(); + if (desc.splitNonconvexRegions) + { + trimPlanes.reset(); + } + // Build mesh which surrounds the cutout + physx::Array<nvidia::ExplicitRenderTriangle> loopMesh; + for (uint32_t k = 0; k < loopSize; ++k) + { + uint32_t kPrime = ccw ? k : loopSize - 1 - k; + uint32_t kPrimeNext = ccw ? ((kPrime + 1) % loopSize) : (kPrime == 0 ? (loopSize - 1) : (kPrime-1)); + const uint32_t vertexIndex0 = desc.splitNonconvexRegions ? cutout.convexLoops[j].polyVerts[kPrime].index : kPrime; + const uint32_t vertexIndex1 = desc.splitNonconvexRegions ? cutout.convexLoops[j].polyVerts[kPrimeNext].index : kPrimeNext; + const physx::PxVec3& v0 = cutout.vertices[vertexIndex0]; + const physx::PxVec3& v1 = cutout.vertices[vertexIndex1]; + const physx::PxVec3 v0World = cutoutTM.transform(v0); + const physx::PxVec3 v1World = cutoutTM.transform(v1); + const physx::PxVec3 quad0 = minPlane.project(v0World); + const physx::PxVec3 quad1 = minPlane.project(v1World); + const physx::PxVec3 quad2 = maxPlane.project(v1World); + const physx::PxVec3 quad3 = maxPlane.project(v0World); + addQuad(hMesh, loopMesh, sliceDepth, materialDesc.interiorSubmeshIndex, materialDesc.uvScale, quad0, quad1, quad2, quad3); + if (cutout.convexLoops.size() == 1 || desc.splitNonconvexRegions) + { + physx::PxVec3 planeNormal = (quad2 - quad0).cross(quad3 - quad1); + planeNormal.normalize(); + trimPlanes.pushBack(physx::PxPlane(0.25f * (quad0 + quad1 + quad2 + quad3), planeNormal)); + } + } + // Smooth the mesh's normals and tangents + PX_ASSERT(loopMesh.size() == 2 * loopSize); + if (loopMesh.size() == 2 * loopSize) + { + for (uint32_t k = 0; k < loopSize; ++k) + { + const uint32_t triIndex0 = 2 * k; + const uint32_t frameIndex = loopMesh[triIndex0].extraDataIndex; + PX_ASSERT(frameIndex == loopMesh[triIndex0 + 1].extraDataIndex); + physx::PxMat44& frame = hMesh.mMaterialFrames[frameIndex].mCoordinateSystem; + const uint32_t triIndex2 = 2 * ((k + 1) % loopSize); + const uint32_t nextFrameIndex = loopMesh[triIndex2].extraDataIndex; + PX_ASSERT(nextFrameIndex == loopMesh[triIndex2 + 1].extraDataIndex); + physx::PxMat44& nextFrame = hMesh.mMaterialFrames[nextFrameIndex].mCoordinateSystem; + const physx::PxVec3 normalK = frame.column2.getXYZ(); + const physx::PxVec3 normalK1 = nextFrame.column2.getXYZ(); + if (normalK.dot(normalK1) < cosSmoothingThresholdAngle) + { + continue; + } + physx::PxVec3 normal = normalK + normalK1; + normal.normalize(); + loopMesh[triIndex0].vertices[1].normal = normal; + loopMesh[triIndex0].vertices[2].normal = normal; + loopMesh[triIndex0 + 1].vertices[0].normal = normal; + loopMesh[triIndex2].vertices[0].normal = normal; + loopMesh[triIndex2 + 1].vertices[1].normal = normal; + loopMesh[triIndex2 + 1].vertices[2].normal = normal; + } + for (uint32_t k = 0; k < loopMesh.size(); ++k) + { + nvidia::ExplicitRenderTriangle& tri = loopMesh[k]; + for (uint32_t v = 0; v < 3; ++v) + { + nvidia::Vertex& vert = tri.vertices[v]; + vert.tangent = vert.binormal.cross(vert.normal); + } + } + } + // Create loop cutout BSP + ApexCSG::IApexBSP* loopBSP = createBSP(hMesh.mBSPMemCache); + ApexCSG::BSPBuildParameters bspBuildParams = gDefaultBuildParameters; + bspBuildParams.rnd = &userRnd; + bspBuildParams.internalTransform = faceBSP.getInternalTransform(); + loopBSP->fromMesh(&loopMesh[0], loopMesh.size(), bspBuildParams); + const uint32_t oldSize = hMesh.partCount(); + // loopBSP will be modified and owned by the new chunk. + canceled = !createCutoutChunk(hMesh, *loopBSP, faceBSP, /**cutoutSource,*/ faceChunkIndex, volumeDesc, cancel); + for (uint32_t partN = oldSize; partN < hMesh.partCount(); ++partN) + { + // Apply graphical noise to new parts, if requested + if (edgeNoise.amplitude > 0.0f) + { + applyNoiseToChunk(hMesh, facePartIndex, partN, edgeNoise, cutoutDepth); + } + // Trim new part collision hulls + for (uint32_t trimN = 0; trimN < trimPlanes.size(); ++trimN) + { + for (uint32_t hullIndex = 0; hullIndex < hMesh.mParts[partN]->mCollision.size(); ++hullIndex) + { + hMesh.mParts[partN]->mCollision[hullIndex]->impl.intersectPlaneSide(trimPlanes[trimN]); + } + } + } + localProgressListener.completeSubtask(); + } + // Trim hulls + if (!canceled) + { + for (uint32_t partN = oldPartCount; partN < hMesh.partCount(); ++partN) + { + for (uint32_t hullIndex = 0; hullIndex < hMesh.mParts[partN]->mCollision.size(); ++hullIndex) + { + ConvexHullImpl& hull = hMesh.mParts[partN]->mCollision[hullIndex]->impl; + if (!desc.splitNonconvexRegions) + { + for (uint32_t trimN = 0; trimN < trimPlanes.size(); ++trimN) + { + hull.intersectPlaneSide(trimPlanes[trimN]); + } + } +// hull.intersectHull(hMesh.mParts[faceChunkIndex]->mCollision.impl); // Do we need this? + } + } + } + + return !canceled; +} + +static void instanceChildren(ExplicitHierarchicalMeshImpl& hMesh, uint32_t instancingChunkIndex, uint32_t instancedChunkIndex) +{ + for (uint32_t chunkIndex = 0; chunkIndex < hMesh.chunkCount(); ++chunkIndex) + { + if (hMesh.mChunks[chunkIndex]->mParentIndex == (int32_t)instancingChunkIndex) + { + // Found a child. Instance. + const uint32_t instancedChildIndex = hMesh.addChunk(); + hMesh.mChunks[instancedChildIndex]->mFlags |= nvidia::apex::DestructibleAsset::ChunkIsInstanced; + hMesh.mChunks[instancedChildIndex]->mParentIndex = (int32_t)instancedChunkIndex; + hMesh.mChunks[instancedChildIndex]->mPartIndex = hMesh.mChunks[chunkIndex]->mPartIndex; // Same part as instancing child + hMesh.mChunks[instancedChildIndex]->mInstancedPositionOffset = hMesh.mChunks[instancedChunkIndex]->mInstancedPositionOffset; // Same offset as parent + hMesh.mChunks[instancedChildIndex]->mInstancedUVOffset = hMesh.mChunks[instancedChunkIndex]->mInstancedUVOffset; // Same offset as parent + instanceChildren(hMesh, chunkIndex, instancedChildIndex); // Recurse + } + } +} + +static bool createFaceCutouts +( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t faceChunkIndex, + ApexCSG::IApexBSP& faceBSP, // May be modified + const nvidia::FractureCutoutDesc& desc, + const nvidia::NoiseParameters& edgeNoise, + float cutoutDepth, + const nvidia::FractureMaterialDesc& materialDesc, + const nvidia::CutoutSetImpl& cutoutSet, + const PxMat44& cutoutTM, + const float mapXLow, + const float mapYLow, + const CollisionDesc& collisionDesc, + const nvidia::FractureSliceDesc& sliceDesc, + const nvidia::FractureVoronoiDesc& voronoiDesc, + const physx::PxPlane& facePlane, + const physx::PxVec3& localCenter, + const physx::PxVec3& localExtents, + nvidia::IProgressListener& progressListener, + volatile bool* cancel +) +{ + nvidia::FractureVoronoiDesc cutoutVoronoiDesc; + physx::Array<physx::PxVec3> perChunkSites; + + switch (desc.chunkFracturingMethod) + { + case FractureCutoutDesc::VoronoiFractureCutoutChunks: + { + cutoutVoronoiDesc = voronoiDesc; + perChunkSites.resize(voronoiDesc.siteCount); + cutoutVoronoiDesc.sites = voronoiDesc.siteCount > 0 ? &perChunkSites[0] : NULL; + cutoutVoronoiDesc.siteCount = voronoiDesc.siteCount; + } + break; + } + + // IApexBSP* cutoutSource = createBSP( hMesh.mBSPMemCache ); + // cutoutSource->copy( faceBSP ); + + const physx::PxVec3 faceNormal = facePlane.n; + + // "Sandwich" planes + const float centerDisp = -facePlane.d - localExtents[2]; + const float paddedExtent = 1.01f * localExtents[2]; + const physx::PxPlane maxPlane(faceNormal, -centerDisp - paddedExtent); + const physx::PxPlane minPlane(faceNormal, -centerDisp + paddedExtent); + + bool canceled = false; + + // Tiling bounds + const float xTol = CUTOUT_MAP_BOUNDS_TOLERANCE*localExtents[0]; + const float yTol = CUTOUT_MAP_BOUNDS_TOLERANCE*localExtents[1]; + const float mapWidth = cutoutTM.column0.magnitude()*cutoutSet.getDimensions()[0]; + const float mapHeight = cutoutTM.column1.magnitude()*cutoutSet.getDimensions()[1]; + const float boundsXLow = localCenter[0] - localExtents[0]; + const float boundsWidth = 2*localExtents[0]; + const float boundsYLow = localCenter[1] - localExtents[1]; + const float boundsHeight = 2*localExtents[1]; + int32_t ixStart = desc.tileFractureMap ? (int32_t)physx::PxFloor((boundsXLow - mapXLow)/mapWidth + xTol) : 0; + int32_t ixStop = desc.tileFractureMap ? (int32_t)physx::PxCeil((boundsXLow - mapXLow + boundsWidth)/mapWidth - xTol) : 1; + int32_t iyStart = desc.tileFractureMap ? (int32_t)physx::PxFloor((boundsYLow - mapYLow)/mapHeight + yTol) : 0; + int32_t iyStop = desc.tileFractureMap ? (int32_t)physx::PxCeil((boundsYLow - mapYLow + boundsHeight)/mapHeight - yTol) : 1; + + // Find UV map + + const physx::PxVec3 xDir = cutoutTM.column0.getXYZ().getNormalized(); + const physx::PxVec3 yDir = cutoutTM.column1.getXYZ().getNormalized(); + + // First find a good representative face triangle + const float faceDiffTolerance = 0.001f; + uint32_t uvMapTriangleIndex = 0; + float uvMapTriangleIndexFaceDiff = PX_MAX_F32; + float uvMapTriangleIndexArea = 0.0f; + const uint32_t facePartIndex = (uint32_t)*hMesh.partIndex(faceChunkIndex); + const uint32_t facePartTriangleCount = hMesh.meshTriangleCount(facePartIndex); + const nvidia::ExplicitRenderTriangle* facePartTriangles = hMesh.meshTriangles(facePartIndex); + for (uint32_t triN = 0; triN < facePartTriangleCount; ++triN) + { + const nvidia::ExplicitRenderTriangle& tri = facePartTriangles[triN]; + physx::PxVec3 triNormal = (tri.vertices[1].position - tri.vertices[0].position).cross(tri.vertices[2].position - tri.vertices[0].position); + const float triArea = triNormal.normalize(); // Actually twice the area, but it's OK + const float triFaceDiff = (faceNormal-triNormal).magnitude(); + if (triFaceDiff < uvMapTriangleIndexFaceDiff - faceDiffTolerance || (triFaceDiff < uvMapTriangleIndexFaceDiff + faceDiffTolerance && triArea > uvMapTriangleIndexArea)) + { // Significantly better normal, or normal is close and the area is bigger + uvMapTriangleIndex = triN; + uvMapTriangleIndexFaceDiff = triFaceDiff; + uvMapTriangleIndexArea = triArea; + } + } + + // Set up interpolation for UV channel 0 + nvidia::TriangleFrame uvMapTriangleFrame(facePartTriangles[uvMapTriangleIndex], (uint64_t)1<<nvidia::TriangleFrame::UV0_u | (uint64_t)1<<nvidia::TriangleFrame::UV0_v); + + if (cutoutSet.isPeriodic()) + { + --ixStart; + ++ixStop; + --iyStart; + ++iyStop; + } + +#define FORCE_INSTANCING 0 +#if !FORCE_INSTANCING + const float volumeTol = PxMax(localExtents[0]*localExtents[1]*localExtents[2]*MESH_INSTANACE_TOLERANCE*MESH_INSTANACE_TOLERANCE*MESH_INSTANACE_TOLERANCE, (float)1.0e-15); +// const float areaTol = PxMax((localExtents[0]*localExtents[1]+localExtents[1]*localExtents[2]+localExtents[2]*localExtents[0])*MESH_INSTANACE_TOLERANCE*MESH_INSTANACE_TOLERANCE, (float)1.0e-10); +#endif + + const bool instanceCongruentChunks = desc.instancingMode == FractureCutoutDesc::InstanceCongruentChunks || desc.instancingMode == FractureCutoutDesc::InstanceAllChunks; + + // Estimate total work for progress + uint32_t totalWork = 0; + for (uint32_t i = 0; i < cutoutSet.cutouts.size(); ++i) + { + totalWork += cutoutSet.cutouts[i].convexLoops.size(); + } + totalWork *= (ixStop-ixStart)*(iyStop-iyStart); + nvidia::HierarchicalProgressListener localProgressListener(PxMax((int)totalWork, 1), &progressListener); + + physx::Array<uint32_t> unhandledChunks; + + if (cutoutDepth == 0.0f) + { + cutoutDepth = 2*localExtents[2]; // handle special case of all-the-way-through cutout. cutoutDepth is only used for noise grid calculations + } + + const unsigned cutoutChunkDepth = hMesh.depth(faceChunkIndex) + 1; + + // Loop over cutouts on the outside loop. For each cutout, create all tiled (potential) clones + for (uint32_t i = 0; i < cutoutSet.cutouts.size() && !canceled; ++i) + { + // Keep track of starting chunk count. We will process the newly created chunks below. + const uint32_t oldChunkCount = hMesh.chunkCount(); + + for (int32_t iy = iyStart; iy < iyStop && !canceled; ++iy) + { + for (int32_t ix = ixStart; ix < ixStop && !canceled; ++ix) + { + physx::PxVec3 offset = (float)ix*mapWidth*xDir + (float)iy*mapHeight*yDir; + nvidia::Vertex interpolation; + interpolation.position = offset; + uvMapTriangleFrame.interpolateVertexData(interpolation); + // BRG - note bizarre need to flip v... + physx::PxVec2 uvOffset(interpolation.uv[0].u, -interpolation.uv[0].v); + PxMat44 offsetCutoutTM(cutoutTM); + offsetCutoutTM.setPosition(offsetCutoutTM.getPosition() + offset); + const uint32_t newChunkIndex = hMesh.chunkCount(); + canceled = !createCutout(hMesh, faceChunkIndex, faceBSP, cutoutSet.cutouts[i], offsetCutoutTM, desc, edgeNoise, cutoutDepth, materialDesc, getVolumeDesc(collisionDesc, cutoutChunkDepth), minPlane, maxPlane, localProgressListener, cancel); + if (!canceled && instanceCongruentChunks && newChunkIndex < hMesh.chunkCount()) + { + PX_ASSERT(newChunkIndex + 1 == hMesh.chunkCount()); + if (newChunkIndex + 1 == hMesh.chunkCount()) + { + hMesh.mChunks[newChunkIndex]->mInstancedPositionOffset = offset; + hMesh.mChunks[newChunkIndex]->mInstancedUVOffset = uvOffset; + } + } + } + } + + // Keep track of which chunks we've checked for congruence + const uint32_t possibleCongruentChunkCount = hMesh.chunkCount()-oldChunkCount; + + unhandledChunks.resize(possibleCongruentChunkCount); + uint32_t index = hMesh.chunkCount(); + for (uint32_t i = 0; i < possibleCongruentChunkCount; ++i) + { + unhandledChunks[i] = --index; + } + + uint32_t unhandledChunkCount = possibleCongruentChunkCount; + while (unhandledChunkCount > 0) + { + // Have a fresh chunk to test for instancing. + const uint32_t chunkIndex = unhandledChunks[--unhandledChunkCount]; + ExplicitHierarchicalMeshImpl::Chunk* chunk = hMesh.mChunks[chunkIndex]; + + // Record its offset and rebase + const physx::PxVec3 instancingBaseOffset = chunk->mInstancedPositionOffset; + const physx::PxVec2 instancingBaseUVOffset = chunk->mInstancedUVOffset; + chunk->mInstancedPositionOffset = physx::PxVec3(0.0f); + chunk->mInstancedUVOffset = physx::PxVec2(0.0f); + + // If this option is selected, slice regions further + switch (desc.chunkFracturingMethod) + { + case FractureCutoutDesc::SliceFractureCutoutChunks: + { + // Split hierarchically + physx::PxPlane trailingPlanes[3]; // passing in depth = 0 will initialize these + physx::PxPlane leadingPlanes[3]; +#if 1 // Eliminating volume calculation here, for performance. May introduce it later once the mesh is calculated. + const float bspVolume = 1.0f; +#else + float bspArea, bspVolume; + chunkBSP->getSurfaceAreaAndVolume(bspArea, bspVolume, true); +#endif + canceled = !hierarchicallySplitChunkInternal(hMesh, chunkIndex, 0, trailingPlanes, leadingPlanes, *hMesh.mParts[(uint32_t)chunk->mPartIndex]->mMeshBSP, bspVolume, sliceDesc, collisionDesc, localProgressListener, cancel); + } + break; + case FractureCutoutDesc::VoronoiFractureCutoutChunks: + { + // Voronoi split + cutoutVoronoiDesc.siteCount = createVoronoiSitesInsideMeshInternal(hMesh, &chunkIndex, 1, voronoiDesc.siteCount > 0 ? &perChunkSites[0] : NULL, NULL, voronoiDesc.siteCount, NULL, &gMicrogridSize, gMeshMode, progressListener ); + canceled = !voronoiSplitChunkInternal(hMesh, chunkIndex, *hMesh.mParts[(uint32_t)chunk->mPartIndex]->mMeshBSP, cutoutVoronoiDesc, collisionDesc, localProgressListener, cancel); + } + break; + } + + // Now see if we can instance this chunk + if (unhandledChunkCount > 0) + { + bool congruentChunkFound = false; + uint32_t testChunkCount = unhandledChunkCount; + while (testChunkCount > 0) + { + const uint32_t testChunkIndex = unhandledChunks[--testChunkCount]; + ExplicitHierarchicalMeshImpl::Chunk* testChunk = hMesh.mChunks[testChunkIndex]; + const uint32_t testPartIndex = (uint32_t)testChunk->mPartIndex; + ExplicitHierarchicalMeshImpl::Part* testPart = hMesh.mParts[testPartIndex]; + + // Create a shifted BSP of the test chunk + ApexCSG::IApexBSP* combinedBSP = createBSP(hMesh.mBSPMemCache); + const physx::PxMat44 tm(physx::PxMat33(physx::PxIdentity), instancingBaseOffset-testChunk->mInstancedPositionOffset); + combinedBSP->copy(*testPart->mMeshBSP, tm); + combinedBSP->combine(*hMesh.mParts[(uint32_t)chunk->mPartIndex]->mMeshBSP); + float xorArea, xorVolume; + combinedBSP->getSurfaceAreaAndVolume(xorArea, xorVolume, true, ApexCSG::Operation::Exclusive_Or); + combinedBSP->release(); + if (xorVolume <= volumeTol) + { + // XOR of the two volumes is nearly zero. Consider these chunks to be congruent, and instance. + congruentChunkFound = true; + testChunk->mInstancedPositionOffset -= instancingBaseOffset; // Correct offset + testChunk->mInstancedUVOffset -= instancingBaseUVOffset; // Correct offset + testChunk->mFlags |= nvidia::apex::DestructibleAsset::ChunkIsInstanced; // Set instance flag + hMesh.removePart((uint32_t)testChunk->mPartIndex); // Remove part for this chunk, since we'll be instancing another part + testChunk->mPartIndex = chunk->mPartIndex; + instanceChildren(hMesh, chunkIndex, testChunkIndex); // Recursive + --unhandledChunkCount; // This chunk is handled now + nvidia::swap(unhandledChunks[unhandledChunkCount],unhandledChunks[testChunkCount]); // Keep the unhandled chunk array packed + } + } + + // If the chunk is instanced, then mark it so + if (congruentChunkFound) + { + chunk->mFlags |= nvidia::apex::DestructibleAsset::ChunkIsInstanced; + } + } + } + + // Second pass at cutout chunks + for (uint32_t j = 0; j < possibleCongruentChunkCount && !canceled; ++j) + { + ExplicitHierarchicalMeshImpl::Chunk* chunk = hMesh.mChunks[oldChunkCount+j]; + if ((chunk->mFlags & nvidia::apex::DestructibleAsset::ChunkIsInstanced) == 0) + { + // This chunk will not be instanced. Zero its offsets. + chunk->mInstancedPositionOffset = physx::PxVec3(0.0f); + chunk->mInstancedUVOffset = physx::PxVec2(0.0f); + } + } + } + +// cutoutSource->release(); + + return !canceled; +} + +static bool cutoutFace +( + ExplicitHierarchicalMeshImpl& hMesh, + physx::Array<physx::PxPlane>& faceTrimPlanes, + ApexCSG::IApexBSP* coreBSP, + uint32_t& coreChunkIndex, // This may be changed if the original core chunk is sliced away completely + const nvidia::FractureCutoutDesc& desc, + const nvidia::NoiseParameters& backfaceNoise, + const nvidia::NoiseParameters& edgeNoise, + const nvidia::FractureMaterialDesc& materialDesc, + const int32_t fractureIndex, + const physx::PxPlane& facePlane, + const nvidia::CutoutSet& iCutoutSetImpl, + const PxMat44& cutoutTM, + const float mapXLow, + const float mapYLow, + const physx::PxBounds3& localBounds, + const float cutoutDepth, + const nvidia::FractureSliceDesc& sliceDesc, + const nvidia::FractureVoronoiDesc& voronoiDesc, + const CollisionDesc& collisionDesc, + nvidia::IProgressListener& progressListener, + bool& stop, + volatile bool* cancel +) +{ + nvidia::HierarchicalProgressListener localProgressListener(PxMax((int32_t)iCutoutSetImpl.getCutoutCount(), 1), &progressListener); + + const physx::PxVec3 localExtents = localBounds.getExtents(); + const physx::PxVec3 localCenter = localBounds.getCenter(); + + const float sizeScale = PxMax(PxMax(localExtents.x, localExtents.y), localExtents.z); + + uint32_t corePartIndex = (uint32_t)hMesh.mChunks[coreChunkIndex]->mPartIndex; + + const uint32_t oldSize = hMesh.chunkCount(); + ApexCSG::IApexBSP* faceBSP = createBSP(hMesh.mBSPMemCache); // face BSP defaults to all space + uint32_t faceChunkIndex = 0xFFFFFFFF; + if (cutoutDepth > 0.0f) // (depth = 0) => slice all the way through + { + nvidia::IntersectMesh grid; + + const float mapWidth = cutoutTM.column0.magnitude()*iCutoutSetImpl.getDimensions()[0]; + const float mapHeight = cutoutTM.column1.magnitude()*iCutoutSetImpl.getDimensions()[1]; + + // Create faceBSP from grid + GridParameters gridParameters; + gridParameters.interiorSubmeshIndex = materialDesc.interiorSubmeshIndex; + gridParameters.noise = backfaceNoise; + gridParameters.level0Mesh = &hMesh.mParts[0]->mMesh; // must be set each time, since this can move with array resizing + gridParameters.sizeScale = sizeScale; + if (desc.instancingMode != FractureCutoutDesc::DoNotInstance) + { + gridParameters.xPeriod = mapWidth; + gridParameters.yPeriod = mapHeight; + } + // Create the slicing plane + physx::PxPlane slicePlane = facePlane; + slicePlane.d += cutoutDepth; + gridParameters.materialFrameIndex = hMesh.addMaterialFrame(); + nvidia::MaterialFrame materialFrame = hMesh.getMaterialFrame(gridParameters.materialFrameIndex); + materialFrame.buildCoordinateSystemFromMaterialDesc(materialDesc, slicePlane); + materialFrame.mFractureMethod = nvidia::FractureMethod::Cutout; + materialFrame.mFractureIndex = fractureIndex; + materialFrame.mSliceDepth = hMesh.depth(coreChunkIndex) + 1; + hMesh.setMaterialFrame(gridParameters.materialFrameIndex, materialFrame); + gridParameters.triangleFrame.setFlat(materialFrame.mCoordinateSystem, materialDesc.uvScale, materialDesc.uvOffset); + buildIntersectMesh(grid, slicePlane, materialFrame, (int32_t)sliceDesc.noiseMode, &gridParameters); + ApexCSG::BSPTolerances bspTolerances = ApexCSG::gDefaultTolerances; + bspTolerances.linear = 0.00001f; + bspTolerances.angular = 0.00001f; + faceBSP->setTolerances(bspTolerances); + ApexCSG::BSPBuildParameters bspBuildParams = gDefaultBuildParameters; + bspBuildParams.rnd = &userRnd; + bspBuildParams.internalTransform = coreBSP->getInternalTransform(); + faceBSP->fromMesh(&grid.m_triangles[0], grid.m_triangles.size(), bspBuildParams); + coreBSP->combine(*faceBSP); + faceBSP->op(*coreBSP, ApexCSG::Operation::A_Minus_B); + coreBSP->op(*coreBSP, ApexCSG::Operation::Intersection); + uint32_t facePartIndex = hMesh.addPart(); + faceChunkIndex = hMesh.addChunk(); + hMesh.mChunks[faceChunkIndex]->mPartIndex = (int32_t)facePartIndex; + faceBSP->toMesh(hMesh.mParts[facePartIndex]->mMesh); + CollisionVolumeDesc volumeDesc = getVolumeDesc(collisionDesc, hMesh.depth(coreChunkIndex) + 1); + if (hMesh.mParts[facePartIndex]->mMesh.size() != 0) + { + hMesh.mParts[facePartIndex]->mMeshBSP->copy(*faceBSP); + hMesh.buildMeshBounds(facePartIndex); + hMesh.buildCollisionGeometryForPart(facePartIndex, volumeDesc); + if (desc.trimFaceCollisionHulls && (gridParameters.noise.amplitude != 0.0f || volumeDesc.mHullMethod != nvidia::ConvexHullMethod::WRAP_GRAPHICS_MESH)) + { + // Trim backface + for (uint32_t hullIndex = 0; hullIndex < hMesh.mParts[facePartIndex]->mCollision.size(); ++hullIndex) + { + ConvexHullImpl& hull = hMesh.mParts[facePartIndex]->mCollision[hullIndex]->impl; + hull.intersectPlaneSide(physx::PxPlane(-slicePlane.n, -slicePlane.d)); + faceTrimPlanes.pushBack(slicePlane); + } + } + hMesh.mChunks[faceChunkIndex]->mParentIndex = 0; + } + else + { + hMesh.removePart(facePartIndex); + hMesh.removeChunk(faceChunkIndex); + faceChunkIndex = 0xFFFFFFFF; + facePartIndex = 0xFFFFFFFF; + } + } + else + { + // Slicing goes all the way through + faceBSP->copy(*coreBSP); + if (oldSize == coreChunkIndex + 1) + { + // Core hasn't been split yet. We don't want a copy of the original mesh at level 1, so remove it. + hMesh.removePart(corePartIndex--); + hMesh.removeChunk(coreChunkIndex--); + } + faceChunkIndex = coreChunkIndex; + // This will break us out of both loops (only want to slice all the way through once): + stop = true; + } + + localProgressListener.setSubtaskWork(1); + + bool canceled = false; + + if (faceChunkIndex < hMesh.chunkCount()) + { + // We have a face chunk. Create cutouts + canceled = !createFaceCutouts(hMesh, faceChunkIndex, *faceBSP, desc, edgeNoise, cutoutDepth, materialDesc, *(const nvidia::CutoutSetImpl*)&iCutoutSetImpl, cutoutTM, mapXLow, mapYLow, collisionDesc, + sliceDesc, voronoiDesc, facePlane, localCenter, localExtents, localProgressListener, cancel); + // If there is anything left in the face, attach it as unfracturable + // Volume rejection ratio, perhaps should be exposed +#if 0 // BRG - to do : better treatment of face leftover + const float volumeRejectionRatio = 0.0001f; + if (faceBSP->getVolume() >= volumeRejectionRatio * faceVolumeEstimate) + { + const uint32_t newPartIndex = hMesh.addPart(); + faceBSP->toMesh(hMesh.mParts[newPartIndex]->mMesh); + if (hMesh.mParts[newPartIndex]->mMesh.size() != 0) + { + hMesh.mParts[newPartIndex]->mMeshBSP->copy(*faceBSP); + hMesh.buildMeshBounds(newPartIndex); + hMesh.mParts[newPartIndex]->mCollision.setEmpty(); // BRG - to do : better treatment of face leftover + hMesh.mParts[newPartIndex]->mParentIndex = faceChunkIndex; + chunkFlags.resize(hMesh.partCount(), 0); + } + else + { + hMesh.removePart(newPartIndex); + } + } +#endif + + localProgressListener.completeSubtask(); + } + faceBSP->release(); + + return !canceled; +} + +bool createChippedMesh +( + ExplicitHierarchicalMesh& iHMesh, + const nvidia::MeshProcessingParameters& meshProcessingParams, + const nvidia::FractureCutoutDesc& desc, + const nvidia::CutoutSet& iCutoutSetImpl, + const nvidia::FractureSliceDesc& sliceDesc, + const nvidia::FractureVoronoiDesc& voronoiDesc, + const CollisionDesc& collisionDesc, + uint32_t randomSeed, + nvidia::IProgressListener& progressListener, + volatile bool* cancel +) +{ + ExplicitHierarchicalMeshImpl& hMesh = *(ExplicitHierarchicalMeshImpl*)&iHMesh; + + if (hMesh.partCount() == 0) + { + return false; + } + + outputMessage("Chipping..."); + progressListener.setProgress(0); + + // Save state if cancel != NULL + physx::PxFileBuf* save = NULL; + class NullEmbedding : public ExplicitHierarchicalMesh::Embedding + { + void serialize(physx::PxFileBuf& stream, Embedding::DataType type) const + { + (void)stream; + (void)type; + } + void deserialize(physx::PxFileBuf& stream, Embedding::DataType type, uint32_t version) + { + (void)stream; + (void)type; + (void)version; + } + } embedding; + if (cancel != NULL) + { + save = nvidia::GetApexSDK()->createMemoryWriteStream(); + if (save != NULL) + { + hMesh.serialize(*save, embedding); + } + } + + hMesh.buildCollisionGeometryForPart(0, getVolumeDesc(collisionDesc, 0)); + + userRnd.m_rnd.setSeed(randomSeed); + + if (hMesh.mParts[0]->mMeshBSP->getType() != ApexCSG::BSPType::Nontrivial) + { + outputMessage("Building mesh BSP..."); + progressListener.setProgress(0); + hMesh.calculateMeshBSP(randomSeed, &progressListener, &meshProcessingParams.microgridSize, meshProcessingParams.meshMode); + outputMessage("Mesh BSP completed."); + userRnd.m_rnd.setSeed(randomSeed); + } + + gIslandGeneration = meshProcessingParams.islandGeneration; + gMicrogridSize = meshProcessingParams.microgridSize; + gVerbosity = meshProcessingParams.verbosity; + + if (hMesh.mParts[0]->mBounds.isEmpty()) + { + return false; // Done, nothing in mesh + } + + hMesh.clear(true); + + for (int i = 0; i < FractureCutoutDesc::DirectionCount; ++i) + { + if ((desc.directions >> i) & 1) + { + hMesh.mSubmeshData.resize(PxMax(hMesh.mRootSubmeshCount, desc.cutoutParameters[i].materialDesc.interiorSubmeshIndex + 1)); + } + } + switch (desc.chunkFracturingMethod) + { + case FractureCutoutDesc::SliceFractureCutoutChunks: + for (int i = 0; i < 3; ++i) + { + hMesh.mSubmeshData.resize(PxMax(hMesh.mRootSubmeshCount, sliceDesc.materialDesc[i].interiorSubmeshIndex + 1)); + } + break; + case FractureCutoutDesc::VoronoiFractureCutoutChunks: + hMesh.mSubmeshData.resize(PxMax(hMesh.mRootSubmeshCount, voronoiDesc.materialDesc.interiorSubmeshIndex + 1)); + break; + } + + // Count directions + uint32_t directionCount = 0; + uint32_t directions = desc.directions; + while (directions) + { + directions = (directions - 1)&directions; + ++directionCount; + } + + if (directionCount == 0 && desc.userDefinedDirection.isZero()) // directions = 0 is the way we invoke the user-supplied normal "UV-based" cutout fracturing + { + return true; // Done, no split directions + } + + // Validate direction ordering + bool dirUsed[FractureCutoutDesc::DirectionCount]; + memset(dirUsed, 0, sizeof(dirUsed) / sizeof(dirUsed[0])); + for (uint32_t dirIndex = 0; dirIndex < FractureCutoutDesc::DirectionCount; ++dirIndex) + { + // The direction must be one found in FractureCutoutDesc::Directions + // and must not be used twice, if it is enabled + if ((directions & desc.directionOrder[dirIndex]) && + (!shdfnd::isPowerOfTwo(desc.directionOrder[dirIndex]) || + desc.directionOrder[dirIndex] <= 0 || + desc.directionOrder[dirIndex] > FractureCutoutDesc::PositiveZ || + dirUsed[lowestSetBit(desc.directionOrder[dirIndex])])) + { + outputMessage("Invalid direction ordering, each direction may be used just once, " + "and must correspond to a direction defined in FractureCutoutDesc::Directions.", + physx::PxErrorCode::eINTERNAL_ERROR); + return false; + } + dirUsed[dirIndex] = true; + } + + nvidia::HierarchicalProgressListener localProgressListener(PxMax((int32_t)directionCount, 1), &progressListener); + + // Core starts as original mesh + uint32_t corePartIndex = hMesh.addPart(); + uint32_t coreChunkIndex = hMesh.addChunk(); + hMesh.mParts[corePartIndex]->mMesh = hMesh.mParts[0]->mMesh; + hMesh.buildMeshBounds(0); + hMesh.mChunks[coreChunkIndex]->mParentIndex = 0; + hMesh.mChunks[coreChunkIndex]->mPartIndex = (int32_t)corePartIndex; + + ApexCSG::IApexBSP* coreBSP = createBSP(hMesh.mBSPMemCache); + coreBSP->copy(*hMesh.mParts[0]->mMeshBSP); + + physx::Array<physx::PxPlane> faceTrimPlanes; + + const physx::PxBounds3& worldBounds = hMesh.mParts[0]->mBounds; + const physx::PxVec3& extents = worldBounds.getExtents(); + const physx::PxVec3& center = worldBounds.getCenter(); + + SliceParameters* sliceParametersAtDepth = (SliceParameters*)PxAlloca(sizeof(SliceParameters) * sliceDesc.maxDepth); + + bool canceled = false; + bool stop = false; + for (uint32_t dirNum = 0; dirNum < FractureCutoutDesc::DirectionCount && !stop && !canceled; ++dirNum) + { + const uint32_t sliceDirIndex = lowestSetBit(desc.directionOrder[dirNum]); + uint32_t sliceAxisNum, sliceSignNum; + getCutoutSliceAxisAndSign(sliceAxisNum, sliceSignNum, sliceDirIndex); + { + if ((desc.directions >> sliceDirIndex) & 1) + { + uint32_t sliceAxes[3]; + generateSliceAxes(sliceAxes, sliceAxisNum); + + localProgressListener.setSubtaskWork(1); + + physx::PxPlane facePlane; + facePlane.n = physx::PxVec3(0, 0, 0); + facePlane.n[sliceAxisNum] = sliceSignNum ? -1.0f : 1.0f; + facePlane.d = -(facePlane.n[sliceAxisNum] * center[sliceAxisNum] + extents[sliceAxisNum]); // coincides with depth = 0 + + bool invertX; + const PxMat44 cutoutTM = createCutoutFrame(center, extents, sliceAxes, sliceSignNum, desc, invertX); + + // Tiling bounds + const float mapWidth = cutoutTM.column0.magnitude()*iCutoutSetImpl.getDimensions()[0]; + const float mapXLow = cutoutTM.getPosition()[sliceAxes[0]] - ((invertX != desc.cutoutWidthInvert[sliceDirIndex])? mapWidth : 0.0f); + const float mapHeight = cutoutTM.column1.magnitude()*iCutoutSetImpl.getDimensions()[1]; + const float mapYLow = cutoutTM.getPosition()[sliceAxes[1]] - (desc.cutoutHeightInvert[sliceDirIndex] ? mapHeight : 0.0f); + + physx::PxBounds3 localBounds; + for (unsigned i = 0; i < 3; ++i) + { + localBounds.minimum[i] = worldBounds.minimum[sliceAxes[i]]; + localBounds.maximum[i] = worldBounds.maximum[sliceAxes[i]]; + } + + // Slice desc, if needed + nvidia::FractureSliceDesc cutoutSliceDesc; + // Create a sliceDesc based off of the GUI slice desc's X and Y components, applied to the + // two axes appropriate for this cutout direction. + cutoutSliceDesc = sliceDesc; + cutoutSliceDesc.sliceParameters = sliceParametersAtDepth; + for (unsigned depth = 0; depth < sliceDesc.maxDepth; ++depth) + { + cutoutSliceDesc.sliceParameters[depth] = sliceDesc.sliceParameters[depth]; + } + for (uint32_t axisN = 0; axisN < 3; ++axisN) + { + cutoutSliceDesc.targetProportions[sliceAxes[axisN]] = sliceDesc.targetProportions[axisN]; + for (uint32_t depth = 0; depth < sliceDesc.maxDepth; ++depth) + { + cutoutSliceDesc.sliceParameters[depth].splitsPerPass[sliceAxes[axisN]] = sliceDesc.sliceParameters[depth].splitsPerPass[axisN]; + cutoutSliceDesc.sliceParameters[depth].linearVariation[sliceAxes[axisN]] = sliceDesc.sliceParameters[depth].linearVariation[axisN]; + cutoutSliceDesc.sliceParameters[depth].angularVariation[sliceAxes[axisN]] = sliceDesc.sliceParameters[depth].angularVariation[axisN]; + cutoutSliceDesc.sliceParameters[depth].noise[sliceAxes[axisN]] = sliceDesc.sliceParameters[depth].noise[axisN]; + } + } + + canceled = !cutoutFace(hMesh, faceTrimPlanes, coreBSP, coreChunkIndex, desc, desc.cutoutParameters[sliceDirIndex].backfaceNoise, desc.cutoutParameters[sliceDirIndex].edgeNoise, + desc.cutoutParameters[sliceDirIndex].materialDesc, (int32_t)sliceDirIndex, facePlane, iCutoutSetImpl, cutoutTM, mapXLow, mapYLow, localBounds, + desc.cutoutParameters[sliceDirIndex].depth, cutoutSliceDesc, voronoiDesc, collisionDesc, localProgressListener, stop, cancel); + + localProgressListener.completeSubtask(); + } + } + } + + if (desc.directions == 0) // user-supplied normal "UV-based" cutout fracturing + { + localProgressListener.setSubtaskWork(1); + + // Create cutout transform from user's supplied mapping and direction + const physx::PxVec3 userNormal = desc.userDefinedDirection.getNormalized(); + + PxMat44 cutoutTM; + cutoutTM.column0 = PxVec4(desc.userUVMapping.column0/(float)desc.cutoutSizeX, 0.f); + cutoutTM.column1 = PxVec4(desc.userUVMapping.column1/(float)desc.cutoutSizeY, 0.f); + cutoutTM.column2 = PxVec4(userNormal, 0.f); + cutoutTM.setPosition(desc.userUVMapping.column2); + + // Also create a local frame to get the local bounds for the mesh + const physx::PxMat33 globalToLocal = physx::PxMat33(desc.userUVMapping.column0.getNormalized(), desc.userUVMapping.column1.getNormalized(), userNormal).getTranspose(); + + physx::Array<nvidia::ExplicitRenderTriangle>& mesh = hMesh.mParts[0]->mMesh; + physx::PxBounds3 localBounds; + localBounds.setEmpty(); + for (uint32_t i = 0; i < mesh.size(); ++i) + { + nvidia::ExplicitRenderTriangle& tri = mesh[i]; + for (int v = 0; v < 3; ++v) + { + localBounds.include(globalToLocal*tri.vertices[v].position); + } + } + + physx::PxPlane facePlane; + facePlane.n = userNormal; + facePlane.d = -localBounds.maximum[2]; // coincides with depth = 0 + + // Tiling bounds + const physx::PxVec3 localOrigin = globalToLocal*cutoutTM.getPosition(); + const float mapXLow = localOrigin[0]; + const float mapYLow = localOrigin[1]; + + canceled = !cutoutFace(hMesh, faceTrimPlanes, coreBSP, coreChunkIndex, desc, desc.userDefinedCutoutParameters.backfaceNoise, desc.userDefinedCutoutParameters.edgeNoise, + desc.userDefinedCutoutParameters.materialDesc, 6, facePlane, iCutoutSetImpl, cutoutTM, mapXLow, mapYLow, localBounds, desc.userDefinedCutoutParameters.depth, + sliceDesc, voronoiDesc, collisionDesc, localProgressListener, stop, cancel); + + localProgressListener.completeSubtask(); + } + + if (!canceled && coreChunkIndex != 0) + { + coreBSP->toMesh(hMesh.mParts[corePartIndex]->mMesh); + if (hMesh.mParts[corePartIndex]->mMesh.size() != 0) + { + hMesh.mParts[corePartIndex]->mMeshBSP->copy(*coreBSP); + hMesh.buildCollisionGeometryForPart(coreChunkIndex, getVolumeDesc(collisionDesc, hMesh.depth(coreChunkIndex))); + for (uint32_t i = 0; i < faceTrimPlanes.size(); ++i) + { + for (uint32_t hullIndex = 0; hullIndex < hMesh.mParts[corePartIndex]->mCollision.size(); ++hullIndex) + { + ConvexHullImpl& hull = hMesh.mParts[corePartIndex]->mCollision[hullIndex]->impl; + hull.intersectPlaneSide(faceTrimPlanes[i]); + } + } + } + else + { + // Remove core mesh and chunk + if (corePartIndex < hMesh.mParts.size()) + { + hMesh.removePart(corePartIndex); + } + if (coreChunkIndex < hMesh.mChunks.size()) + { + hMesh.removeChunk(coreChunkIndex); + } + coreChunkIndex = 0xFFFFFFFF; + corePartIndex = 0xFFFFFFFF; + } + } + + coreBSP->release(); + + // Restore if canceled + if (canceled && save != NULL) + { + uint32_t len; + const void* mem = nvidia::GetApexSDK()->getMemoryWriteBuffer(*save, len); + physx::PxFileBuf* load = nvidia::GetApexSDK()->createMemoryReadStream(mem, len); + if (load != NULL) + { + hMesh.deserialize(*load, embedding); + nvidia::GetApexSDK()->releaseMemoryReadStream(*load); + } + } + + if (save != NULL) + { + nvidia::GetApexSDK()->releaseMemoryReadStream(*save); + } + + if (canceled) + { + return false; + } + + if (meshProcessingParams.removeTJunctions) + { + MeshProcessor meshProcessor; + for (uint32_t i = 0; i < hMesh.partCount(); ++i) + { + meshProcessor.setMesh(hMesh.mParts[i]->mMesh, NULL, 0, 0.0001f*extents.magnitude()); + meshProcessor.removeTJunctions(); + } + } + + hMesh.sortChunks(); + + hMesh.createPartSurfaceNormals(); + + if (desc.instancingMode == FractureCutoutDesc::InstanceAllChunks) + { + for (uint32_t i = 0; i < hMesh.chunkCount(); ++i) + { + hMesh.mChunks[i]->mFlags |= nvidia::apex::DestructibleAsset::ChunkIsInstanced; + } + } + + outputMessage("chipping completed."); + + return true; +} + +class VoronoiMeshSplitter : public MeshSplitter +{ +private: + VoronoiMeshSplitter& operator=(const VoronoiMeshSplitter&); + +public: + VoronoiMeshSplitter(const FractureVoronoiDesc& desc) : mDesc(desc) + { + } + + bool validate(ExplicitHierarchicalMeshImpl& hMesh) + { + if (hMesh.chunkCount() == 0) + { + return false; + } + + if (mDesc.siteCount == 0) + { + return false; + } + + return true; + } + + void initialize(ExplicitHierarchicalMeshImpl& hMesh) + { + hMesh.mSubmeshData.resize(PxMax(hMesh.mRootSubmeshCount, mDesc.materialDesc.interiorSubmeshIndex + 1)); + + // Need to split out DM parameters +// if (mDesc.useDisplacementMaps) +// { +// hMesh.initializeDisplacementMapVolume(mDesc); +// } + } + + bool process + ( + ExplicitHierarchicalMeshImpl& hMesh, + uint32_t chunkIndex, + const ApexCSG::IApexBSP& chunkBSP, + const CollisionDesc& collisionDesc, + nvidia::IProgressListener& progressListener, + volatile bool* cancel + ) + { + return voronoiSplitChunkInternal(hMesh, chunkIndex, chunkBSP, mDesc, collisionDesc, progressListener, cancel); + } + + bool finalize(ExplicitHierarchicalMeshImpl& hMesh) + { + if (mDesc.instanceChunks) + { + for (uint32_t i = 0; i < hMesh.partCount(); ++i) + { + hMesh.mChunks[i]->mFlags |= nvidia::apex::DestructibleAsset::ChunkIsInstanced; + } + } + + return true; + } + +protected: + const FractureVoronoiDesc& mDesc; +}; + +bool createVoronoiSplitMesh +( + ExplicitHierarchicalMesh& iHMesh, + ExplicitHierarchicalMesh& iHMeshCore, + bool exportCoreMesh, + int32_t coreMeshImprintSubmeshIndex, // If this is < 0, use the core mesh materials (was applyCoreMeshMaterialToNeighborChunks). Otherwise, use the given submesh. + const MeshProcessingParameters& meshProcessingParams, + const FractureVoronoiDesc& desc, + const CollisionDesc& collisionDesc, + uint32_t randomSeed, + nvidia::IProgressListener& progressListener, + volatile bool* cancel +) +{ + VoronoiMeshSplitter splitter(desc); + + return splitMeshInternal( + iHMesh, + iHMeshCore, + exportCoreMesh, + coreMeshImprintSubmeshIndex, + meshProcessingParams, + splitter, + collisionDesc, + randomSeed, + progressListener, + cancel); +} + +bool voronoiSplitChunk +( + ExplicitHierarchicalMesh& iHMesh, + uint32_t chunkIndex, + const FractureTools::MeshProcessingParameters& meshProcessingParams, + const FractureTools::FractureVoronoiDesc& desc, + const CollisionDesc& collisionDesc, + uint32_t* randomSeed, + IProgressListener& progressListener, + volatile bool* cancel +) +{ + VoronoiMeshSplitter splitter(desc); + + return splitChunkInternal(iHMesh, chunkIndex, meshProcessingParams, splitter, collisionDesc, randomSeed, progressListener, cancel); +} + +uint32_t createVoronoiSitesInsideMesh +( + ExplicitHierarchicalMesh& iHMesh, + physx::PxVec3* siteBuffer, + uint32_t* siteChunkIndices, + uint32_t siteCount, + uint32_t* randomSeed, + uint32_t* microgridSize, + BSPOpenMode::Enum meshMode, + IProgressListener& progressListener, + uint32_t chunkIndex +) +{ + ExplicitHierarchicalMeshImpl& hMesh = *(ExplicitHierarchicalMeshImpl*)&iHMesh; + + physx::Array<uint32_t> chunkList; + + if (hMesh.mChunks.size() == 0) + { + return 0; + } + + if (chunkIndex >= hMesh.chunkCount()) + { + // Find root-depth chunks + for (uint32_t chunkIndex = 0; chunkIndex < hMesh.chunkCount(); ++chunkIndex) + { + if (hMesh.mChunks[chunkIndex]->isRootLeafChunk()) + { + chunkList.pushBack(chunkIndex); + } + } + + if (chunkList.size() > 0) + { + return createVoronoiSitesInsideMeshInternal(hMesh, &chunkList[0], chunkList.size(), siteBuffer, siteChunkIndices, siteCount, randomSeed, microgridSize, meshMode, progressListener); + } + + return 0; // This means we didn't find a root leaf chunk + } + + return createVoronoiSitesInsideMeshInternal(hMesh, &chunkIndex, 1, siteBuffer, siteChunkIndices, siteCount, randomSeed, microgridSize, meshMode, progressListener); +} + +// Defining these structs here, so as not to offend gnu's sensibilities +struct TriangleData +{ + uint16_t chunkIndex; + physx::PxVec3 triangleNormal; + float summedAreaWeight; + const nvidia::ExplicitRenderTriangle* triangle; +}; + +struct InstanceInfo +{ + uint8_t meshIndex; + int32_t chunkIndex; // Using a int32_t so that the createIndexStartLookup can do its thing + physx::PxMat44 relativeTransform; + + struct ChunkIndexLessThan + { + PX_INLINE bool operator()(const InstanceInfo& x, const InstanceInfo& y) const + { + return x.chunkIndex < y.chunkIndex; + } + }; +}; + +uint32_t createScatterMeshSites +( + uint8_t* meshIndices, + physx::PxMat44* relativeTransforms, + uint32_t* chunkMeshStarts, + uint32_t scatterMeshInstancesBufferSize, + ExplicitHierarchicalMesh& iHMesh, + uint32_t targetChunkCount, + const uint16_t* targetChunkIndices, + uint32_t* randomSeed, + uint32_t scatterMeshAssetCount, + nvidia::RenderMeshAsset** scatterMeshAssets, + const uint32_t* minCount, + const uint32_t* maxCount, + const float* minScales, + const float* maxScales, + const float* maxAngles +) +{ + ExplicitHierarchicalMeshImpl& hMesh = *(ExplicitHierarchicalMeshImpl*)&iHMesh; + + // Cap asset count to 1-byte range + if (scatterMeshAssetCount > 255) + { + scatterMeshAssetCount = 255; + } + + // Set random seed if requested + if (randomSeed != NULL) + { + userRnd.m_rnd.setSeed(*randomSeed); + } + + // Counts for each scatter mesh asset + physx::Array<uint32_t> counts(scatterMeshAssetCount, 0); + + // Create convex hulls for each scatter mesh and add up valid weights + physx::Array<physx::PxVec3> vertices; // Reusing this array for convex hull building + physx::Array<PartConvexHullProxy> hulls(scatterMeshAssetCount); + uint32_t scatterMeshInstancesRequested = 0; + for (uint32_t scatterMeshAssetIndex = 0; scatterMeshAssetIndex < scatterMeshAssetCount; ++scatterMeshAssetIndex) + { + hulls[scatterMeshAssetIndex].impl.setEmpty(); + const nvidia::RenderMeshAsset* rma = scatterMeshAssets[scatterMeshAssetIndex]; + if (rma != NULL) + { + vertices.resize(0); + for (uint32_t submeshIndex = 0; submeshIndex < rma->getSubmeshCount(); ++submeshIndex) + { + const nvidia::RenderSubmesh& submesh = rma->getSubmesh(submeshIndex); + const nvidia::VertexBuffer& vertexBuffer = submesh.getVertexBuffer(); + if (vertexBuffer.getVertexCount() > 0) + { + const nvidia::VertexFormat& vertexFormat = vertexBuffer.getFormat(); + const int32_t posBufferIndex = vertexFormat.getBufferIndexFromID(vertexFormat.getSemanticID(nvidia::RenderVertexSemantic::POSITION)); + const uint32_t oldVertexCount = vertices.size(); + vertices.resize(oldVertexCount + vertexBuffer.getVertexCount()); + if (!vertexBuffer.getBufferData(&vertices[oldVertexCount], nvidia::RenderDataFormat::FLOAT3, sizeof(physx::PxVec3), (uint32_t)posBufferIndex, 0, vertexBuffer.getVertexCount())) + { + vertices.resize(oldVertexCount); // Operation failed, revert vertex array size + } + } + } + if (vertices.size() > 0) + { + physx::Array<physx::PxVec3> directions; + ConvexHullImpl::createKDOPDirections(directions, nvidia::ConvexHullMethod::USE_6_DOP); + hulls[scatterMeshAssetIndex].impl.buildKDOP(&vertices[0], vertices.size(), sizeof(vertices[0]), &directions[0], directions.size()); + if (!hulls[scatterMeshAssetIndex].impl.isEmpty()) + { + counts[scatterMeshAssetIndex] = (uint32_t)userRnd.m_rnd.getScaled((float)minCount[scatterMeshAssetIndex], (float)maxCount[scatterMeshAssetIndex] + 1.0f); + scatterMeshInstancesRequested += counts[scatterMeshAssetIndex]; + } + } + } + } + + // Cap at buffer size + if (scatterMeshInstancesRequested > scatterMeshInstancesBufferSize) + { + scatterMeshInstancesRequested = scatterMeshInstancesBufferSize; + } + + // Return if no instances requested + if (scatterMeshInstancesRequested == 0) + { + return 0; + } + + // Count the interior triangles in all of the target chunks, and add up their areas + // Build an area-weighted lookup table for the various triangles (also reference the chunks) + physx::Array<TriangleData> triangleTable; + float summedAreaWeight = 0.0f; + for (uint32_t chunkNum = 0; chunkNum < targetChunkCount; ++chunkNum) + { + const uint16_t chunkIndex = targetChunkIndices[chunkNum]; + if (chunkIndex >= hMesh.chunkCount()) + { + continue; + } + const uint32_t partIndex = (uint32_t)*hMesh.partIndex(chunkIndex); + const nvidia::ExplicitRenderTriangle* triangles = hMesh.meshTriangles(partIndex); + const uint32_t triangleCount = hMesh.meshTriangleCount(partIndex); + for (uint32_t triangleIndex = 0; triangleIndex < triangleCount; ++triangleIndex) + { + const ExplicitRenderTriangle& triangle = triangles[triangleIndex]; + if (triangle.extraDataIndex != 0xFFFFFFFF) // See if this is an interior triangle + { + + TriangleData& triangleData = triangleTable.insert(); + triangleData.chunkIndex = chunkIndex; + triangleData.triangleNormal = triangle.calculateNormal(); + summedAreaWeight += triangleData.triangleNormal.normalize(); + triangleData.summedAreaWeight = summedAreaWeight; + triangleData.triangle = ▵ + } + } + } + + // Normalize summed area table + if (summedAreaWeight <= 0.0f) + { + return 0; // Non-normalizable + } + const float recipSummedAreaWeight = 1.0f/summedAreaWeight; + for (uint32_t triangleNum = 0; triangleNum < triangleTable.size()-1; ++triangleNum) + { + triangleTable[triangleNum].summedAreaWeight *= recipSummedAreaWeight; + } + triangleTable[triangleTable.size()-1].summedAreaWeight = 1.0f; // Just to be sure + + // Reserve instance info + physx::Array<InstanceInfo> instanceInfo; + instanceInfo.reserve(scatterMeshInstancesRequested); + + // Add scatter meshes + ApexCSG::IApexBSP* hullBSP = createBSP(hMesh.mBSPMemCache); + if (hullBSP == NULL) + { + return 0; + } + + physx::Array<physx::PxPlane> planes; // Reusing this array for bsp building + + for (uint32_t scatterMeshAssetIndex = 0; scatterMeshAssetIndex < scatterMeshAssetCount && instanceInfo.size() < scatterMeshInstancesRequested; ++scatterMeshAssetIndex) + { + bool success = true; + for (uint32_t count = 0; success && count < counts[scatterMeshAssetIndex] && instanceInfo.size() < scatterMeshInstancesRequested; ++count) + { + success = false; + for (uint32_t trial = 0; !success && trial < 1000; ++trial) + { + // Pick triangle + const TriangleData* triangleData = NULL; + const float unitRndForTriangle = userRnd.m_rnd.getUnit(); + for (uint32_t triangleNum = 0; triangleNum < triangleTable.size(); ++triangleNum) + { + if (triangleTable[triangleNum].summedAreaWeight > unitRndForTriangle) + { + triangleData = &triangleTable[triangleNum]; + break; + } + } + if (triangleData == NULL) + { + continue; + } + + // pick scale, angle, and position and build transform + const float scale = physx::PxExp(userRnd.m_rnd.getScaled(physx::PxLog(minScales[scatterMeshAssetIndex]), physx::PxLog(maxScales[scatterMeshAssetIndex]))); + const float angle = (physx::PxPi/180.0f)*userRnd.m_rnd.getScaled(0.0f, maxAngles[scatterMeshAssetIndex]); + // random position in triangle + const Vertex* vertices = triangleData->triangle->vertices; + const physx::PxVec3 position = randomPositionInTriangle(vertices[0].position, vertices[1].position, vertices[2].position, userRnd.m_rnd); + physx::PxVec3 zAxis = triangleData->triangleNormal; + // Rotate z axis into arbitrary vector in triangle plane + physx::PxVec3 para = vertices[1].position - vertices[0].position; + if (para.normalize() > 0.0f) + { + float cosPhi, sinPhi; + physx::shdfnd::sincos(angle, sinPhi, cosPhi); + zAxis = cosPhi*zAxis + sinPhi*para; + } + physx::PxMat44 tm = randomRotationMatrix(zAxis, userRnd.m_rnd); + tm.setPosition(position); + tm.scale(physx::PxVec4(physx::PxVec3(scale), 1.0f)); + + const int32_t parentIndex = *hMesh.parentIndex(triangleData->chunkIndex); + if (parentIndex >= 0) + { + const uint32_t parentPartIndex = (uint32_t)*hMesh.partIndex((uint32_t)parentIndex); + ApexCSG::IApexBSP* parentPartBSP = hMesh.mParts[parentPartIndex]->mMeshBSP; + if (parentPartBSP != NULL) + { + // Create BSP from hull and transform + PartConvexHullProxy& hull = hulls[scatterMeshAssetIndex]; + planes.resize(hull.impl.getPlaneCount()); + for (uint32_t planeIndex = 0; planeIndex < hull.impl.getPlaneCount(); ++planeIndex) + { + planes[planeIndex] = hull.impl.getPlane(planeIndex); + } + hullBSP->fromConvexPolyhedron(&planes[0], planes.size(), parentPartBSP->getInternalTransform()); + hullBSP->copy(*hullBSP, tm); + + // Now combine with chunk parent bsp, and see if the mesh hull bsp lies within the parent bsp + hullBSP->combine(*hMesh.mParts[parentPartIndex]->mMeshBSP); + hullBSP->op(*hullBSP, ApexCSG::Operation::A_Minus_B); + if (hullBSP->getType() == ApexCSG::BSPType::Empty_Set) // True if the hull lies entirely within the parent chunk + { + success = true; + InstanceInfo& info = instanceInfo.insert(); + info.meshIndex = (uint8_t)scatterMeshAssetIndex; + info.chunkIndex = (int32_t)triangleData->chunkIndex; + info.relativeTransform = tm; + } + } + } + } + } + } + + hullBSP->release(); + + // Now sort the instance info by chunk index + if (instanceInfo.size() > 1) + { + nvidia::sort<InstanceInfo, InstanceInfo::ChunkIndexLessThan>(instanceInfo.begin(), instanceInfo.size(), InstanceInfo::ChunkIndexLessThan()); + } + + // Write the info to the output arrays + for (uint32_t instanceNum = 0; instanceNum < instanceInfo.size() && instanceNum < scatterMeshInstancesBufferSize; ++instanceNum) // Second condition instanceNum < scatterMeshInstancesBufferSize should not be necessary + { + const InstanceInfo& info = instanceInfo[instanceNum]; + meshIndices[instanceNum] = info.meshIndex; + relativeTransforms[instanceNum] = info.relativeTransform; + } + + // Finally create an indexed lookup + if (instanceInfo.size() > 0) + { + physx::Array<uint32_t> lookup; + createIndexStartLookup(lookup, 0, hMesh.chunkCount(), &instanceInfo[0].chunkIndex, instanceInfo.size(), sizeof(InstanceInfo)); + + // .. and copy it into the output lookup table + for (uint32_t chunkLookup = 0; chunkLookup <= hMesh.chunkCount(); ++chunkLookup) // <= is intentional + { + chunkMeshStarts[chunkLookup] = lookup[chunkLookup]; + } + } + + return instanceInfo.size(); +} + +PX_INLINE bool intersectPlanes(physx::PxVec3& pos, physx::PxVec3& dir, const physx::PxPlane& plane0, const physx::PxPlane& plane1) +{ + dir = plane0.n.cross(plane1.n); + + if(dir.normalize() < PX_EPS_F32) + { + return false; + } + + pos = physx::PxVec3(0.0f); + + for (int iter = 3; iter--;) + { + // Project onto plane0: + pos = plane0.project(pos); + + // Raycast to plane1: + const physx::PxVec3 b = dir.cross(plane0.n); + pos -= (plane1.distance(pos)/(b.dot(plane1.n)))*b; + } + + return true; +} + +PX_INLINE void renderConvex(nvidia::RenderDebugInterface& debugRender, const physx::PxPlane* planes, uint32_t planeCount, uint32_t color, float tolerance) +{ + RENDER_DEBUG_IFACE(&debugRender)->setCurrentColor(color); + + physx::Array<physx::PxVec3> endpoints; + + const float tol2 = tolerance*tolerance; + + for (uint32_t i = 0; i < planeCount; ++i) + { + // We'll be drawing polygons in this plane + const physx::PxPlane& plane_i = planes[i]; + endpoints.resize(0); + for (uint32_t j = 0; j < planeCount; ++j) + { + if (j == i) + { + continue; + } + const physx::PxPlane& plane_j = planes[j]; + // Find potential edge from intersection if plane_i and plane_j + physx::PxVec3 orig; + physx::PxVec3 edgeDir; + if (!intersectPlanes(orig, edgeDir, plane_i, plane_j)) + { + continue; + } + float minS = -PX_MAX_F32; + float maxS = PX_MAX_F32; + bool intersectionFound = true; + // Clip to planes + for (uint32_t k = 0; k < planeCount; ++k) + { + if (k == i || i == j) + { + continue; + } + const physx::PxPlane& plane_k = planes[k]; + const float num = -plane_k.distance(orig); + const float den = edgeDir.dot(plane_k.n); + if (physx::PxAbs(den) > 10*PX_EPS_F32) + { + const float s = num/den; + if (den > 0.0f) + { + maxS = PxMin(maxS, s); + } + else + { + minS = PxMax(minS, s); + } + if (maxS <= minS) + { + intersectionFound = false; + break; + } + } + else + if (num < -tolerance) + { + intersectionFound = false; + break; + } + } + if (intersectionFound) + { + endpoints.pushBack(orig + minS * edgeDir); + endpoints.pushBack(orig + maxS * edgeDir); + } + } + if (endpoints.size() > 2) + { + physx::Array<physx::PxVec3> verts; + verts.pushBack(endpoints[endpoints.size()-2]); + verts.pushBack(endpoints[endpoints.size()-1]); + endpoints.popBack(); + endpoints.popBack(); + while (endpoints.size()) + { + uint32_t closestN = 0; + float closestDist2 = PX_MAX_F32; + for (uint32_t n = 0; n < endpoints.size(); ++n) + { + const float dist2 = (endpoints[n] - verts[verts.size()-1]).magnitudeSquared(); + if (dist2 < closestDist2) + { + closestDist2 = dist2; + closestN = n; + } + } + if ((endpoints[closestN^1] - verts[0]).magnitudeSquared() < tol2) + { + break; + } + verts.pushBack(endpoints[closestN^1]); + endpoints.replaceWithLast(closestN^1); + endpoints.replaceWithLast(closestN); + } + if (verts.size() > 2) + { + if (((verts[1]-verts[0]).cross(verts[2]-verts[0])).dot(plane_i.n) < 0.0f) + { + for (uint32_t n = verts.size()/2; n--;) + { + nvidia::swap(verts[n], verts[verts.size()-1-n]); + } + } + RENDER_DEBUG_IFACE(&debugRender)->debugPolygon(verts.size(), &verts[0]); + } + } + } +} + +void visualizeVoronoiCells +( + nvidia::RenderDebugInterface& debugRender, + const physx::PxVec3* sites, + uint32_t siteCount, + const uint32_t* cellColors, + uint32_t cellColorCount, + const physx::PxBounds3& bounds, + uint32_t cellIndex /* = 0xFFFFFFFF */ +) +{ + // Rendering tolerance + const float tolerance = 1.0e-5f*bounds.getDimensions().magnitude(); + + // Whether or not to use cellColors + const bool useCellColors = cellColors != NULL && cellColorCount > 0; + + // Whether to draw a single cell or all cells + const bool drawSingleCell = cellIndex < siteCount; + + // Create bound planes + physx::Array<physx::PxPlane> boundPlanes; + boundPlanes.reserve(6); + boundPlanes.pushBack(physx::PxPlane(-1.0f, 0.0f, 0.0f, bounds.minimum.x)); + boundPlanes.pushBack(physx::PxPlane(1.0f, 0.0f, 0.0f, -bounds.maximum.x)); + boundPlanes.pushBack(physx::PxPlane(0.0f, -1.0f, 0.0f, bounds.minimum.y)); + boundPlanes.pushBack(physx::PxPlane(0.0f, 1.0f, 0.0f, -bounds.maximum.y)); + boundPlanes.pushBack(physx::PxPlane(0.0f, 0.0f, -1.0f, bounds.minimum.z)); + boundPlanes.pushBack(physx::PxPlane(0.0f, 0.0f, 1.0f, -bounds.maximum.z)); + + // Iterate over cells + for (VoronoiCellPlaneIterator i(sites, siteCount, boundPlanes.begin(), boundPlanes.size(), drawSingleCell ? cellIndex : 0); i.valid(); i.inc()) + { + const uint32_t cellColor = useCellColors ? cellColors[i.cellIndex()%cellColorCount] : 0xFFFFFFFF; + renderConvex(debugRender, i.cellPlanes(), i.cellPlaneCount(), cellColor, tolerance); + if (drawSingleCell) + { + break; + } + } +} + +bool buildSliceMesh +( + nvidia::IntersectMesh& intersectMesh, + ExplicitHierarchicalMesh& referenceMesh, + const physx::PxPlane& slicePlane, + const FractureTools::NoiseParameters& noiseParameters, + uint32_t randomSeed +) +{ + if( referenceMesh.chunkCount() == 0 ) + { + return false; + } + + ExplicitHierarchicalMeshImpl& hMesh = *(ExplicitHierarchicalMeshImpl*)&referenceMesh; + + GridParameters gridParameters; + gridParameters.interiorSubmeshIndex = 0; + gridParameters.noise = noiseParameters; + const uint32_t partIndex = (uint32_t)hMesh.mChunks[0]->mPartIndex; + gridParameters.level0Mesh = &hMesh.mParts[partIndex]->mMesh; + physx::PxVec3 extents = hMesh.mParts[partIndex]->mBounds.getExtents(); + gridParameters.sizeScale = physx::PxAbs(extents.x*slicePlane.n.x) + physx::PxAbs(extents.y*slicePlane.n.y) + physx::PxAbs(extents.z*slicePlane.n.z); + gridParameters.materialFrameIndex = hMesh.addMaterialFrame(); + nvidia::MaterialFrame materialFrame = hMesh.getMaterialFrame(gridParameters.materialFrameIndex ); + nvidia::FractureMaterialDesc materialDesc; + materialFrame.buildCoordinateSystemFromMaterialDesc(materialDesc, slicePlane); + materialFrame.mFractureMethod = nvidia::FractureMethod::Unknown; // This is only a slice preview + hMesh.setMaterialFrame(gridParameters.materialFrameIndex, materialFrame); + gridParameters.triangleFrame.setFlat(materialFrame.mCoordinateSystem, physx::PxVec2(1.0f), physx::PxVec2(0.0f)); + gridParameters.forceGrid = true; + userRnd.m_rnd.setSeed(randomSeed); + buildIntersectMesh(intersectMesh, slicePlane, materialFrame, 0, &gridParameters); + + return true; +} + +} // namespace FractureTools + +namespace nvidia +{ +namespace apex +{ + +void buildCollisionGeometry(physx::Array<PartConvexHullProxy*>& volumes, const CollisionVolumeDesc& desc, + const physx::PxVec3* vertices, uint32_t vertexCount, uint32_t vertexByteStride, + const uint32_t* indices, uint32_t indexCount) +{ + ConvexHullMethod::Enum hullMethod = desc.mHullMethod; + + do + { + if (hullMethod == nvidia::ConvexHullMethod::CONVEX_DECOMPOSITION) + { + resizeCollision(volumes, 0); + + CONVEX_DECOMPOSITION::ConvexDecomposition* decomposer = CONVEX_DECOMPOSITION::createConvexDecomposition(); + if (decomposer != NULL) + { + CONVEX_DECOMPOSITION::DecompDesc decompDesc; + decompDesc.mCpercent = desc.mConcavityPercent; + //TODO:JWR decompDesc.mPpercent = desc.mMergeThreshold; + decompDesc.mDepth = desc.mRecursionDepth; + + decompDesc.mVcount = vertexCount; + decompDesc.mVertices = (float*)vertices; + decompDesc.mTcount = indexCount / 3; + decompDesc.mIndices = indices; + + uint32_t hullCount = decomposer->performConvexDecomposition(decompDesc); + resizeCollision(volumes, hullCount); + for (uint32_t hullIndex = 0; hullIndex < hullCount; ++hullIndex) + { + CONVEX_DECOMPOSITION::ConvexResult* result = decomposer->getConvexResult(hullIndex,false); + volumes[hullIndex]->buildFromPoints(result->mHullVertices, result->mHullVcount, 3 * sizeof(float)); + if (volumes[hullIndex]->impl.isEmpty()) + { + // fallback + physx::Array<physx::PxVec3> directions; + ConvexHullImpl::createKDOPDirections(directions, nvidia::ConvexHullMethod::USE_26_DOP); + volumes[hullIndex]->impl.buildKDOP(result->mHullVertices, result->mHullVcount, 3 * sizeof(float), directions.begin(), directions.size()); + } + } + decomposer->release(); + } + + if(volumes.size() > 0) + { + break; + } + + // fallback + hullMethod = nvidia::ConvexHullMethod::WRAP_GRAPHICS_MESH; + } + + resizeCollision(volumes, 1); + + if (hullMethod == nvidia::ConvexHullMethod::WRAP_GRAPHICS_MESH) + { + volumes[0]->buildFromPoints(vertices, vertexCount, vertexByteStride); + if (!volumes[0]->impl.isEmpty()) + { + break; + } + + // fallback + hullMethod = nvidia::ConvexHullMethod::USE_26_DOP; + } + + physx::Array<physx::PxVec3> directions; + ConvexHullImpl::createKDOPDirections(directions, hullMethod); + volumes[0]->impl.buildKDOP(vertices, vertexCount, vertexByteStride, directions.begin(), directions.size()); + } while(0); + + // Reduce hulls + for (uint32_t hullIndex = 0; hullIndex < volumes.size(); ++hullIndex) + { + // First try uninflated, then try with inflation. This may find a better reduction + volumes[hullIndex]->reduceHull(desc.mMaxVertexCount, desc.mMaxEdgeCount, desc.mMaxFaceCount, false); + volumes[hullIndex]->reduceHull(desc.mMaxVertexCount, desc.mMaxEdgeCount, desc.mMaxFaceCount, true); + } +} + + +// Serialization of ExplicitSubmeshData + + +void serialize(physx::PxFileBuf& stream, const ExplicitSubmeshData& d) +{ + ApexSimpleString materialName(d.mMaterialName); + apex::serialize(stream, materialName); + stream << d.mVertexFormat.mWinding; + stream << d.mVertexFormat.mHasStaticPositions; + stream << d.mVertexFormat.mHasStaticNormals; + stream << d.mVertexFormat.mHasStaticTangents; + stream << d.mVertexFormat.mHasStaticBinormals; + stream << d.mVertexFormat.mHasStaticColors; + stream << d.mVertexFormat.mHasStaticSeparateBoneBuffer; + stream << d.mVertexFormat.mHasStaticDisplacements; + stream << d.mVertexFormat.mHasDynamicPositions; + stream << d.mVertexFormat.mHasDynamicNormals; + stream << d.mVertexFormat.mHasDynamicTangents; + stream << d.mVertexFormat.mHasDynamicBinormals; + stream << d.mVertexFormat.mHasDynamicColors; + stream << d.mVertexFormat.mHasDynamicSeparateBoneBuffer; + stream << d.mVertexFormat.mHasDynamicDisplacements; + stream << d.mVertexFormat.mUVCount; + stream << d.mVertexFormat.mBonesPerVertex; +} + +void deserialize(physx::PxFileBuf& stream, uint32_t apexVersion, uint32_t meshVersion, ExplicitSubmeshData& d) +{ + ApexSimpleString materialName; + apex::deserialize(stream, apexVersion, materialName); + nvidia::strlcpy(d.mMaterialName, ExplicitSubmeshData::MaterialNameBufferSize, materialName.c_str()); + + if (apexVersion >= ApexStreamVersion::CleanupOfApexRenderMesh) + { + stream >> d.mVertexFormat.mWinding; + stream >> d.mVertexFormat.mHasStaticPositions; + stream >> d.mVertexFormat.mHasStaticNormals; + stream >> d.mVertexFormat.mHasStaticTangents; + stream >> d.mVertexFormat.mHasStaticBinormals; + stream >> d.mVertexFormat.mHasStaticColors; + stream >> d.mVertexFormat.mHasStaticSeparateBoneBuffer; + if (meshVersion >= ExplicitHierarchicalMeshImpl::DisplacementData) + stream >> d.mVertexFormat.mHasStaticDisplacements; + stream >> d.mVertexFormat.mHasDynamicPositions; + stream >> d.mVertexFormat.mHasDynamicNormals; + stream >> d.mVertexFormat.mHasDynamicTangents; + stream >> d.mVertexFormat.mHasDynamicBinormals; + stream >> d.mVertexFormat.mHasDynamicColors; + stream >> d.mVertexFormat.mHasDynamicSeparateBoneBuffer; + if (meshVersion >= ExplicitHierarchicalMeshImpl::DisplacementData) + stream >> d.mVertexFormat.mHasDynamicDisplacements; + stream >> d.mVertexFormat.mUVCount; + if (apexVersion < ApexStreamVersion::RemovedTextureTypeInformationFromVertexFormat) + { + // Dead data + uint32_t textureTypes[VertexFormat::MAX_UV_COUNT]; + for (uint32_t i = 0; i < VertexFormat::MAX_UV_COUNT; ++i) + { + stream >> textureTypes[i]; + } + } + stream >> d.mVertexFormat.mBonesPerVertex; + } + else + { +#if 0 // BRG - to do, implement conversion + bool hasPosition; + bool hasNormal; + bool hasTangent; + bool hasBinormal; + bool hasColor; + uint32_t numBonesPerVertex; + uint32_t uvCount; + RenderCullMode::Enum winding = RenderCullMode::CLOCKWISE; + + // PH: assuming position and normal as the default dynamic flags + uint32_t dynamicFlags = VertexFormatFlag::POSITION | VertexFormatFlag::NORMAL; + + if (version >= ApexStreamVersion::AddedRenderCullModeToRenderMeshAsset) + { + //stream.readBuffer( &winding, sizeof(winding) ); + stream >> winding; + } + if (version >= ApexStreamVersion::AddedDynamicVertexBufferField) + { + stream >> dynamicFlags; + } + if (version >= ApexStreamVersion::AddingTextureTypeInformationToVertexFormat) + { + stream >> hasPosition; + stream >> hasNormal; + stream >> hasTangent; + stream >> hasBinormal; + stream >> hasColor; + if (version >= ApexStreamVersion::RenderMeshAssetRedesign) + { + stream >> numBonesPerVertex; + } + else + { + bool hasBoneIndex; + stream >> hasBoneIndex; + numBonesPerVertex = hasBoneIndex ? 1 : 0; + } + stream >> uvCount; + if (version < ApexStreamVersion::RemovedTextureTypeInformationFromVertexFormat) + { + // Dead data + uint32_t textureTypes[VertexFormat::MAX_UV_COUNT]; + for (uint32_t i = 0; i < VertexFormat::MAX_UV_COUNT; ++i) + { + stream >> textureTypes[i]; + } + } + } + else + { + uint32_t data; + stream >> data; + hasPosition = (data & (1 << 8)) != 0; + hasNormal = (data & (1 << 9)) != 0; + hasTangent = (data & (1 << 10)) != 0; + hasBinormal = (data & (1 << 11)) != 0; + hasColor = (data & (1 << 12)) != 0; + numBonesPerVertex = (data & (1 << 13)) != 0 ? 1 : 0; + uvCount = data & 0xFF; + } + + d.mVertexFormat.mWinding = winding; + d.mVertexFormat.mHasStaticPositions = hasPosition; + d.mVertexFormat.mHasStaticNormals = hasNormal; + d.mVertexFormat.mHasStaticTangents = hasTangent; + d.mVertexFormat.mHasStaticBinormals = hasBinormal; + d.mVertexFormat.mHasStaticColors = hasColor; + d.mVertexFormat.mHasStaticSeparateBoneBuffer = false; + d.mVertexFormat.mHasDynamicPositions = (dynamicFlags & VertexFormatFlag::POSITION) != 0; + d.mVertexFormat.mHasDynamicNormals = (dynamicFlags & VertexFormatFlag::NORMAL) != 0; + d.mVertexFormat.mHasDynamicTangents = (dynamicFlags & VertexFormatFlag::TANGENT) != 0; + d.mVertexFormat.mHasDynamicBinormals = (dynamicFlags & VertexFormatFlag::BINORMAL) != 0; + d.mVertexFormat.mHasDynamicColors = (dynamicFlags & VertexFormatFlag::COLOR) != 0; + d.mVertexFormat.mHasDynamicSeparateBoneBuffer = (dynamicFlags & VertexFormatFlag::SEPARATE_BONE_BUFFER) != 0; + d.mVertexFormat.mUVCount = uvCount; + d.mVertexFormat.mBonesPerVertex = numBonesPerVertex; + + if (version >= ApexStreamVersion::RenderMeshAssetRedesign) + { + uint32_t customBufferCount; + stream >> customBufferCount; + for (uint32_t i = 0; i < customBufferCount; i++) + { + uint32_t stringLength; + stream >> stringLength; + PX_ASSERT(stringLength < 254); + char buf[256]; + stream.read(buf, stringLength); + buf[stringLength] = 0; + uint32_t format; + stream >> format; + } + } +#endif + } +} + + +// Serialization of nvidia::MaterialFrame + + +void serialize(physx::PxFileBuf& stream, const nvidia::MaterialFrame& f) +{ + // f.mCoordinateSystem + const PxMat44& m44 = f.mCoordinateSystem; + stream << m44(0, 0) << m44(0, 1) << m44(0, 2) + << m44(1, 0) << m44(1, 1) << m44(1, 2) + << m44(2, 0) << m44(2, 1) << m44(2, 2) << m44.getPosition(); + + // Other fields of f + stream << f.mUVPlane << f.mUVScale << f.mUVOffset << f.mFractureMethod << f.mFractureIndex << f.mSliceDepth; +} + +void deserialize(physx::PxFileBuf& stream, uint32_t apexVersion, uint32_t meshVersion, nvidia::MaterialFrame& f) +{ + PX_UNUSED(apexVersion); + + f.mSliceDepth = 0; + + if (meshVersion >= ExplicitHierarchicalMeshImpl::ChangedMaterialFrameToIncludeFracturingMethodContext) // First version in which this struct exists + { + // f.mCoordinateSystem + PxMat44 &m44 = f.mCoordinateSystem; + stream >> m44(0, 0) >> m44(0, 1) >> m44(0, 2) + >> m44(1, 0) >> m44(1, 1) >> m44(1, 2) + >> m44(2, 0) >> m44(2, 1) >> m44(2, 2) >> *reinterpret_cast<PxVec3*>(&m44.column3); + + // Other fields of f + stream >> f.mUVPlane >> f.mUVScale >> f.mUVOffset >> f.mFractureMethod >> f.mFractureIndex; + + if (meshVersion >= ExplicitHierarchicalMeshImpl::AddedSliceDepthToMaterialFrame) + { + stream >> f.mSliceDepth; + } + } +} + + +// ExplicitHierarchicalMeshImpl + +ExplicitHierarchicalMeshImpl::ExplicitHierarchicalMeshImpl() +{ + mBSPMemCache = ApexCSG::createBSPMemCache(); + mRootSubmeshCount = 0; +} + +ExplicitHierarchicalMeshImpl::~ExplicitHierarchicalMeshImpl() +{ + clear(); + mBSPMemCache->release(); +} + +uint32_t ExplicitHierarchicalMeshImpl::addPart() +{ + const uint32_t index = mParts.size(); + mParts.insert(); + Part* part = PX_NEW(Part); + part->mMeshBSP = createBSP(mBSPMemCache); + mParts.back() = part; + return index; +} + +bool ExplicitHierarchicalMeshImpl::removePart(uint32_t index) +{ + if (index >= partCount()) + { + return false; + } + + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + if (mChunks[i]->mPartIndex == (int32_t)index) + { + mChunks[i]->mPartIndex = -1; + } + else if (mChunks[i]->mPartIndex > (int32_t)index) + { + --mChunks[i]->mPartIndex; + } + } + + delete mParts[index]; + mParts.remove(index); + + return true; +} + +uint32_t ExplicitHierarchicalMeshImpl::addChunk() +{ + const uint32_t index = mChunks.size(); + mChunks.insert(); + mChunks.back() = PX_NEW(Chunk); + return index; +} + +bool ExplicitHierarchicalMeshImpl::removeChunk(uint32_t index) +{ + if (index >= chunkCount()) + { + return false; + } + + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + if (mChunks[i]->mParentIndex == (int32_t)index) + { + mChunks[i]->mParentIndex = -1; + } + else if (mChunks[i]->mParentIndex > (int32_t)index) + { + --mChunks[i]->mParentIndex; + } + } + + delete mChunks[index]; + mChunks.remove(index); + + return true; +} + +void ExplicitHierarchicalMeshImpl::serialize(physx::PxFileBuf& stream, Embedding& embedding) const +{ + stream << (uint32_t)ExplicitHierarchicalMeshImpl::Current; + stream << (uint32_t)ApexStreamVersion::Current; + stream << mParts.size(); + for (uint32_t i = 0; i < mParts.size(); ++i) + { + stream << mParts[i]->mBounds; + apex::serialize(stream, mParts[i]->mMesh); + stream.storeDword(mParts[i]->mCollision.size()); + for (uint32_t j = 0; j < mParts[i]->mCollision.size(); ++j) + { + apex::serialize(stream, mParts[i]->mCollision[j]->impl); + } + if (mParts[i]->mMeshBSP != NULL) + { + stream << (uint32_t)1; + mParts[i]->mMeshBSP->serialize(stream); + } + else + { + stream << (uint32_t)0; + } + stream << mParts[i]->mFlags; + } + stream << mChunks.size(); + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + stream << mChunks[i]->mParentIndex; + stream << mChunks[i]->mFlags; + stream << mChunks[i]->mPartIndex; + stream << mChunks[i]->mInstancedPositionOffset; + stream << mChunks[i]->mInstancedUVOffset; + stream << mChunks[i]->mPrivateFlags; + } + apex::serialize(stream, mSubmeshData); + apex::serialize(stream, mMaterialFrames); + embedding.serialize(stream, Embedding::MaterialLibrary); + stream << mRootSubmeshCount; +} + +void ExplicitHierarchicalMeshImpl::deserialize(physx::PxFileBuf& stream, Embedding& embedding) +{ + clear(); + + uint32_t meshStreamVersion; + stream >> meshStreamVersion; + uint32_t apexStreamVersion; + stream >> apexStreamVersion; + + if (meshStreamVersion < ExplicitHierarchicalMeshImpl::RemovedExplicitHMesh_mMaxDepth) + { + int32_t maxDepth; + stream >> maxDepth; + } + + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::InstancingData) + { + uint32_t partCount; + stream >> partCount; + mParts.resize(partCount); + for (uint32_t i = 0; i < partCount; ++i) + { + mParts[i] = PX_NEW(Part); + stream >> mParts[i]->mBounds; + apex::deserialize(stream, apexStreamVersion, mParts[i]->mMesh); + resizeCollision(mParts[i]->mCollision, stream.readDword()); + for (uint32_t hullNum = 0; hullNum < mParts[i]->mCollision.size(); ++hullNum) + { + apex::deserialize(stream, apexStreamVersion, mParts[i]->mCollision[hullNum]->impl); + } + mParts[i]->mMeshBSP = createBSP(mBSPMemCache); + uint32_t createMeshBSP; + stream >> createMeshBSP; + if (createMeshBSP) + { + mParts[i]->mMeshBSP->deserialize(stream); + } + else + { + ApexCSG::BSPBuildParameters bspBuildParameters = gDefaultBuildParameters; + bspBuildParameters.internalTransform = physx::PxMat44(physx::PxIdentity); + bspBuildParameters.rnd = &userRnd; + userRnd.m_rnd.setSeed(0); + mParts[i]->mMeshBSP->fromMesh(&mParts[i]->mMesh[0], mParts[i]->mMesh.size(), bspBuildParameters); + } + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::ReaddedFlagsToPart) + { + stream >> mParts[i]->mFlags; + } + } + + uint32_t chunkCount; + stream >> chunkCount; + mChunks.resize(chunkCount); + for (uint32_t i = 0; i < chunkCount; ++i) + { + mChunks[i] = PX_NEW(Chunk); + stream >> mChunks[i]->mParentIndex; + stream >> mChunks[i]->mFlags; + stream >> mChunks[i]->mPartIndex; + stream >> mChunks[i]->mInstancedPositionOffset; + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::UVInstancingData) + { + stream >> mChunks[i]->mInstancedUVOffset; + } + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::IntroducingChunkPrivateFlags) + { + stream >> mChunks[i]->mPrivateFlags; + } + } + } + else + { + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::UsingExplicitPartContainers) + { + uint32_t partCount; + stream >> partCount; + mParts.resize(partCount); + mChunks.resize(partCount); + for (uint32_t i = 0; i < partCount; ++i) + { + mParts[i] = PX_NEW(Part); + mChunks[i] = PX_NEW(Chunk); + stream >> mChunks[i]->mParentIndex; + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::SerializingMeshBounds) + { + stream >> mParts[i]->mBounds; + } + apex::deserialize(stream, apexStreamVersion, mParts[i]->mMesh); + if (meshStreamVersion < ExplicitHierarchicalMeshImpl::SerializingMeshBounds) + { + buildMeshBounds(i); + } + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::MultipleConvexHullsPerChunk) + { + resizeCollision(mParts[i]->mCollision, stream.readDword()); + } + else + { + resizeCollision(mParts[i]->mCollision, 1); + } + for (uint32_t hullNum = 0; hullNum < mParts[i]->mCollision.size(); ++hullNum) + { + apex::deserialize(stream, apexStreamVersion, mParts[i]->mCollision[hullNum]->impl); + } + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::PerPartMeshBSPs) + { + mParts[i]->mMeshBSP = createBSP(mBSPMemCache); + uint32_t createMeshBSP; + stream >> createMeshBSP; + if (createMeshBSP) + { + mParts[i]->mMeshBSP->deserialize(stream); + } + else + { + ApexCSG::BSPBuildParameters bspBuildParameters = gDefaultBuildParameters; + bspBuildParameters.internalTransform = physx::PxMat44(physx::PxIdentity); + mParts[i]->mMeshBSP->fromMesh(&mParts[i]->mMesh[0], mParts[i]->mMesh.size(), bspBuildParameters); + } + } + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::AddedFlagsFieldToPart) + { + stream >> mChunks[i]->mFlags; + } + } + } + else + { + physx::Array<int32_t> parentIndices; + physx::Array< physx::Array< ExplicitRenderTriangle > > meshes; + physx::Array< ConvexHullImpl > meshHulls; + apex::deserialize(stream, apexStreamVersion, parentIndices); + apex::deserialize(stream, apexStreamVersion, meshes); + apex::deserialize(stream, apexStreamVersion, meshHulls); + PX_ASSERT(parentIndices.size() == meshes.size() && meshes.size() == meshHulls.size()); + uint32_t partCount = PxMin(parentIndices.size(), PxMin(meshes.size(), meshHulls.size())); + mParts.resize(partCount); + mChunks.resize(partCount); + for (uint32_t i = 0; i < partCount; ++i) + { + mParts[i] = PX_NEW(Part); + mChunks[i] = PX_NEW(Chunk); + mChunks[i]->mParentIndex = parentIndices[i]; + mParts[i]->mMesh = meshes[i]; + resizeCollision(mParts[i]->mCollision, 1); + mParts[i]->mCollision[0]->impl = meshHulls[i]; + buildMeshBounds(i); + } + } + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + mChunks[i]->mPartIndex = (int32_t)i; + } + } + + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::SerializingMeshBSP && meshStreamVersion < ExplicitHierarchicalMeshImpl::PerPartMeshBSPs) + { + mParts[0]->mMeshBSP = createBSP(mBSPMemCache); + uint32_t createMeshBSP; + stream >> createMeshBSP; + if (createMeshBSP) + { + mParts[0]->mMeshBSP->deserialize(stream); + } + else + { + ApexCSG::BSPBuildParameters bspBuildParameters = gDefaultBuildParameters; + bspBuildParameters.internalTransform = physx::PxMat44(physx::PxIdentity); + mParts[0]->mMeshBSP->fromMesh(&mParts[0]->mMesh[0], mParts[0]->mMesh.size(), bspBuildParameters); + } + } + + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::IncludingVertexFormatInSubmeshData) + { + apex::deserialize(stream, apexStreamVersion, meshStreamVersion, mSubmeshData); + } + else + { + physx::Array<ApexSimpleString> materialNames; + apex::deserialize(stream, apexStreamVersion, materialNames); + mSubmeshData.resize(0); // Make sure the next resize calls constructors + mSubmeshData.resize(materialNames.size()); + for (uint32_t i = 0; i < materialNames.size(); ++i) + { + nvidia::strlcpy(mSubmeshData[i].mMaterialName, ExplicitSubmeshData::MaterialNameBufferSize, materialNames[i].c_str()); + mSubmeshData[i].mVertexFormat.mHasStaticPositions = true; + mSubmeshData[i].mVertexFormat.mHasStaticNormals = true; + mSubmeshData[i].mVertexFormat.mHasStaticTangents = true; + mSubmeshData[i].mVertexFormat.mHasStaticBinormals = true; + mSubmeshData[i].mVertexFormat.mHasStaticColors = true; + mSubmeshData[i].mVertexFormat.mHasStaticDisplacements = false; + mSubmeshData[i].mVertexFormat.mUVCount = 1; + mSubmeshData[i].mVertexFormat.mBonesPerVertex = 1; + } + } + + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::AddedMaterialFramesToHMesh_and_NoiseType_and_GridSize_to_Cleavage) + { + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::ChangedMaterialFrameToIncludeFracturingMethodContext) + { + apex::deserialize(stream, apexStreamVersion, meshStreamVersion, mMaterialFrames); + } + else + { + const uint32_t size = stream.readDword(); + mMaterialFrames.resize(size); + for (uint32_t i = 0; i < size; ++i) + { + PxMat44 &m44 = mMaterialFrames[i].mCoordinateSystem; + stream >> m44(0, 0) >> m44(0, 1) >> m44(0, 2) + >> m44(1, 0) >> m44(1, 1) >> m44(1, 2) + >> m44(2, 0) >> m44(2, 1) >> m44(2, 2) >> *reinterpret_cast<PxVec3*>(&m44.column3); + mMaterialFrames[i].mUVPlane = physx::PxPlane(m44.getPosition(), m44.column2.getXYZ()); + mMaterialFrames[i].mUVScale = physx::PxVec2(1.0f); + mMaterialFrames[i].mUVOffset = physx::PxVec2(0.0f); + mMaterialFrames[i].mFractureMethod = nvidia::FractureMethod::Unknown; + mMaterialFrames[i].mFractureIndex = -1; + } + } + } + else + { + mMaterialFrames.resize(0); + } + + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::AddedMaterialLibraryToMesh) + { + embedding.deserialize(stream, Embedding::MaterialLibrary, meshStreamVersion); + } + + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::AddedCacheChunkSurfaceTracesAndInteriorSubmeshIndex && meshStreamVersion < ExplicitHierarchicalMeshImpl::RemovedInteriorSubmeshIndex) + { + int32_t interiorSubmeshIndex; + stream >> interiorSubmeshIndex; + } + + + if (meshStreamVersion < ExplicitHierarchicalMeshImpl::IntroducingChunkPrivateFlags) + { + uint32_t rootDepth = 0; + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::PerPartMeshBSPs) + { + stream >> rootDepth; + } + + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + mChunks[i]->mPrivateFlags = 0; + const uint32_t chunkDepth = depth(i); + if (chunkDepth <= rootDepth) + { + mChunks[i]->mPrivateFlags |= Chunk::Root; + if (chunkDepth == rootDepth) + { + mChunks[i]->mPrivateFlags |= Chunk::RootLeaf; + } + } + } + } + + if (meshStreamVersion >= ExplicitHierarchicalMeshImpl::StoringRootSubmeshCount) + { + stream >> mRootSubmeshCount; + } + else + { + mRootSubmeshCount = mSubmeshData.size(); + } + + if (meshStreamVersion < ExplicitHierarchicalMeshImpl::RemovedNxChunkAuthoringFlag) + { + /* Need to translate flags: + IsCutoutFaceSplit = (1U << 0), + IsCutoutLeftover = (1U << 1), + Instance = (1U << 31) + */ + for (uint32_t chunkIndex = 0; chunkIndex < mChunks.size(); ++chunkIndex) + { + // IsCutoutFaceSplit and IsCutoutLeftover are no longer used. + // Translate Instance flag: + if (mChunks[chunkIndex]->mFlags & (1U << 31)) + { + mChunks[chunkIndex]->mFlags = nvidia::apex::DestructibleAsset::ChunkIsInstanced; + } + } + } +} + +int32_t ExplicitHierarchicalMeshImpl::maxDepth() const +{ + int32_t max = -1; + int32_t index = (int32_t)chunkCount()-1; + while (index >= 0) + { + index = mChunks[(uint32_t)index]->mParentIndex; + ++max; + } + return max; +} + +uint32_t ExplicitHierarchicalMeshImpl::partCount() const +{ + return mParts.size(); +} + +uint32_t ExplicitHierarchicalMeshImpl::chunkCount() const +{ + return mChunks.size(); +} + +int32_t* ExplicitHierarchicalMeshImpl::parentIndex(uint32_t chunkIndex) +{ + return chunkIndex < chunkCount() ? &mChunks[chunkIndex]->mParentIndex : NULL; +} + +uint64_t ExplicitHierarchicalMeshImpl::chunkUniqueID(uint32_t chunkIndex) +{ + return chunkIndex < chunkCount() ? mChunks[chunkIndex]->getEUID() : (uint64_t)0; +} + +int32_t* ExplicitHierarchicalMeshImpl::partIndex(uint32_t chunkIndex) +{ + return chunkIndex < chunkCount() ? &mChunks[chunkIndex]->mPartIndex : NULL; +} + +physx::PxVec3* ExplicitHierarchicalMeshImpl::instancedPositionOffset(uint32_t chunkIndex) +{ + return chunkIndex < chunkCount() ? &mChunks[chunkIndex]->mInstancedPositionOffset : NULL; +} + +physx::PxVec2* ExplicitHierarchicalMeshImpl::instancedUVOffset(uint32_t chunkIndex) +{ + return chunkIndex < chunkCount() ? &mChunks[chunkIndex]->mInstancedUVOffset : NULL; +} + +uint32_t ExplicitHierarchicalMeshImpl::depth(uint32_t chunkIndex) const +{ + if (chunkIndex >= mChunks.size()) + { + return 0; + } + + uint32_t depth = 0; + int32_t index = (int32_t)chunkIndex; + while ((index = mChunks[(uint32_t)index]->mParentIndex) >= 0) + { + ++depth; + } + + return depth; +} + +uint32_t ExplicitHierarchicalMeshImpl::meshTriangleCount(uint32_t partIndex) const +{ + return partIndex < partCount() ? mParts[partIndex]->mMesh.size() : 0; +} + +ExplicitRenderTriangle* ExplicitHierarchicalMeshImpl::meshTriangles(uint32_t partIndex) +{ + return partIndex < partCount() ? mParts[partIndex]->mMesh.begin() : NULL; +} + +physx::PxBounds3 ExplicitHierarchicalMeshImpl::meshBounds(uint32_t partIndex) const +{ + physx::PxBounds3 bounds; + bounds.setEmpty(); + if (partIndex < partCount()) + { + bounds = mParts[partIndex]->mBounds; + } + return bounds; +} + +physx::PxBounds3 ExplicitHierarchicalMeshImpl::chunkBounds(uint32_t chunkIndex) const +{ + physx::PxBounds3 bounds; + bounds.setEmpty(); + + if (chunkIndex < chunkCount()) + { + bounds = mParts[(uint32_t)mChunks[chunkIndex]->mPartIndex]->mBounds; + bounds.minimum += mChunks[chunkIndex]->mInstancedPositionOffset; + bounds.maximum += mChunks[chunkIndex]->mInstancedPositionOffset; + } + return bounds; +} + +uint32_t* ExplicitHierarchicalMeshImpl::chunkFlags(uint32_t chunkIndex) const +{ + if (chunkIndex < chunkCount()) + { + return &mChunks[chunkIndex]->mFlags; + } + return NULL; +} + +uint32_t ExplicitHierarchicalMeshImpl::convexHullCount(uint32_t partIndex) const +{ + if (partIndex < partCount()) + { + return mParts[partIndex]->mCollision.size(); + } + return 0; +} + +const ExplicitHierarchicalMeshImpl::ConvexHull** ExplicitHierarchicalMeshImpl::convexHulls(uint32_t partIndex) const +{ + if (partIndex < partCount()) + { + Part* part = mParts[partIndex]; + return part->mCollision.size() > 0 ? (const ExplicitHierarchicalMeshImpl::ConvexHull**)&part->mCollision[0] : NULL; + } + return NULL; +} + +physx::PxVec3* ExplicitHierarchicalMeshImpl::surfaceNormal(uint32_t partIndex) +{ + if (partIndex < partCount()) + { + Part* part = mParts[partIndex]; + return &part->mSurfaceNormal; + } + return NULL; +} + +const DisplacementMapVolume& ExplicitHierarchicalMeshImpl::displacementMapVolume() const +{ + return mDisplacementMapVolume; +} + +uint32_t ExplicitHierarchicalMeshImpl::submeshCount() const +{ + return mSubmeshData.size(); +} + +ExplicitSubmeshData* ExplicitHierarchicalMeshImpl::submeshData(uint32_t submeshIndex) +{ + return submeshIndex < mSubmeshData.size() ? mSubmeshData.begin() + submeshIndex : NULL; +} + +uint32_t ExplicitHierarchicalMeshImpl::addSubmesh(const ExplicitSubmeshData& submeshData) +{ + const uint32_t index = mSubmeshData.size(); + mSubmeshData.pushBack(submeshData); + return index; +} + +uint32_t ExplicitHierarchicalMeshImpl::getMaterialFrameCount() const +{ + return mMaterialFrames.size(); +} + +nvidia::MaterialFrame ExplicitHierarchicalMeshImpl::getMaterialFrame(uint32_t index) const +{ + return mMaterialFrames[index]; +} + +void ExplicitHierarchicalMeshImpl::setMaterialFrame(uint32_t index, const nvidia::MaterialFrame& materialFrame) +{ + mMaterialFrames[index] = materialFrame; +} + +uint32_t ExplicitHierarchicalMeshImpl::addMaterialFrame() +{ + mMaterialFrames.insert(); + return mMaterialFrames.size()-1; +} + +void ExplicitHierarchicalMeshImpl::clear(bool keepRoot) +{ + uint32_t newPartCount = 0; + uint32_t index = chunkCount(); + while (index-- > 0) + { + if (!keepRoot || !mChunks[index]->isRootChunk()) + { + removeChunk(index); + } + else + { + newPartCount = PxMax(newPartCount, (uint32_t)(mChunks[index]->mPartIndex+1)); + } + } + + while (newPartCount < partCount()) + { + removePart(partCount()-1); + } + + mMaterialFrames.resize(0); + + if (!keepRoot) + { + mSubmeshData.reset(); + mBSPMemCache->clearAll(); + mRootSubmeshCount = 0; + } +} + +void ExplicitHierarchicalMeshImpl::sortChunks(physx::Array<uint32_t>* indexRemap) +{ + if (mChunks.size() <= 1) + { + return; + } + + // Sort by original parent index + physx::Array<ChunkIndexer> chunkIndices(mChunks.size()); + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + chunkIndices[i].chunk = mChunks[i]; + chunkIndices[i].parentIndex = mChunks[i]->mParentIndex; + chunkIndices[i].index = (int32_t)i; + } + qsort(chunkIndices.begin(), chunkIndices.size(), sizeof(ChunkIndexer), ChunkIndexer::compareParentIndices); + + // Now arrange in depth order + physx::Array<uint32_t> parentStarts; + createIndexStartLookup(parentStarts, -1, chunkIndices.size() + 1, &chunkIndices[0].parentIndex, chunkIndices.size(), sizeof(ChunkIndexer)); + + physx::Array<ChunkIndexer> newChunkIndices; + newChunkIndices.reserve(mChunks.size()); + int32_t parentIndex = -1; + uint32_t nextPart = 0; + while (newChunkIndices.size() < mChunks.size()) + { + const uint32_t start = parentStarts[(uint32_t)parentIndex + 1]; + const uint32_t stop = parentStarts[(uint32_t)parentIndex + 2]; + for (uint32_t index = start; index < stop; ++index) + { + newChunkIndices.pushBack(chunkIndices[index]); + } + parentIndex = newChunkIndices[nextPart++].index; + } + + // Remap the parts and parent indices + physx::Array<uint32_t> internalRemap; + physx::Array<uint32_t>& remap = indexRemap != NULL ? *indexRemap : internalRemap; + remap.resize(newChunkIndices.size()); + for (uint32_t i = 0; i < newChunkIndices.size(); ++i) + { + mChunks[i] = newChunkIndices[i].chunk; + remap[(uint32_t)newChunkIndices[i].index] = i; + } + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + if (mChunks[i]->mParentIndex >= 0) + { + mChunks[i]->mParentIndex = (int32_t)remap[(uint32_t)mChunks[i]->mParentIndex]; + } + } +} + +void ExplicitHierarchicalMeshImpl::createPartSurfaceNormals() +{ + for (uint32_t partIndex = 0; partIndex < mParts.size(); ++partIndex) + { + Part* part = mParts[partIndex]; + physx::Array<ExplicitRenderTriangle>& mesh = part->mMesh; + physx::PxVec3 normal(0.0f); + for (uint32_t triangleIndex = 0; triangleIndex < mesh.size(); ++triangleIndex) + { + ExplicitRenderTriangle& triangle = mesh[triangleIndex]; + if (triangle.extraDataIndex == 0xFFFFFFFF) + { + normal += (triangle.vertices[1].position - triangle.vertices[0].position).cross(triangle.vertices[2].position - triangle.vertices[0].position); + } + } + part->mSurfaceNormal = normal.getNormalized(); + } +} + +void ExplicitHierarchicalMeshImpl::set(const ExplicitHierarchicalMesh& mesh) +{ + const ExplicitHierarchicalMeshImpl& m = (const ExplicitHierarchicalMeshImpl&)mesh; + clear(); + mParts.resize(0); + mParts.reserve(m.mParts.size()); + for (uint32_t i = 0; i < m.mParts.size(); ++i) + { + const uint32_t newPartIndex = addPart(); + PX_ASSERT(newPartIndex == i); + mParts[newPartIndex]->mBounds = m.mParts[i]->mBounds; + mParts[newPartIndex]->mMesh = m.mParts[i]->mMesh; + PX_ASSERT(m.mParts[i]->mMeshBSP != NULL); + mParts[newPartIndex]->mMeshBSP->copy(*m.mParts[i]->mMeshBSP); + resizeCollision(mParts[newPartIndex]->mCollision, m.mParts[i]->mCollision.size()); + for (uint32_t j = 0; j < mParts[newPartIndex]->mCollision.size(); ++j) + { + mParts[newPartIndex]->mCollision[j]->impl = m.mParts[i]->mCollision[j]->impl; + } + mParts[newPartIndex]->mFlags = m.mParts[i]->mFlags; + } + mChunks.resize(m.mChunks.size()); + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + mChunks[i] = PX_NEW(Chunk); + mChunks[i]->mParentIndex = m.mChunks[i]->mParentIndex; + mChunks[i]->mFlags = m.mChunks[i]->mFlags; + mChunks[i]->mPartIndex = m.mChunks[i]->mPartIndex; + mChunks[i]->mInstancedPositionOffset = m.mChunks[i]->mInstancedPositionOffset; + mChunks[i]->mInstancedUVOffset = m.mChunks[i]->mInstancedUVOffset; + mChunks[i]->mPrivateFlags = m.mChunks[i]->mPrivateFlags; + } + mSubmeshData = m.mSubmeshData; + mMaterialFrames = m.mMaterialFrames; + mRootSubmeshCount = m.mRootSubmeshCount; +} + +static void buildCollisionGeometryForPartInternal(physx::Array<PartConvexHullProxy*>& volumes, ExplicitHierarchicalMeshImpl::Part* part, const CollisionVolumeDesc& desc, float inflation = 0.0f) +{ + uint32_t vertexCount = part->mMesh.size() * 3; + if (inflation > 0.0f) + { + vertexCount *= 7; // Will add vertices + } + physx::Array<physx::PxVec3> vertices(vertexCount); + uint32_t vertexN = 0; + for (uint32_t i = 0; i < part->mMesh.size(); ++i) + { + nvidia::ExplicitRenderTriangle& triangle = part->mMesh[i]; + for (uint32_t v = 0; v < 3; ++v) + { + const physx::PxVec3& position = triangle.vertices[v].position; + vertices[vertexN++] = position; + if (inflation > 0.0f) + { + for (uint32_t j = 0; j < 3; ++j) + { + physx::PxVec3 offset(0.0f); + offset[j] = inflation; + for (uint32_t k = 0; k < 2; ++k) + { + vertices[vertexN++] = position + offset; + offset[j] *= -1.0f; + } + } + } + } + } + + // Identity index buffer + PX_ALLOCA(indices, uint32_t, vertices.size()); + for (uint32_t i = 0; i < vertices.size(); ++i) + { + indices[i] = i; + } + + buildCollisionGeometry(volumes, desc, vertices.begin(), vertices.size(), sizeof(physx::PxVec3), indices, vertices.size()); +} + +bool ExplicitHierarchicalMeshImpl::calculatePartBSP(uint32_t partIndex, uint32_t randomSeed, uint32_t microgridSize, BSPOpenMode::Enum meshMode, IProgressListener* progressListener, volatile bool* cancel) +{ + if (partIndex >= mParts.size()) + { + return false; + } + + PX_ASSERT(mParts[partIndex]->mMeshBSP != NULL); + + ApexCSG::BSPBuildParameters bspBuildParameters = gDefaultBuildParameters; + bspBuildParameters.snapGridSize = microgridSize; + bspBuildParameters.internalTransform = physx::PxMat44(physx::PxZero); + bspBuildParameters.rnd = &userRnd; + userRnd.m_rnd.setSeed(randomSeed); + bool ok = mParts[partIndex]->mMeshBSP->fromMesh(&mParts[partIndex]->mMesh[0], mParts[partIndex]->mMesh.size(), bspBuildParameters, progressListener, cancel); + if (!ok) + { + return false; + } + + // Check for open mesh + if (meshMode == BSPOpenMode::Closed) + { + return true; + } + + for (uint32_t chunkIndex = 0; chunkIndex < chunkCount(); ++chunkIndex) + { + // Find a chunk which uses this part + if ((uint32_t)mChunks[chunkIndex]->mPartIndex == partIndex) + { + // If the chunk is a root chunk, test for openness + if (mChunks[chunkIndex]->isRootChunk()) + { + float area, volume; + if (meshMode == BSPOpenMode::Open || !mParts[partIndex]->mMeshBSP->getSurfaceAreaAndVolume(area, volume, true)) + { + // Mark the mesh as open + mParts[partIndex]->mFlags |= Part::MeshOpen; + // Instead of using this mesh's BSP, use the convex hull + physx::Array<PartConvexHullProxy*> volumes; + CollisionVolumeDesc collisionDesc; + collisionDesc.mHullMethod = nvidia::ConvexHullMethod::WRAP_GRAPHICS_MESH; + buildCollisionGeometryForPartInternal(volumes, mParts[partIndex], collisionDesc, mParts[partIndex]->mBounds.getExtents().magnitude()*0.01f); + PX_ASSERT(volumes.size() == 1); + if (volumes.size() > 0) + { + PartConvexHullProxy& hull = *volumes[0]; + physx::Array<physx::PxPlane> planes; + planes.resize(hull.impl.getPlaneCount()); + const physx::PxVec3 extents = hull.impl.getBounds().getExtents(); + const float padding = 0.001f*extents.magnitude(); + for (uint32_t planeIndex = 0; planeIndex < hull.impl.getPlaneCount(); ++planeIndex) + { + planes[planeIndex] = hull.impl.getPlane(planeIndex); + planes[planeIndex].d -= padding; + } + physx::PxMat44 internalTransform = physx::PxMat44(physx::PxIdentity); + const physx::PxVec3 scale(1.0f/extents[0], 1.0f/extents[1], 1.0f/extents[2]); + internalTransform.scale(physx::PxVec4(scale, 1.0f)); + internalTransform.setPosition(-scale.multiply(hull.impl.getBounds().getCenter())); + mParts[partIndex]->mMeshBSP->fromConvexPolyhedron(&planes[0], planes.size(), internalTransform, &mParts[partIndex]->mMesh[0], mParts[partIndex]->mMesh.size()); + } + } + } + break; + } + } + + return true; +} + +void ExplicitHierarchicalMeshImpl::replaceInteriorSubmeshes(uint32_t partIndex, uint32_t frameCount, uint32_t* frameIndices, uint32_t submeshIndex) +{ + if (partIndex >= mParts.size()) + { + return; + } + + Part* part = mParts[partIndex]; + + // Replace render mesh submesh indices + for (uint32_t triangleIndex = 0; triangleIndex < part->mMesh.size(); ++triangleIndex) + { + ExplicitRenderTriangle& triangle = part->mMesh[triangleIndex]; + for (uint32_t frameNum = 0; frameNum < frameCount; ++frameNum) + { + if (triangle.extraDataIndex == frameIndices[frameNum]) + { + triangle.submeshIndex = (int32_t)submeshIndex; + } + } + } + + // Replace BSP mesh submesh indices + part->mMeshBSP->replaceInteriorSubmeshes(frameCount, frameIndices, submeshIndex); +} + +void ExplicitHierarchicalMeshImpl::calculateMeshBSP(uint32_t randomSeed, IProgressListener* progressListener, const uint32_t* microgridSize, BSPOpenMode::Enum meshMode) +{ + if (partCount() == 0) + { + outputMessage("No mesh, cannot calculate BSP.", physx::PxErrorCode::eDEBUG_WARNING); + return; + } + + uint32_t bspCount = 0; + for (uint32_t chunkIndex = 0; chunkIndex < mChunks.size(); ++chunkIndex) + { + if (mChunks[chunkIndex]->isRootLeafChunk()) + { + ++bspCount; + } + } + + if (bspCount == 0) + { + outputMessage("No parts at root depth, no BSPs to calculate", physx::PxErrorCode::eDEBUG_WARNING); + return; + } + + HierarchicalProgressListener progress(PxMax((int32_t)bspCount, 1), progressListener); + + const uint32_t microgridSizeToUse = microgridSize != NULL ? *microgridSize : gMicrogridSize; + + for (uint32_t chunkIndex = 0; chunkIndex < mChunks.size(); ++chunkIndex) + { + if (mChunks[chunkIndex]->isRootLeafChunk()) + { + uint32_t chunkPartIndex = (uint32_t)*partIndex(chunkIndex); + calculatePartBSP(chunkPartIndex, randomSeed, microgridSizeToUse, meshMode, &progress); + progress.completeSubtask(); + } + } +} + +void ExplicitHierarchicalMeshImpl::visualize(RenderDebugInterface& debugRender, uint32_t flags, uint32_t index) const +{ +#ifdef WITHOUT_DEBUG_VISUALIZE + PX_UNUSED(debugRender); + PX_UNUSED(flags); + PX_UNUSED(index); +#else + uint32_t bspMeshFlags = 0; + if (flags & VisualizeMeshBSPInsideRegions) + { + bspMeshFlags |= ApexCSG::BSPVisualizationFlags::InsideRegions; + } + if (flags & VisualizeMeshBSPOutsideRegions) + { + bspMeshFlags |= ApexCSG::BSPVisualizationFlags::OutsideRegions; + } + if (flags & VisualizeMeshBSPSingleRegion) + { + bspMeshFlags |= ApexCSG::BSPVisualizationFlags::SingleRegion; + } + for (uint32_t partIndex = 0; partIndex < mParts.size(); ++partIndex) + { + if (mParts[partIndex]->mMeshBSP != NULL) + { + mParts[partIndex]->mMeshBSP->visualize(debugRender, bspMeshFlags, index); + } + } +#endif +} + +void ExplicitHierarchicalMeshImpl::release() +{ + delete this; +} + +void ExplicitHierarchicalMeshImpl::buildMeshBounds(uint32_t partIndex) +{ + if (partIndex < partCount()) + { + physx::PxBounds3& bounds = mParts[partIndex]->mBounds; + bounds.setEmpty(); + const physx::Array<ExplicitRenderTriangle>& mesh = mParts[partIndex]->mMesh; + for (uint32_t i = 0; i < mesh.size(); ++i) + { + bounds.include(mesh[i].vertices[0].position); + bounds.include(mesh[i].vertices[1].position); + bounds.include(mesh[i].vertices[2].position); + } + } +} + +void ExplicitHierarchicalMeshImpl::buildCollisionGeometryForPart(uint32_t partIndex, const CollisionVolumeDesc& desc) +{ + if (partIndex < partCount()) + { + Part* part = mParts[partIndex]; + buildCollisionGeometryForPartInternal(part->mCollision, part, desc); + } +} + +void ExplicitHierarchicalMeshImpl::aggregateCollisionHullsFromRootChildren(uint32_t chunkIndex) +{ + nvidia::InlineArray<uint32_t,16> rootChildren; + for (uint32_t i = 0; i < mChunks.size(); ++i) + { + if (mChunks[i]->mParentIndex == (int32_t)chunkIndex && mChunks[i]->isRootChunk()) + { + rootChildren.pushBack(i); + } + } + + if (rootChildren.size() != 0) + { + uint32_t newHullCount = 0; + for (uint32_t rootChildNum = 0; rootChildNum < rootChildren.size(); ++rootChildNum) + { + const uint32_t rootChild = rootChildren[rootChildNum]; + aggregateCollisionHullsFromRootChildren(rootChild); + const uint32_t childPartIndex = (uint32_t)mChunks[rootChild]->mPartIndex; + newHullCount += mParts[childPartIndex]->mCollision.size(); + } + const uint32_t partIndex = (uint32_t)mChunks[chunkIndex]->mPartIndex; + resizeCollision(mParts[partIndex]->mCollision, newHullCount); + newHullCount = 0; + for (uint32_t rootChildNum = 0; rootChildNum < rootChildren.size(); ++rootChildNum) + { + const uint32_t rootChild = rootChildren[rootChildNum]; + const uint32_t childPartIndex = (uint32_t)mChunks[rootChild]->mPartIndex; + for (uint32_t hullN = 0; hullN < mParts[childPartIndex]->mCollision.size(); ++hullN) + { + *mParts[partIndex]->mCollision[newHullCount++] = *mParts[childPartIndex]->mCollision[hullN]; + } + } + PX_ASSERT(newHullCount == mParts[partIndex]->mCollision.size()); + } +} + +void ExplicitHierarchicalMeshImpl::buildCollisionGeometryForRootChunkParts(const CollisionDesc& desc, bool aggregateRootChunkParentCollision) +{ + // This helps keep the loops small if there are a lot of child chunks + uint32_t rootChunkStop = 0; + + for (uint32_t chunkIndex = 0; chunkIndex < chunkCount(); ++chunkIndex) + { + if (mChunks[chunkIndex]->isRootChunk()) + { + rootChunkStop = chunkIndex+1; + const uint32_t partIndex = (uint32_t)mChunks[chunkIndex]->mPartIndex; + if (partIndex < mParts.size()) + { + resizeCollision(mParts[partIndex]->mCollision, 0); + } + } + } + + for (uint32_t chunkIndex = 0; chunkIndex < rootChunkStop; ++chunkIndex) + { + if (mChunks[chunkIndex]->isRootLeafChunk() || (mChunks[chunkIndex]->isRootChunk() && !aggregateRootChunkParentCollision)) + { + const uint32_t partIndex = (uint32_t)mChunks[chunkIndex]->mPartIndex; + if (partIndex < mParts.size() && mParts[partIndex]->mCollision.size() == 0) + { + CollisionVolumeDesc volumeDesc = getVolumeDesc(desc, depth(chunkIndex)); + volumeDesc.mMaxVertexCount = volumeDesc.mMaxEdgeCount = volumeDesc.mMaxFaceCount = 0; // Don't reduce hulls until the very end + buildCollisionGeometryForPart(partIndex, volumeDesc); + } + } + } + + if (aggregateRootChunkParentCollision) + { + // Aggregate collision volumes from root depth chunks to their parents, recursing to depth 0 + aggregateCollisionHullsFromRootChildren(0); + } + + if (desc.mMaximumTrimming > 0.0f) + { + // Trim hulls up to root depth + for (uint32_t processDepth = 1; (int32_t)processDepth <= maxDepth(); ++processDepth) + { + physx::Array<uint32_t> chunkIndexArray; + for (uint32_t chunkIndex = 0; chunkIndex < rootChunkStop; ++chunkIndex) + { + if (mChunks[chunkIndex]->isRootChunk() && depth(chunkIndex) == processDepth) + { + chunkIndexArray.pushBack(chunkIndex); + } + } + if (chunkIndexArray.size() > 0) + { + trimChunkHulls(*this, &chunkIndexArray[0], chunkIndexArray.size(), desc.mMaximumTrimming); + } + } + } + + // Finally reduce the hulls + reduceHulls(desc, true); +} + +void ExplicitHierarchicalMeshImpl::reduceHulls(const CollisionDesc& desc, bool inflated) +{ + physx::Array<bool> partReduced(mParts.size(), false); + + for (uint32_t chunkIndex = 0; chunkIndex < mChunks.size(); ++chunkIndex) + { + uint32_t partIndex = (uint32_t)mChunks[chunkIndex]->mPartIndex; + if (partReduced[partIndex]) + { + continue; + } + Part* part = mParts[partIndex]; + CollisionVolumeDesc volumeDesc = getVolumeDesc(desc, depth(chunkIndex)); + for (uint32_t hullIndex = 0; hullIndex < part->mCollision.size(); ++hullIndex) + { + // First try uninflated, then try with inflation (if requested). This may find a better reduction + part->mCollision[hullIndex]->reduceHull(volumeDesc.mMaxVertexCount, volumeDesc.mMaxEdgeCount, volumeDesc.mMaxFaceCount, false); + if (inflated) + { + part->mCollision[hullIndex]->reduceHull(volumeDesc.mMaxVertexCount, volumeDesc.mMaxEdgeCount, volumeDesc.mMaxFaceCount, true); + } + } + partReduced[partIndex] = true; + } +} + +void ExplicitHierarchicalMeshImpl::initializeDisplacementMapVolume(const nvidia::FractureSliceDesc& desc) +{ + mDisplacementMapVolume.init(desc); +} + +} +} // namespace nvidia::apex + +namespace FractureTools +{ +ExplicitHierarchicalMesh* createExplicitHierarchicalMesh() +{ + return PX_NEW(ExplicitHierarchicalMeshImpl)(); +} + +ExplicitHierarchicalMeshImpl::ConvexHull* createExplicitHierarchicalMeshConvexHull() +{ + return PX_NEW(PartConvexHullProxy)(); +} +} // namespace FractureTools + +#endif // !defined(WITHOUT_APEX_AUTHORING) + +//#ifdef _MANAGED +//#pragma managed(pop) +//#endif diff --git a/APEX_1.4/shared/internal/src/authoring/Noise.h b/APEX_1.4/shared/internal/src/authoring/Noise.h new file mode 100644 index 00000000..df5865bb --- /dev/null +++ b/APEX_1.4/shared/internal/src/authoring/Noise.h @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + + +#ifndef NOISE_H +#define NOISE_H + +#include "authoring/ApexCSGMath.h" + +#include "NoiseUtils.h" + +#ifndef WITHOUT_APEX_AUTHORING + +namespace ApexCSG +{ + +class UserRandom; + +/** + Provides Perlin noise sampling across multiple dimensions and for different data types +*/ +template<typename T, int SampleSize = 1024, int D = 3, class VecType = Vec<T, D> > +class PerlinNoise +{ +public: + PerlinNoise(UserRandom& rnd, int octaves = 1, T frequency = 1., T amplitude = 1.) + : mRnd(rnd), + mOctaves(octaves), + mFrequency(frequency), + mAmplitude(amplitude), + mbInit(false) + { + + } + + void reset(int octaves = 1, T frequency = (T)1., T amplitude = (T)1.) + { + mOctaves = octaves; + mFrequency = frequency; + mAmplitude = amplitude; + init(); + } + + T sample(const VecType& point) + { + return perlinNoise(point); + } + +private: + PerlinNoise& operator=(const PerlinNoise&); + + T perlinNoise(VecType point) + { + if (!mbInit) + init(); + + const int octaves = mOctaves; + const T frequency = mFrequency; + T amplitude = mAmplitude; + T result = (T)0; + + point *= frequency; + + for (int i = 0; i < octaves; ++i) + { + result += noiseSample<T, SampleSize>(point, p, g) * amplitude; + point *= (T)2.0; + amplitude *= (T)0.5; + } + + return result; + } + + void init(void) + { + mbInit = true; + + unsigned i, j; + int k; + + for (i = 0 ; i < (unsigned)SampleSize; i++) + { + p[i] = (int)i; + for (j = 0; j < D; ++j) + g[i][j] = (T)((mRnd.getInt() % (SampleSize + SampleSize)) - SampleSize) / SampleSize; + g[i].normalize(); + } + + while (--i) + { + k = p[i]; + j = (unsigned)mRnd.getInt() % SampleSize; + p[i] = p[j]; + p[j] = k; + } + + for (i = 0 ; i < SampleSize + 2; ++i) + { + p [(unsigned)SampleSize + i] = p[i]; + for (j = 0; j < D; ++j) + g[(unsigned)SampleSize + i][j] = g[i][j]; + } + + } + + UserRandom& mRnd; + int mOctaves; + T mFrequency; + T mAmplitude; + + // Permutation vector + int p[(unsigned)(SampleSize + SampleSize + 2)]; + // Gradient vector + VecType g[(unsigned)(SampleSize + SampleSize + 2)]; + + bool mbInit; +}; + +} + +#endif /* #ifndef WITHOUT_APEX_AUTHORING */ + +#endif /* #ifndef NOISE_H */ + diff --git a/APEX_1.4/shared/internal/src/authoring/NoiseUtils.h b/APEX_1.4/shared/internal/src/authoring/NoiseUtils.h new file mode 100644 index 00000000..e284b616 --- /dev/null +++ b/APEX_1.4/shared/internal/src/authoring/NoiseUtils.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2008-2015, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA CORPORATION and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA CORPORATION is strictly prohibited. + */ + + +#ifndef NOISE_UTILS_H +#define NOISE_UTILS_H + +#include "authoring/ApexCSGMath.h" + +#ifndef WITHOUT_APEX_AUTHORING + +namespace ApexCSG +{ + +template<typename T> +PX_INLINE T fade(T t) { return t * t * t * (t * (t * (T)6.0 - (T)15.0) + (T)10.0); } + +template<typename T> +PX_INLINE T lerp(T t, T a, T b) { return a + t * (b - a); } + +template<typename T, class VecT, int SampleSize> +PX_INLINE void setup(int i, VecT& point, T& t, int& b0, int& b1, T& r0, T& r1) +{ + t = point[i] + (0x1000); + b0 = ((int)t) & (SampleSize-1); + b1 = (b0+1) & (SampleSize-1); + r0 = t - (int)t; + r1 = r0 - 1.0f; +} + +template<typename T, class VecT> +PX_INLINE T at2(const T& rx, const T& ry, const VecT& q) +{ + return rx * q[0] + ry * q[1]; +} + +template<typename T, class VecT> +PX_INLINE T at3(const T& rx, const T& ry, const T& rz, const VecT& q) +{ + return rx * q[0] + ry * q[1] + rz * q[2]; +} + +/////////////////////////////////////////////////////////////////////////// + +template<typename T, int SampleSize> +T noiseSample(ApexCSG::Vec<T, 1> point, int* p, ApexCSG::Vec<T,1>* g) +{ + int bx0, bx1; + T rx0, rx1, sx, t, u, v; + + setup<T,Vec<T,1>,SampleSize>(0, point,t, bx0,bx1, rx0,rx1); + + sx = fade(rx0); + + u = rx0 * g[ p[ bx0 ] ]; + v = rx1 * g[ p[ bx1 ] ]; + + return lerp(sx, u, v); +} + +template<typename T, int SampleSize> +T noiseSample(Vec<T,2> point, int* p, Vec<T,2>* g) +{ + int bx0, bx1, by0, by1, b00, b10, b01, b11; + T rx0, rx1, ry0, ry1, sx, sy, a, b, t, u, v; + Vec<T,2> q; + int i, j; + + setup<T,Vec<T,2>,SampleSize>(0, point,t, bx0,bx1, rx0,rx1); + setup<T,Vec<T,2>,SampleSize>(1, point,t, by0,by1, ry0,ry1); + + i = p[bx0]; + j = p[bx1]; + + b00 = p[i + by0]; + b10 = p[j + by0]; + b01 = p[i + by1]; + b11 = p[j + by1]; + + sx = fade(rx0); + sy = fade(ry0); + + q = g[b00]; + u = at2(rx0,ry0,q); + q = g[b10]; + v = at2(rx1,ry0,q); + a = lerp(sx, u, v); + + q = g[b01]; + u = at2(rx0,ry1,q); + q = g[b11]; + v = at2(rx1,ry1,q); + b = lerp(sx, u, v); + + return lerp(sy, a, b); +} + +template<typename T, int SampleSize> +T noiseSample(Vec<T,3> point, int* p, Vec<T,3>* g) +{ + int bx0, bx1, by0, by1, bz0, bz1, b00, b10, b01, b11; + T rx0, rx1, ry0, ry1, rz0, rz1, sy, sz, a, b, c, d, t, u, v; + Vec<T,3> q; + int i, j; + + setup<T,Vec<T,3>,SampleSize>(0, point,t, bx0,bx1, rx0,rx1); + setup<T,Vec<T,3>,SampleSize>(1, point,t, by0,by1, ry0,ry1); + setup<T,Vec<T,3>,SampleSize>(2, point,t, bz0,bz1, rz0,rz1); + + i = p[ bx0 ]; + j = p[ bx1 ]; + + b00 = p[ i + by0 ]; + b10 = p[ j + by0 ]; + b01 = p[ i + by1 ]; + b11 = p[ j + by1 ]; + + t = fade(rx0); + sy = fade(ry0); + sz = fade(rz0); + + q = g[ b00 + bz0 ] ; u = at3(rx0,ry0,rz0,q); + q = g[ b10 + bz0 ] ; v = at3(rx1,ry0,rz0,q); + a = lerp(t, u, v); + + q = g[ b01 + bz0 ] ; u = at3(rx0,ry1,rz0,q); + q = g[ b11 + bz0 ] ; v = at3(rx1,ry1,rz0,q); + b = lerp(t, u, v); + + c = lerp(sy, a, b); + + q = g[ b00 + bz1 ] ; u = at3(rx0,ry0,rz1,q); + q = g[ b10 + bz1 ] ; v = at3(rx1,ry0,rz1,q); + a = lerp(t, u, v); + + q = g[ b01 + bz1 ] ; u = at3(rx0,ry1,rz1,q); + q = g[ b11 + bz1 ] ; v = at3(rx1,ry1,rz1,q); + b = lerp(t, u, v); + + d = lerp(sy, a, b); + + return lerp(sz, c, d); +} + +} + +#endif /* #ifndef WITHOUT_APEX_AUTHORING */ + +#endif /* #ifndef NOISE_UTILS_H */
\ No newline at end of file |