mirror of
https://github.com/copyrighttxt/watrbx-game-engine.git
synced 2026-09-04 20:57:49 +00:00
1228 lines
41 KiB
C++
1228 lines
41 KiB
C++
#include "stdafx.h"
|
|
#include "FastCluster.h"
|
|
|
|
#include "humanoid/Humanoid.h"
|
|
|
|
#include "GfxBase/AsyncResult.h"
|
|
#include "GfxBase/PartIdentifier.h"
|
|
#include "v8datamodel/Decal.h"
|
|
#include "v8datamodel/Accoutrement.h"
|
|
#include "v8datamodel/PartCookie.h"
|
|
|
|
#include "v8world/Clump.h"
|
|
#include "Util/Math.h"
|
|
|
|
#include "SceneManager.h"
|
|
#include "GeometryGenerator.h"
|
|
#include "MaterialGenerator.h"
|
|
#include "Util.h"
|
|
#include "SceneUpdater.h"
|
|
#include "Material.h"
|
|
|
|
#include "GfxBase/RenderCaps.h"
|
|
#include "GfxBase/FrameRateManager.h"
|
|
|
|
#include "SuperCluster.h"
|
|
|
|
#include "VisualEngine.h"
|
|
|
|
#include "rbx/Profiler.h"
|
|
|
|
LOGVARIABLE(RenderFastCluster, 0)
|
|
|
|
namespace RBX
|
|
{
|
|
namespace Graphics
|
|
{
|
|
|
|
// This should be adjusted so that this number + worst-case number of vertices for any single part <= 65536 (16-bit index buffer limit)
|
|
const unsigned int kFastClusterBatchMaxVertices = 32768;
|
|
|
|
// This should be equal to the maximum number of vertices we can draw in a single call
|
|
const unsigned int kFastClusterBatchGroupMaxVertices = 65535;
|
|
|
|
class FastClusterMeshGenerator
|
|
{
|
|
public:
|
|
FastClusterMeshGenerator(VisualEngine* visualEngine, RBX::Humanoid* humanoid, unsigned int maxBones, bool isFW)
|
|
: visualEngine(visualEngine)
|
|
, humanoidIdentifier(humanoid)
|
|
, maxBonesPerBatch(0)
|
|
{
|
|
bones.reserve(maxBones);
|
|
|
|
if (isFW || visualEngine->getRenderCaps()->getSkinningBoneCount() == 0)
|
|
{
|
|
maxBonesPerBatch = 1;
|
|
}
|
|
else
|
|
{
|
|
maxBonesPerBatch = visualEngine->getRenderCaps()->getSkinningBoneCount();
|
|
}
|
|
|
|
colorOrderBGR = visualEngine->getDevice()->getCaps().colorOrderBGR;
|
|
}
|
|
|
|
~FastClusterMeshGenerator()
|
|
{
|
|
visualEngine->getMaterialGenerator()->invalidateCompositCache();
|
|
}
|
|
|
|
const HumanoidIdentifier& getHumanoidIdentifier()
|
|
{
|
|
return humanoidIdentifier;
|
|
}
|
|
|
|
void addBone(RBX::PartInstance* root)
|
|
{
|
|
Bone bone;
|
|
bone.root = root;
|
|
bone.boundsMin = Vector3::maxFinite();
|
|
bone.boundsMax = Vector3::minFinite();
|
|
|
|
bones.push_back(bone);
|
|
}
|
|
|
|
void addInstance(size_t boneIndex, RBX::PartInstance* part, RBX::Decal* decal, unsigned int materialFlags, RenderQueue::Id renderQueue, RBX::AsyncResult* asyncResult)
|
|
{
|
|
RBXASSERT(boneIndex < bones.size());
|
|
|
|
const HumanoidIdentifier* hi = humanoidIdentifier.humanoid ? &humanoidIdentifier : NULL;
|
|
|
|
MaterialGenerator::Result material = visualEngine->getMaterialGenerator()->createMaterial(part, decal, hi, materialFlags);
|
|
|
|
if (!material.material)
|
|
return;
|
|
|
|
MaterialGroup& mg = materialGroups[std::make_pair(material.material.get(), renderQueue)];
|
|
|
|
if (!mg.material)
|
|
{
|
|
mg.material = material.material;
|
|
mg.renderQueue = renderQueue;
|
|
mg.materialResultFlags = material.flags;
|
|
mg.materialFeatures = material.features;
|
|
|
|
if (decal)
|
|
{
|
|
unsigned int opaqueMaterialFlags = materialFlags & ~MaterialGenerator::Flag_Transparent;
|
|
|
|
mg.decalMaterialOpaque = visualEngine->getMaterialGenerator()->createMaterial(part, decal, hi, opaqueMaterialFlags).material;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
RBXASSERT(mg.materialResultFlags == material.flags);
|
|
}
|
|
|
|
// force a batch break on transparent objects so that transparency sorting works
|
|
// don't do this if the geometry is for the same part as the last one - this means a decal on different face,
|
|
// decals on different faces of the same part never have transparency ordering issues
|
|
bool forceBatchBreak = renderQueue == RenderQueue::Id_Transparent && part != getLastPart(mg);
|
|
|
|
// create new batch on vertex count overflows
|
|
if (mg.batches.empty() || mg.batches.back().counter.getVertexCount() >= kFastClusterBatchMaxVertices || forceBatchBreak)
|
|
{
|
|
mg.batches.push_back(Batch());
|
|
}
|
|
else
|
|
{
|
|
// create new batch on bone count overflow
|
|
const Batch& batch = mg.batches.back();
|
|
|
|
if (batch.bones.size() >= maxBonesPerBatch && batch.bones.back() != boneIndex)
|
|
{
|
|
mg.batches.push_back(Batch());
|
|
}
|
|
}
|
|
|
|
Batch& batch = mg.batches.back();
|
|
|
|
// fetch any resources the part might need
|
|
unsigned int resourceFlags = ((materialFlags & MaterialGenerator::Flag_UseCompositTexture) || (hi && hi->isPartHead(part)))
|
|
? GeometryGenerator::Resource_SubstituteBodyParts
|
|
: 0;
|
|
|
|
GeometryGenerator::Resources resources = GeometryGenerator::fetchResources(part, hi, resourceFlags, asyncResult);
|
|
|
|
// accumulate geometry counters in the batch; actual geometry generation is done in finalize()
|
|
GeometryGenerator::Options options;
|
|
|
|
batch.counter.addInstance(part, decal, options, resources);
|
|
|
|
// add new bone if needed
|
|
if (batch.bones.empty() || batch.bones.back() != boneIndex)
|
|
{
|
|
RBXASSERT(batch.bones.size() < maxBonesPerBatch);
|
|
|
|
batch.bones.push_back(boneIndex);
|
|
}
|
|
|
|
// add batch instance
|
|
BatchInstance bi = {part, decal, batch.bones.size() - 1, material.uvOffsetScale, resources};
|
|
|
|
batch.instances.push_back(bi);
|
|
}
|
|
|
|
unsigned int finalize(FastCluster* cluster, FastClusterSharedGeometry& sharedGeometry)
|
|
{
|
|
GeometryGenerator::Vertex* sharedVertexData = NULL;
|
|
unsigned short* sharedIndexData = NULL;
|
|
unsigned int sharedVertexOffset = 0;
|
|
unsigned int sharedIndexOffset = 0;
|
|
|
|
// Gather pointers to all batches
|
|
std::vector<std::pair<MaterialGroup*, Batch*> > batches;
|
|
|
|
for (MaterialGroupMap::iterator it = materialGroups.begin(); it != materialGroups.end(); ++it)
|
|
{
|
|
MaterialGroup& mg = it->second;
|
|
|
|
for (MaterialGroup::BatchList::iterator bit = mg.batches.begin(); bit != mg.batches.end(); ++bit)
|
|
{
|
|
Batch& batch = *bit;
|
|
|
|
if (batch.instances.empty() || batch.counter.getVertexCount() == 0 || batch.counter.getIndexCount() == 0)
|
|
continue;
|
|
|
|
batches.push_back(std::make_pair(&mg, &batch));
|
|
}
|
|
}
|
|
|
|
// Sort batches to make sure batches that can be merged go first
|
|
std::sort(batches.begin(), batches.end(), BatchMaterialPlasticLODComparator());
|
|
|
|
// Get total size of the shared geometry data
|
|
unsigned int sharedVertexCount = 0;
|
|
unsigned int sharedIndexCount = 0;
|
|
|
|
for (size_t i = 0; i < batches.size(); ++i)
|
|
{
|
|
Batch& batch = *batches[i].second;
|
|
|
|
sharedVertexCount += batch.counter.getVertexCount();
|
|
sharedIndexCount += batch.counter.getIndexCount();
|
|
}
|
|
|
|
// Update shared geometry so that it has the required amount of memory
|
|
if (sharedVertexCount > 0 && sharedIndexCount > 0)
|
|
{
|
|
setupSharedGeometry(sharedGeometry, sharedVertexCount, sharedIndexCount, cluster->isFW());
|
|
|
|
sharedVertexData = static_cast<GeometryGenerator::Vertex*>(sharedGeometry.vertexBuffer->lock(GeometryBuffer::Lock_Discard));
|
|
sharedIndexData = static_cast<unsigned short*>(sharedGeometry.indexBuffer->lock(GeometryBuffer::Lock_Discard));
|
|
}
|
|
else
|
|
{
|
|
sharedGeometry.reset();
|
|
}
|
|
|
|
// Create as few geometry objects as we can
|
|
shared_ptr<Geometry> geometry;
|
|
unsigned int geometryBaseVertex = 0;
|
|
|
|
// Create entities in groups
|
|
for (size_t groupBegin = 0; groupBegin < batches.size(); )
|
|
{
|
|
unsigned int groupEnd = groupBegin + 1;
|
|
|
|
unsigned int groupVertexCount = batches[groupBegin].second->counter.getVertexCount();
|
|
|
|
unsigned int groupVertexOffset = sharedVertexOffset;
|
|
unsigned int groupIndexOffset = sharedIndexOffset;
|
|
Extents groupBounds;
|
|
|
|
// Reuse geometry if generated indices stay within 16-bit boundaries
|
|
if (!geometry || groupVertexOffset + groupVertexCount - geometryBaseVertex > kFastClusterBatchGroupMaxVertices)
|
|
{
|
|
geometry = visualEngine->getDevice()->createGeometry(getVertexLayout(), sharedGeometry.vertexBuffer, sharedGeometry.indexBuffer, groupVertexOffset);
|
|
geometryBaseVertex = groupVertexOffset;
|
|
}
|
|
|
|
// Generate geometry for high LOD
|
|
for (size_t bi = groupBegin; bi < groupEnd; ++bi)
|
|
{
|
|
const MaterialGroup& mg = *batches[bi].first;
|
|
const Batch& batch = *batches[bi].second;
|
|
|
|
unsigned int vertexOffset = sharedVertexOffset - geometryBaseVertex;
|
|
|
|
// generate geometry
|
|
std::vector<unsigned int> instanceVertexCount;
|
|
|
|
Extents bounds = generateBatchGeometry(mg, batch, sharedVertexData + geometryBaseVertex, sharedIndexData + sharedIndexOffset, vertexOffset, instanceVertexCount, cluster->isFW());
|
|
|
|
// create geometry batch
|
|
GeometryBatch geometryBatch(geometry, Geometry::Primitive_Triangles, sharedIndexOffset, batch.counter.getIndexCount(), vertexOffset, vertexOffset + batch.counter.getVertexCount());
|
|
|
|
// setup entity
|
|
unsigned char lodMask = (groupEnd - groupBegin > 1) ? ((1 << 0) | (1 << 1)) : 0xff;
|
|
|
|
// override render queue if character will cast shadow
|
|
RenderQueue::Id queueId = mg.renderQueue;
|
|
if (mg.renderQueue == RenderQueue::Id_Opaque && cluster->getHumanoidKey())
|
|
queueId = RenderQueue::Id_OpaqueCasters;
|
|
if (mg.renderQueue == RenderQueue::Id_Transparent && cluster->getHumanoidKey())
|
|
queueId = RenderQueue::Id_TransparentCasters;
|
|
|
|
cluster->addEntity(new FastClusterEntity(cluster, geometryBatch, mg.material, mg.decalMaterialOpaque, queueId, lodMask, batch.bones, bounds, mg.materialFeatures));
|
|
|
|
// update buffer offsets
|
|
sharedVertexOffset += batch.counter.getVertexCount();
|
|
sharedIndexOffset += batch.counter.getIndexCount();
|
|
|
|
groupBounds.expandToContain(bounds);
|
|
}
|
|
|
|
// Create merged entity for low LOD
|
|
if (groupEnd - groupBegin > 1)
|
|
{
|
|
const MaterialGroup& mg = *batches[groupBegin].first;
|
|
const Batch& batch = *batches[groupBegin].second;
|
|
|
|
// create geometry batch
|
|
GeometryBatch geometryBatch(geometry, Geometry::Primitive_Triangles, groupIndexOffset, sharedIndexOffset - groupIndexOffset, groupVertexOffset - geometryBaseVertex, sharedVertexOffset - geometryBaseVertex);
|
|
|
|
// setup entity
|
|
cluster->addEntity(new FastClusterEntity(cluster, geometryBatch, mg.material, mg.decalMaterialOpaque, mg.renderQueue, /* lodMask= */ 1 << 2, batch.bones, groupBounds, mg.materialFeatures));
|
|
}
|
|
|
|
// Move to next batch portion
|
|
groupBegin = groupEnd;
|
|
}
|
|
|
|
if (sharedVertexData && sharedIndexData)
|
|
{
|
|
RBXASSERT(sharedVertexOffset == sharedVertexCount && sharedIndexOffset == sharedIndexCount);
|
|
|
|
RBXPROFILER_SCOPE("Render", "upload");
|
|
|
|
sharedGeometry.vertexBuffer->unlock();
|
|
sharedGeometry.indexBuffer->unlock();
|
|
}
|
|
|
|
return sharedVertexCount;
|
|
}
|
|
|
|
unsigned int getBoneCount()
|
|
{
|
|
return bones.size();
|
|
}
|
|
|
|
RBX::PartInstance* getBoneRoot(unsigned int i)
|
|
{
|
|
return bones[i].root;
|
|
}
|
|
|
|
Extents getBoneBounds(unsigned int i)
|
|
{
|
|
const Bone& bone = bones[i];
|
|
|
|
return Extents(bone.boundsMin, bone.boundsMax);
|
|
}
|
|
|
|
private:
|
|
struct BatchInstance
|
|
{
|
|
RBX::PartInstance* part;
|
|
RBX::Decal* decal;
|
|
unsigned int localBoneIndex;
|
|
G3D::Vector4 uvOffsetScale;
|
|
GeometryGenerator::Resources resources;
|
|
};
|
|
|
|
struct Batch
|
|
{
|
|
GeometryGenerator counter;
|
|
std::vector<BatchInstance> instances;
|
|
std::vector<unsigned int> bones;
|
|
};
|
|
|
|
struct MaterialGroup
|
|
{
|
|
shared_ptr<Material> material;
|
|
RenderQueue::Id renderQueue;
|
|
|
|
shared_ptr<Material> decalMaterialOpaque;
|
|
unsigned int materialResultFlags;
|
|
unsigned int materialFeatures;
|
|
|
|
typedef std::list<Batch> BatchList;
|
|
BatchList batches;
|
|
};
|
|
|
|
struct Bone
|
|
{
|
|
RBX::PartInstance* root;
|
|
RBX::Vector3 boundsMin;
|
|
RBX::Vector3 boundsMax;
|
|
};
|
|
|
|
typedef std::map<std::pair<Material*, RenderQueue::Id>, MaterialGroup> MaterialGroupMap;
|
|
|
|
VisualEngine* visualEngine;
|
|
HumanoidIdentifier humanoidIdentifier;
|
|
|
|
unsigned int maxBonesPerBatch;
|
|
bool colorOrderBGR;
|
|
|
|
MaterialGroupMap materialGroups;
|
|
std::vector<Bone> bones;
|
|
|
|
RBX::PartInstance* getLastPart(const MaterialGroup& mg)
|
|
{
|
|
if (mg.batches.empty())
|
|
return NULL;
|
|
|
|
const Batch& batch = mg.batches.back();
|
|
|
|
if (batch.instances.empty())
|
|
return NULL;
|
|
|
|
return batch.instances.back().part;
|
|
}
|
|
|
|
const shared_ptr<VertexLayout>& getVertexLayout()
|
|
{
|
|
shared_ptr<VertexLayout>& p = visualEngine->getFastClusterLayout();
|
|
|
|
if (!p)
|
|
{
|
|
std::vector<VertexLayout::Element> elements;
|
|
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, pos), VertexLayout::Format_Float3, VertexLayout::Semantic_Position));
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, normal), VertexLayout::Format_Float3, VertexLayout::Semantic_Normal));
|
|
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, color), VertexLayout::Format_Color, VertexLayout::Semantic_Color, 0));
|
|
|
|
bool useShaders = visualEngine->getRenderCaps()->getSkinningBoneCount() > 0;
|
|
|
|
if (useShaders)
|
|
{
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, extra), VertexLayout::Format_UByte4, VertexLayout::Semantic_Color, 1));
|
|
}
|
|
else
|
|
{
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, extra), VertexLayout::Format_Color, VertexLayout::Semantic_Color, 1));
|
|
}
|
|
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, uv), VertexLayout::Format_Float2, VertexLayout::Semantic_Texture, 0));
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, uvStuds), VertexLayout::Format_Float2, VertexLayout::Semantic_Texture, 1));
|
|
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, tangent), VertexLayout::Format_Float3, VertexLayout::Semantic_Texture, 2));
|
|
elements.push_back(VertexLayout::Element(0, offsetof(GeometryGenerator::Vertex, edgeDistances), VertexLayout::Format_Float4, VertexLayout::Semantic_Texture, 3));
|
|
|
|
p = visualEngine->getDevice()->createVertexLayout(elements);
|
|
RBXASSERT(p);
|
|
}
|
|
|
|
return p;
|
|
}
|
|
|
|
bool canUseBuffer(unsigned int currentCount, unsigned int desiredCount)
|
|
{
|
|
// We can use the current buffer if it has enough data and it does not waste >10% of memory
|
|
return
|
|
desiredCount <= currentCount &&
|
|
currentCount - desiredCount < currentCount / 10;
|
|
}
|
|
|
|
void setupSharedGeometry(FastClusterSharedGeometry& sharedGeometry, unsigned int vertexCount, unsigned int indexCount, bool isFW)
|
|
{
|
|
// Create vertex buffer
|
|
if (!sharedGeometry.vertexBuffer || !canUseBuffer(sharedGeometry.vertexBuffer->getElementCount(), vertexCount))
|
|
{
|
|
sharedGeometry.vertexBuffer = visualEngine->getDevice()->createVertexBuffer(sizeof(GeometryGenerator::Vertex), vertexCount, GeometryBuffer::Usage_Static);
|
|
}
|
|
|
|
// Create index buffer
|
|
if (!sharedGeometry.indexBuffer || !canUseBuffer(sharedGeometry.indexBuffer->getElementCount(), indexCount))
|
|
{
|
|
sharedGeometry.indexBuffer = visualEngine->getDevice()->createIndexBuffer(sizeof(unsigned short), indexCount, GeometryBuffer::Usage_Static);
|
|
}
|
|
}
|
|
|
|
CoordinateFrame getRelativeTransform(PartInstance* part, PartInstance* root)
|
|
{
|
|
if (part == root)
|
|
{
|
|
static const CoordinateFrame identity;
|
|
|
|
return identity;
|
|
}
|
|
else if (root)
|
|
{
|
|
const CoordinateFrame& partFrame = part->getCoordinateFrame();
|
|
const CoordinateFrame& rootFrame = root->getCoordinateFrame();
|
|
|
|
return rootFrame.toObjectSpace(partFrame);
|
|
}
|
|
else
|
|
{
|
|
return part->getCoordinateFrame();
|
|
}
|
|
}
|
|
|
|
Extents generateBatchGeometry(const MaterialGroup& mg, const Batch& batch, GeometryGenerator::Vertex* vbptr, unsigned short* ibptr, unsigned int vertexOffset, std::vector<unsigned int>& instanceVertexCount, bool isFW)
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "generateBatchGeometry");
|
|
|
|
instanceVertexCount.reserve(batch.instances.size());
|
|
|
|
GeometryGenerator generator(vbptr, ibptr, vertexOffset);
|
|
|
|
Vector3 boundsMin = Vector3::maxFinite();
|
|
Vector3 boundsMax = Vector3::minFinite();
|
|
|
|
const HumanoidIdentifier* hi = humanoidIdentifier.humanoid ? &humanoidIdentifier : NULL;
|
|
|
|
for (size_t i = 0; i < batch.instances.size(); ++i)
|
|
{
|
|
const BatchInstance& bi = batch.instances[i];
|
|
Bone& bone = bones[batch.bones[bi.localBoneIndex]];
|
|
|
|
GeometryGenerator::Options options(visualEngine, *bi.part, bi.decal, getRelativeTransform(bi.part, bone.root), mg.materialResultFlags, bi.uvOffsetScale, bi.localBoneIndex);
|
|
|
|
generator.resetBounds();
|
|
|
|
size_t oldVertexCount = generator.getVertexCount();
|
|
|
|
generator.addInstance(bi.part, bi.decal, options, bi.resources, hi);
|
|
|
|
instanceVertexCount.push_back(generator.getVertexCount() - oldVertexCount);
|
|
|
|
if (generator.areBoundsValid())
|
|
{
|
|
RBX::AABox partBounds = generator.getBounds();
|
|
|
|
bone.boundsMin = bone.boundsMin.min(partBounds.low());
|
|
bone.boundsMax = bone.boundsMax.max(partBounds.high());
|
|
boundsMin = boundsMin.min(partBounds.low());
|
|
boundsMax = boundsMax.max(partBounds.high());
|
|
}
|
|
}
|
|
|
|
// Patch stud UVs for FFP
|
|
if (visualEngine->getDevice()->getCaps().supportsFFP && (mg.materialResultFlags & MaterialGenerator::Result_UsesTexture) == 0)
|
|
for (unsigned int i = vertexOffset; i < generator.getVertexCount(); ++i)
|
|
vbptr[i].uv = vbptr[i].uvStuds;
|
|
|
|
RBXASSERT(generator.getVertexCount() == batch.counter.getVertexCount() + vertexOffset && generator.getIndexCount() == batch.counter.getIndexCount());
|
|
|
|
return getBounds(boundsMin, boundsMax);
|
|
}
|
|
|
|
Extents getBounds(const Vector3& min, const Vector3& max)
|
|
{
|
|
if (min.x <= max.x)
|
|
return Extents(min, max);
|
|
else
|
|
return Extents();
|
|
}
|
|
|
|
struct BatchMaterialPlasticLODComparator
|
|
{
|
|
bool operator()(const std::pair<MaterialGroup*, Batch*>& lhs, const std::pair<MaterialGroup*, Batch*>& rhs) const
|
|
{
|
|
MaterialGroup* ml = lhs.first;
|
|
MaterialGroup* mr = rhs.first;
|
|
|
|
return (ml->materialResultFlags & MaterialGenerator::Result_PlasticLOD) > (mr->materialResultFlags & MaterialGenerator::Result_PlasticLOD);
|
|
|
|
}
|
|
};
|
|
};
|
|
|
|
struct PartBindingNullPredicate
|
|
{
|
|
template <typename T> bool operator()(const T& part) const
|
|
{
|
|
return part.binding == NULL;
|
|
}
|
|
};
|
|
|
|
struct PartClumpSinglePredicate
|
|
{
|
|
template <typename T> bool operator()(const T& part) const
|
|
{
|
|
const Primitive* p = part.instance->getConstPartPrimitive();
|
|
const Primitive* clumpRoot = p->getRoot<Primitive>();
|
|
|
|
return clumpRoot->numChildren() == 0;
|
|
}
|
|
};
|
|
|
|
struct PartClumpGroupPredicate
|
|
{
|
|
template <typename T> bool operator()(const T& lhs, const T& rhs) const
|
|
{
|
|
return lhs.instance->getClump() < rhs.instance->getClump();
|
|
}
|
|
};
|
|
|
|
FastClusterEntity::FastClusterEntity(FastCluster* cluster, const GeometryBatch& geometry, const shared_ptr<Material>& material, const shared_ptr<Material>& decalMaterialOpaque,
|
|
RenderQueue::Id renderQueueId, unsigned char lodMask, const std::vector<unsigned int>& bones, const Extents& localBounds, unsigned int extraFeatures)
|
|
: RenderEntity(cluster, geometry, material, renderQueueId, lodMask)
|
|
, decalMaterialOpaque(decalMaterialOpaque)
|
|
, extraFeatures(extraFeatures)
|
|
, bones(bones)
|
|
, localBoundsCenter(localBounds.center())
|
|
, sortKeyOffset(0)
|
|
{
|
|
if (decalMaterialOpaque)
|
|
{
|
|
if (const Technique* technique = decalMaterialOpaque->getBestTechnique(0, RenderQueue::Pass_Default))
|
|
decalTexture = technique->getTexture(MaterialGenerator::getDiffuseMapStage());
|
|
|
|
// if decal we want to offset slightly sort key
|
|
sortKeyOffset = -0.01f;
|
|
}
|
|
}
|
|
|
|
FastClusterEntity::~FastClusterEntity()
|
|
{
|
|
}
|
|
|
|
unsigned int FastClusterEntity::getWorldTransforms4x3(float* buffer, unsigned int maxTransforms, const void** cacheKey) const
|
|
{
|
|
if (useCache(cacheKey, this)) return 0;
|
|
|
|
RBXASSERT(bones.size() <= maxTransforms);
|
|
|
|
FastCluster* cluster = static_cast<FastCluster*>(node);
|
|
|
|
for (unsigned int i = 0; i < bones.size(); ++i)
|
|
{
|
|
const CoordinateFrame& cframe = cluster->getTransform(bones[i]);
|
|
|
|
memcpy(&buffer[0], cframe.rotation[0], sizeof(float) * 3);
|
|
buffer[3] = cframe.translation.x;
|
|
|
|
memcpy(&buffer[4], cframe.rotation[1], sizeof(float) * 3);
|
|
buffer[7] = cframe.translation.y;
|
|
|
|
memcpy(&buffer[8], cframe.rotation[2], sizeof(float) * 3);
|
|
buffer[11] = cframe.translation.z;
|
|
|
|
buffer += 12;
|
|
}
|
|
|
|
return bones.size();
|
|
}
|
|
|
|
void FastClusterEntity::updateRenderQueue(RenderQueue& queue, const RenderCamera& camera, unsigned int lodIndex, RenderQueue::Pass pass)
|
|
{
|
|
TextureRef::Status decalStatus = decalTexture.getStatus();
|
|
|
|
if (decalStatus != TextureRef::Status_Null)
|
|
{
|
|
if (decalStatus != TextureRef::Status_Loaded)
|
|
return;
|
|
|
|
if (renderQueueId == RenderQueue::Id_Decals)
|
|
{
|
|
if (!decalTexture.getInfo().alpha)
|
|
{
|
|
material = decalMaterialOpaque;
|
|
renderQueueId = RenderQueue::Id_OpaqueDecals;
|
|
}
|
|
else
|
|
{
|
|
renderQueueId = RenderQueue::Id_TransparentDecals;
|
|
}
|
|
}
|
|
|
|
decalTexture = TextureRef();
|
|
}
|
|
|
|
queue.setFeature(extraFeatures);
|
|
|
|
RenderEntity::updateRenderQueue(queue, camera, lodIndex, pass);
|
|
}
|
|
|
|
float FastClusterEntity::getViewDepth(const RenderCamera& camera) const
|
|
{
|
|
// getViewDepth should only be used for transparency sorting; then there should only be one bone
|
|
RBXASSERT(bones.size() == 1);
|
|
|
|
const CoordinateFrame& transform = static_cast<FastCluster*>(node)->getTransform(bones[0]);
|
|
|
|
Vector3 center = transform.pointToWorldSpace(localBoundsCenter);
|
|
|
|
return RenderEntity::computeViewDepth(camera, center, sortKeyOffset);
|
|
}
|
|
|
|
FastClusterBinding::FastClusterBinding(FastCluster* cluster, const shared_ptr<PartInstance>& part)
|
|
: GfxBinding(part)
|
|
, cluster(cluster)
|
|
{
|
|
bindProperties(part);
|
|
|
|
RBXASSERT(part->getGfxPart() == NULL);
|
|
part->setGfxPart(cluster);
|
|
|
|
FASTLOG4(FLog::RenderFastCluster, "FastCluster[%p]: bound part %p to binding %p (%d connections)", cluster, part.get(), this, connections.size());
|
|
|
|
}
|
|
|
|
void FastClusterBinding::invalidateEntity()
|
|
{
|
|
FASTLOG3(FLog::RenderFastCluster, "FastCluster[%p]: part %p with binding %p requests invalidateEntity", cluster, partInstance.get(), this);
|
|
|
|
cluster->invalidateEntity();
|
|
}
|
|
|
|
void FastClusterBinding::onCoordinateFrameChanged()
|
|
{
|
|
if (cluster->isFW())
|
|
{
|
|
FASTLOG3(FLog::RenderFastCluster, "FastCluster[%p]: part %p with binding %p requests coordinate frame change", cluster, partInstance.get(), this);
|
|
|
|
cluster->invalidateEntity();
|
|
cluster->queueClusterCheck();
|
|
cluster->markLightingDirty();
|
|
}
|
|
}
|
|
|
|
void FastClusterBinding::onSizeChanged()
|
|
{
|
|
FASTLOG3(FLog::RenderFastCluster, "FastCluster[%p]: part %p with binding %p requests size change", cluster, partInstance.get(), this);
|
|
|
|
cluster->invalidateEntity();
|
|
cluster->queueClusterCheck();
|
|
cluster->markLightingDirty();
|
|
}
|
|
|
|
void FastClusterBinding::onTransparencyChanged()
|
|
{
|
|
FASTLOG3(FLog::RenderFastCluster, "FastCluster[%p]: part %p with binding %p requests transparency change", cluster, partInstance.get(), this);
|
|
|
|
cluster->invalidateEntity();
|
|
cluster->markLightingDirty();
|
|
}
|
|
|
|
void FastClusterBinding::onSpecialShapeChanged()
|
|
{
|
|
FASTLOG3(FLog::RenderFastCluster, "FastCluster[%p]: part %p with binding %p requests special shape change", cluster, partInstance.get(), this);
|
|
|
|
cluster->invalidateEntity();
|
|
cluster->markLightingDirty();
|
|
}
|
|
|
|
void FastClusterBinding::unbind()
|
|
{
|
|
FASTLOG4(FLog::RenderFastCluster, "FastCluster[%p]: unbind part %p from binding %p (%d connections)", cluster, partInstance.get(), this, connections.size());
|
|
|
|
RBXASSERT(partInstance->getGfxPart() == cluster || partInstance->getGfxPart() == NULL);
|
|
GfxBinding::unbind();
|
|
}
|
|
|
|
FastClusterSharedGeometry::FastClusterSharedGeometry()
|
|
{
|
|
}
|
|
|
|
void FastClusterSharedGeometry::reset()
|
|
{
|
|
vertexBuffer.reset();
|
|
indexBuffer.reset();
|
|
}
|
|
|
|
FastCluster::FastCluster(VisualEngine* visualEngine, Humanoid* humanoid, SuperCluster* owner, bool fw)
|
|
: RenderNode(visualEngine, CullMode_SpatialHash, humanoid ? Flags_ShadowCaster : 0)
|
|
, humanoid(humanoid)
|
|
, owner(owner)
|
|
, fw(fw)
|
|
, dirty(false)
|
|
{
|
|
if (humanoid)
|
|
FASTLOG2(FLog::RenderFastCluster, "FastCluster[%p]: create (humanoid %p)", this, humanoid);
|
|
else
|
|
{
|
|
const SpatialGridIndex& spatialIndex = owner->getSpatialIndex();
|
|
FASTLOG5(FLog::RenderFastCluster, "FastCluster[%p]: create (grid %dx%dx%d, flags %d)", this, spatialIndex.position.x, spatialIndex.position.y, spatialIndex.position.z, spatialIndex.flags);
|
|
}
|
|
|
|
SceneUpdater* su = visualEngine->getSceneUpdater();
|
|
|
|
// register non-FW clusters for updateCoordinateFrame
|
|
if (!fw)
|
|
su->notifyAwake(this);
|
|
|
|
getStatsBucket().clusters++;
|
|
}
|
|
|
|
|
|
FastCluster::~FastCluster()
|
|
{
|
|
unbind();
|
|
|
|
// delete shared geometry
|
|
sharedGeometry.reset();
|
|
|
|
getStatsBucket().clusters--;
|
|
|
|
// notify scene updater about destruction so that the pointer to FastCluster is no longer stored
|
|
getVisualEngine()->getSceneUpdater()->notifyDestroyed(this);
|
|
}
|
|
|
|
void FastCluster::addPart(const boost::shared_ptr<PartInstance>& part)
|
|
{
|
|
FASTLOG3(FLog::RenderFastCluster, "FastCluster[%p]: addPart %p (part count was %d)", this, part.get(), parts.size());
|
|
FASTLOGS(FLog::RenderFastCluster, "FastCluster part: %s", part->getFullName().c_str());
|
|
|
|
Part p;
|
|
p.instance = part.get();
|
|
p.binding = new FastClusterBinding(this, part);
|
|
|
|
parts.push_back(p);
|
|
|
|
getStatsBucket().parts++;
|
|
|
|
lightDirty = true;
|
|
}
|
|
|
|
void FastCluster::checkCluster()
|
|
{
|
|
if (!owner) return;
|
|
|
|
const SpatialGridIndex& spIndex = getSpatialIndex();
|
|
|
|
FASTLOG5(FLog::RenderFastCluster, "FastCluster[%p]: checking cluster (spatial index %dx%dx%d-%u)", this, spIndex.position.x, spIndex.position.y, spIndex.position.z, spIndex.flags);
|
|
|
|
SceneUpdater* sceneUpdater = getVisualEngine()->getSceneUpdater();
|
|
|
|
bool selfInvalidate = false;
|
|
|
|
for (size_t i = 0; i < parts.size(); ++i)
|
|
{
|
|
Part& part = parts[i];
|
|
|
|
if (!part.binding->isBound())
|
|
{
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
bool isPartFW = SceneUpdater::isPartStatic(part.instance);
|
|
SpatialGridIndex newspIndex;
|
|
|
|
newspIndex = owner->getSpatialGrid()->getIndexUnsafe(part.instance, isPartFW ? SpatialGridIndex::fFW : 0);
|
|
|
|
static Vector3int16 m(-1,-1,-1),M(1,1,1);
|
|
|
|
if ( spIndex.flags != newspIndex.flags || !( spIndex.position - newspIndex.position ).isBetweenInclusive(m,M) )
|
|
{
|
|
if(selfInvalidate == false)
|
|
{
|
|
bool result = sceneUpdater->checkAddSeenFastClusters(newspIndex);
|
|
RBXASSERT(result);
|
|
priorityInvalidateEntity();
|
|
}
|
|
|
|
if(!sceneUpdater->checkAddSeenFastClusters(spIndex))
|
|
{
|
|
RBXASSERT(selfInvalidate == true); // Should never fail on first added cluster
|
|
queueClusterCheck();
|
|
break;
|
|
}
|
|
|
|
if (spIndex.flags != newspIndex.flags)
|
|
FASTLOG4(FLog::RenderFastCluster, "FastCluster[%p]: part %p changed FW status from %d to %d", this, part.instance, spIndex.flags, newspIndex.flags);
|
|
else
|
|
FASTLOG5(FLog::RenderFastCluster, "FastCluster[%p]: part %p changed spatial index to %dx%dx%d", this, part.instance, spIndex.position.x, spIndex.position.y, spIndex.position.z);
|
|
|
|
boost::shared_ptr<PartInstance> instance = shared_from(part.instance);
|
|
|
|
part.binding->unbind();
|
|
delete part.binding;
|
|
part.binding = NULL;
|
|
|
|
getVisualEngine()->getSceneUpdater()->queuePartToCreate(instance);
|
|
|
|
getStatsBucket().parts--;
|
|
|
|
selfInvalidate = true;
|
|
|
|
lightDirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(selfInvalidate)
|
|
{
|
|
parts.erase(std::remove_if(parts.begin(), parts.end(), PartBindingNullPredicate()), parts.end());
|
|
owner->checkCluster( this );
|
|
}
|
|
}
|
|
|
|
void FastCluster::invalidateEntity()
|
|
{
|
|
FASTLOG1(FLog::RenderFastCluster, "FastCluster[%p]: invalidateEntity", this);
|
|
|
|
if (!dirty)
|
|
{
|
|
dirty = true;
|
|
|
|
getVisualEngine()->getSceneUpdater()->queueInvalidateFastCluster(this);
|
|
}
|
|
}
|
|
|
|
void FastCluster::priorityInvalidateEntity()
|
|
{
|
|
FASTLOG1(FLog::RenderFastCluster, "FastCluster[%p]: priorityInvalidateEntity", this);
|
|
|
|
dirty = true;
|
|
|
|
getVisualEngine()->getSceneUpdater()->queuePriorityInvalidateFastCluster(this);
|
|
}
|
|
|
|
|
|
void FastCluster::checkBindings()
|
|
{
|
|
for (size_t i = 0; i < parts.size(); ++i)
|
|
{
|
|
Part& part = parts[i];
|
|
|
|
if (!part.binding->isBound())
|
|
{
|
|
FASTLOG2(FLog::RenderFastCluster, "FastCluster[%p]: part %p is no longer in workspace", this, part.instance);
|
|
|
|
delete part.binding;
|
|
part.binding = NULL;
|
|
}
|
|
else
|
|
{
|
|
Humanoid* newHumanoid = SceneUpdater::getHumanoid(part.instance);
|
|
|
|
if (humanoid != newHumanoid)
|
|
{
|
|
FASTLOG4(FLog::RenderFastCluster, "FastCluster[%p]: part %p changed humanoid from %p to %p", this, part.instance, newHumanoid, humanoid);
|
|
|
|
boost::shared_ptr<PartInstance> instance = shared_from(part.instance);
|
|
|
|
part.binding->unbind();
|
|
delete part.binding;
|
|
part.binding = NULL;
|
|
|
|
getVisualEngine()->getSceneUpdater()->queuePartToCreate(instance);
|
|
}
|
|
}
|
|
|
|
if (!part.binding)
|
|
{
|
|
getStatsBucket().parts--;
|
|
|
|
lightDirty = true;
|
|
}
|
|
}
|
|
|
|
parts.erase(std::remove_if(parts.begin(), parts.end(), PartBindingNullPredicate()), parts.end());
|
|
}
|
|
|
|
void FastCluster::updateEntity(bool assetsUpdated)
|
|
{
|
|
if(!assetsUpdated && !dirty)
|
|
{
|
|
FASTLOG3(FLog::RenderFastCluster, "FastCluster[%p]: skipping updateEntity dirty: %d, assetsUpdated: %d", this, dirty, assetsUpdated);
|
|
return;
|
|
}
|
|
|
|
RBX::Timer<RBX::Time::Precise> timer;
|
|
|
|
// cluster is dirty at this point if updateEntity is a reaction to invalidateEntity, but it may not be dirty if
|
|
// scene updater decides to update the cluster after a requested asset is ready.
|
|
FASTLOG4(FLog::RenderFastCluster, "FastCluster[%p]: updateEntity for %d parts (dirty: %d, light dirty: %d)", this, parts.size(), dirty, lightDirty);
|
|
|
|
// reset dirty state before updating to check it after updating finished
|
|
dirty = false;
|
|
|
|
// update all part bindings
|
|
checkBindings();
|
|
|
|
// invalidate lighting for the old AABB
|
|
if (lightDirty)
|
|
invalidateLighting(getWorldBounds());
|
|
|
|
// if we don't need this cluster any more, destroy it
|
|
if (parts.empty())
|
|
{
|
|
FASTLOG1(FLog::RenderFastCluster, "FastCluster[%p]: destroy (no more parts)", this);
|
|
|
|
if (owner)
|
|
owner->destroyFastCluster(this); // this call deletes the object, should be the last call in this function
|
|
else
|
|
getVisualEngine()->getSceneUpdater()->destroyFastCluster(this); // this call deletes the object, should be the last call in this function
|
|
return;
|
|
}
|
|
|
|
// update cluster geometry
|
|
AsyncResult asyncResult;
|
|
|
|
updateClumpGrouping();
|
|
unsigned int totalVertexCount = updateGeometry(&asyncResult);
|
|
|
|
// subscribe for updateEntity when some pending assets are done loading
|
|
getVisualEngine()->getSceneUpdater()->notifyWaitingForAssets(this, asyncResult.waitingFor);
|
|
|
|
// update block count used for FRM-based culling
|
|
setBlockCount(parts.size());
|
|
|
|
// this call updates the AABB
|
|
updateCoordinateFrame(false);
|
|
|
|
// invalidate lighting for the new AABB
|
|
if (lightDirty)
|
|
invalidateLighting(getWorldBounds());
|
|
|
|
// reset light dirty; this means that any pending changes except for the changes in bone transforms have resulted in light invalidation
|
|
lightDirty = false;
|
|
|
|
// we should not do invalidateEntity from updateEntity - this results in excessive invalidations
|
|
RBXASSERT(!dirty);
|
|
|
|
FASTLOG5(FLog::RenderFastCluster, "FastCluster[%p]: updated geometry for %d parts in %d usec (%d entities, %d vertices)", this, parts.size(), (int)(timer.delta().msec() * 1000), entities.size(), totalVertexCount);
|
|
}
|
|
|
|
void FastCluster::updateClumpGrouping()
|
|
{
|
|
// no need to update grouping for FW
|
|
if (fw) return;
|
|
|
|
// move all single-clump parts to the beginning (to make sorting cheaper)
|
|
std::vector<Part>::iterator middle = std::partition(parts.begin(), parts.end(), PartClumpSinglePredicate());
|
|
|
|
// sort remaining parts by clump to use one bone for clump
|
|
std::sort(middle, parts.end(), PartClumpGroupPredicate());
|
|
|
|
FASTLOG4(FLog::RenderFastCluster, "FastCluster[%p]: %d parts, %d single-clump parts, %d multi-clump parts", this, parts.size(), middle - parts.begin(), parts.end() - middle);
|
|
}
|
|
|
|
unsigned int FastCluster::updateGeometry(AsyncResult* asyncResult)
|
|
{
|
|
RBXPROFILER_SCOPE("Render", "updateGeometry");
|
|
|
|
FastClusterMeshGenerator generator(getVisualEngine(), humanoid, parts.size(), fw);
|
|
|
|
const HumanoidIdentifier& hi = generator.getHumanoidIdentifier();
|
|
|
|
// for FW cluster all parts use one pseudo-bone
|
|
if (fw)
|
|
generator.addBone(NULL);
|
|
|
|
// generate geometry for parts
|
|
RBX::Clump* lastClump = NULL;
|
|
|
|
for (size_t parti = 0; parti < parts.size(); ++parti)
|
|
{
|
|
Part& part = parts[parti];
|
|
|
|
// Material flags
|
|
bool ignoreDecals = false;
|
|
unsigned int materialFlags = MaterialGenerator::createFlags(!fw, part.instance, &hi, ignoreDecals);
|
|
bool partTransparent = (part.instance->getTransparencyUi() > 0);
|
|
|
|
// add a new bone if necessary
|
|
Clump* clump = part.instance->getClump();
|
|
|
|
if (!fw && ( !clump || clump != lastClump ) )
|
|
{
|
|
generator.addBone(part.instance);
|
|
|
|
lastClump = clump;
|
|
}
|
|
|
|
unsigned int boneIndex = generator.getBoneCount() - 1;
|
|
|
|
// add part geometry
|
|
if (part.instance->getTransparencyUi() < 1)
|
|
generator.addInstance(boneIndex, part.instance, NULL, materialFlags, partTransparent ? RenderQueue::Id_Transparent : RenderQueue::Id_Opaque, asyncResult);
|
|
|
|
// add part decals / textures
|
|
if ((part.instance->getCookie() & PartCookie::HAS_DECALS) && part.instance->getChildren() && !ignoreDecals)
|
|
{
|
|
unsigned int decalMaterialFlags = (materialFlags & MaterialGenerator::Flag_Skinned) | MaterialGenerator::Flag_Transparent;
|
|
|
|
const Instances& children = *part.instance->getChildren();
|
|
|
|
for (size_t i = 0; i < children.size(); ++i)
|
|
{
|
|
if (Decal* decal = children[i]->fastDynamicCast<Decal>())
|
|
{
|
|
float decalTransparency = decal->getTransparencyUi();
|
|
if (decalTransparency < 1)
|
|
{
|
|
RenderQueue::Id renderQueue = partTransparent ? RenderQueue::Id_Transparent : decalTransparency > 0 ? RenderQueue::Id_TransparentDecals : RenderQueue::Id_Decals;
|
|
|
|
generator.addInstance(boneIndex, part.instance, decal, decalMaterialFlags, renderQueue, asyncResult);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Destroy all existing entities
|
|
for (size_t i = 0; i < entities.size(); ++i)
|
|
delete entities[i];
|
|
|
|
entities.clear();
|
|
|
|
// Generate new entities
|
|
unsigned int vertexCount = generator.finalize(this, sharedGeometry);
|
|
|
|
// Retrieve bone data
|
|
bones.resize(generator.getBoneCount());
|
|
|
|
for (size_t i = 0; i < bones.size(); ++i)
|
|
{
|
|
bones[i].root = generator.getBoneRoot(i);
|
|
bones[i].localBounds = generator.getBoneBounds(i);
|
|
bones[i].transform = bones[i].root ? bones[i].root->getCoordinateFrame() : CoordinateFrame();
|
|
}
|
|
|
|
return vertexCount;
|
|
}
|
|
|
|
ClusterStats& FastCluster::getStatsBucket()
|
|
{
|
|
RenderStats* stats = getVisualEngine()->getRenderStats();
|
|
|
|
if (humanoid)
|
|
return stats->clusterFastHumanoid;
|
|
else if (fw)
|
|
return stats->clusterFastFW;
|
|
else
|
|
return stats->clusterFast;
|
|
}
|
|
|
|
void FastCluster::updateCoordinateFrame(bool recalcLocalBounds)
|
|
{
|
|
Extents oldWorldBB = getWorldBounds();
|
|
Extents newWorldBB;
|
|
|
|
bool bonesChanged = false;
|
|
|
|
RBX::Time now = RBX::Time::nowFast();
|
|
|
|
for (size_t i = 0; i < bones.size(); ++i)
|
|
{
|
|
Bone& bone = bones[i];
|
|
|
|
if (bone.root && !bone.root->getSleeping())
|
|
{
|
|
CoordinateFrame frame = bone.root->calcRenderingCoordinateFrame(now);
|
|
|
|
if (!Math::fuzzyEq(bone.transform, frame))
|
|
bonesChanged = true;
|
|
|
|
bone.transform = frame;
|
|
|
|
if ( owner && owner->getSpatialGrid()->getIndexUnsafe(bone.root, fw ? SpatialGridIndex::fFW : 0) != owner->getSpatialIndex() )
|
|
{
|
|
// Bone moved significantly, re-cluster
|
|
queueClusterCheck();
|
|
}
|
|
}
|
|
|
|
newWorldBB.expandToContain(bone.localBounds.toWorldSpace(bone.transform));
|
|
}
|
|
|
|
updateWorldBounds(newWorldBB);
|
|
|
|
if (bonesChanged)
|
|
{
|
|
invalidateLighting(oldWorldBB);
|
|
invalidateLighting(newWorldBB);
|
|
}
|
|
}
|
|
|
|
void FastCluster::invalidateLighting(const Extents& bbox)
|
|
{
|
|
if (humanoid) return;
|
|
|
|
if (!bbox.isNull())
|
|
{
|
|
getVisualEngine()->getSceneUpdater()->lightingInvalidateOccupancy(bbox, bbox.center(), fw);
|
|
}
|
|
}
|
|
|
|
void FastCluster::unbind()
|
|
{
|
|
FASTLOG3(FLog::RenderFastCluster, "FastCluster[%p]: unbind (%d parts, %d own connections)", this, parts.size(), connections.size());
|
|
|
|
GfxPart::unbind();
|
|
|
|
getStatsBucket().parts -= parts.size();
|
|
|
|
for (size_t i = 0; i < parts.size(); ++i)
|
|
{
|
|
Part& part = parts[i];
|
|
|
|
part.binding->unbind();
|
|
delete part.binding;
|
|
}
|
|
|
|
bones.clear();
|
|
parts.clear();
|
|
}
|
|
|
|
void FastCluster::onSleepingChanged(bool sleeping, PartInstance* part)
|
|
{
|
|
if (!owner) return;
|
|
|
|
// FW parts react on wakeup, non-FW parts react on sleep
|
|
if ((fw && !sleeping) || (!fw && sleeping))
|
|
{
|
|
bool isPartFW = SceneUpdater::isPartStatic(part);
|
|
|
|
if (isPartFW != fw)
|
|
{
|
|
FASTLOG5(FLog::RenderFastCluster, "FastCluster[%p]: part %p, sleeping %d (fw %d -> %d)", this, part, sleeping, fw, isPartFW);
|
|
|
|
queueClusterCheck();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FastCluster::onClumpChanged(PartInstance* part)
|
|
{
|
|
if (!fw)
|
|
{
|
|
FASTLOG2(FLog::RenderFastCluster, "FastCluster[%p]: part %p requests clump change", this, part);
|
|
|
|
invalidateEntity();
|
|
}
|
|
}
|
|
|
|
void FastCluster::queueClusterCheck()
|
|
{
|
|
if (!owner) return;
|
|
getVisualEngine()->getSceneUpdater()->queueFastClusterCheck(this, isFW());
|
|
}
|
|
|
|
void FastCluster::markLightingDirty()
|
|
{
|
|
lightDirty = true;
|
|
}
|
|
|
|
unsigned int FastCluster::getPartCount()
|
|
{
|
|
return parts.size();
|
|
}
|
|
|
|
const SpatialGridIndex& FastCluster::getSpatialIndex() const
|
|
{
|
|
return owner->getSpatialIndex();
|
|
}
|
|
|
|
void FastCluster::getAllParts(std::vector< shared_ptr<PartInstance> >& retParts) const
|
|
{
|
|
retParts.resize(parts.size());
|
|
for( int j=0,e=parts.size(); j<e; ++j )
|
|
{
|
|
retParts[j] = shared_from( parts[j].instance );
|
|
}
|
|
}
|
|
|
|
}
|
|
}
|