Merge branch 'master' of https://github.com/highfidelity/hifi into hdr

This commit is contained in:
samcake 2016-08-25 15:21:41 -07:00
commit 09a70740a8
43 changed files with 1173 additions and 375 deletions

View file

@ -137,7 +137,7 @@ namespace MenuOption {
const QString Overlays = "Overlays"; const QString Overlays = "Overlays";
const QString PackageModel = "Package Model..."; const QString PackageModel = "Package Model...";
const QString Pair = "Pair"; const QString Pair = "Pair";
const QString PhysicsShowHulls = "Draw Collision Hulls"; const QString PhysicsShowHulls = "Draw Collision Shapes";
const QString PhysicsShowOwned = "Highlight Simulation Ownership"; const QString PhysicsShowOwned = "Highlight Simulation Ownership";
const QString PipelineWarnings = "Log Render Pipeline Warnings"; const QString PipelineWarnings = "Log Render Pipeline Warnings";
const QString Preferences = "General..."; const QString Preferences = "General...";

View file

@ -367,7 +367,7 @@ void AvatarManager::addAvatarToSimulation(Avatar* avatar) {
ShapeInfo shapeInfo; ShapeInfo shapeInfo;
avatar->computeShapeInfo(shapeInfo); avatar->computeShapeInfo(shapeInfo);
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) { if (shape) {
// we don't add to the simulation now, we put it on a list to be added later // we don't add to the simulation now, we put it on a list to be added later
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape); AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);

View file

@ -17,7 +17,7 @@
#include "AvatarMotionState.h" #include "AvatarMotionState.h"
#include "BulletUtil.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); assert(_avatar);
_type = MOTIONSTATE_TYPE_AVATAR; _type = MOTIONSTATE_TYPE_AVATAR;
if (_shape) { if (_shape) {
@ -47,7 +47,7 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const {
} }
// virtual and protected // virtual and protected
btCollisionShape* AvatarMotionState::computeNewShape() { const btCollisionShape* AvatarMotionState::computeNewShape() {
ShapeInfo shapeInfo; ShapeInfo shapeInfo;
_avatar->computeShapeInfo(shapeInfo); _avatar->computeShapeInfo(shapeInfo);
return getShapeManager()->getShape(shapeInfo); return getShapeManager()->getShape(shapeInfo);

View file

@ -20,7 +20,7 @@ class Avatar;
class AvatarMotionState : public ObjectMotionState { class AvatarMotionState : public ObjectMotionState {
public: public:
AvatarMotionState(Avatar* avatar, btCollisionShape* shape); AvatarMotionState(Avatar* avatar, const btCollisionShape* shape);
virtual PhysicsMotionType getMotionType() const override { return _motionType; } virtual PhysicsMotionType getMotionType() const override { return _motionType; }
@ -72,7 +72,7 @@ protected:
~AvatarMotionState(); ~AvatarMotionState();
virtual bool isReadyToComputeShape() const override { return true; } 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 // 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. // instances are "owned" by their corresponding Avatar instance and are deleted in the Avatar dtor.

View file

@ -1,3 +1,5 @@
set(TARGET_NAME animation) set(TARGET_NAME animation)
setup_hifi_library(Network Script) setup_hifi_library(Network Script)
link_hifi_libraries(shared model fbx) link_hifi_libraries(shared model fbx)
target_nsight()

View file

@ -13,6 +13,7 @@
#include <GLMHelpers.h> #include <GLMHelpers.h>
#include <NumericalConstants.h> #include <NumericalConstants.h>
#include <SharedUtil.h> #include <SharedUtil.h>
#include <shared/NsightHelpers.h>
#include "ElbowConstraint.h" #include "ElbowConstraint.h"
#include "SwingTwistConstraint.h" #include "SwingTwistConstraint.h"
@ -144,9 +145,11 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
accumulator.clearAndClean(); accumulator.clearAndClean();
} }
float maxError = FLT_MAX;
int numLoops = 0; int numLoops = 0;
const int MAX_IK_LOOPS = 4; const int MAX_IK_LOOPS = 16;
while (numLoops < MAX_IK_LOOPS) { const float MAX_ERROR_TOLERANCE = 0.1f; // cm
while (maxError > MAX_ERROR_TOLERANCE && numLoops < MAX_IK_LOOPS) {
++numLoops; ++numLoops;
// solve all targets // solve all targets
@ -173,6 +176,18 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i]; absolutePoses[i] = absolutePoses[parentIndex] * _relativePoses[i];
} }
} }
// compute maxError
maxError = 0.0f;
for (size_t i = 0; i < targets.size(); i++) {
if (targets[i].getType() == IKTarget::Type::RotationAndPosition || targets[i].getType() == IKTarget::Type::HmdHead ||
targets[i].getType() == IKTarget::Type::HipsRelativeRotationAndPosition) {
float error = glm::length(absolutePoses[targets[i].getIndex()].trans - targets[i].getTranslation());
if (error > maxError) {
maxError = error;
}
}
}
} }
// finally set the relative rotation of each tip to agree with absolute target rotation // finally set the relative rotation of each tip to agree with absolute target rotation
@ -285,8 +300,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f; const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
if (angle > MIN_ADJUSTMENT_ANGLE) { if (angle > MIN_ADJUSTMENT_ANGLE) {
// reduce angle by a fraction (for stability) // reduce angle by a fraction (for stability)
const float fraction = 0.5f; const float FRACTION = 0.5f;
angle *= fraction; angle *= FRACTION;
deltaRotation = glm::angleAxis(angle, axis); deltaRotation = glm::angleAxis(angle, axis);
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's // The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
@ -308,7 +323,7 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
glm::vec3 axis = glm::normalize(deltaRotation * leverArm); glm::vec3 axis = glm::normalize(deltaRotation * leverArm);
swingTwistDecomposition(missingRotation, axis, swingPart, twistPart); swingTwistDecomposition(missingRotation, axis, swingPart, twistPart);
float dotSign = copysignf(1.0f, twistPart.w); float dotSign = copysignf(1.0f, twistPart.w);
deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, fraction)) * deltaRotation; deltaRotation = glm::normalize(glm::lerp(glm::quat(), dotSign * twistPart, FRACTION)) * deltaRotation;
} }
} }
} }
@ -369,6 +384,7 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
//virtual //virtual
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) { const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
if (dt > MAX_OVERLAY_DT) { if (dt > MAX_OVERLAY_DT) {
dt = MAX_OVERLAY_DT; dt = MAX_OVERLAY_DT;
@ -377,6 +393,9 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
if (_relativePoses.size() != underPoses.size()) { if (_relativePoses.size() != underPoses.size()) {
loadPoses(underPoses); loadPoses(underPoses);
} else { } else {
PROFILE_RANGE_EX("ik/relax", 0xffff00ff, 0);
// relax toward underPoses // relax toward underPoses
// HACK: this relaxation needs to be constant per-frame rather than per-realtime // HACK: this relaxation needs to be constant per-frame rather than per-realtime
// in order to prevent IK "flutter" for bad FPS. The bad news is that the good parts // in order to prevent IK "flutter" for bad FPS. The bad news is that the good parts
@ -410,9 +429,13 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
} }
if (!_relativePoses.empty()) { if (!_relativePoses.empty()) {
// build a list of targets from _targetVarVec // build a list of targets from _targetVarVec
std::vector<IKTarget> targets; std::vector<IKTarget> targets;
{
PROFILE_RANGE_EX("ik/computeTargets", 0xffff00ff, 0);
computeTargets(animVars, targets, underPoses); computeTargets(animVars, targets, underPoses);
}
if (targets.empty()) { if (targets.empty()) {
// no IK targets but still need to enforce constraints // no IK targets but still need to enforce constraints
@ -425,6 +448,10 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
++constraintItr; ++constraintItr;
} }
} else { } else {
{
PROFILE_RANGE_EX("ik/shiftHips", 0xffff00ff, 0);
// shift hips according to the _hipsOffset from the previous frame // shift hips according to the _hipsOffset from the previous frame
float offsetLength = glm::length(_hipsOffset); float offsetLength = glm::length(_hipsOffset);
const float MIN_HIPS_OFFSET_LENGTH = 0.03f; const float MIN_HIPS_OFFSET_LENGTH = 0.03f;
@ -446,8 +473,15 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
+ glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset); + glm::inverse(glm::normalize(hipsFrameRotation)) * (scaleFactor * _hipsOffset);
} }
} }
}
{
PROFILE_RANGE_EX("ik/ccd", 0xffff00ff, 0);
solveWithCyclicCoordinateDescent(targets); solveWithCyclicCoordinateDescent(targets);
}
{
PROFILE_RANGE_EX("ik/measureHipsOffset", 0xffff00ff, 0);
// measure new _hipsOffset for next frame // measure new _hipsOffset for next frame
// by looking for discrepancies between where a targeted endEffector is // by looking for discrepancies between where a targeted endEffector is
@ -485,6 +519,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
_hipsOffset += (newHipsOffset - _hipsOffset) * tau; _hipsOffset += (newHipsOffset - _hipsOffset) * tau;
} }
} }
}
return _relativePoses; return _relativePoses;
} }

