improved re-sync handling of SequenceNumberStats

removed "duplicate" category since it's not 100% accurate
This commit is contained in:
wangyix 2014-08-04 11:09:20 -07:00
parent 99df05f770
commit d394c203b6
6 changed files with 94 additions and 842 deletions

View file

@ -1,674 +0,0 @@
//
// bot_procedural.js
// hifi
//
// Created by Ben Arnold on 7/29/2013
//
// Copyright (c) 2014 HighFidelity, Inc. All rights reserved.
//
// This is an example script that demonstrates an NPC avatar.
//
//
//For procedural walk animation
Script.include("http://s3-us-west-1.amazonaws.com/highfidelity-public/scripts/proceduralAnimationAPI.js");
var procAnimAPI = new ProcAnimAPI();
function getRandomFloat(min, max) {
return Math.random() * (max - min) + min;
}
function getRandomInt (min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function printVector(string, vector) {
print(string + " " + vector.x + ", " + vector.y + ", " + vector.z);
}
var CHANCE_OF_MOVING = 0.005;
var CHANCE_OF_SOUND = 0.005;
var CHANCE_OF_HEAD_TURNING = 0.01;
var CHANCE_OF_BIG_MOVE = 1.0;
var isMoving = false;
var isTurningHead = false;
var isPlayingAudio = false;
var X_MIN = 0.50;
var X_MAX = 15.60;
var Z_MIN = 0.50;
var Z_MAX = 15.10;
var Y_FEET = 0.0;
var AVATAR_PELVIS_HEIGHT = 0.84;
var Y_PELVIS = Y_FEET + AVATAR_PELVIS_HEIGHT;
var MAX_PELVIS_DELTA = 2.5;
var MOVE_RANGE_SMALL = 3.0;
var MOVE_RANGE_BIG = 10.0;
var TURN_RANGE = 70.0;
var STOP_TOLERANCE = 0.05;
var MOVE_RATE = 0.05;
var TURN_RATE = 0.2;
var HEAD_TURN_RATE = 0.05;
var PITCH_RANGE = 15.0;
var YAW_RANGE = 35.0;
var firstPosition = { x: getRandomFloat(X_MIN, X_MAX), y: Y_PELVIS, z: getRandomFloat(Z_MIN, Z_MAX) };
var targetPosition = { x: 0, y: 0, z: 0 };
var targetOrientation = { x: 0, y: 0, z: 0, w: 0 };
var currentOrientation = { x: 0, y: 0, z: 0, w: 0 };
var targetHeadPitch = 0.0;
var targetHeadYaw = 0.0;
var basePelvisHeight = 0.0;
var pelvisOscillatorPosition = 0.0;
var pelvisOscillatorVelocity = 0.0;
function clamp(val, min, max){
return Math.max(min, Math.min(max, val))
}
//Array of all valid bot numbers
var validBotNumbers = [];
// right now we only use bot 63, since many other bots have messed up skeletons and LOD issues
var botNumber = 63;//getRandomInt(0, 99);
var newFaceFilePrefix = "ron";
var newBodyFilePrefix = "bot" + botNumber;
// set the face model fst using the bot number
// there is no need to change the body model - we're using the default
Avatar.faceModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/" + newFaceFilePrefix + ".fst";
Avatar.skeletonModelURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/" + newBodyFilePrefix + "_a.fst";
Avatar.billboardURL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/meshes/billboards/bot" + botNumber + ".png";
Agent.isAvatar = true;
Agent.isListeningToAudioStream = true;
// change the avatar's position to the random one
Avatar.position = firstPosition;
basePelvisHeight = firstPosition.y;
printVector("New dancer, position = ", Avatar.position);
function loadSounds() {
var sound_filenames = ["AB1.raw", "Anchorman2.raw", "B1.raw", "B1.raw", "Bale1.raw", "Bandcamp.raw",
"Big1.raw", "Big2.raw", "Brian1.raw", "Buster1.raw", "CES1.raw", "CES2.raw", "CES3.raw", "CES4.raw",
"Carrie1.raw", "Carrie3.raw", "Charlotte1.raw", "EN1.raw", "EN2.raw", "EN3.raw", "Eugene1.raw", "Francesco1.raw",
"Italian1.raw", "Japanese1.raw", "Leigh1.raw", "Lucille1.raw", "Lucille2.raw", "MeanGirls.raw", "Murray2.raw",
"Nigel1.raw", "PennyLane.raw", "Pitt1.raw", "Ricardo.raw", "SN.raw", "Sake1.raw", "Samantha1.raw", "Samantha2.raw",
"Spicoli1.raw", "Supernatural.raw", "Swearengen1.raw", "TheDude.raw", "Tony.raw", "Triumph1.raw", "Uma1.raw",
"Walken1.raw", "Walken2.raw", "Z1.raw", "Z2.raw"
];
var footstep_filenames = ["FootstepW2Left-12db.wav", "FootstepW2Right-12db.wav", "FootstepW3Left-12db.wav", "FootstepW3Right-12db.wav",
"FootstepW5Left-12db.wav", "FootstepW5Right-12db.wav"];
var SOUND_BASE_URL = "https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Cocktail+Party+Snippets/Raws/";
var FOOTSTEP_BASE_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Footsteps/";
for (var i = 0; i < sound_filenames.length; i++) {
sounds.push(new Sound(SOUND_BASE_URL + sound_filenames[i]));
}
for (var i = 0; i < footstep_filenames.length; i++) {
footstepSounds.push(new Sound(FOOTSTEP_BASE_URL + footstep_filenames[i]));
}
}
var sounds = [];
var footstepSounds = [];
loadSounds();
function playRandomSound() {
if (!Agent.isPlayingAvatarSound) {
var whichSound = Math.floor((Math.random() * sounds.length));
Agent.playAvatarSound(sounds[whichSound]);
}
}
function playRandomFootstepSound() {
var whichSound = Math.floor((Math.random() * footstepSounds.length));
var options = new AudioInjectionOptions();
options.position = Avatar.position;
options.volume = 1.0;
Audio.playSound(footstepSounds[whichSound], options);
}
// ************************************ Facial Animation **********************************
var allBlendShapes = [];
var targetBlendCoefficient = [];
var currentBlendCoefficient = [];
//Blendshape constructor
function addBlendshapeToPose(pose, shapeIndex, val) {
var index = pose.blendShapes.length;
pose.blendShapes[index] = {shapeIndex: shapeIndex, val: val };
}
//The mood of the avatar, determines face. 0 = happy, 1 = angry, 2 = sad.
//Randomly pick avatar mood. 80% happy, 10% mad 10% sad
var randMood = Math.floor(Math.random() * 11);
var avatarMood;
if (randMood == 0) {
avatarMood = 1;
} else if (randMood == 2) {
avatarMood = 2;
} else {
avatarMood = 0;
}
var currentExpression = -1;
//Face pose constructor
var happyPoses = [];
happyPoses[0] = {blendShapes: []};
addBlendshapeToPose(happyPoses[0], 28, 0.7); //MouthSmile_L
addBlendshapeToPose(happyPoses[0], 29, 0.7); //MouthSmile_R
happyPoses[1] = {blendShapes: []};
addBlendshapeToPose(happyPoses[1], 28, 1.0); //MouthSmile_L
addBlendshapeToPose(happyPoses[1], 29, 1.0); //MouthSmile_R
addBlendshapeToPose(happyPoses[1], 21, 0.2); //JawOpen
happyPoses[2] = {blendShapes: []};
addBlendshapeToPose(happyPoses[2], 28, 1.0); //MouthSmile_L
addBlendshapeToPose(happyPoses[2], 29, 1.0); //MouthSmile_R
addBlendshapeToPose(happyPoses[2], 21, 0.5); //JawOpen
addBlendshapeToPose(happyPoses[2], 46, 1.0); //CheekSquint_L
addBlendshapeToPose(happyPoses[2], 47, 1.0); //CheekSquint_R
addBlendshapeToPose(happyPoses[2], 17, 1.0); //BrowsU_L
addBlendshapeToPose(happyPoses[2], 18, 1.0); //BrowsU_R
var angryPoses = [];
angryPoses[0] = {blendShapes: []};
addBlendshapeToPose(angryPoses[0], 26, 0.6); //MouthFrown_L
addBlendshapeToPose(angryPoses[0], 27, 0.6); //MouthFrown_R
addBlendshapeToPose(angryPoses[0], 14, 0.6); //BrowsD_L
addBlendshapeToPose(angryPoses[0], 15, 0.6); //BrowsD_R
angryPoses[1] = {blendShapes: []};
addBlendshapeToPose(angryPoses[1], 26, 0.9); //MouthFrown_L
addBlendshapeToPose(angryPoses[1], 27, 0.9); //MouthFrown_R
addBlendshapeToPose(angryPoses[1], 14, 0.9); //BrowsD_L
addBlendshapeToPose(angryPoses[1], 15, 0.9); //BrowsD_R
angryPoses[2] = {blendShapes: []};
addBlendshapeToPose(angryPoses[2], 26, 1.0); //MouthFrown_L
addBlendshapeToPose(angryPoses[2], 27, 1.0); //MouthFrown_R
addBlendshapeToPose(angryPoses[2], 14, 1.0); //BrowsD_L
addBlendshapeToPose(angryPoses[2], 15, 1.0); //BrowsD_R
addBlendshapeToPose(angryPoses[2], 21, 0.5); //JawOpen
addBlendshapeToPose(angryPoses[2], 46, 1.0); //CheekSquint_L
addBlendshapeToPose(angryPoses[2], 47, 1.0); //CheekSquint_R
var sadPoses = [];
sadPoses[0] = {blendShapes: []};
addBlendshapeToPose(sadPoses[0], 26, 0.6); //MouthFrown_L
addBlendshapeToPose(sadPoses[0], 27, 0.6); //MouthFrown_R
addBlendshapeToPose(sadPoses[0], 16, 0.2); //BrowsU_C
addBlendshapeToPose(sadPoses[0], 2, 0.6); //EyeSquint_L
addBlendshapeToPose(sadPoses[0], 3, 0.6); //EyeSquint_R
sadPoses[1] = {blendShapes: []};
addBlendshapeToPose(sadPoses[1], 26, 0.9); //MouthFrown_L
addBlendshapeToPose(sadPoses[1], 27, 0.9); //MouthFrown_R
addBlendshapeToPose(sadPoses[1], 16, 0.6); //BrowsU_C
addBlendshapeToPose(sadPoses[1], 2, 0.9); //EyeSquint_L
addBlendshapeToPose(sadPoses[1], 3, 0.9); //EyeSquint_R
sadPoses[2] = {blendShapes: []};
addBlendshapeToPose(sadPoses[2], 26, 1.0); //MouthFrown_L
addBlendshapeToPose(sadPoses[2], 27, 1.0); //MouthFrown_R
addBlendshapeToPose(sadPoses[2], 16, 0.1); //BrowsU_C
addBlendshapeToPose(sadPoses[2], 2, 1.0); //EyeSquint_L
addBlendshapeToPose(sadPoses[2], 3, 1.0); //EyeSquint_R
addBlendshapeToPose(sadPoses[2], 21, 0.3); //JawOpen
var facePoses = [];
facePoses[0] = happyPoses;
facePoses[1] = angryPoses;
facePoses[2] = sadPoses;
function addBlendShape(s) {
allBlendShapes[allBlendShapes.length] = s;
}
//It is imperative that the following blendshapes are all present and are in the correct order
addBlendShape("EyeBlink_L"); //0
addBlendShape("EyeBlink_R"); //1
addBlendShape("EyeSquint_L"); //2
addBlendShape("EyeSquint_R"); //3
addBlendShape("EyeDown_L"); //4
addBlendShape("EyeDown_R"); //5
addBlendShape("EyeIn_L"); //6
addBlendShape("EyeIn_R"); //7
addBlendShape("EyeOpen_L"); //8
addBlendShape("EyeOpen_R"); //9
addBlendShape("EyeOut_L"); //10
addBlendShape("EyeOut_R"); //11
addBlendShape("EyeUp_L"); //12
addBlendShape("EyeUp_R"); //13
addBlendShape("BrowsD_L"); //14
addBlendShape("BrowsD_R"); //15
addBlendShape("BrowsU_C"); //16
addBlendShape("BrowsU_L"); //17
addBlendShape("BrowsU_R"); //18
addBlendShape("JawFwd"); //19
addBlendShape("JawLeft"); //20
addBlendShape("JawOpen"); //21
addBlendShape("JawChew"); //22
addBlendShape("JawRight"); //23
addBlendShape("MouthLeft"); //24
addBlendShape("MouthRight"); //25
addBlendShape("MouthFrown_L"); //26
addBlendShape("MouthFrown_R"); //27
addBlendShape("MouthSmile_L"); //28
addBlendShape("MouthSmile_R"); //29
addBlendShape("MouthDimple_L"); //30
addBlendShape("MouthDimple_R"); //31
addBlendShape("LipsStretch_L"); //32
addBlendShape("LipsStretch_R"); //33
addBlendShape("LipsUpperClose"); //34
addBlendShape("LipsLowerClose"); //35
addBlendShape("LipsUpperUp"); //36
addBlendShape("LipsLowerDown"); //37
addBlendShape("LipsUpperOpen"); //38
addBlendShape("LipsLowerOpen"); //39
addBlendShape("LipsFunnel"); //40
addBlendShape("LipsPucker"); //41
addBlendShape("ChinLowerRaise"); //42
addBlendShape("ChinUpperRaise"); //43
addBlendShape("Sneer"); //44
addBlendShape("Puff"); //45
addBlendShape("CheekSquint_L"); //46
addBlendShape("CheekSquint_R"); //47
for (var i = 0; i < allBlendShapes.length; i++) {
targetBlendCoefficient[i] = 0;
currentBlendCoefficient[i] = 0;
}
function setRandomExpression() {
//Clear all expression data for current expression
if (currentExpression != -1) {
var expression = facePoses[avatarMood][currentExpression];
for (var i = 0; i < expression.blendShapes.length; i++) {
targetBlendCoefficient[expression.blendShapes[i].shapeIndex] = 0.0;
}
}
//Get a new current expression
currentExpression = Math.floor(Math.random() * facePoses[avatarMood].length);
var expression = facePoses[avatarMood][currentExpression];
for (var i = 0; i < expression.blendShapes.length; i++) {
targetBlendCoefficient[expression.blendShapes[i].shapeIndex] = expression.blendShapes[i].val;
}
}
var expressionChangeSpeed = 0.1;
function updateBlendShapes(deltaTime) {
for (var i = 0; i < allBlendShapes.length; i++) {
currentBlendCoefficient[i] += (targetBlendCoefficient[i] - currentBlendCoefficient[i]) * expressionChangeSpeed;
Avatar.setBlendshape(allBlendShapes[i], currentBlendCoefficient[i]);
}
}
var BLINK_SPEED = 0.15;
var CHANCE_TO_BLINK = 0.0025;
var MAX_BLINK = 0.85;
var blink = 0.0;
var isBlinking = false;
function updateBlinking(deltaTime) {
if (isBlinking == false) {
if (Math.random() < CHANCE_TO_BLINK) {
isBlinking = true;
} else {
blink -= BLINK_SPEED;
if (blink < 0.0) blink = 0.0;
}
} else {
blink += BLINK_SPEED;
if (blink > MAX_BLINK) {
blink = MAX_BLINK;
isBlinking = false;
}
}
currentBlendCoefficient[0] = blink;
currentBlendCoefficient[1] = blink;
targetBlendCoefficient[0] = blink;
targetBlendCoefficient[1] = blink;
}
// *************************************************************************************
//Procedural walk animation using two keyframes
//We use a separate array for front and back joints
//Pitch, yaw, and roll for the joints
var rightAngles = [];
var leftAngles = [];
//for non mirrored joints such as the spine
var middleAngles = [];
//Actual joint mappings
var SHOULDER_JOINT_NUMBER = 15;
var ELBOW_JOINT_NUMBER = 16;
var JOINT_R_HIP = 1;
var JOINT_R_KNEE = 2;
var JOINT_L_HIP = 6;
var JOINT_L_KNEE = 7;
var JOINT_R_ARM = 15;
var JOINT_R_FOREARM = 16;
var JOINT_L_ARM = 39;
var JOINT_L_FOREARM = 40;
var JOINT_SPINE = 11;
var JOINT_R_FOOT = 3;
var JOINT_L_FOOT = 8;
var JOINT_R_TOE = 4;
var JOINT_L_TOE = 9;
// ******************************* Animation Is Defined Below *************************************
var NUM_FRAMES = 2;
for (var i = 0; i < NUM_FRAMES; i++) {
rightAngles[i] = [];
leftAngles[i] = [];
middleAngles[i] = [];
}
//Joint order for actual joint mappings, should be interleaved R,L,R,L,...S,S,S for R = right, L = left, S = single
var JOINT_ORDER = [];
//*** right / left joints ***
var HIP = 0;
JOINT_ORDER.push(JOINT_R_HIP);
JOINT_ORDER.push(JOINT_L_HIP);
var KNEE = 1;
JOINT_ORDER.push(JOINT_R_KNEE);
JOINT_ORDER.push(JOINT_L_KNEE);
var ARM = 2;
JOINT_ORDER.push(JOINT_R_ARM);
JOINT_ORDER.push(JOINT_L_ARM);
var FOREARM = 3;
JOINT_ORDER.push(JOINT_R_FOREARM);
JOINT_ORDER.push(JOINT_L_FOREARM);
var FOOT = 4;
JOINT_ORDER.push(JOINT_R_FOOT);
JOINT_ORDER.push(JOINT_L_FOOT);
var TOE = 5;
JOINT_ORDER.push(JOINT_R_TOE);
JOINT_ORDER.push(JOINT_L_TOE);
//*** middle joints ***
var SPINE = 0;
JOINT_ORDER.push(JOINT_SPINE);
//We have to store the angles so we can invert yaw and roll when making the animation
//symmetrical
//Front refers to leg, not arm.
//Legs Extending
rightAngles[0][HIP] = [30.0, 0.0, 8.0];
rightAngles[0][KNEE] = [-15.0, 0.0, 0.0];
rightAngles[0][ARM] = [85.0, -25.0, 0.0];
rightAngles[0][FOREARM] = [0.0, 0.0, -15.0];
rightAngles[0][FOOT] = [0.0, 0.0, 0.0];
rightAngles[0][TOE] = [0.0, 0.0, 0.0];
leftAngles[0][HIP] = [-15, 0.0, 8.0];
leftAngles[0][KNEE] = [-26, 0.0, 0.0];
leftAngles[0][ARM] = [85.0, 20.0, 0.0];
leftAngles[0][FOREARM] = [10.0, 0.0, -25.0];
leftAngles[0][FOOT] = [-13.0, 0.0, 0.0];
leftAngles[0][TOE] = [34.0, 0.0, 0.0];
middleAngles[0][SPINE] = [0.0, -15.0, 5.0];
//Legs Passing
rightAngles[1][HIP] = [6.0, 0.0, 8.0];
rightAngles[1][KNEE] = [-12.0, 0.0, 0.0];
rightAngles[1][ARM] = [85.0, 0.0, 0.0];
rightAngles[1][FOREARM] = [0.0, 0.0, -15.0];
rightAngles[1][FOOT] = [6.0, -8.0, 0.0];
rightAngles[1][TOE] = [0.0, 0.0, 0.0];
leftAngles[1][HIP] = [10.0, 0.0, 8.0];
leftAngles[1][KNEE] = [-60.0, 0.0, 0.0];
leftAngles[1][ARM] = [85.0, 0.0, 0.0];
leftAngles[1][FOREARM] = [0.0, 0.0, -15.0];
leftAngles[1][FOOT] = [0.0, 0.0, 0.0];
leftAngles[1][TOE] = [0.0, 0.0, 0.0];
middleAngles[1][SPINE] = [0.0, 0.0, 0.0];
//Actual keyframes for the animation
var walkKeyFrames = procAnimAPI.generateKeyframes(rightAngles, leftAngles, middleAngles, NUM_FRAMES);
// ******************************* Animation Is Defined Above *************************************
// ********************************** Standing Key Frame ******************************************
//We don't have to do any mirroring or anything, since this is just a single pose.
var rightQuats = [];
var leftQuats = [];
var middleQuats = [];
rightQuats[HIP] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 7.0);
rightQuats[KNEE] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0);
rightQuats[ARM] = Quat.fromPitchYawRollDegrees(85.0, 0.0, 0.0);
rightQuats[FOREARM] = Quat.fromPitchYawRollDegrees(0.0, 0.0, -10.0);
rightQuats[FOOT] = Quat.fromPitchYawRollDegrees(0.0, -8.0, 0.0);
rightQuats[TOE] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0);
leftQuats[HIP] = Quat.fromPitchYawRollDegrees(0, 0.0, -7.0);
leftQuats[KNEE] = Quat.fromPitchYawRollDegrees(0, 0.0, 0.0);
leftQuats[ARM] = Quat.fromPitchYawRollDegrees(85.0, 0.0, 0.0);
leftQuats[FOREARM] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 10.0);
leftQuats[FOOT] = Quat.fromPitchYawRollDegrees(0.0, 8.0, 0.0);
leftQuats[TOE] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0);
middleQuats[SPINE] = Quat.fromPitchYawRollDegrees(0.0, 0.0, 0.0);
var standingKeyFrame = new procAnimAPI.KeyFrame(rightQuats, leftQuats, middleQuats);
// ************************************************************************************************
var currentFrame = 0;
var walkTime = 0.0;
var walkWheelRadius = 0.5;
var walkWheelRate = 2.0 * 3.141592 * walkWheelRadius / 8.0;
var avatarAcceleration = 0.75;
var avatarVelocity = 0.0;
var avatarMaxVelocity = 1.4;
function handleAnimation(deltaTime) {
updateBlinking(deltaTime);
updateBlendShapes(deltaTime);
if (Math.random() < 0.01) {
setRandomExpression();
}
if (avatarVelocity == 0.0) {
walkTime = 0.0;
currentFrame = 0;
} else {
walkTime += avatarVelocity * deltaTime;
if (walkTime > walkWheelRate) {
walkTime = 0.0;
currentFrame++;
if (currentFrame % 2 == 1) {
playRandomFootstepSound();
}
if (currentFrame > 3) {
currentFrame = 0;
}
}
}
var frame = walkKeyFrames[currentFrame];
var walkInterp = walkTime / walkWheelRate;
var animInterp = avatarVelocity / (avatarMaxVelocity / 1.3);
if (animInterp > 1.0) animInterp = 1.0;
for (var i = 0; i < JOINT_ORDER.length; i++) {
var walkJoint = procAnimAPI.deCasteljau(frame.rotations[i], frame.nextFrame.rotations[i], frame.controlPoints[i][0], frame.controlPoints[i][1], walkInterp);
var standJoint = standingKeyFrame.rotations[i];
var finalJoint = Quat.mix(standJoint, walkJoint, animInterp);
Avatar.setJointData(JOINT_ORDER[i], finalJoint);
}
}
function jumpWithLoudness(deltaTime) {
// potentially change pelvis height depending on trailing average loudness
pelvisOscillatorVelocity += deltaTime * Agent.lastReceivedAudioLoudness * 700.0 ;
pelvisOscillatorVelocity -= pelvisOscillatorPosition * 0.75;
pelvisOscillatorVelocity *= 0.97;
pelvisOscillatorPosition += deltaTime * pelvisOscillatorVelocity;
Avatar.headPitch = pelvisOscillatorPosition * 60.0;
var pelvisPosition = Avatar.position;
pelvisPosition.y = (Y_PELVIS - 0.35) + pelvisOscillatorPosition;
if (pelvisPosition.y < Y_PELVIS) {
pelvisPosition.y = Y_PELVIS;
} else if (pelvisPosition.y > Y_PELVIS + 1.0) {
pelvisPosition.y = Y_PELVIS + 1.0;
}
Avatar.position = pelvisPosition;
}
var forcedMove = false;
var wasMovingLastFrame = false;
function handleHeadTurn() {
if (!isTurningHead && (Math.random() < CHANCE_OF_HEAD_TURNING)) {
targetHeadPitch = getRandomFloat(-PITCH_RANGE, PITCH_RANGE);
targetHeadYaw = getRandomFloat(-YAW_RANGE, YAW_RANGE);
isTurningHead = true;
} else {
Avatar.headPitch = Avatar.headPitch + (targetHeadPitch - Avatar.headPitch) * HEAD_TURN_RATE;
Avatar.headYaw = Avatar.headYaw + (targetHeadYaw - Avatar.headYaw) * HEAD_TURN_RATE;
if (Math.abs(Avatar.headPitch - targetHeadPitch) < STOP_TOLERANCE &&
Math.abs(Avatar.headYaw - targetHeadYaw) < STOP_TOLERANCE) {
isTurningHead = false;
}
}
}
function stopWalking() {
avatarVelocity = 0.0;
isMoving = false;
}
var MAX_ATTEMPTS = 40;
function handleWalking(deltaTime) {
if (forcedMove || (!isMoving && Math.random() < CHANCE_OF_MOVING)) {
// Set new target location
var moveRange;
if (Math.random() < CHANCE_OF_BIG_MOVE) {
moveRange = MOVE_RANGE_BIG;
} else {
moveRange = MOVE_RANGE_SMALL;
}
//Keep trying new orientations if the desired target location is out of bounds
var attempts = 0;
do {
targetOrientation = Quat.multiply(Avatar.orientation, Quat.angleAxis(getRandomFloat(-TURN_RANGE, TURN_RANGE), { x:0, y:1, z:0 }));
var front = Quat.getFront(targetOrientation);
targetPosition = Vec3.sum(Avatar.position, Vec3.multiply(front, getRandomFloat(0.0, moveRange)));
}
while ((targetPosition.x < X_MIN || targetPosition.x > X_MAX || targetPosition.z < Z_MIN || targetPosition.z > Z_MAX)
&& attempts < MAX_ATTEMPTS);
targetPosition.x = clamp(targetPosition.x, X_MIN, X_MAX);
targetPosition.z = clamp(targetPosition.z, Z_MIN, Z_MAX);
targetPosition.y = Y_PELVIS;
wasMovingLastFrame = true;
isMoving = true;
forcedMove = false;
} else if (isMoving) {
var targetVector = Vec3.subtract(targetPosition, Avatar.position);
var distance = Vec3.length(targetVector);
if (distance <= avatarVelocity * deltaTime) {
Avatar.position = targetPosition;
stopWalking();
} else {
var direction = Vec3.normalize(targetVector);
//Figure out if we should be slowing down
var t = avatarVelocity / avatarAcceleration;
var d = (avatarVelocity / 2.0) * t;
if (distance < d) {
avatarVelocity -= avatarAcceleration * deltaTime;
if (avatarVelocity <= 0) {
stopWalking();
}
} else {
avatarVelocity += avatarAcceleration * deltaTime;
if (avatarVelocity > avatarMaxVelocity) avatarVelocity = avatarMaxVelocity;
}
Avatar.position = Vec3.sum(Avatar.position, Vec3.multiply(direction, avatarVelocity * deltaTime));
Avatar.orientation = Quat.mix(Avatar.orientation, targetOrientation, TURN_RATE);
wasMovingLastFrame = true;
}
}
}
function handleTalking() {
if (Math.random() < CHANCE_OF_SOUND) {
playRandomSound();
}
}
function changePelvisHeight(newHeight) {
var newPosition = Avatar.position;
newPosition.y = newHeight;
Avatar.position = newPosition;
}
function updateBehavior(deltaTime) {
if (AvatarList.containsAvatarWithDisplayName("mrdj")) {
if (wasMovingLastFrame) {
isMoving = false;
}
// we have a DJ, shouldn't we be dancing?
jumpWithLoudness(deltaTime);
} else {
// no DJ, let's just chill on the dancefloor - randomly walking and talking
handleHeadTurn();
handleAnimation(deltaTime);
handleWalking(deltaTime);
handleTalking();
}
}
Script.update.connect(updateBehavior);

