From 6ca17a904aaabffaf50f1ec5ab6854d23b209010 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Mon, 1 Feb 2016 09:58:24 -0800 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 ae6a5aa286df58623d823f66047d5276771eed0e Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Thu, 4 Feb 2016 18:10:43 -0800 Subject: [PATCH 5/8] 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 b1267f9d378e6943bad6437bb8167eaf777db29b Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Fri, 5 Feb 2016 13:53:45 -0800 Subject: [PATCH 6/8] 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 7/8] 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 8/8] 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;