|
|
@@ -6,11 +6,13 @@
|
|
|
#include "BsRendererView.h"
|
|
|
#include "BsRenderBeastIBLUtility.h"
|
|
|
#include "BsRenderBeast.h"
|
|
|
+#include "BsMesh.h"
|
|
|
+#include "BsVertexDataDesc.h"
|
|
|
|
|
|
namespace bs { namespace ct
|
|
|
{
|
|
|
LightProbes::LightProbes()
|
|
|
- :mTetrahedronVolumeDirty(false), mNumAllocatedEntries(0), mNumUsedEntries(0)
|
|
|
+ :mTetrahedronVolumeDirty(false), mMaxCoefficients(0), mMaxTetrahedra(0)
|
|
|
{
|
|
|
resizeCoefficientBuffer(512);
|
|
|
}
|
|
|
@@ -61,162 +63,187 @@ namespace bs { namespace ct
|
|
|
{
|
|
|
if(mTetrahedronVolumeDirty)
|
|
|
{
|
|
|
+ // Move all coefficients into the global buffer
|
|
|
+ UINT32 numCoeffs = 0;
|
|
|
+ for(auto& entry : mVolumes)
|
|
|
+ {
|
|
|
+ UINT32 numProbes = (UINT32)entry.volume->getLightProbePositions().size();
|
|
|
+ numCoeffs += numProbes;
|
|
|
+ }
|
|
|
+
|
|
|
+ if(numCoeffs > mMaxCoefficients)
|
|
|
+ {
|
|
|
+ UINT32 newSize = Math::divideAndRoundUp(numCoeffs, 32U) * 32U;
|
|
|
+ resizeCoefficientBuffer(newSize);
|
|
|
+ }
|
|
|
+
|
|
|
+ UINT8* dest = (UINT8*)mProbeCoefficientsGPU->lock(0, mProbeCoefficientsGPU->getSize(), GBL_WRITE_ONLY_DISCARD);
|
|
|
+ for(auto& entry : mVolumes)
|
|
|
+ {
|
|
|
+ UINT32 numProbes = (UINT32)entry.volume->getLightProbePositions().size();
|
|
|
+ SPtr<GpuBuffer> localBuffer = entry.volume->getCoefficientsBuffer();
|
|
|
+
|
|
|
+ // Note: Some of the coefficients might still be dirty (unrendered). Check for this and write them as black?
|
|
|
+ UINT32 size = numProbes * sizeof(LightProbeSHCoefficients);
|
|
|
+ UINT8* src = (UINT8*)localBuffer->lock(0, size, GBL_READ_ONLY);
|
|
|
+ memcpy(dest, src, size);
|
|
|
+
|
|
|
+ localBuffer->unlock();
|
|
|
+ dest += size;
|
|
|
+ }
|
|
|
+ mProbeCoefficientsGPU->unlock();
|
|
|
+
|
|
|
// Gather all positions
|
|
|
+ UINT32 bufferOffset = 0;
|
|
|
for(auto& entry : mVolumes)
|
|
|
{
|
|
|
+ const Vector<LightProbeInfo>& infos = entry.volume->getLightProbeInfos();
|
|
|
const Vector<Vector3>& positions = entry.volume->getLightProbePositions();
|
|
|
+ UINT32 numProbes = entry.volume->getNumActiveProbes();
|
|
|
|
|
|
+ if (numProbes == 0)
|
|
|
+ continue;
|
|
|
+
|
|
|
Vector3 offset = entry.volume->getPosition();
|
|
|
Quaternion rotation = entry.volume->getRotation();
|
|
|
- for(auto& localPos : positions)
|
|
|
+ for(UINT32 i = 0; i < numProbes; i++)
|
|
|
{
|
|
|
+ Vector3 localPos = positions[i];
|
|
|
Vector3 transformedPos = rotation.rotate(localPos) + offset;
|
|
|
mTempTetrahedronPositions.push_back(transformedPos);
|
|
|
+ mTempTetrahedronBufferIndices.push_back(bufferOffset + infos[i].bufferIdx);
|
|
|
}
|
|
|
+
|
|
|
+ bufferOffset += (UINT32)positions.size();
|
|
|
}
|
|
|
|
|
|
mTetrahedronInfos.clear();
|
|
|
- mTetrahedronBounds.clear();
|
|
|
|
|
|
+ UINT32 innerVertexCount = (UINT32)mTempTetrahedronPositions.size();
|
|
|
generateTetrahedronData(mTempTetrahedronPositions, mTetrahedronInfos, false);
|
|
|
|
|
|
- // Generate bounds
|
|
|
- for(auto& entry : mTetrahedronInfos)
|
|
|
- {
|
|
|
- // Skipping outer faces
|
|
|
- if (entry.volume.neighbors[3] < 0)
|
|
|
- continue;
|
|
|
+ // Generate a mesh out of all the tetrahedron triangles
|
|
|
+ // Note: Currently the entire volume is rendered as a single large mesh, which will isn't optimal as we can't
|
|
|
+ // perform frustum culling. A better option would be to split the mesh into multiple smaller volumes, do
|
|
|
+ // frustum culling and possibly even sort by distance from camera.
|
|
|
+ UINT32 numTetrahedra = (UINT32)mTetrahedronInfos.size();
|
|
|
|
|
|
- AABox aabox = AABox(Vector3::INF, -Vector3::INF);
|
|
|
- for (int i = 0; i < 4; ++i)
|
|
|
- aabox.merge(mTempTetrahedronPositions[entry.volume.neighbors[i]]);
|
|
|
+ UINT32 numVertices = numTetrahedra * 4 * 3;
|
|
|
+ UINT32 numIndices = numTetrahedra * 4 * 3;
|
|
|
|
|
|
- mTetrahedronBounds.push_back(aabox);
|
|
|
- }
|
|
|
+ SPtr<VertexDataDesc> vertexDesc = bs_shared_ptr_new<VertexDataDesc>();
|
|
|
+ vertexDesc->addVertElem(VET_FLOAT3, VES_POSITION);
|
|
|
+ vertexDesc->addVertElem(VET_UINT1, VES_TEXCOORD);
|
|
|
|
|
|
- mTempTetrahedronPositions.clear();
|
|
|
- mTetrahedronVolumeDirty = false;
|
|
|
- }
|
|
|
- }
|
|
|
+ SPtr<MeshData> meshData = MeshData::create(numVertices, numIndices, vertexDesc);
|
|
|
+ auto posIter = meshData->getVec3DataIter(VES_POSITION);
|
|
|
+ auto idIter = meshData->getDWORDDataIter(VES_TEXCOORD);
|
|
|
|
|
|
- void LightProbes::resizeTetrahedronBuffers(VisibleLightProbeData& data, UINT32 count)
|
|
|
- {
|
|
|
- {
|
|
|
- GPU_BUFFER_DESC desc;
|
|
|
- desc.type = GBT_STRUCTURED;
|
|
|
- desc.elementSize = sizeof(TetrahedronBoundsGPU);
|
|
|
- desc.elementCount = count;
|
|
|
- desc.usage = GBU_STATIC;
|
|
|
- desc.format = BF_UNKNOWN;
|
|
|
-
|
|
|
- SPtr<GpuBuffer> newBuffer = GpuBuffer::create(desc);
|
|
|
- if (data.tetrahedronBounds)
|
|
|
- newBuffer->copyData(*data.tetrahedronBounds, 0, 0, data.tetrahedronBounds->getSize(), true);
|
|
|
-
|
|
|
- data.tetrahedronBounds = newBuffer;
|
|
|
- }
|
|
|
-
|
|
|
- {
|
|
|
- GPU_BUFFER_DESC desc;
|
|
|
- desc.type = GBT_STRUCTURED;
|
|
|
- desc.elementSize = sizeof(TetrahedronDataGPU);
|
|
|
- desc.elementCount = count;
|
|
|
- desc.usage = GBU_STATIC;
|
|
|
- desc.format = BF_UNKNOWN;
|
|
|
-
|
|
|
- SPtr<GpuBuffer> newBuffer = GpuBuffer::create(desc);
|
|
|
- if (data.tetrahedronInfos)
|
|
|
- newBuffer->copyData(*data.tetrahedronInfos, 0, 0, data.tetrahedronInfos->getSize(), true);
|
|
|
-
|
|
|
- data.tetrahedronInfos = newBuffer;
|
|
|
- }
|
|
|
+ UINT32 tetIdx = 0;
|
|
|
+ for(auto& entry : mTetrahedronInfos)
|
|
|
+ {
|
|
|
+ const Tetrahedron& volume = entry.volume;
|
|
|
|
|
|
- data.maxNumEntries = count;
|
|
|
- }
|
|
|
+ Vector3 center(BsZero);
|
|
|
+ for(UINT32 i = 0; i < 4; i++)
|
|
|
+ center += mTempTetrahedronPositions[volume.vertices[i]];
|
|
|
|
|
|
- void LightProbes::resizeCoefficientBuffer(UINT32 count)
|
|
|
- {
|
|
|
- GPU_BUFFER_DESC desc;
|
|
|
- desc.type = GBT_STRUCTURED;
|
|
|
- desc.elementSize = sizeof(SHVector3RGB);
|
|
|
- desc.elementCount = count;
|
|
|
- desc.usage = GBU_STATIC;
|
|
|
- desc.format = BF_UNKNOWN;
|
|
|
+ center /= 4.0f;
|
|
|
|
|
|
- SPtr<GpuBuffer> newBuffer = GpuBuffer::create(desc);
|
|
|
- if (mProbeCoefficientsGPU)
|
|
|
- newBuffer->copyData(*mProbeCoefficientsGPU, 0, 0, mProbeCoefficientsGPU->getSize(), true);
|
|
|
+ static const UINT32 Permutations[4][3] =
|
|
|
+ {
|
|
|
+ { 0, 1, 2 },
|
|
|
+ { 0, 1, 3 },
|
|
|
+ { 0, 2, 3 },
|
|
|
+ { 1, 2, 3 }
|
|
|
+ };
|
|
|
|
|
|
- mProbeCoefficientsGPU = newBuffer;
|
|
|
- mNumAllocatedEntries = count;
|
|
|
- }
|
|
|
+ for(UINT32 i = 0; i < 4; i++)
|
|
|
+ {
|
|
|
+ Vector3 A = mTempTetrahedronPositions[volume.vertices[Permutations[i][0]]];
|
|
|
+ Vector3 B = mTempTetrahedronPositions[volume.vertices[Permutations[i][1]]];
|
|
|
+ Vector3 C = mTempTetrahedronPositions[volume.vertices[Permutations[i][2]]];
|
|
|
|
|
|
- void LightProbes::updateVisibleProbes(const RendererView& view, VisibleLightProbeData& output)
|
|
|
- {
|
|
|
- // Ignore all probes past this point
|
|
|
- static const float MAX_PROBE_DISTANCE = 100.0f;
|
|
|
+ // Make sure the triangle is clockwise
|
|
|
+ Vector3 e0 = A - C;
|
|
|
+ Vector3 e1 = B - C;
|
|
|
|
|
|
- const RendererViewProperties& viewProps = view.getProperties();
|
|
|
- const ConvexVolume& worldFrustum = viewProps.cullFrustum;
|
|
|
+ Vector3 normal = e0.cross(e1);
|
|
|
+ if (normal.dot(A - center) < 0.0f)
|
|
|
+ std::swap(B, C);
|
|
|
|
|
|
- const float maxProbeDistance2 = MAX_PROBE_DISTANCE * MAX_PROBE_DISTANCE;
|
|
|
- for (UINT32 i = 0; i < (UINT32)mTetrahedronBounds.size(); i++)
|
|
|
- {
|
|
|
- float distance2 = viewProps.viewOrigin.squaredDistance(mTetrahedronBounds[i].getCenter());
|
|
|
- if (distance2 > maxProbeDistance2)
|
|
|
- continue;
|
|
|
+ posIter.addValue(A);
|
|
|
+ posIter.addValue(B);
|
|
|
+ posIter.addValue(C);
|
|
|
|
|
|
- if (worldFrustum.intersects(mTetrahedronBounds[i]))
|
|
|
- mTempTetrahedronVisibility.push_back(i);
|
|
|
- }
|
|
|
+ idIter.addValue(tetIdx);
|
|
|
+ idIter.addValue(tetIdx);
|
|
|
+ idIter.addValue(tetIdx);
|
|
|
+ }
|
|
|
|
|
|
- UINT32 numVisibleTets = (UINT32)mTempTetrahedronVisibility.size();
|
|
|
- if (numVisibleTets > output.maxNumEntries)
|
|
|
- {
|
|
|
- UINT32 newBufferSize = 256;
|
|
|
- if(output.maxNumEntries > 0)
|
|
|
- newBufferSize = Math::divideAndRoundUp(numVisibleTets, output.maxNumEntries) * output.maxNumEntries;
|
|
|
+ tetIdx++;
|
|
|
+ }
|
|
|
|
|
|
- resizeTetrahedronBuffers(output, newBufferSize);
|
|
|
- }
|
|
|
+ mVolumeMesh = Mesh::create(meshData);
|
|
|
|
|
|
- // Write bounds
|
|
|
- {
|
|
|
- TetrahedronBoundsGPU* dst = (TetrahedronBoundsGPU*)output.tetrahedronBounds->lock(0,
|
|
|
- output.tetrahedronBounds->getSize(), GBL_WRITE_ONLY_DISCARD);
|
|
|
+ // Map vertices to actual SH coefficient indices, and write GPU buffer with tetrahedron information
|
|
|
+ if (numTetrahedra > mMaxTetrahedra)
|
|
|
+ {
|
|
|
+ UINT32 newSize = Math::divideAndRoundUp(numTetrahedra, 64U) * 64U;
|
|
|
+ resizeTetrahedronBuffer(newSize);
|
|
|
+ }
|
|
|
|
|
|
- for (auto& entry : mTempTetrahedronVisibility)
|
|
|
+ TetrahedronDataGPU* dst = (TetrahedronDataGPU*)mTetrahedronInfosGPU->lock(0, mTetrahedronInfosGPU->getSize(),
|
|
|
+ GBL_WRITE_ONLY_DISCARD);
|
|
|
+ for (auto& entry : mTetrahedronInfos)
|
|
|
{
|
|
|
- const AABox& aabox = mTetrahedronBounds[entry];
|
|
|
+ for(UINT32 i = 0; i < 4; ++i)
|
|
|
+ {
|
|
|
+ // Check for outer vertices, which have no SH data associated with them
|
|
|
+ if (entry.volume.vertices[i] >= innerVertexCount)
|
|
|
+ entry.volume.vertices[i] = -1;
|
|
|
+ else
|
|
|
+ entry.volume.vertices[i] = mTempTetrahedronBufferIndices[i];
|
|
|
+ }
|
|
|
|
|
|
- dst->center = aabox.getCenter();
|
|
|
- dst->extents = aabox.getHalfSize();
|
|
|
+ memcpy(dst->indices, entry.volume.vertices, sizeof(UINT32) * 4);
|
|
|
+ memcpy(&dst->transform, &entry.transform, sizeof(float) * 12);
|
|
|
|
|
|
dst++;
|
|
|
}
|
|
|
|
|
|
- output.tetrahedronBounds->unlock();
|
|
|
- }
|
|
|
-
|
|
|
- // Write other information
|
|
|
- {
|
|
|
- TetrahedronDataGPU* dst = (TetrahedronDataGPU*)output.tetrahedronInfos->lock(0,
|
|
|
- output.tetrahedronInfos->getSize(), GBL_WRITE_ONLY_DISCARD);
|
|
|
+ mTetrahedronInfosGPU->unlock();
|
|
|
|
|
|
- for (auto& entry : mTempTetrahedronVisibility)
|
|
|
- {
|
|
|
- const TetrahedronData& data = mTetrahedronInfos[entry];
|
|
|
+ mTempTetrahedronPositions.clear();
|
|
|
+ mTempTetrahedronBufferIndices.clear();
|
|
|
+ mTetrahedronVolumeDirty = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
- memcpy(dst->indices, data.volume.vertices, sizeof(UINT32) * 4);
|
|
|
- memcpy(&dst->transform, &data.transform, sizeof(float) * 12);
|
|
|
+ void LightProbes::resizeTetrahedronBuffer(UINT32 count)
|
|
|
+ {
|
|
|
+ GPU_BUFFER_DESC desc;
|
|
|
+ desc.type = GBT_STRUCTURED;
|
|
|
+ desc.elementSize = sizeof(TetrahedronDataGPU);
|
|
|
+ desc.elementCount = count;
|
|
|
+ desc.usage = GBU_STATIC;
|
|
|
+ desc.format = BF_UNKNOWN;
|
|
|
|
|
|
- dst++;
|
|
|
- }
|
|
|
+ mTetrahedronInfosGPU = GpuBuffer::create(desc);
|
|
|
+ mMaxTetrahedra = count;
|
|
|
+ }
|
|
|
|
|
|
- output.tetrahedronBounds->unlock();
|
|
|
- }
|
|
|
+ void LightProbes::resizeCoefficientBuffer(UINT32 count)
|
|
|
+ {
|
|
|
+ GPU_BUFFER_DESC desc;
|
|
|
+ desc.type = GBT_STRUCTURED;
|
|
|
+ desc.elementSize = sizeof(LightProbeSHCoefficients);
|
|
|
+ desc.elementCount = count;
|
|
|
+ desc.usage = GBU_STATIC;
|
|
|
+ desc.format = BF_UNKNOWN;
|
|
|
|
|
|
- mTempTetrahedronVisibility.clear();
|
|
|
+ mProbeCoefficientsGPU = GpuBuffer::create(desc);
|
|
|
+ mMaxCoefficients = count;
|
|
|
}
|
|
|
|
|
|
/** Hash value generator for std::pair<INT32, INT32>. */
|
|
|
@@ -232,99 +259,24 @@ namespace bs { namespace ct
|
|
|
}
|
|
|
};
|
|
|
|
|
|
- void LightProbes::generateTetrahedronData(const Vector<Vector3>& positions, Vector<TetrahedronData>& output,
|
|
|
- bool includeOuterFaces)
|
|
|
+ void LightProbes::generateTetrahedronData(Vector<Vector3>& positions, Vector<TetrahedronData>& output,
|
|
|
+ bool generateExtrapolationVolume)
|
|
|
{
|
|
|
bs_frame_mark();
|
|
|
{
|
|
|
TetrahedronVolume volume = Triangulation::tetrahedralize(positions);
|
|
|
|
|
|
- // Generate matrices
|
|
|
- UINT32 numOutputTets = (UINT32)volume.tetrahedra.size();
|
|
|
- if (includeOuterFaces)
|
|
|
- numOutputTets += (UINT32)volume.outerFaces.size();
|
|
|
-
|
|
|
- output.reserve(includeOuterFaces);
|
|
|
-
|
|
|
- // Insert inner tetrahedrons, generate matrices
|
|
|
- for(UINT32 i = 0; i < (UINT32)volume.tetrahedra.size(); ++i)
|
|
|
+ if (generateExtrapolationVolume)
|
|
|
{
|
|
|
- TetrahedronData entry;
|
|
|
- entry.volume = volume.tetrahedra[i];
|
|
|
-
|
|
|
- // Generate a matrix that can be used for calculating barycentric coordinates
|
|
|
- // To determine a point within a tetrahedron, using barycentric coordinates, we use:
|
|
|
- // P = (P1 - P4) * a + (P2 - P4) * b + (P3 - P4) * c + P4
|
|
|
- //
|
|
|
- // Where P1, P2, P3, P4 are the corners of the tetrahedron.
|
|
|
- //
|
|
|
- // Expanded for each coordinate this is:
|
|
|
- // x = (x1 - x4) * a + (x2 - x4) * b + (x3 - x4) * c + x4
|
|
|
- // y = (y1 - y4) * a + (y2 - y4) * b + (y3 - y4) * c + y4
|
|
|
- // z = (z1 - z4) * a + (z2 - z4) * b + (z3 - z4) * c + z4
|
|
|
- //
|
|
|
- // In matrix form this is:
|
|
|
- // a
|
|
|
- // P = [P1 - P4, P2 - P4, P3 - P4, P4] [b]
|
|
|
- // c
|
|
|
- // 1
|
|
|
- //
|
|
|
- // Solved for barycentric coordinates:
|
|
|
- // a
|
|
|
- // [b] = Minv * P
|
|
|
- // c
|
|
|
- // 1
|
|
|
- //
|
|
|
- // Where Minv is the inverse of the matrix above.
|
|
|
-
|
|
|
- const Vector3& P1 = positions[volume.tetrahedra[i].vertices[0]];
|
|
|
- const Vector3& P2 = positions[volume.tetrahedra[i].vertices[1]];
|
|
|
- const Vector3& P3 = positions[volume.tetrahedra[i].vertices[2]];
|
|
|
- const Vector3& P4 = positions[volume.tetrahedra[i].vertices[3]];
|
|
|
-
|
|
|
- Matrix4 mat;
|
|
|
- mat.setColumn(0, Vector4(P1 - P4, 0.0f));
|
|
|
- mat.setColumn(1, Vector4(P2 - P4, 0.0f));
|
|
|
- mat.setColumn(2, Vector4(P3 - P4, 0.0f));
|
|
|
- mat.setColumn(3, Vector4(P4, 1.0f));
|
|
|
-
|
|
|
- entry.transform = mat.inverse();
|
|
|
-
|
|
|
- output.push_back(entry);
|
|
|
- }
|
|
|
-
|
|
|
- if (includeOuterFaces)
|
|
|
- {
|
|
|
- // Put outer faces into the Tetrahedron structure, for convenience
|
|
|
- UINT32 outerFaceOffset = (UINT32)volume.tetrahedra.size();
|
|
|
- FrameVector<Tetrahedron> outerTetrahedrons;
|
|
|
-
|
|
|
- outerTetrahedrons.resize(volume.outerFaces.size());
|
|
|
-
|
|
|
- for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
|
|
|
- {
|
|
|
- Tetrahedron outerTetrahedron;
|
|
|
- memcpy(outerTetrahedron.vertices, volume.outerFaces[i].vertices, sizeof(INT32) * 3);
|
|
|
- memset(outerTetrahedron.neighbors, -1, sizeof(INT32) * 3);
|
|
|
-
|
|
|
- outerTetrahedron.vertices[4] = -1; // Marks the tetrahedron as an outer face
|
|
|
- outerTetrahedron.neighbors[4] = volume.outerFaces[i].tetrahedron;
|
|
|
-
|
|
|
- outerTetrahedrons[i] = outerTetrahedron;
|
|
|
- }
|
|
|
-
|
|
|
- // Connect boundary tetrahedrons with these new outer tetrahedrons
|
|
|
- for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
|
|
|
- {
|
|
|
- Tetrahedron& tet = volume.tetrahedra[volume.outerFaces[i].tetrahedron];
|
|
|
- for (UINT32 j = 0; j < 4; j++)
|
|
|
- {
|
|
|
- if (tet.neighbors[j] == -1)
|
|
|
- tet.neighbors[j] = outerFaceOffset + i;
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- // Make a map between outer edges and faces, used in the following algorithms
|
|
|
+ // We don't want ot handle the case where the user looks up a position and it falls outside of the
|
|
|
+ // tetrahedron volume, as the math for projecting the point onto the volume might be too slow for the
|
|
|
+ // shader (which we need in order not to have a sharp cutoff in lighting where the volume ends). Therefore
|
|
|
+ // we extend the tetrahedron volume to "infinity" (technically to some far away distance, but we treat it as
|
|
|
+ // infinity when calculating barycentric coordinates) by adding new points along the outer face normals.
|
|
|
+ UINT32 numOuterFaces = (UINT32)volume.outerFaces.size();
|
|
|
+
|
|
|
+ // Calculate face normals for outer faces
|
|
|
+ //// Make an edge map
|
|
|
struct Edge
|
|
|
{
|
|
|
INT32 faces[2];
|
|
|
@@ -332,7 +284,7 @@ namespace bs { namespace ct
|
|
|
};
|
|
|
|
|
|
FrameUnorderedMap<std::pair<INT32, INT32>, Edge, pair_hash> edgeMap;
|
|
|
- for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
|
|
|
+ for (UINT32 i = 0; i < numOuterFaces; ++i)
|
|
|
{
|
|
|
for (UINT32 j = 0; j < 3; ++j)
|
|
|
{
|
|
|
@@ -360,19 +312,7 @@ namespace bs { namespace ct
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- // Form connections between outer tetrahedrons
|
|
|
- for (auto& entry : edgeMap)
|
|
|
- {
|
|
|
- const Edge& edge = entry.second;
|
|
|
-
|
|
|
- Tetrahedron& tet0 = outerTetrahedrons[outerFaceOffset + edge.faces[0]];
|
|
|
- tet0.neighbors[edge.oppositeVerts[0]] = outerFaceOffset + edge.faces[1];
|
|
|
-
|
|
|
- Tetrahedron& tet1 = outerTetrahedrons[outerFaceOffset + edge.faces[1]];
|
|
|
- tet1.neighbors[edge.oppositeVerts[1]] = outerFaceOffset + edge.faces[0];
|
|
|
- }
|
|
|
-
|
|
|
- // Generate face normals
|
|
|
+ //// Generate face normals
|
|
|
FrameVector<Vector3> faceNormals(volume.outerFaces.size());
|
|
|
for (UINT32 i = 0; i < (UINT32)volume.outerFaces.size(); ++i)
|
|
|
{
|
|
|
@@ -386,23 +326,23 @@ namespace bs { namespace ct
|
|
|
faceNormals[i] = Vector3::normalize(e1.cross(e0));
|
|
|
}
|
|
|
|
|
|
- // Generate vertex normals
|
|
|
- struct VertexAccum
|
|
|
+ //// Generate vertex normals
|
|
|
+ struct FaceVertex
|
|
|
{
|
|
|
- Vector3 sum;
|
|
|
- float weight;
|
|
|
+ Vector3 normal = Vector3::ZERO;
|
|
|
+ UINT32 outerIdx = -1;
|
|
|
};
|
|
|
|
|
|
- FrameUnorderedMap<INT32, Vector3> vertexNormals;
|
|
|
+ FrameUnorderedMap<INT32, FaceVertex> faceVertices;
|
|
|
for (auto& entry : edgeMap)
|
|
|
{
|
|
|
const Edge& edge = entry.second;
|
|
|
|
|
|
auto accumulateNormalForEdgeVertex = [&](UINT32 v0Idx, UINT32 v1Idx)
|
|
|
{
|
|
|
- auto iter = vertexNormals.insert(std::make_pair(v0Idx, Vector3(BsZero)));
|
|
|
+ auto iter = faceVertices.insert(std::make_pair(v0Idx, FaceVertex()));
|
|
|
|
|
|
- Vector3& accum = iter.first->second;
|
|
|
+ FaceVertex& accum = iter.first->second;
|
|
|
const Vector3& v0 = positions[v0Idx];
|
|
|
|
|
|
auto accumulateNormalForFace = [&](INT32 faceIdx, INT32 v2LocIdx)
|
|
|
@@ -418,7 +358,7 @@ namespace bs { namespace ct
|
|
|
Vector3 e1 = Vector3::normalize(v2 - v0);
|
|
|
|
|
|
float weight = acos(e0.dot(e1));
|
|
|
- accum += weight * faceNormals[faceIdx];
|
|
|
+ accum.normal += weight * faceNormals[faceIdx];
|
|
|
};
|
|
|
|
|
|
accumulateNormalForFace(edge.faces[0], entry.second.oppositeVerts[0]);
|
|
|
@@ -429,150 +369,159 @@ namespace bs { namespace ct
|
|
|
accumulateNormalForEdgeVertex(entry.first.second, entry.first.first);
|
|
|
}
|
|
|
|
|
|
- for (auto& entry : vertexNormals)
|
|
|
- entry.second.normalize();
|
|
|
+ for (auto& entry : faceVertices)
|
|
|
+ entry.second.normal.normalize();
|
|
|
|
|
|
- // Insert outer tetrahedrons, generate matrices
|
|
|
- for(UINT32 i = 0; i < (UINT32)outerTetrahedrons.size(); ++i)
|
|
|
+ // For each face vertex, generate an outer vertex along its normal
|
|
|
+ static const float ExtrapolationDistance = 1000.0f;
|
|
|
+ for(auto& entry : faceVertices)
|
|
|
{
|
|
|
- TetrahedronData entry;
|
|
|
- entry.volume = outerTetrahedrons[i];
|
|
|
-
|
|
|
- // We need a way to project a point outside the tetrahedron volume onto an outer face, then calculate
|
|
|
- // triangle's barycentric coordinates. Use use the per-vertex normals to extrude the triangle face into
|
|
|
- // infinity.
|
|
|
-
|
|
|
- // Our point can be represented as:
|
|
|
- // p == a (p0 + t*v0) + b (p1 + t*v1) + c (p2 + t*v2)
|
|
|
- //
|
|
|
- // where a, b and c are barycentric coordinates,
|
|
|
- // p0, p1, p2 are the corners of the face
|
|
|
- // v0, v1, v2 are the vertex normals, per corner
|
|
|
- // t is the distance from the triangle to the point
|
|
|
- //
|
|
|
- // Essentially we're calculating the corners of a bigger triangle that's "t" units away from the
|
|
|
- // face, and its corners lie along the per-vertex normals. Point "p" will lie on that triangle, for which
|
|
|
- // we can then calculate barycentric coordinates normally.
|
|
|
- //
|
|
|
- // First we substitute: c = 1 - a - b
|
|
|
- // p == a (p0 + t v0) + b (p1 + t v1) + (1 - a - b) (p2 + t v2)
|
|
|
- // p == a (p0 + t v0) + b (p1 + t v1) + (p2 + t v2) - a (p2 + t v2) - b (p2 + t v2)
|
|
|
- // p == a (p0 - p2 + t v0 - t v2) + b (p1 - p2 + t v1 - t v2) + (p2 + t v2)
|
|
|
- //
|
|
|
- // And move everything to one side:
|
|
|
- // p - p2 - t v2 == a (p0 - p2 + t ( v0 - v2)) + b (p1 - p2 + t ( v1 - v2))
|
|
|
- // a (p0 - p2 + t ( v0 - v2)) + b (p1 - p2 + t ( v1 - v2)) - (p - p2 - t v2) == 0
|
|
|
- //
|
|
|
- // We rewrite it using:
|
|
|
- // Ap = p0 - p2
|
|
|
- // Av = v0 - v2
|
|
|
- // Bp = p1 - p2
|
|
|
- // Bv = v1 - v2
|
|
|
- // Cp = p - p2
|
|
|
- // Cv = -v2
|
|
|
- //
|
|
|
- // Which yields:
|
|
|
- // a (Ap + t Av) + b (Bp + t Bv) - (Cp + t Cv) == 0
|
|
|
- //
|
|
|
- // Which can be written in matrix form:
|
|
|
- //
|
|
|
- // M = {Ap + t Av, Bp + t Bv, Cp + t Cv}
|
|
|
- // a 0
|
|
|
- // M * [ b ] = [0]
|
|
|
- // -1 0
|
|
|
- //
|
|
|
- // From that we can tell that matrix M cannot be inverted, because if we multiply the zero vector with the
|
|
|
- // inverted matrix the result would be zero, and not [a, b, -1]. Since the matrix cannot be inverted
|
|
|
- // det(M) == 0.
|
|
|
- //
|
|
|
- // We can use that fact to calculate "t". After we have "t" we can calculate barycentric coordinates
|
|
|
- // normally.
|
|
|
- //
|
|
|
- // Solving equation det(M) == 0 yields a cubic in form:
|
|
|
- // p t^3 + q t^2 + r t + s = 0
|
|
|
- //
|
|
|
- // We'll convert this to monic form, by dividing by p:
|
|
|
- // t^3 + q/p t^2 + r/p t + s/p = 0
|
|
|
- //
|
|
|
- // Or if p ends up being zero, we end up with a quadratic instead:
|
|
|
- // q t^2 + r t + s = 0
|
|
|
- //
|
|
|
- // We want to create a matrix that when multiplied with the position, yields us the three coefficients,
|
|
|
- // which we can then use to solve for "t". For this we create a 4x3 matrix, where each row represents
|
|
|
- // a solution for one of the coefficients. We factor contributons to each coefficient whether they depend on
|
|
|
- // position x, y, z, or don't depend on position (row columns, in that order respectively).
|
|
|
-
|
|
|
- const Vector3& p0 = positions[entry.volume.vertices[0]];
|
|
|
- const Vector3& p1 = positions[entry.volume.vertices[1]];
|
|
|
- const Vector3& p2 = positions[entry.volume.vertices[2]];
|
|
|
-
|
|
|
- const Vector3& v0 = vertexNormals[entry.volume.vertices[0]];
|
|
|
- const Vector3& v1 = vertexNormals[entry.volume.vertices[1]];
|
|
|
- const Vector3& v2 = vertexNormals[entry.volume.vertices[2]];
|
|
|
-
|
|
|
- float p =
|
|
|
- v2.x * v1.y * v0.z -
|
|
|
- v1.x * v2.y * v0.z -
|
|
|
- v2.x * v0.y * v1.z +
|
|
|
- v0.x * v2.y * v1.z +
|
|
|
- v1.x * v0.y * v2.z -
|
|
|
- v0.x * v1.y * v2.z;
|
|
|
-
|
|
|
- float qx = -v1.y * v0.z + v2.y * v0.z + v0.y * v1.z - v2.y * v1.z - v0.y * v2.z + v1.y * v2.z;
|
|
|
- float qy = v1.x * v0.z - v2.x * v0.z - v0.x * v1.z + v2.x * v1.z + v0.x * v2.z - v1.x * v2.z;
|
|
|
- float qz = -v1.x * v0.y + v2.x * v0.y + v0.x * v1.y - v2.x * v1.y - v0.x * v2.y + v1.x * v2.y;
|
|
|
- float qw = v2.y * v1.z * p0.x - v1.y * v2.z * p0.x - v2.y * v0.z * p1.x + v0.y * v2.z * p1.x +
|
|
|
- v1.y * v0.z * p2.x - v0.y * v1.z * p2.x - v2.x * v1.z * p0.y + v1.x * v2.z * p0.y +
|
|
|
- v2.x * v0.z * p1.y - v0.x * v2.z * p1.y - v1.x * v0.z * p2.y + v0.x * v1.z * p2.y +
|
|
|
- v2.x * v1.y * p0.z - v1.x * v2.y * p0.z - v2.x * v0.y * p1.z + v0.x * v2.y * p1.z +
|
|
|
- v1.x * v0.y * p2.z - v0.x * v1.y * p2.z;
|
|
|
-
|
|
|
- float rx = v1.z * p0.y - v2.z * p0.y - v0.z * p1.y + v2.z * p1.y + v0.z * p2.y - v1.z * p2.y -
|
|
|
- v1.y * p0.z + v2.y * p0.z + v0.y * p1.z - v2.y * p1.z - v0.y * p2.z + v1.y * p2.z;
|
|
|
- float ry = -v1.z * p0.x + v2.z * p0.x + v0.z * p1.x - v2.z * p1.x - v0.z * p2.x + v1.z * p2.x +
|
|
|
- v1.x * p0.z - v2.x * p0.z - v0.x * p1.z + v2.x * p1.z + v0.x * p2.z - v1.x * p2.z;
|
|
|
- float rz = v1.y * p0.x - v2.y * p0.x - v0.y * p1.x + v2.y * p1.x + v0.y * p2.x - v1.y * p2.x -
|
|
|
- v1.x * p0.y + v2.x * p0.y + v0.x * p1.y - v2.x * p1.y - v0.x * p2.y + v1.x * p2.y;
|
|
|
- float rw = v2.z * p1.x * p0.y - v1.z * p2.x * p0.y - v2.z * p0.x * p1.y + v0.z * p2.x * p1.y +
|
|
|
- v1.z * p0.x * p2.y - v0.z * p1.x * p2.y - v2.y * p1.x * p0.z + v1.y * p2.x * p0.z +
|
|
|
- v2.x * p1.y * p0.z - v1.x * p2.y * p0.z + v2.y * p0.x * p1.z - v0.y * p2.x * p1.z -
|
|
|
- v2.x * p0.y * p1.z + v0.x * p2.y * p1.z - v1.y * p0.x * p2.z + v0.y * p1.x * p2.z +
|
|
|
- v1.x * p0.y * p2.z - v0.x * p1.y * p2.z;
|
|
|
-
|
|
|
- float sx = -p1.y * p0.z + p2.y * p0.z + p0.y * p1.z - p2.y * p1.z - p0.y * p2.z + p1.y * p2.z;
|
|
|
- float sy = p1.x * p0.z - p2.x * p0.z - p0.x * p1.z + p2.x * p1.z + p0.x * p2.z - p1.x * p2.z;
|
|
|
- float sz = -p1.x * p0.y + p2.x * p0.y + p0.x * p1.y - p2.x * p1.y - p0.x * p2.y + p1.x * p2.y;
|
|
|
- float sw = p2.x * p1.y * p0.z - p1.x * p2.y * p0.z - p2.x * p0.y * p1.z +
|
|
|
- p0.x * p2.y * p1.z + p1.x * p0.y * p2.z - p0.x * p1.y * p2.z;
|
|
|
-
|
|
|
- entry.transform[0][0] = qx;
|
|
|
- entry.transform[0][1] = qy;
|
|
|
- entry.transform[0][2] = qz;
|
|
|
- entry.transform[0][3] = qw;
|
|
|
-
|
|
|
- entry.transform[1][0] = rx;
|
|
|
- entry.transform[1][1] = ry;
|
|
|
- entry.transform[1][2] = rz;
|
|
|
- entry.transform[1][3] = rw;
|
|
|
-
|
|
|
- entry.transform[2][0] = sx;
|
|
|
- entry.transform[2][1] = sy;
|
|
|
- entry.transform[2][2] = sz;
|
|
|
- entry.transform[2][3] = sw;
|
|
|
-
|
|
|
- // Unused
|
|
|
- entry.transform[3][0] = 0.0f;
|
|
|
- entry.transform[3][1] = 0.0f;
|
|
|
- entry.transform[3][2] = 0.0f;
|
|
|
- entry.transform[3][3] = 0.0f;
|
|
|
-
|
|
|
- if (fabs(p) > 0.00001f)
|
|
|
- entry.transform = entry.transform * (1.0f / p);
|
|
|
- else // Quadratic
|
|
|
- entry.volume.neighbors[3] = -2;
|
|
|
-
|
|
|
- output.push_back(entry);
|
|
|
+ entry.second.outerIdx = (UINT32)positions.size();
|
|
|
+
|
|
|
+ Vector3 outerPos = positions[entry.first] + entry.second.normal * ExtrapolationDistance;
|
|
|
+ positions.push_back(outerPos);
|
|
|
}
|
|
|
+
|
|
|
+ // For each face, generate outer tetrahedrons
|
|
|
+ Vector<Vector3> outerVolumeVerts;
|
|
|
+ FrameVector<TetrahedronVolume> outerTetrahedra;
|
|
|
+ for (UINT32 i = 0; i < numOuterFaces; ++i)
|
|
|
+ {
|
|
|
+ const TetrahedronFace& face = volume.outerFaces[i];
|
|
|
+ UINT32 originalIndices[6];
|
|
|
+ for (UINT32 j = 0; j < 3; j++)
|
|
|
+ {
|
|
|
+ const FaceVertex& faceVertex = faceVertices[face.vertices[j]];
|
|
|
+
|
|
|
+ outerVolumeVerts.push_back(positions[face.vertices[j]]);
|
|
|
+ outerVolumeVerts.push_back(positions[faceVertex.outerIdx]);
|
|
|
+
|
|
|
+ originalIndices[j * 2 + 0] = face.vertices[j];
|
|
|
+ originalIndices[j * 2 + 1] = faceVertex.outerIdx;
|
|
|
+ }
|
|
|
+
|
|
|
+ TetrahedronVolume outerVolume = Triangulation::tetrahedralize(outerVolumeVerts);
|
|
|
+
|
|
|
+ UINT32 tetStartIdx = (UINT32)volume.tetrahedra.size();
|
|
|
+ for (auto& entry : outerVolume.tetrahedra)
|
|
|
+ {
|
|
|
+ // Remap vertices back to global array
|
|
|
+ for (UINT32 j = 0; j < 4; j++)
|
|
|
+ entry.vertices[j] = originalIndices[entry.vertices[j]];
|
|
|
+
|
|
|
+ // Remap neighbors to global array
|
|
|
+ for (UINT32 j = 0; j < 4; j++)
|
|
|
+ {
|
|
|
+ if (entry.neighbors[j] != -1)
|
|
|
+ {
|
|
|
+ // Valid neighbor, map to global array
|
|
|
+ entry.neighbors[j] += tetStartIdx;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Connect the new volume to the original face
|
|
|
+ for (auto& entry : outerVolume.outerFaces)
|
|
|
+ {
|
|
|
+ bool isValid = true;
|
|
|
+ for (UINT32 j = 0; j < 3; j++)
|
|
|
+ {
|
|
|
+ if (entry.vertices[j] % 2 == 1)
|
|
|
+ {
|
|
|
+ isValid = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!isValid)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ Tetrahedron& outerTet = outerVolume.tetrahedra[entry.tetrahedron];
|
|
|
+
|
|
|
+ UINT32 oppositeVert = -1;
|
|
|
+ for (UINT32 j = 0; j < 4; j++)
|
|
|
+ {
|
|
|
+ if(outerTet.vertices[j] != entry.vertices[0] &&
|
|
|
+ outerTet.vertices[j] != entry.vertices[1] &&
|
|
|
+ outerTet.vertices[j] != entry.vertices[2])
|
|
|
+ {
|
|
|
+ oppositeVert = j;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ assert(outerTet.neighbors[oppositeVert] == -1);
|
|
|
+ outerTet.neighbors[oppositeVert] = face.tetrahedron;
|
|
|
+
|
|
|
+ Tetrahedron& innerTet = volume.tetrahedra[face.tetrahedron];
|
|
|
+ for(UINT32 j = 0; j < 4; j++)
|
|
|
+ {
|
|
|
+ if (innerTet.neighbors[j] == -1)
|
|
|
+ innerTet.neighbors[j] = entry.tetrahedron;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ outerTetrahedra.push_back(outerVolume);
|
|
|
+ outerVolumeVerts.clear();
|
|
|
+
|
|
|
+ // Add to global tetrahedra array
|
|
|
+ for (auto& entry : outerVolume.tetrahedra)
|
|
|
+ volume.tetrahedra.push_back(entry);
|
|
|
+ }
|
|
|
+
|
|
|
+ // Note: Not forming neighbor connections between outer tetrahedrons. Since we generate them separately
|
|
|
+ // we can't guarantee they even exist.
|
|
|
+ }
|
|
|
+
|
|
|
+ // Generate matrices
|
|
|
+ UINT32 numOutputTets = (UINT32)volume.tetrahedra.size();
|
|
|
+ output.reserve(numOutputTets);
|
|
|
+
|
|
|
+ // Insert tetrahedrons, generate matrices
|
|
|
+ for(UINT32 i = 0; i < (UINT32)volume.tetrahedra.size(); ++i)
|
|
|
+ {
|
|
|
+ TetrahedronData entry;
|
|
|
+ entry.volume = volume.tetrahedra[i];
|
|
|
+
|
|
|
+ // Generate a matrix that can be used for calculating barycentric coordinates
|
|
|
+ // To determine a point within a tetrahedron, using barycentric coordinates, we use:
|
|
|
+ // P = (P1 - P4) * a + (P2 - P4) * b + (P3 - P4) * c + P4
|
|
|
+ //
|
|
|
+ // Where P1, P2, P3, P4 are the corners of the tetrahedron.
|
|
|
+ //
|
|
|
+ // Expanded for each coordinate this is:
|
|
|
+ // x = (x1 - x4) * a + (x2 - x4) * b + (x3 - x4) * c + x4
|
|
|
+ // y = (y1 - y4) * a + (y2 - y4) * b + (y3 - y4) * c + y4
|
|
|
+ // z = (z1 - z4) * a + (z2 - z4) * b + (z3 - z4) * c + z4
|
|
|
+ //
|
|
|
+ // In matrix form this is:
|
|
|
+ // a
|
|
|
+ // P = [P1 - P4, P2 - P4, P3 - P4, P4] [b]
|
|
|
+ // c
|
|
|
+ // 1
|
|
|
+ //
|
|
|
+ // Solved for barycentric coordinates:
|
|
|
+ // a
|
|
|
+ // [b] = Minv * P
|
|
|
+ // c
|
|
|
+ // 1
|
|
|
+ //
|
|
|
+ // Where Minv is the inverse of the matrix above.
|
|
|
+
|
|
|
+ const Vector3& P1 = positions[volume.tetrahedra[i].vertices[0]];
|
|
|
+ const Vector3& P2 = positions[volume.tetrahedra[i].vertices[1]];
|
|
|
+ const Vector3& P3 = positions[volume.tetrahedra[i].vertices[2]];
|
|
|
+ const Vector3& P4 = positions[volume.tetrahedra[i].vertices[3]];
|
|
|
+
|
|
|
+ Matrix4 mat;
|
|
|
+ mat.setColumn(0, Vector4(P1 - P4, 0.0f));
|
|
|
+ mat.setColumn(1, Vector4(P2 - P4, 0.0f));
|
|
|
+ mat.setColumn(2, Vector4(P3 - P4, 0.0f));
|
|
|
+ mat.setColumn(3, Vector4(P4, 1.0f));
|
|
|
+
|
|
|
+ entry.transform = mat.inverse();
|
|
|
+
|
|
|
+ output.push_back(entry);
|
|
|
}
|
|
|
}
|
|
|
bs_frame_clear();
|