View file

@ -499,35 +499,18 @@ ModelPointer EntityTreeRenderer::getModelForEntityItem(EntityItemPointer entityI
return result; 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) { void EntityTreeRenderer::processEraseMessage(ReceivedMessage& message, const SharedNodePointer& sourceNode) {
std::static_pointer_cast<EntityTree>(_tree)->processEraseMessage(message, 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; ModelPointer model = nullptr;
// Only create and delete models on the thread that owns the EntityTreeRenderer // Only create and delete models on the thread that owns the EntityTreeRenderer
if (QThread::currentThread() != thread()) { if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection, QMetaObject::invokeMethod(this, "allocateModel", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(ModelPointer, model), Q_RETURN_ARG(ModelPointer, model),
Q_ARG(const QString&, url), Q_ARG(const QString&, url));
Q_ARG(const QString&, collisionUrl));
return model; return model;
} }
@ -536,7 +519,6 @@ ModelPointer EntityTreeRenderer::allocateModel(const QString& url, const QString
model->setLoadingPriority(loadingPriority); model->setLoadingPriority(loadingPriority);
model->init(); model->init();
model->setURL(QUrl(url)); model->setURL(QUrl(url));
model->setCollisionModelURL(QUrl(collisionUrl));
return model; return model;
} }
@ -553,7 +535,6 @@ ModelPointer EntityTreeRenderer::updateModel(ModelPointer model, const QString&
} }
model->setURL(QUrl(newUrl)); model->setURL(QUrl(newUrl));
model->setCollisionModelURL(QUrl(collisionUrl));
return model; return model;
} }

View file

@ -65,7 +65,6 @@ public:
virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) override; virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) override;
virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) override; virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) override;
virtual const FBXGeometry* getCollisionGeometryForEntity(EntityItemPointer entityItem) override;
/// clears the tree /// clears the tree
virtual void clear() override; virtual void clear() override;
@ -74,7 +73,7 @@ public:
void reloadEntityScripts(); void reloadEntityScripts();
/// if a renderable entity item needs a model, we will allocate it for them /// 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 /// 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); Q_INVOKABLE ModelPointer updateModel(ModelPointer original, const QString& newUrl, const QString& collisionUrl);

View file

