mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 23:53:48 +02:00
Merge pull request #8329 from AndrewMeadows/render-collision-geometry
Render collision geometry
This commit is contained in:
commit
ac291a398d
41 changed files with 1078 additions and 317 deletions
interface/src
libraries
entities-renderer/src
EntityTreeRenderer.cppEntityTreeRenderer.hRenderableModelEntityItem.cppRenderableModelEntityItem.hRenderableZoneEntityItem.cpp
entities/src
model-networking/src/model-networking
model/src/model
networking/src
physics
render-utils/src
shared/src
tests/physics
|
@ -137,7 +137,7 @@ namespace MenuOption {
|
|||
const QString Overlays = "Overlays";
|
||||
const QString PackageModel = "Package Model...";
|
||||
const QString Pair = "Pair";
|
||||
const QString PhysicsShowHulls = "Draw Collision Hulls";
|
||||
const QString PhysicsShowHulls = "Draw Collision Shapes";
|
||||
const QString PhysicsShowOwned = "Highlight Simulation Ownership";
|
||||
const QString PipelineWarnings = "Log Render Pipeline Warnings";
|
||||
const QString Preferences = "General...";
|
||||
|
|
|
@ -367,7 +367,7 @@ void AvatarManager::addAvatarToSimulation(Avatar* avatar) {
|
|||
|
||||
ShapeInfo shapeInfo;
|
||||
avatar->computeShapeInfo(shapeInfo);
|
||||
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
|
||||
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
|
||||
if (shape) {
|
||||
// we don't add to the simulation now, we put it on a list to be added later
|
||||
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include "AvatarMotionState.h"
|
||||
#include "BulletUtil.h"
|
||||
|
||||
AvatarMotionState::AvatarMotionState(Avatar* avatar, btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
|
||||
AvatarMotionState::AvatarMotionState(Avatar* avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
|
||||
assert(_avatar);
|
||||
_type = MOTIONSTATE_TYPE_AVATAR;
|
||||
if (_shape) {
|
||||
|
@ -47,7 +47,7 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
|
|||
}
|
||||
|
||||
// virtual and protected
|
||||
btCollisionShape* AvatarMotionState::computeNewShape() {
|
||||
const btCollisionShape* AvatarMotionState::computeNewShape() {
|
||||
ShapeInfo shapeInfo;
|
||||
_avatar->computeShapeInfo(shapeInfo);
|
||||
return getShapeManager()->getShape(shapeInfo);
|
||||
|
|
|
@ -20,7 +20,7 @@ class Avatar;
|
|||
|
||||
class AvatarMotionState : public ObjectMotionState {
|
||||
public:
|
||||
AvatarMotionState(Avatar* avatar, btCollisionShape* shape);
|
||||
AvatarMotionState(Avatar* avatar, const btCollisionShape* shape);
|
||||
|
||||
virtual PhysicsMotionType getMotionType() const override { return _motionType; }
|
||||
|
||||
|
@ -72,7 +72,7 @@ protected:
|
|||
~AvatarMotionState();
|
||||
|
||||
virtual bool isReadyToComputeShape() const override { return true; }
|
||||
virtual btCollisionShape* computeNewShape() override;
|
||||
virtual const btCollisionShape* computeNewShape() override;
|
||||
|
||||
// The AvatarMotionState keeps a RAW backpointer to its Avatar because all AvatarMotionState
|
||||
// instances are "owned" by their corresponding Avatar instance and are deleted in the Avatar dtor.
|
||||
|
|
|
@ -499,35 +499,18 @@ ModelPointer EntityTreeRenderer::getModelForEntityItem(EntityItemPointer entityI
|
|||
return result;
|
||||
}
|
||||
|
||||
const FBXGeometry* EntityTreeRenderer::getCollisionGeometryForEntity(EntityItemPointer entityItem) {
|
||||
const FBXGeometry* result = NULL;
|
||||
|
||||
if (entityItem->getType() == EntityTypes::Model) {
|
||||
std::shared_ptr<RenderableModelEntityItem> modelEntityItem =
|
||||
std::dynamic_pointer_cast<RenderableModelEntityItem>(entityItem);
|
||||
if (modelEntityItem->hasCompoundShapeURL()) {
|
||||
ModelPointer model = modelEntityItem->getModel(this);
|
||||
if (model && model->isCollisionLoaded()) {
|
||||
result = &model->getCollisionFBXGeometry();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
|
||||
std::static_pointer_cast<EntityTree>(_tree)->processEraseMessage(message, sourceNode);
|
||||
}
|
||||
|
||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString& collisionUrl, float loadingPriority) {
|
||||
ModelPointer EntityTreeRenderer::allocateModel(const QString& url, float loadingPriority) {
|
||||
ModelPointer model = nullptr;
|
||||
|
||||
// Only create and delete models on the thread that owns the EntityTreeRenderer
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(ModelPointer, model),
|
||||
Q_ARG(const QString&, url),
|
||||
Q_ARG(const QString&, collisionUrl));
|
||||
Q_ARG(const QString&, url));
|
||||
|
||||
return model;
|
||||
}
|
||||
|
@ -536,7 +519,6 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString
|
|||
model->setLoadingPriority(loadingPriority);
|
||||
model->init();
|
||||
model->setURL(QUrl(url));
|
||||
model->setCollisionModelURL(QUrl(collisionUrl));
|
||||
return model;
|
||||
}
|
||||
|
||||
|
@ -553,7 +535,6 @@ ModelPointer EntityTreeRenderer::updateModel(ModelPointer model, const QString&
|
|||
}
|
||||
|
||||
model->setURL(QUrl(newUrl));
|
||||
model->setCollisionModelURL(QUrl(collisionUrl));
|
||||
return model;
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,6 @@ public:
|
|||
|
||||
virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) override;
|
||||
virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) override;
|
||||
virtual const FBXGeometry* getCollisionGeometryForEntity(EntityItemPointer entityItem) override;
|
||||
|
||||
/// clears the tree
|
||||
virtual void clear() override;
|
||||
|
@ -74,7 +73,7 @@ public:
|
|||
void reloadEntityScripts();
|
||||
|
||||
/// if a renderable entity item needs a model, we will allocate it for them
|
||||
Q_INVOKABLE ModelPointer allocateModel(const QString& url, const QString& collisionUrl, float loadingPriority = 0.0f);
|
||||
Q_INVOKABLE ModelPointer allocateModel(const QString& url, float loadingPriority = 0.0f);
|
||||
|
||||
/// if a renderable entity item needs to update the URL of a model, we will handle that for the entity
|
||||
Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl);
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <glm/gtx/transform.hpp>
|
||||
|
||||
#include <AbstractViewStateInterface.h>
|
||||
#include <CollisionRenderMeshCache.h>
|
||||
#include <Model.h>
|
||||
#include <PerfStat.h>
|
||||
#include <render/Scene.h>
|
||||
|
@ -28,6 +29,9 @@
|
|||
#include "RenderableModelEntityItem.h"
|
||||
#include "RenderableEntityItem.h"
|
||||
|
||||
static CollisionRenderMeshCache collisionMeshCache;
|
||||
|
||||
|
||||
EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
|
||||
EntityItemPointer entity{ new RenderableModelEntityItem(entityID, properties.getDimensionsInitialized()) };
|
||||
entity->setProperties(properties);
|
||||
|
@ -214,21 +218,21 @@ namespace render {
|
|||
bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges) {
|
||||
_myMetaItem = scene->allocateID();
|
||||
|
||||
|
||||
auto renderData = std::make_shared<RenderableModelEntityItemMeta>(self);
|
||||
auto renderPayload = std::make_shared<RenderableModelEntityItemMeta::Payload>(renderData);
|
||||
|
||||
|
||||
pendingChanges.resetItem(_myMetaItem, renderPayload);
|
||||
|
||||
|
||||
if (_model) {
|
||||
render::Item::Status::Getters statusGetters;
|
||||
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
|
||||
|
||||
// note: we don't care if the model fails to add items, we always added our meta item and therefore we return
|
||||
// true so that the system knows our meta item is in the scene!
|
||||
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
|
||||
|
||||
// note: we don't mind if the model fails to add, we'll retry (in render()) until it succeeds
|
||||
_model->addToScene(scene, pendingChanges, statusGetters);
|
||||
}
|
||||
|
||||
// we've successfully added _myMetaItem so we always return true
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -415,19 +419,35 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
|
|||
// Remap textures for the next frame to avoid flicker
|
||||
remapTextures();
|
||||
|
||||
// check to see if when we added our models to the scene they were ready, if they were not ready, then
|
||||
// fix them up in the scene
|
||||
bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0
|
||||
&& getShapeType() == SHAPE_TYPE_COMPOUND;
|
||||
if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) {
|
||||
_showCollisionHull = shouldShowCollisionHull;
|
||||
// update whether the model should be showing collision mesh (this may flag for fixupInScene)
|
||||
bool showingCollisionGeometry = (bool)(args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS);
|
||||
if (showingCollisionGeometry != _showCollisionGeometry) {
|
||||
ShapeType type = getShapeType();
|
||||
_showCollisionGeometry = showingCollisionGeometry;
|
||||
if (_showCollisionGeometry && type != SHAPE_TYPE_STATIC_MESH && type != SHAPE_TYPE_NONE) {
|
||||
// NOTE: it is OK if _collisionMeshKey is nullptr
|
||||
model::MeshPointer mesh = collisionMeshCache.getMesh(_collisionMeshKey);
|
||||
// NOTE: the model will render the collisionGeometry if it has one
|
||||
_model->setCollisionMesh(mesh);
|
||||
} else {
|
||||
// release mesh
|
||||
if (_collisionMeshKey) {
|
||||
collisionMeshCache.releaseMesh(_collisionMeshKey);
|
||||
}
|
||||
// clear model's collision geometry
|
||||
model::MeshPointer mesh = nullptr;
|
||||
_model->setCollisionMesh(mesh);
|
||||
}
|
||||
}
|
||||
|
||||
if (_model->needsFixupInScene()) {
|
||||
render::PendingChanges pendingChanges;
|
||||
|
||||
_model->removeFromScene(scene, pendingChanges);
|
||||
|
||||
render::Item::Status::Getters statusGetters;
|
||||
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
|
||||
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
|
||||
_model->addToScene(scene, pendingChanges, statusGetters);
|
||||
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
}
|
||||
|
@ -471,14 +491,13 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
|
|||
if (!getModelURL().isEmpty()) {
|
||||
// If we don't have a model, allocate one *immediately*
|
||||
if (!_model) {
|
||||
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL(), renderer->getEntityLoadingPriority(*this));
|
||||
_model = _myRenderer->allocateModel(getModelURL(), renderer->getEntityLoadingPriority(*this));
|
||||
_needsInitialSimulation = true;
|
||||
// If we need to change URLs, update it *after rendering* (to avoid access violations)
|
||||
} else if ((QUrl(getModelURL()) != _model->getURL() || QUrl(getCompoundShapeURL()) != _model->getCollisionURL())) {
|
||||
} else if (QUrl(getModelURL()) != _model->getURL()) {
|
||||
QMetaObject::invokeMethod(_myRenderer, "updateModel", Qt::QueuedConnection,
|
||||
Q_ARG(ModelPointer, _model),
|
||||
Q_ARG(const QString&, getModelURL()),
|
||||
Q_ARG(const QString&, getCompoundShapeURL()));
|
||||
Q_ARG(const QString&, getModelURL()));
|
||||
_needsInitialSimulation = true;
|
||||
}
|
||||
// Else we can just return the _model
|
||||
|
@ -546,6 +565,18 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
|
|||
face, surfaceNormal, extraInfo, precisionPicking);
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::setShapeType(ShapeType type) {
|
||||
ModelEntityItem::setShapeType(type);
|
||||
if (_shapeType == SHAPE_TYPE_COMPOUND) {
|
||||
if (!_compoundShapeResource && !_compoundShapeURL.isEmpty()) {
|
||||
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(getCompoundShapeURL());
|
||||
}
|
||||
} else if (_compoundShapeResource && !_compoundShapeURL.isEmpty()) {
|
||||
// the compoundURL has been set but the shapeType does not agree
|
||||
_compoundShapeResource.reset();
|
||||
}
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
|
||||
auto currentCompoundShapeURL = getCompoundShapeURL();
|
||||
ModelEntityItem::setCompoundShapeURL(url);
|
||||
|
@ -555,6 +586,9 @@ void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
|
|||
if (tree) {
|
||||
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
|
||||
}
|
||||
if (_shapeType == SHAPE_TYPE_COMPOUND) {
|
||||
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -562,7 +596,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
|
|||
ShapeType type = getShapeType();
|
||||
|
||||
if (type == SHAPE_TYPE_COMPOUND) {
|
||||
if (!_model || _model->getCollisionURL().isEmpty()) {
|
||||
if (!_model || _compoundShapeURL.isEmpty()) {
|
||||
EntityTreePointer tree = getTree();
|
||||
if (tree) {
|
||||
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
|
||||
|
@ -575,15 +609,18 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (_model->isLoaded() && _model->isCollisionLoaded()) {
|
||||
// we have both URLs AND both geometries AND they are both fully loaded.
|
||||
if (_needsInitialSimulation) {
|
||||
// the _model's offset will be wrong until _needsInitialSimulation is false
|
||||
PerformanceTimer perfTimer("_model->simulate");
|
||||
doInitialModelSimulation();
|
||||
if (_model->isLoaded()) {
|
||||
if (_compoundShapeResource && _compoundShapeResource->isLoaded()) {
|
||||
// we have both URLs AND both geometries AND they are both fully loaded.
|
||||
if (_needsInitialSimulation) {
|
||||
// the _model's offset will be wrong until _needsInitialSimulation is false
|
||||
PerformanceTimer perfTimer("_model->simulate");
|
||||
doInitialModelSimulation();
|
||||
}
|
||||
return true;
|
||||
} else if (!_compoundShapeURL.isEmpty()) {
|
||||
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(_compoundShapeURL);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// the model is still being downloaded.
|
||||
|
@ -594,7 +631,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
|
||||
const uint32_t TRIANGLE_STRIDE = 3;
|
||||
const uint32_t QUAD_STRIDE = 4;
|
||||
|
||||
|
@ -605,10 +642,10 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
|
||||
// should never fall in here when collision model not fully loaded
|
||||
// hence we assert that all geometries exist and are loaded
|
||||
assert(_model && _model->isLoaded() && _model->isCollisionLoaded());
|
||||
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry();
|
||||
assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded());
|
||||
const FBXGeometry& collisionGeometry = _compoundShapeResource->getFBXGeometry();
|
||||
|
||||
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
|
||||
pointCollection.clear();
|
||||
uint32_t i = 0;
|
||||
|
||||
|
@ -684,15 +721,14 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size();
|
||||
// multiply each point by scale before handing the point-set off to the physics engine.
|
||||
// also determine the extents of the collision model.
|
||||
glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint());
|
||||
for (int32_t i = 0; i < pointCollection.size(); i++) {
|
||||
for (int32_t j = 0; j < pointCollection[i].size(); j++) {
|
||||
// compensate for registration
|
||||
pointCollection[i][j] += _model->getOffset();
|
||||
// scale so the collision points match the model points
|
||||
pointCollection[i][j] *= scaleToFit;
|
||||
// back compensate for registration so we can apply that offset to the shapeInfo later
|
||||
pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + _model->getOffset()) - registrationOffset;
|
||||
}
|
||||
}
|
||||
info.setParams(type, dimensions, _compoundShapeURL);
|
||||
shapeInfo.setParams(type, dimensions, _compoundShapeURL);
|
||||
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
|
||||
// should never fall in here when model not fully loaded
|
||||
assert(_model && _model->isLoaded());
|
||||
|
@ -705,29 +741,31 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
const FBXGeometry& fbxGeometry = _model->getFBXGeometry();
|
||||
int numFbxMeshes = fbxGeometry.meshes.size();
|
||||
int totalNumVertices = 0;
|
||||
glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
|
||||
for (int i = 0; i < numFbxMeshes; i++) {
|
||||
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
|
||||
if (mesh.clusters.size() > 0) {
|
||||
const FBXCluster& cluster = mesh.clusters.at(0);
|
||||
auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex);
|
||||
localTransforms.push_back(jointMatrix * cluster.inverseBindMatrix);
|
||||
// we backtranslate by the registration offset so we can apply that offset to the shapeInfo later
|
||||
localTransforms.push_back(invRegistraionOffset * jointMatrix * cluster.inverseBindMatrix);
|
||||
} else {
|
||||
glm::mat4 identity;
|
||||
localTransforms.push_back(identity);
|
||||
localTransforms.push_back(invRegistraionOffset);
|
||||
}
|
||||
totalNumVertices += mesh.vertices.size();
|
||||
}
|
||||
const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6;
|
||||
if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) {
|
||||
qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box.";
|
||||
info.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions);
|
||||
shapeInfo.setParams(SHAPE_TYPE_BOX, 0.5f * dimensions);
|
||||
return;
|
||||
}
|
||||
|
||||
auto& meshes = _model->getGeometry()->getMeshes();
|
||||
int32_t numMeshes = (int32_t)(meshes.size());
|
||||
|
||||
ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||
ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
|
||||
pointCollection.clear();
|
||||
if (type == SHAPE_TYPE_SIMPLE_COMPOUND) {
|
||||
pointCollection.resize(numMeshes);
|
||||
|
@ -735,7 +773,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
pointCollection.resize(1);
|
||||
}
|
||||
|
||||
ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices();
|
||||
ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices();
|
||||
triangleIndices.clear();
|
||||
|
||||
Extents extents;
|
||||
|
@ -909,17 +947,30 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
|
|||
}
|
||||
}
|
||||
|
||||
info.setParams(type, 0.5f * dimensions, _modelURL);
|
||||
shapeInfo.setParams(type, 0.5f * dimensions, _modelURL);
|
||||
} else {
|
||||
ModelEntityItem::computeShapeInfo(info);
|
||||
info.setParams(type, 0.5f * dimensions);
|
||||
adjustShapeInfoByRegistration(info);
|
||||
ModelEntityItem::computeShapeInfo(shapeInfo);
|
||||
shapeInfo.setParams(type, 0.5f * dimensions);
|
||||
}
|
||||
// finally apply the registration offset to the shapeInfo
|
||||
adjustShapeInfoByRegistration(shapeInfo);
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::setCollisionShape(const btCollisionShape* shape) {
|
||||
const void* key = static_cast<const void*>(shape);
|
||||
if (_collisionMeshKey != key) {
|
||||
if (_collisionMeshKey) {
|
||||
collisionMeshCache.releaseMesh(_collisionMeshKey);
|
||||
}
|
||||
_collisionMeshKey = key;
|
||||
// toggle _showCollisionGeometry forces re-evaluation later
|
||||
_showCollisionGeometry = !_showCollisionGeometry;
|
||||
}
|
||||
}
|
||||
|
||||
bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
|
||||
if (EntityItem::contains(point) && _model && _model->isCollisionLoaded()) {
|
||||
return _model->getCollisionFBXGeometry().convexHullContains(worldToEntity(point));
|
||||
if (EntityItem::contains(point) && _model && _compoundShapeResource && _compoundShapeResource->isLoaded()) {
|
||||
return _compoundShapeResource->getFBXGeometry().convexHullContains(worldToEntity(point));
|
||||
}
|
||||
|
||||
return false;
|
||||
|
|
|
@ -56,10 +56,13 @@ public:
|
|||
virtual bool needsToCallUpdate() const override;
|
||||
virtual void update(const quint64& now) override;
|
||||
|
||||
virtual void setShapeType(ShapeType type) override;
|
||||
virtual void setCompoundShapeURL(const QString& url) override;
|
||||
|
||||
virtual bool isReadyToComputeShape() override;
|
||||
virtual void computeShapeInfo(ShapeInfo& info) override;
|
||||
virtual void computeShapeInfo(ShapeInfo& shapeInfo) override;
|
||||
|
||||
void setCollisionShape(const btCollisionShape* shape) override;
|
||||
|
||||
virtual bool contains(const glm::vec3& point) const override;
|
||||
|
||||
|
@ -98,6 +101,7 @@ private:
|
|||
QVariantMap parseTexturesToMap(QString textures);
|
||||
void remapTextures();
|
||||
|
||||
GeometryResource::Pointer _compoundShapeResource;
|
||||
ModelPointer _model = nullptr;
|
||||
bool _needsInitialSimulation = true;
|
||||
bool _needsModelReload = true;
|
||||
|
@ -112,11 +116,11 @@ private:
|
|||
|
||||
render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
|
||||
|
||||
bool _showCollisionHull = false;
|
||||
|
||||
bool getAnimationFrame();
|
||||
|
||||
bool _needsJointSimulation { false };
|
||||
bool _showCollisionGeometry { false };
|
||||
const void* _collisionMeshKey { nullptr };
|
||||
};
|
||||
|
||||
#endif // hifi_RenderableModelEntityItem_h
|
||||
|
|
|
@ -122,7 +122,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
|
|||
_model->removeFromScene(scene, pendingChanges);
|
||||
render::Item::Status::Getters statusGetters;
|
||||
makeEntityItemStatusGetters(getThisPointer(), statusGetters);
|
||||
_model->addToScene(scene, pendingChanges, false);
|
||||
_model->addToScene(scene, pendingChanges);
|
||||
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
|
||||
|
|
|
@ -2213,4 +2213,4 @@ void EntityItem::globalizeProperties(EntityItemProperties& properties, const QSt
|
|||
}
|
||||
QUuid empty;
|
||||
properties.setParentID(empty);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,6 +44,7 @@ class EntityTreeElementExtraEncodeData;
|
|||
class EntityActionInterface;
|
||||
class EntityItemProperties;
|
||||
class EntityTree;
|
||||
class btCollisionShape;
|
||||
typedef std::shared_ptr<EntityTree> EntityTreePointer;
|
||||
typedef std::shared_ptr<EntityActionInterface> EntityActionPointer;
|
||||
typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
|
||||
|
@ -324,6 +325,8 @@ public:
|
|||
/// return preferred shape type (actual physical shape may differ)
|
||||
virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; }
|
||||
|
||||
virtual void setCollisionShape(const btCollisionShape* shape) {}
|
||||
|
||||
// updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags
|
||||
virtual void updateRegistrationPoint(const glm::vec3& value);
|
||||
void updatePosition(const glm::vec3& value);
|
||||
|
|
|
@ -40,7 +40,6 @@ class EntityItemFBXService {
|
|||
public:
|
||||
virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) = 0;
|
||||
virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) = 0;
|
||||
virtual const FBXGeometry* getCollisionGeometryForEntity(EntityItemPointer entityItem) = 0;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -282,8 +282,8 @@ ShapeType ModelEntityItem::computeTrueShapeType() const {
|
|||
type = SHAPE_TYPE_COMPOUND;
|
||||
}
|
||||
if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) {
|
||||
// no compoundURL set --> fall back to NONE
|
||||
type = SHAPE_TYPE_NONE;
|
||||
// no compoundURL set --> fall back to SIMPLE_COMPOUND
|
||||
type = SHAPE_TYPE_SIMPLE_COMPOUND;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
|
|
@ -111,13 +111,13 @@ public:
|
|||
|
||||
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
|
||||
|
||||
signals:
|
||||
void finished(bool success);
|
||||
|
||||
private:
|
||||
void startWatching();
|
||||
void stopWatching();
|
||||
|
||||
signals:
|
||||
void finished(bool success);
|
||||
|
||||
private slots:
|
||||
void resourceFinished(bool success);
|
||||
void resourceRefreshed();
|
||||
|
|
|
@ -130,7 +130,7 @@ protected:
|
|||
void evalVertexStream();
|
||||
|
||||
};
|
||||
typedef std::shared_ptr< Mesh > MeshPointer;
|
||||
using MeshPointer = std::shared_ptr< Mesh >;
|
||||
|
||||
|
||||
class Geometry {
|
||||
|
|
|
@ -226,8 +226,6 @@ private:
|
|||
void resetResourceCounters();
|
||||
void removeResource(const QUrl& url, qint64 size = 0);
|
||||
|
||||
void getResourceAsynchronously(const QUrl& url);
|
||||
|
||||
static int _requestLimit;
|
||||
static int _requestsActive;
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
set(TARGET_NAME physics)
|
||||
setup_hifi_library()
|
||||
link_hifi_libraries(shared fbx entities)
|
||||
link_hifi_libraries(shared fbx entities model)
|
||||
|
||||
target_bullet()
|
||||
|
|
213
libraries/physics/src/CollisionRenderMeshCache.cpp
Normal file
213
libraries/physics/src/CollisionRenderMeshCache.cpp
Normal file
|
@ -0,0 +1,213 @@
|
|||
//
|
||||
// CollisionRenderMeshCache.cpp
|
||||
// libraries/physcis/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];
|
||||
model::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
|
||||
model::Mesh::Part part;
|
||||
part._startIndex = (model::Index)indices.getNumElements();
|
||||
part._numIndices = (model::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(model::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);
|
||||
model::Index indexOffset = (model::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(model::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).normalize();
|
||||
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;
|
||||
}
|
||||
|
||||
model::MeshPointer createMeshFromShape(const void* pointer) {
|
||||
model::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<model::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();
|
||||
}
|
||||
|
||||
model::MeshPointer CollisionRenderMeshCache::getMesh(CollisionRenderMeshCache::Key key) {
|
||||
model::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();
|
||||
}
|
||||
|
48
libraries/physics/src/CollisionRenderMeshCache.h
Normal file
48
libraries/physics/src/CollisionRenderMeshCache.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// CollisionRenderMeshCache.h
|
||||
// libraries/physcis/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 <model/Geometry.h>
|
||||
|
||||
|
||||
class CollisionRenderMeshCache {
|
||||
public:
|
||||
using Key = const void*; // must actually be a const btCollisionShape*
|
||||
|
||||
CollisionRenderMeshCache();
|
||||
~CollisionRenderMeshCache();
|
||||
|
||||
/// \return pointer to geometry
|
||||
model::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, model::MeshPointer>;
|
||||
CollisionMeshMap _meshMap;
|
||||
std::vector<Key> _pendingGarbage;
|
||||
};
|
||||
|
||||
#endif // hifi_CollisionRenderMeshCache_h
|
|
@ -46,7 +46,7 @@ bool entityTreeIsLocked() {
|
|||
|
||||
|
||||
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) :
|
||||
ObjectMotionState(shape),
|
||||
ObjectMotionState(nullptr),
|
||||
_entityPtr(entity),
|
||||
_entity(entity.get()),
|
||||
_serverPosition(0.0f),
|
||||
|
@ -71,6 +71,9 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
|||
assert(_entity);
|
||||
assert(entityTreeIsLocked());
|
||||
setMass(_entity->computeMass());
|
||||
// we need the side-effects of EntityMotionState::setShape() so we call it explicitly here
|
||||
// rather than pass the legit shape pointer to the ObjectMotionState ctor above.
|
||||
setShape(shape);
|
||||
}
|
||||
|
||||
EntityMotionState::~EntityMotionState() {
|
||||
|
@ -264,13 +267,20 @@ bool EntityMotionState::isReadyToComputeShape() const {
|
|||
}
|
||||
|
||||
// virtual and protected
|
||||
btCollisionShape* EntityMotionState::computeNewShape() {
|
||||
const btCollisionShape* EntityMotionState::computeNewShape() {
|
||||
ShapeInfo shapeInfo;
|
||||
assert(entityTreeIsLocked());
|
||||
_entity->computeShapeInfo(shapeInfo);
|
||||
return getShapeManager()->getShape(shapeInfo);
|
||||
}
|
||||
|
||||
void EntityMotionState::setShape(const btCollisionShape* shape) {
|
||||
if (_shape != shape) {
|
||||
ObjectMotionState::setShape(shape);
|
||||
_entity->setCollisionShape(_shape);
|
||||
}
|
||||
}
|
||||
|
||||
bool EntityMotionState::isCandidateForOwnership() const {
|
||||
assert(_body);
|
||||
assert(_entity);
|
||||
|
|
|
@ -88,9 +88,10 @@ protected:
|
|||
bool entityTreeIsLocked() const;
|
||||
#endif
|
||||
|
||||
virtual bool isReadyToComputeShape() const override;
|
||||
virtual btCollisionShape* computeNewShape() override;
|
||||
virtual void setMotionType(PhysicsMotionType motionType);
|
||||
bool isReadyToComputeShape() const override;
|
||||
const btCollisionShape* computeNewShape() override;
|
||||
void setShape(const btCollisionShape* shape) override;
|
||||
void setMotionType(PhysicsMotionType motionType) override;
|
||||
|
||||
// In the glorious future (when entities lib depends on physics lib) the EntityMotionState will be
|
||||
// properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that
|
||||
|
|
|
@ -62,7 +62,7 @@ ShapeManager* ObjectMotionState::getShapeManager() {
|
|||
return shapeManager;
|
||||
}
|
||||
|
||||
ObjectMotionState::ObjectMotionState(btCollisionShape* shape) :
|
||||
ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) :
|
||||
_motionType(MOTION_TYPE_STATIC),
|
||||
_shape(shape),
|
||||
_body(nullptr),
|
||||
|
@ -73,7 +73,7 @@ ObjectMotionState::ObjectMotionState(btCollisionShape* shape) :
|
|||
|
||||
ObjectMotionState::~ObjectMotionState() {
|
||||
assert(!_body);
|
||||
releaseShape();
|
||||
setShape(nullptr);
|
||||
_type = MOTIONSTATE_TYPE_INVALID;
|
||||
}
|
||||
|
||||
|
@ -114,13 +114,6 @@ glm::vec3 ObjectMotionState::getBodyAngularVelocity() const {
|
|||
return bulletToGLM(_body->getAngularVelocity());
|
||||
}
|
||||
|
||||
void ObjectMotionState::releaseShape() {
|
||||
if (_shape) {
|
||||
shapeManager->releaseShape(_shape);
|
||||
_shape = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::setMotionType(PhysicsMotionType motionType) {
|
||||
_motionType = motionType;
|
||||
}
|
||||
|
@ -160,11 +153,21 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
|
|||
_body = body;
|
||||
if (_body) {
|
||||
_body->setUserPointer(this);
|
||||
assert(_body->getCollisionShape() == _shape);
|
||||
}
|
||||
updateCCDConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::setShape(const btCollisionShape* shape) {
|
||||
if (_shape != shape) {
|
||||
if (_shape) {
|
||||
getShapeManager()->releaseShape(_shape);
|
||||
}
|
||||
_shape = shape;
|
||||
}
|
||||
}
|
||||
|
||||
void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
|
||||
if (flags & Simulation::DIRTY_POSITION) {
|
||||
btTransform worldTrans = _body->getWorldTransform();
|
||||
|
@ -251,7 +254,7 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine*
|
|||
if (!isReadyToComputeShape()) {
|
||||
return false;
|
||||
}
|
||||
btCollisionShape* newShape = computeNewShape();
|
||||
const btCollisionShape* newShape = computeNewShape();
|
||||
if (!newShape) {
|
||||
qCDebug(physics) << "Warning: failed to generate new shape!";
|
||||
// failed to generate new shape! --> keep old shape and remove shape-change flag
|
||||
|
@ -265,15 +268,15 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine*
|
|||
return true;
|
||||
}
|
||||
}
|
||||
getShapeManager()->releaseShape(_shape);
|
||||
if (_shape != newShape) {
|
||||
_shape = newShape;
|
||||
_body->setCollisionShape(_shape);
|
||||
|
||||
updateCCDConfiguration();
|
||||
} else {
|
||||
// huh... the shape didn't actually change, so we clear the DIRTY_SHAPE flag
|
||||
if (_shape == newShape) {
|
||||
// the shape didn't actually change, so we clear the DIRTY_SHAPE flag
|
||||
flags &= ~Simulation::DIRTY_SHAPE;
|
||||
// and clear the reference we just created
|
||||
getShapeManager()->releaseShape(_shape);
|
||||
} else {
|
||||
_body->setCollisionShape(const_cast<btCollisionShape*>(newShape));
|
||||
setShape(newShape);
|
||||
updateCCDConfiguration();
|
||||
}
|
||||
}
|
||||
if (flags & EASY_DIRTY_PHYSICS_FLAGS) {
|
||||
|
|
|
@ -78,7 +78,7 @@ public:
|
|||
static void setShapeManager(ShapeManager* manager);
|
||||
static ShapeManager* getShapeManager();
|
||||
|
||||
ObjectMotionState(btCollisionShape* shape);
|
||||
ObjectMotionState(const btCollisionShape* shape);
|
||||
~ObjectMotionState();
|
||||
|
||||
virtual void handleEasyChanges(uint32_t& flags);
|
||||
|
@ -110,11 +110,9 @@ public:
|
|||
|
||||
virtual PhysicsMotionType computePhysicsMotionType() const = 0;
|
||||
|
||||
btCollisionShape* getShape() const { return _shape; }
|
||||
const btCollisionShape* getShape() const { return _shape; }
|
||||
btRigidBody* getRigidBody() const { return _body; }
|
||||
|
||||
void releaseShape();
|
||||
|
||||
virtual bool isMoving() const = 0;
|
||||
|
||||
// These pure virtual methods must be implemented for each MotionState type
|
||||
|
@ -152,16 +150,17 @@ public:
|
|||
|
||||
protected:
|
||||
virtual bool isReadyToComputeShape() const = 0;
|
||||
virtual btCollisionShape* computeNewShape() = 0;
|
||||
void setMotionType(PhysicsMotionType motionType);
|
||||
virtual const btCollisionShape* computeNewShape() = 0;
|
||||
virtual void setMotionType(PhysicsMotionType motionType);
|
||||
void updateCCDConfiguration();
|
||||
|
||||
void setRigidBody(btRigidBody* body);
|
||||
virtual void setShape(const btCollisionShape* shape);
|
||||
|
||||
MotionStateType _type = MOTIONSTATE_TYPE_INVALID; // type of MotionState
|
||||
PhysicsMotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC
|
||||
|
||||
btCollisionShape* _shape;
|
||||
const btCollisionShape* _shape;
|
||||
btRigidBody* _body;
|
||||
float _mass;
|
||||
|
||||
|
|
|
@ -225,7 +225,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
|
|||
<< "at" << entity->getPosition() << " will be reduced";
|
||||
}
|
||||
}
|
||||
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo);
|
||||
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
|
||||
if (shape) {
|
||||
EntityMotionState* motionState = new EntityMotionState(shape, entity);
|
||||
entity->setPhysicsInfo(static_cast<void*>(motionState));
|
||||
|
|
|
@ -76,7 +76,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
|
|||
switch(motionType) {
|
||||
case MOTION_TYPE_KINEMATIC: {
|
||||
if (!body) {
|
||||
btCollisionShape* shape = motionState->getShape();
|
||||
btCollisionShape* shape = const_cast<btCollisionShape*>(motionState->getShape());
|
||||
assert(shape);
|
||||
body = new btRigidBody(mass, motionState, shape, inertia);
|
||||
motionState->setRigidBody(body);
|
||||
|
@ -93,7 +93,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
|
|||
}
|
||||
case MOTION_TYPE_DYNAMIC: {
|
||||
mass = motionState->getMass();
|
||||
btCollisionShape* shape = motionState->getShape();
|
||||
btCollisionShape* shape = const_cast<btCollisionShape*>(motionState->getShape());
|
||||
assert(shape);
|
||||
shape->calculateLocalInertia(mass, inertia);
|
||||
if (!body) {
|
||||
|
@ -120,7 +120,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
|
|||
default: {
|
||||
if (!body) {
|
||||
assert(motionState->getShape());
|
||||
body = new btRigidBody(mass, motionState, motionState->getShape(), inertia);
|
||||
body = new btRigidBody(mass, motionState, const_cast<btCollisionShape*>(motionState->getShape()), inertia);
|
||||
motionState->setRigidBody(body);
|
||||
} else {
|
||||
body->setMassProps(mass, inertia);
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
//
|
||||
|
||||
#include <glm/gtx/norm.hpp>
|
||||
#include <BulletCollision/CollisionShapes/btShapeHull.h>
|
||||
|
||||
#include <SharedUtil.h> // for MILLIMETERS_PER_METER
|
||||
|
||||
|
@ -248,7 +247,7 @@ void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
|
|||
delete dataArray;
|
||||
}
|
||||
|
||||
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
||||
const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
||||
btCollisionShape* shape = NULL;
|
||||
int type = info.getType();
|
||||
switch(type) {
|
||||
|
@ -347,23 +346,39 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
|
|||
}
|
||||
if (shape) {
|
||||
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
|
||||
// this shape has an offset, which we support by wrapping the true shape
|
||||
// in a btCompoundShape with a local transform
|
||||
auto compound = new btCompoundShape();
|
||||
btTransform trans;
|
||||
trans.setIdentity();
|
||||
trans.setOrigin(glmToBullet(info.getOffset()));
|
||||
compound->addChildShape(trans, shape);
|
||||
shape = compound;
|
||||
// we need to apply an offset
|
||||
btTransform offset;
|
||||
offset.setIdentity();
|
||||
offset.setOrigin(glmToBullet(info.getOffset()));
|
||||
|
||||
if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
|
||||
// this shape is already compound
|
||||
// walk through the child shapes and adjust their transforms
|
||||
btCompoundShape* compound = static_cast<btCompoundShape*>(shape);
|
||||
int32_t numSubShapes = compound->getNumChildShapes();
|
||||
for (int32_t i = 0; i < numSubShapes; ++i) {
|
||||
compound->updateChildTransform(i, offset * compound->getChildTransform(i), false);
|
||||
}
|
||||
compound->recalculateLocalAabb();
|
||||
} else {
|
||||
// wrap this shape in a compound
|
||||
auto compound = new btCompoundShape();
|
||||
compound->addChildShape(offset, shape);
|
||||
shape = compound;
|
||||
}
|
||||
}
|
||||
}
|
||||
return shape;
|
||||
}
|
||||
|
||||
void ShapeFactory::deleteShape(btCollisionShape* shape) {
|
||||
void ShapeFactory::deleteShape(const btCollisionShape* shape) {
|
||||
assert(shape);
|
||||
if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
|
||||
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(shape);
|
||||
// ShapeFactory is responsible for deleting all shapes, even the const ones that are stored
|
||||
// in the ShapeManager, so we must cast to non-const here when deleting.
|
||||
// so we cast to non-const here when deleting memory.
|
||||
btCollisionShape* nonConstShape = const_cast<btCollisionShape*>(shape);
|
||||
if (nonConstShape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) {
|
||||
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(nonConstShape);
|
||||
const int numChildShapes = compoundShape->getNumChildShapes();
|
||||
for (int i = 0; i < numChildShapes; i ++) {
|
||||
btCollisionShape* childShape = compoundShape->getChildShape(i);
|
||||
|
@ -375,7 +390,7 @@ void ShapeFactory::deleteShape(btCollisionShape* shape) {
|
|||
}
|
||||
}
|
||||
}
|
||||
delete shape;
|
||||
delete nonConstShape;
|
||||
}
|
||||
|
||||
// the dataArray must be created before we create the StaticMeshShape
|
||||
|
|
|
@ -20,8 +20,8 @@
|
|||
// translates between ShapeInfo and btShape
|
||||
|
||||
namespace ShapeFactory {
|
||||
btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
|
||||
void deleteShape(btCollisionShape* shape);
|
||||
const btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
|
||||
void deleteShape(const btCollisionShape* shape);
|
||||
|
||||
//btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info);
|
||||
//void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray);
|
||||
|
|
|
@ -28,15 +28,15 @@ ShapeManager::~ShapeManager() {
|
|||
_shapeMap.clear();
|
||||
}
|
||||
|
||||
btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
|
||||
const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
|
||||
if (info.getType() == SHAPE_TYPE_NONE) {
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
|
||||
if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) {
|
||||
// tiny shapes are not supported
|
||||
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
|
||||
return NULL;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
DoubleHashKey key = info.getHash();
|
||||
|
@ -45,7 +45,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
|
|||
shapeRef->refCount++;
|
||||
return shapeRef->shape;
|
||||
}
|
||||
btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
|
||||
const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
|
||||
if (shape) {
|
||||
ShapeReference newRef;
|
||||
newRef.refCount = 1;
|
||||
|
|
|
@ -26,7 +26,7 @@ public:
|
|||
~ShapeManager();
|
||||
|
||||
/// \return pointer to shape
|
||||
btCollisionShape* getShape(const ShapeInfo& info);
|
||||
const btCollisionShape* getShape(const ShapeInfo& info);
|
||||
|
||||
/// \return true if shape was found and released
|
||||
bool releaseShape(const btCollisionShape* shape);
|
||||
|
@ -43,11 +43,12 @@ public:
|
|||
private:
|
||||
bool releaseShapeByKey(const DoubleHashKey& key);
|
||||
|
||||
struct ShapeReference {
|
||||
class ShapeReference {
|
||||
public:
|
||||
int refCount;
|
||||
btCollisionShape* shape;
|
||||
const btCollisionShape* shape;
|
||||
DoubleHashKey key;
|
||||
ShapeReference() : refCount(0), shape(NULL) {}
|
||||
ShapeReference() : refCount(0), shape(nullptr) {}
|
||||
};
|
||||
|
||||
btHashMap<DoubleHashKey, ShapeReference> _shapeMap;
|
||||
|
|
|
@ -46,11 +46,9 @@ template <> void payloadRender(const MeshPartPayload::Pointer& payload, RenderAr
|
|||
}
|
||||
}
|
||||
|
||||
MeshPartPayload::MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material, const Transform& transform, const Transform& offsetTransform) {
|
||||
|
||||
MeshPartPayload::MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material) {
|
||||
updateMeshPart(mesh, partIndex);
|
||||
updateMaterial(material);
|
||||
updateTransform(transform, offsetTransform);
|
||||
}
|
||||
|
||||
void MeshPartPayload::updateMeshPart(const std::shared_ptr<const model::Mesh>& drawMesh, int partIndex) {
|
||||
|
@ -414,8 +412,7 @@ ShapeKey ModelMeshPartPayload::getShapeKey() const {
|
|||
// if our index is ever out of range for either meshes or networkMeshes, then skip it, and set our _meshGroupsKnown
|
||||
// to false to rebuild out mesh groups.
|
||||
if (_meshIndex < 0 || _meshIndex >= (int)networkMeshes.size() || _meshIndex > geometry.meshes.size()) {
|
||||
_model->_meshGroupsKnown = false; // regenerate these lists next time around.
|
||||
_model->_readyWhenAdded = false; // in case any of our users are using scenes
|
||||
_model->_needsFixupInScene = true; // trigger remove/add cycle
|
||||
_model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
|
||||
return ShapeKey::Builder::invalid();
|
||||
}
|
||||
|
@ -533,7 +530,7 @@ void ModelMeshPartPayload::startFade() {
|
|||
void ModelMeshPartPayload::render(RenderArgs* args) const {
|
||||
PerformanceTimer perfTimer("ModelMeshPartPayload::render");
|
||||
|
||||
if (!_model->_readyWhenAdded || !_model->_isVisible) {
|
||||
if (!_model->addedToScene() || !_model->isVisible()) {
|
||||
return; // bail asap
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,7 @@ class Model;
|
|||
class MeshPartPayload {
|
||||
public:
|
||||
MeshPartPayload() {}
|
||||
MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material, const Transform& transform, const Transform& offsetTransform);
|
||||
MeshPartPayload(const std::shared_ptr<const model::Mesh>& mesh, int partIndex, model::MaterialPointer material);
|
||||
|
||||
typedef render::Payload<MeshPartPayload> Payload;
|
||||
typedef Payload::DataPointer Pointer;
|
||||
|
|
|
@ -37,9 +37,9 @@ float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
|
|||
#define HTTP_INVALID_COM "http://invalid.com"
|
||||
|
||||
const int NUM_COLLISION_HULL_COLORS = 24;
|
||||
std::vector<model::MaterialPointer> _collisionHullMaterials;
|
||||
std::vector<model::MaterialPointer> _collisionMaterials;
|
||||
|
||||
void initCollisionHullMaterials() {
|
||||
void initCollisionMaterials() {
|
||||
// generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums
|
||||
// (no browns, greys, or dark shades)
|
||||
float component[NUM_COLLISION_HULL_COLORS] = {
|
||||
|
@ -50,7 +50,7 @@ void initCollisionHullMaterials() {
|
|||
1.0f, 1.0f, 1.0f, 1.0f,
|
||||
0.8f, 0.6f, 0.4f, 0.2f
|
||||
};
|
||||
_collisionHullMaterials.reserve(NUM_COLLISION_HULL_COLORS);
|
||||
_collisionMaterials.reserve(NUM_COLLISION_HULL_COLORS);
|
||||
|
||||
// each component gets the same cuve
|
||||
// but offset by a multiple of one third the full width
|
||||
|
@ -72,7 +72,7 @@ void initCollisionHullMaterials() {
|
|||
material->setAlbedo(glm::vec3(red, green, blue));
|
||||
material->setMetallic(0.02f);
|
||||
material->setRoughness(0.5f);
|
||||
_collisionHullMaterials.push_back(material);
|
||||
_collisionMaterials.push_back(material);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +82,6 @@ Model::Model(RigPointer rig, QObject* parent) :
|
|||
_renderGeometry(),
|
||||
_collisionGeometry(),
|
||||
_renderWatcher(_renderGeometry),
|
||||
_collisionWatcher(_collisionGeometry),
|
||||
_translation(0.0f),
|
||||
_rotation(),
|
||||
_scale(1.0f, 1.0f, 1.0f),
|
||||
|
@ -100,7 +99,6 @@ Model::Model(RigPointer rig, QObject* parent) :
|
|||
_calculatedMeshPartBoxesValid(false),
|
||||
_calculatedMeshBoxesValid(false),
|
||||
_calculatedMeshTrianglesValid(false),
|
||||
_meshGroupsKnown(false),
|
||||
_isWireframe(false),
|
||||
_rig(rig)
|
||||
{
|
||||
|
@ -112,7 +110,6 @@ Model::Model(RigPointer rig, QObject* parent) :
|
|||
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
|
||||
|
||||
connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished);
|
||||
connect(&_collisionWatcher, &GeometryResourceWatcher::finished, this, &Model::loadCollisionModelURLFinished);
|
||||
}
|
||||
|
||||
Model::~Model() {
|
||||
|
@ -122,18 +119,11 @@ Model::~Model() {
|
|||
AbstractViewStateInterface* Model::_viewState = NULL;
|
||||
|
||||
bool Model::needsFixupInScene() const {
|
||||
if (readyToAddToScene()) {
|
||||
if (_needsUpdateTextures && _renderGeometry->areTexturesLoaded()) {
|
||||
_needsUpdateTextures = false;
|
||||
return true;
|
||||
}
|
||||
if (!_readyWhenAdded) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return (_needsFixupInScene || !_addedToScene) && !_needsReload && isLoaded();
|
||||
}
|
||||
|
||||
// TODO?: should we combine translation and rotation into single method to avoid double-work?
|
||||
// (figure out where we call these)
|
||||
void Model::setTranslation(const glm::vec3& translation) {
|
||||
_translation = translation;
|
||||
updateRenderItems();
|
||||
|
@ -172,7 +162,15 @@ void Model::setOffset(const glm::vec3& offset) {
|
|||
}
|
||||
|
||||
void Model::updateRenderItems() {
|
||||
if (!_addedToScene) {
|
||||
return;
|
||||
}
|
||||
|
||||
glm::vec3 scale = getScale();
|
||||
if (_collisionGeometry) {
|
||||
// _collisionGeometry is already scaled
|
||||
scale = glm::vec3(1.0f);
|
||||
}
|
||||
_needsUpdateClusterMatrices = true;
|
||||
_renderItemsNeedUpdate = false;
|
||||
|
||||
|
@ -180,7 +178,7 @@ void Model::updateRenderItems() {
|
|||
// the application will ensure only the last lambda is actually invoked.
|
||||
void* key = (void*)this;
|
||||
std::weak_ptr<Model> weakSelf = shared_from_this();
|
||||
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf]() {
|
||||
AbstractViewStateInterface::instance()->pushPostUpdateLambda(key, [weakSelf, scale]() {
|
||||
|
||||
// do nothing, if the model has already been destroyed.
|
||||
auto self = weakSelf.lock();
|
||||
|
@ -191,7 +189,7 @@ void Model::updateRenderItems() {
|
|||
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
|
||||
|
||||
Transform modelTransform;
|
||||
modelTransform.setScale(self->_scale);
|
||||
modelTransform.setScale(scale);
|
||||
modelTransform.setTranslation(self->_translation);
|
||||
modelTransform.setRotation(self->_rotation);
|
||||
|
||||
|
@ -203,10 +201,6 @@ void Model::updateRenderItems() {
|
|||
modelMeshOffset.postTranslate(self->_offset);
|
||||
}
|
||||
|
||||
// only apply offset only, collision mesh does not share the same unit scale as the FBX file's mesh.
|
||||
Transform collisionMeshOffset;
|
||||
collisionMeshOffset.postTranslate(self->_offset);
|
||||
|
||||
uint32_t deleteGeometryCounter = self->_deleteGeometryCounter;
|
||||
|
||||
render::PendingChanges pendingChanges;
|
||||
|
@ -227,6 +221,9 @@ void Model::updateRenderItems() {
|
|||
});
|
||||
}
|
||||
|
||||
// collision mesh does not share the same unit scale as the FBX file's mesh: only apply offset
|
||||
Transform collisionMeshOffset;
|
||||
collisionMeshOffset.setIdentity();
|
||||
foreach (auto itemID, self->_collisionRenderItems.keys()) {
|
||||
pendingChanges.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) {
|
||||
// update the model transform for this render item.
|
||||
|
@ -574,8 +571,8 @@ void Model::renderSetup(RenderArgs* args) {
|
|||
}
|
||||
}
|
||||
|
||||
if (!_meshGroupsKnown && isLoaded()) {
|
||||
segregateMeshGroups();
|
||||
if (!_addedToScene && isLoaded()) {
|
||||
createRenderItemSet();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -596,43 +593,46 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scen
|
|||
|
||||
bool Model::addToScene(std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges,
|
||||
render::Item::Status::Getters& statusGetters,
|
||||
bool showCollisionHull) {
|
||||
if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) {
|
||||
_showCollisionHull = showCollisionHull;
|
||||
segregateMeshGroups();
|
||||
render::Item::Status::Getters& statusGetters) {
|
||||
bool readyToRender = _collisionGeometry || isLoaded();
|
||||
if (!_addedToScene && readyToRender) {
|
||||
createRenderItemSet();
|
||||
}
|
||||
|
||||
bool somethingAdded = false;
|
||||
|
||||
if (_modelMeshRenderItems.empty()) {
|
||||
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||
if (statusGetters.size()) {
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
if (_collisionGeometry) {
|
||||
if (_collisionRenderItems.empty()) {
|
||||
foreach (auto renderItem, _collisionRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||
if (statusGetters.size()) {
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
}
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
_collisionRenderItems.insert(item, renderPayload);
|
||||
}
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
_modelMeshRenderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
somethingAdded = !_collisionRenderItems.empty();
|
||||
}
|
||||
}
|
||||
if (_collisionRenderItems.empty()) {
|
||||
foreach (auto renderItem, _collisionRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||
if (statusGetters.size()) {
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
} else {
|
||||
if (_modelMeshRenderItems.empty()) {
|
||||
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||
if (statusGetters.size()) {
|
||||
renderPayload->addStatusGetters(statusGetters);
|
||||
}
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
_modelMeshRenderItems.insert(item, renderPayload);
|
||||
}
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
_collisionRenderItems.insert(item, renderPayload);
|
||||
somethingAdded = true;
|
||||
somethingAdded = !_modelMeshRenderItems.empty();
|
||||
}
|
||||
}
|
||||
|
||||
updateRenderItems();
|
||||
|
||||
_readyWhenAdded = readyToAddToScene();
|
||||
if (somethingAdded) {
|
||||
_addedToScene = true;
|
||||
updateRenderItems();
|
||||
_needsFixupInScene = false;
|
||||
}
|
||||
|
||||
return somethingAdded;
|
||||
}
|
||||
|
@ -643,13 +643,13 @@ void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::Pendin
|
|||
}
|
||||
_modelMeshRenderItems.clear();
|
||||
_modelMeshRenderItemsSet.clear();
|
||||
|
||||
foreach (auto item, _collisionRenderItems.keys()) {
|
||||
pendingChanges.removeItem(item);
|
||||
}
|
||||
_collisionRenderItems.clear();
|
||||
_collisionRenderItemsSet.clear();
|
||||
_meshGroupsKnown = false;
|
||||
_readyWhenAdded = false;
|
||||
_addedToScene = false;
|
||||
}
|
||||
|
||||
void Model::renderDebugMeshBoxes(gpu::Batch& batch) {
|
||||
|
@ -804,6 +804,7 @@ int Model::getLastFreeJointIndex(int jointIndex) const {
|
|||
void Model::setTextures(const QVariantMap& textures) {
|
||||
if (isLoaded()) {
|
||||
_needsUpdateTextures = true;
|
||||
_needsFixupInScene = true;
|
||||
_renderGeometry->setTextures(textures);
|
||||
}
|
||||
}
|
||||
|
@ -825,8 +826,8 @@ void Model::setURL(const QUrl& url) {
|
|||
|
||||
_needsReload = true;
|
||||
_needsUpdateTextures = true;
|
||||
_meshGroupsKnown = false;
|
||||
_visualGeometryRequestFailed = false;
|
||||
_needsFixupInScene = true;
|
||||
invalidCalculatedMeshBoxes();
|
||||
deleteGeometry();
|
||||
|
||||
|
@ -843,23 +844,6 @@ void Model::loadURLFinished(bool success) {
|
|||
emit setURLFinished(success);
|
||||
}
|
||||
|
||||
void Model::setCollisionModelURL(const QUrl& url) {
|
||||
if (_collisionUrl == url && _collisionWatcher.getURL() == url) {
|
||||
return;
|
||||
}
|
||||
_collisionUrl = url;
|
||||
_collisionGeometryRequestFailed = false;
|
||||
_collisionWatcher.setResource(DependencyManager::get<ModelCache>()->getGeometryResource(url));
|
||||
}
|
||||
|
||||
void Model::loadCollisionModelURLFinished(bool success) {
|
||||
if (!success) {
|
||||
_collisionGeometryRequestFailed = true;
|
||||
}
|
||||
|
||||
emit setCollisionModelURLFinished(success);
|
||||
}
|
||||
|
||||
bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
|
||||
return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
|
||||
}
|
||||
|
@ -1236,21 +1220,21 @@ AABox Model::getRenderableMeshBound() const {
|
|||
}
|
||||
}
|
||||
|
||||
void Model::segregateMeshGroups() {
|
||||
Geometry::Pointer geometry;
|
||||
bool showingCollisionHull = false;
|
||||
if (_showCollisionHull && _collisionGeometry) {
|
||||
if (isCollisionLoaded()) {
|
||||
geometry = _collisionGeometry;
|
||||
showingCollisionHull = true;
|
||||
} else {
|
||||
return;
|
||||
void Model::createRenderItemSet() {
|
||||
if (_collisionGeometry) {
|
||||
if (_collisionRenderItemsSet.empty()) {
|
||||
createCollisionRenderItemSet();
|
||||
}
|
||||
} else {
|
||||
assert(isLoaded());
|
||||
geometry = _renderGeometry;
|
||||
if (_modelMeshRenderItemsSet.empty()) {
|
||||
createVisibleRenderItemSet();
|
||||
}
|
||||
}
|
||||
const auto& meshes = geometry->getMeshes();
|
||||
};
|
||||
|
||||
void Model::createVisibleRenderItemSet() {
|
||||
assert(isLoaded());
|
||||
const auto& meshes = _renderGeometry->getMeshes();
|
||||
|
||||
// all of our mesh vectors must match in size
|
||||
if ((int)meshes.size() != _meshStates.size()) {
|
||||
|
@ -1259,13 +1243,9 @@ void Model::segregateMeshGroups() {
|
|||
}
|
||||
|
||||
// We should not have any existing renderItems if we enter this section of code
|
||||
Q_ASSERT(_modelMeshRenderItems.isEmpty());
|
||||
Q_ASSERT(_modelMeshRenderItemsSet.isEmpty());
|
||||
Q_ASSERT(_collisionRenderItems.isEmpty());
|
||||
Q_ASSERT(_collisionRenderItemsSet.isEmpty());
|
||||
|
||||
_modelMeshRenderItemsSet.clear();
|
||||
_collisionRenderItemsSet.clear();
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(_translation);
|
||||
|
@ -1280,60 +1260,117 @@ void Model::segregateMeshGroups() {
|
|||
uint32_t numMeshes = (uint32_t)meshes.size();
|
||||
for (uint32_t i = 0; i < numMeshes; i++) {
|
||||
const auto& mesh = meshes.at(i);
|
||||
if (mesh) {
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the render payloads
|
||||
int numParts = (int)mesh->getNumParts();
|
||||
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
||||
if (showingCollisionHull) {
|
||||
if (_collisionHullMaterials.empty()) {
|
||||
initCollisionHullMaterials();
|
||||
}
|
||||
_collisionRenderItemsSet << std::make_shared<MeshPartPayload>(mesh, partIndex, _collisionHullMaterials[partIndex % NUM_COLLISION_HULL_COLORS], transform, offset);
|
||||
} else {
|
||||
_modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
|
||||
}
|
||||
|
||||
shapeID++;
|
||||
}
|
||||
// Create the render payloads
|
||||
int numParts = (int)mesh->getNumParts();
|
||||
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
||||
_modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
|
||||
shapeID++;
|
||||
}
|
||||
}
|
||||
_meshGroupsKnown = true;
|
||||
}
|
||||
|
||||
void Model::createCollisionRenderItemSet() {
|
||||
assert((bool)_collisionGeometry);
|
||||
if (_collisionMaterials.empty()) {
|
||||
initCollisionMaterials();
|
||||
}
|
||||
|
||||
const auto& meshes = _collisionGeometry->getMeshes();
|
||||
|
||||
// We should not have any existing renderItems if we enter this section of code
|
||||
Q_ASSERT(_collisionRenderItemsSet.isEmpty());
|
||||
|
||||
Transform identity;
|
||||
identity.setIdentity();
|
||||
Transform offset;
|
||||
offset.postTranslate(_offset);
|
||||
|
||||
// Run through all of the meshes, and place them into their segregated, but unsorted buckets
|
||||
uint32_t numMeshes = (uint32_t)meshes.size();
|
||||
for (uint32_t i = 0; i < numMeshes; i++) {
|
||||
const auto& mesh = meshes.at(i);
|
||||
if (!mesh) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Create the render payloads
|
||||
int numParts = (int)mesh->getNumParts();
|
||||
for (int partIndex = 0; partIndex < numParts; partIndex++) {
|
||||
model::MaterialPointer& material = _collisionMaterials[partIndex % NUM_COLLISION_HULL_COLORS];
|
||||
auto payload = std::make_shared<MeshPartPayload>(mesh, partIndex, material);
|
||||
payload->updateTransform(identity, offset);
|
||||
_collisionRenderItemsSet << payload;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool Model::isRenderable() const {
|
||||
return !_meshStates.isEmpty() || (isLoaded() && _renderGeometry->getMeshes().empty());
|
||||
}
|
||||
|
||||
bool Model::initWhenReady(render::ScenePointer scene) {
|
||||
if (isActive() && isRenderable() && !_meshGroupsKnown && isLoaded()) {
|
||||
segregateMeshGroups();
|
||||
// NOTE: this only called by SkeletonModel
|
||||
if (_addedToScene || !isRenderable()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
render::PendingChanges pendingChanges;
|
||||
createRenderItemSet();
|
||||
|
||||
Transform transform;
|
||||
transform.setTranslation(_translation);
|
||||
transform.setRotation(_rotation);
|
||||
render::PendingChanges pendingChanges;
|
||||
|
||||
Transform offset;
|
||||
offset.setScale(_scale);
|
||||
offset.postTranslate(_offset);
|
||||
|
||||
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||
_modelMeshRenderItems.insert(item, renderPayload);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
}
|
||||
bool addedPendingChanges = false;
|
||||
if (_collisionGeometry) {
|
||||
foreach (auto renderItem, _collisionRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
|
||||
_collisionRenderItems.insert(item, renderPayload);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
}
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
updateRenderItems();
|
||||
|
||||
_readyWhenAdded = true;
|
||||
return true;
|
||||
addedPendingChanges = !_collisionRenderItems.empty();
|
||||
} else {
|
||||
foreach (auto renderItem, _modelMeshRenderItemsSet) {
|
||||
auto item = scene->allocateID();
|
||||
auto renderPayload = std::make_shared<ModelMeshPartPayload::Payload>(renderItem);
|
||||
_modelMeshRenderItems.insert(item, renderPayload);
|
||||
pendingChanges.resetItem(item, renderPayload);
|
||||
}
|
||||
addedPendingChanges = !_modelMeshRenderItems.empty();
|
||||
}
|
||||
return false;
|
||||
_addedToScene = addedPendingChanges;
|
||||
if (addedPendingChanges) {
|
||||
scene->enqueuePendingChanges(pendingChanges);
|
||||
// NOTE: updateRender items enqueues identical pendingChanges (using a lambda)
|
||||
// so it looks like we're doing double work here, but I don't want to remove the call
|
||||
// for fear there is some side effect we'll miss. -- Andrew 2016.07.21
|
||||
// TODO: figure out if we really need this call to updateRenderItems() or not.
|
||||
updateRenderItems();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
class CollisionRenderGeometry : public Geometry {
|
||||
public:
|
||||
CollisionRenderGeometry(model::MeshPointer mesh) {
|
||||
_fbxGeometry = std::make_shared<FBXGeometry>();
|
||||
std::shared_ptr<GeometryMeshes> meshes = std::make_shared<GeometryMeshes>();
|
||||
meshes->push_back(mesh);
|
||||
_meshes = meshes;
|
||||
_meshParts = std::shared_ptr<const GeometryMeshParts>();
|
||||
}
|
||||
};
|
||||
|
||||
void Model::setCollisionMesh(model::MeshPointer mesh) {
|
||||
if (mesh) {
|
||||
_collisionGeometry = std::make_shared<CollisionRenderGeometry>(mesh);
|
||||
} else {
|
||||
_collisionGeometry.reset();
|
||||
}
|
||||
_needsFixupInScene = true;
|
||||
}
|
||||
|
||||
ModelBlender::ModelBlender() :
|
||||
|
|
|
@ -81,24 +81,20 @@ public:
|
|||
// new Scene/Engine rendering support
|
||||
void setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene);
|
||||
bool needsFixupInScene() const;
|
||||
bool readyToAddToScene(RenderArgs* renderArgs = nullptr) const {
|
||||
return !_needsReload && isRenderable() && isActive();
|
||||
}
|
||||
|
||||
bool needsReload() const { return _needsReload; }
|
||||
bool initWhenReady(render::ScenePointer scene);
|
||||
bool addToScene(std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges,
|
||||
bool showCollisionHull = false) {
|
||||
render::PendingChanges& pendingChanges) {
|
||||
auto getters = render::Item::Status::Getters(0);
|
||||
return addToScene(scene, pendingChanges, getters, showCollisionHull);
|
||||
return addToScene(scene, pendingChanges, getters);
|
||||
}
|
||||
bool addToScene(std::shared_ptr<render::Scene> scene,
|
||||
render::PendingChanges& pendingChanges,
|
||||
render::Item::Status::Getters& statusGetters,
|
||||
bool showCollisionHull = false);
|
||||
render::Item::Status::Getters& statusGetters);
|
||||
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
|
||||
void renderSetup(RenderArgs* args);
|
||||
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _renderGeometry->getMeshes().empty()); }
|
||||
bool isRenderable() const;
|
||||
|
||||
bool isVisible() const { return _isVisible; }
|
||||
|
||||
|
@ -114,7 +110,6 @@ public:
|
|||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||
|
||||
bool isLoaded() const { return (bool)_renderGeometry; }
|
||||
bool isCollisionLoaded() const { return (bool)_collisionGeometry; }
|
||||
|
||||
void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; }
|
||||
bool isWireframe() const { return _isWireframe; }
|
||||
|
@ -141,13 +136,6 @@ public:
|
|||
/// Provided as a convenience, will crash if !isLoaded()
|
||||
// And so that getGeometry() isn't chained everywhere
|
||||
const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return _renderGeometry->getFBXGeometry(); }
|
||||
/// Provided as a convenience, will crash if !isCollisionLoaded()
|
||||
const FBXGeometry& getCollisionFBXGeometry() const { assert(isCollisionLoaded()); return _collisionGeometry->getFBXGeometry(); }
|
||||
|
||||
// Set the model to use for collisions.
|
||||
// Should only be called from the model's rendering thread to avoid access violations of changed geometry.
|
||||
Q_INVOKABLE void setCollisionModelURL(const QUrl& url);
|
||||
const QUrl& getCollisionURL() const { return _collisionUrl; }
|
||||
|
||||
bool isActive() const { return isLoaded(); }
|
||||
|
||||
|
@ -185,6 +173,7 @@ public:
|
|||
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
|
||||
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
|
||||
bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const;
|
||||
|
||||
/// \param jointIndex index of joint in model structure
|
||||
/// \param rotation[out] rotation of joint in model-frame
|
||||
/// \return true if joint exists
|
||||
|
@ -239,18 +228,19 @@ public:
|
|||
|
||||
// returns 'true' if needs fullUpdate after geometry change
|
||||
bool updateGeometry();
|
||||
void setCollisionMesh(model::MeshPointer mesh);
|
||||
|
||||
void setLoadingPriority(float priority) { _loadingPriority = priority; }
|
||||
|
||||
public slots:
|
||||
void loadURLFinished(bool success);
|
||||
void loadCollisionModelURLFinished(bool success);
|
||||
|
||||
signals:
|
||||
void setURLFinished(bool success);
|
||||
void setCollisionModelURLFinished(bool success);
|
||||
|
||||
protected:
|
||||
bool addedToScene() const { return _addedToScene; }
|
||||
|
||||
void setPupilDilation(float dilation) { _pupilDilation = dilation; }
|
||||
float getPupilDilation() const { return _pupilDilation; }
|
||||
|
@ -282,10 +272,9 @@ protected:
|
|||
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
||||
|
||||
Geometry::Pointer _renderGeometry; // only ever set by its watcher
|
||||
Geometry::Pointer _collisionGeometry; // only ever set by its watcher
|
||||
Geometry::Pointer _collisionGeometry;
|
||||
|
||||
GeometryResourceWatcher _renderWatcher;
|
||||
GeometryResourceWatcher _collisionWatcher;
|
||||
|
||||
glm::vec3 _translation;
|
||||
glm::quat _rotation;
|
||||
|
@ -353,7 +342,6 @@ protected:
|
|||
QVector<float> _blendshapeCoefficients;
|
||||
|
||||
QUrl _url;
|
||||
QUrl _collisionUrl;
|
||||
bool _isVisible;
|
||||
|
||||
gpu::Buffers _blendedVertexBuffers;
|
||||
|
@ -376,10 +364,10 @@ protected:
|
|||
|
||||
void recalculateMeshBoxes(bool pickAgainstTriangles = false);
|
||||
|
||||
void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes
|
||||
static model::MaterialPointer _collisionHullMaterial;
|
||||
void createRenderItemSet();
|
||||
void createVisibleRenderItemSet();
|
||||
void createCollisionRenderItemSet();
|
||||
|
||||
bool _meshGroupsKnown;
|
||||
bool _isWireframe;
|
||||
|
||||
|
||||
|
@ -396,10 +384,10 @@ protected:
|
|||
QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet;
|
||||
QMap<render::ItemID, render::PayloadPointer> _modelMeshRenderItems;
|
||||
|
||||
bool _readyWhenAdded { false };
|
||||
bool _addedToScene { false }; // has been added to scene
|
||||
bool _needsFixupInScene { true }; // needs to be removed/re-added to scene
|
||||
bool _needsReload { true };
|
||||
bool _needsUpdateClusterMatrices { true };
|
||||
bool _showCollisionHull { false };
|
||||
mutable bool _needsUpdateTextures { true };
|
||||
|
||||
friend class ModelMeshPartPayload;
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
#include "DoubleHashKey.h"
|
||||
|
||||
const uint32_t NUM_PRIMES = 64;
|
||||
const uint32_t PRIMES[] = {
|
||||
const uint32_t PRIMES[] = {
|
||||
4194301U, 4194287U, 4194277U, 4194271U, 4194247U, 4194217U, 4194199U, 4194191U,
|
||||
4194187U, 4194181U, 4194173U, 4194167U, 4194143U, 4194137U, 4194131U, 4194107U,
|
||||
4194103U, 4194023U, 4194011U, 4194007U, 4193977U, 4193971U, 4193963U, 4193957U,
|
||||
|
@ -27,8 +27,8 @@ uint32_t DoubleHashKey::hashFunction(uint32_t value, uint32_t primeIndex) {
|
|||
uint32_t hash = PRIMES[primeIndex % NUM_PRIMES] * (value + 1U);
|
||||
hash += ~(hash << 15);
|
||||
hash ^= (hash >> 10);
|
||||
hash += (hash << 3);
|
||||
hash ^= (hash >> 6);
|
||||
hash += (hash << 3);
|
||||
hash ^= (hash >> 6);
|
||||
hash += ~(hash << 11);
|
||||
return hash ^ (hash >> 16);
|
||||
}
|
||||
|
|
|
@ -22,9 +22,9 @@ public:
|
|||
|
||||
DoubleHashKey() : _hash(0), _hash2(0) { }
|
||||
|
||||
DoubleHashKey(uint32_t value, uint32_t primeIndex = 0) :
|
||||
_hash(hashFunction(value, primeIndex)),
|
||||
_hash2(hashFunction2(value)) {
|
||||
DoubleHashKey(uint32_t value, uint32_t primeIndex = 0) :
|
||||
_hash(hashFunction(value, primeIndex)),
|
||||
_hash2(hashFunction2(value)) {
|
||||
}
|
||||
|
||||
void clear() { _hash = 0; _hash2 = 0; }
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# Declare dependencies
|
||||
macro (SETUP_TESTCASE_DEPENDENCIES)
|
||||
target_bullet()
|
||||
link_hifi_libraries(shared physics)
|
||||
link_hifi_libraries(shared physics gpu model)
|
||||
package_libraries_for_deployment()
|
||||
endmacro ()
|
||||
|
||||
|
|
277
tests/physics/src/CollisionRenderMeshCacheTests.cpp
Normal file
277
tests/physics/src/CollisionRenderMeshCacheTests.cpp
Normal file
|
@ -0,0 +1,277 @@
|
|||
//
|
||||
// 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.cpp"
|
||||
|
||||
|
||||
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
|
||||
model::MeshPointer mesh = cache.getMesh(compoundShape);
|
||||
QVERIFY((bool)mesh);
|
||||
QVERIFY(cache.getNumMeshes() == 1);
|
||||
|
||||
// get the mesh again
|
||||
model::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
|
||||
model::MeshPointer meshA = cache.getMesh(shapeA);
|
||||
model::MeshPointer meshB = cache.getMesh(shapeB);
|
||||
model::MeshPointer meshC = cache.getMesh(shapeC);
|
||||
QVERIFY((bool)meshA);
|
||||
QVERIFY((bool)meshB);
|
||||
QVERIFY((bool)meshC);
|
||||
QVERIFY(cache.getNumMeshes() == 3);
|
||||
|
||||
// get the meshes again
|
||||
model::MeshPointer meshA2 = cache.getMesh(shapeA);
|
||||
model::MeshPointer meshB2 = cache.getMesh(shapeB);
|
||||
model::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;
|
||||
}
|
26
tests/physics/src/CollisionRenderMeshCacheTests.h
Normal file
26
tests/physics/src/CollisionRenderMeshCacheTests.h
Normal file
|
@ -0,0 +1,26 @@
|
|||
//
|
||||
// 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
|
45
tests/physics/src/MeshUtil.cpp
Normal file
45
tests/physics/src/MeshUtil.cpp
Normal file
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// MeshUtil.cpp
|
||||
// tests/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.07.14
|
||||
// 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 "MeshUtil.h"
|
||||
|
||||
#include<unordered_map>
|
||||
|
||||
// returns false if any edge has only one adjacent triangle
|
||||
bool MeshUtil::isClosedManifold(const uint32_t* meshIndices, uint32_t numIndices) {
|
||||
using EdgeList = std::unordered_map<MeshUtil::TriangleEdge, uint32_t>;
|
||||
EdgeList edges;
|
||||
|
||||
// count the triangles for each edge
|
||||
const uint32_t TRIANGLE_STRIDE = 3;
|
||||
for (uint32_t i = 0; i < numIndices; i += TRIANGLE_STRIDE) {
|
||||
MeshUtil::TriangleEdge edge;
|
||||
// the triangles indices are stored in sequential order
|
||||
for (uint32_t j = 0; j < 3; ++j) {
|
||||
edge.setIndices(meshIndices[i + j], meshIndices[i + ((j + 1) % 3)]);
|
||||
|
||||
EdgeList::iterator edgeEntry = edges.find(edge);
|
||||
if (edgeEntry == edges.end()) {
|
||||
edges.insert(std::pair<MeshUtil::TriangleEdge, uint32_t>(edge, 1));
|
||||
} else {
|
||||
edgeEntry->second += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// scan for outside edge
|
||||
for (auto& edgeEntry : edges) {
|
||||
if (edgeEntry.second == 1) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
61
tests/physics/src/MeshUtil.h
Normal file
61
tests/physics/src/MeshUtil.h
Normal file
|
@ -0,0 +1,61 @@
|
|||
//
|
||||
// MeshUtil.h
|
||||
// tests/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.07.14
|
||||
// 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_MeshUtil_h
|
||||
#define hifi_MeshUtil_h
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace MeshUtil {
|
||||
|
||||
class TriangleEdge {
|
||||
public:
|
||||
TriangleEdge() {}
|
||||
TriangleEdge(uint32_t A, uint32_t B) {
|
||||
setIndices(A, B);
|
||||
}
|
||||
void setIndices(uint32_t A, uint32_t B) {
|
||||
if (A < B) {
|
||||
_indexA = A;
|
||||
_indexB = B;
|
||||
} else {
|
||||
_indexA = B;
|
||||
_indexB = A;
|
||||
}
|
||||
}
|
||||
bool operator==(const TriangleEdge& other) const {
|
||||
return _indexA == other._indexA && _indexB == other._indexB;
|
||||
}
|
||||
|
||||
uint32_t getIndexA() const { return _indexA; }
|
||||
uint32_t getIndexB() const { return _indexB; }
|
||||
private:
|
||||
uint32_t _indexA { (uint32_t)(-1) };
|
||||
uint32_t _indexB { (uint32_t)(-1) };
|
||||
};
|
||||
|
||||
bool isClosedManifold(const uint32_t* meshIndices, uint32_t numIndices);
|
||||
|
||||
} // MeshUtil namespace
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<MeshUtil::TriangleEdge> {
|
||||
std::size_t operator()(const MeshUtil::TriangleEdge& edge) const {
|
||||
// use Cantor's pairing function to generate a hash of ZxZ --> Z
|
||||
uint32_t ab = edge.getIndexA() + edge.getIndexB();
|
||||
return hash<uint32_t>()((ab * (ab + 1)) / 2 + edge.getIndexB());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
#endif // hifi_MeshUtil_h
|
|
@ -12,6 +12,7 @@
|
|||
#include <iostream>
|
||||
#include <ShapeManager.h>
|
||||
#include <StreamUtils.h>
|
||||
#include <Extents.h>
|
||||
|
||||
#include "ShapeManagerTests.h"
|
||||
|
||||
|
@ -197,6 +198,7 @@ void ShapeManagerTests::addCompoundShape() {
|
|||
ShapeInfo::PointCollection pointCollection;
|
||||
int numHulls = 5;
|
||||
glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
|
||||
Extents extents;
|
||||
for (int i = 0; i < numHulls; ++i) {
|
||||
glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
|
||||
ShapeInfo::PointList pointList;
|
||||
|
@ -204,13 +206,16 @@ void ShapeManagerTests::addCompoundShape() {
|
|||
for (int j = 0; j < numHullPoints; ++j) {
|
||||
glm::vec3 point = radius * tetrahedron[j] + offset;
|
||||
pointList.push_back(point);
|
||||
extents.addPoint(point);
|
||||
}
|
||||
pointCollection.push_back(pointList);
|
||||
}
|
||||
|
||||
// create the ShapeInfo
|
||||
ShapeInfo info;
|
||||
info.setPointCollection(hulls);
|
||||
glm::vec3 halfExtents = 0.5f * (extents.maximum - extents.minimum);
|
||||
info.setParams(SHAPE_TYPE_COMPOUND, halfExtents);
|
||||
info.setPointCollection(pointCollection);
|
||||
|
||||
// create the shape
|
||||
ShapeManager shapeManager;
|
||||
|
|
Loading…
Reference in a new issue