diff --git a/examples/flockingBirds.js b/examples/flockingBirds.js index 17c60d88aa..3fa8681abe 100644 --- a/examples/flockingBirds.js +++ b/examples/flockingBirds.js @@ -51,7 +51,7 @@ var flockGravity = { x: 0, y: -1, z: 0}; var enableRandomXZThrust = false; // leading birds randomly decide to thrust in some random direction. var enableSomeBirdsLead = false; // birds randomly decide not fly toward flock, causing other birds to follow var leaders = 0; // number of birds leading -var PROBABILITY_TO_LEAD = 0.1; // probabolity a bird will choose to lead +var PROBABILITY_TO_LEAD = 0.1; // probability a bird will choose to lead var birds = new Array(); // array of bird state data diff --git a/examples/particleBird.js b/examples/particleBird.js deleted file mode 100644 index 5241b3550b..0000000000 --- a/examples/particleBird.js +++ /dev/null @@ -1,202 +0,0 @@ -// -// particleBird.js -// examples -// -// Copyright 2014 High Fidelity, Inc. -// -// This sample script moves a voxel around like a bird and sometimes makes tweeting noises -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -function vLength(v) { - return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); -} -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) }; - return rval; -} - -function vMinus(a, b) { - var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; - return rval; -} - -function vPlus(a, b) { - var rval = { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }; - return rval; -} - -function vCopy(a, b) { - a.x = b.x; - a.y = b.y; - a.z = b.z; - return; -} - -// Returns a vector which is fraction of the way between a and b -function vInterpolate(a, b, fraction) { - var rval = { x: a.x + (b.x - a.x) * fraction, y: a.y + (b.y - a.y) * fraction, z: a.z + (b.z - a.z) * fraction }; - return rval; -} - -// Decide what kind of bird we are -var tweet; -var color; -var size; -var which = Math.random(); -if (which < 0.2) { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); - color = { r: 100, g: 50, b: 120 }; - size = 0.08; -} else if (which < 0.4) { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/rosyfacedlovebird.raw"); - color = { r: 100, g: 150, b: 75 }; - size = 0.09; -} else if (which < 0.6) { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/saysphoebe.raw"); - color = { r: 84, g: 121, b: 36 }; - size = 0.05; -} else if (which < 0.8) { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/mexicanWhipoorwill.raw"); - color = { r: 23, g: 197, b: 230 }; - size = 0.12; -} else { - tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/westernscreechowl.raw"); - color = { r: 50, g: 67, b: 144 }; - size = 0.15; -} - - -var startTimeInSeconds = new Date().getTime() / 1000; - -var birdLifetime = 20; // lifetime of the bird in seconds! -var position = { x: 0, y: 0, z: 0 }; -var targetPosition = { x: 0, y: 0, z: 0 }; -var range = 1.0; // Over what distance in meters do you want your bird to fly around -var frame = 0; -var moving = false; -var tweeting = 0; -var moved = false; - -var CHANCE_OF_MOVING = 0.00; -var CHANCE_OF_FLAPPING = 0.05; -var CHANCE_OF_TWEETING = 0.05; -var START_HEIGHT_ABOVE_ME = 1.5; -var BIRD_GRAVITY = -0.1; -var BIRD_FLAP = 1.0; -var myPosition = MyAvatar.position; -var properties = { - lifetime: birdLifetime, - position: { x: myPosition.x, y: myPosition.y + START_HEIGHT_ABOVE_ME, z: myPosition.z }, - velocity: { x: 0, y: Math.random() * BIRD_FLAP, z: 0 }, - gravity: { x: 0, y: BIRD_GRAVITY, z: 0 }, - radius : 0.1, - color: { red: 0, - green: 255, - blue: 0 } -}; -var range = 1.0; // Distance around avatar where I can move -// Create the actual bird -var particleID = Particles.addParticle(properties); -function moveBird(deltaTime) { - - // check to see if we've been running long enough that our bird is dead - var nowTimeInSeconds = new Date().getTime() / 1000; - if ((nowTimeInSeconds - startTimeInSeconds) >= birdLifetime) { - - print("our bird is dying, stop our script"); - Script.stop(); - return; - } - - myPosition = MyAvatar.position; - frame++; - if (frame % 3 == 0) { - // Tweeting behavior - if (tweeting == 0) { - if (Math.random() < CHANCE_OF_TWEETING) { - //print("tweet!" + "\n"); - var options = new AudioInjectionOptions(); - options.position = position; - options.volume = 0.75; - Audio.playSound(tweet, options); - tweeting = 10; - } - } else { - tweeting -= 1; - } - if (Math.random() < CHANCE_OF_FLAPPING) { - // Add a little upward impulse to our bird - // TODO: Get velocity - // - var newProperties = { - velocity: { x:0.0, y: Math.random() * BIRD_FLAP, z: 0.0 } - }; - Particles.editParticle(particleID, newProperties); - print("flap!"); - } - // Moving behavior - if (moving == false) { - if (Math.random() < CHANCE_OF_MOVING) { - targetPosition = randVector(-range, range); - targetPosition = vPlus(targetPosition, myPosition); - - if (targetPosition.x < 0) { - targetPosition.x = 0; - } - if (targetPosition.y < 0) { - targetPosition.y = 0; - } - if (targetPosition.z < 0) { - targetPosition.z = 0; - } - if (targetPosition.x > TREE_SCALE) { - targetPosition.x = TREE_SCALE; - } - if (targetPosition.y > TREE_SCALE) { - targetPosition.y = TREE_SCALE; - } - if (targetPosition.z > TREE_SCALE) { - targetPosition.z = TREE_SCALE; - } - //printVector(position); - moving = true; - } - } - if (moving) { - position = vInterpolate(position, targetPosition, 0.5); - if (vLength(vMinus(position, targetPosition)) < (size / 5.0)) { - moved = false; - moving = false; - } else { - moved = true; - } - } - if (moved || (tweeting > 0)) { - if (tweeting > 0) { - var newProperties = { - position: position, - radius : size * 1.5, - color: { red: Math.random() * 255, green: 0, blue: 0 } - }; - } else { - var newProperties = { - position: position, - radius : size, - color: { red: color.r, green: color.g, blue: color.b } - }; - } - Particles.editParticle(particleID, newProperties); - moved = false; - } - } -} - -// register the call back so it fires before each data send -Script.update.connect(moveBird); diff --git a/examples/particleBirds.js b/examples/particleBirds.js new file mode 100644 index 0000000000..9492a321ce --- /dev/null +++ b/examples/particleBirds.js @@ -0,0 +1,199 @@ +// +// particleBirds.js +// examples +// +// Created by Benjamin Arnold on May 29, 2014 +// Copyright 2014 High Fidelity, Inc. +// +// This sample script creates a swarm of tweeting bird particles that fly around the avatar. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// Multiply vector by scalar +function vScalarMult(v, s) { + var rval = { x: v.x * s, y: v.y * s, z: v.z * 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) }; + return rval; +} + +// Returns a vector which is fraction of the way between a and b +function vInterpolate(a, b, fraction) { + var rval = { x: a.x + (b.x - a.x) * fraction, y: a.y + (b.y - a.y) * fraction, z: a.z + (b.z - a.z) * fraction }; + return rval; +} + +var startTimeInSeconds = new Date().getTime() / 1000; + +var birdLifetime = 20; // lifetime of the birds in seconds! +var range = 1.0; // Over what distance in meters do you want the flock to fly around +var frame = 0; + +var CHANCE_OF_MOVING = 0.1; +var CHANCE_OF_TWEETING = 0.05; +var BIRD_GRAVITY = -0.1; +var BIRD_FLAP_SPEED = 10.0; +var BIRD_VELOCITY = 0.5; +var myPosition = MyAvatar.position; + +var range = 1.0; // Distance around avatar where I can move + +// This is our Bird object +function Bird (particleID, tweetSound, targetPosition) { + this.particleID = particleID; + this.tweetSound = tweetSound; + this.previousFlapOffset = 0; + this.targetPosition = targetPosition; + this.moving = false; + this.tweeting = -1; +} + +// Array of birds +var birds = []; + +function addBird() +{ + // Decide what kind of bird we are + var tweet; + var color; + var size; + var which = Math.random(); + if (which < 0.2) { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); + color = { red: 100, green: 50, blue: 120 }; + size = 0.08; + } else if (which < 0.4) { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/rosyfacedlovebird.raw"); + color = { red: 100, green: 150, blue: 75 }; + size = 0.09; + } else if (which < 0.6) { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/saysphoebe.raw"); + color = { red: 84, green: 121, blue: 36 }; + size = 0.05; + } else if (which < 0.8) { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/mexicanWhipoorwill.raw"); + color = { red: 23, green: 197, blue: 230 }; + size = 0.12; + } else { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/westernscreechowl.raw"); + color = { red: 50, green: 67, blue: 144 }; + size = 0.15; + } + var properties = { + lifetime: birdLifetime, + position: Vec3.sum(randVector(-range, range), myPosition), + velocity: { x: 0, y: 0, z: 0 }, + gravity: { x: 0, y: BIRD_GRAVITY, z: 0 }, + radius : size, + color: color + }; + + birds.push(new Bird(Particles.addParticle(properties), tweet, properties.position)); +} + +var numBirds = 30; + +// Generate the birds +for (var i = 0; i < numBirds; i++) { + addBird(); +} + +// Main update function +function updateBirds(deltaTime) { + + // Check to see if we've been running long enough that our birds are dead + var nowTimeInSeconds = new Date().getTime() / 1000; + if ((nowTimeInSeconds - startTimeInSeconds) >= birdLifetime) { + + print("our birds are dying, stop our script"); + Script.stop(); + return; + } + + frame++; + // Only update every third frame + if ((frame % 3) == 0) { + myPosition = MyAvatar.position; + + // Update all the birds + for (var i = 0; i < numBirds; i++) { + particleID = birds[i].particleID; + var properties = Particles.getParticleProperties(particleID); + + // Tweeting behavior + if (birds[i].tweeting == 0) { + if (Math.random() < CHANCE_OF_TWEETING) { + var options = new AudioInjectionOptions(); + options.position = properties.position; + options.volume = 0.75; + Audio.playSound(birds[i].tweetSound, options); + birds[i].tweeting = 10; + } + } else { + birds[i].tweeting -= 1; + } + + // Begin movement by getting a target + if (birds[i].moving == false) { + if (Math.random() < CHANCE_OF_MOVING) { + var targetPosition = Vec3.sum(randVector(-range, range), myPosition); + + if (targetPosition.x < 0) { + targetPosition.x = 0; + } + if (targetPosition.y < 0) { + targetPosition.y = 0; + } + if (targetPosition.z < 0) { + targetPosition.z = 0; + } + if (targetPosition.x > TREE_SCALE) { + targetPosition.x = TREE_SCALE; + } + if (targetPosition.y > TREE_SCALE) { + targetPosition.y = TREE_SCALE; + } + if (targetPosition.z > TREE_SCALE) { + targetPosition.z = TREE_SCALE; + } + + birds[i].targetPosition = targetPosition; + + birds[i].moving = true; + } + } + // If we are moving, move towards the target + if (birds[i].moving) { + var desiredVelocity = Vec3.subtract(birds[i].targetPosition, properties.position); + desiredVelocity = vScalarMult(Vec3.normalize(desiredVelocity), BIRD_VELOCITY); + + properties.velocity = vInterpolate(properties.velocity, desiredVelocity, 0.2); + // If we are near the target, we should get a new target + if (Vec3.length(Vec3.subtract(properties.position, birds[i].targetPosition)) < (properties.radius / 5.0)) { + birds[i].moving = false; + } + } + + // Use a cosine wave offset to make it look like its flapping. + var offset = Math.cos(nowTimeInSeconds * BIRD_FLAP_SPEED) * properties.radius; + properties.position.y = properties.position.y + (offset - birds[i].previousFlapOffset); + // Change position relative to previous offset. + birds[i].previousFlapOffset = offset; + + // Update the particle + Particles.editParticle(particleID, properties); + } + } +} + +// register the call back so it fires before each data send +Script.update.connect(updateBirds); diff --git a/examples/sit.js b/examples/sit.js index 6ae8a712f1..d10c08c95a 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -169,7 +169,7 @@ Controller.keyPressEvent.connect(keyPressEvent); Script.scriptEnding.connect(function() { for (var i = 0; i < pose.length; i++){ - MyAvatar.clearJointData(pose[i][0]); + MyAvatar.clearJointData(pose[i].joint); } Overlays.deleteOverlay(sitDownButton); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 10201c5c88..533d03d9e7 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -292,8 +292,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : // move the silentNodeTimer to the _nodeThread QTimer* silentNodeTimer = new QTimer(); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->moveToThread(_nodeThread); silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); + silentNodeTimer->moveToThread(_nodeThread); // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); @@ -1007,6 +1007,12 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_At: Menu::getInstance()->goTo(); break; + case Qt::Key_B: + _applicationOverlay.setOculusAngle(_applicationOverlay.getOculusAngle() - RADIANS_PER_DEGREE); + break; + case Qt::Key_N: + _applicationOverlay.setOculusAngle(_applicationOverlay.getOculusAngle() + RADIANS_PER_DEGREE); + break; default: event->ignore(); break; @@ -1097,7 +1103,8 @@ void Application::mouseMoveEvent(QMouseEvent* event) { _lastMouseMove = usecTimestampNow(); - if (_mouseHidden) { + + if (_mouseHidden && !OculusManager::isConnected()) { getGLWidget()->setCursor(Qt::ArrowCursor); _mouseHidden = false; _seenMouseMove = true; @@ -2790,16 +2797,19 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) { glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation(); glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation(); - // get the eye positions relative to the neck and use them to set the face translation - glm::vec3 leftEyePosition, rightEyePosition; - _myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3()); - _myAvatar->getHead()->getFaceModel().getEyePositions(leftEyePosition, rightEyePosition); - _myAvatar->getHead()->getFaceModel().setTranslation((leftEyePosition + rightEyePosition) * -0.5f); - - // get the neck position relative to the body and use it to set the skeleton translation + // get the neck position so we can translate the face relative to it glm::vec3 neckPosition; _myAvatar->getSkeletonModel().setTranslation(glm::vec3()); _myAvatar->getSkeletonModel().getNeckPosition(neckPosition); + + // get the eye position relative to the body + glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition(); + float eyeHeight = eyePosition.y - _myAvatar->getPosition().y; + + // set the translation of the face relative to the neck position + _myAvatar->getHead()->getFaceModel().setTranslation(neckPosition - glm::vec3(0, eyeHeight, 0)); + + // translate the neck relative to the face _myAvatar->getSkeletonModel().setTranslation(_myAvatar->getHead()->getFaceModel().getTranslation() - neckPosition); @@ -3117,9 +3127,15 @@ void Application::domainChanged(const QString& domainHostname) { _environment.resetToDefault(); // reset our node to stats and node to jurisdiction maps... since these must be changing... + _voxelServerJurisdictions.lockForWrite(); _voxelServerJurisdictions.clear(); + _voxelServerJurisdictions.unlock(); + _octreeServerSceneStats.clear(); + + _particleServerJurisdictions.lockForWrite(); _particleServerJurisdictions.clear(); + _particleServerJurisdictions.unlock(); // reset the particle renderer _particles.clear(); @@ -3158,10 +3174,12 @@ void Application::nodeKilled(SharedNodePointer node) { if (node->getType() == NodeType::VoxelServer) { QUuid nodeUUID = node->getUUID(); // see if this is the first we've heard of this node... + _voxelServerJurisdictions.lockForRead(); if (_voxelServerJurisdictions.find(nodeUUID) != _voxelServerJurisdictions.end()) { unsigned char* rootCode = _voxelServerJurisdictions[nodeUUID].getRootOctalCode(); VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); + _voxelServerJurisdictions.unlock(); qDebug("voxel server going away...... v[%f, %f, %f, %f]", rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); @@ -3178,8 +3196,10 @@ void Application::nodeKilled(SharedNodePointer node) { } // If the voxel server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server + _voxelServerJurisdictions.lockForWrite(); _voxelServerJurisdictions.erase(_voxelServerJurisdictions.find(nodeUUID)); } + _voxelServerJurisdictions.unlock(); // also clean up scene stats for that server _octreeSceneStatsLock.lockForWrite(); @@ -3191,10 +3211,12 @@ void Application::nodeKilled(SharedNodePointer node) { } else if (node->getType() == NodeType::ParticleServer) { QUuid nodeUUID = node->getUUID(); // see if this is the first we've heard of this node... + _particleServerJurisdictions.lockForRead(); if (_particleServerJurisdictions.find(nodeUUID) != _particleServerJurisdictions.end()) { unsigned char* rootCode = _particleServerJurisdictions[nodeUUID].getRootOctalCode(); VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); + _particleServerJurisdictions.unlock(); qDebug("particle server going away...... v[%f, %f, %f, %f]", rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); @@ -3211,8 +3233,10 @@ void Application::nodeKilled(SharedNodePointer node) { } // If the particle server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server + _particleServerJurisdictions.lockForWrite(); _particleServerJurisdictions.erase(_particleServerJurisdictions.find(nodeUUID)); } + _particleServerJurisdictions.unlock(); // also clean up scene stats for that server _octreeSceneStatsLock.lockForWrite(); @@ -3225,10 +3249,12 @@ void Application::nodeKilled(SharedNodePointer node) { QUuid nodeUUID = node->getUUID(); // see if this is the first we've heard of this node... + _modelServerJurisdictions.lockForRead(); if (_modelServerJurisdictions.find(nodeUUID) != _modelServerJurisdictions.end()) { unsigned char* rootCode = _modelServerJurisdictions[nodeUUID].getRootOctalCode(); VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); + _modelServerJurisdictions.unlock(); qDebug("model server going away...... v[%f, %f, %f, %f]", rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); @@ -3245,8 +3271,10 @@ void Application::nodeKilled(SharedNodePointer node) { } // If the model server is going away, remove it from our jurisdiction map so we don't send voxels to a dead server + _modelServerJurisdictions.lockForWrite(); _modelServerJurisdictions.erase(_modelServerJurisdictions.find(nodeUUID)); } + _modelServerJurisdictions.unlock(); // also clean up scene stats for that server _octreeSceneStatsLock.lockForWrite(); @@ -3316,7 +3344,10 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin serverType = "Model"; } + jurisdiction->lockForRead(); if (jurisdiction->find(nodeUUID) == jurisdiction->end()) { + jurisdiction->unlock(); + qDebug("stats from new %s server... [%f, %f, %f, %f]", qPrintable(serverType), rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); @@ -3330,6 +3361,8 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin _voxelFades.push_back(fade); _voxelFadesLock.unlock(); } + } else { + jurisdiction->unlock(); } // store jurisdiction details for later use // This is bit of fiddling is because JurisdictionMap assumes it is the owner of the values used to construct it @@ -3337,7 +3370,9 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin // details from the OctreeSceneStats to construct the JurisdictionMap JurisdictionMap jurisdictionMap; jurisdictionMap.copyContents(temp.getJurisdictionRoot(), temp.getJurisdictionEndNodes()); + jurisdiction->lockForWrite(); (*jurisdiction)[nodeUUID] = jurisdictionMap; + jurisdiction->unlock(); } return statsMessageLength; } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index deb052e595..5d9cd1f1c4 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -348,7 +348,6 @@ Menu::Menu() : QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options"); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::AllowOculusCameraModeChange, 0, false); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::AvatarsReceiveShadows, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes); @@ -375,6 +374,10 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); + QMenu* oculusOptionsMenu = developerMenu->addMenu("Oculus Options"); + addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::AllowOculusCameraModeChange, 0, false); + addCheckableActionToQMenuAndActionHash(oculusOptionsMenu, MenuOption::DisplayOculusOverlays, 0, true); + QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options"); addCheckableActionToQMenuAndActionHash(handOptionsMenu, diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 020cd651d2..6bc9adef05 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -326,6 +326,7 @@ namespace MenuOption { const QString DisplayModelBounds = "Display Model Bounds"; const QString DisplayModelElementProxy = "Display Model Element Bounds"; const QString DisplayModelElementChildProxies = "Display Model Element Children"; + const QString DisplayOculusOverlays = "Display Oculus Overlays"; const QString DisplayTimingDetails = "Display Timing Details"; const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes"; const QString EchoLocalAudio = "Echo Local Audio"; diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 79a2e31d80..07ca65b286 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -400,9 +400,9 @@ bool closeEnoughForGovernmentWork(float a, float b) { void runTimingTests() { // How long does it take to make a call to get the time? const int numTests = 1000000; - int iResults[numTests]; + int* iResults = (int*)malloc(sizeof(int) * numTests); float fTest = 1.0; - float fResults[numTests]; + float* fResults = (float*)malloc(sizeof(float) * numTests); QElapsedTimer startTime; startTime.start(); float elapsedUsecs; @@ -413,7 +413,7 @@ void runTimingTests() { // Random number generation startTime.start(); - for (int i = 1; i < numTests; i++) { + for (int i = 0; i < numTests; i++) { iResults[i] = rand(); } elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; @@ -421,16 +421,19 @@ void runTimingTests() { // Random number generation using randFloat() startTime.start(); - for (int i = 1; i < numTests; i++) { + for (int i = 0; i < numTests; i++) { fResults[i] = randFloat(); } elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; qDebug("randFloat() stored in array usecs: %f, first result: %f", elapsedUsecs / (float) numTests, fResults[0]); + free(iResults); + free(fResults); + // PowF function fTest = 1145323.2342f; startTime.start(); - for (int i = 1; i < numTests; i++) { + for (int i = 0; i < numTests; i++) { fTest = powf(fTest, 0.5f); } elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; @@ -440,7 +443,7 @@ void runTimingTests() { float distance; glm::vec3 pointA(randVector()), pointB(randVector()); startTime.start(); - for (int i = 1; i < numTests; i++) { + for (int i = 0; i < numTests; i++) { //glm::vec3 temp = pointA - pointB; //float distanceSquared = glm::dot(temp, temp); distance = glm::distance(pointA, pointB); @@ -454,7 +457,7 @@ void runTimingTests() { float result; startTime.start(); - for (int i = 1; i < numTests; i++) { + for (int i = 0; i < numTests; i++) { glm::vec3 temp = vecA-vecB; result = glm::dot(temp,temp); } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index f761729417..a14152aa04 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -433,11 +433,11 @@ void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { } void MyAvatar::startAnimation(const QString& url, float fps, float priority, - bool loop, bool hold, int firstFrame, int lastFrame, const QStringList& maskedJoints) { + bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), - Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(int, firstFrame), - Q_ARG(int, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(float, firstFrame), + Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints)); return; } AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); @@ -453,11 +453,11 @@ void MyAvatar::startAnimation(const QString& url, float fps, float priority, } void MyAvatar::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, - bool loop, bool hold, int firstFrame, int lastFrame, const QStringList& maskedJoints) { + bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startAnimationByRole", Q_ARG(const QString&, role), Q_ARG(const QString&, url), - Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(int, firstFrame), - Q_ARG(int, lastFrame), Q_ARG(const QStringList&, maskedJoints)); + Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(float, firstFrame), + Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints)); return; } // check for a configured animation for the role @@ -627,8 +627,8 @@ void MyAvatar::loadData(QSettings* settings) { handle->setLoop(settings->value("loop", true).toBool()); handle->setHold(settings->value("hold", false).toBool()); handle->setStartAutomatically(settings->value("startAutomatically", true).toBool()); - handle->setFirstFrame(settings->value("firstFrame", 0).toInt()); - handle->setLastFrame(settings->value("lastFrame", INT_MAX).toInt()); + handle->setFirstFrame(settings->value("firstFrame", 0.0f).toFloat()); + handle->setLastFrame(settings->value("lastFrame", INT_MAX).toFloat()); handle->setMaskedJoints(settings->value("maskedJoints").toStringList()); } settings->endArray(); diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index c0bc12b6b0..d99102c356 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -68,7 +68,7 @@ public: /// Allows scripts to run animations. Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false, - bool hold = false, int firstFrame = 0, int lastFrame = INT_MAX, const QStringList& maskedJoints = QStringList()); + bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); /// Stops an animation as identified by a URL. Q_INVOKABLE void stopAnimation(const QString& url); @@ -76,8 +76,8 @@ public: /// Starts an animation by its role, using the provided URL and parameters if the avatar doesn't have a custom /// animation for the role. Q_INVOKABLE void startAnimationByRole(const QString& role, const QString& url = QString(), float fps = 30.0f, - float priority = 1.0f, bool loop = false, bool hold = false, int firstFrame = 0, - int lastFrame = INT_MAX, const QStringList& maskedJoints = QStringList()); + float priority = 1.0f, bool loop = false, bool hold = false, float firstFrame = 0.0f, + float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList()); /// Stops an animation identified by its role. Q_INVOKABLE void stopAnimationByRole(const QString& role); diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index c8b2d0dcbc..fe8cc37168 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -86,7 +86,7 @@ void OculusManager::display(Camera& whichCamera) { // We only need to render the overlays to a texture once, then we just render the texture as a quad // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() applicationOverlay.renderOverlay(true); - const bool displayOverlays = false; + const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::DisplayOculusOverlays); Application::getInstance()->getGlowEffect()->prepare(); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index ca340808e5..2881d96d88 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1338,7 +1338,7 @@ bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) } const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& freeLineage = geometry.joints.at(jointIndex).freeLineage; - + foreach (int index, freeLineage) { JointState& state = _jointStates[index]; state.restoreRotation(fraction, priority); @@ -1890,8 +1890,8 @@ AnimationHandle::AnimationHandle(Model* model) : _loop(false), _hold(false), _startAutomatically(false), - _firstFrame(0), - _lastFrame(INT_MAX), + _firstFrame(0.0f), + _lastFrame(FLT_MAX), _running(false) { } @@ -1922,35 +1922,34 @@ void AnimationHandle::simulate(float deltaTime) { stop(); return; } - int lastFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - 1); - int firstFrameIndex = qMin(_firstFrame, lastFrameIndex); - if ((!_loop && _frameIndex >= lastFrameIndex) || firstFrameIndex == lastFrameIndex) { + float endFrameIndex = qMin(_lastFrame, animationGeometry.animationFrames.size() - (_loop ? 0.0f : 1.0f)); + float startFrameIndex = qMin(_firstFrame, endFrameIndex); + if ((!_loop && (_frameIndex < startFrameIndex || _frameIndex > endFrameIndex)) || startFrameIndex == endFrameIndex) { // passed the end; apply the last frame - const FBXAnimationFrame& frame = animationGeometry.animationFrames.at(lastFrameIndex); - for (int i = 0; i < _jointMappings.size(); i++) { - int mapping = _jointMappings.at(i); - if (mapping != -1) { - JointState& state = _model->_jointStates[mapping]; - if (_priority >= state._animationPriority) { - state._rotationInParentFrame = frame.rotations.at(i); - state._animationPriority = _priority; - } - } - } + applyFrame(glm::clamp(_frameIndex, startFrameIndex, endFrameIndex)); if (!_hold) { stop(); } return; } - int frameCount = lastFrameIndex - firstFrameIndex + 1; - _frameIndex = firstFrameIndex + glm::mod(qMax(_frameIndex - firstFrameIndex, 0.0f), (float)frameCount); + // wrap within the the desired range + if (_frameIndex < startFrameIndex) { + _frameIndex = endFrameIndex - glm::mod(endFrameIndex - _frameIndex, endFrameIndex - startFrameIndex); + + } else if (_frameIndex > endFrameIndex) { + _frameIndex = startFrameIndex + glm::mod(_frameIndex - startFrameIndex, endFrameIndex - startFrameIndex); + } // blend between the closest two frames - const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at( - firstFrameIndex + ((int)glm::ceil(_frameIndex) - firstFrameIndex) % frameCount); - const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at( - firstFrameIndex + ((int)glm::floor(_frameIndex) - firstFrameIndex) % frameCount); - float frameFraction = glm::fract(_frameIndex); + applyFrame(_frameIndex); +} + +void AnimationHandle::applyFrame(float frameIndex) { + const FBXGeometry& animationGeometry = _animation->getGeometry(); + int frameCount = animationGeometry.animationFrames.size(); + const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at((int)glm::floor(frameIndex) % frameCount); + const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at((int)glm::ceil(frameIndex) % frameCount); + float frameFraction = glm::fract(frameIndex); for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { @@ -2011,7 +2010,7 @@ glm::quat JointState::getRotationFromBindToModelFrame() const { void JointState::restoreRotation(float fraction, float priority) { assert(_fbxJoint != NULL); - if (priority == _animationPriority) { + if (priority == _animationPriority || _animationPriority == 0.0f) { _rotationInParentFrame = safeMix(_rotationInParentFrame, _fbxJoint->rotation, fraction); _animationPriority = 0.0f; } diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 7d7406fc42..4bab2451c4 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -419,11 +419,11 @@ public: void setStartAutomatically(bool startAutomatically); bool getStartAutomatically() const { return _startAutomatically; } - void setFirstFrame(int firstFrame) { _firstFrame = firstFrame; } - int getFirstFrame() const { return _firstFrame; } + void setFirstFrame(float firstFrame) { _firstFrame = firstFrame; } + float getFirstFrame() const { return _firstFrame; } - void setLastFrame(int lastFrame) { _lastFrame = lastFrame; } - int getLastFrame() const { return _lastFrame; } + void setLastFrame(float lastFrame) { _lastFrame = lastFrame; } + float getLastFrame() const { return _lastFrame; } void setMaskedJoints(const QStringList& maskedJoints); const QStringList& getMaskedJoints() const { return _maskedJoints; } @@ -447,6 +447,7 @@ private: AnimationHandle(Model* model); void simulate(float deltaTime); + void applyFrame(float frameIndex); void replaceMatchingPriorities(float newPriority); Model* _model; @@ -459,8 +460,8 @@ private: bool _loop; bool _hold; bool _startAutomatically; - int _firstFrame; - int _lastFrame; + float _firstFrame; + float _lastFrame; QStringList _maskedJoints; bool _running; QVector _jointMappings; diff --git a/interface/src/ui/AnimationsDialog.cpp b/interface/src/ui/AnimationsDialog.cpp index 2456b589da..c5ab826ebb 100644 --- a/interface/src/ui/AnimationsDialog.cpp +++ b/interface/src/ui/AnimationsDialog.cpp @@ -98,6 +98,7 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo layout->addRow("FPS:", _fps = new QDoubleSpinBox()); _fps->setSingleStep(0.01); + _fps->setMinimum(-FLT_MAX); _fps->setMaximum(FLT_MAX); _fps->setValue(handle->getFPS()); connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle())); @@ -128,15 +129,17 @@ AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePo _startAutomatically->setChecked(handle->getStartAutomatically()); connect(_startAutomatically, SIGNAL(toggled(bool)), SLOT(updateHandle())); - layout->addRow("First Frame:", _firstFrame = new QSpinBox()); + layout->addRow("First Frame:", _firstFrame = new QDoubleSpinBox()); + _firstFrame->setSingleStep(0.01); _firstFrame->setMaximum(INT_MAX); _firstFrame->setValue(handle->getFirstFrame()); - connect(_firstFrame, SIGNAL(valueChanged(int)), SLOT(updateHandle())); + connect(_firstFrame, SIGNAL(valueChanged(double)), SLOT(updateHandle())); - layout->addRow("Last Frame:", _lastFrame = new QSpinBox()); + layout->addRow("Last Frame:", _lastFrame = new QDoubleSpinBox()); + _lastFrame->setSingleStep(0.01); _lastFrame->setMaximum(INT_MAX); _lastFrame->setValue(handle->getLastFrame()); - connect(_lastFrame, SIGNAL(valueChanged(int)), SLOT(updateHandle())); + connect(_lastFrame, SIGNAL(valueChanged(double)), SLOT(updateHandle())); QHBoxLayout* buttons = new QHBoxLayout(); layout->addRow(buttons); diff --git a/interface/src/ui/AnimationsDialog.h b/interface/src/ui/AnimationsDialog.h index dd3865741e..b9454d94e5 100644 --- a/interface/src/ui/AnimationsDialog.h +++ b/interface/src/ui/AnimationsDialog.h @@ -22,7 +22,6 @@ class QComboBox; class QDoubleSpinner; class QLineEdit; class QPushButton; -class QSpinBox; class QVBoxLayout; /// Allows users to edit the avatar animations. @@ -71,8 +70,8 @@ private: QCheckBox* _loop; QCheckBox* _hold; QCheckBox* _startAutomatically; - QSpinBox* _firstFrame; - QSpinBox* _lastFrame; + QDoubleSpinBox* _firstFrame; + QDoubleSpinBox* _lastFrame; QLineEdit* _maskedJoints; QPushButton* _chooseMaskedJoints; QPushButton* _start; diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp index 740b310ff2..5869394f04 100644 --- a/interface/src/ui/ApplicationOverlay.cpp +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -19,7 +19,10 @@ #include "ui/Stats.h" -ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL) { +ApplicationOverlay::ApplicationOverlay() : + _framebufferObject(NULL), + _oculusAngle(65.0f * RADIANS_PER_DEGREE), + _distance(0.5f) { } @@ -45,6 +48,10 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { BandwidthMeter* bandwidthMeter = application->getBandwidthMeter(); NodeBounds& nodeBoundsDisplay = application->getNodeBoundsDisplay(); + int mouseX = application->getMouseX(); + int mouseY = application->getMouseY(); + bool renderPointer = renderToTexture; + if (renderToTexture) { getFramebufferObject()->bind(); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); @@ -220,6 +227,34 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { overlays.render2D(); + // Render a crosshair over the pointer when in Oculus + if (renderPointer) { + const float pointerWidth = 10; + const float pointerHeight = 10; + const float crossPad = 4; + + mouseX -= pointerWidth / 2.0f; + mouseY += pointerHeight / 2.0f; + + glBegin(GL_QUADS); + + glColor3f(1, 0, 0); + + //Horizontal crosshair + glVertex2i(mouseX, mouseY - crossPad); + glVertex2i(mouseX + pointerWidth, mouseY - crossPad); + glVertex2i(mouseX + pointerWidth, mouseY - pointerHeight + crossPad); + glVertex2i(mouseX, mouseY - pointerHeight + crossPad); + + //Vertical crosshair + glVertex2i(mouseX + crossPad, mouseY); + glVertex2i(mouseX + pointerWidth - crossPad, mouseY); + glVertex2i(mouseX + pointerWidth - crossPad, mouseY - pointerHeight); + glVertex2i(mouseX + crossPad, mouseY - pointerHeight); + + glEnd(); + } + glPopMatrix(); glMatrixMode(GL_MODELVIEW); @@ -227,7 +262,6 @@ void ApplicationOverlay::renderOverlay(bool renderToTexture) { glEnable(GL_LIGHTING); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); - if (renderToTexture) { getFramebufferObject()->release(); } @@ -262,6 +296,15 @@ void ApplicationOverlay::displayOverlayTexture(Camera& whichCamera) { glDisable(GL_TEXTURE_2D); } +// Fast helper functions +inline float max(float a, float b) { + return (a > b) ? a : b; +} + +inline float min(float a, float b) { + return (a < b) ? a : b; +} + // Draws the FBO texture for Oculus rift. TODO: Draw a curved texture instead of plane. void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { @@ -271,23 +314,36 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { MyAvatar* myAvatar = application->getAvatar(); const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); - // Calculates the world space width and height of the texture based on a desired FOV - const float overlayFov = whichCamera.getFieldOfView() * PI / 180.0f; - const float overlayDistance = 1; + int mouseX = application->getMouseX(); + int mouseY = application->getMouseY(); + int widgetWidth = glWidget->width(); + int widgetHeight = glWidget->height(); + float magnifyWidth = 80.0f; + float magnifyHeight = 60.0f; + const float magnification = 4.0f; + + // Get vertical FoV of the displayed overlay texture + const float halfVerticalAngle = _oculusAngle / 2.0f; + const float verticalAngle = halfVerticalAngle * 2.0f; const float overlayAspectRatio = glWidget->width() / (float)glWidget->height(); - const float overlayHeight = overlayDistance * tan(overlayFov); - const float overlayWidth = overlayHeight * overlayAspectRatio; - const float halfOverlayWidth = overlayWidth / 2; - const float halfOverlayHeight = overlayHeight / 2; + const float halfOverlayHeight = _distance * tan(halfVerticalAngle); + + // The more vertices, the better the curve + const int numHorizontalVertices = 20; + // U texture coordinate width at each quad + const float quadTexWidth = 1.0f / (numHorizontalVertices - 1); + + // Get horizontal angle and angle increment from vertical angle and aspect ratio + const float horizontalAngle = halfVerticalAngle * 2.0f * overlayAspectRatio; + const float angleIncrement = horizontalAngle / (numHorizontalVertices - 1); + const float halfHorizontalAngle = horizontalAngle / 2; glActiveTexture(GL_TEXTURE0); - - glDepthMask(GL_FALSE); + glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); - glDisable(GL_DEPTH_TEST); + glEnable(GL_DEPTH_TEST); glDisable(GL_LIGHTING); glEnable(GL_TEXTURE_2D); @@ -305,21 +361,102 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { glm::vec3 pos = whichCamera.getPosition(); glm::quat rot = myAvatar->getOrientation(); glm::vec3 axis = glm::axis(rot); - pos += rot * glm::vec3(0.0, 0.0, -overlayDistance); - + glTranslatef(pos.x, pos.y, pos.z); glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z); + glColor3f(1.0f, 1.0f, 1.0f); + + glDepthMask(GL_TRUE); + + glEnable(GL_ALPHA_TEST); + glAlphaFunc(GL_GREATER, 0.01f); + + //Draw the magnifying glass + + mouseX -= magnifyWidth / 2; + mouseY -= magnifyHeight / 2; + + //clamp the magnification + if (mouseX < 0) { + magnifyWidth += mouseX; + mouseX = 0; + } else if (mouseX + magnifyWidth > widgetWidth) { + magnifyWidth = widgetWidth - mouseX; + } + if (mouseY < 0) { + magnifyHeight += mouseY; + mouseY = 0; + } else if (mouseY + magnifyHeight > widgetHeight) { + magnifyHeight = widgetHeight - mouseY; + } + + float newWidth = magnifyWidth * magnification; + float newHeight = magnifyHeight * magnification; + float tmp; + + // Magnification Texture Coordinates + float magnifyULeft = mouseX / (float)widgetWidth; + float magnifyURight = (mouseX + magnifyWidth) / (float)widgetWidth; + float magnifyVBottom = 1.0f - mouseY / (float)widgetHeight; + float magnifyVTop = 1.0f - (mouseY + magnifyHeight) / (float)widgetHeight; + + // Coordinates of magnification overlay + float newMouseX = (mouseX + magnifyWidth / 2) - newWidth / 2.0f; + float newMouseY = (mouseY + magnifyHeight / 2) + newHeight / 2.0f; + + // Get angle on the UI + float leftAngle = (newMouseX / (float)widgetWidth) * horizontalAngle - halfHorizontalAngle; + float rightAngle = ((newMouseX + newWidth) / (float)widgetWidth) * horizontalAngle - halfHorizontalAngle; + + float halfMagnifyHeight = magnifyHeight / 2.0f; + + float leftX, rightX, leftZ, rightZ; + + // Get position on hemisphere using angle + leftX = sin(leftAngle) * _distance; + rightX = sin(rightAngle) * _distance; + leftZ = -cos(leftAngle) * _distance; + rightZ = -cos(rightAngle) * _distance; + + float bottomY = (1.0 - newMouseY / (float)widgetHeight) * halfOverlayHeight * 2.0f - halfOverlayHeight; + float topY = bottomY + (newHeight / widgetHeight) * halfOverlayHeight * 2; + + //TODO: Remove immediate mode in favor of VBO glBegin(GL_QUADS); - glTexCoord2f(1, 0); glVertex3f(-halfOverlayWidth, halfOverlayHeight, 0); - glTexCoord2f(0, 0); glVertex3f(halfOverlayWidth, halfOverlayHeight, 0); - glTexCoord2f(0, 1); glVertex3f(halfOverlayWidth, -halfOverlayHeight, 0); - glTexCoord2f(1, 1); glVertex3f(-halfOverlayWidth, -halfOverlayHeight, 0); + + glTexCoord2f(magnifyULeft, magnifyVBottom); glVertex3f(leftX, topY, leftZ); + glTexCoord2f(magnifyURight, magnifyVBottom); glVertex3f(rightX, topY, rightZ); + glTexCoord2f(magnifyURight, magnifyVTop); glVertex3f(rightX, bottomY, rightZ); + glTexCoord2f(magnifyULeft, magnifyVTop); glVertex3f(leftX, bottomY, leftZ); + + glEnd(); + + glDepthMask(GL_FALSE); + glDisable(GL_ALPHA_TEST); + + //TODO: Remove immediate mode in favor of VBO + glBegin(GL_QUADS); + // Place the vertices in a semicircle curve around the camera + for (int i = 0; i < numHorizontalVertices-1; i++) { + + // Calculate the X and Z coordinates from the angles and radius from camera + leftX = sin(angleIncrement * i - halfHorizontalAngle) * _distance; + rightX = sin(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance; + leftZ = -cos(angleIncrement * i - halfHorizontalAngle) * _distance; + rightZ = -cos(angleIncrement * (i + 1) - halfHorizontalAngle) * _distance; + + glTexCoord2f(quadTexWidth * i, 1); glVertex3f(leftX, halfOverlayHeight, leftZ); + glTexCoord2f(quadTexWidth * (i + 1), 1); glVertex3f(rightX, halfOverlayHeight, rightZ); + glTexCoord2f(quadTexWidth * (i + 1), 0); glVertex3f(rightX, -halfOverlayHeight, rightZ); + glTexCoord2f(quadTexWidth * i, 0); glVertex3f(leftX, -halfOverlayHeight, leftZ); + } + glEnd(); glPopMatrix(); - glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); glBindTexture(GL_TEXTURE_2D, 0); glDisable(GL_TEXTURE_2D); diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h index cdc4065f45..4faa0b69f5 100644 --- a/interface/src/ui/ApplicationOverlay.h +++ b/interface/src/ui/ApplicationOverlay.h @@ -28,11 +28,18 @@ public: // Getters QOpenGLFramebufferObject* getFramebufferObject(); + float getOculusAngle() const { return _oculusAngle; } + + // Setters + void setOculusAngle(float oculusAngle) { _oculusAngle = oculusAngle; } private: + ProgramObject _textureProgram; QOpenGLFramebufferObject* _framebufferObject; float _trailingAudioLoudness; + float _oculusAngle; + float _distance; }; #endif // hifi_ApplicationOverlay_h \ No newline at end of file diff --git a/interface/src/ui/NodeBounds.cpp b/interface/src/ui/NodeBounds.cpp index f0e4a25f49..12a93095cd 100644 --- a/interface/src/ui/NodeBounds.cpp +++ b/interface/src/ui/NodeBounds.cpp @@ -70,14 +70,16 @@ void NodeBounds::draw() { } QUuid nodeUUID = node->getUUID(); + serverJurisdictions->lockForRead(); if (serverJurisdictions->find(nodeUUID) != serverJurisdictions->end()) { - const JurisdictionMap& map = serverJurisdictions->value(nodeUUID); + const JurisdictionMap& map = (*serverJurisdictions)[nodeUUID]; unsigned char* rootCode = map.getRootOctalCode(); if (rootCode) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); + serverJurisdictions->unlock(); glm::vec3 location(rootDetails.x, rootDetails.y, rootDetails.z); location *= (float)TREE_SCALE; @@ -123,7 +125,11 @@ void NodeBounds::draw() { selectedCenter = center; selectedScale = scaleFactor; } + } else { + serverJurisdictions->unlock(); } + } else { + serverJurisdictions->unlock(); } } @@ -136,7 +142,7 @@ void NodeBounds::draw() { float red, green, blue; getColorForNodeType(selectedNode->getType(), red, green, blue); - glColor4f(red, green, blue, 0.2); + glColor4f(red, green, blue, 0.2f); glutSolidCube(1.0); glPopMatrix(); @@ -229,7 +235,7 @@ void NodeBounds::drawOverlay() { int mouseX = application->getMouseX(), mouseY = application->getMouseY(), textWidth = widthText(TEXT_SCALE, 0, _overlayText); - glColor4f(0.4, 0.4, 0.4, 0.6); + glColor4f(0.4f, 0.4f, 0.4f, 0.6f); renderBevelCornersRect(mouseX + MOUSE_OFFSET, mouseY - TEXT_HEIGHT - PADDING, textWidth + (2 * PADDING), TEXT_HEIGHT + (2 * PADDING), BACKGROUND_BEVEL); drawText(mouseX + MOUSE_OFFSET + PADDING, mouseY, TEXT_SCALE, ROTATION, FONT, _overlayText, TEXT_COLOR); diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index bd2abba325..3296d8ccb2 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -281,8 +281,10 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser // lookup our nodeUUID in the jurisdiction map, if it's missing then we're // missing at least one jurisdiction + serverJurisdictions.lockForRead(); if (serverJurisdictions.find(nodeUUID) == serverJurisdictions.end()) { serverDetails << " unknown jurisdiction "; + serverJurisdictions.unlock(); } else { const JurisdictionMap& map = serverJurisdictions[nodeUUID]; @@ -305,6 +307,7 @@ void OctreeStatsDialog::showOctreeServersOfType(int& serverCount, NodeType_t ser } else { serverDetails << " jurisdiction has no rootCode"; } // root code + serverJurisdictions.unlock(); } // jurisdiction // now lookup stats details for this server... diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index b4174e6432..9820045321 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -18,6 +18,7 @@ #include #include +#include #include @@ -82,7 +83,7 @@ private: /// Map between node IDs and their reported JurisdictionMap. Typically used by classes that need to know which nodes are /// managing which jurisdictions. -typedef QMap NodeToJurisdictionMap; +class NodeToJurisdictionMap : public QMap, public QReadWriteLock {}; typedef QMap::iterator NodeToJurisdictionMapIterator; diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index 80f75e9278..898c41de08 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -71,9 +71,11 @@ bool OctreeEditPacketSender::serversExist() const { if (_serverJurisdictions) { // lookup our nodeUUID in the jurisdiction map, if it's missing then we're // missing at least one jurisdiction + _serverJurisdictions->lockForRead(); if ((*_serverJurisdictions).find(nodeUUID) == (*_serverJurisdictions).end()) { atLeastOneJurisdictionMissing = true; } + _serverJurisdictions->unlock(); } hasServers = true; } @@ -185,8 +187,10 @@ void OctreeEditPacketSender::queuePacketToNodes(unsigned char* buffer, ssize_t l bool isMyJurisdiction = true; // we need to get the jurisdiction for this // here we need to get the "pending packet" for this server + _serverJurisdictions->lockForRead(); const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID]; isMyJurisdiction = (map.isMyJurisdiction(octCode, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN); + _serverJurisdictions->unlock(); if (isMyJurisdiction) { queuePacketToNode(nodeUUID, buffer, length); } @@ -236,12 +240,14 @@ void OctreeEditPacketSender::queueOctreeEditMessage(PacketType type, unsigned ch if (_serverJurisdictions) { // we need to get the jurisdiction for this // here we need to get the "pending packet" for this server + _serverJurisdictions->lockForRead(); if ((*_serverJurisdictions).find(nodeUUID) != (*_serverJurisdictions).end()) { const JurisdictionMap& map = (*_serverJurisdictions)[nodeUUID]; isMyJurisdiction = (map.isMyJurisdiction(codeColorBuffer, CHECK_NODE_ONLY) == JurisdictionMap::WITHIN); } else { isMyJurisdiction = false; } + _serverJurisdictions->unlock(); } if (isMyJurisdiction) { EditPacketBuffer& packetBuffer = _pendingEditPackets[nodeUUID]; diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 5833cb4170..2372d85b0e 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -45,9 +45,11 @@ void OctreeHeadlessViewer::queryOctree() { qDebug() << "---------------"; qDebug() << "_jurisdictionListener=" << _jurisdictionListener; qDebug() << "Jurisdictions..."; + jurisdictions.lockForRead(); for (NodeToJurisdictionMapIterator i = jurisdictions.begin(); i != jurisdictions.end(); ++i) { qDebug() << i.key() << ": " << &i.value(); } + jurisdictions.unlock(); qDebug() << "---------------"; } @@ -85,7 +87,9 @@ void OctreeHeadlessViewer::queryOctree() { // if we haven't heard from this voxel server, go ahead and send it a query, so we // can get the jurisdiction... + jurisdictions.lockForRead(); if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { + jurisdictions.unlock(); unknownJurisdictionServers++; } else { const JurisdictionMap& map = (jurisdictions)[nodeUUID]; @@ -95,6 +99,7 @@ void OctreeHeadlessViewer::queryOctree() { if (rootCode) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); + jurisdictions.unlock(); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); serverBounds.scale(TREE_SCALE); @@ -103,6 +108,8 @@ void OctreeHeadlessViewer::queryOctree() { if (serverFrustumLocation != ViewFrustum::OUTSIDE) { inViewServers++; } + } else { + jurisdictions.unlock(); } } } @@ -148,7 +155,9 @@ void OctreeHeadlessViewer::queryOctree() { // if we haven't heard from this voxel server, go ahead and send it a query, so we // can get the jurisdiction... + jurisdictions.lockForRead(); if (jurisdictions.find(nodeUUID) == jurisdictions.end()) { + jurisdictions.unlock(); unknownView = true; // assume it's in view if (wantExtraDebugging) { qDebug() << "no known jurisdiction for node " << *node << ", assume it's visible."; @@ -161,6 +170,7 @@ void OctreeHeadlessViewer::queryOctree() { if (rootCode) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); + jurisdictions.unlock(); AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s); serverBounds.scale(TREE_SCALE); @@ -171,6 +181,7 @@ void OctreeHeadlessViewer::queryOctree() { inView = false; } } else { + jurisdictions.unlock(); if (wantExtraDebugging) { qDebug() << "Jurisdiction without RootCode for node " << *node << ". That's unusual!"; }