Merge branch 'master' into ericrius1-toybox

This commit is contained in:
James Pollack 2015-10-01 14:59:54 -07:00
commit 7bcb4e4ae3
30 changed files with 524 additions and 142 deletions

View file

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

View file

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

View file

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

View file

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

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

View file

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

View file

@ -26,13 +26,14 @@
{
"jointName": "Neck",
"positionVar": "neckPosition",
"rotationVar": "neckRotation"
"rotationVar": "neckRotation",
"typeVar": "headAndNeckType"
},
{
"jointName": "Head",
"positionVar": "headPosition",
"rotationVar": "headRotation",
"typeVar": "headType"
"typeVar": "headAndNeckType"
}
]
},

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -184,6 +184,8 @@ public:
case AnimVariant::Type::String:
qCDebug(animation) << " " << pair.first << "=" << pair.second.getString();
break;
default:
assert("AnimVariant::Type" == "valid");
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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