1
0
Fork 0
mirror of https://github.com/beefytech/Beef.git synced 2025-06-10 04:22:20 +02:00

Additional 3d support

This commit is contained in:
Brian Fiete 2021-05-25 10:57:22 -04:00
parent 369bb0640c
commit 39c140f44a
27 changed files with 4063 additions and 126 deletions

View file

@ -6,6 +6,10 @@
#include "util/TLSingleton.h"
#include "FileStream.h"
#include "MemStream.h"
#include "util/MathUtils.h"
#include "util/Sphere.h"
#include "util/HashSet.h"
#include "util/BeefPerf.h"
#pragma warning(disable:4190)
@ -50,14 +54,30 @@ void Beefy::ModelAnimation::GetJointTranslation(int jointIdx, float frameNum, Mo
//
BF_EXPORT ModelInstance* BF_CALLTYPE ModelDef_CreateModelInstance(ModelDef* modelDef)
BF_EXPORT ModelInstance* BF_CALLTYPE ModelDef_CreateModelInstance(ModelDef* modelDef, ModelCreateFlags flags)
{
return gBFApp->mRenderDevice->CreateModelInstance(modelDef);
return gBFApp->mRenderDevice->CreateModelInstance(modelDef, flags);
}
BF_EXPORT void ModelDef_Compact(ModelDef* modelDef)
{
modelDef->Compact();
}
BF_EXPORT void ModelDef_GetBounds(ModelDef* modelDef, Vector3& min, Vector3& max)
{
modelDef->GetBounds(min, max);
}
BF_EXPORT void ModelDef_SetBaseDir(ModelDef* modelDef, char* baseDIr)
{
modelDef->mLoadDir = baseDIr;
}
BF_EXPORT const char* BF_CALLTYPE ModelDef_GetInfo(ModelDef* modelDef)
{
String& outString = *gModelDef_TLStrReturn.Get();
outString.Clear();
for (int meshIdx = 0; meshIdx < (int)modelDef->mMeshes.mSize; meshIdx++)
{
auto mesh = modelDef->mMeshes[meshIdx];
@ -105,6 +125,11 @@ BF_EXPORT void BF_CALLTYPE ModelDef_SetTextures(ModelDef* modelDef, int32 meshId
prims.mTexPaths.Add(paths[i]);
}
BF_EXPORT bool BF_CALLTYPE ModelDef_RayIntersect(ModelDef* modelDef, const Matrix4& worldMtx, const Vector3& origin, const Vector3& vec, Vector3& outIntersect, float& outDistance)
{
return modelDef->RayIntersect(worldMtx, origin, vec, outIntersect, outDistance);
}
BF_EXPORT void BF_CALLTYPE ModelDefAnimation_GetJointTranslation(ModelAnimation* modelAnimation, int jointIdx, float frame, ModelJointTranslation* outJointTranslation)
{
modelAnimation->GetJointTranslation(jointIdx, frame, outJointTranslation);
@ -126,6 +151,11 @@ BF_EXPORT void BF_CALLTYPE ModelDefAnimation_Clip(ModelAnimation* modelAnimation
modelAnimation->mFrames.RemoveRange(numFrames, modelAnimation->mFrames.Count() - numFrames);
}
ModelDef::ModelDef()
{
mFlags = Flags_None;
}
ModelDef::~ModelDef()
{
for (auto& materialInstance : mMaterials)
@ -134,6 +164,459 @@ ModelDef::~ModelDef()
}
}
void ModelDef::Compact()
{
for (auto& mesh : mMeshes)
{
for (auto& prims : mesh.mPrimitives)
{
Array<int> vtxMap;
vtxMap.Insert(0, -1, prims.mVertices.mSize);
int newVtxIdx = 0;
for (uint16 vtxIdx : prims.mIndices)
{
if (vtxMap[vtxIdx] == -1)
vtxMap[vtxIdx] = newVtxIdx++;
}
if (newVtxIdx >= prims.mVertices.mSize)
continue;
for (uint16& vtxIdx : prims.mIndices)
vtxIdx = vtxMap[vtxIdx];
Array<ModelVertex> oldVertices = prims.mVertices;
prims.mVertices.Resize(newVtxIdx);
for (int oldVtxIdx = 0; oldVtxIdx < oldVertices.mSize; oldVtxIdx++)
{
int newVtxIdx = vtxMap[oldVtxIdx];
if (newVtxIdx != -1)
prims.mVertices[newVtxIdx] = oldVertices[oldVtxIdx];
}
}
}
}
void ModelDef::CalcBounds()
{
int vtxCount = 0;
for (auto& mesh : mMeshes)
{
for (auto& prims : mesh.mPrimitives)
{
for (auto& vtx : prims.mVertices)
{
if (vtxCount == 0)
{
mBounds.mMin = vtx.mPosition;
mBounds.mMax = vtx.mPosition;
}
else
{
mBounds.mMin.mX = BF_MIN(mBounds.mMin.mX, vtx.mPosition.mX);
mBounds.mMin.mY = BF_MIN(mBounds.mMin.mY, vtx.mPosition.mY);
mBounds.mMin.mZ = BF_MIN(mBounds.mMin.mZ, vtx.mPosition.mZ);
mBounds.mMax.mX = BF_MAX(mBounds.mMax.mX, vtx.mPosition.mX);
mBounds.mMax.mY = BF_MAX(mBounds.mMax.mY, vtx.mPosition.mY);
mBounds.mMax.mZ = BF_MAX(mBounds.mMax.mZ, vtx.mPosition.mZ);
}
vtxCount++;
}
}
}
mFlags = (Flags)(mFlags | Flags_HasBounds);
}
void ModelDef::GetBounds(Vector3& min, Vector3& max)
{
if ((mFlags & Flags_HasBounds) == 0)
CalcBounds();
min = mBounds.mMin;
max = mBounds.mMax;
}
#define SWAP(x, y) { auto temp = x; x = y; y = temp; }
#define N (sizeof(A)/sizeof(A[0]))
// Partition using Lomuto partition scheme
static int partition(float a[], int left, int right, int pIndex)
{
// pick `pIndex` as a pivot from the array
float pivot = a[pIndex];
// Move pivot to end
SWAP(a[pIndex], a[right]);
// elements less than the pivot will be pushed to the left of `pIndex`;
// elements more than the pivot will be pushed to the right of `pIndex`;
// equal elements can go either way
pIndex = left;
// each time we find an element less than or equal to the pivot, `pIndex`
// is incremented, and that element would be placed before the pivot.
for (int i = left; i < right; i++)
{
if (a[i] <= pivot)
{
SWAP(a[i], a[pIndex]);
pIndex++;
}
}
// move pivot to its final place
SWAP(a[pIndex], a[right]);
// return `pIndex` (index of the pivot element)
return pIndex;
}
// Returns the k'th smallest element in the list within `left…right`
// (i.e., `left <= k <= right`). The search space within the array is
// changing for each round but the list is still the same size.
// Thus, `k` does not need to be updated with each round.
static float quickselect(float A[], int left, int right, int k)
{
// If the array contains only one element, return that element
if (left == right) {
return A[left];
}
// select `pIndex` between left and right
int pIndex = left + rand() % (right - left + 1);
pIndex = partition(A, left, right, pIndex);
// The pivot is in its final sorted position
if (k == pIndex) {
return A[k];
}
// if `k` is less than the pivot index
else if (k < pIndex) {
return quickselect(A, left, pIndex - 1, k);
}
// if `k` is more than the pivot index
else {
return quickselect(A, pIndex + 1, right, k);
}
}
void ModelDef::GenerateCollisionData()
{
BP_ZONE("ModelDef::GenerateCollisionData");
mFlags = (Flags)(mFlags | Flags_HasBVH);
int statsWorkItrs = 0;
int statsWorkTris = 0;
BF_ASSERT(mBVNodes.IsEmpty());
struct _WorkEntry
{
int mParentNodeIdx;
int mTriWorkIdx;
int mTriWorkCount;
};
Array<int32> triWorkList;
int triCount = 0;
for (auto& mesh : mMeshes)
{
for (auto& prims : mesh.mPrimitives)
{
int startIdx = mBVIndices.mSize;
triCount += prims.mIndices.mSize / 3;
triWorkList.Reserve(triWorkList.mSize + triCount);
mBVIndices.Reserve(mBVIndices.mSize + prims.mIndices.mSize);
mBVVertices.Reserve(mBVVertices.mSize + prims.mVertices.mSize);
for (int triIdx = 0; triIdx < triCount; triIdx++)
triWorkList.Add(startIdx / 3 + triIdx);
for (auto idx : prims.mIndices)
{
mBVIndices.Add(idx + startIdx);
}
for (auto& vtx : prims.mVertices)
mBVVertices.Add(vtx.mPosition);
}
}
Array<_WorkEntry> workList;
_WorkEntry workEntry;
workEntry.mParentNodeIdx = -1;
workEntry.mTriWorkIdx = 0;
workEntry.mTriWorkCount = triWorkList.mSize;
workList.Add(workEntry);
Array<Vector3> points;
points.Reserve(triWorkList.mSize);
Array<float> centers;
centers.Reserve(triWorkList.mSize);
Array<int> left;
left.Reserve(triWorkList.mSize);
Array<int> right;
right.Reserve(triWorkList.mSize);
mBVTris.Reserve(triWorkList.mSize * 2);
while (!workList.IsEmpty())
{
auto workEntry = workList.back();
workList.pop_back();
statsWorkItrs++;
statsWorkTris += workEntry.mTriWorkCount;
centers.Clear();
left.Clear();
right.Clear();
int nodeIdx = mBVNodes.mSize;
mBVNodes.Add(ModelBVNode());
if (workEntry.mParentNodeIdx != -1)
{
auto& bvParent = mBVNodes[workEntry.mParentNodeIdx];
if (bvParent.mLeft == -1)
bvParent.mLeft = nodeIdx;
else
{
BF_ASSERT(bvParent.mRight == -1);
bvParent.mRight = nodeIdx;
}
}
for (int triIdxIdx = 0; triIdxIdx < workEntry.mTriWorkCount; triIdxIdx++)
{
bool inLeft = false;
bool inRight = false;
int triIdx = triWorkList.mVals[workEntry.mTriWorkIdx + triIdxIdx];
for (int triVtxIdx = 0; triVtxIdx < 3; triVtxIdx++)
{
int vtxIdx = mBVIndices.mVals[triIdx * 3 + triVtxIdx];
bool isNewVtx = false;
int32* valuePtr = NULL;
auto& vtx = mBVVertices[vtxIdx];
auto& bvNode = mBVNodes[nodeIdx];
if ((triIdxIdx == 0) && (triVtxIdx == 0))
{
bvNode.mBoundAABB.mMin = vtx;
bvNode.mBoundAABB.mMax = vtx;
}
else
{
bvNode.mBoundAABB.mMin.mX = BF_MIN(bvNode.mBoundAABB.mMin.mX, vtx.mX);
bvNode.mBoundAABB.mMin.mY = BF_MIN(bvNode.mBoundAABB.mMin.mY, vtx.mY);
bvNode.mBoundAABB.mMin.mZ = BF_MIN(bvNode.mBoundAABB.mMin.mZ, vtx.mZ);
bvNode.mBoundAABB.mMax.mX = BF_MAX(bvNode.mBoundAABB.mMax.mX, vtx.mX);
bvNode.mBoundAABB.mMax.mY = BF_MAX(bvNode.mBoundAABB.mMax.mY, vtx.mY);
bvNode.mBoundAABB.mMax.mZ = BF_MAX(bvNode.mBoundAABB.mMax.mZ, vtx.mZ);
}
}
}
//mBVNodes[nodeIdx].mBoundSphere = Sphere::MiniBall(points.mVals, points.mSize);
bool didSplit = false;
if (workEntry.mTriWorkCount > 4)
{
int splitPlane = 0;
float splitWidth = 0;
// Split along widest AABB dimension
for (int dimIdx = 0; dimIdx < 3; dimIdx++)
{
float minVal = 0;
float maxVal = 0;
for (int triIdxIdx = 0; triIdxIdx < workEntry.mTriWorkCount; triIdxIdx++)
{
int triIdx = triWorkList.mVals[workEntry.mTriWorkIdx + triIdxIdx];
for (int triVtxIdx = 0; triVtxIdx < 3; triVtxIdx++)
{
int vtxIdx = mBVIndices.mVals[triIdx * 3 + triVtxIdx];
const Vector3& vtx = mBVVertices.mVals[vtxIdx];
float coord = ((float*)&vtx)[dimIdx];
if ((triIdxIdx == 0) && (triVtxIdx == 0))
{
minVal = coord;
maxVal = coord;
}
else
{
minVal = BF_MIN(minVal, coord);
maxVal = BF_MAX(maxVal, coord);
}
}
}
float width = maxVal - minVal;
if (width > splitWidth)
{
splitPlane = dimIdx;
splitWidth = width;
}
}
centers.SetSize(workEntry.mTriWorkCount);
for (int triIdxIdx = 0; triIdxIdx < workEntry.mTriWorkCount; triIdxIdx++)
{
int triIdx = triWorkList.mVals[workEntry.mTriWorkIdx + triIdxIdx];
float coordAcc = 0;
for (int triVtxIdx = 0; triVtxIdx < 3; triVtxIdx++)
{
int vtxIdx = mBVIndices.mVals[triIdx * 3 + triVtxIdx];
const Vector3& vtx = mBVVertices.mVals[vtxIdx];
float coord = ((float*)&vtx)[splitPlane];
coordAcc += coord;
}
float coordAvg = coordAcc / 3;
centers.mVals[triIdxIdx] = coordAvg;
}
float centerCoord = quickselect(centers.mVals, 0, centers.mSize - 1, centers.mSize / 2);
// centers.Sort([](float lhs, float rhs) { return lhs < rhs; });
// centerCoord = centers[centers.mSize / 2];
for (int triIdxIdx = 0; triIdxIdx < workEntry.mTriWorkCount; triIdxIdx++)
{
bool inLeft = false;
bool inRight = false;
int triIdx = triWorkList.mVals[workEntry.mTriWorkIdx + triIdxIdx];
for (int triVtxIdx = 0; triVtxIdx < 3; triVtxIdx++)
{
int vtxIdx = mBVIndices.mVals[triIdx * 3 + triVtxIdx];
const Vector3& vtx = mBVVertices.mVals[vtxIdx];
float coord = ((float*)&vtx)[splitPlane];
if (coord < centerCoord)
inLeft = true;
else
inRight = true;
}
if (inLeft)
left.Add(triIdx);
if (inRight)
right.Add(triIdx);
}
// Don't split if the split didn't significantly separate the triangles
bool doSplit =
(left.mSize <= workEntry.mTriWorkCount * 0.85f) &&
(right.mSize <= workEntry.mTriWorkCount * 0.85f);
if (doSplit)
{
mBVNodes[nodeIdx].mKind = ModelBVNode::Kind_Branch;
mBVNodes[nodeIdx].mLeft = -1;
mBVNodes[nodeIdx].mRight = -1;
_WorkEntry childWorkEntry;
childWorkEntry.mParentNodeIdx = nodeIdx;
childWorkEntry.mTriWorkIdx = triWorkList.mSize;
childWorkEntry.mTriWorkCount = right.mSize;
workList.Add(childWorkEntry);
triWorkList.Insert(triWorkList.mSize, right.mVals, right.mSize);
childWorkEntry.mParentNodeIdx = nodeIdx;
childWorkEntry.mTriWorkIdx = triWorkList.mSize;
childWorkEntry.mTriWorkCount = left.mSize;
workList.Add(childWorkEntry);
triWorkList.Insert(triWorkList.mSize, left.mVals, left.mSize);
continue;
}
}
// Did not split
int triStartIdx = mBVTris.mSize;
//mBVTris.Reserve(mBVTris.mSize + workEntry.mTriWorkCount);
for (int triIdxIdx = 0; triIdxIdx < workEntry.mTriWorkCount; triIdxIdx++)
mBVTris.Add(triWorkList[workEntry.mTriWorkIdx + triIdxIdx]);
auto& bvNode = mBVNodes[nodeIdx];
bvNode.mKind = ModelBVNode::Kind_Leaf;
bvNode.mTriStartIdx = triStartIdx;
bvNode.mTriCount = workEntry.mTriWorkCount;
}
NOP;
}
void ModelDef::RayIntersect(ModelBVNode* bvNode, const Matrix4& worldMtx, const Vector3& origin, const Vector3& vec, Vector3& outIntersect, float& outDistance)
{
// if (!RayIntersectsCircle(origin, vec, bvNode->mBoundSphere, NULL, NULL, NULL))
// return false;
if (!RayIntersectsAABB(origin, vec, bvNode->mBoundAABB, NULL, NULL, NULL))
return;
if (bvNode->mKind == ModelBVNode::Kind_Branch)
{
bool hadIntersect = false;
for (int branchIdx = 0; branchIdx < 2; branchIdx++)
RayIntersect(&mBVNodes[(branchIdx == 0) ? bvNode->mLeft : bvNode->mRight], worldMtx, origin, vec, outIntersect, outDistance);
return;
}
for (int triIdxIdx = 0; triIdxIdx < bvNode->mTriCount; triIdxIdx++)
{
int triIdx = mBVTris[bvNode->mTriStartIdx + triIdxIdx];
Vector3 curIntersect;
float curDistance;
if (RayIntersectsTriangle(origin, vec,
mBVVertices[mBVIndices[triIdx*3+0]], mBVVertices[mBVIndices[triIdx*3+1]], mBVVertices[mBVIndices[triIdx*3+2]],
&curIntersect, &curDistance))
{
if (curDistance < outDistance)
{
outIntersect = curIntersect;
outDistance = curDistance;
}
}
}
}
bool ModelDef::RayIntersect(const Matrix4& worldMtx, const Vector3& origin, const Vector3& vec, Vector3& outIntersect, float& outDistance)
{
if ((mFlags & Flags_HasBounds) == 0)
CalcBounds();
if (!RayIntersectsAABB(origin, vec, mBounds, NULL, NULL, NULL))
return false;
if (mBVNodes.IsEmpty())
GenerateCollisionData();
const float maxDist = 3.40282e+038f;
outDistance = maxDist;
RayIntersect(&mBVNodes[0], worldMtx, origin, vec, outIntersect, outDistance);
return outDistance != maxDist;
}
ModelMaterialDef* ModelMaterialDef::CreateOrGet(const StringImpl& prefix, const StringImpl& path)
{
StringT<128> key = prefix;
@ -211,7 +694,7 @@ public:
bool ReadFile(const StringImpl& fileName, const StringImpl& baseDir)
{
if (fileName.Contains(':'))
if (fileName.StartsWith('@'))
{
int colon = (int)fileName.IndexOf(':');
String addrStr = fileName.Substring(1, colon - 1);
@ -286,5 +769,7 @@ BF_EXPORT StringView BF_CALLTYPE Res_SerializeModel(ModelDef* modelDef)
}
String& outString = *gModelDef_TLStrReturn.Get();
outString.Clear();
outString.Append((char*)ms.GetPtr(), ms.GetSize());
return outString;
}