Merge pull request #7037 from sethalves/dressing-room

Dressing room + grab fixes
This commit is contained in:
Brad Hefta-Gaub 2016-02-08 09:19:29 -08:00
commit c28612ea9a
11 changed files with 958 additions and 40 deletions

View file

@ -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();

View file

@ -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;
@ -161,7 +161,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) {
@ -182,7 +182,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];

View file

@ -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
@ -214,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);
@ -774,6 +779,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);
@ -867,36 +873,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;
}
@ -918,7 +946,7 @@ function MyController(hand) {
return;
}
// near grab or equip with action
if (near) {
if (near && (grabbableData.refCount < 1 || entityHasActions(this.grabbedEntity))) {
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
return;
}
@ -926,6 +954,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;
@ -951,6 +982,18 @@ 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.");
}
}
@ -979,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;
}
@ -1310,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);
@ -1337,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()) {
@ -1403,6 +1449,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:
@ -1602,21 +1659,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) {
Entities.editEntity(this.grabbedEntity, {
velocity: {
x: 0,
y: 0,
z: 0
},
angularVelocity: {
x: 0,
y: 0,
z: 0
}
});
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)) {
noVelocity = true;
}
}
@ -1721,15 +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,
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;

View file

@ -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)

View file

@ -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)

View file

@ -13,9 +13,12 @@
var TEST_MODEL_URL = 'https://s3.amazonaws.com/hifi-public/ozan/avatars/albert/albert/albert.fbx';
var doppelgangers = [];
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 = false;
var THROTTLE_RATE = 100;
var doppelgangers = [];
function Doppelganger(avatar) {
this.initialProperties = {
@ -25,13 +28,21 @@ function Doppelganger(avatar) {
// dimensions: getAvatarDimensions(avatar),
position: putDoppelgangerAcrossFromAvatar(this, avatar),
rotation: rotateDoppelgangerTowardAvatar(this, avatar),
collisionsWillMove: false,
ignoreForCollisions: false,
script: FREEZE_TOGGLER_SCRIPT_URL,
userData: JSON.stringify({
grabbableKey: {
grabbable: false,
wantsTrigger: true
}
})
};
this.id = createDoppelgangerEntity(this);
this.avatar = avatar;
return this;
}
function getJointData(avatar) {
var allJointData = [];
var jointNames = MyAvatar.jointNames;
@ -297,30 +308,113 @@ 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
}
function getAvatarDimensions(avatar) {
return dimensions;
}
function rotateDoppelgangerTowardAvatar(doppelganger, avatar) {
var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0);
avatarRot = Vec3.multiply(-1, avatarRot);
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 connectDoppelgangerUpdates() {
// Script.update.connect(updateDoppelganger);
Script.setInterval(updateDoppelganger, 100);
Script.update.connect(updateDoppelganger);
isConnected = true;
}
function disconnectDoppelgangerUpdates() {
Script.update.disconnect(updateDoppelganger);
print('SHOULD DISCONNECT')
if (isConnected === true) {
Script.update.disconnect(updateDoppelganger);
}
isConnected = false;
}
function updateDoppelganger() {
var sinceLastUpdate = 0;
function updateDoppelganger(deltaTime) {
if (THROTTLE === true) {
sinceLastUpdate = sinceLastUpdate + deltaTime * 100;
if (sinceLastUpdate > THROTTLE_RATE) {
sinceLastUpdate = 0;
} else {
return;
}
}
var absoluteXforms = buildAbsoluteXformsFromMyAvatar();
if (MIRROR_JOINT_DATA) {
var mirroredAbsoluteXforms = [];
@ -346,14 +440,190 @@ function makeDoppelgangerForMyAvatar() {
connectDoppelgangerUpdates();
}
function subscribeToWearableMessages() {
Messages.subscribe('Hifi-Doppelganger-Wearable');
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 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
}
}
makeDoppelgangerForMyAvatar();
subscribeToWearableMessages();
subscribeToWearableMessagesForAvatar();
subscribeToFreezeMessages();
function cleanup() {
//disconnectDoppelgangerUpdates();
if (isConnected === true) {
disconnectDoppelgangerUpdates();
}
doppelgangers.forEach(function(doppelganger) {
print('DOPPELGANGER' + doppelganger.id)
Entities.deleteEntity(doppelganger.id);
});
}
Script.scriptEnding.connect(cleanup);
Script.scriptEnding.connect(cleanup);

View file

@ -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();
})

View file

@ -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 + ")");
};
})

View file

@ -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();
})

View file

@ -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

View file

@ -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();