493 lines
13 KiB
C++
493 lines
13 KiB
C++
/**
|
|
* @file llconvexdecompositionvhacd.cpp
|
|
* @author rye@alchemyviewer.org
|
|
* @brief A VHACD based implementation of LLConvexDecomposition
|
|
*
|
|
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
|
* Second Life Viewer Source Code
|
|
* Copyright (C) 2025, Linden Research, Inc.
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation;
|
|
* version 2.1 of the License only.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
|
* $/LicenseInfo$
|
|
*/
|
|
|
|
#include "linden_common.h"
|
|
|
|
#include "llmath.h"
|
|
#include "v3math.h"
|
|
|
|
#include <string.h>
|
|
#include <memory>
|
|
|
|
#define ENABLE_VHACD_IMPLEMENTATION 1
|
|
#include "VHACD.h"
|
|
|
|
#include "llconvexdecompositionvhacd.h"
|
|
|
|
constexpr S32 MAX_HULLS = 256;
|
|
constexpr S32 MAX_VERTICES_PER_HULL = 256;
|
|
|
|
bool LLConvexDecompositionVHACD::isFunctional()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
LLConvexDecomposition* LLConvexDecompositionVHACD::getInstance()
|
|
{
|
|
return LLSimpleton::getInstance();
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::initSystem()
|
|
{
|
|
createInstance();
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::initThread()
|
|
{
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::quitThread()
|
|
{
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::quitSystem()
|
|
{
|
|
deleteSingleton();
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLConvexDecompositionVHACD::LLConvexDecompositionVHACD()
|
|
{
|
|
//Create our vhacd instance and setup default parameters
|
|
mVHACD = VHACD::CreateVHACD();
|
|
|
|
mVHACDParameters.m_callback = &mVHACDCallback;
|
|
mVHACDParameters.m_logger = &mVHACDLogger;
|
|
|
|
mDecompStages[0].mName = "Analyze";
|
|
mDecompStages[0].mDescription = nullptr;
|
|
|
|
LLCDParam param;
|
|
param.mName = "Fill Mode";
|
|
param.mDescription = nullptr;
|
|
param.mType = LLCDParam::LLCD_ENUM;
|
|
param.mDetails.mEnumValues.mNumEnums = 3;
|
|
|
|
static LLCDParam::LLCDEnumItem fill_enums[3];
|
|
fill_enums[(size_t)VHACD::FillMode::FLOOD_FILL].mName = "Flood";
|
|
fill_enums[(size_t)VHACD::FillMode::FLOOD_FILL].mValue = (int)VHACD::FillMode::FLOOD_FILL;
|
|
fill_enums[(size_t)VHACD::FillMode::SURFACE_ONLY].mName = "Surface Only";
|
|
fill_enums[(size_t)VHACD::FillMode::SURFACE_ONLY].mValue = (int)VHACD::FillMode::SURFACE_ONLY;
|
|
fill_enums[(size_t)VHACD::FillMode::RAYCAST_FILL].mName = "Raycast";
|
|
fill_enums[(size_t)VHACD::FillMode::RAYCAST_FILL].mValue = (int)VHACD::FillMode::RAYCAST_FILL;
|
|
|
|
param.mDetails.mEnumValues.mEnumsArray = fill_enums;
|
|
param.mDefault.mIntOrEnumValue = (int)VHACD::FillMode::FLOOD_FILL;
|
|
param.mStage = 0;
|
|
param.mReserved = -1;
|
|
mDecompParams.push_back(param);
|
|
|
|
enum EVoxelQualityLevels
|
|
{
|
|
E_LOW_QUALITY = 0,
|
|
E_NORMAL_QUALITY,
|
|
E_HIGH_QUALITY,
|
|
E_VERY_HIGH_QUALITY,
|
|
E_ULTRA_QUALITY,
|
|
E_MAX_QUALITY,
|
|
E_NUM_QUALITY_LEVELS
|
|
};
|
|
|
|
param.mName = "Voxel Resolution";
|
|
param.mDescription = nullptr;
|
|
param.mType = LLCDParam::LLCD_ENUM;
|
|
param.mDetails.mEnumValues.mNumEnums = E_NUM_QUALITY_LEVELS;
|
|
|
|
static LLCDParam::LLCDEnumItem voxel_quality_enums[E_NUM_QUALITY_LEVELS];
|
|
voxel_quality_enums[E_LOW_QUALITY].mName = "Low";
|
|
voxel_quality_enums[E_LOW_QUALITY].mValue = 200000;
|
|
voxel_quality_enums[E_NORMAL_QUALITY].mName = "Normal";
|
|
voxel_quality_enums[E_NORMAL_QUALITY].mValue = 400000;
|
|
voxel_quality_enums[E_HIGH_QUALITY].mName = "High";
|
|
voxel_quality_enums[E_HIGH_QUALITY].mValue = 800000;
|
|
voxel_quality_enums[E_VERY_HIGH_QUALITY].mName = "Very High";
|
|
voxel_quality_enums[E_VERY_HIGH_QUALITY].mValue = 1200000;
|
|
voxel_quality_enums[E_ULTRA_QUALITY].mName = "Ultra";
|
|
voxel_quality_enums[E_ULTRA_QUALITY].mValue = 1600000;
|
|
voxel_quality_enums[E_MAX_QUALITY].mName = "Maximum";
|
|
voxel_quality_enums[E_MAX_QUALITY].mValue = 2000000;
|
|
|
|
param.mDetails.mEnumValues.mEnumsArray = voxel_quality_enums;
|
|
param.mDefault.mIntOrEnumValue = 400000;
|
|
param.mStage = 0;
|
|
param.mReserved = -1;
|
|
mDecompParams.push_back(param);
|
|
|
|
param.mName = "Num Hulls";
|
|
param.mDescription = nullptr;
|
|
param.mType = LLCDParam::LLCD_FLOAT;
|
|
param.mDetails.mRange.mLow.mFloat = 1.f;
|
|
param.mDetails.mRange.mHigh.mFloat = MAX_HULLS;
|
|
param.mDetails.mRange.mDelta.mFloat = 1.f;
|
|
param.mDefault.mFloat = 8.f;
|
|
param.mStage = 0;
|
|
param.mReserved = -1;
|
|
mDecompParams.push_back(param);
|
|
|
|
param.mName = "Num Vertices";
|
|
param.mDescription = nullptr;
|
|
param.mType = LLCDParam::LLCD_FLOAT;
|
|
param.mDetails.mRange.mLow.mFloat = 3.f;
|
|
param.mDetails.mRange.mHigh.mFloat = MAX_VERTICES_PER_HULL;
|
|
param.mDetails.mRange.mDelta.mFloat = 1.f;
|
|
param.mDefault.mFloat = 32.f;
|
|
param.mStage = 0;
|
|
param.mReserved = -1;
|
|
mDecompParams.push_back(param);
|
|
|
|
param.mName = "Error Tolerance";
|
|
param.mDescription = nullptr;
|
|
param.mType = LLCDParam::LLCD_FLOAT;
|
|
param.mDetails.mRange.mLow.mFloat = 0.0001f;
|
|
param.mDetails.mRange.mHigh.mFloat = 99.f;
|
|
param.mDetails.mRange.mDelta.mFloat = 0.001f;
|
|
param.mDefault.mFloat = 1.f;
|
|
param.mStage = 0;
|
|
param.mReserved = -1;
|
|
mDecompParams.push_back(param);
|
|
|
|
for (const LLCDParam& param : mDecompParams)
|
|
{
|
|
const char* const name = param.mName;
|
|
|
|
switch (param.mType)
|
|
{
|
|
case LLCDParam::LLCD_FLOAT:
|
|
{
|
|
setParam(name, param.mDefault.mFloat);
|
|
break;
|
|
}
|
|
case LLCDParam::LLCD_ENUM:
|
|
case LLCDParam::LLCD_INTEGER:
|
|
{
|
|
setParam(name, param.mDefault.mIntOrEnumValue);
|
|
break;
|
|
}
|
|
case LLCDParam::LLCD_BOOLEAN:
|
|
{
|
|
setParam(name, (param.mDefault.mBool != 0));
|
|
break;
|
|
}
|
|
case LLCDParam::LLCD_INVALID:
|
|
default:
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
LLConvexDecompositionVHACD::~LLConvexDecompositionVHACD()
|
|
{
|
|
mBoundDecomp = nullptr;
|
|
mDecompData.clear();
|
|
|
|
mVHACD->Release();
|
|
}
|
|
|
|
void LLConvexDecompositionVHACD::genDecomposition(int& decomp)
|
|
{
|
|
int new_decomp_id = static_cast<int>(mDecompData.size()) + 1;
|
|
mDecompData[new_decomp_id] = LLDecompData();
|
|
decomp = new_decomp_id;
|
|
}
|
|
|
|
void LLConvexDecompositionVHACD::deleteDecomposition(int decomp)
|
|
{
|
|
auto iter = mDecompData.find(decomp);
|
|
if (iter != mDecompData.end())
|
|
{
|
|
if (mBoundDecomp == &iter->second)
|
|
{
|
|
mBoundDecomp = nullptr;
|
|
}
|
|
mDecompData.erase(iter);
|
|
}
|
|
}
|
|
|
|
void LLConvexDecompositionVHACD::bindDecomposition(int decomp)
|
|
{
|
|
auto iter = mDecompData.find(decomp);
|
|
if (iter != mDecompData.end())
|
|
{
|
|
mBoundDecomp = &iter->second;
|
|
}
|
|
else
|
|
{
|
|
LL_WARNS() << "Failed to bind unknown decomposition: " << decomp << LL_ENDL;
|
|
mBoundDecomp = nullptr;
|
|
}
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::setParam(const char* name, float val)
|
|
{
|
|
if (name == std::string("Num Hulls"))
|
|
{
|
|
mVHACDParameters.m_maxConvexHulls = llclamp(ll_round(val), 1, MAX_HULLS);
|
|
}
|
|
else if (name == std::string("Num Vertices"))
|
|
{
|
|
mVHACDParameters.m_maxNumVerticesPerCH = llclamp(ll_round(val), 3, MAX_VERTICES_PER_HULL);
|
|
}
|
|
else if (name == std::string("Error Tolerance"))
|
|
{
|
|
mVHACDParameters.m_minimumVolumePercentErrorAllowed = val;
|
|
}
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::setParam(const char* name, bool val)
|
|
{
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::setParam(const char* name, int val)
|
|
{
|
|
if (name == std::string("Fill Mode"))
|
|
{
|
|
mVHACDParameters.m_fillMode = (VHACD::FillMode)val;
|
|
}
|
|
else if (name == std::string("Voxel Resolution"))
|
|
{
|
|
mVHACDParameters.m_resolution = val;
|
|
}
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::setMeshData( const LLCDMeshData* data, bool vertex_based )
|
|
{
|
|
if (!mBoundDecomp)
|
|
{
|
|
return LLCD_NULL_PTR;
|
|
}
|
|
|
|
return mBoundDecomp->mSourceMesh.from(data, vertex_based);
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::registerCallback(int stage, llcdCallbackFunc callback )
|
|
{
|
|
if (stage == 0)
|
|
{
|
|
mVHACDCallback.setCallbackFunc(callback);
|
|
return LLCD_OK;
|
|
}
|
|
else
|
|
{
|
|
return LLCD_INVALID_STAGE;
|
|
}
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::executeStage(int stage)
|
|
{
|
|
if (!mBoundDecomp)
|
|
{
|
|
return LLCD_NULL_PTR;
|
|
}
|
|
|
|
if (stage != 0)
|
|
{
|
|
return LLCD_INVALID_STAGE;
|
|
}
|
|
|
|
mBoundDecomp->mDecomposedHulls.clear();
|
|
|
|
const auto& decomp_mesh = mBoundDecomp->mSourceMesh;
|
|
if (!mVHACD->Compute((const double* const)decomp_mesh.mVertices.data(), static_cast<uint32_t>(decomp_mesh.mVertices.size()), (const uint32_t* const)decomp_mesh.mIndices.data(), static_cast<uint32_t>(decomp_mesh.mIndices.size()), mVHACDParameters))
|
|
{
|
|
return LLCD_INVALID_HULL_DATA;
|
|
}
|
|
|
|
uint32_t num_nulls = mVHACD->GetNConvexHulls();
|
|
if (num_nulls == 0)
|
|
{
|
|
return LLCD_INVALID_HULL_DATA;
|
|
}
|
|
|
|
for (uint32_t i = 0; num_nulls > i; ++i)
|
|
{
|
|
VHACD::IVHACD::ConvexHull ch;
|
|
if (!mVHACD->GetConvexHull(i, ch))
|
|
continue;
|
|
|
|
LLConvexMesh out_mesh;
|
|
out_mesh.setVertices(ch.m_points);
|
|
out_mesh.setIndices(ch.m_triangles);
|
|
|
|
mBoundDecomp->mDecomposedHulls.push_back(std::move(out_mesh));
|
|
}
|
|
|
|
mVHACD->Clean();
|
|
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::buildSingleHull()
|
|
{
|
|
LL_INFOS() << "Building single hull mesh" << LL_ENDL;
|
|
if (!mBoundDecomp || mBoundDecomp->mSourceMesh.mVertices.empty())
|
|
{
|
|
return LLCD_NULL_PTR;
|
|
}
|
|
|
|
mBoundDecomp->mSingleHullMesh.clear();
|
|
|
|
VHACD::QuickHull quickhull;
|
|
uint32_t num_tris = quickhull.ComputeConvexHull(mBoundDecomp->mSourceMesh.mVertices, MAX_VERTICES_PER_HULL);
|
|
if (num_tris > 0)
|
|
{
|
|
mBoundDecomp->mSingleHullMesh.setVertices(quickhull.GetVertices());
|
|
mBoundDecomp->mSingleHullMesh.setIndices(quickhull.GetIndices());
|
|
|
|
return LLCD_OK;
|
|
}
|
|
|
|
return LLCD_INVALID_MESH_DATA;
|
|
}
|
|
|
|
int LLConvexDecompositionVHACD::getNumHullsFromStage(int stage)
|
|
{
|
|
if (!mBoundDecomp || stage != 0)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
return narrow(mBoundDecomp->mDecomposedHulls.size());
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::getSingleHull( LLCDHull* hullOut )
|
|
{
|
|
memset( hullOut, 0, sizeof(LLCDHull) );
|
|
|
|
if (!mBoundDecomp)
|
|
{
|
|
return LLCD_NULL_PTR;
|
|
}
|
|
|
|
if (mBoundDecomp->mSingleHullMesh.vertices.empty())
|
|
{
|
|
return LLCD_INVALID_HULL_DATA;
|
|
}
|
|
|
|
mBoundDecomp->mSingleHullMesh.to(hullOut);
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::getHullFromStage( int stage, int hull, LLCDHull* hullOut )
|
|
{
|
|
memset( hullOut, 0, sizeof(LLCDHull) );
|
|
|
|
if (!mBoundDecomp)
|
|
{
|
|
return LLCD_NULL_PTR;
|
|
}
|
|
|
|
if (stage != 0)
|
|
{
|
|
return LLCD_INVALID_STAGE;
|
|
}
|
|
|
|
if (mBoundDecomp->mDecomposedHulls.empty() || mBoundDecomp->mDecomposedHulls.size() <= hull)
|
|
{
|
|
return LLCD_REQUEST_OUT_OF_RANGE;
|
|
}
|
|
|
|
mBoundDecomp->mDecomposedHulls[hull].to(hullOut);
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::getMeshFromStage( int stage, int hull, LLCDMeshData* meshDataOut )
|
|
{
|
|
memset( meshDataOut, 0, sizeof(LLCDMeshData));
|
|
if (!mBoundDecomp)
|
|
{
|
|
return LLCD_NULL_PTR;
|
|
}
|
|
|
|
if (stage != 0)
|
|
{
|
|
return LLCD_INVALID_STAGE;
|
|
}
|
|
|
|
if (mBoundDecomp->mDecomposedHulls.empty() || mBoundDecomp->mDecomposedHulls.size() <= hull)
|
|
{
|
|
return LLCD_REQUEST_OUT_OF_RANGE;
|
|
}
|
|
|
|
mBoundDecomp->mDecomposedHulls[hull].to(meshDataOut);
|
|
return LLCD_OK;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::getMeshFromHull( LLCDHull* hullIn, LLCDMeshData* meshOut )
|
|
{
|
|
memset(meshOut, 0, sizeof(LLCDMeshData));
|
|
|
|
LLVHACDMesh inMesh(hullIn);
|
|
|
|
VHACD::QuickHull quickhull;
|
|
uint32_t num_tris = quickhull.ComputeConvexHull(inMesh.mVertices, MAX_VERTICES_PER_HULL);
|
|
if (num_tris > 0)
|
|
{
|
|
mMeshFromHullData.setVertices(quickhull.GetVertices());
|
|
mMeshFromHullData.setIndices(quickhull.GetIndices());
|
|
|
|
mMeshFromHullData.to(meshOut);
|
|
return LLCD_OK;
|
|
}
|
|
|
|
return LLCD_INVALID_HULL_DATA;
|
|
}
|
|
|
|
LLCDResult LLConvexDecompositionVHACD::generateSingleHullMeshFromMesh(LLCDMeshData* meshIn, LLCDMeshData* meshOut)
|
|
{
|
|
memset( meshOut, 0, sizeof(LLCDMeshData) );
|
|
|
|
LLVHACDMesh inMesh(meshIn, true);
|
|
|
|
VHACD::QuickHull quickhull;
|
|
uint32_t num_tris = quickhull.ComputeConvexHull(inMesh.mVertices, MAX_VERTICES_PER_HULL);
|
|
if (num_tris > 0)
|
|
{
|
|
mSingleHullMeshFromMeshData.setVertices(quickhull.GetVertices());
|
|
mSingleHullMeshFromMeshData.setIndices(quickhull.GetIndices());
|
|
|
|
mSingleHullMeshFromMeshData.to(meshOut);
|
|
return LLCD_OK;
|
|
}
|
|
|
|
return LLCD_INVALID_MESH_DATA;
|
|
}
|
|
|
|
void LLConvexDecompositionVHACD::loadMeshData(const char* fileIn, LLCDMeshData** meshDataOut)
|
|
{
|
|
static LLCDMeshData meshData;
|
|
memset( &meshData, 0, sizeof(LLCDMeshData) );
|
|
*meshDataOut = &meshData;
|
|
}
|