mirror of
https://github.com/overte-org/overte.git
synced 2025-04-23 06:53:46 +02:00
Merge branch 'master' into ericrius1-toybox
This commit is contained in:
commit
7bcb4e4ae3
30 changed files with 524 additions and 142 deletions
|
@ -233,8 +233,8 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
}
|
||||
|
||||
if (_avatarBillboardTimer) {
|
||||
_avatarIdentityTimer->stop();
|
||||
delete _avatarIdentityTimer;
|
||||
_avatarBillboardTimer->stop();
|
||||
delete _avatarBillboardTimer;
|
||||
_avatarBillboardTimer = nullptr;
|
||||
}
|
||||
}
|
||||
|
@ -364,10 +364,15 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
|||
}
|
||||
|
||||
void Agent::aboutToFinish() {
|
||||
_scriptEngine->stop();
|
||||
setIsAvatar(false);// will stop timers for sending billboards and identity packets
|
||||
if (_scriptEngine) {
|
||||
_scriptEngine->stop();
|
||||
}
|
||||
|
||||
_pingTimer->stop();
|
||||
delete _pingTimer;
|
||||
if (_pingTimer) {
|
||||
_pingTimer->stop();
|
||||
delete _pingTimer;
|
||||
}
|
||||
|
||||
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(NULL);
|
||||
|
|
|
@ -238,7 +238,6 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
return 0;
|
||||
}
|
||||
|
||||
auto nackPacketList = NLPacketList::create(_myServer->getMyEditNackType());
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
int packetsSent = 0;
|
||||
|
||||
|
@ -272,18 +271,19 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
|
|||
|
||||
auto it = missingSequenceNumbers.constBegin();
|
||||
|
||||
while (it != missingSequenceNumbers.constEnd()) {
|
||||
unsigned short int sequenceNumber = *it;
|
||||
nackPacketList->writePrimitive(sequenceNumber);
|
||||
++it;
|
||||
}
|
||||
|
||||
|
||||
if (nackPacketList->getNumPackets()) {
|
||||
if (it != missingSequenceNumbers.constEnd()) {
|
||||
auto nackPacketList = NLPacketList::create(_myServer->getMyEditNackType());
|
||||
|
||||
while (it != missingSequenceNumbers.constEnd()) {
|
||||
unsigned short int sequenceNumber = *it;
|
||||
nackPacketList->writePrimitive(sequenceNumber);
|
||||
++it;
|
||||
}
|
||||
|
||||
qDebug() << "NACK Sent back to editor/client... destinationNode=" << nodeUUID;
|
||||
|
||||
|
||||
packetsSent += nackPacketList->getNumPackets();
|
||||
|
||||
|
||||
// send the list of nack packets
|
||||
nodeList->sendPacketList(std::move(nackPacketList), *destinationNode);
|
||||
}
|
||||
|
|
146
examples/actionInspector.js
Normal file
146
examples/actionInspector.js
Normal file
|
@ -0,0 +1,146 @@
|
|||
//
|
||||
// actionInspector.js
|
||||
// examples
|
||||
//
|
||||
// Created by Seth Alves on 2015-9-30.
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
Script.include("libraries/utils.js");
|
||||
|
||||
|
||||
var INSPECT_RADIUS = 10;
|
||||
var overlays = {};
|
||||
|
||||
|
||||
var toType = function(obj) {
|
||||
return ({}).toString.call(obj).match(/\s([a-zA-Z]+)/)[1].toLowerCase()
|
||||
}
|
||||
|
||||
|
||||
function actionArgumentsToString(actionArguments) {
|
||||
var result = "type: " + actionArguments["type"] + "\n";
|
||||
for (var argumentName in actionArguments) {
|
||||
if (actionArguments.hasOwnProperty(argumentName)) {
|
||||
if (argumentName == "type") {
|
||||
continue;
|
||||
}
|
||||
var arg = actionArguments[argumentName];
|
||||
var argType = toType(arg);
|
||||
var argString = arg;
|
||||
if (argType == "object") {
|
||||
if (Object.keys(arg).length == 3) {
|
||||
argString = vec3toStr(arg, 1);
|
||||
}
|
||||
} else if (argType == "number") {
|
||||
argString = arg.toFixed(2);
|
||||
}
|
||||
result += argumentName + ": "
|
||||
// + toType(arg) + " -- "
|
||||
+ argString + "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
function updateOverlay(entityID, actionText) {
|
||||
var properties = Entities.getEntityProperties(entityID, ["position", "dimensions"]);
|
||||
var position = Vec3.sum(properties.position, {x:0, y:properties.dimensions.y, z:0});
|
||||
// print("position: " + vec3toStr(position) + " " + actionText);
|
||||
if (entityID in overlays) {
|
||||
var overlay = overlays[entityID];
|
||||
Overlays.editOverlay(overlay, {
|
||||
text: actionText,
|
||||
position: position
|
||||
});
|
||||
} else {
|
||||
var lines = actionText.split(/\r\n|\r|\n/);
|
||||
|
||||
var maxLineLength = lines[0].length;
|
||||
for (var i = 1; i < lines.length; i++) {
|
||||
if (lines[i].length > maxLineLength) {
|
||||
maxLineLength = lines[i].length;
|
||||
}
|
||||
}
|
||||
|
||||
var textWidth = maxLineLength * 0.034; // XXX how to know this?
|
||||
var textHeight = .5;
|
||||
var numberOfLines = lines.length;
|
||||
var textMargin = 0.05;
|
||||
var lineHeight = (textHeight - (2 * textMargin)) / numberOfLines;
|
||||
|
||||
overlays[entityID] = Overlays.addOverlay("text3d", {
|
||||
position: position,
|
||||
dimensions: { x: textWidth, y: textHeight },
|
||||
backgroundColor: { red: 0, green: 0, blue: 0},
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
topMargin: textMargin,
|
||||
leftMargin: textMargin,
|
||||
bottomMargin: textMargin,
|
||||
rightMargin: textMargin,
|
||||
text: actionText,
|
||||
lineHeight: lineHeight,
|
||||
alpha: 0.9,
|
||||
backgroundAlpha: 0.9,
|
||||
ignoreRayIntersection: true,
|
||||
visible: true,
|
||||
isFacingAvatar: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function cleanup() {
|
||||
for (var entityID in overlays) {
|
||||
Overlays.deleteOverlay(overlays[entityID]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Script.setInterval(function() {
|
||||
var nearbyEntities = Entities.findEntities(MyAvatar.position, INSPECT_RADIUS);
|
||||
for (var entityIndex = 0; entityIndex < nearbyEntities.length; entityIndex++) {
|
||||
var entityID = nearbyEntities[entityIndex];
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
var actionText = ""
|
||||
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
|
||||
var actionID = actionIDs[actionIndex];
|
||||
var actionArguments = Entities.getActionArguments(entityID, actionID);
|
||||
var actionArgumentText = actionArgumentsToString(actionArguments);
|
||||
if (actionArgumentText != "") {
|
||||
actionText += "-----------------\n";
|
||||
actionText += actionArgumentText;
|
||||
}
|
||||
}
|
||||
if (actionText != "") {
|
||||
updateOverlay(entityID, actionText);
|
||||
}
|
||||
|
||||
// if an entity no longer has an action, remove its overlay
|
||||
if (actionIDs.length == 0) {
|
||||
if (entityID in overlays) {
|
||||
Overlays.deleteOverlay(overlays[entityID]);
|
||||
delete overlays[entityID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// if an entity is too far away, remove its overlay
|
||||
for (var entityID in overlays) {
|
||||
var position = Entities.getEntityProperties(entityID, ["position"]).position;
|
||||
if (Vec3.distance(position, MyAvatar.position) > INSPECT_RADIUS) {
|
||||
Overlays.deleteOverlay(overlays[entityID]);
|
||||
delete overlays[entityID];
|
||||
}
|
||||
}
|
||||
|
||||
}, 100);
|
||||
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
|
@ -66,6 +66,7 @@ var MSEC_PER_SEC = 1000.0;
|
|||
// these control how long an abandoned pointer line will hang around
|
||||
var startTime = Date.now();
|
||||
var LIFETIME = 10;
|
||||
var ACTION_LIFETIME = 10; // seconds
|
||||
|
||||
// states for the state machine
|
||||
var STATE_OFF = 0;
|
||||
|
@ -81,14 +82,25 @@ var STATE_RELEASE = 8;
|
|||
var GRAB_USER_DATA_KEY = "grabKey";
|
||||
var GRABBABLE_DATA_KEY = "grabbableKey";
|
||||
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
var AVATAR_COLLISIONS_MENU_ITEM = "Enable avatar collisions";
|
||||
function getTag() {
|
||||
return "grab-" + MyAvatar.sessionUUID;
|
||||
}
|
||||
|
||||
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
var initialAvatarCollisionsMenu = Menu.isOptionChecked(AVATAR_COLLISIONS_MENU_ITEM);
|
||||
var currentAvatarCollisionsMenu = initialAvatarCollisionsMenu;
|
||||
var noCollisionsCount = 0; // how many hands want collisions disabled?
|
||||
function entityIsGrabbedByOther(entityID) {
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
|
||||
var actionID = actionIDs[actionIndex];
|
||||
var actionArguments = Entities.getActionArguments(entityID, actionID);
|
||||
var tag = actionArguments["tag"];
|
||||
if (tag == getTag()) {
|
||||
continue;
|
||||
}
|
||||
if (tag.slice(0, 5) == "grab-") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function MyController(hand, triggerAction) {
|
||||
|
@ -152,28 +164,6 @@ function MyController(hand, triggerAction) {
|
|||
}
|
||||
};
|
||||
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
this.disableAvatarCollisions = function() {
|
||||
noCollisionsCount += 1;
|
||||
if (currentAvatarCollisionsMenu != false) {
|
||||
currentAvatarCollisionsMenu = false;
|
||||
Menu.setIsOptionChecked(AVATAR_COLLISIONS_MENU_ITEM, false);
|
||||
MyAvatar.updateMotionBehaviorFromMenu();
|
||||
}
|
||||
}
|
||||
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
this.revertAvatarCollisions = function() {
|
||||
noCollisionsCount -= 1;
|
||||
if (noCollisionsCount < 1) {
|
||||
if (currentAvatarCollisionsMenu != initialAvatarCollisionsMenu) {
|
||||
currentAvatarCollisionsMenu = initialAvatarCollisionsMenu;
|
||||
Menu.setIsOptionChecked(AVATAR_COLLISIONS_MENU_ITEM, initialAvatarCollisionsMenu);
|
||||
MyAvatar.updateMotionBehaviorFromMenu();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.lineOn = function(closePoint, farPoint, color) {
|
||||
// draw a line
|
||||
if (this.pointer === null) {
|
||||
|
@ -267,6 +257,10 @@ function MyController(hand, triggerAction) {
|
|||
this.state = STATE_NEAR_GRABBING;
|
||||
|
||||
} else {
|
||||
if (entityIsGrabbedByOther(intersection.entityID)) {
|
||||
// don't allow two people to distance grab the same object
|
||||
return;
|
||||
}
|
||||
// the hand is far from the intersected object. go into distance-holding mode
|
||||
this.state = STATE_DISTANCE_HOLDING;
|
||||
this.lineOn(pickRay.origin, Vec3.multiply(pickRay.direction, LINE_LENGTH), NO_INTERSECT_COLOR);
|
||||
|
@ -306,9 +300,6 @@ function MyController(hand, triggerAction) {
|
|||
|
||||
this.distanceHolding = function() {
|
||||
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
this.disableAvatarCollisions();
|
||||
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.palm);
|
||||
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getSpatialControlRawRotation(this.palm));
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, ["position", "rotation"]);
|
||||
|
@ -320,11 +311,14 @@ function MyController(hand, triggerAction) {
|
|||
this.handPreviousPosition = handControllerPosition;
|
||||
this.handPreviousRotation = handRotation;
|
||||
|
||||
this.actionID = NULL_ACTION_ID;
|
||||
this.actionID = Entities.addAction("spring", this.grabbedEntity, {
|
||||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
tag: getTag(),
|
||||
lifetime: ACTION_LIFETIME
|
||||
});
|
||||
if (this.actionID === NULL_ACTION_ID) {
|
||||
this.actionID = null;
|
||||
|
@ -348,9 +342,6 @@ function MyController(hand, triggerAction) {
|
|||
|
||||
this.continueDistanceHolding = function() {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
this.revertAvatarCollisions();
|
||||
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
@ -390,11 +381,11 @@ function MyController(hand, triggerAction) {
|
|||
var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar);
|
||||
var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar);
|
||||
this.currentAvatarOrientation = currentOrientation;
|
||||
|
||||
|
||||
// how far did hand move this timestep?
|
||||
var handMoved = Vec3.subtract(handControllerPosition, this.handPreviousPosition);
|
||||
this.handPreviousPosition = handControllerPosition;
|
||||
|
||||
|
||||
// magnify the hand movement but not the change from avatar movement & rotation
|
||||
handMoved = Vec3.subtract(handMoved, avatarDeltaPosition);
|
||||
handMoved = Vec3.subtract(handMoved, handMovementFromTurning);
|
||||
|
@ -424,19 +415,14 @@ function MyController(hand, triggerAction) {
|
|||
targetPosition: this.currentObjectPosition,
|
||||
linearTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
targetRotation: this.currentObjectRotation,
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME
|
||||
angularTimeScale: DISTANCE_HOLDING_ACTION_TIMEFRAME,
|
||||
lifetime: ACTION_LIFETIME
|
||||
});
|
||||
};
|
||||
|
||||
this.nearGrabbing = function() {
|
||||
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
this.disableAvatarCollisions();
|
||||
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
this.revertAvatarCollisions();
|
||||
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
@ -457,11 +443,13 @@ function MyController(hand, triggerAction) {
|
|||
var offset = Vec3.subtract(currentObjectPosition, handPosition);
|
||||
var offsetPosition = Vec3.multiplyQbyV(Quat.inverse(Quat.multiply(handRotation, offsetRotation)), offset);
|
||||
|
||||
this.actionID = NULL_ACTION_ID;
|
||||
this.actionID = Entities.addAction("hold", this.grabbedEntity, {
|
||||
hand: this.hand === RIGHT_HAND ? "right" : "left",
|
||||
timeScale: NEAR_GRABBING_ACTION_TIMEFRAME,
|
||||
relativePosition: offsetPosition,
|
||||
relativeRotation: offsetRotation
|
||||
relativeRotation: offsetRotation,
|
||||
lifetime: ACTION_LIFETIME
|
||||
});
|
||||
if (this.actionID === NULL_ACTION_ID) {
|
||||
this.actionID = null;
|
||||
|
@ -483,9 +471,6 @@ function MyController(hand, triggerAction) {
|
|||
|
||||
this.continueNearGrabbing = function() {
|
||||
if (this.triggerSmoothedReleased()) {
|
||||
// HACK -- until we have collision groups, don't allow held object to collide with avatar
|
||||
this.revertAvatarCollisions();
|
||||
|
||||
this.state = STATE_RELEASE;
|
||||
return;
|
||||
}
|
||||
|
@ -495,7 +480,7 @@ function MyController(hand, triggerAction) {
|
|||
// object's actual held offset is an idea intended to make it easier to throw things:
|
||||
// Because we might catch something or transfer it between hands without a good idea
|
||||
// of it's actual offset, let's try imparting a velocity which is at a fixed radius
|
||||
// from the palm.
|
||||
// from the palm.
|
||||
|
||||
var handControllerPosition = Controller.getSpatialControlPosition(this.tip);
|
||||
var now = Date.now();
|
||||
|
@ -507,6 +492,8 @@ function MyController(hand, triggerAction) {
|
|||
this.currentHandControllerTipPosition = handControllerPosition;
|
||||
this.currentObjectTime = now;
|
||||
Entities.callEntityMethod(this.grabbedEntity, "continueNearGrab");
|
||||
|
||||
Entities.updateAction(this.grabbedEntity, this.actionID, {lifetime: ACTION_LIFETIME});
|
||||
};
|
||||
|
||||
this.nearGrabbingNonColliding = function() {
|
||||
|
|
|
@ -13,7 +13,27 @@
|
|||
var MAX_SOLID_ANGLE = 0.01; // objects that appear smaller than this can't be grabbed
|
||||
var ZERO_VEC3 = {x: 0, y: 0, z: 0};
|
||||
var IDENTITY_QUAT = {x: 0, y: 0, z: 0, w: 0};
|
||||
var ACTION_LIFETIME = 120; // 2 minutes
|
||||
|
||||
function getTag() {
|
||||
return "grab-" + MyAvatar.sessionUUID;
|
||||
}
|
||||
|
||||
function entityIsGrabbedByOther(entityID) {
|
||||
var actionIDs = Entities.getActionIDs(entityID);
|
||||
for (var actionIndex = 0; actionIndex < actionIDs.length; actionIndex++) {
|
||||
var actionID = actionIDs[actionIndex];
|
||||
var actionArguments = Entities.getActionArguments(entityID, actionID);
|
||||
var tag = actionArguments["tag"];
|
||||
if (tag == getTag()) {
|
||||
continue;
|
||||
}
|
||||
if (tag.slice(0, 5) == "grab-") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// helper function
|
||||
function mouseIntersectionWithPlane(pointOnPlane, planeNormal, event, maxDistance) {
|
||||
|
@ -288,7 +308,7 @@ Grabber.prototype.moveEvent = function(event) {
|
|||
}
|
||||
this.currentPosition = entityProperties.position;
|
||||
|
||||
var actionArgs = {};
|
||||
var actionArgs = {tag: getTag(), lifetime: ACTION_LIFETIME};
|
||||
|
||||
if (this.mode === "rotate") {
|
||||
var drag = mouse.getDrag();
|
||||
|
@ -303,7 +323,7 @@ Grabber.prototype.moveEvent = function(event) {
|
|||
// var qZero = entityProperties.rotation;
|
||||
//var qZero = this.lastRotation;
|
||||
this.lastRotation = Quat.multiply(deltaQ, this.lastRotation);
|
||||
actionArgs = {targetRotation: this.lastRotation, angularTimeScale: 0.1};
|
||||
actionArgs = {targetRotation: this.lastRotation, angularTimeScale: 0.1, tag: getTag(), lifetime: ACTION_LIFETIME};
|
||||
} else {
|
||||
var newPointOnPlane;
|
||||
if (this.mode === "verticalCylinder") {
|
||||
|
@ -327,13 +347,15 @@ Grabber.prototype.moveEvent = function(event) {
|
|||
}
|
||||
}
|
||||
this.targetPosition = Vec3.subtract(newPointOnPlane, this.offset);
|
||||
actionArgs = {targetPosition: this.targetPosition, linearTimeScale: 0.1};
|
||||
actionArgs = {targetPosition: this.targetPosition, linearTimeScale: 0.1, tag: getTag(), lifetime: ACTION_LIFETIME};
|
||||
|
||||
beacon.updatePosition(this.targetPosition);
|
||||
}
|
||||
|
||||
if (!this.actionID) {
|
||||
this.actionID = Entities.addAction("spring", this.entityID, actionArgs);
|
||||
if (!entityIsGrabbedByOther(this.entityID)) {
|
||||
this.actionID = Entities.addAction("spring", this.entityID, actionArgs);
|
||||
}
|
||||
} else {
|
||||
Entities.updateAction(this.entityID, this.actionID, actionArgs);
|
||||
}
|
||||
|
|
43
examples/toys/basketball_hoop/createHoop.js
Normal file
43
examples/toys/basketball_hoop/createHoop.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
//
|
||||
// createHoop.js
|
||||
// examples/entityScripts
|
||||
//
|
||||
// Created by James B. Pollack on 9/29/2015
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
// This is a script that creates a persistent basketball hoop with a working collision hull. Feel free to move it.
|
||||
// Run basketball.js to make a basketball.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
/*global MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
|
||||
var hoopURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop.fbx";
|
||||
var hoopCollisionHullURL = "http://hifi-public.s3.amazonaws.com/models/basketball_hoop/basketball_hoop_collision_hull.obj";
|
||||
|
||||
var hoopStartPosition =
|
||||
Vec3.sum(MyAvatar.position,
|
||||
Vec3.multiplyQbyV(MyAvatar.orientation, {
|
||||
x: 0,
|
||||
y: 0.0,
|
||||
z: -2
|
||||
}));
|
||||
|
||||
var hoop = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: hoopURL,
|
||||
position: hoopStartPosition,
|
||||
shapeType: 'compound',
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -9.8,
|
||||
z: 0
|
||||
},
|
||||
dimensions: {
|
||||
x: 1.89,
|
||||
y: 3.99,
|
||||
z: 3.79
|
||||
},
|
||||
compoundShapeURL: hoopCollisionHullURL
|
||||
});
|
||||
|
|
@ -10,22 +10,21 @@
|
|||
//
|
||||
|
||||
var array = [];
|
||||
var buffer = "\n\n\n\n\n======= JS API list =======";
|
||||
function listKeys(string, object) {
|
||||
if (string == "listKeys" || string == "array" || string == "buffer" || string == "i") {
|
||||
if (string === "listKeys" || string === "array" || string === "buffer" || string === "i") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (typeof(object) != "object") {
|
||||
if (typeof(object) !== "object" || object === null) {
|
||||
array.push(string + " " + typeof(object));
|
||||
return;
|
||||
}
|
||||
|
||||
var keys = Object.keys(object);
|
||||
for (var i = 0; i < keys.length; ++i) {
|
||||
if (string == "") {
|
||||
if (string === "") {
|
||||
listKeys(keys[i], object[keys[i]]);
|
||||
} else {
|
||||
} else if (keys[i] !== "parent") {
|
||||
listKeys(string + "." + keys[i], object[keys[i]]);
|
||||
}
|
||||
}
|
||||
|
@ -34,9 +33,10 @@ function listKeys(string, object) {
|
|||
listKeys("", this);
|
||||
array.sort();
|
||||
|
||||
var buffer = "\n======= JS API list =======";
|
||||
for (var i = 0; i < array.length; ++i) {
|
||||
buffer = buffer + "\n" + array[i];
|
||||
buffer += "\n" + array[i];
|
||||
}
|
||||
buffer = buffer + "\n========= API END =========\n\n\n\n\n";
|
||||
buffer += "\n========= API END =========\n";
|
||||
|
||||
print(buffer);
|
||||
|
|
|
@ -26,13 +26,14 @@
|
|||
{
|
||||
"jointName": "Neck",
|
||||
"positionVar": "neckPosition",
|
||||
"rotationVar": "neckRotation"
|
||||
"rotationVar": "neckRotation",
|
||||
"typeVar": "headAndNeckType"
|
||||
},
|
||||
{
|
||||
"jointName": "Head",
|
||||
"positionVar": "headPosition",
|
||||
"rotationVar": "headRotation",
|
||||
"typeVar": "headType"
|
||||
"typeVar": "headAndNeckType"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -196,7 +196,6 @@ Hifi.VrMenu {
|
|||
|
||||
function insertItem(menu, beforeItem, newMenuItem) {
|
||||
for (var i = 0; i < menu.items.length; ++i) {
|
||||
console.log(menu.items[i]);
|
||||
if (menu.items[i] === beforeItem) {
|
||||
return menu.insertItem(i, newMenuItem);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,9 @@ EntityActionPointer InterfaceActionFactory::factory(EntityActionType type,
|
|||
if (action) {
|
||||
bool ok = action->updateArguments(arguments);
|
||||
if (ok) {
|
||||
if (action->lifetimeIsOver()) {
|
||||
return nullptr;
|
||||
}
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
@ -63,5 +66,9 @@ EntityActionPointer InterfaceActionFactory::factoryBA(EntityItemPointer ownerEnt
|
|||
if (action) {
|
||||
action->deserialize(data);
|
||||
}
|
||||
if (action->lifetimeIsOver()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
|
|
@ -84,6 +84,9 @@ void AvatarActionHold::updateActionWorker(float deltaTimeStep) {
|
|||
|
||||
|
||||
bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
||||
if (!ObjectAction::updateArguments(arguments)) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
glm::vec3 relativePosition =
|
||||
EntityActionInterface::extractVec3Argument("hold", arguments, "relativePosition", ok, false);
|
||||
|
@ -134,7 +137,7 @@ bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
|||
|
||||
|
||||
QVariantMap AvatarActionHold::getArguments() {
|
||||
QVariantMap arguments;
|
||||
QVariantMap arguments = ObjectAction::getArguments();
|
||||
withReadLock([&]{
|
||||
if (!_mine) {
|
||||
arguments = ObjectActionSpring::getArguments();
|
||||
|
|
|
@ -271,6 +271,25 @@ glm::mat4 MyAvatar::getSensorToWorldMatrix() const {
|
|||
return _sensorToWorldMatrix;
|
||||
}
|
||||
|
||||
// returns true if pos is OUTSIDE of the vertical capsule
|
||||
// where the middle cylinder length is defined by capsuleLen and the radius by capsuleRad.
|
||||
static bool capsuleCheck(const glm::vec3& pos, float capsuleLen, float capsuleRad) {
|
||||
const float halfCapsuleLen = capsuleLen / 2.0f;
|
||||
if (fabs(pos.y) <= halfCapsuleLen) {
|
||||
// cylinder check for middle capsule
|
||||
glm::vec2 horizPos(pos.x, pos.z);
|
||||
return glm::length(horizPos) > capsuleRad;
|
||||
} else if (pos.y > halfCapsuleLen) {
|
||||
glm::vec3 center(0.0f, halfCapsuleLen, 0.0f);
|
||||
return glm::length(center - pos) > capsuleRad;
|
||||
} else if (pos.y < halfCapsuleLen) {
|
||||
glm::vec3 center(0.0f, -halfCapsuleLen, 0.0f);
|
||||
return glm::length(center - pos) > capsuleRad;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Pass a recent sample of the HMD to the avatar.
|
||||
// This can also update the avatar's position to follow the HMD
|
||||
// as it moves through the world.
|
||||
|
@ -289,11 +308,14 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
|
|||
_hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix);
|
||||
|
||||
const float STRAIGHTING_LEAN_DURATION = 0.5f; // seconds
|
||||
const float STRAIGHTING_LEAN_THRESHOLD = 0.2f; // meters
|
||||
|
||||
// define a vertical capsule
|
||||
const float STRAIGHTING_LEAN_CAPSULE_RADIUS = 0.2f; // meters
|
||||
const float STRAIGHTING_LEAN_CAPSULE_LENGTH = 0.05f; // length of the cylinder part of the capsule in meters.
|
||||
|
||||
auto newBodySensorMatrix = deriveBodyFromHMDSensor();
|
||||
glm::vec3 diff = extractTranslation(newBodySensorMatrix) - extractTranslation(_bodySensorMatrix);
|
||||
if (!_straightingLean && glm::length(diff) > STRAIGHTING_LEAN_THRESHOLD) {
|
||||
if (!_straightingLean && capsuleCheck(diff, STRAIGHTING_LEAN_CAPSULE_LENGTH, STRAIGHTING_LEAN_CAPSULE_RADIUS)) {
|
||||
|
||||
// begin homing toward derived body position.
|
||||
_straightingLean = true;
|
||||
|
@ -1854,13 +1876,39 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
|
|||
const glm::quat hmdOrientation = getHMDSensorOrientation();
|
||||
const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation);
|
||||
|
||||
// In sensor space, figure out where the avatar body should be,
|
||||
// by applying offsets from the avatar's neck & head joints.
|
||||
vec3 localEyes = _skeletonModel.getDefaultEyeModelPosition();
|
||||
vec3 localNeck(0.0f, 0.48f, 0.0f); // start with some kind of guess if the skeletonModel is not loaded yet.
|
||||
_skeletonModel.getLocalNeckPosition(localNeck);
|
||||
const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 1.6f, 0.0f);
|
||||
const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 1.6f, 0.0f);
|
||||
const glm::vec3 DEFAULT_NECK_POS(0.0f, 1.5f, 0.0f);
|
||||
const glm::vec3 DEFAULT_HIPS_POS(0.0f, 1.0f, 0.0f);
|
||||
|
||||
vec3 localEyes, localNeck;
|
||||
if (!_debugDrawSkeleton) {
|
||||
const glm::quat rotY180 = glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
localEyes = rotY180 * (((DEFAULT_RIGHT_EYE_POS + DEFAULT_LEFT_EYE_POS) / 2.0f) - DEFAULT_HIPS_POS);
|
||||
localNeck = rotY180 * (DEFAULT_NECK_POS - DEFAULT_HIPS_POS);
|
||||
} else {
|
||||
// TODO: At the moment MyAvatar does not have access to the rig, which has the skeleton, which has the bind poses.
|
||||
// for now use the _debugDrawSkeleton, which is initialized with the same FBX model as the rig.
|
||||
|
||||
// TODO: cache these indices.
|
||||
int rightEyeIndex = _debugDrawSkeleton->nameToJointIndex("RightEye");
|
||||
int leftEyeIndex = _debugDrawSkeleton->nameToJointIndex("LeftEye");
|
||||
int neckIndex = _debugDrawSkeleton->nameToJointIndex("Neck");
|
||||
int hipsIndex = _debugDrawSkeleton->nameToJointIndex("Hips");
|
||||
|
||||
glm::vec3 absRightEyePos = rightEyeIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(rightEyeIndex).trans : DEFAULT_RIGHT_EYE_POS;
|
||||
glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(leftEyeIndex).trans : DEFAULT_LEFT_EYE_POS;
|
||||
glm::vec3 absNeckPos = neckIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(neckIndex).trans : DEFAULT_NECK_POS;
|
||||
glm::vec3 absHipsPos = neckIndex != -1 ? _debugDrawSkeleton->getAbsoluteBindPose(hipsIndex).trans : DEFAULT_HIPS_POS;
|
||||
|
||||
const glm::quat rotY180 = glm::angleAxis((float)PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
localEyes = rotY180 * (((absRightEyePos + absLeftEyePos) / 2.0f) - absHipsPos);
|
||||
localNeck = rotY180 * (absNeckPos - absHipsPos);
|
||||
}
|
||||
|
||||
// apply simplistic head/neck model
|
||||
// figure out where the avatar body should be by applying offsets from the avatar's neck & head joints.
|
||||
|
||||
// eyeToNeck offset is relative full HMD orientation.
|
||||
// while neckToRoot offset is only relative to HMDs yaw.
|
||||
glm::vec3 eyeToNeck = hmdOrientation * (localNeck - localEyes);
|
||||
|
|
|
@ -89,7 +89,7 @@ static int findRootJointInSkeleton(AnimSkeleton::ConstPointer skeleton, int inde
|
|||
return rootIndex;
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets) {
|
||||
void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses) {
|
||||
// build a list of valid targets from _targetVarVec and animVars
|
||||
_maxTargetIndex = -1;
|
||||
bool removeUnfoundJoints = false;
|
||||
|
@ -107,7 +107,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
|
|||
}
|
||||
} else {
|
||||
IKTarget target;
|
||||
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, _relativePoses);
|
||||
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses);
|
||||
target.pose.trans = animVars.lookup(targetVar.positionVar, defaultPose.trans);
|
||||
target.pose.rot = animVars.lookup(targetVar.rotationVar, defaultPose.rot);
|
||||
target.setType(animVars.lookup(targetVar.typeVar, QString("")));
|
||||
|
@ -154,7 +154,6 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
do {
|
||||
int lowestMovedIndex = _relativePoses.size();
|
||||
for (auto& target: targets) {
|
||||
int tipIndex = target.index;
|
||||
if (target.type == IKTarget::Type::RotationOnly) {
|
||||
// the final rotation will be enforced after the iterations
|
||||
continue;
|
||||
|
@ -162,6 +161,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
AnimPose targetPose = target.pose;
|
||||
|
||||
// cache tip absolute transform
|
||||
int tipIndex = target.index;
|
||||
glm::vec3 tipPosition = absolutePoses[tipIndex].trans;
|
||||
glm::quat tipRotation = absolutePoses[tipIndex].rot;
|
||||
|
||||
|
@ -288,6 +288,17 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
}
|
||||
} while (numLoops < MAX_IK_LOOPS);
|
||||
|
||||
/* KEEP: example code for measuring endeffector error of IK solution
|
||||
for (uint32_t i = 0; i < targets.size(); ++i) {
|
||||
auto& target = targets[i];
|
||||
if (target.type == IKTarget::Type::RotationOnly) {
|
||||
continue;
|
||||
}
|
||||
glm::vec3 tipPosition = absolutePoses[target.index].trans;
|
||||
std::cout << i << " IK error = " << glm::distance(tipPosition, target.pose.trans) << std::endl; // adebug
|
||||
}
|
||||
*/
|
||||
|
||||
// finally set the relative rotation of each tip to agree with absolute target rotation
|
||||
for (auto& target: targets) {
|
||||
int tipIndex = target.index;
|
||||
|
@ -312,25 +323,8 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const std::vector<I
|
|||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) {
|
||||
if (!_relativePoses.empty()) {
|
||||
// build a list of targets from _targetVarVec
|
||||
std::vector<IKTarget> targets;
|
||||
computeTargets(animVars, targets);
|
||||
|
||||
if (targets.empty()) {
|
||||
// no IK targets but still need to enforce constraints
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
|
||||
while (constraintItr != _constraints.end()) {
|
||||
int index = constraintItr->first;
|
||||
glm::quat rotation = _relativePoses[index].rot;
|
||||
constraintItr->second->apply(rotation);
|
||||
_relativePoses[index].rot = rotation;
|
||||
++constraintItr;
|
||||
}
|
||||
} else {
|
||||
solveWithCyclicCoordinateDescent(targets);
|
||||
}
|
||||
}
|
||||
// don't call this function, call overlay() instead
|
||||
assert(false);
|
||||
return _relativePoses;
|
||||
}
|
||||
|
||||
|
@ -356,7 +350,27 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
}
|
||||
}
|
||||
}
|
||||
return evaluate(animVars, dt, triggersOut);
|
||||
|
||||
if (!_relativePoses.empty()) {
|
||||
// build a list of targets from _targetVarVec
|
||||
std::vector<IKTarget> targets;
|
||||
computeTargets(animVars, targets, underPoses);
|
||||
|
||||
if (targets.empty()) {
|
||||
// no IK targets but still need to enforce constraints
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
|
||||
while (constraintItr != _constraints.end()) {
|
||||
int index = constraintItr->first;
|
||||
glm::quat rotation = _relativePoses[index].rot;
|
||||
constraintItr->second->apply(rotation);
|
||||
_relativePoses[index].rot = rotation;
|
||||
++constraintItr;
|
||||
}
|
||||
} else {
|
||||
solveWithCyclicCoordinateDescent(targets);
|
||||
}
|
||||
}
|
||||
return _relativePoses;
|
||||
}
|
||||
|
||||
RotationConstraint* AnimInverseKinematics::getConstraint(int index) {
|
||||
|
|
|
@ -50,7 +50,7 @@ protected:
|
|||
void setType(const QString& typeVar) { type = ((typeVar == "RotationOnly") ? Type::RotationOnly : Type::RotationAndPosition); }
|
||||
};
|
||||
|
||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets);
|
||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
||||
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton);
|
||||
|
||||
|
|
|
@ -184,6 +184,8 @@ public:
|
|||
case AnimVariant::Type::String:
|
||||
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
|
||||
break;
|
||||
default:
|
||||
assert("AnimVariant::Type" == "valid");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1026,12 +1026,21 @@ static void computeHeadNeckAnimVars(AnimSkeleton::ConstPointer skeleton, const A
|
|||
const glm::quat hmdOrientation = hmdPose.rot * rotY180; // rotY180 will make z forward not -z
|
||||
|
||||
// TODO: cache jointIndices
|
||||
int rightEyeIndex = skeleton->nameToJointIndex("RightEye");
|
||||
int leftEyeIndex = skeleton->nameToJointIndex("LeftEye");
|
||||
int headIndex = skeleton->nameToJointIndex("Head");
|
||||
int neckIndex = skeleton->nameToJointIndex("Neck");
|
||||
|
||||
const glm::vec3 DEFAULT_RIGHT_EYE_POS(-0.3f, 1.6f, 0.0f);
|
||||
const glm::vec3 DEFAULT_LEFT_EYE_POS(0.3f, 1.6f, 0.0f);
|
||||
const glm::vec3 DEFAULT_HEAD_POS(0.0f, 1.55f, 0.0f);
|
||||
const glm::vec3 DEFAULT_NECK_POS(0.0f, 1.5f, 0.0f);
|
||||
|
||||
// Use absolute bindPose positions just in case the relBindPose have rotations we don't expect.
|
||||
glm::vec3 absRightEyePos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("RightEye")).trans;
|
||||
glm::vec3 absLeftEyePos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("LeftEye")).trans;
|
||||
glm::vec3 absHeadPos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Head")).trans;
|
||||
glm::vec3 absNeckPos = skeleton->getAbsoluteBindPose(skeleton->nameToJointIndex("Neck")).trans;
|
||||
glm::vec3 absRightEyePos = rightEyeIndex != -1 ? skeleton->getAbsoluteBindPose(rightEyeIndex).trans : DEFAULT_RIGHT_EYE_POS;
|
||||
glm::vec3 absLeftEyePos = leftEyeIndex != -1 ? skeleton->getAbsoluteBindPose(leftEyeIndex).trans : DEFAULT_LEFT_EYE_POS;
|
||||
glm::vec3 absHeadPos = headIndex != -1 ? skeleton->getAbsoluteBindPose(headIndex).trans : DEFAULT_HEAD_POS;
|
||||
glm::vec3 absNeckPos = neckIndex != -1 ? skeleton->getAbsoluteBindPose(neckIndex).trans : DEFAULT_NECK_POS;
|
||||
|
||||
glm::vec3 absCenterEyePos = (absRightEyePos + absLeftEyePos) / 2.0f;
|
||||
glm::vec3 eyeOffset = absCenterEyePos - absHeadPos;
|
||||
|
@ -1079,7 +1088,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) {
|
|||
|
||||
_animVars.set("headPosition", headPos);
|
||||
_animVars.set("headRotation", headRot);
|
||||
_animVars.set("headType", QString("RotationAndPosition"));
|
||||
_animVars.set("headAndNeckType", QString("RotationAndPosition"));
|
||||
_animVars.set("neckPosition", neckPos);
|
||||
_animVars.set("neckRotation", neckRot);
|
||||
|
||||
|
@ -1092,7 +1101,7 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) {
|
|||
|
||||
_animVars.unset("headPosition");
|
||||
_animVars.set("headRotation", realLocalHeadOrientation);
|
||||
_animVars.set("headType", QString("RotationOnly"));
|
||||
_animVars.set("headAndNeckType", QString("RotationOnly"));
|
||||
_animVars.unset("neckPosition");
|
||||
_animVars.unset("neckRotation");
|
||||
}
|
||||
|
|
|
@ -966,7 +966,8 @@ bool AvatarData::hasIdentityChangedAfterParsing(NLPacket& packet) {
|
|||
QByteArray AvatarData::identityByteArray() {
|
||||
QByteArray identityData;
|
||||
QDataStream identityStream(&identityData, QIODevice::Append);
|
||||
const QUrl& urlToSend = (_skeletonModelURL == AvatarData::defaultFullAvatarModelUrl()) ? QUrl("") : _skeletonModelURL;
|
||||
QUrl emptyURL("");
|
||||
const QUrl& urlToSend = (_skeletonModelURL == AvatarData::defaultFullAvatarModelUrl()) ? emptyURL : _skeletonModelURL;
|
||||
|
||||
identityStream << QUuid() << _faceModelURL << urlToSend << _attachmentData << _displayName;
|
||||
|
||||
|
|
|
@ -46,6 +46,8 @@ public:
|
|||
static EntityActionType actionTypeFromString(QString actionTypeString);
|
||||
static QString actionTypeToString(EntityActionType actionType);
|
||||
|
||||
virtual bool lifetimeIsOver() { return false; }
|
||||
|
||||
protected:
|
||||
virtual glm::vec3 getPosition() = 0;
|
||||
virtual void setPosition(glm::vec3 position) = 0;
|
||||
|
|
|
@ -23,7 +23,8 @@ std::unique_ptr<NLPacketList> NLPacketList::create(PacketType packetType, QByteA
|
|||
}
|
||||
|
||||
std::unique_ptr<NLPacketList> NLPacketList::fromPacketList(std::unique_ptr<PacketList> packetList) {
|
||||
auto nlPacketList = std::unique_ptr<NLPacketList>(new NLPacketList(std::move(*packetList.release()))); nlPacketList->open(ReadOnly);
|
||||
auto nlPacketList = std::unique_ptr<NLPacketList>(new NLPacketList(std::move(*packetList.release())));
|
||||
nlPacketList->open(ReadOnly);
|
||||
return nlPacketList;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ void SentPacketHistory::packetSent(uint16_t sequenceNumber, const NLPacket& pack
|
|||
}
|
||||
_newestSequenceNumber = sequenceNumber;
|
||||
|
||||
QWriteLocker locker(&_packetsLock);
|
||||
_sentPackets.insert(NLPacket::createCopy(packet));
|
||||
}
|
||||
|
||||
|
@ -48,6 +49,11 @@ const NLPacket* SentPacketHistory::getPacket(uint16_t sequenceNumber) const {
|
|||
if (seqDiff < 0) {
|
||||
seqDiff += UINT16_RANGE;
|
||||
}
|
||||
|
||||
return _sentPackets.get(seqDiff)->get();
|
||||
|
||||
QReadLocker locker(&_packetsLock);
|
||||
auto packet = _sentPackets.get(seqDiff);
|
||||
if (packet) {
|
||||
return packet->get();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
|
@ -12,7 +12,9 @@
|
|||
#define hifi_SentPacketHistory_h
|
||||
|
||||
#include <stdint.h>
|
||||
#include <qbytearray.h>
|
||||
|
||||
#include <QtCore/QByteArray>
|
||||
#include <QtCore/QReadWriteLock>
|
||||
|
||||
#include "NLPacket.h"
|
||||
#include "RingBufferHistory.h"
|
||||
|
@ -29,6 +31,7 @@ public:
|
|||
const NLPacket* getPacket(uint16_t sequenceNumber) const;
|
||||
|
||||
private:
|
||||
mutable QReadWriteLock _packetsLock { QReadWriteLock::Recursive };
|
||||
RingBufferHistory<std::unique_ptr<NLPacket>> _sentPackets; // circular buffer
|
||||
|
||||
uint16_t _newestSequenceNumber;
|
||||
|
|
|
@ -49,7 +49,8 @@ void ThreadedAssignment::setFinished(bool isFinished) {
|
|||
}
|
||||
|
||||
if (_statsTimer) {
|
||||
_statsTimer->stop();
|
||||
_statsTimer->deleteLater();
|
||||
_statsTimer = nullptr;
|
||||
}
|
||||
|
||||
// call our virtual aboutToFinish method - this gives the ThreadedAssignment subclass a chance to cleanup
|
||||
|
@ -105,7 +106,7 @@ void ThreadedAssignment::sendStatsPacket() {
|
|||
void ThreadedAssignment::startSendingStats() {
|
||||
// send the stats packet every 1s
|
||||
if (!_statsTimer) {
|
||||
_statsTimer = new QTimer();
|
||||
_statsTimer = new QTimer;
|
||||
connect(_statsTimer, &QTimer::timeout, this, &ThreadedAssignment::sendStatsPacket);
|
||||
}
|
||||
|
||||
|
|
|
@ -161,13 +161,16 @@ void DefaultCC::onLoss(SequenceNumber rangeStart, SequenceNumber rangeEnd) {
|
|||
|
||||
_lastDecreaseMaxSeq = _sendCurrSeqNum;
|
||||
|
||||
// avoid synchronous rate decrease across connections using randomization
|
||||
std::random_device rd;
|
||||
std::mt19937 generator(rd());
|
||||
std::uniform_int_distribution<> distribution(1, _avgNAKNum);
|
||||
|
||||
_randomDecreaseThreshold = distribution(generator);
|
||||
|
||||
if (_avgNAKNum < 1) {
|
||||
_randomDecreaseThreshold = 1;
|
||||
} else {
|
||||
// avoid synchronous rate decrease across connections using randomization
|
||||
std::random_device rd;
|
||||
std::mt19937 generator(rd());
|
||||
std::uniform_int_distribution<> distribution(1, std::max(1, _avgNAKNum));
|
||||
|
||||
_randomDecreaseThreshold = distribution(generator);
|
||||
}
|
||||
} else if ((_decreaseCount++ < MAX_DECREASES_PER_CONGESTION_EPOCH) && ((++_nakCount % _randomDecreaseThreshold) == 0)) {
|
||||
// there have been fewer than MAX_DECREASES_PER_CONGESTION_EPOCH AND this NAK matches the random count at which we
|
||||
// decided we would decrease the packet send period
|
||||
|
|
|
@ -32,7 +32,7 @@ Connection::Connection(Socket* parentSocket, HifiSockAddr destination, std::uniq
|
|||
_destination(destination),
|
||||
_congestionControl(move(congestionControl))
|
||||
{
|
||||
Q_ASSERT_X(socket, "Connection::Connection", "Must be called with a valid Socket*");
|
||||
Q_ASSERT_X(parentSocket, "Connection::Connection", "Must be called with a valid Socket*");
|
||||
|
||||
Q_ASSERT_X(_congestionControl, "Connection::Connection", "Must be called with a valid CongestionControl object");
|
||||
_congestionControl->init();
|
||||
|
|
|
@ -28,7 +28,8 @@ class Packet;
|
|||
class PacketList : public QIODevice {
|
||||
Q_OBJECT
|
||||
public:
|
||||
static std::unique_ptr<PacketList> create(PacketType packetType, QByteArray extendedHeader = QByteArray(), bool isReliable = false, bool isOrdered = false);
|
||||
static std::unique_ptr<PacketList> create(PacketType packetType, QByteArray extendedHeader = QByteArray(),
|
||||
bool isReliable = false, bool isOrdered = false);
|
||||
static std::unique_ptr<PacketList> fromReceivedPackets(std::list<std::unique_ptr<Packet>>&& packets);
|
||||
|
||||
bool isReliable() const { return _isReliable; }
|
||||
|
|
|
@ -346,6 +346,7 @@ void OctreeEditPacketSender::processNackPacket(NLPacket& packet, SharedNodePoint
|
|||
if (_sentPacketHistories.count(sendingNode->getUUID()) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const SentPacketHistory& sentPacketHistory = _sentPacketHistories[sendingNode->getUUID()];
|
||||
|
||||
// read sequence numbers and queue packets for resend
|
||||
|
|
|
@ -30,6 +30,18 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta
|
|||
dynamicsWorld->removeAction(this);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_expires > 0) {
|
||||
quint64 now = usecTimestampNow();
|
||||
if (now > _expires) {
|
||||
EntityItemPointer ownerEntity = _ownerEntity.lock();
|
||||
_active = false;
|
||||
if (ownerEntity) {
|
||||
ownerEntity->removeAction(nullptr, getID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!_active) {
|
||||
return;
|
||||
}
|
||||
|
@ -37,6 +49,40 @@ void ObjectAction::updateAction(btCollisionWorld* collisionWorld, btScalar delta
|
|||
updateActionWorker(deltaTimeStep);
|
||||
}
|
||||
|
||||
bool ObjectAction::updateArguments(QVariantMap arguments) {
|
||||
bool lifetimeSet = true;
|
||||
float lifetime = EntityActionInterface::extractFloatArgument("action", arguments, "lifetime", lifetimeSet, false);
|
||||
if (lifetimeSet) {
|
||||
quint64 now = usecTimestampNow();
|
||||
_expires = now + (quint64)(lifetime * USECS_PER_SECOND);
|
||||
} else {
|
||||
_expires = 0;
|
||||
}
|
||||
|
||||
bool tagSet = true;
|
||||
QString tag = EntityActionInterface::extractStringArgument("action", arguments, "tag", tagSet, false);
|
||||
if (tagSet) {
|
||||
_tag = tag;
|
||||
} else {
|
||||
tag = "";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap ObjectAction::getArguments() {
|
||||
QVariantMap arguments;
|
||||
if (_expires == 0) {
|
||||
arguments["lifetime"] = 0.0f;
|
||||
} else {
|
||||
quint64 now = usecTimestampNow();
|
||||
arguments["lifetime"] = (float)(_expires - now) / (float)USECS_PER_SECOND;
|
||||
}
|
||||
arguments["tag"] = _tag;
|
||||
return arguments;
|
||||
}
|
||||
|
||||
|
||||
void ObjectAction::debugDraw(btIDebugDraw* debugDrawer) {
|
||||
}
|
||||
|
||||
|
@ -136,3 +182,14 @@ void ObjectAction::activateBody() {
|
|||
}
|
||||
}
|
||||
|
||||
bool ObjectAction::lifetimeIsOver() {
|
||||
if (_expires == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
if (now >= _expires) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -33,8 +33,8 @@ public:
|
|||
virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; }
|
||||
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; }
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) = 0;
|
||||
virtual QVariantMap getArguments() = 0;
|
||||
virtual bool updateArguments(QVariantMap arguments);
|
||||
virtual QVariantMap getArguments();
|
||||
|
||||
// this is called from updateAction and should be overridden by subclasses
|
||||
virtual void updateActionWorker(float deltaTimeStep) = 0;
|
||||
|
@ -46,6 +46,8 @@ public:
|
|||
virtual QByteArray serialize() const = 0;
|
||||
virtual void deserialize(QByteArray serializedArguments) = 0;
|
||||
|
||||
virtual bool lifetimeIsOver();
|
||||
|
||||
protected:
|
||||
|
||||
virtual btRigidBody* getRigidBody();
|
||||
|
@ -59,11 +61,11 @@ protected:
|
|||
virtual void setAngularVelocity(glm::vec3 angularVelocity);
|
||||
virtual void activateBody();
|
||||
|
||||
private:
|
||||
|
||||
protected:
|
||||
bool _active;
|
||||
EntityItemWeakPointer _ownerEntity;
|
||||
|
||||
quint64 _expires; // in seconds since epoch
|
||||
QString _tag;
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectAction_h
|
||||
|
|
|
@ -80,6 +80,9 @@ void ObjectActionOffset::updateActionWorker(btScalar deltaTimeStep) {
|
|||
|
||||
|
||||
bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
|
||||
if (!ObjectAction::updateArguments(arguments)) {
|
||||
return false;
|
||||
}
|
||||
bool ok = true;
|
||||
glm::vec3 pointToOffsetFrom =
|
||||
EntityActionInterface::extractVec3Argument("offset action", arguments, "pointToOffsetFrom", ok, true);
|
||||
|
@ -90,7 +93,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
|
|||
ok = true;
|
||||
float linearTimeScale =
|
||||
EntityActionInterface::extractFloatArgument("offset action", arguments, "linearTimeScale", ok, false);
|
||||
if (!ok) {
|
||||
if (!ok) {
|
||||
linearTimeScale = _linearTimeScale;
|
||||
}
|
||||
|
||||
|
@ -119,7 +122,7 @@ bool ObjectActionOffset::updateArguments(QVariantMap arguments) {
|
|||
}
|
||||
|
||||
QVariantMap ObjectActionOffset::getArguments() {
|
||||
QVariantMap arguments;
|
||||
QVariantMap arguments = ObjectAction::getArguments();
|
||||
withReadLock([&] {
|
||||
arguments["pointToOffsetFrom"] = glmToQMap(_pointToOffsetFrom);
|
||||
arguments["linearTimeScale"] = _linearTimeScale;
|
||||
|
@ -140,6 +143,9 @@ QByteArray ObjectActionOffset::serialize() const {
|
|||
dataStream << _linearTimeScale;
|
||||
dataStream << _positionalTargetSet;
|
||||
|
||||
dataStream << _expires;
|
||||
dataStream << _tag;
|
||||
|
||||
return ba;
|
||||
}
|
||||
|
||||
|
@ -165,5 +171,8 @@ void ObjectActionOffset::deserialize(QByteArray serializedArguments) {
|
|||
dataStream >> _linearTimeScale;
|
||||
dataStream >> _positionalTargetSet;
|
||||
|
||||
dataStream >> _expires;
|
||||
dataStream >> _tag;
|
||||
|
||||
_active = true;
|
||||
}
|
||||
|
|
|
@ -109,6 +109,9 @@ void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
|
|||
const float MIN_TIMESCALE = 0.1f;
|
||||
|
||||
bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
||||
if (!ObjectAction::updateArguments(arguments)) {
|
||||
return false;
|
||||
}
|
||||
// targets are required, spring-constants are optional
|
||||
bool ok = true;
|
||||
glm::vec3 positionalTarget =
|
||||
|
@ -155,7 +158,7 @@ bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
|||
}
|
||||
|
||||
QVariantMap ObjectActionSpring::getArguments() {
|
||||
QVariantMap arguments;
|
||||
QVariantMap arguments = ObjectAction::getArguments();
|
||||
withReadLock([&] {
|
||||
arguments["linearTimeScale"] = _linearTimeScale;
|
||||
arguments["targetPosition"] = glmToQMap(_positionalTarget);
|
||||
|
@ -182,6 +185,9 @@ QByteArray ObjectActionSpring::serialize() const {
|
|||
dataStream << _angularTimeScale;
|
||||
dataStream << _rotationalTargetSet;
|
||||
|
||||
dataStream << _expires;
|
||||
dataStream << _tag;
|
||||
|
||||
return serializedActionArguments;
|
||||
}
|
||||
|
||||
|
@ -210,5 +216,8 @@ void ObjectActionSpring::deserialize(QByteArray serializedArguments) {
|
|||
dataStream >> _angularTimeScale;
|
||||
dataStream >> _rotationalTargetSet;
|
||||
|
||||
dataStream >> _expires;
|
||||
dataStream >> _tag;
|
||||
|
||||
_active = true;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue