content/hifi-public/scripts/attachedEntitiesManager.js
Dale Glass 0d14e5a379 Initial data.
Needs a lot of cleanup. Data has been de-duplicated, and where identical copies existed, one of them
has been replaced with a symlink.

Some files have been excluded, such as binaries, installers and debug dumps. Some of that may still
be present.
2022-02-13 18:59:11 +01:00

296 lines
11 KiB
JavaScript

//
// attachedEntitiesManager.js
//
// Created by Seth Alves on 2016-1-20
// 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
//
Script.include("libraries/utils.js");
var NULL_UUID = "{00000000-0000-0000-0000-000000000000}";
var DEFAULT_WEARABLE_DATA = {
joints: {}
};
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;
var SHOW_TOOL_BAR = false;
// tool bar
if (SHOW_TOOL_BAR) {
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
var BUTTON_SIZE = 32;
var PADDING = 3;
Script.include(["libraries/toolBars.js"]);
var toolBar = new ToolBar(0, 0, ToolBar.VERTICAL, "highfidelity.attachedEntities.toolbar", function(screenSize) {
return {
x: (BUTTON_SIZE + PADDING),
y: (screenSize.y / 2 - BUTTON_SIZE * 2 + PADDING)
};
});
var saveButton = toolBar.addOverlay("image", {
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: ".../save.png",
color: {
red: 255,
green: 255,
blue: 255
},
alpha: 1
});
var loadButton = toolBar.addOverlay("image", {
width: BUTTON_SIZE,
height: BUTTON_SIZE,
imageURL: ".../load.png",
color: {
red: 255,
green: 255,
blue: 255
},
alpha: 1
});
}
function mousePressEvent(event) {
var clickedOverlay = Overlays.getOverlayAtPoint({
x: event.x,
y: event.y
});
if (clickedOverlay == saveButton) {
manager.saveAttachedEntities();
} else if (clickedOverlay == loadButton) {
manager.loadAttachedEntities();
}
}
function scriptEnding() {
if (SHOW_TOOL_BAR) {
toolBar.cleanup();
}
}
if (SHOW_TOOL_BAR) {
Controller.mousePressEvent.connect(mousePressEvent);
}
Script.scriptEnding.connect(scriptEnding);
// attached entites
function AttachedEntitiesManager() {
this.subscribeToMessages = function() {
Messages.subscribe('Hifi-Object-Manipulation');
Messages.messageReceived.connect(this.handleWearableMessages);
}
this.handleWearableMessages = function(channel, message, sender) {
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');
return;
}
if (parsedMessage.action === 'update' ||
parsedMessage.action === 'loaded') {
// ignore
} else if (parsedMessage.action === 'release') {
manager.handleEntityRelease(parsedMessage.grabbedEntity, parsedMessage.joint)
// manager.saveAttachedEntities();
} else if (parsedMessage.action === 'equip') {
// manager.saveAttachedEntities();
} else {
print('attachedEntitiesManager -- unknown actions: ' + parsedMessage.action);
}
}
this.avatarIsInDressingRoom = function() {
// return true if MyAvatar is near the dressing room
var possibleDressingRoom = Entities.findEntities(MyAvatar.position, DRESSING_ROOM_DISTANCE);
for (i = 0; i < possibleDressingRoom.length; i++) {
var entityID = possibleDressingRoom[i];
var props = Entities.getEntityProperties(entityID);
if (props.name == 'Hifi-Dressing-Room-Base') {
return true;
}
}
return false;
}
this.handleEntityRelease = function(grabbedEntity, releasedFromJoint) {
// if this is still equipped, just rewrite the position information.
var grabData = getEntityCustomData('grabKey', grabbedEntity, {});
if ("refCount" in grabData && grabData.refCount > 0) {
manager.updateRelativeOffsets(grabbedEntity);
return;
}
var allowedJoints = getEntityCustomData('wearable', grabbedEntity, DEFAULT_WEARABLE_DATA).joints;
var props = Entities.getEntityProperties(grabbedEntity, ["position", "parentID", "parentJointIndex"]);
if (props.parentID === NULL_UUID || props.parentID === MyAvatar.sessionUUID) {
var bestJointName = "";
var bestJointIndex = -1;
var bestJointDistance = 0;
var bestJointOffset = null;
for (var jointName in allowedJoints) {
if ((releasedFromJoint == "LeftHand" || releasedFromJoint == "RightHand") &&
(jointName == "LeftHand" || jointName == "RightHand")) {
// don't auto-attach to a hand if a hand just dropped something
continue;
}
var jointIndex = MyAvatar.getJointIndex(jointName);
if (jointIndex >= 0) {
var jointPosition = MyAvatar.getJointPosition(jointIndex);
var distanceFromJoint = Vec3.distance(jointPosition, props.position);
if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) {
if (bestJointIndex == -1 || distanceFromJoint < bestJointDistance) {
bestJointName = jointName;
bestJointIndex = jointIndex;
bestJointDistance = distanceFromJoint;
bestJointOffset = allowedJoints[jointName];
}
}
}
}
if (bestJointIndex != -1) {
var wearProps = {
parentID: MyAvatar.sessionUUID,
parentJointIndex: bestJointIndex
};
if (bestJointOffset && bestJointOffset.constructor === Array) {
if (this.avatarIsInDressingRoom() || bestJointOffset.length < 2) {
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];
}
}
Entities.editEntity(grabbedEntity, wearProps);
} else if (props.parentID != NULL_UUID) {
// drop the entity and set it to have no parent (not on the avatar), unless it's being equipped in a hand.
if (props.parentID === MyAvatar.sessionUUID &&
(props.parentJointIndex == MyAvatar.getJointIndex("RightHand") ||
props.parentJointIndex == MyAvatar.getJointIndex("LeftHand"))) {
// this is equipped on a hand -- don't clear the parent.
} else {
Entities.editEntity(grabbedEntity, { parentID: NULL_UUID });
}
}
}
}
this.updateRelativeOffsets = function(entityID) {
// save the preferred (current) relative position and rotation into the user-data of the entity
var props = Entities.getEntityProperties(entityID);
if (props.parentID == MyAvatar.sessionUUID) {
grabData = getEntityCustomData('grabKey', entityID, {});
var wearableData = getEntityCustomData('wearable', entityID, DEFAULT_WEARABLE_DATA);
var currentJointName = MyAvatar.getJointNames()[props.parentJointIndex];
wearableData.joints[currentJointName] = [props.localPosition, props.localRotation];
setEntityCustomData('wearable', entityID, wearableData);
return true;
}
return false;
}
this.saveAttachedEntities = function() {
print("--- saving attached entities ---");
saveData = [];
var nearbyEntities = Entities.findEntities(MyAvatar.position, ATTACHED_ENTITY_SEARCH_DISTANCE);
for (i = 0; i < nearbyEntities.length; i++) {
var entityID = nearbyEntities[i];
if (this.updateRelativeOffsets(entityID)) {
var props = Entities.getEntityProperties(entityID); // refresh, because updateRelativeOffsets changed them
this.scrubProperties(props);
saveData.push(props);
}
}
Settings.setValue(ATTACHED_ENTITIES_SETTINGS_KEY, JSON.stringify(saveData));
}
this.scrubProperties = function(props) {
var toScrub = ["queryAACube", "position", "rotation",
"created", "ageAsText", "naturalDimensions",
"naturalPosition", "velocity", "acceleration",
"angularVelocity", "boundingBox"];
toScrub.forEach(function(propertyName) {
delete props[propertyName];
});
// if the userData has a grabKey, clear old state
if ("userData" in props) {
try {
parsedUserData = JSON.parse(props.userData);
if ("grabKey" in parsedUserData) {
parsedUserData.grabKey.refCount = 0;
delete parsedUserData.grabKey["avatarId"];
props["userData"] = JSON.stringify(parsedUserData);
}
} catch (e) {
}
}
}
this.loadAttachedEntities = function(grabbedEntity) {
print("--- loading attached entities ---");
jsonAttachmentData = Settings.getValue(ATTACHED_ENTITIES_SETTINGS_KEY);
var loadData = [];
try {
loadData = JSON.parse(jsonAttachmentData);
} catch (e) {
print('error parsing saved attachment data');
return;
}
for (i = 0; i < loadData.length; i ++) {
var savedProps = loadData[ i ];
var currentProps = Entities.getEntityProperties(savedProps.id);
if (currentProps.id == savedProps.id &&
// TODO -- also check that parentJointIndex matches?
currentProps.parentID == MyAvatar.sessionUUID) {
// entity is already in-world. TODO -- patch it up?
continue;
}
this.scrubProperties(savedProps);
delete savedProps["id"];
savedProps.parentID = MyAvatar.sessionUUID; // this will change between sessions
var loadedEntityID = Entities.addEntity(savedProps);
Messages.sendMessage('Hifi-Object-Manipulation', JSON.stringify({
action: 'loaded',
grabbedEntity: loadedEntityID
}));
}
}
}
var manager = new AttachedEntitiesManager();
manager.subscribeToMessages();