3
0
Fork 0
mirror of https://github.com/lubosz/overte.git synced 2025-04-27 09:55:45 +02:00

merge upstream/master into andrew/inertia

Conflicts:
	interface/src/avatar/MyAvatar.cpp
This commit is contained in:
Andrew Meadows 2014-09-12 17:50:26 -07:00
commit f2882618be
137 changed files with 5667 additions and 2118 deletions
assignment-client/src/octree
domain-server/src
examples
interface
libraries

View file

@ -878,10 +878,10 @@ void OctreeServer::setupDatagramProcessingThread() {
// we do not want this event loop to be the handler for UDP datagrams, so disconnect
disconnect(&nodeList->getNodeSocket(), 0, this, 0);
// setup a QThread with us as parent that will house the AudioMixerDatagramProcessor
// setup a QThread with us as parent that will house the OctreeServerDatagramProcessor
_datagramProcessingThread = new QThread(this);
// create an AudioMixerDatagramProcessor and move it to that thread
// create an OctreeServerDatagramProcessor and move it to that thread
OctreeServerDatagramProcessor* datagramProcessor = new OctreeServerDatagramProcessor(nodeList->getNodeSocket(), thread());
datagramProcessor->moveToThread(_datagramProcessingThread);

View file

@ -189,6 +189,12 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
populateDefaultStaticAssignmentsExcludingTypes(parsedTypes);
LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort);
const QString DOMAIN_CONFIG_ID_KEY = "id";
// set our LimitedNodeList UUID to match the UUID from our config
// nodes will currently use this to add resources to data-web that relate to our domain
nodeList->setSessionUUID(_argumentVariantMap.value(DOMAIN_CONFIG_ID_KEY).toString());
connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded);
connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled);

View file

@ -0,0 +1,737 @@
// botProceduralWayPoints.js
// modified by Adrian McCarlie for worklist job hifi: #19977
// 1 September 2014.
// this script enables a user to define unlimited number of way points on the Domain,
// these points form a path that the bot will follow, once the final way point is reached,
// the bot will return to the start and continue until script is stopped.
// pause times for each way point can be set individually.
// User must input the x, y, z co-ords for wayPoints[] and time in milliseconds for pauseTimes[].
//
// original script
// 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);
}
//input co-ords for start position and 7 other positions
var AVATAR_PELVIS_HEIGHT = 0.84;//only change this if you have an odd size avatar
var wayPoints = []; //input locations for all the waypoints
wayPoints[0] = {x:8131.5, y:202.0, z:8261.5}; //input the location of the start position
wayPoints[1] = {x: 8160.5, y: 202.0, z: 8261.5}; //input the location of the first way point
wayPoints[2] = {x: 8160.5, y: 203.0, z: 8270.5};
wayPoints[3] = {x: 8142.5, y: 204.0, z: 8270.5};
wayPoints[4] = {x: 8142.5, y: 204.0, z: 8272.5};
wayPoints[5] = {x: 8160.5, y: 203.0, z: 8272.5};
wayPoints[6] = {x: 8160.5, y: 202.0, z: 8284.5};
wayPoints[7] = {x: 8111.5, y: 202.0, z: 8284.5};// continue to add locations and add or remove lines as needed.
var pauseTimes = []; // the number of pauseTimes must equal the number of wayPoints. Time is in milliseconds
pauseTimes[0] = 5000; //waiting to go to wayPoint0 (startPoint)
pauseTimes[1] = 10000; //waiting to go to wayPoint1
pauseTimes[2] = 3000;
pauseTimes[3] = 3000;
pauseTimes[4] = 8000;
pauseTimes[5] = 6000;
pauseTimes[6] = 3000;
pauseTimes[7] = 3000;// add or delete to match way points.
wayPoints[0].y = wayPoints[0].y + AVATAR_PELVIS_HEIGHT;
wayPoints[1].y = wayPoints[1].y + AVATAR_PELVIS_HEIGHT;
wayPoints[2].y = wayPoints[2].y + AVATAR_PELVIS_HEIGHT;
wayPoints[3].y = wayPoints[3].y + AVATAR_PELVIS_HEIGHT;
wayPoints[4].y = wayPoints[4].y + AVATAR_PELVIS_HEIGHT;
wayPoints[5].y = wayPoints[5].y + AVATAR_PELVIS_HEIGHT;
wayPoints[6].y = wayPoints[6].y + AVATAR_PELVIS_HEIGHT;
wayPoints[7].y = wayPoints[7].y + AVATAR_PELVIS_HEIGHT;
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 AVATAR_PELVIS_HEIGHT = 1.84;
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 = wayPoints[0];
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 wayPoints[0]
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 pauseTimer;
function pause(checkPoint, rotation, delay){
pauseTimer = Script.setTimeout(function() {
targetPosition = checkPoint;
targetOrientation = rotation;
isMoving = true;
}, delay);
}
function handleWalking(deltaTime) {
if (!isMoving){
if(targetPosition.x == 0){targetPosition = wayPoints[1]; isMoving = true;} //Start by heading for wayPoint1
else{
for (var j = 0; j <= wayPoints.length; j++) {
if (targetPosition == wayPoints[j]) {
if(j == wayPoints.length -1){ j= -1;}
var k = j + 1;
var toTarget = Vec3.normalize(Vec3.subtract(wayPoints[k], Avatar.position));
var localVector = Vec3.multiplyQbyV(Avatar.orientation, { x: 0, y: 0, z: -1 });
toTarget.y = 0; // I recommend doing that if you don't want weird rotation to occur that are not around Y.
var axis = Vec3.normalize(Vec3.cross(toTarget, localVector));
var angle = Math.acos(Vec3.dot(toTarget, localVector)) * 180 / Math.PI;
if (Vec3.dot(Vec3.cross(axis, localVector), toTarget) < 0) {
angle = -angle;
}
var delta = 1;
var quat = Quat.angleAxis(angle, axis);
Avatar.orientation = Quat.multiply(quat, Avatar.orientation);
pause(wayPoints[k], Avatar.orientation, pauseTimes[k]);
break;
}
}
}
}
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));
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

@ -1,5 +1,5 @@
//
// butterflyFlockTest1.js
// butterflies.js
//
//
// Created by Adrian McCarlie on August 2, 2014
@ -23,9 +23,6 @@ function vScalarMult(v, s) {
return rval;
}
function printVector(v) {
print(v.x + ", " + v.y + ", " + v.z + "\n");
}
// Create a random vector with individual lengths between a,b
function randVector(a, b) {
var rval = { x: a + Math.random() * (b - a), y: a + Math.random() * (b - a), z: a + Math.random() * (b - a) };
@ -40,7 +37,8 @@ function vInterpolate(a, b, fraction) {
var startTimeInSeconds = new Date().getTime() / 1000;
var lifeTime = 60; // lifetime of the butterflies in seconds!
var NATURAL_SIZE_OF_BUTTERFLY = { x: 9.512, y: 4.427, z: 1.169 };
var lifeTime = 600; // lifetime of the butterflies in seconds
var range = 1.0; // Over what distance in meters do you want the flock to fly around
var frame = 0;
@ -49,7 +47,7 @@ var BUTTERFLY_GRAVITY = 0;//-0.06;
var BUTTERFLY_FLAP_SPEED = 1.0;
var BUTTERFLY_VELOCITY = 0.55;
var DISTANCE_IN_FRONT_OF_ME = 1.5;
var DISTANCE_ABOVE_ME = 1.5;
var DISTANCE_ABOVE_ME = 1.0;
var flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum(
Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_ABOVE_ME),
Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_IN_FRONT_OF_ME)));
@ -81,18 +79,13 @@ function addButterfly() {
var color = { red: 100, green: 100, blue: 100 };
var size = 0;
var which = Math.random();
if (which < 0.2) {
size = 0.08;
} else if (which < 0.4) {
size = 0.09;
} else if (which < 0.6) {
size = 0.8;
} else if (which < 0.8) {
size = 0.8;
} else {
size = 0.8;
}
var minSize = 0.06;
var randomSize = 0.2;
var maxSize = minSize + randomSize;
size = 0.06 + Math.random() * 0.2;
var dimensions = Vec3.multiply(NATURAL_SIZE_OF_BUTTERFLY, (size / maxSize));
flockPosition = Vec3.sum(MyAvatar.position,Vec3.sum(
Vec3.multiply(Quat.getFront(MyAvatar.orientation), DISTANCE_ABOVE_ME),
@ -105,7 +98,7 @@ function addButterfly() {
velocity: { x: 0, y: 0.0, z: 0 },
gravity: { x: 0, y: 1.0, z: 0 },
damping: 0.1,
radius : size,
dimensions: dimensions,
color: color,
rotation: rotation,
animationURL: "http://business.ozblog.me/objects/butterfly/newButterfly2.fbx",
@ -212,12 +205,13 @@ function updateButterflies(deltaTime) {
var desiredVelocity = Vec3.subtract(butterflies[i].targetPosition, properties.position);
desiredVelocity = vScalarMult(Vec3.normalize(desiredVelocity), BUTTERFLY_VELOCITY);
properties.velocity = vInterpolate(properties.velocity, desiredVelocity, 0.2);
properties.velocity = vInterpolate(properties.velocity, desiredVelocity, 0.5);
properties.velocity.y = holding ;
// If we are near the target, we should get a new target
if (Vec3.length(Vec3.subtract(properties.position, butterflies[i].targetPosition)) < (properties.radius / 1.0)) {
var halfLargestDimension = Vec3.length(properties.dimensions) / 2.0;
if (Vec3.length(Vec3.subtract(properties.position, butterflies[i].targetPosition)) < (halfLargestDimension)) {
butterflies[i].moving = false;
}
@ -228,7 +222,7 @@ function updateButterflies(deltaTime) {
}
// Use a cosine wave offset to make it look like its flapping.
var offset = Math.cos(nowTimeInSeconds * BUTTERFLY_FLAP_SPEED) * (properties.radius);
var offset = Math.cos(nowTimeInSeconds * BUTTERFLY_FLAP_SPEED) * (halfLargestDimension);
properties.position.y = properties.position.y + (offset - butterflies[i].previousFlapOffset);
// Change position relative to previous offset.
butterflies[i].previousFlapOffset = offset;
@ -238,4 +232,11 @@ function updateButterflies(deltaTime) {
}
// register the call back so it fires before each data send
Script.update.connect(updateButterflies);
Script.update.connect(updateButterflies);
// Delete our little friends if script is stopped
Script.scriptEnding.connect(function() {
for (var i = 0; i < numButterflies; i++) {
Entities.deleteEntity(butterflies[i].entityID);
}
});

View file

@ -13,4 +13,5 @@ Script.load("editVoxels.js");
Script.load("editModels.js");
Script.load("selectAudioDevice.js");
Script.load("hydraMove.js");
Script.load("headMove.js");
Script.load("inspect.js");

View file

@ -40,7 +40,7 @@ var LEFT = 0;
var RIGHT = 1;
var SPAWN_DISTANCE = 1;
var DEFAULT_RADIUS = 0.10;
var DEFAULT_DIMENSION = 0.20;
var modelURLs = [
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
@ -1147,10 +1147,10 @@ var toolBar = (function () {
}, true, false);
browseModelsButton = toolBar.addTool({
imageURL: toolIconUrl + "list-icon.png",
imageURL: toolIconUrl + "list-icon.svg",
width: toolWidth,
height: toolHeight,
alpha: 0.7,
alpha: 0.9,
visible: true
});
@ -1220,7 +1220,7 @@ var toolBar = (function () {
Entities.addEntity({
type: "Model",
position: position,
radius: DEFAULT_RADIUS,
dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION },
modelURL: url
});
print("Model added: " + url);
@ -1311,8 +1311,9 @@ var toolBar = (function () {
Entities.addEntity({
type: "Box",
position: position,
radius: DEFAULT_RADIUS,
dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION },
color: { red: 255, green: 0, blue: 0 }
});
} else {
print("Can't create box: Box would be out of bounds.");
@ -1327,7 +1328,7 @@ var toolBar = (function () {
Entities.addEntity({
type: "Sphere",
position: position,
radius: DEFAULT_RADIUS,
dimensions: { x: DEFAULT_DIMENSION, y: DEFAULT_DIMENSION, z: DEFAULT_DIMENSION },
color: { red: 255, green: 0, blue: 0 }
});
} else {
@ -1804,7 +1805,7 @@ function controller(wichSide) {
this.modelURL = "";
this.oldModelRotation;
this.oldModelPosition;
this.oldModelRadius;
this.oldModelHalfDiagonal;
this.positionAtGrab;
this.rotationAtGrab;
@ -1864,7 +1865,7 @@ function controller(wichSide) {
this.oldModelPosition = properties.position;
this.oldModelRotation = properties.rotation;
this.oldModelRadius = properties.radius;
this.oldModelHalfDiagonal = Vec3.length(properties.dimensions) / 2.0;
this.positionAtGrab = this.palmPosition;
this.rotationAtGrab = this.rotation;
@ -1873,7 +1874,7 @@ function controller(wichSide) {
this.jointsIntersectingFromStart = [];
for (var i = 0; i < jointList.length; i++) {
var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition);
if (distance < this.oldModelRadius) {
if (distance < this.oldModelHalfDiagonal) {
this.jointsIntersectingFromStart.push(i);
}
}
@ -1897,10 +1898,10 @@ function controller(wichSide) {
if (closestJointIndex != -1) {
print("closestJoint: " + jointList[closestJointIndex]);
print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelRadius + ")");
print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelHalfDiagonal + ")");
}
if (closestJointDistance < this.oldModelRadius) {
if (closestJointDistance < this.oldModelHalfDiagonal) {
if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1 ||
(leftController.grabbing && rightController.grabbing &&
@ -1916,7 +1917,7 @@ function controller(wichSide) {
var attachmentRotation = Quat.multiply(Quat.inverse(jointRotation), this.oldModelRotation);
MyAvatar.attach(this.modelURL, jointList[closestJointIndex],
attachmentOffset, attachmentRotation, 2.0 * this.oldModelRadius,
attachmentOffset, attachmentRotation, 2.0 * this.oldModelHalfDiagonal,
true, false);
Entities.deleteEntity(this.entityID);
}
@ -1970,11 +1971,12 @@ function controller(wichSide) {
var z = Vec3.dot(Vec3.subtract(P, A), this.right);
var X = Vec3.sum(A, Vec3.multiply(B, x));
var d = Vec3.length(Vec3.subtract(P, X));
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
if (angularSize > MAX_ANGULAR_SIZE) {
print("Angular size too big: " + 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14);
print("Angular size too big: " + angularSize);
return { valid: false };
}
@ -2021,7 +2023,10 @@ function controller(wichSide) {
origin: this.palmPosition,
direction: this.front
});
var angularSize = 2 * Math.atan(intersection.properties.radius / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14;
var halfDiagonal = Vec3.length(intersection.properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), intersection.properties.position)) * 180 / 3.14;
if (intersection.accurate && intersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
this.glowedIntersectingModel = intersection.entityID;
Entities.editEntity(this.glowedIntersectingModel, { glowLevel: 0.25 });
@ -2099,7 +2104,7 @@ function controller(wichSide) {
var indicesToRemove = [];
for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) {
var distance = Vec3.distance(MyAvatar.getJointPosition(this.jointsIntersectingFromStart[i]), this.oldModelPosition);
if (distance >= this.oldModelRadius) {
if (distance >= this.oldModelHalfDiagonal) {
indicesToRemove.push(this.jointsIntersectingFromStart[i]);
}
@ -2192,7 +2197,12 @@ function controller(wichSide) {
Vec3.multiplyQbyV(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), attachments[attachmentIndex].translation)),
rotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName),
attachments[attachmentIndex].rotation),
radius: attachments[attachmentIndex].scale / 2.0,
// TODO: how do we know the correct dimensions for detachment???
dimensions: { x: attachments[attachmentIndex].scale / 2.0,
y: attachments[attachmentIndex].scale / 2.0,
z: attachments[attachmentIndex].scale / 2.0 },
modelURL: attachments[attachmentIndex].modelURL
};
@ -2310,15 +2320,21 @@ function moveEntities() {
Entities.editEntity(leftController.entityID, {
position: newPosition,
rotation: rotation,
radius: leftController.oldModelRadius * ratio
// TODO: how do we know the correct dimensions for detachment???
//radius: leftController.oldModelHalfDiagonal * ratio
dimensions: { x: leftController.oldModelHalfDiagonal * ratio,
y: leftController.oldModelHalfDiagonal * ratio,
z: leftController.oldModelHalfDiagonal * ratio }
});
leftController.oldModelPosition = newPosition;
leftController.oldModelRotation = rotation;
leftController.oldModelRadius *= ratio;
leftController.oldModelHalfDiagonal *= ratio;
rightController.oldModelPosition = newPosition;
rightController.oldModelRotation = rotation;
rightController.oldModelRadius *= ratio;
rightController.oldModelHalfDiagonal *= ratio;
return;
}
leftController.moveEntity();
@ -2379,7 +2395,7 @@ function Tooltip() {
this.x = 285;
this.y = 115;
this.width = 500;
this.height = 145;
this.height = 180; // 145;
this.margin = 5;
this.decimals = 3;
@ -2407,7 +2423,14 @@ function Tooltip() {
text += "Pitch: " + angles.x.toFixed(this.decimals) + "\n"
text += "Yaw: " + angles.y.toFixed(this.decimals) + "\n"
text += "Roll: " + angles.z.toFixed(this.decimals) + "\n"
text += "Scale: " + 2 * properties.radius.toFixed(this.decimals) + "\n"
text += "Dimensions: " + properties.dimensions.x.toFixed(this.decimals) + ", "
+ properties.dimensions.y.toFixed(this.decimals) + ", "
+ properties.dimensions.z.toFixed(this.decimals) + "\n";
text += "Natural Dimensions: " + properties.naturalDimensions.x.toFixed(this.decimals) + ", "
+ properties.naturalDimensions.y.toFixed(this.decimals) + ", "
+ properties.naturalDimensions.z.toFixed(this.decimals) + "\n";
text += "ID: " + properties.id + "\n"
if (properties.type == "Model") {
text += "Model URL: " + properties.modelURL + "\n"
@ -2426,6 +2449,7 @@ function Tooltip() {
text += "Lifetime: " + properties.lifetime + "\n"
}
text += "Age: " + properties.ageAsText + "\n"
text += "Script: " + properties.script + "\n"
Overlays.editOverlay(this.textOverlay, { text: text });
@ -2477,7 +2501,9 @@ function mousePressEvent(event) {
if (isLocked(properties)) {
print("Model locked " + properties.id);
} else {
print("Checking properties: " + properties.id + " " + properties.isKnownID);
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
print("Checking properties: " + properties.id + " " + properties.isKnownID + " - Half Diagonal:" + halfDiagonal);
// P P - Model
// /| A - Palm
// / | d B - unit vector toward tip
@ -2496,8 +2522,9 @@ function mousePressEvent(event) {
var x = Vec3.dot(Vec3.subtract(P, A), B);
var X = Vec3.sum(A, Vec3.multiply(B, x));
var d = Vec3.length(Vec3.subtract(P, X));
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14;
if (0 < x && angularSize > MIN_ANGULAR_SIZE) {
if (angularSize < MAX_ANGULAR_SIZE) {
entitySelected = true;
@ -2506,13 +2533,13 @@ function mousePressEvent(event) {
orientation = MyAvatar.orientation;
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
} else {
print("Angular size too big: " + 2 * Math.atan(properties.radius / Vec3.distance(Camera.getPosition(), properties.position)) * 180 / 3.14);
print("Angular size too big: " + angularSize);
}
}
}
}
if (entitySelected) {
selectedEntityProperties.oldRadius = selectedEntityProperties.radius;
selectedEntityProperties.oldDimensions = selectedEntityProperties.dimensions;
selectedEntityProperties.oldPosition = {
x: selectedEntityProperties.position.x,
y: selectedEntityProperties.position.y,
@ -2550,8 +2577,12 @@ function mouseMoveEvent(event) {
glowedEntityID.id = -1;
glowedEntityID.isKnownID = false;
}
var halfDiagonal = Vec3.length(entityIntersection.properties.dimensions) / 2.0;
var angularSize = 2 * Math.atan(entityIntersection.properties.radius / Vec3.distance(Camera.getPosition(), entityIntersection.properties.position)) * 180 / 3.14;
var angularSize = 2 * Math.atan(halfDiagonal / Vec3.distance(Camera.getPosition(),
entityIntersection.properties.position)) * 180 / 3.14;
if (entityIntersection.entityID.isKnownID && angularSize > MIN_ANGULAR_SIZE && angularSize < MAX_ANGULAR_SIZE) {
Entities.editEntity(entityIntersection.entityID, { glowLevel: 0.25 });
glowedEntityID = entityIntersection.entityID;
@ -2573,7 +2604,7 @@ function mouseMoveEvent(event) {
}
pickRay = Camera.computePickRay(event.x, event.y);
if (wasShifted != event.isShifted || modifier != oldModifier) {
selectedEntityProperties.oldRadius = selectedEntityProperties.radius;
selectedEntityProperties.oldDimensions = selectedEntityProperties.dimensions;
selectedEntityProperties.oldPosition = {
x: selectedEntityProperties.position.x,
@ -2603,9 +2634,12 @@ function mouseMoveEvent(event) {
return;
case 1:
// Let's Scale
selectedEntityProperties.radius = (selectedEntityProperties.oldRadius *
selectedEntityProperties.dimensions = Vec3.multiply(selectedEntityProperties.dimensions,
(1.0 + (mouseLastPosition.y - event.y) / SCALE_FACTOR));
if (selectedEntityProperties.radius < 0.01) {
var halfDiagonal = Vec3.length(selectedEntityProperties.dimensions) / 2.0;
if (halfDiagonal < 0.01) {
print("Scale too small ... bailling.");
return;
}
@ -2753,6 +2787,13 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent);
setupModelMenus();
var propertiesForEditedEntity;
var editEntityFormArray;
var editModelID = -1;
var dimensionX;
var dimensionY;
var dimensionZ;
function handeMenuEvent(menuItem) {
print("menuItemEvent() in JS... menuItem=" + menuItem);
if (menuItem == "Delete") {
@ -2781,7 +2822,7 @@ function handeMenuEvent(menuItem) {
print(" Delete Entity.... not holding...");
}
} else if (menuItem == "Edit Properties...") {
var editModelID = -1;
editModelID = -1;
if (leftController.grabbing) {
print(" Edit Properties.... leftController.entityID="+ leftController.entityID);
editModelID = leftController.entityID;
@ -2797,79 +2838,111 @@ function handeMenuEvent(menuItem) {
if (editModelID != -1) {
print(" Edit Properties.... about to edit properties...");
var properties = Entities.getEntityProperties(editModelID);
propertiesForEditedEntity = Entities.getEntityProperties(editModelID);
var properties = propertiesForEditedEntity;
var array = new Array();
var index = 0;
var decimals = 3;
if (properties.type == "Model") {
array.push({ label: "Model URL:", value: properties.modelURL });
index++;
array.push({ label: "Animation URL:", value: properties.animationURL });
index++;
array.push({ label: "Animation is playing:", value: properties.animationIsPlaying });
index++;
array.push({ label: "Animation FPS:", value: properties.animationFPS });
index++;
array.push({ label: "Animation Frame:", value: properties.animationFrameIndex });
index++;
}
array.push({ label: "Position:", type: "header" });
index++;
array.push({ label: "X:", value: properties.position.x.toFixed(decimals) });
index++;
array.push({ label: "Y:", value: properties.position.y.toFixed(decimals) });
index++;
array.push({ label: "Z:", value: properties.position.z.toFixed(decimals) });
index++;
array.push({ label: "Registration X:", value: properties.registrationPoint.x.toFixed(decimals) });
index++;
array.push({ label: "Registration Y:", value: properties.registrationPoint.y.toFixed(decimals) });
index++;
array.push({ label: "Registration Z:", value: properties.registrationPoint.z.toFixed(decimals) });
index++;
array.push({ label: "Rotation:", type: "header" });
index++;
var angles = Quat.safeEulerAngles(properties.rotation);
array.push({ label: "Pitch:", value: angles.x.toFixed(decimals) });
index++;
array.push({ label: "Yaw:", value: angles.y.toFixed(decimals) });
index++;
array.push({ label: "Roll:", value: angles.z.toFixed(decimals) });
array.push({ label: "Scale:", value: 2 * properties.radius.toFixed(decimals) });
index++;
array.push({ label: "Velocity X:", value: properties.velocity.x.toFixed(decimals) });
array.push({ label: "Velocity Y:", value: properties.velocity.y.toFixed(decimals) });
array.push({ label: "Velocity Z:", value: properties.velocity.z.toFixed(decimals) });
array.push({ label: "Damping:", value: properties.damping.toFixed(decimals) });
array.push({ label: "Dimensions:", type: "header" });
index++;
array.push({ label: "Width:", value: properties.dimensions.x.toFixed(decimals) });
dimensionX = index;
index++;
array.push({ label: "Height:", value: properties.dimensions.y.toFixed(decimals) });
dimensionY = index;
index++;
array.push({ label: "Depth:", value: properties.dimensions.z.toFixed(decimals) });
dimensionZ = index;
index++;
array.push({ label: "", type: "inlineButton", buttonLabel: "Reset to Natural Dimensions", name: "resetDimensions" });
index++;
array.push({ label: "Velocity:", type: "header" });
index++;
array.push({ label: "Linear X:", value: properties.velocity.x.toFixed(decimals) });
index++;
array.push({ label: "Linear Y:", value: properties.velocity.y.toFixed(decimals) });
index++;
array.push({ label: "Linear Z:", value: properties.velocity.z.toFixed(decimals) });
index++;
array.push({ label: "Linear Damping:", value: properties.damping.toFixed(decimals) });
index++;
array.push({ label: "Angular Pitch:", value: properties.angularVelocity.x.toFixed(decimals) });
index++;
array.push({ label: "Angular Yaw:", value: properties.angularVelocity.y.toFixed(decimals) });
index++;
array.push({ label: "Angular Roll:", value: properties.angularVelocity.z.toFixed(decimals) });
index++;
array.push({ label: "Angular Damping:", value: properties.angularDamping.toFixed(decimals) });
index++;
array.push({ label: "Gravity X:", value: properties.gravity.x.toFixed(decimals) });
index++;
array.push({ label: "Gravity Y:", value: properties.gravity.y.toFixed(decimals) });
index++;
array.push({ label: "Gravity Z:", value: properties.gravity.z.toFixed(decimals) });
index++;
array.push({ label: "Lifetime:", value: properties.lifetime.toFixed(decimals) });
index++;
array.push({ label: "Visible:", value: properties.visible });
index++;
if (properties.type == "Box" || properties.type == "Sphere") {
array.push({ label: "Color:", type: "header" });
index++;
array.push({ label: "Red:", value: properties.color.red });
index++;
array.push({ label: "Green:", value: properties.color.green });
index++;
array.push({ label: "Blue:", value: properties.color.blue });
index++;
}
array.push({ button: "Cancel" });
if (Window.form("Edit Properties", array)) {
var index = 0;
if (properties.type == "Model") {
properties.modelURL = array[index++].value;
properties.animationURL = array[index++].value;
properties.animationIsPlaying = array[index++].value;
properties.animationFPS = array[index++].value;
properties.animationFrameIndex = array[index++].value;
}
properties.position.x = array[index++].value;
properties.position.y = array[index++].value;
properties.position.z = array[index++].value;
angles.x = array[index++].value;
angles.y = array[index++].value;
angles.z = array[index++].value;
properties.rotation = Quat.fromVec3Degrees(angles);
properties.radius = array[index++].value / 2;
index++;
properties.velocity.x = array[index++].value;
properties.velocity.y = array[index++].value;
properties.velocity.z = array[index++].value;
properties.damping = array[index++].value;
properties.gravity.x = array[index++].value;
properties.gravity.y = array[index++].value;
properties.gravity.z = array[index++].value;
properties.lifetime = array[index++].value; // give ourselves that many more seconds
if (properties.type == "Box" || properties.type == "Sphere") {
properties.color.red = array[index++].value;
properties.color.green = array[index++].value;
properties.color.blue = array[index++].value;
}
Entities.editEntity(editModelID, properties);
}
modelSelected = false;
editEntityFormArray = array;
Window.nonBlockingForm("Edit Properties", array);
}
} else if (menuItem == "Paste Models") {
modelImporter.paste();
@ -2930,3 +3003,76 @@ Controller.keyReleaseEvent.connect(function (event) {
handeMenuEvent("Delete");
}
});
Window.inlineButtonClicked.connect(function (name) {
if (name == "resetDimensions") {
var decimals = 3;
Window.reloadNonBlockingForm([
{ value: propertiesForEditedEntity.naturalDimensions.x.toFixed(decimals), oldIndex: dimensionX },
{ value: propertiesForEditedEntity.naturalDimensions.y.toFixed(decimals), oldIndex: dimensionY },
{ value: propertiesForEditedEntity.naturalDimensions.z.toFixed(decimals), oldIndex: dimensionZ }
]);
}
});
Window.nonBlockingFormClosed.connect(function() {
array = editEntityFormArray;
if (Window.getNonBlockingFormResult(array)) {
var properties = propertiesForEditedEntity;
var index = 0;
if (properties.type == "Model") {
properties.modelURL = array[index++].value;
properties.animationURL = array[index++].value;
properties.animationIsPlaying = array[index++].value;
properties.animationFPS = array[index++].value;
properties.animationFrameIndex = array[index++].value;
}
index++; // skip header
properties.position.x = array[index++].value;
properties.position.y = array[index++].value;
properties.position.z = array[index++].value;
properties.registrationPoint.x = array[index++].value;
properties.registrationPoint.y = array[index++].value;
properties.registrationPoint.z = array[index++].value;
index++; // skip header
var angles = Quat.safeEulerAngles(properties.rotation);
angles.x = array[index++].value;
angles.y = array[index++].value;
angles.z = array[index++].value;
properties.rotation = Quat.fromVec3Degrees(angles);
index++; // skip header
properties.dimensions.x = array[index++].value;
properties.dimensions.y = array[index++].value;
properties.dimensions.z = array[index++].value;
index++; // skip reset button
index++; // skip header
properties.velocity.x = array[index++].value;
properties.velocity.y = array[index++].value;
properties.velocity.z = array[index++].value;
properties.damping = array[index++].value;
properties.angularVelocity.x = array[index++].value;
properties.angularVelocity.y = array[index++].value;
properties.angularVelocity.z = array[index++].value;
properties.angularDamping = array[index++].value;
properties.gravity.x = array[index++].value;
properties.gravity.y = array[index++].value;
properties.gravity.z = array[index++].value;
properties.lifetime = array[index++].value;
properties.visible = array[index++].value;
if (properties.type == "Box" || properties.type == "Sphere") {
index++; // skip header
properties.color.red = array[index++].value;
properties.color.green = array[index++].value;
properties.color.blue = array[index++].value;
}
Entities.editEntity(editModelID, properties);
}
modelSelected = false;
});

View file

@ -0,0 +1,57 @@
//
// globalServicesExample.js
// examples
//
// Created by Thijs Wenker on 9/12/14.
// Copyright 2014 High Fidelity, Inc.
//
// Example usage of the GlobalServices object. You could use it to make your own chatbox.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
function onConnected() {
if (GlobalServices.onlineUsers.length > 0) {
sendMessageForm()
return;
}
Script.setTimeout(function() { sendMessageForm(); }, 5000);
}
function onDisconnected(reason) {
switch(reason) {
case "logout":
Window.alert("logged out!");
break;
}
}
function onOnlineUsersChanged(users) {
print(users);
}
function onIncommingMessage(user, message) {
print(user + ": " + message);
if (message === "hello") {
GlobalServices.chat("hello, @" + user + "!");
}
}
function sendMessageForm() {
var form =
[
{ label: "To:", options: ["(noone)"].concat(GlobalServices.onlineUsers) },
{ label: "Message:", value: "Enter message here" }
];
if (Window.form("Send message on public chat", form)) {
GlobalServices.chat(form[0].value == "(noone)" ? form[1].value : "@" + form[0].value + ", " + form[1].value);
}
}
GlobalServices.connected.connect(onConnected);
GlobalServices.disconnected.connect(onDisconnected);
GlobalServices.onlineUsersChanged.connect(onOnlineUsersChanged);
GlobalServices.incomingMessage.connect(onIncommingMessage);

View file

@ -0,0 +1,216 @@
//
// gracefulControls.js
// examples
//
// Created by Ryan Huffman on 9/11/14
// Copyright 2014 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
//
var DEFAULT_PARAMETERS = {
// Coefficient to use for linear drag. Higher numbers will cause motion to
// slow down more quickly.
DRAG_COEFFICIENT: 0.9,
MAX_SPEED: 40.0,
ACCELERATION: 1.0,
MOUSE_YAW_SCALE: -0.125,
MOUSE_PITCH_SCALE: -0.125,
MOUSE_SENSITIVITY: 0.5,
// Damping frequency, adjust to change mouse look behavior
W: 4.2,
}
var BRAKE_PARAMETERS = {
DRAG_COEFFICIENT: 4.9,
MAX_SPEED: DEFAULT_PARAMETERS.MAX_SPEED,
ACCELERATION: 0,
W: 1.0,
MOUSE_YAW_SCALE: -0.125,
MOUSE_PITCH_SCALE: -0.125,
MOUSE_SENSITIVITY: 0.5,
}
var movementParameters = DEFAULT_PARAMETERS;
// Movement keys
var KEY_BRAKE = "q";
var KEY_FORWARD = "w";
var KEY_BACKWARD = "s";
var KEY_LEFT = "a";
var KEY_RIGHT = "d";
var KEY_UP = "e";
var KEY_DOWN = "c";
var KEY_ENABLE = "SPACE";
var CAPTURED_KEYS = [KEY_BRAKE, KEY_FORWARD, KEY_BACKWARD, KEY_LEFT, KEY_RIGHT, KEY_UP, KEY_DOWN, KEY_ENABLE];
// Global Variables
var keys = {};
var velocity = { x: 0, y: 0, z: 0 };
var velocityVertical = 0;
var enabled = false;
var lastX = Window.getCursorPositionX();
var lastY = Window.getCursorPositionY();
var yawFromMouse = 0;
var pitchFromMouse = 0;
var yawSpeed = 0;
var pitchSpeed = 0;
function keyPressEvent(event) {
if (event.text == "ESC") {
disable();
} else if (event.text == KEY_ENABLE) {
if (Window.hasFocus()) {
enable();
}
} else if (event.text == KEY_BRAKE) {
movementParameters = BRAKE_PARAMETERS;
}
keys[event.text] = true;
}
function keyReleaseEvent(event) {
if (event.text == KEY_BRAKE) {
movementParameters = DEFAULT_PARAMETERS;
}
delete keys[event.text];
}
function update(dt) {
var maxMove = 3.0 * dt;
var targetVelocity = { x: 0, y: 0, z: 0 };
var targetVelocityVertical = 0;
var acceleration = movementParameters.ACCELERATION;
if (keys[KEY_FORWARD]) {
targetVelocity.z -= acceleration * dt;
}
if (keys[KEY_LEFT]) {
targetVelocity.x -= acceleration * dt;
}
if (keys[KEY_BACKWARD]) {
targetVelocity.z += acceleration * dt;
}
if (keys[KEY_RIGHT]) {
targetVelocity.x += acceleration * dt;
}
if (keys[KEY_UP]) {
targetVelocityVertical += acceleration * dt;
}
if (keys[KEY_DOWN]) {
targetVelocityVertical -= acceleration * dt;
}
if (enabled && Window.hasFocus()) {
var x = Window.getCursorPositionX();
var y = Window.getCursorPositionY();
yawFromMouse += ((x - lastX) * movementParameters.MOUSE_YAW_SCALE * movementParameters.MOUSE_SENSITIVITY);
pitchFromMouse += ((y - lastY) * movementParameters.MOUSE_PITCH_SCALE * movementParameters.MOUSE_SENSITIVITY);
pitchFromMouse = Math.max(-180, Math.min(180, pitchFromMouse));
resetCursorPosition();
}
// Here we use a linear damping model - http://en.wikipedia.org/wiki/Damping#Linear_damping
// Because we are using a critically damped model (no oscillation), ζ = 1 and
// so we derive the formula: acceleration = -(2 * w0 * v) - (w0^2 * x)
var W = movementParameters.W;
yawAccel = (W * W * yawFromMouse) - (2 * W * yawSpeed);
pitchAccel = (W * W * pitchFromMouse) - (2 * W * pitchSpeed);
yawSpeed += yawAccel * dt;
var yawMove = yawSpeed * dt;
var newOrientation = Quat.multiply(MyAvatar.orientation, Quat.fromVec3Degrees( { x: 0, y: yawMove, z: 0 } ));
MyAvatar.orientation = newOrientation;
yawFromMouse -= yawMove;
pitchSpeed += pitchAccel * dt;
var pitchMove = pitchSpeed * dt;
var newPitch = MyAvatar.headPitch + pitchMove;
MyAvatar.headPitch = newPitch;
pitchFromMouse -= pitchMove;
// If force isn't being applied in a direction, add drag;
if (targetVelocity.x == 0) {
targetVelocity.x -= (velocity.x * movementParameters.DRAG_COEFFICIENT * dt);
}
if (targetVelocity.z == 0) {
targetVelocity.z -= (velocity.z * movementParameters.DRAG_COEFFICIENT * dt);
}
velocity = Vec3.sum(velocity, targetVelocity);
var maxSpeed = movementParameters.MAX_SPEED;
velocity.x = Math.max(-maxSpeed, Math.min(maxSpeed, velocity.x));
velocity.z = Math.max(-maxSpeed, Math.min(maxSpeed, velocity.z));
var v = Vec3.multiplyQbyV(MyAvatar.headOrientation, velocity);
if (targetVelocityVertical == 0) {
targetVelocityVertical -= (velocityVertical * movementParameters.DRAG_COEFFICIENT * dt);
}
velocityVertical += targetVelocityVertical;
velocityVertical = Math.max(-maxSpeed, Math.min(maxSpeed, velocityVertical));
v.y += velocityVertical;
MyAvatar.setVelocity(v);
}
function vecToString(vec) {
return vec.x + ", " + vec.y + ", " + vec.z;
}
function scriptEnding() {
disable();
}
function resetCursorPosition() {
var newX = Window.x + Window.innerWidth / 2;
var newY = Window.y + Window.innerHeight / 2;
Window.setCursorPosition(newX, newY);
lastX = newX;
lastY = newY;
}
function enable() {
if (!enabled) {
enabled = true;
resetCursorPosition();
// Reset movement variables
yawFromMouse = 0;
pitchFromMouse = 0;
yawSpeed = 0;
pitchSpeed = 0;
velocityVertical = 0;
for (var i = 0; i < CAPTURED_KEYS.length; i++) {
Controller.captureKeyEvents({ text: CAPTURED_KEYS[i] });
}
Window.setCursorVisible(false);
Script.update.connect(update);
}
}
function disable() {
if (enabled) {
enabled = false;
for (var i = 0; i < CAPTURED_KEYS.length; i++) {
Controller.releaseKeyEvents({ text: CAPTURED_KEYS[i] });
}
Window.setCursorVisible(true);
Script.update.disconnect(update);
}
}
Controller.keyPressEvent.connect(keyPressEvent);
Controller.keyReleaseEvent.connect(keyReleaseEvent);
Script.scriptEnding.connect(scriptEnding);

139
examples/headMove.js Normal file
View file

@ -0,0 +1,139 @@
//
// headMove.js
// examples
//
// Created by Philip Rosedale on September 8, 2014
// Copyright 2014 High Fidelity, Inc.
//
// Press the spacebar and move/turn your head to move around.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
var debug = false;
var movingWithHead = false;
var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw;
var HEAD_MOVE_DEAD_ZONE = 0.0;
var HEAD_STRAFE_DEAD_ZONE = 0.0;
var HEAD_ROTATE_DEAD_ZONE = 0.0;
var HEAD_THRUST_FWD_SCALE = 12000.0;
var HEAD_THRUST_STRAFE_SCALE = 2000.0;
var HEAD_YAW_RATE = 1.0;
var HEAD_PITCH_RATE = 1.0;
var HEAD_ROLL_THRUST_SCALE = 75.0;
var HEAD_PITCH_LIFT_THRUST = 3.0;
var WALL_BOUNCE = 4000.0;
// If these values are set to something
var maxVelocity = 1.25;
var noFly = true;
//var roomLimits = { xMin: 618, xMax: 635.5, zMin: 528, zMax: 552.5 };
var roomLimits = { xMin: -1, xMax: 0, zMin: 0, zMax: 0 };
function isInRoom(position) {
var BUFFER = 2.0;
if (roomLimits.xMin < 0) {
return false;
}
if ((position.x > (roomLimits.xMin - BUFFER)) &&
(position.x < (roomLimits.xMax + BUFFER)) &&
(position.z > (roomLimits.zMin - BUFFER)) &&
(position.z < (roomLimits.zMax + BUFFER)))
{
return true;
} else {
return false;
}
}
function moveWithHead(deltaTime) {
var thrust = { x: 0, y: 0, z: 0 };
var position = MyAvatar.position;
if (movingWithHead) {
var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw;
var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch;
var deltaRoll = MyAvatar.getHeadFinalRoll() - headStartRoll;
var velocity = MyAvatar.getVelocity();
var bodyLocalCurrentHeadVector = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position);
bodyLocalCurrentHeadVector = Vec3.multiplyQbyV(Quat.angleAxis(-deltaYaw, {x:0, y: 1, z:0}), bodyLocalCurrentHeadVector);
var headDelta = Vec3.subtract(bodyLocalCurrentHeadVector, headStartPosition);
headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta);
headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion
var forward = Quat.getFront(Camera.getOrientation());
var right = Quat.getRight(Camera.getOrientation());
var up = Quat.getUp(Camera.getOrientation());
if (noFly) {
forward.y = 0.0;
forward = Vec3.normalize(forward);
right.y = 0.0;
right = Vec3.normalize(right);
up = { x: 0, y: 1, z: 0};
}
// Thrust based on leaning forward and side-to-side
if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) {
if (Math.abs(Vec3.dot(velocity, forward)) < maxVelocity) {
thrust = Vec3.sum(thrust, Vec3.multiply(forward, -headDelta.z * HEAD_THRUST_FWD_SCALE * deltaTime));
}
}
if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) {
if (Math.abs(Vec3.dot(velocity, right)) < maxVelocity) {
thrust = Vec3.sum(thrust, Vec3.multiply(right, headDelta.x * HEAD_THRUST_STRAFE_SCALE * deltaTime));
}
}
if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) {
var orientation = Quat.multiply(Quat.angleAxis((deltaYaw + deltaRoll) * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation);
MyAvatar.orientation = orientation;
}
// Thrust Up/Down based on head pitch
if (!noFly) {
if ((Math.abs(Vec3.dot(velocity, up)) < maxVelocity)) {
thrust = Vec3.sum(thrust, Vec3.multiply({ x:0, y:1, z:0 }, (MyAvatar.getHeadFinalPitch() - headStartFinalPitch) * HEAD_PITCH_LIFT_THRUST * deltaTime));
}
}
// For head trackers, adjust pitch by head pitch
MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime;
}
if (isInRoom(position)) {
// Impose constraints to keep you in the space
if (position.x < roomLimits.xMin) {
thrust.x += (roomLimits.xMin - position.x) * WALL_BOUNCE * deltaTime;
} else if (position.x > roomLimits.xMax) {
thrust.x += (roomLimits.xMax - position.x) * WALL_BOUNCE * deltaTime;
}
if (position.z < roomLimits.zMin) {
thrust.z += (roomLimits.zMin - position.z) * WALL_BOUNCE * deltaTime;
} else if (position.z > roomLimits.zMax) {
thrust.z += (roomLimits.zMax - position.z) * WALL_BOUNCE * deltaTime;
}
}
// Check against movement box limits
MyAvatar.addThrust(thrust);
}
Controller.keyPressEvent.connect(function(event) {
if (event.text == "SPACE" && !movingWithHead) {
movingWithHead = true;
headStartPosition = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position);
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
headStartRoll = MyAvatar.getHeadFinalRoll();
headStartYaw = MyAvatar.getHeadFinalYaw();
}
});
Controller.keyReleaseEvent.connect(function(event) {
if (event.text == "SPACE") {
movingWithHead = false;
}
});
Script.update.connect(moveWithHead);

View file

@ -2,7 +2,9 @@
// hydraMove.js
// examples
//
// Created by Brad Hefta-Gaub on 2/10/14.
// Created by Brad Hefta-Gaub on February 10, 2014
// Updated by Philip Rosedale on September 8, 2014
//
// Copyright 2014 High Fidelity, Inc.
//
// This is an example script that demonstrates use of the Controller and MyAvatar classes to implement
@ -34,8 +36,7 @@ var grabbingWithRightHand = false;
var wasGrabbingWithRightHand = false;
var grabbingWithLeftHand = false;
var wasGrabbingWithLeftHand = false;
var movingWithHead = false;
var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw;
var EPSILON = 0.000001;
var velocity = { x: 0, y: 0, z: 0};
var THRUST_MAG_UP = 100.0;
@ -243,47 +244,6 @@ function handleGrabBehavior(deltaTime) {
wasGrabbingWithLeftHand = grabbingWithLeftHand;
}
var HEAD_MOVE_DEAD_ZONE = 0.0;
var HEAD_STRAFE_DEAD_ZONE = 0.0;
var HEAD_ROTATE_DEAD_ZONE = 0.0;
var HEAD_THRUST_FWD_SCALE = 12000.0;
var HEAD_THRUST_STRAFE_SCALE = 1000.0;
var HEAD_YAW_RATE = 2.0;
var HEAD_PITCH_RATE = 1.0;
var HEAD_ROLL_THRUST_SCALE = 75.0;
var HEAD_PITCH_LIFT_THRUST = 3.0;
function moveWithHead(deltaTime) {
if (movingWithHead) {
var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw;
var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch;
var bodyLocalCurrentHeadVector = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position);
bodyLocalCurrentHeadVector = Vec3.multiplyQbyV(Quat.angleAxis(-deltaYaw, {x:0, y: 1, z:0}), bodyLocalCurrentHeadVector);
var headDelta = Vec3.subtract(bodyLocalCurrentHeadVector, headStartPosition);
headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta);
headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion
// Thrust based on leaning forward and side-to-side
if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) {
MyAvatar.addThrust(Vec3.multiply(Quat.getFront(Camera.getOrientation()), -headDelta.z * HEAD_THRUST_FWD_SCALE * deltaTime));
}
if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) {
MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), headDelta.x * HEAD_THRUST_STRAFE_SCALE * deltaTime));
}
if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) {
var orientation = Quat.multiply(Quat.angleAxis(deltaYaw * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation);
MyAvatar.orientation = orientation;
}
// Thrust Up/Down based on head pitch
MyAvatar.addThrust(Vec3.multiply({ x:0, y:1, z:0 }, (MyAvatar.getHeadFinalPitch() - headStartFinalPitch) * HEAD_PITCH_LIFT_THRUST * deltaTime));
// For head trackers, adjust pitch by head pitch
MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime;
// Thrust strafe based on roll ange
MyAvatar.addThrust(Vec3.multiply(Quat.getRight(Camera.getOrientation()), -(MyAvatar.getHeadFinalRoll() - headStartRoll) * HEAD_ROLL_THRUST_SCALE * deltaTime));
}
}
// Update for joysticks and move button
function flyWithHydra(deltaTime) {
var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER);
@ -318,12 +278,10 @@ function flyWithHydra(deltaTime) {
MyAvatar.orientation = Quat.multiply(orientation, deltaOrientation);
// change the headPitch based on our x controller
//pitch += viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime;
var newPitch = MyAvatar.headPitch + (viewJoystickPosition.y * JOYSTICK_PITCH_MAG * deltaTime);
MyAvatar.headPitch = newPitch;
}
handleGrabBehavior(deltaTime);
moveWithHead(deltaTime);
displayDebug();
}
@ -340,19 +298,4 @@ function scriptEnding() {
}
Script.scriptEnding.connect(scriptEnding);
Controller.keyPressEvent.connect(function(event) {
if (event.text == "SPACE" && !movingWithHead) {
movingWithHead = true;
headStartPosition = Vec3.subtract(MyAvatar.getHeadPosition(), MyAvatar.position);
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
headStartRoll = MyAvatar.getHeadFinalRoll();
headStartYaw = MyAvatar.getHeadFinalYaw();
}
});
Controller.keyReleaseEvent.connect(function(event) {
if (event.text == "SPACE") {
movingWithHead = false;
}
});

View file

@ -255,4 +255,8 @@ Script.update.connect(function(deltaTime) {
});
Script.scriptEnding.connect(function() {
var i;
for (i = 0; i < jointControllers.length; i += 1) {
Controller.releaseInputController(jointControllers[i].c);
}
});

View file

@ -41,6 +41,7 @@
#include <QShortcut>
#include <QTimer>
#include <QUrl>
#include <QWindow>
#include <QtDebug>
#include <QFileDialog>
#include <QDesktopServices>
@ -50,6 +51,7 @@
#include <QMimeData>
#include <QMessageBox>
#include <AddressManager.h>
#include <AccountManager.h>
#include <AudioInjector.h>
#include <EntityScriptingInterface.h>
@ -79,10 +81,11 @@
#include "scripting/AccountScriptingInterface.h"
#include "scripting/AudioDeviceScriptingInterface.h"
#include "scripting/ClipboardScriptingInterface.h"
#include "scripting/GlobalServicesScriptingInterface.h"
#include "scripting/LocationScriptingInterface.h"
#include "scripting/MenuScriptingInterface.h"
#include "scripting/SettingsScriptingInterface.h"
#include "scripting/WindowScriptingInterface.h"
#include "scripting/LocationScriptingInterface.h"
#include "ui/InfoView.h"
#include "ui/OAuthWebViewHandler.h"
@ -288,6 +291,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
// once the event loop has started, check and signal for an access token
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
AddressManager& addressManager = AddressManager::getInstance();
// connect to the domainChangeRequired signal on AddressManager
connect(&addressManager, &AddressManager::possibleDomainChangeRequired,
this, &Application::changeDomainHostname);
// when -url in command line, teleport to location
addressManager.handleLookupString(getCmdOption(argc, constArgv, "-url"));
_settings = new QSettings(this);
_numChangedSettings = 0;
@ -357,9 +369,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
Particle::setVoxelEditPacketSender(&_voxelEditSender);
Particle::setParticleEditPacketSender(&_particleEditSender);
// when -url in command line, teleport to location
urlGoTo(argc, constArgv);
// For now we're going to set the PPS for outbound packets to be super high, this is
// probably not the right long term solution. But for now, we're going to do this to
// allow you to move a particle around in your hand
@ -405,9 +414,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
connect(_window, &MainWindow::windowGeometryChanged,
_runningScriptsWidget, &RunningScriptsWidget::setBoundary);
//When -url in command line, teleport to location
urlGoTo(argc, constArgv);
// call the OAuthWebviewHandler static getter so that its instance lives in our thread
OAuthWebViewHandler::getInstance();
// make sure the High Fidelity root CA is in our list of trusted certs
@ -805,13 +811,16 @@ bool Application::event(QEvent* event) {
// handle custom URL
if (event->type() == QEvent::FileOpen) {
QFileOpenEvent* fileEvent = static_cast<QFileOpenEvent*>(event);
bool isHifiSchemeURL = !fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME);
if (isHifiSchemeURL) {
Menu::getInstance()->goToURL(fileEvent->url().toLocalFile());
if (!fileEvent->url().isEmpty()) {
AddressManager::getInstance().handleLookupString(fileEvent->url().toLocalFile());
}
return false;
}
return QApplication::event(event);
}
@ -904,12 +913,19 @@ void Application::keyPressEvent(QKeyEvent* event) {
break;
case Qt::Key_D:
_myAvatar->setDriveKeys(ROT_RIGHT, 1.f);
if (!isMeta) {
_myAvatar->setDriveKeys(ROT_RIGHT, 1.f);
}
break;
case Qt::Key_Return:
case Qt::Key_Enter:
Menu::getInstance()->triggerOption(MenuOption::Chat);
if (isMeta) {
Menu::getInstance()->triggerOption(MenuOption::AddressBar);
} else {
Menu::getInstance()->triggerOption(MenuOption::Chat);
}
break;
case Qt::Key_Up:
@ -1056,10 +1072,6 @@ void Application::keyPressEvent(QKeyEvent* event) {
case Qt::Key_Equal:
_myAvatar->resetSize();
break;
case Qt::Key_At:
Menu::getInstance()->goTo();
break;
default:
event->ignore();
break;
@ -1072,7 +1084,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
_keysPressed.remove(event->key());
_controllerScriptingInterface.emitKeyReleaseEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isKeyCaptured(event)) {
return;
@ -1124,7 +1136,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
_myAvatar->setDriveKeys(RIGHT, 0.f);
_myAvatar->setDriveKeys(ROT_RIGHT, 0.f);
break;
case Qt::Key_Control:
case Qt::Key_Shift:
case Qt::Key_Meta:
case Qt::Key_Alt:
_myAvatar->clearDriveKeys();
break;
default:
event->ignore();
break;
@ -1152,8 +1169,7 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
showMouse = false;
}
QMouseEvent deviceEvent = getDeviceEvent(event, deviceID);
_controllerScriptingInterface.emitMouseMoveEvent(&deviceEvent, deviceID); // send events to any registered scripts
_controllerScriptingInterface.emitMouseMoveEvent(event, deviceID); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isMouseCaptured()) {
@ -1168,13 +1184,12 @@ void Application::mouseMoveEvent(QMouseEvent* event, unsigned int deviceID) {
_seenMouseMove = true;
}
_mouseX = deviceEvent.x();
_mouseY = deviceEvent.y();
_mouseX = event->x();
_mouseY = event->y();
}
void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
QMouseEvent deviceEvent = getDeviceEvent(event, deviceID);
_controllerScriptingInterface.emitMousePressEvent(&deviceEvent); // send events to any registered scripts
_controllerScriptingInterface.emitMousePressEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isMouseCaptured()) {
@ -1184,8 +1199,8 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
if (activeWindow() == _window) {
if (event->button() == Qt::LeftButton) {
_mouseX = deviceEvent.x();
_mouseY = deviceEvent.y();
_mouseX = event->x();
_mouseY = event->y();
_mouseDragStartedX = _mouseX;
_mouseDragStartedY = _mouseY;
_mousePressed = true;
@ -1207,8 +1222,7 @@ void Application::mousePressEvent(QMouseEvent* event, unsigned int deviceID) {
}
void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
QMouseEvent deviceEvent = getDeviceEvent(event, deviceID);
_controllerScriptingInterface.emitMouseReleaseEvent(&deviceEvent); // send events to any registered scripts
_controllerScriptingInterface.emitMouseReleaseEvent(event); // send events to any registered scripts
// if one of our scripts have asked to capture this event, then stop processing it
if (_controllerScriptingInterface.isMouseCaptured()) {
@ -1217,8 +1231,8 @@ void Application::mouseReleaseEvent(QMouseEvent* event, unsigned int deviceID) {
if (activeWindow() == _window) {
if (event->button() == Qt::LeftButton) {
_mouseX = deviceEvent.x();
_mouseY = deviceEvent.y();
_mouseX = event->x();
_mouseY = event->y();
_mousePressed = false;
checkBandwidthMeterClick();
if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) {
@ -1316,7 +1330,7 @@ void Application::dropEvent(QDropEvent *event) {
SnapshotMetaData* snapshotData = Snapshot::parseSnapshotData(snapshotPath);
if (snapshotData) {
if (!snapshotData->getDomain().isEmpty()) {
Menu::getInstance()->goToDomain(snapshotData->getDomain());
changeDomainHostname(snapshotData->getDomain());
}
_myAvatar->setPosition(snapshotData->getLocation());
@ -1417,7 +1431,7 @@ void Application::checkBandwidthMeterClick() {
if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth) &&
glm::compMax(glm::abs(glm::ivec2(_mouseX - _mouseDragStartedX, _mouseY - _mouseDragStartedY)))
<= BANDWIDTH_METER_CLICK_MAX_DRAG_LENGTH
&& _bandwidthMeter.isWithinArea(_mouseX, _mouseY, _glWidget->getDeviceWidth(), _glWidget->getDeviceHeight())) {
&& _bandwidthMeter.isWithinArea(_mouseX, _mouseY, _glWidget->width(), _glWidget->height())) {
// The bandwidth meter is visible, the click didn't get dragged too far and
// we actually hit the bandwidth meter
@ -1735,8 +1749,8 @@ void Application::init() {
_voxelShader.init();
_pointShader.init();
_mouseX = _glWidget->getDeviceWidth() / 2;
_mouseY = _glWidget->getDeviceHeight() / 2;
_mouseX = _glWidget->width() / 2;
_mouseY = _glWidget->height() / 2;
QCursor::setPos(_mouseX, _mouseY);
// TODO: move _myAvatar out of Application. Move relevant code to MyAvataar or AvatarManager
@ -1891,8 +1905,8 @@ void Application::updateMouseRay() {
// if the mouse pointer isn't visible, act like it's at the center of the screen
float x = 0.5f, y = 0.5f;
if (!_mouseHidden) {
x = _mouseX / (float)_glWidget->getDeviceWidth();
y = _mouseY / (float)_glWidget->getDeviceHeight();
x = _mouseX / (float)_glWidget->width();
y = _mouseY / (float)_glWidget->height();
}
_viewFrustum.computePickRay(x, y, _mouseRayOrigin, _mouseRayDirection);
@ -2332,14 +2346,6 @@ int Application::sendNackPackets() {
return packetsSent;
}
QMouseEvent Application::getDeviceEvent(QMouseEvent* event, unsigned int deviceID) {
if (deviceID > 0) {
return *event;
}
return QMouseEvent(event->type(), QPointF(_glWidget->getDeviceX(event->x()), _glWidget->getDeviceY(event->y())),
event->windowPos(), event->screenPos(), event->button(), event->buttons(), event->modifiers());
}
void Application::queryOctree(NodeType_t serverType, PacketType packetType, NodeToJurisdictionMap& jurisdictions) {
//qDebug() << ">>> inside... queryOctree()... _viewFrustum.getFieldOfView()=" << _viewFrustum.getFieldOfView();
@ -2734,6 +2740,9 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()");
// transform by eye offset
// load the view frustum
loadViewFrustum(whichCamera, _displayViewFrustum);
// flip x if in mirror mode (also requires reversing winding order for backface culling)
if (whichCamera.getMode() == CAMERA_MODE_MIRROR) {
glScalef(-1.0f, 1.0f, 1.0f);
@ -2981,7 +2990,14 @@ void Application::getProjectionMatrix(glm::dmat4* projectionMatrix) {
void Application::computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const {
// allow 3DTV/Oculus to override parameters from camera
_viewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
if (OculusManager::isConnected()) {
OculusManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
} else if (TV3DManager::isConnected()) {
TV3DManager::overrideOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane);
}
}
glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) {
@ -3041,8 +3057,16 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
_mirrorCamera.update(1.0f/_fps);
// set the bounds of rear mirror view
glViewport(region.x(), _glWidget->getDeviceHeight() - region.y() - region.height(), region.width(), region.height());
glScissor(region.x(), _glWidget->getDeviceHeight() - region.y() - region.height(), region.width(), region.height());
if (billboard) {
glViewport(region.x(), _glWidget->getDeviceHeight() - region.y() - region.height(), region.width(), region.height());
glScissor(region.x(), _glWidget->getDeviceHeight() - region.y() - region.height(), region.width(), region.height());
} else {
// if not rendering the billboard, the region is in device independent coordinates; must convert to device
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio();
int x = region.x() * ratio, y = region.y() * ratio, width = region.width() * ratio, height = region.height() * ratio;
glViewport(x, _glWidget->getDeviceHeight() - y - height, width, height);
glScissor(x, _glWidget->getDeviceHeight() - y - height, width, height);
}
bool updateViewFrustum = false;
updateProjectionMatrix(_mirrorCamera, updateViewFrustum);
glEnable(GL_SCISSOR_TEST);
@ -3291,8 +3315,8 @@ void Application::deleteVoxelAt(const VoxelDetail& voxel) {
void Application::resetSensors() {
_mouseX = _glWidget->getDeviceWidth() / 2;
_mouseY = _glWidget->getDeviceHeight() / 2;
_mouseX = _glWidget->width() / 2;
_mouseY = _glWidget->height() / 2;
_faceplus.reset();
_faceshift.reset();
@ -3359,31 +3383,56 @@ void Application::updateWindowTitle(){
title += " - ₵" + creditBalanceString;
}
#ifndef WIN32
// crashes with vs2013/win32
qDebug("Application title set to: %s", title.toStdString().c_str());
#endif
_window->setWindowTitle(title);
}
void Application::updateLocationInServer() {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.isLoggedIn()) {
const QUuid& domainUUID = NodeList::getInstance()->getDomainHandler().getUUID();
if (accountManager.isLoggedIn() && !domainUUID.isNull()) {
// construct a QJsonObject given the user's current address information
QJsonObject updatedLocationObject;
QJsonObject rootObject;
QJsonObject addressObject;
addressObject.insert("position", QString(createByteArray(_myAvatar->getPosition())));
addressObject.insert("orientation", QString(createByteArray(glm::degrees(safeEulerAngles(_myAvatar->getOrientation())))));
addressObject.insert("domain", NodeList::getInstance()->getDomainHandler().getHostname());
QJsonObject locationObject;
QString pathString = AddressManager::pathForPositionAndOrientation(_myAvatar->getPosition(),
true,
_myAvatar->getOrientation());
const QString LOCATION_KEY_IN_ROOT = "location";
const QString PATH_KEY_IN_LOCATION = "path";
const QString DOMAIN_ID_KEY_IN_LOCATION = "domain_id";
locationObject.insert(PATH_KEY_IN_LOCATION, pathString);
locationObject.insert(DOMAIN_ID_KEY_IN_LOCATION, domainUUID.toString());
updatedLocationObject.insert("address", addressObject);
rootObject.insert(LOCATION_KEY_IN_ROOT, locationObject);
accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), QJsonDocument(updatedLocationObject).toJson());
accountManager.authenticatedRequest("/api/v1/users/location", QNetworkAccessManager::PutOperation,
JSONCallbackParameters(), QJsonDocument(rootObject).toJson());
}
}
void Application::changeDomainHostname(const QString &newDomainHostname) {
NodeList* nodeList = NodeList::getInstance();
if (!nodeList->getDomainHandler().isCurrentHostname(newDomainHostname)) {
// tell the MyAvatar object to send a kill packet so that it dissapears from its old avatar mixer immediately
_myAvatar->sendKillAvatar();
// call the domain hostname change as a queued connection on the nodelist
QMetaObject::invokeMethod(&NodeList::getInstance()->getDomainHandler(), "setHostname",
Q_ARG(const QString&, newDomainHostname));
}
}
void Application::domainChanged(const QString& domainHostname) {
updateWindowTitle();
@ -3767,12 +3816,11 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
QScriptValue windowValue = scriptEngine->registerGlobalObject("Window", WindowScriptingInterface::getInstance());
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter, windowValue);
LocationScriptingInterface::locationSetter, windowValue);
// register `location` on the global object.
scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter,
LocationScriptingInterface::locationSetter);
LocationScriptingInterface::locationSetter);
scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance());
@ -3780,9 +3828,11 @@ ScriptEngine* Application::loadScript(const QString& scriptFilename, bool isUser
scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector);
scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("Metavoxels", &_metavoxels);
scriptEngine->registerGlobalObject("GlobalServices", GlobalServicesScriptingInterface::getInstance());
scriptEngine->registerGlobalObject("AvatarManager", &_avatarManager);
#ifdef HAVE_RTMIDI
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
#endif
@ -3900,6 +3950,17 @@ void Application::uploadAttachment() {
uploadModel(ATTACHMENT_MODEL);
}
void Application::openUrl(const QUrl& url) {
if (!url.isEmpty()) {
if (url.scheme() == HIFI_URL_SCHEME) {
AddressManager::getInstance().handleLookupString(url.toString());
} else {
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);
}
}
}
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
@ -4070,6 +4131,14 @@ void Application::skipVersion(QString latestVersion) {
skipFile.write(latestVersion.toStdString().c_str());
}
void Application::setCursorVisible(bool visible) {
if (visible) {
restoreOverrideCursor();
} else {
setOverrideCursor(Qt::BlankCursor);
}
}
void Application::takeSnapshot() {
QMediaPlayer* player = new QMediaPlayer();
QFileInfo inf = QFileInfo(Application::resourcesPath() + "sounds/snap.wav");
@ -4088,37 +4157,3 @@ void Application::takeSnapshot() {
}
_snapshotShareDialog->show();
}
void Application::urlGoTo(int argc, const char * constArgv[]) {
//Gets the url (hifi://domain/destination/orientation)
QString customUrl = getCmdOption(argc, constArgv, "-url");
if(customUrl.startsWith(CUSTOM_URL_SCHEME + "//")) {
QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts);
if (urlParts.count() == 1) {
// location coordinates or place name
QString domain = urlParts[0];
Menu::goToDomain(domain);
} else if (urlParts.count() > 1) {
// if url has 2 or more parts, the first one is domain name
QString domain = urlParts[0];
// second part is either a destination coordinate or
// a place name
QString destination = urlParts[1];
// any third part is an avatar orientation.
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
Menu::goToDomain(domain);
// goto either @user, #place, or x-xx,y-yy,z-zz
// style co-ordinate.
Menu::goTo(destination);
if (!orientation.isEmpty()) {
// location orientation
Menu::goToOrientation(orientation);
}
}
}
}

View file

@ -114,7 +114,6 @@ static const float NODE_KILLED_GREEN = 0.0f;
static const float NODE_KILLED_BLUE = 0.0f;
static const QString SNAPSHOT_EXTENSION = ".jpg";
static const QString CUSTOM_URL_SCHEME = "hifi:";
static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees
static const float BILLBOARD_DISTANCE = 5.0f; // meters
@ -153,7 +152,6 @@ public:
void initializeGL();
void paintGL();
void resizeGL(int width, int height);
void urlGoTo(int argc, const char * constArgv[]);
void keyPressEvent(QKeyEvent* event);
void keyReleaseEvent(QKeyEvent* event);
@ -192,6 +190,7 @@ public:
const AudioReflector* getAudioReflector() const { return &_audioReflector; }
Camera* getCamera() { return &_myCamera; }
ViewFrustum* getViewFrustum() { return &_viewFrustum; }
ViewFrustum* getDisplayViewFrustum() { return &_displayViewFrustum; }
ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; }
VoxelImporter* getVoxelImporter() { return &_voxelImporter; }
VoxelSystem* getVoxels() { return &_voxels; }
@ -298,6 +297,8 @@ public:
QStringList getRunningScripts() { return _scriptEnginesHash.keys(); }
ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; }
void setCursorVisible(bool visible);
signals:
/// Fired when we're simulating; allows external parties to hook in.
@ -313,6 +314,7 @@ signals:
void importDone();
public slots:
void changeDomainHostname(const QString& newDomainHostname);
void domainChanged(const QString& domainHostname);
void updateWindowTitle();
void updateLocationInServer();
@ -352,6 +354,8 @@ public slots:
void uploadHead();
void uploadSkeleton();
void uploadAttachment();
void openUrl(const QUrl& url);
void bumpSettings() { ++_numChangedSettings; }
@ -436,8 +440,6 @@ private:
int sendNackPackets();
QMouseEvent getDeviceEvent(QMouseEvent* event, unsigned int deviceID);
MainWindow* _window;
GLCanvas* _glWidget; // our GLCanvas has a couple extra features
@ -488,6 +490,7 @@ private:
ViewFrustum _viewFrustum; // current state of view frustum, perspective, orientation, etc.
ViewFrustum _lastQueriedViewFrustum; /// last view frustum used to query octree servers (voxels, particles)
ViewFrustum _displayViewFrustum;
ViewFrustum _shadowViewFrustum;
quint64 _lastQueriedTime;

View file

@ -40,6 +40,7 @@
#include <glm/glm.hpp>
#include "Audio.h"
#include "Menu.h"
#include "Util.h"
#include "PositionalAudioStream.h"
@ -82,7 +83,7 @@ Audio::Audio(QObject* parent) :
_noiseGateSampleCounter(0),
_noiseGateOpen(false),
_noiseGateEnabled(true),
_toneInjectionEnabled(false),
_audioSourceInjectEnabled(false),
_noiseGateFramesToClose(0),
_totalInputAudioSamples(0),
_collisionSoundMagnitude(0.0f),
@ -101,6 +102,8 @@ Audio::Audio(QObject* parent) :
_scopeOutputOffset(0),
_framesPerScope(DEFAULT_FRAMES_PER_SCOPE),
_samplesPerScope(NETWORK_SAMPLES_PER_FRAME * _framesPerScope),
_noiseSourceEnabled(false),
_toneSourceEnabled(true),
_peqEnabled(false),
_scopeInput(0),
_scopeOutputLeft(0),
@ -137,6 +140,10 @@ void Audio::reset() {
_receivedAudioStream.reset();
resetStats();
_peq.reset();
_noiseSource.reset();
_toneSource.reset();
_sourceGain.reset();
_inputGain.reset();
}
void Audio::resetStats() {
@ -157,14 +164,24 @@ void Audio::audioMixerKilled() {
resetStats();
}
QAudioDeviceInfo getNamedAudioDeviceForMode(QAudio::Mode mode, const QString& deviceName) {
QAudioDeviceInfo result;
#ifdef WIN32
// NOTE
// this is a workaround for a windows only QtBug https://bugreports.qt-project.org/browse/QTBUG-16117
// static QAudioDeviceInfo objects get deallocated when QList<QAudioDevieInfo> objects go out of scope
result = (mode == QAudio::AudioInput) ?
QAudioDeviceInfo::defaultInputDevice() :
QAudioDeviceInfo::defaultOutputDevice();
#else
foreach(QAudioDeviceInfo audioDevice, QAudioDeviceInfo::availableDevices(mode)) {
qDebug() << audioDevice.deviceName() << " " << deviceName;
if (audioDevice.deviceName().trimmed() == deviceName.trimmed()) {
result = audioDevice;
}
}
#endif
return result;
}
@ -270,6 +287,7 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
pMMDeviceEnumerator = NULL;
CoUninitialize();
}
qDebug() << "DEBUG [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]";
return getNamedAudioDeviceForMode(mode, deviceName);
@ -424,14 +442,25 @@ void Audio::start() {
qDebug() << "Unable to set up audio output because of a problem with output format.";
}
_peq.initialize( _inputFormat.sampleRate(), _audioInput->bufferSize() );
_inputFrameBuffer.initialize( _inputFormat.channelCount(), _audioInput->bufferSize() * 8 );
_peq.initialize( _inputFormat.sampleRate() );
_inputGain.initialize();
_sourceGain.initialize();
_noiseSource.initialize();
_toneSource.initialize();
_sourceGain.setParameters(0.25f,0.0f);
_inputGain.setParameters(1.0f,0.0f);
}
void Audio::stop() {
_inputFrameBuffer.finalize();
_peq.finalize();
_inputGain.finalize();
_sourceGain.finalize();
_noiseSource.finalize();
_toneSource.finalize();
// "switch" to invalid devices in order to shut down the state
switchInputToAudioDevice(QAudioDeviceInfo());
switchOutputToAudioDevice(QAudioDeviceInfo());
@ -477,14 +506,30 @@ void Audio::handleAudioInput() {
QByteArray inputByteArray = _inputDevice->readAll();
int16_t* inputFrameData = (int16_t*)inputByteArray.data();
const int inputFrameCount = inputByteArray.size() / sizeof(int16_t);
_inputFrameBuffer.copyFrames(1, inputFrameCount, inputFrameData, false /*copy in*/);
_inputGain.render(_inputFrameBuffer); // input/mic gain+mute
// Add audio source injection if enabled
if (_audioSourceInjectEnabled && !_muted) {
if (_toneSourceEnabled) { // sine generator
_toneSource.render(_inputFrameBuffer);
}
else if(_noiseSourceEnabled) { // pink noise generator
_noiseSource.render(_inputFrameBuffer);
}
_sourceGain.render(_inputFrameBuffer); // post gain
}
if (_peqEnabled && !_muted) {
// we wish to pre-filter our captured input, prior to loopback
int16_t* ioBuffer = (int16_t*)inputByteArray.data();
_peq.render(ioBuffer, ioBuffer, inputByteArray.size() / sizeof(int16_t));
_peq.render(_inputFrameBuffer); // 3-band parametric eq
}
_inputFrameBuffer.copyFrames(1, inputFrameCount, inputFrameData, true /*copy out*/);
if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted && _audioOutput) {
// if this person wants local loopback add that to the locally injected audio
@ -522,7 +567,7 @@ void Audio::handleAudioInput() {
int16_t* inputAudioSamples = new int16_t[inputSamplesRequired];
_inputRingBuffer.readSamples(inputAudioSamples, inputSamplesRequired);
const int numNetworkBytes = _isStereoInput ? NETWORK_BUFFER_LENGTH_BYTES_STEREO : NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL;
const int numNetworkSamples = _isStereoInput ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL;
@ -599,20 +644,10 @@ void Audio::handleAudioInput() {
_dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.0f - DC_OFFSET_AVERAGING) * measuredDcOffset;
}
// Add tone injection if enabled
const float TONE_FREQ = 220.0f / SAMPLE_RATE * TWO_PI;
const float QUARTER_VOLUME = 8192.0f;
if (_toneInjectionEnabled) {
loudness = 0.0f;
for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) {
networkAudioSamples[i] = QUARTER_VOLUME * sinf(TONE_FREQ * (float)(i + _proceduralEffectSample));
loudness += fabsf(networkAudioSamples[i]);
}
}
_lastInputLoudness = fabs(loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL);
// If Noise Gate is enabled, check and turn the gate on and off
if (!_toneInjectionEnabled && _noiseGateEnabled) {
if (!_audioSourceInjectEnabled && _noiseGateEnabled) {
float averageOfAllSampleFrames = 0.0f;
_noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness;
if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) {
@ -1041,8 +1076,18 @@ void Audio::processProceduralAudio(int16_t* monoInput, int numSamples) {
}
}
void Audio::toggleToneInjection() {
_toneInjectionEnabled = !_toneInjectionEnabled;
void Audio::toggleAudioSourceInject() {
_audioSourceInjectEnabled = !_audioSourceInjectEnabled;
}
void Audio::selectAudioSourcePinkNoise() {
_noiseSourceEnabled = Menu::getInstance()->isOptionChecked(MenuOption::AudioSourcePinkNoise);
_toneSourceEnabled = !_noiseSourceEnabled;
}
void Audio::selectAudioSourceSine440() {
_toneSourceEnabled = Menu::getInstance()->isOptionChecked(MenuOption::AudioSourceSine440);
_noiseSourceEnabled = !_toneSourceEnabled;
}
void Audio::toggleAudioSpatialProcessing() {

View file

@ -20,6 +20,13 @@
#include "Recorder.h"
#include "RingBufferHistory.h"
#include "MovingMinMaxAvg.h"
#include "AudioRingBuffer.h"
#include "AudioFormat.h"
#include "AudioBuffer.h"
#include "AudioSourceTone.h"
#include "AudioSourceNoise.h"
#include "AudioGain.h"
#include "AudioPan.h"
#include "AudioFilter.h"
#include "AudioFilterBank.h"
@ -116,7 +123,9 @@ public slots:
void audioMixerKilled();
void toggleMute();
void toggleAudioNoiseReduction();
void toggleToneInjection();
void toggleAudioSourceInject();
void selectAudioSourcePinkNoise();
void selectAudioSourceSine440();
void toggleScope();
void toggleScopePause();
void toggleStats();
@ -199,7 +208,8 @@ private:
int _noiseGateSampleCounter;
bool _noiseGateOpen;
bool _noiseGateEnabled;
bool _toneInjectionEnabled;
bool _audioSourceInjectEnabled;
int _noiseGateFramesToClose;
int _totalInputAudioSamples;
@ -282,10 +292,27 @@ private:
int _framesPerScope;
int _samplesPerScope;
// Multi-band parametric EQ
bool _peqEnabled;
AudioFilterPEQ3m _peq;
// Input framebuffer
AudioBufferFloat32 _inputFrameBuffer;
// Input gain
AudioGain _inputGain;
// Post tone/pink noise generator gain
AudioGain _sourceGain;
// Pink noise source
bool _noiseSourceEnabled;
AudioSourcePinkNoise _noiseSource;
// Tone source
bool _toneSourceEnabled;
AudioSourceTone _toneSource;
// Multi-band parametric EQ
bool _peqEnabled;
AudioFilterPEQ3m _peq;
QMutex _guard;
QByteArray* _scopeInput;
QByteArray* _scopeOutputLeft;

View file

@ -262,8 +262,8 @@ CameraScriptableObject::CameraScriptableObject(Camera* camera, ViewFrustum* view
}
PickRay CameraScriptableObject::computePickRay(float x, float y) {
float screenWidth = Application::getInstance()->getGLWidget()->getDeviceWidth();
float screenHeight = Application::getInstance()->getGLWidget()->getDeviceHeight();
float screenWidth = Application::getInstance()->getGLWidget()->width();
float screenHeight = Application::getInstance()->getGLWidget()->height();
PickRay result;
if (OculusManager::isConnected()) {
result.origin = _camera->getPosition();

View file

@ -43,14 +43,6 @@ int GLCanvas::getDeviceHeight() const {
return height() * (windowHandle() ? windowHandle()->devicePixelRatio() : 1.0f);
}
int GLCanvas::getDeviceX(int x) const {
return x * getDeviceWidth() / width();
}
int GLCanvas::getDeviceY(int y) const {
return y * getDeviceHeight() / height();
}
void GLCanvas::initializeGL() {
Application::getInstance()->initializeGL();
setAttribute(Qt::WA_AcceptTouchEvents);

View file

@ -26,9 +26,6 @@ public:
int getDeviceHeight() const;
QSize getDeviceSize() const { return QSize(getDeviceWidth(), getDeviceHeight()); }
int getDeviceX(int x) const;
int getDeviceY(int y) const;
protected:
QTimer _frameTimer;

View file

@ -30,6 +30,7 @@
#include <QDesktopServices>
#include <AccountManager.h>
#include <AddressManager.h>
#include <XmppClient.h>
#include <UUID.h>
#include <UserActivityLogger.h>
@ -70,6 +71,7 @@ Menu* Menu::getInstance() {
const ViewFrustumOffset DEFAULT_FRUSTUM_OFFSET = {-135.0f, 0.0f, 0.0f, 25.0f, 0.0f};
const float DEFAULT_FACESHIFT_EYE_DEFLECTION = 0.25f;
const QString DEFAULT_FACESHIFT_HOSTNAME = "localhost";
const float DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER = 1.0f;
const int ONE_SECOND_OF_FRAMES = 60;
const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES;
@ -87,6 +89,7 @@ Menu::Menu() :
_fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES),
_realWorldFieldOfView(DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
_faceshiftEyeDeflection(DEFAULT_FACESHIFT_EYE_DEFLECTION),
_faceshiftHostname(DEFAULT_FACESHIFT_HOSTNAME),
_frustumDrawMode(FRUSTUM_DRAW_MODE_ALL),
_viewFrustumOffset(DEFAULT_FRUSTUM_OFFSET),
_jsConsole(NULL),
@ -139,9 +142,14 @@ Menu::Menu() :
// call our toggle login function now so the menu option is setup properly
toggleLoginMenuItem();
// connect to the appropriate slots of the AccountManager so that we can change the Login/Logout menu item
// connect to the appropriate signal of the AccountManager so that we can change the Login/Logout menu item
connect(&accountManager, &AccountManager::profileChanged, this, &Menu::toggleLoginMenuItem);
connect(&accountManager, &AccountManager::logoutComplete, this, &Menu::toggleLoginMenuItem);
// connect to signal of account manager so we can tell user when the user/place they looked at is offline
AddressManager& addressManager = AddressManager::getInstance();
connect(&addressManager, &AddressManager::lookupResultIsOffline, this, &Menu::displayAddressOfflineMessage);
connect(&addressManager, &AddressManager::lookupResultIsNotFound, this, &Menu::displayAddressNotFoundMessage);
addDisabledActionAndSeparator(fileMenu, "Scripts");
addActionToQMenuAndActionHash(fileMenu, MenuOption::LoadScript, Qt::CTRL | Qt::Key_O, appInstance, SLOT(loadDialog()));
@ -153,21 +161,6 @@ Menu::Menu() :
appInstance, SLOT(toggleRunningScriptsWidget()));
addDisabledActionAndSeparator(fileMenu, "Go");
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoHome,
Qt::CTRL | Qt::Key_G,
appInstance->getAvatar(),
SLOT(goHome()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoToDomain,
Qt::CTRL | Qt::Key_D,
this,
SLOT(goToDomainDialog()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoToLocation,
Qt::CTRL | Qt::SHIFT | Qt::Key_L,
this,
SLOT(goToLocation()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::NameLocation,
Qt::CTRL | Qt::Key_N,
@ -179,12 +172,10 @@ Menu::Menu() :
this,
SLOT(toggleLocationList()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoTo,
Qt::Key_At,
MenuOption::AddressBar,
Qt::CTRL | Qt::Key_Enter,
this,
SLOT(goTo()));
connect(&LocationManager::getInstance(), &LocationManager::multipleDestinationsFound,
this, &Menu::multipleDestinationsDecision);
SLOT(toggleAddressBar()));
addDisabledActionAndSeparator(fileMenu, "Upload Avatar Model");
addActionToQMenuAndActionHash(fileMenu, MenuOption::UploadHead, 0, Application::getInstance(), SLOT(uploadHead()));
@ -546,11 +537,31 @@ Menu::Menu() :
0,
this,
SLOT(muteEnvironment()));
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioToneInjection,
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioSourceInject,
0,
false,
appInstance->getAudio(),
SLOT(toggleToneInjection()));
SLOT(toggleAudioSourceInject()));
QMenu* audioSourceMenu = audioDebugMenu->addMenu("Generated Audio Source");
{
QAction *pinkNoise = addCheckableActionToQMenuAndActionHash(audioSourceMenu, MenuOption::AudioSourcePinkNoise,
0,
false,
appInstance->getAudio(),
SLOT(selectAudioSourcePinkNoise()));
QAction *sine440 = addCheckableActionToQMenuAndActionHash(audioSourceMenu, MenuOption::AudioSourceSine440,
0,
true,
appInstance->getAudio(),
SLOT(selectAudioSourceSine440()));
QActionGroup* audioSourceGroup = new QActionGroup(audioSourceMenu);
audioSourceGroup->addAction(pinkNoise);
audioSourceGroup->addAction(sine440);
}
addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScope,
Qt::CTRL | Qt::Key_P, false,
appInstance->getAudio(),
@ -694,6 +705,7 @@ void Menu::loadSettings(QSettings* settings) {
_fieldOfView = loadSetting(settings, "fieldOfView", DEFAULT_FIELD_OF_VIEW_DEGREES);
_realWorldFieldOfView = loadSetting(settings, "realWorldFieldOfView", DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES);
_faceshiftEyeDeflection = loadSetting(settings, "faceshiftEyeDeflection", DEFAULT_FACESHIFT_EYE_DEFLECTION);
_faceshiftHostname = settings->value("faceshiftHostname", DEFAULT_FACESHIFT_HOSTNAME).toString();
_maxVoxels = loadSetting(settings, "maxVoxels", DEFAULT_MAX_VOXELS_PER_SYSTEM);
_maxVoxelPacketsPerSecond = loadSetting(settings, "maxVoxelsPPS", DEFAULT_MAX_VOXEL_PPS);
_voxelSizeScale = loadSetting(settings, "voxelSizeScale", DEFAULT_OCTREE_SIZE_SCALE);
@ -759,6 +771,7 @@ void Menu::saveSettings(QSettings* settings) {
settings->setValue("fieldOfView", _fieldOfView);
settings->setValue("faceshiftEyeDeflection", _faceshiftEyeDeflection);
settings->setValue("faceshiftHostname", _faceshiftHostname);
settings->setValue("maxVoxels", _maxVoxels);
settings->setValue("maxVoxelsPPS", _maxVoxelPacketsPerSecond);
settings->setValue("voxelSizeScale", _voxelSizeScale);
@ -1133,145 +1146,33 @@ void Menu::changePrivateKey() {
sendFakeEnterEvent();
}
void Menu::goToDomain(const QString newDomain) {
if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) {
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
Application::getInstance()->getAvatar()->sendKillAvatar();
void Menu::toggleAddressBar() {
// give our nodeList the new domain-server hostname
NodeList::getInstance()->getDomainHandler().setHostname(newDomain);
QInputDialog addressBarDialog(Application::getInstance()->getWindow());
addressBarDialog.setWindowTitle("Address Bar");
addressBarDialog.setWindowFlags(Qt::Sheet);
addressBarDialog.setLabelText("place, domain, @user, example.com, /position/orientation");
addressBarDialog.resize(addressBarDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW,
addressBarDialog.size().height());
int dialogReturn = addressBarDialog.exec();
if (dialogReturn == QDialog::Accepted && !addressBarDialog.textValue().isEmpty()) {
// let the AddressManger figure out what to do with this
AddressManager::getInstance().handleLookupString(addressBarDialog.textValue());
}
}
void Menu::goToDomainDialog() {
QString currentDomainHostname = NodeList::getInstance()->getDomainHandler().getHostname();
if (NodeList::getInstance()->getDomainHandler().getPort() != DEFAULT_DOMAIN_SERVER_PORT) {
// add the port to the currentDomainHostname string if it is custom
currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainHandler().getPort()));
}
QInputDialog domainDialog(Application::getInstance()->getWindow());
domainDialog.setWindowTitle("Go to Domain");
domainDialog.setLabelText("Domain server:");
domainDialog.setTextValue(currentDomainHostname);
domainDialog.resize(domainDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, domainDialog.size().height());
int dialogReturn = domainDialog.exec();
if (dialogReturn == QDialog::Accepted) {
QString newHostname(DEFAULT_DOMAIN_HOSTNAME);
if (domainDialog.textValue().size() > 0) {
// the user input a new hostname, use that
newHostname = domainDialog.textValue();
}
goToDomain(newHostname);
}
sendFakeEnterEvent();
}
void Menu::goToOrientation(QString orientation) {
LocationManager::getInstance().goToOrientation(orientation);
void Menu::displayAddressOfflineMessage() {
QMessageBox::information(Application::getInstance()->getWindow(), "Address offline",
"That user or place is currently offline.");
}
bool Menu::goToDestination(QString destination) {
return LocationManager::getInstance().goToDestination(destination);
}
void Menu::goTo(QString destination) {
LocationManager::getInstance().goTo(destination);
}
void Menu::goTo() {
QInputDialog gotoDialog(Application::getInstance()->getWindow());
gotoDialog.setWindowTitle("Go to");
gotoDialog.setLabelText("Destination or URL:\n @user, #place, hifi://domain/location/orientation");
QString destination = QString();
gotoDialog.setTextValue(destination);
gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height());
int dialogReturn = gotoDialog.exec();
if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) {
QString desiredDestination = gotoDialog.textValue();
if (!goToURL(desiredDestination)) {;
goTo(desiredDestination);
}
}
sendFakeEnterEvent();
}
bool Menu::goToURL(QString location) {
if (location.startsWith(CUSTOM_URL_SCHEME + "/")) {
QStringList urlParts = location.remove(0, CUSTOM_URL_SCHEME.length()).split('/', QString::SkipEmptyParts);
if (urlParts.count() > 1) {
// if url has 2 or more parts, the first one is domain name
QString domain = urlParts[0];
// second part is either a destination coordinate or
// a place name
QString destination = urlParts[1];
// any third part is an avatar orientation.
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
goToDomain(domain);
// goto either @user, #place, or x-xx,y-yy,z-zz
// style co-ordinate.
goTo(destination);
if (!orientation.isEmpty()) {
// location orientation
goToOrientation(orientation);
}
} else if (urlParts.count() == 1) {
QString destination = urlParts[0];
// If this starts with # or @, treat it as a user/location, otherwise treat it as a domain
if (destination[0] == '#' || destination[0] == '@') {
goTo(destination);
} else {
goToDomain(destination);
}
}
return true;
}
return false;
}
void Menu::goToUser(const QString& user) {
LocationManager::getInstance().goTo(user);
}
/// Open a url, shortcutting any "hifi" scheme URLs to the local application.
void Menu::openUrl(const QUrl& url) {
if (url.scheme() == "hifi") {
goToURL(url.toString());
} else {
QDesktopServices::openUrl(url);
}
}
void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData) {
QMessageBox msgBox;
msgBox.setText("Both user and location exists with same name");
msgBox.setInformativeText("Where you wanna go?");
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Open);
msgBox.button(QMessageBox::Ok)->setText("User");
msgBox.button(QMessageBox::Open)->setText("Place");
int userResponse = msgBox.exec();
if (userResponse == QMessageBox::Ok) {
Application::getInstance()->getAvatar()->goToLocationFromAddress(userData["address"].toObject());
} else if (userResponse == QMessageBox::Open) {
Application::getInstance()->getAvatar()->goToLocationFromAddress(placeData["address"].toObject());
}
void Menu::displayAddressNotFoundMessage() {
QMessageBox::information(Application::getInstance()->getWindow(), "Address not found",
"There is no address information for that user or place.");
}
void Menu::muteEnvironment() {
@ -1298,43 +1199,13 @@ void Menu::muteEnvironment() {
free(packet);
}
void Menu::goToLocation() {
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();
QString currentLocation = QString("%1, %2, %3").arg(QString::number(avatarPos.x),
QString::number(avatarPos.y), QString::number(avatarPos.z));
void Menu::displayNameLocationResponse(const QString& errorString) {
QInputDialog coordinateDialog(Application::getInstance()->getWindow());
coordinateDialog.setWindowTitle("Go to Location");
coordinateDialog.setLabelText("Coordinate as x,y,z:");
coordinateDialog.setTextValue(currentLocation);
coordinateDialog.resize(coordinateDialog.parentWidget()->size().width() * 0.30, coordinateDialog.size().height());
int dialogReturn = coordinateDialog.exec();
if (dialogReturn == QDialog::Accepted && !coordinateDialog.textValue().isEmpty()) {
goToDestination(coordinateDialog.textValue());
}
sendFakeEnterEvent();
}
void Menu::namedLocationCreated(LocationManager::NamedLocationCreateResponse response) {
if (response == LocationManager::Created) {
return;
}
QMessageBox msgBox;
switch (response) {
case LocationManager::AlreadyExists:
msgBox.setText("That name has been already claimed, try something else.");
break;
default:
msgBox.setText("An unexpected error has occurred, please try again later.");
break;
}
msgBox.exec();
if (!errorString.isEmpty()) {
QMessageBox msgBox;
msgBox.setText(errorString);
msgBox.exec();
}
}
void Menu::toggleLocationList() {
@ -1352,23 +1223,34 @@ void Menu::nameLocation() {
// check if user is logged in or show login dialog if not
AccountManager& accountManager = AccountManager::getInstance();
if (!accountManager.isLoggedIn()) {
QMessageBox msgBox;
msgBox.setText("We need to tie this location to your username.");
msgBox.setInformativeText("Please login first, then try naming the location again.");
msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
msgBox.button(QMessageBox::Ok)->setText("Login");
if (msgBox.exec() == QMessageBox::Ok) {
loginForCurrentDomain();
}
return;
}
DomainHandler& domainHandler = NodeList::getInstance()->getDomainHandler();
if (domainHandler.getUUID().isNull()) {
const QString UNREGISTERED_DOMAIN_MESSAGE = "This domain is not registered with High Fidelity."
"\n\nYou cannot create a global location in an unregistered domain.";
QMessageBox::critical(this, "Unregistered Domain", UNREGISTERED_DOMAIN_MESSAGE);
return;
}
QInputDialog nameDialog(Application::getInstance()->getWindow());
nameDialog.setWindowTitle("Name this location");
nameDialog.setLabelText("Name this location, then share that name with others.\n"
"When they come here, they'll be in the same location and orientation\n"
"When they come here, they'll have the same viewpoint\n"
"(wherever you are standing and looking now) as you.\n\n"
"Location name:");
@ -1383,10 +1265,10 @@ void Menu::nameLocation() {
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
LocationManager* manager = new LocationManager();
connect(manager, &LocationManager::creationCompleted, this, &Menu::namedLocationCreated);
connect(manager, &LocationManager::creationCompleted, this, &Menu::displayNameLocationResponse);
NamedLocation* location = new NamedLocation(locationName,
myAvatar->getPosition(), myAvatar->getOrientation(),
NodeList::getInstance()->getDomainHandler().getHostname());
domainHandler.getUUID());
manager->createNamedLocation(location);
}
}

View file

@ -104,6 +104,8 @@ public:
float getFaceshiftEyeDeflection() const { return _faceshiftEyeDeflection; }
void setFaceshiftEyeDeflection(float faceshiftEyeDeflection) { _faceshiftEyeDeflection = faceshiftEyeDeflection; bumpSettings(); }
const QString& getFaceshiftHostname() const { return _faceshiftHostname; }
void setFaceshiftHostname(const QString& hostname) { _faceshiftHostname = hostname; bumpSettings(); }
QString getSnapshotsLocation() const;
void setSnapshotsLocation(QString snapshotsLocation) { _snapshotsLocation = snapshotsLocation; bumpSettings(); }
@ -162,11 +164,6 @@ public:
int menuItemLocation = UNSPECIFIED_POSITION);
void removeAction(QMenu* menu, const QString& actionName);
bool static goToDestination(QString destination);
void static goToOrientation(QString orientation);
void static goToDomain(const QString newDomain);
void static goTo(QString destination);
const QByteArray& getWalletPrivateKey() const { return _walletPrivateKey; }
@ -185,11 +182,8 @@ public slots:
void saveSettings(QSettings* settings = NULL);
void importSettings();
void exportSettings();
void goTo();
bool goToURL(QString location);
void goToUser(const QString& user);
void toggleAddressBar();
void pasteToVoxel();
void openUrl(const QUrl& url);
void toggleLoginMenuItem();
@ -211,8 +205,6 @@ private slots:
void editAttachments();
void editAnimations();
void changePrivateKey();
void goToDomainDialog();
void goToLocation();
void nameLocation();
void toggleLocationList();
void bandwidthDetailsClosed();
@ -226,8 +218,9 @@ private slots:
void toggleConsole();
void toggleChat();
void audioMuteToggled();
void namedLocationCreated(LocationManager::NamedLocationCreateResponse response);
void multipleDestinationsDecision(const QJsonObject& userData, const QJsonObject& placeData);
void displayNameLocationResponse(const QString& errorString);
void displayAddressOfflineMessage();
void displayAddressNotFoundMessage();
void muteEnvironment();
private:
@ -271,6 +264,7 @@ private:
float _fieldOfView; /// in Degrees, doesn't apply to HMD like Oculus
float _realWorldFieldOfView; // The actual FOV set by the user's monitor size and view distance
float _faceshiftEyeDeflection;
QString _faceshiftHostname;
FrustumDrawMode _frustumDrawMode;
ViewFrustumOffset _viewFrustumOffset;
QPointer<MetavoxelEditor> _MetavoxelEditor;
@ -315,6 +309,7 @@ private:
namespace MenuOption {
const QString AboutApp = "About Interface";
const QString AddressBar = "Show Address Bar";
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
const QString AlternateIK = "Alternate IK";
const QString AmbientOcclusion = "Ambient Occlusion";
@ -347,7 +342,9 @@ namespace MenuOption {
const QString AudioSpatialProcessingSlightlyRandomSurfaces = "Slightly Random Surfaces";
const QString AudioSpatialProcessingStereoSource = "Stereo Source";
const QString AudioSpatialProcessingWithDiffusions = "With Diffusions";
const QString AudioToneInjection = "Inject Test Tone";
const QString AudioSourceInject = "Generated Audio";
const QString AudioSourcePinkNoise = "Pink Noise";
const QString AudioSourceSine440 = "Sine 440hz";
const QString Avatars = "Avatars";
const QString AvatarsReceiveShadows = "Avatars Receive Shadows";
const QString Bandwidth = "Bandwidth Display";
@ -398,10 +395,7 @@ namespace MenuOption {
const QString FullscreenMirror = "Fullscreen Mirror";
const QString GlowMode = "Cycle Glow Mode";
const QString GlowWhenSpeaking = "Glow When Speaking";
const QString GoHome = "Go Home";
const QString GoToDomain = "Go To Domain...";
const QString GoTo = "Go To...";
const QString GoToLocation = "Go To Location...";
const QString GoToUser = "Go To User";
const QString HeadMouse = "Head Mouse";
const QString IncreaseAvatarSize = "Increase Avatar Size";
const QString IncreaseVoxelSize = "Increase Voxel Size";

View file

@ -133,7 +133,7 @@ const GLenum COLOR_NORMAL_DRAW_BUFFERS[] = { GL_COLOR_ATTACHMENT0, GL_COLOR_ATTA
void MetavoxelSystem::render() {
// update the frustum
ViewFrustum* viewFrustum = Application::getInstance()->getViewFrustum();
ViewFrustum* viewFrustum = Application::getInstance()->getDisplayViewFrustum();
_frustum.set(viewFrustum->getFarTopLeft(), viewFrustum->getFarTopRight(), viewFrustum->getFarBottomLeft(),
viewFrustum->getFarBottomRight(), viewFrustum->getNearTopLeft(), viewFrustum->getNearTopRight(),
viewFrustum->getNearBottomLeft(), viewFrustum->getNearBottomRight());
@ -181,6 +181,14 @@ void MetavoxelSystem::render() {
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPrimaryNormalTextureID());
// get the viewport side (left, right, both)
int viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
const int VIEWPORT_X_INDEX = 0;
const int VIEWPORT_WIDTH_INDEX = 2;
float sMin = viewport[VIEWPORT_X_INDEX] / (float)primaryFBO->width();
float sWidth = viewport[VIEWPORT_WIDTH_INDEX] / (float)primaryFBO->width();
if (Menu::getInstance()->getShadowsEnabled()) {
glActiveTexture(GL_TEXTURE2);
glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPrimaryDepthTextureID());
@ -210,10 +218,13 @@ void MetavoxelSystem::render() {
program->setUniformValue(locations->nearLocation, nearVal);
program->setUniformValue(locations->depthScale, (farVal - nearVal) / farVal);
float nearScale = -1.0f / nearVal;
program->setUniformValue(locations->depthTexCoordOffset, left * nearScale, bottom * nearScale);
program->setUniformValue(locations->depthTexCoordScale, (right - left) * nearScale, (top - bottom) * nearScale);
float sScale = 1.0f / sWidth;
float depthTexCoordScaleS = (right - left) * nearScale * sScale;
program->setUniformValue(locations->depthTexCoordOffset, left * nearScale - sMin * depthTexCoordScaleS,
bottom * nearScale);
program->setUniformValue(locations->depthTexCoordScale, depthTexCoordScaleS, (top - bottom) * nearScale);
renderFullscreenQuad();
renderFullscreenQuad(sMin, sMin + sWidth);
program->release();
@ -226,7 +237,7 @@ void MetavoxelSystem::render() {
} else {
_directionalLight.bind();
renderFullscreenQuad();
renderFullscreenQuad(sMin, sMin + sWidth);
_directionalLight.release();
}
@ -245,7 +256,7 @@ void MetavoxelSystem::render() {
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
renderFullscreenQuad();
renderFullscreenQuad(sMin, sMin + sWidth);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
@ -2176,7 +2187,7 @@ private:
SpannerRenderVisitor::SpannerRenderVisitor(const MetavoxelLOD& lod) :
SpannerVisitor(QVector<AttributePointer>() << AttributeRegistry::getInstance()->getSpannersAttribute(),
QVector<AttributePointer>(), QVector<AttributePointer>(), QVector<AttributePointer>(),
lod, encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())),
lod, encodeOrder(Application::getInstance()->getDisplayViewFrustum()->getDirection())),
_containmentDepth(INT_MAX) {
}
@ -2212,7 +2223,7 @@ private:
BufferRenderVisitor::BufferRenderVisitor(const AttributePointer& attribute) :
MetavoxelVisitor(QVector<AttributePointer>() << attribute),
_order(encodeOrder(Application::getInstance()->getViewFrustum()->getDirection())),
_order(encodeOrder(Application::getInstance()->getDisplayViewFrustum()->getDirection())),
_containmentDepth(INT_MAX) {
}
@ -2246,12 +2257,12 @@ void DefaultMetavoxelRendererImplementation::render(MetavoxelData& data, Metavox
float viewportWidth = viewport[VIEWPORT_WIDTH_INDEX];
float viewportHeight = viewport[VIEWPORT_HEIGHT_INDEX];
float viewportDiagonal = sqrtf(viewportWidth * viewportWidth + viewportHeight * viewportHeight);
float worldDiagonal = glm::distance(Application::getInstance()->getViewFrustum()->getNearBottomLeft(),
Application::getInstance()->getViewFrustum()->getNearTopRight());
float worldDiagonal = glm::distance(Application::getInstance()->getDisplayViewFrustum()->getNearBottomLeft(),
Application::getInstance()->getDisplayViewFrustum()->getNearTopRight());
_pointProgram.bind();
_pointProgram.setUniformValue(_pointScaleLocation, viewportDiagonal *
Application::getInstance()->getViewFrustum()->getNearClip() / worldDiagonal);
Application::getInstance()->getDisplayViewFrustum()->getNearClip() / worldDiagonal);
glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_COLOR_ARRAY);

View file

@ -48,6 +48,7 @@ static const QString TRANSLATION_Y_FIELD = "ty";
static const QString TRANSLATION_Z_FIELD = "tz";
static const QString JOINT_FIELD = "joint";
static const QString FREE_JOINT_FIELD = "freeJoint";
static const QString BLENDSHAPE_FIELD = "bs";
static const QString S3_URL = "http://public.highfidelity.io";
static const QString MODEL_URL = "/api/v1/models";
@ -192,6 +193,45 @@ bool ModelUploader::zip() {
mapping.insertMulti(FREE_JOINT_FIELD, "RightForeArm");
}
// mixamo blendshapes
if (!mapping.contains(BLENDSHAPE_FIELD) && geometry.applicationName == "mixamo.com") {
QVariantHash blendshapes;
blendshapes.insert("EyeBlink_L", QVariantList() << "Blink_Left" << 1.0);
blendshapes.insert("EyeBlink_R", QVariantList() << "Blink_Right" << 1.0);
blendshapes.insert("EyeSquint_L", QVariantList() << "Squint_Left" << 1.0);
blendshapes.insert("EyeSquint_R", QVariantList() << "Squint_Right" << 1.0);
blendshapes.insert("EyeOpen_L", QVariantList() << "EyesWide_Left" << 1.0);
blendshapes.insert("EyeOpen_R", QVariantList() << "EyesWide_Right" << 1.0);
blendshapes.insert("BrowsD_L", QVariantList() << "BrowsDown_Left" << 1.0);
blendshapes.insert("BrowsD_R", QVariantList() << "BrowsDown_Right" << 1.0);
blendshapes.insert("BrowsU_L", QVariantList() << "BrowsUp_Left" << 1.0);
blendshapes.insert("BrowsU_R", QVariantList() << "BrowsUp_Right" << 1.0);
blendshapes.insert("JawFwd", QVariantList() << "JawForeward" << 1.0);
blendshapes.insert("JawOpen", QVariantList() << "Jaw_Down" << 1.0);
blendshapes.insert("JawLeft", QVariantList() << "Jaw_Left" << 1.0);
blendshapes.insert("JawRight", QVariantList() << "Jaw_Right" << 1.0);
blendshapes.insert("JawChew", QVariantList() << "Jaw_Up" << 1.0);
blendshapes.insert("MouthLeft", QVariantList() << "Midmouth_Left" << 1.0);
blendshapes.insert("MouthRight", QVariantList() << "Midmouth_Right" << 1.0);
blendshapes.insert("MouthFrown_L", QVariantList() << "Frown_Left" << 1.0);
blendshapes.insert("MouthFrown_R", QVariantList() << "Frown_Right" << 1.0);
blendshapes.insert("MouthSmile_L", QVariantList() << "Smile_Left" << 1.0);
blendshapes.insert("MouthSmile_R", QVariantList() << "Smile_Right" << 1.0);
blendshapes.insert("LipsUpperUp", QVariantList() << "UpperLipUp_Left" << 0.5);
blendshapes.insertMulti("LipsUpperUp", QVariantList() << "UpperLipUp_Right" << 0.5);
blendshapes.insert("Puff", QVariantList() << "CheekPuff_Left" << 0.5);
blendshapes.insertMulti("Puff", QVariantList() << "CheekPuff_Right" << 0.5);
blendshapes.insert("Sneer", QVariantList() << "NoseScrunch_Left" << 0.5);
blendshapes.insertMulti("Sneer", QVariantList() << "NoseScrunch_Right" << 0.5);
blendshapes.insert("CheekSquint_L", QVariantList() << "Squint_Left" << 1.0);
blendshapes.insert("CheekSquint_R", QVariantList() << "Squint_Right" << 1.0);
blendshapes.insert("LipsPucker", QVariantList() << "MouthNarrow_Left" << 0.5);
blendshapes.insertMulti("LipsPucker", QVariantList() << "MouthNarrow_Right" << 0.5);
blendshapes.insert("LipsLowerDown", QVariantList() << "LowerLipDown_Left" << 0.5);
blendshapes.insertMulti("LipsLowerDown", QVariantList() << "LowerLipDown_Right" << 0.5);
mapping.insert(BLENDSHAPE_FIELD, blendshapes);
}
// open the dialog to configure the rest
ModelPropertiesDialog properties(_modelType, mapping, basePath, geometry);
if (properties.exec() == QDialog::Rejected) {
@ -381,11 +421,11 @@ void ModelUploader::uploadSuccess(const QJsonObject& jsonResponse) {
checkS3();
}
void ModelUploader::uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString) {
void ModelUploader::uploadFailed(QNetworkReply& errorReply) {
if (_progressDialog) {
_progressDialog->reject();
}
qDebug() << "Model upload failed (" << errorCode << "): " << errorString;
qDebug() << "Model upload failed (" << errorReply.error() << "): " << errorReply.errorString();
QMessageBox::warning(NULL,
QString("ModelUploader::uploadFailed()"),
QString("There was a problem with your upload, please try again later."),

View file

@ -43,7 +43,7 @@ private slots:
void checkJSON(const QJsonObject& jsonResponse);
void uploadUpdate(qint64 bytesSent, qint64 bytesTotal);
void uploadSuccess(const QJsonObject& jsonResponse);
void uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString);
void uploadFailed(QNetworkReply& errorReply);
void checkS3();
void processCheck();

View file

@ -18,14 +18,13 @@
#include "Application.h"
#include "UserLocationsModel.h"
static const QString PLACES_GET = "/api/v1/places";
static const QString PLACES_UPDATE = "/api/v1/places/%1";
static const QString PLACES_DELETE= "/api/v1/places/%1";
static const QString LOCATIONS_GET = "/api/v1/locations";
static const QString LOCATION_UPDATE_OR_DELETE = "/api/v1/locations/%1";
UserLocation::UserLocation(QString id, QString name, QString location) :
UserLocation::UserLocation(const QString& id, const QString& name, const QString& address) :
_id(id),
_name(name),
_location(location),
_address(address),
_previousName(name),
_updating(false) {
}
@ -35,10 +34,15 @@ void UserLocation::requestRename(const QString& newName) {
_updating = true;
JSONCallbackParameters callbackParams(this, "handleRenameResponse", this, "handleRenameError");
QJsonObject jsonNameObject;
jsonNameObject.insert("name", QJsonValue(newName));
jsonNameObject.insert("name", newName);
QJsonObject locationObject;
locationObject.insert("location", jsonNameObject);
QJsonDocument jsonDocument(jsonNameObject);
AccountManager::getInstance().authenticatedRequest(PLACES_UPDATE.arg(_id),
AccountManager::getInstance().authenticatedRequest(LOCATION_UPDATE_OR_DELETE.arg(_id),
QNetworkAccessManager::PutOperation,
callbackParams,
jsonDocument.toJson());
@ -54,7 +58,9 @@ void UserLocation::handleRenameResponse(const QJsonObject& responseData) {
QJsonValue status = responseData["status"];
if (!status.isUndefined() && status.toString() == "success") {
QString updatedName = responseData["data"].toObject()["name"].toString();
qDebug() << responseData;
QString updatedName = responseData["data"].toObject()["location"].toObject()["name"].toString();
qDebug() << "The updated name is" << updatedName;
_name = updatedName;
} else {
_name = _previousName;
@ -75,10 +81,10 @@ void UserLocation::handleRenameResponse(const QJsonObject& responseData) {
emit updated(_name);
}
void UserLocation::handleRenameError(QNetworkReply::NetworkError error, const QString& errorString) {
void UserLocation::handleRenameError(QNetworkReply& errorReply) {
_updating = false;
QString msg = "There was an error renaming location '" + _name + "': " + errorString;
QString msg = "There was an error renaming location '" + _name + "': " + errorReply.errorString();
qDebug() << msg;
QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg);
@ -90,7 +96,7 @@ void UserLocation::requestDelete() {
_updating = true;
JSONCallbackParameters callbackParams(this, "handleDeleteResponse", this, "handleDeleteError");
AccountManager::getInstance().authenticatedRequest(PLACES_DELETE.arg(_id),
AccountManager::getInstance().authenticatedRequest(LOCATION_UPDATE_OR_DELETE.arg(_id),
QNetworkAccessManager::DeleteOperation,
callbackParams);
}
@ -109,10 +115,10 @@ void UserLocation::handleDeleteResponse(const QJsonObject& responseData) {
}
}
void UserLocation::handleDeleteError(QNetworkReply::NetworkError error, const QString& errorString) {
void UserLocation::handleDeleteError(QNetworkReply& errorReply) {
_updating = false;
QString msg = "There was an error deleting location '" + _name + "': " + errorString;
QString msg = "There was an error deleting location '" + _name + "': " + errorReply.errorString();
qDebug() << msg;
QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg);
}
@ -153,7 +159,7 @@ void UserLocationsModel::refresh() {
endResetModel();
JSONCallbackParameters callbackParams(this, "handleLocationsResponse");
AccountManager::getInstance().authenticatedRequest(PLACES_GET,
AccountManager::getInstance().authenticatedRequest(LOCATIONS_GET,
QNetworkAccessManager::GetOperation,
callbackParams);
}
@ -165,14 +171,13 @@ void UserLocationsModel::handleLocationsResponse(const QJsonObject& responseData
QJsonValue status = responseData["status"];
if (!status.isUndefined() && status.toString() == "success") {
beginResetModel();
QJsonArray locations = responseData["data"].toObject()["places"].toArray();
QJsonArray locations = responseData["data"].toObject()["locations"].toArray();
for (QJsonArray::const_iterator it = locations.constBegin(); it != locations.constEnd(); it++) {
QJsonObject location = (*it).toObject();
QJsonObject address = location["address"].toObject();
QString locationAddress = "hifi://" + location["domain"].toObject()["name"].toString()
+ location["path"].toString();
UserLocation* userLocation = new UserLocation(location["id"].toString(), location["name"].toString(),
"hifi://" + address["domain"].toString()
+ "/" + address["position"].toString()
+ "/" + address["orientation"].toString());
locationAddress);
_locations.append(userLocation);
connect(userLocation, &UserLocation::deleted, this, &UserLocationsModel::removeLocation);
connect(userLocation, &UserLocation::updated, this, &UserLocationsModel::update);
@ -214,8 +219,8 @@ QVariant UserLocationsModel::data(const QModelIndex& index, int role) const {
return QVariant();
} else if (index.column() == NameColumn) {
return _locations[index.row()]->name();
} else if (index.column() == LocationColumn) {
return QVariant(_locations[index.row()]->location());
} else if (index.column() == AddressColumn) {
return QVariant(_locations[index.row()]->address());
}
}
@ -226,7 +231,7 @@ QVariant UserLocationsModel::headerData(int section, Qt::Orientation orientation
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case NameColumn: return "Name";
case LocationColumn: return "Location";
case AddressColumn: return "Address";
default: return QVariant();
}
}

View file

@ -20,20 +20,20 @@
class UserLocation : public QObject {
Q_OBJECT
public:
UserLocation(QString id, QString name, QString location);
UserLocation(const QString& id, const QString& name, const QString& address);
bool isUpdating() { return _updating; }
void requestRename(const QString& newName);
void requestDelete();
QString id() { return _id; }
QString name() { return _name; }
QString location() { return _location; }
const QString& id() { return _id; }
const QString& name() { return _name; }
const QString& address() { return _address; }
public slots:
void handleRenameResponse(const QJsonObject& responseData);
void handleRenameError(QNetworkReply::NetworkError error, const QString& errorString);
void handleRenameError(QNetworkReply& errorReply);
void handleDeleteResponse(const QJsonObject& responseData);
void handleDeleteError(QNetworkReply::NetworkError error, const QString& errorString);
void handleDeleteError(QNetworkReply& errorReply);
signals:
void updated(const QString& name);
@ -42,7 +42,7 @@ signals:
private:
QString _id;
QString _name;
QString _location;
QString _address;
QString _previousName;
bool _updating;
@ -65,7 +65,7 @@ public:
enum Columns {
NameColumn = 0,
LocationColumn
AddressColumn
};
public slots:

View file

@ -181,9 +181,10 @@ const glm::vec3 randVector() {
}
static TextRenderer* textRenderer(int mono) {
static TextRenderer* monoRenderer = new TextRenderer(MONO_FONT_FAMILY);
static TextRenderer* proportionalRenderer = new TextRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT);
static TextRenderer* inconsolataRenderer = new TextRenderer(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false);
static TextRenderer* monoRenderer = TextRenderer::getInstance(MONO_FONT_FAMILY);
static TextRenderer* proportionalRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY,
-1, -1, false, TextRenderer::SHADOW_EFFECT);
static TextRenderer* inconsolataRenderer = TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false);
switch (mono) {
case 1:
return monoRenderer;

View file

@ -11,6 +11,9 @@
#include <vector>
#include <QDesktopWidget>
#include <QWindow>
#include <glm/glm.hpp>
#include <glm/gtx/quaternion.hpp>
#include <glm/gtx/vector_angle.hpp>
@ -50,8 +53,8 @@ Avatar::Avatar() :
AvatarData(),
_skeletonModel(this),
_bodyYawDelta(0.0f),
_lastPosition(0.0f),
_velocity(0.0f),
_positionDeltaAccumulator(0.0f),
_lastVelocity(0.0f),
_acceleration(0.0f),
_angularVelocity(0.0f),
@ -204,15 +207,31 @@ void Avatar::simulate(float deltaTime) {
_displayNameAlpha = abs(_displayNameAlpha - _displayNameTargetAlpha) < 0.01f ? _displayNameTargetAlpha : _displayNameAlpha;
}
_position += _velocity * deltaTime;
// NOTE: we shouldn't extrapolate an Avatar instance forward in time...
// until velocity is included in AvatarData update message.
//_position += _velocity * deltaTime;
measureMotionDerivatives(deltaTime);
}
void Avatar::slamPosition(const glm::vec3& newPosition) {
AvatarData::setPosition(newPosition);
_positionDeltaAccumulator = glm::vec3(0.0f);
_velocity = glm::vec3(0.0f);
_lastVelocity = glm::vec3(0.0f);
}
void Avatar::applyPositionDelta(const glm::vec3& delta) {
_position += delta;
_positionDeltaAccumulator += delta;
}
void Avatar::measureMotionDerivatives(float deltaTime) {
// linear
float invDeltaTime = 1.0f / deltaTime;
_velocity = (_position - _lastPosition) * invDeltaTime;
_lastPosition = _position;
// Floating point error prevents us from computing velocity in a naive way
// (e.g. vel = (pos - oldPos) / dt) so instead we use _positionOffsetAccumulator.
_velocity = _positionDeltaAccumulator * invDeltaTime;
_positionDeltaAccumulator = glm::vec3(0.0f);
_acceleration = (_velocity - _lastVelocity) * invDeltaTime;
_lastVelocity = _velocity;
// angular
@ -223,20 +242,6 @@ void Avatar::measureMotionDerivatives(float deltaTime) {
_lastOrientation = getOrientation();
}
void Avatar::setPosition(const glm::vec3 position, bool overideReferential) {
AvatarData::setPosition(position, overideReferential);
_lastPosition = position;
_velocity = glm::vec3(0.0f);
_lastVelocity = glm::vec3(0.0f);
}
void Avatar::slamPosition(const glm::vec3& newPosition) {
_position = newPosition;
_lastPosition = newPosition;
_velocity = glm::vec3(0.0f);
_lastVelocity = glm::vec3(0.0f);
}
void Avatar::setMouseRay(const glm::vec3 &origin, const glm::vec3 &direction) {
_mouseRayOrigin = origin;
_mouseRayDirection = direction;
@ -248,8 +253,9 @@ enum TextRendererType {
};
static TextRenderer* textRenderer(TextRendererType type) {
static TextRenderer* chatRenderer = new TextRenderer(SANS_FONT_FAMILY, 24, -1, false, TextRenderer::SHADOW_EFFECT);
static TextRenderer* displayNameRenderer = new TextRenderer(SANS_FONT_FAMILY, 12, -1, false, TextRenderer::NO_EFFECT);
static TextRenderer* chatRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, 24, -1,
false, TextRenderer::SHADOW_EFFECT);
static TextRenderer* displayNameRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, 12);
switch(type) {
case CHAT:
@ -667,7 +673,8 @@ void Avatar::renderDisplayName() {
if (success) {
double textWindowHeight = abs(result1[1] - result0[1]);
float scaleFactor = (textWindowHeight > EPSILON) ? 1.0f / textWindowHeight : 1.0f;
float scaleFactor = QApplication::desktop()->windowHandle()->devicePixelRatio() *
((textWindowHeight > EPSILON) ? 1.0f / textWindowHeight : 1.0f);
glScalef(scaleFactor, scaleFactor, 1.0);
glScalef(1.0f, -1.0f, 1.0f); // TextRenderer::draw paints the text upside down in y axis

View file

@ -161,9 +161,13 @@ public:
/// \param vector position to be scaled. Will store the result
void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const;
void setPosition(const glm::vec3 position, bool overideReferential = false);
void slamPosition(const glm::vec3& newPosition);
void slamPosition(const glm::vec3& position);
// Call this when updating Avatar position with a delta. This will allow us to
// _accurately_ measure position changes and compute the resulting velocity
// (otherwise floating point error will cause problems at large positions).
void applyPositionDelta(const glm::vec3& delta);
public slots:
void updateCollisionGroups();
@ -176,12 +180,15 @@ protected:
QVector<Model*> _attachmentModels;
float _bodyYawDelta;
glm::vec3 _velocity;
// These position histories and derivatives are in the world-frame.
// The derivatives are the MEASURED results of all external and internal forces
// and are therefor READ-ONLY --> motion control of the Avatar is NOT obtained
// by setting these values.
glm::vec3 _lastPosition;
glm::vec3 _velocity;
// Floating point error prevents us from accurately measuring velocity using a naive approach
// (e.g. vel = (pos - lastPos)/dt) so instead we use _positionDeltaAccumulator.
glm::vec3 _positionDeltaAccumulator;
glm::vec3 _lastVelocity;
glm::vec3 _acceleration;
glm::vec3 _angularVelocity;

View file

@ -33,7 +33,7 @@ ModelReferential::ModelReferential(Referential* referential, EntityTree* tree, A
const EntityItem* item = _tree->findEntityByID(_entityID);
if (item != NULL) {
_refScale = item->getRadius();
_refScale = item->getLargestDimension();
_refRotation = item->getRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
update();
@ -52,7 +52,7 @@ ModelReferential::ModelReferential(const QUuid& entityID, EntityTree* tree, Avat
return;
}
_refScale = item->getRadius();
_refScale = item->getLargestDimension();
_refRotation = item->getRotation();
_refPosition = item->getPosition() * (float)TREE_SCALE;
@ -69,8 +69,8 @@ void ModelReferential::update() {
}
bool somethingChanged = false;
if (item->getRadius() != _refScale) {
_refScale = item->getRadius();
if (item->getLargestDimension() != _refScale) {
_refScale = item->getLargestDimension();
_avatar->setTargetScale(_refScale * _scale, true);
somethingChanged = true;
}
@ -109,7 +109,7 @@ JointReferential::JointReferential(Referential* referential, EntityTree* tree, A
const EntityItem* item = _tree->findEntityByID(_entityID);
const Model* model = getModel(item);
if (!isValid() || model == NULL || _jointIndex >= (uint32_t)(model->getJointStateCount())) {
_refScale = item->getRadius();
_refScale = item->getLargestDimension();
model->getJointRotationInWorldFrame(_jointIndex, _refRotation);
model->getJointPositionInWorldFrame(_jointIndex, _refPosition);
}
@ -129,7 +129,7 @@ JointReferential::JointReferential(uint32_t jointIndex, const QUuid& entityID, E
return;
}
_refScale = item->getRadius();
_refScale = item->getLargestDimension();
model->getJointRotationInWorldFrame(_jointIndex, _refRotation);
model->getJointPositionInWorldFrame(_jointIndex, _refPosition);
@ -147,8 +147,8 @@ void JointReferential::update() {
}
bool somethingChanged = false;
if (item->getRadius() != _refScale) {
_refScale = item->getRadius();
if (item->getLargestDimension() != _refScale) {
_refScale = item->getLargestDimension();
_avatar->setTargetScale(_refScale * _scale, true);
somethingChanged = true;
}

View file

@ -21,6 +21,7 @@
#include <QtCore/QTimer>
#include <AccountManager.h>
#include <AddressManager.h>
#include <GeometryUtil.h>
#include <NodeList.h>
#include <PacketHeaders.h>
@ -90,6 +91,9 @@ MyAvatar::MyAvatar() :
_skeletonModel.setEnableShapes(true);
_skeletonModel.buildRagdoll();
// connect to AddressManager signal for location jumps
connect(&AddressManager::getInstance(), &AddressManager::locationChangeRequired, this, &MyAvatar::goToLocation);
}
MyAvatar::~MyAvatar() {
@ -226,7 +230,7 @@ void MyAvatar::simulate(float deltaTime) {
const float MAX_RAGDOLL_DISPLACEMENT_2 = 1.0f;
float length2 = glm::length2(ragdollDisplacement);
if (length2 > EPSILON && length2 < MAX_RAGDOLL_DISPLACEMENT_2) {
setPosition(getPosition() + ragdollDisplacement);
applyPositionDelta(ragdollDisplacement);
}
} else {
_skeletonModel.moveShapesTowardJoints(1.0f);
@ -796,9 +800,11 @@ void MyAvatar::loadData(QSettings* settings) {
getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f));
_position.x = loadSetting(settings, "position_x", START_LOCATION.x);
_position.y = loadSetting(settings, "position_y", START_LOCATION.y);
_position.z = loadSetting(settings, "position_z", START_LOCATION.z);
glm::vec3 newPosition;
newPosition.x = loadSetting(settings, "position_x", START_LOCATION.x);
newPosition.y = loadSetting(settings, "position_y", START_LOCATION.y);
newPosition.z = loadSetting(settings, "position_z", START_LOCATION.z);
slamPosition(newPosition);
getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f));
@ -1265,15 +1271,11 @@ void MyAvatar::updatePosition(float deltaTime) {
if (_motionBehaviors & AVATAR_MOTION_MOTOR_ENABLED) {
glm::vec3 targetVelocity = _motorVelocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) {
// rotate _motorVelocity into world frame
// rotate targetVelocity into world frame
glm::quat rotation = getHead()->getCameraOrientation();
targetVelocity = rotation * _motorVelocity;
}
glm::vec3 targetDirection(0.0f);
if (glm::length2(targetVelocity) > EPSILON) {
targetDirection = glm::normalize(targetVelocity);
}
glm::vec3 deltaVelocity = targetVelocity - velocity;
if (_motionBehaviors & AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY && glm::length2(_gravity) > EPSILON) {
@ -1305,7 +1307,7 @@ void MyAvatar::updatePosition(float deltaTime) {
// update position
const float MIN_AVATAR_SPEED = 0.075f;
if (speed > MIN_AVATAR_SPEED) {
_position += velocity * deltaTime;
applyPositionDelta(deltaTime * velocity);
}
}
@ -1783,11 +1785,6 @@ void MyAvatar::maybeUpdateBillboard() {
sendBillboardPacket();
}
void MyAvatar::goHome() {
qDebug("Going Home!");
slamPosition(START_LOCATION);
}
void MyAvatar::increaseSize() {
if ((1.0f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) {
_targetScale *= (1.0f + SCALING_RATIO);
@ -1807,45 +1804,26 @@ void MyAvatar::resetSize() {
qDebug("Reseted scale to %f", _targetScale);
}
void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
QJsonObject locationObject = jsonObject["data"].toObject()["address"].toObject();
bool isOnline = jsonObject["data"].toObject()["online"].toBool();
if (isOnline ) {
goToLocationFromAddress(locationObject);
} else {
QMessageBox::warning(Application::getInstance()->getWindow(), "", "The user is not online.");
void MyAvatar::goToLocation(const glm::vec3& newPosition, bool hasOrientation, const glm::vec3& newOrientation) {
glm::quat quatOrientation = getOrientation();
qDebug().nospace() << "MyAvatar goToLocation - moving to " << newPosition.x << ", "
<< newPosition.y << ", " << newPosition.z;
if (hasOrientation) {
qDebug().nospace() << "MyAvatar goToLocation - new orientation is "
<< newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z;
// orient the user to face the target
glm::quat quatOrientation = glm::quat(glm::radians(newOrientation))
* glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
setOrientation(quatOrientation);
}
}
void MyAvatar::goToLocationFromAddress(const QJsonObject& locationObject) {
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
sendKillAvatar();
QString positionString = locationObject["position"].toString();
QString orientationString = locationObject["orientation"].toString();
QString domainHostnameString = locationObject["domain"].toString();
qDebug() << "Changing domain to" << domainHostnameString <<
", position to" << positionString <<
", and orientation to" << orientationString;
QStringList coordinateItems = positionString.split(',');
QStringList orientationItems = orientationString.split(',');
NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString);
// orient the user to face the target
glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(),
orientationItems[1].toFloat(),
orientationItems[2].toFloat())))
* glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
setOrientation(newOrientation);
// move the user a couple units away
const float DISTANCE_TO_USER = 2.0f;
glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(),
coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
slamPosition(newPosition);
glm::vec3 shiftedPosition = newPosition - quatOrientation * IDENTITY_FRONT * DISTANCE_TO_USER;
slamPosition(shiftedPosition);
emit transformChanged();
}
@ -1992,3 +1970,9 @@ glm::vec3 MyAvatar::getLaserPointerTipPosition(const PalmData* palm) {
return palm->getPosition();
}
void MyAvatar::clearDriveKeys() {
for (int i = 0; i < MAX_DRIVE_KEYS; ++i) {
_driveKeys[i] = 0.0f;
}
}

View file

@ -97,6 +97,7 @@ public:
AttachmentData loadAttachmentData(const QUrl& modelURL, const QString& jointName = QString()) const;
// Set what driving keys are being pressed to control thrust levels
void clearDriveKeys();
void setDriveKeys(int key, float val) { _driveKeys[key] = val; };
bool getDriveKeys(int key) { return _driveKeys[key] != 0.f; };
void jump() { _shouldJump = true; };
@ -149,19 +150,19 @@ public:
const PlayerPointer getPlayer() const { return _player; }
public slots:
void goHome();
void increaseSize();
void decreaseSize();
void resetSize();
void goToLocationFromResponse(const QJsonObject& jsonObject);
void goToLocationFromAddress(const QJsonObject& jsonObject);
void goToLocation(const glm::vec3& newPosition, bool hasOrientation = false, const glm::vec3& newOrientation = glm::vec3());
// Set/Get update the thrust that will move the avatar around
void addThrust(glm::vec3 newThrust) { _thrust += newThrust; };
glm::vec3 getThrust() { return _thrust; };
void setThrust(glm::vec3 newThrust) { _thrust = newThrust; }
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
void updateMotionBehaviorsFromMenu();
void onToggleRagdoll();

View file

@ -60,6 +60,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
setRotation(_owningAvatar->getOrientation() * refOrientation);
const float MODEL_SCALE = 0.0006f;
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale() * MODEL_SCALE);
setBlendshapeCoefficients(_owningAvatar->getHead()->getBlendshapeCoefficients());
Model::simulate(deltaTime, fullUpdate);

View file

@ -151,7 +151,7 @@ void Faceshift::connectSocket() {
qDebug("Faceshift: Connecting...");
}
_tcpSocket.connectToHost("localhost", FACESHIFT_PORT);
_tcpSocket.connectToHost(Menu::getInstance()->getFaceshiftHostname(), FACESHIFT_PORT);
_tracking = false;
}
}

View file

@ -53,6 +53,7 @@ unsigned int OculusManager::_frameIndex = 0;
bool OculusManager::_frameTimingActive = false;
bool OculusManager::_programInitialized = false;
Camera* OculusManager::_camera = NULL;
int OculusManager::_activeEyeIndex = -1;
#endif
@ -330,6 +331,8 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p
//Render each eye into an fbo
for (int eyeIndex = 0; eyeIndex < ovrEye_Count; eyeIndex++) {
_activeEyeIndex = eyeIndex;
#if defined(__APPLE__) || defined(_WIN32)
ovrEyeType eye = _ovrHmd->EyeRenderOrder[eyeIndex];
#else
@ -363,6 +366,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p
Application::getInstance()->displaySide(*_camera);
applicationOverlay.displayOverlayTextureOculus(*_camera);
_activeEyeIndex = -1;
}
//Wait till time-warp to reduce latency
@ -528,3 +532,16 @@ QSize OculusManager::getRenderTargetSize() {
return QSize(100, 100);
#endif
}
void OculusManager::overrideOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) {
#ifdef HAVE_LIBOVR
if (_activeEyeIndex != -1) {
const ovrFovPort& port = _eyeFov[_activeEyeIndex];
right = nearVal * port.RightTan;
left = -nearVal * port.LeftTan;
top = nearVal * port.UpTan;
bottom = -nearVal * port.DownTan;
}
#endif
}

View file

@ -43,6 +43,9 @@ public:
static glm::vec3 getRelativePosition();
static QSize getRenderTargetSize();
static void overrideOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane);
private:
#ifdef HAVE_LIBOVR
static void generateDistortionMesh();
@ -92,6 +95,7 @@ private:
static bool _frameTimingActive;
static bool _programInitialized;
static Camera* _camera;
static int _activeEyeIndex;
#endif
};

View file

@ -207,10 +207,11 @@ void PrioVR::renderCalibrationCountdown() {
Application::getInstance()->disconnect(this);
return;
}
static TextRenderer textRenderer(MONO_FONT_FAMILY, 18, QFont::Bold, false, TextRenderer::OUTLINE_EFFECT, 2);
static TextRenderer* textRenderer = TextRenderer::getInstance(MONO_FONT_FAMILY, 18, QFont::Bold,
false, TextRenderer::OUTLINE_EFFECT, 2);
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
textRenderer.draw((Application::getInstance()->getGLWidget()->getDeviceWidth() -
textRenderer.computeWidth(text.constData())) / 2, Application::getInstance()->getGLWidget()->getDeviceHeight() / 2,
textRenderer->draw((Application::getInstance()->getGLWidget()->width() -
textRenderer->computeWidth(text.constData())) / 2, Application::getInstance()->getGLWidget()->height() / 2,
text);
#endif
}

View file

@ -396,10 +396,10 @@ void SixenseManager::emulateMouse(PalmData* palm, int index) {
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
// Get the pixel range over which the xAngle and yAngle are scaled
float cursorRange = widget->getDeviceWidth() * getCursorPixelRangeMult();
float cursorRange = widget->width() * getCursorPixelRangeMult();
pos.setX(widget->getDeviceWidth() / 2.0f + cursorRange * xAngle);
pos.setY(widget->getDeviceHeight() / 2.0f + cursorRange * yAngle);
pos.setX(widget->width() / 2.0f + cursorRange * xAngle);
pos.setY(widget->height() / 2.0f + cursorRange * yAngle);
}

View file

@ -25,6 +25,7 @@ int TV3DManager::_screenHeight = 1;
double TV3DManager::_aspect = 1.0;
eyeFrustum TV3DManager::_leftEye;
eyeFrustum TV3DManager::_rightEye;
eyeFrustum* TV3DManager::_activeEye = NULL;
bool TV3DManager::isConnected() {
@ -93,8 +94,6 @@ void TV3DManager::display(Camera& whichCamera) {
int portalW = Application::getInstance()->getGLWidget()->getDeviceWidth() / 2;
int portalH = Application::getInstance()->getGLWidget()->getDeviceHeight();
const bool glowEnabled = Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect);
ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay();
// We only need to render the overlays to a texture once, then we just render the texture as a quad
@ -102,9 +101,7 @@ void TV3DManager::display(Camera& whichCamera) {
applicationOverlay.renderOverlay(true);
const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::UserInterface);
if (glowEnabled) {
Application::getInstance()->getGlowEffect()->prepare();
}
Application::getInstance()->getGlowEffect()->prepare();
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -115,7 +112,7 @@ void TV3DManager::display(Camera& whichCamera) {
glPushMatrix();
{
_activeEye = &_leftEye;
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); // reset projection matrix
glFrustum(_leftEye.left, _leftEye.right, _leftEye.bottom, _leftEye.top, nearZ, farZ); // set left view frustum
@ -132,6 +129,7 @@ void TV3DManager::display(Camera& whichCamera) {
if (displayOverlays) {
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
}
_activeEye = NULL;
}
glPopMatrix();
glDisable(GL_SCISSOR_TEST);
@ -144,6 +142,7 @@ void TV3DManager::display(Camera& whichCamera) {
glScissor(portalX, portalY, portalW, portalH);
glPushMatrix();
{
_activeEye = &_rightEye;
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); // reset projection matrix
glFrustum(_rightEye.left, _rightEye.right, _rightEye.bottom, _rightEye.top, nearZ, farZ); // set left view frustum
@ -160,6 +159,7 @@ void TV3DManager::display(Camera& whichCamera) {
if (displayOverlays) {
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
}
_activeEye = NULL;
}
glPopMatrix();
glDisable(GL_SCISSOR_TEST);
@ -168,7 +168,15 @@ void TV3DManager::display(Camera& whichCamera) {
glViewport(0, 0, Application::getInstance()->getGLWidget()->getDeviceWidth(),
Application::getInstance()->getGLWidget()->getDeviceHeight());
if (glowEnabled) {
Application::getInstance()->getGlowEffect()->render();
Application::getInstance()->getGlowEffect()->render();
}
void TV3DManager::overrideOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) {
if (_activeEye) {
left = _activeEye->left;
right = _activeEye->right;
bottom = _activeEye->bottom;
top = _activeEye->top;
}
}

View file

@ -14,6 +14,8 @@
#include <iostream>
#include <glm/glm.hpp>
class Camera;
struct eyeFrustum {
@ -32,6 +34,8 @@ public:
static bool isConnected();
static void configureCamera(Camera& camera, int screenWidth, int screenHeight);
static void display(Camera& whichCamera);
static void overrideOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane);
private:
static void setFrustum(Camera& whichCamera);
static int _screenWidth;
@ -39,6 +43,7 @@ private:
static double _aspect;
static eyeFrustum _leftEye;
static eyeFrustum _rightEye;
static eyeFrustum* _activeEye;
};
#endif // hifi_TV3DManager_h

View file

@ -192,7 +192,71 @@ bool EntityTreeRenderer::shouldRenderEntity(float largestDimension, float distan
return (distanceToCamera <= visibleDistanceAtScale);
}
void EntityTreeRenderer::renderProxies(const EntityItem* entity, RenderArgs* args) {
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
if (!isShadowMode && displayModelBounds) {
PerformanceTimer perfTimer("renderProxies");
AACube maxCube = entity->getMaximumAACube();
AACube minCube = entity->getMinimumAACube();
AABox entityBox = entity->getAABox();
maxCube.scale((float)TREE_SCALE);
minCube.scale((float)TREE_SCALE);
entityBox.scale((float)TREE_SCALE);
glm::vec3 maxCenter = maxCube.calcCenter();
glm::vec3 minCenter = minCube.calcCenter();
glm::vec3 entityBoxCenter = entityBox.calcCenter();
glm::vec3 entityBoxScale = entityBox.getScale();
// draw the max bounding cube
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(maxCenter.x, maxCenter.y, maxCenter.z);
glutWireCube(maxCube.getScale());
glPopMatrix();
// draw the min bounding cube
glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
glPushMatrix();
glTranslatef(minCenter.x, minCenter.y, minCenter.z);
glutWireCube(minCube.getScale());
glPopMatrix();
// draw the entityBox bounding box
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glPushMatrix();
glTranslatef(entityBoxCenter.x, entityBoxCenter.y, entityBoxCenter.z);
glScalef(entityBoxScale.x, entityBoxScale.y, entityBoxScale.z);
glutWireCube(1.0f);
glPopMatrix();
glm::vec3 position = entity->getPosition() * (float)TREE_SCALE;
glm::vec3 center = entity->getCenter() * (float)TREE_SCALE;
glm::vec3 dimensions = entity->getDimensions() * (float)TREE_SCALE;
glm::quat rotation = entity->getRotation();
glColor4f(1.0f, 0.0f, 1.0f, 1.0f);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
glScalef(dimensions.x, dimensions.y, dimensions.z);
glutWireCube(1.0f);
glPopMatrix();
glPopMatrix();
}
}
void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
bool wantDebug = false;
args->_elementsTouched++;
// actually render it here...
// we need to iterate the actual entityItems of the element
@ -214,26 +278,48 @@ void EntityTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
for (uint16_t i = 0; i < numberOfEntities; i++) {
EntityItem* entityItem = entityItems[i];
// render entityItem
AACube entityCube = entityItem->getAACube();
entityCube.scale(TREE_SCALE);
// TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum...
float distance = distanceToCamera(entityCube.calcCenter(), *args->_viewFrustum);
if (shouldRenderEntity(entityCube.getLargestDimension(), distance) &&
args->_viewFrustum->cubeInFrustum(entityCube) != ViewFrustum::OUTSIDE) {
if (entityItem->isVisible()) {
// render entityItem
AABox entityBox = entityItem->getAABox();
Glower* glower = NULL;
if (entityItem->getGlowLevel() > 0.0f) {
glower = new Glower(entityItem->getGlowLevel());
entityBox.scale(TREE_SCALE);
// TODO: some entity types (like lights) might want to be rendered even
// when they are outside of the view frustum...
float distance = distanceToCamera(entityBox.calcCenter(), *args->_viewFrustum);
if (wantDebug) {
qDebug() << "------- renderElement() ----------";
qDebug() << " type:" << EntityTypes::getEntityTypeName(entityItem->getType());
if (entityItem->getType() == EntityTypes::Model) {
ModelEntityItem* modelEntity = static_cast<ModelEntityItem*>(entityItem);
qDebug() << " url:" << modelEntity->getModelURL();
}
qDebug() << " entityBox:" << entityBox;
qDebug() << " dimensions:" << entityItem->getDimensionsInMeters() << "in meters";
qDebug() << " largestDimension:" << entityBox.getLargestDimension() << "in meters";
qDebug() << " shouldRender:" << shouldRenderEntity(entityBox.getLargestDimension(), distance);
qDebug() << " in frustum:" << (args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE);
}
entityItem->render(args);
if (glower) {
delete glower;
if (shouldRenderEntity(entityBox.getLargestDimension(), distance) &&
args->_viewFrustum->boxInFrustum(entityBox) != ViewFrustum::OUTSIDE) {
renderProxies(entityItem, args);
Glower* glower = NULL;
if (entityItem->getGlowLevel() > 0.0f) {
glower = new Glower(entityItem->getGlowLevel());
}
entityItem->render(args);
if (glower) {
delete glower;
}
} else {
args->_itemsOutOfView++;
}
} else {
args->_itemsOutOfView++;
}
}
}

View file

@ -79,6 +79,7 @@ private:
float distanceToCamera(const glm::vec3& center, const ViewFrustum& viewFrustum) const;
bool shouldRenderEntity(float largestDimension, float distanceToCamera) const;
void renderProxies(const EntityItem* entity, RenderArgs* args);
};

View file

@ -32,12 +32,13 @@ EntityItem* RenderableBoxEntityItem::factory(const EntityItemID& entityID, const
void RenderableBoxEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableBoxEntityItem::render");
assert(getType() == EntityTypes::Box);
glm::vec3 position = getPosition() * (float)TREE_SCALE;
float size = getSize() * (float)TREE_SCALE;
glm::vec3 position = getPositionInMeters();
glm::vec3 center = getCenter() * (float)TREE_SCALE;
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
glm::vec3 halfDimensions = dimensions / 2.0f;
glm::quat rotation = getRotation();
const bool useGlutCube = false;
const bool useGlutCube = true;
if (useGlutCube) {
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
@ -45,10 +46,14 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glutSolidCube(size);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
glScalef(dimensions.x, dimensions.y, dimensions.z);
glutSolidCube(1.0f);
glPopMatrix();
glPopMatrix();
} else {
static GLfloat vertices[] = { 1, 1, 1, -1, 1, 1, -1,-1, 1, 1,-1, 1, // v0,v1,v2,v3 (front)
1, 1, 1, 1,-1, 1, 1,-1,-1, 1, 1,-1, // v0,v3,v4,v5 (right)
1, 1, 1, 1, 1,-1, -1, 1,-1, -1, 1, 1, // v0,v5,v6,v1 (top)
@ -79,20 +84,19 @@ void RenderableBoxEntityItem::render(RenderArgs* args) {
glNormalPointer(GL_FLOAT, 0, normals);
glVertexPointer(3, GL_FLOAT, 0, vertices);
//glEnable(GL_BLEND);
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
// we need to do half the size because the geometry in the VBOs are from -1,-1,-1 to 1,1,1
float halfSize = size/2.0f;
glScalef(halfSize, halfSize, halfSize);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
// we need to do half the size because the geometry in the VBOs are from -1,-1,-1 to 1,1,1
glScalef(halfDimensions.x, halfDimensions.y, halfDimensions.z);
glDrawElements(GL_TRIANGLES, 36, GL_UNSIGNED_BYTE, indices);
glPopMatrix();
glPopMatrix();
glDisableClientState(GL_VERTEX_ARRAY); // disable vertex arrays

View file

@ -65,8 +65,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
bool drawAsModel = hasModel();
glm::vec3 position = getPosition() * (float)TREE_SCALE;
float radius = getRadius() * (float)TREE_SCALE;
float size = getSize() * (float)TREE_SCALE;
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
if (drawAsModel) {
glPushMatrix();
@ -98,8 +98,8 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
glm::quat rotation = getRotation();
if (needsSimulation() && _model->isActive()) {
_model->setScaleToFit(true, radius * 2.0f);
_model->setSnapModelToCenter(true);
_model->setScaleToFit(true, dimensions);
_model->setSnapModelToRegistrationPoint(true, getRegistrationPoint());
_model->setRotation(rotation);
_model->setTranslation(position);
@ -128,52 +128,6 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
glutWireCube(size);
glPopMatrix();
}
bool isShadowMode = args->_renderMode == OctreeRenderer::SHADOW_RENDER_MODE;
bool displayModelBounds = Menu::getInstance()->isOptionChecked(MenuOption::DisplayModelBounds);
if (!isShadowMode && displayModelBounds) {
PerformanceTimer perfTimer("displayModelBounds");
glm::vec3 unRotatedMinimum = _model->getUnscaledMeshExtents().minimum;
glm::vec3 unRotatedMaximum = _model->getUnscaledMeshExtents().maximum;
glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum;
float width = unRotatedExtents.x;
float height = unRotatedExtents.y;
float depth = unRotatedExtents.z;
Extents rotatedExtents = _model->getUnscaledMeshExtents();
calculateRotatedExtents(rotatedExtents, rotation);
glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum;
const glm::vec3& modelScale = _model->getScale();
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
// draw the orignal bounding cube
glColor4f(1.0f, 1.0f, 0.0f, 1.0f);
glutWireCube(size);
// draw the rotated bounding cube
glColor4f(0.0f, 0.0f, 1.0f, 1.0f);
glPushMatrix();
glScalef(rotatedSize.x * modelScale.x, rotatedSize.y * modelScale.y, rotatedSize.z * modelScale.z);
glutWireCube(1.0);
glPopMatrix();
// draw the model relative bounding box
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glScalef(width * modelScale.x, height * modelScale.y, depth * modelScale.z);
glColor3f(0.0f, 1.0f, 0.0f);
glutWireCube(1.0);
glPopMatrix();
}
} else {
// if we couldn't get a model, then just draw a cube
glColor3ub(getColor()[RED_INDEX],getColor()[GREEN_INDEX],getColor()[BLUE_INDEX]);

View file

@ -31,12 +31,24 @@ EntityItem* RenderableSphereEntityItem::factory(const EntityItemID& entityID, co
void RenderableSphereEntityItem::render(RenderArgs* args) {
PerformanceTimer perfTimer("RenderableSphereEntityItem::render");
assert(getType() == EntityTypes::Sphere);
glm::vec3 position = getPosition() * (float)TREE_SCALE;
float radius = getRadius() * (float)TREE_SCALE;
glm::vec3 position = getPositionInMeters();
glm::vec3 center = getCenterInMeters();
glm::vec3 dimensions = getDimensions() * (float)TREE_SCALE;
glm::quat rotation = getRotation();
glColor3ub(getColor()[RED_INDEX], getColor()[GREEN_INDEX], getColor()[BLUE_INDEX]);
glPushMatrix();
glTranslatef(position.x, position.y, position.z);
glutSolidSphere(radius, 15, 15);
glm::vec3 axis = glm::axis(rotation);
glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z);
glPushMatrix();
glm::vec3 positionToCenter = center - position;
glTranslatef(positionToCenter.x, positionToCenter.y, positionToCenter.z);
glScalef(dimensions.x, dimensions.y, dimensions.z);
glutSolidSphere(0.5f, 15, 15);
glPopMatrix();
glPopMatrix();
};

View file

@ -9,43 +9,33 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QMessageBox>
#include <qjsonobject.h>
#include <AccountManager.h>
#include "Application.h"
#include "LocationManager.h"
#include <UserActivityLogger.h>
const QString GET_USER_ADDRESS = "/api/v1/users/%1/address";
const QString GET_PLACE_ADDRESS = "/api/v1/places/%1";
const QString GET_ADDRESSES = "/api/v1/addresses/%1";
const QString POST_PLACE_CREATE = "/api/v1/places/";
LocationManager::LocationManager() {
};
const QString POST_LOCATION_CREATE = "/api/v1/locations/";
LocationManager& LocationManager::getInstance() {
static LocationManager sharedInstance;
return sharedInstance;
}
const QString UNKNOWN_ERROR_MESSAGE = "Unknown error creating named location. Please try again!";
void LocationManager::namedLocationDataReceived(const QJsonObject& data) {
if (data.isEmpty()) {
return;
}
if (data.contains("status") && data["status"].toString() == "success") {
emit creationCompleted(LocationManager::Created);
emit creationCompleted(QString());
} else {
emit creationCompleted(LocationManager::AlreadyExists);
emit creationCompleted(UNKNOWN_ERROR_MESSAGE);
}
}
void LocationManager::errorDataReceived(QNetworkReply::NetworkError error, const QString& message) {
emit creationCompleted(LocationManager::SystemError);
}
void LocationManager::createNamedLocation(NamedLocation* namedLocation) {
AccountManager& accountManager = AccountManager::getInstance();
if (accountManager.isLoggedIn()) {
@ -55,185 +45,45 @@ void LocationManager::createNamedLocation(NamedLocation* namedLocation) {
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "errorDataReceived";
accountManager.authenticatedRequest(POST_PLACE_CREATE, QNetworkAccessManager::PostOperation,
accountManager.authenticatedRequest(POST_LOCATION_CREATE, QNetworkAccessManager::PostOperation,
callbackParams, namedLocation->toJsonString().toUtf8());
}
}
void LocationManager::goTo(QString destination) {
const QString USER_DESTINATION_TYPE = "user";
const QString PLACE_DESTINATION_TYPE = "place";
const QString OTHER_DESTINATION_TYPE = "coordinate_or_username";
void LocationManager::errorDataReceived(QNetworkReply& errorReply) {
if (destination.startsWith("@")) {
// remove '@' and go to user
QString destinationUser = destination.remove(0, 1);
UserActivityLogger::getInstance().wentTo(USER_DESTINATION_TYPE, destinationUser);
goToUser(destinationUser);
return;
}
if (destination.startsWith("#")) {
// remove '#' and go to named place
QString destinationPlace = destination.remove(0, 1);
UserActivityLogger::getInstance().wentTo(PLACE_DESTINATION_TYPE, destinationPlace);
goToPlace(destinationPlace);
return;
}
// go to coordinate destination or to Username
if (!goToDestination(destination)) {
destination = QString(QUrl::toPercentEncoding(destination));
UserActivityLogger::getInstance().wentTo(OTHER_DESTINATION_TYPE, destination);
if (errorReply.header(QNetworkRequest::ContentTypeHeader).toString().startsWith("application/json")) {
// we have some JSON error data we can parse for our error message
QJsonDocument responseJson = QJsonDocument::fromJson(errorReply.readAll());
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = this;
callbackParams.jsonCallbackMethod = "goToAddressFromResponse";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "handleAddressLookupError";
QJsonObject dataObject = responseJson.object()["data"].toObject();
AccountManager::getInstance().authenticatedRequest(GET_ADDRESSES.arg(destination),
QNetworkAccessManager::GetOperation,
callbackParams);
}
}
void LocationManager::goToAddressFromResponse(const QJsonObject& responseData) {
QJsonValue status = responseData["status"];
const QJsonObject& data = responseData["data"].toObject();
const QJsonValue& userObject = data["user"];
const QJsonValue& placeObject = data["place"];
if (!placeObject.isUndefined() && !userObject.isUndefined()) {
emit multipleDestinationsFound(userObject.toObject(), placeObject.toObject());
} else if (placeObject.isUndefined()) {
Application::getInstance()->getAvatar()->goToLocationFromAddress(userObject.toObject()["address"].toObject());
} else {
Application::getInstance()->getAvatar()->goToLocationFromAddress(placeObject.toObject()["address"].toObject());
}
}
void LocationManager::goToUser(QString userName) {
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar();
callbackParams.jsonCallbackMethod = "goToLocationFromResponse";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "handleAddressLookupError";
userName = QString(QUrl::toPercentEncoding(userName));
AccountManager::getInstance().authenticatedRequest(GET_USER_ADDRESS.arg(userName),
QNetworkAccessManager::GetOperation,
callbackParams);
}
void LocationManager::goToPlace(QString placeName) {
JSONCallbackParameters callbackParams;
callbackParams.jsonCallbackReceiver = Application::getInstance()->getAvatar();
callbackParams.jsonCallbackMethod = "goToLocationFromResponse";
callbackParams.errorCallbackReceiver = this;
callbackParams.errorCallbackMethod = "handleAddressLookupError";
placeName = QString(QUrl::toPercentEncoding(placeName));
AccountManager::getInstance().authenticatedRequest(GET_PLACE_ADDRESS.arg(placeName),
QNetworkAccessManager::GetOperation,
callbackParams);
}
void LocationManager::goToOrientation(QString orientation) {
if (orientation.isEmpty()) {
return;
}
QStringList orientationItems = orientation.remove(' ').split(QRegExp("_|,"), QString::SkipEmptyParts);
const int NUMBER_OF_ORIENTATION_ITEMS = 4;
const int W_ITEM = 0;
const int X_ITEM = 1;
const int Y_ITEM = 2;
const int Z_ITEM = 3;
if (orientationItems.size() == NUMBER_OF_ORIENTATION_ITEMS) {
// replace last occurrence of '_' with decimal point
replaceLastOccurrence('-', '.', orientationItems[W_ITEM]);
replaceLastOccurrence('-', '.', orientationItems[X_ITEM]);
replaceLastOccurrence('-', '.', orientationItems[Y_ITEM]);
replaceLastOccurrence('-', '.', orientationItems[Z_ITEM]);
double w = orientationItems[W_ITEM].toDouble();
double x = orientationItems[X_ITEM].toDouble();
double y = orientationItems[Y_ITEM].toDouble();
double z = orientationItems[Z_ITEM].toDouble();
glm::quat newAvatarOrientation(w, x, y, z);
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::quat avatarOrientation = myAvatar->getOrientation();
if (newAvatarOrientation != avatarOrientation) {
myAvatar->setOrientation(newAvatarOrientation);
emit myAvatar->transformChanged();
qDebug() << dataObject;
QString errorString = "There was a problem creating that location.\n";
// construct the error string from the returned attribute errors
foreach(const QString& key, dataObject.keys()) {
errorString += "\n\u2022 " + key + " - ";
QJsonValue keyedErrorValue = dataObject[key];
if (keyedErrorValue.isArray()) {
foreach(const QJsonValue& attributeErrorValue, keyedErrorValue.toArray()) {
errorString += attributeErrorValue.toString() + ", ";
}
// remove the trailing comma at end of error list
errorString.remove(errorString.length() - 2, 2);
} else if (keyedErrorValue.isString()) {
errorString += keyedErrorValue.toString();
}
}
}
}
bool LocationManager::goToDestination(QString destination) {
QStringList coordinateItems = destination.remove(' ').split(QRegExp("_|,"), QString::SkipEmptyParts);
const int NUMBER_OF_COORDINATE_ITEMS = 3;
const int X_ITEM = 0;
const int Y_ITEM = 1;
const int Z_ITEM = 2;
if (coordinateItems.size() == NUMBER_OF_COORDINATE_ITEMS) {
// replace last occurrence of '_' with decimal point
replaceLastOccurrence('-', '.', coordinateItems[X_ITEM]);
replaceLastOccurrence('-', '.', coordinateItems[Y_ITEM]);
replaceLastOccurrence('-', '.', coordinateItems[Z_ITEM]);
double x = coordinateItems[X_ITEM].toDouble();
double y = coordinateItems[Y_ITEM].toDouble();
double z = coordinateItems[Z_ITEM].toDouble();
glm::vec3 newAvatarPos(x, y, z);
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
glm::vec3 avatarPos = myAvatar->getPosition();
if (newAvatarPos != avatarPos) {
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
MyAvatar::sendKillAvatar();
qDebug("Going To Location: %f, %f, %f...", x, y, z);
myAvatar->setPosition(newAvatarPos);
emit myAvatar->transformChanged();
}
return true;
}
// no coordinates were parsed
return false;
}
void LocationManager::handleAddressLookupError(QNetworkReply::NetworkError networkError,
const QString& errorString) {
QString messageBoxString;
if (networkError == QNetworkReply::ContentNotFoundError) {
messageBoxString = "That address could not be found.";
// emit our creationCompleted signal with the error
emit creationCompleted(errorString);
} else {
messageBoxString = errorString;
}
QMessageBox::warning(Application::getInstance()->getWindow(), "", messageBoxString);
}
void LocationManager::replaceLastOccurrence(const QChar search, const QChar replace, QString& string) {
int lastIndex;
lastIndex = string.lastIndexOf(search);
if (lastIndex > 0) {
lastIndex = string.lastIndexOf(search);
string.replace(lastIndex, 1, replace);
creationCompleted(UNKNOWN_ERROR_MESSAGE);
}
}

View file

@ -13,8 +13,8 @@
#define hifi_LocationManager_h
#include <QtCore>
#include <QtNetwork/QNetworkReply>
#include "AccountManager.h"
#include "NamedLocation.h"
class LocationManager : public QObject {
@ -29,29 +29,14 @@ public:
SystemError
};
LocationManager();
void createNamedLocation(NamedLocation* namedLocation);
void goTo(QString destination);
void goToUser(QString userName);
void goToPlace(QString placeName);
void goToOrientation(QString orientation);
bool goToDestination(QString destination);
public slots:
void handleAddressLookupError(QNetworkReply::NetworkError networkError, const QString& errorString);
private:
void replaceLastOccurrence(const QChar search, const QChar replace, QString& string);
signals:
void creationCompleted(LocationManager::NamedLocationCreateResponse response);
void multipleDestinationsFound(const QJsonObject& userData, const QJsonObject& placeData);
void creationCompleted(const QString& errorMessage);
private slots:
void namedLocationDataReceived(const QJsonObject& data);
void errorDataReceived(QNetworkReply::NetworkError error, const QString& message);
void goToAddressFromResponse(const QJsonObject& jsonObject);
void errorDataReceived(QNetworkReply& errorReply);
};

View file

@ -9,19 +9,25 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <AddressManager.h>
#include <UUID.h>
#include "NamedLocation.h"
const QString JSON_FORMAT = "{\"address\":{\"position\":\"%1,%2,%3\","
"\"orientation\":\"%4,%5,%6,%7\",\"domain\":\"%8\"},\"name\":\"%9\"}";
NamedLocation::NamedLocation(const QString& name,
const glm::vec3& position, const glm::quat& orientation,
const QUuid& domainID) :
_name(name),
_position(position),
_orientation(orientation),
_domainID(domainID)
{
}
const QString JSON_FORMAT = "{\"location\":{\"path\":\"%1\",\"domain_id\":\"%2\",\"name\":\"%3\"}}";
QString NamedLocation::toJsonString() {
return JSON_FORMAT.arg(QString::number(_location.x),
QString::number(_location.y),
QString::number(_location.z),
QString::number(_orientation.w),
QString::number(_orientation.x),
QString::number(_orientation.y),
QString::number(_orientation.z),
_domain,
_locationName);
return JSON_FORMAT.arg(AddressManager::pathForPositionAndOrientation(_position, true, _orientation),
uuidStringWithoutCurlyBraces(_domainID), _name);
}

View file

@ -22,39 +22,33 @@ class NamedLocation : public QObject {
Q_OBJECT
public:
NamedLocation(QString locationName, glm::vec3 location, glm::quat orientation, QString domain) {
_locationName = locationName;
_location = location;
_orientation = orientation;
_domain = domain;
}
NamedLocation(const QString& name, const glm::vec3& position, const glm::quat& orientation, const QUuid& domainID);
QString toJsonString();
bool isEmpty() { return _locationName.isNull() || _locationName.isEmpty(); }
bool isEmpty() { return _name.isNull() || _name.isEmpty(); }
void setLocationName(QString locationName) { _locationName = locationName; }
QString locationName() { return _locationName; }
void setName(QString name) { _name = name; }
const QString& getName() const { return _name; }
void setLocation(glm::vec3 location) { _location = location; }
glm::vec3 location() { return _location; }
void setLocation(glm::vec3 position) { _position = position; }
const glm::vec3& getPosition() const { return _position; }
void setOrientation(glm::quat orentation) { _orientation = orentation; }
glm::quat orientation() { return _orientation; }
void setOrientation(const glm::quat& orentation) { _orientation = orentation; }
const glm::quat& getOrientation() const { return _orientation; }
void setDomain(QString domain) { _domain = domain; }
QString domain() { return _domain; }
void setDomainID(const QUuid& domainID) { _domainID = domainID; }
const QUuid& getDomainID() const { return _domainID; }
signals:
void dataReceived(bool locationExists);
private:
QString _locationName;
QString _name;
QString _createdBy;
glm::vec3 _location;
glm::vec3 _position;
glm::quat _orientation;
QString _domain;
QUuid _domainID;
};

View file

@ -116,9 +116,9 @@ void AmbientOcclusionEffect::render() {
glGetIntegerv(GL_VIEWPORT, viewport);
const int VIEWPORT_X_INDEX = 0;
const int VIEWPORT_WIDTH_INDEX = 2;
QSize widgetSize = Application::getInstance()->getGLWidget()->getDeviceSize();
float sMin = viewport[VIEWPORT_X_INDEX] / (float)widgetSize.width();
float sWidth = viewport[VIEWPORT_WIDTH_INDEX] / (float)widgetSize.width();
QOpenGLFramebufferObject* primaryFBO = Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject();
float sMin = viewport[VIEWPORT_X_INDEX] / (float)primaryFBO->width();
float sWidth = viewport[VIEWPORT_WIDTH_INDEX] / (float)primaryFBO->width();
_occlusionProgram->bind();
_occlusionProgram->setUniformValue(_nearLocation, nearVal);
@ -126,7 +126,7 @@ void AmbientOcclusionEffect::render() {
_occlusionProgram->setUniformValue(_leftBottomLocation, left, bottom);
_occlusionProgram->setUniformValue(_rightTopLocation, right, top);
_occlusionProgram->setUniformValue(_noiseScaleLocation, viewport[VIEWPORT_WIDTH_INDEX] / (float)ROTATION_WIDTH,
widgetSize.height() / (float)ROTATION_HEIGHT);
primaryFBO->height() / (float)ROTATION_HEIGHT);
_occlusionProgram->setUniformValue(_texCoordOffsetLocation, sMin, 0.0f);
_occlusionProgram->setUniformValue(_texCoordScaleLocation, sWidth, 1.0f);
@ -148,7 +148,7 @@ void AmbientOcclusionEffect::render() {
glBindTexture(GL_TEXTURE_2D, freeFBO->texture());
_blurProgram->bind();
_blurProgram->setUniformValue(_blurScaleLocation, 1.0f / widgetSize.width(), 1.0f / widgetSize.height());
_blurProgram->setUniformValue(_blurScaleLocation, 1.0f / primaryFBO->width(), 1.0f / primaryFBO->height());
renderFullscreenQuad(sMin, sMin + sWidth);

View file

@ -298,8 +298,8 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
void GeometryCache::setBlendedVertices(const QPointer<Model>& model, const QWeakPointer<NetworkGeometry>& geometry,
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
if (!model.isNull() && model->getGeometry() == geometry) {
model->setBlendedVertices(vertices, normals);
if (!model.isNull()) {
model->setBlendedVertices(geometry, vertices, normals);
}
}

View file

@ -54,14 +54,17 @@ Model::Model(QObject* parent) :
QObject(parent),
_scale(1.0f, 1.0f, 1.0f),
_scaleToFit(false),
_scaleToFitLargestDimension(0.0f),
_scaleToFitDimensions(0.0f),
_scaledToFit(false),
_snapModelToCenter(false),
_snappedToCenter(false),
_snapModelToRegistrationPoint(false),
_snappedToRegistrationPoint(false),
_showTrueJointTransforms(true),
_lodDistance(0.0f),
_pupilDilation(0.0f),
_url("http://invalid.com") {
_url("http://invalid.com"),
_blenderPending(false),
_blendRequired(false) {
// we may have been created in the network thread, but we live in the main thread
moveToThread(Application::getInstance()->thread());
}
@ -157,8 +160,8 @@ void Model::setOffset(const glm::vec3& offset) {
_offset = offset;
// if someone manually sets our offset, then we are no longer snapped to center
_snapModelToCenter = false;
_snappedToCenter = false;
_snapModelToRegistrationPoint = false;
_snappedToRegistrationPoint = false;
}
void Model::initProgram(ProgramObject& program, Model::Locations& locations,
@ -845,8 +848,8 @@ Blender::Blender(Model* model, const QWeakPointer<NetworkGeometry>& geometry,
}
void Blender::run() {
// make sure the model/geometry still exists
if (_model.isNull() || _geometry.isNull()) {
// make sure the model still exists
if (_model.isNull()) {
return;
}
QVector<glm::vec3> vertices, normals;
@ -882,50 +885,57 @@ void Blender::run() {
Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));
}
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
if (_scaleToFit != scaleToFit || _scaleToFitLargestDimension != largestDimension) {
void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) {
if (_scaleToFit != scaleToFit || _scaleToFitDimensions != dimensions) {
_scaleToFit = scaleToFit;
_scaleToFitLargestDimension = largestDimension;
_scaleToFitDimensions = dimensions;
_scaledToFit = false; // force rescaling
}
}
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
setScaleToFit(scaleToFit, glm::vec3(largestDimension, largestDimension, largestDimension));
}
void Model::scaleToFit() {
Extents modelMeshExtents = getUnscaledMeshExtents();
// size is our "target size in world space"
// we need to set our model scale so that the extents of the mesh, fit in a cube that size...
float maxDimension = glm::distance(modelMeshExtents.maximum, modelMeshExtents.minimum);
float maxScale = _scaleToFitLargestDimension / maxDimension;
glm::vec3 scale(maxScale, maxScale, maxScale);
setScaleInternal(scale);
glm::vec3 meshDimensions = modelMeshExtents.maximum - modelMeshExtents.minimum;
glm::vec3 rescaleDimensions = _scaleToFitDimensions / meshDimensions;
setScaleInternal(rescaleDimensions);
_scaledToFit = true;
}
void Model::setSnapModelToCenter(bool snapModelToCenter) {
if (_snapModelToCenter != snapModelToCenter) {
_snapModelToCenter = snapModelToCenter;
_snappedToCenter = false; // force re-centering
void Model::setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint) {
glm::vec3 clampedRegistrationPoint = glm::clamp(registrationPoint, 0.0f, 1.0f);
if (_snapModelToRegistrationPoint != snapModelToRegistrationPoint || _registrationPoint != clampedRegistrationPoint) {
_snapModelToRegistrationPoint = snapModelToRegistrationPoint;
_registrationPoint = clampedRegistrationPoint;
_snappedToRegistrationPoint = false; // force re-centering
}
}
void Model::snapToCenter() {
void Model::snapToRegistrationPoint() {
Extents modelMeshExtents = getUnscaledMeshExtents();
glm::vec3 halfDimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum) * 0.5f;
glm::vec3 offset = -modelMeshExtents.minimum - halfDimensions;
glm::vec3 dimensions = (modelMeshExtents.maximum - modelMeshExtents.minimum);
glm::vec3 offset = -modelMeshExtents.minimum - (dimensions * _registrationPoint);
_offset = offset;
_snappedToCenter = true;
_snappedToRegistrationPoint = true;
}
void Model::simulate(float deltaTime, bool fullUpdate) {
fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit) || (_snapModelToCenter && !_snappedToCenter);
fullUpdate = updateGeometry() || fullUpdate || (_scaleToFit && !_scaledToFit)
|| (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint);
if (isActive() && fullUpdate) {
// check for scale to fit
if (_scaleToFit && !_scaledToFit) {
scaleToFit();
}
if (_snapModelToCenter && !_snappedToCenter) {
snapToCenter();
if (_snapModelToRegistrationPoint && !_snappedToRegistrationPoint) {
snapToRegistrationPoint();
}
simulateInternal(deltaTime);
}
@ -991,9 +1001,15 @@ void Model::simulateInternal(float deltaTime) {
}
}
// post the blender
// post the blender if we're not currently waiting for one to finish
if (geometry.hasBlendedMeshes()) {
QThreadPool::globalInstance()->start(new Blender(this, _geometry, geometry.meshes, _blendshapeCoefficients));
if (_blenderPending) {
_blendRequired = true;
} else {
_blendRequired = false;
_blenderPending = true;
QThreadPool::globalInstance()->start(new Blender(this, _geometry, geometry.meshes, _blendshapeCoefficients));
}
}
}
@ -1256,14 +1272,25 @@ void Model::renderJointCollisionShapes(float alpha) {
// implement this when we have shapes for regular models
}
void Model::setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
if (_blendedVertexBuffers.isEmpty()) {
void Model::setBlendedVertices(const QWeakPointer<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices,
const QVector<glm::vec3>& normals) {
_blenderPending = false;
// start the next blender if required
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
if (_blendRequired) {
_blendRequired = false;
if (fbxGeometry.hasBlendedMeshes()) {
_blenderPending = true;
QThreadPool::globalInstance()->start(new Blender(this, _geometry, fbxGeometry.meshes, _blendshapeCoefficients));
}
}
if (_geometry != geometry || _blendedVertexBuffers.isEmpty()) {
return;
}
const FBXGeometry& geometry = _geometry->getFBXGeometry();
int index = 0;
for (int i = 0; i < geometry.meshes.size(); i++) {
const FBXMesh& mesh = geometry.meshes.at(i);
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
if (mesh.blendshapes.isEmpty()) {
continue;
}

View file

@ -52,10 +52,18 @@ public:
void setScaleToFit(bool scaleToFit, float largestDimension = 0.0f);
bool getScaleToFit() const { return _scaleToFit; } /// is scale to fit enabled
bool getIsScaledToFit() const { return _scaledToFit; } /// is model scaled to fit
bool getScaleToFitDimension() const { return _scaleToFitLargestDimension; } /// the dimension model is scaled to
const glm::vec3& getScaleToFitDimensions() const { return _scaleToFitDimensions; } /// the dimensions model is scaled to
void setScaleToFit(bool scaleToFit, const glm::vec3& dimensions);
void setSnapModelToCenter(bool snapModelToCenter);
bool getSnapModelToCenter() { return _snapModelToCenter; }
void setSnapModelToCenter(bool snapModelToCenter) {
setSnapModelToRegistrationPoint(snapModelToCenter, glm::vec3(0.5f,0.5f,0.5f));
};
bool getSnapModelToCenter() {
return _snapModelToRegistrationPoint && _registrationPoint == glm::vec3(0.5f,0.5f,0.5f);
}
void setSnapModelToRegistrationPoint(bool snapModelToRegistrationPoint, const glm::vec3& registrationPoint);
bool getSnapModelToRegistrationPoint() { return _snapModelToRegistrationPoint; }
void setScale(const glm::vec3& scale);
const glm::vec3& getScale() const { return _scale; }
@ -158,7 +166,8 @@ public:
virtual void renderJointCollisionShapes(float alpha);
/// Sets blended vertices computed in a separate thread.
void setBlendedVertices(const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
void setBlendedVertices(const QWeakPointer<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices,
const QVector<glm::vec3>& normals);
class LocalLight {
public:
@ -181,11 +190,13 @@ protected:
glm::vec3 _offset;
bool _scaleToFit; /// If you set scaleToFit, we will calculate scale based on MeshExtents
float _scaleToFitLargestDimension; /// this is the dimension that scale to fit will use
glm::vec3 _scaleToFitDimensions; /// this is the dimensions that scale to fit will use
bool _scaledToFit; /// have we scaled to fit
bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
bool _snappedToCenter; /// are we currently snapped to center
bool _snapModelToRegistrationPoint; /// is the model's offset automatically adjusted to a registration point in model space
bool _snappedToRegistrationPoint; /// are we currently snapped to a registration point
glm::vec3 _registrationPoint; /// the point in model space our center is snapped to
bool _showTrueJointTransforms;
QVector<LocalLight> _localLights;
@ -206,7 +217,7 @@ protected:
void setScaleInternal(const glm::vec3& scale);
void scaleToFit();
void snapToCenter();
void snapToRegistrationPoint();
void simulateInternal(float deltaTime);
@ -274,6 +285,9 @@ private:
glm::vec4 _localLightColors[MAX_LOCAL_LIGHTS];
glm::vec4 _localLightDirections[MAX_LOCAL_LIGHTS];
bool _blenderPending;
bool _blendRequired;
static ProgramObject _program;
static ProgramObject _normalMapProgram;
static ProgramObject _specularMapProgram;

View file

@ -497,13 +497,9 @@ void NetworkTexture::setImage(const QImage& image, bool translucent, const QColo
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image.width(), image.height(), 0,
GL_RGB, GL_UNSIGNED_BYTE, image.constBits());
}
if (_type == SPLAT_TEXTURE) {
// generate mipmaps for splat textures
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
} else {
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
}
// generate mipmaps
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
}
@ -541,7 +537,8 @@ QSharedPointer<Texture> DilatableNetworkTexture::getDilatedTexture(float dilatio
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, dilatedImage.width(), dilatedImage.height(), 0,
GL_RGB, GL_UNSIGNED_BYTE, dilatedImage.constBits());
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glGenerateMipmap(GL_TEXTURE_2D);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glBindTexture(GL_TEXTURE_2D, 0);
}

View file

@ -257,7 +257,7 @@ void ControllerScriptingInterface::releaseJoystick(int joystickIndex) {
glm::vec2 ControllerScriptingInterface::getViewportDimensions() const {
GLCanvas* widget = Application::getInstance()->getGLWidget();
return glm::vec2(widget->getDeviceWidth(), widget->getDeviceHeight());
return glm::vec2(widget->width(), widget->height());
}
AbstractInputController* ControllerScriptingInterface::createInputController(const QString& deviceName, const QString& tracker) {
@ -297,6 +297,10 @@ AbstractInputController* ControllerScriptingInterface::createInputController(con
}
}
void ControllerScriptingInterface::releaseInputController(AbstractInputController* input) {
_inputControllers.erase(input->getKey());
}
void ControllerScriptingInterface::updateInputControllers() {
//TODO C++11 for (auto it = _inputControllers.begin(); it != _inputControllers.end(); it++) {
for (InputControllerMap::iterator it = _inputControllers.begin(); it != _inputControllers.end(); it++) {

View file

@ -76,8 +76,6 @@ public:
void updateInputControllers();
void releaseInputController(AbstractInputController* input);
public slots:
virtual bool isPrimaryButtonPressed() const;
virtual glm::vec2 getPrimaryJoystickPosition() const;
@ -116,6 +114,8 @@ public slots:
/// Factory to create an InputController
virtual AbstractInputController* createInputController(const QString& deviceName, const QString& tracker);
virtual void releaseInputController(AbstractInputController* input);
private:
const PalmData* getPrimaryPalm() const;
const PalmData* getPalm(int palmIndex) const;

View file

@ -0,0 +1,113 @@
//
// GlobalServicesScriptingInterface.cpp
// interface/src/scripting
//
// Created by Thijs Wenker on 9/10/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AccountManager.h"
#include "XmppClient.h"
#include "GlobalServicesScriptingInterface.h"
GlobalServicesScriptingInterface::GlobalServicesScriptingInterface() {
AccountManager& accountManager = AccountManager::getInstance();
connect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
connect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
#ifdef HAVE_QXMPP
const XmppClient& xmppClient = XmppClient::getInstance();
connect(&xmppClient, &XmppClient::joinedPublicChatRoom, this, &GlobalServicesScriptingInterface::connected);
connect(&xmppClient, &XmppClient::joinedPublicChatRoom, this, &GlobalServicesScriptingInterface::onConnected);
const QXmppClient& qxmppClient = XmppClient::getInstance().getXMPPClient();
connect(&qxmppClient, &QXmppClient::messageReceived, this, &GlobalServicesScriptingInterface::messageReceived);
#endif // HAVE_QXMPP
}
GlobalServicesScriptingInterface::~GlobalServicesScriptingInterface() {
AccountManager& accountManager = AccountManager::getInstance();
disconnect(&accountManager, &AccountManager::usernameChanged, this, &GlobalServicesScriptingInterface::myUsernameChanged);
disconnect(&accountManager, &AccountManager::logoutComplete, this, &GlobalServicesScriptingInterface::loggedOut);
#ifdef HAVE_QXMPP
const XmppClient& xmppClient = XmppClient::getInstance();
disconnect(&xmppClient, &XmppClient::joinedPublicChatRoom, this, &GlobalServicesScriptingInterface::connected);
disconnect(&xmppClient, &XmppClient::joinedPublicChatRoom, this, &GlobalServicesScriptingInterface::onConnected);
const QXmppClient& qxmppClient = XmppClient::getInstance().getXMPPClient();
disconnect(&qxmppClient, &QXmppClient::messageReceived, this, &GlobalServicesScriptingInterface::messageReceived);
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
disconnect(publicChatRoom, &QXmppMucRoom::participantsChanged, this, &GlobalServicesScriptingInterface::participantsChanged);
#endif // HAVE_QXMPP
}
void GlobalServicesScriptingInterface::onConnected() {
#ifdef HAVE_QXMPP
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
connect(publicChatRoom, &QXmppMucRoom::participantsChanged, this, &GlobalServicesScriptingInterface::participantsChanged, Qt::UniqueConnection);
#endif // HAVE_QXMPP
}
void GlobalServicesScriptingInterface::participantsChanged() {
#ifdef HAVE_QXMPP
emit GlobalServicesScriptingInterface::onlineUsersChanged(this->getOnlineUsers());
#endif // HAVE_QXMPP
}
GlobalServicesScriptingInterface* GlobalServicesScriptingInterface::getInstance() {
static GlobalServicesScriptingInterface sharedInstance;
return &sharedInstance;
}
bool GlobalServicesScriptingInterface::isConnected() {
#ifdef HAVE_QXMPP
return XmppClient::getInstance().getXMPPClient().isConnected();
#else
return false;
#endif // HAVE_QXMPP
}
QScriptValue GlobalServicesScriptingInterface::chat(const QString& message) {
#ifdef HAVE_QXMPP
if (XmppClient::getInstance().getXMPPClient().isConnected()) {
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
QXmppMessage messageObject;
messageObject.setTo(publicChatRoom->jid());
messageObject.setType(QXmppMessage::GroupChat);
messageObject.setBody(message);
return XmppClient::getInstance().getXMPPClient().sendPacket(messageObject);
}
#endif // HAVE_QXMPP
return false;
}
QString GlobalServicesScriptingInterface::getMyUsername() {
return AccountManager::getInstance().getAccountInfo().getUsername();
}
QStringList GlobalServicesScriptingInterface::getOnlineUsers() {
#ifdef HAVE_QXMPP
if (XmppClient::getInstance().getXMPPClient().isConnected()) {
QStringList usernames;
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
foreach(const QString& participant, XmppClient::getInstance().getPublicChatRoom()->participants()) {
usernames.append(participant.right(participant.count() - 1 - publicChatRoom->jid().count()));
}
return usernames;
}
#endif // HAVE_QXMPP
return QStringList();
}
void GlobalServicesScriptingInterface::loggedOut() {
emit GlobalServicesScriptingInterface::disconnected(QString("logout"));
}
void GlobalServicesScriptingInterface::messageReceived(const QXmppMessage& message) {
if (message.type() != QXmppMessage::GroupChat) {
return;
}
const QXmppMucRoom* publicChatRoom = XmppClient::getInstance().getPublicChatRoom();
emit GlobalServicesScriptingInterface::incomingMessage(message.from().right(message.from().count() - 1 - publicChatRoom->jid().count()), message.body());
}

View file

@ -0,0 +1,60 @@
//
// GlobalServicesScriptingInterface.h
// interface/src/scripting
//
// Created by Thijs Wenker on 9/10/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_GlobalServicesScriptingInterface_h
#define hifi_GlobalServicesScriptingInterface_h
#include <QObject>
#include <QScriptContext>
#include <QScriptEngine>
#include <QScriptValue>
#include <QString>
#include <QStringList>
#ifdef HAVE_QXMPP
#include <QXmppClient.h>
#include <QXmppMessage.h>
#endif
class GlobalServicesScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(bool isConnected READ isConnected)
Q_PROPERTY(QString myUsername READ getMyUsername)
Q_PROPERTY(QStringList onlineUsers READ getOnlineUsers)
GlobalServicesScriptingInterface();
~GlobalServicesScriptingInterface();
public:
static GlobalServicesScriptingInterface* getInstance();
bool isConnected();
QString getMyUsername();
QStringList getOnlineUsers();
public slots:
QScriptValue chat(const QString& message);
private slots:
void loggedOut();
void onConnected();
void participantsChanged();
void messageReceived(const QXmppMessage& message);
signals:
void connected();
void disconnected(const QString& reason);
void incomingMessage(const QString& username, const QString& message);
void onlineUsersChanged(const QStringList& usernames);
void myUsernameChanged(const QString& username);
};
#endif // hifi_GlobalServicesScriptingInterface_h

View file

@ -29,10 +29,9 @@ QString LocationScriptingInterface::getHref() {
}
QString LocationScriptingInterface::getPathname() {
const glm::vec3& position = Application::getInstance()->getAvatar()->getPosition();
QString path;
path.sprintf("/%.4f,%.4f,%.4f", position.x, position.y, position.z);
return path;
MyAvatar* applicationAvatar = Application::getInstance()->getAvatar();
return AddressManager::pathForPositionAndOrientation(applicationAvatar->getPosition(),
true, applicationAvatar->getOrientation());
}
QString LocationScriptingInterface::getHostname() {
@ -40,7 +39,7 @@ QString LocationScriptingInterface::getHostname() {
}
void LocationScriptingInterface::assign(const QString& url) {
QMetaObject::invokeMethod(Menu::getInstance(), "goToURL", Q_ARG(const QString&, url));
QMetaObject::invokeMethod(&AddressManager::getInstance(), "handleLookupString", Q_ARG(const QString&, url));
}
QScriptValue LocationScriptingInterface::locationGetter(QScriptContext* context, QScriptEngine* engine) {

View file

@ -18,6 +18,8 @@
#include <QScriptValue>
#include <QString>
#include <AddressManager.h>
#include "Application.h"
class LocationScriptingInterface : public QObject {
@ -33,7 +35,7 @@ public:
bool isConnected();
QString getHref();
QString getProtocol() { return CUSTOM_URL_SCHEME; };
QString getProtocol() { return HIFI_URL_SCHEME; };
QString getPathname();
QString getHostname();

View file

@ -27,7 +27,31 @@ WindowScriptingInterface* WindowScriptingInterface::getInstance() {
return &sharedInstance;
}
WindowScriptingInterface::WindowScriptingInterface() {
WindowScriptingInterface::WindowScriptingInterface() :
_editDialog(NULL),
_nonBlockingFormActive(false),
_formResult(QDialog::Rejected)
{
}
QScriptValue WindowScriptingInterface::hasFocus() {
return Application::getInstance()->getGLWidget()->hasFocus();
}
void WindowScriptingInterface::setCursorVisible(bool visible) {
Application::getInstance()->setCursorVisible(visible);
}
void WindowScriptingInterface::setCursorPosition(int x, int y) {
QCursor::setPos(x, y);
}
QScriptValue WindowScriptingInterface::getCursorPositionX() {
return QCursor::pos().x();
}
QScriptValue WindowScriptingInterface::getCursorPositionY() {
return QCursor::pos().y();
}
QScriptValue WindowScriptingInterface::alert(const QString& message) {
@ -84,6 +108,26 @@ QScriptValue WindowScriptingInterface::s3Browse(const QString& nameFilter) {
return retVal;
}
void WindowScriptingInterface::nonBlockingForm(const QString& title, QScriptValue form) {
QMetaObject::invokeMethod(this, "showNonBlockingForm", Qt::BlockingQueuedConnection,
Q_ARG(const QString&, title), Q_ARG(QScriptValue, form));
}
void WindowScriptingInterface::reloadNonBlockingForm(QScriptValue newValues) {
QMetaObject::invokeMethod(this, "doReloadNonBlockingForm", Qt::BlockingQueuedConnection,
Q_ARG(QScriptValue, newValues));
}
QScriptValue WindowScriptingInterface::getNonBlockingFormResult(QScriptValue form) {
QScriptValue retVal;
QMetaObject::invokeMethod(this, "doGetNonBlockingFormResult", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QScriptValue, retVal),
Q_ARG(QScriptValue, form));
return retVal;
}
/// Display an alert box
/// \param const QString& message message to display
/// \return QScriptValue::UndefinedValue
@ -126,12 +170,131 @@ void WindowScriptingInterface::chooseDirectory() {
button->setText(buttonText);
}
void WindowScriptingInterface::inlineButtonClicked() {
QPushButton* button = reinterpret_cast<QPushButton*>(sender());
QString name = button->property("name").toString();
emit inlineButtonClicked(name);
}
QString WindowScriptingInterface::jsRegExp2QtRegExp(QString string) {
// Converts string representation of RegExp from JavaScript format to Qt format.
return string.mid(1, string.length() - 2) // No enclosing slashes.
.replace("\\/", "/"); // No escaping of forward slash.
}
void WindowScriptingInterface::showNonBlockingForm(const QString& title, QScriptValue form) {
if (!form.isArray() || (form.isArray() && form.property("length").toInt32() <= 0)) {
return;
}
// what should we do if someone calls us while we still think we have a dialog showing???
if (_editDialog) {
qDebug() << "Show Non-Blocking Form called when form already active.";
return;
}
_form = form;
_editDialog = createForm(title, _form);
_nonBlockingFormActive = true;
connect(_editDialog, SIGNAL(accepted()), this, SLOT(nonBlockingFormAccepted()));
connect(_editDialog, SIGNAL(rejected()), this, SLOT(nonBlockingFormRejected()));
_editDialog->setModal(true);
_editDialog->show();
}
void WindowScriptingInterface::doReloadNonBlockingForm(QScriptValue newValues) {
if (!newValues.isArray() || (newValues.isArray() && newValues.property("length").toInt32() <= 0)) {
return;
}
// what should we do if someone calls us while we still think we have a dialog showing???
if (!_editDialog) {
qDebug() << "Reload Non-Blocking Form called when no form is active.";
return;
}
for (int i = 0; i < newValues.property("length").toInt32(); ++i) {
QScriptValue item = newValues.property(i);
if (item.property("oldIndex").isValid()) {
int oldIndex = item.property("oldIndex").toInt32();
QScriptValue oldItem = _form.property(oldIndex);
if (oldItem.isValid()) {
QLineEdit* originalEdit = _edits[oldItem.property("editIndex").toInt32()];
originalEdit->setText(item.property("value").toString());
}
}
}
}
bool WindowScriptingInterface::nonBlockingFormActive() {
return _nonBlockingFormActive;
}
QScriptValue WindowScriptingInterface::doGetNonBlockingFormResult(QScriptValue array) {
QScriptValue retVal;
if (_formResult == QDialog::Accepted) {
int e = -1;
int d = -1;
int c = -1;
for (int i = 0; i < _form.property("length").toInt32(); ++i) {
QScriptValue item = _form.property(i);
QScriptValue value = item.property("value");
if (item.property("button").toString() != "") {
// Nothing to do
} else if (item.property("type").toString() == "inlineButton") {
// Nothing to do
} else if (item.property("type").toString() == "header") {
// Nothing to do
} else if (item.property("directory").toString() != "") {
d += 1;
value = _directories.at(d)->property("path").toString();
item.setProperty("directory", value);
_form.setProperty(i, item);
} else if (item.property("options").isArray()) {
c += 1;
item.setProperty("value", _combos.at(c)->currentText());
_form.setProperty(i, item);
} else {
e += 1;
bool ok = true;
if (value.isNumber()) {
value = _edits.at(e)->text().toDouble(&ok);
} else if (value.isString()) {
value = _edits.at(e)->text();
} else if (value.isBool()) {
if (_edits.at(e)->text() == "true") {
value = true;
} else if (_edits.at(e)->text() == "false") {
value = false;
} else {
ok = false;
}
}
if (ok) {
item.setProperty("value", value);
_form.setProperty(i, item);
}
}
}
}
delete _editDialog;
_editDialog = NULL;
_form = QScriptValue();
_edits.clear();
_directories.clear();
array = _form;
return (_formResult == QDialog::Accepted);
}
/// Display a form layout with an edit box
/// \param const QString& title title to display
/// \param const QScriptValue form to display as an array of objects:
@ -140,129 +303,166 @@ QString WindowScriptingInterface::jsRegExp2QtRegExp(QString string) {
/// - button ("Cancel")
/// \return QScriptValue `true` if 'OK' was clicked, `false` otherwise
QScriptValue WindowScriptingInterface::showForm(const QString& title, QScriptValue form) {
if (form.isArray() && form.property("length").toInt32() <= 0) {
return false;
}
QDialog* editDialog = createForm(title, form);
if (form.isArray() && form.property("length").toInt32() > 0) {
QDialog* editDialog = new QDialog(Application::getInstance()->getWindow());
editDialog->setWindowTitle(title);
bool cancelButton = false;
QVBoxLayout* layout = new QVBoxLayout();
editDialog->setLayout(layout);
QScrollArea* area = new QScrollArea();
layout->addWidget(area);
area->setWidgetResizable(true);
QWidget* container = new QWidget();
QFormLayout* formLayout = new QFormLayout();
container->setLayout(formLayout);
container->sizePolicy().setHorizontalStretch(1);
formLayout->setRowWrapPolicy(QFormLayout::DontWrapRows);
formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
formLayout->setFormAlignment(Qt::AlignHCenter | Qt::AlignTop);
formLayout->setLabelAlignment(Qt::AlignLeft);
area->setWidget(container);
QVector<QLineEdit*> edits;
QVector<QPushButton*> directories;
int result = editDialog->exec();
if (result == QDialog::Accepted) {
int e = -1;
int d = -1;
int c = -1;
for (int i = 0; i < form.property("length").toInt32(); ++i) {
QScriptValue item = form.property(i);
QScriptValue value = item.property("value");
if (item.property("button").toString() != "") {
cancelButton = cancelButton || item.property("button").toString().toLower() == "cancel";
// Nothing to do
} else if (item.property("type").toString() == "inlineButton") {
// Nothing to do
} else if (item.property("type").toString() == "header") {
// Nothing to do
} else if (item.property("directory").toString() != "") {
QString path = item.property("directory").toString();
QString title = item.property("title").toString();
if (title == "") {
title = "Choose Directory";
}
QString displayAsString = item.property("displayAs").toString();
QRegExp displayAs = QRegExp(displayAsString != "" ? jsRegExp2QtRegExp(displayAsString) : "^(.*)$");
QString validateAsString = item.property("validateAs").toString();
QRegExp validateAs = QRegExp(validateAsString != "" ? jsRegExp2QtRegExp(validateAsString) : ".*");
QString errorMessage = item.property("errorMessage").toString();
if (errorMessage == "") {
errorMessage = "Invalid directory";
}
QPushButton* directory = new QPushButton(displayAs.cap(1));
directory->setProperty("title", title);
directory->setProperty("path", path);
directory->setProperty("displayAs", displayAs);
directory->setProperty("validateAs", validateAs);
directory->setProperty("errorMessage", errorMessage);
displayAs.indexIn(path);
directory->setText(displayAs.cap(1) != "" ? displayAs.cap(1) : ".");
directory->setMinimumWidth(200);
directories.push_back(directory);
formLayout->addRow(new QLabel(item.property("label").toString()), directory);
connect(directory, SIGNAL(clicked(bool)), SLOT(chooseDirectory()));
d += 1;
value = _directories.at(d)->property("path").toString();
item.setProperty("directory", value);
form.setProperty(i, item);
} else if (item.property("options").isArray()) {
c += 1;
item.setProperty("value", _combos.at(c)->currentText());
_form.setProperty(i, item);
} else {
QLineEdit* edit = new QLineEdit(item.property("value").toString());
edit->setMinimumWidth(200);
edits.push_back(edit);
formLayout->addRow(new QLabel(item.property("label").toString()), edit);
}
}
QDialogButtonBox* buttons = new QDialogButtonBox(
QDialogButtonBox::Ok
| (cancelButton ? QDialogButtonBox::Cancel : QDialogButtonBox::NoButton)
);
connect(buttons, SIGNAL(accepted()), editDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), editDialog, SLOT(reject()));
layout->addWidget(buttons);
int result = editDialog->exec();
if (result == QDialog::Accepted) {
int e = -1;
int d = -1;
for (int i = 0; i < form.property("length").toInt32(); ++i) {
QScriptValue item = form.property(i);
QScriptValue value = item.property("value");
if (item.property("button").toString() != "") {
// Nothing to do
} else if (item.property("directory").toString() != "") {
d += 1;
value = directories.at(d)->property("path").toString();
item.setProperty("directory", value);
e += 1;
bool ok = true;
if (value.isNumber()) {
value = _edits.at(e)->text().toDouble(&ok);
} else if (value.isString()) {
value = _edits.at(e)->text();
} else if (value.isBool()) {
if (_edits.at(e)->text() == "true") {
value = true;
} else if (_edits.at(e)->text() == "false") {
value = false;
} else {
ok = false;
}
}
if (ok) {
item.setProperty("value", value);
form.setProperty(i, item);
} else {
e += 1;
bool ok = true;
if (value.isNumber()) {
value = edits.at(e)->text().toDouble(&ok);
} else if (value.isString()) {
value = edits.at(e)->text();
} else if (value.isBool()) {
if (edits.at(e)->text() == "true") {
value = true;
} else if (edits.at(e)->text() == "false") {
value = false;
} else {
ok = false;
}
}
if (ok) {
item.setProperty("value", value);
form.setProperty(i, item);
}
}
}
}
delete editDialog;
return (result == QDialog::Accepted);
}
return false;
delete editDialog;
_edits.clear();
_directories.clear();
return (result == QDialog::Accepted);
}
QDialog* WindowScriptingInterface::createForm(const QString& title, QScriptValue form) {
QDialog* editDialog = new QDialog(Application::getInstance()->getWindow());
editDialog->setWindowTitle(title);
bool cancelButton = false;
QVBoxLayout* layout = new QVBoxLayout();
editDialog->setLayout(layout);
QScrollArea* area = new QScrollArea();
layout->addWidget(area);
area->setWidgetResizable(true);
QWidget* container = new QWidget();
QFormLayout* formLayout = new QFormLayout();
container->setLayout(formLayout);
container->sizePolicy().setHorizontalStretch(1);
formLayout->setRowWrapPolicy(QFormLayout::DontWrapRows);
formLayout->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow);
formLayout->setFormAlignment(Qt::AlignHCenter | Qt::AlignTop);
formLayout->setLabelAlignment(Qt::AlignLeft);
area->setWidget(container);
for (int i = 0; i < form.property("length").toInt32(); ++i) {
QScriptValue item = form.property(i);
if (item.property("button").toString() != "") {
cancelButton = cancelButton || item.property("button").toString().toLower() == "cancel";
} else if (item.property("directory").toString() != "") {
QString path = item.property("directory").toString();
QString title = item.property("title").toString();
if (title == "") {
title = "Choose Directory";
}
QString displayAsString = item.property("displayAs").toString();
QRegExp displayAs = QRegExp(displayAsString != "" ? jsRegExp2QtRegExp(displayAsString) : "^(.*)$");
QString validateAsString = item.property("validateAs").toString();
QRegExp validateAs = QRegExp(validateAsString != "" ? jsRegExp2QtRegExp(validateAsString) : ".*");
QString errorMessage = item.property("errorMessage").toString();
if (errorMessage == "") {
errorMessage = "Invalid directory";
}
QPushButton* directory = new QPushButton(displayAs.cap(1));
directory->setProperty("title", title);
directory->setProperty("path", path);
directory->setProperty("displayAs", displayAs);
directory->setProperty("validateAs", validateAs);
directory->setProperty("errorMessage", errorMessage);
displayAs.indexIn(path);
directory->setText(displayAs.cap(1) != "" ? displayAs.cap(1) : ".");
directory->setMinimumWidth(200);
_directories.push_back(directory);
formLayout->addRow(new QLabel(item.property("label").toString()), directory);
connect(directory, SIGNAL(clicked(bool)), SLOT(chooseDirectory()));
} else if (item.property("type").toString() == "inlineButton") {
QString buttonLabel = item.property("buttonLabel").toString();
QPushButton* inlineButton = new QPushButton(buttonLabel);
inlineButton->setMinimumWidth(200);
inlineButton->setProperty("name", item.property("name").toString());
formLayout->addRow(new QLabel(item.property("label").toString()), inlineButton);
connect(inlineButton, SIGNAL(clicked(bool)), SLOT(inlineButtonClicked()));
} else if (item.property("type").toString() == "header") {
formLayout->addRow(new QLabel(item.property("label").toString()));
} else if (item.property("options").isArray()) {
QComboBox* combo = new QComboBox();
combo->setMinimumWidth(200);
QStringList options = item.property("options").toVariant().toStringList();
for (QStringList::const_iterator it = options.begin(); it != options.end(); it += 1) {
combo->addItem(*it);
}
_combos.push_back(combo);
formLayout->addRow(new QLabel(item.property("label").toString()), combo);
} else {
QLineEdit* edit = new QLineEdit(item.property("value").toString());
edit->setMinimumWidth(200);
int editIndex = _edits.size();
_edits.push_back(edit);
item.setProperty("editIndex", editIndex);
formLayout->addRow(new QLabel(item.property("label").toString()), edit);
}
}
QDialogButtonBox* buttons = new QDialogButtonBox(
QDialogButtonBox::Ok
| (cancelButton ? QDialogButtonBox::Cancel : QDialogButtonBox::NoButton)
);
connect(buttons, SIGNAL(accepted()), editDialog, SLOT(accept()));
connect(buttons, SIGNAL(rejected()), editDialog, SLOT(reject()));
layout->addWidget(buttons);
return editDialog;
}
/// Display a prompt with a text box
@ -336,3 +536,11 @@ int WindowScriptingInterface::getInnerWidth() {
int WindowScriptingInterface::getInnerHeight() {
return Application::getInstance()->getWindow()->geometry().height();
}
int WindowScriptingInterface::getX() {
return Application::getInstance()->getWindow()->x();
}
int WindowScriptingInterface::getY() {
return Application::getInstance()->getWindow()->y();
}

View file

@ -20,12 +20,21 @@ class WindowScriptingInterface : public QObject {
Q_OBJECT
Q_PROPERTY(int innerWidth READ getInnerWidth)
Q_PROPERTY(int innerHeight READ getInnerHeight)
Q_PROPERTY(int x READ getX)
Q_PROPERTY(int y READ getY)
public:
static WindowScriptingInterface* getInstance();
int getInnerWidth();
int getInnerHeight();
int getX();
int getY();
public slots:
QScriptValue getCursorPositionX();
QScriptValue getCursorPositionY();
void setCursorPosition(int x, int y);
void setCursorVisible(bool visible);
QScriptValue hasFocus();
QScriptValue alert(const QString& message = "");
QScriptValue confirm(const QString& message = "");
QScriptValue form(const QString& title, QScriptValue array);
@ -34,6 +43,14 @@ public slots:
QScriptValue save(const QString& title = "", const QString& directory = "", const QString& nameFilter = "");
QScriptValue s3Browse(const QString& nameFilter = "");
void nonBlockingForm(const QString& title, QScriptValue array);
void reloadNonBlockingForm(QScriptValue array);
QScriptValue getNonBlockingFormResult(QScriptValue array);
signals:
void inlineButtonClicked(const QString& name);
void nonBlockingFormClosed();
private slots:
QScriptValue showAlert(const QString& message);
QScriptValue showConfirm(const QString& message);
@ -42,12 +59,30 @@ private slots:
QScriptValue showBrowse(const QString& title, const QString& directory, const QString& nameFilter,
QFileDialog::AcceptMode acceptMode = QFileDialog::AcceptOpen);
QScriptValue showS3Browse(const QString& nameFilter);
void showNonBlockingForm(const QString& title, QScriptValue array);
void doReloadNonBlockingForm(QScriptValue array);
bool nonBlockingFormActive();
QScriptValue doGetNonBlockingFormResult(QScriptValue array);
void chooseDirectory();
void inlineButtonClicked();
void nonBlockingFormAccepted() { _nonBlockingFormActive = false; _formResult = QDialog::Accepted; emit nonBlockingFormClosed(); }
void nonBlockingFormRejected() { _nonBlockingFormActive = false; _formResult = QDialog::Rejected; emit nonBlockingFormClosed(); }
private:
WindowScriptingInterface();
QString jsRegExp2QtRegExp(QString string);
QDialog* createForm(const QString& title, QScriptValue form);
QDialog* _editDialog;
QScriptValue _form;
bool _nonBlockingFormActive;
int _formResult;
QVector<QComboBox*> _combos;
QVector<QLineEdit*> _edits;
QVector<QPushButton*> _directories;
};
#endif // hifi_WindowScriptingInterface_h

View file

@ -99,14 +99,14 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) {
glPushMatrix();
glLoadIdentity();
gluOrtho2D(0, glWidget->getDeviceWidth(), glWidget->getDeviceHeight(), 0);
gluOrtho2D(0, glWidget->width(), glWidget->height(), 0);
glDisable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
renderAudioMeter();
if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) {
myAvatar->renderHeadMouse(glWidget->getDeviceWidth(), glWidget->getDeviceHeight());
myAvatar->renderHeadMouse(glWidget->width(), glWidget->height());
}
renderStatsAndLogs();
@ -305,8 +305,8 @@ QPoint ApplicationOverlay::getPalmClickLocation(const PalmData *palm) const {
float u = asin(collisionPos.x) / (_textureFov)+0.5f;
float v = 1.0 - (asin(collisionPos.y) / (_textureFov)+0.5f);
rv.setX(u * glWidget->getDeviceWidth());
rv.setY(v * glWidget->getDeviceHeight());
rv.setX(u * glWidget->width());
rv.setY(v * glWidget->height());
}
} else {
//if they did not click on the overlay, just set the coords to INT_MAX
@ -323,8 +323,8 @@ QPoint ApplicationOverlay::getPalmClickLocation(const PalmData *palm) const {
ndcSpacePos = glm::vec3(clipSpacePos) / clipSpacePos.w;
}
rv.setX(((ndcSpacePos.x + 1.0) / 2.0) * glWidget->getDeviceWidth());
rv.setY((1.0 - ((ndcSpacePos.y + 1.0) / 2.0)) * glWidget->getDeviceHeight());
rv.setX(((ndcSpacePos.x + 1.0) / 2.0) * glWidget->width());
rv.setY((1.0 - ((ndcSpacePos.y + 1.0) / 2.0)) * glWidget->height());
}
return rv;
}
@ -414,7 +414,7 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
renderTexturedHemisphere();
renderPointersOculus(whichCamera.getPosition());
renderPointersOculus(myAvatar->getHead()->getEyePosition());
glDepthMask(GL_TRUE);
glBindTexture(GL_TEXTURE_2D, 0);
@ -496,11 +496,11 @@ void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float as
//draw the mouse pointer
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
const float reticleSize = 40.0f / application->getGLWidget()->getDeviceWidth() * quadWidth;
const float reticleSize = 40.0f / application->getGLWidget()->width() * quadWidth;
x -= reticleSize / 2.0f;
y += reticleSize / 2.0f;
const float mouseX = (application->getMouseX() / (float)application->getGLWidget()->getDeviceWidth()) * quadWidth;
const float mouseY = (1.0 - (application->getMouseY() / (float)application->getGLWidget()->getDeviceHeight())) * quadHeight;
const float mouseX = (application->getMouseX() / (float)application->getGLWidget()->width()) * quadWidth;
const float mouseY = (1.0 - (application->getMouseY() / (float)application->getGLWidget()->height())) * quadHeight;
glBegin(GL_QUADS);
@ -671,14 +671,14 @@ void ApplicationOverlay::renderControllerPointers() {
float yAngle = 0.5f - ((atan2(direction.z, direction.y) + M_PI_2));
// Get the pixel range over which the xAngle and yAngle are scaled
float cursorRange = glWidget->getDeviceWidth() * application->getSixenseManager()->getCursorPixelRangeMult();
float cursorRange = glWidget->width() * application->getSixenseManager()->getCursorPixelRangeMult();
mouseX = (glWidget->getDeviceWidth() / 2.0f + cursorRange * xAngle);
mouseY = (glWidget->getDeviceHeight() / 2.0f + cursorRange * yAngle);
mouseX = (glWidget->width() / 2.0f + cursorRange * xAngle);
mouseY = (glWidget->height() / 2.0f + cursorRange * yAngle);
}
//If the cursor is out of the screen then don't render it
if (mouseX < 0 || mouseX >= glWidget->getDeviceWidth() || mouseY < 0 || mouseY >= glWidget->getDeviceHeight()) {
if (mouseX < 0 || mouseX >= glWidget->width() || mouseY < 0 || mouseY >= glWidget->height()) {
_reticleActive[index] = false;
continue;
}
@ -709,8 +709,8 @@ void ApplicationOverlay::renderPointersOculus(const glm::vec3& eyePos) {
GLCanvas* glWidget = application->getGLWidget();
glm::vec3 cursorVerts[4];
const int widgetWidth = glWidget->getDeviceWidth();
const int widgetHeight = glWidget->getDeviceHeight();
const int widgetWidth = glWidget->width();
const int widgetHeight = glWidget->height();
const float reticleSize = 50.0f;
@ -850,8 +850,8 @@ void ApplicationOverlay::renderMagnifier(int mouseX, int mouseY, float sizeMult,
Application* application = Application::getInstance();
GLCanvas* glWidget = application->getGLWidget();
const int widgetWidth = glWidget->getDeviceWidth();
const int widgetHeight = glWidget->getDeviceHeight();
const int widgetWidth = glWidget->width();
const int widgetHeight = glWidget->height();
const float magnifyWidth = MAGNIFY_WIDTH * sizeMult;
const float magnifyHeight = MAGNIFY_HEIGHT * sizeMult;
@ -968,7 +968,7 @@ void ApplicationOverlay::renderAudioMeter() {
float collisionSoundMagnitude = audio->getCollisionSoundMagnitude();
const float VISIBLE_COLLISION_SOUND_MAGNITUDE = 0.5f;
if (collisionSoundMagnitude > VISIBLE_COLLISION_SOUND_MAGNITUDE) {
renderCollisionOverlay(glWidget->getDeviceWidth(), glWidget->getDeviceHeight(),
renderCollisionOverlay(glWidget->width(), glWidget->height(),
audio->getCollisionSoundMagnitude());
}
}
@ -1019,16 +1019,16 @@ void ApplicationOverlay::renderAudioMeter() {
if ((audio->getTimeSinceLastClip() > 0.f) && (audio->getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)) {
const float MAX_MAGNITUDE = 0.7f;
float magnitude = MAX_MAGNITUDE * (1 - audio->getTimeSinceLastClip() / CLIPPING_INDICATOR_TIME);
renderCollisionOverlay(glWidget->getDeviceWidth(), glWidget->getDeviceHeight(), magnitude, 1.0f);
renderCollisionOverlay(glWidget->width(), glWidget->height(), magnitude, 1.0f);
}
audio->renderToolBox(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP,
audioMeterY,
Menu::getInstance()->isOptionChecked(MenuOption::Mirror));
audio->renderScope(glWidget->getDeviceWidth(), glWidget->getDeviceHeight());
audio->renderScope(glWidget->width(), glWidget->height());
audio->renderStats(WHITE_TEXT, glWidget->getDeviceWidth(), glWidget->getDeviceHeight());
audio->renderStats(WHITE_TEXT, glWidget->width(), glWidget->height());
glBegin(GL_QUADS);
if (isClipping) {
@ -1108,8 +1108,8 @@ void ApplicationOverlay::renderStatsAndLogs() {
application->getPacketsPerSecond(), application->getBytesPerSecond(), voxelPacketsToProcess);
// Bandwidth meter
if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) {
Stats::drawBackground(0x33333399, glWidget->getDeviceWidth() - 296, glWidget->getDeviceHeight() - 68, 296, 68);
bandwidthMeter->render(glWidget->getDeviceWidth(), glWidget->getDeviceHeight());
Stats::drawBackground(0x33333399, glWidget->width() - 296, glWidget->height() - 68, 296, 68);
bandwidthMeter->render(glWidget->width(), glWidget->height());
}
}
@ -1122,7 +1122,7 @@ void ApplicationOverlay::renderStatsAndLogs() {
(Menu::getInstance()->isOptionChecked(MenuOption::Stats) &&
Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth))
? 80 : 20;
drawText(glWidget->getDeviceWidth() - 100, glWidget->getDeviceHeight() - timerBottom,
drawText(glWidget->width() - 100, glWidget->height() - timerBottom,
0.30f, 0.0f, 0, frameTimer, WHITE_TEXT);
}
nodeBoundsDisplay.drawOverlay();
@ -1247,8 +1247,8 @@ void ApplicationOverlay::renderDomainConnectionStatusBorder() {
if (nodeList && !nodeList->getDomainHandler().isConnected()) {
GLCanvas* glWidget = Application::getInstance()->getGLWidget();
int right = glWidget->getDeviceWidth();
int bottom = glWidget->getDeviceHeight();
int right = glWidget->width();
int bottom = glWidget->height();
glColor3f(CONNECTION_STATUS_BORDER_COLOR[0],
CONNECTION_STATUS_BORDER_COLOR[1],

View file

@ -50,7 +50,7 @@ BandwidthMeter::ChannelInfo BandwidthMeter::_CHANNELS[] = {
};
BandwidthMeter::BandwidthMeter() :
_textRenderer(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false),
_textRenderer(TextRenderer::getInstance(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false)),
_scaleMaxIndex(INITIAL_SCALE_MAXIMUM_INDEX) {
_channels = static_cast<ChannelInfo*>( malloc(sizeof(_CHANNELS)) );
@ -140,7 +140,7 @@ void BandwidthMeter::render(int screenWidth, int screenHeight) {
float totalMax = glm::max(totalIn, totalOut);
// Get font / caption metrics
QFontMetrics const& fontMetrics = _textRenderer.metrics();
QFontMetrics const& fontMetrics = _textRenderer->metrics();
int fontDescent = fontMetrics.descent();
int labelWidthIn = fontMetrics.width(CAPTION_IN);
int labelWidthOut = fontMetrics.width(CAPTION_OUT);
@ -163,9 +163,9 @@ void BandwidthMeter::render(int screenWidth, int screenHeight) {
// Render captions
setColorRGBA(COLOR_TEXT);
_textRenderer.draw(barWidth + SPACING_LEFT_CAPTION_UNIT, textYcenteredLine, CAPTION_UNIT);
_textRenderer.draw(-labelWidthIn - SPACING_RIGHT_CAPTION_IN_OUT, textYupperLine, CAPTION_IN);
_textRenderer.draw(-labelWidthOut - SPACING_RIGHT_CAPTION_IN_OUT, textYlowerLine, CAPTION_OUT);
_textRenderer->draw(barWidth + SPACING_LEFT_CAPTION_UNIT, textYcenteredLine, CAPTION_UNIT);
_textRenderer->draw(-labelWidthIn - SPACING_RIGHT_CAPTION_IN_OUT, textYupperLine, CAPTION_IN);
_textRenderer->draw(-labelWidthOut - SPACING_RIGHT_CAPTION_IN_OUT, textYlowerLine, CAPTION_OUT);
// Render vertical lines for the frame
setColorRGBA(COLOR_FRAME);
@ -229,11 +229,11 @@ void BandwidthMeter::render(int screenWidth, int screenHeight) {
char fmtBuf[8];
setColorRGBA(COLOR_TEXT);
sprintf(fmtBuf, "%0.1f", totalIn);
_textRenderer.draw(glm::max(xIn - fontMetrics.width(fmtBuf) - PADDING_HORIZ_VALUE,
_textRenderer->draw(glm::max(xIn - fontMetrics.width(fmtBuf) - PADDING_HORIZ_VALUE,
PADDING_HORIZ_VALUE),
textYupperLine, fmtBuf);
sprintf(fmtBuf, "%0.1f", totalOut);
_textRenderer.draw(glm::max(xOut - fontMetrics.width(fmtBuf) - PADDING_HORIZ_VALUE,
_textRenderer->draw(glm::max(xOut - fontMetrics.width(fmtBuf) - PADDING_HORIZ_VALUE,
PADDING_HORIZ_VALUE),
textYlowerLine, fmtBuf);

View file

@ -78,7 +78,7 @@ private:
static ChannelInfo _CHANNELS[];
TextRenderer _textRenderer;
TextRenderer* _textRenderer;
ChannelInfo* _channels;
Stream _streams[N_STREAMS];
int _scaleMaxIndex;

View file

@ -19,8 +19,8 @@ ChatMessageArea::ChatMessageArea(bool useFixedHeight) : QTextBrowser(), _useFixe
connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged,
this, &ChatMessageArea::updateLayout);
connect(this, &QTextBrowser::anchorClicked,
Menu::getInstance(), &Menu::openUrl);
connect(this, &QTextBrowser::anchorClicked, Application::getInstance(), &Application::openUrl);
}
void ChatMessageArea::setHtml(const QString& html) {

View file

@ -17,6 +17,9 @@
#include <QSizePolicy>
#include <QTimer>
#include <AddressManager.h>
#include <AccountManager.h>
#include "Application.h"
#include "ChatMessageArea.h"
#include "FlowLayout.h"
@ -28,7 +31,6 @@
#include "ChatWindow.h"
const int NUM_MESSAGES_TO_TIME_STAMP = 20;
const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?)|(?:hifi))://\\S+)");
@ -169,7 +171,7 @@ bool ChatWindow::eventFilter(QObject* sender, QEvent* event) {
} else if (event->type() == QEvent::MouseButtonRelease) {
QVariant userVar = sender->property("user");
if (userVar.isValid()) {
Menu::getInstance()->goToUser("@" + userVar.toString());
AddressManager::getInstance().goToUser(userVar.toString());
return true;
}
}

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <limits>
// include this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL
#include "InterfaceConfig.h"
@ -37,6 +39,8 @@
#include "Application.h"
#include "MetavoxelEditor.h"
using namespace std;
enum GridPlane {
GRID_PLANE_XY, GRID_PLANE_XZ, GRID_PLANE_YZ
};
@ -959,7 +963,8 @@ void HeightfieldTool::render() {
}
ImportHeightfieldTool::ImportHeightfieldTool(MetavoxelEditor* editor) :
HeightfieldTool(editor, "Import Heightfield") {
HeightfieldTool(editor, "Import Heightfield"),
_loadingImage(false) {
_form->addRow("Block Size:", _blockSize = new QSpinBox());
_blockSize->setPrefix("2^");
@ -970,6 +975,32 @@ ImportHeightfieldTool::ImportHeightfieldTool(MetavoxelEditor* editor) :
&ImportHeightfieldTool::updatePreview);
_form->addRow("Height:", _height = new QPushButton());
connect(_height, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectHeightFile);
_form->addRow(_rawOptions = new QWidget());
QHBoxLayout* rawLayout = new QHBoxLayout();
_rawOptions->setLayout(rawLayout);
_rawOptions->setVisible(false);
rawLayout->addStretch(1);
rawLayout->addWidget(new QLabel("Scale:"));
rawLayout->addWidget(_heightScale = new QDoubleSpinBox());
const double MAX_OFFSET_SCALE = 100000.0;
_heightScale->setMaximum(MAX_OFFSET_SCALE);
_heightScale->setSingleStep(0.0001);
_heightScale->setDecimals(4);
_heightScale->setValue(1.0);
connect(_heightScale, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
&ImportHeightfieldTool::updateHeightImage);
rawLayout->addSpacing(15);
rawLayout->addWidget(new QLabel("Offset:"));
rawLayout->addWidget(_heightOffset = new QDoubleSpinBox());
_heightOffset->setMinimum(-MAX_OFFSET_SCALE);
_heightOffset->setMaximum(MAX_OFFSET_SCALE);
_heightOffset->setDecimals(4);
connect(_heightOffset, static_cast<void (QDoubleSpinBox::*)(double)>(&QDoubleSpinBox::valueChanged), this,
&ImportHeightfieldTool::updateHeightImage);
_form->addRow("Color:", _color = new QPushButton());
connect(_color, &QAbstractButton::clicked, this, &ImportHeightfieldTool::selectColorFile);
@ -1012,22 +1043,50 @@ void ImportHeightfieldTool::apply() {
}
}
const float EIGHT_BIT_MAXIMUM = 255.0f;
void ImportHeightfieldTool::selectHeightFile() {
QString filename = QFileDialog::getOpenFileName(this, "Select Height Image", QString(), "Images (*.png *.jpg)");
QString filename = QFileDialog::getOpenFileName(this, "Select Height Image", QString(),
"Images (*.png *.jpg *.bmp *.raw)");
if (filename.isNull()) {
return;
}
if (filename.toLower().endsWith(".raw")) {
QFile input(filename);
input.open(QIODevice::ReadOnly);
QDataStream in(&input);
in.setByteOrder(QDataStream::LittleEndian);
_rawHeight.clear();
int minHeight = numeric_limits<quint16>::max();
int maxHeight = numeric_limits<quint16>::min();
while (!in.atEnd()) {
quint16 height;
in >> height;
_rawHeight.append(height);
minHeight = qMin(minHeight, (int)height);
maxHeight = qMax(maxHeight, (int)height);
}
_height->setText(filename);
_rawOptions->setVisible(true);
_loadingImage = true;
_heightScale->setValue((EIGHT_BIT_MAXIMUM - 1.0f) / (maxHeight - minHeight));
_heightOffset->setValue(-minHeight * _heightScale->value() + 1.0);
_loadingImage = false;
updateHeightImage();
return;
}
if (!_heightImage.load(filename)) {
QMessageBox::warning(this, "Invalid Image", "The selected image could not be read.");
return;
}
_rawOptions->setVisible(false);
_heightImage = _heightImage.convertToFormat(QImage::Format_RGB888);
_height->setText(filename);
updatePreview();
}
void ImportHeightfieldTool::selectColorFile() {
QString filename = QFileDialog::getOpenFileName(this, "Select Color Image", QString(), "Images (*.png *.jpg)");
QString filename = QFileDialog::getOpenFileName(this, "Select Color Image", QString(), "Images (*.png *.jpg *.bmp)");
if (filename.isNull()) {
return;
}
@ -1040,6 +1099,26 @@ void ImportHeightfieldTool::selectColorFile() {
updatePreview();
}
void ImportHeightfieldTool::updateHeightImage() {
if (_loadingImage) {
return;
}
int size = glm::sqrt(_rawHeight.size());
_heightImage = QImage(size, size, QImage::Format_RGB888);
const quint16* src = _rawHeight.constData();
float scale = _heightScale->value(), offset = _heightOffset->value();
for (int y = 0; y < size; y++) {
uchar* dest = _heightImage.scanLine(y);
for (const quint16* end = src + size; src != end; src++) {
uchar height = glm::clamp(*src * scale + offset, 1.0f, EIGHT_BIT_MAXIMUM);
*dest++ = height;
*dest++ = height;
*dest++ = height;
}
}
updatePreview();
}
void ImportHeightfieldTool::updatePreview() {
QVector<BufferDataPointer> buffers;
if (_heightImage.width() > 0 && _heightImage.height() > 0) {
@ -1061,7 +1140,7 @@ void ImportHeightfieldTool::updatePreview() {
uchar* src = _heightImage.scanLine(extendedI + y) + extendedJ * DataBlock::COLOR_BYTES;
char* dest = height.data() + (y + offsetY) * heightSize + offsetX;
for (int x = 0; x < columns; x++) {
*dest++ = *src;
*dest++ = qMax((uchar)1, *src);
src += DataBlock::COLOR_BYTES;
}
}

View file

@ -291,6 +291,7 @@ private slots:
void selectHeightFile();
void selectColorFile();
void updateHeightImage();
void updatePreview();
void renderPreview();
@ -299,8 +300,14 @@ private:
QSpinBox* _blockSize;
QPushButton* _height;
QWidget* _rawOptions;
QDoubleSpinBox* _heightScale;
QDoubleSpinBox* _heightOffset;
bool _loadingImage;
QPushButton* _color;
QVector<quint16> _rawHeight;
QImage _heightImage;
QImage _colorImage;

View file

@ -41,8 +41,8 @@ void NodeBounds::draw() {
// itself after the cursor disappears.
Application* application = Application::getInstance();
GLCanvas* glWidget = application->getGLWidget();
float mouseX = application->getMouseX() / (float)glWidget->getDeviceWidth();
float mouseY = application->getMouseY() / (float)glWidget->getDeviceHeight();
float mouseX = application->getMouseX() / (float)glWidget->width();
float mouseY = application->getMouseY() / (float)glWidget->height();
glm::vec3 mouseRayOrigin;
glm::vec3 mouseRayDirection;
application->getViewFrustum()->computePickRay(mouseX, mouseY, mouseRayOrigin, mouseRayDirection);

View file

@ -11,6 +11,8 @@
#include <QtWebKitWidgets/QWebView>
#include <AccountManager.h>
#include "Application.h"
#include "OAuthWebViewHandler.h"

View file

@ -121,6 +121,8 @@ void PreferencesDialog::loadPreferences() {
ui.faceshiftEyeDeflectionSider->setValue(menuInstance->getFaceshiftEyeDeflection() *
ui.faceshiftEyeDeflectionSider->maximum());
ui.faceshiftHostnameEdit->setText(menuInstance->getFaceshiftHostname());
const InboundAudioStream::Settings& streamSettings = menuInstance->getReceivedAudioStreamSettings();
ui.dynamicJitterBuffersCheckBox->setChecked(streamSettings._dynamicJitterBuffers);
@ -165,19 +167,29 @@ void PreferencesDialog::savePreferences() {
}
QUrl faceModelURL(ui.faceURLEdit->text());
if (faceModelURL.toString() != _faceURLString) {
// change the faceModelURL in the profile, it will also update this user's BlendFace
myAvatar->setFaceModelURL(faceModelURL);
UserActivityLogger::getInstance().changedModel("head", faceModelURL.toString());
shouldDispatchIdentityPacket = true;
QString faceModelURLString = faceModelURL.toString();
if (faceModelURLString != _faceURLString) {
if (faceModelURLString.isEmpty() || faceModelURLString.toLower().endsWith(".fst")) {
// change the faceModelURL in the profile, it will also update this user's BlendFace
myAvatar->setFaceModelURL(faceModelURL);
UserActivityLogger::getInstance().changedModel("head", faceModelURLString);
shouldDispatchIdentityPacket = true;
} else {
qDebug() << "ERROR: Head model not FST or blank - " << faceModelURLString;
}
}
QUrl skeletonModelURL(ui.skeletonURLEdit->text());
if (skeletonModelURL.toString() != _skeletonURLString) {
// change the skeletonModelURL in the profile, it will also update this user's Body
myAvatar->setSkeletonModelURL(skeletonModelURL);
UserActivityLogger::getInstance().changedModel("skeleton", skeletonModelURL.toString());
shouldDispatchIdentityPacket = true;
QString skeletonModelURLString = skeletonModelURL.toString();
if (skeletonModelURLString != _skeletonURLString) {
if (skeletonModelURLString.isEmpty() || skeletonModelURLString.toLower().endsWith(".fst")) {
// change the skeletonModelURL in the profile, it will also update this user's Body
myAvatar->setSkeletonModelURL(skeletonModelURL);
UserActivityLogger::getInstance().changedModel("skeleton", skeletonModelURLString);
shouldDispatchIdentityPacket = true;
} else {
qDebug() << "ERROR: Skeleton model not FST or blank - " << skeletonModelURLString;
}
}
if (shouldDispatchIdentityPacket) {
@ -211,6 +223,9 @@ void PreferencesDialog::savePreferences() {
Menu::getInstance()->setFaceshiftEyeDeflection(ui.faceshiftEyeDeflectionSider->value() /
(float)ui.faceshiftEyeDeflectionSider->maximum());
Menu::getInstance()->setFaceshiftHostname(ui.faceshiftHostnameEdit->text());
Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value());
Menu::getInstance()->setOculusUIAngularSize(ui.oculusUIAngularSizeSpin->value());

View file

@ -23,6 +23,8 @@
#include <QTimer>
#include <QWidget>
#include <NetworkAccessManager.h>
#include "Application.h"
#include "ScriptHighlighting.h"

View file

@ -57,7 +57,7 @@ Stats::Stats():
_metavoxelReceiveTotal(0)
{
GLCanvas* glWidget = Application::getInstance()->getGLWidget();
resetWidth(glWidget->getDeviceWidth(), 0);
resetWidth(glWidget->width(), 0);
}
void Stats::toggleExpanded() {
@ -114,7 +114,7 @@ void Stats::checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseD
// top-right stats click
lines = _expanded ? 11 : 3;
statsHeight = lines * STATS_PELS_PER_LINE + 10;
statsWidth = glWidget->getDeviceWidth() - statsX;
statsWidth = glWidget->width() - statsX;
if (mouseX > statsX && mouseX < statsX + statsWidth && mouseY > statsY && mouseY < statsY + statsHeight) {
toggleExpanded();
return;
@ -123,7 +123,7 @@ void Stats::checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseD
void Stats::resetWidth(int width, int horizontalOffset) {
GLCanvas* glWidget = Application::getInstance()->getGLWidget();
int extraSpace = glWidget->getDeviceWidth() - horizontalOffset -2
int extraSpace = glWidget->width() - horizontalOffset -2
- STATS_GENERAL_MIN_WIDTH
- (Menu::getInstance()->isOptionChecked(MenuOption::TestPing) ? STATS_PING_MIN_WIDTH -1 : 0)
- STATS_GEO_MIN_WIDTH
@ -147,7 +147,7 @@ void Stats::resetWidth(int width, int horizontalOffset) {
_pingStatsWidth += (int) extraSpace / panels;
}
_geoStatsWidth += (int) extraSpace / panels;
_voxelStatsWidth += glWidget->getDeviceWidth() - (_generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3);
_voxelStatsWidth += glWidget->width() - (_generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3);
}
}
@ -210,7 +210,7 @@ void Stats::display(
std::stringstream voxelStats;
if (_lastHorizontalOffset != horizontalOffset) {
resetWidth(glWidget->getDeviceWidth(), horizontalOffset);
resetWidth(glWidget->width(), horizontalOffset);
_lastHorizontalOffset = horizontalOffset;
}
@ -410,7 +410,7 @@ void Stats::display(
}
}
drawBackground(backgroundColor, horizontalOffset, 0, glWidget->getDeviceWidth() - horizontalOffset,
drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset,
lines * STATS_PELS_PER_LINE + 10);
horizontalOffset += 5;

View file

@ -9,11 +9,14 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QApplication>
#include <QDesktopWidget>
#include <QFont>
#include <QPaintEngine>
#include <QtDebug>
#include <QString>
#include <QStringList>
#include <QWindow>
#include "InterfaceConfig.h"
#include "TextRenderer.h"
@ -21,15 +24,23 @@
// the width/height of the cached glyph textures
const int IMAGE_SIZE = 256;
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
static uint qHash(const TextRenderer::Properties& key, uint seed = 0) {
// can be switched to qHash(key.font, seed) when we require Qt 5.3+
return qHash(key.font.family(), qHash(key.font.pointSize(), seed));
}
TextRenderer::TextRenderer(const char* family, int pointSize, int weight,
bool italic, EffectType effectType, int effectThickness, QColor color)
: _font(family, pointSize, weight, italic), _metrics(_font), _effectType(effectType),
_effectThickness(effectThickness), _x(IMAGE_SIZE), _y(IMAGE_SIZE), _rowHeight(0), _color(color) {
_font.setKerning(false);
static bool operator==(const TextRenderer::Properties& p1, const TextRenderer::Properties& p2) {
return p1.font == p2.font && p1.effect == p2.effect && p1.effectThickness == p2.effectThickness && p1.color == p2.color;
}
TextRenderer* TextRenderer::getInstance(const char* family, int pointSize, int weight, bool italic,
EffectType effect, int effectThickness, const QColor& color) {
Properties properties = { QFont(family, pointSize, weight, italic), effect, effectThickness, color };
TextRenderer*& instance = _instances[properties];
if (!instance) {
instance = new TextRenderer(properties);
}
return instance;
}
TextRenderer::~TextRenderer() {
@ -73,7 +84,7 @@ int TextRenderer::draw(int x, int y, const char* str) {
int bottom = y + glyph.bounds().y();
int top = y + glyph.bounds().y() + glyph.bounds().height();
float scale = 1.0 / IMAGE_SIZE;
float scale = QApplication::desktop()->windowHandle()->devicePixelRatio() / IMAGE_SIZE;
float ls = glyph.location().x() * scale;
float rs = (glyph.location().x() + glyph.bounds().width()) * scale;
float bt = glyph.location().y() * scale;
@ -112,6 +123,19 @@ int TextRenderer::computeWidth(const char* str)
return width;
}
TextRenderer::TextRenderer(const Properties& properties) :
_font(properties.font),
_metrics(_font),
_effectType(properties.effect),
_effectThickness(properties.effectThickness),
_x(IMAGE_SIZE),
_y(IMAGE_SIZE),
_rowHeight(0),
_color(properties.color) {
_font.setKerning(false);
}
const Glyph& TextRenderer::getGlyph(char c) {
Glyph& glyph = _glyphs[c];
if (glyph.isValid()) {
@ -119,21 +143,25 @@ const Glyph& TextRenderer::getGlyph(char c) {
}
// we use 'J' as a representative size for the solid block character
QChar ch = (c == SOLID_BLOCK_CHAR) ? QChar('J') : QChar(c);
QRect bounds = _metrics.boundingRect(ch);
if (bounds.isEmpty()) {
QRect baseBounds = _metrics.boundingRect(ch);
if (baseBounds.isEmpty()) {
glyph = Glyph(0, QPoint(), QRect(), _metrics.width(ch));
return glyph;
}
// grow the bounds to account for effect, if any
if (_effectType == SHADOW_EFFECT) {
bounds.adjust(-_effectThickness, 0, 0, _effectThickness);
baseBounds.adjust(-_effectThickness, 0, 0, _effectThickness);
} else if (_effectType == OUTLINE_EFFECT) {
bounds.adjust(-_effectThickness, -_effectThickness, _effectThickness, _effectThickness);
baseBounds.adjust(-_effectThickness, -_effectThickness, _effectThickness, _effectThickness);
}
// grow the bounds to account for antialiasing
bounds.adjust(-1, -1, 1, 1);
baseBounds.adjust(-1, -1, 1, 1);
// adjust bounds for device pixel scaling
float ratio = QApplication::desktop()->windowHandle()->devicePixelRatio();
QRect bounds(baseBounds.x() * ratio, baseBounds.y() * ratio, baseBounds.width() * ratio, baseBounds.height() * ratio);
if (_x + bounds.width() > IMAGE_SIZE) {
// we can't fit it on the current row; move to next
@ -162,9 +190,16 @@ const Glyph& TextRenderer::getGlyph(char c) {
} else {
image.fill(0);
QPainter painter(&image);
painter.setFont(_font);
QFont font = _font;
if (ratio == 1.0f) {
painter.setFont(_font);
} else {
QFont enlargedFont = _font;
enlargedFont.setPointSize(_font.pointSize() * ratio);
painter.setFont(enlargedFont);
}
if (_effectType == SHADOW_EFFECT) {
for (int i = 0; i < _effectThickness; i++) {
for (int i = 0; i < _effectThickness * ratio; i++) {
painter.drawText(-bounds.x() - 1 - i, -bounds.y() + 1 + i, ch);
}
} else if (_effectType == OUTLINE_EFFECT) {
@ -173,7 +208,7 @@ const Glyph& TextRenderer::getGlyph(char c) {
font.setStyleStrategy(QFont::ForceOutline);
path.addText(-bounds.x() - 0.5, -bounds.y() + 0.5, font, ch);
QPen pen;
pen.setWidth(_effectThickness);
pen.setWidth(_effectThickness * ratio);
pen.setJoinStyle(Qt::RoundJoin);
pen.setCapStyle(Qt::RoundCap);
painter.setPen(pen);
@ -185,10 +220,17 @@ const Glyph& TextRenderer::getGlyph(char c) {
}
glTexSubImage2D(GL_TEXTURE_2D, 0, _x, _y, bounds.width(), bounds.height(), GL_RGBA, GL_UNSIGNED_BYTE, image.constBits());
glyph = Glyph(_currentTextureID, QPoint(_x, _y), bounds, _metrics.width(ch));
glyph = Glyph(_currentTextureID, QPoint(_x / ratio, _y / ratio), baseBounds, _metrics.width(ch));
_x += bounds.width();
_rowHeight = qMax(_rowHeight, bounds.height());
glBindTexture(GL_TEXTURE_2D, 0);
return glyph;
}
QHash<TextRenderer::Properties, TextRenderer*> TextRenderer::_instances;
Glyph::Glyph(int textureID, const QPoint& location, const QRect& bounds, int width) :
_textureID(textureID), _location(location), _bounds(bounds), _width(width) {
}

View file

@ -33,7 +33,6 @@ const char SOLID_BLOCK_CHAR = 127;
// the Inconsolata font family
#define INCONSOLATA_FONT_FAMILY "Inconsolata"
class Glyph;
class TextRenderer {
@ -41,9 +40,17 @@ public:
enum EffectType { NO_EFFECT, SHADOW_EFFECT, OUTLINE_EFFECT };
TextRenderer(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
EffectType effect = NO_EFFECT, int effectThickness = 1,
QColor color = QColor(255, 255, 255));
class Properties {
public:
QFont font;
EffectType effect;
int effectThickness;
QColor color;
};
static TextRenderer* getInstance(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
EffectType effect = NO_EFFECT, int effectThickness = 1, const QColor& color = QColor(255, 255, 255));
~TextRenderer();
const QFontMetrics& metrics() const { return _metrics; }
@ -59,7 +66,9 @@ public:
private:
const Glyph& getGlyph (char c);
TextRenderer(const Properties& properties);
const Glyph& getGlyph(char c);
// the font to render
QFont _font;
@ -90,6 +99,8 @@ private:
// text color
QColor _color;
static QHash<Properties, TextRenderer*> _instances;
};
class Glyph {

View file

@ -13,6 +13,8 @@
#include <QInputDialog>
#include <QPushButton>
#include <AddressManager.h>
#include "Menu.h"
#include "UserLocationsDialog.h"
@ -51,8 +53,8 @@ void UserLocationsDialog::updateEnabled() {
}
void UserLocationsDialog::goToModelIndex(const QModelIndex& index) {
QVariant location = _proxyModel.data(index.sibling(index.row(), UserLocationsModel::LocationColumn));
Menu::getInstance()->goToURL(location.toString());
QVariant address = _proxyModel.data(index.sibling(index.row(), UserLocationsModel::AddressColumn));
AddressManager::getInstance().handleLookupString(address.toString());
}
void UserLocationsDialog::deleteSelection() {

View file

@ -9,7 +9,9 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "../../Application.h"
#include <NetworkAccessManager.h>
#include "Application.h"
#include "BillboardOverlay.h"

View file

@ -58,7 +58,7 @@ void ModelOverlay::render() {
float depth = unRotatedExtents.z;
Extents rotatedExtents = _model.getUnscaledMeshExtents();
calculateRotatedExtents(rotatedExtents, _rotation);
rotatedExtents.rotate(_rotation);
glm::vec3 rotatedSize = rotatedExtents.maximum - rotatedExtents.minimum;

View file

@ -45,22 +45,21 @@ void TextOverlay::render() {
//TextRenderer(const char* family, int pointSize = -1, int weight = -1, bool italic = false,
// EffectType effect = NO_EFFECT, int effectThickness = 1);
TextRenderer textRenderer(SANS_FONT_FAMILY, _fontSize, 50, false, TextRenderer::NO_EFFECT, 1,
QColor(_color.red, _color.green, _color.blue));
TextRenderer* textRenderer = TextRenderer::getInstance(SANS_FONT_FAMILY, _fontSize, 50);
const int leftAdjust = -1; // required to make text render relative to left edge of bounds
const int topAdjust = -2; // required to make text render relative to top edge of bounds
int x = _bounds.left() + _leftMargin + leftAdjust;
int y = _bounds.top() + _topMargin + topAdjust;
glColor3f(1.0f, 1.0f, 1.0f);
glColor3f(_color.red / MAX_COLOR, _color.green / MAX_COLOR, _color.blue / MAX_COLOR);
QStringList lines = _text.split("\n");
int lineOffset = 0;
foreach(QString thisLine, lines) {
if (lineOffset == 0) {
lineOffset = textRenderer.calculateHeight(qPrintable(thisLine));
lineOffset = textRenderer->calculateHeight(qPrintable(thisLine));
}
lineOffset += textRenderer.draw(x, y + lineOffset, qPrintable(thisLine));
lineOffset += textRenderer->draw(x, y + lineOffset, qPrintable(thisLine));
const int lineGap = 2;
lineOffset += lineGap;

View file

@ -1726,6 +1726,70 @@
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_999">
<property name="spacing">
<number>0</number>
</property>
<property name="topMargin">
<number>7</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>7</number>
</property>
<item>
<widget class="QLabel" name="label_999">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="text">
<string>Faceshift hostname</string>
</property>
<property name="indent">
<number>0</number>
</property>
<property name="buddy">
<cstring>faceshiftHostnameEdit</cstring>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_999">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</spacer>
</item>
<item>
<widget class="QLineEdit" name="faceshiftHostnameEdit">
<property name="font">
<font>
<family>Arial</family>
</font>
</property>
<property name="layoutDirection">
<enum>Qt::LeftToRight</enum>
</property>
<property name="styleSheet">
<string notr="true"/>
</property>
<property name="placeholderText">
<string>localhost</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="voxelsTitleLabel">
<property name="sizePolicy">

View file

@ -0,0 +1,212 @@
//
// AudioBuffer.h
// hifi
//
// Created by Craig Hansen-Sturm on 8/29/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioBuffer_h
#define hifi_AudioBuffer_h
#include <typeinfo>
template< typename T >
class AudioFrameBuffer {
uint16_t _channelCount;
uint16_t _channelCountMax;
uint16_t _frameCount;
uint16_t _frameCountMax;
T** _frameBuffer;
void allocateFrames() {
_frameBuffer = new T*[_channelCountMax];
if (_frameBuffer) {
for (uint16_t i = 0; i < _channelCountMax; ++i) {
_frameBuffer[i] = new T[_frameCountMax];
}
}
}
void deallocateFrames() {
if (_frameBuffer) {
for (uint16_t i = 0; i < _channelCountMax; ++i) {
delete _frameBuffer[i];
}
delete _frameBuffer;
}
_frameBuffer = NULL;
}
public:
AudioFrameBuffer() :
_channelCount(0),
_frameCount(0),
_frameCountMax(0),
_frameBuffer(NULL) {
}
AudioFrameBuffer(const uint16_t channelCount, const uint16_t frameCount) :
_channelCount(channelCount),
_channelCountMax(channelCount),
_frameCount(frameCount),
_frameCountMax(frameCount),
_frameBuffer(NULL) {
allocateFrames();
}
~AudioFrameBuffer() {
finalize();
}
void initialize(const uint16_t channelCount, const uint16_t frameCount) {
if (_frameBuffer) {
finalize();
}
_channelCount = channelCount;
_channelCountMax = channelCount;
_frameCount = frameCount;
_frameCountMax = frameCount;
allocateFrames();
}
void finalize() {
deallocateFrames();
_channelCount = 0;
_channelCountMax = 0;
_frameCount = 0;
_frameCountMax = 0;
}
T**& getFrameData() {
return _frameBuffer;
}
uint16_t getChannelCount() {
return _channelCount;
}
uint16_t getFrameCount() {
return _frameCount;
}
void zeroFrames() {
if (!_frameBuffer) {
return;
}
for (uint16_t i = 0; i < _channelCountMax; ++i) {
memset(_frameBuffer[i], 0, sizeof(T)*_frameCountMax);
}
}
template< typename S >
void copyFrames(uint16_t channelCount, const uint16_t frameCount, S* frames, const bool copyOut = false) {
if ( !_frameBuffer || !frames) {
return;
}
if (channelCount <=_channelCountMax && frameCount <=_frameCountMax) {
// We always allow copying fewer frames than we have allocated
_frameCount = frameCount;
_channelCount = channelCount;
}
else {
//
// However we do not attempt to copy more frames than we've allocated ;-) This is a framing error caused by either
// a/ the platform audio driver not correctly queuing and regularly smoothing device IO capture frames -or-
// b/ our IO processing thread (currently running on a Qt GUI thread) has been delayed/scheduled too late.
//
// The fix is not to make the problem worse by allocating additional frames on this thread, rather, it is to handle
// dynamic re-sizing off the IO processing thread. While a/ is not in our control, we will address the off thread
// re-sizing,, as well as b/, in later releases.
//
// For now, we log this condition, and do our best to recover by copying as many frames as we have allocated.
// Unfortunately, this will result (temporarily), in an audible discontinuity.
//
// If you repeatedly receive this error, contact craig@highfidelity.io and send me what audio device you are using,
// what audio-stack you are using (pulse/alsa, core audio, ...), what OS, and what the reported frame/channel
// counts are. In addition, any information about what you were doing at the time of the discontinuity, would be
// useful (e.g., accessing any client features/menus)
//
qDebug() << "Audio framing error: _channelCount="
<< _channelCount
<< "channelCountMax="
<< _channelCountMax
<< "_frameCount="
<< _frameCount
<< "frameCountMax="
<< _frameCountMax;
_channelCount = std::min(_channelCount,_channelCountMax);
_frameCount = std::min(_frameCount,_frameCountMax);
}
if (copyOut) {
S* dst = frames;
if(typeid(T) == typeid(S)) { // source and destination types are the same
for (int i = 0; i < _frameCount; ++i) {
for (int j = 0; j < _channelCount; ++j) {
*dst++ = _frameBuffer[j][i];
}
}
}
else {
if(typeid(T) == typeid(float32_t) &&
typeid(S) == typeid(int16_t)) {
const int scale = (2 << ((8 * sizeof(S)) - 1));
for (int i = 0; i < _frameCount; ++i) {
for (int j = 0; j < _channelCount; ++j) {
*dst++ = (S)(_frameBuffer[j][i] * scale);
}
}
}
else {
assert(0); // currently unsupported conversion
}
}
}
else { // copyIn
S* src = frames;
if(typeid(T) == typeid(S)) { // source and destination types are the same
for (int i = 0; i < _frameCount; ++i) {
for (int j = 0; j < _channelCount; ++j) {
_frameBuffer[j][i] = *src++;
}
}
}
else {
if(typeid(T) == typeid(float32_t) &&
typeid(S) == typeid(int16_t)) {
const int scale = (2 << ((8 * sizeof(S)) - 1));
for (int i = 0; i < _frameCount; ++i) {
for (int j = 0; j < _channelCount; ++j) {
_frameBuffer[j][i] = ((T)(*src++)) / scale;
}
}
}
else {
assert(0); // currently unsupported conversion
}
}
}
}
};
typedef AudioFrameBuffer< float32_t > AudioBufferFloat32;
typedef AudioFrameBuffer< int32_t > AudioBufferSInt32;
#endif // hifi_AudioBuffer_h

View file

@ -37,11 +37,11 @@ public:
//
// ctor/dtor
//
AudioBiquad()
: _xm1(0.)
, _xm2(0.)
, _ym1(0.)
, _ym2(0.) {
AudioBiquad() :
_xm1(0.),
_xm2(0.),
_ym1(0.),
_ym2(0.) {
setParameters(0.,0.,0.,0.,0.);
}

View file

@ -9,9 +9,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <assert.h>
#include <math.h>
#include <SharedUtil.h>
#include "AudioRingBuffer.h"
#include "AudioFormat.h"
#include "AudioBuffer.h"
#include "AudioFilter.h"
#include "AudioFilterBank.h"

View file

@ -49,9 +49,9 @@ public:
//
// ctor/dtor
//
AudioFilterBank()
: _sampleRate(0.)
, _frameCount(0) {
AudioFilterBank() :
_sampleRate(0.0f),
_frameCount(0) {
for (int i = 0; i < _channelCount; ++i) {
_buffer[ i ] = NULL;
}
@ -64,7 +64,7 @@ public:
//
// public interface
//
void initialize(const float sampleRate, const int frameCount) {
void initialize(const float sampleRate, const int frameCount = 0) {
finalize();
for (int i = 0; i < _channelCount; ++i) {
@ -141,6 +141,16 @@ public:
}
}
void render(AudioBufferFloat32& frameBuffer) {
float32_t** samples = frameBuffer.getFrameData();
for (uint16_t j = 0; j < frameBuffer.getChannelCount(); ++j) {
for (int i = 0; i < _filterCount; ++i) {
_filters[i][j].render( samples[j], samples[j], frameBuffer.getFrameCount() );
}
}
}
void reset() {
for (int i = 0; i < _filterCount; ++i) {
for (int j = 0; j < _channelCount; ++j) {

View file

@ -0,0 +1,85 @@
//
// AudioFormat.h
// hifi
//
// Created by Craig Hansen-Sturm on 8/28/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioFormat_h
#define hifi_AudioFormat_h
#ifndef _FLOAT32_T
#define _FLOAT32_T
typedef float float32_t;
#endif
#ifndef _FLOAT64_T
#define _FLOAT64_T
typedef double float64_t;
#endif
//
// Audio format structure (currently for uncompressed streams only)
//
struct AudioFormat {
struct Flags {
uint32_t _isFloat : 1;
uint32_t _isSigned : 1;
uint32_t _isInterleaved : 1;
uint32_t _isBigEndian : 1;
uint32_t _isPacked : 1;
uint32_t _reserved : 27;
} _flags;
uint32_t _bytesPerFrame;
uint32_t _channelsPerFrame;
uint32_t _bitsPerChannel;
float64_t _sampleRate;
AudioFormat() {
memset(this, 0, sizeof(*this));
}
~AudioFormat() { }
AudioFormat& operator=(const AudioFormat& fmt) {
memcpy(this, &fmt, sizeof(*this));
return *this;
}
bool operator==(const AudioFormat& fmt) {
return memcmp(this, &fmt, sizeof(*this)) == 0;
}
bool operator!=(const AudioFormat& fmt) {
return memcmp(this, &fmt, sizeof(*this)) != 0;
}
void setCanonicalFloat32(uint32_t channels) {
assert(channels > 0 && channels <= 2);
_sampleRate = SAMPLE_RATE; // todo: create audio constants header
_bitsPerChannel = sizeof(float32_t) * 8;
_channelsPerFrame = channels;
_bytesPerFrame = _channelsPerFrame * _bitsPerChannel / 8;
_flags._isFloat = true;
_flags._isInterleaved = _channelsPerFrame > 1;
}
void setCanonicalInt16(uint32_t channels) {
assert(channels > 0 && channels <= 2);
_sampleRate = SAMPLE_RATE; // todo: create audio constants header
_bitsPerChannel = sizeof(int16_t) * 8;
_channelsPerFrame = channels;
_bytesPerFrame = _channelsPerFrame * _bitsPerChannel / 8;
_flags._isSigned = true;
_flags._isInterleaved = _channelsPerFrame > 1;
}
};
#endif // hifi_AudioFormat_h

View file

@ -0,0 +1,137 @@
//
// AudioGain.h
// hifi
//
// Created by Craig Hansen-Sturm on 9/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioGain_h
#define hifi_AudioGain_h
class AudioGain
{
float32_t _gain;
bool _mute;
public:
AudioGain() {
initialize();
}
~AudioGain() {
finalize();
}
void initialize() {
setParameters(1.0f,0.0f);
}
void finalize() {
}
void reset() {
initialize();
}
void setParameters(const float gain, const float mute) {
_gain = std::min(std::max(gain, 0.0f), 1.0f);
_mute = mute != 0.0f;
}
void getParameters(float& gain, float& mute) {
gain = _gain;
mute = _mute ? 1.0f : 0.0f;
}
void render(AudioBufferFloat32& frameBuffer) {
if (_mute) {
frameBuffer.zeroFrames();
return;
}
float32_t** samples = frameBuffer.getFrameData();
bool frameAlignment16 = (frameBuffer.getFrameCount() & 0x0F) == 0;
if (frameAlignment16) {
if (frameBuffer.getChannelCount() == 1) {
for (uint16_t i = 0; i < frameBuffer.getFrameCount(); i += 16) {
samples[0][i + 0] *= _gain;
samples[0][i + 1] *= _gain;
samples[0][i + 2] *= _gain;
samples[0][i + 3] *= _gain;
samples[0][i + 4] *= _gain;
samples[0][i + 5] *= _gain;
samples[0][i + 6] *= _gain;
samples[0][i + 7] *= _gain;
samples[0][i + 8] *= _gain;
samples[0][i + 9] *= _gain;
samples[0][i + 10] *= _gain;
samples[0][i + 11] *= _gain;
samples[0][i + 12] *= _gain;
samples[0][i + 13] *= _gain;
samples[0][i + 14] *= _gain;
samples[0][i + 15] *= _gain;
}
}
else if (frameBuffer.getChannelCount() == 2) {
for (uint16_t i = 0; i < frameBuffer.getFrameCount(); i += 16) {
samples[0][i + 0] *= _gain;
samples[0][i + 1] *= _gain;
samples[0][i + 2] *= _gain;
samples[0][i + 3] *= _gain;
samples[0][i + 4] *= _gain;
samples[0][i + 5] *= _gain;
samples[0][i + 6] *= _gain;
samples[0][i + 7] *= _gain;
samples[0][i + 8] *= _gain;
samples[0][i + 9] *= _gain;
samples[0][i + 10] *= _gain;
samples[0][i + 11] *= _gain;
samples[0][i + 12] *= _gain;
samples[0][i + 13] *= _gain;
samples[0][i + 14] *= _gain;
samples[0][i + 15] *= _gain;
samples[1][i + 0] *= _gain;
samples[1][i + 1] *= _gain;
samples[1][i + 2] *= _gain;
samples[1][i + 3] *= _gain;
samples[1][i + 4] *= _gain;
samples[1][i + 5] *= _gain;
samples[1][i + 6] *= _gain;
samples[1][i + 7] *= _gain;
samples[1][i + 8] *= _gain;
samples[1][i + 9] *= _gain;
samples[1][i + 10] *= _gain;
samples[1][i + 11] *= _gain;
samples[1][i + 12] *= _gain;
samples[1][i + 13] *= _gain;
samples[1][i + 14] *= _gain;
samples[1][i + 15] *= _gain;
}
}
else {
assert("unsupported channel format");
}
}
else {
for (uint16_t j = 0; j < frameBuffer.getChannelCount(); ++j) {
for (uint16_t i = 0; i < frameBuffer.getFrameCount(); i += 1) {
samples[j][i] *= _gain;
}
}
}
}
};
#endif // AudioGain_h

View file

@ -0,0 +1,23 @@
//
// AudioSourceTone.cpp
// hifi
//
// Created by Craig Hansen-Sturm on 8/10/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <assert.h>
#include <math.h>
#include <SharedUtil.h>
#include "AudioRingBuffer.h"
#include "AudioFormat.h"
#include "AudioBuffer.h"
#include "AudioPan.h"
float32_t AudioPan::ONE_MINUS_EPSILON = 1.0f - EPSILON;
float32_t AudioPan::ZERO_PLUS_EPSILON = 0.0f + EPSILON;
float32_t AudioPan::ONE_HALF_MINUS_EPSILON = 0.5f - EPSILON;
float32_t AudioPan::ONE_HALF_PLUS_EPSILON = 0.5f + EPSILON;

View file

@ -0,0 +1,141 @@
//
// AudioPan.h
// hifi
//
// Created by Craig Hansen-Sturm on 9/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioPan_h
#define hifi_AudioPan_h
class AudioPan
{
float32_t _pan;
float32_t _gainLeft;
float32_t _gainRight;
static float32_t ONE_MINUS_EPSILON;
static float32_t ZERO_PLUS_EPSILON;
static float32_t ONE_HALF_MINUS_EPSILON;
static float32_t ONE_HALF_PLUS_EPSILON;
void updateCoefficients() {
// implement constant power sin^2 + cos^2 = 1 panning law
if (_pan >= ONE_MINUS_EPSILON) { // full right
_gainLeft = 0.0f;
_gainRight = 1.0f;
}
else if (_pan <= ZERO_PLUS_EPSILON) { // full left
_gainLeft = 1.0f;
_gainRight = 0.0f;
}
else if ((_pan >= ONE_HALF_MINUS_EPSILON) && (_pan <= ONE_HALF_PLUS_EPSILON)) { // center
_gainLeft = 1.0f / SQUARE_ROOT_OF_2;
_gainRight = 1.0f / SQUARE_ROOT_OF_2;
}
else { // intermediate cases
_gainLeft = cosf( TWO_PI * _pan );
_gainRight = sinf( TWO_PI * _pan );
}
}
public:
AudioPan() {
initialize();
}
~AudioPan() {
finalize();
}
void initialize() {
setParameters(0.5f);
}
void finalize() {
}
void reset() {
initialize();
}
void setParameters(const float32_t pan) {
// pan ranges between 0.0 and 1.0f inclusive. 0.5f is midpoint between full left and full right
_pan = std::min(std::max(pan, 0.0f), 1.0f);
updateCoefficients();
}
void getParameters(float32_t& pan) {
pan = _pan;
}
void render(AudioBufferFloat32& frameBuffer) {
if (frameBuffer.getChannelCount() != 2) {
return;
}
float32_t** samples = frameBuffer.getFrameData();
bool frameAlignment16 = (frameBuffer.getFrameCount() & 0x0F) == 0;
if (frameAlignment16) {
if (frameBuffer.getChannelCount() == 2) {
for (uint16_t i = 0; i < frameBuffer.getFrameCount(); i += 16) {
samples[0][i + 0] *= _gainLeft;
samples[0][i + 1] *= _gainLeft;
samples[0][i + 2] *= _gainLeft;
samples[0][i + 3] *= _gainLeft;
samples[0][i + 4] *= _gainLeft;
samples[0][i + 5] *= _gainLeft;
samples[0][i + 6] *= _gainLeft;
samples[0][i + 7] *= _gainLeft;
samples[0][i + 8] *= _gainLeft;
samples[0][i + 9] *= _gainLeft;
samples[0][i + 10] *= _gainLeft;
samples[0][i + 11] *= _gainLeft;
samples[0][i + 12] *= _gainLeft;
samples[0][i + 13] *= _gainLeft;
samples[0][i + 14] *= _gainLeft;
samples[0][i + 15] *= _gainLeft;
samples[1][i + 0] *= _gainRight;
samples[1][i + 1] *= _gainRight;
samples[1][i + 2] *= _gainRight;
samples[1][i + 3] *= _gainRight;
samples[1][i + 4] *= _gainRight;
samples[1][i + 5] *= _gainRight;
samples[1][i + 6] *= _gainRight;
samples[1][i + 7] *= _gainRight;
samples[1][i + 8] *= _gainRight;
samples[1][i + 9] *= _gainRight;
samples[1][i + 10] *= _gainRight;
samples[1][i + 11] *= _gainRight;
samples[1][i + 12] *= _gainRight;
samples[1][i + 13] *= _gainRight;
samples[1][i + 14] *= _gainRight;
samples[1][i + 15] *= _gainRight;
}
}
else {
assert("unsupported channel format");
}
}
else {
for (uint16_t i = 0; i < frameBuffer.getFrameCount(); i += 1) {
samples[0][i] *= _gainLeft;
samples[1][i] *= _gainRight;
}
}
}
};
#endif // AudioPan_h

View file

@ -0,0 +1,21 @@
//
// AudioSourceNoise.cpp
// hifi
//
// Created by Craig Hansen-Sturm on 8/10/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <assert.h>
#include <math.h>
#include <SharedUtil.h>
#include "AudioRingBuffer.h"
#include "AudioFormat.h"
#include "AudioBuffer.h"
#include "AudioSourceNoise.h"
template<>
uint32_t AudioSourcePinkNoise::_randomSeed = 1974; // a truly random number

View file

@ -0,0 +1,103 @@
//
// AudioSourceNoise.h
// hifi
//
// Created by Craig Hansen-Sturm on 9/1/14.
// Copyright 2014 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
//
// Adapted from code by Phil Burk http://www.firstpr.com.au/dsp/pink-noise/
//
#ifndef hifi_AudioSourceNoise_h
#define hifi_AudioSourceNoise_h
template< const uint16_t N = 30>
class AudioSourceNoise
{
static const uint16_t _randomRows = N;
static const uint16_t _randomBits = 24;
static const uint16_t _randomShift = (sizeof(int32_t) * 8) - _randomBits;
static uint32_t _randomSeed;
int32_t _rows[_randomRows];
int32_t _runningSum; // used to optimize summing of generators.
uint16_t _index; // incremented each sample.
uint16_t _indexMask; // index wrapped by ANDing with this mask.
float32_t _scale; // used to scale within range of -1.0 to +1.0
static uint32_t generateRandomNumber() {
_randomSeed = (_randomSeed * 196314165) + 907633515;
return _randomSeed >> _randomShift;
}
public:
AudioSourceNoise() {
initialize();
}
~AudioSourceNoise() {
finalize();
}
void initialize() {
memset(_rows, 0, _randomRows * sizeof(int32_t));
_runningSum = 0;
_index = 0;
_indexMask = (1 << _randomRows) - 1;
_scale = 1.0f / ((_randomRows + 1) * (1 << (_randomBits - 1)));
}
void finalize() {
}
void reset() {
initialize();
}
void setParameters(void) {
}
void getParameters(void) {
}
void render(AudioBufferFloat32& frameBuffer) {
uint32_t randomNumber;
float32_t** samples = frameBuffer.getFrameData();
for (uint16_t i = 0; i < frameBuffer.getFrameCount(); ++i) {
for (uint16_t j = 0; j < frameBuffer.getChannelCount(); ++j) {
_index = (_index + 1) & _indexMask; // increment and mask index.
if (_index != 0) { // if index is zero, don't update any random values.
uint32_t numZeros = 0; // determine how many trailing zeros in _index
uint32_t tmp = _index;
while ((tmp & 1) == 0) {
tmp >>= 1;
numZeros++;
}
// replace the indexed _rows random value. subtract and add back to _runningSum instead
// of adding all the random values together. only one value changes each time.
_runningSum -= _rows[numZeros];
randomNumber = generateRandomNumber();
_runningSum += randomNumber;
_rows[numZeros] = randomNumber;
}
// add extra white noise value and scale between -1.0 and +1.0
samples[j][i] = (_runningSum + generateRandomNumber()) * _scale;
}
}
}
};
typedef AudioSourceNoise<> AudioSourcePinkNoise;
#endif // AudioSourceNoise_h

View file

@ -0,0 +1,20 @@
//
// AudioSourceTone.cpp
// hifi
//
// Created by Craig Hansen-Sturm on 8/10/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <assert.h>
#include <math.h>
#include <SharedUtil.h>
#include "AudioRingBuffer.h"
#include "AudioFormat.h"
#include "AudioBuffer.h"
#include "AudioSourceTone.h"
uint32_t AudioSourceTone::_frameOffset = 0;

View file

@ -0,0 +1,72 @@
//
// AudioSourceTone.h
// hifi
//
// Created by Craig Hansen-Sturm on 9/1/14.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_AudioSourceTone_h
#define hifi_AudioSourceTone_h
class AudioSourceTone
{
static uint32_t _frameOffset;
float32_t _frequency;
float32_t _amplitude;
float32_t _sampleRate;
float32_t _omega;
public:
AudioSourceTone() {
initialize();
}
~AudioSourceTone() {
finalize();
}
void initialize() {
_frameOffset = 0;
setParameters(SAMPLE_RATE, 220.0f, 0.9f);
}
void finalize() {
}
void reset() {
_frameOffset = 0;
}
void setParameters(const float32_t sampleRate, const float32_t frequency, const float32_t amplitude) {
_sampleRate = std::max(sampleRate, 1.0f);
_frequency = std::max(frequency, 1.0f);
_amplitude = std::max(amplitude, 1.0f);
_omega = _frequency / _sampleRate * TWO_PI;
}
void getParameters(float32_t& sampleRate, float32_t& frequency, float32_t& amplitude) {
sampleRate = _sampleRate;
frequency = _frequency;
amplitude = _amplitude;
}
void render(AudioBufferFloat32& frameBuffer) {
// note: this is a placeholder implementation. final version will not include any transcendental ops in our render loop
float32_t** samples = frameBuffer.getFrameData();
for (uint16_t i = 0; i < frameBuffer.getFrameCount(); ++i) {
for (uint16_t j = 0; j < frameBuffer.getChannelCount(); ++j) {
samples[j][i] = sinf((i + _frameOffset) * _omega);
}
}
_frameOffset += frameBuffer.getFrameCount();
}
};
#endif

View file

@ -16,6 +16,8 @@
#include <AABox.h>
#include "InboundAudioStream.h"
#include "AudioFormat.h"
#include "AudioBuffer.h"
#include "AudioFilter.h"
#include "AudioFilterBank.h"

View file

@ -35,7 +35,8 @@ using namespace std;
AvatarData::AvatarData() :
_sessionUUID(),
_handPosition(0,0,0),
_position(0.0f),
_handPosition(0.0f),
_referential(NULL),
_bodyYaw(-90.f),
_bodyPitch(0.0f),
@ -326,7 +327,7 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) {
}
return maxAvailableSize;
}
_position = position;
setPosition(position);
// rotation (NOTE: This needs to become a quaternion to save two bytes)
float yaw, pitch, roll;

View file

@ -25,8 +25,7 @@ AddEntityOperator::AddEntityOperator(EntityTree* tree,
{
// caller must have verified existence of newEntity
assert(_newEntity);
_newEntityBox = _newEntity->getAACube().clamp(0.0f, 1.0f);
_newEntityBox = _newEntity->getMaximumAACube().clamp(0.0f, 1.0f);
}
bool AddEntityOperator::preRecursion(OctreeElement* element) {

View file

@ -20,13 +20,15 @@
EntityItem* BoxEntityItem::factory(const EntityItemID& entityID, const EntityItemProperties& properties) {
return new BoxEntityItem(entityID, properties);
EntityItem* result = new BoxEntityItem(entityID, properties);
return result;
}
BoxEntityItem::BoxEntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) :
EntityItem(entityItemID, properties)
{
EntityItem(entityItemID)
{
_type = EntityTypes::Box;
_created = properties.getCreated();
setProperties(properties, true);
}
@ -44,18 +46,9 @@ EntityItemProperties BoxEntityItem::getProperties() const {
bool BoxEntityItem::setProperties(const EntityItemProperties& properties, bool forceCopy) {
bool somethingChanged = false;
somethingChanged = EntityItem::setProperties(properties, forceCopy); // set the properties in our base class
if (properties._colorChanged || forceCopy) {
setColor(properties._color);
somethingChanged = true;
}
if (properties._glowLevelChanged || forceCopy) {
setGlowLevel(properties._glowLevel);
somethingChanged = true;
}
SET_ENTITY_PROPERTY_FROM_PROPERTIES(color, setColor);
if (somethingChanged) {
bool wantDebug = false;

View file

@ -27,14 +27,21 @@ const float EntityItem::IMMORTAL = -1.0f; /// special lifetime which means the e
const float EntityItem::DEFAULT_GLOW_LEVEL = 0.0f;
const float EntityItem::DEFAULT_MASS = 1.0f;
const float EntityItem::DEFAULT_LIFETIME = EntityItem::IMMORTAL;
const float EntityItem::DEFAULT_DAMPING = 0.99f;
const float EntityItem::DEFAULT_DAMPING = 0.5f;
const glm::vec3 EntityItem::NO_VELOCITY = glm::vec3(0, 0, 0);
const float EntityItem::EPSILON_VELOCITY_LENGTH = (1.0f / 10000.0f) / (float)TREE_SCALE; // really small
const float EntityItem::EPSILON_VELOCITY_LENGTH = (1.0f / 1000.0f) / (float)TREE_SCALE; // really small: 1mm/second
const glm::vec3 EntityItem::DEFAULT_VELOCITY = EntityItem::NO_VELOCITY;
const glm::vec3 EntityItem::NO_GRAVITY = glm::vec3(0, 0, 0);
const glm::vec3 EntityItem::REGULAR_GRAVITY = glm::vec3(0, (-9.8f / TREE_SCALE), 0);
const glm::vec3 EntityItem::DEFAULT_GRAVITY = EntityItem::NO_GRAVITY;
const QString EntityItem::DEFAULT_SCRIPT = QString("");
const glm::quat EntityItem::DEFAULT_ROTATION;
const glm::vec3 EntityItem::DEFAULT_DIMENSIONS = glm::vec3(0.1f, 0.1f, 0.1f);
const glm::vec3 EntityItem::DEFAULT_REGISTRATION_POINT = glm::vec3(0.5f, 0.5f, 0.5f); // center
const glm::vec3 EntityItem::NO_ANGULAR_VELOCITY = glm::vec3(0.0f, 0.0f, 0.0f);
const glm::vec3 EntityItem::DEFAULT_ANGULAR_VELOCITY = NO_ANGULAR_VELOCITY;
const float EntityItem::DEFAULT_ANGULAR_DAMPING = 0.5f;
const bool EntityItem::DEFAULT_VISIBLE = true;
void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_id = entityItemID.id;
@ -50,8 +57,8 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_created = 0; // TODO: when do we actually want to make this "now"
_position = glm::vec3(0,0,0);
_radius = 0;
_rotation = ENTITY_DEFAULT_ROTATION;
_rotation = DEFAULT_ROTATION;
_dimensions = DEFAULT_DIMENSIONS;
_glowLevel = DEFAULT_GLOW_LEVEL;
_mass = DEFAULT_MASS;
@ -59,6 +66,20 @@ void EntityItem::initFromEntityItemID(const EntityItemID& entityItemID) {
_gravity = DEFAULT_GRAVITY;
_damping = DEFAULT_DAMPING;
_lifetime = DEFAULT_LIFETIME;
_registrationPoint = DEFAULT_REGISTRATION_POINT;
_angularVelocity = DEFAULT_ANGULAR_VELOCITY;
_angularDamping = DEFAULT_ANGULAR_DAMPING;
_visible = DEFAULT_VISIBLE;
}
EntityItem::EntityItem(const EntityItemID& entityItemID) {
_type = EntityTypes::Unknown;
_lastEdited = 0;
_lastEditedFromRemote = 0;
_lastEditedFromRemoteInRemoteTime = 0;
_lastUpdated = 0;
_created = 0;
initFromEntityItemID(entityItemID);
}
EntityItem::EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties) {
@ -76,7 +97,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
EntityPropertyFlags requestedProperties;
requestedProperties += PROP_POSITION;
requestedProperties += PROP_RADIUS;
requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete
requestedProperties += PROP_ROTATION;
requestedProperties += PROP_MASS;
requestedProperties += PROP_VELOCITY;
@ -84,6 +105,10 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_DAMPING;
requestedProperties += PROP_LIFETIME;
requestedProperties += PROP_SCRIPT;
requestedProperties += PROP_REGISTRATION_POINT;
requestedProperties += PROP_ANGULAR_VELOCITY;
requestedProperties += PROP_ANGULAR_DAMPING;
requestedProperties += PROP_VISIBLE;
return requestedProperties;
}
@ -178,10 +203,14 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
// These items would go here once supported....
// PROP_PAGED_PROPERTY,
// PROP_CUSTOM_PROPERTIES_INCLUDED,
// PROP_VISIBLE,
APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, getPosition());
APPEND_ENTITY_PROPERTY(PROP_RADIUS, appendValue, getRadius());
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, getDimensions()); // NOTE: PROP_RADIUS obsolete
if (wantDebug) {
qDebug() << " APPEND_ENTITY_PROPERTY() PROP_DIMENSIONS:" << getDimensions();
}
APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, getRotation());
APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, getMass());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, getVelocity());
@ -189,6 +218,10 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, getDamping());
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, appendValue, getLifetime());
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, appendValue, getScript());
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, appendValue, getRegistrationPoint());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, appendValue, getAngularVelocity());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, getAngularDamping());
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, getVisible());
appendSubclassData(packetData, params, entityTreeElementExtraEncodeData,
requestedProperties,
@ -408,9 +441,36 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
EntityPropertyFlags propertyFlags = encodedPropertyFlags;
dataAt += propertyFlags.getEncodedLength();
bytesRead += propertyFlags.getEncodedLength();
READ_ENTITY_PROPERTY(PROP_POSITION, glm::vec3, _position);
READ_ENTITY_PROPERTY(PROP_RADIUS, float, _radius);
// Old bitstreams had PROP_RADIUS, new bitstreams have PROP_DIMENSIONS
if (args.bitstreamVersion < VERSION_ENTITIES_SUPPORT_DIMENSIONS) {
if (propertyFlags.getHasProperty(PROP_RADIUS)) {
float fromBuffer;
memcpy(&fromBuffer, dataAt, sizeof(fromBuffer));
dataAt += sizeof(fromBuffer);
bytesRead += sizeof(fromBuffer);
if (overwriteLocalData) {
setRadius(fromBuffer);
}
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() OLD FORMAT... found PROP_RADIUS";
}
}
} else {
READ_ENTITY_PROPERTY(PROP_DIMENSIONS, glm::vec3, _dimensions);
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() NEW FORMAT... look for PROP_DIMENSIONS";
}
}
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() _dimensions:" << getDimensionsInMeters() << " in meters";
}
READ_ENTITY_PROPERTY_QUAT(PROP_ROTATION, _rotation);
READ_ENTITY_PROPERTY(PROP_MASS, float, _mass);
READ_ENTITY_PROPERTY(PROP_VELOCITY, glm::vec3, _velocity);
@ -418,6 +478,15 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_DAMPING, float, _damping);
READ_ENTITY_PROPERTY(PROP_LIFETIME, float, _lifetime);
READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript);
READ_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, glm::vec3, _registrationPoint);
READ_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, glm::vec3, _angularVelocity);
READ_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, float, _angularDamping);
READ_ENTITY_PROPERTY(PROP_VISIBLE, bool, _visible);
if (wantDebug) {
qDebug() << " readEntityDataFromBuffer() _registrationPoint:" << _registrationPoint;
qDebug() << " readEntityDataFromBuffer() _visible:" << _visible;
}
bytesRead += readEntitySubclassDataFromBuffer(dataAt, (bytesLeftToRead - bytesRead), args, propertyFlags, overwriteLocalData);
@ -429,7 +498,7 @@ void EntityItem::debugDump() const {
qDebug() << "EntityItem id:" << getEntityItemID();
qDebug(" edited ago:%f", getEditedAgo());
qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z);
qDebug(" radius:%f", getRadius());
qDebug() << " dimensions:" << _dimensions;
}
// adjust any internal timestamps to fix clock skew for this server
@ -453,9 +522,14 @@ void EntityItem::adjustEditPacketForClockSkew(unsigned char* editPacketBuffer, s
}
}
// TODO: we probably want to change this to make "down" be the direction of the entity's gravity vector
// for now, this is always true DOWN even if entity has non-down gravity.
// TODO: the old code had "&& _velocity.y >= -EPSILON && _velocity.y <= EPSILON" --- what was I thinking?
bool EntityItem::isRestingOnSurface() const {
return _position.y <= _radius
&& _velocity.y >= -EPSILON && _velocity.y <= EPSILON
glm::vec3 downwardVelocity = glm::vec3(0.0f, _velocity.y, 0.0f);
return _position.y <= getDistanceToBottomOfEntity()
&& (glm::length(downwardVelocity) <= EPSILON_VELOCITY_LENGTH)
&& _gravity.y < 0.0f;
}
@ -466,9 +540,39 @@ void EntityItem::update(const quint64& updateTime) {
if (wantDebug) {
qDebug() << "********** EntityItem::update()";
qDebug() << " entity ID=" << getEntityItemID();
qDebug() << " updateTime=" << updateTime;
qDebug() << " _lastUpdated=" << _lastUpdated;
qDebug() << " timeElapsed=" << timeElapsed;
qDebug() << " hasVelocity=" << hasVelocity();
qDebug() << " hasGravity=" << hasGravity();
qDebug() << " isRestingOnSurface=" << isRestingOnSurface();
qDebug() << " hasAngularVelocity=" << hasAngularVelocity();
qDebug() << " getAngularVelocity=" << getAngularVelocity();
qDebug() << " isMortal=" << isMortal();
qDebug() << " getAge()=" << getAge();
qDebug() << " getLifetime()=" << getLifetime();
if (hasVelocity() || (hasGravity() && !isRestingOnSurface())) {
qDebug() << " MOVING...=";
qDebug() << " hasVelocity=" << hasVelocity();
qDebug() << " hasGravity=" << hasGravity();
qDebug() << " isRestingOnSurface=" << isRestingOnSurface();
qDebug() << " hasAngularVelocity=" << hasAngularVelocity();
qDebug() << " getAngularVelocity=" << getAngularVelocity();
}
if (hasAngularVelocity()) {
qDebug() << " CHANGING...=";
qDebug() << " hasAngularVelocity=" << hasAngularVelocity();
qDebug() << " getAngularVelocity=" << getAngularVelocity();
}
if (isMortal()) {
qDebug() << " MORTAL...=";
qDebug() << " isMortal=" << isMortal();
qDebug() << " getAge()=" << getAge();
qDebug() << " getLifetime()=" << getLifetime();
}
}
_lastUpdated = updateTime;
@ -477,22 +581,54 @@ void EntityItem::update(const quint64& updateTime) {
qDebug() << "********** EntityItem::update() .... SETTING _lastUpdated=" << _lastUpdated;
}
if (hasAngularVelocity()) {
glm::quat rotation = getRotation();
glm::vec3 angularVelocity = glm::radians(getAngularVelocity());
float angularSpeed = glm::length(angularVelocity);
if (angularSpeed < EPSILON_VELOCITY_LENGTH) {
setAngularVelocity(NO_ANGULAR_VELOCITY);
} else {
float angle = timeElapsed * angularSpeed;
glm::quat dQ = glm::angleAxis(angle, glm::normalize(angularVelocity));
rotation = dQ * rotation;
setRotation(rotation);
// handle damping for angular velocity
if (getAngularDamping() > 0.0f) {
glm::vec3 dampingResistance = getAngularVelocity() * getAngularDamping();
glm::vec3 newAngularVelocity = getAngularVelocity() - (dampingResistance * timeElapsed);
setAngularVelocity(newAngularVelocity);
if (wantDebug) {
qDebug() << " getDamping():" << getDamping();
qDebug() << " dampingResistance:" << dampingResistance;
qDebug() << " newAngularVelocity:" << newAngularVelocity;
}
}
}
}
if (hasVelocity() || hasGravity()) {
glm::vec3 position = getPosition();
glm::vec3 velocity = getVelocity();
glm::vec3 newPosition = position + (velocity * timeElapsed);
if (wantDebug) {
qDebug() << "EntityItem::update()....";
qDebug() << " timeElapsed:" << timeElapsed;
qDebug() << " old AACube:" << getAACube();
qDebug() << " old AACube:" << getMaximumAACube();
qDebug() << " old position:" << position;
qDebug() << " old velocity:" << velocity;
qDebug() << " old getAABox:" << getAABox();
qDebug() << " getDistanceToBottomOfEntity():" << getDistanceToBottomOfEntity() * (float)TREE_SCALE << " in meters";
qDebug() << " newPosition:" << newPosition;
qDebug() << " glm::distance(newPosition, position):" << glm::distance(newPosition, position);
}
position += velocity * timeElapsed;
position = newPosition;
// handle bounces off the ground... We bounce at the height of our radius...
if (position.y <= _radius) {
// handle bounces off the ground... We bounce at the distance to the bottom of our entity
if (position.y <= getDistanceToBottomOfEntity()) {
velocity = velocity * glm::vec3(1,-1,1);
// if we've slowed considerably, then just stop moving
@ -500,7 +636,7 @@ void EntityItem::update(const quint64& updateTime) {
velocity = NO_VELOCITY;
}
position.y = _radius;
position.y = getDistanceToBottomOfEntity();
}
// handle gravity....
@ -512,10 +648,10 @@ void EntityItem::update(const quint64& updateTime) {
// "ground" plane of the domain, but for now it
if (hasGravity() && isRestingOnSurface()) {
velocity.y = 0.0f;
position.y = _radius;
position.y = getDistanceToBottomOfEntity();
}
// handle damping
// handle damping for velocity
glm::vec3 dampingResistance = velocity * getDamping();
if (wantDebug) {
qDebug() << " getDamping():" << getDamping();
@ -526,21 +662,23 @@ void EntityItem::update(const quint64& updateTime) {
if (wantDebug) {
qDebug() << " velocity AFTER dampingResistance:" << velocity;
qDebug() << " glm::length(velocity):" << glm::length(velocity);
qDebug() << " EPSILON_VELOCITY_LENGTH:" << EPSILON_VELOCITY_LENGTH;
}
// round velocity to zero if it's close enough...
if (glm::length(velocity) <= EPSILON_VELOCITY_LENGTH) {
velocity = NO_VELOCITY;
}
setPosition(position);
setVelocity(velocity);
if (wantDebug) {
qDebug() << " new position:" << position;
qDebug() << " new velocity:" << velocity;
}
setPosition(position);
setVelocity(velocity);
if (wantDebug) {
qDebug() << " new AACube:" << getAACube();
qDebug() << " new AACube:" << getMaximumAACube();
qDebug() << " old getAABox:" << getAABox();
}
}
}
@ -549,6 +687,9 @@ EntityItem::SimulationState EntityItem::getSimulationState() const {
if (hasVelocity() || (hasGravity() && !isRestingOnSurface())) {
return EntityItem::Moving;
}
if (hasAngularVelocity()) {
return EntityItem::Changing;
}
if (isMortal()) {
return EntityItem::Mortal;
}
@ -566,33 +707,26 @@ void EntityItem::copyChangedProperties(const EntityItem& other) {
EntityItemProperties EntityItem::getProperties() const {
EntityItemProperties properties;
properties._id = getID();
properties._idSet = true;
properties._created = _created;
properties._type = getType();
properties._position = getPosition() * (float) TREE_SCALE;
properties._radius = getRadius() * (float) TREE_SCALE;
properties._rotation = getRotation();
properties._mass = getMass();
properties._velocity = getVelocity() * (float) TREE_SCALE;
properties._gravity = getGravity() * (float) TREE_SCALE;
properties._damping = getDamping();
properties._lifetime = getLifetime();
properties._script = getScript();
properties._positionChanged = false;
properties._radiusChanged = false;
properties._rotationChanged = false;
properties._massChanged = false;
properties._velocityChanged = false;
properties._gravityChanged = false;
properties._dampingChanged = false;
properties._lifetimeChanged = false;
properties._scriptChanged = false;
COPY_ENTITY_PROPERTY_TO_PROPERTIES(position, getPositionInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(dimensions, getDimensionsInMeters); // NOTE: radius is obsolete
COPY_ENTITY_PROPERTY_TO_PROPERTIES(rotation, getRotation);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(mass, getMass);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(velocity, getVelocityInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(gravity, getGravityInMeters);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(damping, getDamping);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(lifetime, getLifetime);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(script, getScript);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(registrationPoint, getRegistrationPoint);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularVelocity, getAngularVelocity);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(angularDamping, getAngularDamping);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(glowLevel, getGlowLevel);
COPY_ENTITY_PROPERTY_TO_PROPERTIES(visible, getVisible);
properties._defaultSettings = false;
@ -610,55 +744,21 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc
_created = properties.getCreated();
}
}
if (properties._positionChanged || forceCopy) {
// clamp positions to the domain to prevent someone from moving an entity out of the domain
setPosition(glm::clamp(properties._position / (float) TREE_SCALE, 0.0f, 1.0f));
somethingChanged = true;
}
if (properties._radiusChanged || forceCopy) {
setRadius(properties._radius / (float) TREE_SCALE);
somethingChanged = true;
}
if (properties._rotationChanged || forceCopy) {
setRotation(properties._rotation);
somethingChanged = true;
}
if (properties._massChanged || forceCopy) {
setMass(properties._mass);
somethingChanged = true;
}
if (properties._velocityChanged || forceCopy) {
setVelocity(properties._velocity / (float) TREE_SCALE);
somethingChanged = true;
}
if (properties._massChanged || forceCopy) {
setMass(properties._mass);
somethingChanged = true;
}
if (properties._gravityChanged || forceCopy) {
setGravity(properties._gravity / (float) TREE_SCALE);
somethingChanged = true;
}
if (properties._dampingChanged || forceCopy) {
setDamping(properties._damping);
somethingChanged = true;
}
if (properties._lifetimeChanged || forceCopy) {
setLifetime(properties._lifetime);
somethingChanged = true;
}
if (properties._scriptChanged || forceCopy) {
setScript(properties._script);
somethingChanged = true;
}
SET_ENTITY_PROPERTY_FROM_PROPERTIES(position, setPositionInMeters);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(dimensions, setDimensionsInMeters); // NOTE: radius is obsolete
SET_ENTITY_PROPERTY_FROM_PROPERTIES(rotation, setRotation);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(mass, setMass);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(velocity, setVelocityInMeters);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(gravity, setGravityInMeters);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(damping, setDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(lifetime, setLifetime);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(script, setScript);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(registrationPoint, setRegistrationPoint);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularVelocity, setAngularVelocity);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(angularDamping, setAngularDamping);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(glowLevel, setGlowLevel);
SET_ENTITY_PROPERTY_FROM_PROPERTIES(visible, setVisible);
if (somethingChanged) {
somethingChangedNotification(); // notify derived classes that something has changed
@ -675,3 +775,127 @@ bool EntityItem::setProperties(const EntityItemProperties& properties, bool forc
return somethingChanged;
}
// TODO: is this really correct? how do we use size, does it need to handle rotation?
float EntityItem::getSize() const {
return glm::length(_dimensions);
}
float EntityItem::getDistanceToBottomOfEntity() const {
glm::vec3 minimumPoint = getAABox().getMinimumPoint();
return getPosition().y - minimumPoint.y;
}
// TODO: doesn't this need to handle rotation?
glm::vec3 EntityItem::getCenter() const {
return _position + (_dimensions * (glm::vec3(0.5f,0.5f,0.5f) - _registrationPoint));
}
/// The maximum bounding cube for the entity, independent of it's rotation.
/// This accounts for the registration point (upon which rotation occurs around).
///
AACube EntityItem::getMaximumAACube() const {
// * we know that the position is the center of rotation
glm::vec3 centerOfRotation = _position; // also where _registration point is
// * we know that the registration point is the center of rotation
// * we can calculate the length of the furthest extent from the registration point
// as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint)
glm::vec3 registrationPoint = (_dimensions * _registrationPoint);
glm::vec3 registrationRemainder = (_dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint));
glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder);
// * we know that if you rotate in any direction you would create a sphere
// that has a radius of the length of furthest extent from registration point
float radius = glm::length(furthestExtentFromRegistration);
// * we know that the minimum bounding cube of this maximum possible sphere is
// (center - radius) to (center + radius)
glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius);
AACube boundingCube(minimumCorner, radius * 2.0f);
return boundingCube;
}
/// The minimum bounding cube for the entity accounting for it's rotation.
/// This accounts for the registration point (upon which rotation occurs around).
///
AACube EntityItem::getMinimumAACube() const {
// _position represents the position of the registration point.
glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint;
glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint);
glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder;
Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation());
// shift the extents to be relative to the position/registration point
rotatedExtentsRelativeToRegistrationPoint.shiftBy(_position);
// the cube that best encompasses extents is...
AABox box(rotatedExtentsRelativeToRegistrationPoint);
glm::vec3 centerOfBox = box.calcCenter();
float longestSide = box.getLargestDimension();
float halfLongestSide = longestSide / 2.0f;
glm::vec3 cornerOfCube = centerOfBox - glm::vec3(halfLongestSide, halfLongestSide, halfLongestSide);
// old implementation... not correct!!!
return AACube(cornerOfCube, longestSide);
}
AABox EntityItem::getAABox() const {
// _position represents the position of the registration point.
glm::vec3 registrationRemainder = glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint;
glm::vec3 unrotatedMinRelativeToEntity = glm::vec3(0.0f, 0.0f, 0.0f) - (_dimensions * _registrationPoint);
glm::vec3 unrotatedMaxRelativeToEntity = _dimensions * registrationRemainder;
Extents unrotatedExtentsRelativeToRegistrationPoint = { unrotatedMinRelativeToEntity, unrotatedMaxRelativeToEntity };
Extents rotatedExtentsRelativeToRegistrationPoint = unrotatedExtentsRelativeToRegistrationPoint.getRotated(getRotation());
// shift the extents to be relative to the position/registration point
rotatedExtentsRelativeToRegistrationPoint.shiftBy(_position);
return AABox(rotatedExtentsRelativeToRegistrationPoint);
}
// NOTE: This should only be used in cases of old bitstreams which only contain radius data
// 0,0,0 --> maxDimension,maxDimension,maxDimension
// ... has a corner to corner distance of glm::length(maxDimension,maxDimension,maxDimension)
// ... radius = cornerToCornerLength / 2.0f
// ... radius * 2.0f = cornerToCornerLength
// ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2)
// ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2)
// ... radius * 2.0f = sqrt(3 x maxDimension ^ 2)
// ... (radius * 2.0f) ^2 = 3 x maxDimension ^ 2
// ... ((radius * 2.0f) ^2) / 3 = maxDimension ^ 2
// ... sqrt(((radius * 2.0f) ^2) / 3) = maxDimension
// ... sqrt((diameter ^2) / 3) = maxDimension
//
void EntityItem::setRadius(float value) {
float diameter = value * 2.0f;
float maxDimension = sqrt((diameter * diameter) / 3.0f);
_dimensions = glm::vec3(maxDimension, maxDimension, maxDimension);
bool wantDebug = false;
if (wantDebug) {
qDebug() << "EntityItem::setRadius()...";
qDebug() << " radius:" << value;
qDebug() << " diameter:" << diameter;
qDebug() << " maxDimension:" << maxDimension;
qDebug() << " _dimensions:" << _dimensions;
}
}
// TODO: get rid of all users of this function...
// ... radius = cornerToCornerLength / 2.0f
// ... cornerToCornerLength = sqrt(3 x maxDimension ^ 2)
// ... radius = sqrt(3 x maxDimension ^ 2) / 2.0f;
float EntityItem::getRadius() const {
float length = glm::length(_dimensions);
float radius = length / 2.0f;
return radius;
}

View file

@ -25,6 +25,7 @@
#include "EntityItemProperties.h"
#include "EntityTypes.h"
class EntityTreeElement;
class EntityTreeElementExtraEncodeData;
#define DONT_ALLOW_INSTANTIATION virtual void pureVirtualFunctionPlaceHolder() = 0;
@ -39,6 +40,7 @@ class EntityItem {
public:
DONT_ALLOW_INSTANTIATION // This class can not be instantiated directly
EntityItem(const EntityItemID& entityItemID);
EntityItem(const EntityItemID& entityItemID, const EntityItemProperties& properties);
virtual ~EntityItem() { }
@ -119,11 +121,27 @@ public:
// attributes applicable to all entity types
EntityTypes::EntityType getType() const { return _type; }
const glm::vec3& getPosition() const { return _position; } /// get position in domain scale units (0.0 - 1.0)
glm::vec3 getPositionInMeters() const { return _position * (float) TREE_SCALE; } /// get position in meters
void setPosition(const glm::vec3& value) { _position = value; } /// set position in domain scale units (0.0 - 1.0)
void setPositionInMeters(const glm::vec3& value) /// set position in meter units (0.0 - TREE_SCALE)
{ setPosition(glm::clamp(value / (float) TREE_SCALE, 0.0f, 1.0f)); }
float getRadius() const { return _radius; } /// get radius in domain scale units (0.0 - 1.0)
void setRadius(float value) { _radius = value; } /// set radius in domain scale units (0.0 - 1.0)
glm::vec3 getCenter() const; /// calculates center of the entity in domain scale units (0.0 - 1.0)
glm::vec3 getCenterInMeters() const { return getCenter() * (float) TREE_SCALE; }
static const glm::vec3 DEFAULT_DIMENSIONS;
const glm::vec3& getDimensions() const { return _dimensions; } /// get dimensions in domain scale units (0.0 - 1.0)
glm::vec3 getDimensionsInMeters() const { return _dimensions * (float) TREE_SCALE; } /// get dimensions in meters
float getDistanceToBottomOfEntity() const; /// get the distance from the position of the entity to its "bottom" in y axis
float getLargestDimension() const { return glm::length(_dimensions); } /// get the largest possible dimension
/// set dimensions in domain scale units (0.0 - 1.0) this will also reset radius appropriately
void setDimensions(const glm::vec3& value) { _dimensions = value; }
/// set dimensions in meter units (0.0 - TREE_SCALE) this will also reset radius appropriately
void setDimensionsInMeters(const glm::vec3& value) { setDimensions(value / (float) TREE_SCALE); }
static const glm::quat DEFAULT_ROTATION;
const glm::quat& getRotation() const { return _rotation; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; }
@ -138,15 +156,19 @@ public:
static const glm::vec3 DEFAULT_VELOCITY;
static const glm::vec3 NO_VELOCITY;
static const float EPSILON_VELOCITY_LENGTH;
const glm::vec3& getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second
const glm::vec3 getVelocity() const { return _velocity; } /// velocity in domain scale units (0.0-1.0) per second
glm::vec3 getVelocityInMeters() const { return _velocity * (float) TREE_SCALE; } /// get velocity in meters
void setVelocity(const glm::vec3& value) { _velocity = value; } /// velocity in domain scale units (0.0-1.0) per second
void setVelocityInMeters(const glm::vec3& value) { _velocity = value / (float) TREE_SCALE; } /// velocity in meters
bool hasVelocity() const { return _velocity != NO_VELOCITY; }
static const glm::vec3 DEFAULT_GRAVITY;
static const glm::vec3 REGULAR_GRAVITY;
static const glm::vec3 NO_GRAVITY;
const glm::vec3& getGravity() const { return _gravity; } /// gravity in domain scale units (0.0-1.0) per second squared
glm::vec3 getGravityInMeters() const { return _gravity * (float) TREE_SCALE; } /// get gravity in meters
void setGravity(const glm::vec3& value) { _gravity = value; } /// gravity in domain scale units (0.0-1.0) per second squared
void setGravityInMeters(const glm::vec3& value) { _gravity = value / (float) TREE_SCALE; } /// gravity in meters
bool hasGravity() const { return _gravity != NO_GRAVITY; }
// TODO: this should eventually be updated to support resting on collisions with other surfaces
@ -173,14 +195,38 @@ public:
bool lifetimeHasExpired() const;
// position, size, and bounds related helpers
float getSize() const { return _radius * 2.0f; } /// get maximum dimension in domain scale units (0.0 - 1.0)
glm::vec3 getMinimumPoint() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPoint() const { return _position + glm::vec3(_radius, _radius, _radius); }
AACube getAACube() const { return AACube(getMinimumPoint(), getSize()); } /// AACube in domain scale units (0.0 - 1.0)
float getSize() const; /// get maximum dimension in domain scale units (0.0 - 1.0)
AACube getMaximumAACube() const;
AACube getMinimumAACube() const;
AABox getAABox() const; /// axis aligned bounding box in domain scale units (0.0 - 1.0)
static const QString DEFAULT_SCRIPT;
const QString& getScript() const { return _script; }
void setScript(const QString& value) { _script = value; }
static const glm::vec3 DEFAULT_REGISTRATION_POINT;
const glm::vec3& getRegistrationPoint() const { return _registrationPoint; } /// registration point as ratio of entity
void setRegistrationPoint(const glm::vec3& value) { _registrationPoint = glm::clamp(value, 0.0f, 1.0f); } /// registration point as ratio of entity
static const glm::vec3 NO_ANGULAR_VELOCITY;
static const glm::vec3 DEFAULT_ANGULAR_VELOCITY;
const glm::vec3& getAngularVelocity() const { return _angularVelocity; }
void setAngularVelocity(const glm::vec3& value) { _angularVelocity = value; }
bool hasAngularVelocity() const { return _angularVelocity != NO_ANGULAR_VELOCITY; }
static const float DEFAULT_ANGULAR_DAMPING;
float getAngularDamping() const { return _angularDamping; }
void setAngularDamping(float value) { _angularDamping = value; }
static const bool DEFAULT_VISIBLE;
bool getVisible() const { return _visible; }
void setVisible(bool value) { _visible = value; }
bool isVisible() const { return _visible; }
bool isInvisible() const { return !_visible; }
// TODO: We need to get rid of these users of getRadius()...
float getRadius() const;
protected:
virtual void initFromEntityItemID(const EntityItemID& entityItemID); // maybe useful to allow subclasses to init
@ -196,7 +242,7 @@ protected:
quint64 _created;
glm::vec3 _position;
float _radius;
glm::vec3 _dimensions;
glm::quat _rotation;
float _glowLevel;
float _mass;
@ -205,6 +251,17 @@ protected:
float _damping;
float _lifetime;
QString _script;
glm::vec3 _registrationPoint;
glm::vec3 _angularVelocity;
float _angularDamping;
bool _visible;
// NOTE: Radius support is obsolete, but these private helper functions are available for this class to
// parse old data streams
/// set radius in domain scale units (0.0 - 1.0) this will also reset dimensions to be equal for each axis
void setRadius(float value);
};

View file

@ -18,6 +18,7 @@
#include "EntityItem.h"
#include "EntityItemProperties.h"
#include "ModelEntityItem.h"
EntityItemProperties::EntityItemProperties() :
@ -28,17 +29,21 @@ EntityItemProperties::EntityItemProperties() :
_type(EntityTypes::Unknown),
_position(0),
_radius(ENTITY_DEFAULT_RADIUS),
_rotation(ENTITY_DEFAULT_ROTATION),
_dimensions(EntityItem::DEFAULT_DIMENSIONS),
_rotation(EntityItem::DEFAULT_ROTATION),
_mass(EntityItem::DEFAULT_MASS),
_velocity(EntityItem::DEFAULT_VELOCITY),
_gravity(EntityItem::DEFAULT_GRAVITY),
_damping(EntityItem::DEFAULT_DAMPING),
_lifetime(EntityItem::DEFAULT_LIFETIME),
_script(EntityItem::DEFAULT_SCRIPT),
_registrationPoint(EntityItem::DEFAULT_REGISTRATION_POINT),
_angularVelocity(EntityItem::DEFAULT_ANGULAR_VELOCITY),
_angularDamping(EntityItem::DEFAULT_ANGULAR_DAMPING),
_visible(EntityItem::DEFAULT_VISIBLE),
_positionChanged(false),
_radiusChanged(false),
_dimensionsChanged(false),
_rotationChanged(false),
_massChanged(false),
_velocityChanged(false),
@ -46,15 +51,20 @@ EntityItemProperties::EntityItemProperties() :
_dampingChanged(false),
_lifetimeChanged(false),
_scriptChanged(false),
_registrationPointChanged(false),
_angularVelocityChanged(false),
_angularDampingChanged(false),
_visibleChanged(false),
_color(),
_modelURL(""),
_animationURL(""),
_animationIsPlaying(false),
_animationFrameIndex(0.0),
_animationFPS(ENTITY_DEFAULT_ANIMATION_FPS),
_animationIsPlaying(ModelEntityItem::DEFAULT_ANIMATION_IS_PLAYING),
_animationFrameIndex(ModelEntityItem::DEFAULT_ANIMATION_FRAME_INDEX),
_animationFPS(ModelEntityItem::DEFAULT_ANIMATION_FPS),
_glowLevel(0.0f),
_naturalDimensions(1.0f, 1.0f, 1.0f),
_colorChanged(false),
_modelURLChanged(false),
_animationURLChanged(false),
@ -73,7 +83,7 @@ void EntityItemProperties::debugDump() const {
qDebug() << " _id=" << _id;
qDebug() << " _idSet=" << _idSet;
qDebug() << " _position=" << _position.x << "," << _position.y << "," << _position.z;
qDebug() << " _radius=" << _radius;
qDebug() << " _dimensions=" << getDimensions();
qDebug() << " _modelURL=" << _modelURL;
qDebug() << " changed properties...";
EntityPropertyFlags props = getChangedProperties();
@ -82,65 +92,26 @@ void EntityItemProperties::debugDump() const {
EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
EntityPropertyFlags changedProperties;
if (_radiusChanged) {
changedProperties += PROP_RADIUS;
}
if (_positionChanged) {
changedProperties += PROP_POSITION;
}
if (_rotationChanged) {
changedProperties += PROP_ROTATION;
}
if (_massChanged) {
changedProperties += PROP_MASS;
}
if (_velocityChanged) {
changedProperties += PROP_VELOCITY;
}
if (_gravityChanged) {
changedProperties += PROP_GRAVITY;
}
if (_dampingChanged) {
changedProperties += PROP_DAMPING;
}
if (_lifetimeChanged) {
changedProperties += PROP_LIFETIME;
}
if (_scriptChanged) {
changedProperties += PROP_SCRIPT;
}
if (_colorChanged) {
changedProperties += PROP_COLOR;
}
if (_modelURLChanged) {
changedProperties += PROP_MODEL_URL;
}
if (_animationURLChanged) {
changedProperties += PROP_ANIMATION_URL;
}
if (_animationIsPlayingChanged) {
changedProperties += PROP_ANIMATION_PLAYING;
}
if (_animationFrameIndexChanged) {
changedProperties += PROP_ANIMATION_FRAME_INDEX;
}
if (_animationFPSChanged) {
changedProperties += PROP_ANIMATION_FPS;
}
CHECK_PROPERTY_CHANGE(PROP_DIMENSIONS, dimensions);
CHECK_PROPERTY_CHANGE(PROP_POSITION, position);
CHECK_PROPERTY_CHANGE(PROP_ROTATION, rotation);
CHECK_PROPERTY_CHANGE(PROP_MASS, mass);
CHECK_PROPERTY_CHANGE(PROP_VELOCITY, velocity);
CHECK_PROPERTY_CHANGE(PROP_GRAVITY, gravity);
CHECK_PROPERTY_CHANGE(PROP_DAMPING, damping);
CHECK_PROPERTY_CHANGE(PROP_LIFETIME, lifetime);
CHECK_PROPERTY_CHANGE(PROP_SCRIPT, script);
CHECK_PROPERTY_CHANGE(PROP_COLOR, color);
CHECK_PROPERTY_CHANGE(PROP_MODEL_URL, modelURL);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_URL, animationURL);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_PLAYING, animationIsPlaying);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FRAME_INDEX, animationFrameIndex);
CHECK_PROPERTY_CHANGE(PROP_ANIMATION_FPS, animationFPS);
CHECK_PROPERTY_CHANGE(PROP_VISIBLE, visible);
CHECK_PROPERTY_CHANGE(PROP_REGISTRATION_POINT, registrationPoint);
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_VELOCITY, angularVelocity);
CHECK_PROPERTY_CHANGE(PROP_ANGULAR_DAMPING, angularDamping);
return changedProperties;
}
@ -149,38 +120,35 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
QScriptValue properties = engine->newObject();
if (_idSet) {
properties.setProperty("id", _id.toString());
bool isKnownID = (_id != UNKNOWN_ENTITY_ID);
properties.setProperty("isKnownID", isKnownID);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(id, _id.toString());
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(isKnownID, (_id != UNKNOWN_ENTITY_ID));
} else {
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(isKnownID, false);
}
properties.setProperty("type", EntityTypes::getEntityTypeName(_type));
QScriptValue position = vec3toScriptValue(engine, _position);
properties.setProperty("position", position);
properties.setProperty("radius", _radius);
QScriptValue rotation = quatToScriptValue(engine, _rotation);
properties.setProperty("rotation", rotation);
properties.setProperty("mass", _mass);
QScriptValue velocity = vec3toScriptValue(engine, _velocity);
properties.setProperty("velocity", velocity);
QScriptValue gravity = vec3toScriptValue(engine, _gravity);
properties.setProperty("gravity", gravity);
properties.setProperty("damping", _damping);
properties.setProperty("lifetime", _lifetime);
properties.setProperty("age", getAge()); // gettable, but not settable
properties.setProperty("ageAsText", formatSecondsElapsed(getAge())); // gettable, but not settable
properties.setProperty("script", _script);
QScriptValue color = xColorToScriptValue(engine, _color);
properties.setProperty("color", color);
properties.setProperty("modelURL", _modelURL);
properties.setProperty("animationURL", _animationURL);
properties.setProperty("animationIsPlaying", _animationIsPlaying);
properties.setProperty("animationFrameIndex", _animationFrameIndex);
properties.setProperty("animationFPS", _animationFPS);
properties.setProperty("glowLevel", _glowLevel);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(type, EntityTypes::getEntityTypeName(_type));
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(position);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(dimensions);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(naturalDimensions); // gettable, but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE_QUAT(rotation);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(velocity);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(gravity);
COPY_PROPERTY_TO_QSCRIPTVALUE(damping);
COPY_PROPERTY_TO_QSCRIPTVALUE(lifetime);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(age, getAge()); // gettable, but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(ageAsText, formatSecondsElapsed(getAge())); // gettable, but not settable
COPY_PROPERTY_TO_QSCRIPTVALUE(script);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(registrationPoint);
COPY_PROPERTY_TO_QSCRIPTVALUE_VEC3(angularVelocity);
COPY_PROPERTY_TO_QSCRIPTVALUE(angularDamping);
COPY_PROPERTY_TO_QSCRIPTVALUE(visible);
COPY_PROPERTY_TO_QSCRIPTVALUE_COLOR(color);
COPY_PROPERTY_TO_QSCRIPTVALUE(modelURL);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationURL);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationIsPlaying);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFrameIndex);
COPY_PROPERTY_TO_QSCRIPTVALUE(animationFPS);
COPY_PROPERTY_TO_QSCRIPTVALUE(glowLevel);
// Sitting properties support
QScriptValue sittingPoints = engine->newObject();
@ -192,217 +160,39 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine) cons
sittingPoints.setProperty(i, sittingPoint);
}
sittingPoints.setProperty("length", _sittingPoints.size());
properties.setProperty("sittingPoints", sittingPoints);
COPY_PROPERTY_TO_QSCRIPTVALUE_GETTER(sittingPoints, sittingPoints); // gettable, but not settable
return properties;
}
void EntityItemProperties::copyFromScriptValue(const QScriptValue& object) {
QScriptValue typeScriptValue = object.property("type");
if (typeScriptValue.isValid()) {
QString typeName;
typeName = typeScriptValue.toVariant().toString();
_type = EntityTypes::getEntityTypeFromName(typeName);
setType(typeScriptValue.toVariant().toString());
}
QScriptValue position = object.property("position");
if (position.isValid()) {
QScriptValue x = position.property("x");
QScriptValue y = position.property("y");
QScriptValue z = position.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newPosition;
newPosition.x = x.toVariant().toFloat();
newPosition.y = y.toVariant().toFloat();
newPosition.z = z.toVariant().toFloat();
if (_defaultSettings || newPosition != _position) {
setPosition(newPosition); // gives us automatic clamping
}
}
}
QScriptValue radius = object.property("radius");
if (radius.isValid()) {
float newRadius;
newRadius = radius.toVariant().toFloat();
if (_defaultSettings || newRadius != _radius) {
_radius = newRadius;
_radiusChanged = true;
}
}
QScriptValue rotation = object.property("rotation");
if (rotation.isValid()) {
QScriptValue x = rotation.property("x");
QScriptValue y = rotation.property("y");
QScriptValue z = rotation.property("z");
QScriptValue w = rotation.property("w");
if (x.isValid() && y.isValid() && z.isValid() && w.isValid()) {
glm::quat newRotation;
newRotation.x = x.toVariant().toFloat();
newRotation.y = y.toVariant().toFloat();
newRotation.z = z.toVariant().toFloat();
newRotation.w = w.toVariant().toFloat();
if (_defaultSettings || newRotation != _rotation) {
_rotation = newRotation;
_rotationChanged = true;
}
}
}
QScriptValue mass = object.property("mass");
if (mass.isValid()) {
float newValue;
newValue = mass.toVariant().toFloat();
if (_defaultSettings || newValue != _mass) {
_mass = newValue;
_massChanged = true;
}
}
QScriptValue velocity = object.property("velocity");
if (velocity.isValid()) {
QScriptValue x = velocity.property("x");
QScriptValue y = velocity.property("y");
QScriptValue z = velocity.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newValue;
newValue.x = x.toVariant().toFloat();
newValue.y = y.toVariant().toFloat();
newValue.z = z.toVariant().toFloat();
if (_defaultSettings || newValue != _velocity) {
_velocity = newValue;
_velocityChanged = true;
}
}
}
QScriptValue gravity = object.property("gravity");
if (gravity.isValid()) {
QScriptValue x = gravity.property("x");
QScriptValue y = gravity.property("y");
QScriptValue z = gravity.property("z");
if (x.isValid() && y.isValid() && z.isValid()) {
glm::vec3 newValue;
newValue.x = x.toVariant().toFloat();
newValue.y = y.toVariant().toFloat();
newValue.z = z.toVariant().toFloat();
if (_defaultSettings || newValue != _gravity) {
_gravity = newValue;
_gravityChanged = true;
}
}
}
QScriptValue damping = object.property("damping");
if (damping.isValid()) {
float newValue;
newValue = damping.toVariant().toFloat();
if (_defaultSettings || newValue != _damping) {
_damping = newValue;
_dampingChanged = true;
}
}
QScriptValue lifetime = object.property("lifetime");
if (lifetime.isValid()) {
float newValue;
newValue = lifetime.toVariant().toFloat();
if (_defaultSettings || newValue != _lifetime) {
_lifetime = newValue;
_lifetimeChanged = true;
}
}
QScriptValue script = object.property("script");
if (script.isValid()) {
QString newValue;
newValue = script.toVariant().toString();
if (_defaultSettings || newValue != _script) {
_script = newValue;
_scriptChanged = true;
}
}
QScriptValue color = object.property("color");
if (color.isValid()) {
QScriptValue red = color.property("red");
QScriptValue green = color.property("green");
QScriptValue blue = color.property("blue");
if (red.isValid() && green.isValid() && blue.isValid()) {
xColor newColor;
newColor.red = red.toVariant().toInt();
newColor.green = green.toVariant().toInt();
newColor.blue = blue.toVariant().toInt();
if (_defaultSettings || (newColor.red != _color.red ||
newColor.green != _color.green ||
newColor.blue != _color.blue)) {
_color = newColor;
_colorChanged = true;
}
}
}
QScriptValue modelURL = object.property("modelURL");
if (modelURL.isValid()) {
QString newModelURL;
newModelURL = modelURL.toVariant().toString();
if (_defaultSettings || newModelURL != _modelURL) {
_modelURL = newModelURL;
_modelURLChanged = true;
}
}
QScriptValue animationURL = object.property("animationURL");
if (animationURL.isValid()) {
QString newAnimationURL;
newAnimationURL = animationURL.toVariant().toString();
if (_defaultSettings || newAnimationURL != _animationURL) {
_animationURL = newAnimationURL;
_animationURLChanged = true;
}
}
QScriptValue animationIsPlaying = object.property("animationIsPlaying");
if (animationIsPlaying.isValid()) {
bool newIsAnimationPlaying;
newIsAnimationPlaying = animationIsPlaying.toVariant().toBool();
if (_defaultSettings || newIsAnimationPlaying != _animationIsPlaying) {
_animationIsPlaying = newIsAnimationPlaying;
_animationIsPlayingChanged = true;
}
}
QScriptValue animationFrameIndex = object.property("animationFrameIndex");
if (animationFrameIndex.isValid()) {
float newFrameIndex;
newFrameIndex = animationFrameIndex.toVariant().toFloat();
if (_defaultSettings || newFrameIndex != _animationFrameIndex) {
_animationFrameIndex = newFrameIndex;
_animationFrameIndexChanged = true;
}
}
QScriptValue animationFPS = object.property("animationFPS");
if (animationFPS.isValid()) {
float newFPS;
newFPS = animationFPS.toVariant().toFloat();
if (_defaultSettings || newFPS != _animationFPS) {
_animationFPS = newFPS;
_animationFPSChanged = true;
}
}
QScriptValue glowLevel = object.property("glowLevel");
if (glowLevel.isValid()) {
float newGlowLevel;
newGlowLevel = glowLevel.toVariant().toFloat();
if (_defaultSettings || newGlowLevel != _glowLevel) {
_glowLevel = newGlowLevel;
_glowLevelChanged = true;
}
}
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(position, setPosition);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(dimensions, setDimensions);
COPY_PROPERTY_FROM_QSCRIPTVALUE_QUAT(rotation, setRotation);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(mass, setMass);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(velocity, setVelocity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(gravity, setGravity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(damping, setDamping);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(lifetime, setLifetime);
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(script, setScript);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(registrationPoint, setRegistrationPoint);
COPY_PROPERTY_FROM_QSCRIPTVALUE_VEC3(angularVelocity, setAngularVelocity);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(angularDamping, setAngularDamping);
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(visible, setVisible);
COPY_PROPERTY_FROM_QSCRIPTVALUE_COLOR(color, setColor);
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(modelURL, setModelURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE_STRING(animationURL, setAnimationURL);
COPY_PROPERTY_FROM_QSCRIPTVALUE_BOOL(animationIsPlaying, setAnimationIsPlaying);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFPS, setAnimationFPS);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(animationFrameIndex, setAnimationFrameIndex);
COPY_PROPERTY_FROM_QSCRIPTVALUE_FLOAT(glowLevel, setGlowLevel);
_lastEdited = usecTimestampNow();
}
@ -432,7 +222,6 @@ void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemP
//
bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
unsigned char* bufferOut, int sizeIn, int& sizeOut) {
OctreePacketData ourDataPacket(false, sizeIn); // create a packetData object to add out packet details too.
OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro
@ -532,23 +321,26 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
// These items would go here once supported....
// PROP_PAGED_PROPERTY,
// PROP_CUSTOM_PROPERTIES_INCLUDED,
// PROP_VISIBLE,
APPEND_ENTITY_PROPERTY(PROP_POSITION, appendPosition, properties.getPosition());
APPEND_ENTITY_PROPERTY(PROP_RADIUS, appendValue, properties.getRadius());
APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, appendValue, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete
APPEND_ENTITY_PROPERTY(PROP_ROTATION, appendValue, properties.getRotation());
APPEND_ENTITY_PROPERTY(PROP_MASS, appendValue, properties.getMass());
APPEND_ENTITY_PROPERTY(PROP_VELOCITY, appendValue, properties.getVelocity());
APPEND_ENTITY_PROPERTY(PROP_GRAVITY, appendValue, properties.getGravity());
APPEND_ENTITY_PROPERTY(PROP_DAMPING, appendValue, properties.getDamping());
APPEND_ENTITY_PROPERTY(PROP_LIFETIME, appendValue, properties.getLifetime());
//APPEND_ENTITY_PROPERTY(PROP_SCRIPT, appendValue, properties.getScript()); // not supported by edit messages
APPEND_ENTITY_PROPERTY(PROP_SCRIPT, appendValue, properties.getScript());
APPEND_ENTITY_PROPERTY(PROP_COLOR, appendColor, properties.getColor());
APPEND_ENTITY_PROPERTY(PROP_MODEL_URL, appendValue, properties.getModelURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_URL, appendValue, properties.getAnimationURL());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FPS, appendValue, properties.getAnimationFPS());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_FRAME_INDEX, appendValue, properties.getAnimationFrameIndex());
APPEND_ENTITY_PROPERTY(PROP_ANIMATION_PLAYING, appendValue, properties.getAnimationIsPlaying());
APPEND_ENTITY_PROPERTY(PROP_REGISTRATION_POINT, appendValue, properties.getRegistrationPoint());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, appendValue, properties.getAngularVelocity());
APPEND_ENTITY_PROPERTY(PROP_ANGULAR_DAMPING, appendValue, properties.getAngularDamping());
APPEND_ENTITY_PROPERTY(PROP_VISIBLE, appendValue, properties.getVisible());
}
if (propertyCount > 0) {
int endOfEntityItemData = packetData->getUncompressedByteOffset();
@ -727,20 +519,24 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
processedBytes += propertyFlags.getEncodedLength();
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_RADIUS, float, setRadius);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete
READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(PROP_ROTATION, setRotation);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MASS, float, setMass);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_GRAVITY, glm::vec3, setGravity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DAMPING, float, setDamping);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_LIFETIME, float, setLifetime);
//READ_ENTITY_PROPERTY_STRING(PROP_SCRIPT,setScript); // not yet supported by edit messages...
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_SCRIPT,setScript);
READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(PROP_COLOR, setColor);
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_MODEL_URL, setModelURL);
READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(PROP_ANIMATION_URL, setAnimationURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FPS, float, setAnimationFPS);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_FRAME_INDEX, float, setAnimationFrameIndex);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANIMATION_PLAYING, bool, setAnimationIsPlaying);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_REGISTRATION_POINT, glm::vec3, setRegistrationPoint);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_VELOCITY, glm::vec3, setAngularVelocity);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ANGULAR_DAMPING, float, setAngularDamping);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VISIBLE, bool, setVisible);
return valid;
}
@ -774,10 +570,8 @@ bool EntityItemProperties::encodeEraseEntityMessage(const EntityItemID& entityIt
return true;
}
void EntityItemProperties::markAllChanged() {
_positionChanged = true;
_radiusChanged = true;
_rotationChanged = true;
_massChanged = true;
_velocityChanged = true;
@ -785,7 +579,10 @@ void EntityItemProperties::markAllChanged() {
_dampingChanged = true;
_lifetimeChanged = true;
_scriptChanged = true;
_registrationPointChanged = true;
_angularVelocityChanged = true;
_angularDampingChanged = true;
_visibleChanged = true;
_colorChanged = true;
_modelURLChanged = true;
_animationURLChanged = true;
@ -793,5 +590,36 @@ void EntityItemProperties::markAllChanged() {
_animationFrameIndexChanged = true;
_animationFPSChanged = true;
_glowLevelChanged = true;
}
AACube EntityItemProperties::getMaximumAACubeInTreeUnits() const {
AACube maxCube = getMaximumAACubeInMeters();
maxCube.scale(1 / (float)TREE_SCALE);
return maxCube;
}
/// The maximum bounding cube for the entity, independent of it's rotation.
/// This accounts for the registration point (upon which rotation occurs around).
///
AACube EntityItemProperties::getMaximumAACubeInMeters() const {
// * we know that the position is the center of rotation
glm::vec3 centerOfRotation = _position; // also where _registration point is
// * we know that the registration point is the center of rotation
// * we can calculate the length of the furthest extent from the registration point
// as the dimensions * max (registrationPoint, (1.0,1.0,1.0) - registrationPoint)
glm::vec3 registrationPoint = (_dimensions * _registrationPoint);
glm::vec3 registrationRemainder = (_dimensions * (glm::vec3(1.0f, 1.0f, 1.0f) - _registrationPoint));
glm::vec3 furthestExtentFromRegistration = glm::max(registrationPoint, registrationRemainder);
// * we know that if you rotate in any direction you would create a sphere
// that has a radius of the length of furthest extent from registration point
float radius = glm::length(furthestExtentFromRegistration);
// * we know that the minimum bounding cube of this maximum possible sphere is
// (center - radius) to (center + radius)
glm::vec3 minimumCorner = centerOfRotation - glm::vec3(radius, radius, radius);
float diameter = radius * 2.0f;
return AACube(minimumCorner, diameter);
}

View file

@ -15,6 +15,7 @@
#include <stdint.h>
#include <glm/glm.hpp>
#include <glm/gtx/extented_min_max.hpp>
#include <QtScript/QScriptEngine>
#include <QtCore/QObject>
@ -28,20 +29,9 @@
#include "EntityItemID.h"
#include "EntityItemPropertiesMacros.h"
#include "EntityTypes.h"
// TODO: should these be static members of EntityItem or EntityItemProperties?
const float ENTITY_DEFAULT_RADIUS = 0.1f / TREE_SCALE;
const float ENTITY_MINIMUM_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container
const QString ENTITY_DEFAULT_MODEL_URL("");
const glm::quat ENTITY_DEFAULT_ROTATION;
const QString ENTITY_DEFAULT_ANIMATION_URL("");
const float ENTITY_DEFAULT_ANIMATION_FPS = 30.0f;
const quint64 UNKNOWN_CREATED_TIME = (quint64)(-1);
const quint64 USE_EXISTING_CREATED_TIME = (quint64)(-2);
// PropertyFlags support
enum EntityPropertyList {
PROP_PAGED_PROPERTY,
@ -50,7 +40,8 @@ enum EntityPropertyList {
// these properties are supported by the EntityItem base class
PROP_VISIBLE,
PROP_POSITION,
PROP_RADIUS,
PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams
PROP_DIMENSIONS = PROP_RADIUS,
PROP_ROTATION,
PROP_MASS,
PROP_VELOCITY,
@ -67,16 +58,24 @@ enum EntityPropertyList {
PROP_ANIMATION_FRAME_INDEX,
PROP_ANIMATION_PLAYING,
PROP_LAST_ITEM = PROP_ANIMATION_PLAYING
// these properties are supported by the EntityItem base class
PROP_REGISTRATION_POINT,
PROP_ANGULAR_VELOCITY,
PROP_ANGULAR_DAMPING,
PROP_LAST_ITEM = PROP_ANGULAR_DAMPING
};
typedef PropertyFlags<EntityPropertyList> EntityPropertyFlags;
const quint64 UNKNOWN_CREATED_TIME = (quint64)(-1);
const quint64 USE_EXISTING_CREATED_TIME = (quint64)(-2);
/// A collection of properties of an entity item used in the scripting API. Translates between the actual properties of an
/// entity and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete
/// set of entity item properties via JavaScript hashes/QScriptValues
/// all units for position, radius, etc are in meter units
/// all units for position, dimensions, etc are in meter units
class EntityItemProperties {
friend class EntityItem; // TODO: consider removing this friend relationship and use public methods
friend class ModelEntityItem; // TODO: consider removing this friend relationship and use public methods
@ -96,31 +95,27 @@ public:
/// used by EntityScriptingInterface to return EntityItemProperties for unknown models
void setIsUnknownID() { _id = UNKNOWN_ENTITY_ID; _idSet = true; }
glm::vec3 getMinimumPointMeters() const { return _position - glm::vec3(_radius, _radius, _radius); }
glm::vec3 getMaximumPointMeters() const { return _position + glm::vec3(_radius, _radius, _radius); }
AACube getAACubeMeters() const { return AACube(getMinimumPointMeters(), getMaxDimension()); } /// AACube in meter units
glm::vec3 getMinimumPointTreeUnits() const { return getMinimumPointMeters() / (float)TREE_SCALE; }
glm::vec3 getMaximumPointTreeUnits() const { return getMaximumPointMeters() / (float)TREE_SCALE; }
/// AACube in domain scale units (0.0 - 1.0)
AACube getAACubeTreeUnits() const {
return AACube(getMinimumPointMeters() / (float)TREE_SCALE, getMaxDimension() / (float)TREE_SCALE);
}
AACube getMaximumAACubeInTreeUnits() const;
AACube getMaximumAACubeInMeters() const;
void debugDump() const;
// properties of all entities
EntityTypes::EntityType getType() const { return _type; }
const glm::vec3& getPosition() const { return _position; }
float getRadius() const { return _radius; }
float getMaxDimension() const { return _radius * 2.0f; }
glm::vec3 getDimensions() const { return glm::vec3(_radius, _radius, _radius) * 2.0f; }
const glm::quat& getRotation() const { return _rotation; }
void setType(EntityTypes::EntityType type) { _type = type; }
const glm::vec3& getPosition() const { return _position; }
/// set position in meter units, will be clamped to domain bounds
void setPosition(const glm::vec3& value) { _position = glm::clamp(value, 0.0f, (float)TREE_SCALE); _positionChanged = true; }
void setRadius(float value) { _radius = value; _radiusChanged = true; }
const glm::vec3& getDimensions() const { return _dimensions; }
void setDimensions(const glm::vec3& value) { _dimensions = value; _dimensionsChanged = true; }
float getMaxDimension() const { return glm::max(_dimensions.x, _dimensions.y, _dimensions.z); }
const glm::quat& getRotation() const { return _rotation; }
void setRotation(const glm::quat& rotation) { _rotation = rotation; _rotationChanged = true; }
float getMass() const { return _mass; }
@ -148,9 +143,9 @@ public:
// NOTE: how do we handle _defaultSettings???
bool containsBoundsProperties() const { return (_positionChanged || _radiusChanged); }
bool containsBoundsProperties() const { return (_positionChanged || _dimensionsChanged); }
bool containsPositionChange() const { return _positionChanged; }
bool containsRadiusChange() const { return _radiusChanged; }
bool containsDimensionsChange() const { return _dimensionsChanged; }
// TODO: this need to be more generic. for now, we're going to have the properties class support these as
// named getter/setters, but we want to move them to generic types...
@ -162,6 +157,7 @@ public:
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
float getAnimationFPS() const { return _animationFPS; }
float getGlowLevel() const { return _glowLevel; }
const QString& getScript() const { return _script; }
// model related properties
void setColor(const xColor& value) { _color = value; _colorChanged = true; }
@ -171,6 +167,7 @@ public:
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; }
void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; }
void setGlowLevel(float value) { _glowLevel = value; _glowLevelChanged = true; }
void setScript(const QString& value) { _script = value; _scriptChanged = true; }
static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties,
@ -184,7 +181,6 @@ public:
EntityItemID& entityID, EntityItemProperties& properties);
bool positionChanged() const { return _positionChanged; }
bool radiusChanged() const { return _radiusChanged; }
bool rotationChanged() const { return _rotationChanged; }
bool massChanged() const { return _massChanged; }
bool velocityChanged() const { return _velocityChanged; }
@ -192,6 +188,8 @@ public:
bool dampingChanged() const { return _dampingChanged; }
bool lifetimeChanged() const { return _lifetimeChanged; }
bool scriptChanged() const { return _scriptChanged; }
bool dimensionsChanged() const { return _dimensionsChanged; }
bool registrationPointChanged() const { return _registrationPointChanged; }
bool colorChanged() const { return _colorChanged; }
bool modelURLChanged() const { return _modelURLChanged; }
bool animationURLChanged() const { return _animationURLChanged; }
@ -203,6 +201,24 @@ public:
void clearID() { _id = UNKNOWN_ENTITY_ID; _idSet = false; }
void markAllChanged();
QVector<SittingPoint> getSittingPoints() const { return _sittingPoints; }
void setSittingPoints(QVector<SittingPoint> sittingPoints) { _sittingPoints = sittingPoints; }
const glm::vec3& getNaturalDimensions() const { return _naturalDimensions; }
void setNaturalDimensions(const glm::vec3& value) { _naturalDimensions = value; }
const glm::vec3& getRegistrationPoint() const { return _registrationPoint; }
void setRegistrationPoint(const glm::vec3& value) { _registrationPoint = value; _registrationPointChanged = true; }
const glm::vec3& getAngularVelocity() const { return _angularVelocity; }
void setAngularVelocity(const glm::vec3& value) { _angularVelocity = value; _angularVelocityChanged = true; }
float getAngularDamping() const { return _angularDamping; }
void setAngularDamping(float value) { _angularDamping = value; _angularDampingChanged = true; }
bool getVisible() const { return _visible; }
void setVisible(bool value) { _visible = value; _visibleChanged = true; }
private:
void setLastEdited(quint64 usecTime) { _lastEdited = usecTime; }
@ -212,8 +228,11 @@ private:
quint64 _created;
EntityTypes::EntityType _type;
void setType(const QString& typeName) { _type = EntityTypes::getEntityTypeFromName(typeName); }
glm::vec3 _position;
float _radius;
glm::vec3 _dimensions;
glm::quat _rotation;
float _mass;
glm::vec3 _velocity;
@ -221,9 +240,13 @@ private:
float _damping;
float _lifetime;
QString _script;
glm::vec3 _registrationPoint;
glm::vec3 _angularVelocity;
float _angularDamping;
bool _visible;
bool _positionChanged;
bool _radiusChanged;
bool _dimensionsChanged;
bool _rotationChanged;
bool _massChanged;
bool _velocityChanged;
@ -231,6 +254,10 @@ private:
bool _dampingChanged;
bool _lifetimeChanged;
bool _scriptChanged;
bool _registrationPointChanged;
bool _angularVelocityChanged;
bool _angularDampingChanged;
bool _visibleChanged;
// TODO: this need to be more generic. for now, we're going to have the properties class support these as
// named getter/setters, but we want to move them to generic types...
@ -242,6 +269,7 @@ private:
float _animationFPS;
float _glowLevel;
QVector<SittingPoint> _sittingPoints;
glm::vec3 _naturalDimensions;
bool _colorChanged;
bool _modelURLChanged;
@ -257,107 +285,4 @@ Q_DECLARE_METATYPE(EntityItemProperties);
QScriptValue EntityItemPropertiesToScriptValue(QScriptEngine* engine, const EntityItemProperties& properties);
void EntityItemPropertiesFromScriptValue(const QScriptValue &object, EntityItemProperties& properties);
#define APPEND_ENTITY_PROPERTY(P,O,V) \
if (requestedProperties.getHasProperty(P)) { \
LevelDetails propertyLevel = packetData->startLevel(); \
successPropertyFits = packetData->O(V); \
if (successPropertyFits) { \
propertyFlags |= P; \
propertiesDidntFit -= P; \
propertyCount++; \
packetData->endLevel(propertyLevel); \
} else { \
packetData->discardLevel(propertyLevel); \
appendState = OctreeElement::PARTIAL; \
} \
} else { \
propertiesDidntFit -= P; \
}
#define READ_ENTITY_PROPERTY(P,T,M) \
if (propertyFlags.getHasProperty(P)) { \
T fromBuffer; \
memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \
dataAt += sizeof(fromBuffer); \
bytesRead += sizeof(fromBuffer); \
if (overwriteLocalData) { \
M = fromBuffer; \
} \
}
#define READ_ENTITY_PROPERTY_QUAT(P,M) \
if (propertyFlags.getHasProperty(P)) { \
glm::quat fromBuffer; \
int bytes = unpackOrientationQuatFromBytes(dataAt, fromBuffer); \
dataAt += bytes; \
bytesRead += bytes; \
if (overwriteLocalData) { \
M = fromBuffer; \
} \
}
#define READ_ENTITY_PROPERTY_STRING(P,O) \
if (propertyFlags.getHasProperty(P)) { \
uint16_t length; \
memcpy(&length, dataAt, sizeof(length)); \
dataAt += sizeof(length); \
bytesRead += sizeof(length); \
QString value((const char*)dataAt); \
dataAt += length; \
bytesRead += length; \
if (overwriteLocalData) { \
O(value); \
} \
}
#define READ_ENTITY_PROPERTY_COLOR(P,M) \
if (propertyFlags.getHasProperty(P)) { \
if (overwriteLocalData) { \
memcpy(M, dataAt, sizeof(M)); \
} \
dataAt += sizeof(rgbColor); \
bytesRead += sizeof(rgbColor); \
}
#define READ_ENTITY_PROPERTY_TO_PROPERTIES(P,T,O) \
if (propertyFlags.getHasProperty(P)) { \
T fromBuffer; \
memcpy(&fromBuffer, dataAt, sizeof(fromBuffer)); \
dataAt += sizeof(fromBuffer); \
processedBytes += sizeof(fromBuffer); \
properties.O(fromBuffer); \
}
#define READ_ENTITY_PROPERTY_QUAT_TO_PROPERTIES(P,O) \
if (propertyFlags.getHasProperty(P)) { \
glm::quat fromBuffer; \
int bytes = unpackOrientationQuatFromBytes(dataAt, fromBuffer); \
dataAt += bytes; \
processedBytes += bytes; \
properties.O(fromBuffer); \
}
#define READ_ENTITY_PROPERTY_STRING_TO_PROPERTIES(P,O) \
if (propertyFlags.getHasProperty(P)) { \
uint16_t length; \
memcpy(&length, dataAt, sizeof(length)); \
dataAt += sizeof(length); \
processedBytes += sizeof(length); \
QString value((const char*)dataAt); \
dataAt += length; \
processedBytes += length; \
properties.O(value); \
}
#define READ_ENTITY_PROPERTY_COLOR_TO_PROPERTIES(P,O) \
if (propertyFlags.getHasProperty(P)) { \
xColor color; \
memcpy(&color, dataAt, sizeof(color)); \
dataAt += sizeof(color); \
processedBytes += sizeof(color); \
properties.O(color); \
}
#endif // hifi_EntityItemProperties_h

Some files were not shown because too many files have changed in this diff Show more