diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index fab9be24d3..9e4dbcd347 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -141,18 +141,6 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes return packetsSent; // without sending... } - const unsigned char* messageData = nodeData->getPacket(); - - int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast(messageData)); - const unsigned char* dataAt = messageData + numBytesPacketHeader; - dataAt += sizeof(OCTREE_PACKET_FLAGS); - OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); - dataAt += sizeof(OCTREE_PACKET_SEQUENCE); - - OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); - dataAt += sizeof(OCTREE_PACKET_SENT_TIME); - - // If we've got a stats message ready to send, then see if we can piggyback them together if (nodeData->stats.isReadyToSend() && !nodeData->isShuttingDown()) { // Send the stats message to the client @@ -174,7 +162,17 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes _totalBytes += nodeData->getPacketLength(); _totalPackets++; if (debug) { + const unsigned char* messageData = nodeData->getPacket(); + int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast(messageData)); + const unsigned char* dataAt = messageData + numBytesPacketHeader; + dataAt += sizeof(OCTREE_PACKET_FLAGS); + OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SEQUENCE); + OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SENT_TIME); + qDebug() << "Adding stats to packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << + " timestamp: " << timestamp << " statsMessageLength: " << statsMessageLength << " original size: " << nodeData->getPacketLength() << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; @@ -196,7 +194,17 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes _totalBytes += statsMessageLength; _totalPackets++; if (debug) { + const unsigned char* messageData = nodeData->getPacket(); + int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast(messageData)); + const unsigned char* dataAt = messageData + numBytesPacketHeader; + dataAt += sizeof(OCTREE_PACKET_FLAGS); + OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SEQUENCE); + OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SENT_TIME); + qDebug() << "Sending separate stats packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << + " timestamp: " << timestamp << " size: " << statsMessageLength << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } @@ -215,7 +223,17 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes _totalBytes += nodeData->getPacketLength(); _totalPackets++; if (debug) { + const unsigned char* messageData = nodeData->getPacket(); + int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast(messageData)); + const unsigned char* dataAt = messageData + numBytesPacketHeader; + dataAt += sizeof(OCTREE_PACKET_FLAGS); + OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SEQUENCE); + OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SENT_TIME); + qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << + " timestamp: " << timestamp << " size: " << nodeData->getPacketLength() << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } @@ -234,7 +252,17 @@ int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytes _totalBytes += nodeData->getPacketLength(); _totalPackets++; if (debug) { + const unsigned char* messageData = nodeData->getPacket(); + int numBytesPacketHeader = numBytesForPacketHeader(reinterpret_cast(messageData)); + const unsigned char* dataAt = messageData + numBytesPacketHeader; + dataAt += sizeof(OCTREE_PACKET_FLAGS); + OCTREE_PACKET_SEQUENCE sequence = (*(OCTREE_PACKET_SEQUENCE*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SEQUENCE); + OCTREE_PACKET_SENT_TIME timestamp = (*(OCTREE_PACKET_SENT_TIME*)dataAt); + dataAt += sizeof(OCTREE_PACKET_SENT_TIME); + qDebug() << "Sending packet at " << now << " [" << _totalPackets <<"]: sequence: " << sequence << + " timestamp: " << timestamp << " size: " << nodeData->getPacketLength() << " [" << _totalBytes << "] wasted bytes:" << thisWastedBytes << " [" << _totalWastedBytes << "]"; } diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index b3f9649883..18d518d49d 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -10,6 +10,7 @@ Script.include("lookWithTouch.js"); Script.include("editVoxels.js"); +Script.include("editModels.js"); Script.include("selectAudioDevice.js"); Script.include("hydraMove.js"); Script.include("inspect.js"); \ No newline at end of file diff --git a/examples/editModels.js b/examples/editModels.js index 266811f4cc..24ab7da1a1 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -766,18 +766,30 @@ function mouseReleaseEvent(event) { glowedModelID.isKnownID = false; } - - +// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already +// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that +// added it. +var modelMenuAddedDelete = false; function setupModelMenus() { + print("setupModelMenus()"); // add our menuitems - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete Model", shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" }); + if (!Menu.menuItemExists("Edit","Delete")) { + print("no delete... adding ours"); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", + shortcutKeyEvent: { text: "backspace" }, afterItem: "Models" }); + modelMenuAddedDelete = true; + } else { + print("delete exists... don't add ours"); + } } function cleanupModelMenus() { - // delete our menuitems - Menu.removeSeparator("Edit", "Models"); - Menu.removeMenuItem("Edit", "Delete Model"); + if (modelMenuAddedDelete) { + // delete our menuitems + Menu.removeSeparator("Edit", "Models"); + Menu.removeMenuItem("Edit", "Delete"); + } } function scriptEnding() { @@ -797,7 +809,7 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent); setupModelMenus(); Menu.menuItemEvent.connect(function(menuItem){ print("menuItemEvent() in JS... menuItem=" + menuItem); - if (menuItem == "Delete Model") { + if (menuItem == "Delete") { if (leftController.grabbing) { print(" Delete Model.... leftController.modelID="+ leftController.modelID); Models.deleteModel(leftController.modelID); diff --git a/examples/editVoxels.js b/examples/editVoxels.js index 22df8604e1..cff0d65743 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -1117,7 +1117,12 @@ function keyReleaseEvent(event) { trackKeyReleaseEvent(event); // used by preview support } -function setupMenus() { + +// In order for editVoxels and editModels to play nice together, they each check to see if a "delete" menu item already +// exists. If it doesn't they add it. If it does they don't. They also only delete the menu item if they were the one that +// added it. +var voxelMenuAddedDelete = false; +function setupVoxelMenus() { // hook up menus Menu.menuItemEvent.connect(menuItemEvent); @@ -1127,7 +1132,13 @@ function setupMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Copy", shortcutKey: "CTRL+C", afterItem: "Cut" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste", shortcutKey: "CTRL+V", afterItem: "Copy" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Nudge", shortcutKey: "CTRL+N", afterItem: "Paste" }); - Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", shortcutKeyEvent: { text: "backspace" }, afterItem: "Nudge" }); + + + if (!Menu.menuItemExists("Edit","Delete")) { + Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", + shortcutKeyEvent: { text: "backspace" }, afterItem: "Nudge" }); + voxelMenuAddedDelete = true; + } Menu.addMenuItem({ menuName: "File", menuItemName: "Voxels", isSeparator: true, beforeItem: "Settings" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Export Voxels", shortcutKey: "CTRL+E", afterItem: "Voxels" }); @@ -1141,7 +1152,9 @@ function cleanupMenus() { Menu.removeMenuItem("Edit", "Copy"); Menu.removeMenuItem("Edit", "Paste"); Menu.removeMenuItem("Edit", "Nudge"); - Menu.removeMenuItem("Edit", "Delete"); + if (voxelMenuAddedDelete) { + Menu.removeMenuItem("Edit", "Delete"); + } Menu.removeSeparator("File", "Voxels"); Menu.removeMenuItem("File", "Export Voxels"); Menu.removeMenuItem("File", "Import Voxels"); @@ -1482,4 +1495,4 @@ Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); -setupMenus(); +setupVoxelMenus(); diff --git a/examples/menuExample.js b/examples/menuExample.js index 00c6418269..c459a8d73d 100644 --- a/examples/menuExample.js +++ b/examples/menuExample.js @@ -13,68 +13,88 @@ function setupMenus() { - Menu.addMenu("Foo"); - Menu.addMenuItem("Foo","Foo item 1", "SHIFT+CTRL+F" ); - Menu.addMenuItem("Foo","Foo item 2", "SHIFT+F" ); - Menu.addMenuItem("Foo","Foo item 3", "META+F" ); - Menu.addMenuItem({ - menuName: "Foo", - menuItemName: "Foo item 4", - isCheckable: true, - isChecked: true - }); + if (!Menu.menuExists("Foo")) { + Window.alert("Adding Menu Foo!"); + Menu.addMenu("Foo"); + Menu.addMenuItem("Foo","Foo item 1", "SHIFT+CTRL+F" ); + Menu.addMenuItem("Foo","Foo item 2", "SHIFT+F" ); + Menu.addMenuItem("Foo","Foo item 3", "META+F" ); - Menu.addMenuItem({ - menuName: "Foo", - menuItemName: "Foo item 5", - shortcutKey: "ALT+F", - isCheckable: true - }); + Menu.addMenuItem({ + menuName: "Foo", + menuItemName: "Foo item 4", + isCheckable: true, + isChecked: true + }); + + Menu.addMenuItem({ + menuName: "Foo", + menuItemName: "Foo item 5", + shortcutKey: "ALT+F", + isCheckable: true + }); - Menu.addSeparator("Foo","Removable Tools"); - Menu.addMenuItem("Foo","Remove Foo item 4"); - Menu.addMenuItem("Foo","Remove Foo"); - Menu.addMenuItem("Foo","Remove Bar-Spam"); - Menu.addMenu("Bar"); + Menu.addSeparator("Foo","Removable Tools"); + Menu.addMenuItem("Foo","Remove Foo item 4"); + Menu.addMenuItem("Foo","Remove Foo"); + Menu.addMenuItem("Foo","Remove Bar-Spam"); - Menu.addMenuItem("Bar","Bar item 1", "b"); - Menu.addMenuItem({ - menuName: "Bar", - menuItemName: "Bar item 2", - shortcutKeyEvent: { text: "B", isControl: true } - }); + Menu.addMenuItem("Foo","Remove Spam item 2"); - Menu.addMenu("Bar > Spam"); - Menu.addMenuItem("Bar > Spam","Spam item 1"); - Menu.addMenuItem({ - menuName: "Bar > Spam", - menuItemName: "Spam item 2", - isCheckable: true, - isChecked: false - }); + Menu.addMenuItem({ + menuName: "Foo", + menuItemName: "Remove Spam item 2" + }); + } else { + Window.alert("Menu Foo already exists!"); + } + + if (!Menu.menuExists("Bar")) { + Window.alert("Adding Menu Bar!"); + Menu.addMenu("Bar"); + Menu.addMenuItem("Bar","Bar item 1", "b"); + Menu.addMenuItem({ + menuName: "Bar", + menuItemName: "Bar item 2", + shortcutKeyEvent: { text: "B", isControl: true } + }); + + Menu.addMenu("Bar > Spam"); + Menu.addMenuItem("Bar > Spam","Spam item 1"); + Menu.addMenuItem({ + menuName: "Bar > Spam", + menuItemName: "Spam item 2", + isCheckable: true, + isChecked: false + }); - Menu.addSeparator("Bar > Spam","Other Items"); - Menu.addMenuItem("Bar > Spam","Remove Spam item 2"); - Menu.addMenuItem("Foo","Remove Spam item 2"); + Menu.addSeparator("Bar > Spam","Other Items"); + Menu.addMenuItem("Bar > Spam","Remove Spam item 2"); + } - Menu.addMenuItem({ - menuName: "Foo", - menuItemName: "Remove Spam item 2" - }); - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: "before Cut", - beforeItem: "Cut" - }); - - Menu.addMenuItem({ - menuName: "Edit", - menuItemName: "after Nudge", - afterItem: "Nudge" - }); + if (Menu.menuItemExists("Edit","Cut")) { + Window.alert("Menu Item Cut exist adding 'before Cut'."); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "before Cut", + beforeItem: "Cut" + }); + } else { + Window.alert("Menu Item Cut doesn't exist!"); + } + if (Menu.menuItemExists("Edit","Nudge")) { + Window.alert("Menu Item Nudge exist adding 'after Nudge'."); + Menu.addMenuItem({ + menuName: "Edit", + menuItemName: "after Nudge", + afterItem: "Nudge" + }); + } else { + Window.alert("Menu Item Nudge doesn't exist!"); + } } function scriptEnding() { @@ -82,6 +102,10 @@ function scriptEnding() { Menu.removeMenu("Foo"); Menu.removeMenu("Bar"); + + Menu.removeMenuItem("Edit", "before Cut"); + Menu.removeMenuItem("Edit", "after Nudge"); + } function menuItemEvent(menuItem) { 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 0e731fde79..5f9a57f52b 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -2789,16 +2789,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); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index c865f9c6ef..deb052e595 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -858,6 +858,7 @@ QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, void Menu::removeAction(QMenu* menu, const QString& actionName) { menu->removeAction(_actionHash.value(actionName)); + _actionHash.remove(actionName); } void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) { @@ -1650,6 +1651,16 @@ void Menu::removeMenu(const QString& menuName) { } } +bool Menu::menuExists(const QString& menuName) { + QAction* action = getMenuAction(menuName); + + // only proceed if the menu actually exists + if (action) { + return true; + } + return false; +} + void Menu::addSeparator(const QString& menuName, const QString& separatorName) { QMenu* menuObj = getMenu(menuName); if (menuObj) { @@ -1725,8 +1736,17 @@ void Menu::removeMenuItem(const QString& menu, const QString& menuitem) { QMenu* menuObj = getMenu(menu); if (menuObj) { removeAction(menuObj, menuitem); + QMenuBar::repaint(); } - QMenuBar::repaint(); +}; + +bool Menu::menuItemExists(const QString& menu, const QString& menuitem) { + QMenu* menuObj = getMenu(menu); + QAction* menuItemAction = _actionHash.value(menuitem); + if (menuObj && menuItemAction) { + return true; + } + return false; }; QString Menu::getSnapshotsLocation() const { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 18d6fbfda0..020cd651d2 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -169,10 +169,12 @@ public slots: QMenu* addMenu(const QString& menuName); void removeMenu(const QString& menuName); + bool menuExists(const QString& menuName); void addSeparator(const QString& menuName, const QString& separatorName); void removeSeparator(const QString& menuName, const QString& separatorName); void addMenuItem(const MenuItemProperties& properties); void removeMenuItem(const QString& menuName, const QString& menuitem); + bool menuItemExists(const QString& menuName, const QString& menuitem); bool isOptionChecked(const QString& menuOption) const; void setIsOptionChecked(const QString& menuOption, bool isChecked); diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 6ecc45e1e3..acd3d2c31f 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -49,7 +49,7 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // get the rotation axes in joint space and use them to adjust the rotation glm::mat3 axes = glm::mat3_cast(_rotation); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState._transform * glm::translate(state._translation) * + glm::mat3 inverse = glm::mat3(glm::inverse(parentState._transform * glm::translate(state.getDefaultTranslationInParentFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation))); state._rotation = glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) * glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1])) @@ -59,7 +59,7 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // likewise with the eye joints - glm::mat4 inverse = glm::inverse(parentState._transform * glm::translate(state._translation) * + glm::mat4 inverse = glm::inverse(parentState._transform * glm::translate(state.getDefaultTranslationInParentFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)); glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientation() * IDENTITY_FRONT, 0.0f)); glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() + diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index ef5fc3d34b..f60c38198a 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -45,7 +45,8 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } int jointIndex = geometry.humanIKJointIndices.at(humanIKJointIndex); if (jointIndex != -1) { - setJointRotation(jointIndex, _rotation * prioVR->getJointRotations().at(i), PALM_PRIORITY); + JointState& state = _jointStates[jointIndex]; + state.setRotation(_rotation * prioVR->getJointRotations().at(i), PALM_PRIORITY); } } return; @@ -131,7 +132,7 @@ bool operator<(const IndexValue& firstIndex, const IndexValue& secondIndex) { } void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) { - if (jointIndex == -1) { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return; } setJointPosition(jointIndex, position, glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); @@ -145,17 +146,16 @@ void SkeletonModel::applyHandPosition(int jointIndex, const glm::vec3& position) if (forearmLength < EPSILON) { return; } - glm::quat handRotation; - getJointRotation(jointIndex, handRotation, true); + JointState& state = _jointStates[jointIndex]; + glm::quat handRotation = state.getJointRotation(true); // align hand with forearm float sign = (jointIndex == geometry.rightHandJointIndex) ? 1.0f : -1.0f; - applyRotationDelta(jointIndex, rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), - forearmVector), true, PALM_PRIORITY); + state.applyRotationDelta(rotationBetween(handRotation * glm::vec3(-sign, 0.0f, 0.0f), forearmVector), true, PALM_PRIORITY); } void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { - if (jointIndex == -1) { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -169,9 +169,11 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { glm::quat palmRotation; if (!Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK) && Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) { - getJointRotation(parentJointIndex, palmRotation, true); + JointState parentState = _jointStates[parentJointIndex]; + palmRotation = parentState.getJointRotation(true); } else { - getJointRotation(jointIndex, palmRotation, true); + JointState state = _jointStates[jointIndex]; + palmRotation = state.getJointRotation(true); } palmRotation = rotationBetween(palmRotation * geometry.palmDirection, palm.getNormal()) * palmRotation; @@ -188,7 +190,9 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { setJointPosition(parentJointIndex, palm.getPosition() + forearmVector * geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale), glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); - setJointRotation(parentJointIndex, palmRotation, PALM_PRIORITY); + JointState& parentState = _jointStates[parentJointIndex]; + parentState.setRotation(palmRotation, PALM_PRIORITY); + // slam parent-relative rotation to identity _jointStates[jointIndex]._rotation = glm::quat(); } else { @@ -229,7 +233,7 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const } // get the rotation axes in joint space and use them to adjust the rotation glm::mat3 axes = glm::mat3_cast(_rotation); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState._transform * glm::translate(state._translation) * + glm::mat3 inverse = glm::mat3(glm::inverse(parentState._transform * glm::translate(state.getDefaultTranslationInParentFrame()) * joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation))); state._rotation = glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), glm::normalize(inverse * axes[2])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), @@ -245,7 +249,7 @@ void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const } void SkeletonModel::renderJointConstraints(int jointIndex) { - if (jointIndex == -1) { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -351,36 +355,29 @@ void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, c // ik solution glm::vec3 elbowPosition = shoulderPosition + glm::normalize(shoulderToWrist) * mid - tangent * height; - glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f); - glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition); - setJointRotation(shoulderJointIndex, shoulderRotation, PALM_PRIORITY); + + JointState& shoulderState = _jointStates[shoulderJointIndex]; + shoulderState.setRotation(shoulderRotation, PALM_PRIORITY); - setJointRotation(elbowJointIndex, rotationBetween(shoulderRotation * forwardVector, - wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY); + JointState& elbowState = _jointStates[elbowJointIndex]; + elbowState.setRotation(rotationBetween(shoulderRotation * forwardVector, wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY); - setJointRotation(jointIndex, rotation, PALM_PRIORITY); + JointState& handState = _jointStates[jointIndex]; + handState.setRotation(rotation, PALM_PRIORITY); } bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const { return getJointPosition(getLeftHandJointIndex(), position); } -bool SkeletonModel::getLeftHandRotation(glm::quat& rotation) const { - return getJointRotation(getLeftHandJointIndex(), rotation); -} - bool SkeletonModel::getRightHandPosition(glm::vec3& position) const { return getJointPosition(getRightHandJointIndex(), position); } -bool SkeletonModel::getRightHandRotation(glm::quat& rotation) const { - return getJointRotation(getRightHandJointIndex(), rotation); -} - -bool SkeletonModel::restoreLeftHandPosition(float percent, float priority) { - return restoreJointPosition(getLeftHandJointIndex(), percent, priority); +bool SkeletonModel::restoreLeftHandPosition(float fraction, float priority) { + return restoreJointPosition(getLeftHandJointIndex(), fraction, priority); } bool SkeletonModel::getLeftShoulderPosition(glm::vec3& position) const { @@ -391,8 +388,8 @@ float SkeletonModel::getLeftArmLength() const { return getLimbLength(getLeftHandJointIndex()); } -bool SkeletonModel::restoreRightHandPosition(float percent, float priority) { - return restoreJointPosition(getRightHandJointIndex(), percent, priority); +bool SkeletonModel::restoreRightHandPosition(float fraction, float priority) { + return restoreJointPosition(getRightHandJointIndex(), fraction, priority); } bool SkeletonModel::getRightShoulderPosition(glm::vec3& position) const { diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 60e925b239..91070e4ad6 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -45,22 +45,14 @@ public: /// \return true whether or not the position was found bool getLeftHandPosition(glm::vec3& position) const; - /// Retrieve the rotation of the left hand - /// \return true whether or not the rotation was found - bool getLeftHandRotation(glm::quat& rotation) const; - /// Retrieve the position of the right hand /// \return true whether or not the position was found bool getRightHandPosition(glm::vec3& position) const; - /// Retrieve the rotation of the right hand - /// \return true whether or not the rotation was found - bool getRightHandRotation(glm::quat& rotation) const; - - /// Restores some percentage of the default position of the left hand. - /// \param percent the percentage of the default position to restore + /// Restores some fraction of the default position of the left hand. + /// \param fraction the fraction of the default position to restore /// \return whether or not the left hand joint was found - bool restoreLeftHandPosition(float percent = 1.0f, float priority = 1.0f); + bool restoreLeftHandPosition(float fraction = 1.0f, float priority = 1.0f); /// Gets the position of the left shoulder. /// \return whether or not the left shoulder joint was found @@ -69,10 +61,10 @@ public: /// Returns the extended length from the left hand to its last free ancestor. float getLeftArmLength() const; - /// Restores some percentage of the default position of the right hand. - /// \param percent the percentage of the default position to restore + /// Restores some fraction of the default position of the right hand. + /// \param fraction the fraction of the default position to restore /// \return whether or not the right hand joint was found - bool restoreRightHandPosition(float percent = 1.0f, float priority = 1.0f); + bool restoreRightHandPosition(float fraction = 1.0f, float priority = 1.0f); /// Gets the position of the right shoulder. /// \return whether or not the right shoulder joint was found diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 7598d018cb..4ed0e2a4f2 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -41,9 +41,9 @@ Model::Model(QObject* parent) : _snappedToCenter(false), _rootIndex(-1), _shapesAreDirty(true), - _boundingRadius(0.f), - _boundingShape(), - _boundingShapeLocalOffset(0.f), + _boundingRadius(0.0f), + _boundingShape(), + _boundingShapeLocalOffset(0.0f), _lodDistance(0.0f), _pupilDilation(0.0f), _url("http://invalid.com") { @@ -125,7 +125,7 @@ void Model::setScale(const glm::vec3& scale) { void Model::setScaleInternal(const glm::vec3& scale) { float scaleLength = glm::length(_scale); float relativeDeltaScale = glm::length(_scale - scale) / scaleLength; - + const float ONE_PERCENT = 0.01f; if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) { _scale = scale; @@ -186,12 +186,12 @@ QVector Model::createJointStates(const FBXGeometry& geometry) { if (parentIndex == -1) { _rootIndex = i; glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - state.updateWorldTransform(baseTransform, _rotation); + state.computeTransforms(baseTransform, _rotation); ++numJointsSet; jointIsSet[i] = true; } else if (jointIsSet[parentIndex]) { const JointState& parentState = jointStates.at(parentIndex); - state.updateWorldTransform(parentState._transform, parentState._combinedRotation); + state.computeTransforms(parentState._transform, parentState._combinedRotation); ++numJointsSet; jointIsSet[i] = true; } @@ -660,8 +660,8 @@ Extents Model::getMeshExtents() const { // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix - glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0)); - glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0)); + glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); + glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum * _scale, maximum * _scale }; return scaledExtents; } @@ -675,8 +675,8 @@ Extents Model::getUnscaledMeshExtents() const { // even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which // is captured in the offset matrix - glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0)); - glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0)); + glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0f)); + glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0f)); Extents scaledExtents = { minimum, maximum }; return scaledExtents; @@ -701,9 +701,8 @@ void Model::setJointState(int index, bool valid, const glm::quat& rotation, floa if (valid) { state._rotation = rotation; state._animationPriority = priority; - } else if (priority == state._animationPriority) { - state._rotation = _geometry->getFBXGeometry().joints.at(index).rotation; - state._animationPriority = 0.0f; + } else { + state.restoreRotation(1.0f, priority); } } } @@ -733,7 +732,7 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo } bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { - if (jointIndex == -1 || _jointStates.isEmpty()) { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; } position = _translation + extractTranslation(_jointStates[jointIndex]._transform); @@ -741,17 +740,15 @@ bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { } bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) const { - if (jointIndex == -1 || _jointStates.isEmpty()) { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; } - rotation = _jointStates[jointIndex]._combinedRotation * - (fromBind ? _geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation : - _geometry->getFBXGeometry().joints[jointIndex].inverseDefaultRotation); + rotation = _jointStates[jointIndex].getJointRotation(fromBind); return true; } bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { - if (jointIndex == -1 || _jointStates.isEmpty()) { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { return false; } rotation = _jointStates[jointIndex]._combinedRotation; @@ -821,7 +818,7 @@ void Model::rebuildShapes() { // however we must have a shape for each joint, // so we make a bogus sphere with zero radius. // TODO: implement collision groups for more control over what collides with what - SphereShape* sphere = new SphereShape(0.f, glm::vec3(0.0f)); + SphereShape* sphere = new SphereShape(0.0f, glm::vec3(0.0f)); _jointShapes.push_back(sphere); } } @@ -963,8 +960,8 @@ void Model::resetShapePositions() { void Model::updateShapePositions() { if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) { - glm::vec3 rootPosition(0.f); - _boundingRadius = 0.f; + glm::vec3 rootPosition(0.0f); + _boundingRadius = 0.0f; float uniformScale = extractUniformScale(_scale); const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { @@ -1190,13 +1187,14 @@ void Model::simulate(float deltaTime, bool fullUpdate) { } void Model::simulateInternal(float deltaTime) { + // NOTE: this is a recursive call that walks all attachments, and their attachments + // update the world space transforms for all joints + // update animations foreach (const AnimationHandlePointer& handle, _runningAnimations) { handle->simulate(deltaTime); } - // NOTE: this is a recursive call that walks all attachments, and their attachments - // update the world space transforms for all joints for (int i = 0; i < _jointStates.size(); i++) { updateJointState(i); } @@ -1244,10 +1242,10 @@ void Model::updateJointState(int index) { if (joint.parentIndex == -1) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - state.updateWorldTransform(baseTransform, _rotation); + state.computeTransforms(baseTransform, _rotation); } else { const JointState& parentState = _jointStates.at(joint.parentIndex); - state.updateWorldTransform(parentState._transform, parentState._combinedRotation); + state.computeTransforms(parentState._transform, parentState._combinedRotation); } } @@ -1274,9 +1272,12 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const // first, try to rotate the end effector as close as possible to the target rotation, if any glm::quat endRotation; if (useRotation) { - getJointRotation(jointIndex, endRotation, true); - applyRotationDelta(jointIndex, rotation * glm::inverse(endRotation), true, priority); - getJointRotation(jointIndex, endRotation, true); + JointState& state = _jointStates[jointIndex]; + + // TODO: figure out what this is trying to do and combine it into one JointState method + endRotation = state.getJointRotation(true); + state.applyRotationDelta(rotation * glm::inverse(endRotation), true, priority); + endRotation = state.getJointRotation(true); } // then, we go from the joint upwards, rotating the end as close as possible to the target @@ -1319,7 +1320,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const 1.0f / (combinedWeight + 1.0f)); } } - applyRotationDelta(index, combinedDelta, true, priority); + state.applyRotationDelta(combinedDelta, true, priority); glm::quat actualDelta = state._combinedRotation * glm::inverse(oldCombinedRotation); endPosition = actualDelta * jointVector + jointPosition; if (useRotation) { @@ -1337,37 +1338,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const return true; } -bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, float priority) { - if (jointIndex == -1 || _jointStates.isEmpty()) { - return false; - } - JointState& state = _jointStates[jointIndex]; - if (priority >= state._animationPriority) { - state._rotation = state._rotation * glm::inverse(state._combinedRotation) * rotation * - glm::inverse(_geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation); - state._animationPriority = priority; - } - return true; -} - -void Model::setJointTranslation(int jointIndex, const glm::vec3& translation) { - JointState& state = _jointStates[jointIndex]; - const FBXJoint& joint = state.getFBXJoint(); - - glm::mat4 parentTransform; - if (joint.parentIndex == -1) { - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - parentTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; - - } else { - parentTransform = _jointStates.at(joint.parentIndex)._transform; - } - glm::vec3 preTranslation = extractTranslation(joint.preTransform * glm::mat4_cast(joint.preRotation * - state._rotation * joint.postRotation) * joint.postTransform); - state._translation = glm::vec3(glm::inverse(parentTransform) * glm::vec4(translation, 1.0f)) - preTranslation; -} - -bool Model::restoreJointPosition(int jointIndex, float percent, float priority) { +bool Model::restoreJointPosition(int jointIndex, float fraction, float priority) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } @@ -1376,12 +1347,7 @@ bool Model::restoreJointPosition(int jointIndex, float percent, float priority) foreach (int index, freeLineage) { JointState& state = _jointStates[index]; - if (priority == state._animationPriority) { - const FBXJoint& joint = geometry.joints.at(index); - state._rotation = safeMix(state._rotation, joint.rotation, percent); - state._translation = glm::mix(state._translation, joint.translation, percent); - state._animationPriority = 0.0f; - } + state.restoreRotation(fraction, priority); } return true; } @@ -1400,27 +1366,6 @@ float Model::getLimbLength(int jointIndex) const { return length; } -void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain, float priority) { - JointState& state = _jointStates[jointIndex]; - if (priority < state._animationPriority) { - return; - } - state._animationPriority = priority; - const FBXJoint& joint = state.getFBXJoint(); - if (!constrain || (joint.rotationMin == glm::vec3(-PI, -PI, -PI) && - joint.rotationMax == glm::vec3(PI, PI, PI))) { - // no constraints - state._rotation = state._rotation * glm::inverse(state._combinedRotation) * delta * state._combinedRotation; - state._combinedRotation = delta * state._combinedRotation; - return; - } - glm::quat targetRotation = delta * state._combinedRotation; - glm::vec3 eulers = safeEulerAngles(state._rotation * glm::inverse(state._combinedRotation) * targetRotation); - glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax)); - state._combinedRotation = state._combinedRotation * glm::inverse(state._rotation) * newRotation; - state._rotation = newRotation; -} - const int BALL_SUBDIVISIONS = 10; void Model::renderJointCollisionShapes(float alpha) { @@ -1463,7 +1408,7 @@ void Model::renderJointCollisionShapes(float alpha) { glutSolidSphere(capsule->getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); // draw a green cylinder between the two points - glm::vec3 origin(0.f); + glm::vec3 origin(0.0f); glColor4f(0.6f, 0.8f, 0.6f, alpha); Avatar::renderJointConnectingCone( origin, axis, capsule->getRadius(), capsule->getRadius()); } @@ -1495,7 +1440,7 @@ void Model::renderBoundingCollisionShapes(float alpha) { glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); // draw a green cylinder between the two points - glm::vec3 origin(0.f); + glm::vec3 origin(0.0f); glColor4f(0.6f, 0.8f, 0.6f, alpha); Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius()); @@ -1523,7 +1468,7 @@ void Model::applyCollision(CollisionInfo& collision) { return; } - glm::vec3 jointPosition(0.f); + glm::vec3 jointPosition(0.0f); int jointIndex = collision._intData; if (getJointPosition(jointIndex, jointPosition)) { const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; @@ -2039,31 +1984,75 @@ void AnimationHandle::replaceMatchingPriorities(float newPriority) { // JointState TODO: move this class to its own files // ---------------------------------------------------------------------------- JointState::JointState() : - _translation(0.0f), _animationPriority(0.0f), _fbxJoint(NULL) { } -void JointState::setFBXJoint(const FBXJoint* joint) { +void JointState::setFBXJoint(const FBXJoint* joint) { assert(joint != NULL); - _translation = joint->translation; _rotation = joint->rotation; // NOTE: JointState does not own the FBXJoint to which it points. _fbxJoint = joint; } -void JointState::updateWorldTransform(const glm::mat4& baseTransform, const glm::quat& parentRotation) { - assert(_fbxJoint != NULL); - glm::quat combinedRotation = _fbxJoint->preRotation * _rotation * _fbxJoint->postRotation; - _transform = baseTransform * glm::translate(_translation) * _fbxJoint->preTransform * glm::mat4_cast(combinedRotation) * _fbxJoint->postTransform; - _combinedRotation = parentRotation * combinedRotation; -} - void JointState::copyState(const JointState& state) { - _translation = state._translation; _rotation = state._rotation; _transform = state._transform; _combinedRotation = state._combinedRotation; _animationPriority = state._animationPriority; // DO NOT copy _fbxJoint } + +void JointState::computeTransforms(const glm::mat4& baseTransform, const glm::quat& baseRotation) { + assert(_fbxJoint != NULL); + glm::quat combinedRotation = _fbxJoint->preRotation * _rotation * _fbxJoint->postRotation; + _transform = baseTransform * glm::translate(_fbxJoint->translation) * _fbxJoint->preTransform + * glm::mat4_cast(combinedRotation) * _fbxJoint->postTransform; + _combinedRotation = baseRotation * combinedRotation; +} + +glm::quat JointState::getJointRotation(bool fromBind) const { + assert(_fbxJoint != NULL); + return _combinedRotation * (fromBind ? _fbxJoint->inverseBindRotation : _fbxJoint->inverseDefaultRotation); +} + +void JointState::restoreRotation(float fraction, float priority) { + assert(_fbxJoint != NULL); + if (priority == _animationPriority) { + _rotation = safeMix(_rotation, _fbxJoint->rotation, fraction); + _animationPriority = 0.0f; + } +} + +void JointState::setRotation(const glm::quat& rotation, float priority) { + assert(_fbxJoint != NULL); + if (priority >= _animationPriority) { + _rotation = _rotation * glm::inverse(_combinedRotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation); + _animationPriority = priority; + } +} + +void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, float priority) { + assert(_fbxJoint != NULL); + if (priority < _animationPriority) { + return; + } + _animationPriority = priority; + if (!constrain || (_fbxJoint->rotationMin == glm::vec3(-PI, -PI, -PI) && + _fbxJoint->rotationMax == glm::vec3(PI, PI, PI))) { + // no constraints + _rotation = _rotation * glm::inverse(_combinedRotation) * delta * _combinedRotation; + _combinedRotation = delta * _combinedRotation; + return; + } + glm::quat targetRotation = delta * _combinedRotation; + glm::vec3 eulers = safeEulerAngles(_rotation * glm::inverse(_combinedRotation) * targetRotation); + glm::quat newRotation = glm::quat(glm::clamp(eulers, _fbxJoint->rotationMin, _fbxJoint->rotationMax)); + _combinedRotation = _combinedRotation * glm::inverse(_rotation) * newRotation; + _rotation = newRotation; +} + +const glm::vec3& JointState::getDefaultTranslationInParentFrame() const { + assert(_fbxJoint != NULL); + return _fbxJoint->translation; +} diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index c604c418eb..fed83ecaae 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -38,11 +38,24 @@ public: void setFBXJoint(const FBXJoint* joint); const FBXJoint& getFBXJoint() const { return *_fbxJoint; } - void updateWorldTransform(const glm::mat4& baseTransform, const glm::quat& parentRotation); - void copyState(const JointState& state); - glm::vec3 _translation; // translation relative to parent + /// computes new _transform and _combinedRotation + void computeTransforms(const glm::mat4& baseTransform, const glm::quat& baseRotation); + + /// \return rotation from the joint's default (or bind) frame to world frame + glm::quat getJointRotation(bool fromBind = false) const; + + void applyRotationDelta(const glm::quat& delta, bool constrain = true, float priority = 1.0f); + + const glm::vec3& getDefaultTranslationInParentFrame() const; + + void restoreRotation(float fraction, float priority); + + /// \param rotation is from bind- to world-frame + /// computes parent relative _rotation and sets that + void setRotation(const glm::quat& rotation, float priority); + glm::quat _rotation; // rotation relative to parent glm::mat4 _transform; // rotation to world frame + translation in model frame glm::quat _combinedRotation; // rotation from joint local to world frame @@ -234,22 +247,17 @@ protected: bool setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation = glm::quat(), bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false, const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f), float priority = 1.0f); - bool setJointRotation(int jointIndex, const glm::quat& rotation, float priority = 1.0f); - - void setJointTranslation(int jointIndex, const glm::vec3& translation); /// Restores the indexed joint to its default position. - /// \param percent the percentage of the default position to apply (i.e., 0.25f to slerp one fourth of the way to + /// \param fraction the fraction of the default position to apply (i.e., 0.25f to slerp one fourth of the way to /// the original position /// \return true if the joint was found - bool restoreJointPosition(int jointIndex, float percent = 1.0f, float priority = 0.0f); + bool restoreJointPosition(int jointIndex, float fraction = 1.0f, float priority = 0.0f); /// Computes and returns the extended length of the limb terminating at the specified joint and starting at the joint's /// first free ancestor. float getLimbLength(int jointIndex) const; - void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true, float priority = 1.0f); - void computeBoundingShape(const FBXGeometry& geometry); private: diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index 11d681bfc7..277c611f04 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -35,6 +35,14 @@ void MenuScriptingInterface::removeMenu(const QString& menu) { QMetaObject::invokeMethod(Menu::getInstance(), "removeMenu", Q_ARG(const QString&, menu)); } +bool MenuScriptingInterface::menuExists(const QString& menu) { + bool result; + QMetaObject::invokeMethod(Menu::getInstance(), "menuExists", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, menu)); + return result; +} + void MenuScriptingInterface::addSeparator(const QString& menuName, const QString& separatorName) { QMetaObject::invokeMethod(Menu::getInstance(), "addSeparator", Q_ARG(const QString&, menuName), @@ -67,6 +75,15 @@ void MenuScriptingInterface::removeMenuItem(const QString& menu, const QString& Q_ARG(const QString&, menuitem)); }; +bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& menuitem) { + bool result; + QMetaObject::invokeMethod(Menu::getInstance(), "menuItemExists", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(bool, result), + Q_ARG(const QString&, menu), + Q_ARG(const QString&, menuitem)); + return result; +} + bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) { bool result; QMetaObject::invokeMethod(Menu::getInstance(), "isOptionChecked", Qt::BlockingQueuedConnection, diff --git a/interface/src/scripting/MenuScriptingInterface.h b/interface/src/scripting/MenuScriptingInterface.h index d127bd6edc..fda8207780 100644 --- a/interface/src/scripting/MenuScriptingInterface.h +++ b/interface/src/scripting/MenuScriptingInterface.h @@ -33,6 +33,7 @@ private slots: public slots: void addMenu(const QString& menuName); void removeMenu(const QString& menuName); + bool menuExists(const QString& menuName); void addSeparator(const QString& menuName, const QString& separatorName); void removeSeparator(const QString& menuName, const QString& separatorName); @@ -42,6 +43,7 @@ public slots: void addMenuItem(const QString& menuName, const QString& menuitem); void removeMenuItem(const QString& menuName, const QString& menuitem); + bool menuItemExists(const QString& menuName, const QString& menuitem); bool isOptionChecked(const QString& menuOption); void setIsOptionChecked(const QString& menuOption, bool isChecked);