remove cruft from rendering collision shapes

This commit is contained in:
Andrew Meadows 2018-07-16 10:29:34 -07:00
parent c225c3deca
commit 207aea8712
9 changed files with 0 additions and 598 deletions

View file

@ -699,14 +699,6 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
adjustShapeInfoByRegistration(shapeInfo);
}
void RenderableModelEntityItem::setCollisionShape(const btCollisionShape* shape) {
const void* key = static_cast<const void*>(shape);
if (_collisionMeshKey != key) {
_collisionMeshKey = key;
emit requestCollisionGeometryUpdate();
}
}
void RenderableModelEntityItem::setJointMap(std::vector<int> jointMap) {
if (jointMap.size() > 0) {
_jointMap = jointMap;
@ -1278,10 +1270,6 @@ bool ModelEntityRenderer::needsRenderUpdateFromTypedEntity(const TypedEntityPoin
return false;
}
void ModelEntityRenderer::setCollisionMeshKey(const void*key) {
_collisionMeshKey = key;
}
void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) {
DETAILED_PROFILE_RANGE(simulation_physics, __FUNCTION__);
if (_hasModel != entity->hasModel()) {

View file

@ -78,8 +78,6 @@ public:
virtual bool isReadyToComputeShape() const override;
virtual void computeShapeInfo(ShapeInfo& shapeInfo) override;
void setCollisionShape(const btCollisionShape* shape) override;
virtual bool contains(const glm::vec3& point) const override;
void stopModelOverrideIfNoParent();
@ -112,10 +110,6 @@ public:
virtual QStringList getJointNames() const override;
bool getMeshes(MeshProxyList& result) override; // deprecated
const void* getCollisionMeshKey() const { return _collisionMeshKey; }
signals:
void requestCollisionGeometryUpdate();
private:
bool needsUpdateModelBounds() const;
@ -130,7 +124,6 @@ private:
QVariantMap _originalTextures;
bool _dimensionsInitialized { true };
bool _needsJointSimulation { false };
const void* _collisionMeshKey { nullptr };
};
namespace render { namespace entities {
@ -161,7 +154,6 @@ protected:
virtual bool needsRenderUpdate() const override;
virtual void doRender(RenderArgs* args) override;
virtual void doRenderUpdateSynchronousTyped(const ScenePointer& scene, Transaction& transaction, const TypedEntityPointer& entity) override;
void setCollisionMeshKey(const void* key);
render::hifi::Tag getTagMask() const override;

View file

@ -378,8 +378,6 @@ public:
/// return preferred shape type (actual physical shape may differ)
virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; }
virtual void setCollisionShape(const btCollisionShape* shape) {}
void setPosition(const glm::vec3& value);
virtual void setParentID(const QUuid& parentID) override;
virtual void setShapeType(ShapeType type) { /* do nothing */ }

View file

@ -1,217 +0,0 @@
//
// CollisionRenderMeshCache.cpp
// libraries/physics/src
//
// Created by Andrew Meadows 2016.07.13
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CollisionRenderMeshCache.h"
#include <cassert>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionShapes/btShapeHull.h>
#include <ShapeInfo.h> // for MAX_HULL_POINTS
const int32_t MAX_HULL_INDICES = 6 * MAX_HULL_POINTS;
const int32_t MAX_HULL_NORMALS = MAX_HULL_INDICES;
float tempVertices[MAX_HULL_NORMALS];
graphics::Index tempIndexBuffer[MAX_HULL_INDICES];
bool copyShapeToMesh(const btTransform& transform, const btConvexShape* shape,
gpu::BufferView& vertices, gpu::BufferView& indices, gpu::BufferView& parts,
gpu::BufferView& normals) {
assert(shape);
btShapeHull hull(shape);
if (!hull.buildHull(shape->getMargin())) {
return false;
}
int32_t numHullIndices = hull.numIndices();
assert(numHullIndices <= MAX_HULL_INDICES);
int32_t numHullVertices = hull.numVertices();
assert(numHullVertices <= MAX_HULL_POINTS);
{ // new part
graphics::Mesh::Part part;
part._startIndex = (graphics::Index)indices.getNumElements();
part._numIndices = (graphics::Index)numHullIndices;
// FIXME: the render code cannot handle the case where part._baseVertex != 0
//part._baseVertex = vertices.getNumElements(); // DOES NOT WORK
part._baseVertex = 0;
gpu::BufferView::Size numBytes = sizeof(graphics::Mesh::Part);
const gpu::Byte* data = reinterpret_cast<const gpu::Byte*>(&part);
parts._buffer->append(numBytes, data);
parts._size = parts._buffer->getSize();
}
const int32_t SIZE_OF_VEC3 = 3 * sizeof(float);
graphics::Index indexOffset = (graphics::Index)vertices.getNumElements();
{ // new indices
const uint32_t* hullIndices = hull.getIndexPointer();
// FIXME: the render code cannot handle the case where part._baseVertex != 0
// so we must add an offset to each index
for (int32_t i = 0; i < numHullIndices; ++i) {
tempIndexBuffer[i] = hullIndices[i] + indexOffset;
}
const gpu::Byte* data = reinterpret_cast<const gpu::Byte*>(tempIndexBuffer);
gpu::BufferView::Size numBytes = (gpu::BufferView::Size)(sizeof(graphics::Index) * numHullIndices);
indices._buffer->append(numBytes, data);
indices._size = indices._buffer->getSize();
}
{ // new vertices
const btVector3* hullVertices = hull.getVertexPointer();
assert(numHullVertices <= MAX_HULL_POINTS);
for (int32_t i = 0; i < numHullVertices; ++i) {
btVector3 transformedPoint = transform * hullVertices[i];
memcpy(tempVertices + 3 * i, transformedPoint.m_floats, SIZE_OF_VEC3);
}
gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices);
const gpu::Byte* data = reinterpret_cast<const gpu::Byte*>(tempVertices);
vertices._buffer->append(numBytes, data);
vertices._size = vertices._buffer->getSize();
}
{ // new normals
// compute average point
btVector3 avgVertex(0.0f, 0.0f, 0.0f);
const btVector3* hullVertices = hull.getVertexPointer();
for (int i = 0; i < numHullVertices; ++i) {
avgVertex += hullVertices[i];
}
avgVertex = transform * (avgVertex * (1.0f / (float)numHullVertices));
for (int i = 0; i < numHullVertices; ++i) {
btVector3 norm = transform * hullVertices[i] - avgVertex;
btScalar normLength = norm.length();
if (normLength > FLT_EPSILON) {
norm /= normLength;
}
memcpy(tempVertices + 3 * i, norm.m_floats, SIZE_OF_VEC3);
}
gpu::BufferView::Size numBytes = sizeof(float) * (3 * numHullVertices);
const gpu::Byte* data = reinterpret_cast<const gpu::Byte*>(tempVertices);
normals._buffer->append(numBytes, data);
normals._size = vertices._buffer->getSize();
}
return true;
}
graphics::MeshPointer createMeshFromShape(const void* pointer) {
graphics::MeshPointer mesh;
if (!pointer) {
return mesh;
}
// pointer must be a const btCollisionShape* (cast to void*), but it only
// needs to be valid here when its render mesh is created, after this call
// the cache doesn't care what happens to the shape behind the pointer
const btCollisionShape* shape = static_cast<const btCollisionShape*>(pointer);
int32_t shapeType = shape->getShapeType();
if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE || shape->isConvex()) {
// allocate buffers for it
gpu::BufferView vertices(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
gpu::BufferView indices(new gpu::Buffer(), gpu::Element(gpu::SCALAR, gpu::UINT32, gpu::INDEX));
gpu::BufferView parts(new gpu::Buffer(), gpu::Element(gpu::VEC4, gpu::UINT32, gpu::PART));
gpu::BufferView normals(new gpu::Buffer(), gpu::Element(gpu::VEC3, gpu::FLOAT, gpu::XYZ));
int32_t numSuccesses = 0;
if (shapeType == (int32_t)COMPOUND_SHAPE_PROXYTYPE) {
const btCompoundShape* compoundShape = static_cast<const btCompoundShape*>(shape);
int32_t numSubShapes = compoundShape->getNumChildShapes();
for (int32_t i = 0; i < numSubShapes; ++i) {
const btCollisionShape* childShape = compoundShape->getChildShape(i);
if (childShape->isConvex()) {
const btConvexShape* convexShape = static_cast<const btConvexShape*>(childShape);
if (copyShapeToMesh(compoundShape->getChildTransform(i), convexShape, vertices, indices, parts, normals)) {
numSuccesses++;
}
}
}
} else {
// shape is convex
const btConvexShape* convexShape = static_cast<const btConvexShape*>(shape);
btTransform transform;
transform.setIdentity();
if (copyShapeToMesh(transform, convexShape, vertices, indices, parts, normals)) {
numSuccesses++;
}
}
if (numSuccesses > 0) {
mesh = std::make_shared<graphics::Mesh>();
mesh->setVertexBuffer(vertices);
mesh->setIndexBuffer(indices);
mesh->setPartBuffer(parts);
mesh->addAttribute(gpu::Stream::NORMAL, normals);
} else {
// TODO: log failure message here
}
}
return mesh;
}
CollisionRenderMeshCache::CollisionRenderMeshCache() {
}
CollisionRenderMeshCache::~CollisionRenderMeshCache() {
_meshMap.clear();
_pendingGarbage.clear();
}
graphics::MeshPointer CollisionRenderMeshCache::getMesh(CollisionRenderMeshCache::Key key) {
graphics::MeshPointer mesh;
if (key) {
CollisionMeshMap::const_iterator itr = _meshMap.find(key);
if (itr == _meshMap.end()) {
// make mesh and add it to map
mesh = createMeshFromShape(key);
if (mesh) {
_meshMap.insert(std::make_pair(key, mesh));
}
} else {
mesh = itr->second;
}
}
const uint32_t MAX_NUM_PENDING_GARBAGE = 20;
if (_pendingGarbage.size() > MAX_NUM_PENDING_GARBAGE) {
collectGarbage();
}
return mesh;
}
bool CollisionRenderMeshCache::releaseMesh(CollisionRenderMeshCache::Key key) {
if (!key) {
return false;
}
CollisionMeshMap::const_iterator itr = _meshMap.find(key);
if (itr != _meshMap.end()) {
_pendingGarbage.push_back(key);
return true;
}
return false;
}
void CollisionRenderMeshCache::collectGarbage() {
uint32_t numShapes = (uint32_t)_pendingGarbage.size();
for (uint32_t i = 0; i < numShapes; ++i) {
CollisionRenderMeshCache::Key key = _pendingGarbage[i];
CollisionMeshMap::const_iterator itr = _meshMap.find(key);
if (itr != _meshMap.end()) {
if ((*itr).second.use_count() == 1) {
// we hold the only reference
_meshMap.erase(itr);
}
}
}
_pendingGarbage.clear();
}

View file

@ -1,48 +0,0 @@
//
// CollisionRenderMeshCache.h
// libraries/physics/src
//
// Created by Andrew Meadows 2016.07.13
// Copyright 2016 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CollisionRenderMeshCache_h
#define hifi_CollisionRenderMeshCache_h
#include <memory>
#include <vector>
#include <unordered_map>
#include <graphics/Geometry.h>
class CollisionRenderMeshCache {
public:
using Key = const void*; // must actually be a const btCollisionShape*
CollisionRenderMeshCache();
~CollisionRenderMeshCache();
/// \return pointer to geometry
graphics::MeshPointer getMesh(Key key);
/// \return true if geometry was found and released
bool releaseMesh(Key key);
/// delete geometries that have zero references
void collectGarbage();
// validation methods
uint32_t getNumMeshes() const { return (uint32_t)_meshMap.size(); }
bool hasMesh(Key key) const { return _meshMap.find(key) == _meshMap.end(); }
private:
using CollisionMeshMap = std::unordered_map<Key, graphics::MeshPointer>;
CollisionMeshMap _meshMap;
std::vector<Key> _pendingGarbage;
};
#endif // hifi_CollisionRenderMeshCache_h

View file

@ -307,13 +307,6 @@ const btCollisionShape* EntityMotionState::computeNewShape() {
return getShapeManager()->getShape(shapeInfo);
}
void EntityMotionState::setShape(const btCollisionShape* shape) {
if (_shape != shape) {
ObjectMotionState::setShape(shape);
_entity->setCollisionShape(_shape);
}
}
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
// NOTE: this method is only ever called when the entity simulation is locally owned
DETAILED_PROFILE_RANGE(simulation_physics, "CheckOutOfSync");

View file

@ -118,7 +118,6 @@ protected:
bool isReadyToComputeShape() const override;
const btCollisionShape* computeNewShape() override;
void setShape(const btCollisionShape* shape) override;
void setMotionType(PhysicsMotionType motionType) override;
// EntityMotionState keeps a SharedPointer to its EntityItem which is only set in the CTOR

View file

@ -1,277 +0,0 @@
//
// CollisionRenderMeshCacheTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 2014.10.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CollisionRenderMeshCacheTests.h"
#include <iostream>
#include <cstdlib>
#include <btBulletDynamicsCommon.h>
#include <BulletCollision/CollisionShapes/btShapeHull.h>
#include <CollisionRenderMeshCache.h>
#include <ShapeInfo.h> // for MAX_HULL_POINTS
#include "MeshUtil.h"
QTEST_MAIN(CollisionRenderMeshCacheTests)
const float INV_SQRT_THREE = 0.577350269f;
const uint32_t numSphereDirections = 6 + 8;
btVector3 sphereDirections[] = {
btVector3(1.0f, 0.0f, 0.0f),
btVector3(-1.0f, 0.0f, 0.0f),
btVector3(0.0f, 1.0f, 0.0f),
btVector3(0.0f, -1.0f, 0.0f),
btVector3(0.0f, 0.0f, 1.0f),
btVector3(0.0f, 0.0f, -1.0f),
btVector3(INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE),
btVector3(INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE),
btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE),
btVector3(INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE),
btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, INV_SQRT_THREE),
btVector3(-INV_SQRT_THREE, INV_SQRT_THREE, -INV_SQRT_THREE),
btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, INV_SQRT_THREE),
btVector3(-INV_SQRT_THREE, -INV_SQRT_THREE, -INV_SQRT_THREE)
};
float randomFloat() {
return 2.0f * ((float)rand() / (float)RAND_MAX) - 1.0f;
}
btBoxShape* createBoxShape(const btVector3& extent) {
btBoxShape* shape = new btBoxShape(0.5f * extent);
return shape;
}
btConvexHullShape* createConvexHull(float radius) {
btConvexHullShape* hull = new btConvexHullShape();
for (uint32_t i = 0; i < numSphereDirections; ++i) {
btVector3 point = radius * sphereDirections[i];
hull->addPoint(point, false);
}
hull->recalcLocalAabb();
return hull;
}
void CollisionRenderMeshCacheTests::testShapeHullManifold() {
// make a box shape
btVector3 extent(1.0f, 2.0f, 3.0f);
btBoxShape* box = createBoxShape(extent);
// wrap it with a ShapeHull
btShapeHull hull(box);
const float MARGIN = 0.0f;
hull.buildHull(MARGIN);
// verify the vertex count is capped
uint32_t numVertices = (uint32_t)hull.numVertices();
QVERIFY(numVertices <= MAX_HULL_POINTS);
// verify the mesh is inside the radius
btVector3 halfExtents = box->getHalfExtentsWithMargin();
float ACCEPTABLE_EXTENTS_ERROR = 0.01f;
float maxRadius = halfExtents.length() + ACCEPTABLE_EXTENTS_ERROR;
const btVector3* meshVertices = hull.getVertexPointer();
for (uint32_t i = 0; i < numVertices; ++i) {
btVector3 vertex = meshVertices[i];
QVERIFY(vertex.length() <= maxRadius);
}
// verify the index count is capped
uint32_t numIndices = (uint32_t)hull.numIndices();
QVERIFY(numIndices < 6 * MAX_HULL_POINTS);
// verify the index count is a multiple of 3
QVERIFY(numIndices % 3 == 0);
// verify the mesh is closed
const uint32_t* meshIndices = hull.getIndexPointer();
bool isClosed = MeshUtil::isClosedManifold(meshIndices, numIndices);
QVERIFY(isClosed);
// verify the triangle normals are outward using right-hand-rule
const uint32_t INDICES_PER_TRIANGLE = 3;
for (uint32_t i = 0; i < numIndices; i += INDICES_PER_TRIANGLE) {
btVector3 A = meshVertices[meshIndices[i]];
btVector3 B = meshVertices[meshIndices[i+1]];
btVector3 C = meshVertices[meshIndices[i+2]];
btVector3 face = (B - A).cross(C - B);
btVector3 center = (A + B + C) / 3.0f;
QVERIFY(face.dot(center) > 0.0f);
}
// delete unmanaged memory
delete box;
}
void CollisionRenderMeshCacheTests::testCompoundShape() {
uint32_t numSubShapes = 3;
btVector3 centers[] = {
btVector3(1.0f, 0.0f, 0.0f),
btVector3(0.0f, -2.0f, 0.0f),
btVector3(0.0f, 0.0f, 3.0f),
};
float radii[] = { 3.0f, 2.0f, 1.0f };
btCompoundShape* compoundShape = new btCompoundShape();
for (uint32_t i = 0; i < numSubShapes; ++i) {
btTransform transform;
transform.setOrigin(centers[i]);
btConvexHullShape* hull = createConvexHull(radii[i]);
compoundShape->addChildShape(transform, hull);
}
// create the cache
CollisionRenderMeshCache cache;
QVERIFY(cache.getNumMeshes() == 0);
// get the mesh once
graphics::MeshPointer mesh = cache.getMesh(compoundShape);
QVERIFY((bool)mesh);
QVERIFY(cache.getNumMeshes() == 1);
// get the mesh again
graphics::MeshPointer mesh2 = cache.getMesh(compoundShape);
QVERIFY(mesh2 == mesh);
QVERIFY(cache.getNumMeshes() == 1);
// forget the mesh once
cache.releaseMesh(compoundShape);
mesh.reset();
QVERIFY(cache.getNumMeshes() == 1);
// collect garbage (should still cache mesh)
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 1);
// forget the mesh a second time (should still cache mesh)
cache.releaseMesh(compoundShape);
mesh2.reset();
QVERIFY(cache.getNumMeshes() == 1);
// collect garbage (should no longer cache mesh)
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 0);
// delete unmanaged memory
for (int i = 0; i < compoundShape->getNumChildShapes(); ++i) {
delete compoundShape->getChildShape(i);
}
delete compoundShape;
}
void CollisionRenderMeshCacheTests::testMultipleShapes() {
// shapeA is compound of hulls
uint32_t numSubShapes = 3;
btVector3 centers[] = {
btVector3(1.0f, 0.0f, 0.0f),
btVector3(0.0f, -2.0f, 0.0f),
btVector3(0.0f, 0.0f, 3.0f),
};
float radii[] = { 3.0f, 2.0f, 1.0f };
btCompoundShape* shapeA = new btCompoundShape();
for (uint32_t i = 0; i < numSubShapes; ++i) {
btTransform transform;
transform.setOrigin(centers[i]);
btConvexHullShape* hull = createConvexHull(radii[i]);
shapeA->addChildShape(transform, hull);
}
// shapeB is compound of boxes
btVector3 extents[] = {
btVector3(1.0f, 2.0f, 3.0f),
btVector3(2.0f, 3.0f, 1.0f),
btVector3(3.0f, 1.0f, 2.0f),
};
btCompoundShape* shapeB = new btCompoundShape();
for (uint32_t i = 0; i < numSubShapes; ++i) {
btTransform transform;
transform.setOrigin(centers[i]);
btBoxShape* box = createBoxShape(extents[i]);
shapeB->addChildShape(transform, box);
}
// shapeC is just a box
btVector3 extentC(7.0f, 3.0f, 5.0f);
btBoxShape* shapeC = createBoxShape(extentC);
// create the cache
CollisionRenderMeshCache cache;
QVERIFY(cache.getNumMeshes() == 0);
// get the meshes
graphics::MeshPointer meshA = cache.getMesh(shapeA);
graphics::MeshPointer meshB = cache.getMesh(shapeB);
graphics::MeshPointer meshC = cache.getMesh(shapeC);
QVERIFY((bool)meshA);
QVERIFY((bool)meshB);
QVERIFY((bool)meshC);
QVERIFY(cache.getNumMeshes() == 3);
// get the meshes again
graphics::MeshPointer meshA2 = cache.getMesh(shapeA);
graphics::MeshPointer meshB2 = cache.getMesh(shapeB);
graphics::MeshPointer meshC2 = cache.getMesh(shapeC);
QVERIFY(meshA == meshA2);
QVERIFY(meshB == meshB2);
QVERIFY(meshC == meshC2);
QVERIFY(cache.getNumMeshes() == 3);
// forget the meshes once
cache.releaseMesh(shapeA);
cache.releaseMesh(shapeB);
cache.releaseMesh(shapeC);
meshA2.reset();
meshB2.reset();
meshC2.reset();
QVERIFY(cache.getNumMeshes() == 3);
// collect garbage (should still cache mesh)
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 3);
// forget again, one mesh at a time...
// shapeA...
cache.releaseMesh(shapeA);
meshA.reset();
QVERIFY(cache.getNumMeshes() == 3);
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 2);
// shapeB...
cache.releaseMesh(shapeB);
meshB.reset();
QVERIFY(cache.getNumMeshes() == 2);
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 1);
// shapeC...
cache.releaseMesh(shapeC);
meshC.reset();
QVERIFY(cache.getNumMeshes() == 1);
cache.collectGarbage();
QVERIFY(cache.getNumMeshes() == 0);
// delete unmanaged memory
for (int i = 0; i < shapeA->getNumChildShapes(); ++i) {
delete shapeA->getChildShape(i);
}
delete shapeA;
for (int i = 0; i < shapeB->getNumChildShapes(); ++i) {
delete shapeB->getChildShape(i);
}
delete shapeB;
delete shapeC;
}

View file

@ -1,26 +0,0 @@
//
// CollisionRenderMeshCacheTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.10.30
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CollisionRenderMeshCacheTests_h
#define hifi_CollisionRenderMeshCacheTests_h
#include <QtTest/QtTest>
class CollisionRenderMeshCacheTests : public QObject {
Q_OBJECT
private slots:
void testShapeHullManifold();
void testCompoundShape();
void testMultipleShapes();
};
#endif // hifi_CollisionRenderMeshCacheTests_h