mirror of
https://github.com/overte-org/overte.git
synced 2025-07-24 01:03:58 +02:00
grab js api; send grab information through trait system
This commit is contained in:
parent
aeafb3c9a7
commit
78aff6e95c
44 changed files with 1907 additions and 145 deletions
|
@ -146,7 +146,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (traitType == AvatarTraits::AvatarEntity) {
|
if (traitType == AvatarTraits::AvatarEntity ||
|
||||||
|
traitType == AvatarTraits::Grab) {
|
||||||
auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID);
|
auto& instanceVersionRef = _lastReceivedTraitVersions.getInstanceValueRef(traitType, instanceID);
|
||||||
|
|
||||||
if (packetTraitVersion > instanceVersionRef) {
|
if (packetTraitVersion > instanceVersionRef) {
|
||||||
|
|
|
@ -208,6 +208,8 @@
|
||||||
#include "InterfaceParentFinder.h"
|
#include "InterfaceParentFinder.h"
|
||||||
#include "ui/OctreeStatsProvider.h"
|
#include "ui/OctreeStatsProvider.h"
|
||||||
|
|
||||||
|
#include "avatar/GrabManager.h"
|
||||||
|
|
||||||
#include <GPUIdent.h>
|
#include <GPUIdent.h>
|
||||||
#include <gl/GLHelpers.h>
|
#include <gl/GLHelpers.h>
|
||||||
#include <src/scripting/GooglePolyScriptingInterface.h>
|
#include <src/scripting/GooglePolyScriptingInterface.h>
|
||||||
|
@ -919,6 +921,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
||||||
DependencyManager::set<ResourceRequestObserver>();
|
DependencyManager::set<ResourceRequestObserver>();
|
||||||
DependencyManager::set<Keyboard>();
|
DependencyManager::set<Keyboard>();
|
||||||
DependencyManager::set<KeyboardScriptingInterface>();
|
DependencyManager::set<KeyboardScriptingInterface>();
|
||||||
|
DependencyManager::set<GrabManager>();
|
||||||
|
|
||||||
return previousSessionCrashed;
|
return previousSessionCrashed;
|
||||||
}
|
}
|
||||||
|
@ -6085,6 +6088,9 @@ void Application::update(float deltaTime) {
|
||||||
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
||||||
updateDialogs(deltaTime); // update various stats dialogs if present
|
updateDialogs(deltaTime); // update various stats dialogs if present
|
||||||
|
|
||||||
|
auto grabManager = DependencyManager::get<GrabManager>();
|
||||||
|
grabManager->simulateGrabs();
|
||||||
|
|
||||||
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
|
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -58,6 +58,16 @@ AvatarActionHold::~AvatarActionHold() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarActionHold::removeFromOwner() {
|
||||||
|
auto avatarManager = DependencyManager::get<AvatarManager>();
|
||||||
|
if (avatarManager) {
|
||||||
|
auto myAvatar = avatarManager->getMyAvatar();
|
||||||
|
if (myAvatar) {
|
||||||
|
myAvatar->removeHoldAction(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool AvatarActionHold::getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
|
bool AvatarActionHold::getAvatarRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
MyCharacterController* controller = myAvatar ? myAvatar->getCharacterController() : nullptr;
|
MyCharacterController* controller = myAvatar ? myAvatar->getCharacterController() : nullptr;
|
||||||
|
@ -226,7 +236,7 @@ bool AvatarActionHold::getTarget(float deltaTimeStep, glm::quat& rotation, glm::
|
||||||
}
|
}
|
||||||
|
|
||||||
rotation = palmRotation * _relativeRotation;
|
rotation = palmRotation * _relativeRotation;
|
||||||
position = palmPosition + rotation * _relativePosition;
|
position = palmPosition + palmRotation * _relativePosition;
|
||||||
|
|
||||||
// update linearVelocity based on offset via _relativePosition;
|
// update linearVelocity based on offset via _relativePosition;
|
||||||
linearVelocity = linearVelocity + glm::cross(angularVelocity, position - palmPosition);
|
linearVelocity = linearVelocity + glm::cross(angularVelocity, position - palmPosition);
|
||||||
|
@ -369,8 +379,12 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
||||||
hand = _hand;
|
hand = _hand;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok = true;
|
||||||
|
holderID = EntityDynamicInterface::extractStringArgument("hold", arguments, "holderID", ok, false);
|
||||||
|
if (!ok) {
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
holderID = myAvatar->getSessionUUID();
|
holderID = myAvatar->getSessionUUID();
|
||||||
|
}
|
||||||
|
|
||||||
ok = true;
|
ok = true;
|
||||||
kinematic = EntityDynamicInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
|
kinematic = EntityDynamicInterface::extractBooleanArgument("hold", arguments, "kinematic", ok, false);
|
||||||
|
|
|
@ -26,6 +26,8 @@ public:
|
||||||
AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity);
|
AvatarActionHold(const QUuid& id, EntityItemPointer ownerEntity);
|
||||||
virtual ~AvatarActionHold();
|
virtual ~AvatarActionHold();
|
||||||
|
|
||||||
|
virtual void removeFromOwner() override;
|
||||||
|
|
||||||
virtual bool updateArguments(QVariantMap arguments) override;
|
virtual bool updateArguments(QVariantMap arguments) override;
|
||||||
virtual QVariantMap getArguments() override;
|
virtual QVariantMap getArguments() override;
|
||||||
|
|
||||||
|
|
|
@ -887,3 +887,13 @@ QVariantMap AvatarManager::getPalData(const QStringList& specificAvatarIdentifie
|
||||||
doc.insert("data", palData);
|
doc.insert("data", palData);
|
||||||
return doc.toVariantMap();
|
return doc.toVariantMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AvatarManager::accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators) {
|
||||||
|
auto avatarMap = getHashCopy();
|
||||||
|
AvatarHash::iterator itr = avatarMap.begin();
|
||||||
|
while (itr != avatarMap.end()) {
|
||||||
|
const auto& avatar = std::static_pointer_cast<Avatar>(*itr);
|
||||||
|
avatar->accumulateGrabPositions(grabAccumulators);
|
||||||
|
itr++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -198,6 +198,8 @@ public:
|
||||||
void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction);
|
void handleProcessedPhysicsTransaction(PhysicsEngine::Transaction& transaction);
|
||||||
void removeDeadAvatarEntities(const SetOfEntities& deadEntities);
|
void removeDeadAvatarEntities(const SetOfEntities& deadEntities);
|
||||||
|
|
||||||
|
void accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* @function AvatarManager.updateAvatarRenderStatus
|
* @function AvatarManager.updateAvatarRenderStatus
|
||||||
|
|
39
interface/src/avatar/GrabManager.cpp
Normal file
39
interface/src/avatar/GrabManager.cpp
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
//
|
||||||
|
// GrabManager.cpp
|
||||||
|
// interface/src/avatar/
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2018-12-4.
|
||||||
|
// Copyright 2018 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 "GrabManager.h"
|
||||||
|
|
||||||
|
void GrabManager::simulateGrabs() {
|
||||||
|
QSharedPointer<AvatarManager> avatarManager = DependencyManager::get<AvatarManager>();
|
||||||
|
|
||||||
|
// Update grabbed objects
|
||||||
|
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
|
auto entityTree = entityTreeRenderer->getTree();
|
||||||
|
entityTree->withReadLock([&] {
|
||||||
|
PROFILE_RANGE(simulation, "Grabs");
|
||||||
|
|
||||||
|
std::map<QUuid, GrabLocationAccumulator> grabAccumulators;
|
||||||
|
avatarManager->accumulateGrabPositions(grabAccumulators);
|
||||||
|
|
||||||
|
for (auto& accumulatedLocation : grabAccumulators) {
|
||||||
|
QUuid grabbedThingID = accumulatedLocation.first;
|
||||||
|
GrabLocationAccumulator& acc = accumulatedLocation.second;
|
||||||
|
bool success;
|
||||||
|
SpatiallyNestablePointer grabbedThing = SpatiallyNestable::findByID(grabbedThingID, success);
|
||||||
|
if (success && grabbedThing) {
|
||||||
|
glm::vec3 finalPosition = acc.finalizePosition();
|
||||||
|
glm::quat finalOrientation = acc.finalizeOrientation();
|
||||||
|
grabbedThing->setWorldTransform(finalPosition, finalOrientation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
23
interface/src/avatar/GrabManager.h
Normal file
23
interface/src/avatar/GrabManager.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//
|
||||||
|
// GrabManager.h
|
||||||
|
// interface/src/avatar/
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2018-12-4.
|
||||||
|
// Copyright 2018 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 <AvatarData.h>
|
||||||
|
#include <EntityTreeRenderer.h>
|
||||||
|
#include "AvatarManager.h"
|
||||||
|
|
||||||
|
class GrabManager : public QObject, public Dependency {
|
||||||
|
Q_OBJECT
|
||||||
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
|
public:
|
||||||
|
void simulateGrabs();
|
||||||
|
|
||||||
|
};
|
|
@ -820,6 +820,7 @@ void MyAvatar::simulate(float deltaTime) {
|
||||||
// and all of its joints, now update our attachements.
|
// and all of its joints, now update our attachements.
|
||||||
Avatar::simulateAttachments(deltaTime);
|
Avatar::simulateAttachments(deltaTime);
|
||||||
relayJointDataToChildren();
|
relayJointDataToChildren();
|
||||||
|
updateGrabs();
|
||||||
|
|
||||||
if (!_skeletonModel->hasSkeleton()) {
|
if (!_skeletonModel->hasSkeleton()) {
|
||||||
// All the simulation that can be done has been done
|
// All the simulation that can be done has been done
|
||||||
|
@ -874,47 +875,12 @@ void MyAvatar::simulate(float deltaTime) {
|
||||||
zoneAllowsFlying = zone->getFlyingAllowed();
|
zoneAllowsFlying = zone->getFlyingAllowed();
|
||||||
collisionlessAllowed = zone->getGhostingAllowed();
|
collisionlessAllowed = zone->getGhostingAllowed();
|
||||||
}
|
}
|
||||||
auto now = usecTimestampNow();
|
|
||||||
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
EntityEditPacketSender* packetSender = qApp->getEntityEditPacketSender();
|
||||||
MovingEntitiesOperator moveOperator;
|
bool force = false;
|
||||||
|
bool iShouldTellServer = true;
|
||||||
forEachDescendant([&](SpatiallyNestablePointer object) {
|
forEachDescendant([&](SpatiallyNestablePointer object) {
|
||||||
// if the queryBox has changed, tell the entity-server
|
entityTree->updateEntityQueryAACube(object, packetSender, force, iShouldTellServer);
|
||||||
if (object->getNestableType() == NestableType::Entity && object->updateQueryAACube()) {
|
|
||||||
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
|
||||||
bool success;
|
|
||||||
AACube newCube = entity->getQueryAACube(success);
|
|
||||||
if (success) {
|
|
||||||
moveOperator.addEntityToMoveList(entity, newCube);
|
|
||||||
}
|
|
||||||
// send an edit packet to update the entity-server about the queryAABox
|
|
||||||
if (packetSender && entity->isDomainEntity()) {
|
|
||||||
EntityItemProperties properties = entity->getProperties();
|
|
||||||
properties.setQueryAACubeDirty();
|
|
||||||
properties.setLastEdited(now);
|
|
||||||
|
|
||||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree,
|
|
||||||
entity->getID(), properties);
|
|
||||||
entity->setLastBroadcast(usecTimestampNow());
|
|
||||||
|
|
||||||
entity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
|
||||||
EntityItemPointer entityDescendant = std::dynamic_pointer_cast<EntityItem>(descendant);
|
|
||||||
if (entityDescendant && entityDescendant->isDomainEntity() && descendant->updateQueryAACube()) {
|
|
||||||
EntityItemProperties descendantProperties;
|
|
||||||
descendantProperties.setQueryAACube(descendant->getQueryAACube());
|
|
||||||
descendantProperties.setLastEdited(now);
|
|
||||||
packetSender->queueEditEntityMessage(PacketType::EntityEdit, entityTree,
|
|
||||||
entityDescendant->getID(), descendantProperties);
|
|
||||||
entityDescendant->setLastBroadcast(now); // for debug/physics status icons
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// also update the position of children in our local octree
|
|
||||||
if (moveOperator.hasMovingEntities()) {
|
|
||||||
PerformanceTimer perfTimer("recurseTreeWithOperator");
|
|
||||||
entityTree->recurseTreeWithOperator(&moveOperator);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
bool isPhysicsEnabled = qApp->isPhysicsEnabled();
|
bool isPhysicsEnabled = qApp->isPhysicsEnabled();
|
||||||
_characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled);
|
_characterController.setFlyingAllowed((zoneAllowsFlying && _enableFlying) || !isPhysicsEnabled);
|
||||||
|
@ -4746,3 +4712,50 @@ SpatialParentTree* MyAvatar::getParentTree() const {
|
||||||
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
||||||
return entityTree.get();
|
return entityTree.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const QUuid MyAvatar::grab(const QUuid& targetID, int parentJointIndex,
|
||||||
|
glm::vec3 positionalOffset, glm::quat rotationalOffset) {
|
||||||
|
auto grabID = QUuid::createUuid();
|
||||||
|
// create a temporary grab object to get grabData
|
||||||
|
|
||||||
|
QString hand = "none";
|
||||||
|
if (parentJointIndex == CONTROLLER_RIGHTHAND_INDEX ||
|
||||||
|
parentJointIndex == CAMERA_RELATIVE_CONTROLLER_RIGHTHAND_INDEX ||
|
||||||
|
parentJointIndex == FARGRAB_RIGHTHAND_INDEX ||
|
||||||
|
parentJointIndex == getJointIndex("RightHand")) {
|
||||||
|
hand = "right";
|
||||||
|
} else if (parentJointIndex == CONTROLLER_LEFTHAND_INDEX ||
|
||||||
|
parentJointIndex == CAMERA_RELATIVE_CONTROLLER_LEFTHAND_INDEX ||
|
||||||
|
parentJointIndex == FARGRAB_LEFTHAND_INDEX ||
|
||||||
|
parentJointIndex == getJointIndex("LeftHand")) {
|
||||||
|
hand = "left";
|
||||||
|
}
|
||||||
|
|
||||||
|
Grab tmpGrab(DependencyManager::get<NodeList>()->getSessionUUID(),
|
||||||
|
targetID, parentJointIndex, hand, positionalOffset, rotationalOffset);
|
||||||
|
QByteArray grabData = tmpGrab.toByteArray();
|
||||||
|
bool dataChanged = updateAvatarGrabData(grabID, grabData);
|
||||||
|
|
||||||
|
if (dataChanged && _clientTraitsHandler) {
|
||||||
|
// indicate that the changed data should be sent to the mixer
|
||||||
|
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return grabID;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::releaseGrab(const QUuid& grabID) {
|
||||||
|
bool tellHandler { false };
|
||||||
|
|
||||||
|
_avatarGrabsLock.withWriteLock([&] {
|
||||||
|
if (_avatarGrabData.remove(grabID)) {
|
||||||
|
_deletedAvatarGrabs.insert(grabID);
|
||||||
|
tellHandler = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (tellHandler && _clientTraitsHandler) {
|
||||||
|
// indicate the deletion of the data to the mixer
|
||||||
|
_clientTraitsHandler->markInstancedTraitDeleted(AvatarTraits::Grab, grabID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1170,6 +1170,25 @@ public:
|
||||||
|
|
||||||
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
|
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Create a new grab.
|
||||||
|
* @function MyAvatar.grab
|
||||||
|
* @param {Uuid} targetID - id of grabbed thing
|
||||||
|
* @param {number} parentJointIndex - avatar joint being used to grab
|
||||||
|
* @param {Vec3} offset - target's positional offset from joint
|
||||||
|
* @param {Quat} rotationalOffset - target's rotational offset from joint
|
||||||
|
* @returns {Uuid} id of the new grab
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE const QUuid grab(const QUuid& targetID, int parentJointIndex,
|
||||||
|
glm::vec3 positionalOffset, glm::quat rotationalOffset);
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Release (delete) a grab.
|
||||||
|
* @function MyAvatar.releaseGrab
|
||||||
|
* @param {Uuid} grabID - id of grabbed thing
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void releaseGrab(const QUuid& grabID);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <shared/Camera.h>
|
#include <shared/Camera.h>
|
||||||
#include <SoftAttachmentModel.h>
|
#include <SoftAttachmentModel.h>
|
||||||
#include <render/TransitionStage.h>
|
#include <render/TransitionStage.h>
|
||||||
|
#include <GLMHelpers.h>
|
||||||
#include "ModelEntityItem.h"
|
#include "ModelEntityItem.h"
|
||||||
#include "RenderableModelEntityItem.h"
|
#include "RenderableModelEntityItem.h"
|
||||||
|
|
||||||
|
@ -481,6 +482,103 @@ void Avatar::removeAvatarEntitiesFromTree() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Avatar::updateGrabs() {
|
||||||
|
|
||||||
|
// update the Grabs according to any changes in _avatarGrabData
|
||||||
|
_avatarGrabsLock.withWriteLock([&] {
|
||||||
|
if (_avatarGrabDataChanged) {
|
||||||
|
foreach (auto grabID, _avatarGrabData.keys()) {
|
||||||
|
AvatarGrabMap::iterator grabItr = _avatarGrabs.find(grabID);
|
||||||
|
if (grabItr == _avatarGrabs.end()) {
|
||||||
|
GrabPointer grab = std::make_shared<Grab>();
|
||||||
|
grab->fromByteArray(_avatarGrabData.value(grabID));
|
||||||
|
_avatarGrabs[grabID] = grab;
|
||||||
|
_changedAvatarGrabs.insert(grabID);
|
||||||
|
} else {
|
||||||
|
GrabPointer grab = grabItr.value();
|
||||||
|
bool changed = grab->fromByteArray(_avatarGrabData.value(grabID));
|
||||||
|
if (changed) {
|
||||||
|
_changedAvatarGrabs.insert(grabID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_avatarGrabDataChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
|
auto entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
|
EntityEditPacketSender* packetSender = treeRenderer ? treeRenderer->getPacketSender() : nullptr;
|
||||||
|
auto sessionID = DependencyManager::get<NodeList>()->getSessionUUID();
|
||||||
|
|
||||||
|
QMutableSetIterator<QUuid> delItr(_deletedAvatarGrabs);
|
||||||
|
while (delItr.hasNext()) {
|
||||||
|
QUuid grabID = delItr.next();
|
||||||
|
GrabPointer grab = _avatarGrabs[grabID];
|
||||||
|
if (!grab) {
|
||||||
|
delItr.remove();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success);
|
||||||
|
|
||||||
|
// only clear this entry from the _deletedAvatarGrabs if we found the entity.
|
||||||
|
if (success && target) {
|
||||||
|
bool iShouldTellServer = target->getEditSenderID() == sessionID;
|
||||||
|
target->removeGrab(grab);
|
||||||
|
delItr.remove();
|
||||||
|
// in case this is the last grab on an entity, we need to shrink the queryAACube and tell the server
|
||||||
|
// about the final position.
|
||||||
|
if (entityTree) {
|
||||||
|
bool force = true;
|
||||||
|
entityTree->withWriteLock([&] {
|
||||||
|
entityTree->updateEntityQueryAACube(target, packetSender, force, iShouldTellServer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_avatarGrabs.remove(grabID);
|
||||||
|
_changedAvatarGrabs.remove(grabID);
|
||||||
|
}
|
||||||
|
|
||||||
|
QMutableSetIterator<QUuid> changeItr(_changedAvatarGrabs);
|
||||||
|
while (changeItr.hasNext()) {
|
||||||
|
QUuid grabID = changeItr.next();
|
||||||
|
GrabPointer& grab = _avatarGrabs[grabID];
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
SpatiallyNestablePointer target = SpatiallyNestable::findByID(grab->getTargetID(), success);
|
||||||
|
|
||||||
|
if (success && target) {
|
||||||
|
target->addGrab(grab);
|
||||||
|
// only clear this entry from the _changedAvatarGrabs if we found the entity.
|
||||||
|
changeItr.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void Avatar::accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators) {
|
||||||
|
// relay avatar's joint position to grabbed target in a way that allows for averaging
|
||||||
|
_avatarGrabsLock.withReadLock([&] {
|
||||||
|
foreach (auto grabID, _avatarGrabs.keys()) {
|
||||||
|
const GrabPointer& grab = _avatarGrabs.value(grabID);
|
||||||
|
|
||||||
|
if (!grab->getActionID().isNull()) {
|
||||||
|
continue; // the accumulated value isn't used, in this case.
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 jointTranslation = getAbsoluteJointTranslationInObjectFrame(grab->getParentJointIndex());
|
||||||
|
glm::quat jointRotation = getAbsoluteJointRotationInObjectFrame(grab->getParentJointIndex());
|
||||||
|
glm::mat4 jointMat = createMatFromQuatAndPos(jointRotation, jointTranslation);
|
||||||
|
glm::mat4 offsetMat = createMatFromQuatAndPos(grab->getRotationalOffset(), grab->getPositionalOffset());
|
||||||
|
glm::mat4 avatarMat = getTransform().getMatrix();
|
||||||
|
glm::mat4 worldTransform = avatarMat * jointMat * offsetMat;
|
||||||
|
GrabLocationAccumulator& grabLocationAccumulator = grabAccumulators[grab->getTargetID()];
|
||||||
|
grabLocationAccumulator.accumulate(extractTranslation(worldTransform), extractRotation(worldTransform));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
void Avatar::relayJointDataToChildren() {
|
void Avatar::relayJointDataToChildren() {
|
||||||
forEachChild([&](SpatiallyNestablePointer child) {
|
forEachChild([&](SpatiallyNestablePointer child) {
|
||||||
if (child->getNestableType() == NestableType::Entity) {
|
if (child->getNestableType() == NestableType::Entity) {
|
||||||
|
@ -582,6 +680,7 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
||||||
head->setScale(getModelScale());
|
head->setScale(getModelScale());
|
||||||
head->simulate(deltaTime);
|
head->simulate(deltaTime);
|
||||||
relayJointDataToChildren();
|
relayJointDataToChildren();
|
||||||
|
updateGrabs();
|
||||||
} else {
|
} else {
|
||||||
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
||||||
_skeletonModel->simulate(deltaTime, false);
|
_skeletonModel->simulate(deltaTime, false);
|
||||||
|
@ -1295,6 +1394,9 @@ glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (index) {
|
switch (index) {
|
||||||
|
case NO_JOINT_INDEX: {
|
||||||
|
return glm::quat();
|
||||||
|
}
|
||||||
case SENSOR_TO_WORLD_MATRIX_INDEX: {
|
case SENSOR_TO_WORLD_MATRIX_INDEX: {
|
||||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||||
glm::mat4 avatarMatrix = getLocalTransform().getMatrix();
|
glm::mat4 avatarMatrix = getLocalTransform().getMatrix();
|
||||||
|
@ -1344,6 +1446,9 @@ glm::vec3 Avatar::getAbsoluteJointTranslationInObjectFrame(int index) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (index) {
|
switch (index) {
|
||||||
|
case NO_JOINT_INDEX: {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
case SENSOR_TO_WORLD_MATRIX_INDEX: {
|
case SENSOR_TO_WORLD_MATRIX_INDEX: {
|
||||||
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
glm::mat4 sensorToWorldMatrix = getSensorToWorldMatrix();
|
||||||
glm::mat4 avatarMatrix = getLocalTransform().getMatrix();
|
glm::mat4 avatarMatrix = getLocalTransform().getMatrix();
|
||||||
|
|
|
@ -440,6 +440,8 @@ public:
|
||||||
std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); };
|
std::shared_ptr<AvatarTransit> getTransit() { return std::make_shared<AvatarTransit>(_transit); };
|
||||||
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config);
|
AvatarTransit::Status updateTransit(float deltaTime, const glm::vec3& avatarPosition, float avatarScale, const AvatarTransit::TransitConfig& config);
|
||||||
|
|
||||||
|
void accumulateGrabPositions(std::map<QUuid, GrabLocationAccumulator>& grabAccumulators);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void targetScaleChanged(float targetScale);
|
void targetScaleChanged(float targetScale);
|
||||||
|
|
||||||
|
@ -542,6 +544,7 @@ protected:
|
||||||
|
|
||||||
// protected methods...
|
// protected methods...
|
||||||
bool isLookingAtMe(AvatarSharedPointer avatar) const;
|
bool isLookingAtMe(AvatarSharedPointer avatar) const;
|
||||||
|
void updateGrabs();
|
||||||
void relayJointDataToChildren();
|
void relayJointDataToChildren();
|
||||||
|
|
||||||
void fade(render::Transaction& transaction, render::Transition::Type type);
|
void fade(render::Transaction& transaction, render::Transition::Type type);
|
||||||
|
@ -628,6 +631,8 @@ protected:
|
||||||
|
|
||||||
static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets,
|
static void metaBlendshapeOperator(render::ItemID renderItemID, int blendshapeNumber, const QVector<BlendshapeOffset>& blendshapeOffsets,
|
||||||
const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs);
|
const QVector<int>& blendedMeshSizes, const render::ItemIDs& subItemIDs);
|
||||||
|
|
||||||
|
AvatarGrabMap _avatarGrabs;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Avatar_h
|
#endif // hifi_Avatar_h
|
||||||
|
|
|
@ -1893,11 +1893,12 @@ qint64 AvatarData::packTrait(AvatarTraits::TraitType traitType, ExtendedIODevice
|
||||||
return bytesWritten;
|
return bytesWritten;
|
||||||
}
|
}
|
||||||
|
|
||||||
qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID,
|
|
||||||
|
qint64 AvatarData::packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitInstanceID traitInstanceID,
|
||||||
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
||||||
qint64 bytesWritten = 0;
|
qint64 bytesWritten = 0;
|
||||||
|
|
||||||
if (traitType == AvatarTraits::AvatarEntity) {
|
|
||||||
// grab a read lock on the avatar entities and check for entity data for the given ID
|
// grab a read lock on the avatar entities and check for entity data for the given ID
|
||||||
QByteArray entityBinaryData;
|
QByteArray entityBinaryData;
|
||||||
|
|
||||||
|
@ -1929,6 +1930,59 @@ qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTr
|
||||||
} else {
|
} else {
|
||||||
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
|
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
qint64 AvatarData::packGrabTraitInstance(AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitInstanceID traitInstanceID,
|
||||||
|
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
||||||
|
qint64 bytesWritten = 0;
|
||||||
|
|
||||||
|
// grab a read lock on the avatar grabs and check for grab data for the given ID
|
||||||
|
QByteArray grabBinaryData;
|
||||||
|
|
||||||
|
_avatarGrabsLock.withReadLock([this, &grabBinaryData, &traitInstanceID] {
|
||||||
|
if (_avatarGrabData.contains(traitInstanceID)) {
|
||||||
|
grabBinaryData = _avatarGrabData[traitInstanceID];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (grabBinaryData.size() > AvatarTraits::MAXIMUM_TRAIT_SIZE) {
|
||||||
|
qWarning() << "Refusing to pack instanced trait" << traitType << "of size" << grabBinaryData.size()
|
||||||
|
<< "bytes since it exceeds the maximum size " << AvatarTraits::MAXIMUM_TRAIT_SIZE << "bytes";
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesWritten += destination.writePrimitive(traitType);
|
||||||
|
|
||||||
|
if (traitVersion > AvatarTraits::DEFAULT_TRAIT_VERSION) {
|
||||||
|
bytesWritten += destination.writePrimitive(traitVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
bytesWritten += destination.write(traitInstanceID.toRfc4122());
|
||||||
|
|
||||||
|
if (!grabBinaryData.isNull()) {
|
||||||
|
AvatarTraits::TraitWireSize grabBinarySize = grabBinaryData.size();
|
||||||
|
|
||||||
|
bytesWritten += destination.writePrimitive(grabBinarySize);
|
||||||
|
bytesWritten += destination.write(grabBinaryData);
|
||||||
|
} else {
|
||||||
|
bytesWritten += destination.writePrimitive(AvatarTraits::DELETED_TRAIT_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bytesWritten;
|
||||||
|
}
|
||||||
|
|
||||||
|
qint64 AvatarData::packTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID traitInstanceID,
|
||||||
|
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion) {
|
||||||
|
qint64 bytesWritten = 0;
|
||||||
|
|
||||||
|
if (traitType == AvatarTraits::AvatarEntity) {
|
||||||
|
packAvatarEntityTraitInstance(traitType, traitInstanceID, destination, traitVersion);
|
||||||
|
} else if (traitType == AvatarTraits::Grab) {
|
||||||
|
packGrabTraitInstance(traitType, traitInstanceID, destination, traitVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
return bytesWritten;
|
return bytesWritten;
|
||||||
|
@ -1940,6 +1994,9 @@ void AvatarData::prepareResetTraitInstances() {
|
||||||
foreach (auto entityID, _avatarEntityData.keys()) {
|
foreach (auto entityID, _avatarEntityData.keys()) {
|
||||||
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
|
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::AvatarEntity, entityID);
|
||||||
}
|
}
|
||||||
|
foreach (auto grabID, _avatarGrabData.keys()) {
|
||||||
|
_clientTraitsHandler->markInstancedTraitUpdated(AvatarTraits::Grab, grabID);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1956,12 +2013,16 @@ void AvatarData::processTraitInstance(AvatarTraits::TraitType traitType,
|
||||||
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) {
|
AvatarTraits::TraitInstanceID instanceID, QByteArray traitBinaryData) {
|
||||||
if (traitType == AvatarTraits::AvatarEntity) {
|
if (traitType == AvatarTraits::AvatarEntity) {
|
||||||
updateAvatarEntity(instanceID, traitBinaryData);
|
updateAvatarEntity(instanceID, traitBinaryData);
|
||||||
|
} else if (traitType == AvatarTraits::Grab) {
|
||||||
|
updateAvatarGrabData(instanceID, traitBinaryData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) {
|
void AvatarData::processDeletedTraitInstance(AvatarTraits::TraitType traitType, AvatarTraits::TraitInstanceID instanceID) {
|
||||||
if (traitType == AvatarTraits::AvatarEntity) {
|
if (traitType == AvatarTraits::AvatarEntity) {
|
||||||
clearAvatarEntity(instanceID);
|
clearAvatarEntity(instanceID);
|
||||||
|
} else if (traitType == AvatarTraits::Grab) {
|
||||||
|
clearAvatarGrabData(instanceID);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2909,3 +2970,38 @@ AABox AvatarData::getDefaultBubbleBox() const {
|
||||||
bubbleBox.translate(_globalPosition);
|
bubbleBox.translate(_globalPosition);
|
||||||
return bubbleBox;
|
return bubbleBox;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AvatarData::updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData) {
|
||||||
|
bool changed { false };
|
||||||
|
_avatarGrabsLock.withWriteLock([&] {
|
||||||
|
AvatarGrabDataMap::iterator itr = _avatarGrabData.find(grabID);
|
||||||
|
if (itr == _avatarGrabData.end()) {
|
||||||
|
// create a new one
|
||||||
|
if (_avatarGrabData.size() < MAX_NUM_AVATAR_GRABS) {
|
||||||
|
_avatarGrabData.insert(grabID, grabData);
|
||||||
|
_avatarGrabDataChanged = true;
|
||||||
|
changed = true;
|
||||||
|
} else {
|
||||||
|
qCWarning(avatars) << "Can't create more grabs on avatar, limit reached.";
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// update an existing one
|
||||||
|
if (itr.value() != grabData) {
|
||||||
|
itr.value() = grabData;
|
||||||
|
_avatarGrabDataChanged = true;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AvatarData::clearAvatarGrabData(const QUuid& grabID) {
|
||||||
|
_avatarGrabsLock.withWriteLock([&] {
|
||||||
|
if (_avatarGrabData.remove(grabID)) {
|
||||||
|
_avatarGrabDataChanged = true;
|
||||||
|
_deletedAvatarGrabs.insert(grabID);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -54,15 +54,21 @@
|
||||||
#include "AvatarTraits.h"
|
#include "AvatarTraits.h"
|
||||||
#include "HeadData.h"
|
#include "HeadData.h"
|
||||||
#include "PathUtils.h"
|
#include "PathUtils.h"
|
||||||
|
#include "Grab.h"
|
||||||
|
|
||||||
#include <graphics/Material.h>
|
#include <graphics/Material.h>
|
||||||
|
|
||||||
using AvatarSharedPointer = std::shared_ptr<AvatarData>;
|
using AvatarSharedPointer = std::shared_ptr<AvatarData>;
|
||||||
using AvatarWeakPointer = std::weak_ptr<AvatarData>;
|
using AvatarWeakPointer = std::weak_ptr<AvatarData>;
|
||||||
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
|
using AvatarHash = QHash<QUuid, AvatarSharedPointer>;
|
||||||
|
|
||||||
using AvatarEntityMap = QMap<QUuid, QByteArray>;
|
using AvatarEntityMap = QMap<QUuid, QByteArray>;
|
||||||
using AvatarEntityIDs = QSet<QUuid>;
|
using AvatarEntityIDs = QSet<QUuid>;
|
||||||
|
|
||||||
|
using AvatarGrabDataMap = QMap<QUuid, QByteArray>;
|
||||||
|
using AvatarGrabIDs = QSet<QUuid>;
|
||||||
|
using AvatarGrabMap = QMap<QUuid, GrabPointer>;
|
||||||
|
|
||||||
using AvatarDataSequenceNumber = uint16_t;
|
using AvatarDataSequenceNumber = uint16_t;
|
||||||
|
|
||||||
// avatar motion behaviors
|
// avatar motion behaviors
|
||||||
|
@ -1342,6 +1348,13 @@ protected:
|
||||||
bool hasParent() const { return !getParentID().isNull(); }
|
bool hasParent() const { return !getParentID().isNull(); }
|
||||||
bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
|
bool hasFaceTracker() const { return _headData ? _headData->_isFaceTrackerConnected : false; }
|
||||||
|
|
||||||
|
qint64 packAvatarEntityTraitInstance(AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitInstanceID traitInstanceID,
|
||||||
|
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion);
|
||||||
|
qint64 packGrabTraitInstance(AvatarTraits::TraitType traitType,
|
||||||
|
AvatarTraits::TraitInstanceID traitInstanceID,
|
||||||
|
ExtendedIODevice& destination, AvatarTraits::TraitVersion traitVersion);
|
||||||
|
|
||||||
// isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master"
|
// isReplicated will be true on downstream Avatar Mixers and their clients, but false on the upstream "master"
|
||||||
// Audio Mixer that the replicated avatar is connected to.
|
// Audio Mixer that the replicated avatar is connected to.
|
||||||
bool _isReplicated{ false };
|
bool _isReplicated{ false };
|
||||||
|
@ -1452,6 +1465,12 @@ protected:
|
||||||
AvatarEntityMap _avatarEntityData;
|
AvatarEntityMap _avatarEntityData;
|
||||||
bool _avatarEntityDataChanged { false };
|
bool _avatarEntityDataChanged { false };
|
||||||
|
|
||||||
|
mutable ReadWriteLockable _avatarGrabsLock;
|
||||||
|
AvatarGrabDataMap _avatarGrabData;
|
||||||
|
bool _avatarGrabDataChanged { false }; // by network
|
||||||
|
AvatarGrabIDs _changedAvatarGrabs; // updated grab IDs -- changes needed to entities or physics
|
||||||
|
AvatarGrabIDs _deletedAvatarGrabs; // deleted grab IDs -- changes needed to entities or physics
|
||||||
|
|
||||||
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
|
// used to transform any sensor into world space, including the _hmdSensorMat, or hand controllers.
|
||||||
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
|
ThreadSafeValueCache<glm::mat4> _sensorToWorldMatrixCache { glm::mat4() };
|
||||||
ThreadSafeValueCache<glm::mat4> _controllerLeftHandMatrixCache { glm::mat4() };
|
ThreadSafeValueCache<glm::mat4> _controllerLeftHandMatrixCache { glm::mat4() };
|
||||||
|
@ -1511,6 +1530,9 @@ protected:
|
||||||
f(index);
|
f(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool updateAvatarGrabData(const QUuid& grabID, const QByteArray& grabData);
|
||||||
|
void clearAvatarGrabData(const QUuid& grabID);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
||||||
static QUrl _defaultFullAvatarModelUrl;
|
static QUrl _defaultFullAvatarModelUrl;
|
||||||
|
@ -1614,6 +1636,7 @@ QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEnt
|
||||||
void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value);
|
void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap& value);
|
||||||
|
|
||||||
// faux joint indexes (-1 means invalid)
|
// faux joint indexes (-1 means invalid)
|
||||||
|
const int NO_JOINT_INDEX = 65535; // -1
|
||||||
const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; // -2
|
const int SENSOR_TO_WORLD_MATRIX_INDEX = 65534; // -2
|
||||||
const int CONTROLLER_RIGHTHAND_INDEX = 65533; // -3
|
const int CONTROLLER_RIGHTHAND_INDEX = 65533; // -3
|
||||||
const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4
|
const int CONTROLLER_LEFTHAND_INDEX = 65532; // -4
|
||||||
|
@ -1626,5 +1649,6 @@ const int FARGRAB_MOUSE_INDEX = 65526; // -10
|
||||||
|
|
||||||
const int LOWEST_PSEUDO_JOINT_INDEX = 65526;
|
const int LOWEST_PSEUDO_JOINT_INDEX = 65526;
|
||||||
|
|
||||||
|
const int MAX_NUM_AVATAR_GRABS = 6;
|
||||||
|
|
||||||
#endif // hifi_AvatarData_h
|
#endif // hifi_AvatarData_h
|
||||||
|
|
|
@ -24,6 +24,7 @@ namespace AvatarTraits {
|
||||||
SkeletonModelURL,
|
SkeletonModelURL,
|
||||||
FirstInstancedTrait,
|
FirstInstancedTrait,
|
||||||
AvatarEntity = FirstInstancedTrait,
|
AvatarEntity = FirstInstancedTrait,
|
||||||
|
Grab,
|
||||||
TotalTraitTypes
|
TotalTraitTypes
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#include <ScriptEngine.h>
|
#include <ScriptEngine.h>
|
||||||
#include <EntitySimulation.h>
|
#include <EntitySimulation.h>
|
||||||
#include <ZoneRenderer.h>
|
#include <ZoneRenderer.h>
|
||||||
|
#include <PhysicalEntitySimulation.h>
|
||||||
|
|
||||||
#include "EntitiesRendererLogging.h"
|
#include "EntitiesRendererLogging.h"
|
||||||
#include "RenderableEntityItem.h"
|
#include "RenderableEntityItem.h"
|
||||||
|
@ -1250,3 +1251,11 @@ void EntityTreeRenderer::onEntityChanged(const EntityItemID& id) {
|
||||||
_changedEntities.insert(id);
|
_changedEntities.insert(id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EntityEditPacketSender* EntityTreeRenderer::getPacketSender() {
|
||||||
|
EntityTreePointer tree = getTree();
|
||||||
|
EntitySimulationPointer simulation = tree ? tree->getSimulation() : nullptr;
|
||||||
|
PhysicalEntitySimulationPointer peSimulation = std::static_pointer_cast<PhysicalEntitySimulation>(simulation);
|
||||||
|
EntityEditPacketSender* packetSender = peSimulation ? peSimulation->getPacketSender() : nullptr;
|
||||||
|
return packetSender;
|
||||||
|
}
|
||||||
|
|
|
@ -120,6 +120,8 @@ public:
|
||||||
static void setGetAvatarUpOperator(std::function<glm::vec3()> getAvatarUpOperator) { _getAvatarUpOperator = getAvatarUpOperator; }
|
static void setGetAvatarUpOperator(std::function<glm::vec3()> getAvatarUpOperator) { _getAvatarUpOperator = getAvatarUpOperator; }
|
||||||
static glm::vec3 getAvatarUp() { return _getAvatarUpOperator(); }
|
static glm::vec3 getAvatarUp() { return _getAvatarUpOperator(); }
|
||||||
|
|
||||||
|
EntityEditPacketSender* getPacketSender();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void enterEntity(const EntityItemID& entityItemID);
|
void enterEntity(const EntityItemID& entityItemID);
|
||||||
void leaveEntity(const EntityItemID& entityItemID);
|
void leaveEntity(const EntityItemID& entityItemID);
|
||||||
|
|
|
@ -50,6 +50,8 @@ public:
|
||||||
const QUuid& getID() const { return _id; }
|
const QUuid& getID() const { return _id; }
|
||||||
EntityDynamicType getType() const { return _type; }
|
EntityDynamicType getType() const { return _type; }
|
||||||
|
|
||||||
|
virtual void removeFromOwner() { }
|
||||||
|
|
||||||
virtual void remapIDs(QHash<EntityItemID, EntityItemID>& map) = 0;
|
virtual void remapIDs(QHash<EntityItemID, EntityItemID>& map) = 0;
|
||||||
|
|
||||||
virtual bool isAction() const { return false; }
|
virtual bool isAction() const { return false; }
|
||||||
|
|
|
@ -31,6 +31,8 @@
|
||||||
#include <SharedUtil.h> // usecTimestampNow()
|
#include <SharedUtil.h> // usecTimestampNow()
|
||||||
#include <LogHandler.h>
|
#include <LogHandler.h>
|
||||||
#include <Extents.h>
|
#include <Extents.h>
|
||||||
|
#include <QVariantGLM.h>
|
||||||
|
#include <Grab.h>
|
||||||
|
|
||||||
#include "EntityScriptingInterface.h"
|
#include "EntityScriptingInterface.h"
|
||||||
#include "EntitiesLogging.h"
|
#include "EntitiesLogging.h"
|
||||||
|
@ -38,6 +40,7 @@
|
||||||
#include "EntitySimulation.h"
|
#include "EntitySimulation.h"
|
||||||
#include "EntityDynamicFactoryInterface.h"
|
#include "EntityDynamicFactoryInterface.h"
|
||||||
|
|
||||||
|
|
||||||
Q_DECLARE_METATYPE(EntityItemPointer);
|
Q_DECLARE_METATYPE(EntityItemPointer);
|
||||||
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
|
int entityItemPointernMetaTypeId = qRegisterMetaType<EntityItemPointer>();
|
||||||
|
|
||||||
|
@ -1647,7 +1650,10 @@ AACube EntityItem::getQueryAACube(bool& success) const {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityItem::shouldPuffQueryAACube() const {
|
bool EntityItem::shouldPuffQueryAACube() const {
|
||||||
return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent();
|
bool hasGrabs = _grabsLock.resultWithReadLock<bool>([&] {
|
||||||
|
return _grabs.count() > 0;
|
||||||
|
});
|
||||||
|
return hasActions() || isChildOfMyAvatar() || isMovingRelativeToParent() || hasGrabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: get rid of all users of this function...
|
// TODO: get rid of all users of this function...
|
||||||
|
@ -2455,13 +2461,22 @@ bool EntityItem::shouldSuppressLocationEdits() const {
|
||||||
QList<EntityDynamicPointer> EntityItem::getActionsOfType(EntityDynamicType typeToGet) const {
|
QList<EntityDynamicPointer> EntityItem::getActionsOfType(EntityDynamicType typeToGet) const {
|
||||||
QList<EntityDynamicPointer> result;
|
QList<EntityDynamicPointer> result;
|
||||||
|
|
||||||
QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
|
for (QHash<QUuid, EntityDynamicPointer>::const_iterator i = _objectActions.begin();
|
||||||
while (i != _objectActions.end()) {
|
i != _objectActions.end();
|
||||||
|
i++) {
|
||||||
|
EntityDynamicPointer action = i.value();
|
||||||
|
if (action->getType() == typeToGet && action->isActive()) {
|
||||||
|
result += action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (QHash<QUuid, EntityDynamicPointer>::const_iterator i = _grabActions.begin();
|
||||||
|
i != _grabActions.end();
|
||||||
|
i++) {
|
||||||
EntityDynamicPointer action = i.value();
|
EntityDynamicPointer action = i.value();
|
||||||
if (action->getType() == typeToGet && action->isActive()) {
|
if (action->getType() == typeToGet && action->isActive()) {
|
||||||
result += action;
|
result += action;
|
||||||
}
|
}
|
||||||
i++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
@ -3282,3 +3297,64 @@ void EntityItem::prepareForSimulationOwnershipBid(EntityItemProperties& properti
|
||||||
bool EntityItem::isWearable() const {
|
bool EntityItem::isWearable() const {
|
||||||
return isVisible() && (getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || getParentID() == AVATAR_SELF_ID);
|
return isVisible() && (getParentID() == DependencyManager::get<NodeList>()->getSessionUUID() || getParentID() == AVATAR_SELF_ID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityItem::addGrab(GrabPointer grab) {
|
||||||
|
SpatiallyNestable::addGrab(grab);
|
||||||
|
|
||||||
|
if (getDynamic()) {
|
||||||
|
EntityTreePointer entityTree = getTree();
|
||||||
|
assert(entityTree);
|
||||||
|
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||||
|
assert(simulation);
|
||||||
|
|
||||||
|
auto actionFactory = DependencyManager::get<EntityDynamicFactoryInterface>();
|
||||||
|
QUuid actionID = QUuid::createUuid();
|
||||||
|
|
||||||
|
EntityDynamicType dynamicType;
|
||||||
|
QVariantMap arguments;
|
||||||
|
int grabParentJointIndex =grab->getParentJointIndex();
|
||||||
|
if (grabParentJointIndex == FARGRAB_RIGHTHAND_INDEX || grabParentJointIndex == FARGRAB_LEFTHAND_INDEX) {
|
||||||
|
// add a far-grab action
|
||||||
|
dynamicType = DYNAMIC_TYPE_FAR_GRAB;
|
||||||
|
arguments["otherID"] = grab->getOwnerID();
|
||||||
|
arguments["otherJointIndex"] = grabParentJointIndex;
|
||||||
|
arguments["targetPosition"] = vec3ToQMap(grab->getPositionalOffset());
|
||||||
|
arguments["targetRotation"] = quatToQMap(grab->getRotationalOffset());
|
||||||
|
arguments["linearTimeScale"] = 0.05;
|
||||||
|
arguments["angularTimeScale"] = 0.05;
|
||||||
|
} else {
|
||||||
|
// add a near-grab action
|
||||||
|
dynamicType = DYNAMIC_TYPE_HOLD;
|
||||||
|
arguments["holderID"] = grab->getOwnerID();
|
||||||
|
arguments["hand"] = grab->getHand();
|
||||||
|
arguments["timeScale"] = 0.05;
|
||||||
|
arguments["relativePosition"] = vec3ToQMap(grab->getPositionalOffset());
|
||||||
|
arguments["relativeRotation"] = quatToQMap(grab->getRotationalOffset());
|
||||||
|
arguments["kinematic"] = _grabProperties.getGrabKinematic();
|
||||||
|
arguments["kinematicSetVelocity"] = true;
|
||||||
|
arguments["ignoreIK"] = _grabProperties.getGrabFollowsController();
|
||||||
|
}
|
||||||
|
EntityDynamicPointer action = actionFactory->factory(dynamicType, actionID, getThisPointer(), arguments);
|
||||||
|
grab->setActionID(actionID);
|
||||||
|
_grabActions[actionID] = action;
|
||||||
|
simulation->addDynamic(action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityItem::removeGrab(GrabPointer grab) {
|
||||||
|
SpatiallyNestable::removeGrab(grab);
|
||||||
|
|
||||||
|
QUuid actionID = grab->getActionID();
|
||||||
|
if (!actionID.isNull()) {
|
||||||
|
EntityDynamicPointer action = _grabActions.value(actionID);
|
||||||
|
if (action) {
|
||||||
|
_grabActions.remove(actionID);
|
||||||
|
EntityTreePointer entityTree = getTree();
|
||||||
|
EntitySimulationPointer simulation = entityTree ? entityTree->getSimulation() : nullptr;
|
||||||
|
if (simulation) {
|
||||||
|
action->removeFromSimulation(simulation);
|
||||||
|
action->removeFromOwner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -51,7 +51,6 @@ typedef std::shared_ptr<EntityDynamicInterface> EntityDynamicPointer;
|
||||||
typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
|
typedef std::shared_ptr<EntityTreeElement> EntityTreeElementPointer;
|
||||||
using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr<EntityTreeElementExtraEncodeData>;
|
using EntityTreeElementExtraEncodeDataPointer = std::shared_ptr<EntityTreeElementExtraEncodeData>;
|
||||||
|
|
||||||
|
|
||||||
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
|
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
|
||||||
#define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() override { };
|
#define ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() override { };
|
||||||
|
|
||||||
|
@ -554,6 +553,9 @@ public:
|
||||||
|
|
||||||
void prepareForSimulationOwnershipBid(EntityItemProperties& properties, uint64_t now, uint8_t priority);
|
void prepareForSimulationOwnershipBid(EntityItemProperties& properties, uint64_t now, uint8_t priority);
|
||||||
|
|
||||||
|
virtual void addGrab(GrabPointer grab) override;
|
||||||
|
virtual void removeGrab(GrabPointer grab) override;
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void requestRenderUpdate();
|
void requestRenderUpdate();
|
||||||
void spaceUpdate(std::pair<int32_t, glm::vec4> data);
|
void spaceUpdate(std::pair<int32_t, glm::vec4> data);
|
||||||
|
@ -734,6 +736,8 @@ protected:
|
||||||
|
|
||||||
GrabPropertyGroup _grabProperties;
|
GrabPropertyGroup _grabProperties;
|
||||||
|
|
||||||
|
QHash<QUuid, EntityDynamicPointer> _grabActions;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
|
std::unordered_map<std::string, graphics::MultiMaterial> _materials;
|
||||||
std::mutex _materialsLock;
|
std::mutex _materialsLock;
|
||||||
|
|
|
@ -2419,6 +2419,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
|
||||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController);
|
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController);
|
||||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable);
|
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable);
|
||||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable);
|
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable);
|
||||||
|
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_DELEGATE_TO_PARENT, Grab, grab, GrabDelegateToParent, grabDelegateToParent);
|
||||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab,
|
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab,
|
||||||
EquippableLeftPosition, equippableLeftPosition);
|
EquippableLeftPosition, equippableLeftPosition);
|
||||||
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab,
|
ADD_GROUP_PROPERTY_TO_MAP(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab,
|
||||||
|
|
|
@ -385,6 +385,8 @@ public:
|
||||||
|
|
||||||
void setQueryAACubeDirty() { _queryAACubeChanged = true; }
|
void setQueryAACubeDirty() { _queryAACubeChanged = true; }
|
||||||
|
|
||||||
|
void setLocationDirty() { _positionChanged = true; _rotationChanged = true; }
|
||||||
|
|
||||||
void setCreated(QDateTime& v);
|
void setCreated(QDateTime& v);
|
||||||
|
|
||||||
bool hasTransformOrVelocityChanges() const;
|
bool hasTransformOrVelocityChanges() const;
|
||||||
|
|
|
@ -190,6 +190,7 @@ QDebug& operator<<(QDebug& dbg, const EntityPropertyFlags& f) {
|
||||||
result = f.getHasProperty(PROP_GRAB_FOLLOWS_CONTROLLER) ? result + "grab.FollowsController " : result;
|
result = f.getHasProperty(PROP_GRAB_FOLLOWS_CONTROLLER) ? result + "grab.FollowsController " : result;
|
||||||
result = f.getHasProperty(PROP_GRAB_TRIGGERABLE) ? result + "grab.Triggerable " : result;
|
result = f.getHasProperty(PROP_GRAB_TRIGGERABLE) ? result + "grab.Triggerable " : result;
|
||||||
result = f.getHasProperty(PROP_GRAB_EQUIPPABLE) ? result + "grab.Equippable " : result;
|
result = f.getHasProperty(PROP_GRAB_EQUIPPABLE) ? result + "grab.Equippable " : result;
|
||||||
|
result = f.getHasProperty(PROP_GRAB_DELEGATE_TO_PARENT) ? result + "grab.GrabDelegateToParent " : result;
|
||||||
result =
|
result =
|
||||||
f.getHasProperty(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET) ? result + "grab.LeftEquippablePositionOffset " : result;
|
f.getHasProperty(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET) ? result + "grab.LeftEquippablePositionOffset " : result;
|
||||||
result =
|
result =
|
||||||
|
|
|
@ -267,6 +267,7 @@ enum EntityPropertyList {
|
||||||
PROP_GRAB_FOLLOWS_CONTROLLER,
|
PROP_GRAB_FOLLOWS_CONTROLLER,
|
||||||
PROP_GRAB_TRIGGERABLE,
|
PROP_GRAB_TRIGGERABLE,
|
||||||
PROP_GRAB_EQUIPPABLE,
|
PROP_GRAB_EQUIPPABLE,
|
||||||
|
PROP_GRAB_DELEGATE_TO_PARENT,
|
||||||
PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET,
|
PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET,
|
||||||
PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET,
|
PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET,
|
||||||
PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET,
|
PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET,
|
||||||
|
|
|
@ -2268,3 +2268,126 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 EntityScriptingInterface::worldToLocalPosition(glm::vec3 worldPosition, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::vec3 localPosition = SpatiallyNestable::worldToLocal(worldPosition, parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return localPosition;
|
||||||
|
} else {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::quat EntityScriptingInterface::worldToLocalRotation(glm::quat worldRotation, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::quat localRotation = SpatiallyNestable::worldToLocal(worldRotation, parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return localRotation;
|
||||||
|
} else {
|
||||||
|
return glm::quat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 EntityScriptingInterface::worldToLocalVelocity(glm::vec3 worldVelocity, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::vec3 localVelocity = SpatiallyNestable::worldToLocalVelocity(worldVelocity, parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return localVelocity;
|
||||||
|
} else {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 EntityScriptingInterface::worldToLocalAngularVelocity(glm::vec3 worldAngularVelocity, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::vec3 localAngularVelocity = SpatiallyNestable::worldToLocalAngularVelocity(worldAngularVelocity, parentID,
|
||||||
|
parentJointIndex, scalesWithParent,
|
||||||
|
success);
|
||||||
|
if (success) {
|
||||||
|
return localAngularVelocity;
|
||||||
|
} else {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 EntityScriptingInterface::worldToLocalDimensions(glm::vec3 worldDimensions, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
|
||||||
|
bool success;
|
||||||
|
glm::vec3 localDimensions = SpatiallyNestable::worldToLocalDimensions(worldDimensions, parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return localDimensions;
|
||||||
|
} else {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 EntityScriptingInterface::localToWorldPosition(glm::vec3 localPosition, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::vec3 worldPosition = SpatiallyNestable::localToWorld(localPosition, parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return worldPosition;
|
||||||
|
} else {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::quat EntityScriptingInterface::localToWorldRotation(glm::quat localRotation, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::quat worldRotation = SpatiallyNestable::localToWorld(localRotation, parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return worldRotation;
|
||||||
|
} else {
|
||||||
|
return glm::quat();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 EntityScriptingInterface::localToWorldVelocity(glm::vec3 localVelocity, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::vec3 worldVelocity = SpatiallyNestable::localToWorldVelocity(localVelocity, parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return worldVelocity;
|
||||||
|
} else {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 EntityScriptingInterface::localToWorldAngularVelocity(glm::vec3 localAngularVelocity, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::vec3 worldAngularVelocity = SpatiallyNestable::localToWorldAngularVelocity(localAngularVelocity,
|
||||||
|
parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return worldAngularVelocity;
|
||||||
|
} else {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 EntityScriptingInterface::localToWorldDimensions(glm::vec3 localDimensions, const QUuid& parentID,
|
||||||
|
int parentJointIndex, bool scalesWithParent) {
|
||||||
|
bool success;
|
||||||
|
glm::vec3 worldDimensions = SpatiallyNestable::localToWorldDimensions(localDimensions, parentID, parentJointIndex,
|
||||||
|
scalesWithParent, success);
|
||||||
|
if (success) {
|
||||||
|
return worldDimensions;
|
||||||
|
} else {
|
||||||
|
return glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1573,6 +1573,109 @@ public slots:
|
||||||
* print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */
|
* print("Scale: " + JSON.stringify(Mat4.extractScale(transform))); // { x: 1, y: 1, z: 1 } */
|
||||||
Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID);
|
Q_INVOKABLE glm::mat4 getEntityLocalTransform(const QUuid& entityID);
|
||||||
|
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.worldToLocalPosition
|
||||||
|
* @param {Vec3} worldPosition
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Vec3}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 worldToLocalPosition(glm::vec3 worldPosition, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.worldToLocalRotation
|
||||||
|
* @param {Quat} worldRotation
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Quat}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::quat worldToLocalRotation(glm::quat worldRotation, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.worldToLocalVelocity
|
||||||
|
* @param {Vec3} worldVelocity
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Vec3}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 worldToLocalVelocity(glm::vec3 worldVelocity, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.worldToLocalAngularVelocity
|
||||||
|
* @param {Vec3} worldAngularVelocity
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Vec3}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 worldToLocalAngularVelocity(glm::vec3 worldAngularVelocity, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.worldToLocalDimensions
|
||||||
|
* @param {Vec3} worldDimensions
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Vec3}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 worldToLocalDimensions(glm::vec3 worldDimensions, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.localToWorldPosition
|
||||||
|
* @param {Vec3} localPosition
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Vec3}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 localToWorldPosition(glm::vec3 localPosition, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.localToWorldRotation
|
||||||
|
* @param {Quat} localRotation
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Quat}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::quat localToWorldRotation(glm::quat localRotation, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.localToWorldVelocity
|
||||||
|
* @param {Vec3} localVelocity
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Vec3}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 localToWorldVelocity(glm::vec3 localVelocity, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.localToWorldAngularVelocity
|
||||||
|
* @param {Vec3} localAngularVelocity
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Vec3}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 localToWorldAngularVelocity(glm::vec3 localAngularVelocity, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
/**jsdoc
|
||||||
|
* @function Entities.localToWorldDimensions
|
||||||
|
* @param {Vec3} localDimensions
|
||||||
|
* @param {Uuid} parentID
|
||||||
|
* @param {number} parentJointIndex
|
||||||
|
* @param {boolean} scalesWithparent
|
||||||
|
* @returns {Vec3}
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 localToWorldDimensions(glm::vec3 localDimensions, const QUuid& parentID,
|
||||||
|
int parentJointIndex = -1, bool scalesWithParent = false);
|
||||||
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Get the static certificate for an entity. The static certificate contains static properties of the item which cannot
|
* Get the static certificate for an entity. The static certificate contains static properties of the item which cannot
|
||||||
* be altered.
|
* be altered.
|
||||||
|
|
|
@ -2586,6 +2586,8 @@ void convertGrabUserDataToProperties(EntityItemProperties& properties) {
|
||||||
grabProperties.setEquippable(equippable.toBool());
|
grabProperties.setEquippable(equippable.toBool());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
grabProperties.setGrabDelegateToParent(true);
|
||||||
|
|
||||||
if (grabbableKey["spatialKey"].isObject()) {
|
if (grabbableKey["spatialKey"].isObject()) {
|
||||||
QJsonObject spatialKey = grabbableKey["spatialKey"].toObject();
|
QJsonObject spatialKey = grabbableKey["spatialKey"].toObject();
|
||||||
grabProperties.setEquippable(true);
|
grabProperties.setEquippable(true);
|
||||||
|
@ -2956,3 +2958,52 @@ bool EntityTree::removeMaterialFromOverlay(const QUuid& overlayID, graphics::Mat
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void EntityTree::updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
||||||
|
MovingEntitiesOperator& moveOperator, bool force, bool tellServer) {
|
||||||
|
// if the queryBox has changed, tell the entity-server
|
||||||
|
if (object->getNestableType() == NestableType::Entity && object->updateQueryAACube()) {
|
||||||
|
EntityItemPointer entity = std::static_pointer_cast<EntityItem>(object);
|
||||||
|
if (entity->updateQueryAACube() || force) {
|
||||||
|
bool success;
|
||||||
|
AACube newCube = entity->getQueryAACube(success);
|
||||||
|
if (success) {
|
||||||
|
moveOperator.addEntityToMoveList(entity, newCube);
|
||||||
|
}
|
||||||
|
// send an edit packet to update the entity-server about the queryAABox
|
||||||
|
// unless it is client-only
|
||||||
|
if (tellServer && packetSender && entity->isDomainEntity()) {
|
||||||
|
quint64 now = usecTimestampNow();
|
||||||
|
EntityItemProperties properties = entity->getProperties();
|
||||||
|
properties.setQueryAACubeDirty();
|
||||||
|
properties.setLocationDirty();
|
||||||
|
properties.setLastEdited(now);
|
||||||
|
|
||||||
|
packetSender->queueEditEntityMessage(PacketType::EntityEdit, getThisPointer(), entity->getID(), properties);
|
||||||
|
entity->setLastBroadcast(now); // for debug/physics status icons
|
||||||
|
}
|
||||||
|
|
||||||
|
entity->markDirtyFlags(Simulation::DIRTY_POSITION);
|
||||||
|
entityChanged(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
object->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||||
|
updateEntityQueryAACubeWorker(descendant, packetSender, moveOperator, force, tellServer);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void EntityTree::updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
||||||
|
bool force, bool tellServer) {
|
||||||
|
// This is used when something other than a script or physics moves an entity. We need to put it in the
|
||||||
|
// correct place in our local octree, update its and its children's queryAACubes, and send an edit
|
||||||
|
// packet to the entity-server.
|
||||||
|
MovingEntitiesOperator moveOperator;
|
||||||
|
|
||||||
|
updateEntityQueryAACubeWorker(object, packetSender, moveOperator, force, tellServer);
|
||||||
|
|
||||||
|
if (moveOperator.hasMovingEntities()) {
|
||||||
|
PerformanceTimer perfTimer("recurseTreeWithOperator");
|
||||||
|
recurseTreeWithOperator(&moveOperator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -277,6 +277,9 @@ public:
|
||||||
|
|
||||||
std::map<QString, QString> getNamedPaths() const { return _namedPaths; }
|
std::map<QString, QString> getNamedPaths() const { return _namedPaths; }
|
||||||
|
|
||||||
|
void updateEntityQueryAACube(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
||||||
|
bool force, bool tellServer);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void deletingEntity(const EntityItemID& entityID);
|
void deletingEntity(const EntityItemID& entityID);
|
||||||
void deletingEntityPointer(EntityItem* entityID);
|
void deletingEntityPointer(EntityItem* entityID);
|
||||||
|
@ -392,6 +395,9 @@ private:
|
||||||
bool _serverlessDomain { false };
|
bool _serverlessDomain { false };
|
||||||
|
|
||||||
std::map<QString, QString> _namedPaths;
|
std::map<QString, QString> _namedPaths;
|
||||||
|
|
||||||
|
void updateEntityQueryAACubeWorker(SpatiallyNestablePointer object, EntityEditPacketSender* packetSender,
|
||||||
|
MovingEntitiesOperator& moveOperator, bool force, bool tellServer);
|
||||||
};
|
};
|
||||||
|
|
||||||
void convertGrabUserDataToProperties(EntityItemProperties& properties);
|
void convertGrabUserDataToProperties(EntityItemProperties& properties);
|
||||||
|
|
|
@ -24,6 +24,8 @@ void GrabPropertyGroup::copyToScriptValue(const EntityPropertyFlags& desiredProp
|
||||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController);
|
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_FOLLOWS_CONTROLLER, Grab, grab, GrabFollowsController, grabFollowsController);
|
||||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable);
|
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_TRIGGERABLE, Grab, grab, Triggerable, triggerable);
|
||||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable);
|
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_EQUIPPABLE, Grab, grab, Equippable, equippable);
|
||||||
|
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_DELEGATE_TO_PARENT, Grab, grab,
|
||||||
|
GrabDelegateToParent, grabDelegateToParent);
|
||||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab,
|
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, Grab, grab,
|
||||||
EquippableLeftPosition, equippableLeftPosition);
|
EquippableLeftPosition, equippableLeftPosition);
|
||||||
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab,
|
COPY_GROUP_PROPERTY_TO_QSCRIPTVALUE(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, Grab, grab,
|
||||||
|
@ -141,6 +143,7 @@ bool GrabPropertyGroup::appendToEditPacket(OctreePacketData* packetData,
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, getGrabFollowsController());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, getGrabFollowsController());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, getTriggerable());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, getTriggerable());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, getEquippable());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, getEquippable());
|
||||||
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, getGrabDelegateToParent());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, getEquippableLeftPosition());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, getEquippableLeftPosition());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, getEquippableLeftRotation());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, getEquippableLeftRotation());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, getEquippableRightPosition());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, getEquippableRightPosition());
|
||||||
|
@ -164,6 +167,7 @@ bool GrabPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags,
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, bool, setGrabFollowsController);
|
READ_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, bool, setGrabFollowsController);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, bool, setTriggerable);
|
READ_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, bool, setTriggerable);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, bool, setEquippable);
|
READ_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, bool, setEquippable);
|
||||||
|
READ_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, bool, setGrabDelegateToParent);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableLeftPosition);
|
READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableLeftPosition);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, glm::quat, setEquippableLeftRotation);
|
READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, glm::quat, setEquippableLeftRotation);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableRightPosition);
|
READ_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableRightPosition);
|
||||||
|
@ -177,6 +181,7 @@ bool GrabPropertyGroup::decodeFromEditPacket(EntityPropertyFlags& propertyFlags,
|
||||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_FOLLOWS_CONTROLLER, GrabFollowsController);
|
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_FOLLOWS_CONTROLLER, GrabFollowsController);
|
||||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_TRIGGERABLE, Triggerable);
|
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_TRIGGERABLE, Triggerable);
|
||||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_EQUIPPABLE, Equippable);
|
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_EQUIPPABLE, Equippable);
|
||||||
|
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_DELEGATE_TO_PARENT, GrabDelegateToParent);
|
||||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, EquippableLeftPosition);
|
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, EquippableLeftPosition);
|
||||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, EquippableLeftRotation);
|
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, EquippableLeftRotation);
|
||||||
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, EquippableRightPosition);
|
DECODE_GROUP_PROPERTY_HAS_CHANGED(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, EquippableRightPosition);
|
||||||
|
@ -215,6 +220,7 @@ EntityPropertyFlags GrabPropertyGroup::getChangedProperties() const {
|
||||||
CHECK_PROPERTY_CHANGE(PROP_GRAB_FOLLOWS_CONTROLLER, grabFollowsController);
|
CHECK_PROPERTY_CHANGE(PROP_GRAB_FOLLOWS_CONTROLLER, grabFollowsController);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_GRAB_TRIGGERABLE, triggerable);
|
CHECK_PROPERTY_CHANGE(PROP_GRAB_TRIGGERABLE, triggerable);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_GRAB_EQUIPPABLE, equippable);
|
CHECK_PROPERTY_CHANGE(PROP_GRAB_EQUIPPABLE, equippable);
|
||||||
|
CHECK_PROPERTY_CHANGE(PROP_GRAB_DELEGATE_TO_PARENT, grabDelegateToParent);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, equippableLeftPosition);
|
CHECK_PROPERTY_CHANGE(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, equippableLeftPosition);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, equippableLeftRotation);
|
CHECK_PROPERTY_CHANGE(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, equippableLeftRotation);
|
||||||
CHECK_PROPERTY_CHANGE(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, equippableRightPosition);
|
CHECK_PROPERTY_CHANGE(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, equippableRightPosition);
|
||||||
|
@ -273,6 +279,7 @@ EntityPropertyFlags GrabPropertyGroup::getEntityProperties(EncodeBitstreamParams
|
||||||
requestedProperties += PROP_GRAB_FOLLOWS_CONTROLLER;
|
requestedProperties += PROP_GRAB_FOLLOWS_CONTROLLER;
|
||||||
requestedProperties += PROP_GRAB_TRIGGERABLE;
|
requestedProperties += PROP_GRAB_TRIGGERABLE;
|
||||||
requestedProperties += PROP_GRAB_EQUIPPABLE;
|
requestedProperties += PROP_GRAB_EQUIPPABLE;
|
||||||
|
requestedProperties += PROP_GRAB_DELEGATE_TO_PARENT;
|
||||||
requestedProperties += PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET;
|
requestedProperties += PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET;
|
||||||
requestedProperties += PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET;
|
requestedProperties += PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET;
|
||||||
requestedProperties += PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET;
|
requestedProperties += PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET;
|
||||||
|
@ -299,6 +306,7 @@ void GrabPropertyGroup::appendSubclassData(OctreePacketData* packetData, EncodeB
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, getGrabFollowsController());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, getGrabFollowsController());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, getTriggerable());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, getTriggerable());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, getEquippable());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, getEquippable());
|
||||||
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, getGrabDelegateToParent());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, getEquippableLeftPosition());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, getEquippableLeftPosition());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, getEquippableLeftRotation());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, getEquippableLeftRotation());
|
||||||
APPEND_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, getEquippableRightPosition());
|
APPEND_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, getEquippableRightPosition());
|
||||||
|
@ -321,6 +329,7 @@ int GrabPropertyGroup::readEntitySubclassDataFromBuffer(const unsigned char* dat
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, bool, setGrabFollowsController);
|
READ_ENTITY_PROPERTY(PROP_GRAB_FOLLOWS_CONTROLLER, bool, setGrabFollowsController);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, bool, setTriggerable);
|
READ_ENTITY_PROPERTY(PROP_GRAB_TRIGGERABLE, bool, setTriggerable);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, bool, setEquippable);
|
READ_ENTITY_PROPERTY(PROP_GRAB_EQUIPPABLE, bool, setEquippable);
|
||||||
|
READ_ENTITY_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, bool, setGrabDelegateToParent);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableLeftPosition);
|
READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableLeftPosition);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, glm::quat, setEquippableLeftRotation);
|
READ_ENTITY_PROPERTY(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, glm::quat, setEquippableLeftRotation);
|
||||||
READ_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableRightPosition);
|
READ_ENTITY_PROPERTY(PROP_GRAB_RIGHT_EQUIPPABLE_POSITION_OFFSET, glm::vec3, setEquippableRightPosition);
|
||||||
|
|
|
@ -31,6 +31,7 @@ static const bool INITIAL_KINEMATIC { true };
|
||||||
static const bool INITIAL_FOLLOWS_CONTROLLER { true };
|
static const bool INITIAL_FOLLOWS_CONTROLLER { true };
|
||||||
static const bool INITIAL_TRIGGERABLE { false };
|
static const bool INITIAL_TRIGGERABLE { false };
|
||||||
static const bool INITIAL_EQUIPPABLE { false };
|
static const bool INITIAL_EQUIPPABLE { false };
|
||||||
|
static const bool INITIAL_GRAB_DELEGATE_TO_PARENT { true };
|
||||||
static const glm::vec3 INITIAL_LEFT_EQUIPPABLE_POSITION { glm::vec3(0.0f) };
|
static const glm::vec3 INITIAL_LEFT_EQUIPPABLE_POSITION { glm::vec3(0.0f) };
|
||||||
static const glm::quat INITIAL_LEFT_EQUIPPABLE_ROTATION { glm::quat() };
|
static const glm::quat INITIAL_LEFT_EQUIPPABLE_ROTATION { glm::quat() };
|
||||||
static const glm::vec3 INITIAL_RIGHT_EQUIPPABLE_POSITION { glm::vec3(0.0f) };
|
static const glm::vec3 INITIAL_RIGHT_EQUIPPABLE_POSITION { glm::vec3(0.0f) };
|
||||||
|
@ -123,6 +124,8 @@ public:
|
||||||
INITIAL_FOLLOWS_CONTROLLER);
|
INITIAL_FOLLOWS_CONTROLLER);
|
||||||
DEFINE_PROPERTY(PROP_GRAB_TRIGGERABLE, Triggerable, triggerable, bool, INITIAL_TRIGGERABLE);
|
DEFINE_PROPERTY(PROP_GRAB_TRIGGERABLE, Triggerable, triggerable, bool, INITIAL_TRIGGERABLE);
|
||||||
DEFINE_PROPERTY(PROP_GRAB_EQUIPPABLE, Equippable, equippable, bool, INITIAL_EQUIPPABLE);
|
DEFINE_PROPERTY(PROP_GRAB_EQUIPPABLE, Equippable, equippable, bool, INITIAL_EQUIPPABLE);
|
||||||
|
DEFINE_PROPERTY(PROP_GRAB_DELEGATE_TO_PARENT, GrabDelegateToParent, grabDelegateToParent, bool,
|
||||||
|
INITIAL_GRAB_DELEGATE_TO_PARENT);
|
||||||
DEFINE_PROPERTY_REF(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, EquippableLeftPosition, equippableLeftPosition,
|
DEFINE_PROPERTY_REF(PROP_GRAB_LEFT_EQUIPPABLE_POSITION_OFFSET, EquippableLeftPosition, equippableLeftPosition,
|
||||||
glm::vec3, INITIAL_LEFT_EQUIPPABLE_POSITION);
|
glm::vec3, INITIAL_LEFT_EQUIPPABLE_POSITION);
|
||||||
DEFINE_PROPERTY_REF(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, EquippableLeftRotation, equippableLeftRotation,
|
DEFINE_PROPERTY_REF(PROP_GRAB_LEFT_EQUIPPABLE_ROTATION_OFFSET, EquippableLeftRotation, equippableLeftRotation,
|
||||||
|
|
|
@ -40,7 +40,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
||||||
case PacketType::AvatarData:
|
case PacketType::AvatarData:
|
||||||
case PacketType::BulkAvatarData:
|
case PacketType::BulkAvatarData:
|
||||||
case PacketType::KillAvatar:
|
case PacketType::KillAvatar:
|
||||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::JointTransScaled);
|
return static_cast<PacketVersion>(AvatarMixerPacketVersion::GrabTraits);
|
||||||
case PacketType::MessagesData:
|
case PacketType::MessagesData:
|
||||||
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
||||||
// ICE packets
|
// ICE packets
|
||||||
|
|
|
@ -303,7 +303,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
||||||
MigrateSkeletonURLToTraits,
|
MigrateSkeletonURLToTraits,
|
||||||
MigrateAvatarEntitiesToTraits,
|
MigrateAvatarEntitiesToTraits,
|
||||||
FarGrabJointsRedux,
|
FarGrabJointsRedux,
|
||||||
JointTransScaled
|
JointTransScaled,
|
||||||
|
GrabTraits
|
||||||
};
|
};
|
||||||
|
|
||||||
enum class DomainConnectRequestVersion : PacketVersion {
|
enum class DomainConnectRequestVersion : PacketVersion {
|
||||||
|
|
|
@ -47,22 +47,39 @@ ObjectActionTractor::~ObjectActionTractor() {
|
||||||
bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
|
bool ObjectActionTractor::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) {
|
float& linearTimeScale, float& angularTimeScale) {
|
||||||
bool success { true };
|
SpatiallyNestablePointer other = getOther();
|
||||||
EntityItemPointer other = std::dynamic_pointer_cast<EntityItem>(getOther());
|
return resultWithReadLock<bool>([&]{
|
||||||
withReadLock([&]{
|
|
||||||
linearTimeScale = _linearTimeScale;
|
linearTimeScale = _linearTimeScale;
|
||||||
angularTimeScale = _angularTimeScale;
|
angularTimeScale = _angularTimeScale;
|
||||||
|
|
||||||
if (!_otherID.isNull()) {
|
if (!_otherID.isNull()) {
|
||||||
if (other && other->isReadyToComputeShape()) {
|
bool otherIsReady { true };
|
||||||
rotation = _desiredRotationalTarget * other->getWorldOrientation();
|
if (other && other->getNestableType() == NestableType::Entity) {
|
||||||
position = other->getWorldOrientation() * _desiredPositionalTarget + other->getWorldPosition();
|
EntityItemPointer otherEntity = std::static_pointer_cast<EntityItem>(other);
|
||||||
|
otherIsReady = otherEntity->isReadyToComputeShape();
|
||||||
|
}
|
||||||
|
if (other && otherIsReady) {
|
||||||
|
bool success;
|
||||||
|
glm::vec3 otherWorldPosition = other->getWorldPosition(_otherJointIndex, success);
|
||||||
|
if (!success) {
|
||||||
|
linearTimeScale = FLT_MAX;
|
||||||
|
angularTimeScale = FLT_MAX;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
glm::quat otherWorldOrientation = other->getWorldOrientation(_otherJointIndex, success);
|
||||||
|
if (!success) {
|
||||||
|
linearTimeScale = FLT_MAX;
|
||||||
|
angularTimeScale = FLT_MAX;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
rotation = _desiredRotationalTarget * otherWorldOrientation;
|
||||||
|
position = otherWorldOrientation * _desiredPositionalTarget + otherWorldPosition;
|
||||||
} else {
|
} else {
|
||||||
// we should have an "other" but can't find it, or its collision shape isn't loaded,
|
// we should have an "other" but can't find it, or its collision shape isn't loaded,
|
||||||
// so disable the tractor.
|
// so disable the tractor.
|
||||||
linearTimeScale = FLT_MAX;
|
linearTimeScale = FLT_MAX;
|
||||||
angularTimeScale = FLT_MAX;
|
angularTimeScale = FLT_MAX;
|
||||||
success = false;
|
return false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
rotation = _desiredRotationalTarget;
|
rotation = _desiredRotationalTarget;
|
||||||
|
@ -70,8 +87,8 @@ bool ObjectActionTractor::getTarget(float deltaTimeStep, glm::quat& rotation, gl
|
||||||
}
|
}
|
||||||
linearVelocity = glm::vec3();
|
linearVelocity = glm::vec3();
|
||||||
angularVelocity = glm::vec3();
|
angularVelocity = glm::vec3();
|
||||||
|
return true;
|
||||||
});
|
});
|
||||||
return success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) {
|
bool ObjectActionTractor::prepareForTractorUpdate(btScalar deltaTimeStep) {
|
||||||
|
@ -239,7 +256,7 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) {
|
||||||
glm::quat rotationalTarget;
|
glm::quat rotationalTarget;
|
||||||
float angularTimeScale;
|
float angularTimeScale;
|
||||||
QUuid otherID;
|
QUuid otherID;
|
||||||
|
int otherJointIndex;
|
||||||
|
|
||||||
bool needUpdate = false;
|
bool needUpdate = false;
|
||||||
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
||||||
|
@ -274,18 +291,25 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) {
|
||||||
}
|
}
|
||||||
|
|
||||||
ok = true;
|
ok = true;
|
||||||
otherID = QUuid(EntityDynamicInterface::extractStringArgument("tractor action",
|
otherID = QUuid(EntityDynamicInterface::extractStringArgument("tractor action", arguments, "otherID", ok, false));
|
||||||
arguments, "otherID", ok, false));
|
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
otherID = _otherID;
|
otherID = _otherID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ok = true;
|
||||||
|
otherJointIndex = EntityDynamicInterface::extractIntegerArgument("tractor action", arguments,
|
||||||
|
"otherJointIndex", ok, false);
|
||||||
|
if (!ok) {
|
||||||
|
otherJointIndex = _otherJointIndex;
|
||||||
|
}
|
||||||
|
|
||||||
if (somethingChanged ||
|
if (somethingChanged ||
|
||||||
positionalTarget != _desiredPositionalTarget ||
|
positionalTarget != _desiredPositionalTarget ||
|
||||||
linearTimeScale != _linearTimeScale ||
|
linearTimeScale != _linearTimeScale ||
|
||||||
rotationalTarget != _desiredRotationalTarget ||
|
rotationalTarget != _desiredRotationalTarget ||
|
||||||
angularTimeScale != _angularTimeScale ||
|
angularTimeScale != _angularTimeScale ||
|
||||||
otherID != _otherID) {
|
otherID != _otherID ||
|
||||||
|
otherJointIndex != _otherJointIndex) {
|
||||||
// something changed
|
// something changed
|
||||||
needUpdate = true;
|
needUpdate = true;
|
||||||
}
|
}
|
||||||
|
@ -298,6 +322,7 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) {
|
||||||
_desiredRotationalTarget = rotationalTarget;
|
_desiredRotationalTarget = rotationalTarget;
|
||||||
_angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
|
_angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
|
||||||
_otherID = otherID;
|
_otherID = otherID;
|
||||||
|
_otherJointIndex = otherJointIndex;
|
||||||
_active = true;
|
_active = true;
|
||||||
|
|
||||||
auto ownerEntity = _ownerEntity.lock();
|
auto ownerEntity = _ownerEntity.lock();
|
||||||
|
@ -322,6 +347,8 @@ bool ObjectActionTractor::updateArguments(QVariantMap arguments) {
|
||||||
* @property {Quat} targetRotation=0,0,0,1 - The target rotation.
|
* @property {Quat} targetRotation=0,0,0,1 - The target rotation.
|
||||||
* @property {Uuid} otherID=null - If an entity ID, the <code>targetPosition</code> and <code>targetRotation</code> are
|
* @property {Uuid} otherID=null - If an entity ID, the <code>targetPosition</code> and <code>targetRotation</code> are
|
||||||
* relative to this entity's position and rotation.
|
* relative to this entity's position and rotation.
|
||||||
|
* @property {Uuid} otherJointIndex=null - If an entity JointIndex, the <code>targetPosition</code> and
|
||||||
|
* <code>targetRotation</code> are relative to this entity's joint's position and rotation.
|
||||||
* @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the
|
* @property {number} linearTimeScale=3.4e+38 - Controls how long it takes for the entity's position to catch up with the
|
||||||
* target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action
|
* target position. The value is the time for the action to catch up to 1/e = 0.368 of the target value, where the action
|
||||||
* is applied using an exponential decay.
|
* is applied using an exponential decay.
|
||||||
|
@ -339,6 +366,7 @@ QVariantMap ObjectActionTractor::getArguments() {
|
||||||
arguments["angularTimeScale"] = _angularTimeScale;
|
arguments["angularTimeScale"] = _angularTimeScale;
|
||||||
|
|
||||||
arguments["otherID"] = _otherID;
|
arguments["otherID"] = _otherID;
|
||||||
|
arguments["otherJointIndex"] = _otherJointIndex;
|
||||||
});
|
});
|
||||||
return arguments;
|
return arguments;
|
||||||
}
|
}
|
||||||
|
@ -354,6 +382,7 @@ void ObjectActionTractor::serializeParameters(QDataStream& dataStream) const {
|
||||||
dataStream << localTimeToServerTime(_expires);
|
dataStream << localTimeToServerTime(_expires);
|
||||||
dataStream << _tag;
|
dataStream << _tag;
|
||||||
dataStream << _otherID;
|
dataStream << _otherID;
|
||||||
|
dataStream << _otherJointIndex;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -387,6 +416,7 @@ void ObjectActionTractor::deserializeParameters(QByteArray serializedArguments,
|
||||||
dataStream >> _tag;
|
dataStream >> _tag;
|
||||||
|
|
||||||
dataStream >> _otherID;
|
dataStream >> _otherID;
|
||||||
|
dataStream >> _otherJointIndex;
|
||||||
|
|
||||||
_active = true;
|
_active = true;
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,6 +66,7 @@ protected:
|
||||||
EntityItemID _otherID;
|
EntityItemID _otherID;
|
||||||
SpatiallyNestableWeakPointer _other;
|
SpatiallyNestableWeakPointer _other;
|
||||||
SpatiallyNestablePointer getOther();
|
SpatiallyNestablePointer getOther();
|
||||||
|
int _otherJointIndex { -1 };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
qint64 getEntityServerClockSkew() const;
|
qint64 getEntityServerClockSkew() const;
|
||||||
|
|
70
libraries/shared/src/Grab.cpp
Normal file
70
libraries/shared/src/Grab.cpp
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
//
|
||||||
|
// Grab.cpp
|
||||||
|
// libraries/avatars/src
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2018-9-1.
|
||||||
|
// Copyright 2018 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 "Grab.h"
|
||||||
|
|
||||||
|
QByteArray Grab::toByteArray() {
|
||||||
|
QByteArray ba;
|
||||||
|
QDataStream dataStream(&ba, QIODevice::WriteOnly);
|
||||||
|
const int dataEncodingVersion = 1;
|
||||||
|
dataStream << dataEncodingVersion << _ownerID << _targetID << _parentJointIndex
|
||||||
|
<< _hand << _positionalOffset << _rotationalOffset;
|
||||||
|
return ba;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Grab::fromByteArray(const QByteArray& grabData) {
|
||||||
|
QDataStream dataStream(grabData);
|
||||||
|
|
||||||
|
int dataEncodingVersion;
|
||||||
|
QUuid newOwnerID { QUuid() };
|
||||||
|
QUuid newTargetID { QUuid() };
|
||||||
|
int newParentJointIndex { -1 };
|
||||||
|
QString newHand { "none" };
|
||||||
|
glm::vec3 newPositionalOffset { glm::vec3(0.0f) };
|
||||||
|
glm::quat newRotationalOffset { glm::quat() };
|
||||||
|
|
||||||
|
dataStream >> dataEncodingVersion;
|
||||||
|
assert(dataEncodingVersion == 1);
|
||||||
|
dataStream >> newOwnerID;
|
||||||
|
dataStream >> newTargetID;
|
||||||
|
dataStream >> newParentJointIndex;
|
||||||
|
dataStream >> newHand;
|
||||||
|
dataStream >> newPositionalOffset;
|
||||||
|
dataStream >> newRotationalOffset;
|
||||||
|
|
||||||
|
bool somethingChanged { false };
|
||||||
|
if (_ownerID != newOwnerID) {
|
||||||
|
_ownerID = newOwnerID;
|
||||||
|
somethingChanged = true;
|
||||||
|
}
|
||||||
|
if (_targetID != newTargetID) {
|
||||||
|
_targetID = newTargetID;
|
||||||
|
somethingChanged = true;
|
||||||
|
}
|
||||||
|
if (_parentJointIndex != newParentJointIndex) {
|
||||||
|
_parentJointIndex = newParentJointIndex;
|
||||||
|
somethingChanged = true;
|
||||||
|
}
|
||||||
|
if (_hand != newHand) {
|
||||||
|
_hand = newHand;
|
||||||
|
somethingChanged = true;
|
||||||
|
}
|
||||||
|
if (_positionalOffset != newPositionalOffset) {
|
||||||
|
_positionalOffset = newPositionalOffset;
|
||||||
|
somethingChanged = true;
|
||||||
|
}
|
||||||
|
if (_rotationalOffset != newRotationalOffset) {
|
||||||
|
_rotationalOffset = newRotationalOffset;
|
||||||
|
somethingChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return somethingChanged;
|
||||||
|
}
|
99
libraries/shared/src/Grab.h
Normal file
99
libraries/shared/src/Grab.h
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
//
|
||||||
|
// Grab.h
|
||||||
|
// libraries/avatars/src
|
||||||
|
//
|
||||||
|
// Created by Seth Alves on 2018-9-1.
|
||||||
|
// Copyright 2018 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_Grab_h
|
||||||
|
#define hifi_Grab_h
|
||||||
|
|
||||||
|
#include <QUuid>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include "GLMHelpers.h"
|
||||||
|
#include "StreamUtils.h"
|
||||||
|
|
||||||
|
class Grab;
|
||||||
|
using GrabPointer = std::shared_ptr<Grab>;
|
||||||
|
using GrabWeakPointer = std::weak_ptr<Grab>;
|
||||||
|
|
||||||
|
class GrabLocationAccumulator {
|
||||||
|
public:
|
||||||
|
void accumulate(glm::vec3 position, glm::quat orientation) {
|
||||||
|
_position += position;
|
||||||
|
_orientation = orientation; // XXX
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 finalizePosition() { return count > 0 ? _position * (1.0f / count) : glm::vec3(0.0f); }
|
||||||
|
glm::quat finalizeOrientation() { return _orientation; } // XXX
|
||||||
|
|
||||||
|
protected:
|
||||||
|
glm::vec3 _position;
|
||||||
|
glm::quat _orientation;
|
||||||
|
int count { 0 };
|
||||||
|
};
|
||||||
|
|
||||||
|
class Grab {
|
||||||
|
public:
|
||||||
|
Grab() {};
|
||||||
|
Grab(const QUuid& newOwnerID, const QUuid& newTargetID, int newParentJointIndex, const QString& newHand,
|
||||||
|
glm::vec3 newPositionalOffset, glm::quat newRotationalOffset) :
|
||||||
|
_ownerID(newOwnerID),
|
||||||
|
_targetID(newTargetID),
|
||||||
|
_parentJointIndex(newParentJointIndex),
|
||||||
|
_hand(newHand),
|
||||||
|
_positionalOffset(newPositionalOffset),
|
||||||
|
_rotationalOffset(newRotationalOffset) {}
|
||||||
|
|
||||||
|
QByteArray toByteArray();
|
||||||
|
bool fromByteArray(const QByteArray& grabData);
|
||||||
|
|
||||||
|
Grab& operator=(const GrabPointer& other) {
|
||||||
|
_ownerID = other->_ownerID;
|
||||||
|
_targetID = other->_targetID;
|
||||||
|
_parentJointIndex = other->_parentJointIndex;
|
||||||
|
_hand = other->_hand;
|
||||||
|
_positionalOffset = other->_positionalOffset;
|
||||||
|
_rotationalOffset = other->_rotationalOffset;
|
||||||
|
_actionID = other->_actionID;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
QUuid getActionID() const { return _actionID; }
|
||||||
|
void setActionID(const QUuid& actionID) { _actionID = actionID; }
|
||||||
|
|
||||||
|
QUuid getOwnerID() const { return _ownerID; }
|
||||||
|
void setOwnerID(QUuid ownerID) { _ownerID = ownerID; }
|
||||||
|
|
||||||
|
QUuid getTargetID() const { return _targetID; }
|
||||||
|
void setTargetID(QUuid targetID) { _targetID = targetID; }
|
||||||
|
|
||||||
|
int getParentJointIndex() const { return _parentJointIndex; }
|
||||||
|
void setParentJointIndex(int parentJointIndex) { _parentJointIndex = parentJointIndex; }
|
||||||
|
|
||||||
|
QString getHand() const { return _hand; }
|
||||||
|
void setHand(QString hand) { _hand = hand; }
|
||||||
|
|
||||||
|
glm::vec3 getPositionalOffset() const { return _positionalOffset; }
|
||||||
|
void setPositionalOffset(glm::vec3 positionalOffset) { _positionalOffset = positionalOffset; }
|
||||||
|
|
||||||
|
glm::quat getRotationalOffset() const { return _rotationalOffset; }
|
||||||
|
void setRotationalOffset(glm::quat rotationalOffset) { _rotationalOffset = rotationalOffset; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
QUuid _actionID; // if an action is created in bullet for this grab, this is the ID
|
||||||
|
QUuid _ownerID; // avatar ID of grabber
|
||||||
|
QUuid _targetID; // SpatiallyNestable ID of grabbed
|
||||||
|
int _parentJointIndex { -1 }; // which avatar joint is being used to grab
|
||||||
|
QString _hand; // "left" or "right"
|
||||||
|
glm::vec3 _positionalOffset; // relative to joint
|
||||||
|
glm::quat _rotationalOffset; // relative to joint
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif // hifi_Grab_h
|
|
@ -1369,3 +1369,35 @@ bool SpatiallyNestable::isParentPathComplete(int depth) const {
|
||||||
|
|
||||||
return parent->isParentPathComplete(depth + 1);
|
return parent->isParentPathComplete(depth + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void SpatiallyNestable::addGrab(GrabPointer grab) {
|
||||||
|
_grabsLock.withWriteLock([&] {
|
||||||
|
_grabs.insert(grab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpatiallyNestable::removeGrab(GrabPointer grab) {
|
||||||
|
_grabsLock.withWriteLock([&] {
|
||||||
|
_grabs.remove(grab);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
QUuid SpatiallyNestable::getEditSenderID() {
|
||||||
|
// if more than one avatar is grabbing something, decide which one should tell the enity-server about it
|
||||||
|
QUuid editSenderID;
|
||||||
|
bool editSenderIDSet { false };
|
||||||
|
_grabsLock.withReadLock([&] {
|
||||||
|
foreach (const GrabPointer &grab, _grabs) {
|
||||||
|
QUuid ownerID = grab->getOwnerID();
|
||||||
|
if (!editSenderIDSet) {
|
||||||
|
editSenderID = ownerID;
|
||||||
|
editSenderIDSet = true;
|
||||||
|
} else {
|
||||||
|
if (ownerID < editSenderID) {
|
||||||
|
editSenderID = ownerID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return editSenderID;
|
||||||
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@
|
||||||
#include "AACube.h"
|
#include "AACube.h"
|
||||||
#include "SpatialParentFinder.h"
|
#include "SpatialParentFinder.h"
|
||||||
#include "shared/ReadWriteLockable.h"
|
#include "shared/ReadWriteLockable.h"
|
||||||
|
#include "Grab.h"
|
||||||
|
|
||||||
class SpatiallyNestable;
|
class SpatiallyNestable;
|
||||||
using SpatiallyNestableWeakPointer = std::weak_ptr<SpatiallyNestable>;
|
using SpatiallyNestableWeakPointer = std::weak_ptr<SpatiallyNestable>;
|
||||||
|
@ -213,6 +214,10 @@ public:
|
||||||
virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed
|
virtual void dimensionsChanged() { _queryAACubeSet = false; } // called when a this object's dimensions have changed
|
||||||
virtual void parentDeleted() { } // called on children of a deleted parent
|
virtual void parentDeleted() { } // called on children of a deleted parent
|
||||||
|
|
||||||
|
virtual void addGrab(GrabPointer grab);
|
||||||
|
virtual void removeGrab(GrabPointer grab);
|
||||||
|
virtual QUuid getEditSenderID();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
QUuid _id;
|
QUuid _id;
|
||||||
mutable SpatiallyNestableWeakPointer _parent;
|
mutable SpatiallyNestableWeakPointer _parent;
|
||||||
|
@ -232,6 +237,9 @@ protected:
|
||||||
quint64 _translationChanged { 0 };
|
quint64 _translationChanged { 0 };
|
||||||
quint64 _rotationChanged { 0 };
|
quint64 _rotationChanged { 0 };
|
||||||
|
|
||||||
|
mutable ReadWriteLockable _grabsLock;
|
||||||
|
QSet<GrabPointer> _grabs;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SpatiallyNestable() = delete;
|
SpatiallyNestable() = delete;
|
||||||
const NestableType _nestableType; // EntityItem or an AvatarData
|
const NestableType _nestableType; // EntityItem or an AvatarData
|
||||||
|
|
|
@ -40,7 +40,8 @@ function updateOverlay(entityID, queryAACube) {
|
||||||
blue: 255
|
blue: 255
|
||||||
},
|
},
|
||||||
alpha: 1,
|
alpha: 1,
|
||||||
solid: false
|
solid: false,
|
||||||
|
grabbable: false
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
521
scripts/system/controllers/controllerModules/farGrabEntity.js
Normal file
521
scripts/system/controllers/controllerModules/farGrabEntity.js
Normal file
|
@ -0,0 +1,521 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// farGrabEntity.js
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
/* jslint bitwise: true */
|
||||||
|
|
||||||
|
/* global Script, Controller, RIGHT_HAND, LEFT_HAND, Mat4, MyAvatar, Vec3, Quat, getEnabledModuleByName, makeRunningValues,
|
||||||
|
Entities, enableDispatcherModule, disableDispatcherModule, entityIsGrabbable, makeDispatcherModuleParameters, MSECS_PER_SEC,
|
||||||
|
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, TRIGGER_OFF_VALUE, TRIGGER_ON_VALUE, ZERO_VEC,
|
||||||
|
projectOntoEntityXYPlane, ContextOverlay, HMD, Picks, makeLaserLockInfo, makeLaserParams, AddressManager,
|
||||||
|
getEntityParents, Selection, DISPATCHER_HOVERING_LIST, unhighlightTargetEntity, Messages, findGroupParent,
|
||||||
|
worldPositionToRegistrationFrameMatrix, DISPATCHER_PROPERTIES
|
||||||
|
*/
|
||||||
|
|
||||||
|
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
|
Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
var MARGIN = 25;
|
||||||
|
|
||||||
|
function TargetObject(entityID, entityProps) {
|
||||||
|
this.entityID = entityID;
|
||||||
|
this.entityProps = entityProps;
|
||||||
|
this.targetEntityID = null;
|
||||||
|
this.targetEntityProps = null;
|
||||||
|
|
||||||
|
this.getTargetEntity = function() {
|
||||||
|
var parentPropsLength = this.parentProps.length;
|
||||||
|
if (parentPropsLength !== 0) {
|
||||||
|
var targetEntity = {
|
||||||
|
id: this.parentProps[parentPropsLength - 1].id,
|
||||||
|
props: this.parentProps[parentPropsLength - 1]};
|
||||||
|
this.targetEntityID = targetEntity.id;
|
||||||
|
this.targetEntityProps = targetEntity.props;
|
||||||
|
return targetEntity;
|
||||||
|
}
|
||||||
|
this.targetEntityID = this.entityID;
|
||||||
|
this.targetEntityProps = this.entityProps;
|
||||||
|
return {
|
||||||
|
id: this.entityID,
|
||||||
|
props: this.entityProps};
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function FarGrabEntity(hand) {
|
||||||
|
this.hand = hand;
|
||||||
|
this.targetEntityID = null;
|
||||||
|
this.targetObject = null;
|
||||||
|
this.previouslyUnhooked = {};
|
||||||
|
this.potentialEntityWithContextOverlay = false;
|
||||||
|
this.entityWithContextOverlay = false;
|
||||||
|
this.contextOverlayTimer = false;
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
this.reticleMinX = MARGIN;
|
||||||
|
this.reticleMaxX = 0;
|
||||||
|
this.reticleMinY = MARGIN;
|
||||||
|
this.reticleMaxY = 0;
|
||||||
|
this.lastUnexpectedChildrenCheckTime = 0;
|
||||||
|
|
||||||
|
var FAR_GRAB_JOINTS = [65527, 65528]; // FARGRAB_LEFTHAND_INDEX, FARGRAB_RIGHTHAND_INDEX
|
||||||
|
|
||||||
|
var DISTANCE_HOLDING_RADIUS_FACTOR = 3.5; // multiplied by distance between hand and object
|
||||||
|
var DISTANCE_HOLDING_ACTION_TIMEFRAME = 0.1; // how quickly objects move to their new position
|
||||||
|
var DISTANCE_HOLDING_UNITY_MASS = 1200; // The mass at which the distance holding action timeframe is unmodified
|
||||||
|
var DISTANCE_HOLDING_UNITY_DISTANCE = 6; // The distance at which the distance holding action timeframe is unmodified
|
||||||
|
|
||||||
|
this.parameters = makeDispatcherModuleParameters(
|
||||||
|
540,
|
||||||
|
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||||
|
[],
|
||||||
|
100,
|
||||||
|
makeLaserParams(this.hand, false));
|
||||||
|
|
||||||
|
|
||||||
|
this.handToController = function() {
|
||||||
|
return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.distanceGrabTimescale = function(mass, distance) {
|
||||||
|
var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass /
|
||||||
|
DISTANCE_HOLDING_UNITY_MASS * distance /
|
||||||
|
DISTANCE_HOLDING_UNITY_DISTANCE;
|
||||||
|
if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) {
|
||||||
|
timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME;
|
||||||
|
}
|
||||||
|
return timeScale;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getMass = function(dimensions, density) {
|
||||||
|
return (dimensions.x * dimensions.y * dimensions.z) * density;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.startFarGrabEntity = function (controllerData, targetProps) {
|
||||||
|
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||||
|
var worldControllerPosition = controllerLocation.position;
|
||||||
|
var worldControllerRotation = controllerLocation.orientation;
|
||||||
|
// transform the position into room space
|
||||||
|
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||||
|
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
|
||||||
|
|
||||||
|
var now = Date.now();
|
||||||
|
|
||||||
|
// add the action and initialize some variables
|
||||||
|
this.currentObjectPosition = targetProps.position;
|
||||||
|
this.currentObjectRotation = targetProps.rotation;
|
||||||
|
this.currentObjectTime = now;
|
||||||
|
|
||||||
|
this.grabRadius = this.grabbedDistance;
|
||||||
|
this.grabRadialVelocity = 0.0;
|
||||||
|
|
||||||
|
// offset between controller vector at the grab radius and the entity position
|
||||||
|
var targetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));
|
||||||
|
targetPosition = Vec3.sum(targetPosition, worldControllerPosition);
|
||||||
|
this.offsetPosition = Vec3.subtract(this.currentObjectPosition, targetPosition);
|
||||||
|
|
||||||
|
// compute a constant based on the initial conditions which we use below to exaggerate hand motion
|
||||||
|
// onto the held object
|
||||||
|
this.radiusScalar = Math.log(this.grabRadius + 1.0);
|
||||||
|
if (this.radiusScalar < 1.0) {
|
||||||
|
this.radiusScalar = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// compute the mass for the purpose of energy and how quickly to move object
|
||||||
|
this.mass = this.getMass(targetProps.dimensions, targetProps.density);
|
||||||
|
|
||||||
|
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||||
|
unhighlightTargetEntity(this.targetEntityID);
|
||||||
|
var message = {
|
||||||
|
hand: this.hand,
|
||||||
|
entityID: this.targetEntityID
|
||||||
|
};
|
||||||
|
|
||||||
|
Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
|
||||||
|
|
||||||
|
var newTargetPosLocal = MyAvatar.worldToJointPoint(targetProps.position);
|
||||||
|
MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal);
|
||||||
|
MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], { x: 0, y: 0, z: 0, w: 1 });
|
||||||
|
|
||||||
|
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||||
|
Entities.callEntityMethod(targetProps.id, "startNearGrab", args);
|
||||||
|
|
||||||
|
this.targetEntityID = targetProps.id;
|
||||||
|
|
||||||
|
|
||||||
|
if (this.grabID) {
|
||||||
|
MyAvatar.releaseGrab(this.grabID);
|
||||||
|
}
|
||||||
|
var farJointIndex = FAR_GRAB_JOINTS[this.hand];
|
||||||
|
this.grabID = MyAvatar.grab(targetProps.id, farJointIndex,
|
||||||
|
Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, farJointIndex),
|
||||||
|
Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, farJointIndex));
|
||||||
|
|
||||||
|
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||||
|
action: 'grab',
|
||||||
|
grabbedEntity: targetProps.id,
|
||||||
|
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||||
|
}));
|
||||||
|
this.grabbing = true;
|
||||||
|
|
||||||
|
this.previousRoomControllerPosition = roomControllerPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.continueDistanceHolding = function(controllerData) {
|
||||||
|
var controllerLocation = controllerData.controllerLocations[this.hand];
|
||||||
|
var worldControllerPosition = controllerLocation.position;
|
||||||
|
var worldControllerRotation = controllerLocation.orientation;
|
||||||
|
|
||||||
|
// also transform the position into room space
|
||||||
|
var worldToSensorMat = Mat4.inverse(MyAvatar.getSensorToWorldMatrix());
|
||||||
|
var roomControllerPosition = Mat4.transformPoint(worldToSensorMat, worldControllerPosition);
|
||||||
|
|
||||||
|
var targetProps = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
|
||||||
|
var now = Date.now();
|
||||||
|
var deltaObjectTime = (now - this.currentObjectTime) / MSECS_PER_SEC; // convert to seconds
|
||||||
|
this.currentObjectTime = now;
|
||||||
|
|
||||||
|
// the action was set up when this.distanceHolding was called. update the targets.
|
||||||
|
var radius = Vec3.distance(this.currentObjectPosition, worldControllerPosition) *
|
||||||
|
this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;
|
||||||
|
if (radius < 1.0) {
|
||||||
|
radius = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
var roomHandDelta = Vec3.subtract(roomControllerPosition, this.previousRoomControllerPosition);
|
||||||
|
var worldHandDelta = Mat4.transformVector(MyAvatar.getSensorToWorldMatrix(), roomHandDelta);
|
||||||
|
var handMoved = Vec3.multiply(worldHandDelta, radius);
|
||||||
|
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved);
|
||||||
|
|
||||||
|
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||||
|
Entities.callEntityMethod(this.targetEntityID, "continueDistanceGrab", args);
|
||||||
|
|
||||||
|
// Update radialVelocity
|
||||||
|
var lastVelocity = Vec3.multiply(worldHandDelta, 1.0 / deltaObjectTime);
|
||||||
|
var delta = Vec3.normalize(Vec3.subtract(targetProps.position, worldControllerPosition));
|
||||||
|
var newRadialVelocity = Vec3.dot(lastVelocity, delta);
|
||||||
|
|
||||||
|
var VELOCITY_AVERAGING_TIME = 0.016;
|
||||||
|
var blendFactor = deltaObjectTime / VELOCITY_AVERAGING_TIME;
|
||||||
|
if (blendFactor < 0.0) {
|
||||||
|
blendFactor = 0.0;
|
||||||
|
} else if (blendFactor > 1.0) {
|
||||||
|
blendFactor = 1.0;
|
||||||
|
}
|
||||||
|
this.grabRadialVelocity = blendFactor * newRadialVelocity + (1.0 - blendFactor) * this.grabRadialVelocity;
|
||||||
|
|
||||||
|
var RADIAL_GRAB_AMPLIFIER = 10.0;
|
||||||
|
if (Math.abs(this.grabRadialVelocity) > 0.0) {
|
||||||
|
this.grabRadius = this.grabRadius + (this.grabRadialVelocity * deltaObjectTime *
|
||||||
|
this.grabRadius * RADIAL_GRAB_AMPLIFIER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// don't let grabRadius go all the way to zero, because it can't come back from that
|
||||||
|
var MINIMUM_GRAB_RADIUS = 0.1;
|
||||||
|
if (this.grabRadius < MINIMUM_GRAB_RADIUS) {
|
||||||
|
this.grabRadius = MINIMUM_GRAB_RADIUS;
|
||||||
|
}
|
||||||
|
var newTargetPosition = Vec3.multiply(this.grabRadius, Quat.getUp(worldControllerRotation));
|
||||||
|
newTargetPosition = Vec3.sum(newTargetPosition, worldControllerPosition);
|
||||||
|
newTargetPosition = Vec3.sum(newTargetPosition, this.offsetPosition);
|
||||||
|
|
||||||
|
// MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], MyAvatar.worldToJointPoint(newTargetPosition));
|
||||||
|
|
||||||
|
// var newTargetPosLocal = Mat4.transformPoint(MyAvatar.getSensorToWorldMatrix(), newTargetPosition);
|
||||||
|
var newTargetPosLocal = MyAvatar.worldToJointPoint(newTargetPosition);
|
||||||
|
MyAvatar.setJointTranslation(FAR_GRAB_JOINTS[this.hand], newTargetPosLocal);
|
||||||
|
MyAvatar.setJointRotation(FAR_GRAB_JOINTS[this.hand], { x: 0, y: 0, z: 0, w: 1 });
|
||||||
|
|
||||||
|
this.previousRoomControllerPosition = roomControllerPosition;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.endFarGrabEntity = function (controllerData) {
|
||||||
|
if (this.grabID) {
|
||||||
|
MyAvatar.releaseGrab(this.grabID);
|
||||||
|
this.grabID = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hapticTargetID = null;
|
||||||
|
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||||
|
Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args);
|
||||||
|
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||||
|
action: 'release',
|
||||||
|
grabbedEntity: this.targetEntityID,
|
||||||
|
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||||
|
}));
|
||||||
|
unhighlightTargetEntity(this.targetEntityID);
|
||||||
|
this.grabbing = false;
|
||||||
|
this.targetEntityID = null;
|
||||||
|
this.potentialEntityWithContextOverlay = false;
|
||||||
|
MyAvatar.clearJointData(FAR_GRAB_JOINTS[this.hand]);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.updateRecommendedArea = function() {
|
||||||
|
var dims = Controller.getViewportDimensions();
|
||||||
|
this.reticleMaxX = dims.x - MARGIN;
|
||||||
|
this.reticleMaxY = dims.y - MARGIN;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.calculateNewReticlePosition = function(intersection) {
|
||||||
|
this.updateRecommendedArea();
|
||||||
|
var point2d = HMD.overlayFromWorldPoint(intersection);
|
||||||
|
point2d.x = Math.max(this.reticleMinX, Math.min(point2d.x, this.reticleMaxX));
|
||||||
|
point2d.y = Math.max(this.reticleMinY, Math.min(point2d.y, this.reticleMaxY));
|
||||||
|
return point2d;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.notPointingAtEntity = function(controllerData) {
|
||||||
|
var intersection = controllerData.rayPicks[this.hand];
|
||||||
|
var entityProperty = Entities.getEntityProperties(intersection.objectID, DISPATCHER_PROPERTIES);
|
||||||
|
var entityType = entityProperty.type;
|
||||||
|
var hudRayPick = controllerData.hudRayPicks[this.hand];
|
||||||
|
var point2d = this.calculateNewReticlePosition(hudRayPick.intersection);
|
||||||
|
if ((intersection.type === Picks.INTERSECTED_ENTITY && entityType === "Web") ||
|
||||||
|
intersection.type === Picks.INTERSECTED_OVERLAY || Window.isPointOnDesktopWindow(point2d)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.destroyContextOverlay = function(controllerData) {
|
||||||
|
if (this.entityWithContextOverlay) {
|
||||||
|
ContextOverlay.destroyContextOverlay(this.entityWithContextOverlay);
|
||||||
|
this.entityWithContextOverlay = false;
|
||||||
|
this.potentialEntityWithContextOverlay = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.targetIsNull = function() {
|
||||||
|
var properties = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
|
||||||
|
if (Object.keys(properties).length === 0 && this.distanceHolding) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getTargetProps = function (controllerData) {
|
||||||
|
var targetEntity = controllerData.rayPicks[this.hand].objectID;
|
||||||
|
if (targetEntity) {
|
||||||
|
var gtProps = Entities.getEntityProperties(targetEntity, DISPATCHER_PROPERTIES);
|
||||||
|
if (entityIsGrabbable(gtProps)) {
|
||||||
|
// give haptic feedback
|
||||||
|
if (gtProps.id !== this.hapticTargetID) {
|
||||||
|
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||||
|
this.hapticTargetID = gtProps.id;
|
||||||
|
}
|
||||||
|
// if we've attempted to grab a child, roll up to the root of the tree
|
||||||
|
var groupRootProps = findGroupParent(controllerData, gtProps);
|
||||||
|
if (entityIsGrabbable(groupRootProps)) {
|
||||||
|
return groupRootProps;
|
||||||
|
}
|
||||||
|
return gtProps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isReady = function (controllerData) {
|
||||||
|
if (HMD.active) {
|
||||||
|
if (this.notPointingAtEntity(controllerData)) {
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.distanceHolding = false;
|
||||||
|
|
||||||
|
if (controllerData.triggerValues[this.hand] > TRIGGER_ON_VALUE) {
|
||||||
|
return makeRunningValues(true, [], []);
|
||||||
|
} else {
|
||||||
|
this.destroyContextOverlay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.run = function (controllerData) {
|
||||||
|
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE ||
|
||||||
|
this.notPointingAtEntity(controllerData) || this.targetIsNull()) {
|
||||||
|
this.endFarGrabEntity(controllerData);
|
||||||
|
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
this.intersectionDistance = controllerData.rayPicks[this.hand].distance;
|
||||||
|
|
||||||
|
// gather up the readiness of the near-grab modules
|
||||||
|
var nearGrabNames = [
|
||||||
|
this.hand === RIGHT_HAND ? "RightScaleAvatar" : "LeftScaleAvatar",
|
||||||
|
this.hand === RIGHT_HAND ? "RightFarTriggerEntity" : "LeftFarTriggerEntity",
|
||||||
|
this.hand === RIGHT_HAND ? "RightNearGrabEntity" : "LeftNearGrabEntity",
|
||||||
|
this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay",
|
||||||
|
this.hand === RIGHT_HAND ? "RightNearTabletHighlight" : "LeftNearTabletHighlight"
|
||||||
|
];
|
||||||
|
|
||||||
|
var nearGrabReadiness = [];
|
||||||
|
for (var i = 0; i < nearGrabNames.length; i++) {
|
||||||
|
var nearGrabModule = getEnabledModuleByName(nearGrabNames[i]);
|
||||||
|
var ready = nearGrabModule ? nearGrabModule.isReady(controllerData) : makeRunningValues(false, [], []);
|
||||||
|
nearGrabReadiness.push(ready);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.targetEntityID) {
|
||||||
|
// if we are doing a distance grab and the object or tablet gets close enough to the controller,
|
||||||
|
// stop the far-grab so the near-grab or equip can take over.
|
||||||
|
for (var k = 0; k < nearGrabReadiness.length; k++) {
|
||||||
|
if (nearGrabReadiness[k].active && (nearGrabReadiness[k].targets[0] === this.targetEntityID ||
|
||||||
|
HMD.tabletID && nearGrabReadiness[k].targets[0] === HMD.tabletID)) {
|
||||||
|
this.endFarGrabEntity(controllerData);
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.continueDistanceHolding(controllerData);
|
||||||
|
} else {
|
||||||
|
// if we are doing a distance search and this controller moves into a position
|
||||||
|
// where it could near-grab something, stop searching.
|
||||||
|
for (var j = 0; j < nearGrabReadiness.length; j++) {
|
||||||
|
if (nearGrabReadiness[j].active) {
|
||||||
|
this.endFarGrabEntity(controllerData);
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var rayPickInfo = controllerData.rayPicks[this.hand];
|
||||||
|
if (rayPickInfo.type === Picks.INTERSECTED_ENTITY) {
|
||||||
|
if (controllerData.triggerClicks[this.hand]) {
|
||||||
|
var entityID = rayPickInfo.objectID;
|
||||||
|
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
var targetProps = Entities.getEntityProperties(entityID, DISPATCHER_PROPERTIES);
|
||||||
|
if (targetProps.href !== "") {
|
||||||
|
AddressManager.handleLookupString(targetProps.href);
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.targetObject = new TargetObject(entityID, targetProps);
|
||||||
|
this.targetObject.parentProps = getEntityParents(targetProps);
|
||||||
|
|
||||||
|
if (this.contextOverlayTimer) {
|
||||||
|
Script.clearTimeout(this.contextOverlayTimer);
|
||||||
|
}
|
||||||
|
this.contextOverlayTimer = false;
|
||||||
|
if (entityID === this.entityWithContextOverlay) {
|
||||||
|
this.destroyContextOverlay();
|
||||||
|
} else {
|
||||||
|
Selection.removeFromSelectedItemsList("contextOverlayHighlightList", "entity", entityID);
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetEntity = this.targetObject.getTargetEntity();
|
||||||
|
entityID = targetEntity.id;
|
||||||
|
targetProps = targetEntity.props;
|
||||||
|
|
||||||
|
if (entityIsGrabbable(targetProps) || entityIsGrabbable(this.targetObject.entityProps)) {
|
||||||
|
|
||||||
|
this.targetEntityID = entityID;
|
||||||
|
this.grabbedDistance = rayPickInfo.distance;
|
||||||
|
this.distanceHolding = true;
|
||||||
|
this.startFarGrabEntity(controllerData, targetProps);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
var targetEntityID = rayPickInfo.objectID;
|
||||||
|
if (this.highlightedEntity !== targetEntityID) {
|
||||||
|
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
|
||||||
|
var selectionTargetProps = Entities.getEntityProperties(targetEntityID, DISPATCHER_PROPERTIES);
|
||||||
|
|
||||||
|
var selectionTargetObject = new TargetObject(targetEntityID, selectionTargetProps);
|
||||||
|
selectionTargetObject.parentProps = getEntityParents(selectionTargetProps);
|
||||||
|
var selectionTargetEntity = selectionTargetObject.getTargetEntity();
|
||||||
|
|
||||||
|
if (entityIsGrabbable(selectionTargetEntity.props) ||
|
||||||
|
entityIsGrabbable(selectionTargetObject.entityProps)) {
|
||||||
|
|
||||||
|
Selection.addToSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", rayPickInfo.objectID);
|
||||||
|
}
|
||||||
|
this.highlightedEntity = rayPickInfo.objectID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.entityWithContextOverlay) {
|
||||||
|
var _this = this;
|
||||||
|
|
||||||
|
if (_this.potentialEntityWithContextOverlay !== rayPickInfo.objectID) {
|
||||||
|
if (_this.contextOverlayTimer) {
|
||||||
|
Script.clearTimeout(_this.contextOverlayTimer);
|
||||||
|
}
|
||||||
|
_this.contextOverlayTimer = false;
|
||||||
|
_this.potentialEntityWithContextOverlay = rayPickInfo.objectID;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_this.contextOverlayTimer) {
|
||||||
|
_this.contextOverlayTimer = Script.setTimeout(function () {
|
||||||
|
if (!_this.entityWithContextOverlay &&
|
||||||
|
_this.contextOverlayTimer &&
|
||||||
|
_this.potentialEntityWithContextOverlay === rayPickInfo.objectID) {
|
||||||
|
var cotProps = Entities.getEntityProperties(rayPickInfo.objectID,
|
||||||
|
DISPATCHER_PROPERTIES);
|
||||||
|
var pointerEvent = {
|
||||||
|
type: "Move",
|
||||||
|
id: _this.hand + 1, // 0 is reserved for hardware mouse
|
||||||
|
pos2D: projectOntoEntityXYPlane(rayPickInfo.objectID,
|
||||||
|
rayPickInfo.intersection, cotProps),
|
||||||
|
pos3D: rayPickInfo.intersection,
|
||||||
|
normal: rayPickInfo.surfaceNormal,
|
||||||
|
direction: Vec3.subtract(ZERO_VEC, rayPickInfo.surfaceNormal),
|
||||||
|
button: "Secondary"
|
||||||
|
};
|
||||||
|
if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.objectID, pointerEvent)) {
|
||||||
|
_this.entityWithContextOverlay = rayPickInfo.objectID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_this.contextOverlayTimer = false;
|
||||||
|
}, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (this.highlightedEntity) {
|
||||||
|
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this.exitIfDisabled(controllerData);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.exitIfDisabled = function(controllerData) {
|
||||||
|
var moduleName = this.hand === RIGHT_HAND ? "RightDisableModules" : "LeftDisableModules";
|
||||||
|
var disableModule = getEnabledModuleByName(moduleName);
|
||||||
|
if (disableModule) {
|
||||||
|
if (disableModule.disableModules) {
|
||||||
|
this.endFarGrabEntity(controllerData);
|
||||||
|
Selection.removeFromSelectedItemsList(DISPATCHER_HOVERING_LIST, "entity", this.highlightedEntity);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var grabbedThing = this.distanceHolding ? this.targetObject.entityID : null;
|
||||||
|
var offset = this.calculateOffset(controllerData);
|
||||||
|
var laserLockInfo = makeLaserLockInfo(grabbedThing, false, this.hand, offset);
|
||||||
|
return makeRunningValues(true, [], [], laserLockInfo);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.calculateOffset = function(controllerData) {
|
||||||
|
if (this.distanceHolding) {
|
||||||
|
var targetProps = Entities.getEntityProperties(this.targetObject.entityID,
|
||||||
|
[ "position", "rotation", "registrationPoint", "dimensions" ]);
|
||||||
|
return worldPositionToRegistrationFrameMatrix(targetProps, controllerData.rayPicks[this.hand].intersection);
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftFarGrabEntity = new FarGrabEntity(LEFT_HAND);
|
||||||
|
var rightFarGrabEntity = new FarGrabEntity(RIGHT_HAND);
|
||||||
|
|
||||||
|
enableDispatcherModule("LeftFarGrabEntity", leftFarGrabEntity);
|
||||||
|
enableDispatcherModule("RightFarGrabEntity", rightFarGrabEntity);
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
disableDispatcherModule("LeftFarGrabEntity");
|
||||||
|
disableDispatcherModule("RightFarGrabEntity");
|
||||||
|
}
|
||||||
|
Script.scriptEnding.connect(cleanup);
|
||||||
|
}());
|
|
@ -8,10 +8,10 @@
|
||||||
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND,
|
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND,
|
||||||
getControllerJointIndex, getGrabbableData, enableDispatcherModule, disableDispatcherModule,
|
getControllerJointIndex, getGrabbableData, enableDispatcherModule, disableDispatcherModule,
|
||||||
propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable,
|
propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, entityIsGrabbable,
|
||||||
Quat, Vec3, MSECS_PER_SEC, getControllerWorldLocation, makeDispatcherModuleParameters, makeRunningValues,
|
MSECS_PER_SEC, makeDispatcherModuleParameters, makeRunningValues,
|
||||||
TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity,
|
TRIGGER_OFF_VALUE, NEAR_GRAB_RADIUS, findGroupParent, entityIsCloneable, propsAreCloneDynamic, cloneEntity,
|
||||||
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, unhighlightTargetEntity, Uuid,
|
HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, unhighlightTargetEntity, Uuid,
|
||||||
DISPATCHER_PROPERTIES
|
DISPATCHER_PROPERTIES, HMD
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
|
@ -65,25 +65,14 @@ Script.include("/~/system/libraries/cloneEntityUtils.js");
|
||||||
this.grabFollowsController = grabbableData.grabFollowsController;
|
this.grabFollowsController = grabbableData.grabFollowsController;
|
||||||
this.kinematicGrab = grabbableData.grabKinematic;
|
this.kinematicGrab = grabbableData.grabKinematic;
|
||||||
|
|
||||||
var handRotation;
|
var handJointIndex;
|
||||||
var handPosition;
|
if (HMD.mounted && HMD.isHandControllerAvailable() && grabbableData.grabFollowsController) {
|
||||||
if (this.grabFollowsController) {
|
handJointIndex = getControllerJointIndex(this.hand);
|
||||||
var controllerID =
|
|
||||||
(this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
|
||||||
var controllerLocation = getControllerWorldLocation(controllerID, false);
|
|
||||||
handRotation = controllerLocation.orientation;
|
|
||||||
handPosition = controllerLocation.position;
|
|
||||||
} else {
|
} else {
|
||||||
handRotation = this.getHandRotation();
|
handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||||
handPosition = this.getHandPosition();
|
|
||||||
}
|
}
|
||||||
|
this.offsetPosition = Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, handJointIndex);
|
||||||
var objectRotation = targetProps.rotation;
|
this.offsetRotation = Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, handJointIndex);
|
||||||
this.offsetRotation = Quat.multiply(Quat.inverse(handRotation), objectRotation);
|
|
||||||
|
|
||||||
var currentObjectPosition = targetProps.position;
|
|
||||||
var offset = Vec3.subtract(currentObjectPosition, handPosition);
|
|
||||||
this.offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, this.offsetRotation)), offset);
|
|
||||||
|
|
||||||
var now = Date.now();
|
var now = Date.now();
|
||||||
this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC);
|
this.actionTimeout = now + (ACTION_TTL * MSECS_PER_SEC);
|
||||||
|
|
249
scripts/system/controllers/controllerModules/nearGrabEntity.js
Normal file
249
scripts/system/controllers/controllerModules/nearGrabEntity.js
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
// nearGrabEntity.js
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
|
||||||
|
|
||||||
|
/* global Script, Entities, MyAvatar, Controller, RIGHT_HAND, LEFT_HAND, getControllerJointIndex,
|
||||||
|
enableDispatcherModule, disableDispatcherModule, propsArePhysical, Messages, HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION,
|
||||||
|
TRIGGER_OFF_VALUE, makeDispatcherModuleParameters, entityIsGrabbable, makeRunningValues, NEAR_GRAB_RADIUS,
|
||||||
|
findGroupParent, Vec3, cloneEntity, entityIsCloneable, propsAreCloneDynamic, HAPTIC_PULSE_STRENGTH,
|
||||||
|
HAPTIC_PULSE_DURATION, BUMPER_ON_VALUE, findHandChildEntities, TEAR_AWAY_DISTANCE, MSECS_PER_SEC, TEAR_AWAY_CHECK_TIME,
|
||||||
|
TEAR_AWAY_COUNT, distanceBetweenPointAndEntityBoundingBox, Uuid, highlightTargetEntity, unhighlightTargetEntity,
|
||||||
|
distanceBetweenEntityLocalPositionAndBoundingBox, getGrabbableData, getGrabPointSphereOffset, DISPATCHER_PROPERTIES
|
||||||
|
*/
|
||||||
|
|
||||||
|
Script.include("/~/system/libraries/controllerDispatcherUtils.js");
|
||||||
|
Script.include("/~/system/libraries/cloneEntityUtils.js");
|
||||||
|
Script.include("/~/system/libraries/controllers.js");
|
||||||
|
|
||||||
|
(function() {
|
||||||
|
|
||||||
|
// XXX this.ignoreIK = (grabbableData.ignoreIK !== undefined) ? grabbableData.ignoreIK : true;
|
||||||
|
// XXX this.kinematicGrab = (grabbableData.kinematic !== undefined) ? grabbableData.kinematic : NEAR_GRABBING_KINEMATIC;
|
||||||
|
|
||||||
|
// this offset needs to match the one in libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp:378
|
||||||
|
var GRAB_POINT_SPHERE_OFFSET = { x: 0.04, y: 0.13, z: 0.039 }; // x = upward, y = forward, z = lateral
|
||||||
|
|
||||||
|
function getGrabOffset(handController) {
|
||||||
|
var offset = getGrabPointSphereOffset(handController, true);
|
||||||
|
offset.y = -offset.y;
|
||||||
|
return Vec3.multiply(MyAvatar.sensorToWorldScale, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
function NearGrabEntity(hand) {
|
||||||
|
this.hand = hand;
|
||||||
|
this.targetEntityID = null;
|
||||||
|
this.grabbing = false;
|
||||||
|
this.hapticTargetID = null;
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
this.cloneAllowed = true;
|
||||||
|
this.grabID = null;
|
||||||
|
|
||||||
|
this.parameters = makeDispatcherModuleParameters(
|
||||||
|
500,
|
||||||
|
this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"],
|
||||||
|
[],
|
||||||
|
100);
|
||||||
|
|
||||||
|
this.startNearGrabEntity = function (controllerData, targetProps) {
|
||||||
|
var grabData = getGrabbableData(targetProps);
|
||||||
|
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||||
|
unhighlightTargetEntity(this.targetEntityID);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
var message = {
|
||||||
|
hand: this.hand,
|
||||||
|
entityID: this.targetEntityID
|
||||||
|
};
|
||||||
|
|
||||||
|
Messages.sendLocalMessage('Hifi-unhighlight-entity', JSON.stringify(message));
|
||||||
|
var handJointIndex;
|
||||||
|
if (HMD.mounted && HMD.isHandControllerAvailable() && grabData.grabFollowsController) {
|
||||||
|
handJointIndex = getControllerJointIndex(this.hand);
|
||||||
|
} else {
|
||||||
|
handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||||
|
Entities.callEntityMethod(targetProps.id, "startNearGrab", args);
|
||||||
|
|
||||||
|
this.targetEntityID = targetProps.id;
|
||||||
|
|
||||||
|
if (this.grabID) {
|
||||||
|
MyAvatar.releaseGrab(this.grabID);
|
||||||
|
}
|
||||||
|
|
||||||
|
var relativePosition = Entities.worldToLocalPosition(targetProps.position, MyAvatar.SELF_ID, handJointIndex);
|
||||||
|
var relativeRotation = Entities.worldToLocalRotation(targetProps.rotation, MyAvatar.SELF_ID, handJointIndex);
|
||||||
|
|
||||||
|
this.grabID = MyAvatar.grab(targetProps.id, handJointIndex, relativePosition, relativeRotation);
|
||||||
|
|
||||||
|
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||||
|
action: 'grab',
|
||||||
|
grabbedEntity: targetProps.id,
|
||||||
|
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||||
|
}));
|
||||||
|
this.grabbing = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.endNearGrabEntity = function (controllerData) {
|
||||||
|
if (this.grabID) {
|
||||||
|
MyAvatar.releaseGrab(this.grabID);
|
||||||
|
this.grabID = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.hapticTargetID = null;
|
||||||
|
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
|
||||||
|
|
||||||
|
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||||
|
Entities.callEntityMethod(this.targetEntityID, "releaseGrab", args);
|
||||||
|
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
|
||||||
|
action: 'release',
|
||||||
|
grabbedEntity: this.targetEntityID,
|
||||||
|
joint: this.hand === RIGHT_HAND ? "RightHand" : "LeftHand"
|
||||||
|
}));
|
||||||
|
unhighlightTargetEntity(this.targetEntityID);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
this.grabbing = false;
|
||||||
|
this.targetEntityID = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.getTargetProps = function (controllerData) {
|
||||||
|
// nearbyEntityProperties is already sorted by length from controller
|
||||||
|
var nearbyEntityProperties = controllerData.nearbyEntityProperties[this.hand];
|
||||||
|
var sensorScaleFactor = MyAvatar.sensorToWorldScale;
|
||||||
|
for (var i = 0; i < nearbyEntityProperties.length; i++) {
|
||||||
|
var props = nearbyEntityProperties[i];
|
||||||
|
var handPosition = controllerData.controllerLocations[this.hand].position;
|
||||||
|
var dist = distanceBetweenPointAndEntityBoundingBox(handPosition, props);
|
||||||
|
var distance = Vec3.distance(handPosition, props.position);
|
||||||
|
if ((dist > TEAR_AWAY_DISTANCE) ||
|
||||||
|
(distance > NEAR_GRAB_RADIUS * sensorScaleFactor)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (entityIsGrabbable(props) || entityIsCloneable(props)) {
|
||||||
|
// give haptic feedback
|
||||||
|
if (props.id !== this.hapticTargetID) {
|
||||||
|
Controller.triggerHapticPulse(HAPTIC_PULSE_STRENGTH, HAPTIC_PULSE_DURATION, this.hand);
|
||||||
|
this.hapticTargetID = props.id;
|
||||||
|
}
|
||||||
|
if (!entityIsCloneable(props)) {
|
||||||
|
// if we've attempted to grab a non-cloneable child, roll up to the root of the tree
|
||||||
|
var groupRootProps = findGroupParent(controllerData, props);
|
||||||
|
if (entityIsGrabbable(groupRootProps)) {
|
||||||
|
return groupRootProps;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return props;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
this.isReady = function (controllerData, deltaTime) {
|
||||||
|
this.targetEntityID = null;
|
||||||
|
this.grabbing = false;
|
||||||
|
|
||||||
|
if (controllerData.triggerValues[this.hand] < TRIGGER_OFF_VALUE &&
|
||||||
|
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||||
|
this.cloneAllowed = true;
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
var targetProps = this.getTargetProps(controllerData);
|
||||||
|
if (targetProps) {
|
||||||
|
this.targetEntityID = targetProps.id;
|
||||||
|
this.highlightedEntity = this.targetEntityID;
|
||||||
|
highlightTargetEntity(this.targetEntityID);
|
||||||
|
return makeRunningValues(true, [this.targetEntityID], []);
|
||||||
|
} else {
|
||||||
|
if (this.highlightedEntity) {
|
||||||
|
unhighlightTargetEntity(this.highlightedEntity);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
}
|
||||||
|
this.hapticTargetID = null;
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
this.run = function (controllerData, deltaTime) {
|
||||||
|
if (this.grabbing) {
|
||||||
|
if (controllerData.triggerClicks[this.hand] < TRIGGER_OFF_VALUE &&
|
||||||
|
controllerData.secondaryValues[this.hand] < TRIGGER_OFF_VALUE) {
|
||||||
|
this.endNearGrabEntity(controllerData);
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
|
||||||
|
var props = controllerData.nearbyEntityPropertiesByID[this.targetEntityID];
|
||||||
|
if (!props) {
|
||||||
|
props = Entities.getEntityProperties(this.targetEntityID, DISPATCHER_PROPERTIES);
|
||||||
|
if (!props) {
|
||||||
|
// entity was deleted
|
||||||
|
unhighlightTargetEntity(this.targetEntityID);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
this.grabbing = false;
|
||||||
|
this.targetEntityID = null;
|
||||||
|
this.hapticTargetID = null;
|
||||||
|
return makeRunningValues(false, [], []);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var args = [this.hand === RIGHT_HAND ? "right" : "left", MyAvatar.sessionUUID];
|
||||||
|
Entities.callEntityMethod(this.targetEntityID, "continueNearGrab", args);
|
||||||
|
} else {
|
||||||
|
// still searching / highlighting
|
||||||
|
var readiness = this.isReady(controllerData);
|
||||||
|
if (!readiness.active) {
|
||||||
|
unhighlightTargetEntity(this.highlightedEntity);
|
||||||
|
this.highlightedEntity = null;
|
||||||
|
return readiness;
|
||||||
|
}
|
||||||
|
if (controllerData.triggerClicks[this.hand] || controllerData.secondaryValues[this.hand] > BUMPER_ON_VALUE) {
|
||||||
|
// switch to grab
|
||||||
|
var targetProps = this.getTargetProps(controllerData);
|
||||||
|
var targetCloneable = entityIsCloneable(targetProps);
|
||||||
|
|
||||||
|
if (targetCloneable) {
|
||||||
|
if (this.cloneAllowed) {
|
||||||
|
var cloneID = cloneEntity(targetProps);
|
||||||
|
if (cloneID !== null) {
|
||||||
|
var cloneProps = Entities.getEntityProperties(cloneID, DISPATCHER_PROPERTIES);
|
||||||
|
this.grabbing = true;
|
||||||
|
this.targetEntityID = cloneID;
|
||||||
|
this.startNearGrabEntity(controllerData, cloneProps);
|
||||||
|
this.cloneAllowed = false; // prevent another clone call until inputs released
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (targetProps) {
|
||||||
|
this.grabbing = true;
|
||||||
|
this.startNearGrabEntity(controllerData, targetProps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return makeRunningValues(true, [this.targetEntityID], []);
|
||||||
|
};
|
||||||
|
|
||||||
|
this.cleanup = function () {
|
||||||
|
if (this.targetEntityID) {
|
||||||
|
this.endNearGrabEntity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
var leftNearGrabEntity = new NearGrabEntity(LEFT_HAND);
|
||||||
|
var rightNearGrabEntity = new NearGrabEntity(RIGHT_HAND);
|
||||||
|
|
||||||
|
enableDispatcherModule("LeftNearGrabEntity", leftNearGrabEntity);
|
||||||
|
enableDispatcherModule("RightNearGrabEntity", rightNearGrabEntity);
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
leftNearGrabEntity.cleanup();
|
||||||
|
rightNearGrabEntity.cleanup();
|
||||||
|
disableDispatcherModule("LeftNearGrabEntity");
|
||||||
|
disableDispatcherModule("RightNearGrabEntity");
|
||||||
|
}
|
||||||
|
Script.scriptEnding.connect(cleanup);
|
||||||
|
}());
|
|
@ -18,11 +18,7 @@ var CONTOLLER_SCRIPTS = [
|
||||||
"toggleAdvancedMovementForHandControllers.js",
|
"toggleAdvancedMovementForHandControllers.js",
|
||||||
"handTouch.js",
|
"handTouch.js",
|
||||||
"controllerDispatcher.js",
|
"controllerDispatcher.js",
|
||||||
"controllerModules/nearParentGrabEntity.js",
|
|
||||||
"controllerModules/nearParentGrabOverlay.js",
|
"controllerModules/nearParentGrabOverlay.js",
|
||||||
"controllerModules/nearActionGrabEntity.js",
|
|
||||||
"controllerModules/farActionGrabEntityDynOnly.js",
|
|
||||||
"controllerModules/farParentGrabEntity.js",
|
|
||||||
"controllerModules/stylusInput.js",
|
"controllerModules/stylusInput.js",
|
||||||
"controllerModules/equipEntity.js",
|
"controllerModules/equipEntity.js",
|
||||||
"controllerModules/nearTrigger.js",
|
"controllerModules/nearTrigger.js",
|
||||||
|
@ -39,6 +35,16 @@ var CONTOLLER_SCRIPTS = [
|
||||||
"controllerModules/nearTabletHighlight.js"
|
"controllerModules/nearTabletHighlight.js"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
if (Settings.getValue("useTraitsGrab", false)) {
|
||||||
|
CONTOLLER_SCRIPTS.push("controllerModules/nearGrabEntity.js");
|
||||||
|
CONTOLLER_SCRIPTS.push("controllerModules/farGrabEntity.js");
|
||||||
|
} else {
|
||||||
|
CONTOLLER_SCRIPTS.push("controllerModules/nearParentGrabEntity.js");
|
||||||
|
CONTOLLER_SCRIPTS.push("controllerModules/nearActionGrabEntity.js");
|
||||||
|
CONTOLLER_SCRIPTS.push("controllerModules/farActionGrabEntityDynOnly.js");
|
||||||
|
CONTOLLER_SCRIPTS.push("controllerModules/farParentGrabEntity.js");
|
||||||
|
}
|
||||||
|
|
||||||
var DEBUG_MENU_ITEM = "Debug defaultScripts.js";
|
var DEBUG_MENU_ITEM = "Debug defaultScripts.js";
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -104,7 +104,7 @@ TEAR_AWAY_CHECK_TIME = 0.15; // seconds, duration between checks
|
||||||
NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance.
|
NEAR_GRAB_DISTANCE = 0.14; // Grab an entity if its bounding box is within this distance.
|
||||||
// Smaller than TEAR_AWAY_DISTANCE for hysteresis.
|
// Smaller than TEAR_AWAY_DISTANCE for hysteresis.
|
||||||
|
|
||||||
DISPATCHER_HOVERING_LIST = "dispactherHoveringList";
|
DISPATCHER_HOVERING_LIST = "dispatcherHoveringList";
|
||||||
DISPATCHER_HOVERING_STYLE = {
|
DISPATCHER_HOVERING_STYLE = {
|
||||||
isOutlineSmooth: true,
|
isOutlineSmooth: true,
|
||||||
outlineWidth: 0,
|
outlineWidth: 0,
|
||||||
|
@ -144,6 +144,7 @@ DISPATCHER_PROPERTIES = [
|
||||||
"grab.grabFollowsController",
|
"grab.grabFollowsController",
|
||||||
"grab.triggerable",
|
"grab.triggerable",
|
||||||
"grab.equippable",
|
"grab.equippable",
|
||||||
|
"grab.grabDelegateToParent",
|
||||||
"grab.equippableLeftPosition",
|
"grab.equippableLeftPosition",
|
||||||
"grab.equippableLeftRotation",
|
"grab.equippableLeftRotation",
|
||||||
"grab.equippableRightPosition",
|
"grab.equippableRightPosition",
|
||||||
|
@ -332,15 +333,15 @@ getControllerJointIndex = function (hand) {
|
||||||
if (now - getControllerJointIndexCacheTime[hand] > GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME) {
|
if (now - getControllerJointIndexCacheTime[hand] > GET_CONTROLLERJOINTINDEX_CACHE_REFRESH_TIME) {
|
||||||
if (HMD.isHandControllerAvailable()) {
|
if (HMD.isHandControllerAvailable()) {
|
||||||
var controllerJointIndex = -1;
|
var controllerJointIndex = -1;
|
||||||
if (Camera.mode === "first person") {
|
// if (Camera.mode === "first person") {
|
||||||
controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
|
// controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
|
||||||
"_CONTROLLER_RIGHTHAND" :
|
// "_CONTROLLER_RIGHTHAND" :
|
||||||
"_CONTROLLER_LEFTHAND");
|
// "_CONTROLLER_LEFTHAND");
|
||||||
} else if (Camera.mode === "third person") {
|
// } else if (Camera.mode === "third person") {
|
||||||
controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
|
controllerJointIndex = MyAvatar.getJointIndex(hand === RIGHT_HAND ?
|
||||||
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
|
"_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" :
|
||||||
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
|
"_CAMERA_RELATIVE_CONTROLLER_LEFTHAND");
|
||||||
}
|
// }
|
||||||
|
|
||||||
getControllerJointIndexCacheTime[hand] = now;
|
getControllerJointIndexCacheTime[hand] = now;
|
||||||
getControllerJointIndexCache[hand] = controllerJointIndex;
|
getControllerJointIndexCache[hand] = controllerJointIndex;
|
||||||
|
@ -409,7 +410,8 @@ ensureDynamic = function (entityID) {
|
||||||
};
|
};
|
||||||
|
|
||||||
findGroupParent = function (controllerData, targetProps) {
|
findGroupParent = function (controllerData, targetProps) {
|
||||||
while (targetProps.parentID &&
|
while (targetProps.grab.grabDelegateToParent &&
|
||||||
|
targetProps.parentID &&
|
||||||
targetProps.parentID !== Uuid.NULL &&
|
targetProps.parentID !== Uuid.NULL &&
|
||||||
Entities.getNestableType(targetProps.parentID) == "entity") {
|
Entities.getNestableType(targetProps.parentID) == "entity") {
|
||||||
var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);
|
var parentProps = Entities.getEntityProperties(targetProps.parentID, DISPATCHER_PROPERTIES);
|
||||||
|
|
Loading…
Reference in a new issue