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..15ff09d13d 100644
--- a/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
+++ b/libraries/avatars-renderer/src/avatars-renderer/SkeletonModel.cpp
@@ -33,6 +33,7 @@ SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent) :
{
// SkeletonModels, and by extention Avatars, use Dual Quaternion skinning.
_useDualQuaternionSkinning = true;
+ _forceOffset = true;
// Avatars all cast shadow
setCanCastShadow(true);
@@ -156,17 +157,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 c694f496d6..6213853de7 100644
--- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
+++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp
@@ -149,10 +149,12 @@ 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->getRegistrationPoint() != registrationPoint ||
+ !model->getIsScaledToFit() || _needsToRescaleModel ||
+ _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 +164,9 @@ 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;
_needsToRescaleModel = false;
}
@@ -176,10 +178,11 @@ 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);
+ locationChanged();
_needsInitialSimulation = false;
_needsJointSimulation = false;
updateRenderItems = true;
@@ -219,6 +222,16 @@ EntityItemProperties RenderableModelEntityItem::getProperties(const EntityProper
return properties;
}
+glm::vec3 RenderableModelEntityItem::getPivot() const {
+ auto model = getModel();
+ auto pivot = EntityItem::getPivot();
+ if (!model || !model->isLoaded() || !_useOriginalPivot) {
+ return pivot;
+ }
+
+ return pivot + model->getOriginalOffset();
+}
+
bool RenderableModelEntityItem::supportsDetailedIntersection() const {
return true;
}
@@ -443,14 +456,15 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
// multiply each point by scale before handing the point-set off to the physics engine.
// also determine the extents of the collision model.
glm::vec3 registrationOffset = dimensions * (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - getRegistrationPoint());
+ glm::vec3 offset = model->getSnapModelToRegistrationPoint() ? model->getOffset() : glm::vec3(0.0f);
for (int32_t i = 0; i < pointCollection.size(); i++) {
for (int32_t j = 0; j < pointCollection[i].size(); j++) {
// back compensate for registration so we can apply that offset to the shapeInfo later
- pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + model->getOffset()) - registrationOffset;
+ pointCollection[i][j] = scaleToFit * (pointCollection[i][j] + offset) - registrationOffset;
}
}
- shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL());
- adjustShapeInfoByRegistration(shapeInfo);
+ shapeInfo.setParams(type, 0.5f * extents, getCompoundShapeURL() + model->getSnapModelToRegistrationPoint());
+ adjustShapeInfoByRegistration(shapeInfo, model->getSnapModelToRegistrationPoint());
} else if (type >= SHAPE_TYPE_SIMPLE_HULL && type <= SHAPE_TYPE_STATIC_MESH) {
updateModelBounds();
model->updateGeometry();
@@ -682,8 +696,8 @@ void RenderableModelEntityItem::computeShapeInfo(ShapeInfo& shapeInfo) {
}
}
- shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL());
- adjustShapeInfoByRegistration(shapeInfo);
+ shapeInfo.setParams(type, 0.5f * extents.size(), getModelURL() + model->getSnapModelToRegistrationPoint());
+ adjustShapeInfoByRegistration(shapeInfo, model->getSnapModelToRegistrationPoint());
} 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..4f80b8b5d1 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;
}
@@ -1623,15 +1628,16 @@ AACube EntityItem::getMaximumAACube(bool& success) const {
_recalcMaxAACube = false;
// 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);
+ // and then scale by dimensions and add the absolute value of the pivot
+ glm::vec3 registrationPoint = getRegistrationPoint();
+ glm::vec3 maxExtents = getScaledDimensions() * glm::max(registrationPoint, glm::vec3(1.0f) - registrationPoint);
// there exists a sphere that contains maxExtents for all rotations
float radius = glm::length(maxExtents);
// put a cube around the sphere
// TODO? replace _maxAACube with _boundingSphereRadius
- glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius);
+ glm::vec3 minimumCorner = (centerOfRotation + getWorldOrientation() * getPivot()) - glm::vec3(radius, radius, radius);
_maxAACube = AACube(minimumCorner, radius * 2.0f);
}
} else {
@@ -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
@@ -1722,12 +1734,22 @@ float EntityItem::getRadius() const {
return 0.5f * glm::length(getScaledDimensions());
}
-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);
+void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info, bool includePivot) const {
+ glm::vec3 offset;
+ glm::vec3 registrationPoint = getRegistrationPoint();
+ if (registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) {
+ offset += (ENTITY_ITEM_DEFAULT_REGISTRATION_POINT - registrationPoint) * getScaledDimensions();
+ }
+
+ if (includePivot) {
+ glm::vec3 pivot = getPivot();
+ if (pivot != ENTITY_ITEM_ZERO_VEC3) {
+ offset += pivot;
+ }
+ }
+
+ if (offset != ENTITY_ITEM_ZERO_VEC3) {
+ info.setOffset(offset);
}
}
@@ -1739,7 +1761,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 +1819,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 +2919,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..bc3fd8b61e 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);
@@ -403,7 +404,7 @@ public:
// TODO: get rid of users of getRadius()...
float getRadius() const;
- virtual void adjustShapeInfoByRegistration(ShapeInfo& info) const;
+ virtual void adjustShapeInfoByRegistration(ShapeInfo& info, bool includePivot = true) const;
virtual bool contains(const glm::vec3& point) const;
virtual bool isReadyToComputeShape() const { return !isDead(); }
diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp
index ff55137a03..5b4b60e599 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
@@ -1002,6 +1003,9 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
* @property {string} blendshapeCoefficients - A JSON string of a map of blendshape names to values. Only stores set values.
* When editing this property, only coefficients that you are editing will change; it will not explicitly reset other
* coefficients.
+ * @property {boolean} useOriginalPivot=false - If false
, the model will be centered based on its content,
+ * ignoring any offset in the model itself. If true
, the model will respect its original offset. Currently,
+ * only pivots relative to {x: 0, y: 0, z: 0}
are supported.
* @property {string} textures="" - A JSON string of texture name, URL pairs used when rendering the model in place of the
* model's original textures. Use a texture name from the originalTextures
property to override that texture.
* Only the texture names and URLs to be overridden need be specified; original textures are used where there are no
@@ -1733,6 +1737,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 +2147,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 +2440,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 +2797,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);
@@ -3231,6 +3239,7 @@ OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketTy
APPEND_ENTITY_PROPERTY(PROP_RELAY_PARENT_JOINTS, properties.getRelayParentJoints());
APPEND_ENTITY_PROPERTY(PROP_GROUP_CULLED, properties.getGroupCulled());
APPEND_ENTITY_PROPERTY(PROP_BLENDSHAPE_COEFFICIENTS, properties.getBlendshapeCoefficients());
+ APPEND_ENTITY_PROPERTY(PROP_USE_ORIGINAL_PIVOT, properties.getUseOriginalPivot());
_staticAnimation.setProperties(properties);
_staticAnimation.appendToEditPacket(packetData, requestedProperties, propertyFlags, propertiesDidntFit, propertyCount, appendState);
@@ -3720,6 +3729,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RELAY_PARENT_JOINTS, bool, setRelayParentJoints);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GROUP_CULLED, bool, setGroupCulled);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_BLENDSHAPE_COEFFICIENTS, QString, setBlendshapeCoefficients);
+ READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_USE_ORIGINAL_PIVOT, bool, setUseOriginalPivot);
properties.getAnimation().decodeFromEditPacket(propertyFlags, dataAt, processedBytes);
}
@@ -4129,6 +4139,7 @@ void EntityItemProperties::markAllChanged() {
_relayParentJointsChanged = true;
_groupCulledChanged = true;
_blendshapeCoefficientsChanged = true;
+ _useOriginalPivotChanged = true;
_animation.markAllChanged();
// Light
@@ -4701,6 +4712,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..66d44d89de 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);
@@ -277,11 +274,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 +347,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 +370,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 +440,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 +458,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 +471,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 +492,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 +510,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 +523,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 +566,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 +581,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 +602,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 +633,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 +672,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..61362896ee 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,25 @@ QVector ModelEntityItem::getBlendshapeCoefficientVector() {
return _blendshapeCoefficientsVector;
});
}
+
+void ModelEntityItem::setUseOriginalPivot(bool value) {
+ bool changed = false;
+ withWriteLock([&] {
+ if (_useOriginalPivot != value) {
+ _needsRenderUpdate = true;
+ _useOriginalPivot = value;
+ changed = true;
+ }
+ });
+
+ if (changed) {
+ markDirtyFlags(Simulation::DIRTY_SHAPE | Simulation::DIRTY_MASS);
+ locationChanged();
+ }
+}
+
+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/hfm/src/hfm/HFM.cpp b/libraries/hfm/src/hfm/HFM.cpp
index 8fb0720c0d..dd13d6d4f3 100644
--- a/libraries/hfm/src/hfm/HFM.cpp
+++ b/libraries/hfm/src/hfm/HFM.cpp
@@ -104,7 +104,7 @@ bool HFMModel::convexHullContains(const glm::vec3& point) const {
auto checkEachPrimitive = [=](HFMMesh& mesh, QVector indices, int primitiveSize) -> bool {
// Check whether the point is "behind" all the primitives.
// But first must transform from model-frame into mesh-frame
- glm::vec3 transformedPoint = glm::vec3(glm::inverse(mesh.modelTransform) * glm::vec4(point, 1.0f));
+ glm::vec3 transformedPoint = glm::vec3(glm::inverse(offset * mesh.modelTransform) * glm::vec4(point, 1.0f));
int verticesSize = mesh.vertices.size();
for (int j = 0;
j < indices.size() - 2; // -2 in case the vertices aren't the right size -- we access j + 2 below
diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h
index fc74f4459c..5c98cf6fdd 100644
--- a/libraries/networking/src/udt/PacketHeaders.h
+++ b/libraries/networking/src/udt/PacketHeaders.h
@@ -283,6 +283,7 @@ enum class EntityVersion : PacketVersion {
ZoneOcclusion,
ModelBlendshapes,
TransparentWeb,
+ UseOriginalPivot,
// Add new versions above here
NUM_PACKET_TYPE,
diff --git a/libraries/render-utils/src/Model.cpp b/libraries/render-utils/src/Model.cpp
index 9ad343639d..521214354e 100644
--- a/libraries/render-utils/src/Model.cpp
+++ b/libraries/render-utils/src/Model.cpp
@@ -343,7 +343,11 @@ 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);
+ glm::mat4 transRot = createMatFromQuatAndPos(_rotation, _translation);
+ glm::mat4 modelToWorldMatrix = transRot;
+ if (!_snapModelToRegistrationPoint) {
+ modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset());
+ }
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
Extents modelExtents = getMeshExtents(); // NOTE: unrotated
@@ -375,8 +379,12 @@ 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 = transRot;
+ if (_snapModelToRegistrationPoint || _forceOffset) {
+ meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(_offset));
+ } else {
+ meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint)));
+ }
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
@@ -498,7 +506,11 @@ 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);
+ glm::mat4 transRot = createMatFromQuatAndPos(_rotation, _translation);
+ glm::mat4 modelToWorldMatrix = transRot;
+ if (!_snapModelToRegistrationPoint) {
+ modelToWorldMatrix = modelToWorldMatrix * glm::translate(getOriginalOffset());
+ }
glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix);
Extents modelExtents = getMeshExtents(); // NOTE: unrotated
@@ -531,8 +543,12 @@ 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 = transRot;
+ if (_snapModelToRegistrationPoint || _forceOffset) {
+ meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(_offset));
+ } else {
+ meshToWorldMatrix = meshToWorldMatrix * (glm::scale(_scale) * glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint)));
+ }
glm::mat4 worldToMeshMatrix = glm::inverse(meshToWorldMatrix);
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
@@ -1182,17 +1198,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 {
@@ -1417,6 +1424,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 + (0.5f * dimensions);
+ glm::mat4 transform = glm::scale(_scale) * glm::translate(offset);
+ return transform[3];
+}
+
+
void Model::setUseDualQuaternionSkinning(bool value) {
_useDualQuaternionSkinning = value;
}
@@ -1437,7 +1453,8 @@ 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 || _forceOffset) ?
+ glm::translate(_offset) : glm::translate(getNaturalDimensions() * (0.5f - _registrationPoint)));
updateRig(deltaTime, parentTransform);
}
}
diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h
index 1e7ab55d5a..9bf828f9fb 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 _forceOffset { false };
std::vector _meshStates;
virtual void initJointStates();
void setScaleInternal(const glm::vec3& scale);
- void snapToRegistrationPoint();
virtual void updateRig(float deltaTime, glm::mat4 parentTransform);
diff --git a/scripts/system/create/assets/data/createAppTooltips.json b/scripts/system/create/assets/data/createAppTooltips.json
index 478793ab78..7c70a8dea8 100644
--- a/scripts/system/create/assets/data/createAppTooltips.json
+++ b/scripts/system/create/assets/data/createAppTooltips.json
@@ -601,6 +601,9 @@
"groupCulled": {
"tooltip": "If false, individual pieces of the entity may be culled by the render engine. If true, either the entire entity will be culled, or it won't at all."
},
+ "useOriginalPivot": {
+ "tooltip": "If false, the model will be centered based on its content, ignoring any offset in the model itself. If true, the model will respect its original offset."
+ },
"webColor": {
"tooltip": "The tint of the web entity."
},
diff --git a/scripts/system/create/edit.js b/scripts/system/create/edit.js
index 05bc7d2381..9bd0147002 100644
--- a/scripts/system/create/edit.js
+++ b/scripts/system/create/edit.js
@@ -741,6 +741,7 @@ var toolBar = (function () {
grabbable: result.grabbable
},
dynamic: dynamic,
+ useOriginalPivot: result.useOriginalPivot
});
}
}
diff --git a/scripts/system/create/entityProperties/html/js/entityProperties.js b/scripts/system/create/entityProperties/html/js/entityProperties.js
index f3f92a887e..592808bd15 100644
--- a/scripts/system/create/entityProperties/html/js/entityProperties.js
+++ b/scripts/system/create/entityProperties/html/js/entityProperties.js
@@ -614,6 +614,11 @@ const GROUPS = [
propertyID: "compoundShapeURL",
hideIfCertified: true,
},
+ {
+ label: "Use Original Pivot",
+ type: "bool",
+ propertyID: "useOriginalPivot",
+ },
{
label: "Animation",
type: "string",
diff --git a/scripts/system/create/qml/NewModelDialog.qml b/scripts/system/create/qml/NewModelDialog.qml
index 758706a79b..c03e7c1c35 100644
--- a/scripts/system/create/qml/NewModelDialog.qml
+++ b/scripts/system/create/qml/NewModelDialog.qml
@@ -120,6 +120,12 @@ Rectangle {
height: 600
spacing: 10
+ CheckBox {
+ id: useOriginalPivot
+ text: qsTr("Use Original Pivot")
+ checked: true
+ }
+
CheckBox {
id: grabbable
text: qsTr("Grabbable")
@@ -219,7 +225,8 @@ Rectangle {
url: modelURL.text,
dynamic: dynamic.checked,
collisionShapeIndex: collisionType.currentIndex,
- grabbable: grabbable.checked
+ grabbable: grabbable.checked,
+ useOriginalPivot: useOriginalPivot.checked
}
});
}