Merge pull request #5986 from sethalves/kinematic-grab

Kinematic grab
This commit is contained in:
Andrew Meadows 2015-10-06 10:30:06 -07:00
commit 2c276bb13e
8 changed files with 352 additions and 35 deletions

View file

@ -79,8 +79,8 @@ var STATE_NEAR_GRABBING_NON_COLLIDING = 6;
var STATE_CONTINUE_NEAR_GRABBING_NON_COLLIDING = 7;
var STATE_RELEASE = 8;
var GRAB_USER_DATA_KEY = "grabKey";
var GRABBABLE_DATA_KEY = "grabbableKey";
var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with grab.js
var GRAB_USER_DATA_KEY = "grabKey"; // shared with grab.js
function getTag() {
return "grab-" + MyAvatar.sessionUUID;
@ -312,7 +312,8 @@ function MyController(hand, triggerAction) {
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation",
"gravity", "ignoreForCollisions"]);
// add the action and initialize some variables
this.currentObjectPosition = grabbedProperties.position;
@ -336,7 +337,7 @@ function MyController(hand, triggerAction) {
if (this.actionID !== null) {
this.setState(STATE_CONTINUE_DISTANCE_HOLDING);
this.activateEntity(this.grabbedEntity);
this.activateEntity(this.grabbedEntity, grabbedProperties);
if (this.hand === RIGHT_HAND) {
Entities.callEntityMethod(this.grabbedEntity, "setRightHand");
} else {
@ -439,9 +440,9 @@ function MyController(hand, triggerAction) {
this.lineOff();
this.activateEntity(this.grabbedEntity);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity,
["position", "rotation", "gravity", "ignoreForCollisions"]);
this.activateEntity(this.grabbedEntity, grabbedProperties);
var handRotation = this.getHandRotation();
var handPosition = this.getHandPosition();
@ -454,7 +455,7 @@ function MyController(hand, triggerAction) {
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
this.actionID = NULL_ACTION_ID;
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
this.actionID = Entities.addAction("kinematic-hold", this.grabbedEntity, {
hand: this.hand === RIGHT_HAND ? "right" : "left",
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
relativePosition: offsetPosition,
@ -640,20 +641,35 @@ function MyController(hand, triggerAction) {
this.release();
};
this.activateEntity = function() {
var data = {
activated: true,
avatarId: MyAvatar.sessionUUID
};
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
this.activateEntity = function(entityID, grabbedProperties) {
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
data["activated"] = true;
data["avatarId"] = MyAvatar.sessionUUID;
data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1;
// zero gravity and set ignoreForCollisions to true, but in a way that lets us put them back, after all grabs are done
if (data["refCount"] == 1) {
data["gravity"] = grabbedProperties.gravity;
data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions;
Entities.editEntity(entityID, {gravity: {x:0, y:0, z:0}, ignoreForCollisions: true});
}
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
};
this.deactivateEntity = function() {
var data = {
activated: false,
avatarId: null
};
setEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, data);
this.deactivateEntity = function(entityID) {
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
if (data && data["refCount"]) {
data["refCount"] = data["refCount"] - 1;
if (data["refCount"] < 1) {
Entities.editEntity(entityID, {
gravity: data["gravity"],
ignoreForCollisions: data["ignoreForCollisions"]
});
data = null;
}
} else {
data = null;
}
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
};
}

View file

@ -26,7 +26,8 @@ var IDENTITY_QUAT = {
z: 0,
w: 0
};
var GRABBABLE_DATA_KEY = "grabbableKey";
var GRABBABLE_DATA_KEY = "grabbableKey"; // shared with handControllerGrab.js
var GRAB_USER_DATA_KEY = "grabKey"; // shared with handControllerGrab.js
var defaultGrabbableData = {
grabbable: true
@ -247,7 +248,6 @@ Grabber = function() {
this.currentPosition = ZERO_VEC3;
this.planeNormal = ZERO_VEC3;
this.originalGravity = ZERO_VEC3;
// maxDistance is a function of the size of the object.
this.maxDistance;
@ -346,14 +346,11 @@ Grabber.prototype.pressEvent = function(event) {
return;
}
Entities.editEntity(clickedEntity, {
gravity: ZERO_VEC3
});
this.activateEntity(clickedEntity, entityProperties);
this.isGrabbing = true;
this.entityID = clickedEntity;
this.currentPosition = entityProperties.position;
this.originalGravity = entityProperties.gravity;
this.targetPosition = {
x: this.startPosition.x,
y: this.startPosition.y,
@ -379,12 +376,7 @@ Grabber.prototype.pressEvent = function(event) {
Grabber.prototype.releaseEvent = function() {
if (this.isGrabbing) {
if (Vec3.length(this.originalGravity) != 0) {
Entities.editEntity(this.entityID, {
gravity: this.originalGravity
});
}
this.deactivateEntity(this.entityID);
this.isGrabbing = false
Entities.deleteAction(this.entityID, this.actionID);
this.actionID = null;
@ -503,6 +495,39 @@ Grabber.prototype.keyPressEvent = function(event) {
this.computeNewGrabPlane();
}
Grabber.prototype.activateEntity = function(entityID, grabbedProperties) {
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
data["activated"] = true;
data["avatarId"] = MyAvatar.sessionUUID;
data["refCount"] = data["refCount"] ? data["refCount"] + 1 : 1;
// zero gravity and set ignoreForCollisions to true, but in a way that lets us put them back, after all grabs are done
if (data["refCount"] == 1) {
data["gravity"] = grabbedProperties.gravity;
data["ignoreForCollisions"] = grabbedProperties.ignoreForCollisions;
Entities.editEntity(entityID, {gravity: {x:0, y:0, z:0}, ignoreForCollisions: true});
}
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
};
Grabber.prototype.deactivateEntity = function(entityID) {
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
if (data && data["refCount"]) {
data["refCount"] = data["refCount"] - 1;
if (data["refCount"] < 1) {
Entities.editEntity(entityID, {
gravity: data["gravity"],
ignoreForCollisions: data["ignoreForCollisions"]
});
data = null;
}
} else {
data = null;
}
setEntityCustomData(GRAB_USER_DATA_KEY, entityID, data);
};
var grabber = new Grabber();
function pressEvent(event) {

View file

@ -79,13 +79,21 @@ getEntityUserData = function(id) {
// Non-destructively modify the user data of an entity.
setEntityCustomData = function(customKey, id, data) {
var userData = getEntityUserData(id);
userData[customKey] = data;
if (data == null) {
delete userData[customKey];
} else {
userData[customKey] = data;
}
setEntityUserData(id, userData);
}
getEntityCustomData = function(customKey, id, defaultValue) {
var userData = getEntityUserData(id);
return userData[customKey] ? userData[customKey] : defaultValue;
if (undefined != userData[customKey]) {
return userData[customKey];
} else {
return defaultValue;
}
}
mergeObjects = function(proto, custom) {

View file

@ -12,6 +12,7 @@
#include <avatar/AvatarActionHold.h>
#include <avatar/AvatarActionKinematicHold.h>
#include <ObjectActionOffset.h>
#include <ObjectActionSpring.h>
@ -28,6 +29,8 @@ EntityActionPointer interfaceActionFactory(EntityActionType type, const QUuid& i
return (EntityActionPointer) new ObjectActionSpring(id, ownerEntity);
case ACTION_TYPE_HOLD:
return (EntityActionPointer) new AvatarActionHold(id, ownerEntity);
case ACTION_TYPE_KINEMATIC_HOLD:
return (EntityActionPointer) new AvatarActionKinematicHold(id, ownerEntity);
}
assert(false);

View file

@ -0,0 +1,188 @@
//
// AvatarActionKinematicHold.cpp
// interface/src/avatar/
//
// Created by Seth Alves 2015-6-9
// Copyright 2015 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 "avatar/MyAvatar.h"
#include "avatar/AvatarManager.h"
#include "AvatarActionKinematicHold.h"
const uint16_t AvatarActionKinematicHold::holdVersion = 1;
AvatarActionKinematicHold::AvatarActionKinematicHold(const QUuid& id, EntityItemPointer ownerEntity) :
ObjectActionSpring(id, ownerEntity),
_relativePosition(glm::vec3(0.0f)),
_relativeRotation(glm::quat()),
_hand("right"),
_mine(false),
_previousPositionalTarget(Vectors::ZERO),
_previousRotationalTarget(Quaternions::IDENTITY)
{
_type = ACTION_TYPE_KINEMATIC_HOLD;
#if WANT_DEBUG
qDebug() << "AvatarActionKinematicHold::AvatarActionKinematicHold";
#endif
}
AvatarActionKinematicHold::~AvatarActionKinematicHold() {
#if WANT_DEBUG
qDebug() << "AvatarActionKinematicHold::~AvatarActionKinematicHold";
#endif
}
void AvatarActionKinematicHold::updateActionWorker(float deltaTimeStep) {
if (!_mine) {
// if a local script isn't updating this, then we are just getting spring-action data over the wire.
// let the super-class handle it.
ObjectActionSpring::updateActionWorker(deltaTimeStep);
return;
}
assert(deltaTimeStep > 0.0f);
glm::quat rotation;
glm::vec3 position;
glm::vec3 offset;
bool gotLock = withTryReadLock([&]{
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
glm::vec3 palmPosition;
glm::quat palmRotation;
if (_hand == "right") {
palmPosition = myAvatar->getRightPalmPosition();
palmRotation = myAvatar->getRightPalmRotation();
} else {
palmPosition = myAvatar->getLeftPalmPosition();
palmRotation = myAvatar->getLeftPalmRotation();
}
rotation = palmRotation * _relativeRotation;
offset = rotation * _relativePosition;
position = palmPosition + offset;
});
if (gotLock) {
gotLock = withTryWriteLock([&]{
if (_positionalTarget != position || _rotationalTarget != rotation) {
_positionalTarget = position;
_rotationalTarget = rotation;
auto ownerEntity = _ownerEntity.lock();
if (ownerEntity) {
ownerEntity->setActionDataDirty(true);
void* physicsInfo = ownerEntity->getPhysicsInfo();
if (physicsInfo) {
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
btRigidBody* rigidBody = motionState ? motionState->getRigidBody() : nullptr;
if (!rigidBody) {
qDebug() << "ObjectActionSpring::updateActionWorker no rigidBody";
return;
}
if (_setVelocity) {
if (_previousSet) {
glm::vec3 positionalVelocity = (_positionalTarget - _previousPositionalTarget) / deltaTimeStep;
rigidBody->setLinearVelocity(glmToBullet(positionalVelocity));
// back up along velocity a bit in order to smooth out a "vibrating" appearance
_positionalTarget -= positionalVelocity * deltaTimeStep / 2.0f;
}
}
btTransform worldTrans = rigidBody->getWorldTransform();
worldTrans.setOrigin(glmToBullet(_positionalTarget));
worldTrans.setRotation(glmToBullet(_rotationalTarget));
rigidBody->setWorldTransform(worldTrans);
_previousPositionalTarget = _positionalTarget;
_previousRotationalTarget = _rotationalTarget;
_previousSet = true;
}
}
}
});
}
if (gotLock) {
ObjectActionSpring::updateActionWorker(deltaTimeStep);
}
}
bool AvatarActionKinematicHold::updateArguments(QVariantMap arguments) {
if (!ObjectAction::updateArguments(arguments)) {
return false;
}
bool ok = true;
glm::vec3 relativePosition =
EntityActionInterface::extractVec3Argument("kinematic-hold", arguments, "relativePosition", ok, false);
if (!ok) {
relativePosition = _relativePosition;
}
ok = true;
glm::quat relativeRotation =
EntityActionInterface::extractQuatArgument("kinematic-hold", arguments, "relativeRotation", ok, false);
if (!ok) {
relativeRotation = _relativeRotation;
}
ok = true;
QString hand =
EntityActionInterface::extractStringArgument("kinematic-hold", arguments, "hand", ok, false);
if (!ok || !(hand == "left" || hand == "right")) {
hand = _hand;
}
ok = true;
int setVelocity =
EntityActionInterface::extractIntegerArgument("kinematic-hold", arguments, "setVelocity", ok, false);
if (!ok) {
setVelocity = false;
}
if (relativePosition != _relativePosition
|| relativeRotation != _relativeRotation
|| hand != _hand) {
withWriteLock([&] {
_relativePosition = relativePosition;
_relativeRotation = relativeRotation;
_hand = hand;
_setVelocity = setVelocity;
_mine = true;
_active = true;
activateBody();
});
}
return true;
}
QVariantMap AvatarActionKinematicHold::getArguments() {
QVariantMap arguments = ObjectAction::getArguments();
withReadLock([&]{
if (!_mine) {
arguments = ObjectActionSpring::getArguments();
return;
}
arguments["relativePosition"] = glmToQMap(_relativePosition);
arguments["relativeRotation"] = glmToQMap(_relativeRotation);
arguments["setVelocity"] = _setVelocity;
arguments["hand"] = _hand;
});
return arguments;
}
void AvatarActionKinematicHold::deserialize(QByteArray serializedArguments) {
if (!_mine) {
ObjectActionSpring::deserialize(serializedArguments);
}
}

View file

@ -0,0 +1,47 @@
//
// AvatarActionKinematicHold.h
// interface/src/avatar/
//
// Created by Seth Alves 2015-10-2
// Copyright 2015 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_AvatarActionKinematicHold_h
#define hifi_AvatarActionKinematicHold_h
#include <QUuid>
#include <EntityItem.h>
#include <ObjectActionSpring.h>
class AvatarActionKinematicHold : public ObjectActionSpring {
public:
AvatarActionKinematicHold(const QUuid& id, EntityItemPointer ownerEntity);
virtual ~AvatarActionKinematicHold();
virtual bool updateArguments(QVariantMap arguments);
virtual QVariantMap getArguments();
virtual void updateActionWorker(float deltaTimeStep);
virtual void deserialize(QByteArray serializedArguments);
private:
static const uint16_t holdVersion;
glm::vec3 _relativePosition;
glm::quat _relativeRotation;
QString _hand;
bool _mine = false;
bool _previousSet = false;
glm::vec3 _previousPositionalTarget;
glm::quat _previousRotationalTarget;
bool _setVelocity = false;
};
#endif // hifi_AvatarActionKinematicHold_h

View file

@ -100,6 +100,9 @@ EntityActionType EntityActionInterface::actionTypeFromString(QString actionTypeS
if (normalizedActionTypeString == "hold") {
return ACTION_TYPE_HOLD;
}
if (normalizedActionTypeString == "kinematichold") {
return ACTION_TYPE_KINEMATIC_HOLD;
}
qDebug() << "Warning -- EntityActionInterface::actionTypeFromString got unknown action-type name" << actionTypeString;
return ACTION_TYPE_NONE;
@ -115,6 +118,8 @@ QString EntityActionInterface::actionTypeToString(EntityActionType actionType) {
return "spring";
case ACTION_TYPE_HOLD:
return "hold";
case ACTION_TYPE_KINEMATIC_HOLD:
return "kinematic-hold";
}
assert(false);
return "none";
@ -244,6 +249,28 @@ float EntityActionInterface::extractFloatArgument(QString objectName, QVariantMa
return v;
}
int EntityActionInterface::extractIntegerArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {
if (required) {
qDebug() << objectName << "requires argument:" << argumentName;
}
ok = false;
return 0.0f;
}
QVariant vV = arguments[argumentName];
bool vOk = true;
int v = vV.toInt(&vOk);
if (!vOk || v != v) {
ok = false;
return 0;
}
return v;
}
QString EntityActionInterface::extractStringArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required) {
if (!arguments.contains(argumentName)) {

View file

@ -23,7 +23,8 @@ enum EntityActionType {
ACTION_TYPE_NONE = 0,
ACTION_TYPE_OFFSET = 1000,
ACTION_TYPE_SPRING = 2000,
ACTION_TYPE_HOLD = 3000
ACTION_TYPE_HOLD = 3000,
ACTION_TYPE_KINEMATIC_HOLD = 4000
};
@ -69,6 +70,8 @@ protected:
QString argumentName, bool& ok, bool required = true);
static float extractFloatArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required = true);
static int extractIntegerArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required = true);
static QString extractStringArgument(QString objectName, QVariantMap arguments,
QString argumentName, bool& ok, bool required = true);