@ -17,6 +17,7 @@
#include <glm/gtx/transform.hpp> #include <glm/gtx/transform.hpp>
#include <AbstractViewStateInterface.h> #include <AbstractViewStateInterface.h>
#include <CollisionRenderMeshCache.h>
#include <Model.h> #include <Model.h>
#include <PerfStat.h> #include <PerfStat.h>
#include <render/Scene.h> #include <render/Scene.h>
@ -28,6 +29,9 @@
#include "RenderableModelEntityItem.h" #include "RenderableModelEntityItem.h"
#include "RenderableEntityItem.h" #include "RenderableEntityItem.h"
static CollisionRenderMeshCache collisionMeshCache;
EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) { EntityItemPointer RenderableModelEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
EntityItemPointer entity{ new RenderableModelEntityItem(entityID, properties.getDimensionsInitialized()) }; EntityItemPointer entity{ new RenderableModelEntityItem(entityID, properties.getDimensionsInitialized()) };
entity->setProperties(properties); entity->setProperties(properties);
@ -224,11 +228,11 @@ bool RenderableModelEntityItem::addToScene(EntityItemPointer self, std::shared_p
render::Item::Status::Getters statusGetters; render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), 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 // note: we don't mind if the model fails to add, we'll retry (in render()) until it succeeds
// true so that the system knows our meta item is in the scene! _model->addToScene(scene, pendingChanges, statusGetters);
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull);
} }
// we've successfully added _myMetaItem so we always return true
return true; return true;
} }
@ -415,19 +419,35 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
// Remap textures for the next frame to avoid flicker // Remap textures for the next frame to avoid flicker
remapTextures(); remapTextures();
// check to see if when we added our models to the scene they were ready, if they were not ready, then // update whether the model should be showing collision mesh (this may flag for fixupInScene)
// fix them up in the scene bool showingCollisionGeometry = (bool)(args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS);
bool shouldShowCollisionHull = (args->_debugFlags & (int)RenderArgs::RENDER_DEBUG_HULLS) > 0 if (showingCollisionGeometry != _showCollisionGeometry) {
&& getShapeType() == SHAPE_TYPE_COMPOUND; ShapeType type = getShapeType();
if (_model->needsFixupInScene() || _showCollisionHull != shouldShowCollisionHull) { _showCollisionGeometry = showingCollisionGeometry;
_showCollisionHull = shouldShowCollisionHull; 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; render::PendingChanges pendingChanges;
_model->removeFromScene(scene, pendingChanges); _model->removeFromScene(scene, pendingChanges);
render::Item::Status::Getters statusGetters; render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters); makeEntityItemStatusGetters(getThisPointer(), statusGetters);
_model->addToScene(scene, pendingChanges, statusGetters, _showCollisionHull); _model->addToScene(scene, pendingChanges, statusGetters);
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);
} }
@ -471,14 +491,13 @@ ModelPointer RenderableModelEntityItem::getModel(EntityTreeRenderer* renderer) {
if (!getModelURL().isEmpty()) { if (!getModelURL().isEmpty()) {
// If we don't have a model, allocate one *immediately* // If we don't have a model, allocate one *immediately*
if (!_model) { if (!_model) {
_model = _myRenderer->allocateModel(getModelURL(), getCompoundShapeURL(), renderer->getEntityLoadingPriority(*this)); _model = _myRenderer->allocateModel(getModelURL(), renderer->getEntityLoadingPriority(*this));
_needsInitialSimulation = true; _needsInitialSimulation = true;
// If we need to change URLs, update it *after rendering* (to avoid access violations) // 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, QMetaObject::invokeMethod(_myRenderer, "updateModel", Qt::QueuedConnection,
Q_ARG(ModelPointer, _model), Q_ARG(ModelPointer, _model),
Q_ARG(const QString&, getModelURL()), Q_ARG(const QString&, getModelURL()));
Q_ARG(const QString&, getCompoundShapeURL()));
_needsInitialSimulation = true; _needsInitialSimulation = true;
} }
// Else we can just return the _model // Else we can just return the _model
@ -546,6 +565,18 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
face, surfaceNormal, extraInfo, precisionPicking); 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) { void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
auto currentCompoundShapeURL = getCompoundShapeURL(); auto currentCompoundShapeURL = getCompoundShapeURL();
ModelEntityItem::setCompoundShapeURL(url); ModelEntityItem::setCompoundShapeURL(url);
@ -555,6 +586,9 @@ void RenderableModelEntityItem::setCompoundShapeURL(const QString& url) {
if (tree) { if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); 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(); ShapeType type = getShapeType();
if (type == SHAPE_TYPE_COMPOUND) { if (type == SHAPE_TYPE_COMPOUND) {
if (!_model || _model->getCollisionURL().isEmpty()) { if (!_model || _compoundShapeURL.isEmpty()) {
EntityTreePointer tree = getTree(); EntityTreePointer tree = getTree();
if (tree) { if (tree) {
QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID())); QMetaObject::invokeMethod(tree.get(), "callLoader", Qt::QueuedConnection, Q_ARG(EntityItemID, getID()));
@ -575,15 +609,18 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
return false; return false;
} }
if (_model->isLoaded() && _model->isCollisionLoaded()) { if (_model->isLoaded()) {
if (_compoundShapeResource && _compoundShapeResource->isLoaded()) {
// we have both URLs AND both geometries AND they are both fully loaded. // we have both URLs AND both geometries AND they are both fully loaded.
if (_needsInitialSimulation) { if (_needsInitialSimulation) {
// the _model's offset will be wrong until _needsInitialSimulation is false // the _model's offset will be wrong until _needsInitialSimulation is false
PerformanceTimer perfTimer("_model->simulate"); PerformanceTimer perfTimer("_model->simulate");
doInitialModelSimulation(); doInitialModelSimulation();
} }
return true; return true;
} else if (!_compoundShapeURL.isEmpty()) {
_compoundShapeResource = DependencyManager::get<ModelCache>()->getGeometryResource(_compoundShapeURL);
}
} }
// the model is still being downloaded. // the model is still being downloaded.
@ -594,7 +631,7 @@ bool RenderableModelEntityItem::isReadyToComputeShape() {
return true; return true;
} }
void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) { void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
const uint32_t TRIANGLE_STRIDE = 3; const uint32_t TRIANGLE_STRIDE = 3;
const uint32_t QUAD_STRIDE = 4; 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 // should never fall in here when collision model not fully loaded
// hence we assert that all geometries exist and are loaded // hence we assert that all geometries exist and are loaded
assert(_model && _model->isLoaded() && _model->isCollisionLoaded()); assert(_model && _model->isLoaded() && _compoundShapeResource && _compoundShapeResource->isLoaded());
const FBXGeometry& collisionGeometry = _model->getCollisionFBXGeometry(); const FBXGeometry& collisionGeometry = _compoundShapeResource->getFBXGeometry();
ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
pointCollection.clear(); pointCollection.clear();
uint32_t i = 0; uint32_t i = 0;
@ -684,15 +721,14 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size(); glm::vec3 scaleToFit = dimensions / _model->getFBXGeometry().getUnscaledMeshExtents().size();
// multiply each point by scale before handing the point-set off to the physics engine. // multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model. // 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 i = 0; i < pointCollection.size(); i++) {
for (int32_t j = 0; j < pointCollection[i].size(); j++) { for (int32_t j = 0; j < pointCollection[i].size(); j++) {
// compensate for registration // back compensate for registration so we can apply that offset to the shapeInfo later
pointCollection[i][j] += _model->getOffset(); pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + _model->getOffset()) - registrationOffset;
// scale so the collision points match the model points
pointCollection[i][j] *= scaleToFit;
} }
} }
info.setParams(type, dimensions, _compoundShapeURL); shapeInfo.setParams(type, dimensions, _compoundShapeURL);
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) { } else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
// should never fall in here when model not fully loaded // should never fall in here when model not fully loaded
assert(_model && _model->isLoaded()); assert(_model && _model->isLoaded());
@ -705,29 +741,31 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
const FBXGeometry& fbxGeometry = _model->getFBXGeometry(); const FBXGeometry& fbxGeometry = _model->getFBXGeometry();
int numFbxMeshes = fbxGeometry.meshes.size(); int numFbxMeshes = fbxGeometry.meshes.size();
int totalNumVertices = 0; int totalNumVertices = 0;
glm::mat4 invRegistraionOffset = glm::translate(dimensions * (getRegistrationPoint() - ENTITY_ITEM_DEFAULT_REGISTRATION_POINT));
for (int i = 0; i < numFbxMeshes; i++) { for (int i = 0; i < numFbxMeshes; i++) {
const FBXMesh& mesh = fbxGeometry.meshes.at(i); const FBXMesh& mesh = fbxGeometry.meshes.at(i);
if (mesh.clusters.size() > 0) { if (mesh.clusters.size() > 0) {
const FBXCluster& cluster = mesh.clusters.at(0); const FBXCluster& cluster = mesh.clusters.at(0);
auto jointMatrix = _model->getRig()->getJointTransform(cluster.jointIndex); 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 { } else {
glm::mat4 identity; glm::mat4 identity;
localTransforms.push_back(identity); localTransforms.push_back(invRegistraionOffset);
} }
totalNumVertices += mesh.vertices.size(); totalNumVertices += mesh.vertices.size();
} }
const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6; const int32_t MAX_VERTICES_PER_STATIC_MESH = 1e6;
if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) { if (totalNumVertices > MAX_VERTICES_PER_STATIC_MESH) {
qWarning() << "model" << getModelURL() << "has too many vertices" << totalNumVertices << "and will collide as a box."; 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; return;
} }
auto& meshes = _model->getGeometry()->getMeshes(); auto& meshes = _model->getGeometry()->getMeshes();
int32_t numMeshes = (int32_t)(meshes.size()); int32_t numMeshes = (int32_t)(meshes.size());
ShapeInfo::PointCollection& pointCollection = info.getPointCollection(); ShapeInfo::PointCollection& pointCollection = shapeInfo.getPointCollection();
pointCollection.clear(); pointCollection.clear();
if (type == SHAPE_TYPE_SIMPLE_COMPOUND) { if (type == SHAPE_TYPE_SIMPLE_COMPOUND) {
pointCollection.resize(numMeshes); pointCollection.resize(numMeshes);
@ -735,7 +773,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& info) {
pointCollection.resize(1); pointCollection.resize(1);
} }
ShapeInfo::TriangleIndices& triangleIndices = info.getTriangleIndices(); ShapeInfo::TriangleIndices& triangleIndices = shapeInfo.getTriangleIndices();
triangleIndices.clear(); triangleIndices.clear();
Extents extents; 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 { } else {
ModelEntityItem::computeShapeInfo(info); ModelEntityItem::computeShapeInfo(shapeInfo);
info.setParams(type, 0.5f * dimensions); shapeInfo.setParams(type, 0.5f * dimensions);
adjustShapeInfoByRegistration(info); }
// 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 { bool RenderableModelEntityItem::contains(const glm::vec3& point) const {
if (EntityItem::contains(point) && _model && _model->isCollisionLoaded()) { if (EntityItem::contains(point) && _model && _compoundShapeResource && _compoundShapeResource->isLoaded()) {
return _model->getCollisionFBXGeometry().convexHullContains(worldToEntity(point)); return _compoundShapeResource->getFBXGeometry().convexHullContains(worldToEntity(point));
} }
return false; return false;

View file

@ -56,10 +56,13 @@ public:
virtual bool needsToCallUpdate() const override; virtual bool needsToCallUpdate() const override;
virtual void update(const quint64& now) override; virtual void update(const quint64& now) override;
virtual void setShapeType(ShapeType type) override;
virtual void setCompoundShapeURL(const QString& url) override; virtual void setCompoundShapeURL(const QString& url) override;
virtual bool isReadyToComputeShape() 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; virtual bool contains(const glm::vec3& point) const override;
@ -98,6 +101,7 @@ private:
QVariantMap parseTexturesToMap(QString textures); QVariantMap parseTexturesToMap(QString textures);
void remapTextures(); void remapTextures();
GeometryResource::Pointer _compoundShapeResource;
ModelPointer _model = nullptr; ModelPointer _model = nullptr;
bool _needsInitialSimulation = true; bool _needsInitialSimulation = true;
bool _needsModelReload = true; bool _needsModelReload = true;
@ -112,11 +116,11 @@ private:
render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID }; render::ItemID _myMetaItem{ render::Item::INVALID_ITEM_ID };
bool _showCollisionHull = false;
bool getAnimationFrame(); bool getAnimationFrame();
bool _needsJointSimulation { false }; bool _needsJointSimulation { false };
bool _showCollisionGeometry { false };
const void* _collisionMeshKey { nullptr };
}; };
#endif // hifi_RenderableModelEntityItem_h #endif // hifi_RenderableModelEntityItem_h

View file

