mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 20:48:56 +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
|
//For procedural walk animation
|
||||||
HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
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();
|
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_ENTITY_SEARCH_DISTANCE = 10.0;
|
||||||
var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES";
|
var ATTACHED_ENTITIES_SETTINGS_KEY = "ATTACHED_ENTITIES";
|
||||||
var DRESSING_ROOM_DISTANCE = 2.0;
|
var DRESSING_ROOM_DISTANCE = 2.0;
|
||||||
|
@ -161,7 +161,7 @@ function AttachedEntitiesManager() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
var jointIndex = MyAvatar.getJointIndex(jointName);
|
var jointIndex = MyAvatar.getJointIndex(jointName);
|
||||||
if (jointIndex > 0) {
|
if (jointIndex >= 0) {
|
||||||
var jointPosition = MyAvatar.getJointPosition(jointIndex);
|
var jointPosition = MyAvatar.getJointPosition(jointIndex);
|
||||||
var distanceFromJoint = Vec3.distance(jointPosition, props.position);
|
var distanceFromJoint = Vec3.distance(jointPosition, props.position);
|
||||||
if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) {
|
if (distanceFromJoint <= MINIMUM_DROP_DISTANCE_FROM_JOINT) {
|
||||||
|
@ -182,7 +182,9 @@ function AttachedEntitiesManager() {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (bestJointOffset && bestJointOffset.constructor === Array && bestJointOffset.length > 1) {
|
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.
|
// don't snap the entity to the preferred position if the avatar is in the dressing room.
|
||||||
wearProps.localPosition = bestJointOffset[0];
|
wearProps.localPosition = bestJointOffset[0];
|
||||||
wearProps.localRotation = bestJointOffset[1];
|
wearProps.localRotation = bestJointOffset[1];
|
||||||
|
|
|
@ -19,6 +19,7 @@ Script.include("../libraries/utils.js");
|
||||||
//
|
//
|
||||||
var WANT_DEBUG = false;
|
var WANT_DEBUG = false;
|
||||||
var WANT_DEBUG_STATE = false;
|
var WANT_DEBUG_STATE = false;
|
||||||
|
var WANT_DEBUG_SEARCH_NAME = null;
|
||||||
|
|
||||||
//
|
//
|
||||||
// these tune time-averaging and "on" value for analog trigger
|
// these tune time-averaging and "on" value for analog trigger
|
||||||
|
@ -214,6 +215,10 @@ function getTag() {
|
||||||
return "grab-" + MyAvatar.sessionUUID;
|
return "grab-" + MyAvatar.sessionUUID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function entityHasActions(entityID) {
|
||||||
|
return Entities.getActionIDs(entityID).length > 0;
|
||||||
|
}
|
||||||
|
|
||||||
function entityIsGrabbedByOther(entityID) {
|
function entityIsGrabbedByOther(entityID) {
|
||||||
// by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*.
|
// by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*.
|
||||||
var actionIDs = Entities.getActionIDs(entityID);
|
var actionIDs = Entities.getActionIDs(entityID);
|
||||||
|
@ -774,6 +779,7 @@ function MyController(hand) {
|
||||||
this.search = function() {
|
this.search = function() {
|
||||||
this.grabbedEntity = null;
|
this.grabbedEntity = null;
|
||||||
this.isInitialGrab = false;
|
this.isInitialGrab = false;
|
||||||
|
this.doubleParentGrab = false;
|
||||||
|
|
||||||
if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) {
|
if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) {
|
||||||
this.setState(STATE_RELEASE);
|
this.setState(STATE_RELEASE);
|
||||||
|
@ -867,36 +873,58 @@ function MyController(hand) {
|
||||||
grabbable = false;
|
grabbable = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("grabbable" in grabbableDataForCandidate) {
|
if ("grabbable" in grabbableDataForCandidate) {
|
||||||
// if userData indicates that this is grabbable or not, override the default.
|
// if userData indicates that this is grabbable or not, override the default.
|
||||||
grabbable = grabbableDataForCandidate.grabbable;
|
grabbable = grabbableDataForCandidate.grabbable;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!grabbable && !grabbableDataForCandidate.wantsTrigger) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (forbiddenTypes.indexOf(propsForCandidate.type) >= 0) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (propsForCandidate.locked && !grabbableDataForCandidate.wantsTrigger) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
if (forbiddenNames.indexOf(propsForCandidate.name) >= 0) {
|
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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
distance = Vec3.distance(propsForCandidate.position, handPosition);
|
distance = Vec3.distance(propsForCandidate.position, handPosition);
|
||||||
if (distance > PICK_MAX_DISTANCE) {
|
if (distance > PICK_MAX_DISTANCE) {
|
||||||
// too far away, don't grab
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_EQUIP_SEARCHING) {
|
if (propsForCandidate.parentID != NULL_UUID && this.state == STATE_EQUIP_SEARCHING) {
|
||||||
// don't allow a double-equip
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.state == STATE_SEARCHING && !isPhysical && distance > NEAR_PICK_MAX_DISTANCE) {
|
if (this.state == STATE_SEARCHING && !isPhysical && distance > NEAR_PICK_MAX_DISTANCE) {
|
||||||
// we can't distance-grab non-physical
|
// 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;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -918,7 +946,7 @@ function MyController(hand) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// near grab or equip with action
|
// 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);
|
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -926,6 +954,9 @@ function MyController(hand) {
|
||||||
if ((isPhysical || this.state == STATE_EQUIP_SEARCHING) && !near) {
|
if ((isPhysical || this.state == STATE_EQUIP_SEARCHING) && !near) {
|
||||||
if (entityIsGrabbedByOther(intersection.entityID)) {
|
if (entityIsGrabbedByOther(intersection.entityID)) {
|
||||||
// don't distance grab something that is already grabbed.
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
this.temporaryPositionOffset = null;
|
this.temporaryPositionOffset = null;
|
||||||
|
@ -951,6 +982,18 @@ function MyController(hand) {
|
||||||
if (grabbableData.refCount < 1) {
|
if (grabbableData.refCount < 1) {
|
||||||
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
|
this.setState(this.state == STATE_SEARCHING ? STATE_NEAR_GRABBING : STATE_EQUIP);
|
||||||
return;
|
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) {
|
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) {
|
if (timeScale < DISTANCE_HOLDING_ACTION_TIMEFRAME) {
|
||||||
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 handPosition = this.getHandPosition();
|
||||||
|
|
||||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
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) {
|
if (isPhysical && this.state == STATE_NEAR_GRABBING) {
|
||||||
// grab entity via action
|
// grab entity via action
|
||||||
if (!this.setupHoldAction()) {
|
if (!this.setupHoldAction()) {
|
||||||
|
@ -1403,6 +1449,17 @@ function MyController(hand) {
|
||||||
return;
|
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.
|
// 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
|
// 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:
|
// 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.grabbedEntity !== null) {
|
||||||
if (this.actionID !== null) {
|
if (this.actionID !== null) {
|
||||||
Entities.deleteAction(this.grabbedEntity, this.actionID);
|
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);
|
var releaseVelocityData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
||||||
if (releaseVelocityData.disableReleaseVelocity === true || !this.isInitialGrab) {
|
if (releaseVelocityData.disableReleaseVelocity === true ||
|
||||||
Entities.editEntity(this.grabbedEntity, {
|
// this next line allowed both:
|
||||||
velocity: {
|
// (1) far-grab, pull to self, near grab, then throw
|
||||||
x: 0,
|
// (2) equip something physical and adjust it with a other-hand grab without the thing drifting
|
||||||
y: 0,
|
(!this.isInitialGrab && grabData.refCount > 1)) {
|
||||||
z: 0
|
|
||||||
},
|
|
||||||
angularVelocity: {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
z: 0
|
|
||||||
}
|
|
||||||
});
|
|
||||||
noVelocity = true;
|
noVelocity = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1721,15 +1771,27 @@ function MyController(hand) {
|
||||||
data["dynamic"] &&
|
data["dynamic"] &&
|
||||||
data["parentID"] == NULL_UUID &&
|
data["parentID"] == NULL_UUID &&
|
||||||
!data["collisionless"]) {
|
!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);
|
Entities.editEntity(entityID, deactiveProps);
|
||||||
|
|
||||||
if (forceVelocity) {
|
|
||||||
Entities.editEntity(entityID, {velocity:{x:0, y:0.1, z:0}});
|
|
||||||
}
|
|
||||||
data = null;
|
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 {
|
} else {
|
||||||
data = null;
|
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 TEST_MODEL_URL = 'https://s3.amazonaws.com/hifi-public/ozan/avatars/albert/albert/albert.fbx';
|
||||||
|
|
||||||
var doppelgangers = [];
|
|
||||||
|
|
||||||
var MIRROR_JOINT_DATA = true;
|
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) {
|
function Doppelganger(avatar) {
|
||||||
this.initialProperties = {
|
this.initialProperties = {
|
||||||
|
@ -25,13 +28,21 @@ function Doppelganger(avatar) {
|
||||||
// dimensions: getAvatarDimensions(avatar),
|
// dimensions: getAvatarDimensions(avatar),
|
||||||
position: putDoppelgangerAcrossFromAvatar(this, avatar),
|
position: putDoppelgangerAcrossFromAvatar(this, avatar),
|
||||||
rotation: rotateDoppelgangerTowardAvatar(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.id = createDoppelgangerEntity(this);
|
||||||
this.avatar = avatar;
|
this.avatar = avatar;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJointData(avatar) {
|
function getJointData(avatar) {
|
||||||
var allJointData = [];
|
var allJointData = [];
|
||||||
var jointNames = MyAvatar.jointNames;
|
var jointNames = MyAvatar.jointNames;
|
||||||
|
@ -297,30 +308,113 @@ function createDoppelgangerEntity(doppelganger) {
|
||||||
|
|
||||||
function putDoppelgangerAcrossFromAvatar(doppelganger, avatar) {
|
function putDoppelgangerAcrossFromAvatar(doppelganger, avatar) {
|
||||||
var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0);
|
var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0);
|
||||||
var basePosition = Vec3.sum(avatar.position, Vec3.multiply(1.5, Quat.getFront(avatarRot)));
|
var position;
|
||||||
return basePosition;
|
|
||||||
|
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) {
|
function getAvatarDimensions(avatar) {
|
||||||
return dimensions;
|
return dimensions;
|
||||||
}
|
}
|
||||||
|
|
||||||
function rotateDoppelgangerTowardAvatar(doppelganger, avatar) {
|
function rotateDoppelgangerTowardAvatar(doppelganger, avatar) {
|
||||||
var avatarRot = Quat.fromPitchYawRollDegrees(0, avatar.bodyYaw, 0.0);
|
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;
|
return avatarRot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var isConnected = false;
|
||||||
|
|
||||||
function connectDoppelgangerUpdates() {
|
function connectDoppelgangerUpdates() {
|
||||||
// Script.update.connect(updateDoppelganger);
|
Script.update.connect(updateDoppelganger);
|
||||||
Script.setInterval(updateDoppelganger, 100);
|
isConnected = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
function disconnectDoppelgangerUpdates() {
|
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();
|
var absoluteXforms = buildAbsoluteXformsFromMyAvatar();
|
||||||
if (MIRROR_JOINT_DATA) {
|
if (MIRROR_JOINT_DATA) {
|
||||||
var mirroredAbsoluteXforms = [];
|
var mirroredAbsoluteXforms = [];
|
||||||
|
@ -346,14 +440,190 @@ function makeDoppelgangerForMyAvatar() {
|
||||||
connectDoppelgangerUpdates();
|
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();
|
makeDoppelgangerForMyAvatar();
|
||||||
|
subscribeToWearableMessages();
|
||||||
|
subscribeToWearableMessagesForAvatar();
|
||||||
|
subscribeToFreezeMessages();
|
||||||
|
|
||||||
function cleanup() {
|
function cleanup() {
|
||||||
//disconnectDoppelgangerUpdates();
|
if (isConnected === true) {
|
||||||
|
disconnectDoppelgangerUpdates();
|
||||||
|
}
|
||||||
|
|
||||||
doppelgangers.forEach(function(doppelganger) {
|
doppelgangers.forEach(function(doppelganger) {
|
||||||
|
print('DOPPELGANGER' + doppelganger.id)
|
||||||
Entities.deleteEntity(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});
|
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() {
|
function update() {
|
||||||
// refill energy
|
currentEnergy = clamp(MyAvatar.energy, 0, 1);
|
||||||
currentEnergy += energyChargeRate;
|
|
||||||
|
|
||||||
// Avatar acceleration
|
|
||||||
currentEnergy -= avatarAccelerationEnergy();
|
|
||||||
|
|
||||||
// Teleport cost
|
|
||||||
if (teleported()) {
|
|
||||||
currentEnergy = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Making sounds
|
|
||||||
currentEnergy -= audioEnergy();
|
|
||||||
|
|
||||||
|
|
||||||
currentEnergy = clamp(currentEnergy, 0, 1);
|
|
||||||
setEnergy(currentEnergy);
|
setEnergy(currentEnergy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,5 +61,10 @@ function cleanup() {
|
||||||
Overlays.deleteOverlay(bar);
|
Overlays.deleteOverlay(bar);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function energyChanged(newValue) {
|
||||||
|
Entities.currentAvatarEnergy = newValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
MyAvatar.energyChanged.connect(energyChanged);
|
||||||
Script.update.connect(update);
|
Script.update.connect(update);
|
||||||
Script.scriptEnding.connect(cleanup);
|
Script.scriptEnding.connect(cleanup);
|
||||||
|
|
|
@ -3,9 +3,9 @@ import QtQuick.Controls 1.4
|
||||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||||
import Qt.labs.settings 1.0
|
import Qt.labs.settings 1.0
|
||||||
|
|
||||||
import "../styles" as Hifi
|
import "../../styles" as Hifi
|
||||||
import "../controls" as HifiControls
|
import "../../controls" as HifiControls
|
||||||
import "../windows"
|
import "../../windows"
|
||||||
|
|
||||||
Window {
|
Window {
|
||||||
id: root
|
id: root
|
||||||
|
@ -19,12 +19,6 @@ Window {
|
||||||
property var scripts: ScriptDiscoveryService;
|
property var scripts: ScriptDiscoveryService;
|
||||||
property var scriptsModel: scripts.scriptsModelFilter
|
property var scriptsModel: scripts.scriptsModelFilter
|
||||||
property var runningScriptsModel: ListModel { }
|
property var runningScriptsModel: ListModel { }
|
||||||
property var fileFilters: ListModel {
|
|
||||||
id: jsFilters
|
|
||||||
ListElement { text: "Javascript Files (*.js)"; filter: "*.js" }
|
|
||||||
ListElement { text: "All Files (*.*)"; filter: "*.*" }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
Settings {
|
Settings {
|
||||||
category: "Overlay.RunningScripts"
|
category: "Overlay.RunningScripts"
|
||||||
|
@ -249,7 +243,30 @@ Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
model: scriptsModel
|
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 {
|
HifiControls.TextField {
|
|
@ -45,6 +45,30 @@ Fadable {
|
||||||
// The content to place inside the window, determined by the client
|
// The content to place inside the window, determined by the client
|
||||||
default property var content
|
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
|
// 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
|
// in the window and have a higher Z-order than the content, but follow
|
||||||
// the position and size of frame decoration
|
// the position and size of frame decoration
|
||||||
|
|
|
@ -1260,7 +1260,7 @@ void Application::initializeUi() {
|
||||||
auto resultVec = _compositor.screenToOverlay(toGlm(pt));
|
auto resultVec = _compositor.screenToOverlay(toGlm(pt));
|
||||||
result = QPointF(resultVec.x, resultVec.y);
|
result = QPointF(resultVec.x, resultVec.y);
|
||||||
}
|
}
|
||||||
return result;
|
return result.toPoint();
|
||||||
});
|
});
|
||||||
offscreenUi->resume();
|
offscreenUi->resume();
|
||||||
connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){
|
connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){
|
||||||
|
@ -4418,7 +4418,7 @@ bool Application::displayAvatarAttachmentConfirmationDialog(const QString& name)
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::toggleRunningScriptsWidget() {
|
void Application::toggleRunningScriptsWidget() {
|
||||||
static const QUrl url("dialogs/RunningScripts.qml");
|
static const QUrl url("hifi/dialogs/RunningScripts.qml");
|
||||||
DependencyManager::get<OffscreenUi>()->show(url, "RunningScripts");
|
DependencyManager::get<OffscreenUi>()->show(url, "RunningScripts");
|
||||||
//if (_runningScriptsWidget->isVisible()) {
|
//if (_runningScriptsWidget->isVisible()) {
|
||||||
// if (_runningScriptsWidget->hasFocus()) {
|
// if (_runningScriptsWidget->hasFocus()) {
|
||||||
|
|
|
@ -298,8 +298,20 @@ void MyAvatar::update(float deltaTime) {
|
||||||
auto audio = DependencyManager::get<AudioClient>();
|
auto audio = DependencyManager::get<AudioClient>();
|
||||||
head->setAudioLoudness(audio->getLastInputLoudness());
|
head->setAudioLoudness(audio->getLastInputLoudness());
|
||||||
head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
|
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);
|
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 rightHandPose READ getRightHandPose)
|
||||||
Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
|
Q_PROPERTY(controller::Pose leftHandTipPose READ getLeftHandTipPose)
|
||||||
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
|
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
|
||||||
|
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
MyAvatar(RigPointer rig);
|
MyAvatar(RigPointer rig);
|
||||||
|
@ -276,8 +277,10 @@ signals:
|
||||||
void transformChanged();
|
void transformChanged();
|
||||||
void newCollisionSoundURL(const QUrl& url);
|
void newCollisionSoundURL(const QUrl& url);
|
||||||
void collisionWithEntity(const Collision& collision);
|
void collisionWithEntity(const Collision& collision);
|
||||||
|
void energyChanged(float newEnergy);
|
||||||
void positionGoneTo();
|
void positionGoneTo();
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
glm::vec3 getWorldBodyPosition() const;
|
glm::vec3 getWorldBodyPosition() const;
|
||||||
|
@ -413,9 +416,21 @@ private:
|
||||||
|
|
||||||
AtRestDetector _hmdAtRestDetector;
|
AtRestDetector _hmdAtRestDetector;
|
||||||
bool _lastIsMoving { false };
|
bool _lastIsMoving { false };
|
||||||
|
|
||||||
bool _hoverReferenceCameraFacingIsCaptured { false };
|
bool _hoverReferenceCameraFacingIsCaptured { false };
|
||||||
glm::vec3 _hoverReferenceCameraFacing; // hmd sensor space
|
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);
|
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||||
|
|
|
@ -15,12 +15,13 @@
|
||||||
|
|
||||||
bool AnimClip::usePreAndPostPoseFromAnim = false;
|
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),
|
AnimNode(AnimNode::Type::Clip, id),
|
||||||
_startFrame(startFrame),
|
_startFrame(startFrame),
|
||||||
_endFrame(endFrame),
|
_endFrame(endFrame),
|
||||||
_timeScale(timeScale),
|
_timeScale(timeScale),
|
||||||
_loopFlag(loopFlag),
|
_loopFlag(loopFlag),
|
||||||
|
_mirrorFlag(mirrorFlag),
|
||||||
_frame(startFrame)
|
_frame(startFrame)
|
||||||
{
|
{
|
||||||
loadURL(url);
|
loadURL(url);
|
||||||
|
@ -37,6 +38,7 @@ const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt,
|
||||||
_endFrame = animVars.lookup(_endFrameVar, _endFrame);
|
_endFrame = animVars.lookup(_endFrameVar, _endFrame);
|
||||||
_timeScale = animVars.lookup(_timeScaleVar, _timeScale);
|
_timeScale = animVars.lookup(_timeScaleVar, _timeScale);
|
||||||
_loopFlag = animVars.lookup(_loopFlagVar, _loopFlag);
|
_loopFlag = animVars.lookup(_loopFlagVar, _loopFlag);
|
||||||
|
_mirrorFlag = animVars.lookup(_mirrorFlagVar, _mirrorFlag);
|
||||||
float frame = animVars.lookup(_frameVar, _frame);
|
float frame = animVars.lookup(_frameVar, _frame);
|
||||||
|
|
||||||
_frame = ::accumulateTime(_startFrame, _endFrame, _timeScale, frame, dt, _loopFlag, _id, triggersOut);
|
_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()) {
|
if (_anim.size()) {
|
||||||
|
|
||||||
|
// lazy creation of mirrored animation frames.
|
||||||
|
if (_mirrorFlag && _anim.size() != _mirrorAnim.size()) {
|
||||||
|
buildMirrorAnim();
|
||||||
|
}
|
||||||
|
|
||||||
int prevIndex = (int)glm::floor(_frame);
|
int prevIndex = (int)glm::floor(_frame);
|
||||||
int nextIndex;
|
int nextIndex;
|
||||||
if (_loopFlag && _frame >= _endFrame) {
|
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);
|
prevIndex = std::min(std::max(0, prevIndex), frameCount - 1);
|
||||||
nextIndex = std::min(std::max(0, nextIndex), frameCount - 1);
|
nextIndex = std::min(std::max(0, nextIndex), frameCount - 1);
|
||||||
|
|
||||||
const AnimPoseVec& prevFrame = _anim[prevIndex];
|
const AnimPoseVec& prevFrame = _mirrorFlag ? _mirrorAnim[prevIndex] : _anim[prevIndex];
|
||||||
const AnimPoseVec& nextFrame = _anim[nextIndex];
|
const AnimPoseVec& nextFrame = _mirrorFlag ? _mirrorAnim[nextIndex] : _anim[nextIndex];
|
||||||
float alpha = glm::fract(_frame);
|
float alpha = glm::fract(_frame);
|
||||||
|
|
||||||
::blend(_poses.size(), &prevFrame[0], &nextFrame[0], alpha, &_poses[0]);
|
::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);
|
_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 {
|
const AnimPoseVec& AnimClip::getPosesInternal() const {
|
||||||
return _poses;
|
return _poses;
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
|
|
||||||
static bool usePreAndPostPoseFromAnim;
|
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 ~AnimClip() override;
|
||||||
|
|
||||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) 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 setEndFrameVar(const QString& endFrameVar) { _endFrameVar = endFrameVar; }
|
||||||
void setTimeScaleVar(const QString& timeScaleVar) { _timeScaleVar = timeScaleVar; }
|
void setTimeScaleVar(const QString& timeScaleVar) { _timeScaleVar = timeScaleVar; }
|
||||||
void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; }
|
void setLoopFlagVar(const QString& loopFlagVar) { _loopFlagVar = loopFlagVar; }
|
||||||
|
void setMirrorFlagVar(const QString& mirrorFlagVar) { _mirrorFlagVar = mirrorFlagVar; }
|
||||||
void setFrameVar(const QString& frameVar) { _frameVar = frameVar; }
|
void setFrameVar(const QString& frameVar) { _frameVar = frameVar; }
|
||||||
|
|
||||||
float getStartFrame() const { return _startFrame; }
|
float getStartFrame() const { return _startFrame; }
|
||||||
|
@ -49,12 +50,16 @@ public:
|
||||||
bool getLoopFlag() const { return _loopFlag; }
|
bool getLoopFlag() const { return _loopFlag; }
|
||||||
void setLoopFlag(bool loopFlag) { _loopFlag = loopFlag; }
|
void setLoopFlag(bool loopFlag) { _loopFlag = loopFlag; }
|
||||||
|
|
||||||
|
bool getMirrorFlag() const { return _mirrorFlag; }
|
||||||
|
void setMirrorFlag(bool mirrorFlag) { _mirrorFlag = mirrorFlag; }
|
||||||
|
|
||||||
void loadURL(const QString& url);
|
void loadURL(const QString& url);
|
||||||
protected:
|
protected:
|
||||||
|
|
||||||
virtual void setCurrentFrameInternal(float frame) override;
|
virtual void setCurrentFrameInternal(float frame) override;
|
||||||
|
|
||||||
void copyFromNetworkAnim();
|
void copyFromNetworkAnim();
|
||||||
|
void buildMirrorAnim();
|
||||||
|
|
||||||
// for AnimDebugDraw rendering
|
// for AnimDebugDraw rendering
|
||||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
virtual const AnimPoseVec& getPosesInternal() const override;
|
||||||
|
@ -64,18 +69,21 @@ protected:
|
||||||
|
|
||||||
// _anim[frame][joint]
|
// _anim[frame][joint]
|
||||||
std::vector<AnimPoseVec> _anim;
|
std::vector<AnimPoseVec> _anim;
|
||||||
|
std::vector<AnimPoseVec> _mirrorAnim;
|
||||||
|
|
||||||
QString _url;
|
QString _url;
|
||||||
float _startFrame;
|
float _startFrame;
|
||||||
float _endFrame;
|
float _endFrame;
|
||||||
float _timeScale;
|
float _timeScale;
|
||||||
bool _loopFlag;
|
bool _loopFlag;
|
||||||
|
bool _mirrorFlag;
|
||||||
float _frame;
|
float _frame;
|
||||||
|
|
||||||
QString _startFrameVar;
|
QString _startFrameVar;
|
||||||
QString _endFrameVar;
|
QString _endFrameVar;
|
||||||
QString _timeScaleVar;
|
QString _timeScaleVar;
|
||||||
QString _loopFlagVar;
|
QString _loopFlagVar;
|
||||||
|
QString _mirrorFlagVar;
|
||||||
QString _frameVar;
|
QString _frameVar;
|
||||||
|
|
||||||
// no copies
|
// no copies
|
||||||
|
|
|
@ -155,6 +155,14 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
|
||||||
} \
|
} \
|
||||||
bool NAME = NAME##_VAL.toBool()
|
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) \
|
#define READ_FLOAT(NAME, JSON_OBJ, ID, URL, ERROR_RETURN) \
|
||||||
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
|
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
|
||||||
if (!NAME##_VAL.isDouble()) { \
|
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(endFrame, jsonObj, id, jsonUrl, nullptr);
|
||||||
READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr);
|
READ_FLOAT(timeScale, jsonObj, id, jsonUrl, nullptr);
|
||||||
READ_BOOL(loopFlag, 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(startFrameVar, jsonObj);
|
||||||
READ_OPTIONAL_STRING(endFrameVar, jsonObj);
|
READ_OPTIONAL_STRING(endFrameVar, jsonObj);
|
||||||
READ_OPTIONAL_STRING(timeScaleVar, jsonObj);
|
READ_OPTIONAL_STRING(timeScaleVar, jsonObj);
|
||||||
READ_OPTIONAL_STRING(loopFlagVar, 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()) {
|
if (!startFrameVar.isEmpty()) {
|
||||||
node->setStartFrameVar(startFrameVar);
|
node->setStartFrameVar(startFrameVar);
|
||||||
|
@ -252,6 +262,9 @@ static AnimNode::Pointer loadClipNode(const QJsonObject& jsonObj, const QString&
|
||||||
if (!loopFlagVar.isEmpty()) {
|
if (!loopFlagVar.isEmpty()) {
|
||||||
node->setLoopFlagVar(loopFlagVar);
|
node->setLoopFlagVar(loopFlagVar);
|
||||||
}
|
}
|
||||||
|
if (!mirrorFlagVar.isEmpty()) {
|
||||||
|
node->setMirrorFlagVar(mirrorFlagVar);
|
||||||
|
}
|
||||||
|
|
||||||
return node;
|
return node;
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,11 @@ AnimPose AnimPose::inverse() const {
|
||||||
return AnimPose(glm::inverse(static_cast<glm::mat4>(*this)));
|
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 {
|
AnimPose::operator glm::mat4() const {
|
||||||
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
|
glm::vec3 xAxis = rot * glm::vec3(scale.x, 0.0f, 0.0f);
|
||||||
glm::vec3 yAxis = rot * glm::vec3(0.0f, scale.y, 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 operator*(const AnimPose& rhs) const;
|
||||||
|
|
||||||
AnimPose inverse() const;
|
AnimPose inverse() const;
|
||||||
|
AnimPose mirror() const;
|
||||||
operator glm::mat4() const;
|
operator glm::mat4() const;
|
||||||
|
|
||||||
glm::vec3 scale;
|
glm::vec3 scale;
|
||||||
|
|
|
@ -87,7 +87,8 @@ AnimPose AnimSkeleton::getAbsolutePose(int jointIndex, const AnimPoseVec& poses)
|
||||||
|
|
||||||
void AnimSkeleton::convertRelativePosesToAbsolute(AnimPoseVec& poses) const {
|
void AnimSkeleton::convertRelativePosesToAbsolute(AnimPoseVec& poses) const {
|
||||||
// poses start off relative and leave in absolute frame
|
// 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;
|
int parentIndex = _joints[i].parentIndex;
|
||||||
if (parentIndex != -1) {
|
if (parentIndex != -1) {
|
||||||
poses[i] = poses[parentIndex] * poses[i];
|
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) {
|
void AnimSkeleton::buildSkeletonFromJoints(const std::vector<FBXJoint>& joints) {
|
||||||
_joints = 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
|
#ifndef NDEBUG
|
||||||
|
|
|
@ -53,6 +53,10 @@ public:
|
||||||
AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const;
|
AnimPose getAbsolutePose(int jointIndex, const AnimPoseVec& poses) const;
|
||||||
|
|
||||||
void convertRelativePosesToAbsolute(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
|
#ifndef NDEBUG
|
||||||
void dump() const;
|
void dump() const;
|
||||||
|
@ -69,6 +73,7 @@ protected:
|
||||||
AnimPoseVec _absoluteDefaultPoses;
|
AnimPoseVec _absoluteDefaultPoses;
|
||||||
AnimPoseVec _relativePreRotationPoses;
|
AnimPoseVec _relativePreRotationPoses;
|
||||||
AnimPoseVec _relativePostRotationPoses;
|
AnimPoseVec _relativePostRotationPoses;
|
||||||
|
std::vector<int> _mirrorMap;
|
||||||
|
|
||||||
// no copies
|
// no copies
|
||||||
AnimSkeleton(const AnimSkeleton&) = delete;
|
AnimSkeleton(const AnimSkeleton&) = delete;
|
||||||
|
|
|
@ -120,7 +120,7 @@ void Rig::overrideRoleAnimation(const QString& role, const QString& url, float f
|
||||||
_origRoleAnimations[role] = node;
|
_origRoleAnimations[role] = node;
|
||||||
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
|
const float REFERENCE_FRAMES_PER_SECOND = 30.0f;
|
||||||
float timeScale = fps / REFERENCE_FRAMES_PER_SECOND;
|
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();
|
AnimNode::Pointer parent = node->getParent();
|
||||||
parent->replaceChild(node, clipNode);
|
parent->replaceChild(node, clipNode);
|
||||||
} else {
|
} else {
|
||||||
|
@ -152,7 +152,7 @@ void Rig::prefetchAnimation(const QString& url) {
|
||||||
|
|
||||||
// This will begin loading the NetworkGeometry for the given URL.
|
// This will begin loading the NetworkGeometry for the given URL.
|
||||||
// which should speed us up if we request it later via overrideAnimation.
|
// 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);
|
_prefetchedAnimations.push_back(clipNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -652,6 +652,7 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
|
||||||
}
|
}
|
||||||
if (_simulationOwner.set(newSimOwner)) {
|
if (_simulationOwner.set(newSimOwner)) {
|
||||||
_dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
|
_dirtyFlags |= Simulation::DIRTY_SIMULATOR_ID;
|
||||||
|
somethingChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
{ // When we own the simulation we don't accept updates to the entity's transform/velocities
|
{ // 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;
|
return tree;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityItem::wantTerseEditLogging() {
|
bool EntityItem::wantTerseEditLogging() const {
|
||||||
EntityTreePointer tree = getTree();
|
EntityTreePointer tree = getTree();
|
||||||
return tree ? tree->wantTerseEditLogging() : false;
|
return tree ? tree->wantTerseEditLogging() : false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,7 +104,7 @@ public:
|
||||||
quint64 getLastBroadcast() const { return _lastBroadcast; }
|
quint64 getLastBroadcast() const { return _lastBroadcast; }
|
||||||
void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; }
|
void setLastBroadcast(quint64 lastBroadcast) { _lastBroadcast = lastBroadcast; }
|
||||||
|
|
||||||
void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); }
|
void markAsChangedOnServer() { _changedOnServer = usecTimestampNow(); }
|
||||||
quint64 getLastChangedOnServer() const { return _changedOnServer; }
|
quint64 getLastChangedOnServer() const { return _changedOnServer; }
|
||||||
|
|
||||||
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
// TODO: eventually only include properties changed since the params.lastViewFrustumSent time
|
||||||
|
@ -351,14 +351,14 @@ public:
|
||||||
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
void setPhysicsInfo(void* data) { _physicsInfo = data; }
|
||||||
EntityTreeElementPointer getElement() const { return _element; }
|
EntityTreeElementPointer getElement() const { return _element; }
|
||||||
EntityTreePointer getTree() const;
|
EntityTreePointer getTree() const;
|
||||||
bool wantTerseEditLogging();
|
bool wantTerseEditLogging() const;
|
||||||
|
|
||||||
glm::mat4 getEntityToWorldMatrix() const;
|
glm::mat4 getEntityToWorldMatrix() const;
|
||||||
glm::mat4 getWorldToEntityMatrix() const;
|
glm::mat4 getWorldToEntityMatrix() const;
|
||||||
glm::vec3 worldToEntity(const glm::vec3& point) const;
|
glm::vec3 worldToEntity(const glm::vec3& point) const;
|
||||||
glm::vec3 entityToWorld(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;
|
void getAllTerseUpdateProperties(EntityItemProperties& properties) const;
|
||||||
|
|
||||||
|
|
|
@ -910,7 +910,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
|
||||||
success = packetData->startSubTree(octcode);
|
success = packetData->startSubTree(octcode);
|
||||||
delete[] octcode;
|
delete[] octcode;
|
||||||
|
|
||||||
// assuming we have rome to fit our octalCode, proceed...
|
// assuming we have room to fit our octalCode, proceed...
|
||||||
if (success) {
|
if (success) {
|
||||||
|
|
||||||
// Now add our edit content details...
|
// Now add our edit content details...
|
||||||
|
|
|
@ -8,7 +8,6 @@
|
||||||
// Distributed under the Apache License, Version 2.0.
|
// Distributed under the Apache License, Version 2.0.
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "EntityScriptingInterface.h"
|
#include "EntityScriptingInterface.h"
|
||||||
|
|
||||||
#include "EntityItemID.h"
|
#include "EntityItemID.h"
|
||||||
|
@ -123,6 +122,20 @@ QUuid EntityScriptingInterface::addEntity(const EntityItemProperties& properties
|
||||||
EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties);
|
EntityItemProperties propertiesWithSimID = convertLocationFromScriptSemantics(properties);
|
||||||
propertiesWithSimID.setDimensionsInitialized(properties.dimensionsChanged());
|
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());
|
EntityItemID id = EntityItemID(QUuid::createUuid());
|
||||||
|
|
||||||
// If we have a local entity tree set, then also update it.
|
// 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) {
|
QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties& scriptSideProperties) {
|
||||||
EntityItemProperties properties = 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);
|
EntityItemID entityID(id);
|
||||||
if (!_entityTree) {
|
if (!_entityTree) {
|
||||||
queueEntityMessage(PacketType::EntityEdit, entityID, properties);
|
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;
|
return id;
|
||||||
}
|
}
|
||||||
// If we have a local entity tree set, then also update it.
|
// 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) {
|
if (!entity) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
//existing entity, retrieve old velocity for check down below
|
||||||
|
oldVelocity = entity->getVelocity().length();
|
||||||
|
|
||||||
if (!scriptSideProperties.parentIDChanged()) {
|
if (!scriptSideProperties.parentIDChanged()) {
|
||||||
properties.setParentID(entity->getParentID());
|
properties.setParentID(entity->getParentID());
|
||||||
}
|
}
|
||||||
|
@ -246,6 +281,16 @@ QUuid EntityScriptingInterface::editEntity(QUuid id, const EntityItemProperties&
|
||||||
}
|
}
|
||||||
properties = convertLocationFromScriptSemantics(properties);
|
properties = convertLocationFromScriptSemantics(properties);
|
||||||
updatedEntity = _entityTree->updateEntity(entityID, 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) {
|
if (!updatedEntity) {
|
||||||
|
@ -320,6 +365,21 @@ void EntityScriptingInterface::deleteEntity(QUuid id) {
|
||||||
_entityTree->withWriteLock([&] {
|
_entityTree->withWriteLock([&] {
|
||||||
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
EntityItemPointer entity = _entityTree->findEntityByEntityItemID(entityID);
|
||||||
if (entity) {
|
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()) {
|
if (entity->getLocked()) {
|
||||||
shouldDelete = false;
|
shouldDelete = false;
|
||||||
} else {
|
} else {
|
||||||
|
@ -996,3 +1056,20 @@ QStringList EntityScriptingInterface::getJointNames(const QUuid& entityID) {
|
||||||
Q_RETURN_ARG(QStringList, result), Q_ARG(QUuid, entityID));
|
Q_RETURN_ARG(QStringList, result), Q_ARG(QUuid, entityID));
|
||||||
return result;
|
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/QObject>
|
||||||
#include <QtCore/QStringList>
|
#include <QtCore/QStringList>
|
||||||
|
#include <QtQml/QJSValue>
|
||||||
|
#include <QtQml/QJSValueList>
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
#include <DependencyManager.h>
|
||||||
#include <Octree.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
|
/// handles scripting of Entity commands from JS passed to assigned clients
|
||||||
class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency {
|
class EntityScriptingInterface : public OctreeScriptingInterface, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(float currentAvatarEnergy READ getCurrentAvatarEnergy WRITE setCurrentAvatarEnergy)
|
||||||
|
Q_PROPERTY(float costMultiplier READ getCostMultiplier WRITE setCostMultiplier)
|
||||||
public:
|
public:
|
||||||
EntityScriptingInterface(bool bidOnSimulationOwnership);
|
EntityScriptingInterface(bool bidOnSimulationOwnership);
|
||||||
|
|
||||||
|
@ -67,7 +72,7 @@ public:
|
||||||
void setEntityTree(EntityTreePointer modelTree);
|
void setEntityTree(EntityTreePointer modelTree);
|
||||||
EntityTreePointer getEntityTree() { return _entityTree; }
|
EntityTreePointer getEntityTree() { return _entityTree; }
|
||||||
void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) { _entitiesScriptEngine = engine; }
|
void setEntitiesScriptEngine(EntitiesScriptEngineProvider* engine) { _entitiesScriptEngine = engine; }
|
||||||
|
float calculateCost(float mass, float oldVelocity, float newVelocity);
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
// returns true if the DomainServer will allow this Node/Avatar to make changes
|
// 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 int getJointIndex(const QUuid& entityID, const QString& name);
|
||||||
Q_INVOKABLE QStringList getJointNames(const QUuid& entityID);
|
Q_INVOKABLE QStringList getJointNames(const QUuid& entityID);
|
||||||
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
void collisionWithEntity(const EntityItemID& idA, const EntityItemID& idB, const Collision& collision);
|
||||||
|
@ -188,6 +194,7 @@ signals:
|
||||||
void deletingEntity(const EntityItemID& entityID);
|
void deletingEntity(const EntityItemID& entityID);
|
||||||
void addingEntity(const EntityItemID& entityID);
|
void addingEntity(const EntityItemID& entityID);
|
||||||
void clearingEntities();
|
void clearingEntities();
|
||||||
|
void debitEnergySource(float value);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool actionWorker(const QUuid& entityID, std::function<bool(EntitySimulation*, EntityItemPointer)> actor);
|
bool actionWorker(const QUuid& entityID, std::function<bool(EntitySimulation*, EntityItemPointer)> actor);
|
||||||
|
@ -205,7 +212,15 @@ private:
|
||||||
|
|
||||||
EntityTreePointer _entityTree;
|
EntityTreePointer _entityTree;
|
||||||
EntitiesScriptEngineProvider* _entitiesScriptEngine { nullptr };
|
EntitiesScriptEngineProvider* _entitiesScriptEngine { nullptr };
|
||||||
|
|
||||||
bool _bidOnSimulationOwnership { false };
|
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
|
#endif // hifi_EntityScriptingInterface_h
|
||||||
|
|
|
@ -11,38 +11,58 @@
|
||||||
|
|
||||||
//#include <PerfStat.h>
|
//#include <PerfStat.h>
|
||||||
|
|
||||||
#include "EntityItem.h"
|
|
||||||
#include "SimpleEntitySimulation.h"
|
#include "SimpleEntitySimulation.h"
|
||||||
|
|
||||||
|
#include <DirtyOctreeElementOperator.h>
|
||||||
|
|
||||||
|
#include "EntityItem.h"
|
||||||
#include "EntitiesLogging.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) {
|
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,
|
if (_entitiesWithSimulator.size() == 0) {
|
||||||
// clear the owner. This guards against an interface failing to release the Entity when it
|
return;
|
||||||
// has finished simulating it.
|
}
|
||||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
|
||||||
|
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);
|
QMutexLocker lock(&_mutex);
|
||||||
SetOfEntities::iterator itemItr = _entitiesWithSimulator.begin();
|
SetOfEntities::iterator itemItr = _entitiesWithSimulator.begin();
|
||||||
while (itemItr != _entitiesWithSimulator.end()) {
|
while (itemItr != _entitiesWithSimulator.end()) {
|
||||||
EntityItemPointer entity = *itemItr;
|
EntityItemPointer entity = *itemItr;
|
||||||
if (entity->getSimulatorID().isNull()) {
|
quint64 expiry = entity->getLastChangedOnServer() + MIN_SIMULATION_OWNERSHIP_UPDATE_PERIOD;
|
||||||
itemItr = _entitiesWithSimulator.erase(itemItr);
|
if (expiry < now) {
|
||||||
} else if (now - entity->getLastChangedOnServer() >= AUTO_REMOVE_SIMULATION_OWNER_USEC) {
|
if (entity->getSimulatorID().isNull()) {
|
||||||
SharedNodePointer ownerNode = nodeList->nodeWithUUID(entity->getSimulatorID());
|
// no simulators are volunteering
|
||||||
if (ownerNode.isNull() || !ownerNode->isAlive()) {
|
|
||||||
qCDebug(entities) << "auto-removing simulation owner" << entity->getSimulatorID();
|
|
||||||
entity->clearSimulationOwnership();
|
|
||||||
itemItr = _entitiesWithSimulator.erase(itemItr);
|
|
||||||
// zero the velocity on this entity so that it doesn't drift far away
|
// 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 {
|
} 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 {
|
entity->markAsChangedOnServer();
|
||||||
++itemItr;
|
// 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;
|
virtual void clearEntitiesInternal() override;
|
||||||
|
|
||||||
SetOfEntities _entitiesWithSimulator;
|
SetOfEntities _entitiesWithSimulator;
|
||||||
|
quint64 _nextSimulationExpiry { 0 };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_SimpleEntitySimulation_h
|
#endif // hifi_SimpleEntitySimulation_h
|
||||||
|
|
|
@ -16,11 +16,11 @@
|
||||||
|
|
||||||
#include <NumericalConstants.h>
|
#include <NumericalConstants.h>
|
||||||
|
|
||||||
// static
|
// static
|
||||||
const int SimulationOwner::NUM_BYTES_ENCODED = NUM_BYTES_RFC4122_UUID + 1;
|
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) {
|
: _id(other._id), _priority(other._priority), _expiry(other._expiry) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,11 +48,6 @@ void SimulationOwner::clear() {
|
||||||
|
|
||||||
void SimulationOwner::setPriority(quint8 priority) {
|
void SimulationOwner::setPriority(quint8 priority) {
|
||||||
_priority = priority;
|
_priority = priority;
|
||||||
if (_priority == 0) {
|
|
||||||
// when priority is zero we clear everything
|
|
||||||
_expiry = 0;
|
|
||||||
_id = QUuid();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SimulationOwner::promotePriority(quint8 priority) {
|
void SimulationOwner::promotePriority(quint8 priority) {
|
||||||
|
|
|
@ -18,10 +18,10 @@
|
||||||
#include <SharedUtil.h>
|
#include <SharedUtil.h>
|
||||||
#include <UUID.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
|
// 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.
|
// to RECRUIT priority so that other volunteers don't accidentally take over.
|
||||||
const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01;
|
const quint8 VOLUNTEER_SIMULATION_PRIORITY = 0x01;
|
||||||
const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
|
const quint8 RECRUIT_SIMULATION_PRIORITY = VOLUNTEER_SIMULATION_PRIORITY + 1;
|
||||||
|
|
|
@ -35,7 +35,7 @@ public:
|
||||||
OffscreenQmlSurface();
|
OffscreenQmlSurface();
|
||||||
virtual ~OffscreenQmlSurface();
|
virtual ~OffscreenQmlSurface();
|
||||||
|
|
||||||
using MouseTranslator = std::function<QPointF(const QPointF&)>;
|
using MouseTranslator = std::function<QPoint(const QPointF&)>;
|
||||||
|
|
||||||
virtual void create(QOpenGLContext* context);
|
virtual void create(QOpenGLContext* context);
|
||||||
void resize(const QSize& size);
|
void resize(const QSize& size);
|
||||||
|
@ -94,7 +94,7 @@ private:
|
||||||
bool _polish{ true };
|
bool _polish{ true };
|
||||||
bool _paused{ true };
|
bool _paused{ true };
|
||||||
uint8_t _maxFps{ 60 };
|
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() {
|
SendQueue& Connection::getSendQueue() {
|
||||||
if (!_sendQueue) {
|
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
|
// Lasily create send queue
|
||||||
_sendQueue = SendQueue::create(_parentSocket, _destination);
|
_sendQueue = SendQueue::create(_parentSocket, _destination, _inactiveSendQueueSequenceNumber);
|
||||||
|
|
||||||
#ifdef UDT_CONNECTION_DEBUG
|
#ifdef UDT_CONNECTION_DEBUG
|
||||||
qCDebug(networking) << "Created SendQueue for connection to" << _destination;
|
qCDebug(networking) << "Created SendQueue for connection to" << _destination;
|
||||||
|
@ -105,6 +109,10 @@ SendQueue& Connection::getSendQueue() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Connection::queueInactive() {
|
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
|
// tell our current send queue to go down and reset our ptr to it to null
|
||||||
stopSendQueue();
|
stopSendQueue();
|
||||||
|
|
||||||
|
|
|
@ -139,6 +139,8 @@ private:
|
||||||
|
|
||||||
SequenceNumber _lastSentACK; // The last sent ACK
|
SequenceNumber _lastSentACK; // The last sent ACK
|
||||||
SequenceNumber _lastSentACK2; // The last sent ACK sub-sequence number in an ACK2
|
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 _acksDuringSYN { 1 }; // The number of non-SYN ACKs sent during SYN
|
||||||
int _lightACKsDuringSYN { 1 }; // The number of lite ACKs sent during SYN interval
|
int _lightACKsDuringSYN { 1 }; // The number of lite ACKs sent during SYN interval
|
||||||
|
|
|
@ -52,11 +52,11 @@ private:
|
||||||
Mutex2& _mutex2;
|
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*");
|
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
|
// Setup queue private thread
|
||||||
QThread* thread = new QThread;
|
QThread* thread = new QThread;
|
||||||
thread->setObjectName("Networking: SendQueue " + destination.objectName()); // Name thread for easier debug
|
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;
|
return queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) :
|
SendQueue::SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber) :
|
||||||
_socket(socket),
|
_socket(socket),
|
||||||
_destination(dest)
|
_destination(dest),
|
||||||
|
_currentSequenceNumber(currentSequenceNumber)
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void SendQueue::queuePacket(std::unique_ptr<Packet> packet) {
|
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 NUM_TIMEOUTS_BEFORE_INACTIVE = 16;
|
||||||
static const int MIN_SECONDS_BEFORE_INACTIVE_MS = 5 * 1000;
|
static const int MIN_SECONDS_BEFORE_INACTIVE_MS = 5 * 1000;
|
||||||
if (_timeoutExpiryCount >= NUM_TIMEOUTS_BEFORE_INACTIVE &&
|
if (_timeoutExpiryCount >= NUM_TIMEOUTS_BEFORE_INACTIVE &&
|
||||||
|
_lastReceiverResponse > 0 &&
|
||||||
(QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse) > MIN_SECONDS_BEFORE_INACTIVE_MS) {
|
(QDateTime::currentMSecsSinceEpoch() - _lastReceiverResponse) > MIN_SECONDS_BEFORE_INACTIVE_MS) {
|
||||||
// If the flow window has been full for over CONSIDER_INACTIVE_AFTER,
|
// 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
|
// then signal the queue is inactive and return so it can be cleaned up
|
||||||
|
|
|
@ -50,7 +50,8 @@ public:
|
||||||
Stopped
|
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 queuePacket(std::unique_ptr<Packet> packet);
|
||||||
void queuePacketList(std::unique_ptr<PacketList> packetList);
|
void queuePacketList(std::unique_ptr<PacketList> packetList);
|
||||||
|
@ -83,7 +84,7 @@ private slots:
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SendQueue(Socket* socket, HifiSockAddr dest);
|
SendQueue(Socket* socket, HifiSockAddr dest, SequenceNumber currentSequenceNumber);
|
||||||
SendQueue(SendQueue& other) = delete;
|
SendQueue(SendQueue& other) = delete;
|
||||||
SendQueue(SendQueue&& other) = delete;
|
SendQueue(SendQueue&& other) = delete;
|
||||||
|
|
||||||
|
@ -108,7 +109,7 @@ private:
|
||||||
|
|
||||||
std::atomic<uint32_t> _lastACKSequenceNumber { 0 }; // Last ACKed sequence number
|
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<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
|
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"
|
#include "EntityTree.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f;
|
const uint8_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
|
||||||
static const quint8 STEPS_TO_DECIDE_BALLISTIC = 4;
|
|
||||||
|
|
||||||
const uint32_t LOOPS_FOR_SIMULATION_ORPHAN = 50;
|
|
||||||
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
|
const quint64 USECS_BETWEEN_OWNERSHIP_BIDS = USECS_PER_SECOND / 5;
|
||||||
|
|
||||||
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
|
#ifdef WANT_DEBUG_ENTITY_TREE_LOCKS
|
||||||
|
@ -52,8 +49,6 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
||||||
ObjectMotionState(shape),
|
ObjectMotionState(shape),
|
||||||
_entityPtr(entity),
|
_entityPtr(entity),
|
||||||
_entity(entity.get()),
|
_entity(entity.get()),
|
||||||
_sentInactive(true),
|
|
||||||
_lastStep(0),
|
|
||||||
_serverPosition(0.0f),
|
_serverPosition(0.0f),
|
||||||
_serverRotation(),
|
_serverRotation(),
|
||||||
_serverVelocity(0.0f),
|
_serverVelocity(0.0f),
|
||||||
|
@ -61,13 +56,16 @@ EntityMotionState::EntityMotionState(btCollisionShape* shape, EntityItemPointer
|
||||||
_serverGravity(0.0f),
|
_serverGravity(0.0f),
|
||||||
_serverAcceleration(0.0f),
|
_serverAcceleration(0.0f),
|
||||||
_serverActionData(QByteArray()),
|
_serverActionData(QByteArray()),
|
||||||
_lastMeasureStep(0),
|
|
||||||
_lastVelocity(glm::vec3(0.0f)),
|
_lastVelocity(glm::vec3(0.0f)),
|
||||||
_measuredAcceleration(glm::vec3(0.0f)),
|
_measuredAcceleration(glm::vec3(0.0f)),
|
||||||
_measuredDeltaTime(0.0f),
|
|
||||||
_accelerationNearlyGravityCount(0),
|
|
||||||
_nextOwnershipBid(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;
|
_type = MOTIONSTATE_TYPE_ENTITY;
|
||||||
assert(_entity);
|
assert(_entity);
|
||||||
|
@ -102,27 +100,35 @@ bool EntityMotionState::handleEasyChanges(uint32_t& flags) {
|
||||||
ObjectMotionState::handleEasyChanges(flags);
|
ObjectMotionState::handleEasyChanges(flags);
|
||||||
|
|
||||||
if (flags & Simulation::DIRTY_SIMULATOR_ID) {
|
if (flags & Simulation::DIRTY_SIMULATOR_ID) {
|
||||||
_loopsWithoutOwner = 0;
|
|
||||||
if (_entity->getSimulatorID().isNull()) {
|
if (_entity->getSimulatorID().isNull()) {
|
||||||
// simulation ownership is being removed
|
// simulation ownership has been removed by an external simulator
|
||||||
// remove the ACTIVATION flag because this object is coming to rest
|
if (glm::length2(_entity->getVelocity()) == 0.0f) {
|
||||||
// according to a remote simulation and we don't want to wake it up again
|
// this object is coming to rest --> clear the ACTIVATION flag and outgoing priority
|
||||||
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
|
flags &= ~Simulation::DIRTY_PHYSICS_ACTIVATION;
|
||||||
// hint to Bullet that the object is deactivating
|
_body->setActivationState(WANTS_DEACTIVATION);
|
||||||
_body->setActivationState(WANTS_DEACTIVATION);
|
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
|
||||||
_outgoingPriority = NO_PRORITY;
|
_loopsWithoutOwner = 0;
|
||||||
} else {
|
} 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;
|
_nextOwnershipBid = usecTimestampNow() + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||||
if (Physics::getSessionUUID() == _entity->getSimulatorID() || _entity->getSimulationPriority() >= _outgoingPriority) {
|
if (Physics::getSessionUUID() == _entity->getSimulatorID() || _entity->getSimulationPriority() >= _outgoingPriority) {
|
||||||
// we own the simulation or our priority looses to (or ties with) remote
|
// either we already own the simulation or our old outgoing priority momentarily looses to current owner
|
||||||
_outgoingPriority = NO_PRORITY;
|
// so we clear it
|
||||||
|
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (flags & Simulation::DIRTY_SIMULATOR_OWNERSHIP) {
|
if (flags & Simulation::DIRTY_SIMULATOR_OWNERSHIP) {
|
||||||
// (DIRTY_SIMULATOR_OWNERSHIP really means "we should bid for ownership with SCRIPT priority")
|
// The DIRTY_SIMULATOR_OWNERSHIP bit really means "we should bid for ownership at SCRIPT priority".
|
||||||
// we're manipulating this object directly via script, so we artificially
|
// Since that bit is set there must be a local script that is updating the physics properties of the objects
|
||||||
// manipulate the logic to trigger an immediate bid for ownership
|
// therefore we upgrade _outgoingPriority to trigger a bid for ownership.
|
||||||
setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY);
|
setOutgoingPriority(SCRIPT_EDIT_SIMULATION_PRIORITY);
|
||||||
}
|
}
|
||||||
if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
|
if ((flags & Simulation::DIRTY_PHYSICS_ACTIVATION) && !_body->isActive()) {
|
||||||
|
@ -203,7 +209,6 @@ void EntityMotionState::setWorldTransform(const btTransform& worldTrans) {
|
||||||
_loopsWithoutOwner++;
|
_loopsWithoutOwner++;
|
||||||
|
|
||||||
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) {
|
if (_loopsWithoutOwner > LOOPS_FOR_SIMULATION_ORPHAN && usecTimestampNow() > _nextOwnershipBid) {
|
||||||
//qDebug() << "Warning -- claiming something I saw moving." << getName();
|
|
||||||
setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
|
setOutgoingPriority(VOLUNTEER_SIMULATION_PRIORITY);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -235,14 +240,14 @@ btCollisionShape* EntityMotionState::computeNewShape() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const {
|
bool EntityMotionState::isCandidateForOwnership(const QUuid& sessionID) const {
|
||||||
if (!_body || !_entity) {
|
assert(_body);
|
||||||
return false;
|
assert(_entity);
|
||||||
}
|
|
||||||
assert(entityTreeIsLocked());
|
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) {
|
bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
||||||
|
// NOTE: we only get here if we think we own the simulation
|
||||||
assert(_body);
|
assert(_body);
|
||||||
// if we've never checked before, our _lastStep will be 0, and we need to initialize our state
|
// if we've never checked before, our _lastStep will be 0, and we need to initialize our state
|
||||||
if (_lastStep == 0) {
|
if (_lastStep == 0) {
|
||||||
|
@ -253,7 +258,7 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
||||||
_serverAngularVelocity = bulletToGLM(_body->getAngularVelocity());
|
_serverAngularVelocity = bulletToGLM(_body->getAngularVelocity());
|
||||||
_lastStep = simulationStep;
|
_lastStep = simulationStep;
|
||||||
_serverActionData = _entity->getActionData();
|
_serverActionData = _entity->getActionData();
|
||||||
_sentInactive = true;
|
_numInactiveUpdates = 1;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -266,16 +271,21 @@ bool EntityMotionState::remoteSimulationOutOfSync(uint32_t simulationStep) {
|
||||||
int numSteps = simulationStep - _lastStep;
|
int numSteps = simulationStep - _lastStep;
|
||||||
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
float dt = (float)(numSteps) * PHYSICS_ENGINE_FIXED_SUBSTEP;
|
||||||
|
|
||||||
const float INACTIVE_UPDATE_PERIOD = 0.5f;
|
if (_numInactiveUpdates > 0) {
|
||||||
if (_sentInactive) {
|
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
|
// we resend the inactive update every INACTIVE_UPDATE_PERIOD
|
||||||
// until it is removed from the outgoing updates
|
// until it is removed from the outgoing updates
|
||||||
// (which happens when we don't own the simulation and it isn't touching our simulation)
|
// (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);
|
return (dt > INACTIVE_UPDATE_PERIOD);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isActive = _body->isActive();
|
if (!_body->isActive()) {
|
||||||
if (!isActive) {
|
|
||||||
// object has gone inactive but our last send was moving --> send non-moving update immediately
|
// object has gone inactive but our last send was moving --> send non-moving update immediately
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -374,11 +384,12 @@ bool EntityMotionState::shouldSendUpdate(uint32_t simulationStep, const QUuid& s
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_entity->getSimulatorID() != sessionID) {
|
if (_entity->getSimulatorID() != sessionID) {
|
||||||
// we don't own the simulation, but maybe we should...
|
// we don't own the simulation
|
||||||
if (_outgoingPriority != NO_PRORITY) {
|
if (_outgoingPriority != ZERO_SIMULATION_PRIORITY) {
|
||||||
|
// but we would like to own it
|
||||||
if (_outgoingPriority < _entity->getSimulationPriority()) {
|
if (_outgoingPriority < _entity->getSimulationPriority()) {
|
||||||
// our priority loses to remote, so we don't bother to bid
|
// but our priority loses to remote, so we don't bother trying
|
||||||
_outgoingPriority = NO_PRORITY;
|
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return usecTimestampNow() > _nextOwnershipBid;
|
return usecTimestampNow() > _nextOwnershipBid;
|
||||||
|
@ -400,10 +411,12 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
||||||
_entity->setVelocity(zero);
|
_entity->setVelocity(zero);
|
||||||
_entity->setAngularVelocity(zero);
|
_entity->setAngularVelocity(zero);
|
||||||
_entity->setAcceleration(zero);
|
_entity->setAcceleration(zero);
|
||||||
_sentInactive = true;
|
_numInactiveUpdates++;
|
||||||
} else {
|
} else {
|
||||||
|
const uint8_t STEPS_TO_DECIDE_BALLISTIC = 4;
|
||||||
float gravityLength = glm::length(_entity->getGravity());
|
float gravityLength = glm::length(_entity->getGravity());
|
||||||
float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength);
|
float accVsGravity = glm::abs(glm::length(_measuredAcceleration) - gravityLength);
|
||||||
|
const float ACCELERATION_EQUIVALENT_EPSILON_RATIO = 0.1f;
|
||||||
if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) {
|
if (accVsGravity < ACCELERATION_EQUIVALENT_EPSILON_RATIO * gravityLength) {
|
||||||
// acceleration measured during the most recent simulation step was close to gravity.
|
// acceleration measured during the most recent simulation step was close to gravity.
|
||||||
if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) {
|
if (getAccelerationNearlyGravityCount() < STEPS_TO_DECIDE_BALLISTIC) {
|
||||||
|
@ -440,7 +453,7 @@ void EntityMotionState::sendUpdate(OctreeEditPacketSender* packetSender, const Q
|
||||||
_entity->setVelocity(zero);
|
_entity->setVelocity(zero);
|
||||||
_entity->setAngularVelocity(zero);
|
_entity->setAngularVelocity(zero);
|
||||||
}
|
}
|
||||||
_sentInactive = false;
|
_numInactiveUpdates = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// remember properties for local server prediction
|
// 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
|
// 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
|
// but we remember that we do still own it... and rely on the server to tell us that we don't
|
||||||
properties.clearSimulationOwner();
|
properties.clearSimulationOwner();
|
||||||
_outgoingPriority = NO_PRORITY;
|
_outgoingPriority = ZERO_SIMULATION_PRIORITY;
|
||||||
}
|
}
|
||||||
// else the ownership is not changing so we don't bother to pack it
|
// else the ownership is not changing so we don't bother to pack it
|
||||||
} else {
|
} else {
|
||||||
// we don't own the simulation for this entity yet, but we're sending a bid for it
|
// 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;
|
_nextOwnershipBid = now + USECS_BETWEEN_OWNERSHIP_BIDS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -558,7 +571,7 @@ void EntityMotionState::clearIncomingDirtyFlags() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// virtual
|
// virtual
|
||||||
quint8 EntityMotionState::getSimulationPriority() const {
|
uint8_t EntityMotionState::getSimulationPriority() const {
|
||||||
return _entity->getSimulationPriority();
|
return _entity->getSimulationPriority();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -568,7 +581,7 @@ QUuid EntityMotionState::getSimulatorID() const {
|
||||||
return _entity->getSimulatorID();
|
return _entity->getSimulatorID();
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityMotionState::bump(quint8 priority) {
|
void EntityMotionState::bump(uint8_t priority) {
|
||||||
setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
|
setOutgoingPriority(glm::max(VOLUNTEER_SIMULATION_PRIORITY, --priority));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,7 +614,7 @@ void EntityMotionState::measureBodyAcceleration() {
|
||||||
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
|
if (numSubsteps > PHYSICS_ENGINE_MAX_NUM_SUBSTEPS) {
|
||||||
_loopsWithoutOwner = 0;
|
_loopsWithoutOwner = 0;
|
||||||
_lastStep = ObjectMotionState::getWorldSimulationStep();
|
_lastStep = ObjectMotionState::getWorldSimulationStep();
|
||||||
_sentInactive = false;
|
_numInactiveUpdates = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -631,6 +644,6 @@ void EntityMotionState::computeCollisionGroupAndMask(int16_t& group, int16_t& ma
|
||||||
_entity->computeCollisionGroupAndFinalMask(group, mask);
|
_entity->computeCollisionGroupAndFinalMask(group, mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityMotionState::setOutgoingPriority(quint8 priority) {
|
void EntityMotionState::setOutgoingPriority(uint8_t priority) {
|
||||||
_outgoingPriority = glm::max<quint8>(_outgoingPriority, priority);
|
_outgoingPriority = glm::max<uint8_t>(_outgoingPriority, priority);
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ public:
|
||||||
|
|
||||||
void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; }
|
void incrementAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount++; }
|
||||||
void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; }
|
void resetAccelerationNearlyGravityCount() { _accelerationNearlyGravityCount = 0; }
|
||||||
quint8 getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; }
|
uint8_t getAccelerationNearlyGravityCount() { return _accelerationNearlyGravityCount; }
|
||||||
|
|
||||||
virtual float getObjectRestitution() const override { return _entity->getRestitution(); }
|
virtual float getObjectRestitution() const override { return _entity->getRestitution(); }
|
||||||
virtual float getObjectFriction() const override { return _entity->getFriction(); }
|
virtual float getObjectFriction() const override { return _entity->getFriction(); }
|
||||||
|
@ -69,9 +69,9 @@ public:
|
||||||
|
|
||||||
virtual const QUuid getObjectID() const override { return _entity->getID(); }
|
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 QUuid getSimulatorID() const override;
|
||||||
virtual void bump(quint8 priority) override;
|
virtual void bump(uint8_t priority) override;
|
||||||
|
|
||||||
EntityItemPointer getEntity() const { return _entityPtr.lock(); }
|
EntityItemPointer getEntity() const { return _entityPtr.lock(); }
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ public:
|
||||||
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
|
virtual void computeCollisionGroupAndMask(int16_t& group, int16_t& mask) const override;
|
||||||
|
|
||||||
// eternal logic can suggest a simuator priority bid for the next outgoing update
|
// 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;
|
friend class PhysicalEntitySimulation;
|
||||||
|
|
||||||
|
@ -106,10 +106,6 @@ protected:
|
||||||
// Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid.
|
// Meanwhile we also keep a raw EntityItem* for internal stuff where the pointer is guaranteed valid.
|
||||||
EntityItem* _entity;
|
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::vec3 _serverPosition; // in simulation-frame (not world-frame)
|
||||||
glm::quat _serverRotation;
|
glm::quat _serverRotation;
|
||||||
glm::vec3 _serverVelocity;
|
glm::vec3 _serverVelocity;
|
||||||
|
@ -118,15 +114,18 @@ protected:
|
||||||
glm::vec3 _serverAcceleration;
|
glm::vec3 _serverAcceleration;
|
||||||
QByteArray _serverActionData;
|
QByteArray _serverActionData;
|
||||||
|
|
||||||
uint32_t _lastMeasureStep;
|
|
||||||
glm::vec3 _lastVelocity;
|
glm::vec3 _lastVelocity;
|
||||||
glm::vec3 _measuredAcceleration;
|
glm::vec3 _measuredAcceleration;
|
||||||
float _measuredDeltaTime;
|
quint64 _nextOwnershipBid { 0 };
|
||||||
|
|
||||||
quint8 _accelerationNearlyGravityCount;
|
float _measuredDeltaTime;
|
||||||
quint64 _nextOwnershipBid = NO_PRORITY;
|
uint32_t _lastMeasureStep;
|
||||||
uint32_t _loopsWithoutOwner;
|
uint32_t _lastStep; // last step of server extrapolation
|
||||||
quint8 _outgoingPriority = NO_PRORITY;
|
|
||||||
|
uint8_t _loopsWithoutOwner;
|
||||||
|
uint8_t _accelerationNearlyGravityCount;
|
||||||
|
uint8_t _numInactiveUpdates { 1 };
|
||||||
|
uint8_t _outgoingPriority { ZERO_SIMULATION_PRIORITY };
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_EntityMotionState_h
|
#endif // hifi_EntityMotionState_h
|
||||||
|
|
|
@ -249,6 +249,7 @@ void PhysicalEntitySimulation::getObjectsToChange(VectorOfMotionStates& result)
|
||||||
|
|
||||||
void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& motionStates, const QUuid& sessionID) {
|
void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates& motionStates, const QUuid& sessionID) {
|
||||||
QMutexLocker lock(&_mutex);
|
QMutexLocker lock(&_mutex);
|
||||||
|
|
||||||
// walk the motionStates looking for those that correspond to entities
|
// walk the motionStates looking for those that correspond to entities
|
||||||
for (auto stateItr : motionStates) {
|
for (auto stateItr : motionStates) {
|
||||||
ObjectMotionState* state = &(*stateItr);
|
ObjectMotionState* state = &(*stateItr);
|
||||||
|
@ -273,13 +274,15 @@ void PhysicalEntitySimulation::handleOutgoingChanges(const VectorOfMotionStates&
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// send outgoing packets
|
// look for entities to prune or update
|
||||||
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
|
QSet<EntityMotionState*>::iterator stateItr = _outgoingChanges.begin();
|
||||||
while (stateItr != _outgoingChanges.end()) {
|
while (stateItr != _outgoingChanges.end()) {
|
||||||
EntityMotionState* state = *stateItr;
|
EntityMotionState* state = *stateItr;
|
||||||
if (!state->isCandidateForOwnership(sessionID)) {
|
if (!state->isCandidateForOwnership(sessionID)) {
|
||||||
|
// prune
|
||||||
stateItr = _outgoingChanges.erase(stateItr);
|
stateItr = _outgoingChanges.erase(stateItr);
|
||||||
} else if (state->shouldSendUpdate(numSubsteps, sessionID)) {
|
} else if (state->shouldSendUpdate(numSubsteps, sessionID)) {
|
||||||
|
// update
|
||||||
state->sendUpdate(_entityPacketSender, sessionID, numSubsteps);
|
state->sendUpdate(_entityPacketSender, sessionID, numSubsteps);
|
||||||
++stateItr;
|
++stateItr;
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -299,35 +299,35 @@
|
||||||
},
|
},
|
||||||
dynamic: true,
|
dynamic: true,
|
||||||
userData: JSON.stringify({
|
userData: JSON.stringify({
|
||||||
wearable: {
|
"wearable": {
|
||||||
joints: {
|
"joints": {
|
||||||
RightHand: [{
|
"RightHand": [{
|
||||||
x: 0.07079616189002991,
|
"x": 0.07079616189002991,
|
||||||
y: 0.20177987217903137,
|
"y": 0.20177987217903137,
|
||||||
z: 0.06374628841876984
|
"z": 0.06374628841876984
|
||||||
}, {
|
}, {
|
||||||
x: -0.5863648653030396,
|
"x": -0.5863648653030396,
|
||||||
y: -0.46007341146469116,
|
"y": -0.46007341146469116,
|
||||||
z: 0.46949487924575806,
|
"z": 0.46949487924575806,
|
||||||
w: -0.4733745753765106
|
"w": -0.4733745753765106
|
||||||
}],
|
}],
|
||||||
LeftHand: [{
|
"LeftHand": [{
|
||||||
x: 0.1802254319190979,
|
"x": 0.0012094751000404358,
|
||||||
y: 0.13442856073379517,
|
"y": 0.1991066336631775,
|
||||||
z: 0.08504903316497803
|
"z": 0.079972043633461
|
||||||
}, {
|
}, {
|
||||||
x: 0.2198076844215393,
|
"x": 0.29249316453933716,
|
||||||
y: -0.7377811074256897,
|
"y": -0.6115763187408447,
|
||||||
z: 0.2780133783817291,
|
"z": 0.5668558478355408,
|
||||||
w: 0.574519157409668
|
"w": 0.46807748079299927
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grabbableKey: {
|
"grabbableKey": {
|
||||||
invertSolidWhileHeld: true
|
"invertSolidWhileHeld": true
|
||||||
},
|
},
|
||||||
resetMe: {
|
"resetMe": {
|
||||||
resetMe: true
|
"resetMe": true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -283,35 +283,35 @@ MasterReset = function() {
|
||||||
damping: 0.5,
|
damping: 0.5,
|
||||||
collisionSoundURL: "http://hifi-content.s3.amazonaws.com/james/pistol/sounds/drop.wav",
|
collisionSoundURL: "http://hifi-content.s3.amazonaws.com/james/pistol/sounds/drop.wav",
|
||||||
userData: JSON.stringify({
|
userData: JSON.stringify({
|
||||||
wearable: {
|
"wearable": {
|
||||||
joints: {
|
"joints": {
|
||||||
RightHand: [{
|
"RightHand": [{
|
||||||
x: 0.07079616189002991,
|
"x": 0.07079616189002991,
|
||||||
y: 0.20177987217903137,
|
"y": 0.20177987217903137,
|
||||||
z: 0.06374628841876984
|
"z": 0.06374628841876984
|
||||||
}, {
|
}, {
|
||||||
x: -0.5863648653030396,
|
"x": -0.5863648653030396,
|
||||||
y: -0.46007341146469116,
|
"y": -0.46007341146469116,
|
||||||
z: 0.46949487924575806,
|
"z": 0.46949487924575806,
|
||||||
w: -0.4733745753765106
|
"w": -0.4733745753765106
|
||||||
}],
|
}],
|
||||||
LeftHand: [{
|
"LeftHand": [{
|
||||||
x: 0.1802254319190979,
|
"x": 0.0012094751000404358,
|
||||||
y: 0.13442856073379517,
|
"y": 0.1991066336631775,
|
||||||
z: 0.08504903316497803
|
"z": 0.079972043633461
|
||||||
}, {
|
}, {
|
||||||
x: 0.2198076844215393,
|
"x": 0.29249316453933716,
|
||||||
y: -0.7377811074256897,
|
"y": -0.6115763187408447,
|
||||||
z: 0.2780133783817291,
|
"z": 0.5668558478355408,
|
||||||
w: 0.574519157409668
|
"w": 0.46807748079299927
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
grabbableKey: {
|
"grabbableKey": {
|
||||||
invertSolidWhileHeld: true
|
"invertSolidWhileHeld": true
|
||||||
},
|
},
|
||||||
resetMe: {
|
"resetMe": {
|
||||||
resetMe: true
|
"resetMe": true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue