diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h
index 27ab375a42..ebd65de612 100644
--- a/interface/src/ui/Stats.h
+++ b/interface/src/ui/Stats.h
@@ -248,9 +248,9 @@ private: \
* Read-only.
* @property {string} lodStatus - Description of the current LOD.
* Read-only.
- * @property {string} numEntityUpdates - The number of entity updates that happened last frame.
+ * @property {number} numEntityUpdates - The number of entity updates that happened last frame.
* Read-only.
- * @property {string} numNeededEntityUpdates - The total number of entity updates scheduled for last frame.
+ * @property {number} numNeededEntityUpdates - The total number of entity updates scheduled for last frame.
* Read-only.
* @property {string} timingStats - Details of the average time (ms) spent in and number of calls made to different parts of
* the code. Provided only if timingExpanded
is true
. Only the top 10 items are provided if
@@ -547,8 +547,8 @@ class Stats : public QQuickItem {
STATS_PROPERTY(int, lodAngle, 0)
STATS_PROPERTY(int, lodTargetFramerate, 0)
STATS_PROPERTY(QString, lodStatus, QString())
- STATS_PROPERTY(int, numEntityUpdates, 0)
- STATS_PROPERTY(int, numNeededEntityUpdates, 0)
+ STATS_PROPERTY(quint64, numEntityUpdates, 0)
+ STATS_PROPERTY(quint64, numNeededEntityUpdates, 0)
STATS_PROPERTY(QString, timingStats, QString())
STATS_PROPERTY(QString, gameUpdateStats, QString())
STATS_PROPERTY(int, serverElements, 0)
diff --git a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
index 4950b86f75..5ac808d3fb 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
@@ -156,17 +156,13 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
updateAttitude(_owningAvatar->getWorldOrientation());
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
+ Parent::simulate(deltaTime, fullUpdate);
if (fullUpdate) {
-
- Parent::simulate(deltaTime, fullUpdate);
-
// let rig compute the model offset
glm::vec3 registrationPoint;
if (_rig.getModelRegistrationPoint(registrationPoint)) {
setOffset(registrationPoint);
}
- } else {
- Parent::simulate(deltaTime, fullUpdate);
}
// FIXME: This texture loading logic should probably live in Avatar, to mirror RenderableModelEntityItem,
diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.h b/libraries/entities-renderer/src/EntityTreeRenderer.h
index f7623aad10..009e5f6c4f 100644
--- a/libraries/entities-renderer/src/EntityTreeRenderer.h
+++ b/libraries/entities-renderer/src/EntityTreeRenderer.h
@@ -136,8 +136,8 @@ public:
static bool addMaterialToAvatar(const QUuid& avatarID, graphics::MaterialLayer material, const std::string& parentMaterialName);
static bool removeMaterialFromAvatar(const QUuid& avatarID, graphics::MaterialPointer material, const std::string& parentMaterialName);
- int getPrevNumEntityUpdates() const { return _prevNumEntityUpdates; }
- int getPrevTotalNeededEntityUpdates() const { return _prevTotalNeededEntityUpdates; }
+ size_t getPrevNumEntityUpdates() const { return _prevNumEntityUpdates; }
+ size_t getPrevTotalNeededEntityUpdates() const { return _prevTotalNeededEntityUpdates; }
signals:
void enterEntity(const EntityItemID& entityItemID);
@@ -253,8 +253,8 @@ private:
ReadWriteLockable _changedEntitiesGuard;
std::unordered_set _changedEntities;
- int _prevNumEntityUpdates { 0 };
- int _prevTotalNeededEntityUpdates { 0 };
+ size_t _prevNumEntityUpdates { 0 };
+ size_t _prevTotalNeededEntityUpdates { 0 };
std::unordered_set _renderablesToUpdate;
std::unordered_map _entitiesInScene;
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
index d6b7313ae7..940bf55e86 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -149,10 +149,11 @@ void RenderableModelEntityItem::updateModelBounds() {
bool overridingModelTransform = model->isOverridingModelTransformAndOffset();
glm::vec3 scaledDimensions = getScaledDimensions();
glm::vec3 registrationPoint = getRegistrationPoint();
+ bool needsSimulate = false;
if (!overridingModelTransform &&
(model->getScaleToFitDimensions() != scaledDimensions ||
model->getRegistrationPoint() != registrationPoint ||
- !model->getIsScaledToFit() || _needsToRescaleModel)) {
+ !model->getIsScaledToFit() || _useOriginalPivot == model->getSnapModelToRegistrationPoint())) {
// The machinery for updateModelBounds will give existing models the opportunity to fix their
// translation/rotation/scale/registration. The first two are straightforward, but the latter two
// have guards to make sure they don't happen after they've already been set. Here we reset those guards.
@@ -162,9 +163,10 @@ void RenderableModelEntityItem::updateModelBounds() {
// now recalculate the bounds and registration
model->setScaleToFit(true, scaledDimensions);
- model->setSnapModelToRegistrationPoint(true, registrationPoint);
+ model->setSnapModelToRegistrationPoint(!_useOriginalPivot, registrationPoint);
updateRenderItems = true;
- model->scaleToFit();
+ needsSimulate = true;
+ locationChanged();
_needsToRescaleModel = false;
}
@@ -176,7 +178,7 @@ void RenderableModelEntityItem::updateModelBounds() {
updateRenderItems = true;
}
- if (_needsInitialSimulation || _needsJointSimulation || isAnimatingSomething()) {
+ if (_needsInitialSimulation || _needsJointSimulation || needsSimulate || isAnimatingSomething()) {
// NOTE: on isAnimatingSomething() we need to call Model::simulate() which calls Rig::updateRig()
// TODO: there is opportunity to further optimize the isAnimatingSomething() case.
model->simulate(0.0f);
@@ -222,6 +224,16 @@ EntityItemProperties RenderableModelEntityItem::getProperties(const EntityProper
return properties;
}
+glm::vec3 RenderableModelEntityItem::getPivot() const {
+ auto model = getModel();
+ auto raycastOffset = EntityItem::getPivot();
+ if (!model || !model->isLoaded() || !_useOriginalPivot) {
+ return raycastOffset;
+ }
+
+ return raycastOffset + model->getOriginalOffset();
+}
+
bool RenderableModelEntityItem::supportsDetailedIntersection() const {
return true;
}
@@ -452,7 +464,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + model->getOffset()) - registrationOffset;
}
}
- shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL());
+ shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL() + model->getSnapModelToRegistrationPoint());
adjustShapeInfoByRegistration(shapeInfo);
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
updateModelBounds();
@@ -685,7 +697,7 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
}
}
- shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL());
+ shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL() + model->getSnapModelToRegistrationPoint());
adjustShapeInfoByRegistration(shapeInfo);
} else {
EntityItem::computeShapeInfo(shapeInfo);
diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.h b/libraries/entities-renderer/src/RenderableModelEntityItem.h
index 4501f6d88c..1ef11b6906 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.h
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.h
@@ -42,7 +42,7 @@ protected:
void setModel(const ModelPointer& model);
ModelPointer getModel() const;
- bool _needsInitialSimulation{ true };
+ bool _needsInitialSimulation { true };
private:
ModelPointer _model;
};
@@ -63,6 +63,7 @@ public:
virtual EntityItemProperties getProperties(const EntityPropertyFlags& desiredProperties, bool allowEmptyDesiredProperties) const override;
void updateModelBounds();
+ glm::vec3 getPivot() const override;
virtual bool supportsDetailedIntersection() const override;
virtual bool findDetailedRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
OctreeElementPointer& element, float& distance,
diff --git a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp
index f0a6684654..d779409e9c 100644
--- a/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderablePolyVoxEntityItem.cpp
@@ -723,8 +723,10 @@ ShapeType RenderablePolyVoxEntityItem::getShapeType() const {
}
void RenderablePolyVoxEntityItem::setRegistrationPoint(const glm::vec3& value) {
- if (value != _registrationPoint) {
- _shapeReady = false;
+ if (value != getRegistrationPoint()) {
+ withWriteLock([&] {
+ _shapeReady = false;
+ });
EntityItem::setRegistrationPoint(value);
startUpdates();
}
diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp
index d7a5e992e1..48490aeb51 100644
--- a/libraries/entities/src/EntityItem.cpp
+++ b/libraries/entities/src/EntityItem.cpp
@@ -1607,8 +1607,13 @@ void EntityItem::recordCreationTime() {
const Transform EntityItem::getTransformToCenter(bool& success) const {
Transform result = getTransform(success);
- if (getRegistrationPoint() != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center
- result.postTranslate((ENTITY_ITEM_HALF_VEC3 - getRegistrationPoint()) * getScaledDimensions()); // Position to center
+ glm::vec3 pivot = getPivot();
+ if (pivot != ENTITY_ITEM_ZERO_VEC3) {
+ result.postTranslate(pivot);
+ }
+ glm::vec3 registrationPoint = getRegistrationPoint();
+ if (registrationPoint != ENTITY_ITEM_HALF_VEC3) { // If it is not already centered, translate to center
+ result.postTranslate((ENTITY_ITEM_HALF_VEC3 - registrationPoint) * getScaledDimensions()); // Position to center
}
return result;
}
@@ -1624,7 +1629,8 @@ AACube EntityItem::getMaximumAACube(bool& success) const {
// we want to compute the furthestExtent that an entity can extend out from its "position"
// to do this we compute the max of these two vec3s: registration and 1-registration
// and then scale by dimensions
- glm::vec3 maxExtents = getScaledDimensions() * glm::max(_registrationPoint, glm::vec3(1.0f) - _registrationPoint);
+ glm::vec3 offset = getScaledDimensions() * getRegistrationPoint() + getPivot();
+ glm::vec3 maxExtents = glm::max(offset, glm::vec3(1.0f) - offset);
// there exists a sphere that contains maxExtents for all rotations
float radius = glm::length(maxExtents);
@@ -1650,9 +1656,12 @@ AACube EntityItem::getMinimumAACube(bool& success) const {
if (success) {
_recalcMinAACube = false;
glm::vec3 dimensions = getScaledDimensions();
- glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint);
- glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint);
+ glm::vec3 registrationPoint = getRegistrationPoint();
+ glm::vec3 pivot = getPivot();
+ glm::vec3 unrotatedMinRelativeToEntity = -(dimensions * registrationPoint);
+ glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (ENTITY_ITEM_ONE_VEC3 - registrationPoint);
Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
+ extents.shiftBy(pivot);
extents.rotate(getWorldOrientation());
// shift the extents to be relative to the position/registration point
@@ -1680,9 +1689,12 @@ AABox EntityItem::getAABox(bool& success) const {
if (success) {
_recalcAABox = false;
glm::vec3 dimensions = getScaledDimensions();
- glm::vec3 unrotatedMinRelativeToEntity = - (dimensions * _registrationPoint);
- glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint);
+ glm::vec3 registrationPoint = getRegistrationPoint();
+ glm::vec3 pivot = getPivot();
+ glm::vec3 unrotatedMinRelativeToEntity = -(dimensions * registrationPoint);
+ glm::vec3 unrotatedMaxRelativeToEntity = dimensions * (ENTITY_ITEM_ONE_VEC3 - registrationPoint);
Extents extents = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
+ extents.shiftBy(pivot);
extents.rotate(getWorldOrientation());
// shift the extents to be relative to the position/registration point
@@ -1723,11 +1735,10 @@ float EntityItem::getRadius() const {
}
void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const {
- if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
- glm::mat4 scale = glm::scale(getScaledDimensions());
- glm::mat4 registration = scale * glm::translate(ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint());
- glm::vec3 regTransVec = glm::vec3(registration[3]); // extract position component from matrix
- info.setOffset(regTransVec);
+ glm::vec3 registrationPoint = getRegistrationPoint();
+ if (registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
+ glm::vec3 registration = (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - registrationPoint) * getScaledDimensions();
+ info.setOffset(registration);
}
}
@@ -1739,7 +1750,7 @@ bool EntityItem::contains(const glm::vec3& point) const {
// anything with shapeType == SPHERE must collide as a bounding sphere in the world-frame regardless of dimensions
// therefore we must do math using an unscaled localPoint relative to sphere center
glm::vec3 dimensions = getScaledDimensions();
- glm::vec3 localPoint = point - (getWorldPosition() + getWorldOrientation() * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint())));
+ glm::vec3 localPoint = point - (getWorldPosition() + getWorldOrientation() * (dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint()) + getPivot()));
const float HALF_SQUARED = 0.25f;
return glm::length2(localPoint) < HALF_SQUARED * glm::length2(dimensions);
}
@@ -1797,11 +1808,16 @@ float EntityItem::getVolumeEstimate() const {
}
void EntityItem::setRegistrationPoint(const glm::vec3& value) {
- if (value != _registrationPoint) {
- withWriteLock([&] {
+ bool changed = false;
+ withWriteLock([&] {
+ if (value != _registrationPoint) {
_registrationPoint = glm::clamp(value, glm::vec3(ENTITY_ITEM_MIN_REGISTRATION_POINT),
glm::vec3(ENTITY_ITEM_MAX_REGISTRATION_POINT));
- });
+ changed = true;
+ }
+ });
+
+ if (changed) {
dimensionsChanged(); // Registration Point affects the bounding box
markDirtyFlags(Simulation::DIRTY_SHAPE);
}
@@ -2892,11 +2908,9 @@ QString EntityItem::getCollisionSoundURL() const {
}
glm::vec3 EntityItem::getRegistrationPoint() const {
- glm::vec3 result;
- withReadLock([&] {
- result = _registrationPoint;
+ return resultWithReadLock([&] {
+ return _registrationPoint;
});
- return result;
}
float EntityItem::getAngularDamping() const {
diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h
index 2a6952fc0d..e12c2d864b 100644
--- a/libraries/entities/src/EntityItem.h
+++ b/libraries/entities/src/EntityItem.h
@@ -204,6 +204,7 @@ public:
virtual glm::vec3 getScaledDimensions() const;
virtual void setScaledDimensions(const glm::vec3& value);
virtual glm::vec3 getRaycastDimensions() const { return getScaledDimensions(); }
+ virtual glm::vec3 getPivot() const { return glm::vec3(0.0f); } // pivot offset for positioning, mainly for model entities
glm::vec3 getUnscaledDimensions() const;
virtual void setUnscaledDimensions(const glm::vec3& value);
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index ff55137a03..06e981238d 100644
--- a/libraries/entities/src/EntityItemProperties.cpp
+++ b/libraries/entities/src/EntityItemProperties.cpp
@@ -538,6 +538,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
CHECK_PROPERTY_CHANGE(PROP_GROUP_CULLED, groupCulled);
CHECK_PROPERTY_CHANGE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients);
+ CHECK_PROPERTY_CHANGE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot);
changedProperties += _animation.getChangedProperties();
// Light
@@ -1733,6 +1734,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_RELAY_PARENT_JOINTS, relayParentJoints);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_GROUP_CULLED, groupCulled);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_BLENDSHAPE_COEFFICIENTS, blendshapeCoefficients);
+ COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USE_ORIGINAL_PIVOT, useOriginalPivot);
if (!psuedoPropertyFlagsButDesiredEmpty) {
_animation.copyToScriptValue(_desiredProperties, properties, engine, skipDefaults, defaultEntityProperties);
}
@@ -2142,6 +2144,7 @@ void EntityItemProperties::copyFromScriptValue(const QScriptValue& object, bool
COPY_PROPERTY_FROM_QSCRIPTVALUE(relayParentJoints, bool, setRelayParentJoints);
COPY_PROPERTY_FROM_QSCRIPTVALUE(groupCulled, bool, setGroupCulled);
COPY_PROPERTY_FROM_QSCRIPTVALUE(blendshapeCoefficients, QString, setBlendshapeCoefficients);
+ COPY_PROPERTY_FROM_QSCRIPTVALUE(useOriginalPivot, bool, setUseOriginalPivot);
_animation.copyFromScriptValue(object, _defaultSettings);
// Light
@@ -2434,6 +2437,7 @@ void EntityItemProperties::merge(const EntityItemProperties& other) {
COPY_PROPERTY_IF_CHANGED(relayParentJoints);
COPY_PROPERTY_IF_CHANGED(groupCulled);
COPY_PROPERTY_IF_CHANGED(blendshapeCoefficients);
+ COPY_PROPERTY_IF_CHANGED(useOriginalPivot);
_animation.merge(other._animation);
// Light
@@ -2790,6 +2794,7 @@ bool EntityItemProperties::getPropertyInfo(const QString& propertyName, EntityPr
ADD_PROPERTY_TO_MAP(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool);
ADD_PROPERTY_TO_MAP(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool);
ADD_PROPERTY_TO_MAP(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString);
+ ADD_PROPERTY_TO_MAP(PROP_USE_ORIGINAL_PIVOT, UseOriginalPivot, useOriginalPivot, bool);
{ // Animation
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_URL, Animation, animation, URL, url);
ADD_GROUP_PROPERTY_TO_MAP(PROP_ANIMATION_ALLOW_TRANSLATION, Animation, animation, AllowTranslation, allowTranslation);
@@ -4129,6 +4134,7 @@ void EntityItemProperties::markAllChanged() {
_relayParentJointsChanged = true;
_groupCulledChanged = true;
_blendshapeCoefficientsChanged = true;
+ _useOriginalPivotChanged = true;
_animation.markAllChanged();
// Light
@@ -4701,6 +4707,9 @@ QList EntityItemProperties::listChangedProperties() {
if (blendshapeCoefficientsChanged()) {
out += "blendshapeCoefficients";
}
+ if (useOriginalPivotChanged()) {
+ out += "useOriginalPivot";
+ }
getAnimation().listChangedProperties(out);
// Light
diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h
index f7fde73430..f056187d4f 100644
--- a/libraries/entities/src/EntityItemProperties.h
+++ b/libraries/entities/src/EntityItemProperties.h
@@ -302,6 +302,7 @@ public:
DEFINE_PROPERTY(PROP_RELAY_PARENT_JOINTS, RelayParentJoints, relayParentJoints, bool, ENTITY_ITEM_DEFAULT_RELAY_PARENT_JOINTS);
DEFINE_PROPERTY_REF(PROP_GROUP_CULLED, GroupCulled, groupCulled, bool, false);
DEFINE_PROPERTY_REF(PROP_BLENDSHAPE_COEFFICIENTS, BlendshapeCoefficients, blendshapeCoefficients, QString, "");
+ DEFINE_PROPERTY_REF(PROP_USE_ORIGINAL_PIVOT, UseOriginalPivot, useOriginalPivot, bool, false);
DEFINE_PROPERTY_GROUP(Animation, animation, AnimationPropertyGroup);
// Light
diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h
index 93bb8a89a7..35c1f9a908 100644
--- a/libraries/entities/src/EntityPropertyFlags.h
+++ b/libraries/entities/src/EntityPropertyFlags.h
@@ -218,16 +218,17 @@ enum EntityPropertyList {
PROP_RELAY_PARENT_JOINTS = PROP_DERIVED_6,
PROP_GROUP_CULLED = PROP_DERIVED_7,
PROP_BLENDSHAPE_COEFFICIENTS = PROP_DERIVED_8,
+ PROP_USE_ORIGINAL_PIVOT = PROP_DERIVED_9,
// Animation
- PROP_ANIMATION_URL = PROP_DERIVED_9,
- PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_10,
- PROP_ANIMATION_FPS = PROP_DERIVED_11,
- PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_12,
- PROP_ANIMATION_PLAYING = PROP_DERIVED_13,
- PROP_ANIMATION_LOOP = PROP_DERIVED_14,
- PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_15,
- PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_16,
- PROP_ANIMATION_HOLD = PROP_DERIVED_17,
+ PROP_ANIMATION_URL = PROP_DERIVED_10,
+ PROP_ANIMATION_ALLOW_TRANSLATION = PROP_DERIVED_11,
+ PROP_ANIMATION_FPS = PROP_DERIVED_12,
+ PROP_ANIMATION_FRAME_INDEX = PROP_DERIVED_13,
+ PROP_ANIMATION_PLAYING = PROP_DERIVED_14,
+ PROP_ANIMATION_LOOP = PROP_DERIVED_15,
+ PROP_ANIMATION_FIRST_FRAME = PROP_DERIVED_16,
+ PROP_ANIMATION_LAST_FRAME = PROP_DERIVED_17,
+ PROP_ANIMATION_HOLD = PROP_DERIVED_18,
// Light
PROP_IS_SPOTLIGHT = PROP_DERIVED_0,
diff --git a/libraries/entities/src/EntityTreeElement.cpp b/libraries/entities/src/EntityTreeElement.cpp
index 9af0bbfdb6..16b6ccac9b 100644
--- a/libraries/entities/src/EntityTreeElement.cpp
+++ b/libraries/entities/src/EntityTreeElement.cpp
@@ -205,10 +205,7 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori
// (this is faster and more likely to cull results than the filter check below so we do it first)
bool success;
AABox entityBox = entity->getAABox(success);
- if (!success) {
- return;
- }
- if (!entityBox.rayHitsBoundingSphere(origin, direction)) {
+ if (!success || !entityBox.rayHitsBoundingSphere(origin, direction)) {
return;
}
@@ -226,7 +223,7 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori
glm::vec3 dimensions = entity->getRaycastDimensions();
glm::vec3 registrationPoint = entity->getRegistrationPoint();
- glm::vec3 corner = -(dimensions * registrationPoint);
+ glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions);
@@ -244,6 +241,9 @@ EntityItemID EntityTreeElement::evalDetailedRayIntersection(const glm::vec3& ori
// now ask the entity if we actually intersect
if (entity->supportsDetailedIntersection()) {
QVariantMap localExtraInfo;
+ if (entity->getName().contains("boop")) {
+ qDebug() << entity->getName() << entity->getPivot() << entityFrameBox;
+ }
if (entity->findDetailedRayIntersection(origin, direction, element, localDistance,
localFace, localSurfaceNormal, localExtraInfo, searchFilter.isPrecise())) {
if (localDistance < distance) {
@@ -277,11 +277,12 @@ bool EntityTreeElement::findSpherePenetration(const glm::vec3& center, float rad
bool result = false;
withReadLock([&] {
foreach(EntityItemPointer entity, _entityItems) {
- glm::vec3 entityCenter = entity->getWorldPosition();
+ bool success;
+ glm::vec3 entityCenter = entity->getCenterPosition(success);
float entityRadius = entity->getRadius();
// don't penetrate yourself
- if (entityCenter == center && entityRadius == radius) {
+ if (!success || (entityCenter == center && entityRadius == radius)) {
return;
}
@@ -349,15 +350,12 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3
// (this is faster and more likely to cull results than the filter check below so we do it first)
bool success;
AABox entityBox = entity->getAABox(success);
- if (!success) {
- return;
- }
// Instead of checking parabolaInstersectsBoundingSphere here, we are just going to check if the plane
// defined by the parabola slices the sphere. The solution to parabolaIntersectsBoundingSphere is cubic,
// the solution to which is more computationally expensive than the quadratic AABox::findParabolaIntersection
// below
- if (!entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) {
+ if (!success || !entityBox.parabolaPlaneIntersectsBoundingSphere(origin, velocity, acceleration, normal)) {
return;
}
@@ -375,7 +373,7 @@ EntityItemID EntityTreeElement::evalDetailedParabolaIntersection(const glm::vec3
glm::vec3 dimensions = entity->getRaycastDimensions();
glm::vec3 registrationPoint = entity->getRegistrationPoint();
- glm::vec3 corner = -(dimensions * registrationPoint);
+ glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions);
@@ -445,7 +443,6 @@ void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float ra
bool success;
AABox entityBox = entity->getAABox(success);
-
// if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case
glm::vec3 penetration;
if (success && entityBox.findSpherePenetration(position, radius, penetration)) {
@@ -464,10 +461,9 @@ void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float ra
float entityTrueRadius = dimensions.x / 2.0f;
bool success;
- if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) {
- if (success) {
- foundEntities.push_back(entity->getID());
- }
+ glm::vec3 center = entity->getCenterPosition(success);
+ if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) {
+ foundEntities.push_back(entity->getID());
}
} else {
// determine the worldToEntityMatrix that doesn't include scale because
@@ -478,7 +474,7 @@ void EntityTreeElement::evalEntitiesInSphere(const glm::vec3& position, float ra
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 registrationPoint = entity->getRegistrationPoint();
- glm::vec3 corner = -(dimensions * registrationPoint);
+ glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions);
@@ -499,7 +495,6 @@ void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position,
bool success;
AABox entityBox = entity->getAABox(success);
-
// if the sphere doesn't intersect with our world frame AABox, we don't need to consider the more complex case
glm::vec3 penetration;
if (success && entityBox.findSpherePenetration(position, radius, penetration)) {
@@ -518,10 +513,9 @@ void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position,
float entityTrueRadius = dimensions.x / 2.0f;
bool success;
- if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) {
- if (success) {
- foundEntities.push_back(entity->getID());
- }
+ glm::vec3 center = entity->getCenterPosition(success);
+ if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) {
+ foundEntities.push_back(entity->getID());
}
} else {
// determine the worldToEntityMatrix that doesn't include scale because
@@ -532,7 +526,7 @@ void EntityTreeElement::evalEntitiesInSphereWithType(const glm::vec3& position,
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 registrationPoint = entity->getRegistrationPoint();
- glm::vec3 corner = -(dimensions * registrationPoint);
+ glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions);
@@ -575,12 +569,11 @@ void EntityTreeElement::evalEntitiesInSphereWithName(const glm::vec3& position,
// NOTE: entity->getRadius() doesn't return the true radius, it returns the radius of the
// maximum bounding sphere, which is actually larger than our actual radius
float entityTrueRadius = dimensions.x / 2.0f;
-
bool success;
- if (findSphereSpherePenetration(position, radius, entity->getCenterPosition(success), entityTrueRadius, penetration)) {
- if (success) {
- foundEntities.push_back(entity->getID());
- }
+ glm::vec3 center = entity->getCenterPosition(success);
+
+ if (success && findSphereSpherePenetration(position, radius, center, entityTrueRadius, penetration)) {
+ foundEntities.push_back(entity->getID());
}
} else {
// determine the worldToEntityMatrix that doesn't include scale because
@@ -591,7 +584,7 @@ void EntityTreeElement::evalEntitiesInSphereWithName(const glm::vec3& position,
glm::mat4 worldToEntityMatrix = glm::inverse(entityToWorldMatrix);
glm::vec3 registrationPoint = entity->getRegistrationPoint();
- glm::vec3 corner = -(dimensions * registrationPoint);
+ glm::vec3 corner = -(dimensions * registrationPoint) + entity->getPivot();
AABox entityFrameBox(corner, dimensions);
@@ -612,6 +605,7 @@ void EntityTreeElement::evalEntitiesInCube(const AACube& cube, PickFilter search
bool success;
AABox entityBox = entity->getAABox(success);
+
// FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better
// FIXME - consider allowing the entity to determine penetration so that
// entities could presumably dull actuall hull testing if they wanted to
@@ -642,6 +636,7 @@ void EntityTreeElement::evalEntitiesInBox(const AABox& box, PickFilter searchFil
bool success;
AABox entityBox = entity->getAABox(success);
+
// FIXME - handle entity->getShapeType() == SHAPE_TYPE_SPHERE case better
// FIXME - consider allowing the entity to determine penetration so that
// entities could presumably dull actuall hull testing if they wanted to
@@ -680,7 +675,7 @@ void EntityTreeElement::evalEntitiesInFrustum(const ViewFrustum& frustum, PickFi
});
}
-void EntityTreeElement::getEntities(EntityItemFilter& filter, QVector& foundEntities) {
+void EntityTreeElement::getEntities(EntityItemFilter& filter, QVector& foundEntities) {
forEachEntity([&](EntityItemPointer entity) {
if (filter(entity)) {
foundEntities.push_back(entity);
diff --git a/libraries/entities/src/ModelEntityItem.cpp b/libraries/entities/src/ModelEntityItem.cpp
index 4716db6a41..50fd1f267a 100644
--- a/libraries/entities/src/ModelEntityItem.cpp
+++ b/libraries/entities/src/ModelEntityItem.cpp
@@ -73,6 +73,7 @@ EntityItemProperties ModelEntityItem::getProperties(const EntityPropertyFlags& d
COPY_ENTITY_PROPERTY_TO_PROPERTIES(relayParentJoints, getRelayParentJoints);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(groupCulled, getGroupCulled);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(blendshapeCoefficients, getBlendshapeCoefficients);
+ COPY_ENTITY_PROPERTY_TO_PROPERTIES(useOriginalPivot, getUseOriginalPivot);
withReadLock([&] {
_animationProperties.getProperties(properties);
});
@@ -96,6 +97,7 @@ bool ModelEntityItem::setSubClassProperties(const EntityItemProperties& properti
SET_ENTITY_PROPERTY_FROM_PROPERTIES(relayParentJoints, setRelayParentJoints);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(groupCulled, setGroupCulled);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(blendshapeCoefficients, setBlendshapeCoefficients);
+ SET_ENTITY_PROPERTY_FROM_PROPERTIES(useOriginalPivot, setUseOriginalPivot);
withWriteLock([&] {
AnimationPropertyGroup animationProperties = _animationProperties;
@@ -130,6 +132,7 @@ int ModelEntityItem::readEntitySubclassDataFromBuffer(const unsigned char* data,
READ_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints);
READ_ENTITY_PROPERTY(PROP_GROUP_CULLED, bool, setGroupCulled);
READ_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients);
+ READ_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, bool, setUseOriginalPivot);
// grab a local copy of _animationProperties to avoid multiple locks
int bytesFromAnimation;
@@ -169,6 +172,7 @@ EntityPropertyFlags ModelEntityItem::getEntityProperties(EncodeBitstreamParams&
requestedProperties += PROP_RELAY_PARENT_JOINTS;
requestedProperties += PROP_GROUP_CULLED;
requestedProperties += PROP_BLENDSHAPE_COEFFICIENTS;
+ requestedProperties += PROP_USE_ORIGINAL_PIVOT;
requestedProperties += _animationProperties.getEntityProperties(params);
return requestedProperties;
@@ -198,6 +202,7 @@ void ModelEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, getRelayParentJoints());
APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, getGroupCulled());
APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, getBlendshapeCoefficients());
+ APPEND_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, getUseOriginalPivot());
withReadLock([&] {
_animationProperties.appendSubclassData(packetData, params, entityTreeElementExtraEncodeData, requestedProperties,
@@ -251,6 +256,7 @@ void ModelEntityItem::debugDump() const {
qCDebug(entities) << " model URL:" << getModelURL();
qCDebug(entities) << " compound shape URL:" << getCompoundShapeURL();
qCDebug(entities) << " blendshapeCoefficients:" << getBlendshapeCoefficients();
+ qCDebug(entities) << " useOrigialPivot:" << getUseOriginalPivot();
}
void ModelEntityItem::setShapeType(ShapeType type) {
@@ -713,3 +719,16 @@ QVector ModelEntityItem::getBlendshapeCoefficientVector() {
return _blendshapeCoefficientsVector;
});
}
+
+void ModelEntityItem::setUseOriginalPivot(bool value) {
+ withWriteLock([&] {
+ _needsRenderUpdate |= _useOriginalPivot != value;
+ _useOriginalPivot = value;
+ });
+}
+
+bool ModelEntityItem::getUseOriginalPivot() const {
+ return resultWithReadLock([&] {
+ return _useOriginalPivot;
+ });
+}
diff --git a/libraries/entities/src/ModelEntityItem.h b/libraries/entities/src/ModelEntityItem.h
index b835b48d13..6e92b225a1 100644
--- a/libraries/entities/src/ModelEntityItem.h
+++ b/libraries/entities/src/ModelEntityItem.h
@@ -119,6 +119,9 @@ public:
bool blendshapesChanged() const { return _blendshapesChanged; }
QVector getBlendshapeCoefficientVector();
+ bool getUseOriginalPivot() const;
+ void setUseOriginalPivot(bool useOriginalPivot);
+
private:
void setAnimationSettings(const QString& value); // only called for old bitstream format
bool applyNewAnimationProperties(AnimationPropertyGroup newProperties);
@@ -152,6 +155,7 @@ protected:
bool _relayParentJoints;
bool _groupCulled { false };
QVariantMap _blendshapeCoefficientsMap;
+ bool _useOriginalPivot { false };
ThreadSafeValueCache _compoundShapeURL;
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index 1015c87038..3ce43e059e 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -151,6 +151,7 @@ void Model::setOffset(const glm::vec3& offset) {
// if someone manually sets our offset, then we are no longer snapped to center
_snapModelToRegistrationPoint = false;
_snappedToRegistrationPoint = false;
+ _needsTransformUpdate = true;
}
void Model::calculateTextureInfo() {
@@ -344,6 +345,9 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
// extents is the entity relative, scaled, centered extents of the entity
glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation);
+ if (!_snapModelToRegistrationPoint) {
+ modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset());
+ }
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
Extents modelExtents = getMeshExtents(); // NOTE: unrotated
@@ -375,8 +379,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
calculateTriangleSets(hfmModel);
}
- glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset);
- glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix;
+ glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * (glm::scale(_scale) * glm::translate(_offset));
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
@@ -499,6 +502,9 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co
// extents is the entity relative, scaled, centered extents of the entity
glm::mat4 modelToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation);
+ if (!_snapModelToRegistrationPoint) {
+ modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset());
+ }
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
Extents modelExtents = getMeshExtents(); // NOTE: unrotated
@@ -531,8 +537,7 @@ bool Model::findParabolaIntersectionAgainstSubMeshes(const glm::vec3& origin, co
calculateTriangleSets(hfmModel);
}
- glm::mat4 meshToModelMatrix = glm::scale(_scale) * glm::translate(_offset);
- glm::mat4 meshToWorldMatrix = modelToWorldMatrix * meshToModelMatrix;
+ glm::mat4 meshToWorldMatrix = createMatFromQuatAndPos(_rotation, _translation) * (glm::scale(_scale) * glm::translate(_offset));
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
@@ -1182,17 +1187,8 @@ glm::vec3 Model::getNaturalDimensions() const {
}
Extents Model::getMeshExtents() const {
- if (!isLoaded()) {
- return Extents();
- }
- const Extents& extents = getHFMModel().meshExtents;
-
- // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
- // is captured in the offset matrix
- glm::vec3 minimum = glm::vec3(getHFMModel().offset * glm::vec4(extents.minimum, 1.0f));
- glm::vec3 maximum = glm::vec3(getHFMModel().offset * glm::vec4(extents.maximum, 1.0f));
- Extents scaledExtents = { minimum * _scale, maximum * _scale };
- return scaledExtents;
+ Extents extents = getUnscaledMeshExtents();
+ return { extents.minimum * _scale, extents.maximum * _scale};
}
Extents Model::getUnscaledMeshExtents() const {
@@ -1406,6 +1402,7 @@ void Model::setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, c
_snapModelToRegistrationPoint = snapModelToRegistrationPoint;
_registrationPoint = clampedRegistrationPoint;
_snappedToRegistrationPoint = false; // force re-centering
+ _needsTransformUpdate = true;
}
}
@@ -1417,6 +1414,15 @@ void Model::snapToRegistrationPoint() {
_snappedToRegistrationPoint = true;
}
+glm::vec3 Model::getOriginalOffset() const {
+ Extents modelMeshExtents = getUnscaledMeshExtents();
+ glm::vec3 dimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum);
+ glm::vec3 offset = modelMeshExtents.minimum + (dimensions * _registrationPoint);
+ glm::mat4 transform = glm::scale(_scale) * glm::translate(offset);
+ return transform[3];
+}
+
+
void Model::setUseDualQuaternionSkinning(bool value) {
_useDualQuaternionSkinning = value;
}
@@ -1424,7 +1430,7 @@ void Model::setUseDualQuaternionSkinning(bool value) {
void Model::simulate(float deltaTime, bool fullUpdate) {
DETAILED_PROFILE_RANGE(simulation_detail, __FUNCTION__);
fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit)
- || (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint);
+ || (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint) || _needsTransformUpdate;
if (isLoaded() && fullUpdate) {
onInvalidate();
@@ -1437,8 +1443,10 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
snapToRegistrationPoint();
}
// update the world space transforms for all joints
- glm::mat4 parentTransform = glm::scale(_scale) * glm::translate(_offset);
+ glm::mat4 parentTransform = glm::scale(_scale) * (_snapModelToRegistrationPoint ?
+ glm::translate(_offset) : glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint)));
updateRig(deltaTime, parentTransform);
+ _needsTransformUpdate = false;
}
}
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index 1e7ab55d5a..7b606840f5 100644
--- a/libraries/render-utils/src/Model.h
+++ b/libraries/render-utils/src/Model.h
@@ -167,6 +167,7 @@ public:
void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint);
bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; }
+ bool getSnappedToRegistrationPoint() { return _snappedToRegistrationPoint; }
virtual void simulate(float deltaTime, bool fullUpdate = true);
virtual void updateClusterMatrices();
@@ -203,6 +204,7 @@ public:
void setOffset(const glm::vec3& offset);
const glm::vec3& getOffset() const { return _offset; }
+ glm::vec3 getOriginalOffset() const;
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f, bool forceRescale = false);
void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions, bool forceRescale = false);
@@ -348,6 +350,7 @@ public:
virtual bool replaceScriptableModelMeshPart(scriptable::ScriptableModelBasePointer model, int meshIndex, int partIndex) override;
void scaleToFit();
+ void snapToRegistrationPoint();
bool getUseDualQuaternionSkinning() const { return _useDualQuaternionSkinning; }
void setUseDualQuaternionSkinning(bool value);
@@ -409,14 +412,14 @@ protected:
bool _snapModelToRegistrationPoint; /// is the model's offset automatically adjusted to a registration point in model space
bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point
- glm::vec3 _registrationPoint = glm::vec3(0.5f); /// the point in model space our center is snapped to
+ glm::vec3 _registrationPoint { glm::vec3(0.5f) }; /// the point in model space our center is snapped to
+ bool _needsTransformUpdate { false };
std::vector _meshStates;
virtual void initJointStates();
void setScaleInternal(const glm::vec3& scale);
- void snapToRegistrationPoint();
virtual void updateRig(float deltaTime, glm::mat4 parentTransform);