From 59950236d3cb777aed2e4a72e06928cc5b153b38 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 22 Apr 2017 16:22:00 -0700 Subject: [PATCH 01/23] attempt to make hinge motor work better --- .../physics/src/ObjectConstraintHinge.cpp | 118 ++++++++++++++++-- libraries/physics/src/ObjectConstraintHinge.h | 35 ++++++ 2 files changed, 144 insertions(+), 9 deletions(-) diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 6c55d9c5dd..c3044c5431 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -48,6 +48,50 @@ QList ObjectConstraintHinge::getRigidBodies() { return result; } +void ObjectConstraintHinge::prepareForPhysicsSimulation() { + // setting the motor velocity doesn't appear to work for anyone. constantly adjusting the + // target angle seems to work. + // https://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=9&t=7020 + uint64_t now = usecTimestampNow(); + withWriteLock([&]{ + btHingeConstraint* constraint = static_cast(_constraint); + if (!constraint) { + return; + } + if (_previousMotorTime == 0) { + _previousMotorTime = now; + return; + } + if (_motorVelocity != 0.0f) { + if (_startMotorTime == 0) { + _startMotorTime = now; + } + float dt = (float)(now - _previousMotorTime) / (float)USECS_PER_SECOND; + float t = (float)(now - _startMotorTime) / (float)USECS_PER_SECOND; + float motorTarget = _motorVelocity * t; + while (motorTarget > PI) { + motorTarget -= PI; + } + while (motorTarget < PI) { + motorTarget += PI; + } + if (!_motorEnabled) { + constraint->enableMotor(true); + _motorEnabled = true; + } + constraint->setMaxMotorImpulse(_maxImpulse); + constraint->setMotorTarget(motorTarget, dt); + } else if (_motorTargetTimeScale > 0.0f) { + // XXX + } else if (_motorEnabled) { + constraint->enableMotor(false); + _motorEnabled = false; + _startMotorTime = 0; + } + _previousMotorTime = now; + }); +} + void ObjectConstraintHinge::updateHinge() { btHingeConstraint* constraint { nullptr }; float low; @@ -56,28 +100,45 @@ void ObjectConstraintHinge::updateHinge() { float biasFactor; float relaxationFactor; float motorVelocity; + float motorTarget; + float motorTargetTimeScale; + float maxImpulse; withReadLock([&]{ constraint = static_cast(_constraint); low = _low; high = _high; - softness = _softness; + maxImpulse = _maxImpulse; biasFactor = _biasFactor; relaxationFactor = _relaxationFactor; + + // under the hood, motorTarget sets a veloctiy and must be called repeatedly to maintain + // a velocity. _motorTargetTimeScale of 0.0f means the target isn't set. motorVelocity is + // only considered when motorTarget isn't set. motorVelocity = _motorVelocity; + motorTarget = _motorTarget; + motorTargetTimeScale = _motorTargetTimeScale; + softness = _softness; }); if (!constraint) { return; } - constraint->setLimit(low, high, softness, biasFactor, relaxationFactor); - if (motorVelocity != 0.0f) { - constraint->setMotorTargetVelocity(motorVelocity); - constraint->enableMotor(true); - } else { - constraint->enableMotor(false); - } + // constraint->setLimit(low, high, softness, biasFactor, relaxationFactor); + // if (motorTargetTimeScale > 0.0f) { + // qDebug() << "--- setting motor target:" << motorTarget << motorTargetTimeScale; + // constraint->setMotorTarget(motorTarget, motorTargetTimeScale); + // constraint->enableMotor(true); + // withWriteLock([&]{ + // _motorTargetTimeScale = 0.0f; // it's a one-shot. + // }); + // } else if (motorVelocity != 0.0f) { + // // constraint->setMotorTargetVelocity(motorVelocity); + // // constraint->enableMotor(true); + // } else { + // constraint->enableMotor(false); + // } } @@ -151,6 +212,9 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { float biasFactor; float relaxationFactor; float motorVelocity; + float motorTarget; + float motorTargetTimeScale; + float maxImpulse; bool needUpdate = false; bool somethingChanged = ObjectDynamic::updateArguments(arguments); @@ -224,6 +288,27 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { motorVelocity = _motorVelocity; } + ok = true; + motorTarget = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, + "motorTarget", ok, false); + if (!ok) { + motorTarget = _motorTarget; + } + + ok = true; + motorTargetTimeScale = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, + "motorTargetTimeScale", ok, false); + if (!ok) { + motorTargetTimeScale = _motorTargetTimeScale; + } + + ok = true; + maxImpulse = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, "maxImpulse", ok, false); + if (!ok) { + maxImpulse = _maxImpulse; + } + + if (somethingChanged || pivotInA != _pivotInA || axisInA != _axisInA || @@ -235,7 +320,10 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { softness != _softness || biasFactor != _biasFactor || relaxationFactor != _relaxationFactor || - motorVelocity != _motorVelocity) { + motorVelocity != _motorVelocity || + motorTarget != _motorTarget || + motorTargetTimeScale != _motorTargetTimeScale || + maxImpulse != _maxImpulse) { // something changed needUpdate = true; } @@ -254,6 +342,9 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { _biasFactor = biasFactor; _relaxationFactor = relaxationFactor; _motorVelocity = motorVelocity; + _motorTarget = motorTarget; + _motorTargetTimeScale = motorTargetTimeScale; + _maxImpulse = maxImpulse; _active = true; @@ -285,6 +376,9 @@ QVariantMap ObjectConstraintHinge::getArguments() { arguments["biasFactor"] = _biasFactor; arguments["relaxationFactor"] = _relaxationFactor; arguments["motorVelocity"] = _motorVelocity; + arguments["motorTarget"] = _motorTarget; + arguments["motorTargetTimeScale"] = _motorTargetTimeScale; + arguments["maxImpulse"] = _maxImpulse; arguments["angle"] = static_cast(_constraint)->getHingeAngle(); // [-PI,PI] } }); @@ -315,6 +409,9 @@ QByteArray ObjectConstraintHinge::serialize() const { dataStream << _tag; dataStream << _motorVelocity; + dataStream << _motorTarget; + dataStream << _motorTargetTimeScale; + dataStream << _maxImpulse; }); return serializedConstraintArguments; @@ -357,6 +454,9 @@ void ObjectConstraintHinge::deserialize(QByteArray serializedArguments) { dataStream >> _tag; dataStream >> _motorVelocity; + dataStream >> _motorTarget; + dataStream >> _motorTargetTimeScale; + dataStream >> _maxImpulse; _active = true; }); diff --git a/libraries/physics/src/ObjectConstraintHinge.h b/libraries/physics/src/ObjectConstraintHinge.h index 7d2cac7511..3dbd3e8152 100644 --- a/libraries/physics/src/ObjectConstraintHinge.h +++ b/libraries/physics/src/ObjectConstraintHinge.h @@ -21,6 +21,8 @@ public: ObjectConstraintHinge(const QUuid& id, EntityItemPointer ownerEntity); virtual ~ObjectConstraintHinge(); + virtual void prepareForPhysicsSimulation() override; + virtual bool updateArguments(QVariantMap arguments) override; virtual QVariantMap getArguments() override; @@ -44,10 +46,43 @@ protected: float _low { -2.0f * PI }; float _high { 2.0f * PI }; + + // https://gamedev.stackexchange.com/questions/71436/what-are-the-parameters-for-bthingeconstraintsetlimit + // + // softness: a negative measure of the friction that determines how much the hinge rotates for a given force. A high + // softness would make the hinge rotate easily like it's oiled then. + // biasFactor: an offset for the relaxed rotation of the hinge. It won't be right in the middle of the low and high angles + // anymore. 1.0f is the neural value. + // relaxationFactor: a measure of how much force is applied internally to bring the hinge in its central rotation. + // This is right in the middle of the low and high angles. For example, consider a western swing door. After + // walking through it will swing in both directions but at the end it stays right in the middle. + + // http://javadoc.jmonkeyengine.org/com/jme3/bullet/joints/HingeJoint.html + // + // _softness - the factor at which the velocity error correction starts operating, i.e. a softness of 0.9 means that + // the vel. corr starts at 90% of the limit range. + // _biasFactor - the magnitude of the position correction. It tells you how strictly the position error (drift) is + // corrected. + // _relaxationFactor - the rate at which velocity errors are corrected. This can be seen as the strength of the + // limits. A low value will make the the limits more spongy. + + + float _maxImpulse { 1.0f }; + float _softness { 0.9f }; + float _biasFactor { 0.3f }; + float _relaxationFactor { 1.0f }; + float _motorVelocity { 0.0f }; + float _motorTarget { 0.0f }; + float _motorTargetTimeScale { 0.0f }; + + uint64_t _startMotorTime { 0 }; + uint64_t _previousMotorTime { 0 }; + + bool _motorEnabled { false }; }; #endif // hifi_ObjectConstraintHinge_h From e9461f812d77111cc70f065e14d83111d24eb777 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 22 Apr 2017 17:09:26 -0700 Subject: [PATCH 02/23] don't run motor for other's hinge --- .../physics/src/ObjectConstraintHinge.cpp | 19 +++++++++++++------ .../system/libraries/entitySelectionTool.js | 1 + 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index c3044c5431..036adbc915 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -52,6 +52,13 @@ void ObjectConstraintHinge::prepareForPhysicsSimulation() { // setting the motor velocity doesn't appear to work for anyone. constantly adjusting the // target angle seems to work. // https://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=9&t=7020 + + if (!isMine()) { + // XXX + // don't activate motor for someone else's action? + // maybe don't if this interface isn't the sim owner? + return; + } uint64_t now = usecTimestampNow(); withWriteLock([&]{ btHingeConstraint* constraint = static_cast(_constraint); @@ -69,12 +76,12 @@ void ObjectConstraintHinge::prepareForPhysicsSimulation() { float dt = (float)(now - _previousMotorTime) / (float)USECS_PER_SECOND; float t = (float)(now - _startMotorTime) / (float)USECS_PER_SECOND; float motorTarget = _motorVelocity * t; - while (motorTarget > PI) { - motorTarget -= PI; - } - while (motorTarget < PI) { - motorTarget += PI; - } + + // brige motorTarget into the range of [-PI, PI] + motorTarget += PI; + motorTarget = fmodf(motorTarget, 2.0f * PI); + motorTarget -= PI; + if (!_motorEnabled) { constraint->enableMotor(true); _motorEnabled = true; diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index 79d45d5cd2..bb3d3a57c1 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2490,6 +2490,7 @@ SelectionDisplay = (function() { y: 0, z: vector.z }); + print("translateXZTool " + JSON.stringify(newPosition)); Entities.editEntity(SelectionManager.selections[i], { position: newPosition, }); From 25070c3bcaf973aaafba029eec7e7c5020ddfeff Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 22 Apr 2017 17:56:40 -0700 Subject: [PATCH 03/23] try not bringing motor target into -PI,PI range --- .../physics/src/ObjectConstraintHinge.cpp | 24 ++++--------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 036adbc915..e14c467228 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -77,10 +77,10 @@ void ObjectConstraintHinge::prepareForPhysicsSimulation() { float t = (float)(now - _startMotorTime) / (float)USECS_PER_SECOND; float motorTarget = _motorVelocity * t; - // brige motorTarget into the range of [-PI, PI] - motorTarget += PI; - motorTarget = fmodf(motorTarget, 2.0f * PI); - motorTarget -= PI; + // // bring motorTarget into the range of [-PI, PI] + // motorTarget += PI; + // motorTarget = fmodf(motorTarget, 2.0f * PI); + // motorTarget -= PI; if (!_motorEnabled) { constraint->enableMotor(true); @@ -88,6 +88,7 @@ void ObjectConstraintHinge::prepareForPhysicsSimulation() { } constraint->setMaxMotorImpulse(_maxImpulse); constraint->setMotorTarget(motorTarget, dt); + } else if (_motorTargetTimeScale > 0.0f) { // XXX } else if (_motorEnabled) { @@ -131,21 +132,6 @@ void ObjectConstraintHinge::updateHinge() { if (!constraint) { return; } - - // constraint->setLimit(low, high, softness, biasFactor, relaxationFactor); - // if (motorTargetTimeScale > 0.0f) { - // qDebug() << "--- setting motor target:" << motorTarget << motorTargetTimeScale; - // constraint->setMotorTarget(motorTarget, motorTargetTimeScale); - // constraint->enableMotor(true); - // withWriteLock([&]{ - // _motorTargetTimeScale = 0.0f; // it's a one-shot. - // }); - // } else if (motorVelocity != 0.0f) { - // // constraint->setMotorTargetVelocity(motorVelocity); - // // constraint->enableMotor(true); - // } else { - // constraint->enableMotor(false); - // } } From f172730a38ed63a7308486e8cf5f777064d3e4ab Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 22 Apr 2017 20:09:14 -0700 Subject: [PATCH 04/23] try to allow axisInA to be adjusted on the fly --- libraries/physics/src/ObjectConstraintHinge.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index e14c467228..55d9181059 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -102,6 +102,7 @@ void ObjectConstraintHinge::prepareForPhysicsSimulation() { void ObjectConstraintHinge::updateHinge() { btHingeConstraint* constraint { nullptr }; + glm::vec3 axisInA; float low; float high; float softness; @@ -113,6 +114,7 @@ void ObjectConstraintHinge::updateHinge() { float maxImpulse; withReadLock([&]{ + axisInA = _axisInA; constraint = static_cast(_constraint); low = _low; high = _high; @@ -132,6 +134,9 @@ void ObjectConstraintHinge::updateHinge() { if (!constraint) { return; } + + auto bulletAxisInA = glmToBullet(axisInA); + constraint->setAxis(bulletAxisInA); } From b65b34a81b2a58030f228f45a291e6753bc4ca06 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 22 Apr 2017 20:45:23 -0700 Subject: [PATCH 05/23] try to allow axisInA to be adjusted on the fly --- libraries/physics/src/ObjectConstraintHinge.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 55d9181059..6599f74867 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -192,7 +192,7 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() { forceBodyNonStatic(); activateBody(); - updateHinge(); + // updateHinge(); return constraint; } From b196dd082b0ef9eab222e48d2619a202a9cb6690 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 27 Apr 2017 07:56:35 -0700 Subject: [PATCH 06/23] expose bullet slider constraint to javascript --- interface/src/InterfaceDynamicFactory.cpp | 3 + .../entities/src/EntityDynamicInterface.cpp | 5 + .../entities/src/EntityDynamicInterface.h | 3 +- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + .../physics/src/ObjectConstraintHinge.cpp | 9 +- libraries/physics/src/ObjectConstraintHinge.h | 4 +- .../physics/src/ObjectConstraintSlider.cpp | 410 ++++++++++++++++++ .../physics/src/ObjectConstraintSlider.h | 63 +++ 9 files changed, 489 insertions(+), 11 deletions(-) create mode 100644 libraries/physics/src/ObjectConstraintSlider.cpp create mode 100644 libraries/physics/src/ObjectConstraintSlider.h diff --git a/interface/src/InterfaceDynamicFactory.cpp b/interface/src/InterfaceDynamicFactory.cpp index 5951ccef9e..8f8d57994a 100644 --- a/interface/src/InterfaceDynamicFactory.cpp +++ b/interface/src/InterfaceDynamicFactory.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #include #include "InterfaceDynamicFactory.h" @@ -38,6 +39,8 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid return std::make_shared(id, ownerEntity); case DYNAMIC_TYPE_FAR_GRAB: return std::make_shared(id, ownerEntity); + case DYNAMIC_TYPE_SLIDER: + return std::make_shared(id, ownerEntity); } Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity dynamic type"); diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index bed3185b8f..5538639053 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -117,6 +117,9 @@ EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicT if (normalizedDynamicTypeString == "fargrab") { return DYNAMIC_TYPE_FAR_GRAB; } + if (normalizedDynamicTypeString == "slider") { + return DYNAMIC_TYPE_SLIDER; + } qCDebug(entities) << "Warning -- EntityDynamicInterface::dynamicTypeFromString got unknown dynamic-type name" << dynamicTypeString; @@ -139,6 +142,8 @@ QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicTyp return "hinge"; case DYNAMIC_TYPE_FAR_GRAB: return "far-grab"; + case DYNAMIC_TYPE_SLIDER: + return "slider"; } assert(false); return "none"; diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h index 93d9ffa43e..cfb82dbdf8 100644 --- a/libraries/entities/src/EntityDynamicInterface.h +++ b/libraries/entities/src/EntityDynamicInterface.h @@ -31,7 +31,8 @@ enum EntityDynamicType { DYNAMIC_TYPE_HOLD = 3000, DYNAMIC_TYPE_TRAVEL_ORIENTED = 4000, DYNAMIC_TYPE_HINGE = 5000, - DYNAMIC_TYPE_FAR_GRAB = 6000 + DYNAMIC_TYPE_FAR_GRAB = 6000, + DYNAMIC_TYPE_SLIDER = 7000 }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 3ad4dbf28d..706a523367 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return VERSION_ENTITIES_HINGE_CONSTRAINT; + return VERSION_ENTITIES_SLIDER_CONSTRAINT; case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::JSONFilterWithFamilyTree); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 074876862f..affc7fd0b0 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -207,6 +207,7 @@ const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66; const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67; const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68; const PacketVersion VERSION_ENTITIES_HINGE_CONSTRAINT = 69; +const PacketVersion VERSION_ENTITIES_SLIDER_CONSTRAINT = 70; enum class EntityQueryPacketVersion: PacketVersion { JSONFilter = 18, diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 6599f74867..9f591da18b 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -54,7 +54,7 @@ void ObjectConstraintHinge::prepareForPhysicsSimulation() { // https://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=9&t=7020 if (!isMine()) { - // XXX + // TODO // don't activate motor for someone else's action? // maybe don't if this interface isn't the sim owner? return; @@ -77,11 +77,6 @@ void ObjectConstraintHinge::prepareForPhysicsSimulation() { float t = (float)(now - _startMotorTime) / (float)USECS_PER_SECOND; float motorTarget = _motorVelocity * t; - // // bring motorTarget into the range of [-PI, PI] - // motorTarget += PI; - // motorTarget = fmodf(motorTarget, 2.0f * PI); - // motorTarget -= PI; - if (!_motorEnabled) { constraint->enableMotor(true); _motorEnabled = true; @@ -90,7 +85,7 @@ void ObjectConstraintHinge::prepareForPhysicsSimulation() { constraint->setMotorTarget(motorTarget, dt); } else if (_motorTargetTimeScale > 0.0f) { - // XXX + // TODO -- we probably want a spring-like action here } else if (_motorEnabled) { constraint->enableMotor(false); _motorEnabled = false; diff --git a/libraries/physics/src/ObjectConstraintHinge.h b/libraries/physics/src/ObjectConstraintHinge.h index 3dbd3e8152..cd5f6ac7c2 100644 --- a/libraries/physics/src/ObjectConstraintHinge.h +++ b/libraries/physics/src/ObjectConstraintHinge.h @@ -44,8 +44,8 @@ protected: glm::vec3 _pivotInB; glm::vec3 _axisInB; - float _low { -2.0f * PI }; - float _high { 2.0f * PI }; + float _low { -TWO_PI }; + float _high { TWO_PI }; // https://gamedev.stackexchange.com/questions/71436/what-are-the-parameters-for-bthingeconstraintsetlimit // diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp new file mode 100644 index 0000000000..1e48d20a86 --- /dev/null +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -0,0 +1,410 @@ +// +// ObjectConstraintSlider.cpp +// libraries/physics/src +// +// Created by Seth Alves 2017-4-23 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QVariantGLM.h" + +#include "EntityTree.h" +#include "ObjectConstraintSlider.h" +#include "PhysicsLogging.h" + + +const uint16_t ObjectConstraintSlider::constraintVersion = 1; + + +ObjectConstraintSlider::ObjectConstraintSlider(const QUuid& id, EntityItemPointer ownerEntity) : + ObjectConstraint(DYNAMIC_TYPE_SLIDER, id, ownerEntity), + _pointInA(glm::vec3(0.0f)), + _axisInA(glm::vec3(0.0f)) +{ + #if WANT_DEBUG + qCDebug(physics) << "ObjectConstraintSlider::ObjectConstraintSlider"; + #endif +} + +ObjectConstraintSlider::~ObjectConstraintSlider() { + #if WANT_DEBUG + qCDebug(physics) << "ObjectConstraintSlider::~ObjectConstraintSlider"; + #endif +} + +QList ObjectConstraintSlider::getRigidBodies() { + QList result; + result += getRigidBody(); + QUuid otherEntityID; + withReadLock([&]{ + otherEntityID = _otherEntityID; + }); + if (!otherEntityID.isNull()) { + result += getOtherRigidBody(otherEntityID); + } + return result; +} + +void ObjectConstraintSlider::prepareForPhysicsSimulation() { +} + +void ObjectConstraintSlider::updateSlider() { + btSliderConstraint* constraint { nullptr }; + + withReadLock([&]{ + // TODO -- write this + constraint = static_cast(_constraint); + }); + + if (!constraint) { + return; + } + + + + // constraint->setFrames (const btTransform &frameA, const btTransform &frameB); + constraint->setLowerLinLimit(_linearLow); + constraint->setUpperLinLimit(_linearHigh); + constraint->setLowerAngLimit(_angularLow); + constraint->setUpperAngLimit(_angularHigh); + +} + + +btTypedConstraint* ObjectConstraintSlider::getConstraint() { + btSliderConstraint* constraint { nullptr }; + QUuid otherEntityID; + glm::vec3 pointInA; + glm::vec3 axisInA; + glm::vec3 pointInB; + glm::vec3 axisInB; + + withReadLock([&]{ + constraint = static_cast(_constraint); + pointInA = _pointInA; + axisInA = _axisInA; + otherEntityID = _otherEntityID; + pointInB = _pointInB; + axisInB = _axisInB; + }); + if (constraint) { + return constraint; + } + + btRigidBody* rigidBodyA = getRigidBody(); + if (!rigidBodyA) { + qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyA"; + return nullptr; + } + + if (!otherEntityID.isNull()) { + // This slider is between two entities... find the other rigid body. + + glm::quat rotA = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA)); + glm::quat rotB = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInB)); + + btTransform frameInA(glmToBullet(rotA), glmToBullet(pointInA)); + btTransform frameInB(glmToBullet(rotB), glmToBullet(pointInB)); + + btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); + if (!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(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(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; +} + + +bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { + glm::vec3 pointInA; + glm::vec3 axisInA; + QUuid otherEntityID; + glm::vec3 pointInB; + glm::vec3 axisInB; + float linearLow; + float linearHigh; + float angularLow; + float angularHigh; + float linearTarget; + float linearTimeScale; + bool linearTargetSet; + float angularTarget; + float angularTimeScale; + bool angularTargetSet; + + + bool needUpdate = false; + bool somethingChanged = ObjectDynamic::updateArguments(arguments); + withReadLock([&]{ + bool ok = true; + pointInA = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "point", ok, false); + if (!ok) { + pointInA = _pointInA; + } + + ok = true; + axisInA = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "axis", ok, false); + if (!ok) { + axisInA = _axisInA; + } + + ok = true; + otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("slider constraint", + arguments, "otherEntityID", ok, false)); + if (!ok) { + otherEntityID = _otherEntityID; + } + + ok = true; + pointInB = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "otherPoint", ok, false); + if (!ok) { + pointInB = _pointInB; + } + + ok = true; + axisInB = EntityDynamicInterface::extractVec3Argument("slider constraint", arguments, "otherAxis", ok, false); + if (!ok) { + axisInB = _axisInB; + } + + ok = true; + linearLow = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "linearLow", ok, false); + if (!ok) { + linearLow = _linearLow; + } + + ok = true; + linearHigh = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "linearHigh", ok, false); + if (!ok) { + linearHigh = _linearHigh; + } + + ok = true; + angularLow = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "angularLow", ok, false); + if (!ok) { + angularLow = _angularLow; + } + + ok = true; + angularHigh = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, "angularHigh", ok, false); + if (!ok) { + angularHigh = _angularHigh; + } + + + ok = true; + linearTarget = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, + "linearTarget", ok, false); + if (!ok) { + linearTarget = _linearTarget; + linearTargetSet = _linearTargetSet; + } else { + linearTargetSet = true; + } + + + ok = true; + linearTimeScale = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, + "linearTimeScale", ok, false); + if (!ok) { + linearTimeScale = _linearTimeScale; + } + + ok = true; + angularTarget = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, + "angularTarget", ok, false); + if (!ok) { + angularTarget = _angularTarget; + angularTargetSet = _angularTargetSet; + } else { + angularTargetSet = true; + } + + + ok = true; + angularTimeScale = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, + "angularTimeScale", ok, false); + if (!ok) { + angularTimeScale = _angularTimeScale; + } + + + if (somethingChanged || + pointInA != _pointInA || + axisInA != _axisInA || + otherEntityID != _otherEntityID || + pointInB != _pointInB || + axisInB != _axisInB || + linearLow != _linearLow || + linearHigh != _linearHigh || + angularLow != _angularLow || + angularHigh != _angularHigh || + linearTarget != _linearTarget || + linearTimeScale != _linearTimeScale || + linearTargetSet != _linearTargetSet || + angularTarget != _angularTarget || + angularTimeScale != _angularTimeScale || + angularTargetSet != _angularTargetSet) { + // something changed + needUpdate = true; + } + }); + + if (needUpdate) { + withWriteLock([&] { + _pointInA = pointInA; + _axisInA = axisInA; + _otherEntityID = otherEntityID; + _pointInB = pointInB; + _axisInB = axisInB; + _linearLow = linearLow; + _linearHigh = linearHigh; + _angularLow = angularLow; + _angularHigh = angularHigh; + _linearTarget = linearTarget; + _linearTimeScale = linearTimeScale; + _linearTargetSet = linearTargetSet; + _angularTarget = angularTarget; + _angularTimeScale = angularTimeScale; + _angularTargetSet = angularTargetSet; + + _active = true; + + auto ownerEntity = _ownerEntity.lock(); + if (ownerEntity) { + ownerEntity->setDynamicDataDirty(true); + ownerEntity->setDynamicDataNeedsTransmit(true); + } + }); + + updateSlider(); + } + + return true; +} + +QVariantMap ObjectConstraintSlider::getArguments() { + QVariantMap arguments = ObjectDynamic::getArguments(); + withReadLock([&] { + if (_constraint) { + arguments["point"] = glmToQMap(_pointInA); + arguments["axis"] = glmToQMap(_axisInA); + arguments["otherEntityID"] = _otherEntityID; + arguments["otherPoint"] = glmToQMap(_pointInB); + arguments["otherAxis"] = glmToQMap(_axisInB); + arguments["linearLow"] = _linearLow; + arguments["linearHigh"] = _linearHigh; + arguments["angularLow"] = _angularLow; + arguments["angularHigh"] = _angularHigh; + arguments["linearTarget"] = _linearTarget; + arguments["linearTimeScale"] = _linearTimeScale; + arguments["linearTargetSet"] = _linearTargetSet; + arguments["angularTarget"] = _angularTarget; + arguments["angularTimeScale"] = _angularTimeScale; + arguments["angularTargetSet"] = _angularTargetSet; + arguments["linearPosition"] = static_cast(_constraint)->getLinearPos(); + arguments["angularPosition"] = static_cast(_constraint)->getAngularPos(); + } + }); + return arguments; +} + +QByteArray ObjectConstraintSlider::serialize() const { + QByteArray serializedConstraintArguments; + QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly); + + dataStream << DYNAMIC_TYPE_SLIDER; + dataStream << getID(); + dataStream << ObjectConstraintSlider::constraintVersion; + + withReadLock([&] { + dataStream << localTimeToServerTime(_expires); + dataStream << _tag; + + dataStream << _pointInA; + dataStream << _axisInA; + dataStream << _otherEntityID; + dataStream << _pointInB; + dataStream << _axisInB; + dataStream << _linearLow; + dataStream << _linearHigh; + dataStream << _angularLow; + dataStream << _angularHigh; + dataStream << _linearTarget; + dataStream << _linearTimeScale; + dataStream << _linearTargetSet; + dataStream << _angularTarget; + dataStream << _angularTimeScale; + dataStream << _angularTargetSet; + }); + + return serializedConstraintArguments; +} + +void ObjectConstraintSlider::deserialize(QByteArray serializedArguments) { + QDataStream dataStream(serializedArguments); + + EntityDynamicType type; + dataStream >> type; + assert(type == getType()); + + QUuid id; + dataStream >> id; + assert(id == getID()); + + uint16_t serializationVersion; + dataStream >> serializationVersion; + if (serializationVersion != ObjectConstraintSlider::constraintVersion) { + assert(false); + return; + } + + withWriteLock([&] { + quint64 serverExpires; + dataStream >> serverExpires; + _expires = serverTimeToLocalTime(serverExpires); + dataStream >> _tag; + + dataStream >> _pointInA; + dataStream >> _axisInA; + dataStream >> _otherEntityID; + dataStream >> _pointInB; + dataStream >> _axisInB; + dataStream >> _linearLow; + dataStream >> _linearHigh; + dataStream >> _angularLow; + dataStream >> _angularHigh; + dataStream >> _linearTarget; + dataStream >> _linearTimeScale; + dataStream >> _linearTargetSet; + dataStream >> _angularTarget; + dataStream >> _angularTimeScale; + dataStream >> _angularTargetSet; + + _active = true; + }); +} diff --git a/libraries/physics/src/ObjectConstraintSlider.h b/libraries/physics/src/ObjectConstraintSlider.h new file mode 100644 index 0000000000..d0746b0aba --- /dev/null +++ b/libraries/physics/src/ObjectConstraintSlider.h @@ -0,0 +1,63 @@ +// +// ObjectConstraintSlider.h +// libraries/physics/src +// +// Created by Seth Alves 2017-4-23 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ObjectConstraintSlider_h +#define hifi_ObjectConstraintSlider_h + +#include "ObjectConstraint.h" + +// http://bulletphysics.org/Bullet/BulletFull/classbtSliderConstraint.html + +class ObjectConstraintSlider : public ObjectConstraint { +public: + ObjectConstraintSlider(const QUuid& id, EntityItemPointer ownerEntity); + virtual ~ObjectConstraintSlider(); + + virtual void prepareForPhysicsSimulation() override; + + virtual bool updateArguments(QVariantMap arguments) override; + virtual QVariantMap getArguments() override; + + virtual QByteArray serialize() const override; + virtual void deserialize(QByteArray serializedArguments) override; + + virtual QList getRigidBodies() override; + virtual btTypedConstraint* getConstraint() override; + +protected: + static const uint16_t constraintVersion; + + void updateSlider(); + + glm::vec3 _pointInA; + glm::vec3 _axisInA; + + EntityItemID _otherEntityID; + glm::vec3 _pointInB; + glm::vec3 _axisInB; + + float _linearLow { std::numeric_limits::max() }; + float _linearHigh { std::numeric_limits::min() }; + + float _angularLow { -TWO_PI }; + float _angularHigh { TWO_PI }; + + float _linearTarget { 0.0f }; + float _linearTimeScale { 0.0f }; + bool _linearTargetSet { false }; + + float _angularTarget { 0.0f }; + float _angularTimeScale { 0.0f }; + bool _angularTargetSet { false }; + +}; + +#endif // hifi_ObjectConstraintSlider_h From 351cd54b420f44585a48510867ffdda8751c6d8f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 27 Apr 2017 12:58:42 -0700 Subject: [PATCH 07/23] remove debug print --- scripts/system/libraries/entitySelectionTool.js | 1 - 1 file changed, 1 deletion(-) diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index bb3d3a57c1..79d45d5cd2 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -2490,7 +2490,6 @@ SelectionDisplay = (function() { y: 0, z: vector.z }); - print("translateXZTool " + JSON.stringify(newPosition)); Entities.editEntity(SelectionManager.selections[i], { position: newPosition, }); From b0c12dd84ae7eaf4866928e2bef10bdb44ac93bb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 27 Apr 2017 13:04:47 -0700 Subject: [PATCH 08/23] warn rather than crash if we get unexpected action data --- interface/src/InterfaceDynamicFactory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/src/InterfaceDynamicFactory.cpp b/interface/src/InterfaceDynamicFactory.cpp index 8f8d57994a..71f6b572e0 100644 --- a/interface/src/InterfaceDynamicFactory.cpp +++ b/interface/src/InterfaceDynamicFactory.cpp @@ -43,7 +43,7 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid return std::make_shared(id, ownerEntity); } - Q_ASSERT_X(false, Q_FUNC_INFO, "Unknown entity dynamic type"); + qDebug() << "Unknown entity dynamic type"; return EntityDynamicPointer(); } From 21df08aa8831fbec9bca9c7afe8e9891c5c16813 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 27 Apr 2017 14:02:38 -0700 Subject: [PATCH 09/23] set limits when slider constraint is first created --- libraries/physics/src/ObjectConstraintSlider.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index 1e48d20a86..fb8d8b9f68 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -63,8 +63,6 @@ void ObjectConstraintSlider::updateSlider() { return; } - - // constraint->setFrames (const btTransform &frameA, const btTransform &frameB); constraint->setLowerLinLimit(_linearLow); constraint->setUpperLinLimit(_linearHigh); @@ -133,7 +131,7 @@ btTypedConstraint* ObjectConstraintSlider::getConstraint() { forceBodyNonStatic(); activateBody(); - // updateSlider(); + updateSlider(); return constraint; } From 6623028d71df16b5f45e27d48bcca3039c11368e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 29 Apr 2017 08:35:55 -0700 Subject: [PATCH 10/23] ball-and-socket constraint --- interface/src/InterfaceDynamicFactory.cpp | 3 + .../entities/src/EntityDynamicInterface.cpp | 5 + .../entities/src/EntityDynamicInterface.h | 3 +- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 1 + .../src/ObjectConstraintBallSocket.cpp | 240 ++++++++++++++++++ .../physics/src/ObjectConstraintBallSocket.h | 46 ++++ .../physics/src/ObjectConstraintSlider.cpp | 1 - 8 files changed, 298 insertions(+), 3 deletions(-) create mode 100644 libraries/physics/src/ObjectConstraintBallSocket.cpp create mode 100644 libraries/physics/src/ObjectConstraintBallSocket.h diff --git a/interface/src/InterfaceDynamicFactory.cpp b/interface/src/InterfaceDynamicFactory.cpp index 71f6b572e0..5fe938e861 100644 --- a/interface/src/InterfaceDynamicFactory.cpp +++ b/interface/src/InterfaceDynamicFactory.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include "InterfaceDynamicFactory.h" @@ -41,6 +42,8 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid return std::make_shared(id, ownerEntity); case DYNAMIC_TYPE_SLIDER: return std::make_shared(id, ownerEntity); + case DYNAMIC_TYPE_BALL_SOCKET: + return std::make_shared(id, ownerEntity); } qDebug() << "Unknown entity dynamic type"; diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index da5ab7d8c4..89bba4379f 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -120,6 +120,9 @@ EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicT if (normalizedDynamicTypeString == "slider") { return DYNAMIC_TYPE_SLIDER; } + if (normalizedDynamicTypeString == "ballsocket") { + return DYNAMIC_TYPE_BALL_SOCKET; + } qCDebug(entities) << "Warning -- EntityDynamicInterface::dynamicTypeFromString got unknown dynamic-type name" << dynamicTypeString; @@ -144,6 +147,8 @@ QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicTyp return "far-grab"; case DYNAMIC_TYPE_SLIDER: return "slider"; + case DYNAMIC_TYPE_BALL_SOCKET: + return "ball-socket"; } assert(false); return "none"; diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h index cfb82dbdf8..28b1d89b99 100644 --- a/libraries/entities/src/EntityDynamicInterface.h +++ b/libraries/entities/src/EntityDynamicInterface.h @@ -32,7 +32,8 @@ enum EntityDynamicType { DYNAMIC_TYPE_TRAVEL_ORIENTED = 4000, DYNAMIC_TYPE_HINGE = 5000, DYNAMIC_TYPE_FAR_GRAB = 6000, - DYNAMIC_TYPE_SLIDER = 7000 + DYNAMIC_TYPE_SLIDER = 7000, + DYNAMIC_TYPE_BALL_SOCKET = 8000 }; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 082f7ade1e..ce51ccd019 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return VERSION_ENTITIES_SLIDER_CONSTRAINT; + return VERSION_ENTITIES_MORE_CONSTRAINTS; case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::JSONFilterWithFamilyTree); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 80646251a9..f84b16818a 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -209,6 +209,7 @@ const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67; const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68; const PacketVersion VERSION_ENTITIES_HINGE_CONSTRAINT = 69; const PacketVersion VERSION_ENTITIES_SLIDER_CONSTRAINT = 70; +const PacketVersion VERSION_ENTITIES_MORE_CONSTRAINTS = 71; enum class EntityQueryPacketVersion: PacketVersion { JSONFilter = 18, diff --git a/libraries/physics/src/ObjectConstraintBallSocket.cpp b/libraries/physics/src/ObjectConstraintBallSocket.cpp new file mode 100644 index 0000000000..35f138e840 --- /dev/null +++ b/libraries/physics/src/ObjectConstraintBallSocket.cpp @@ -0,0 +1,240 @@ +// +// ObjectConstraintBallSocket.cpp +// libraries/physics/src +// +// Created by Seth Alves 2017-4-29 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QVariantGLM.h" + +#include "EntityTree.h" +#include "ObjectConstraintBallSocket.h" +#include "PhysicsLogging.h" + + +const uint16_t ObjectConstraintBallSocket::constraintVersion = 1; + + +ObjectConstraintBallSocket::ObjectConstraintBallSocket(const QUuid& id, EntityItemPointer ownerEntity) : + ObjectConstraint(DYNAMIC_TYPE_BALL_SOCKET, id, ownerEntity), + _pivotInA(glm::vec3(0.0f)), + _pivotInB(glm::vec3(0.0f)) +{ + #if WANT_DEBUG + qCDebug(physics) << "ObjectConstraintBallSocket::ObjectConstraintBallSocket"; + #endif +} + +ObjectConstraintBallSocket::~ObjectConstraintBallSocket() { + #if WANT_DEBUG + qCDebug(physics) << "ObjectConstraintBallSocket::~ObjectConstraintBallSocket"; + #endif +} + +QList ObjectConstraintBallSocket::getRigidBodies() { + QList result; + result += getRigidBody(); + QUuid otherEntityID; + withReadLock([&]{ + otherEntityID = _otherEntityID; + }); + if (!otherEntityID.isNull()) { + result += getOtherRigidBody(otherEntityID); + } + return result; +} + +void ObjectConstraintBallSocket::prepareForPhysicsSimulation() { +} + +void ObjectConstraintBallSocket::updateBallSocket() { + btPoint2PointConstraint* constraint { nullptr }; + + withReadLock([&]{ + constraint = static_cast(_constraint); + }); + + if (!constraint) { + return; + } + + constraint->setPivotA(glmToBullet(_pivotInA)); + constraint->setPivotB(glmToBullet(_pivotInB)); +} + + +btTypedConstraint* ObjectConstraintBallSocket::getConstraint() { + btPoint2PointConstraint* constraint { nullptr }; + QUuid otherEntityID; + glm::vec3 pivotInA; + glm::vec3 pivotInB; + + withReadLock([&]{ + constraint = static_cast(_constraint); + pivotInA = _pivotInA; + otherEntityID = _otherEntityID; + pivotInB = _pivotInB; + }); + if (constraint) { + return constraint; + } + + 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) { + 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; +} + + +bool ObjectConstraintBallSocket::updateArguments(QVariantMap arguments) { + glm::vec3 pivotInA; + QUuid otherEntityID; + glm::vec3 pivotInB; + + bool needUpdate = false; + bool somethingChanged = ObjectDynamic::updateArguments(arguments); + withReadLock([&]{ + bool ok = true; + pivotInA = EntityDynamicInterface::extractVec3Argument("ball-socket constraint", arguments, "pivot", ok, false); + if (!ok) { + pivotInA = _pivotInA; + } + + ok = true; + otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("ball-socket constraint", + arguments, "otherEntityID", ok, false)); + if (!ok) { + otherEntityID = _otherEntityID; + } + + ok = true; + pivotInB = EntityDynamicInterface::extractVec3Argument("ball-socket constraint", arguments, "otherPivot", ok, false); + if (!ok) { + pivotInB = _pivotInB; + } + + if (somethingChanged || + pivotInA != _pivotInA || + otherEntityID != _otherEntityID || + pivotInB != _pivotInB) { + // something changed + needUpdate = true; + } + }); + + if (needUpdate) { + withWriteLock([&] { + _pivotInA = pivotInA; + _otherEntityID = otherEntityID; + _pivotInB = pivotInB; + + _active = true; + + auto ownerEntity = _ownerEntity.lock(); + if (ownerEntity) { + ownerEntity->setDynamicDataDirty(true); + ownerEntity->setDynamicDataNeedsTransmit(true); + } + }); + + updateBallSocket(); + } + + return true; +} + +QVariantMap ObjectConstraintBallSocket::getArguments() { + QVariantMap arguments = ObjectDynamic::getArguments(); + withReadLock([&] { + if (_constraint) { + arguments["pivot"] = glmToQMap(_pivotInA); + arguments["otherEntityID"] = _otherEntityID; + arguments["otherPivot"] = glmToQMap(_pivotInB); + } + }); + return arguments; +} + +QByteArray ObjectConstraintBallSocket::serialize() const { + QByteArray serializedConstraintArguments; + QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly); + + dataStream << DYNAMIC_TYPE_BALL_SOCKET; + dataStream << getID(); + dataStream << ObjectConstraintBallSocket::constraintVersion; + + withReadLock([&] { + dataStream << localTimeToServerTime(_expires); + dataStream << _tag; + + dataStream << _pivotInA; + dataStream << _otherEntityID; + dataStream << _pivotInB; + }); + + return serializedConstraintArguments; +} + +void ObjectConstraintBallSocket::deserialize(QByteArray serializedArguments) { + QDataStream dataStream(serializedArguments); + + EntityDynamicType type; + dataStream >> type; + assert(type == getType()); + + QUuid id; + dataStream >> id; + assert(id == getID()); + + uint16_t serializationVersion; + dataStream >> serializationVersion; + if (serializationVersion != ObjectConstraintBallSocket::constraintVersion) { + assert(false); + return; + } + + withWriteLock([&] { + quint64 serverExpires; + dataStream >> serverExpires; + _expires = serverTimeToLocalTime(serverExpires); + dataStream >> _tag; + + dataStream >> _pivotInA; + dataStream >> _otherEntityID; + dataStream >> _pivotInB; + + _active = true; + }); +} diff --git a/libraries/physics/src/ObjectConstraintBallSocket.h b/libraries/physics/src/ObjectConstraintBallSocket.h new file mode 100644 index 0000000000..9e0b942a6f --- /dev/null +++ b/libraries/physics/src/ObjectConstraintBallSocket.h @@ -0,0 +1,46 @@ +// +// ObjectConstraintBallSocket.h +// libraries/physics/src +// +// Created by Seth Alves 2017-4-29 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ObjectConstraintBallSocket_h +#define hifi_ObjectConstraintBallSocket_h + +#include "ObjectConstraint.h" + +// http://bulletphysics.org/Bullet/BulletFull/classbtBallSocketConstraint.html + +class ObjectConstraintBallSocket : public ObjectConstraint { +public: + ObjectConstraintBallSocket(const QUuid& id, EntityItemPointer ownerEntity); + virtual ~ObjectConstraintBallSocket(); + + virtual void prepareForPhysicsSimulation() override; + + virtual bool updateArguments(QVariantMap arguments) override; + virtual QVariantMap getArguments() override; + + virtual QByteArray serialize() const override; + virtual void deserialize(QByteArray serializedArguments) override; + + virtual QList getRigidBodies() override; + virtual btTypedConstraint* getConstraint() override; + +protected: + static const uint16_t constraintVersion; + + void updateBallSocket(); + + glm::vec3 _pivotInA; + + EntityItemID _otherEntityID; + glm::vec3 _pivotInB; +}; + +#endif // hifi_ObjectConstraintBallSocket_h diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index fb8d8b9f68..8a722d1744 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -55,7 +55,6 @@ void ObjectConstraintSlider::updateSlider() { btSliderConstraint* constraint { nullptr }; withReadLock([&]{ - // TODO -- write this constraint = static_cast(_constraint); }); From 4c4e0dffcc652308ecd59e7ddadf8aaed40294e6 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 29 Apr 2017 12:30:12 -0700 Subject: [PATCH 11/23] bullet cone-twist constraint --- interface/src/InterfaceDynamicFactory.cpp | 3 + .../entities/src/EntityDynamicInterface.cpp | 5 + .../entities/src/EntityDynamicInterface.h | 3 +- .../physics/src/ObjectConstraintConeTwist.cpp | 371 ++++++++++++++++++ .../physics/src/ObjectConstraintConeTwist.h | 55 +++ 5 files changed, 436 insertions(+), 1 deletion(-) create mode 100644 libraries/physics/src/ObjectConstraintConeTwist.cpp create mode 100644 libraries/physics/src/ObjectConstraintConeTwist.h diff --git a/interface/src/InterfaceDynamicFactory.cpp b/interface/src/InterfaceDynamicFactory.cpp index 5fe938e861..5acc0700c0 100644 --- a/interface/src/InterfaceDynamicFactory.cpp +++ b/interface/src/InterfaceDynamicFactory.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "InterfaceDynamicFactory.h" @@ -44,6 +45,8 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid return std::make_shared(id, ownerEntity); case DYNAMIC_TYPE_BALL_SOCKET: return std::make_shared(id, ownerEntity); + case DYNAMIC_TYPE_CONE_TWIST: + return std::make_shared(id, ownerEntity); } qDebug() << "Unknown entity dynamic type"; diff --git a/libraries/entities/src/EntityDynamicInterface.cpp b/libraries/entities/src/EntityDynamicInterface.cpp index 89bba4379f..57f86105b2 100644 --- a/libraries/entities/src/EntityDynamicInterface.cpp +++ b/libraries/entities/src/EntityDynamicInterface.cpp @@ -123,6 +123,9 @@ EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicT if (normalizedDynamicTypeString == "ballsocket") { return DYNAMIC_TYPE_BALL_SOCKET; } + if (normalizedDynamicTypeString == "conetwist") { + return DYNAMIC_TYPE_CONE_TWIST; + } qCDebug(entities) << "Warning -- EntityDynamicInterface::dynamicTypeFromString got unknown dynamic-type name" << dynamicTypeString; @@ -149,6 +152,8 @@ QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicTyp return "slider"; case DYNAMIC_TYPE_BALL_SOCKET: return "ball-socket"; + case DYNAMIC_TYPE_CONE_TWIST: + return "cone-twist"; } assert(false); return "none"; diff --git a/libraries/entities/src/EntityDynamicInterface.h b/libraries/entities/src/EntityDynamicInterface.h index 28b1d89b99..c04aaf67b2 100644 --- a/libraries/entities/src/EntityDynamicInterface.h +++ b/libraries/entities/src/EntityDynamicInterface.h @@ -33,7 +33,8 @@ enum EntityDynamicType { DYNAMIC_TYPE_HINGE = 5000, DYNAMIC_TYPE_FAR_GRAB = 6000, DYNAMIC_TYPE_SLIDER = 7000, - DYNAMIC_TYPE_BALL_SOCKET = 8000 + DYNAMIC_TYPE_BALL_SOCKET = 8000, + DYNAMIC_TYPE_CONE_TWIST = 9000 }; diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp new file mode 100644 index 0000000000..7a7120cd09 --- /dev/null +++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp @@ -0,0 +1,371 @@ +// +// ObjectConstraintConeTwist.cpp +// libraries/physics/src +// +// Created by Seth Alves 2017-4-29 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include "QVariantGLM.h" + +#include "EntityTree.h" +#include "ObjectConstraintConeTwist.h" +#include "PhysicsLogging.h" + + +const uint16_t ObjectConstraintConeTwist::constraintVersion = 1; + + +ObjectConstraintConeTwist::ObjectConstraintConeTwist(const QUuid& id, EntityItemPointer ownerEntity) : + ObjectConstraint(DYNAMIC_TYPE_CONE_TWIST, id, ownerEntity), + _pivotInA(glm::vec3(0.0f)), + _axisInA(glm::vec3(0.0f)) +{ + #if WANT_DEBUG + qCDebug(physics) << "ObjectConstraintConeTwist::ObjectConstraintConeTwist"; + #endif +} + +ObjectConstraintConeTwist::~ObjectConstraintConeTwist() { + #if WANT_DEBUG + qCDebug(physics) << "ObjectConstraintConeTwist::~ObjectConstraintConeTwist"; + #endif +} + +QList ObjectConstraintConeTwist::getRigidBodies() { + QList result; + result += getRigidBody(); + QUuid otherEntityID; + withReadLock([&]{ + otherEntityID = _otherEntityID; + }); + if (!otherEntityID.isNull()) { + result += getOtherRigidBody(otherEntityID); + } + return result; +} + +void ObjectConstraintConeTwist::prepareForPhysicsSimulation() { +} + +void ObjectConstraintConeTwist::updateConeTwist() { + btConeTwistConstraint* constraint { nullptr }; + float swingSpan1; + float swingSpan2; + float twistSpan; + float softness; + float biasFactor; + float relaxationFactor; + + withReadLock([&]{ + constraint = static_cast(_constraint); + swingSpan1 = _swingSpan1; + swingSpan2 = _swingSpan2; + twistSpan = _twistSpan; + softness = _softness; + biasFactor = _biasFactor; + relaxationFactor = _relaxationFactor; + }); + + if (!constraint) { + return; + } + + constraint->setLimit(swingSpan1, + swingSpan2, + twistSpan, + softness, + biasFactor, + relaxationFactor); +} + + +btTypedConstraint* ObjectConstraintConeTwist::getConstraint() { + btConeTwistConstraint* constraint { nullptr }; + QUuid otherEntityID; + glm::vec3 pivotInA; + glm::vec3 axisInA; + glm::vec3 pivotInB; + glm::vec3 axisInB; + + withReadLock([&]{ + constraint = static_cast(_constraint); + pivotInA = _pivotInA; + axisInA = _axisInA; + otherEntityID = _otherEntityID; + pivotInB = _pivotInB; + axisInB = _axisInB; + }); + if (constraint) { + return constraint; + } + + btRigidBody* rigidBodyA = getRigidBody(); + if (!rigidBodyA) { + qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyA"; + return nullptr; + } + + if (!otherEntityID.isNull()) { + // This coneTwist is between two entities... find the other rigid body. + + glm::quat rotA = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA)); + glm::quat rotB = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInB)); + + btTransform frameInA(glmToBullet(rotA), glmToBullet(pivotInA)); + btTransform frameInB(glmToBullet(rotB), glmToBullet(pivotInB)); + + btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID); + if (!rigidBodyB) { + return nullptr; + } + + constraint = new btConeTwistConstraint(*rigidBodyA, *rigidBodyB, frameInA, frameInB); + } else { + // This coneTwist is between an entity and the world-frame. + + glm::quat rot = glm::rotation(glm::vec3(1.0f, 0.0f, 0.0f), glm::normalize(axisInA)); + + btTransform frameInA(glmToBullet(rot), glmToBullet(pivotInA)); + + constraint = new btConeTwistConstraint(*rigidBodyA, frameInA); + } + + withWriteLock([&]{ + _constraint = constraint; + }); + + // if we don't wake up rigidBodyA, we may not send the dynamicData property over the network + forceBodyNonStatic(); + activateBody(); + + updateConeTwist(); + + return constraint; +} + + +bool ObjectConstraintConeTwist::updateArguments(QVariantMap arguments) { + glm::vec3 pivotInA; + glm::vec3 axisInA; + QUuid otherEntityID; + glm::vec3 pivotInB; + glm::vec3 axisInB; + float swingSpan1; + float swingSpan2; + float twistSpan; + float softness; + float biasFactor; + float relaxationFactor; + + bool needUpdate = false; + bool somethingChanged = ObjectDynamic::updateArguments(arguments); + withReadLock([&]{ + bool ok = true; + pivotInA = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "pivot", ok, false); + if (!ok) { + pivotInA = _pivotInA; + } + + ok = true; + axisInA = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "axis", ok, false); + if (!ok) { + axisInA = _axisInA; + } + + ok = true; + otherEntityID = QUuid(EntityDynamicInterface::extractStringArgument("coneTwist constraint", + arguments, "otherEntityID", ok, false)); + if (!ok) { + otherEntityID = _otherEntityID; + } + + ok = true; + pivotInB = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "otherPivot", ok, false); + if (!ok) { + pivotInB = _pivotInB; + } + + ok = true; + axisInB = EntityDynamicInterface::extractVec3Argument("coneTwist constraint", arguments, "otherAxis", ok, false); + if (!ok) { + axisInB = _axisInB; + } + + ok = true; + swingSpan1 = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "swingSpan1", ok, false); + if (!ok) { + swingSpan1 = _swingSpan1; + } + + ok = true; + swingSpan2 = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "swingSpan2", ok, false); + if (!ok) { + swingSpan2 = _swingSpan2; + } + + ok = true; + twistSpan = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "twistSpan", ok, false); + if (!ok) { + twistSpan = _twistSpan; + } + + ok = true; + softness = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "softness", ok, false); + if (!ok) { + softness = _softness; + } + + ok = true; + biasFactor = EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "biasFactor", ok, false); + if (!ok) { + biasFactor = _biasFactor; + } + + ok = true; + relaxationFactor = + EntityDynamicInterface::extractFloatArgument("coneTwist constraint", arguments, "relaxationFactor", ok, false); + if (!ok) { + relaxationFactor = _relaxationFactor; + } + + if (somethingChanged || + pivotInA != _pivotInA || + axisInA != _axisInA || + otherEntityID != _otherEntityID || + pivotInB != _pivotInB || + axisInB != _axisInB || + swingSpan1 != _swingSpan1 || + swingSpan2 != _swingSpan2 || + twistSpan != _twistSpan || + softness != _softness || + biasFactor != _biasFactor || + relaxationFactor != _relaxationFactor) { + // something changed + needUpdate = true; + } + }); + + if (needUpdate) { + withWriteLock([&] { + _pivotInA = pivotInA; + _axisInA = axisInA; + _otherEntityID = otherEntityID; + _pivotInB = pivotInB; + _axisInB = axisInB; + _swingSpan1 = swingSpan1; + _swingSpan2 = swingSpan2; + _twistSpan = twistSpan; + _softness = softness; + _biasFactor = biasFactor; + _relaxationFactor = relaxationFactor; + + _active = true; + + auto ownerEntity = _ownerEntity.lock(); + if (ownerEntity) { + ownerEntity->setDynamicDataDirty(true); + ownerEntity->setDynamicDataNeedsTransmit(true); + } + }); + + updateConeTwist(); + } + + return true; +} + +QVariantMap ObjectConstraintConeTwist::getArguments() { + QVariantMap arguments = ObjectDynamic::getArguments(); + withReadLock([&] { + if (_constraint) { + arguments["pivot"] = glmToQMap(_pivotInA); + arguments["axis"] = glmToQMap(_axisInA); + arguments["otherEntityID"] = _otherEntityID; + arguments["otherPivot"] = glmToQMap(_pivotInB); + arguments["otherAxis"] = glmToQMap(_axisInB); + arguments["swingSpan1"] = _swingSpan1; + arguments["swingSpan2"] = _swingSpan2; + arguments["twistSpan"] = _twistSpan; + arguments["softness"] = _softness; + arguments["biasFactor"] = _biasFactor; + arguments["relaxationFactor"] = _relaxationFactor; + + + // arguments["linearPosition"] = static_cast(_constraint)->getLinearPos(); + // arguments["angularPosition"] = static_cast(_constraint)->getAngularPos(); + } + }); + return arguments; +} + +QByteArray ObjectConstraintConeTwist::serialize() const { + QByteArray serializedConstraintArguments; + QDataStream dataStream(&serializedConstraintArguments, QIODevice::WriteOnly); + + dataStream << DYNAMIC_TYPE_CONE_TWIST; + dataStream << getID(); + dataStream << ObjectConstraintConeTwist::constraintVersion; + + withReadLock([&] { + dataStream << localTimeToServerTime(_expires); + dataStream << _tag; + + dataStream << _pivotInA; + dataStream << _axisInA; + dataStream << _otherEntityID; + dataStream << _pivotInB; + dataStream << _axisInB; + dataStream << _swingSpan1; + dataStream << _swingSpan2; + dataStream << _twistSpan; + dataStream << _softness; + dataStream << _biasFactor; + dataStream << _relaxationFactor; + }); + + return serializedConstraintArguments; +} + +void ObjectConstraintConeTwist::deserialize(QByteArray serializedArguments) { + QDataStream dataStream(serializedArguments); + + EntityDynamicType type; + dataStream >> type; + assert(type == getType()); + + QUuid id; + dataStream >> id; + assert(id == getID()); + + uint16_t serializationVersion; + dataStream >> serializationVersion; + if (serializationVersion != ObjectConstraintConeTwist::constraintVersion) { + assert(false); + return; + } + + withWriteLock([&] { + quint64 serverExpires; + dataStream >> serverExpires; + _expires = serverTimeToLocalTime(serverExpires); + dataStream >> _tag; + + dataStream >> _pivotInA; + dataStream >> _axisInA; + dataStream >> _otherEntityID; + dataStream >> _pivotInB; + dataStream >> _axisInB; + dataStream >> _swingSpan1; + dataStream >> _swingSpan2; + dataStream >> _twistSpan; + dataStream >> _softness; + dataStream >> _biasFactor; + dataStream >> _relaxationFactor; + + _active = true; + }); +} diff --git a/libraries/physics/src/ObjectConstraintConeTwist.h b/libraries/physics/src/ObjectConstraintConeTwist.h new file mode 100644 index 0000000000..02297e2b91 --- /dev/null +++ b/libraries/physics/src/ObjectConstraintConeTwist.h @@ -0,0 +1,55 @@ +// +// ObjectConstraintConeTwist.h +// libraries/physics/src +// +// Created by Seth Alves 2017-4-23 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_ObjectConstraintConeTwist_h +#define hifi_ObjectConstraintConeTwist_h + +#include "ObjectConstraint.h" + +// http://bulletphysics.org/Bullet/BulletFull/classbtConeTwistConstraint.html + +class ObjectConstraintConeTwist : public ObjectConstraint { +public: + ObjectConstraintConeTwist(const QUuid& id, EntityItemPointer ownerEntity); + virtual ~ObjectConstraintConeTwist(); + + virtual void prepareForPhysicsSimulation() override; + + virtual bool updateArguments(QVariantMap arguments) override; + virtual QVariantMap getArguments() override; + + virtual QByteArray serialize() const override; + virtual void deserialize(QByteArray serializedArguments) override; + + virtual QList getRigidBodies() override; + virtual btTypedConstraint* getConstraint() override; + +protected: + static const uint16_t constraintVersion; + + void updateConeTwist(); + + glm::vec3 _pivotInA; + glm::vec3 _axisInA; + + EntityItemID _otherEntityID; + glm::vec3 _pivotInB; + glm::vec3 _axisInB; + + float _swingSpan1 { TWO_PI }; + float _swingSpan2 { TWO_PI };; + float _twistSpan { TWO_PI };; + float _softness { 1.0f }; + float _biasFactor {0.3f }; + float _relaxationFactor { 1.0f }; +}; + +#endif // hifi_ObjectConstraintConeTwist_h From e52c94895e5076c946cfb0a51dfd268712745409 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 29 Apr 2017 14:18:14 -0700 Subject: [PATCH 12/23] remove motor from hinge. fix hinge limits --- .../physics/src/ObjectConstraintHinge.cpp | 113 +----------------- libraries/physics/src/ObjectConstraintHinge.h | 12 -- 2 files changed, 3 insertions(+), 122 deletions(-) diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 9f591da18b..94052c7625 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -49,50 +49,6 @@ QList ObjectConstraintHinge::getRigidBodies() { } void ObjectConstraintHinge::prepareForPhysicsSimulation() { - // setting the motor velocity doesn't appear to work for anyone. constantly adjusting the - // target angle seems to work. - // https://www.bulletphysics.org/Bullet/phpBB3/viewtopic.php?f=9&t=7020 - - if (!isMine()) { - // TODO - // don't activate motor for someone else's action? - // maybe don't if this interface isn't the sim owner? - return; - } - uint64_t now = usecTimestampNow(); - withWriteLock([&]{ - btHingeConstraint* constraint = static_cast(_constraint); - if (!constraint) { - return; - } - if (_previousMotorTime == 0) { - _previousMotorTime = now; - return; - } - if (_motorVelocity != 0.0f) { - if (_startMotorTime == 0) { - _startMotorTime = now; - } - float dt = (float)(now - _previousMotorTime) / (float)USECS_PER_SECOND; - float t = (float)(now - _startMotorTime) / (float)USECS_PER_SECOND; - float motorTarget = _motorVelocity * t; - - if (!_motorEnabled) { - constraint->enableMotor(true); - _motorEnabled = true; - } - constraint->setMaxMotorImpulse(_maxImpulse); - constraint->setMotorTarget(motorTarget, dt); - - } else if (_motorTargetTimeScale > 0.0f) { - // TODO -- we probably want a spring-like action here - } else if (_motorEnabled) { - constraint->enableMotor(false); - _motorEnabled = false; - _startMotorTime = 0; - } - _previousMotorTime = now; - }); } void ObjectConstraintHinge::updateHinge() { @@ -103,9 +59,6 @@ void ObjectConstraintHinge::updateHinge() { float softness; float biasFactor; float relaxationFactor; - float motorVelocity; - float motorTarget; - float motorTargetTimeScale; float maxImpulse; withReadLock([&]{ @@ -116,13 +69,6 @@ void ObjectConstraintHinge::updateHinge() { maxImpulse = _maxImpulse; biasFactor = _biasFactor; relaxationFactor = _relaxationFactor; - - // under the hood, motorTarget sets a veloctiy and must be called repeatedly to maintain - // a velocity. _motorTargetTimeScale of 0.0f means the target isn't set. motorVelocity is - // only considered when motorTarget isn't set. - motorVelocity = _motorVelocity; - motorTarget = _motorTarget; - motorTargetTimeScale = _motorTargetTimeScale; softness = _softness; }); @@ -132,6 +78,7 @@ void ObjectConstraintHinge::updateHinge() { auto bulletAxisInA = glmToBullet(axisInA); constraint->setAxis(bulletAxisInA); + constraint->setLimit(low, high, softness, biasFactor, relaxationFactor); } @@ -187,7 +134,7 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() { forceBodyNonStatic(); activateBody(); - // updateHinge(); + updateHinge(); return constraint; } @@ -204,10 +151,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { float softness; float biasFactor; float relaxationFactor; - float motorVelocity; - float motorTarget; - float motorTargetTimeScale; - float maxImpulse; bool needUpdate = false; bool somethingChanged = ObjectDynamic::updateArguments(arguments); @@ -274,34 +217,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { relaxationFactor = _relaxationFactor; } - ok = true; - motorVelocity = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, - "motorVelocity", ok, false); - if (!ok) { - motorVelocity = _motorVelocity; - } - - ok = true; - motorTarget = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, - "motorTarget", ok, false); - if (!ok) { - motorTarget = _motorTarget; - } - - ok = true; - motorTargetTimeScale = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, - "motorTargetTimeScale", ok, false); - if (!ok) { - motorTargetTimeScale = _motorTargetTimeScale; - } - - ok = true; - maxImpulse = EntityDynamicInterface::extractFloatArgument("hinge constraint", arguments, "maxImpulse", ok, false); - if (!ok) { - maxImpulse = _maxImpulse; - } - - if (somethingChanged || pivotInA != _pivotInA || axisInA != _axisInA || @@ -312,11 +227,7 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { high != _high || softness != _softness || biasFactor != _biasFactor || - relaxationFactor != _relaxationFactor || - motorVelocity != _motorVelocity || - motorTarget != _motorTarget || - motorTargetTimeScale != _motorTargetTimeScale || - maxImpulse != _maxImpulse) { + relaxationFactor != _relaxationFactor) { // something changed needUpdate = true; } @@ -334,10 +245,6 @@ bool ObjectConstraintHinge::updateArguments(QVariantMap arguments) { _softness = softness; _biasFactor = biasFactor; _relaxationFactor = relaxationFactor; - _motorVelocity = motorVelocity; - _motorTarget = motorTarget; - _motorTargetTimeScale = motorTargetTimeScale; - _maxImpulse = maxImpulse; _active = true; @@ -368,10 +275,6 @@ QVariantMap ObjectConstraintHinge::getArguments() { arguments["softness"] = _softness; arguments["biasFactor"] = _biasFactor; arguments["relaxationFactor"] = _relaxationFactor; - arguments["motorVelocity"] = _motorVelocity; - arguments["motorTarget"] = _motorTarget; - arguments["motorTargetTimeScale"] = _motorTargetTimeScale; - arguments["maxImpulse"] = _maxImpulse; arguments["angle"] = static_cast(_constraint)->getHingeAngle(); // [-PI,PI] } }); @@ -400,11 +303,6 @@ QByteArray ObjectConstraintHinge::serialize() const { dataStream << localTimeToServerTime(_expires); dataStream << _tag; - - dataStream << _motorVelocity; - dataStream << _motorTarget; - dataStream << _motorTargetTimeScale; - dataStream << _maxImpulse; }); return serializedConstraintArguments; @@ -446,11 +344,6 @@ void ObjectConstraintHinge::deserialize(QByteArray serializedArguments) { dataStream >> _tag; - dataStream >> _motorVelocity; - dataStream >> _motorTarget; - dataStream >> _motorTargetTimeScale; - dataStream >> _maxImpulse; - _active = true; }); } diff --git a/libraries/physics/src/ObjectConstraintHinge.h b/libraries/physics/src/ObjectConstraintHinge.h index cd5f6ac7c2..5e9975ccf8 100644 --- a/libraries/physics/src/ObjectConstraintHinge.h +++ b/libraries/physics/src/ObjectConstraintHinge.h @@ -68,21 +68,9 @@ protected: float _maxImpulse { 1.0f }; - float _softness { 0.9f }; - float _biasFactor { 0.3f }; - float _relaxationFactor { 1.0f }; - - float _motorVelocity { 0.0f }; - float _motorTarget { 0.0f }; - float _motorTargetTimeScale { 0.0f }; - - uint64_t _startMotorTime { 0 }; - uint64_t _previousMotorTime { 0 }; - - bool _motorEnabled { false }; }; #endif // hifi_ObjectConstraintHinge_h From 41f699ec9b58cb9a60355e9ef47a18d85cac79df Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 29 Apr 2017 14:28:56 -0700 Subject: [PATCH 13/23] fix hinge limits --- libraries/physics/src/ObjectConstraintHinge.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 94052c7625..14a666f09e 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -59,14 +59,12 @@ void ObjectConstraintHinge::updateHinge() { float softness; float biasFactor; float relaxationFactor; - float maxImpulse; withReadLock([&]{ axisInA = _axisInA; constraint = static_cast(_constraint); low = _low; high = _high; - maxImpulse = _maxImpulse; biasFactor = _biasFactor; relaxationFactor = _relaxationFactor; softness = _softness; @@ -76,8 +74,8 @@ void ObjectConstraintHinge::updateHinge() { return; } - auto bulletAxisInA = glmToBullet(axisInA); - constraint->setAxis(bulletAxisInA); + // auto bulletAxisInA = glmToBullet(axisInA); + // constraint->setAxis(bulletAxisInA); constraint->setLimit(low, high, softness, biasFactor, relaxationFactor); } From 8f2fc24885f813a762ed36597b81a1f1fc0f651e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sat, 29 Apr 2017 15:46:04 -0700 Subject: [PATCH 14/23] remove motors from constraints --- libraries/physics/src/ObjectConstraintHinge.h | 1 - .../physics/src/ObjectConstraintSlider.cpp | 77 +------------------ .../physics/src/ObjectConstraintSlider.h | 9 --- 3 files changed, 1 insertion(+), 86 deletions(-) diff --git a/libraries/physics/src/ObjectConstraintHinge.h b/libraries/physics/src/ObjectConstraintHinge.h index 5e9975ccf8..07ce8eb8a3 100644 --- a/libraries/physics/src/ObjectConstraintHinge.h +++ b/libraries/physics/src/ObjectConstraintHinge.h @@ -67,7 +67,6 @@ protected: // limits. A low value will make the the limits more spongy. - float _maxImpulse { 1.0f }; float _softness { 0.9f }; float _biasFactor { 0.3f }; float _relaxationFactor { 1.0f }; diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index 8a722d1744..6a59199356 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -146,13 +146,6 @@ bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { float linearHigh; float angularLow; float angularHigh; - float linearTarget; - float linearTimeScale; - bool linearTargetSet; - float angularTarget; - float angularTimeScale; - bool angularTargetSet; - bool needUpdate = false; bool somethingChanged = ObjectDynamic::updateArguments(arguments); @@ -212,44 +205,6 @@ bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { angularHigh = _angularHigh; } - - ok = true; - linearTarget = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, - "linearTarget", ok, false); - if (!ok) { - linearTarget = _linearTarget; - linearTargetSet = _linearTargetSet; - } else { - linearTargetSet = true; - } - - - ok = true; - linearTimeScale = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, - "linearTimeScale", ok, false); - if (!ok) { - linearTimeScale = _linearTimeScale; - } - - ok = true; - angularTarget = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, - "angularTarget", ok, false); - if (!ok) { - angularTarget = _angularTarget; - angularTargetSet = _angularTargetSet; - } else { - angularTargetSet = true; - } - - - ok = true; - angularTimeScale = EntityDynamicInterface::extractFloatArgument("slider constraint", arguments, - "angularTimeScale", ok, false); - if (!ok) { - angularTimeScale = _angularTimeScale; - } - - if (somethingChanged || pointInA != _pointInA || axisInA != _axisInA || @@ -259,13 +214,7 @@ bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { linearLow != _linearLow || linearHigh != _linearHigh || angularLow != _angularLow || - angularHigh != _angularHigh || - linearTarget != _linearTarget || - linearTimeScale != _linearTimeScale || - linearTargetSet != _linearTargetSet || - angularTarget != _angularTarget || - angularTimeScale != _angularTimeScale || - angularTargetSet != _angularTargetSet) { + angularHigh != _angularHigh) { // something changed needUpdate = true; } @@ -282,12 +231,6 @@ bool ObjectConstraintSlider::updateArguments(QVariantMap arguments) { _linearHigh = linearHigh; _angularLow = angularLow; _angularHigh = angularHigh; - _linearTarget = linearTarget; - _linearTimeScale = linearTimeScale; - _linearTargetSet = linearTargetSet; - _angularTarget = angularTarget; - _angularTimeScale = angularTimeScale; - _angularTargetSet = angularTargetSet; _active = true; @@ -317,12 +260,6 @@ QVariantMap ObjectConstraintSlider::getArguments() { arguments["linearHigh"] = _linearHigh; arguments["angularLow"] = _angularLow; arguments["angularHigh"] = _angularHigh; - arguments["linearTarget"] = _linearTarget; - arguments["linearTimeScale"] = _linearTimeScale; - arguments["linearTargetSet"] = _linearTargetSet; - arguments["angularTarget"] = _angularTarget; - arguments["angularTimeScale"] = _angularTimeScale; - arguments["angularTargetSet"] = _angularTargetSet; arguments["linearPosition"] = static_cast(_constraint)->getLinearPos(); arguments["angularPosition"] = static_cast(_constraint)->getAngularPos(); } @@ -351,12 +288,6 @@ QByteArray ObjectConstraintSlider::serialize() const { dataStream << _linearHigh; dataStream << _angularLow; dataStream << _angularHigh; - dataStream << _linearTarget; - dataStream << _linearTimeScale; - dataStream << _linearTargetSet; - dataStream << _angularTarget; - dataStream << _angularTimeScale; - dataStream << _angularTargetSet; }); return serializedConstraintArguments; @@ -395,12 +326,6 @@ void ObjectConstraintSlider::deserialize(QByteArray serializedArguments) { dataStream >> _linearHigh; dataStream >> _angularLow; dataStream >> _angularHigh; - dataStream >> _linearTarget; - dataStream >> _linearTimeScale; - dataStream >> _linearTargetSet; - dataStream >> _angularTarget; - dataStream >> _angularTimeScale; - dataStream >> _angularTargetSet; _active = true; }); diff --git a/libraries/physics/src/ObjectConstraintSlider.h b/libraries/physics/src/ObjectConstraintSlider.h index d0746b0aba..d616b9954c 100644 --- a/libraries/physics/src/ObjectConstraintSlider.h +++ b/libraries/physics/src/ObjectConstraintSlider.h @@ -49,15 +49,6 @@ protected: float _angularLow { -TWO_PI }; float _angularHigh { TWO_PI }; - - float _linearTarget { 0.0f }; - float _linearTimeScale { 0.0f }; - bool _linearTargetSet { false }; - - float _angularTarget { 0.0f }; - float _angularTimeScale { 0.0f }; - bool _angularTargetSet { false }; - }; #endif // hifi_ObjectConstraintSlider_h From 7ce0ef2ec456cf95b3f6f96c7a795f6fae7bba72 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 30 Apr 2017 10:04:07 -0700 Subject: [PATCH 15/23] fix spring action so that it can be linear or rotational or both --- interface/src/avatar/AvatarActionHold.cpp | 6 +- interface/src/avatar/AvatarActionHold.h | 3 +- libraries/physics/src/ObjectActionSpring.cpp | 91 ++++++++++++-------- libraries/physics/src/ObjectActionSpring.h | 3 +- 4 files changed, 65 insertions(+), 38 deletions(-) diff --git a/interface/src/avatar/AvatarActionHold.cpp b/interface/src/avatar/AvatarActionHold.cpp index 98ff687eb3..627dc2ba02 100644 --- a/interface/src/avatar/AvatarActionHold.cpp +++ b/interface/src/avatar/AvatarActionHold.cpp @@ -113,7 +113,8 @@ void AvatarActionHold::prepareForPhysicsSimulation() { } bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity) { + glm::vec3& linearVelocity, glm::vec3& angularVelocity, + float& linearTimeScale, float& angularTimeScale) { auto avatarManager = DependencyManager::get(); auto holdingAvatar = std::static_pointer_cast(avatarManager->getAvatarBySessionID(_holderID)); @@ -213,6 +214,9 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm:: // update linearVelocity based on offset via _relativePosition; linearVelocity = linearVelocity + glm::cross(angularVelocity, position - palmPosition); + + linearTimeScale = _linearTimeScale; + angularTimeScale = _angularTimeScale; }); return true; diff --git a/interface/src/avatar/AvatarActionHold.h b/interface/src/avatar/AvatarActionHold.h index f0b42111ed..7eeda53e06 100644 --- a/interface/src/avatar/AvatarActionHold.h +++ b/interface/src/avatar/AvatarActionHold.h @@ -38,7 +38,8 @@ public: bool getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation); virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity) override; + glm::vec3& linearVelocity, glm::vec3& angularVelocity, + float& linearTimeScale, float& angularTimeScale) override; virtual void prepareForPhysicsSimulation() override; diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index df7e5f87a3..7c35806bf0 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -16,6 +16,7 @@ #include "PhysicsLogging.h" const float SPRING_MAX_SPEED = 10.0f; +const float MAX_SPRING_TIMESCALE = 600.0f; // 10 min is a long time const uint16_t ObjectActionSpring::springVersion = 1; @@ -42,11 +43,16 @@ ObjectActionSpring::~ObjectActionSpring() { } bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity) { - rotation = _desiredRotationalTarget; - position = _desiredPositionalTarget; - linearVelocity = glm::vec3(); - angularVelocity = glm::vec3(); + glm::vec3& linearVelocity, glm::vec3& angularVelocity, + float& linearTimeScale, float& angularTimeScale) { + withReadLock([&]{ + rotation = _desiredRotationalTarget; + position = _desiredPositionalTarget; + linearVelocity = glm::vec3(); + angularVelocity = glm::vec3(); + linearTimeScale = _linearTimeScale; + angularTimeScale = _angularTimeScale; + }); return true; } @@ -61,8 +67,10 @@ bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) { glm::vec3 linearVelocity; glm::vec3 angularVelocity; - bool valid = false; - int springCount = 0; + bool linearValid = false; + int linearSpringCount = 0; + bool angularValid = false; + int angularSpringCount = 0; QList springDerivedActions; springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_SPRING)); @@ -73,41 +81,55 @@ bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) { std::shared_ptr springAction = std::static_pointer_cast(action); glm::quat rotationForAction; glm::vec3 positionForAction; - glm::vec3 linearVelocityForAction, angularVelocityForAction; - bool success = springAction->getTarget(deltaTimeStep, rotationForAction, - positionForAction, linearVelocityForAction, - angularVelocityForAction); + glm::vec3 linearVelocityForAction; + glm::vec3 angularVelocityForAction; + float linearTimeScale; + float angularTimeScale; + bool success = springAction->getTarget(deltaTimeStep, + rotationForAction, positionForAction, + linearVelocityForAction, angularVelocityForAction, + linearTimeScale, angularTimeScale); if (success) { - springCount ++; - if (springAction.get() == this) { - // only use the rotation for this action - valid = true; - rotation = rotationForAction; + if (angularTimeScale < MAX_SPRING_TIMESCALE) { + angularValid = true; + angularSpringCount++; + angularVelocity += angularVelocityForAction; + if (springAction.get() == this) { + // only use the rotation for this action + rotation = rotationForAction; + } } - position += positionForAction; - linearVelocity += linearVelocityForAction; - angularVelocity += angularVelocityForAction; + if (linearTimeScale < MAX_SPRING_TIMESCALE) { + linearValid = true; + linearSpringCount++; + position += positionForAction; + linearVelocity += linearVelocityForAction; + } } } - if (valid && springCount > 0) { - position /= springCount; - linearVelocity /= springCount; - angularVelocity /= springCount; - + if ((angularValid && angularSpringCount > 0) || (linearValid && linearSpringCount > 0)) { withWriteLock([&]{ - _positionalTarget = position; - _rotationalTarget = rotation; - _linearVelocityTarget = linearVelocity; - _angularVelocityTarget = angularVelocity; - _positionalTargetSet = true; - _rotationalTargetSet = true; - _active = true; + if (linearValid && linearSpringCount > 0) { + position /= linearSpringCount; + linearVelocity /= linearSpringCount; + _positionalTarget = position; + _linearVelocityTarget = linearVelocity; + _positionalTargetSet = true; + _active = true; + } + if (angularValid && angularSpringCount > 0) { + angularVelocity /= angularSpringCount; + _rotationalTarget = rotation; + _angularVelocityTarget = angularVelocity; + _rotationalTargetSet = true; + _active = true; + } }); } - return valid; + return linearValid || angularValid; } @@ -133,8 +155,7 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { return; } - const float MAX_TIMESCALE = 600.0f; // 10 min is a long time - if (_linearTimeScale < MAX_TIMESCALE) { + if (_linearTimeScale < MAX_SPRING_TIMESCALE) { btVector3 targetVelocity(0.0f, 0.0f, 0.0f); btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget); float offsetLength = offset.length(); @@ -150,7 +171,7 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) { rigidBody->setLinearVelocity(targetVelocity); } - if (_angularTimeScale < MAX_TIMESCALE) { + if (_angularTimeScale < MAX_SPRING_TIMESCALE) { btVector3 targetVelocity(0.0f, 0.0f, 0.0f); btQuaternion bodyRotation = rigidBody->getOrientation(); diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h index de9562d3fa..83a65b36b4 100644 --- a/libraries/physics/src/ObjectActionSpring.h +++ b/libraries/physics/src/ObjectActionSpring.h @@ -28,7 +28,8 @@ public: virtual void deserialize(QByteArray serializedArguments) override; virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, - glm::vec3& linearVelocity, glm::vec3& angularVelocity); + glm::vec3& linearVelocity, glm::vec3& angularVelocity, + float& linearTimeScale, float& angularTimeScale); protected: static const uint16_t springVersion; From c9dfebb713340e9015327e527898ae07ad644898 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 30 Apr 2017 10:58:58 -0700 Subject: [PATCH 16/23] allow spring action to have targets relative to another entity --- libraries/physics/src/ObjectActionSpring.cpp | 63 +++++++++++++++++++- libraries/physics/src/ObjectActionSpring.h | 4 ++ 2 files changed, 64 insertions(+), 3 deletions(-) diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 7c35806bf0..03ab1a845e 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -42,12 +42,53 @@ ObjectActionSpring::~ObjectActionSpring() { #endif } +SpatiallyNestablePointer ObjectActionSpring::getOther() { + SpatiallyNestablePointer other; + withWriteLock([&]{ + if (_otherID == QUuid()) { + // no other + return; + } + other = _other.lock(); + if (other && other->getID() == _otherID) { + // other is already up-to-date + return; + } + if (other) { + // we have a pointer to other, but it's wrong + other.reset(); + _other.reset(); + } + // we have an other-id but no pointer to other cached + QSharedPointer parentFinder = DependencyManager::get(); + if (!parentFinder) { + return; + } + EntityItemPointer ownerEntity = _ownerEntity.lock(); + if (!ownerEntity) { + return; + } + bool success; + _other = parentFinder->find(_otherID, success, ownerEntity->getParentTree()); + if (success) { + other = _other.lock(); + } + }); + return other; +} + bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position, glm::vec3& linearVelocity, glm::vec3& angularVelocity, float& linearTimeScale, float& angularTimeScale) { + SpatiallyNestablePointer other = getOther(); withReadLock([&]{ - rotation = _desiredRotationalTarget; - position = _desiredPositionalTarget; + if (other) { + rotation = _desiredRotationalTarget * other->getRotation(); + position = _desiredPositionalTarget + other->getPosition(); + } else { + rotation = _desiredRotationalTarget; + position = _desiredPositionalTarget; + } linearVelocity = glm::vec3(); angularVelocity = glm::vec3(); linearTimeScale = _linearTimeScale; @@ -210,6 +251,8 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { float linearTimeScale; glm::quat rotationalTarget; float angularTimeScale; + QUuid otherID; + bool needUpdate = false; bool somethingChanged = ObjectDynamic::updateArguments(arguments); @@ -239,11 +282,19 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { angularTimeScale = _angularTimeScale; } + ok = true; + otherID = QUuid(EntityDynamicInterface::extractStringArgument("spring action", + arguments, "otherID", ok, false)); + if (!ok) { + otherID = _otherID; + } + if (somethingChanged || positionalTarget != _desiredPositionalTarget || linearTimeScale != _linearTimeScale || rotationalTarget != _desiredRotationalTarget || - angularTimeScale != _angularTimeScale) { + angularTimeScale != _angularTimeScale || + otherID != _otherID) { // something changed needUpdate = true; } @@ -255,6 +306,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) { _linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale)); _desiredRotationalTarget = rotationalTarget; _angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale)); + _otherID = otherID; _active = true; auto ownerEntity = _ownerEntity.lock(); @@ -277,6 +329,8 @@ QVariantMap ObjectActionSpring::getArguments() { arguments["targetRotation"] = glmToQMap(_desiredRotationalTarget); arguments["angularTimeScale"] = _angularTimeScale; + + arguments["otherID"] = _otherID; }); return arguments; } @@ -291,6 +345,7 @@ void ObjectActionSpring::serializeParameters(QDataStream& dataStream) const { dataStream << _rotationalTargetSet; dataStream << localTimeToServerTime(_expires); dataStream << _tag; + dataStream << _otherID; }); } @@ -323,6 +378,8 @@ void ObjectActionSpring::deserializeParameters(QByteArray serializedArguments, Q dataStream >> _tag; + dataStream >> _otherID; + _active = true; }); } diff --git a/libraries/physics/src/ObjectActionSpring.h b/libraries/physics/src/ObjectActionSpring.h index 83a65b36b4..8f810d7956 100644 --- a/libraries/physics/src/ObjectActionSpring.h +++ b/libraries/physics/src/ObjectActionSpring.h @@ -47,6 +47,10 @@ protected: glm::vec3 _linearVelocityTarget; glm::vec3 _angularVelocityTarget; + EntityItemID _otherID; + SpatiallyNestableWeakPointer _other; + SpatiallyNestablePointer getOther(); + virtual bool prepareForSpringUpdate(btScalar deltaTimeStep); void serializeParameters(QDataStream& dataStream) const; From dabec586ec71a1d5cb25d13a7a066e5833e36e3a Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 30 Apr 2017 11:01:34 -0700 Subject: [PATCH 17/23] if spring is linked to another entity which we can't find, disable the spring --- libraries/physics/src/ObjectActionSpring.cpp | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 03ab1a845e..3cd3926073 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -82,17 +82,24 @@ bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm float& linearTimeScale, float& angularTimeScale) { SpatiallyNestablePointer other = getOther(); withReadLock([&]{ - if (other) { - rotation = _desiredRotationalTarget * other->getRotation(); - position = _desiredPositionalTarget + other->getPosition(); + linearTimeScale = _linearTimeScale; + angularTimeScale = _angularTimeScale; + + if (_otherID != QUuid()) { + if (other) { + rotation = _desiredRotationalTarget * other->getRotation(); + position = _desiredPositionalTarget + other->getPosition(); + } else { + // we should have an "other" but can't find it, so disable the spring. + linearTimeScale = FLT_MAX; + angularTimeScale = FLT_MAX; + } } else { rotation = _desiredRotationalTarget; position = _desiredPositionalTarget; } linearVelocity = glm::vec3(); angularVelocity = glm::vec3(); - linearTimeScale = _linearTimeScale; - angularTimeScale = _angularTimeScale; }); return true; } From 41663c58b8c547ad32da2b4813b5598c8b782c6d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 30 Apr 2017 12:09:37 -0700 Subject: [PATCH 18/23] test app for bullet dynamics --- .../dynamics/dynamics-tests-interface.js | 64 ++ .../tests/dynamics/dynamics-tests.html | 37 + .../developer/tests/dynamics/dynamicsTests.js | 749 ++++++++++++++++++ .../tests/dynamics/dynamicsTests.svg | 69 ++ 4 files changed, 919 insertions(+) create mode 100644 scripts/developer/tests/dynamics/dynamics-tests-interface.js create mode 100644 scripts/developer/tests/dynamics/dynamics-tests.html create mode 100644 scripts/developer/tests/dynamics/dynamicsTests.js create mode 100644 scripts/developer/tests/dynamics/dynamicsTests.svg diff --git a/scripts/developer/tests/dynamics/dynamics-tests-interface.js b/scripts/developer/tests/dynamics/dynamics-tests-interface.js new file mode 100644 index 0000000000..53517fab24 --- /dev/null +++ b/scripts/developer/tests/dynamics/dynamics-tests-interface.js @@ -0,0 +1,64 @@ +"use strict"; +/* globals $, EventBridge */ + +var parameters = { + "lifetime":"integer" +}; + + +function getQueryArgByName(name, url) { + if (!url) { + url = window.location.href; + } + name = name.replace(/[\[\]]/g, "\\$&"); + var regex = new RegExp("[?&]" + name + "(=([^&#]*)|&|#|$)"), + results = regex.exec(url); + if (!results) return null; + if (!results[2]) return ''; + return decodeURIComponent(results[2].replace(/\+/g, " ")); +} + + +function addCommandParameters(params) { + // copy from html elements into an associative-array which will get passed (as JSON) through the EventBridge + for (var parameterName in parameters) { + if (parameters.hasOwnProperty(parameterName)) { + var parameterType = parameters[parameterName]; + var strVal = $("#" + parameterName).val(); + if (parameterType == "integer") { + params[parameterName] = parseInt(strVal); + } else if (parameterType == "float") { + params[parameterName] = parseFloat(strVal); + } else { + params[parameterName] = strVal; + } + } + } + return params; +} + + +$(document).ready(function() { + // hook all buttons to EventBridge + $(":button").each(function(index) { + $(this).click(function() { + EventBridge.emitWebEvent(JSON.stringify(addCommandParameters({ "dynamics-tests-command": this.id }))); + }); + }); + + // copy parameters from query-args into elements + for (var parameterName in parameters) { + if (parameters.hasOwnProperty(parameterName)) { + var val = getQueryArgByName(parameterName); + if (val) { + var parameterType = parameters[parameterName]; + if (parameterType == "integer") { + val = parseInt(val); + } else if (parameterType == "float") { + val = parseFloat(val); + } + $("#" + parameterName).val(val.toString()); + } + } + } +}); diff --git a/scripts/developer/tests/dynamics/dynamics-tests.html b/scripts/developer/tests/dynamics/dynamics-tests.html new file mode 100644 index 0000000000..0f324e121c --- /dev/null +++ b/scripts/developer/tests/dynamics/dynamics-tests.html @@ -0,0 +1,37 @@ + + + Dynamics Tests + + + + + + + lifetime: +
+
+ A platform with a lever. The lever can be moved in a cone and rotated. A spring brings it back to its neutral position. +
+
+ A grabbable door with a hinge between it and world-space. +
+
+ A chain of blocks connected by hinges. +
+
+ The block can only move up and down over a range of 1/2 meter. +
+
+ A chain of blocks connected by slider constraints. +
+
+ A chain of spheres connected by ball-and-socket joints between the spheres. +
+
+ A chain of spheres connected by ball-and-socket joints coincident-with the spheres. +
+
+ A self-righting ragdoll. The head is on a weak spring vs the body. + + + diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js new file mode 100644 index 0000000000..18585ef152 --- /dev/null +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -0,0 +1,749 @@ + +"use strict"; + +/* global Entities, Script, Tablet, MyAvatar, Vec3 */ + +(function() { // BEGIN LOCAL_SCOPE + + var DYNAMICS_TESTS_URL = Script.resolvePath("dynamics-tests.html"); + var DEFAULT_LIFETIME = 120; // seconds + + var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + + var button = tablet.addButton({ + icon: Script.resolvePath("dynamicsTests.svg"), + text: "Dynamics", + sortOrder: 15 + }); + + + + function coneTwistAndSpringLeverTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: -0.5, z: -2})); + var lifetime = params.lifetime; + + var baseID = Entities.addEntity({ + name: "cone-twist test -- base", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: 0.5, y: 0.2, z: 0.5 }, + position: Vec3.sum(pos, { x: 0, y: 0, z:0 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + var leverID = Entities.addEntity({ + name: "cone-twist test -- lever", + type: "Box", + color: { blue: 128, green: 100, red: 200 }, + dimensions: { x: 0.05, y: 1, z: 0.05 }, + position: Vec3.sum(pos, { x: 0, y: 0.7, z:0 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addEntity({ + name: "cone-twist test -- handle", + type: "Box", + color: { blue: 30, green: 100, red: 200 }, + dimensions: { x: 0.1, y: 0.08, z: 0.08 }, + position: Vec3.sum(pos, { x: 0, y: 0.7 + 0.5, z:0 }), + dynamic: false, + collisionless: true, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + parentID: leverID, + userData: "{ \"grabbableKey\": { \"grabbable\": false } }" + }); + + Entities.addAction("cone-twist", baseID, { + pivot: { x: 0, y: 0.2, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: leverID, + otherPivot: { x: 0, y: -0.55, z: 0 }, + otherAxis: { x: 0, y: 1, z: 0 }, + swingSpan1: Math.PI / 4, + swingSpan2: Math.PI / 4, + twistSpan: Math.PI / 2, + tag: "cone-twist test" + }); + + Entities.addAction("spring", leverID, { + targetRotation: { x: 0, y: 0, z: 0, w: 1 }, + angularTimeScale: 0.2, + tag: "cone-twist test spring" + }); + + + Entities.editEntity(baseID, { gravity: { x: 0, y: -5, z: 0 } }); + } + + function doorVSWorldTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var doorID = Entities.addEntity({ + name: "door test", + type: "Box", + color: { blue: 128, green: 20, red: 20 }, + dimensions: { x: 1.0, y: 2, z: 0.1 }, + position: pos, + dynamic: true, + collisionless: false, + lifetime: lifetime, + gravity: { x: 0, y: 0, z: 0 }, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", doorID, { + pivot: { x: -0.5, y: 0, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + low: 0, + high: Math.PI, + tag: "door hinge test" + }); + } + + function hingeChainTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var offset = 0.28; + var prevEntityID = null; + for (var i = 0; i < 5; i++) { + var newID = Entities.addEntity({ + name: "hinge test " + i, + type: "Box", + color: { blue: 128, green: 40 * i, red: 20 }, + dimensions: { x: 0.2, y: 0.2, z: 0.1 }, + position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}), + dynamic: true, + collisionless: false, + lifetime: lifetime, + gravity: { x: 0, y: 0, z: 0 }, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + if (prevEntityID) { + Entities.addAction("hinge", prevEntityID, { + pivot: { x: 0, y: offset / 2, z: 0 }, + axis: { x: 1, y: 0, z: 0 }, + otherEntityID: newID, + otherPivot: { x: 0, y: -offset / 2, z: 0 }, + otherAxis: { x: 1, y: 0, z: 0 }, + tag: "A/B hinge test " + i + }); + } + prevEntityID = newID; + } + } + + function sliderVSWorldTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var sliderEntityID = Entities.addEntity({ + name: "slider test", + type: "Box", + color: { blue: 128, green: 20, red: 20 }, + dimensions: { x: 0.2, y: 0.2, z: 0.2 }, + position: pos, + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("slider", sliderEntityID, { + point: { x: -0.5, y: 0, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + linearLow: 0, + linearHigh: 0.6, + tag: "slider test" + }); + } + + function sliderChainTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var offset = 0.28; + var prevEntityID = null; + for (var i = 0; i < 7; i++) { + var newID = Entities.addEntity({ + name: "hinge test " + i, + type: "Box", + color: { blue: 128, green: 40 * i, red: 20 }, + dimensions: { x: 0.2, y: 0.1, z: 0.2 }, + position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + if (prevEntityID) { + Entities.addAction("slider", prevEntityID, { + point: { x: 0, y: 0, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: newID, + otherPoint: { x: 0, y: -offset / 2, z: 0 }, + otherAxis: { x: 0, y: 1, z: 0 }, + linearLow: 0, + linearHigh: 0.6, + tag: "A/B slider test " + i + }); + } + prevEntityID = newID; + } + } + + function ballSocketBetweenTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var offset = 0.2; + var diameter = offset - 0.01; + var prevEntityID = null; + for (var i = 0; i < 7; i++) { + var newID = Entities.addEntity({ + name: "ball and socket test " + i, + type: "Sphere", + color: { blue: 128, green: 40 * i, red: 20 }, + dimensions: { x: diameter, y: diameter, z: diameter }, + position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}), + dynamic: true, + collisionless: false, + lifetime: lifetime, + gravity: { x: 0, y: 0, z: 0 }, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + if (prevEntityID) { + Entities.addAction("ball-socket", prevEntityID, { + pivot: { x: 0, y: offset / 2, z: 0 }, + otherEntityID: newID, + otherPivot: { x: 0, y: -offset / 2, z: 0 }, + tag: "A/B ball-and-socket test " + i + }); + } + prevEntityID = newID; + } + } + + function ballSocketCoincidentTest(params) { + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 0.1, z: -2})); + var lifetime = params.lifetime; + + var offset = 0.2; + var diameter = offset - 0.01; + var prevEntityID = null; + for (var i = 0; i < 7; i++) { + var newID = Entities.addEntity({ + name: "ball and socket test " + i, + type: "Sphere", + color: { blue: 128, green: 40 * i, red: 20 }, + dimensions: { x: diameter, y: diameter, z: diameter }, + position: Vec3.sum(pos, {x: 0, y: offset * i, z:0}), + dynamic: true, + collisionless: false, + lifetime: lifetime, + gravity: { x: 0, y: 0, z: 0 }, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + if (prevEntityID) { + Entities.addAction("ball-socket", prevEntityID, { + pivot: { x: 0, y: 0, z: 0 }, + otherEntityID: newID, + otherPivot: { x: 0, y: offset, z: 0 }, + tag: "A/B ball-and-socket test " + i + }); + } + prevEntityID = newID; + } + } + + function ragdollTest(params) { + var scale = 1.6; + var lifetime = 120; + var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 1.0, z: -2})); + + var neckLength = scale * 0.05; + var shoulderGap = scale * 0.1; + var elbowGap = scale * 0.06; + var hipGap = scale * 0.07; + var kneeGap = scale * 0.08; + var ankleGap = scale * 0.06; + var ankleMin = 0; + var ankleMax = Math.PI / 4; + + var headSize = scale * 0.2; + + var bodyHeight = scale * 0.4; + var bodyWidth = scale * 0.3; + var bodyDepth = scale * 0.2; + + var upperArmThickness = scale * 0.05; + var upperArmLength = scale * 0.2; + + var lowerArmThickness = scale * 0.05; + var lowerArmLength = scale * 0.2; + + var legLength = scale * 0.3; + var legThickness = scale * 0.08; + + var shinLength = scale * 0.2; + var shinThickness = scale * 0.06; + + var footLength = scale * 0.2; + var footThickness = scale * 0.03; + var footWidth = scale * 0.08; + + + // + // body + // + + var bodyID = Entities.addEntity({ + name: "ragdoll body", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: bodyDepth, y: bodyHeight, z: bodyWidth }, + position: Vec3.sum(pos, { x: 0, y: scale * 0.0, z:0 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + // + // head + // + + var headID = Entities.addEntity({ + name: "ragdoll head", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: headSize, y: headSize, z: headSize }, + position: Vec3.sum(pos, { x: 0, y: bodyHeight / 2 + headSize / 2 + neckLength, z:0 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0.5, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("spring", headID, { + targetRotation: { x: 0, y: 0, z: 0, w: 1 }, + angularTimeScale: 2.0, + otherID: bodyID, + tag: "cone-twist test spring" + }); + + + var noseID = Entities.addEntity({ + name: "ragdoll nose", + type: "Box", + color: { blue: 128, green: 100, red: 100 }, + dimensions: { x: headSize / 5, y: headSize / 5, z: headSize / 5 }, + localPosition: { x: headSize / 2 + headSize / 10, y: 0, z: 0 }, + dynamic: false, + collisionless: true, + lifetime: lifetime, + parentID: headID, + userData: "{ \"grabbableKey\": { \"grabbable\": false } }" + }); + + Entities.addAction("cone-twist", headID, { + pivot: { x: 0, y: -headSize / 2 - neckLength / 2, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: bodyID, + otherPivot: { x: 0, y: bodyHeight / 2 + neckLength / 2, z: 0 }, + otherAxis: { x: 0, y: 1, z: 0 }, + swingSpan1: Math.PI / 4, + swingSpan2: Math.PI / 4, + twistSpan: Math.PI / 2, + tag: "ragdoll neck joint" + }); + + // + // right upper arm + // + + var rightUpperArmID = Entities.addEntity({ + name: "ragdoll right arm", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: upperArmThickness, y: upperArmThickness, z: upperArmLength }, + position: Vec3.sum(pos, { x: 0, + y: bodyHeight / 2 + upperArmThickness / 2, + z: bodyWidth / 2 + shoulderGap + upperArmLength / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("cone-twist", bodyID, { + pivot: { x: 0, y: bodyHeight / 2 + upperArmThickness / 2, z: bodyWidth / 2 + shoulderGap / 2 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: rightUpperArmID, + otherPivot: { x: 0, y: 0, z: -upperArmLength / 2 - shoulderGap / 2 }, + otherAxis: { x: 0, y: 0, z: 1 }, + swingSpan1: Math.PI / 2, + swingSpan2: Math.PI / 2, + twistSpan: 0, + tag: "ragdoll right shoulder joint" + }); + + // + // left upper arm + // + + var leftUpperArmID = Entities.addEntity({ + name: "ragdoll left arm", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: upperArmThickness, y: upperArmThickness, z: upperArmLength }, + position: Vec3.sum(pos, { x: 0, + y: bodyHeight / 2 + upperArmThickness / 2, + z: -bodyWidth / 2 - shoulderGap - upperArmLength / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("cone-twist", bodyID, { + pivot: { x: 0, y: bodyHeight / 2 + upperArmThickness / 2, z: -bodyWidth / 2 - shoulderGap / 2 }, + axis: { x: 0, y: 0, z: -1 }, + otherEntityID: leftUpperArmID, + otherPivot: { x: 0, y: 0, z: upperArmLength / 2 + shoulderGap / 2 }, + otherAxis: { x: 0, y: 0, z: -1 }, + swingSpan1: Math.PI / 2, + swingSpan2: Math.PI / 2, + twistSpan: 0, + tag: "ragdoll left shoulder joint" + }); + + // + // right lower arm + // + + var rightLowerArmID = Entities.addEntity({ + name: "ragdoll right lower arm", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: lowerArmThickness, y: lowerArmThickness, z: lowerArmLength }, + position: Vec3.sum(pos, { x: 0, + y: bodyHeight / 2 - upperArmThickness / 2, + z: bodyWidth / 2 + shoulderGap + upperArmLength + elbowGap + lowerArmLength / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -1, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", rightLowerArmID, { + pivot: { x: 0, y: 0, z: -lowerArmLength / 2 - elbowGap / 2 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: rightUpperArmID, + otherPivot: { x: 0, y: 0, z: upperArmLength / 2 + elbowGap / 2 }, + otherAxis: { x: 0, y: 1, z: 0 }, + low: Math.PI / -2, + high: 0, + tag: "ragdoll right elbow joint" + }); + + // + // left lower arm + // + + var leftLowerArmID = Entities.addEntity({ + name: "ragdoll left lower arm", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: lowerArmThickness, y: lowerArmThickness, z: lowerArmLength }, + position: Vec3.sum(pos, { x: 0, + y: bodyHeight / 2 - upperArmThickness / 2, + z: -bodyWidth / 2 - shoulderGap - upperArmLength - elbowGap - lowerArmLength / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -1, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", leftLowerArmID, { + pivot: { x: 0, y: 0, z: lowerArmLength / 2 + elbowGap / 2 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: leftUpperArmID, + otherPivot: { x: 0, y: 0, z: -upperArmLength / 2 - elbowGap / 2 }, + otherAxis: { x: 0, y: 1, z: 0 }, + low: 0, + high: Math.PI / 2, + tag: "ragdoll left elbow joint" + }); + + // + // right leg + // + + var rightLegID = Entities.addEntity({ + name: "ragdoll right arm", + type: "Box", + color: { blue: 20, green: 200, red: 20 }, + dimensions: { x: legThickness, y: legLength, z: legThickness }, + position: Vec3.sum(pos, { x: 0, y: -bodyHeight / 2 - hipGap - legLength / 2, z: bodyWidth / 2 - legThickness / 2 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("cone-twist", rightLegID, { + pivot: { x: 0, y: legLength / 2 + hipGap / 2, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: bodyID, + otherPivot: { x: 0, y: -bodyHeight / 2 - hipGap / 2, z: bodyWidth / 2 - legThickness / 2 }, + otherAxis: Vec3.normalize({ x: -1, y: 1, z: 0 }), + swingSpan1: Math.PI / 4, + swingSpan2: Math.PI / 4, + twistSpan: 0, + tag: "ragdoll right hip joint" + }); + + // + // left leg + // + + var leftLegID = Entities.addEntity({ + name: "ragdoll left arm", + type: "Box", + color: { blue: 20, green: 200, red: 20 }, + dimensions: { x: legThickness, y: legLength, z: legThickness }, + position: Vec3.sum(pos, { x: 0, y: -bodyHeight / 2 - hipGap - legLength / 2, z: -bodyWidth / 2 + legThickness / 2 }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: 0, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("cone-twist", leftLegID, { + pivot: { x: 0, y: legLength / 2 + hipGap / 2, z: 0 }, + axis: { x: 0, y: 1, z: 0 }, + otherEntityID: bodyID, + otherPivot: { x: 0, y: -bodyHeight / 2 - hipGap / 2, z: -bodyWidth / 2 + legThickness / 2 }, + otherAxis: Vec3.normalize({ x: -1, y: 1, z: 0 }), + swingSpan1: Math.PI / 4, + swingSpan2: Math.PI / 4, + twistSpan: 0, + tag: "ragdoll left hip joint" + }); + + // + // right shin + // + + var rightShinID = Entities.addEntity({ + name: "ragdoll right shin", + type: "Box", + color: { blue: 20, green: 200, red: 20 }, + dimensions: { x: shinThickness, y: shinLength, z: shinThickness }, + position: Vec3.sum(pos, { x: 0, + y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength / 2, + z: bodyWidth / 2 - legThickness / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -2, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", rightShinID, { + pivot: { x: 0, y: shinLength / 2 + kneeGap / 2, z: 0 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: rightLegID, + otherPivot: { x: 0, y: -legLength / 2 - kneeGap / 2, z: 0 }, + otherAxis: { x: 0, y: 0, z: 1 }, + low: 0, + high: Math.PI / 2, + tag: "ragdoll right knee joint" + }); + + + // + // left shin + // + + var leftShinID = Entities.addEntity({ + name: "ragdoll left shin", + type: "Box", + color: { blue: 20, green: 200, red: 20 }, + dimensions: { x: shinThickness, y: shinLength, z: shinThickness }, + position: Vec3.sum(pos, { x: 0, + y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength / 2, + z: -bodyWidth / 2 + legThickness / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -2, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", leftShinID, { + pivot: { x: 0, y: shinLength / 2 + kneeGap / 2, z: 0 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: leftLegID, + otherPivot: { x: 0, y: -legLength / 2 - kneeGap / 2, z: 0 }, + otherAxis: { x: 0, y: 0, z: 1 }, + low: 0, + high: Math.PI / 2, + tag: "ragdoll left knee joint" + }); + + // + // right foot + // + + var rightFootID = Entities.addEntity({ + name: "ragdoll right foot", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: footLength, y: footThickness, z: footWidth }, + position: Vec3.sum(pos, { x: -shinThickness / 2 + footLength / 2, + y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength - ankleGap - footThickness / 2, + z: bodyWidth / 2 - legThickness / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -5, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", rightFootID, { + pivot: { x: -footLength / 2 + shinThickness / 2, y: ankleGap / 2, z: 0 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: rightShinID, + otherPivot: { x: 0, y: -shinLength / 2 - ankleGap / 2, z: 0 }, + otherAxis: { x: 0, y: 0, z: 1 }, + low: ankleMin, + high: ankleMax, + tag: "ragdoll right ankle joint" + }); + + // + // left foot + // + + var leftFootID = Entities.addEntity({ + name: "ragdoll left foot", + type: "Box", + color: { blue: 128, green: 100, red: 20 }, + dimensions: { x: footLength, y: footThickness, z: footWidth }, + position: Vec3.sum(pos, { x: -shinThickness / 2 + footLength / 2, + y: -bodyHeight / 2 - hipGap - legLength - kneeGap - shinLength - ankleGap - footThickness / 2, + z: bodyWidth / 2 - legThickness / 2 + }), + dynamic: true, + collisionless: false, + gravity: { x: 0, y: -5, z: 0 }, + lifetime: lifetime, + userData: "{ \"grabbableKey\": { \"grabbable\": true, \"kinematic\": false } }" + }); + + Entities.addAction("hinge", leftFootID, { + pivot: { x: -footLength / 2 + shinThickness / 2, y: ankleGap / 2, z: 0 }, + axis: { x: 0, y: 0, z: 1 }, + otherEntityID: leftShinID, + otherPivot: { x: 0, y: -shinLength / 2 - ankleGap / 2, z: 0 }, + otherAxis: { x: 0, y: 0, z: 1 }, + low: ankleMin, + high: ankleMax, + tag: "ragdoll left ankle joint" + }); + + } + + function onWebEventReceived(eventString) { + print("received web event: " + JSON.stringify(eventString)); + if (typeof eventString === "string") { + var event; + try { + event = JSON.parse(eventString); + } catch(e) { + return; + } + + if (event["dynamics-tests-command"]) { + var commandToFunctionMap = { + "cone-twist-and-spring-lever-test": coneTwistAndSpringLeverTest, + "door-vs-world-test": doorVSWorldTest, + "hinge-chain-test": hingeChainTest, + "slider-vs-world-test": sliderVSWorldTest, + "slider-chain-test": sliderChainTest, + "ball-socket-between-test": ballSocketBetweenTest, + "ball-socket-coincident-test": ballSocketCoincidentTest, + "ragdoll-test": ragdollTest + }; + + var cmd = event["dynamics-tests-command"]; + if (commandToFunctionMap.hasOwnProperty(cmd)) { + var func = commandToFunctionMap[cmd]; + func(event); + } + } + } + } + + + var onDynamicsTestsScreen = false; + var shouldActivateButton = false; + + function onClicked() { + if (onDynamicsTestsScreen) { + tablet.gotoHomeScreen(); + } else { + shouldActivateButton = true; + tablet.gotoWebScreen(DYNAMICS_TESTS_URL + + "?lifetime=" + DEFAULT_LIFETIME.toString() + ); + onDynamicsTestsScreen = true; + } + } + + function onScreenChanged() { + // for toolbar mode: change button to active when window is first openend, false otherwise. + button.editProperties({isActive: shouldActivateButton}); + shouldActivateButton = false; + onDynamicsTestsScreen = shouldActivateButton; + } + + function cleanup() { + button.clicked.disconnect(onClicked); + tablet.removeButton(button); + } + + button.clicked.connect(onClicked); + tablet.webEventReceived.connect(onWebEventReceived); + tablet.screenChanged.connect(onScreenChanged); + Script.scriptEnding.connect(cleanup); +}()); // END LOCAL_SCOPE diff --git a/scripts/developer/tests/dynamics/dynamicsTests.svg b/scripts/developer/tests/dynamics/dynamicsTests.svg new file mode 100644 index 0000000000..a1407e87d4 --- /dev/null +++ b/scripts/developer/tests/dynamics/dynamicsTests.svg @@ -0,0 +1,69 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + From 1b67a8b251730bb7797ac6bbededc42960004dfc Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 30 Apr 2017 12:23:34 -0700 Subject: [PATCH 19/23] cleanups --- libraries/networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 +-- libraries/physics/src/ObjectConstraintConeTwist.cpp | 4 ---- .../tests/dynamics/dynamics-tests-interface.js | 12 ++++++++++++ scripts/developer/tests/dynamics/dynamicsTests.js | 10 ++++++++++ 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index ce51ccd019..82b4bf703d 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -49,7 +49,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return VERSION_ENTITIES_MORE_CONSTRAINTS; + return VERSION_ENTITIES_BULLET_DYNAMICS; case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::JSONFilterWithFamilyTree); case PacketType::AvatarIdentity: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index f84b16818a..746ae80361 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -208,8 +208,7 @@ const PacketVersion VERSION_ENTITIES_SERVER_SCRIPTS = 66; const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67; const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68; const PacketVersion VERSION_ENTITIES_HINGE_CONSTRAINT = 69; -const PacketVersion VERSION_ENTITIES_SLIDER_CONSTRAINT = 70; -const PacketVersion VERSION_ENTITIES_MORE_CONSTRAINTS = 71; +const PacketVersion VERSION_ENTITIES_BULLET_DYNAMICS = 70; enum class EntityQueryPacketVersion: PacketVersion { JSONFilter = 18, diff --git a/libraries/physics/src/ObjectConstraintConeTwist.cpp b/libraries/physics/src/ObjectConstraintConeTwist.cpp index 7a7120cd09..a0a9a5fe0c 100644 --- a/libraries/physics/src/ObjectConstraintConeTwist.cpp +++ b/libraries/physics/src/ObjectConstraintConeTwist.cpp @@ -293,10 +293,6 @@ QVariantMap ObjectConstraintConeTwist::getArguments() { arguments["softness"] = _softness; arguments["biasFactor"] = _biasFactor; arguments["relaxationFactor"] = _relaxationFactor; - - - // arguments["linearPosition"] = static_cast(_constraint)->getLinearPos(); - // arguments["angularPosition"] = static_cast(_constraint)->getAngularPos(); } }); return arguments; diff --git a/scripts/developer/tests/dynamics/dynamics-tests-interface.js b/scripts/developer/tests/dynamics/dynamics-tests-interface.js index 53517fab24..7db8b86a05 100644 --- a/scripts/developer/tests/dynamics/dynamics-tests-interface.js +++ b/scripts/developer/tests/dynamics/dynamics-tests-interface.js @@ -1,3 +1,15 @@ +// +// dynamics-tests-interface.js +// scripts/developer/tests/dynamics/ +// +// Created by Seth Alves 2017-4-30 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + "use strict"; /* globals $, EventBridge */ diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js index 18585ef152..7e55b831ca 100644 --- a/scripts/developer/tests/dynamics/dynamicsTests.js +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -1,3 +1,13 @@ +// +// dynamicsTests.js +// scripts/developer/tests/dynamics/ +// +// Created by Seth Alves 2017-4-30 +// Copyright 2017 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// "use strict"; From 2e23a073f717531f4899a738c4a62b83440d7986 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 30 Apr 2017 14:03:45 -0700 Subject: [PATCH 20/23] trying to get electron package to download --- server-console/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-console/package.json b/server-console/package.json index f72ffc347f..16d6459656 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -10,7 +10,7 @@ "devDependencies": { "electron-compilers": "^1.0.1", "electron-packager": "^6.0.2", - "electron-prebuilt": "0.37.5" + "electron": "0.37.5" }, "repository": { "type": "git", From 6808cf62b61942796ae90a5dd4f2239296cead40 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Sun, 30 Apr 2017 14:09:44 -0700 Subject: [PATCH 21/23] trying to get electron package to download --- server-console/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server-console/package.json b/server-console/package.json index 16d6459656..f72ffc347f 100644 --- a/server-console/package.json +++ b/server-console/package.json @@ -10,7 +10,7 @@ "devDependencies": { "electron-compilers": "^1.0.1", "electron-packager": "^6.0.2", - "electron": "0.37.5" + "electron-prebuilt": "0.37.5" }, "repository": { "type": "git", From 3ec3005c6dac00ddb261fa700c9f2a9e659d2240 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 1 May 2017 15:32:17 -0700 Subject: [PATCH 22/23] code review --- libraries/physics/src/ObjectActionSpring.cpp | 4 ++-- libraries/physics/src/ObjectConstraintHinge.cpp | 2 -- libraries/physics/src/ObjectConstraintSlider.cpp | 6 ------ 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/libraries/physics/src/ObjectActionSpring.cpp b/libraries/physics/src/ObjectActionSpring.cpp index 3cd3926073..8c73f43d42 100644 --- a/libraries/physics/src/ObjectActionSpring.cpp +++ b/libraries/physics/src/ObjectActionSpring.cpp @@ -85,10 +85,10 @@ bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm linearTimeScale = _linearTimeScale; angularTimeScale = _angularTimeScale; - if (_otherID != QUuid()) { + if (!_otherID.isNull()) { if (other) { rotation = _desiredRotationalTarget * other->getRotation(); - position = _desiredPositionalTarget + other->getPosition(); + position = other->getRotation() * _desiredPositionalTarget + other->getPosition(); } else { // we should have an "other" but can't find it, so disable the spring. linearTimeScale = FLT_MAX; diff --git a/libraries/physics/src/ObjectConstraintHinge.cpp b/libraries/physics/src/ObjectConstraintHinge.cpp index 14a666f09e..dd82de924c 100644 --- a/libraries/physics/src/ObjectConstraintHinge.cpp +++ b/libraries/physics/src/ObjectConstraintHinge.cpp @@ -74,8 +74,6 @@ void ObjectConstraintHinge::updateHinge() { return; } - // auto bulletAxisInA = glmToBullet(axisInA); - // constraint->setAxis(bulletAxisInA); constraint->setLimit(low, high, softness, biasFactor, relaxationFactor); } diff --git a/libraries/physics/src/ObjectConstraintSlider.cpp b/libraries/physics/src/ObjectConstraintSlider.cpp index 6a59199356..d7d4df78af 100644 --- a/libraries/physics/src/ObjectConstraintSlider.cpp +++ b/libraries/physics/src/ObjectConstraintSlider.cpp @@ -24,15 +24,9 @@ ObjectConstraintSlider::ObjectConstraintSlider(const QUuid& id, EntityItemPointe _pointInA(glm::vec3(0.0f)), _axisInA(glm::vec3(0.0f)) { - #if WANT_DEBUG - qCDebug(physics) << "ObjectConstraintSlider::ObjectConstraintSlider"; - #endif } ObjectConstraintSlider::~ObjectConstraintSlider() { - #if WANT_DEBUG - qCDebug(physics) << "ObjectConstraintSlider::~ObjectConstraintSlider"; - #endif } QList ObjectConstraintSlider::getRigidBodies() { From cc586b7c6d1f3eb64138b29d4163157795801bcb Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 2 May 2017 14:12:55 -0700 Subject: [PATCH 23/23] make ragdoll test honor lifetime from UI --- scripts/developer/tests/dynamics/dynamicsTests.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/developer/tests/dynamics/dynamicsTests.js b/scripts/developer/tests/dynamics/dynamicsTests.js index 7e55b831ca..376eff182b 100644 --- a/scripts/developer/tests/dynamics/dynamicsTests.js +++ b/scripts/developer/tests/dynamics/dynamicsTests.js @@ -280,7 +280,7 @@ function ragdollTest(params) { var scale = 1.6; - var lifetime = 120; + var lifetime = params.lifetime; var pos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, {x: 0, y: 1.0, z: -2})); var neckLength = scale * 0.05;