From 47f7d55e2c5481f5c158beec2e91cdb7b211a27e Mon Sep 17 00:00:00 2001 From: "Babiuch, Ryan Nicholas" Date: Mon, 1 Feb 2016 08:32:52 -0600 Subject: [PATCH 01/26] Working energy usage for entity manipulation. - Working example in examples/example/ui/MyEnergyBar.js --- examples/example/ui/MyEnergyBar.js | 67 +++++++++++++++++++ examples/example/ui/energyBar.js | 44 ++---------- interface/src/avatar/MyAvatar.cpp | 44 +++++++++++- interface/src/avatar/MyAvatar.h | 16 +++++ .../entities/src/EntityScriptingInterface.cpp | 59 +++++++++++++++- .../entities/src/EntityScriptingInterface.h | 14 +++- 6 files changed, 202 insertions(+), 42 deletions(-) create mode 100644 examples/example/ui/MyEnergyBar.js diff --git a/examples/example/ui/MyEnergyBar.js b/examples/example/ui/MyEnergyBar.js new file mode 100644 index 0000000000..ac7ef0d39c --- /dev/null +++ b/examples/example/ui/MyEnergyBar.js @@ -0,0 +1,67 @@ +Script.include("../../libraries/utils.js"); +var energyColor = {red: 0, green: 200, blue: 0}; +var lowEnergyColor = {red: 255, green: 0, blue: 0}; +var totalWidth = 200; +var paddingRight = 50; +var xPosition = Window.innerWidth - totalWidth - paddingRight; +var lowEnergyThreshold = 0.3; +var currentEnergy = 1.0; +var energyLossRate = 0.003; +var energyChargeRate = 0.003; +var isGrabbing = false; +var refractoryPeriod = 2000; + +var lastAvatarVelocity = MyAvatar.getVelocity(); +var lastAvatarPosition = MyAvatar.position; + +var background = Overlays.addOverlay("text", { + x: xPosition, + y: 20, + width: totalWidth, + height: 10, + backgroundColor: {red: 255, green: 0, blue: 0} +}) + +var bar = Overlays.addOverlay("text", { + x: xPosition, + y: 20, + width: totalWidth, + height: 10, + backgroundColor: energyColor +}); + + +// Takes an energy value between 0 and 1 and sets energy bar width appropriately +function setEnergy(energy) { + energy = clamp(energy, 0, 1); + var barWidth = totalWidth * energy; + var color = energy <= lowEnergyThreshold ? lowEnergyColor: energyColor; + Overlays.editOverlay(bar, { width: barWidth, backgroundColor: color}); +} + +function update() { + currentEnergy = clamp(MyAvatar.energy, 0, 1); + setEnergy(currentEnergy); +} + +function cleanup() { + Overlays.deleteOverlay(background); + Overlays.deleteOverlay(bar); +} + +function energyChanged(newValue) { + Entities.currentAvatarEnergy = newValue; +} + +function debitAvatarEnergy(value) { + MyAvatar.energy = MyAvatar.energy - value; +} +function calculateCost(mass, oldVelocity, newVelocity) { + return mass * (newVelocity - oldVelocity); +} + +Entities.addCostFunction(calculateCost); +Entities.debitEnergySource.connect(debitAvatarEnergy); +MyAvatar.energyChanged.connect(energyChanged); +Script.update.connect(update); +Script.scriptEnding.connect(cleanup); diff --git a/examples/example/ui/energyBar.js b/examples/example/ui/energyBar.js index a45b09f6d4..498eef2751 100644 --- a/examples/example/ui/energyBar.js +++ b/examples/example/ui/energyBar.js @@ -51,45 +51,8 @@ function setEnergy(energy) { Overlays.editOverlay(bar, { width: barWidth, backgroundColor: color}); } -function avatarAccelerationEnergy() { - var AVATAR_MOVEMENT_ENERGY_CONSTANT = 0.001; - var velocity = MyAvatar.getVelocity(); - var dV = Math.abs(Vec3.length(velocity) - Vec3.length(lastAvatarVelocity)); - var dE = Vec3.length(lastAvatarVelocity) * dV * AVATAR_MOVEMENT_ENERGY_CONSTANT; - lastAvatarVelocity = velocity; - return dE; -} - -function teleported() { - var MAX_AVATAR_MOVEMENT_PER_FRAME = 30.0; - var position = MyAvatar.position; - var dP = Vec3.length(Vec3.subtract(position, lastAvatarPosition)); - lastAvatarPosition = position; - return (dP > MAX_AVATAR_MOVEMENT_PER_FRAME); -} - -function audioEnergy() { - var AUDIO_ENERGY_CONSTANT = 0.000001; - return MyAvatar.audioLoudness * AUDIO_ENERGY_CONSTANT; -} - function update() { - // refill energy - currentEnergy += energyChargeRate; - - // Avatar acceleration - currentEnergy -= avatarAccelerationEnergy(); - - // Teleport cost - if (teleported()) { - currentEnergy = 0; - } - - // Making sounds - currentEnergy -= audioEnergy(); - - - currentEnergy = clamp(currentEnergy, 0, 1); + currentEnergy = clamp(MyAvatar.energy, 0, 1); setEnergy(currentEnergy); } @@ -98,5 +61,10 @@ function cleanup() { Overlays.deleteOverlay(bar); } +function energyChanged(newValue) { + Entities.currentAvatarEnergy = newValue; +} + +MyAvatar.energyChanged.connect(energyChanged); Script.update.connect(update); Script.scriptEnding.connect(cleanup); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 3e54ba99b9..8ab55d450c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -295,8 +295,20 @@ void MyAvatar::update(float deltaTime) { auto audio = DependencyManager::get(); head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); - - simulate(deltaTime); + + simulate(deltaTime); + + currentEnergy += energyChargeRate; + currentEnergy -= getAccelerationEnergy(); + currentEnergy -= getAudioEnergy(); + + if(didTeleport()) { + currentEnergy = 0.0f; + } + currentEnergy = max(0.0f, min(currentEnergy,1.0f)); + emit energyChanged(currentEnergy); + + } extern QByteArray avatarStateToFrame(const AvatarData* _avatar); @@ -1883,3 +1895,31 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co } } +float MyAvatar::getAccelerationEnergy() { + glm::vec3 velocity = getVelocity(); + int changeInVelocity = abs(velocity.length() - priorVelocity.length()); + float changeInEnergy = priorVelocity.length()*changeInVelocity*AVATAR_MOVEMENT_ENERGY_CONSTANT; + priorVelocity = velocity; + + return changeInEnergy; +} + +float MyAvatar::getEnergy() { + return currentEnergy; +} + +void MyAvatar::setEnergy(float value) { + currentEnergy = value; +} + +float MyAvatar::getAudioEnergy() { + return getAudioLoudness()*AUDIO_ENERGY_CONSTANT; +} + +bool MyAvatar::didTeleport() { + glm::vec3 pos = getPosition(); + glm::vec3 changeInPosition = pos - lastPosition; + lastPosition = pos; + return (changeInPosition.length() > MAX_AVATAR_MOVEMENT_PER_FRAME); +} + diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index ed6c3cb883..a596d9c7cd 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -78,6 +78,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(controller::Pose rightHandPose READ getRightHandPose) Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose) Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose) + Q_PROPERTY(float energy READ getEnergy WRITE setEnergy) public: MyAvatar(RigPointer rig); @@ -272,6 +273,7 @@ signals: void transformChanged(); void newCollisionSoundURL(const QUrl& url); void collisionWithEntity(const Collision& collision); + void energyChanged(float newEnergy); private: @@ -408,6 +410,20 @@ private: AtRestDetector _hmdAtRestDetector; bool _lastIsMoving { false }; + + float AVATAR_MOVEMENT_ENERGY_CONSTANT { 0.001f }; + float AUDIO_ENERGY_CONSTANT { 0.000001f }; + float MAX_AVATAR_MOVEMENT_PER_FRAME { 30.0f }; + float currentEnergy { 0.0f }; + float energyChargeRate { 0.003f }; + glm::vec3 priorVelocity; + glm::vec3 lastPosition; + float getAudioEnergy(); + float getAccelerationEnergy(); + float getEnergy(); + void setEnergy(float value); + bool didTeleport(); + }; QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 8fd7be912e..9c61d7c297 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -8,7 +8,6 @@ // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - #include "EntityScriptingInterface.h" #include "EntityItemID.h" @@ -122,6 +121,21 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties); propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); + auto dimensions = propertiesWithSimID.getDimensions(); + float volume = dimensions.x*dimensions.y*dimensions.z; + auto density = propertiesWithSimID.getDensity(); + auto newVelocity = propertiesWithSimID.getVelocity().length(); + double cost = calculateCost(density*volume, 0, newVelocity); + cost *= ENTITY_MANIPULATION_MULTIPLIER; //try this as a constant for now + + if(cost > _currentAvatarEnergy) { + return QUuid(); + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } + + EntityItemID id = EntityItemID(QUuid::createUuid()); // If we have a local entity tree set, then also update it. @@ -211,6 +225,21 @@ EntityItemProperties EntityScriptingInterface::getEntityProperties(QUuid identit QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) { EntityItemProperties properties = scriptSideProperties; + + auto dimensions = properties.getDimensions(); + float volume = dimensions.x*dimensions.y*dimensions.z; + auto density = properties.getDensity(); + auto newVelocity = properties.getVelocity().length(); + double cost = calculateCost(density*volume, 0, newVelocity); + cost *= ENTITY_MANIPULATION_MULTIPLIER; + + if(cost > _currentAvatarEnergy) { + return QUuid(); + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } + EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); @@ -316,6 +345,21 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { _entityTree->withWriteLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID); if (entity) { + + auto dimensions = entity->getDimensions(); + float volume = dimensions.x*dimensions.y*dimensions.z; + auto density = entity->getDensity(); + auto velocity = entity->getVelocity().length(); + double cost = calculateCost(density*volume, velocity, 0); + cost *= ENTITY_MANIPULATION_MULTIPLIER; + + if(cost > _currentAvatarEnergy) { + return; + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } + if (entity->getLocked()) { shouldDelete = false; } else { @@ -992,3 +1036,16 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) { Q_RETURN_ARG(QStringList, result), Q_ARG(QUuid, entityID)); return result; } + +void EntityScriptingInterface::addCostFunction(QScriptValue costFunction) { + _costFunction = &costFunction; +} + +double EntityScriptingInterface::calculateCost(float mass, float oldVelocity,float newVelocity) { + return std::abs(mass * (newVelocity - oldVelocity)); +} + +void EntityScriptingInterface::setCurrentAvatarEnergy(float energy) { + // qCDebug(entities) << "NEW AVATAR ENERGY IN ENTITY SCRIPTING INTERFACE: " << energy; + _currentAvatarEnergy = energy; +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index 641da7518e..ff98ab4eb7 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -16,6 +16,8 @@ #include #include +#include +#include #include #include @@ -57,6 +59,8 @@ void RayToEntityIntersectionResultFromScriptValue(const QScriptValue& object, Ra /// handles scripting of Entity commands from JS passed to assigned clients class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency { Q_OBJECT + + Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy) public: EntityScriptingInterface(); @@ -67,7 +71,8 @@ public: void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) { _entitiesScriptEngine = engine; } - + double calculateCost(float mass, float oldVelocity, float newVelocity); + Q_INVOKABLE void addCostFunction(QScriptValue costFunction); public slots: // returns true if the DomainServer will allow this Node/Avatar to make changes @@ -163,6 +168,7 @@ public slots: Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name); Q_INVOKABLE QStringList getJointNames(const QUuid& entityID); + signals: void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision); @@ -188,6 +194,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); void clearingEntities(); + void debitEnergySource(double value); private: bool actionWorker(const QUuid& entityID, std::function actor); @@ -205,6 +212,11 @@ private: EntityTreePointer _entityTree; EntitiesScriptEngineProvider* _entitiesScriptEngine = nullptr; + QScriptValue* _costFunction = nullptr; + float _currentAvatarEnergy; + float getCurrentAvatarEnergy() { return _currentAvatarEnergy; } + void setCurrentAvatarEnergy(float energy); + float ENTITY_MANIPULATION_MULTIPLIER = { 0.01f }; }; #endif // hifi_EntityScriptingInterface_h From 6ca17a904aaabffaf50f1ec5ab6854d23b209010 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 1 Feb 2016 09:58:24 -0800 Subject: [PATCH 02/26] merge James' doppelganger changes into Tony's --- .../doppelganger.js | 317 +++++++++++++++++- 1 file changed, 310 insertions(+), 7 deletions(-) rename examples/{example/avatarcontrol => dressing_room}/doppelganger.js (55%) diff --git a/examples/example/avatarcontrol/doppelganger.js b/examples/dressing_room/doppelganger.js similarity index 55% rename from examples/example/avatarcontrol/doppelganger.js rename to examples/dressing_room/doppelganger.js index 09462156b1..ca3446ece7 100644 --- a/examples/example/avatarcontrol/doppelganger.js +++ b/examples/dressing_room/doppelganger.js @@ -12,11 +12,14 @@ // To-Do: mirror joints, rotate avatar fully, automatically get avatar fbx, make sure dimensions for avatar are right when u bring it in var TEST_MODEL_URL = 'https://s3.amazonaws.com/hifi-public/ozan/avatars/albert/albert/albert.fbx'; +var MIRRORED_ENTITY_SCRIPT_URL = Script.resolvePath('mirroredEntity.js'); +var FREEZE_TOGGLER_SCRIPT_URL = Script.resolvePath('freezeToggler.js?' + Math.random(0, 1000)) +var THROTTLE = true; +var THROTTLE_RATE = 100; +var MIRROR_JOINT_DATA = true; var doppelgangers = []; -var MIRROR_JOINT_DATA = true; - function Doppelganger(avatar) { this.initialProperties = { name: 'Hifi-Doppelganger', @@ -25,6 +28,15 @@ function Doppelganger(avatar) { // dimensions: getAvatarDimensions(avatar), position: putDoppelgangerAcrossFromAvatar(this, avatar), rotation: rotateDoppelgangerTowardAvatar(this, avatar), + dynamic: false, + collisionless: false, + script: FREEZE_TOGGLER_SCRIPT_URL, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false, + wantsTrigger: true + } + }) }; this.id = createDoppelgangerEntity(this); @@ -297,10 +309,82 @@ function createDoppelgangerEntity(doppelganger) { function putDoppelgangerAcrossFromAvatar(doppelganger, avatar) { var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0); - var basePosition = Vec3.sum(avatar.position, Vec3.multiply(1.5, Quat.getFront(avatarRot))); - return basePosition; + var position; + + var ids = Entities.findEntities(MyAvatar.position, 20); + var hasBase = false; + for (var i = 0; i < ids.length; i++) { + var entityID = ids[i]; + var props = Entities.getEntityProperties(entityID, "name"); + var name = props.name; + if (name === "Hifi-Dressing-Room-Base") { + var details = Entities.getEntityProperties(entityID, ["position", "dimensions"]); + details.position.y += getAvatarFootOffset(); + details.position.y += details.dimensions.y / 2; + position = details.position; + hasBase = true; + } + } + + if (hasBase === false) { + position = Vec3.sum(avatar.position, Vec3.multiply(1.5, Quat.getFront(avatarRot))); + + } + + return position; } +function getAvatarFootOffset() { + var data = getJointData(); + var upperLeg, lowerLeg, foot, toe, toeTop; + data.forEach(function(d) { + + var jointName = d.joint; + if (jointName === "RightUpLeg") { + upperLeg = d.translation.y; + } + if (jointName === "RightLeg") { + lowerLeg = d.translation.y; + } + if (jointName === "RightFoot") { + foot = d.translation.y; + } + if (jointName === "RightToeBase") { + toe = d.translation.y; + } + if (jointName === "RightToe_End") { + toeTop = d.translation.y + } + }) + + var myPosition = MyAvatar.position; + var offset = upperLeg + lowerLeg + foot + toe + toeTop; + offset = offset / 100; + return offset +} + +{ + var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0); + + var ids = Entities.findEntities(MyAvatar.position, 20); + var hasBase = false; + for (var i = 0; i < ids.length; i++) { + var entityID = ids[i]; + var props = Entities.getEntityProperties(entityID, "name"); + var name = props.name; + if (name === "Hifi-Dressing-Room-Base") { + var details = Entities.getEntityProperties(entityID, "rotation"); + avatarRot = details.rotation; + } + } + if (hasBase === false) { + avatarRot = Vec3.multiply(-1, avatarRot); + } + return avatarRot; +} + +var isConnected = false; + function getAvatarDimensions(avatar) { return dimensions; } @@ -312,15 +396,27 @@ function rotateDoppelgangerTowardAvatar(doppelganger, avatar) { } function connectDoppelgangerUpdates() { - // Script.update.connect(updateDoppelganger); - Script.setInterval(updateDoppelganger, 100); + Script.update.connect(updateDoppelganger); + isConnected = true; } function disconnectDoppelgangerUpdates() { Script.update.disconnect(updateDoppelganger); + isConnected = false; } +var sinceLastUpdate = 0; + function updateDoppelganger() { + if (THROTTLE === true) { + sinceLastUpdate = sinceLastUpdate + deltaTime * 100; + if (sinceLastUpdate > THROTTLE_RATE) { + sinceLastUpdate = 0; + } else { + return; + } + } + var absoluteXforms = buildAbsoluteXformsFromMyAvatar(); if (MIRROR_JOINT_DATA) { var mirroredAbsoluteXforms = []; @@ -340,6 +436,211 @@ function updateDoppelganger() { }); } +function subscribeToWearableMessages() { + Messages.subscribe('Hifi-Doppelganger-Wearable'); + Messages.messageReceived.connect(handleWearableMessages); +} + +function subscribeToWearableMessagesForAvatar() { + Messages.subscribe('Hifi-Doppelganger-Wearable-Avatar'); + Messages.messageReceived.connect(handleWearableMessages); +} + + +function subscribeToFreezeMessages() { + Messages.subscribe('Hifi-Doppelganger-Freeze'); + Messages.messageReceived.connect(handleFreezeMessages); +} + +function handleFreezeMessages(channel, message, sender) { + if (channel !== 'Hifi-Doppelganger-Freeze') { + return; + } + if (sender !== MyAvatar.sessionUUID) { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + print('error parsing wearable message'); + } + print('MESSAGE ACTION::' + parsedMessage.action) + if (parsedMessage.action === 'freeze') { + print('ACTUAL FREEZE') + disconnectDoppelgangerUpdates(); + } + if (parsedMessage.action === 'unfreeze') { + print('ACTUAL UNFREEZE') + + connectDoppelgangerUpdates(); + } + +} + +var wearablePairs = []; + +function handleWearableMessages(channel, message, sender) { + if (channel !== 'Hifi-Doppelganger-Wearable' || 'Hifi-Doppelganger-Wearable-Avatar') { + return; + } + + + if (sender !== MyAvatar.sessionUUID) { + return; + } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + print('error parsing wearable message'); + } + print('parsed message!!!') + + if (channel === 'Hifi-Doppelganger-Wearable') { + mirrorEntitiesForDoppelganger(doppelgangers[0], parsedMessage); + } + if (channel === 'Hifi-Doppelganger-Wearable') { + mirrorEntitiesForAvatar(parsedMessge); + } + +} + +function mirrorEntitiesForAvatar(avatar, parsedMessage) { + var action = parsedMessage.action; + print('IN MIRROR ENTITIES CALL' + action) + + var baseEntity = parsedMessage.baseEntity; + + var wearableProps = Entities.getEntityProperties(baseEntity); + print('WEARABLE PROPS::') + delete wearableProps.id; + delete wearableProps.created; + delete wearableProps.age; + delete wearableProps.ageAsText; + + var joint = wearableProps.parentJointIndex; + if (action === 'add') { + print('IN AVATAR ADD') + } + if (action === 'remove') { + print('IN AVATAR REMOVE') + } + + if (action === 'update') { + print('IN AVATAR UPDATE') + } + +} + +function mirrorEntitiesForDoppelganger(doppelganger, parsedMessage) { + var doppelgangerProps = Entities.getEntityProperties(doppelganger.id); + + var action = parsedMessage.action; + print('IN MIRROR ENTITIES CALL' + action) + + var baseEntity = parsedMessage.baseEntity; + + var wearableProps = Entities.getEntityProperties(baseEntity); + + print('WEARABLE PROPS::') + delete wearableProps.id; + delete wearableProps.created; + delete wearableProps.age; + delete wearableProps.ageAsText; + //delete wearableProps.position; + // add to dg + // add to avatar + // moved item on dg + // moved item on avatar + // remove item from dg + // remove item from avatar + + var joint = wearableProps.parentJointIndex; + if (action === 'add') { + print('IN DOPPELGANGER ADD'); + + wearableProps.parentID = doppelganger.id; + wearableProps.parentJointIndex = joint; + + //create a new one + wearableProps.script = MIRRORED_ENTITY_SCRIPT_URL; + wearableProps.name = 'Hifi-Doppelganger-Mirrored-Entity'; + wearableProps.userData = JSON.stringify({ + doppelgangerKey: { + baseEntity: baseEntity, + doppelganger: doppelganger.id + } + }) + var mirrorEntity = Entities.addEntity(wearableProps); + + var mirrorEntityProps = Entities.getEntityProperties(mirrorEntity) + print('MIRROR PROPS::' + JSON.stringify(mirrorEntityProps)) + var wearablePair = { + baseEntity: baseEntity, + mirrorEntity: mirrorEntity + } + + wearablePairs.push(wearablePair); + } + + if (action === 'update') { + wearableProps.parentID = doppelganger.id; + + var mirrorEntity = getMirrorEntityForBaseEntity(baseEntity); + // print('MIRROR ENTITY, newPosition' + mirrorEntity + ":::" + JSON.stringify(newPosition)) + Entities.editEntity(mirrorEntity, wearableProps) + } + + if (action === 'remove') { + Entities.deleteEntity(getMirrorEntityForBaseEntity(baseEntity)) + wearablePairs = wearablePairs.filter(function(obj) { + return obj.baseEntity !== baseEntity; + }); + } + + if (action === 'updateBase') { + //this gets called when the mirrored entity gets grabbed. now we move the + var mirrorEntityProperties = Entities.getEntityProperties(message.mirrorEntity) + var doppelgangerToMirrorEntity = Vec3.subtract(doppelgangerProps.position, mirrorEntityProperties.position); + var newPosition = Vec3.sum(MyAvatar.position, doppelgangerToMirrorEntity); + + delete mirrorEntityProperties.id; + delete mirrorEntityProperties.created; + delete mirrorEntityProperties.age; + delete mirrorEntityProperties.ageAsText; + mirrorEntityProperties.position = newPosition; + mirrorEntityProperties.parentID = MyAvatar.sessionUUID; + Entities.editEntity(message.baseEntity, mirrorEntityProperties); + } +} + +function getMirrorEntityForBaseEntity(baseEntity) { + var result = wearablePairs.filter(function(obj) { + return obj.baseEntity === baseEntity; + }); + if (result.length === 0) { + return false; + } else { + return result[0].mirrorEntity + } +} + +function getBaseEntityForMirrorEntity(mirrorEntity) { + var result = wearablePairs.filter(function(obj) { + return obj.mirrorEntity === mirrorEntity; + }); + if (result.length === 0) { + return false; + } else { + return result[0].baseEntity + } +} + function makeDoppelgangerForMyAvatar() { var doppelganger = createDoppelganger(MyAvatar); doppelgangers.push(doppelganger); @@ -349,7 +650,9 @@ function makeDoppelgangerForMyAvatar() { makeDoppelgangerForMyAvatar(); function cleanup() { - //disconnectDoppelgangerUpdates(); + if (isConnected === true) { + disconnectDoppelgangerUpdates(); + } doppelgangers.forEach(function(doppelganger) { Entities.deleteEntity(doppelganger.id); From 182ae22b44a50472a5fa73c87cb5c4a1e5b59354 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 1 Feb 2016 09:58:35 -0800 Subject: [PATCH 03/26] merge James' doppelganger changes into Tony's --- .../dressing_room/createPlatformWithLights.js | 160 ++++++++++++++++++ .../dressing_room/createTableWithItems.js | 117 +++++++++++++ examples/dressing_room/freezeToggler.js | 76 +++++++++ examples/dressing_room/loadingAreaEntity.js | 34 ++++ examples/dressing_room/mirroredEntity.js | 48 ++++++ examples/dressing_room/setupDressingRoom.js | 11 ++ examples/dressing_room/wearablesManager.js | 138 +++++++++++++++ 7 files changed, 584 insertions(+) create mode 100644 examples/dressing_room/createPlatformWithLights.js create mode 100644 examples/dressing_room/createTableWithItems.js create mode 100644 examples/dressing_room/freezeToggler.js create mode 100644 examples/dressing_room/loadingAreaEntity.js create mode 100644 examples/dressing_room/mirroredEntity.js create mode 100644 examples/dressing_room/setupDressingRoom.js create mode 100644 examples/dressing_room/wearablesManager.js diff --git a/examples/dressing_room/createPlatformWithLights.js b/examples/dressing_room/createPlatformWithLights.js new file mode 100644 index 0000000000..10061163fd --- /dev/null +++ b/examples/dressing_room/createPlatformWithLights.js @@ -0,0 +1,160 @@ +// +// createDressingPlatform.js +// +// Created by James B. Pollack @imgntn on 1/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This script shows how to hook up a model entity to your avatar to act as a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var basePlatform; +var basePosition = Vec3.sum(Vec3.sum(MyAvatar.position, { + x: 0, + y: -1, + z: 0 +}), Vec3.multiply(2, Quat.getFront(Camera.getOrientation()))); + +var loadArea; +var LOAD_AREA_SCRIPT_URL = Script.resolvePath('loadingAreaEntity.js'); + +function createBasePlatform() { + var properties = { + type: 'Box', + name: 'Hifi-Dressing-Room-Base', + dimensions: { + x: 4, + y: 0.10, + z: 4 + }, + color: { + red: 255, + green: 0, + blue: 255 + }, + position: basePosition, + collisionsWillMove: false, + ignoreForCollisions: false, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + } + basePlatform = Entities.addEntity(properties); +} + +function createLoadArea() { + // on enter, load the wearables manager and the doppelganger manager; + // on exit, stop the scripts (at least call cleanup); + var properties = { + type: 'Box', + shapeType: 'box', + name: 'Hifi-Dressing-Room-Load-Area', + dimensions: { + x: 0.25, + y: 0.25, + z: 0.25 + }, + color: { + red: 0, + green: 255, + blue: 0 + }, + visible: true, + position: basePosition, + collisionsWillMove: false, + ignoreForCollisions: true, + script: LOAD_AREA_SCRIPT_URL, + + } + loadArea = Entities.addEntity(properties); +} +var lights = []; + +function createLightAtPosition(position) { + var lightProperties = { + name: 'Hifi-Spotlight', + type: "Light", + isSpotlight: true, + dimensions: { + x: 2, + y: 2, + z: 8 + }, + color: { + red: 255, + green: 255, + blue: 255 + }, + intensity: 0.035, + exponent: 1, + cutoff: 40, + lifetime: -1, + position: position, + rotation: getLightRotation(position) + }; + + light = Entities.addEntity(lightProperties); + lights.push(light); +} + +function createLights() { + var lightPosition = { + x: basePosition.x - 2, + y: basePosition.y + 3, + z: basePosition.z + } + createLightAtPosition(lightPosition); + + var lightPosition = { + x: basePosition.x + 2, + y: basePosition.y + 3, + z: basePosition.z + } + + createLightAtPosition(lightPosition); + var lightPosition = { + x: basePosition.x, + y: basePosition.y + 3, + z: basePosition.z + 2 + } + + createLightAtPosition(lightPosition); + var lightPosition = { + x: basePosition.x, + y: basePosition.y + 3, + z: basePosition.z - 2 + } + + createLightAtPosition(lightPosition); + +} + +function getLightRotation(myPosition) { + + var sourceToTargetVec = Vec3.subtract(basePosition, myPosition); + var emitOrientation = Quat.rotationBetween(Vec3.UNIT_NEG_Z, sourceToTargetVec); + + return emitOrientation +} + +function init() { + createBasePlatform(); + createLights(); + // createLoadArea(); +} + + +function cleanup() { + Entities.deleteEntity(basePlatform); + + while (lights.length > 0) { + Entities.deleteEntity(lights.pop()); + } + // Entities.deleteEntity(loadArea); +} +init(); +Script.scriptEnding.connect(cleanup) \ No newline at end of file diff --git a/examples/dressing_room/createTableWithItems.js b/examples/dressing_room/createTableWithItems.js new file mode 100644 index 0000000000..b8119e3077 --- /dev/null +++ b/examples/dressing_room/createTableWithItems.js @@ -0,0 +1,117 @@ +// +// createTableWithItems.js +// +// Created by James B. Pollack @imgntn on 1/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This script shows how to hook up a model entity to your avatar to act as a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +var table, wearable; + +var TABLE_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/james/doppelganger/table.FBX'; +var TABLE_DIMENSIONS = { + x: 0.76, + y: 1.06, + z: 0.76 +}; + +function createTable() { + var avatarRot = Quat.fromPitchYawRollDegrees(0, MyAvatar.bodyYaw, 0.0); + var position, rotation; + var ids = Entities.findEntities(MyAvatar.position, 20); + var hasBase = false; + for (var i = 0; i < ids.length; i++) { + var entityID = ids[i]; + var props = Entities.getEntityProperties(entityID, "name"); + var name = props.name; + if (name === "Hifi-Dressing-Room-Base") { + var details = Entities.getEntityProperties(entityID, ["position", "dimensions", "rotation"]); + var rightVector = Quat.getRight(details.rotation); + var rightDistance = 1.5; + position = Vec3.sum(Vec3.multiply(rightDistance, rightVector), details.position); + position.y = details.position.y += TABLE_DIMENSIONS.y / 2 + rotation = details.rotation; + hasBase = true; + } + } + + if (hasBase === false) { + position = Vec3.sum(MyAvatar.position, Vec3.multiply(1.5, Quat.getFront(avatarRot))); + rotation = avatarRot; + } + + var tableProperties = { + name: 'Hifi-Dressing-Room-Table', + type: 'Model', + shapeType:'box', + modelURL: TABLE_MODEL_URL, + dimensions: TABLE_DIMENSIONS, + position: position, + rotation: rotation, + collisionsWillMove: false, + ignoreForCollisions: false, + userData: JSON.stringify({ + grabbableKey: { + grabbable: false + } + }) + } + print('TABLE PROPS', JSON.stringify(tableProperties)) + table = Entities.addEntity(tableProperties); +} + + +function createWearable() { + var tableProperties = Entities.getEntityProperties(table); + var properties = { + type: 'Model', + modelURL: 'https://s3.amazonaws.com/hifi-public/tony/cowboy-hat.fbx', + name: 'Hifi-Wearable', + dimensions: { + x: 0.25, + y: 0.25, + z: 0.25 + }, + color: { + red: 0, + green: 255, + blue: 0 + }, + position: { + x: tableProperties.position.x, + y: tableProperties.position.y + tableProperties.dimensions.y / 1.5, + z: tableProperties.position.z + }, + userData: JSON.stringify({ + "grabbableKey": { + "invertSolidWhileHeld": false + }, + "wearable": { + "joints": ["head", "Head", "hair", "neck"] + }, + handControllerKey: { + disableReleaseVelocity: true, + disableMoveWithHead: true, + } + }) + } + wearable = Entities.addEntity(properties); +} + +function init() { + createTable(); + createWearable(); +} + +function cleanup() { + Entities.deleteEntity(table); + Entities.deleteEntity(wearable); + +} +init(); +Script.scriptEnding.connect(cleanup) \ No newline at end of file diff --git a/examples/dressing_room/freezeToggler.js b/examples/dressing_room/freezeToggler.js new file mode 100644 index 0000000000..6b83a606da --- /dev/null +++ b/examples/dressing_room/freezeToggler.js @@ -0,0 +1,76 @@ +// +// dopppelgangerEntity.js +// +// Created by James B. Pollack @imgntn on 1/6/2016 +// Copyright 2016 High Fidelity, Inc. +// +// for freezing / unfreezing the doppelganger +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + var COUNTDOWN_LENGTH = 0; + var _this; + + Dopppelganger = function() { + + _this = this; + }; + + Dopppelganger.prototype = { + isFrozen: false, + startNearTrigger: function() { + print('DOPPELGANGER NEAR TRIGGER') + }, + startFarTrigger: function() { + print('DOPPELGANGER FAR TRIGGER') + if (this.isFrozen === false) { + this.freeze(); + } else { + this.unfreeze(); + } + + }, + clickReleaseOnEntity: function(entityID, mouseEvent) { + print('DOPPELGANGER CLICK') + if (!mouseEvent.isLeftButton) { + return; + } + if (this.isFrozen === false) { + this.freeze(); + } else { + this.unfreeze(); + } + + }, + freeze: function() { + print('FREEZE YO') + this.isFrozen = true; + var data = { + action: 'freeze' + } + + Script.setTimeout(function() { + Messages.sendMessage('Hifi-Doppelganger-Freeze', JSON.stringify(data)); + }, COUNTDOWN_LENGTH) + + }, + unfreeze: function() { + this.isFrozen = false; + var data = { + action: 'unfreeze' + } + Messages.sendMessage('Hifi-Doppelganger-Freeze', JSON.stringify(data)); + }, + + preload: function(entityID) { + this.entityID = entityID; + this.initialProperties = Entities.getEntityProperties(this.entityID); + this.userData = JSON.parse(this.initialProperties.userData); + }, + }; + + return new Dopppelganger(); +}) \ No newline at end of file diff --git a/examples/dressing_room/loadingAreaEntity.js b/examples/dressing_room/loadingAreaEntity.js new file mode 100644 index 0000000000..73ea142d59 --- /dev/null +++ b/examples/dressing_room/loadingAreaEntity.js @@ -0,0 +1,34 @@ +// +// loadingAreaEntity.js +// +// Created by James B. Pollack @imgntn on 1/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This script shows how to hook up a model entity to your avatar to act as a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var WEARABLES_MANAGER_SCRIPT = Script.resolvePath('wearablesManager.js'); + var DOPPELGANGER_SCRIPT = Script.resolvePath('doppelganger.js'); + var CREATE_TEST_WEARABLE_SCRIPT = Script.resolvePath('createTestWearable.js'); + this.preload = function(entityID) { + print("preload(" + entityID + ")"); + }; + + this.enterEntity = function(entityID) { + print("enterEntity(" + entityID + ")"); + // Script.load(WEARABLES_MANAGER_SCRIPT); + // Script.load(DOPPELGANGER_SCRIPT); + // Script.load(CREATE_TEST_WEARABLE_SCRIPT); + + }; + + this.leaveEntity = function(entityID) { + print("leaveEntity(" + entityID + ")"); + }; + +}) \ No newline at end of file diff --git a/examples/dressing_room/mirroredEntity.js b/examples/dressing_room/mirroredEntity.js new file mode 100644 index 0000000000..0310b21f96 --- /dev/null +++ b/examples/dressing_room/mirroredEntity.js @@ -0,0 +1,48 @@ +// +// mirroredEntity.js +// +// Created by James B. Pollack @imgntn on 1/6/2016 +// Copyright 2016 High Fidelity, Inc. +// +// when grabbed, this entity relays updates to update the base entity +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +(function() { + + var _this; + + MirroredEntity = function() { + _this = this; + }; + + MirroredEntity.prototype = { + startNearGrab: function () { + print("I was just grabbed... entity:" + this.entityID); + }, + continueNearGrab: function () { + print("I am still being grabbed... entity:" + this.entityID); + var data = { + action:'updateBase', + baseEntity:this.userData.doppelgangerKey.baseEntity, + mirrorEntity:this.entityID, + doppelganger:this.userData.doppelgangerKey.doppelganger + } + Messages.sendMessage('Hifi-Doppelganger-Wearable',data) + }, + + releaseGrab: function () { + print("I was released... entity:" + this.entityID); + }, + + preload: function(entityID) { + this.entityID = entityID; + this.initialProperties = Entities.getEntityProperties(this.entityID); + this.userData = JSON.parse(this.initialProperties.userData); + }, + }; + + return new MirroredEntity(); +}) diff --git a/examples/dressing_room/setupDressingRoom.js b/examples/dressing_room/setupDressingRoom.js new file mode 100644 index 0000000000..6de346ace2 --- /dev/null +++ b/examples/dressing_room/setupDressingRoom.js @@ -0,0 +1,11 @@ +var createPlatformWithLights = Script.resolvePath('createPlatformWithLights.js?'+Math.random(0,100)); +Script.include(createPlatformWithLights); +var createTableWithItems = Script.resolvePath('createTableWithItems.js?'+Math.random(0,100)); +Script.include(createTableWithItems); +var doppelganger = Script.resolvePath('doppelganger.js?'+Math.random(0,100)); +Script.include(doppelganger); +var wearablesManager = Script.resolvePath('wearablesManager.js?'+Math.random(0,100)); +Script.include(wearablesManager); +var handControllerGrab = Script.resolvePath('../controllers/handControllerGrab.js?'+Math.random(0,100)); +Script.include(handControllerGrab); +//put it in an interior diff --git a/examples/dressing_room/wearablesManager.js b/examples/dressing_room/wearablesManager.js new file mode 100644 index 0000000000..99623023d0 --- /dev/null +++ b/examples/dressing_room/wearablesManager.js @@ -0,0 +1,138 @@ +// +// wearablesManager.js +// +// Created by James B. Pollack @imgntn on 1/7/2016 +// Copyright 2016 High Fidelity, Inc. +// +// This script handles messages from the grab script related to wearables, and interacts with a doppelganger. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// todo: +// add camera countdown / freezing unfreezing the doppelganger +// add ability to drop wearables on doppelganger +// which means creating a mirror entity on the avatar ... + +Script.include("../libraries/utils.js"); + +var NULL_UUID = "{00000000-0000-0000-0000-000000000000}"; +var DEFAULT_WEARABLE_DATA = { + joints: {} +}; + +function WearablesManager() { + + this.wearables = []; + + this.subscribeToMessages = function() { + Messages.subscribe('Hifi-Object-Manipulation'); + Messages.messageReceived.connect(this.handleWearableMessages); + } + + this.handleWearableMessages = function(channel, message, sender) { + // print('wearablesManager messageReceived ::: ' + channel + " ::: " + message) + if (channel !== 'Hifi-Object-Manipulation') { + return; + } + // if (sender !== MyAvatar.sessionUUID) { + // print('wearablesManager got message from wrong sender'); + // return; + // } + + var parsedMessage = null; + + try { + parsedMessage = JSON.parse(message); + } catch (e) { + print('error parsing wearable message'); + } + + if (parsedMessage.action === 'update' && manager.wearables.length !== 0) { + manager.updateWearable(parsedMessage.grabbedEntity) + } else if (parsedMessage.action === 'update' && manager.wearables.length === 0) { + } else if (parsedMessage.action === 'release') { + manager.checkIfWearable(parsedMessage.grabbedEntity) + } else { + print('unknown actions: ' + parsedMessage.action); + } + } + + this.updateWearable = function(grabbedEntity) { + if (this.wearables.length > 0) { + + //only do this check if we already have some wearables for the doppelganger + var hasWearableAlready = this.wearables.indexOf(grabbedEntity); + var props = Entities.getEntityProperties(grabbedEntity); + + if (hasWearableAlready > -1) { + var data = { + action: 'update', + baseEntity: grabbedEntity, + } + + Messages.sendMessage('Hifi-Doppelganger-Wearable', JSON.stringify(data)) + } + } + } + + this.checkIfWearableOnDoppelganger = function(grabbedEntity) { + var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints; + + var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID"]); + if (props.parentID === NULL_UUID || props.parentID === MyAvatar.sessionUUID) { + var bestJointName = ""; + var bestJointIndex = -1; + var bestJointDistance = 0; + for (var jointName in allowedJoints) { + //do this for the model + var jointIndex = Entities.getJointIndex(doppelganger.id,jointName); + var jointPosition = Entities.getJointPosition(doppelganger.id,jointIndex); + var distanceFromJoint = Vec3.distance(jointPosition, props.position); + if (distanceFromJoint < 0.4) { + if (bestJointIndex == -1 || distanceFromJoint < bestJointDistance) { + bestJointName = jointName; + bestJointIndex = jointIndex; + bestJointDistance = distanceFromJoint; + } + } + } + + if (bestJointIndex != -1) { + Entities.editEntity(grabbedEntity, { + parentID: doppelganger.id, + parentJointIndex: bestJointIndex + }); + + if (this.wearables.indexOf(grabbedEntity) < 0) { + var data = { + action: 'addToDoppelganger', + baseEntity: grabbedEntity, + } + Messages.sendMessage('Hifi-Doppelganger-Wearable-Avatar', JSON.stringify(data)); + this.wearables.push(grabbedEntity) + } + } else { + Entities.editEntity(grabbedEntity, { + parentID: NULL_UUID + }); + + var hasWearableAlready = this.wearables.indexOf(grabbedEntity); + if (hasWearableAlready > -1) { + var data = { + action: 'removeFromDoppelganger', + baseEntity: grabbedEntity + } + + Messages.sendMessage('Hifi-Doppelganger-Wearable-Avatar', JSON.stringify(data)); + } + + this.wearables.splice(hasWearableAlready, 1) + } + } + } +} + +var manager = new WearablesManager(); +manager.subscribeToMessages(); From 260d27f4d7e3039473f9acb8475f12c9cccbd056 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Tue, 2 Feb 2016 14:31:20 -0800 Subject: [PATCH 04/26] increase drop-on-joint radius. fix bug that kept from dropping on hips (index 0). save adjustments if in dressing room --- examples/attachedEntitiesManager.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/examples/attachedEntitiesManager.js b/examples/attachedEntitiesManager.js index 01f8f861c9..4d3235667c 100644 --- a/examples/attachedEntitiesManager.js +++ b/examples/attachedEntitiesManager.js @@ -18,7 +18,7 @@ var DEFAULT_WEARABLE_DATA = { }; -var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.4; +var MINIMUM_DROP_DISTANCE_FROM_JOINT = 0.8; var ATTACHED_ENTITY_SEARCH_DISTANCE = 10.0; var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES"; var DRESSING_ROOM_DISTANCE = 2.0; @@ -154,7 +154,7 @@ function AttachedEntitiesManager() { continue; } var jointIndex = MyAvatar.getJointIndex(jointName); - if (jointIndex > 0) { + if (jointIndex >= 0) { var jointPosition = MyAvatar.getJointPosition(jointIndex); var distanceFromJoint = Vec3.distance(jointPosition, props.position); if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) { @@ -175,7 +175,9 @@ function AttachedEntitiesManager() { }; if (bestJointOffset && bestJointOffset.constructor === Array && bestJointOffset.length > 1) { - if (!this.avatarIsInDressingRoom()) { + if (this.avatarIsInDressingRoom()) { + this.updateRelativeOffsets(grabbedEntity); + } else { // don't snap the entity to the preferred position if the avatar is in the dressing room. wearProps.localPosition = bestJointOffset[0]; wearProps.localRotation = bestJointOffset[1]; From 0587981a20db969b4a78c00b0405da6a0f9a53c6 Mon Sep 17 00:00:00 2001 From: "James B. Pollack" Date: Tue, 2 Feb 2016 15:07:38 -0800 Subject: [PATCH 05/26] add doppleganger features --- examples/acScripts/botProceduralWayPoints.js | 2 +- examples/dressing_room/doppelganger.js | 93 +++++++------------- 2 files changed, 31 insertions(+), 64 deletions(-) diff --git a/examples/acScripts/botProceduralWayPoints.js b/examples/acScripts/botProceduralWayPoints.js index fbe8b03c3d..1642d0f4db 100644 --- a/examples/acScripts/botProceduralWayPoints.js +++ b/examples/acScripts/botProceduralWayPoints.js @@ -21,7 +21,7 @@ //For procedural walk animation HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/"; -Script.include(HIFI_PUBLIC_BUCKET + "scripts/proceduralAnimationAPI.js"); +Script.include(HIFI_PUBLIC_BUCKET + "scripts/acScripts/proceduralAnimationAPI.js"); var procAnimAPI = new ProcAnimAPI(); diff --git a/examples/dressing_room/doppelganger.js b/examples/dressing_room/doppelganger.js index ca3446ece7..10f6468e9f 100644 --- a/examples/dressing_room/doppelganger.js +++ b/examples/dressing_room/doppelganger.js @@ -12,12 +12,12 @@ // To-Do: mirror joints, rotate avatar fully, automatically get avatar fbx, make sure dimensions for avatar are right when u bring it in var TEST_MODEL_URL = 'https://s3.amazonaws.com/hifi-public/ozan/avatars/albert/albert/albert.fbx'; + +var MIRROR_JOINT_DATA = true; var MIRRORED_ENTITY_SCRIPT_URL = Script.resolvePath('mirroredEntity.js'); var FREEZE_TOGGLER_SCRIPT_URL = Script.resolvePath('freezeToggler.js?' + Math.random(0, 1000)) -var THROTTLE = true; +var THROTTLE = false; var THROTTLE_RATE = 100; -var MIRROR_JOINT_DATA = true; - var doppelgangers = []; function Doppelganger(avatar) { @@ -28,8 +28,8 @@ function Doppelganger(avatar) { // dimensions: getAvatarDimensions(avatar), position: putDoppelgangerAcrossFromAvatar(this, avatar), rotation: rotateDoppelgangerTowardAvatar(this, avatar), - dynamic: false, - collisionless: false, + collisionsWillMove: false, + ignoreForCollisions: false, script: FREEZE_TOGGLER_SCRIPT_URL, userData: JSON.stringify({ grabbableKey: { @@ -43,7 +43,6 @@ function Doppelganger(avatar) { this.avatar = avatar; return this; } - function getJointData(avatar) { var allJointData = []; var jointNames = MyAvatar.jointNames; @@ -363,7 +362,12 @@ function getAvatarFootOffset() { return offset } -{ + +function getAvatarDimensions(avatar) { + return dimensions; +} + +function rotateDoppelgangerTowardAvatar(doppelganger, avatar) { var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0); var ids = Entities.findEntities(MyAvatar.position, 20); @@ -385,30 +389,23 @@ function getAvatarFootOffset() { var isConnected = false; -function getAvatarDimensions(avatar) { - return dimensions; -} - -function rotateDoppelgangerTowardAvatar(doppelganger, avatar) { - var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0); - avatarRot = Vec3.multiply(-1, avatarRot); - return avatarRot; -} - function connectDoppelgangerUpdates() { Script.update.connect(updateDoppelganger); isConnected = true; } function disconnectDoppelgangerUpdates() { - Script.update.disconnect(updateDoppelganger); + print('SHOULD DISCONNECT') + if (isConnected === true) { + Script.update.disconnect(updateDoppelganger); + } isConnected = false; } var sinceLastUpdate = 0; -function updateDoppelganger() { - if (THROTTLE === true) { +function updateDoppelganger(deltaTime) { + if (THROTTLE === true) { sinceLastUpdate = sinceLastUpdate + deltaTime * 100; if (sinceLastUpdate > THROTTLE_RATE) { sinceLastUpdate = 0; @@ -417,6 +414,7 @@ function updateDoppelganger() { } } + var absoluteXforms = buildAbsoluteXformsFromMyAvatar(); if (MIRROR_JOINT_DATA) { var mirroredAbsoluteXforms = []; @@ -436,17 +434,17 @@ function updateDoppelganger() { }); } +function makeDoppelgangerForMyAvatar() { + var doppelganger = createDoppelganger(MyAvatar); + doppelgangers.push(doppelganger); + connectDoppelgangerUpdates(); +} + function subscribeToWearableMessages() { Messages.subscribe('Hifi-Doppelganger-Wearable'); Messages.messageReceived.connect(handleWearableMessages); } -function subscribeToWearableMessagesForAvatar() { - Messages.subscribe('Hifi-Doppelganger-Wearable-Avatar'); - Messages.messageReceived.connect(handleWearableMessages); -} - - function subscribeToFreezeMessages() { Messages.subscribe('Hifi-Doppelganger-Freeze'); Messages.messageReceived.connect(handleFreezeMessages); @@ -487,7 +485,6 @@ function handleWearableMessages(channel, message, sender) { return; } - if (sender !== MyAvatar.sessionUUID) { return; } @@ -510,33 +507,6 @@ function handleWearableMessages(channel, message, sender) { } -function mirrorEntitiesForAvatar(avatar, parsedMessage) { - var action = parsedMessage.action; - print('IN MIRROR ENTITIES CALL' + action) - - var baseEntity = parsedMessage.baseEntity; - - var wearableProps = Entities.getEntityProperties(baseEntity); - print('WEARABLE PROPS::') - delete wearableProps.id; - delete wearableProps.created; - delete wearableProps.age; - delete wearableProps.ageAsText; - - var joint = wearableProps.parentJointIndex; - if (action === 'add') { - print('IN AVATAR ADD') - } - if (action === 'remove') { - print('IN AVATAR REMOVE') - } - - if (action === 'update') { - print('IN AVATAR UPDATE') - } - -} - function mirrorEntitiesForDoppelganger(doppelganger, parsedMessage) { var doppelgangerProps = Entities.getEntityProperties(doppelganger.id); @@ -604,7 +574,7 @@ function mirrorEntitiesForDoppelganger(doppelganger, parsedMessage) { } if (action === 'updateBase') { - //this gets called when the mirrored entity gets grabbed. now we move the + //this gets called when the mirrored entity gets grabbed. now we move the var mirrorEntityProperties = Entities.getEntityProperties(message.mirrorEntity) var doppelgangerToMirrorEntity = Vec3.subtract(doppelgangerProps.position, mirrorEntityProperties.position); var newPosition = Vec3.sum(MyAvatar.position, doppelgangerToMirrorEntity); @@ -641,13 +611,10 @@ function getBaseEntityForMirrorEntity(mirrorEntity) { } } -function makeDoppelgangerForMyAvatar() { - var doppelganger = createDoppelganger(MyAvatar); - doppelgangers.push(doppelganger); - connectDoppelgangerUpdates(); -} - makeDoppelgangerForMyAvatar(); +subscribeToWearableMessages(); +subscribeToWearableMessagesForAvatar(); +subscribeToFreezeMessages(); function cleanup() { if (isConnected === true) { @@ -655,8 +622,8 @@ function cleanup() { } doppelgangers.forEach(function(doppelganger) { + print('DOPPELGANGER' + doppelganger.id) Entities.deleteEntity(doppelganger.id); }); } - -Script.scriptEnding.connect(cleanup); +Script.scriptEnding.connect(cleanup); \ No newline at end of file From e6abc026c832e22c3e6c8b4342e59f4de5546c39 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 2 Feb 2016 17:02:29 -0800 Subject: [PATCH 06/26] AnimClip: mirror animation support --- libraries/animation/src/AnimClip.cpp | 26 +++++++++++-- libraries/animation/src/AnimClip.h | 8 +++- libraries/animation/src/AnimNodeLoader.cpp | 11 +++++- libraries/animation/src/AnimPose.cpp | 5 +++ libraries/animation/src/AnimPose.h | 1 + libraries/animation/src/AnimSkeleton.cpp | 45 +++++++++++++++++++++- libraries/animation/src/AnimSkeleton.h | 5 +++ libraries/animation/src/Rig.cpp | 4 +- 8 files changed, 97 insertions(+), 8 deletions(-) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index 90cd85e727..f8e045fc3b 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -15,12 +15,13 @@ bool AnimClip::usePreAndPostPoseFromAnim = false; -AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag) : +AnimClip::AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag) : AnimNode(AnimNode::Type::Clip, id), _startFrame(startFrame), _endFrame(endFrame), _timeScale(timeScale), _loopFlag(loopFlag), + _mirrorFlag(mirrorFlag), _frame(startFrame) { loadURL(url); @@ -49,6 +50,12 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, } if (_anim.size()) { + + // lazy creation of mirrored animation frames. + if (_mirrorFlag && _anim.size() != _mirrorAnim.size()) { + buildMirrorAnim(); + } + int prevIndex = (int)glm::floor(_frame); int nextIndex; if (_loopFlag && _frame >= _endFrame) { @@ -63,8 +70,8 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, prevIndex = std::min(std::max(0, prevIndex), frameCount - 1); nextIndex = std::min(std::max(0, nextIndex), frameCount - 1); - const AnimPoseVec& prevFrame = _anim[prevIndex]; - const AnimPoseVec& nextFrame = _anim[nextIndex]; + const AnimPoseVec& prevFrame = _mirrorFlag ? _mirrorAnim[prevIndex] : _anim[prevIndex]; + const AnimPoseVec& nextFrame = _mirrorFlag ? _mirrorAnim[nextIndex] : _anim[nextIndex]; float alpha = glm::fract(_frame); ::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]); @@ -162,9 +169,22 @@ void AnimClip::copyFromNetworkAnim() { } } + // mirrorAnim will be re-built on demand, if needed. + _mirrorAnim.clear(); + _poses.resize(skeletonJointCount); } +void AnimClip::buildMirrorAnim() { + assert(_skeleton); + + _mirrorAnim.clear(); + _mirrorAnim.reserve(_anim.size()); + for (auto& relPoses : _anim) { + _mirrorAnim.push_back(relPoses); + _skeleton->mirrorRelativePoses(_mirrorAnim.back()); + } +} const AnimPoseVec& AnimClip::getPosesInternal() const { return _poses; diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index 7d58ae4f9a..bb4f61ffc0 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -27,7 +27,7 @@ public: static bool usePreAndPostPoseFromAnim; - AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag); + AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag); virtual ~AnimClip() override; virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override; @@ -49,12 +49,16 @@ public: bool getLoopFlag() const { return _loopFlag; } void setLoopFlag(bool loopFlag) { _loopFlag = loopFlag; } + bool getMirrorFlag() const { return _mirrorFlag; } + void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; } + void loadURL(const QString& url); protected: virtual void setCurrentFrameInternal(float frame) override; void copyFromNetworkAnim(); + void buildMirrorAnim(); // for AnimDebugDraw rendering virtual const AnimPoseVec& getPosesInternal() const override; @@ -64,12 +68,14 @@ protected: // _anim[frame][joint] std::vector _anim; + std::vector _mirrorAnim; QString _url; float _startFrame; float _endFrame; float _timeScale; bool _loopFlag; + bool _mirrorFlag; float _frame; QString _startFrameVar; diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index 568da8dd63..b26f34f531 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -145,6 +145,14 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) { } \ bool NAME = NAME##_VAL.toBool() +#define READ_OPTIONAL_BOOL(NAME, JSON_OBJ, DEFAULT) \ + auto NAME##_VAL = JSON_OBJ.value(#NAME); \ + bool NAME = DEFAULT; \ + if (NAME##_VAL.isBool()) { \ + NAME = NAME##_VAL.toBool(); \ + } \ + do {} while (0) + #define READ_FLOAT(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \ auto NAME##_VAL = JSON_OBJ.value(#NAME); \ if (!NAME##_VAL.isDouble()) { \ @@ -222,13 +230,14 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& READ_FLOAT(endFrame, jsonObj, id, jsonUrl, nullptr); READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr); READ_BOOL(loopFlag, jsonObj, id, jsonUrl, nullptr); + READ_OPTIONAL_BOOL(mirrorFlag, jsonObj, false); READ_OPTIONAL_STRING(startFrameVar, jsonObj); READ_OPTIONAL_STRING(endFrameVar, jsonObj); READ_OPTIONAL_STRING(timeScaleVar, jsonObj); READ_OPTIONAL_STRING(loopFlagVar, jsonObj); - auto node = std::make_shared(id, url, startFrame, endFrame, timeScale, loopFlag); + auto node = std::make_shared(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag); if (!startFrameVar.isEmpty()) { node->setStartFrameVar(startFrameVar); diff --git a/libraries/animation/src/AnimPose.cpp b/libraries/animation/src/AnimPose.cpp index 0c6af2d5bd..439ef5336f 100644 --- a/libraries/animation/src/AnimPose.cpp +++ b/libraries/animation/src/AnimPose.cpp @@ -51,6 +51,11 @@ AnimPose AnimPose::inverse() const { return AnimPose(glm::inverse(static_cast(*this))); } +// mirror about x-axis without applying negative scale. +AnimPose AnimPose::mirror() const { + return AnimPose(scale, glm::quat(rot.w, rot.x, -rot.y, -rot.z), glm::vec3(-trans.x, trans.y, trans.z)); +} + AnimPose::operator glm::mat4() const { glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f); glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 0.0f); diff --git a/libraries/animation/src/AnimPose.h b/libraries/animation/src/AnimPose.h index 852d84ec1b..6ffa9bb321 100644 --- a/libraries/animation/src/AnimPose.h +++ b/libraries/animation/src/AnimPose.h @@ -30,6 +30,7 @@ struct AnimPose { AnimPose operator*(const AnimPose& rhs) const; AnimPose inverse() const; + AnimPose mirror() const; operator glm::mat4() const; glm::vec3 scale; diff --git a/libraries/animation/src/AnimSkeleton.cpp b/libraries/animation/src/AnimSkeleton.cpp index 8f45b785d1..2d37be9b87 100644 --- a/libraries/animation/src/AnimSkeleton.cpp +++ b/libraries/animation/src/AnimSkeleton.cpp @@ -87,7 +87,8 @@ AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& poses) void AnimSkeleton::convertRelativePosesToAbsolute(AnimPoseVec& poses) const { // poses start off relative and leave in absolute frame - for (int i = 0; i < (int)poses.size() && i < (int)_joints.size(); ++i) { + int lastIndex = std::min((int)poses.size(), (int)_joints.size()); + for (int i = 0; i < lastIndex; ++i) { int parentIndex = _joints[i].parentIndex; if (parentIndex != -1) { poses[i] = poses[parentIndex] * poses[i]; @@ -95,6 +96,30 @@ void AnimSkeleton::convertRelativePosesToAbsolute(AnimPoseVec& poses) const { } } +void AnimSkeleton::convertAbsolutePosesToRelative(AnimPoseVec& poses) const { + // poses start off absolute and leave in relative frame + int lastIndex = std::min((int)poses.size(), (int)_joints.size()); + for (int i = lastIndex - 1; i >= 0; --i) { + int parentIndex = _joints[i].parentIndex; + if (parentIndex != -1) { + poses[i] = poses[parentIndex].inverse() * poses[i]; + } + } +} + +void AnimSkeleton::mirrorRelativePoses(AnimPoseVec& poses) const { + convertRelativePosesToAbsolute(poses); + mirrorAbsolutePoses(poses); + convertAbsolutePosesToRelative(poses); +} + +void AnimSkeleton::mirrorAbsolutePoses(AnimPoseVec& poses) const { + AnimPoseVec temp = poses; + for (int i = 0; i < (int)poses.size(); i++) { + poses[_mirrorMap[i]] = temp[i].mirror(); + } +} + void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) { _joints = joints; @@ -150,6 +175,24 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector& joints) } } } + + // build mirror map. + _mirrorMap.reserve(_joints.size()); + for (int i = 0; i < (int)joints.size(); i++) { + int mirrorJointIndex = -1; + if (_joints[i].name.startsWith("Left")) { + QString mirrorJointName = QString(_joints[i].name).replace(0, 4, "Right"); + mirrorJointIndex = nameToJointIndex(mirrorJointName); + } else if (_joints[i].name.startsWith("Right")) { + QString mirrorJointName = QString(_joints[i].name).replace(0, 5, "Left"); + mirrorJointIndex = nameToJointIndex(mirrorJointName); + } + if (mirrorJointIndex >= 0) { + _mirrorMap.push_back(mirrorJointIndex); + } else { + _mirrorMap.push_back(i); + } + } } #ifndef NDEBUG diff --git a/libraries/animation/src/AnimSkeleton.h b/libraries/animation/src/AnimSkeleton.h index 757f4e5c3e..fc246bc4c0 100644 --- a/libraries/animation/src/AnimSkeleton.h +++ b/libraries/animation/src/AnimSkeleton.h @@ -53,6 +53,10 @@ public: AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const; void convertRelativePosesToAbsolute(AnimPoseVec& poses) const; + void convertAbsolutePosesToRelative(AnimPoseVec& poses) const; + + void mirrorRelativePoses(AnimPoseVec& poses) const; + void mirrorAbsolutePoses(AnimPoseVec& poses) const; #ifndef NDEBUG void dump() const; @@ -69,6 +73,7 @@ protected: AnimPoseVec _absoluteDefaultPoses; AnimPoseVec _relativePreRotationPoses; AnimPoseVec _relativePostRotationPoses; + std::vector _mirrorMap; // no copies AnimSkeleton(const AnimSkeleton&) = delete; diff --git a/libraries/animation/src/Rig.cpp b/libraries/animation/src/Rig.cpp index 4d72bfbaea..1e75df0ccc 100644 --- a/libraries/animation/src/Rig.cpp +++ b/libraries/animation/src/Rig.cpp @@ -120,7 +120,7 @@ void Rig::overrideRoleAnimation(const QString& role, const QString& url, float f _origRoleAnimations[role] = node; const float REFERENCE_FRAMES_PER_SECOND = 30.0f; float timeScale = fps / REFERENCE_FRAMES_PER_SECOND; - auto clipNode = std::make_shared(role, url, firstFrame, lastFrame, timeScale, loop); + auto clipNode = std::make_shared(role, url, firstFrame, lastFrame, timeScale, loop, false); AnimNode::Pointer parent = node->getParent(); parent->replaceChild(node, clipNode); } else { @@ -152,7 +152,7 @@ void Rig::prefetchAnimation(const QString& url) { // This will begin loading the NetworkGeometry for the given URL. // which should speed us up if we request it later via overrideAnimation. - auto clipNode = std::make_shared("prefetch", url, 0, 0, 1.0, false); + auto clipNode = std::make_shared("prefetch", url, 0, 0, 1.0, false, false); _prefetchedAnimations.push_back(clipNode); } From 03d5bc885b5631272669e0ca7b80e6b5f9bb52a0 Mon Sep 17 00:00:00 2001 From: "Anthony J. Thibault" Date: Tue, 2 Feb 2016 17:10:15 -0800 Subject: [PATCH 07/26] AnimClip: added mirrorFlag anim var --- libraries/animation/src/AnimClip.cpp | 1 + libraries/animation/src/AnimClip.h | 2 ++ libraries/animation/src/AnimNodeLoader.cpp | 4 ++++ 3 files changed, 7 insertions(+) diff --git a/libraries/animation/src/AnimClip.cpp b/libraries/animation/src/AnimClip.cpp index f8e045fc3b..e8f429f22c 100644 --- a/libraries/animation/src/AnimClip.cpp +++ b/libraries/animation/src/AnimClip.cpp @@ -38,6 +38,7 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, _endFrame = animVars.lookup(_endFrameVar, _endFrame); _timeScale = animVars.lookup(_timeScaleVar, _timeScale); _loopFlag = animVars.lookup(_loopFlagVar, _loopFlag); + _mirrorFlag = animVars.lookup(_mirrorFlagVar, _mirrorFlag); float frame = animVars.lookup(_frameVar, _frame); _frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggersOut); diff --git a/libraries/animation/src/AnimClip.h b/libraries/animation/src/AnimClip.h index bb4f61ffc0..7989f6d172 100644 --- a/libraries/animation/src/AnimClip.h +++ b/libraries/animation/src/AnimClip.h @@ -36,6 +36,7 @@ public: void setEndFrameVar(const QString& endFrameVar) { _endFrameVar = endFrameVar; } void setTimeScaleVar(const QString& timeScaleVar) { _timeScaleVar = timeScaleVar; } void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; } + void setMirrorFlagVar(const QString& mirrorFlagVar) { _mirrorFlagVar = mirrorFlagVar; } void setFrameVar(const QString& frameVar) { _frameVar = frameVar; } float getStartFrame() const { return _startFrame; } @@ -82,6 +83,7 @@ protected: QString _endFrameVar; QString _timeScaleVar; QString _loopFlagVar; + QString _mirrorFlagVar; QString _frameVar; // no copies diff --git a/libraries/animation/src/AnimNodeLoader.cpp b/libraries/animation/src/AnimNodeLoader.cpp index b26f34f531..52f44a8cd9 100644 --- a/libraries/animation/src/AnimNodeLoader.cpp +++ b/libraries/animation/src/AnimNodeLoader.cpp @@ -236,6 +236,7 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& READ_OPTIONAL_STRING(endFrameVar, jsonObj); READ_OPTIONAL_STRING(timeScaleVar, jsonObj); READ_OPTIONAL_STRING(loopFlagVar, jsonObj); + READ_OPTIONAL_STRING(mirrorFlagVar, jsonObj); auto node = std::make_shared(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag); @@ -251,6 +252,9 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString& if (!loopFlagVar.isEmpty()) { node->setLoopFlagVar(loopFlagVar); } + if (!mirrorFlagVar.isEmpty()) { + node->setMirrorFlagVar(mirrorFlagVar); + } return node; } From 7dd5bca17fad848eac97056fffa2e68a3ff61795 Mon Sep 17 00:00:00 2001 From: "Babiuch, Ryan Nicholas" Date: Wed, 3 Feb 2016 10:46:28 -0600 Subject: [PATCH 08/26] Bug fixes and debt reductions on energy calculations. - removed superfluous code interfacing with scripts - favor floats over doubles in cost calculations - default avatar energy is maximum float value for cases where energy script is not loaded in order to still manipulate entities. --- examples/example/ui/MyEnergyBar.js | 4 ---- libraries/entities/src/EntityScriptingInterface.cpp | 6 +----- libraries/entities/src/EntityScriptingInterface.h | 8 +++----- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/examples/example/ui/MyEnergyBar.js b/examples/example/ui/MyEnergyBar.js index ac7ef0d39c..4c263b9843 100644 --- a/examples/example/ui/MyEnergyBar.js +++ b/examples/example/ui/MyEnergyBar.js @@ -56,11 +56,7 @@ function energyChanged(newValue) { function debitAvatarEnergy(value) { MyAvatar.energy = MyAvatar.energy - value; } -function calculateCost(mass, oldVelocity, newVelocity) { - return mass * (newVelocity - oldVelocity); -} -Entities.addCostFunction(calculateCost); Entities.debitEnergySource.connect(debitAvatarEnergy); MyAvatar.energyChanged.connect(energyChanged); Script.update.connect(update); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 9c61d7c297..3ba2ef0465 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1037,11 +1037,7 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) { return result; } -void EntityScriptingInterface::addCostFunction(QScriptValue costFunction) { - _costFunction = &costFunction; -} - -double EntityScriptingInterface::calculateCost(float mass, float oldVelocity,float newVelocity) { +float EntityScriptingInterface::calculateCost(float mass, float oldVelocity,float newVelocity) { return std::abs(mass * (newVelocity - oldVelocity)); } diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index ff98ab4eb7..ac56dd3739 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -71,8 +71,7 @@ public: void setEntityTree(EntityTreePointer modelTree); EntityTreePointer getEntityTree() { return _entityTree; } void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) { _entitiesScriptEngine = engine; } - double calculateCost(float mass, float oldVelocity, float newVelocity); - Q_INVOKABLE void addCostFunction(QScriptValue costFunction); + float calculateCost(float mass, float oldVelocity, float newVelocity); public slots: // returns true if the DomainServer will allow this Node/Avatar to make changes @@ -194,7 +193,7 @@ signals: void deletingEntity(const EntityItemID& entityID); void addingEntity(const EntityItemID& entityID); void clearingEntities(); - void debitEnergySource(double value); + void debitEnergySource(float value); private: bool actionWorker(const QUuid& entityID, std::function actor); @@ -212,8 +211,7 @@ private: EntityTreePointer _entityTree; EntitiesScriptEngineProvider* _entitiesScriptEngine = nullptr; - QScriptValue* _costFunction = nullptr; - float _currentAvatarEnergy; + float _currentAvatarEnergy = { FLT_MAX }; float getCurrentAvatarEnergy() { return _currentAvatarEnergy; } void setCurrentAvatarEnergy(float energy); float ENTITY_MANIPULATION_MULTIPLIER = { 0.01f }; From 3cb2f9c4b7ae96dcb12826087f510d14348926c1 Mon Sep 17 00:00:00 2001 From: "Babiuch, Ryan Nicholas" Date: Wed, 3 Feb 2016 13:29:42 -0600 Subject: [PATCH 09/26] Exposed energy cost multiplier to scripting. - field: costMultiplier --- examples/example/ui/MyEnergyBar.js | 2 +- .../entities/src/EntityScriptingInterface.cpp | 14 +++++++++++--- libraries/entities/src/EntityScriptingInterface.h | 6 +++++- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/examples/example/ui/MyEnergyBar.js b/examples/example/ui/MyEnergyBar.js index 4c263b9843..c24f998e1a 100644 --- a/examples/example/ui/MyEnergyBar.js +++ b/examples/example/ui/MyEnergyBar.js @@ -56,7 +56,7 @@ function energyChanged(newValue) { function debitAvatarEnergy(value) { MyAvatar.energy = MyAvatar.energy - value; } - +Entities.costMultiplier = 0.002; Entities.debitEnergySource.connect(debitAvatarEnergy); MyAvatar.energyChanged.connect(energyChanged); Script.update.connect(update); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 3ba2ef0465..5ae6f99622 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -126,7 +126,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties auto density = propertiesWithSimID.getDensity(); auto newVelocity = propertiesWithSimID.getVelocity().length(); double cost = calculateCost(density*volume, 0, newVelocity); - cost *= ENTITY_MANIPULATION_MULTIPLIER; //try this as a constant for now + cost *= costMultiplier; if(cost > _currentAvatarEnergy) { return QUuid(); @@ -231,7 +231,7 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& auto density = properties.getDensity(); auto newVelocity = properties.getVelocity().length(); double cost = calculateCost(density*volume, 0, newVelocity); - cost *= ENTITY_MANIPULATION_MULTIPLIER; + cost *= costMultiplier; if(cost > _currentAvatarEnergy) { return QUuid(); @@ -351,7 +351,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { auto density = entity->getDensity(); auto velocity = entity->getVelocity().length(); double cost = calculateCost(density*volume, velocity, 0); - cost *= ENTITY_MANIPULATION_MULTIPLIER; + cost *= costMultiplier; if(cost > _currentAvatarEnergy) { return; @@ -1045,3 +1045,11 @@ void EntityScriptingInterface::setCurrentAvatarEnergy(float energy) { // qCDebug(entities) << "NEW AVATAR ENERGY IN ENTITY SCRIPTING INTERFACE: " << energy; _currentAvatarEnergy = energy; } + +float EntityScriptingInterface::getCostMultiplier() { + return costMultiplier; +} + +void EntityScriptingInterface::setCostMultiplier(float value) { + costMultiplier = value; +} diff --git a/libraries/entities/src/EntityScriptingInterface.h b/libraries/entities/src/EntityScriptingInterface.h index ac56dd3739..3bb86fba53 100644 --- a/libraries/entities/src/EntityScriptingInterface.h +++ b/libraries/entities/src/EntityScriptingInterface.h @@ -61,6 +61,7 @@ class EntityScriptingInterface : public OctreeScriptingInterface, public Depende Q_OBJECT Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy) + Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier) public: EntityScriptingInterface(); @@ -214,7 +215,10 @@ private: float _currentAvatarEnergy = { FLT_MAX }; float getCurrentAvatarEnergy() { return _currentAvatarEnergy; } void setCurrentAvatarEnergy(float energy); - float ENTITY_MANIPULATION_MULTIPLIER = { 0.01f }; + + float costMultiplier = { 0.01f }; + float getCostMultiplier(); + void setCostMultiplier(float value); }; #endif // hifi_EntityScriptingInterface_h From dca7ff967cdfa9b0fdcbde2d7b7e4871fced78a6 Mon Sep 17 00:00:00 2001 From: "Babiuch, Ryan Nicholas" Date: Thu, 4 Feb 2016 08:26:56 -0600 Subject: [PATCH 10/26] Conform to coding standard. --- interface/src/avatar/MyAvatar.cpp | 4 ++-- .../entities/src/EntityScriptingInterface.cpp | 14 +++++++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 9d5c39ffc9..e906bf2485 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1866,7 +1866,7 @@ glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, co float MyAvatar::getAccelerationEnergy() { glm::vec3 velocity = getVelocity(); int changeInVelocity = abs(velocity.length() - priorVelocity.length()); - float changeInEnergy = priorVelocity.length()*changeInVelocity*AVATAR_MOVEMENT_ENERGY_CONSTANT; + float changeInEnergy = priorVelocity.length() * changeInVelocity * AVATAR_MOVEMENT_ENERGY_CONSTANT; priorVelocity = velocity; return changeInEnergy; @@ -1881,7 +1881,7 @@ void MyAvatar::setEnergy(float value) { } float MyAvatar::getAudioEnergy() { - return getAudioLoudness()*AUDIO_ENERGY_CONSTANT; + return getAudioLoudness() * AUDIO_ENERGY_CONSTANT; } bool MyAvatar::didTeleport() { diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 44044f0231..790a1e1919 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -123,10 +123,10 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged()); auto dimensions = propertiesWithSimID.getDimensions(); - float volume = dimensions.x*dimensions.y*dimensions.z; + float volume = dimensions.x * dimensions.y * dimensions.z; auto density = propertiesWithSimID.getDensity(); auto newVelocity = propertiesWithSimID.getVelocity().length(); - double cost = calculateCost(density*volume, 0, newVelocity); + double cost = calculateCost(density * volume, 0, newVelocity); cost *= costMultiplier; if(cost > _currentAvatarEnergy) { @@ -231,10 +231,10 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& EntityItemProperties properties = scriptSideProperties; auto dimensions = properties.getDimensions(); - float volume = dimensions.x*dimensions.y*dimensions.z; + float volume = dimensions.x * dimensions.y * dimensions.z; auto density = properties.getDensity(); auto newVelocity = properties.getVelocity().length(); - double cost = calculateCost(density*volume, 0, newVelocity); + double cost = calculateCost(density * volume, 0, newVelocity); cost *= costMultiplier; if(cost > _currentAvatarEnergy) { @@ -351,10 +351,10 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { if (entity) { auto dimensions = entity->getDimensions(); - float volume = dimensions.x*dimensions.y*dimensions.z; + float volume = dimensions.x * dimensions.y * dimensions.z; auto density = entity->getDensity(); auto velocity = entity->getVelocity().length(); - double cost = calculateCost(density*volume, velocity, 0); + double cost = calculateCost(density * volume, velocity, 0); cost *= costMultiplier; if(cost > _currentAvatarEnergy) { @@ -1041,7 +1041,7 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) { return result; } -float EntityScriptingInterface::calculateCost(float mass, float oldVelocity,float newVelocity) { +float EntityScriptingInterface::calculateCost(float mass, float oldVelocity, float newVelocity) { return std::abs(mass * (newVelocity - oldVelocity)); } From 0069849da3f310b5427abfed1a8cdedf1ef15618 Mon Sep 17 00:00:00 2001 From: "Babiuch, Ryan Nicholas" Date: Thu, 4 Feb 2016 08:55:23 -0600 Subject: [PATCH 11/26] Consider existing velocity in calculating energy costs when editing entities. --- .../entities/src/EntityScriptingInterface.cpp | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index 790a1e1919..3d252abc11 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -126,7 +126,7 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties float volume = dimensions.x * dimensions.y * dimensions.z; auto density = propertiesWithSimID.getDensity(); auto newVelocity = propertiesWithSimID.getVelocity().length(); - double cost = calculateCost(density * volume, 0, newVelocity); + float cost = calculateCost(density * volume, 0, newVelocity); cost *= costMultiplier; if(cost > _currentAvatarEnergy) { @@ -136,7 +136,6 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties emit debitEnergySource(cost); } - EntityItemID id = EntityItemID(QUuid::createUuid()); // If we have a local entity tree set, then also update it. @@ -234,19 +233,23 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& float volume = dimensions.x * dimensions.y * dimensions.z; auto density = properties.getDensity(); auto newVelocity = properties.getVelocity().length(); - double cost = calculateCost(density * volume, 0, newVelocity); - cost *= costMultiplier; - - if(cost > _currentAvatarEnergy) { - return QUuid(); - } else { - //debit the avatar energy and continue - emit debitEnergySource(cost); - } + float oldVelocity = { 0.0f }; EntityItemID entityID(id); if (!_entityTree) { queueEntityMessage(PacketType::EntityEdit, entityID, properties); + + //if there is no local entity entity tree, no existing velocity, use 0. + float cost = calculateCost(density * volume, oldVelocity, newVelocity); + cost *= costMultiplier; + + if(cost > _currentAvatarEnergy) { + return QUuid(); + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } + return id; } // If we have a local entity tree set, then also update it. @@ -260,6 +263,9 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& if (!entity) { return; } + //existing entity, retrieve old velocity for check down below + oldVelocity = entity->getVelocity().length(); + if (!scriptSideProperties.parentIDChanged()) { properties.setParentID(entity->getParentID()); } @@ -275,6 +281,16 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& } properties = convertLocationFromScriptSemantics(properties); updatedEntity = _entityTree->updateEntity(entityID, properties); + + float cost = calculateCost(density * volume, oldVelocity, newVelocity); + cost *= costMultiplier; + + if(cost > _currentAvatarEnergy) { + updatedEntity = false; + } else { + //debit the avatar energy and continue + emit debitEnergySource(cost); + } }); if (!updatedEntity) { @@ -354,7 +370,7 @@ void EntityScriptingInterface::deleteEntity(QUuid id) { float volume = dimensions.x * dimensions.y * dimensions.z; auto density = entity->getDensity(); auto velocity = entity->getVelocity().length(); - double cost = calculateCost(density * volume, velocity, 0); + float cost = calculateCost(density * volume, velocity, 0); cost *= costMultiplier; if(cost > _currentAvatarEnergy) { From e3351c05a27579e6b53d987e26c801a0f93d661d Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 2 Feb 2016 14:26:59 -0800 Subject: [PATCH 12/26] make some methods const --- libraries/entities/src/EntityItem.cpp | 2 +- libraries/entities/src/EntityItem.h | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 99dff84407..b2102ed740 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -987,7 +987,7 @@ EntityTreePointer EntityItem::getTree() const { return tree; } -bool EntityItem::wantTerseEditLogging() { +bool EntityItem::wantTerseEditLogging() const { EntityTreePointer tree = getTree(); return tree ? tree->wantTerseEditLogging() : false; } diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index c8bf62f83e..8271aedb15 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -104,7 +104,7 @@ public: quint64 getLastBroadcast() const { return _lastBroadcast; } void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; } - void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); } + void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); } quint64 getLastChangedOnServer() const { return _changedOnServer; } // TODO: eventually only include properties changed since the params.lastViewFrustumSent time @@ -351,14 +351,14 @@ public: void setPhysicsInfo(void* data) { _physicsInfo = data; } EntityTreeElementPointer getElement() const { return _element; } EntityTreePointer getTree() const; - bool wantTerseEditLogging(); + bool wantTerseEditLogging() const; glm::mat4 getEntityToWorldMatrix() const; glm::mat4 getWorldToEntityMatrix() const; glm::vec3 worldToEntity(const glm::vec3& point) const; glm::vec3 entityToWorld(const glm::vec3& point) const; - quint64 getLastEditedFromRemote() { return _lastEditedFromRemote; } + quint64 getLastEditedFromRemote() const { return _lastEditedFromRemote; } void getAllTerseUpdateProperties(EntityItemProperties& properties) const; From 05fb866bb5cfdacb46a33d83bf1a3210d764d960 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 2 Feb 2016 14:27:14 -0800 Subject: [PATCH 13/26] fix spelling typo in comment --- libraries/entities/src/EntityItemProperties.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 0a72789485..8e0983f62a 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -910,7 +910,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem success = packetData->startSubTree(octcode); delete[] octcode; - // assuming we have rome to fit our octalCode, proceed... + // assuming we have room to fit our octalCode, proceed... if (success) { // Now add our edit content details... From 381049acb3d0393c7b52f3e31ccbf7498927799f Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 2 Feb 2016 14:28:59 -0800 Subject: [PATCH 14/26] clear simulation ownership when owners vanish --- .../entities/src/SimpleEntitySimulation.cpp | 44 ++++++++++++------- .../entities/src/SimpleEntitySimulation.h | 1 + 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index 120b536161..f0461abb97 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -15,34 +15,46 @@ #include "SimpleEntitySimulation.h" #include "EntitiesLogging.h" -const quint64 AUTO_REMOVE_SIMULATION_OWNER_USEC = 2 * USECS_PER_SECOND; +const quint64 MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD = 2 * USECS_PER_SECOND; void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { - // If an Entity has a simulation owner and we don't get an update for some amount of time, - // clear the owner. This guards against an interface failing to release the Entity when it - // has finished simulating it. - auto nodeList = DependencyManager::get(); + if (_entitiesWithSimulator.size() == 0) { + return; + } + + if (now < _nextSimulationExpiry) { + // nothing has expired yet + return; + } + + // If an Entity has a simulation owner but there has been no update for a while: clear the owner. + // If an Entity goes ownerless for too long: zero velocity and remove from _entitiesWithSimulator. + _nextSimulationExpiry = now + MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD; QMutexLocker lock(&_mutex); SetOfEntities::iterator itemItr = _entitiesWithSimulator.begin(); while (itemItr != _entitiesWithSimulator.end()) { EntityItemPointer entity = *itemItr; - if (entity->getSimulatorID().isNull()) { - itemItr = _entitiesWithSimulator.erase(itemItr); - } else if (now - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) { - SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID()); - if (ownerNode.isNull() || !ownerNode->isAlive()) { - qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID(); - entity->clearSimulationOwnership(); - itemItr = _entitiesWithSimulator.erase(itemItr); + quint64 expiry = entity->getLastChangedOnServer() + MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD; + if (expiry < now) { + if (entity->getSimulatorID().isNull()) { + // no simulators are volunteering // zero the velocity on this entity so that it doesn't drift far away entity->setVelocity(glm::vec3(0.0f)); + // remove from list + itemItr = _entitiesWithSimulator.erase(itemItr); + continue; } else { - ++itemItr; + // the simulator has stopped updating this object + // clear ownership and restart timer, giving nearby simulators time to volunteer + qCDebug(entities) << "auto-removing simulation owner " << entity.getSimulatorID(); + entity->clearSimulationOwnership(); + entity->markAsChangedOnServer(); } - } else { - ++itemItr; + } else if (expiry < _nextSimulationExpiry) { + _nextSimulationExpiry = expiry; } + ++itemItr; } } diff --git a/libraries/entities/src/SimpleEntitySimulation.h b/libraries/entities/src/SimpleEntitySimulation.h index 83c51525a8..53a7574bf2 100644 --- a/libraries/entities/src/SimpleEntitySimulation.h +++ b/libraries/entities/src/SimpleEntitySimulation.h @@ -29,6 +29,7 @@ protected: virtual void clearEntitiesInternal() override; SetOfEntities _entitiesWithSimulator; + quint64 _nextSimulationExpiry { 0 }; }; #endif // hifi_SimpleEntitySimulation_h From e51edaa1170f5532d706feb7f6887f1feb60c534 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Tue, 2 Feb 2016 16:46:07 -0800 Subject: [PATCH 15/26] update some comments --- libraries/physics/src/EntityMotionState.cpp | 29 +++++++++---------- libraries/physics/src/EntityMotionState.h | 6 ++-- .../physics/src/PhysicalEntitySimulation.cpp | 3 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 84d2282e7a..52151b7136 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -104,25 +104,25 @@ bool EntityMotionState::handleEasyChanges(uint32_t& flags) { if (flags & Simulation::DIRTY_SIMULATOR_ID) { _loopsWithoutOwner = 0; if (_entity->getSimulatorID().isNull()) { - // simulation ownership is being removed - // remove the ACTIVATION flag because this object is coming to rest - // according to a remote simulation and we don't want to wake it up again + // simulation ownership has been removed by an external simulator + // --> clear the ACTIVATION flag and outgoing priority because this object is coming to rest flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; - // hint to Bullet that the object is deactivating _body->setActivationState(WANTS_DEACTIVATION); _outgoingPriority = NO_PRORITY; - } else { + } else { + // this entity's simulation is owned by someone, so we push its ownership expiry into the future _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; if (Physics::getSessionUUID() == _entity->getSimulatorID() || _entity->getSimulationPriority() >= _outgoingPriority) { - // we own the simulation or our priority looses to (or ties with) remote + // either we already own the simulation or our old outgoing priority momentarily looses to current owner + // so we clear it _outgoingPriority = NO_PRORITY; } } } if (flags & Simulation::DIRTY_SIMULATOR_OWNERSHIP) { - // (DIRTY_SIMULATOR_OWNERSHIP really means "we should bid for ownership with SCRIPT priority") - // we're manipulating this object directly via script, so we artificially - // manipulate the logic to trigger an immediate bid for ownership + // The DIRTY_SIMULATOR_OWNERSHIP bit really means "we should bid for ownership at SCRIPT priority". + // Since that bit is set there must be a local script that is updating the physics properties of the objects + // therefore we upgrade _outgoingPriority to trigger a bid for ownership. setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY); } if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) { @@ -203,7 +203,6 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) { _loopsWithoutOwner++; if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) { - //qDebug() << "Warning -- claiming something I saw moving." << getName(); setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); } } @@ -235,9 +234,8 @@ btCollisionShape* EntityMotionState::computeNewShape() { } bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { - if (!_body || !_entity) { - return false; - } + assert(_body); + assert(_entity); assert(entityTreeIsLocked()); return _outgoingPriority != NO_PRORITY || sessionID == _entity->getSimulatorID() || _entity->actionDataNeedsTransmit(); } @@ -374,10 +372,11 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s } if (_entity->getSimulatorID() != sessionID) { - // we don't own the simulation, but maybe we should... + // we don't own the simulation if (_outgoingPriority != NO_PRORITY) { + // but we would like to own it if (_outgoingPriority < _entity->getSimulationPriority()) { - // our priority loses to remote, so we don't bother to bid + // but our priority loses to remote, so we don't bother trying _outgoingPriority = NO_PRORITY; return false; } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 2c999d0aca..7e350a4bae 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -106,7 +106,7 @@ protected: // Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid. EntityItem* _entity; - bool _sentInactive; // true if body was inactive when we sent last update + bool _triedToReleaseOwnership; // true if we tried to release ownership // these are for the prediction of the remote server's simple extrapolation uint32_t _lastStep; // last step of server extrapolation @@ -124,8 +124,8 @@ protected: float _measuredDeltaTime; quint8 _accelerationNearlyGravityCount; - quint64 _nextOwnershipBid = NO_PRORITY; - uint32_t _loopsWithoutOwner; + quint64 _nextOwnershipBid { 0 }; + quint64 _orphanExpiry { 0 }; quint8 _outgoingPriority = NO_PRORITY; }; diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index d2c91f29dd..d5e8d972fe 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -249,6 +249,7 @@ void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result) void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& motionStates, const QUuid& sessionID) { QMutexLocker lock(&_mutex); + // walk the motionStates looking for those that correspond to entities for (auto stateItr : motionStates) { ObjectMotionState* state = &(*stateItr); @@ -273,7 +274,7 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& return; } - // send outgoing packets + // look for entities that need outgoing packets QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; From 91a2f86482667b7c300b778e3a620d595d89ee67 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 4 Feb 2016 13:22:26 -0800 Subject: [PATCH 16/26] update some comments --- libraries/physics/src/PhysicalEntitySimulation.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/libraries/physics/src/PhysicalEntitySimulation.cpp b/libraries/physics/src/PhysicalEntitySimulation.cpp index d5e8d972fe..71c78b8b86 100644 --- a/libraries/physics/src/PhysicalEntitySimulation.cpp +++ b/libraries/physics/src/PhysicalEntitySimulation.cpp @@ -274,13 +274,15 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& return; } - // look for entities that need outgoing packets + // look for entities to prune or update QSet::iterator stateItr = _outgoingChanges.begin(); while (stateItr != _outgoingChanges.end()) { EntityMotionState* state = *stateItr; if (!state->isCandidateForOwnership(sessionID)) { + // prune stateItr = _outgoingChanges.erase(stateItr); } else if (state->shouldSendUpdate(numSubsteps, sessionID)) { + // update state->sendUpdate(_entityPacketSender, sessionID, numSubsteps); ++stateItr; } else { From 959f924b1d3fd3e05cea16b934b853579f69e0bf Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 4 Feb 2016 13:22:48 -0800 Subject: [PATCH 17/26] flag entity as changed when changing simulatorID --- libraries/entities/src/EntityItem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index b2102ed740..a4a646f532 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -652,6 +652,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef } if (_simulationOwner.set(newSimOwner)) { _dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID; + somethingChanged = true; } } { // When we own the simulation we don't accept updates to the entity's transform/velocities From af57f5d12085acdec96472fb55f9880fcbc2546b Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 4 Feb 2016 13:24:08 -0800 Subject: [PATCH 18/26] add simple and fast DirtyOctreeElementOperator --- .../octree/src/DirtyOctreeElementOperator.cpp | 30 +++++++++++++++++++ .../octree/src/DirtyOctreeElementOperator.h | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) create mode 100644 libraries/octree/src/DirtyOctreeElementOperator.cpp create mode 100644 libraries/octree/src/DirtyOctreeElementOperator.h diff --git a/libraries/octree/src/DirtyOctreeElementOperator.cpp b/libraries/octree/src/DirtyOctreeElementOperator.cpp new file mode 100644 index 0000000000..0f7e6d9f04 --- /dev/null +++ b/libraries/octree/src/DirtyOctreeElementOperator.cpp @@ -0,0 +1,30 @@ +// +// DirtyOctreeElementOperator.cpp +// libraries/entities/src +// +// Created by Andrew Meawdows 2016.02.04 +// Copyright 2016 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 "DirtyOctreeElementOperator.h" + +DirtyOctreeElementOperator::DirtyOctreeElementOperator(OctreeElementPointer element) + : _element(element) { + assert(_element.get()); + _point = _element->getAACube().calcCenter(); +} + +bool DirtyOctreeElementOperator::preRecursion(OctreeElementPointer element) { + if (element == _element) { + return false; + } + return element->getAACube().contains(_point); +} + +bool DirtyOctreeElementOperator::postRecursion(OctreeElementPointer element) { + element->markWithChangedTime(); + return true; +} diff --git a/libraries/octree/src/DirtyOctreeElementOperator.h b/libraries/octree/src/DirtyOctreeElementOperator.h new file mode 100644 index 0000000000..a5eec780a0 --- /dev/null +++ b/libraries/octree/src/DirtyOctreeElementOperator.h @@ -0,0 +1,30 @@ +// +// DirtyOctreeElementOperator.h +// libraries/entities/src +// +// Created by Andrew Meawdows 2016.02.04 +// Copyright 2016 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_DirtyOctreeElementOperator_h +#define hifi_DirtyOctreeElementOperator_h + +#include "Octree.h" + +class DirtyOctreeElementOperator : public RecurseOctreeOperator { +public: + DirtyOctreeElementOperator(OctreeElementPointer element); + + ~DirtyOctreeElementOperator() {} + + virtual bool preRecursion(OctreeElementPointer element); + virtual bool postRecursion(OctreeElementPointer element); +private: + glm::vec3 _point; + OctreeElementPointer _element; +}; + +#endif // hifi_DirtyOctreeElementOperator_h From 2da46ff26a16d8f812d12745735c121856c5e1f8 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 4 Feb 2016 13:24:30 -0800 Subject: [PATCH 19/26] server-side release ownership --- .../entities/src/SimpleEntitySimulation.cpp | 16 +++- libraries/entities/src/SimulationOwner.cpp | 9 +-- libraries/entities/src/SimulationOwner.h | 4 +- libraries/physics/src/EntityMotionState.cpp | 80 +++++++++++-------- libraries/physics/src/EntityMotionState.h | 27 +++---- 5 files changed, 76 insertions(+), 60 deletions(-) diff --git a/libraries/entities/src/SimpleEntitySimulation.cpp b/libraries/entities/src/SimpleEntitySimulation.cpp index f0461abb97..bdf27f4440 100644 --- a/libraries/entities/src/SimpleEntitySimulation.cpp +++ b/libraries/entities/src/SimpleEntitySimulation.cpp @@ -11,8 +11,11 @@ //#include -#include "EntityItem.h" #include "SimpleEntitySimulation.h" + +#include + +#include "EntityItem.h" #include "EntitiesLogging.h" const quint64 MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD = 2 * USECS_PER_SECOND; @@ -40,17 +43,22 @@ void SimpleEntitySimulation::updateEntitiesInternal(const quint64& now) { if (entity->getSimulatorID().isNull()) { // no simulators are volunteering // zero the velocity on this entity so that it doesn't drift far away - entity->setVelocity(glm::vec3(0.0f)); + entity->setVelocity(Vectors::ZERO); + entity->setAngularVelocity(Vectors::ZERO); + entity->setAcceleration(Vectors::ZERO); // remove from list itemItr = _entitiesWithSimulator.erase(itemItr); continue; } else { // the simulator has stopped updating this object // clear ownership and restart timer, giving nearby simulators time to volunteer - qCDebug(entities) << "auto-removing simulation owner " << entity.getSimulatorID(); + qCDebug(entities) << "auto-removing simulation owner " << entity->getSimulatorID(); entity->clearSimulationOwnership(); - entity->markAsChangedOnServer(); } + entity->markAsChangedOnServer(); + // dirty all the tree elements that contain the entity + DirtyOctreeElementOperator op(entity->getElement()); + getEntityTree()->recurseTreeWithOperator(&op); } else if (expiry < _nextSimulationExpiry) { _nextSimulationExpiry = expiry; } diff --git a/libraries/entities/src/SimulationOwner.cpp b/libraries/entities/src/SimulationOwner.cpp index 24f6784954..669d811df9 100644 --- a/libraries/entities/src/SimulationOwner.cpp +++ b/libraries/entities/src/SimulationOwner.cpp @@ -16,11 +16,11 @@ #include -// static +// static const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1; -SimulationOwner::SimulationOwner(const SimulationOwner& other) +SimulationOwner::SimulationOwner(const SimulationOwner& other) : _id(other._id), _priority(other._priority), _expiry(other._expiry) { } @@ -48,11 +48,6 @@ void SimulationOwner::clear() { void SimulationOwner::setPriority(quint8 priority) { _priority = priority; - if (_priority == 0) { - // when priority is zero we clear everything - _expiry = 0; - _id = QUuid(); - } } void SimulationOwner::promotePriority(quint8 priority) { diff --git a/libraries/entities/src/SimulationOwner.h b/libraries/entities/src/SimulationOwner.h index a848ad6e84..bd17128003 100644 --- a/libraries/entities/src/SimulationOwner.h +++ b/libraries/entities/src/SimulationOwner.h @@ -18,10 +18,10 @@ #include #include -const quint8 NO_PRORITY = 0x00; +const quint8 ZERO_SIMULATION_PRIORITY = 0x00; // Simulation observers will bid to simulate unowned active objects at the lowest possible priority -// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it +// which is VOLUNTEER. If the server accepts a VOLUNTEER bid it will automatically bump it // to RECRUIT priority so that other volunteers don't accidentally take over. const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01; const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1; diff --git a/libraries/physics/src/EntityMotionState.cpp b/libraries/physics/src/EntityMotionState.cpp index 52151b7136..155186e891 100644 --- a/libraries/physics/src/EntityMotionState.cpp +++ b/libraries/physics/src/EntityMotionState.cpp @@ -26,10 +26,7 @@ #include "EntityTree.h" #endif -static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f; -static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4; - -const uint32_t LOOPS_FOR_SIMULATION_ORPHAN = 50; +const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50; const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5; #ifdef WANT_DEBUG_ENTITY_TREE_LOCKS @@ -52,8 +49,6 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer ObjectMotionState(shape), _entityPtr(entity), _entity(entity.get()), - _sentInactive(true), - _lastStep(0), _serverPosition(0.0f), _serverRotation(), _serverVelocity(0.0f), @@ -61,13 +56,16 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer _serverGravity(0.0f), _serverAcceleration(0.0f), _serverActionData(QByteArray()), - _lastMeasureStep(0), _lastVelocity(glm::vec3(0.0f)), _measuredAcceleration(glm::vec3(0.0f)), - _measuredDeltaTime(0.0f), - _accelerationNearlyGravityCount(0), _nextOwnershipBid(0), - _loopsWithoutOwner(0) + _measuredDeltaTime(0.0f), + _lastMeasureStep(0), + _lastStep(0), + _loopsWithoutOwner(0), + _accelerationNearlyGravityCount(0), + _numInactiveUpdates(1), + _outgoingPriority(ZERO_SIMULATION_PRIORITY) { _type = MOTIONSTATE_TYPE_ENTITY; assert(_entity); @@ -102,20 +100,28 @@ bool EntityMotionState::handleEasyChanges(uint32_t& flags) { ObjectMotionState::handleEasyChanges(flags); if (flags & Simulation::DIRTY_SIMULATOR_ID) { - _loopsWithoutOwner = 0; if (_entity->getSimulatorID().isNull()) { // simulation ownership has been removed by an external simulator - // --> clear the ACTIVATION flag and outgoing priority because this object is coming to rest - flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; - _body->setActivationState(WANTS_DEACTIVATION); - _outgoingPriority = NO_PRORITY; + if (glm::length2(_entity->getVelocity()) == 0.0f) { + // this object is coming to rest --> clear the ACTIVATION flag and outgoing priority + flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION; + _body->setActivationState(WANTS_DEACTIVATION); + _outgoingPriority = ZERO_SIMULATION_PRIORITY; + _loopsWithoutOwner = 0; + } else { + // unowned object is still moving --> we should volunteer to own it + // TODO? put a delay in here proportional to distance from object? + setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY); + _loopsWithoutOwner = LOOPS_FOR_SIMULATION_ORPHAN; + _nextOwnershipBid = 0; + } } else { // this entity's simulation is owned by someone, so we push its ownership expiry into the future _nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS; if (Physics::getSessionUUID() == _entity->getSimulatorID() || _entity->getSimulationPriority() >= _outgoingPriority) { // either we already own the simulation or our old outgoing priority momentarily looses to current owner // so we clear it - _outgoingPriority = NO_PRORITY; + _outgoingPriority = ZERO_SIMULATION_PRIORITY; } } } @@ -237,10 +243,11 @@ bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const { assert(_body); assert(_entity); assert(entityTreeIsLocked()); - return _outgoingPriority != NO_PRORITY || sessionID == _entity->getSimulatorID() || _entity->actionDataNeedsTransmit(); + return _outgoingPriority != ZERO_SIMULATION_PRIORITY || sessionID == _entity->getSimulatorID() || _entity->actionDataNeedsTransmit(); } bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { + // NOTE: we only get here if we think we own the simulation assert(_body); // if we've never checked before, our _lastStep will be 0, and we need to initialize our state if (_lastStep == 0) { @@ -251,7 +258,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { _serverAngularVelocity = bulletToGLM(_body->getAngularVelocity()); _lastStep = simulationStep; _serverActionData = _entity->getActionData(); - _sentInactive = true; + _numInactiveUpdates = 1; return false; } @@ -264,16 +271,21 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) { int numSteps = simulationStep - _lastStep; float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP; - const float INACTIVE_UPDATE_PERIOD = 0.5f; - if (_sentInactive) { + if (_numInactiveUpdates > 0) { + const uint8_t MAX_NUM_INACTIVE_UPDATES = 3; + if (_numInactiveUpdates > MAX_NUM_INACTIVE_UPDATES) { + // clear local ownership (stop sending updates) and let the server clear itself + _entity->clearSimulationOwnership(); + return false; + } // we resend the inactive update every INACTIVE_UPDATE_PERIOD // until it is removed from the outgoing updates // (which happens when we don't own the simulation and it isn't touching our simulation) + const float INACTIVE_UPDATE_PERIOD = 0.5f; return (dt > INACTIVE_UPDATE_PERIOD); } - bool isActive = _body->isActive(); - if (!isActive) { + if (!_body->isActive()) { // object has gone inactive but our last send was moving --> send non-moving update immediately return true; } @@ -373,11 +385,11 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s if (_entity->getSimulatorID() != sessionID) { // we don't own the simulation - if (_outgoingPriority != NO_PRORITY) { + if (_outgoingPriority != ZERO_SIMULATION_PRIORITY) { // but we would like to own it if (_outgoingPriority < _entity->getSimulationPriority()) { // but our priority loses to remote, so we don't bother trying - _outgoingPriority = NO_PRORITY; + _outgoingPriority = ZERO_SIMULATION_PRIORITY; return false; } return usecTimestampNow() > _nextOwnershipBid; @@ -399,10 +411,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _entity->setVelocity(zero); _entity->setAngularVelocity(zero); _entity->setAcceleration(zero); - _sentInactive = true; + _numInactiveUpdates++; } else { + const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4; float gravityLength = glm::length(_entity->getGravity()); float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength); + const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f; if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) { // acceleration measured during the most recent simulation step was close to gravity. if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) { @@ -439,7 +453,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q _entity->setVelocity(zero); _entity->setAngularVelocity(zero); } - _sentInactive = false; + _numInactiveUpdates = 0; } // remember properties for local server prediction @@ -487,12 +501,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q // we own the simulation but the entity has stopped, so we tell the server that we're clearing simulatorID // but we remember that we do still own it... and rely on the server to tell us that we don't properties.clearSimulationOwner(); - _outgoingPriority = NO_PRORITY; + _outgoingPriority = ZERO_SIMULATION_PRIORITY; } // else the ownership is not changing so we don't bother to pack it } else { // we don't own the simulation for this entity yet, but we're sending a bid for it - properties.setSimulationOwner(sessionID, glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); + properties.setSimulationOwner(sessionID, glm::max(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY)); _nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS; } @@ -557,7 +571,7 @@ void EntityMotionState::clearIncomingDirtyFlags() { } // virtual -quint8 EntityMotionState::getSimulationPriority() const { +uint8_t EntityMotionState::getSimulationPriority() const { return _entity->getSimulationPriority(); } @@ -567,7 +581,7 @@ QUuid EntityMotionState::getSimulatorID() const { return _entity->getSimulatorID(); } -void EntityMotionState::bump(quint8 priority) { +void EntityMotionState::bump(uint8_t priority) { setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority)); } @@ -600,7 +614,7 @@ void EntityMotionState::measureBodyAcceleration() { if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) { _loopsWithoutOwner = 0; _lastStep = ObjectMotionState::getWorldSimulationStep(); - _sentInactive = false; + _numInactiveUpdates = 0; } } } @@ -630,6 +644,6 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma _entity->computeCollisionGroupAndFinalMask(group, mask); } -void EntityMotionState::setOutgoingPriority(quint8 priority) { - _outgoingPriority = glm::max(_outgoingPriority, priority); +void EntityMotionState::setOutgoingPriority(uint8_t priority) { + _outgoingPriority = glm::max(_outgoingPriority, priority); } diff --git a/libraries/physics/src/EntityMotionState.h b/libraries/physics/src/EntityMotionState.h index 7e350a4bae..cab8448dd9 100644 --- a/libraries/physics/src/EntityMotionState.h +++ b/libraries/physics/src/EntityMotionState.h @@ -53,7 +53,7 @@ public: void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; } void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; } - quint8 getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; } + uint8_t getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; } virtual float getObjectRestitution() const override { return _entity->getRestitution(); } virtual float getObjectFriction() const override { return _entity->getFriction(); } @@ -69,9 +69,9 @@ public: virtual const QUuid getObjectID() const override { return _entity->getID(); } - virtual quint8 getSimulationPriority() const override; + virtual uint8_t getSimulationPriority() const override; virtual QUuid getSimulatorID() const override; - virtual void bump(quint8 priority) override; + virtual void bump(uint8_t priority) override; EntityItemPointer getEntity() const { return _entityPtr.lock(); } @@ -83,7 +83,7 @@ public: virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override; // eternal logic can suggest a simuator priority bid for the next outgoing update - void setOutgoingPriority(quint8 priority); + void setOutgoingPriority(uint8_t priority); friend class PhysicalEntitySimulation; @@ -106,10 +106,6 @@ protected: // Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid. EntityItem* _entity; - bool _triedToReleaseOwnership; // true if we tried to release ownership - - // these are for the prediction of the remote server's simple extrapolation - uint32_t _lastStep; // last step of server extrapolation glm::vec3 _serverPosition; // in simulation-frame (not world-frame) glm::quat _serverRotation; glm::vec3 _serverVelocity; @@ -118,15 +114,18 @@ protected: glm::vec3 _serverAcceleration; QByteArray _serverActionData; - uint32_t _lastMeasureStep; glm::vec3 _lastVelocity; glm::vec3 _measuredAcceleration; - float _measuredDeltaTime; - - quint8 _accelerationNearlyGravityCount; quint64 _nextOwnershipBid { 0 }; - quint64 _orphanExpiry { 0 }; - quint8 _outgoingPriority = NO_PRORITY; + + float _measuredDeltaTime; + uint32_t _lastMeasureStep; + uint32_t _lastStep; // last step of server extrapolation + + uint8_t _loopsWithoutOwner; + uint8_t _accelerationNearlyGravityCount; + uint8_t _numInactiveUpdates { 1 }; + uint8_t _outgoingPriority { ZERO_SIMULATION_PRIORITY }; }; #endif // hifi_EntityMotionState_h From 42e9a4ebf04d3a21051ea9d76378b0dc13fad5c3 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 4 Feb 2016 13:43:50 -0800 Subject: [PATCH 20/26] fix ATP SendQueue failure to receive after re-activity --- libraries/networking/src/udt/Connection.cpp | 10 +++++++++- libraries/networking/src/udt/Connection.h | 2 ++ libraries/networking/src/udt/SendQueue.cpp | 13 ++++++++----- libraries/networking/src/udt/SendQueue.h | 7 ++++--- 4 files changed, 23 insertions(+), 9 deletions(-) diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index e63d44e72f..3b5b26d05d 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -82,8 +82,12 @@ void Connection::resetRTT() { SendQueue& Connection::getSendQueue() { if (!_sendQueue) { + + // we may have a sequence number from the previous inactive queue - re-use that so that the + // receiver is getting the sequence numbers it expects (given that the connection must still be active) + // Lasily create send queue - _sendQueue = SendQueue::create(_parentSocket, _destination); + _sendQueue = SendQueue::create(_parentSocket, _destination, _inactiveSendQueueSequenceNumber); #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Created SendQueue for connection to" << _destination; @@ -105,6 +109,10 @@ SendQueue& Connection::getSendQueue() { } void Connection::queueInactive() { + // get the current sequence number from the send queue, this is to be re-used if the send + // queue is re-activated for this connection + _inactiveSendQueueSequenceNumber = _sendQueue->getCurrentSequenceNumber(); + // tell our current send queue to go down and reset our ptr to it to null stopSendQueue(); diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index f8c4acd7d9..b58b7ec570 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -139,6 +139,8 @@ private: SequenceNumber _lastSentACK; // The last sent ACK SequenceNumber _lastSentACK2; // The last sent ACK sub-sequence number in an ACK2 + + SequenceNumber _inactiveSendQueueSequenceNumber { 0 }; int _acksDuringSYN { 1 }; // The number of non-SYN ACKs sent during SYN int _lightACKsDuringSYN { 1 }; // The number of lite ACKs sent during SYN interval diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index ba99efdcea..4d6e431b45 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -52,11 +52,11 @@ private: Mutex2& _mutex2; }; -std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination) { +std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destination, SequenceNumber currentSequenceNumber) { Q_ASSERT_X(socket, "SendQueue::create", "Must be called with a valid Socket*"); - auto queue = std::unique_ptr(new SendQueue(socket, destination)); - + auto queue = std::unique_ptr(new SendQueue(socket, destination, currentSequenceNumber)); + // Setup queue private thread QThread* thread = new QThread; thread->setObjectName("Networking: SendQueue " + destination.objectName()); // Name thread for easier debug @@ -74,10 +74,12 @@ std::unique_ptr SendQueue::create(Socket* socket, HifiSockAddr destin return queue; } -SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) : +SendQueue::SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber) : _socket(socket), - _destination(dest) + _destination(dest), + _currentSequenceNumber(currentSequenceNumber) { + } void SendQueue::queuePacket(std::unique_ptr packet) { @@ -389,6 +391,7 @@ bool SendQueue::isInactive(bool sentAPacket) { static const int NUM_TIMEOUTS_BEFORE_INACTIVE = 16; static const int MIN_SECONDS_BEFORE_INACTIVE_MS = 5 * 1000; if (_timeoutExpiryCount >= NUM_TIMEOUTS_BEFORE_INACTIVE && + _lastReceiverResponse > 0 && (QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse) > MIN_SECONDS_BEFORE_INACTIVE_MS) { // If the flow window has been full for over CONSIDER_INACTIVE_AFTER, // then signal the queue is inactive and return so it can be cleaned up diff --git a/libraries/networking/src/udt/SendQueue.h b/libraries/networking/src/udt/SendQueue.h index 3fe4006139..5428e7a26d 100644 --- a/libraries/networking/src/udt/SendQueue.h +++ b/libraries/networking/src/udt/SendQueue.h @@ -50,7 +50,8 @@ public: Stopped }; - static std::unique_ptr create(Socket* socket, HifiSockAddr destination); + static std::unique_ptr create(Socket* socket, HifiSockAddr destination, + SequenceNumber currentSequenceNumber = SequenceNumber()); void queuePacket(std::unique_ptr packet); void queuePacketList(std::unique_ptr packetList); @@ -83,7 +84,7 @@ private slots: void run(); private: - SendQueue(Socket* socket, HifiSockAddr dest); + SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber); SendQueue(SendQueue& other) = delete; SendQueue(SendQueue&& other) = delete; @@ -108,7 +109,7 @@ private: std::atomic _lastACKSequenceNumber { 0 }; // Last ACKed sequence number - SequenceNumber _currentSequenceNumber; // Last sequence number sent out + SequenceNumber _currentSequenceNumber { 0 }; // Last sequence number sent out std::atomic _atomicCurrentSequenceNumber { 0 }; // Atomic for last sequence number sent out std::atomic _packetSendPeriod { 0 }; // Interval between two packet send event in microseconds, set from CC From ae6a5aa286df58623d823f66047d5276771eed0e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 4 Feb 2016 18:10:43 -0800 Subject: [PATCH 21/26] add some debugging to handControllerGrab --- examples/controllers/handControllerGrab.js | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 4b022e5a84..4403637bae 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -19,6 +19,7 @@ Script.include("../libraries/utils.js"); // var WANT_DEBUG = false; var WANT_DEBUG_STATE = false; +var WANT_DEBUG_SEARCH_NAME = null; // // these tune time-averaging and "on" value for analog trigger @@ -867,36 +868,58 @@ function MyController(hand) { grabbable = false; } } + if ("grabbable" in grabbableDataForCandidate) { // if userData indicates that this is grabbable or not, override the default. grabbable = grabbableDataForCandidate.grabbable; } if (!grabbable && !grabbableDataForCandidate.wantsTrigger) { + if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not grabbable."); + } continue; } if (forbiddenTypes.indexOf(propsForCandidate.type) >= 0) { + if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': forbidden entity type."); + } continue; } if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) { + if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': locked and not triggerable."); + } continue; } if (forbiddenNames.indexOf(propsForCandidate.name) >= 0) { + if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': forbidden name."); + } continue; } distance = Vec3.distance(propsForCandidate.position, handPosition); if (distance > PICK_MAX_DISTANCE) { // too far away, don't grab + if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': too far away."); + } continue; } if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_EQUIP_SEARCHING) { // don't allow a double-equip + if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': it's a child"); + } continue; } if (this.state == STATE_SEARCHING && !isPhysical && distance > NEAR_PICK_MAX_DISTANCE) { // we can't distance-grab non-physical + if (WANT_DEBUG_SEARCH_NAME && propsForCandidate.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': not physical and too far for near-grab"); + } continue; } @@ -926,6 +949,9 @@ function MyController(hand) { if ((isPhysical || this.state == STATE_EQUIP_SEARCHING) && !near) { if (entityIsGrabbedByOther(intersection.entityID)) { // don't distance grab something that is already grabbed. + if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': already grabbed by another."); + } return; } this.temporaryPositionOffset = null; @@ -952,6 +978,9 @@ function MyController(hand) { this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); return; } + if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { + print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': fell through."); + } } //search line visualizations From ebf0bf394af46b8a42e34cf1b38810d8a994725c Mon Sep 17 00:00:00 2001 From: Bradley Austin Davis Date: Fri, 5 Feb 2016 11:43:31 -0800 Subject: [PATCH 22/26] Fix native text rendering by forcing positions to integer values --- .../qml/{ => hifi}/dialogs/RunningScripts.qml | 37 ++++++++++++++----- interface/resources/qml/windows/Window.qml | 24 ++++++++++++ interface/src/Application.cpp | 4 +- libraries/gl/src/gl/OffscreenQmlSurface.h | 4 +- 4 files changed, 55 insertions(+), 14 deletions(-) rename interface/resources/qml/{ => hifi}/dialogs/RunningScripts.qml (90%) diff --git a/interface/resources/qml/dialogs/RunningScripts.qml b/interface/resources/qml/hifi/dialogs/RunningScripts.qml similarity index 90% rename from interface/resources/qml/dialogs/RunningScripts.qml rename to interface/resources/qml/hifi/dialogs/RunningScripts.qml index c9c9062bbd..f61bf3f96d 100644 --- a/interface/resources/qml/dialogs/RunningScripts.qml +++ b/interface/resources/qml/hifi/dialogs/RunningScripts.qml @@ -3,9 +3,9 @@ import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 as OriginalDialogs import Qt.labs.settings 1.0 -import "../styles" as Hifi -import "../controls" as HifiControls -import "../windows" +import "../../styles" as Hifi +import "../../controls" as HifiControls +import "../../windows" Window { id: root @@ -19,12 +19,6 @@ Window { property var scripts: ScriptDiscoveryService; property var scriptsModel: scripts.scriptsModelFilter property var runningScriptsModel: ListModel { } - property var fileFilters: ListModel { - id: jsFilters - ListElement { text: "Javascript Files (*.js)"; filter: "*.js" } - ListElement { text: "All Files (*.*)"; filter: "*.*" } - } - Settings { category: "Overlay.RunningScripts" @@ -249,7 +243,30 @@ Window { } } model: scriptsModel - TableViewColumn { title: "Name"; role: "display"; } + + MouseArea { + anchors.fill: parent + acceptedButtons: Qt.RightButton + onClicked: treeView.foo(); + } + + function foo() { + var localRect = Qt.rect(0, 0, width, height); + var rect = desktop.mapFromItem(treeView, 0, 0, width, height) + console.log("Local Rect " + localRect) + console.log("Rect " + rect) + console.log("Desktop size " + Qt.size(desktop.width, desktop.height)); + } + + TableViewColumn { + title: "Name"; + role: "display"; +// delegate: Text { +// text: styleData.value +// renderType: Text.QtRendering +// elite: styleData.elideMode +// } + } } HifiControls.TextField { diff --git a/interface/resources/qml/windows/Window.qml b/interface/resources/qml/windows/Window.qml index ce1834d464..d7891da2ea 100644 --- a/interface/resources/qml/windows/Window.qml +++ b/interface/resources/qml/windows/Window.qml @@ -45,6 +45,30 @@ Fadable { // The content to place inside the window, determined by the client default property var content + property var rectifier: Timer { + property bool executing: false; + interval: 100 + repeat: false + running: false + + onTriggered: { + executing = true; + x = Math.floor(x); + y = Math.floor(y); + executing = false; + } + + function begin() { + if (!executing) { + restart(); + } + } + } + + + onXChanged: rectifier.begin(); + onYChanged: rectifier.begin(); + // This mouse area serves to raise the window. To function, it must live // in the window and have a higher Z-order than the content, but follow // the position and size of frame decoration diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index bf145d0e29..6db1b26db7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1260,7 +1260,7 @@ void Application::initializeUi() { auto resultVec = _compositor.screenToOverlay(toGlm(pt)); result = QPointF(resultVec.x, resultVec.y); } - return result; + return result.toPoint(); }); offscreenUi->resume(); connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){ @@ -4418,7 +4418,7 @@ bool Application::displayAvatarAttachmentConfirmationDialog(const QString& name) } void Application::toggleRunningScriptsWidget() { - static const QUrl url("dialogs/RunningScripts.qml"); + static const QUrl url("hifi/dialogs/RunningScripts.qml"); DependencyManager::get()->show(url, "RunningScripts"); //if (_runningScriptsWidget->isVisible()) { // if (_runningScriptsWidget->hasFocus()) { diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.h b/libraries/gl/src/gl/OffscreenQmlSurface.h index fb916178ad..9e3ee06f92 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.h +++ b/libraries/gl/src/gl/OffscreenQmlSurface.h @@ -35,7 +35,7 @@ public: OffscreenQmlSurface(); virtual ~OffscreenQmlSurface(); - using MouseTranslator = std::function; + using MouseTranslator = std::function; virtual void create(QOpenGLContext* context); void resize(const QSize& size); @@ -94,7 +94,7 @@ private: bool _polish{ true }; bool _paused{ true }; uint8_t _maxFps{ 60 }; - MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p; } }; + MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; }; From b1267f9d378e6943bad6437bb8167eaf777db29b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 5 Feb 2016 13:53:45 -0800 Subject: [PATCH 23/26] try to put things back when a double-parent equip is released. if an equpped object is pulled too far from a hand, automatically unequip it. --- examples/controllers/handControllerGrab.js | 39 ++++++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index 4403637bae..cc37e8ca47 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -775,6 +775,7 @@ function MyController(hand) { this.search = function() { this.grabbedEntity = null; this.isInitialGrab = false; + this.doubleParentGrab = false; if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) { this.setState(STATE_RELEASE); @@ -941,7 +942,7 @@ function MyController(hand) { return; } // near grab or equip with action - if (near) { + if (near && grabbableData.refCount < 1) { this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); return; } @@ -977,6 +978,15 @@ function MyController(hand) { if (grabbableData.refCount < 1) { this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); return; + } else { + // it's not physical and it's already held via parenting. go ahead and grab it, but + // save off the current parent and joint. this wont always be right if there are more than + // two grabs and the order of release isn't opposite of the order of grabs. + this.doubleParentGrab = true; + this.previousParentID = props.parentID; + this.previousParentJointIndex = props.parentJointIndex; + this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); + return; } if (WANT_DEBUG_SEARCH_NAME && props.name == WANT_DEBUG_SEARCH_NAME) { print("grab is skipping '" + WANT_DEBUG_SEARCH_NAME + "': fell through."); @@ -1432,6 +1442,17 @@ function MyController(hand) { return; } + var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID"]); + if (props.parentID == MyAvatar.sessionUUID && + Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) { + // for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip. + print("handControllerGrab -- autoreleasing held or equipped item because it is far from hand."); + this.setState(STATE_RELEASE); + this.callEntityMethodOnGrabbed(this.state == STATE_NEAR_GRABBING ? "releaseGrab" : "releaseEquip", + [JSON.stringify(this.hand)]); + return; + } + // Keep track of the fingertip velocity to impart when we release the object. // Note that the idea of using a constant 'tip' velocity regardless of the // object's actual held offset is an idea intended to make it easier to throw things: @@ -1631,9 +1652,14 @@ function MyController(hand) { if (this.grabbedEntity !== null) { if (this.actionID !== null) { Entities.deleteAction(this.grabbedEntity, this.actionID); - //sometimes we want things to stay right where they are when we let go. + // sometimes we want things to stay right where they are when we let go. + var grabData = getEntityCustomData(GRAB_USER_DATA_KEY, this.grabbedEntity, {}); var releaseVelocityData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); - if (releaseVelocityData.disableReleaseVelocity === true || !this.isInitialGrab) { + if (releaseVelocityData.disableReleaseVelocity === true || + // this next line allowed both: + // (1) far-grab, pull to self, near grab, then throw + // (2) equip something physical and adjust it with a other-hand grab without the thing drifting + (!this.isInitialGrab && grabData.refCount > 1)) { Entities.editEntity(this.grabbedEntity, { velocity: { x: 0, @@ -1759,6 +1785,13 @@ function MyController(hand) { Entities.editEntity(entityID, {velocity:{x:0, y:0.1, z:0}}); } data = null; + } else if (this.doubleParentGrab) { + // we parent-grabbed this from another parent grab. try to put it back where we found it. + var deactiveProps = { + parentID: this.previousParentID, + parentJointIndex: this.previousParentJointIndex + }; + Entities.editEntity(entityID, deactiveProps); } } else { data = null; From 07c70a1ba20410596207bc512353c2732bd92aba Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 5 Feb 2016 14:25:46 -0800 Subject: [PATCH 24/26] refix double action grabs --- examples/controllers/handControllerGrab.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index cc37e8ca47..ee2523ee63 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -215,6 +215,10 @@ function getTag() { return "grab-" + MyAvatar.sessionUUID; } +function entityHasActions(entityID) { + return Entities.getActionIDs(entityID).length > 0; +} + function entityIsGrabbedByOther(entityID) { // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. var actionIDs = Entities.getActionIDs(entityID); @@ -1018,7 +1022,9 @@ function MyController(hand) { }; this.distanceGrabTimescale = function(mass, distance) { - var timeScale = DISTANCE_HOLDING_ACTION_TIMEFRAME * mass / DISTANCE_HOLDING_UNITY_MASS * distance / DISTANCE_HOLDING_UNITY_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; } @@ -1349,7 +1355,8 @@ function MyController(hand) { }); } - var handRotation = this.getHandRotation(); + // var handRotation = this.getHandRotation(); + var handRotation = (this.hand === RIGHT_HAND) ? MyAvatar.getRightPalmRotation() : MyAvatar.getLeftPalmRotation(); var handPosition = this.getHandPosition(); var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA); @@ -1376,7 +1383,7 @@ function MyController(hand) { } } - var isPhysical = this.propsArePhysical(grabbedProperties); + var isPhysical = this.propsArePhysical(grabbedProperties) || entityHasActions(this.grabbedEntity); if (isPhysical && this.state == STATE_NEAR_GRABBING) { // grab entity via action if (!this.setupHoldAction()) { From d56702028350542cee23b734c369e84a4f9dc79d Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 5 Feb 2016 15:01:46 -0800 Subject: [PATCH 25/26] trying to zero velocity during an equiped adjustment --- examples/controllers/handControllerGrab.js | 31 +++++++++------------- 1 file changed, 12 insertions(+), 19 deletions(-) diff --git a/examples/controllers/handControllerGrab.js b/examples/controllers/handControllerGrab.js index ee2523ee63..61c57fd86f 100644 --- a/examples/controllers/handControllerGrab.js +++ b/examples/controllers/handControllerGrab.js @@ -946,7 +946,7 @@ function MyController(hand) { return; } // near grab or equip with action - if (near && grabbableData.refCount < 1) { + if (near && (grabbableData.refCount < 1 || entityHasActions(this.grabbedEntity))) { this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP); return; } @@ -1667,18 +1667,6 @@ function MyController(hand) { // (1) far-grab, pull to self, near grab, then throw // (2) equip something physical and adjust it with a other-hand grab without the thing drifting (!this.isInitialGrab && grabData.refCount > 1)) { - Entities.editEntity(this.grabbedEntity, { - velocity: { - x: 0, - y: 0, - z: 0 - }, - angularVelocity: { - x: 0, - y: 0, - z: 0 - } - }); noVelocity = true; } } @@ -1783,22 +1771,27 @@ function MyController(hand) { data["dynamic"] && data["parentID"] == NULL_UUID && !data["collisionless"]) { - forceVelocity = true; + deactiveProps["velocity"] = {x: 0.0, y: 0.1, z: 0.0}; + } + if (noVelocity) { + deactiveProps["velocity"] = {x: 0.0, y: 0.0, z: 0.0}; + deactiveProps["angularVelocity"] = {x: 0.0, y: 0.0, z: 0.0}; } Entities.editEntity(entityID, deactiveProps); - - if (forceVelocity) { - Entities.editEntity(entityID, {velocity:{x:0, y:0.1, z:0}}); - } data = null; } else if (this.doubleParentGrab) { // we parent-grabbed this from another parent grab. try to put it back where we found it. var deactiveProps = { parentID: this.previousParentID, - parentJointIndex: this.previousParentJointIndex + parentJointIndex: this.previousParentJointIndex, + velocity: {x: 0.0, y: 0.0, z: 0.0}, + angularVelocity: {x: 0.0, y: 0.0, z: 0.0} }; Entities.editEntity(entityID, deactiveProps); + } else if (noVelocity) { + Entities.editEntity(entityID, {velocity: {x: 0.0, y: 0.0, z: 0.0}, + angularVelocity: {x: 0.0, y: 0.0, z: 0.0}}); } } else { data = null; From 39bb68931fd7759b4a25dbcb87933e0be5e8e4a6 Mon Sep 17 00:00:00 2001 From: ericrius1 Date: Fri, 5 Feb 2016 15:48:34 -0800 Subject: [PATCH 26/26] updated gun position --- unpublishedScripts/hiddenEntityReset.js | 44 ++++++++++++------------- unpublishedScripts/masterReset.js | 44 ++++++++++++------------- 2 files changed, 44 insertions(+), 44 deletions(-) diff --git a/unpublishedScripts/hiddenEntityReset.js b/unpublishedScripts/hiddenEntityReset.js index 903388bb43..4a64b34510 100644 --- a/unpublishedScripts/hiddenEntityReset.js +++ b/unpublishedScripts/hiddenEntityReset.js @@ -299,35 +299,35 @@ }, dynamic: true, userData: JSON.stringify({ - wearable: { - joints: { - RightHand: [{ - x: 0.07079616189002991, - y: 0.20177987217903137, - z: 0.06374628841876984 + "wearable": { + "joints": { + "RightHand": [{ + "x": 0.07079616189002991, + "y": 0.20177987217903137, + "z": 0.06374628841876984 }, { - x: -0.5863648653030396, - y: -0.46007341146469116, - z: 0.46949487924575806, - w: -0.4733745753765106 + "x": -0.5863648653030396, + "y": -0.46007341146469116, + "z": 0.46949487924575806, + "w": -0.4733745753765106 }], - LeftHand: [{ - x: 0.1802254319190979, - y: 0.13442856073379517, - z: 0.08504903316497803 + "LeftHand": [{ + "x": 0.0012094751000404358, + "y": 0.1991066336631775, + "z": 0.079972043633461 }, { - x: 0.2198076844215393, - y: -0.7377811074256897, - z: 0.2780133783817291, - w: 0.574519157409668 + "x": 0.29249316453933716, + "y": -0.6115763187408447, + "z": 0.5668558478355408, + "w": 0.46807748079299927 }] } }, - grabbableKey: { - invertSolidWhileHeld: true + "grabbableKey": { + "invertSolidWhileHeld": true }, - resetMe: { - resetMe: true + "resetMe": { + "resetMe": true } }) }); diff --git a/unpublishedScripts/masterReset.js b/unpublishedScripts/masterReset.js index 306ae294fd..c5669b9ac0 100644 --- a/unpublishedScripts/masterReset.js +++ b/unpublishedScripts/masterReset.js @@ -283,35 +283,35 @@ MasterReset = function() { damping: 0.5, collisionSoundURL: "http://hifi-content.s3.amazonaws.com/james/pistol/sounds/drop.wav", userData: JSON.stringify({ - wearable: { - joints: { - RightHand: [{ - x: 0.07079616189002991, - y: 0.20177987217903137, - z: 0.06374628841876984 + "wearable": { + "joints": { + "RightHand": [{ + "x": 0.07079616189002991, + "y": 0.20177987217903137, + "z": 0.06374628841876984 }, { - x: -0.5863648653030396, - y: -0.46007341146469116, - z: 0.46949487924575806, - w: -0.4733745753765106 + "x": -0.5863648653030396, + "y": -0.46007341146469116, + "z": 0.46949487924575806, + "w": -0.4733745753765106 }], - LeftHand: [{ - x: 0.1802254319190979, - y: 0.13442856073379517, - z: 0.08504903316497803 + "LeftHand": [{ + "x": 0.0012094751000404358, + "y": 0.1991066336631775, + "z": 0.079972043633461 }, { - x: 0.2198076844215393, - y: -0.7377811074256897, - z: 0.2780133783817291, - w: 0.574519157409668 + "x": 0.29249316453933716, + "y": -0.6115763187408447, + "z": 0.5668558478355408, + "w": 0.46807748079299927 }] } }, - grabbableKey: { - invertSolidWhileHeld: true + "grabbableKey": { + "invertSolidWhileHeld": true }, - resetMe: { - resetMe: true + "resetMe": { + "resetMe": true } }) });