Merge branch 'master' of https://github.com/highfidelity/hifi into red

This commit is contained in:
samcake 2016-02-08 10:30:54 -08:00
commit ad3f3a6dcf
47 changed files with 1591 additions and 252 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();

View file

@ -0,0 +1,63 @@
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;
}
Entities.costMultiplier = 0.02;
Entities.debitEnergySource.connect(debitAvatarEnergy);
MyAvatar.energyChanged.connect(energyChanged);
Script.update.connect(update);
Script.scriptEnding.connect(cleanup);

View file

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

View file

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

View file

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

View file

@ -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<OffscreenUi>()->show(url, "RunningScripts");
//if (_runningScriptsWidget->isVisible()) {
// if (_runningScriptsWidget->hasFocus()) {

View file

@ -298,8 +298,20 @@ void MyAvatar::update(float deltaTime) {
auto audio = DependencyManager::get<AudioClient>();
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);
@ -1892,3 +1904,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);
}

View file

@ -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);
@ -276,8 +277,10 @@ signals:
void transformChanged();
void newCollisionSoundURL(const QUrl& url);
void collisionWithEntity(const Collision& collision);
void energyChanged(float newEnergy);
void positionGoneTo();
private:
glm::vec3 getWorldBodyPosition() const;
@ -413,9 +416,21 @@ private:
AtRestDetector _hmdAtRestDetector;
bool _lastIsMoving { false };
bool _hoverReferenceCameraFacingIsCaptured { false };
glm::vec3 _hoverReferenceCameraFacing; // hmd sensor space
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);

View file

@ -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);
@ -37,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);
@ -49,6 +51,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 +71,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 +170,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;

View file

@ -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;
@ -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; }
@ -49,12 +50,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,18 +69,21 @@ protected:
// _anim[frame][joint]
std::vector<AnimPoseVec> _anim;
std::vector<AnimPoseVec> _mirrorAnim;
QString _url;
float _startFrame;
float _endFrame;
float _timeScale;
bool _loopFlag;
bool _mirrorFlag;
float _frame;
QString _startFrameVar;
QString _endFrameVar;
QString _timeScaleVar;
QString _loopFlagVar;
QString _mirrorFlagVar;
QString _frameVar;
// no copies

View file

@ -155,6 +155,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()) { \
@ -232,13 +240,15 @@ 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);
READ_OPTIONAL_STRING(mirrorFlagVar, jsonObj);
auto node = std::make_shared<AnimClip>(id, url, startFrame, endFrame, timeScale, loopFlag);
auto node = std::make_shared<AnimClip>(id, url, startFrame, endFrame, timeScale, loopFlag, mirrorFlag);
if (!startFrameVar.isEmpty()) {
node->setStartFrameVar(startFrameVar);
@ -252,6 +262,9 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString&
if (!loopFlagVar.isEmpty()) {
node->setLoopFlagVar(loopFlagVar);
}
if (!mirrorFlagVar.isEmpty()) {
node->setMirrorFlagVar(mirrorFlagVar);
}
return node;
}

View file

@ -51,6 +51,11 @@ AnimPose AnimPose::inverse() const {
return AnimPose(glm::inverse(static_cast<glm::mat4>(*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);

View file

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

View file

@ -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<FBXJoint>& joints) {
_joints = joints;
@ -150,6 +175,24 @@ void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& 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

View file

@ -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<int> _mirrorMap;
// no copies
AnimSkeleton(const AnimSkeleton&) = delete;

View file

@ -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<AnimClip>(role, url, firstFrame, lastFrame, timeScale, loop);
auto clipNode = std::make_shared<AnimClip>(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<AnimClip>("prefetch", url, 0, 0, 1.0, false);
auto clipNode = std::make_shared<AnimClip>("prefetch", url, 0, 0, 1.0, false, false);
_prefetchedAnimations.push_back(clipNode);
}

View file

@ -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
@ -987,7 +988,7 @@ EntityTreePointer EntityItem::getTree() const {
return tree;
}
bool EntityItem::wantTerseEditLogging() {
bool EntityItem::wantTerseEditLogging() const {
EntityTreePointer tree = getTree();
return tree ? tree->wantTerseEditLogging() : false;
}

View file

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

View file

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

View file

@ -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"
@ -123,6 +122,20 @@ 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();
float cost = calculateCost(density * volume, 0, newVelocity);
cost *= costMultiplier;
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.
@ -215,9 +228,28 @@ 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();
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.
@ -231,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());
}
@ -246,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) {
@ -320,6 +365,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();
float cost = calculateCost(density * volume, velocity, 0);
cost *= costMultiplier;
if(cost > _currentAvatarEnergy) {
return;
} else {
//debit the avatar energy and continue
emit debitEnergySource(cost);
}
if (entity->getLocked()) {
shouldDelete = false;
} else {
@ -996,3 +1056,20 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) {
Q_RETURN_ARG(QStringList, result), Q_ARG(QUuid, entityID));
return result;
}
float 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;
}
float EntityScriptingInterface::getCostMultiplier() {
return costMultiplier;
}
void EntityScriptingInterface::setCostMultiplier(float value) {
costMultiplier = value;
}

