コード例 #1
0
bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
    glm::vec3 pointToOffsetFrom;
    float linearTimeScale;
    float linearDistance;

    bool needUpdate = false;
    bool somethingChanged = ObjectAction::updateArguments(arguments);

    withReadLock([&] {
        bool ok = true;
        pointToOffsetFrom =
        EntityActionInterface::extractVec3Argument("offset action", arguments, "pointToOffsetFrom", ok, true);
        if (!ok) {
            pointToOffsetFrom = _pointToOffsetFrom;
        }

        ok = true;
        linearTimeScale =
        EntityActionInterface::extractFloatArgument("offset action", arguments, "linearTimeScale", ok, false);
        if (!ok) {
            linearTimeScale = _linearTimeScale;
        }

        ok = true;
        linearDistance =
        EntityActionInterface::extractFloatArgument("offset action", arguments, "linearDistance", ok, false);
        if (!ok) {
            linearDistance = _linearDistance;
        }

        // only change stuff if something actually changed
        if (somethingChanged ||
        _pointToOffsetFrom != pointToOffsetFrom ||
        _linearTimeScale != linearTimeScale ||
        _linearDistance != linearDistance) {
            needUpdate = true;
        }
    });


    if (needUpdate) {
        withWriteLock([&] {
            _pointToOffsetFrom = pointToOffsetFrom;
            _linearTimeScale = linearTimeScale;
            _linearDistance = linearDistance;
            _positionalTargetSet = true;
            _active = true;

            auto ownerEntity = _ownerEntity.lock();
            if (ownerEntity) {
                ownerEntity->setActionDataDirty(true);
            }
        });
        activateBody();
    }

    return true;
}
コード例 #2
0
btTypedConstraint* ObjectConstraintBallSocket::getConstraint() {
    btPoint2PointConstraint* constraint { nullptr };
    QUuid otherEntityID;
    glm::vec3 pivotInA;
    glm::vec3 pivotInB;

    withReadLock([&]{
        constraint = static_cast<btPoint2PointConstraint*>(_constraint);
        pivotInA = _pivotInA;
        otherEntityID = _otherID;
        pivotInB = _pivotInB;
    });
    if (constraint) {
        return constraint;
    }

    static QString repeatedBallSocketNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex(
        "ObjectConstraintBallSocket::getConstraint -- no rigidBody.*");

    btRigidBody* rigidBodyA = getRigidBody();
    if (!rigidBodyA) {
        qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyA";
        return nullptr;
    }

    if (!otherEntityID.isNull()) {
        // This constraint is between two entities... find the other rigid body.

        btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
        if (!rigidBodyB) {
            qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyB";
            return nullptr;
        }

        constraint = new btPoint2PointConstraint(*rigidBodyA, *rigidBodyB, glmToBullet(pivotInA), glmToBullet(pivotInB));
    } else {
        // This constraint is between an entity and the world-frame.

        constraint = new btPoint2PointConstraint(*rigidBodyA, glmToBullet(pivotInA));
    }

    withWriteLock([&]{
        _constraint = constraint;
    });

    // if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
    forceBodyNonStatic();
    activateBody();

    updateBallSocket();

    return constraint;
}
コード例 #3
0
void AvatarActionHold::prepareForPhysicsSimulation() {
    auto avatarManager = DependencyManager::get<AvatarManager>();
    auto holdingAvatar = std::static_pointer_cast<Avatar>(avatarManager->getAvatarBySessionID(_holderID));

    if (!holdingAvatar || !holdingAvatar->isMyAvatar()) {
        return;
    }

    withWriteLock([&]{
        glm::vec3 avatarRigidBodyPosition;
        glm::quat avatarRigidBodyRotation;
        getAvatarRigidBodyLocation(avatarRigidBodyPosition, avatarRigidBodyRotation);

        if (_ignoreIK) {
            return;
        }

        glm::vec3 palmPosition;
        glm::quat palmRotation;
        if (_hand == "right") {
            palmPosition = holdingAvatar->getUncachedRightPalmPosition();
            palmRotation = holdingAvatar->getUncachedRightPalmRotation();
        } else {
            palmPosition = holdingAvatar->getUncachedLeftPalmPosition();
            palmRotation = holdingAvatar->getUncachedLeftPalmRotation();
        }


        // determine the difference in translation and rotation between the avatar's
        // rigid body and the palm position.  The avatar's rigid body will be moved by bullet
        // between this call and the call to getTarget, below.  A call to get*PalmPosition in
        // getTarget would get the palm position of the previous location of the avatar (because
        // bullet has moved the av's rigid body but the rigid body's location has not yet been
        // copied out into the Avatar class.
        //glm::quat avatarRotationInverse = glm::inverse(avatarRigidBodyRotation);

        // the offset should be in the frame of the avatar, but something about the order
        // things are updated makes this wrong:
        //   _palmOffsetFromRigidBody = avatarRotationInverse * (palmPosition - avatarRigidBodyPosition);
        // I'll leave it here as a comment in case avatar handling changes.
        _palmOffsetFromRigidBody = palmPosition - avatarRigidBodyPosition;

        // rotation should also be needed, but again, the order of updates makes this unneeded.  leaving
        // code here for future reference.
        // _palmRotationFromRigidBody = avatarRotationInverse * palmRotation;
    });

    activateBody(true);
}
コード例 #4
0
void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
    bool gotLock = false;
    glm::quat rotation;
    glm::vec3 position;
    std::shared_ptr<Avatar> holdingAvatar = nullptr;

    gotLock = withTryReadLock([&] {
        QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
        AvatarSharedPointer holdingAvatarData = avatarManager->getAvatarBySessionID(_holderID);
        holdingAvatar = std::static_pointer_cast<Avatar>(holdingAvatarData);

        if (holdingAvatar) {
            glm::vec3 offset;
            glm::vec3 palmPosition;
            glm::quat palmRotation;
            if (_hand == "right") {
                palmPosition = holdingAvatar->getRightPalmPosition();
                palmRotation = holdingAvatar->getRightPalmRotation();
            } else {
                palmPosition = holdingAvatar->getLeftPalmPosition();
                palmRotation = holdingAvatar->getLeftPalmRotation();
            }

            rotation = palmRotation * _relativeRotation;
            offset = rotation * _relativePosition;
            position = palmPosition + offset;
        }
    });

    if (holdingAvatar) {
        if (gotLock) {
            gotLock = withTryWriteLock([&] {
                _positionalTarget = position;
                _rotationalTarget = rotation;
                _positionalTargetSet = true;
                _rotationalTargetSet = true;
                _active = true;
            });
            if (gotLock) {
                if (_kinematic) {
                    doKinematicUpdate(deltaTimeStep);
                } else {
                    activateBody();
                    ObjectActionSpring::updateActionWorker(deltaTimeStep);
                }
            }
        }
    }
}
コード例 #5
0
ファイル: AvatarActionHold.cpp プロジェクト: charukcs/hifi
bool AvatarActionHold::updateArguments(QVariantMap arguments) {
    bool ok = true;
    glm::vec3 relativePosition =
        EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
    if (!ok) {
        relativePosition = _relativePosition;
    }

    ok = true;
    glm::quat relativeRotation =
        EntityActionInterface::extractQuatArgument("hold", arguments, "relativeRotation", ok, false);
    if (!ok) {
        relativeRotation = _relativeRotation;
    }
    
    ok = true;
    float timeScale =
        EntityActionInterface::extractFloatArgument("hold", arguments, "timeScale", ok, false);
    if (!ok) {
        timeScale = _linearTimeScale;
    }

    ok = true;
    QString hand =
        EntityActionInterface::extractStringArgument("hold", arguments, "hand", ok, false);
    if (!ok || !(hand == "left" || hand == "right")) {
        hand = _hand;
    }

    if (relativePosition != _relativePosition
            || relativeRotation != _relativeRotation
            || timeScale != _linearTimeScale
            || hand != _hand) {
        withWriteLock([&] {
            _relativePosition = relativePosition;
            _relativeRotation = relativeRotation;
            const float MIN_TIMESCALE = 0.1f;
            _linearTimeScale = glm::min(MIN_TIMESCALE, timeScale);
            _angularTimeScale = _linearTimeScale;
            _hand = hand;

            _mine = true;
            _active = true;
            activateBody();
        });
    }
    return true;
}
コード例 #6
0
ファイル: ObjectActionSpring.cpp プロジェクト: rabedik/hifi
bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
    // targets are required, spring-constants are optional
    bool ok = true;
    glm::vec3 positionalTarget =
        EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ok, false);
    if (!ok) {
        positionalTarget = _positionalTarget;
    }
    ok = true;
    float linearTimeScale =
        EntityActionInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", ok, false);
    if (!ok || linearTimeScale <= 0.0f) {
        linearTimeScale = _linearTimeScale;
    }

    ok = true;
    glm::quat rotationalTarget =
        EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", ok, false);
    if (!ok) {
        rotationalTarget = _rotationalTarget;
    }

    ok = true;
    float angularTimeScale =
        EntityActionInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", ok, false);
    if (!ok) {
        angularTimeScale = _angularTimeScale;
    }

    if (positionalTarget != _positionalTarget
            || linearTimeScale != _linearTimeScale
            || rotationalTarget != _rotationalTarget
            || angularTimeScale != _angularTimeScale) {
        // something changed
        lockForWrite();
        _positionalTarget = positionalTarget;
        _linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale));
        _rotationalTarget = rotationalTarget;
        _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
        _active = true;
        activateBody();
        unlock();
    }
    return true;
}
コード例 #7
0
bool ObjectActionTravelOriented::updateArguments(QVariantMap arguments) {
    glm::vec3 forward;
    float angularTimeScale;

    bool needUpdate = false;
    bool somethingChanged = ObjectDynamic::updateArguments(arguments);
    withReadLock([&]{
        bool ok = true;
        forward = EntityDynamicInterface::extractVec3Argument("travel oriented action", arguments, "forward", ok, true);
        if (!ok) {
            forward = _forward;
        }
        ok = true;
        angularTimeScale =
            EntityDynamicInterface::extractFloatArgument("travel oriented action", arguments, "angularTimeScale", ok, false);
        if (!ok) {
            angularTimeScale = _angularTimeScale;
        }

        if (somethingChanged ||
            forward != _forward ||
            angularTimeScale != _angularTimeScale) {
            // something changed
            needUpdate = true;
        }
    });

    if (needUpdate) {
        withWriteLock([&] {
            _forward = forward;
            _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
            _active = (_forward != glm::vec3());

            auto ownerEntity = _ownerEntity.lock();
            if (ownerEntity) {
                ownerEntity->setDynamicDataDirty(true);
                ownerEntity->setDynamicDataNeedsTransmit(true);
            }
        });
        activateBody();
    }

    return true;
}
コード例 #8
0
bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
    if (!ObjectAction::updateArguments(arguments)) {
        return false;
    }
    bool ok = true;
    glm::vec3 pointToOffsetFrom =
        EntityActionInterface::extractVec3Argument("offset action", arguments, "pointToOffsetFrom", ok, true);
    if (!ok) {
        pointToOffsetFrom = _pointToOffsetFrom;
    }

    ok = true;
    float linearTimeScale =
        EntityActionInterface::extractFloatArgument("offset action", arguments, "linearTimeScale", ok, false);
    if (!ok) {
        linearTimeScale = _linearTimeScale;
    }

    ok = true;
    float linearDistance =
        EntityActionInterface::extractFloatArgument("offset action", arguments, "linearDistance", ok, false);
    if (!ok) {
        linearDistance = _linearDistance;
    }

    // only change stuff if something actually changed
    if (_pointToOffsetFrom != pointToOffsetFrom
            || _linearTimeScale != linearTimeScale
            || _linearDistance != linearDistance) {

        withWriteLock([&] {
            _pointToOffsetFrom = pointToOffsetFrom;
            _linearTimeScale = linearTimeScale;
            _linearDistance = linearDistance;
            _positionalTargetSet = true;
            _active = true;
            activateBody();
        });
    }
    return true;
}
コード例 #9
0
void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
    auto ownerEntity = _ownerEntity.lock();
    if (!ownerEntity) {
        qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning entity";
        return;
    }
    void* physicsInfo = ownerEntity->getPhysicsInfo();
    if (!physicsInfo) {
        qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning physics info";
        return;
    }
    ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
    btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr;
    if (!rigidBody) {
        qDebug() << "AvatarActionHold::doKinematicUpdate -- no rigidBody";
        return;
    }

    withWriteLock([&] {
        if (_kinematicSetVelocity) {
            if (_previousSet) {
                glm::vec3 positionalVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep;
                rigidBody->setLinearVelocity(glmToBullet(positionalVelocity));
            }
        }

        btTransform worldTrans = rigidBody->getWorldTransform();
        worldTrans.setOrigin(glmToBullet(_positionalTarget));
        worldTrans.setRotation(glmToBullet(_rotationalTarget));
        rigidBody->setWorldTransform(worldTrans);

        motionState->dirtyInternalKinematicChanges();

        _previousPositionalTarget = _positionalTarget;
        _previousRotationalTarget = _rotationalTarget;
        _previousSet = true;
    });

    activateBody();
}
コード例 #10
0
btTypedConstraint* ObjectConstraintSlider::getConstraint() {
    btSliderConstraint* constraint { nullptr };
    QUuid otherEntityID;
    glm::vec3 pointInA;
    glm::vec3 axisInA;
    glm::vec3 pointInB;
    glm::vec3 axisInB;

    withReadLock([&]{
        constraint = static_cast<btSliderConstraint*>(_constraint);
        pointInA = _pointInA;
        axisInA = _axisInA;
        otherEntityID = _otherID;
        pointInB = _pointInB;
        axisInB = _axisInB;
    });
    if (constraint) {
        return constraint;
    }

    static int repeatMessageID = LogHandler::getInstance().newRepeatedMessageID();

    btRigidBody* rigidBodyA = getRigidBody();
    if (!rigidBodyA) {
        HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintSlider::getConstraint -- no rigidBodyA");
        return nullptr;
    }

    if (glm::length(axisInA) < FLT_EPSILON) {
        qCWarning(physics) << "slider axis cannot be a zero vector";
        axisInA = DEFAULT_SLIDER_AXIS;
    } else {
        axisInA = glm::normalize(axisInA);
    }

    if (!otherEntityID.isNull()) {
        // This slider is between two entities... find the other rigid body.

        if (glm::length(axisInB) < FLT_EPSILON) {
            qCWarning(physics) << "slider axis cannot be a zero vector";
            axisInB = DEFAULT_SLIDER_AXIS;
        } else {
            axisInB = glm::normalize(axisInB);
        }

        glm::quat rotA = glm::rotation(DEFAULT_SLIDER_AXIS, axisInA);
        glm::quat rotB = glm::rotation(DEFAULT_SLIDER_AXIS, axisInB);

        btTransform frameInA(glmToBullet(rotA), glmToBullet(pointInA));
        btTransform frameInB(glmToBullet(rotB), glmToBullet(pointInB));

        btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
        if (!rigidBodyB) {
            HIFI_FCDEBUG_ID(physics(), repeatMessageID, "ObjectConstraintSlider::getConstraint -- no rigidBodyB");
            return nullptr;
        }

        constraint = new btSliderConstraint(*rigidBodyA, *rigidBodyB, frameInA, frameInB, true);
    } else {
        // This slider is between an entity and the world-frame.

        glm::quat rot = glm::rotation(DEFAULT_SLIDER_AXIS, axisInA);

        btTransform frameInA(glmToBullet(rot), glmToBullet(pointInA));

        constraint = new btSliderConstraint(*rigidBodyA, frameInA, true);
    }

    withWriteLock([&]{
        _constraint = constraint;
    });

    // if we don't wake up rigidBodyA, we may not send the dynamicData property over the network
    forceBodyNonStatic();
    activateBody();

    updateSlider();

    return constraint;
}
コード例 #11
0
void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
    auto ownerEntity = _ownerEntity.lock();
    if (!ownerEntity) {
        qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning entity";
        return;
    }
    if (ownerEntity->getParentID() != QUuid()) {
        // if the held entity has been given a parent, stop acting on it.
        return;
    }

    void* physicsInfo = ownerEntity->getPhysicsInfo();
    if (!physicsInfo) {
        qDebug() << "AvatarActionHold::doKinematicUpdate -- no owning physics info";
        return;
    }
    ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
    btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr;
    if (!rigidBody) {
        qDebug() << "AvatarActionHold::doKinematicUpdate -- no rigidBody";
        return;
    }

    withWriteLock([&]{
        if (_previousSet &&
            _positionalTarget != _previousPositionalTarget) { // don't average in a zero velocity if we get the same data
            glm::vec3 oneFrameVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep;

            _measuredLinearVelocities[_measuredLinearVelocitiesIndex++] = oneFrameVelocity;
            if (_measuredLinearVelocitiesIndex >= AvatarActionHold::velocitySmoothFrames) {
                _measuredLinearVelocitiesIndex = 0;
            }
        }

        glm::vec3 measuredLinearVelocity;
        for (int i = 0; i < AvatarActionHold::velocitySmoothFrames; i++) {
            // there is a bit of lag between when someone releases the trigger and when the software reacts to
            // the release.  we calculate the velocity from previous frames but we don't include several
            // of the most recent.
            //
            // if _measuredLinearVelocitiesIndex is
            //     0 -- ignore i of 3 4 5
            //     1 -- ignore i of 4 5 0
            //     2 -- ignore i of 5 0 1
            //     3 -- ignore i of 0 1 2
            //     4 -- ignore i of 1 2 3
            //     5 -- ignore i of 2 3 4

            // This code is now disabled, but I'm leaving it commented-out because I suspect it will come back.
            // if ((i + 1) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
            //     (i + 2) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex ||
            //     (i + 3) % AvatarActionHold::velocitySmoothFrames == _measuredLinearVelocitiesIndex) {
            //     continue;
            // }

            measuredLinearVelocity += _measuredLinearVelocities[i];
        }
        measuredLinearVelocity /= (float)(AvatarActionHold::velocitySmoothFrames
                                          // - 3  // 3 because of the 3 we skipped, above
                                          );

        if (_kinematicSetVelocity) {
            rigidBody->setLinearVelocity(glmToBullet(measuredLinearVelocity));
            rigidBody->setAngularVelocity(glmToBullet(_angularVelocityTarget));
        }

        btTransform worldTrans = rigidBody->getWorldTransform();
        worldTrans.setOrigin(glmToBullet(_positionalTarget));
        worldTrans.setRotation(glmToBullet(_rotationalTarget));
        rigidBody->setWorldTransform(worldTrans);

        motionState->dirtyInternalKinematicChanges();

        _previousPositionalTarget = _positionalTarget;
        _previousRotationalTarget = _rotationalTarget;
        _previousDeltaTimeStep = deltaTimeStep;
        _previousSet = true;
    });

    forceBodyNonStatic();
    activateBody(true);
}
コード例 #12
0
ファイル: ObjectActionSpring.cpp プロジェクト: CryptArc/hifi
bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
    glm::vec3 positionalTarget;
    float linearTimeScale;
    glm::quat rotationalTarget;
    float angularTimeScale;

    bool needUpdate = false;
    bool somethingChanged = ObjectAction::updateArguments(arguments);
    withReadLock([&]{
        // targets are required, spring-constants are optional
        bool ok = true;
        positionalTarget = EntityActionInterface::extractVec3Argument("spring action", arguments, "targetPosition", ok, false);
        if (!ok) {
            positionalTarget = _positionalTarget;
        }
        ok = true;
        linearTimeScale = EntityActionInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", ok, false);
        if (!ok || linearTimeScale <= 0.0f) {
            linearTimeScale = _linearTimeScale;
        }

        ok = true;
        rotationalTarget = EntityActionInterface::extractQuatArgument("spring action", arguments, "targetRotation", ok, false);
        if (!ok) {
            rotationalTarget = _rotationalTarget;
        }

        ok = true;
        angularTimeScale =
            EntityActionInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", ok, false);
        if (!ok) {
            angularTimeScale = _angularTimeScale;
        }

        if (somethingChanged ||
            positionalTarget != _positionalTarget ||
            linearTimeScale != _linearTimeScale ||
            rotationalTarget != _rotationalTarget ||
            angularTimeScale != _angularTimeScale) {
            // something changed
            needUpdate = true;
        }
    });

    if (needUpdate) {
        withWriteLock([&] {
            _positionalTarget = positionalTarget;
            _linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale));
            _rotationalTarget = rotationalTarget;
            _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
            _active = true;

            auto ownerEntity = _ownerEntity.lock();
            if (ownerEntity) {
                ownerEntity->setActionDataDirty(true);
                ownerEntity->setActionDataNeedsTransmit(true);
            }
        });
        activateBody();
    }

    return true;
}
コード例 #13
0
bool AvatarActionHold::updateArguments(QVariantMap arguments) {
    glm::vec3 relativePosition;
    glm::quat relativeRotation;
    float timeScale;
    QString hand;
    QUuid holderID;
    bool kinematic;
    bool kinematicSetVelocity;
    bool needUpdate = false;

    bool somethingChanged = ObjectAction::updateArguments(arguments);
    withReadLock([&] {
        bool ok = true;
        relativePosition = EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
        if (!ok) {
            relativePosition = _relativePosition;
        }

        ok = true;
        relativeRotation = EntityActionInterface::extractQuatArgument("hold", arguments, "relativeRotation", ok, false);
        if (!ok) {
            relativeRotation = _relativeRotation;
        }

        ok = true;
        timeScale = EntityActionInterface::extractFloatArgument("hold", arguments, "timeScale", ok, false);
        if (!ok) {
            timeScale = _linearTimeScale;
        }

        ok = true;
        hand = EntityActionInterface::extractStringArgument("hold", arguments, "hand", ok, false);
        if (!ok || !(hand == "left" || hand == "right")) {
            hand = _hand;
        }

        ok = true;
        auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
        holderID = myAvatar->getSessionUUID();

        ok = true;
        kinematic = EntityActionInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
        if (!ok) {
            _kinematic = false;
        }

        ok = true;
        kinematicSetVelocity = EntityActionInterface::extractBooleanArgument("hold", arguments,
        "kinematicSetVelocity", ok, false);
        if (!ok) {
            _kinematicSetVelocity = false;
        }

        if (somethingChanged ||
        relativePosition != _relativePosition ||
        relativeRotation != _relativeRotation ||
        timeScale != _linearTimeScale ||
        hand != _hand ||
        holderID != _holderID ||
        kinematic != _kinematic ||
        kinematicSetVelocity != _kinematicSetVelocity) {
            needUpdate = true;
        }
    });

    if (needUpdate) {
        withWriteLock([&] {
            _relativePosition = relativePosition;
            _relativeRotation = relativeRotation;
            const float MIN_TIMESCALE = 0.1f;
            _linearTimeScale = glm::max(MIN_TIMESCALE, timeScale);
            _angularTimeScale = _linearTimeScale;
            _hand = hand;
            _holderID = holderID;
            _kinematic = kinematic;
            _kinematicSetVelocity = kinematicSetVelocity;
            _active = true;

            auto ownerEntity = _ownerEntity.lock();
            if (ownerEntity) {
                ownerEntity->setActionDataDirty(true);
            }
        });
        activateBody();
    }

    return true;
}