@ -122,7 +122,7 @@ void RenderableZoneEntityItem::render(RenderArgs* args) {
_model->removeFromScene(scene, pendingChanges); _model->removeFromScene(scene, pendingChanges);
render::Item::Status::Getters statusGetters; render::Item::Status::Getters statusGetters;
makeEntityItemStatusGetters(getThisPointer(), statusGetters); makeEntityItemStatusGetters(getThisPointer(), statusGetters);
_model->addToScene(scene, pendingChanges, false); _model->addToScene(scene, pendingChanges);
scene->enqueuePendingChanges(pendingChanges); scene->enqueuePendingChanges(pendingChanges);

View file

@ -44,6 +44,7 @@ class EntityTreeElementExtraEncodeData;
class EntityActionInterface; class EntityActionInterface;
class EntityItemProperties; class EntityItemProperties;
class EntityTree; class EntityTree;
class btCollisionShape;
typedef std::shared_ptr<EntityTree> EntityTreePointer; typedef std::shared_ptr<EntityTree> EntityTreePointer;
typedef std::shared_ptr<EntityActionInterface> EntityActionPointer; typedef std::shared_ptr<EntityActionInterface> EntityActionPointer;
typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer; typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
@ -324,6 +325,8 @@ public:
/// return preferred shape type (actual physical shape may differ) /// return preferred shape type (actual physical shape may differ)
virtual ShapeType getShapeType() const { return SHAPE_TYPE_NONE; } 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 // updateFoo() methods to be used when changes need to be accumulated in the _dirtyFlags
virtual void updateRegistrationPoint(const glm::vec3& value); virtual void updateRegistrationPoint(const glm::vec3& value);
void updatePosition(const glm::vec3& value); void updatePosition(const glm::vec3& value);

View file

@ -40,7 +40,6 @@ class EntityItemFBXService {
public: public:
virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) = 0; virtual const FBXGeometry* getGeometryForEntity(EntityItemPointer entityItem) = 0;
virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) = 0; virtual ModelPointer getModelForEntityItem(EntityItemPointer entityItem) = 0;
virtual const FBXGeometry* getCollisionGeometryForEntity(EntityItemPointer entityItem) = 0;
}; };

View file

@ -282,8 +282,8 @@ ShapeType ModelEntityItem::computeTrueShapeType() const {
type = SHAPE_TYPE_COMPOUND; type = SHAPE_TYPE_COMPOUND;
} }
if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) { if (type == SHAPE_TYPE_COMPOUND && !hasCompoundShapeURL()) {
// no compoundURL set --> fall back to NONE // no compoundURL set --> fall back to SIMPLE_COMPOUND
type = SHAPE_TYPE_NONE; type = SHAPE_TYPE_SIMPLE_COMPOUND;
} }
return type; return type;
} }

View file

@ -111,13 +111,13 @@ public:
QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); } QUrl getURL() const { return (bool)_resource ? _resource->getURL() : QUrl(); }
signals:
void finished(bool success);
private: private:
void startWatching(); void startWatching();
void stopWatching(); void stopWatching();
signals:
void finished(bool success);
private slots: private slots:
void resourceFinished(bool success); void resourceFinished(bool success);
void resourceRefreshed(); void resourceRefreshed();

View file