View file

@ -16,6 +16,8 @@
#include <QtCore/QObject>
#include <QtCore/QStringList>
#include <QtQml/QJSValue>
#include <QtQml/QJSValueList>
#include <DependencyManager.h>
#include <Octree.h>
@ -57,6 +59,9 @@ 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)
Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier)
public:
EntityScriptingInterface(bool bidOnSimulationOwnership);
@ -67,7 +72,7 @@ public:
void setEntityTree(EntityTreePointer modelTree);
EntityTreePointer getEntityTree() { return _entityTree; }
void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) { _entitiesScriptEngine = engine; }
float calculateCost(float mass, float oldVelocity, float newVelocity);
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(float value);
private:
bool actionWorker(const QUuid& entityID, std::function<bool(EntitySimulation*, EntityItemPointer)> actor);
@ -205,7 +212,15 @@ private:
EntityTreePointer _entityTree;
EntitiesScriptEngineProvider* _entitiesScriptEngine { nullptr };
bool _bidOnSimulationOwnership { false };
float _currentAvatarEnergy = { FLT_MAX };
float getCurrentAvatarEnergy() { return _currentAvatarEnergy; }
void setCurrentAvatarEnergy(float energy);
float costMultiplier = { 0.01f };
float getCostMultiplier();
void setCostMultiplier(float value);
};
#endif // hifi_EntityScriptingInterface_h

View file

@ -11,38 +11,58 @@
//#include <PerfStat.h>
#include "EntityItem.h"
#include "SimpleEntitySimulation.h"
#include <DirtyOctreeElementOperator.h>
#include "EntityItem.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<LimitedNodeList>();
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));
entity->setVelocity(Vectors::ZERO);
entity->setAngularVelocity(Vectors::ZERO);
entity->setAcceleration(Vectors::ZERO);
// 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();
}
} else {
++itemItr;
entity->markAsChangedOnServer();
// dirty all the tree elements that contain the entity
DirtyOctreeElementOperator op(entity->getElement());
getEntityTree()->recurseTreeWithOperator(&op);
} else if (expiry < _nextSimulationExpiry) {
_nextSimulationExpiry = expiry;
}
++itemItr;
}
}

View file

@ -29,6 +29,7 @@ protected:
virtual void clearEntitiesInternal() override;
SetOfEntities _entitiesWithSimulator;
quint64 _nextSimulationExpiry { 0 };
};
#endif // hifi_SimpleEntitySimulation_h

View file

@ -16,11 +16,11 @@
#include <NumericalConstants.h>
// 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) {

View file

@ -18,10 +18,10 @@
#include <SharedUtil.h>
#include <UUID.h>
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;

View file

@ -35,7 +35,7 @@ public:
OffscreenQmlSurface();
virtual ~OffscreenQmlSurface();
using MouseTranslator = std::function<QPointF(const QPointF&)>;
using MouseTranslator = std::function<QPoint(const QPointF&)>;
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(); } };
};

View file

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

View file

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

View file