View file

@ -372,7 +372,6 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
QString incomingEarlyString = locale.toString((uint)seqStats.getEarly());
QString incomingLikelyLostString = locale.toString((uint)seqStats.getLost());
QString incomingRecovered = locale.toString((uint)seqStats.getRecovered());
QString incomingDuplicateString = locale.toString((uint)seqStats.getDuplicate());
int clockSkewInMS = node->getClockSkewUsec() / (int)USECS_PER_MSEC;
QString incomingFlightTimeString = locale.toString((int)stats.getIncomingFlightTimeAverage());
@ -386,8 +385,7 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser
serverDetails << "<br/>" << " Out of Order: " << qPrintable(incomingOutOfOrderString) <<
"/ Early: " << qPrintable(incomingEarlyString) <<
"/ Late: " << qPrintable(incomingLateString) <<
"/ Unreasonable: " << qPrintable(incomingUnreasonableString) <<
"/ Duplicate: " << qPrintable(incomingDuplicateString);
"/ Unreasonable: " << qPrintable(incomingUnreasonableString);
serverDetails << "<br/>" <<
" Average Flight Time: " << qPrintable(incomingFlightTimeString) << " msecs";

View file

@ -14,70 +14,23 @@
#include <limits>
SequenceNumberStats::SequenceNumberStats(int statsHistoryLength, bool canDetectOutOfSync)
: _received(0),
_lastReceivedSequence(0),
: _lastReceivedSequence(0),
_missingSet(),
_stats(),
_lastSenderUUID(),
_statsHistory(statsHistoryLength),
_canHaveChild(canDetectOutOfSync),
_childInstance(NULL),
_consecutiveReasonable(0)
_lastUnreasonableSequence(0),
_consecutiveUnreasonableOnTime(0)
{
}
SequenceNumberStats::SequenceNumberStats(const SequenceNumberStats& other)
: _received(other._received),
_lastReceivedSequence(other._lastReceivedSequence),
_missingSet(other._missingSet),
_stats(other._stats),
_lastSenderUUID(other._lastSenderUUID),
_statsHistory(other._statsHistory),
_childInstance(NULL),
_canHaveChild(other._canHaveChild),
_consecutiveReasonable(other._consecutiveReasonable)
{
if (other._childInstance) {
_childInstance = new SequenceNumberStats(*other._childInstance);
}
}
SequenceNumberStats& SequenceNumberStats::operator=(const SequenceNumberStats& rhs) {
_received = rhs._received;
_lastReceivedSequence = rhs._lastReceivedSequence;
_missingSet = rhs._missingSet;
_stats = rhs._stats;
_lastSenderUUID = rhs._lastSenderUUID;
_statsHistory = rhs._statsHistory;
_canHaveChild = rhs._canHaveChild;
_consecutiveReasonable = rhs._consecutiveReasonable;
if (rhs._childInstance) {
_childInstance = new SequenceNumberStats(*rhs._childInstance);
} else {
_childInstance = NULL;
}
return *this;
}
SequenceNumberStats::~SequenceNumberStats() {
if (_childInstance) {
delete _childInstance;
}
}
void SequenceNumberStats::reset() {
_received = 0;
_missingSet.clear();
_stats = PacketStreamStats();
_lastSenderUUID = QUuid();
_statsHistory.clear();
if (_childInstance) {
delete _childInstance;
_childInstance = NULL;
}
_consecutiveReasonable = 0;
_lastUnreasonableSequence = 0;
_consecutiveUnreasonableOnTime = 0;
}
static const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
@ -88,7 +41,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
// if the sender node has changed, reset all stats
if (senderUUID != _lastSenderUUID) {
if (_received > 0) {
if (_stats._received > 0) {
qDebug() << "sequence number stats was reset due to new sender node";
qDebug() << "previous:" << _lastSenderUUID << "current:" << senderUUID;
reset();
@ -97,9 +50,9 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
}
// determine our expected sequence number... handle rollover appropriately
quint16 expected = _received > 0 ? _lastReceivedSequence + (quint16)1 : incoming;
quint16 expected = _stats._received > 0 ? _lastReceivedSequence + (quint16)1 : incoming;
_received++;
_stats._received++;
if (incoming == expected) { // on time
arrivalInfo._status = OnTime;
@ -130,72 +83,11 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
qDebug() << "unreasonable sequence number:" << incoming << "previous:" << _lastReceivedSequence;
_stats._unreasonable++;
_consecutiveReasonable = 0;
// if _canHaveChild, create a child instance of SequenceNumberStats to track this unreasonable seq num and ones in the future.
// if the child instance detects a valid stream of seq nums up to some length, the seq nums sender probably
// fell out of sync with us.
if (_canHaveChild) {
if (!_childInstance) {
_childInstance = new SequenceNumberStats(0, false);
}
ArrivalInfo unreasonableTrackerArrivalInfo = _childInstance->sequenceNumberReceived(incoming);
// the child instance will be used to detect some threshold number seq nums in a row that are perfectly
// in order.
const int UNREASONABLE_TRACKER_RECEIVED_THRESHOLD = 8;
if (unreasonableTrackerArrivalInfo._status != OnTime) {
_childInstance->reset();
} else if (_childInstance->getReceived() >= UNREASONABLE_TRACKER_RECEIVED_THRESHOLD) {
// the child instance has detected a threshold number of consecutive seq nums.
// copy its state to this instance.
_received = _childInstance->_received;
_lastReceivedSequence = _childInstance->_lastReceivedSequence;
_missingSet = _childInstance->_missingSet;
_stats = _childInstance->_stats;
// don't copy _lastSenderUUID; _unreasonableTracker always has null UUID for that member.
// ours should be up-to-date.
// don't copy _statsHistory; _unreasonableTracker keeps a history of length 0.
// simply clear ours.
_statsHistory.clear();
arrivalInfo = unreasonableTrackerArrivalInfo;
// delete child instance;
delete _childInstance;
_childInstance = NULL;
}
}
receivedUnreasonable(incoming);
return arrivalInfo;
}
_consecutiveReasonable++;
// if we got a reasonable seq num but have a child instance tracking unreasonable seq nums,
// reset it. if many consecutive reasonable seq nums have occurred (implying the unreasonable seq num
// that caused the creation of the child instance was probably a fluke), delete our child instance.
if (_childInstance) {
const int CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD = 8;
if (_consecutiveReasonable >= CONSECUTIVE_REASONABLE_CHILD_DELETE_THRESHOLD) {
_childInstance->reset();
} else {
delete _childInstance;
_childInstance = NULL;
}
}
// now that rollover has been corrected for (if it occurred), incomingInt and expectedInt can be
// compared to each other directly, though one of them might be negative
@ -219,7 +111,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
for (int missingInt = expectedInt; missingInt < incomingInt; missingInt++) {
_missingSet.insert((quint16)(missingInt < 0 ? missingInt + UINT16_RANGE : missingInt));
}
// prune missing sequence list if it gets too big; sequence numbers that are older than MAX_REASONABLE_SEQUENCE_GAP
// will be removed.
if (_missingSet.size() > MAX_REASONABLE_SEQUENCE_GAP) {
@ -236,7 +128,7 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
// remove this from missing sequence number if it's in there
if (_missingSet.remove(incoming)) {
arrivalInfo._status = Late;
arrivalInfo._status = Recovered;
if (wantExtraDebugging) {
qDebug() << "found it in _missingSet";
@ -244,19 +136,62 @@ SequenceNumberStats::ArrivalInfo SequenceNumberStats::sequenceNumberReceived(qui
_stats._lost--;
_stats._recovered++;
} else {
arrivalInfo._status = Duplicate;
// this late seq num is not in our missing set. it is possibly a duplicate, or possibly a late
// packet that should have arrived before our first received packet. we'll count these
// as unreasonable.
if (wantExtraDebugging) {
qDebug() << "sequence:" << incoming << "was NOT found in _missingSet and is probably a duplicate";
}
_stats._duplicate++;
arrivalInfo._status = Unreasonable;
qDebug() << "unreasonable sequence number:" << incoming << "(possible duplicate)";
_stats._unreasonable++;
receivedUnreasonable(incoming);
return arrivalInfo;
}
}
}
// if we've made it here, we received a reasonable seq number.
_consecutiveUnreasonableOnTime = 0;
return arrivalInfo;
}
void SequenceNumberStats::receivedUnreasonable(quint16 incoming) {
const int CONSECUTIVE_UNREASONABLE_ON_TIME_THRESHOLD = 8;
quint16 expected = _consecutiveUnreasonableOnTime > 0 ? _lastUnreasonableSequence + (quint16)1 : incoming;
if (incoming == expected) {
_consecutiveUnreasonableOnTime++;
_lastUnreasonableSequence = incoming;
if (_consecutiveUnreasonableOnTime >= CONSECUTIVE_UNREASONABLE_ON_TIME_THRESHOLD) {
// we've received many unreasonable seq numbers in a row, all in order. we're probably out of sync with
// the seq num sender. update our state to get back in sync with the sender.
_lastReceivedSequence = incoming;
_missingSet.clear();
_stats._received = CONSECUTIVE_UNREASONABLE_ON_TIME_THRESHOLD;
_stats._unreasonable = 0;
_stats._early = 0;
_stats._late = 0;
_stats._lost = 0;
_stats._recovered = 0;
_stats._expectedReceived = CONSECUTIVE_UNREASONABLE_ON_TIME_THRESHOLD;
_statsHistory.clear();
_consecutiveUnreasonableOnTime = 0;
qDebug() << "re-synced with sequence number sender";
}
} else {
_consecutiveUnreasonableOnTime = 0;
}
}
void SequenceNumberStats::pruneMissingSet(const bool wantExtraDebugging) {
if (wantExtraDebugging) {
qDebug() << "pruning _missingSet! size:" << _missingSet.size();
@ -311,7 +246,7 @@ PacketStreamStats SequenceNumberStats::getStatsForHistoryWindow() const {
const PacketStreamStats* newestStats = _statsHistory.getNewestEntry();
const PacketStreamStats* oldestStats = _statsHistory.get(_statsHistory.getNumEntries() - 1);
// this catches cases where history is length 1 or 0 (both are NULL in case of 0)
if (newestStats == oldestStats) {
return PacketStreamStats();
@ -319,13 +254,13 @@ PacketStreamStats SequenceNumberStats::getStatsForHistoryWindow() const {
// calculate difference between newest stats and oldest stats to get window stats
PacketStreamStats windowStats;
windowStats._expectedReceived = newestStats->_expectedReceived - oldestStats->_expectedReceived;
windowStats._received = newestStats->_received - oldestStats->_received;
windowStats._unreasonable = newestStats->_unreasonable - oldestStats->_unreasonable;
windowStats._early = newestStats->_early - oldestStats->_early;
windowStats._late = newestStats->_late - oldestStats->_late;
windowStats._lost = newestStats->_lost - oldestStats->_lost;
windowStats._recovered = newestStats->_recovered - oldestStats->_recovered;
windowStats._duplicate = newestStats->_duplicate - oldestStats->_duplicate;
windowStats._expectedReceived = newestStats->_expectedReceived - oldestStats->_expectedReceived;
return windowStats;
}

View file

@ -22,27 +22,24 @@ const int MAX_REASONABLE_SEQUENCE_GAP = 1000;
class PacketStreamStats {
public:
PacketStreamStats()
: _expectedReceived(0),
: _received(0),
_unreasonable(0),
_early(0),
_late(0),
_lost(0),
_recovered(0),
_duplicate(0)
_expectedReceived(0)
{}
float getOutOfOrderRate() const { return (float)(_early + _late) / _expectedReceived; }
float getEaryRate() const { return (float)_early / _expectedReceived; }
float getLateRate() const { return (float)_late / _expectedReceived; }
float getLostRate() const { return (float)_lost / _expectedReceived; }
quint32 _expectedReceived;
quint32 _received;
quint32 _unreasonable;
quint32 _early;
quint32 _late;
quint32 _lost;
quint32 _recovered;
quint32 _duplicate;
quint32 _expectedReceived;
};
class SequenceNumberStats {
@ -51,8 +48,7 @@ public:
OnTime,
Unreasonable,
Early,
Late, // recovered
Duplicate
Recovered,
};
class ArrivalInfo {
@ -63,18 +59,13 @@ public:
SequenceNumberStats(int statsHistoryLength = 0, bool canDetectOutOfSync = true);
SequenceNumberStats(const SequenceNumberStats& other);
SequenceNumberStats& operator=(const SequenceNumberStats& rhs);
~SequenceNumberStats();
void reset();
ArrivalInfo sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false);
void pruneMissingSet(const bool wantExtraDebugging = false);
void pushStatsToHistory() { _statsHistory.insert(_stats); }
quint32 getReceived() const { return _received; }
float getUnreasonableRate() const { return _stats._unreasonable / _received; }
quint32 getReceived() const { return _stats._received; }
quint32 getExpectedReceived() const { return _stats._expectedReceived; }
quint32 getUnreasonable() const { return _stats._unreasonable; }
quint32 getOutOfOrder() const { return _stats._early + _stats._late; }
@ -82,15 +73,15 @@ public:
quint32 getLate() const { return _stats._late; }
quint32 getLost() const { return _stats._lost; }
quint32 getRecovered() const { return _stats._recovered; }
quint32 getDuplicate() const { return _stats._duplicate; }
const PacketStreamStats& getStats() const { return _stats; }
PacketStreamStats getStatsForHistoryWindow() const;
const QSet<quint16>& getMissingSet() const { return _missingSet; }
private:
int _received;
void receivedUnreasonable(quint16 incoming);
private:
quint16 _lastReceivedSequence;
QSet<quint16> _missingSet;
@ -100,13 +91,8 @@ private:
RingBufferHistory<PacketStreamStats> _statsHistory;
// to deal with the incoming seq nums going out of sync with this tracker, we'll create another instance
// of this class when we encounter an unreasonable
bool _canHaveChild;
SequenceNumberStats* _childInstance;
int _consecutiveReasonable;
quint16 _lastUnreasonableSequence;
int _consecutiveUnreasonableOnTime;
};
#endif // hifi_SequenceNumberStats_h

View file

@ -20,7 +20,7 @@ void SequenceNumberStatsTests::runAllTests() {
earlyLateTest();
duplicateTest();
pruneTest();
recursiveTest();
resyncTest();
}
const quint32 UINT16_RANGE = std::numeric_limits<quint16>::max() + 1;
@ -38,7 +38,6 @@ void SequenceNumberStatsTests::rolloverTest() {
stats.sequenceNumberReceived(seq);
seq = seq + (quint16)1;
assert(stats.getDuplicate() == 0);
assert(stats.getEarly() == 0);
assert(stats.getLate() == 0);
assert(stats.getLost() == 0);
@ -69,7 +68,6 @@ void SequenceNumberStatsTests::earlyLateTest() {
seq = seq + (quint16)1;
numSent++;
assert(stats.getDuplicate() == 0);
assert(stats.getEarly() == numEarly);
assert(stats.getLate() == numLate);
assert(stats.getLost() == numLost);
@ -89,7 +87,6 @@ void SequenceNumberStatsTests::earlyLateTest() {
seq = seq + (quint16)1;
numSent++;
assert(stats.getDuplicate() == 0);
assert(stats.getEarly() == numEarly);
assert(stats.getLate() == numLate);
assert(stats.getLost() == numLost);
@ -106,7 +103,6 @@ void SequenceNumberStatsTests::earlyLateTest() {
numLost--;
numRecovered++;
assert(stats.getDuplicate() == 0);
assert(stats.getEarly() == numEarly);
assert(stats.getLate() == numLate);
assert(stats.getLost() == numLost);
@ -135,7 +131,7 @@ void SequenceNumberStatsTests::duplicateTest() {
quint32 numLost = 0;
for (int R = 0; R < 2; R++) {
for (int T = 0; T < 10000; T++) {
for (int T = 0; T < 5; T++) {
quint16 duplicate = seq;
@ -145,7 +141,7 @@ void SequenceNumberStatsTests::duplicateTest() {
seq = seq + (quint16)1;
numSent++;
assert(stats.getDuplicate() == numDuplicate);
assert(stats.getUnreasonable() == numDuplicate);
assert(stats.getEarly() == numEarly);
assert(stats.getLate() == numLate);
assert(stats.getLost() == numLost);
@ -167,7 +163,7 @@ void SequenceNumberStatsTests::duplicateTest() {
seq = seq + (quint16)1;
numSent++;
assert(stats.getDuplicate() == numDuplicate);
assert(stats.getUnreasonable() == numDuplicate);
assert(stats.getEarly() == numEarly);
assert(stats.getLate() == numLate);
assert(stats.getLost() == numLost);
@ -183,7 +179,7 @@ void SequenceNumberStatsTests::duplicateTest() {
numDuplicate++;
numLate++;
assert(stats.getDuplicate() == numDuplicate);
assert(stats.getUnreasonable() == numDuplicate);
assert(stats.getEarly() == numEarly);
assert(stats.getLate() == numLate);
assert(stats.getLost() == numLost);
@ -199,7 +195,7 @@ void SequenceNumberStatsTests::duplicateTest() {
numDuplicate++;
numLate++;
assert(stats.getDuplicate() == numDuplicate);
assert(stats.getUnreasonable() == numDuplicate);
assert(stats.getEarly() == numEarly);
assert(stats.getLate() == numLate);
assert(stats.getLost() == numLost);
@ -279,7 +275,7 @@ void SequenceNumberStatsTests::pruneTest() {
}
}
void SequenceNumberStatsTests::recursiveTest() {
void SequenceNumberStatsTests::resyncTest() {
SequenceNumberStats stats(0);
@ -302,9 +298,20 @@ void SequenceNumberStatsTests::recursiveTest() {
sequence = 0;
for (int R = 0; R < 7; R++) {
stats.sequenceNumberReceived(sequence);
sequence += (quint16)2000;
sequence += (quint16)1;
}
assert(stats.getUnreasonable() == 7);
sequence = 6000;
for (int R = 0; R < 7; R++) {
stats.sequenceNumberReceived(sequence);
sequence += (quint16)1;
}
assert(stats.getUnreasonable() == 14);
sequence = 9000;
for (int i = 0; i < 10; i++) {
stats.sequenceNumberReceived(sequence);
sequence += (quint16)1;

View file

@ -23,7 +23,7 @@ namespace SequenceNumberStatsTests {
void earlyLateTest();
void duplicateTest();
void pruneTest();
void recursiveTest();
void resyncTest();
};
#endif // hifi_SequenceNumberStatsTests_h