@ -130,7 +130,7 @@ protected:
void evalVertexStream(); void evalVertexStream();
}; };
typedef std::shared_ptr< Mesh > MeshPointer; using MeshPointer = std::shared_ptr< Mesh >;
class Geometry { class Geometry {

View file

@ -226,8 +226,6 @@ private:
void resetResourceCounters(); void resetResourceCounters();
void removeResource(const QUrl& url, qint64 size = 0); void removeResource(const QUrl& url, qint64 size = 0);
void getResourceAsynchronously(const QUrl& url);
static int _requestLimit; static int _requestLimit;
static int _requestsActive; static int _requestsActive;

View file

@ -1,5 +1,5 @@
set(TARGET_NAME physics) set(TARGET_NAME physics)
setup_hifi_library() setup_hifi_library()
link_hifi_libraries(shared fbx entities) link_hifi_libraries(shared fbx entities model)
target_bullet() target_bullet()

View 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();
}

View 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

View file

@ -46,7 +46,7 @@ bool entityTreeIsLocked() {
EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) : EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer entity) :
ObjectMotionState(shape), ObjectMotionState(nullptr),
_entityPtr(entity), _entityPtr(entity),
_entity(entity.get()), _entity(entity.get()),
_serverPosition(0.0f), _serverPosition(0.0f),
@ -71,6 +71,9 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
assert(_entity); assert(_entity);
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
setMass(_entity->computeMass()); 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() { EntityMotionState::~EntityMotionState() {
@ -264,13 +267,20 @@ bool EntityMotionState::isReadyToComputeShape() const {
} }
// virtual and protected // virtual and protected
btCollisionShape* EntityMotionState::computeNewShape() { const btCollisionShape* EntityMotionState::computeNewShape() {
ShapeInfo shapeInfo; ShapeInfo shapeInfo;
assert(entityTreeIsLocked()); assert(entityTreeIsLocked());
_entity->computeShapeInfo(shapeInfo); _entity->computeShapeInfo(shapeInfo);
return getShapeManager()->getShape(shapeInfo); return getShapeManager()->getShape(shapeInfo);
} }
void EntityMotionState::setShape(const btCollisionShape* shape) {
if (_shape != shape) {
ObjectMotionState::setShape(shape);
_entity->setCollisionShape(_shape);
}
}
bool EntityMotionState::isCandidateForOwnership() const { bool EntityMotionState::isCandidateForOwnership() const {
assert(_body); assert(_body);
assert(_entity); assert(_entity);

View file

@ -88,9 +88,10 @@ protected:
bool entityTreeIsLocked() const; bool entityTreeIsLocked() const;
#endif #endif
virtual bool isReadyToComputeShape() const override; bool isReadyToComputeShape() const override;
virtual btCollisionShape* computeNewShape() override; const btCollisionShape* computeNewShape() override;
virtual void setMotionType(PhysicsMotionType motionType); 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 // 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 // properly "owned" by the EntityItem and will be deleted by it in the dtor. In pursuit of that

View file

@ -62,7 +62,7 @@ ShapeManager* ObjectMotionState::getShapeManager() {
return shapeManager; return shapeManager;
} }
ObjectMotionState::ObjectMotionState(btCollisionShape* shape) : ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) :
_motionType(MOTION_TYPE_STATIC), _motionType(MOTION_TYPE_STATIC),
_shape(shape), _shape(shape),
_body(nullptr), _body(nullptr),
@ -73,7 +73,7 @@ ObjectMotionState::ObjectMotionState(btCollisionShape* shape) :
ObjectMotionState::~ObjectMotionState() { ObjectMotionState::~ObjectMotionState() {
assert(!_body); assert(!_body);
releaseShape(); setShape(nullptr);
_type = MOTIONSTATE_TYPE_INVALID; _type = MOTIONSTATE_TYPE_INVALID;
} }
@ -114,13 +114,6 @@ glm::vec3 ObjectMotionState::getBodyAngularVelocity() const {
return bulletToGLM(_body->getAngularVelocity()); return bulletToGLM(_body->getAngularVelocity());
} }
void ObjectMotionState::releaseShape() {
if (_shape) {
shapeManager->releaseShape(_shape);
_shape = nullptr;
}
}
void ObjectMotionState::setMotionType(PhysicsMotionType motionType) { void ObjectMotionState::setMotionType(PhysicsMotionType motionType) {
_motionType = motionType; _motionType = motionType;
} }
@ -160,11 +153,21 @@ void ObjectMotionState::setRigidBody(btRigidBody* body) {
_body = body; _body = body;
if (_body) { if (_body) {
_body->setUserPointer(this); _body->setUserPointer(this);
assert(_body->getCollisionShape() == _shape);
} }
updateCCDConfiguration(); updateCCDConfiguration();
} }
} }
void ObjectMotionState::setShape(const btCollisionShape* shape) {
if (_shape != shape) {
if (_shape) {
getShapeManager()->releaseShape(_shape);
}
_shape = shape;
}
}
void ObjectMotionState::handleEasyChanges(uint32_t& flags) { void ObjectMotionState::handleEasyChanges(uint32_t& flags) {
if (flags & Simulation::DIRTY_POSITION) { if (flags & Simulation::DIRTY_POSITION) {
btTransform worldTrans = _body->getWorldTransform(); btTransform worldTrans = _body->getWorldTransform();
@ -251,7 +254,7 @@ bool ObjectMotionState::handleHardAndEasyChanges(uint32_t& flags, PhysicsEngine*
if (!isReadyToComputeShape()) { if (!isReadyToComputeShape()) {
return false; return false;
} }
btCollisionShape* newShape = computeNewShape(); const btCollisionShape* newShape = computeNewShape();
if (!newShape) { if (!newShape) {
qCDebug(physics) << "Warning: failed to generate new shape!"; qCDebug(physics) << "Warning: failed to generate new shape!";
// failed to generate new shape! --> keep old shape and remove shape-change flag // 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; return true;
} }
} }
getShapeManager()->releaseShape(_shape); if (_shape == newShape) {
if (_shape != newShape) { // the shape didn't actually change, so we clear the DIRTY_SHAPE flag
_shape = newShape;
_body->setCollisionShape(_shape);
updateCCDConfiguration();
} else {
// huh... the shape didn't actually change, so we clear the DIRTY_SHAPE flag
flags &= ~Simulation::DIRTY_SHAPE; 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) { if (flags & EASY_DIRTY_PHYSICS_FLAGS) {

View file

@ -78,7 +78,7 @@ public:
static void setShapeManager(ShapeManager* manager); static void setShapeManager(ShapeManager* manager);
static ShapeManager* getShapeManager(); static ShapeManager* getShapeManager();
ObjectMotionState(btCollisionShape* shape); ObjectMotionState(const btCollisionShape* shape);
~ObjectMotionState(); ~ObjectMotionState();
virtual void handleEasyChanges(uint32_t& flags); virtual void handleEasyChanges(uint32_t& flags);
@ -110,11 +110,9 @@ public:
virtual PhysicsMotionType computePhysicsMotionType() const = 0; virtual PhysicsMotionType computePhysicsMotionType() const = 0;
btCollisionShape* getShape() const { return _shape; } const btCollisionShape* getShape() const { return _shape; }
btRigidBody* getRigidBody() const { return _body; } btRigidBody* getRigidBody() const { return _body; }
void releaseShape();
virtual bool isMoving() const = 0; virtual bool isMoving() const = 0;
// These pure virtual methods must be implemented for each MotionState type // These pure virtual methods must be implemented for each MotionState type
@ -152,16 +150,17 @@ public:
protected: protected:
virtual bool isReadyToComputeShape() const = 0; virtual bool isReadyToComputeShape() const = 0;
virtual btCollisionShape* computeNewShape() = 0; virtual const btCollisionShape* computeNewShape() = 0;
void setMotionType(PhysicsMotionType motionType); virtual void setMotionType(PhysicsMotionType motionType);
void updateCCDConfiguration(); void updateCCDConfiguration();
void setRigidBody(btRigidBody* body); void setRigidBody(btRigidBody* body);
virtual void setShape(const btCollisionShape* shape);
MotionStateType _type = MOTIONSTATE_TYPE_INVALID; // type of MotionState MotionStateType _type = MOTIONSTATE_TYPE_INVALID; // type of MotionState
PhysicsMotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC PhysicsMotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC
btCollisionShape* _shape; const btCollisionShape* _shape;
btRigidBody* _body; btRigidBody* _body;
float _mass; float _mass;

View file

@ -225,7 +225,7 @@ void PhysicalEntitySimulation::getObjectsToAddToPhysics(VectorOfMotionStates& re
<< "at" << entity->getPosition() << " will be reduced"; << "at" << entity->getPosition() << " will be reduced";
} }
} }
btCollisionShape* shape = ObjectMotionState::getShapeManager()->getShape(shapeInfo); btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
if (shape) { if (shape) {
EntityMotionState* motionState = new EntityMotionState(shape, entity); EntityMotionState* motionState = new EntityMotionState(shape, entity);
entity->setPhysicsInfo(static_cast<void*>(motionState)); entity->setPhysicsInfo(static_cast<void*>(motionState));

View file

@ -76,7 +76,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
switch(motionType) { switch(motionType) {
case MOTION_TYPE_KINEMATIC: { case MOTION_TYPE_KINEMATIC: {
if (!body) { if (!body) {
btCollisionShape* shape = motionState->getShape(); btCollisionShape* shape = const_cast<btCollisionShape*>(motionState->getShape());
assert(shape); assert(shape);
body = new btRigidBody(mass, motionState, shape, inertia); body = new btRigidBody(mass, motionState, shape, inertia);
motionState->setRigidBody(body); motionState->setRigidBody(body);
@ -93,7 +93,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
} }
case MOTION_TYPE_DYNAMIC: { case MOTION_TYPE_DYNAMIC: {
mass = motionState->getMass(); mass = motionState->getMass();
btCollisionShape* shape = motionState->getShape(); btCollisionShape* shape = const_cast<btCollisionShape*>(motionState->getShape());
assert(shape); assert(shape);
shape->calculateLocalInertia(mass, inertia); shape->calculateLocalInertia(mass, inertia);
if (!body) { if (!body) {
@ -120,7 +120,7 @@ void PhysicsEngine::addObjectToDynamicsWorld(ObjectMotionState* motionState) {
default: { default: {
if (!body) { if (!body) {
assert(motionState->getShape()); 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); motionState->setRigidBody(body);
} else { } else {
body->setMassProps(mass, inertia); body->setMassProps(mass, inertia);

View file

@ -10,7 +10,6 @@
// //
#include <glm/gtx/norm.hpp> #include <glm/gtx/norm.hpp>
#include <BulletCollision/CollisionShapes/btShapeHull.h>
#include <SharedUtil.h> // for MILLIMETERS_PER_METER #include <SharedUtil.h> // for MILLIMETERS_PER_METER
@ -248,7 +247,7 @@ void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray) {
delete dataArray; delete dataArray;
} }
btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) { const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
btCollisionShape* shape = NULL; btCollisionShape* shape = NULL;
int type = info.getType(); int type = info.getType();
switch(type) { switch(type) {
@ -347,23 +346,39 @@ btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info) {
} }
if (shape) { if (shape) {
if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) { if (glm::length2(info.getOffset()) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET) {
// this shape has an offset, which we support by wrapping the true shape // we need to apply an offset
// in a btCompoundShape with a local transform 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(); auto compound = new btCompoundShape();
btTransform trans; compound->addChildShape(offset, shape);
trans.setIdentity();
trans.setOrigin(glmToBullet(info.getOffset()));
compound->addChildShape(trans, shape);
shape = compound; shape = compound;
} }
} }
}
return shape; return shape;
} }
void ShapeFactory::deleteShape(btCollisionShape* shape) { void ShapeFactory::deleteShape(const btCollisionShape* shape) {
assert(shape); assert(shape);
if (shape->getShapeType() == (int)COMPOUND_SHAPE_PROXYTYPE) { // ShapeFactory is responsible for deleting all shapes, even the const ones that are stored
btCompoundShape* compoundShape = static_cast<btCompoundShape*>(shape); // 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(); const int numChildShapes = compoundShape->getNumChildShapes();
for (int i = 0; i < numChildShapes; i ++) { for (int i = 0; i < numChildShapes; i ++) {
btCollisionShape* childShape = compoundShape->getChildShape(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 // the dataArray must be created before we create the StaticMeshShape

View file

@ -20,8 +20,8 @@
// translates between ShapeInfo and btShape // translates between ShapeInfo and btShape
namespace ShapeFactory { namespace ShapeFactory {
btCollisionShape* createShapeFromInfo(const ShapeInfo& info); const btCollisionShape* createShapeFromInfo(const ShapeInfo& info);
void deleteShape(btCollisionShape* shape); void deleteShape(const btCollisionShape* shape);
//btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info); //btTriangleIndexVertexArray* createStaticMeshArray(const ShapeInfo& info);
//void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray); //void deleteStaticMeshArray(btTriangleIndexVertexArray* dataArray);

View file

@ -28,15 +28,15 @@ ShapeManager::~ShapeManager() {
_shapeMap.clear(); _shapeMap.clear();
} }
btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) { const btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
if (info.getType() == SHAPE_TYPE_NONE) { if (info.getType() == SHAPE_TYPE_NONE) {
return NULL; return nullptr;
} }
const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube const float MIN_SHAPE_DIAGONAL_SQUARED = 3.0e-4f; // 1 cm cube
if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) { if (4.0f * glm::length2(info.getHalfExtents()) < MIN_SHAPE_DIAGONAL_SQUARED) {
// tiny shapes are not supported // tiny shapes are not supported
// qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal; // qCDebug(physics) << "ShapeManager::getShape -- not making shape due to size" << diagonal;
return NULL; return nullptr;
} }
DoubleHashKey key = info.getHash(); DoubleHashKey key = info.getHash();
@ -45,7 +45,7 @@ btCollisionShape* ShapeManager::getShape(const ShapeInfo& info) {
shapeRef->refCount++; shapeRef->refCount++;
return shapeRef->shape; return shapeRef->shape;
} }
btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info); const btCollisionShape* shape = ShapeFactory::createShapeFromInfo(info);
if (shape) { if (shape) {
ShapeReference newRef; ShapeReference newRef;
newRef.refCount = 1; newRef.refCount = 1;

View file

@ -26,7 +26,7 @@ public:
~ShapeManager(); ~ShapeManager();
/// \return pointer to shape /// \return pointer to shape
btCollisionShape* getShape(const ShapeInfo& info); const btCollisionShape* getShape(const ShapeInfo& info);
/// \return true if shape was found and released /// \return true if shape was found and released
bool releaseShape(const btCollisionShape* shape); bool releaseShape(const btCollisionShape* shape);
@ -43,11 +43,12 @@ public:
private: private:
bool releaseShapeByKey(const DoubleHashKey& key); bool releaseShapeByKey(const DoubleHashKey& key);
struct ShapeReference { class ShapeReference {
public:
int refCount; int refCount;
btCollisionShape* shape; const btCollisionShape* shape;
DoubleHashKey key; DoubleHashKey key;
ShapeReference() : refCount(0), shape(NULL) {} ShapeReference() : refCount(0), shape(nullptr) {}
}; };
btHashMap<DoubleHashKey, ShapeReference> _shapeMap; btHashMap<DoubleHashKey, ShapeReference> _shapeMap;

View file

@ -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); updateMeshPart(mesh, partIndex);
updateMaterial(material); updateMaterial(material);
updateTransform(transform, offsetTransform);
} }
void MeshPartPayload::updateMeshPart(const std::shared_ptr<const model::Mesh>& drawMesh, int partIndex) { 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 // 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. // to false to rebuild out mesh groups.
if (_meshIndex < 0 || _meshIndex >= (int)networkMeshes.size() || _meshIndex > geometry.meshes.size()) { if (_meshIndex < 0 || _meshIndex >= (int)networkMeshes.size() || _meshIndex > geometry.meshes.size()) {
_model->_meshGroupsKnown = false; // regenerate these lists next time around. _model->_needsFixupInScene = true; // trigger remove/add cycle
_model->_readyWhenAdded = false; // in case any of our users are using scenes
_model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid _model->invalidCalculatedMeshBoxes(); // if we have to reload, we need to assume our mesh boxes are all invalid
return ShapeKey::Builder::invalid(); return ShapeKey::Builder::invalid();
} }
@ -533,7 +530,7 @@ void ModelMeshPartPayload::startFade() {
void ModelMeshPartPayload::render(RenderArgs* args) const { void ModelMeshPartPayload::render(RenderArgs* args) const {
PerformanceTimer perfTimer("ModelMeshPartPayload::render"); PerformanceTimer perfTimer("ModelMeshPartPayload::render");
if (!_model->_readyWhenAdded || !_model->_isVisible) { if (!_model->addedToScene() || !_model->isVisible()) {
return; // bail asap return; // bail asap
} }

View file

@ -26,7 +26,7 @@ class Model;
class MeshPartPayload { class MeshPartPayload {
public: public:
MeshPartPayload() {} 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 render::Payload<MeshPartPayload> Payload;
typedef Payload::DataPointer Pointer; typedef Payload::DataPointer Pointer;

View file

@ -37,9 +37,9 @@ float Model::FAKE_DIMENSION_PLACEHOLDER = -1.0f;
#define HTTP_INVALID_COM "http://invalid.com" #define HTTP_INVALID_COM "http://invalid.com"
const int NUM_COLLISION_HULL_COLORS = 24; 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 // generates bright colors in red, green, blue, yellow, magenta, and cyan spectrums
// (no browns, greys, or dark shades) // (no browns, greys, or dark shades)
float component[NUM_COLLISION_HULL_COLORS] = { float component[NUM_COLLISION_HULL_COLORS] = {
@ -50,7 +50,7 @@ void initCollisionHullMaterials() {
1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f,
0.8f, 0.6f, 0.4f, 0.2f 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 // each component gets the same cuve
// but offset by a multiple of one third the full width // 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->setAlbedo(glm::vec3(red, green, blue));
material->setMetallic(0.02f); material->setMetallic(0.02f);
material->setRoughness(0.5f); material->setRoughness(0.5f);
_collisionHullMaterials.push_back(material); _collisionMaterials.push_back(material);
} }
} }
} }
@ -82,7 +82,6 @@ Model::Model(RigPointer rig, QObject* parent) :
_renderGeometry(), _renderGeometry(),
_collisionGeometry(), _collisionGeometry(),
_renderWatcher(_renderGeometry), _renderWatcher(_renderGeometry),
_collisionWatcher(_collisionGeometry),
_translation(0.0f), _translation(0.0f),
_rotation(), _rotation(),
_scale(1.0f, 1.0f, 1.0f), _scale(1.0f, 1.0f, 1.0f),
@ -100,7 +99,6 @@ Model::Model(RigPointer rig, QObject* parent) :
_calculatedMeshPartBoxesValid(false), _calculatedMeshPartBoxesValid(false),
_calculatedMeshBoxesValid(false), _calculatedMeshBoxesValid(false),
_calculatedMeshTrianglesValid(false), _calculatedMeshTrianglesValid(false),
_meshGroupsKnown(false),
_isWireframe(false), _isWireframe(false),
_rig(rig) _rig(rig)
{ {
@ -112,7 +110,6 @@ Model::Model(RigPointer rig, QObject* parent) :
setSnapModelToRegistrationPoint(true, glm::vec3(0.5f)); setSnapModelToRegistrationPoint(true, glm::vec3(0.5f));
connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished); connect(&_renderWatcher, &GeometryResourceWatcher::finished, this, &Model::loadURLFinished);
connect(&_collisionWatcher, &GeometryResourceWatcher::finished, this, &Model::loadCollisionModelURLFinished);
} }
Model::~Model() { Model::~Model() {
@ -122,18 +119,11 @@ Model::~Model() {
AbstractViewStateInterface* Model::_viewState = NULL; AbstractViewStateInterface* Model::_viewState = NULL;
bool Model::needsFixupInScene() const { bool Model::needsFixupInScene() const {
if (readyToAddToScene()) { return (_needsFixupInScene || !_addedToScene) && !_needsReload && isLoaded();
if (_needsUpdateTextures && _renderGeometry->areTexturesLoaded()) {
_needsUpdateTextures = false;
return true;
}
if (!_readyWhenAdded) {
return true;
}
}
return false;
} }
// 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) { void Model::setTranslation(const glm::vec3& translation) {
_translation = translation; _translation = translation;
updateRenderItems(); updateRenderItems();
@ -172,7 +162,15 @@ void Model::setOffset(const glm::vec3& offset) {
} }
void Model::updateRenderItems() { void Model::updateRenderItems() {
if (!_addedToScene) {
return;
}
glm::vec3 scale = getScale();
if (_collisionGeometry) {
// _collisionGeometry is already scaled
scale = glm::vec3(1.0f);
}
_needsUpdateClusterMatrices = true; _needsUpdateClusterMatrices = true;
_renderItemsNeedUpdate = false; _renderItemsNeedUpdate = false;
@ -180,7 +178,7 @@ void Model::updateRenderItems() {
// the application will ensure only the last lambda is actually invoked. // the application will ensure only the last lambda is actually invoked.
void* key = (void*)this; void* key = (void*)this;
std::weak_ptr<Model> weakSelf = shared_from_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. // do nothing, if the model has already been destroyed.
auto self = weakSelf.lock(); auto self = weakSelf.lock();
@ -191,7 +189,7 @@ void Model::updateRenderItems() {
render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene(); render::ScenePointer scene = AbstractViewStateInterface::instance()->getMain3DScene();
Transform modelTransform; Transform modelTransform;
modelTransform.setScale(self->_scale); modelTransform.setScale(scale);
modelTransform.setTranslation(self->_translation); modelTransform.setTranslation(self->_translation);
modelTransform.setRotation(self->_rotation); modelTransform.setRotation(self->_rotation);
@ -203,10 +201,6 @@ void Model::updateRenderItems() {
modelMeshOffset.postTranslate(self->_offset); 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; uint32_t deleteGeometryCounter = self->_deleteGeometryCounter;
render::PendingChanges pendingChanges; 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()) { foreach (auto itemID, self->_collisionRenderItems.keys()) {
pendingChanges.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) { pendingChanges.updateItem<MeshPartPayload>(itemID, [modelTransform, collisionMeshOffset](MeshPartPayload& data) {
// update the model transform for this render item. // update the model transform for this render item.
@ -574,8 +571,8 @@ void Model::renderSetup(RenderArgs* args) {
} }
} }
if (!_meshGroupsKnown && isLoaded()) { if (!_addedToScene && isLoaded()) {
segregateMeshGroups(); createRenderItemSet();
} }
} }
@ -596,27 +593,14 @@ void Model::setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scen
bool Model::addToScene(std::shared_ptr<render::Scene> scene, bool Model::addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges, render::PendingChanges& pendingChanges,
render::Item::Status::Getters& statusGetters, render::Item::Status::Getters& statusGetters) {
bool showCollisionHull) { bool readyToRender = _collisionGeometry || isLoaded();
if ((!_meshGroupsKnown || showCollisionHull != _showCollisionHull) && isLoaded()) { if (!_addedToScene && readyToRender) {
_showCollisionHull = showCollisionHull; createRenderItemSet();
segregateMeshGroups();
} }
bool somethingAdded = false; bool somethingAdded = false;
if (_collisionGeometry) {
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);
somethingAdded = true;
}
}
if (_collisionRenderItems.empty()) { if (_collisionRenderItems.empty()) {
foreach (auto renderItem, _collisionRenderItemsSet) { foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID(); auto item = scene->allocateID();
@ -626,13 +610,29 @@ bool Model::addToScene(std::shared_ptr<render::Scene> scene,
} }
pendingChanges.resetItem(item, renderPayload); pendingChanges.resetItem(item, renderPayload);
_collisionRenderItems.insert(item, renderPayload); _collisionRenderItems.insert(item, renderPayload);
somethingAdded = true; }
somethingAdded = !_collisionRenderItems.empty();
}
} 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);
}
somethingAdded = !_modelMeshRenderItems.empty();
} }
} }
if (somethingAdded) {
_addedToScene = true;
updateRenderItems(); updateRenderItems();
_needsFixupInScene = false;
_readyWhenAdded = readyToAddToScene(); }
return somethingAdded; return somethingAdded;
} }
@ -643,13 +643,13 @@ void Model::removeFromScene(std::shared_ptr<render::Scene> scene, render::Pendin
} }
_modelMeshRenderItems.clear(); _modelMeshRenderItems.clear();
_modelMeshRenderItemsSet.clear(); _modelMeshRenderItemsSet.clear();
foreach (auto item, _collisionRenderItems.keys()) { foreach (auto item, _collisionRenderItems.keys()) {
pendingChanges.removeItem(item); pendingChanges.removeItem(item);
} }
_collisionRenderItems.clear(); _collisionRenderItems.clear();
_collisionRenderItemsSet.clear(); _collisionRenderItemsSet.clear();
_meshGroupsKnown = false; _addedToScene = false;
_readyWhenAdded = false;
} }
void Model::renderDebugMeshBoxes(gpu::Batch& batch) { void Model::renderDebugMeshBoxes(gpu::Batch& batch) {
@ -804,6 +804,7 @@ int Model::getLastFreeJointIndex(int jointIndex) const {
void Model::setTextures(const QVariantMap& textures) { void Model::setTextures(const QVariantMap& textures) {
if (isLoaded()) { if (isLoaded()) {
_needsUpdateTextures = true; _needsUpdateTextures = true;
_needsFixupInScene = true;
_renderGeometry->setTextures(textures); _renderGeometry->setTextures(textures);
} }
} }
@ -825,8 +826,8 @@ void Model::setURL(const QUrl& url) {
_needsReload = true; _needsReload = true;
_needsUpdateTextures = true; _needsUpdateTextures = true;
_meshGroupsKnown = false;
_visualGeometryRequestFailed = false; _visualGeometryRequestFailed = false;
_needsFixupInScene = true;
invalidCalculatedMeshBoxes(); invalidCalculatedMeshBoxes();
deleteGeometry(); deleteGeometry();
@ -843,23 +844,6 @@ void Model::loadURLFinished(bool success) {
emit setURLFinished(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 { bool Model::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const {
return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation); return _rig->getJointPositionInWorldFrame(jointIndex, position, _translation, _rotation);
} }
@ -1236,21 +1220,21 @@ AABox Model::getRenderableMeshBound() const {
} }
} }
void Model::segregateMeshGroups() { void Model::createRenderItemSet() {
Geometry::Pointer geometry; if (_collisionGeometry) {
bool showingCollisionHull = false; if (_collisionRenderItemsSet.empty()) {
if (_showCollisionHull && _collisionGeometry) { createCollisionRenderItemSet();
if (isCollisionLoaded()) {
geometry = _collisionGeometry;
showingCollisionHull = true;
} else {
return;
} }
} else { } else {
if (_modelMeshRenderItemsSet.empty()) {
createVisibleRenderItemSet();
}
}
};
void Model::createVisibleRenderItemSet() {
assert(isLoaded()); assert(isLoaded());
geometry = _renderGeometry; const auto& meshes = _renderGeometry->getMeshes();
}
const auto& meshes = geometry->getMeshes();
// all of our mesh vectors must match in size // all of our mesh vectors must match in size
if ((int)meshes.size() != _meshStates.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 // We should not have any existing renderItems if we enter this section of code
Q_ASSERT(_modelMeshRenderItems.isEmpty());
Q_ASSERT(_modelMeshRenderItemsSet.isEmpty()); Q_ASSERT(_modelMeshRenderItemsSet.isEmpty());
Q_ASSERT(_collisionRenderItems.isEmpty());
Q_ASSERT(_collisionRenderItemsSet.isEmpty());
_modelMeshRenderItemsSet.clear(); _modelMeshRenderItemsSet.clear();
_collisionRenderItemsSet.clear();
Transform transform; Transform transform;
transform.setTranslation(_translation); transform.setTranslation(_translation);
@ -1280,60 +1260,117 @@ void Model::segregateMeshGroups() {
uint32_t numMeshes = (uint32_t)meshes.size(); uint32_t numMeshes = (uint32_t)meshes.size();
for (uint32_t i = 0; i < numMeshes; i++) { for (uint32_t i = 0; i < numMeshes; i++) {
const auto& mesh = meshes.at(i); const auto& mesh = meshes.at(i);
if (mesh) { if (!mesh) {
continue;
}
// Create the render payloads // Create the render payloads
int numParts = (int)mesh->getNumParts(); int numParts = (int)mesh->getNumParts();
for (int partIndex = 0; partIndex < numParts; partIndex++) { 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); _modelMeshRenderItemsSet << std::make_shared<ModelMeshPartPayload>(this, i, partIndex, shapeID, transform, offset);
}
shapeID++; 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) { bool Model::initWhenReady(render::ScenePointer scene) {
if (isActive() && isRenderable() && !_meshGroupsKnown && isLoaded()) { // NOTE: this only called by SkeletonModel
segregateMeshGroups(); if (_addedToScene || !isRenderable()) {
return false;
}
createRenderItemSet();
render::PendingChanges pendingChanges; render::PendingChanges pendingChanges;
Transform transform; bool addedPendingChanges = false;
transform.setTranslation(_translation); if (_collisionGeometry) {
transform.setRotation(_rotation);
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);
}
foreach (auto renderItem, _collisionRenderItemsSet) { foreach (auto renderItem, _collisionRenderItemsSet) {
auto item = scene->allocateID(); auto item = scene->allocateID();
auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem); auto renderPayload = std::make_shared<MeshPartPayload::Payload>(renderItem);
_collisionRenderItems.insert(item, renderPayload); _collisionRenderItems.insert(item, renderPayload);
pendingChanges.resetItem(item, renderPayload); pendingChanges.resetItem(item, renderPayload);
} }
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();
}
_addedToScene = addedPendingChanges;
if (addedPendingChanges) {
scene->enqueuePendingChanges(pendingChanges); 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(); updateRenderItems();
}
_readyWhenAdded = true;
return true; return true;
} }
return false;
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() : ModelBlender::ModelBlender() :

View file

@ -81,24 +81,20 @@ public:
// new Scene/Engine rendering support // new Scene/Engine rendering support
void setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene); void setVisibleInScene(bool newValue, std::shared_ptr<render::Scene> scene);
bool needsFixupInScene() const; bool needsFixupInScene() const;
bool readyToAddToScene(RenderArgs* renderArgs = nullptr) const {
return !_needsReload && isRenderable() && isActive();
}
bool needsReload() const { return _needsReload; } bool needsReload() const { return _needsReload; }
bool initWhenReady(render::ScenePointer scene); bool initWhenReady(render::ScenePointer scene);
bool addToScene(std::shared_ptr<render::Scene> scene, bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges, render::PendingChanges& pendingChanges) {
bool showCollisionHull = false) {
auto getters = render::Item::Status::Getters(0); 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, bool addToScene(std::shared_ptr<render::Scene> scene,
render::PendingChanges& pendingChanges, render::PendingChanges& pendingChanges,
render::Item::Status::Getters& statusGetters, render::Item::Status::Getters& statusGetters);
bool showCollisionHull = false);
void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges); void removeFromScene(std::shared_ptr<render::Scene> scene, render::PendingChanges& pendingChanges);
void renderSetup(RenderArgs* args); void renderSetup(RenderArgs* args);
bool isRenderable() const { return !_meshStates.isEmpty() || (isActive() && _renderGeometry->getMeshes().empty()); } bool isRenderable() const;
bool isVisible() const { return _isVisible; } bool isVisible() const { return _isVisible; }
@ -114,7 +110,6 @@ public:
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals); const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
bool isLoaded() const { return (bool)_renderGeometry; } bool isLoaded() const { return (bool)_renderGeometry; }
bool isCollisionLoaded() const { return (bool)_collisionGeometry; }
void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; } void setIsWireframe(bool isWireframe) { _isWireframe = isWireframe; }
bool isWireframe() const { return _isWireframe; } bool isWireframe() const { return _isWireframe; }
@ -141,13 +136,6 @@ public:
/// Provided as a convenience, will crash if !isLoaded() /// Provided as a convenience, will crash if !isLoaded()
// And so that getGeometry() isn't chained everywhere // And so that getGeometry() isn't chained everywhere
const FBXGeometry& getFBXGeometry() const { assert(isLoaded()); return _renderGeometry->getFBXGeometry(); } 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(); } bool isActive() const { return isLoaded(); }
@ -185,6 +173,7 @@ public:
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const; bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position) const;
bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const; bool getJointRotationInWorldFrame(int jointIndex, glm::quat& rotation) const;
bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const; bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const;
/// \param jointIndex index of joint in model structure /// \param jointIndex index of joint in model structure
/// \param rotation[out] rotation of joint in model-frame /// \param rotation[out] rotation of joint in model-frame
/// \return true if joint exists /// \return true if joint exists
@ -239,18 +228,19 @@ public:
// returns 'true' if needs fullUpdate after geometry change // returns 'true' if needs fullUpdate after geometry change
bool updateGeometry(); bool updateGeometry();
void setCollisionMesh(model::MeshPointer mesh);
void setLoadingPriority(float priority) { _loadingPriority = priority; } void setLoadingPriority(float priority) { _loadingPriority = priority; }
public slots: public slots:
void loadURLFinished(bool success); void loadURLFinished(bool success);
void loadCollisionModelURLFinished(bool success);
signals: signals:
void setURLFinished(bool success); void setURLFinished(bool success);
void setCollisionModelURLFinished(bool success); void setCollisionModelURLFinished(bool success);
protected: protected:
bool addedToScene() const { return _addedToScene; }
void setPupilDilation(float dilation) { _pupilDilation = dilation; } void setPupilDilation(float dilation) { _pupilDilation = dilation; }
float getPupilDilation() const { return _pupilDilation; } float getPupilDilation() const { return _pupilDilation; }
@ -282,10 +272,9 @@ protected:
bool getJointPosition(int jointIndex, glm::vec3& position) const; bool getJointPosition(int jointIndex, glm::vec3& position) const;
Geometry::Pointer _renderGeometry; // only ever set by its watcher Geometry::Pointer _renderGeometry; // only ever set by its watcher
Geometry::Pointer _collisionGeometry; // only ever set by its watcher Geometry::Pointer _collisionGeometry;
GeometryResourceWatcher _renderWatcher; GeometryResourceWatcher _renderWatcher;
GeometryResourceWatcher _collisionWatcher;
glm::vec3 _translation; glm::vec3 _translation;
glm::quat _rotation; glm::quat _rotation;
@ -353,7 +342,6 @@ protected:
QVector<float> _blendshapeCoefficients; QVector<float> _blendshapeCoefficients;
QUrl _url; QUrl _url;
QUrl _collisionUrl;
bool _isVisible; bool _isVisible;
gpu::Buffers _blendedVertexBuffers; gpu::Buffers _blendedVertexBuffers;
@ -376,10 +364,10 @@ protected:
void recalculateMeshBoxes(bool pickAgainstTriangles = false); void recalculateMeshBoxes(bool pickAgainstTriangles = false);
void segregateMeshGroups(); // used to calculate our list of translucent vs opaque meshes void createRenderItemSet();
static model::MaterialPointer _collisionHullMaterial; void createVisibleRenderItemSet();
void createCollisionRenderItemSet();
bool _meshGroupsKnown;
bool _isWireframe; bool _isWireframe;
@ -396,10 +384,10 @@ protected:
QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet; QSet<std::shared_ptr<ModelMeshPartPayload>> _modelMeshRenderItemsSet;
QMap<render::ItemID, render::PayloadPointer> _modelMeshRenderItems; 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 _needsReload { true };
bool _needsUpdateClusterMatrices { true }; bool _needsUpdateClusterMatrices { true };
bool _showCollisionHull { false };
mutable bool _needsUpdateTextures { true }; mutable bool _needsUpdateTextures { true };
friend class ModelMeshPartPayload; friend class ModelMeshPartPayload;

View file

@ -2,7 +2,7 @@
# Declare dependencies # Declare dependencies
macro (SETUP_TESTCASE_DEPENDENCIES) macro (SETUP_TESTCASE_DEPENDENCIES)
target_bullet() target_bullet()
link_hifi_libraries(shared physics) link_hifi_libraries(shared physics gpu model)
package_libraries_for_deployment() package_libraries_for_deployment()
endmacro () endmacro ()

View 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;
}

View 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

View 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;
}

View 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

View file

@ -12,6 +12,7 @@
#include <iostream> #include <iostream>
#include <ShapeManager.h> #include <ShapeManager.h>
#include <StreamUtils.h> #include <StreamUtils.h>
#include <Extents.h>
#include "ShapeManagerTests.h" #include "ShapeManagerTests.h"
@ -197,6 +198,7 @@ void ShapeManagerTests::addCompoundShape() {
ShapeInfo::PointCollection pointCollection; ShapeInfo::PointCollection pointCollection;
int numHulls = 5; int numHulls = 5;
glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f); glm::vec3 offsetNormal(1.0f, 0.0f, 0.0f);
Extents extents;
for (int i = 0; i < numHulls; ++i) { for (int i = 0; i < numHulls; ++i) {
glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal; glm::vec3 offset = (float)(i - numHulls/2) * offsetNormal;
ShapeInfo::PointList pointList; ShapeInfo::PointList pointList;
@ -204,13 +206,16 @@ void ShapeManagerTests::addCompoundShape() {
for (int j = 0; j < numHullPoints; ++j) { for (int j = 0; j < numHullPoints; ++j) {
glm::vec3 point = radius * tetrahedron[j] + offset; glm::vec3 point = radius * tetrahedron[j] + offset;
pointList.push_back(point); pointList.push_back(point);
extents.addPoint(point);
} }
pointCollection.push_back(pointList); pointCollection.push_back(pointList);
} }
// create the ShapeInfo // create the ShapeInfo
ShapeInfo info; 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 // create the shape
ShapeManager shapeManager; ShapeManager shapeManager;