@ -52,11 +52,11 @@ private:
Mutex2& _mutex2;
};
std::unique_ptr<SendQueue> SendQueue::create(Socket* socket, HifiSockAddr destination) {
std::unique_ptr<SendQueue> 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<SendQueue>(new SendQueue(socket, destination));
auto queue = std::unique_ptr<SendQueue>(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> 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> 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

View file

@ -50,7 +50,8 @@ public:
Stopped
};
static std::unique_ptr<SendQueue> create(Socket* socket, HifiSockAddr destination);
static std::unique_ptr<SendQueue> create(Socket* socket, HifiSockAddr destination,
SequenceNumber currentSequenceNumber = SequenceNumber());
void queuePacket(std::unique_ptr<Packet> packet);
void queuePacketList(std::unique_ptr<PacketList> 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<uint32_t> _lastACKSequenceNumber { 0 }; // Last ACKed sequence number
SequenceNumber _currentSequenceNumber; // Last sequence number sent out
SequenceNumber _currentSequenceNumber { 0 }; // Last sequence number sent out
std::atomic<uint32_t> _atomicCurrentSequenceNumber { 0 }; // Atomic for last sequence number sent out
std::atomic<int> _packetSendPeriod { 0 }; // Interval between two packet send event in microseconds, set from CC

View file

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

View file

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

View file

@ -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,27 +100,35 @@ bool EntityMotionState::handleEasyChanges(uint32_t& flags) {
ObjectMotionState::handleEasyChanges(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
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
// hint to Bullet that the object is deactivating
_body->setActivationState(WANTS_DEACTIVATION);
_outgoingPriority = NO_PRORITY;
} else {
// simulation ownership has been removed by an external simulator
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) {
// we own the simulation or our priority looses to (or ties with) remote
_outgoingPriority = NO_PRORITY;
// either we already own the simulation or our old outgoing priority momentarily looses to current owner
// so we clear it
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
}
}
}
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 +209,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,14 +240,14 @@ 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();
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) {
@ -253,7 +258,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
_serverAngularVelocity = bulletToGLM(_body->getAngularVelocity());
_lastStep = simulationStep;
_serverActionData = _entity->getActionData();
_sentInactive = true;
_numInactiveUpdates = 1;
return false;
}
@ -266,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;
}
@ -374,11 +384,12 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s
}
if (_entity->getSimulatorID() != sessionID) {
// we don't own the simulation, but maybe we should...
if (_outgoingPriority != NO_PRORITY) {
// we don't own the simulation
if (_outgoingPriority != ZERO_SIMULATION_PRIORITY) {
// but we would like to own it
if (_outgoingPriority < _entity->getSimulationPriority()) {
// our priority loses to remote, so we don't bother to bid
_outgoingPriority = NO_PRORITY;
// but our priority loses to remote, so we don't bother trying
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
return false;
}
return usecTimestampNow() > _nextOwnershipBid;
@ -400,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) {
@ -440,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
@ -488,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<quint8>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY));
properties.setSimulationOwner(sessionID, glm::max<uint8_t>(_outgoingPriority, VOLUNTEER_SIMULATION_PRIORITY));
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;
}
@ -558,7 +571,7 @@ void EntityMotionState::clearIncomingDirtyFlags() {
}
// virtual
quint8 EntityMotionState::getSimulationPriority() const {
uint8_t EntityMotionState::getSimulationPriority() const {
return _entity->getSimulationPriority();
}
@ -568,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));
}
@ -601,7 +614,7 @@ void EntityMotionState::measureBodyAcceleration() {
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
_loopsWithoutOwner = 0;
_lastStep = ObjectMotionState::getWorldSimulationStep();
_sentInactive = false;
_numInactiveUpdates = 0;
}
}
}
@ -631,6 +644,6 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
_entity->computeCollisionGroupAndFinalMask(group, mask);
}
void EntityMotionState::setOutgoingPriority(quint8 priority) {
_outgoingPriority = glm::max<quint8>(_outgoingPriority, priority);
void EntityMotionState::setOutgoingPriority(uint8_t priority) {
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
}

View file

@ -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 _sentInactive; // true if body was inactive when we sent last update
// 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;
quint64 _nextOwnershipBid { 0 };
quint8 _accelerationNearlyGravityCount;
quint64 _nextOwnershipBid = NO_PRORITY;
uint32_t _loopsWithoutOwner;
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

View file

@ -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,13 +274,15 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates&
return;
}
// send outgoing packets
// look for entities to prune or update
QSet<EntityMotionState*>::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 {

View file

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

View file

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