mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into red
This commit is contained in:
commit
ad3f3a6dcf
47 changed files with 1591 additions and 252 deletions
|
@ -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();
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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;
|
||||
|
|
160
examples/dressing_room/createPlatformWithLights.js
Normal file
160
examples/dressing_room/createPlatformWithLights.js
Normal 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)
|
117
examples/dressing_room/createTableWithItems.js
Normal file
117
examples/dressing_room/createTableWithItems.js
Normal 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)
|
|
@ -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);
|
76
examples/dressing_room/freezeToggler.js
Normal file
76
examples/dressing_room/freezeToggler.js
Normal 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();
|
||||
})
|
34
examples/dressing_room/loadingAreaEntity.js
Normal file
34
examples/dressing_room/loadingAreaEntity.js
Normal 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 + ")");
|
||||
};
|
||||
|
||||
})
|
48
examples/dressing_room/mirroredEntity.js
Normal file
48
examples/dressing_room/mirroredEntity.js
Normal 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();
|
||||
})
|
11
examples/dressing_room/setupDressingRoom.js
Normal file
11
examples/dressing_room/setupDressingRoom.js
Normal 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
|
138
examples/dressing_room/wearablesManager.js
Normal file
138
examples/dressing_room/wearablesManager.js
Normal 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();
|
63
examples/example/ui/MyEnergyBar.js
Normal file
63
examples/example/ui/MyEnergyBar.js
Normal 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);
|
|
@ -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);
|
||||
|
|
|
@ -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 {
|
|
@ -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
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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...
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ protected:
|
|||
virtual void clearEntitiesInternal() override;
|
||||
|
||||
SetOfEntities _entitiesWithSimulator;
|
||||
quint64 _nextSimulationExpiry { 0 };
|
||||
};
|
||||
|
||||
#endif // hifi_SimpleEntitySimulation_h
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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(); } };
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
30
libraries/octree/src/DirtyOctreeElementOperator.cpp
Normal file
30
libraries/octree/src/DirtyOctreeElementOperator.cpp
Normal 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;
|
||||
}
|
30
libraries/octree/src/DirtyOctreeElementOperator.h
Normal file
30
libraries/octree/src/DirtyOctreeElementOperator.h
Normal 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
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
|
@ -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
|
||||
}
|
||||
})
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue