diff --git a/assignment-client/src/models/ModelServer.cpp b/assignment-client/src/models/ModelServer.cpp index a71c98ed83..ff2367ec6e 100644 --- a/assignment-client/src/models/ModelServer.cpp +++ b/assignment-client/src/models/ModelServer.cpp @@ -86,7 +86,7 @@ bool ModelServer::hasSpecialPacketToSend(const SharedNodePointer& node) { return shouldSendDeletedModels; } -int ModelServer::sendSpecialPacket(const SharedNodePointer& node) { +int ModelServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node) { unsigned char outputBuffer[MAX_PACKET_SIZE]; size_t packetLength = 0; @@ -100,12 +100,13 @@ int ModelServer::sendSpecialPacket(const SharedNodePointer& node) { // TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 models? while (hasMoreToSend) { - hasMoreToSend = tree->encodeModelsDeletedSince(deletedModelsSentAt, + hasMoreToSend = tree->encodeModelsDeletedSince(queryNode->getSequenceNumber(), deletedModelsSentAt, outputBuffer, MAX_PACKET_SIZE, packetLength); //qDebug() << "sending PacketType_MODEL_ERASE packetLength:" << packetLength; NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node)); + queryNode->incrementSequenceNumber(); } nodeData->setLastDeletedModelsSentAt(deletePacketSentAt); diff --git a/assignment-client/src/models/ModelServer.h b/assignment-client/src/models/ModelServer.h index c2ca6d1f5b..7e7f239f2a 100644 --- a/assignment-client/src/models/ModelServer.h +++ b/assignment-client/src/models/ModelServer.h @@ -37,7 +37,7 @@ public: // subclass may implement these method virtual void beforeRun(); virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); - virtual int sendSpecialPacket(const SharedNodePointer& node); + virtual int sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node); virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode); diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index ede6dee5e3..6acd85bff6 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -362,7 +362,3 @@ void OctreeQueryNode::dumpOutOfView() { } } } - -void OctreeQueryNode::incrementSequenceNumber() { - _sequenceNumber++; -} \ No newline at end of file diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index 1bb3a72a0c..eb420039e6 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -100,7 +100,9 @@ public: void forceNodeShutdown(); bool isShuttingDown() const { return _isShuttingDown; } - void incrementSequenceNumber(); + void incrementSequenceNumber() { _sequenceNumber++; } + + OCTREE_PACKET_SEQUENCE getSequenceNumber() const { return _sequenceNumber; } private slots: void sendThreadFinished(); diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 8d6803adac..9e4dbcd347 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -141,14 +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); - - // 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 @@ -170,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 << "]"; @@ -192,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 << "]"; } @@ -211,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 << "]"; } @@ -230,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 << "]"; } @@ -529,7 +561,8 @@ int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrus // send the environment packet // TODO: should we turn this into a while loop to better handle sending multiple special packets if (_myServer->hasSpecialPacketToSend(_node) && !nodeData->isShuttingDown()) { - trueBytesSent += _myServer->sendSpecialPacket(_node); + trueBytesSent += _myServer->sendSpecialPacket(nodeData, _node); + nodeData->resetOctreePacket(); // because nodeData's _sequenceNumber has changed truePacketsSent++; packetsSentThisInterval++; } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index ff62561c58..f0db93feb3 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -72,7 +72,7 @@ public: // subclass may implement these method virtual void beforeRun() { }; virtual bool hasSpecialPacketToSend(const SharedNodePointer& node) { return false; } - virtual int sendSpecialPacket(const SharedNodePointer& node) { return 0; } + virtual int sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node) { return 0; } static void attachQueryNodeToNode(Node* newNode); diff --git a/assignment-client/src/particles/ParticleServer.cpp b/assignment-client/src/particles/ParticleServer.cpp index 649ee0cff9..1dd65f11f3 100644 --- a/assignment-client/src/particles/ParticleServer.cpp +++ b/assignment-client/src/particles/ParticleServer.cpp @@ -86,7 +86,7 @@ bool ParticleServer::hasSpecialPacketToSend(const SharedNodePointer& node) { return shouldSendDeletedParticles; } -int ParticleServer::sendSpecialPacket(const SharedNodePointer& node) { +int ParticleServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node) { unsigned char outputBuffer[MAX_PACKET_SIZE]; size_t packetLength = 0; @@ -100,12 +100,13 @@ int ParticleServer::sendSpecialPacket(const SharedNodePointer& node) { // TODO: is it possible to send too many of these packets? what if you deleted 1,000,000 particles? while (hasMoreToSend) { - hasMoreToSend = tree->encodeParticlesDeletedSince(deletedParticlesSentAt, + hasMoreToSend = tree->encodeParticlesDeletedSince(queryNode->getSequenceNumber(), deletedParticlesSentAt, outputBuffer, MAX_PACKET_SIZE, packetLength); //qDebug() << "sending PacketType_PARTICLE_ERASE packetLength:" << packetLength; NodeList::getInstance()->writeDatagram((char*) outputBuffer, packetLength, SharedNodePointer(node)); + queryNode->incrementSequenceNumber(); } nodeData->setLastDeletedParticlesSentAt(deletePacketSentAt); diff --git a/assignment-client/src/particles/ParticleServer.h b/assignment-client/src/particles/ParticleServer.h index 1e0bb1b967..3066c5fa98 100644 --- a/assignment-client/src/particles/ParticleServer.h +++ b/assignment-client/src/particles/ParticleServer.h @@ -37,7 +37,7 @@ public: // subclass may implement these method virtual void beforeRun(); virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); - virtual int sendSpecialPacket(const SharedNodePointer& node); + virtual int sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node); virtual void particleCreated(const Particle& newParticle, const SharedNodePointer& senderNode); diff --git a/assignment-client/src/voxels/VoxelServer.cpp b/assignment-client/src/voxels/VoxelServer.cpp index 7747b8336b..8f4a8bab36 100644 --- a/assignment-client/src/voxels/VoxelServer.cpp +++ b/assignment-client/src/voxels/VoxelServer.cpp @@ -40,9 +40,34 @@ bool VoxelServer::hasSpecialPacketToSend(const SharedNodePointer& node) { return shouldSendEnvironments; } -int VoxelServer::sendSpecialPacket(const SharedNodePointer& node) { +int VoxelServer::sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node) { + + unsigned char* copyAt = _tempOutputBuffer; + int numBytesPacketHeader = populatePacketHeader(reinterpret_cast(_tempOutputBuffer), PacketTypeEnvironmentData); + copyAt += numBytesPacketHeader; int envPacketLength = numBytesPacketHeader; + + // pack in flags + OCTREE_PACKET_FLAGS flags = 0; + OCTREE_PACKET_FLAGS* flagsAt = (OCTREE_PACKET_FLAGS*)copyAt; + *flagsAt = flags; + copyAt += sizeof(OCTREE_PACKET_FLAGS); + envPacketLength += sizeof(OCTREE_PACKET_FLAGS); + + // pack in sequence number + OCTREE_PACKET_SEQUENCE* sequenceAt = (OCTREE_PACKET_SEQUENCE*)copyAt; + *sequenceAt = queryNode->getSequenceNumber(); + copyAt += sizeof(OCTREE_PACKET_SEQUENCE); + envPacketLength += sizeof(OCTREE_PACKET_SEQUENCE); + + // pack in timestamp + OCTREE_PACKET_SENT_TIME now = usecTimestampNow(); + OCTREE_PACKET_SENT_TIME* timeAt = (OCTREE_PACKET_SENT_TIME*)copyAt; + *timeAt = now; + copyAt += sizeof(OCTREE_PACKET_SENT_TIME); + envPacketLength += sizeof(OCTREE_PACKET_SENT_TIME); + int environmentsToSend = getSendMinimalEnvironment() ? 1 : getEnvironmentDataCount(); for (int i = 0; i < environmentsToSend; i++) { @@ -50,6 +75,8 @@ int VoxelServer::sendSpecialPacket(const SharedNodePointer& node) { } NodeList::getInstance()->writeDatagram((char*) _tempOutputBuffer, envPacketLength, SharedNodePointer(node)); + queryNode->incrementSequenceNumber(); + return envPacketLength; } diff --git a/assignment-client/src/voxels/VoxelServer.h b/assignment-client/src/voxels/VoxelServer.h index bec60cadfe..4e04c48cfd 100644 --- a/assignment-client/src/voxels/VoxelServer.h +++ b/assignment-client/src/voxels/VoxelServer.h @@ -46,7 +46,7 @@ public: // subclass may implement these method virtual void beforeRun(); virtual bool hasSpecialPacketToSend(const SharedNodePointer& node); - virtual int sendSpecialPacket(const SharedNodePointer& node); + virtual int sendSpecialPacket(OctreeQueryNode* queryNode, const SharedNodePointer& node); private: bool _sendEnvironments; diff --git a/examples/currentAPI.js b/examples/currentAPI.js new file mode 100644 index 0000000000..30b24910f9 --- /dev/null +++ b/examples/currentAPI.js @@ -0,0 +1,42 @@ +// +// currentAPI.js +// examples +// +// Created by Clément Brisset on 5/30/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var array = []; +var buffer = "\n\n\n\n\n======= JS API list ======="; +function listKeys(string, object) { + if (string == "listKeys" || string == "array" || string == "buffer" || string == "i") { + return; + } + + if (typeof(object) != "object") { + array.push(string + " " + typeof(object)); + return; + } + + var keys = Object.keys(object); + for (var i = 0; i < keys.length; ++i) { + if (string == "") { + listKeys(keys[i], object[keys[i]]); + } else { + listKeys(string + "." + keys[i], object[keys[i]]); + } + } +} + +listKeys("", this); +array.sort(); + +for (var i = 0; i < array.length; ++i) { + buffer = buffer + "\n" + array[i]; +} +buffer = buffer + "\n========= API END =========\n\n\n\n\n"; + +print(buffer); 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 30d1e4edf4..24ab7da1a1 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -40,6 +40,8 @@ var modelURLs = [ var toolBar; +var jointList = MyAvatar.getJointNames(); + function isLocked(properties) { // special case to lock the ground plane model in hq. if (location.hostname == "hq.highfidelity.io" && @@ -81,10 +83,13 @@ function controller(wichSide) { this.grabbing = false; this.modelID = { isKnownID: false }; + this.modelURL = ""; this.oldModelRotation; this.oldModelPosition; this.oldModelRadius; + this.jointsIntersectingFromStart = []; + this.laser = Overlays.addOverlay("line3d", { position: { x: 0, y: 0, z: 0 }, end: { x: 0, y: 0, z: 0 }, @@ -134,16 +139,63 @@ function controller(wichSide) { this.grabbing = true; this.modelID = modelID; + this.modelURL = properties.modelURL; this.oldModelPosition = properties.position; this.oldModelRotation = properties.modelRotation; this.oldModelRadius = properties.radius; + + this.jointsIntersectingFromStart = []; + for (var i = 0; i < jointList.length; i++) { + var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); + if (distance < this.oldModelRadius) { + this.jointsIntersectingFromStart.push(i); + } + } } } this.release = function () { + if (this.grabbing) { + jointList = MyAvatar.getJointNames(); + + var closestJointIndex = -1; + var closestJointDistance = 10; + for (var i = 0; i < jointList.length; i++) { + var distance = Vec3.distance(MyAvatar.getJointPosition(jointList[i]), this.oldModelPosition); + if (distance < closestJointDistance) { + closestJointDistance = distance; + closestJointIndex = i; + } + } + + print("closestJoint: " + jointList[closestJointIndex]); + print("closestJointDistance (attach max distance): " + closestJointDistance + " (" + this.oldModelRadius + ")"); + + if (closestJointDistance < this.oldModelRadius) { + + if (this.jointsIntersectingFromStart.indexOf(closestJointIndex) != -1) { + // Do nothing + } else { + print("Attaching to " + jointList[closestJointIndex]); + var jointPosition = MyAvatar.getJointPosition(jointList[closestJointIndex]); + var jointRotation = MyAvatar.getJointCombinedRotation(jointList[closestJointIndex]); + + var attachmentOffset = Vec3.subtract(this.oldModelPosition, jointPosition); + attachmentOffset = Vec3.multiplyQbyV(Quat.inverse(jointRotation), attachmentOffset); + var attachmentRotation = Quat.multiply(Quat.inverse(jointRotation), this.oldModelRotation); + + MyAvatar.attach(this.modelURL, jointList[closestJointIndex], + attachmentOffset, attachmentRotation, 2.0 * this.oldModelRadius, + true, false); + Models.deleteModel(this.modelID); + } + } + } + this.grabbing = false; this.modelID.isKnownID = false; + this.jointsIntersectingFromStart = []; } this.checkTrigger = function () { @@ -251,14 +303,21 @@ function controller(wichSide) { position: newPosition, modelRotation: newRotation }); -// print("Moving " + this.modelID.id); -// Vec3.print("Old Position: ", this.oldModelPosition); -// Vec3.print("Sav Position: ", newPosition); -// Quat.print("Old Rotation: ", this.oldModelRotation); -// Quat.print("New Rotation: ", newRotation); this.oldModelRotation = newRotation; this.oldModelPosition = newPosition; + + var indicesToRemove = []; + for (var i = 0; i < this.jointsIntersectingFromStart.length; ++i) { + var distance = Vec3.distance(MyAvatar.getJointPosition(this.jointsIntersectingFromStart[i]), this.oldModelPosition); + if (distance >= this.oldModelRadius) { + indicesToRemove.push(this.jointsIntersectingFromStart[i]); + } + + } + for (var i = 0; i < indicesToRemove.length; ++i) { + this.jointsIntersectingFromStart.splice(this.jointsIntersectingFromStart.indexOf(indicesToRemove[i], 1)); + } } } @@ -292,6 +351,43 @@ function controller(wichSide) { } if (this.pressing) { + // Checking for attachments intersecting + var attachments = MyAvatar.getAttachmentData(); + var attachmentIndex = -1; + var attachmentX = LASER_LENGTH_FACTOR; + + for (var i = 0; i < attachments.length; ++i) { + var position = Vec3.sum(MyAvatar.getJointPosition(attachments[i].jointName), attachments[i].translation); + var scale = attachments[i].scale; + + var A = this.palmPosition; + var B = this.front; + var P = position; + + var x = Vec3.dot(Vec3.subtract(P, A), B); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + + if (d < scale / 2.0 && 0 < x && x < attachmentX) { + attachmentIndex = i; + attachmentX = d; + } + } + + if (attachmentIndex != -1) { + MyAvatar.detachOne(attachments[attachmentIndex].modelURL, attachments[attachmentIndex].jointName); + Models.addModel({ + position: Vec3.sum(MyAvatar.getJointPosition(attachments[attachmentIndex].jointName), + attachments[attachmentIndex].translation), + modelRotation: Quat.multiply(MyAvatar.getJointCombinedRotation(attachments[attachmentIndex].jointName), + attachments[attachmentIndex].rotation), + radius: attachments[attachmentIndex].scale / 2.0, + modelURL: attachments[attachmentIndex].modelURL + }); + } + + // There is none so ... + // Checking model tree Vec3.print("Looking at: ", this.palmPosition); var pickRay = { origin: this.palmPosition, direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; @@ -306,7 +402,7 @@ function controller(wichSide) { var identify = Models.identifyModel(foundModel); if (!identify.isKnownID) { print("Unknown ID " + identify.id + " (update loop " + foundModel.id + ")"); - continue; + return; } foundModel = identify; } @@ -410,8 +506,6 @@ function checkController(deltaTime) { moveOverlays(); } - - function initToolBar() { toolBar = new ToolBar(0, 0, ToolBar.VERTICAL); // New Model @@ -474,10 +568,15 @@ function mousePressEvent(event) { } var position = Vec3.sum(MyAvatar.position, Vec3.multiply(Quat.getFront(MyAvatar.orientation), SPAWN_DISTANCE)); - Models.addModel({ position: position, - radius: radiusDefault, - modelURL: url - }); + + if (position.x > 0 && position.y > 0 && position.z > 0) { + Models.addModel({ position: position, + radius: radiusDefault, + modelURL: url + }); + } else { + print("Can't create model: Model would be out of bounds."); + } } else { var pickRay = Camera.computePickRay(event.x, event.y); @@ -493,7 +592,7 @@ function mousePressEvent(event) { var identify = Models.identifyModel(foundModel); if (!identify.isKnownID) { print("Unknown ID " + identify.id + " (update loop " + foundModel.id + ")"); - continue; + return; } foundModel = identify; } @@ -667,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() { @@ -698,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 14bea50bb0..cff0d65743 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -28,8 +28,10 @@ var NEW_VOXEL_SIZE = 1.0; var NEW_VOXEL_DISTANCE_FROM_CAMERA = 3.0; var PIXELS_PER_EXTRUDE_VOXEL = 16; var WHEEL_PIXELS_PER_SCALE_CHANGE = 100; -var MAX_VOXEL_SCALE = 16.0; -var MIN_VOXEL_SCALE = 1.0 / Math.pow(2.0, 8.0); +var MAX_VOXEL_SCALE_POWER = 4; +var MIN_VOXEL_SCALE_POWER = -8; +var MAX_VOXEL_SCALE = Math.pow(2.0, MAX_VOXEL_SCALE_POWER); +var MIN_VOXEL_SCALE = Math.pow(2.0, MIN_VOXEL_SCALE_POWER); var WHITE_COLOR = { red: 255, green: 255, blue: 255 }; var MAX_PASTE_VOXEL_SCALE = 256; @@ -330,6 +332,13 @@ function ScaleSelector() { visible: false }); this.setScale = function(scale) { + if (scale > MAX_VOXEL_SCALE) { + scale = MAX_VOXEL_SCALE; + } + if (scale < MIN_VOXEL_SCALE) { + scale = MIN_VOXEL_SCALE; + } + this.scale = scale; this.power = Math.floor(Math.log(scale) / Math.log(2)); rescaleImport(); @@ -391,12 +400,9 @@ function ScaleSelector() { this.incrementScale = function() { copyScale = false; - if (this.power < 13) { + if (this.power < MAX_VOXEL_SCALE_POWER) { ++this.power; this.scale *= 2.0; - if (this.scale > MAX_VOXEL_SCALE) { - this.scale = MAX_VOXEL_SCALE; - } this.update(); rescaleImport(); resizeVoxelSound.play(voxelSizePlus); @@ -405,7 +411,7 @@ function ScaleSelector() { this.decrementScale = function() { copyScale = false; - if (-4 < this.power) { + if (MIN_VOXEL_SCALE_POWER < this.power) { --this.power; this.scale /= 2.0; this.update(); @@ -1111,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); @@ -1121,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" }); @@ -1135,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"); @@ -1476,4 +1495,4 @@ Script.scriptEnding.connect(scriptEnding); Script.update.connect(update); -setupMenus(); +setupVoxelMenus(); diff --git a/examples/locationsMenu.js b/examples/locationsMenu.js new file mode 100644 index 0000000000..6f4a28fe38 --- /dev/null +++ b/examples/locationsMenu.js @@ -0,0 +1,302 @@ +// +// locationsMenu.js +// examples +// +// Created by Ryan Huffman on 5/28/14 +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var scriptUrl = "https://script.google.com/macros/s/AKfycbwIo4lmF-qUwX1Z-9eA_P-g2gse9oFhNcjVyyksGukyDDEFXgU/exec?action=listOwners&domain=alpha.highfidelity.io"; + +var LocationMenu = function(opts) { + var self = this; + + var pageSize = opts.pageSize || 10; + var menuWidth = opts.menuWidth || 150; + var menuHeight = opts.menuItemHeight || 24; + + var inactiveColor = { red: 51, green: 102, blue: 102 }; + var activeColor = { red: 18, green: 66, blue: 66 }; + var prevNextColor = { red: 192, green: 192, blue: 192 }; + var disabledColor = { red: 64, green: 64, blue: 64}; + var position = { x: 0, y: 0 }; + + var locationIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/location.svg"; + var toolHeight = 50; + var toolWidth = 50; + var visible = false; + var menuItemOffset = { + x: 55, + y: 0, + }; + var menuItemPadding = 5; + var margin = 7; + var fullMenuHeight = (2 * menuItemOffset.y) + (menuHeight * (pageSize + 1)); + var menuOffset = -fullMenuHeight + toolHeight; + + var windowDimensions = Controller.getViewportDimensions(); + + this.locations = []; + this.numPages = 1; + this.page = 0; + + this.menuToggleButton = Overlays.addOverlay("image", { + x: position.x, + y: position.y, + width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: locationIconUrl, + alpha: 0.9 + }); + + this.background = Overlays.addOverlay("text", { + x: 0, + y: 0, + width: menuWidth + 10, + height: (menuHeight * (pageSize + 1)) + 10, + color: { red: 0, green: 0, blue: 0}, + topMargin: 4, + leftMargin: 4, + text: "", + visible: visible, + }); + + this.menuItems = []; + for (var i = 0; i < pageSize; i++) { + var menuItem = Overlays.addOverlay("text", { + x: 0, + y: 0, + width: menuWidth, + height: menuHeight, + color: inactiveColor, + topMargin: margin, + leftMargin: margin, + text: (i == 0) ? "Loading..." : "", + visible: visible, + }); + this.menuItems.push({ overlay: menuItem, location: null }); + } + + this.previousButton = Overlays.addOverlay("text", { + x: 0, + y: 0, + width: menuWidth / 2, + height: menuHeight, + color: disabledColor, + topMargin: margin, + leftMargin: margin, + text: "Previous", + visible: visible, + }); + + this.nextButton = Overlays.addOverlay("text", { + x: 0, + y: 0, + width: menuWidth / 2, + height: menuHeight, + color: disabledColor, + topMargin: margin, + leftMargin: margin, + text: "Next", + visible: visible, + }); + + this.reposition = function(force) { + var newWindowDimensions = Controller.getViewportDimensions(); + if (force || newWindowDimensions.y != windowDimensions.y) { + windowDimensions = newWindowDimensions; + + position.x = 8; + position.y = Math.floor(windowDimensions.y / 2) + 25 + 50 + 8; + + Overlays.editOverlay(self.menuToggleButton, { + x: position.x, + y: position.y, + }); + Overlays.editOverlay(self.background, { + x: position.x + menuItemOffset.x, + y: position.y + menuItemOffset.y - 2 * menuItemPadding + menuOffset, + }); + for (var i = 0; i < pageSize; i++) { + Overlays.editOverlay(self.menuItems[i].overlay, { + x: position.x + menuItemOffset.x + menuItemPadding, + y: position.y + menuItemOffset.y - menuItemPadding + (i * menuHeight) + menuOffset, + }); + } + Overlays.editOverlay(self.previousButton, { + x: position.x + menuItemOffset.x + menuItemPadding, + y: position.y + menuItemOffset.y - menuItemPadding + (pageSize * menuHeight) + menuOffset, + }); + Overlays.editOverlay(self.nextButton, { + x: position.x + menuItemOffset.x + menuItemPadding + (menuWidth / 2), + y: position.y + menuItemOffset.y - menuItemPadding + (pageSize * menuHeight) + menuOffset, + }); + } + } + + this.updateLocations = function(locations) { + this.locations = locations; + this.numPages = Math.ceil(locations.length / pageSize); + this.goToPage(0); + } + + this.setError = function() { + Overlays.editOverlay(this.menuItems[0].overlay, { text: "Error loading data" }); + } + + this.toggleMenu = function() { + visible = !visible; + for (var i = 0; i < this.menuItems.length; i++) { + Overlays.editOverlay(this.menuItems[i].overlay, { visible: visible}); + } + Overlays.editOverlay(this.previousButton, { visible: visible}); + Overlays.editOverlay(this.nextButton, { visible: visible}); + Overlays.editOverlay(this.background, { visible: visible}); + if (visible) { + Overlays.editOverlay(this.menuToggleButton, { subImage: { x: 0, y: 0, width: toolWidth, height: toolHeight } }), + } else { + Overlays.editOverlay(this.menuToggleButton, { subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight } }), + } + } + + this.goToPage = function(pageNumber) { + if (pageNumber < 0 || pageNumber >= this.numPages) { + return; + } + + this.page = pageNumber; + var start = pageNumber * pageSize; + for (var i = 0; i < pageSize; i++) { + var update = {}; + var location = null; + if (start + i < this.locations.length) { + location = this.locations[start + i]; + update.text = (start + i + 1) + ". " + location.username; + update.color = inactiveColor; + } else { + update.text = ""; + update.color = disabledColor; + } + Overlays.editOverlay(this.menuItems[i].overlay, update); + this.menuItems[i].location = location; + } + + this.previousEnabled = pageNumber > 0; + this.nextEnabled = pageNumber < (this.numPages - 1); + + Overlays.editOverlay(this.previousButton, { color: this.previousEnabled ? prevNextColor : disabledColor}); + Overlays.editOverlay(this.nextButton, { color: this.nextEnabled ? prevNextColor : disabledColor }); + } + + this.mousePressEvent = function(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + + if (clickedOverlay == self.menuToggleButton) { + self.toggleMenu(); + } else if (clickedOverlay == self.previousButton) { + if (self.previousEnabled) { + Overlays.editOverlay(clickedOverlay, { color: activeColor }); + } + } else if (clickedOverlay == self.nextButton) { + if (self.nextEnabled) { + Overlays.editOverlay(clickedOverlay, { color: activeColor }); + } + } else { + for (var i = 0; i < self.menuItems.length; i++) { + if (clickedOverlay == self.menuItems[i].overlay) { + if (self.menuItems[i].location != null) { + Overlays.editOverlay(clickedOverlay, { color: activeColor }); + } + break; + } + } + } + } + + this.mouseReleaseEvent = function(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + + if (clickedOverlay == self.previousButton) { + if (self.previousEnabled) { + Overlays.editOverlay(clickedOverlay, { color: inactiveColor }); + self.goToPage(self.page - 1); + } + } else if (clickedOverlay == self.nextButton) { + if (self.nextEnabled) { + Overlays.editOverlay(clickedOverlay, { color: inactiveColor }); + self.goToPage(self.page + 1); + } + } else { + for (var i = 0; i < self.menuItems.length; i++) { + if (clickedOverlay == self.menuItems[i].overlay) { + if (self.menuItems[i].location != null) { + Overlays.editOverlay(clickedOverlay, { color: inactiveColor }); + var location = self.menuItems[i].location; + Window.location = "hifi://" + location.domain + "/" + + location.x + "," + location.y + "," + location.z; + } + break; + } + } + } + } + + this.cleanup = function() { + for (var i = 0; i < self.menuItems.length; i++) { + Overlays.deleteOverlay(self.menuItems[i].overlay); + } + Overlays.deleteOverlay(self.menuToggleButton); + Overlays.deleteOverlay(self.previousButton); + Overlays.deleteOverlay(self.nextButton); + Overlays.deleteOverlay(self.background); + } + + Controller.mousePressEvent.connect(this.mousePressEvent); + Controller.mouseReleaseEvent.connect(this.mouseReleaseEvent); + Script.update.connect(this.reposition); + Script.scriptEnding.connect(this.cleanup); + + this.reposition(true); +}; + +var locationMenu = new LocationMenu({ pageSize: 8 }); + +print("Loading strip data from " + scriptUrl); + +var req = new XMLHttpRequest(); +req.responseType = 'json'; + +req.onreadystatechange = function() { + if (req.readyState == req.DONE) { + if (req.status == 200 && req.response != null) { + for (var domain in req.response) { + var locations = req.response[domain]; + var users = []; + for (var i = 0; i < locations.length; i++) { + var loc = locations[i]; + var x1 = loc[1], + x2 = loc[2], + y1 = loc[3], + y2 = loc[4]; + users.push({ + domain: domain, + username: loc[0], + x: x1, + y: 300, + z: y1, + }); + } + locationMenu.updateLocations(users); + } + } else { + print("Error loading data: " + req.status + " " + req.statusText + ", " + req.errorCode + ": " + req.responseText); + locationMenu.setError(); + } + } +} + +req.open("GET", scriptUrl); +req.send(); 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/playSoundLoop.js b/examples/playSoundLoop.js new file mode 100644 index 0000000000..86226468bc --- /dev/null +++ b/examples/playSoundLoop.js @@ -0,0 +1,43 @@ +// +// playSoundLoop.js +// examples +// +// Created by David Rowe on 5/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// This example script plays a sound in a continuous loop. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Guitars/Guitar+-+Nylon+A.raw"); + +var soundPlaying = false; + +function keyPressEvent(event) { + if (event.text === "1") { + if (!Audio.isInjectorPlaying(soundPlaying)) { + var options = new AudioInjectionOptions(); + options.position = MyAvatar.position; + options.volume = 0.5; + options.loop = true; + soundPlaying = Audio.playSound(sound, options); + print("Started sound loop"); + } else { + Audio.stopInjector(soundPlaying); + print("Stopped sound loop"); + } + } +} + +function scriptEnding() { + if (Audio.isInjectorPlaying(soundPlaying)) { + Audio.stopInjector(soundPlaying); + print("Stopped sound loop"); + } +} + +// Connect a call back that happens every frame +Script.scriptEnding.connect(scriptEnding); +Controller.keyPressEvent.connect(keyPressEvent); diff --git a/examples/sit.js b/examples/sit.js index 1df877dba6..6ae8a712f1 100644 --- a/examples/sit.js +++ b/examples/sit.js @@ -40,6 +40,8 @@ var passedTime = 0.0; var startPosition = null; var animationLenght = 2.0; +var sitting = false; + // This is the pose we would like to end up var pose = [ {joint:"RightUpLeg", rotation: {x:100.0, y:15.0, z:0.0}}, @@ -101,31 +103,41 @@ var standingUpAnimation = function(deltaTime){ } } +function sitDown() { + sitting = true; + passedTime = 0.0; + startPosition = MyAvatar.position; + storeStartPoseAndTransition(); + try{ + Script.update.disconnect(standingUpAnimation); + } catch(e){ + // no need to handle. if it wasn't connected no harm done + } + Script.update.connect(sittingDownAnimation); + Overlays.editOverlay(sitDownButton, { visible: false }); + Overlays.editOverlay(standUpButton, { visible: true }); +} + +function standUp() { + sitting = false; + passedTime = 0.0; + startPosition = MyAvatar.position; + try{ + Script.update.disconnect(sittingDownAnimation); + } catch (e){} + Script.update.connect(standingUpAnimation); + Overlays.editOverlay(standUpButton, { visible: false }); + Overlays.editOverlay(sitDownButton, { visible: true }); +} + Controller.mousePressEvent.connect(function(event){ var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); if (clickedOverlay == sitDownButton) { - passedTime = 0.0; - startPosition = MyAvatar.position; - storeStartPoseAndTransition(); - try{ - Script.update.disconnect(standingUpAnimation); - } catch(e){ - // no need to handle. if it wasn't connected no harm done - } - Script.update.connect(sittingDownAnimation); - Overlays.editOverlay(sitDownButton, { visible: false }); - Overlays.editOverlay(standUpButton, { visible: true }); + sitDown(); } else if (clickedOverlay == standUpButton) { - passedTime = 0.0; - startPosition = MyAvatar.position; - try{ - Script.update.disconnect(sittingDownAnimation); - } catch (e){} - Script.update.connect(standingUpAnimation); - Overlays.editOverlay(standUpButton, { visible: false }); - Overlays.editOverlay(sitDownButton, { visible: true }); + standUp(); } }) @@ -140,7 +152,19 @@ function update(deltaTime){ } } +function keyPressEvent(event) { + if (event.text === ".") { + if (sitting) { + standUp(); + } else { + sitDown(); + } + } +} + + Script.update.connect(update); +Controller.keyPressEvent.connect(keyPressEvent); Script.scriptEnding.connect(function() { diff --git a/interface/resources/shaders/cascaded_shadow_map.frag b/interface/resources/shaders/cascaded_shadow_map.frag new file mode 100644 index 0000000000..337f98a228 --- /dev/null +++ b/interface/resources/shaders/cascaded_shadow_map.frag @@ -0,0 +1,40 @@ +#version 120 + +// +// cascaded_shadow_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the distances to the cascade sections +uniform vec3 shadowDistances; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the color in shadow +varying vec4 shadowColor; + +// the interpolated position +varying vec4 position; + +void main(void) { + // compute the index of the cascade to use and the corresponding texture coordinates + int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0))); + vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position), + dot(gl_EyePlaneR[shadowIndex], position)); + + gl_FragColor = mix(shadowColor, gl_Color, 0.25 * + (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r)); +} diff --git a/interface/resources/shaders/cascaded_shadow_map.vert b/interface/resources/shaders/cascaded_shadow_map.vert new file mode 100644 index 0000000000..68ff95b28a --- /dev/null +++ b/interface/resources/shaders/cascaded_shadow_map.vert @@ -0,0 +1,33 @@ +#version 120 + +// +// cascaded_shadow_map.vert +// vertex shader +// +// Created by Andrzej Kapolka on 5/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the color in shadow +varying vec4 shadowColor; + +// the interpolated position +varying vec4 position; + +void main(void) { + // the shadow color includes only the ambient terms + shadowColor = gl_Color * (gl_LightModel.ambient + gl_LightSource[0].ambient); + + // the normal color includes diffuse + vec4 normal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0)); + gl_FrontColor = shadowColor + gl_Color * (gl_LightSource[0].diffuse * max(0.0, dot(normal, gl_LightSource[0].position))); + + // generate the shadow texture coordinates using the eye position + position = gl_ModelViewMatrix * gl_Vertex; + + // use the fixed function transform + gl_Position = ftransform(); +} diff --git a/interface/resources/shaders/model_cascaded_shadow_map.frag b/interface/resources/shaders/model_cascaded_shadow_map.frag new file mode 100644 index 0000000000..720c43b656 --- /dev/null +++ b/interface/resources/shaders/model_cascaded_shadow_map.frag @@ -0,0 +1,56 @@ +#version 120 + +// +// model_cascaded_shadow_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the distances to the cascade sections +uniform vec3 shadowDistances; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the index of the cascade to use and the corresponding texture coordinates + int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0))); + vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position), + dot(gl_EyePlaneR[shadowIndex], position)); + + // compute the base color based on OpenGL lighting model + vec4 normalizedNormal = normalize(normal); + float diffuse = dot(normalizedNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + + // compute the specular component (sans exponent) + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(position.xyz, 0.0))), + normalizedNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + + vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0); +} diff --git a/interface/resources/shaders/model_cascaded_shadow_normal_map.frag b/interface/resources/shaders/model_cascaded_shadow_normal_map.frag new file mode 100644 index 0000000000..5758333392 --- /dev/null +++ b/interface/resources/shaders/model_cascaded_shadow_normal_map.frag @@ -0,0 +1,69 @@ +#version 120 + +// +// model_cascaded_shadow_normal_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the normal map texture +uniform sampler2D normalMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the distances to the cascade sections +uniform vec3 shadowDistances; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 interpolatedPosition; + +// the interpolated normal +varying vec4 interpolatedNormal; + +// the interpolated tangent +varying vec4 interpolatedTangent; + +void main(void) { + vec3 normalizedNormal = normalize(vec3(interpolatedNormal)); + vec3 normalizedTangent = normalize(vec3(interpolatedTangent)); + vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); + vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) * 2.0 - vec3(1.0, 1.0, 1.0); + + // compute the index of the cascade to use and the corresponding texture coordinates + int shadowIndex = int(dot(step(vec3(interpolatedPosition.z), shadowDistances), vec3(1.0, 1.0, 1.0))); + vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], interpolatedPosition), + dot(gl_EyePlaneT[shadowIndex], interpolatedPosition), + dot(gl_EyePlaneR[shadowIndex], interpolatedPosition)); + + // compute the base color based on OpenGL lighting model + vec4 viewNormal = vec4(normalizedTangent * localNormal.x + + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); + float diffuse = dot(viewNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + + // compute the specular component (sans exponent) + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - + normalize(vec4(vec3(interpolatedPosition), 0.0))), viewNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + + vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0); +} diff --git a/interface/resources/shaders/model_cascaded_shadow_normal_specular_map.frag b/interface/resources/shaders/model_cascaded_shadow_normal_specular_map.frag new file mode 100644 index 0000000000..2b949710f3 --- /dev/null +++ b/interface/resources/shaders/model_cascaded_shadow_normal_specular_map.frag @@ -0,0 +1,72 @@ +#version 120 + +// +// model_cascaded_shadow_normal_specular_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the normal map texture +uniform sampler2D normalMap; + +// the specular map texture +uniform sampler2D specularMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the distances to the cascade sections +uniform vec3 shadowDistances; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position +varying vec4 interpolatedPosition; + +// the interpolated normal +varying vec4 interpolatedNormal; + +// the interpolated tangent +varying vec4 interpolatedTangent; + +void main(void) { + vec3 normalizedNormal = normalize(vec3(interpolatedNormal)); + vec3 normalizedTangent = normalize(vec3(interpolatedTangent)); + vec3 normalizedBitangent = normalize(cross(normalizedNormal, normalizedTangent)); + vec3 localNormal = vec3(texture2D(normalMap, gl_TexCoord[0].st)) * 2.0 - vec3(1.0, 1.0, 1.0); + + // compute the index of the cascade to use and the corresponding texture coordinates + int shadowIndex = int(dot(step(vec3(interpolatedPosition.z), shadowDistances), vec3(1.0, 1.0, 1.0))); + vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], interpolatedPosition), + dot(gl_EyePlaneT[shadowIndex], interpolatedPosition), + dot(gl_EyePlaneR[shadowIndex], interpolatedPosition)); + + // compute the base color based on OpenGL lighting model + vec4 viewNormal = vec4(normalizedTangent * localNormal.x + + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); + float diffuse = dot(viewNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, shadowTexCoord + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + + // compute the specular component (sans exponent) + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - + normalize(vec4(interpolatedPosition.xyz, 0.0))), viewNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * + gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); +} diff --git a/interface/resources/shaders/model_cascaded_shadow_specular_map.frag b/interface/resources/shaders/model_cascaded_shadow_specular_map.frag new file mode 100644 index 0000000000..ba8ba6b85b --- /dev/null +++ b/interface/resources/shaders/model_cascaded_shadow_specular_map.frag @@ -0,0 +1,59 @@ +#version 120 + +// +// model_cascaded_shadow_specular_map.frag +// fragment shader +// +// Created by Andrzej Kapolka on 5/29/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +// the diffuse texture +uniform sampler2D diffuseMap; + +// the specular texture +uniform sampler2D specularMap; + +// the shadow texture +uniform sampler2DShadow shadowMap; + +// the distances to the cascade sections +uniform vec3 shadowDistances; + +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + +// the interpolated position in view space +varying vec4 position; + +// the interpolated normal +varying vec4 normal; + +void main(void) { + // compute the index of the cascade to use and the corresponding texture coordinates + int shadowIndex = int(dot(step(vec3(position.z), shadowDistances), vec3(1.0, 1.0, 1.0))); + vec3 shadowTexCoord = vec3(dot(gl_EyePlaneS[shadowIndex], position), dot(gl_EyePlaneT[shadowIndex], position), + dot(gl_EyePlaneR[shadowIndex], position)); + + // compute the base color based on OpenGL lighting model + vec4 normalizedNormal = normalize(normal); + float diffuse = dot(normalizedNormal, gl_LightSource[0].position); + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); + vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); + + // compute the specular component (sans exponent) + float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position - normalize(vec4(position.xyz, 0.0))), + normalizedNormal)); + + // modulate texture by base color and add specular contribution + gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) * + gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0); +} diff --git a/interface/resources/shaders/shadow_map.frag b/interface/resources/shaders/shadow_map.frag index fb3474d9ef..fa79040ce3 100644 --- a/interface/resources/shaders/shadow_map.frag +++ b/interface/resources/shaders/shadow_map.frag @@ -16,6 +16,7 @@ uniform sampler2DShadow shadowMap; // the inverse of the size of the shadow map const float shadowScale = 1.0 / 2048.0; +// the color in shadow varying vec4 shadowColor; void main(void) { diff --git a/interface/resources/shaders/shadow_map.vert b/interface/resources/shaders/shadow_map.vert index abe5f99b44..5d2affba98 100644 --- a/interface/resources/shaders/shadow_map.vert +++ b/interface/resources/shaders/shadow_map.vert @@ -11,6 +11,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +// the color in shadow varying vec4 shadowColor; void main(void) { @@ -21,10 +22,10 @@ void main(void) { vec4 normal = normalize(gl_ModelViewMatrix * vec4(gl_Normal, 0.0)); gl_FrontColor = shadowColor + gl_Color * (gl_LightSource[0].diffuse * max(0.0, dot(normal, gl_LightSource[0].position))); - // generate the shadow texture coordinate using the eye position + // generate the shadow texture coordinates using the eye position vec4 eyePosition = gl_ModelViewMatrix * gl_Vertex; gl_TexCoord[0] = vec4(dot(gl_EyePlaneS[0], eyePosition), dot(gl_EyePlaneT[0], eyePosition), - dot(gl_EyePlaneR[0], eyePosition), 1.0); + dot(gl_EyePlaneR[0], eyePosition), 1.0); // use the fixed function transform gl_Position = ftransform(); diff --git a/interface/resources/styles/console.qss b/interface/resources/styles/console.qss new file mode 100644 index 0000000000..021d5d84e9 --- /dev/null +++ b/interface/resources/styles/console.qss @@ -0,0 +1,20 @@ +* { + font-family: Inconsolata, Lucida Console, Andale Mono, Monaco; + font-size: 14px; +} + +#promptTextEdit { + color: #425d72; +} + +#promptTextEdit:!enabled { + color: #7f7f7f; +} + +#promptGutterLabel { + color: #a9bbc3; +} + +#promptGutterLabel:!enabled { + color: #7f7f7f; +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index fac0ef154f..0e731fde79 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -105,6 +105,8 @@ const int STARTUP_JITTER_SAMPLES = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / 2 const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml"; const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; +const QString DEFAULT_SCRIPTS_JS_URL = "http://public.highfidelity.io/scripts/defaultScripts.js"; + void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (message.size() > 0) { QString dateString = QDateTime::currentDateTime().toTimeSpec(Qt::LocalTime).toString(Qt::ISODate); @@ -162,10 +164,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _bytesPerSecond(0), _nodeBoundsDisplay(this), _previousScriptLocation(), + _applicationOverlay(), _runningScriptsWidget(new RunningScriptsWidget(_window)), _runningScriptsWidgetWasVisible(false), - _trayIcon(new QSystemTrayIcon(_window)), - _applicationOverlay() + _trayIcon(new QSystemTrayIcon(_window)) { // read the ApplicationInfo.ini file for Name/Version/Domain information QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat); @@ -362,7 +364,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : qDebug() << "This is a first run..."; // clear the scripts, and set out script to our default scripts clearScriptsBeforeRunning(); - loadScript("http://public.highfidelity.io/scripts/defaultScripts.js"); + loadScript(DEFAULT_SCRIPTS_JS_URL); QMutexLocker locker(&_settingsMutex); _settings->setValue("firstRun",QVariant(false)); @@ -552,6 +554,8 @@ void Application::initializeGL() { } void Application::paintGL() { + PerformanceTimer perfTimer("paintGL"); + PerformanceWarning::setSuppressShortTimings(Menu::getInstance()->isOptionChecked(MenuOption::SuppressShortTimings)); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::paintGL()"); @@ -616,7 +620,7 @@ void Application::paintGL() { whichCamera = _viewFrustumOffsetCamera; } - if (Menu::getInstance()->isOptionChecked(MenuOption::Shadows)) { + if (Menu::getInstance()->getShadowsEnabled()) { updateShadowMap(); } @@ -646,7 +650,10 @@ void Application::paintGL() { _rearMirrorTools->render(true); } - _applicationOverlay.renderOverlay(); + { + PerformanceTimer perfTimer("paintGL/renderOverlay"); + _applicationOverlay.renderOverlay(); + } } _frameCount++; @@ -1286,11 +1293,13 @@ void Application::timer() { } void Application::idle() { + PerformanceTimer perfTimer("idle"); + // Normally we check PipelineWarnings, but since idle will often take more than 10ms we only show these idle timing // details if we're in ExtraDebugging mode. However, the ::update() and it's subcomponents will show their timing // details normally. bool showWarnings = getLogger()->extraDebugging(); - PerformanceWarning warn(showWarnings, "Application::idle()"); + PerformanceWarning warn(showWarnings, "idle()"); // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran @@ -1298,15 +1307,18 @@ void Application::idle() { if (timeSinceLastUpdate > IDLE_SIMULATE_MSECS) { _lastTimeUpdated.start(); { + PerformanceTimer perfTimer("idle/update"); PerformanceWarning warn(showWarnings, "Application::idle()... update()"); const float BIGGEST_DELTA_TIME_SECS = 0.25f; update(glm::clamp((float)timeSinceLastUpdate / 1000.f, 0.f, BIGGEST_DELTA_TIME_SECS)); } { + PerformanceTimer perfTimer("idle/updateGL"); PerformanceWarning warn(showWarnings, "Application::idle()... updateGL()"); _glWidget->updateGL(); } { + PerformanceTimer perfTimer("idle/rest"); PerformanceWarning warn(showWarnings, "Application::idle()... rest of it"); _idleLoopStdev.addValue(timeSinceLastUpdate); @@ -1318,14 +1330,16 @@ void Application::idle() { } if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) { + PerformanceTimer perfTimer("idle/rest/_buckyBalls"); _buckyBalls.simulate(timeSinceLastUpdate / 1000.f, Application::getInstance()->getAvatar()->getHandData()); } // After finishing all of the above work, restart the idle timer, allowing 2ms to process events. idleTimer->start(2); - } - if (_numChangedSettings > 0) { - saveSettings(); + + if (_numChangedSettings > 0) { + saveSettings(); + } } } } @@ -1727,6 +1741,7 @@ bool Application::isLookingAtMyAvatar(Avatar* avatar) { } void Application::updateLOD() { + PerformanceTimer perfTimer("idle/update/updateLOD"); // adjust it unless we were asked to disable this feature, or if we're currently in throttleRendering mode if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableAutoAdjustLOD) && !isThrottleRendering()) { Menu::getInstance()->autoAdjustLOD(_fps); @@ -1736,6 +1751,7 @@ void Application::updateLOD() { } void Application::updateMouseRay() { + PerformanceTimer perfTimer("idle/update/updateMouseRay"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMouseRay()"); @@ -1768,6 +1784,7 @@ void Application::updateMouseRay() { } void Application::updateFaceshift() { + PerformanceTimer perfTimer("idle/update/updateFaceshift"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateFaceshift()"); @@ -1782,6 +1799,7 @@ void Application::updateFaceshift() { } void Application::updateVisage() { + PerformanceTimer perfTimer("idle/update/updateVisage"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateVisage()"); @@ -1791,6 +1809,7 @@ void Application::updateVisage() { } void Application::updateMyAvatarLookAtPosition() { + PerformanceTimer perfTimer("idle/update/updateMyAvatarLookAtPosition"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()"); @@ -1856,6 +1875,7 @@ void Application::updateMyAvatarLookAtPosition() { } void Application::updateThreads(float deltaTime) { + PerformanceTimer perfTimer("idle/update/updateThreads"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateThreads()"); @@ -1870,6 +1890,7 @@ void Application::updateThreads(float deltaTime) { } void Application::updateMetavoxels(float deltaTime) { + PerformanceTimer perfTimer("idle/update/updateMetavoxels"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMetavoxels()"); @@ -1899,6 +1920,7 @@ void Application::cameraMenuChanged() { } void Application::updateCamera(float deltaTime) { + PerformanceTimer perfTimer("idle/update/updateCamera"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateCamera()"); @@ -1916,6 +1938,7 @@ void Application::updateCamera(float deltaTime) { } void Application::updateDialogs(float deltaTime) { + PerformanceTimer perfTimer("idle/update/updateDialogs"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateDialogs()"); @@ -1932,6 +1955,7 @@ void Application::updateDialogs(float deltaTime) { } void Application::updateCursor(float deltaTime) { + PerformanceTimer perfTimer("idle/update/updateCursor"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateCursor()"); @@ -1956,51 +1980,87 @@ void Application::updateCursor(float deltaTime) { } void Application::update(float deltaTime) { + //PerformanceTimer perfTimer("idle/update"); // NOTE: we track this above in Application::idle() + bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::update()"); updateLOD(); - - // check what's under the mouse and update the mouse voxel - updateMouseRay(); - + updateMouseRay(); // check what's under the mouse and update the mouse voxel updateFaceshift(); updateVisage(); - _myAvatar->updateLookAtTargetAvatar(); + + { + PerformanceTimer perfTimer("idle/update/updateLookAtTargetAvatar"); + _myAvatar->updateLookAtTargetAvatar(); + } updateMyAvatarLookAtPosition(); - _sixenseManager.update(deltaTime); - _joystickManager.update(); - _prioVR.update(deltaTime); - updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes + { + PerformanceTimer perfTimer("idle/update/sixense,joystick,prioVR"); + _sixenseManager.update(deltaTime); + _joystickManager.update(); + _prioVR.update(deltaTime); + } + + { + PerformanceTimer perfTimer("idle/update/updateMyAvatar"); + updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes + } + updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process... - _avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them... + + { + PerformanceTimer perfTimer("idle/update/_avatarManager"); + _avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them... + } updateMetavoxels(deltaTime); // update metavoxels updateCamera(deltaTime); // handle various camera tweaks like off axis projection updateDialogs(deltaTime); // update various stats dialogs if present updateCursor(deltaTime); // Handle cursor updates - _particles.update(); // update the particles... - _particleCollisionSystem.update(); // collide the particles... + { + PerformanceTimer perfTimer("idle/update/_particles"); + _particles.update(); // update the particles... + } + { + PerformanceTimer perfTimer("idle/update/_particleCollisionSystem"); + _particleCollisionSystem.update(); // collide the particles... + } - _models.update(); // update the models... + { + PerformanceTimer perfTimer("idle/update/_models"); + _models.update(); // update the models... + } - _overlays.update(deltaTime); + { + PerformanceTimer perfTimer("idle/update/_overlays"); + _overlays.update(deltaTime); + } - // let external parties know we're updating - emit simulating(deltaTime); + { + PerformanceTimer perfTimer("idle/update/emit simulating"); + // let external parties know we're updating + emit simulating(deltaTime); + } } void Application::updateMyAvatar(float deltaTime) { + PerformanceTimer perfTimer("updateMyAvatar"); bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateMyAvatar()"); - _myAvatar->update(deltaTime); + { + PerformanceTimer perfTimer("updateMyAvatar/_myAvatar->update()"); + _myAvatar->update(deltaTime); + } - // send head/hand data to the avatar mixer and voxel server - QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData); - packet.append(_myAvatar->toByteArray()); - - controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer); + { + // send head/hand data to the avatar mixer and voxel server + PerformanceTimer perfTimer("updateMyAvatar/sendToAvatarMixer"); + QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData); + packet.append(_myAvatar->toByteArray()); + controlledBroadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer); + } // Update _viewFrustum with latest camera and view frustum data... // NOTE: we get this from the view frustum, to make it simpler, since the @@ -2008,22 +2068,28 @@ void Application::updateMyAvatar(float deltaTime) { // We could optimize this to not actually load the viewFrustum, since we don't // actually need to calculate the view frustum planes to send these details // to the server. - loadViewFrustum(_myCamera, _viewFrustum); + { + PerformanceTimer perfTimer("updateMyAvatar/loadViewFrustum"); + loadViewFrustum(_myCamera, _viewFrustum); + } // Update my voxel servers with my current voxel query... - quint64 now = usecTimestampNow(); - quint64 sinceLastQuery = now - _lastQueriedTime; - const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; - bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; - bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum); + { + PerformanceTimer perfTimer("updateMyAvatar/queryOctree"); + quint64 now = usecTimestampNow(); + quint64 sinceLastQuery = now - _lastQueriedTime; + const quint64 TOO_LONG_SINCE_LAST_QUERY = 3 * USECS_PER_SECOND; + bool queryIsDue = sinceLastQuery > TOO_LONG_SINCE_LAST_QUERY; + bool viewIsDifferentEnough = !_lastQueriedViewFrustum.isVerySimilar(_viewFrustum); - // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it - if (queryIsDue || viewIsDifferentEnough) { - _lastQueriedTime = now; - queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions); - queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions); - queryOctree(NodeType::ModelServer, PacketTypeModelQuery, _modelServerJurisdictions); - _lastQueriedViewFrustum = _viewFrustum; + // if it's been a while since our last query or the view has significantly changed then send a query, otherwise suppress it + if (queryIsDue || viewIsDifferentEnough) { + _lastQueriedTime = now; + queryOctree(NodeType::VoxelServer, PacketTypeVoxelQuery, _voxelServerJurisdictions); + queryOctree(NodeType::ParticleServer, PacketTypeParticleQuery, _particleServerJurisdictions); + queryOctree(NodeType::ModelServer, PacketTypeModelQuery, _modelServerJurisdictions); + _lastQueriedViewFrustum = _viewFrustum; + } } } @@ -2251,99 +2317,122 @@ glm::vec3 Application::getSunDirection() { } void Application::updateShadowMap() { + PerformanceTimer perfTimer("paintGL/updateShadowMap"); QOpenGLFramebufferObject* fbo = _textureCache.getShadowFramebufferObject(); fbo->bind(); glEnable(GL_DEPTH_TEST); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glViewport(0, 0, fbo->width(), fbo->height()); - glm::vec3 lightDirection = -getSunDirection(); glm::quat rotation = rotationBetween(IDENTITY_FRONT, lightDirection); glm::quat inverseRotation = glm::inverse(rotation); - float nearScale = 0.0f; - const float MAX_SHADOW_DISTANCE = 2.0f; - float farScale = (MAX_SHADOW_DISTANCE - _viewFrustum.getNearClip()) / - (_viewFrustum.getFarClip() - _viewFrustum.getNearClip()); + + const float SHADOW_MATRIX_DISTANCES[] = { 0.0f, 2.0f, 6.0f, 14.0f, 30.0f }; + const glm::vec2 MAP_COORDS[] = { glm::vec2(0.0f, 0.0f), glm::vec2(0.5f, 0.0f), + glm::vec2(0.0f, 0.5f), glm::vec2(0.5f, 0.5f) }; + + float frustumScale = 1.0f / (_viewFrustum.getFarClip() - _viewFrustum.getNearClip()); loadViewFrustum(_myCamera, _viewFrustum); - glm::vec3 points[] = { - glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), nearScale), - glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), nearScale), - glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), nearScale), - glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), nearScale), - glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), farScale), - glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), farScale), - glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), farScale), - glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), farScale) }; - glm::vec3 center; - for (size_t i = 0; i < sizeof(points) / sizeof(points[0]); i++) { - center += points[i]; - } - center /= (float)(sizeof(points) / sizeof(points[0])); - float radius = 0.0f; - for (size_t i = 0; i < sizeof(points) / sizeof(points[0]); i++) { - radius = qMax(radius, glm::distance(points[i], center)); - } - center = inverseRotation * center; - // to reduce texture "shimmer," move in texel increments - float texelSize = (2.0f * radius) / fbo->width(); - center = glm::vec3(roundf(center.x / texelSize) * texelSize, roundf(center.y / texelSize) * texelSize, - roundf(center.z / texelSize) * texelSize); + int matrixCount = 1; + int targetSize = fbo->width(); + float targetScale = 1.0f; + if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + matrixCount = CASCADED_SHADOW_MATRIX_COUNT; + targetSize = fbo->width() / 2; + targetScale = 0.5f; + } + for (int i = 0; i < matrixCount; i++) { + const glm::vec2& coord = MAP_COORDS[i]; + glViewport(coord.s * fbo->width(), coord.t * fbo->height(), targetSize, targetSize); + + float nearScale = SHADOW_MATRIX_DISTANCES[i] * frustumScale; + float farScale = SHADOW_MATRIX_DISTANCES[i + 1] * frustumScale; + glm::vec3 points[] = { + glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), nearScale), + glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), nearScale), + glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), nearScale), + glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), nearScale), + glm::mix(_viewFrustum.getNearTopLeft(), _viewFrustum.getFarTopLeft(), farScale), + glm::mix(_viewFrustum.getNearTopRight(), _viewFrustum.getFarTopRight(), farScale), + glm::mix(_viewFrustum.getNearBottomLeft(), _viewFrustum.getFarBottomLeft(), farScale), + glm::mix(_viewFrustum.getNearBottomRight(), _viewFrustum.getFarBottomRight(), farScale) }; + glm::vec3 center; + for (size_t j = 0; j < sizeof(points) / sizeof(points[0]); j++) { + center += points[j]; + } + center /= (float)(sizeof(points) / sizeof(points[0])); + float radius = 0.0f; + for (size_t j = 0; j < sizeof(points) / sizeof(points[0]); j++) { + radius = qMax(radius, glm::distance(points[j], center)); + } + if (i < 3) { + const float RADIUS_SCALE = 0.5f; + _shadowDistances[i] = -glm::distance(_viewFrustum.getPosition(), center) - radius * RADIUS_SCALE; + } + center = inverseRotation * center; + + // to reduce texture "shimmer," move in texel increments + float texelSize = (2.0f * radius) / targetSize; + center = glm::vec3(roundf(center.x / texelSize) * texelSize, roundf(center.y / texelSize) * texelSize, + roundf(center.z / texelSize) * texelSize); + + glm::vec3 minima(center.x - radius, center.y - radius, center.z - radius); + glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius); + + // stretch out our extents in z so that we get all of the avatars + minima.z -= _viewFrustum.getFarClip() * 0.5f; + maxima.z += _viewFrustum.getFarClip() * 0.5f; + + // save the combined matrix for rendering + _shadowMatrices[i] = glm::transpose(glm::translate(glm::vec3(coord, 0.0f)) * + glm::scale(glm::vec3(targetScale, targetScale, 1.0f)) * + glm::translate(glm::vec3(0.5f, 0.5f, 0.5f)) * glm::scale(glm::vec3(0.5f, 0.5f, 0.5f)) * + glm::ortho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z) * glm::mat4_cast(inverseRotation)); + + // update the shadow view frustum + _shadowViewFrustum.setPosition(rotation * ((minima + maxima) * 0.5f)); + _shadowViewFrustum.setOrientation(rotation); + _shadowViewFrustum.setOrthographic(true); + _shadowViewFrustum.setWidth(maxima.x - minima.x); + _shadowViewFrustum.setHeight(maxima.y - minima.y); + _shadowViewFrustum.setNearClip(minima.z); + _shadowViewFrustum.setFarClip(maxima.z); + _shadowViewFrustum.setEyeOffsetPosition(glm::vec3()); + _shadowViewFrustum.setEyeOffsetOrientation(glm::quat()); + _shadowViewFrustum.calculate(); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + glm::vec3 axis = glm::axis(inverseRotation); + glRotatef(glm::degrees(glm::angle(inverseRotation)), axis.x, axis.y, axis.z); + + // store view matrix without translation, which we'll use for precision-sensitive objects + updateUntranslatedViewMatrix(); + + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.1f, 4.0f); // magic numbers courtesy http://www.eecs.berkeley.edu/~ravir/6160/papers/shadowmaps.ppt + + _avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE); + _particles.render(OctreeRenderer::SHADOW_RENDER_MODE); + _models.render(OctreeRenderer::SHADOW_RENDER_MODE); + + glDisable(GL_POLYGON_OFFSET_FILL); + + glPopMatrix(); + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + + glMatrixMode(GL_MODELVIEW); + } - glm::vec3 minima(center.x - radius, center.y - radius, center.z - radius); - glm::vec3 maxima(center.x + radius, center.y + radius, center.z + radius); - - // stretch out our extents in z so that we get all of the avatars - minima.z -= _viewFrustum.getFarClip() * 0.5f; - maxima.z += _viewFrustum.getFarClip() * 0.5f; - - // save the combined matrix for rendering - _shadowMatrix = glm::transpose(glm::translate(glm::vec3(0.5f, 0.5f, 0.5f)) * glm::scale(glm::vec3(0.5f, 0.5f, 0.5f)) * - glm::ortho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z) * glm::mat4_cast(inverseRotation)); - - // update the shadow view frustum - _shadowViewFrustum.setPosition(rotation * ((minima + maxima) * 0.5f)); - _shadowViewFrustum.setOrientation(rotation); - _shadowViewFrustum.setOrthographic(true); - _shadowViewFrustum.setWidth(maxima.x - minima.x); - _shadowViewFrustum.setHeight(maxima.y - minima.y); - _shadowViewFrustum.setNearClip(minima.z); - _shadowViewFrustum.setFarClip(maxima.z); - _shadowViewFrustum.setEyeOffsetPosition(glm::vec3()); - _shadowViewFrustum.setEyeOffsetOrientation(glm::quat()); - _shadowViewFrustum.calculate(); - - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - glLoadIdentity(); - glOrtho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z); - - glMatrixMode(GL_MODELVIEW); - glPushMatrix(); - glLoadIdentity(); - glm::vec3 axis = glm::axis(inverseRotation); - glRotatef(glm::degrees(glm::angle(inverseRotation)), axis.x, axis.y, axis.z); - - // store view matrix without translation, which we'll use for precision-sensitive objects - updateUntranslatedViewMatrix(); - - glEnable(GL_POLYGON_OFFSET_FILL); - glPolygonOffset(1.1f, 4.0f); // magic numbers courtesy http://www.eecs.berkeley.edu/~ravir/6160/papers/shadowmaps.ppt - - _avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE); - _particles.render(OctreeRenderer::SHADOW_RENDER_MODE); - _models.render(OctreeRenderer::SHADOW_RENDER_MODE); - - glDisable(GL_POLYGON_OFFSET_FILL); - - glPopMatrix(); - - glMatrixMode(GL_PROJECTION); - glPopMatrix(); - - glMatrixMode(GL_MODELVIEW); - fbo->release(); glViewport(0, 0, _glWidget->width(), _glWidget->height()); @@ -2390,6 +2479,7 @@ QImage Application::renderAvatarBillboard() { } void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { + PerformanceTimer perfTimer("paintGL/displaySide"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide()"); // transform by eye offset @@ -2422,9 +2512,27 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { glTranslatef(_viewMatrixTranslation.x, _viewMatrixTranslation.y, _viewMatrixTranslation.z); // Setup 3D lights (after the camera transform, so that they are positioned in world space) - setupWorldLight(); + { + PerformanceTimer perfTimer("paintGL/displaySide/setupWorldLight"); + setupWorldLight(); + } + + // setup shadow matrices (again, after the camera transform) + int shadowMatrixCount = 0; + if (Menu::getInstance()->isOptionChecked(MenuOption::SimpleShadows)) { + shadowMatrixCount = 1; + } else if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + shadowMatrixCount = CASCADED_SHADOW_MATRIX_COUNT; + } + for (int i = shadowMatrixCount - 1; i >= 0; i--) { + glActiveTexture(GL_TEXTURE0 + i); + glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&_shadowMatrices[i][0]); + glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&_shadowMatrices[i][1]); + glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&_shadowMatrices[i][2]); + } if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Stars)) { + PerformanceTimer perfTimer("paintGL/displaySide/stars"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... stars..."); if (!_stars.isStarsLoaded()) { @@ -2453,6 +2561,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // draw the sky dome if (!selfAvatarOnly && Menu::getInstance()->isOptionChecked(MenuOption::Atmosphere)) { + PerformanceTimer perfTimer("paintGL/displaySide/atmosphere"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... atmosphere..."); _environment.renderAtmospheres(whichCamera); @@ -2472,10 +2581,14 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR); // draw the audio reflector overlay - _audioReflector.render(); - + { + PerformanceTimer perfTimer("paintGL/displaySide/audioReflector"); + _audioReflector.render(); + } + // Draw voxels if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { + PerformanceTimer perfTimer("paintGL/displaySide/voxels"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxels..."); _voxels.render(); @@ -2483,12 +2596,14 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // also, metavoxels if (Menu::getInstance()->isOptionChecked(MenuOption::Metavoxels)) { + PerformanceTimer perfTimer("paintGL/displaySide/metavoxels"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... metavoxels..."); _metavoxels.render(); } if (Menu::getInstance()->isOptionChecked(MenuOption::BuckyBalls)) { + PerformanceTimer perfTimer("paintGL/displaySide/buckyBalls"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... bucky balls..."); _buckyBalls.render(); @@ -2496,6 +2611,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // render particles... if (Menu::getInstance()->isOptionChecked(MenuOption::Particles)) { + PerformanceTimer perfTimer("paintGL/displaySide/particles"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... particles..."); _particles.render(); @@ -2503,6 +2619,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // render models... if (Menu::getInstance()->isOptionChecked(MenuOption::Models)) { + PerformanceTimer perfTimer("paintGL/displaySide/models"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... models..."); _models.render(); @@ -2510,6 +2627,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // render the ambient occlusion effect if enabled if (Menu::getInstance()->isOptionChecked(MenuOption::AmbientOcclusion)) { + PerformanceTimer perfTimer("paintGL/displaySide/AmbientOcclusion"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... AmbientOcclusion..."); _ambientOcclusionEffect.render(); @@ -2523,16 +2641,21 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR); - _avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, selfAvatarOnly); + { + PerformanceTimer perfTimer("paintGL/displaySide/renderAvatars"); + _avatarManager.renderAvatars(mirrorMode ? Avatar::MIRROR_RENDER_MODE : Avatar::NORMAL_RENDER_MODE, selfAvatarOnly); + } if (!selfAvatarOnly) { // Render the world box if (whichCamera.getMode() != CAMERA_MODE_MIRROR && Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + PerformanceTimer perfTimer("paintGL/displaySide/renderWorldBox"); renderWorldBox(); } - // brad's frustum for debugging + // view frustum for debugging if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayFrustum) && whichCamera.getMode() != CAMERA_MODE_MIRROR) { + PerformanceTimer perfTimer("paintGL/displaySide/ViewFrustum"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... renderViewFrustum..."); renderViewFrustum(_viewFrustum); @@ -2540,6 +2663,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { // render voxel fades if they exist if (_voxelFades.size() > 0) { + PerformanceTimer perfTimer("paintGL/displaySide/voxel fades"); PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displaySide() ... voxel fades..."); _voxelFadesLock.lockForWrite(); @@ -2555,10 +2679,16 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } // give external parties a change to hook in - emit renderingInWorldInterface(); + { + PerformanceTimer perfTimer("paintGL/displaySide/inWorldInterface"); + emit renderingInWorldInterface(); + } // render JS/scriptable overlays - _overlays.render3D(); + { + PerformanceTimer perfTimer("paintGL/displaySide/3dOverlays"); + _overlays.render3D(); + } } } @@ -3254,16 +3384,21 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript return _scriptEnginesHash[scriptName]; } - // start the script on a new thread... - QUrl scriptUrl(scriptName); - ScriptEngine* scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); - _scriptEnginesHash.insert(scriptUrl.toString(), scriptEngine); + ScriptEngine* scriptEngine; + if (scriptName.isNull()) { + scriptEngine = new ScriptEngine(NO_SCRIPT, "", &_controllerScriptingInterface); + } else { + // start the script on a new thread... + QUrl scriptUrl(scriptName); + scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface); + _scriptEnginesHash.insert(scriptName, scriptEngine); - if (!scriptEngine->hasScript()) { - qDebug() << "Application::loadScript(), script failed to load..."; - return NULL; + if (!scriptEngine->hasScript()) { + qDebug() << "Application::loadScript(), script failed to load..."; + return NULL; + } + _runningScriptsWidget->setRunningScripts(getRunningScripts()); } - _runningScriptsWidget->setRunningScripts(getRunningScripts()); // setup the packet senders and jurisdiction listeners of the script engine's scripting interfaces so // we can use the same ones from the application. @@ -3362,6 +3497,12 @@ void Application::reloadAllScripts() { stopAllScripts(true); } +void Application::loadDefaultScripts() { + if (!_scriptEnginesHash.contains(DEFAULT_SCRIPTS_JS_URL)) { + loadScript(DEFAULT_SCRIPTS_JS_URL); + } +} + void Application::manageRunningScriptsWidgetVisibility(bool shown) { if (_runningScriptsWidgetWasVisible && shown) { _runningScriptsWidget->show(); diff --git a/interface/src/Application.h b/interface/src/Application.h index 0065944611..f3d9c0fd27 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -261,11 +261,11 @@ public: /// result from matrix multiplication at high translation magnitudes. void loadTranslatedViewMatrix(const glm::vec3& translation); - const glm::mat4& getShadowMatrix() const { return _shadowMatrix; } - void getModelViewMatrix(glm::dmat4* modelViewMatrix); void getProjectionMatrix(glm::dmat4* projectionMatrix); + const glm::vec3& getShadowDistances() const { return _shadowDistances; } + /// Computes the off-axis frustum parameters for the view frustum, taking mirroring into account. void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal, float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const; @@ -323,11 +323,12 @@ public slots: void loadScriptURLDialog(); void toggleLogDialog(); void initAvatarAndViewFrustum(); - ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false); + ScriptEngine* loadScript(const QString& fileNameString = QString(), bool loadScriptFromEditor = false); void scriptFinished(const QString& scriptName); void stopAllScripts(bool restart = false); void stopScript(const QString& scriptName); void reloadAllScripts(); + void loadDefaultScripts(); void toggleRunningScriptsWidget(); void uploadHead(); @@ -491,7 +492,9 @@ private: float _rotateMirror; float _raiseMirror; - glm::mat4 _shadowMatrix; + static const int CASCADED_SHADOW_MATRIX_COUNT = 4; + glm::mat4 _shadowMatrices[CASCADED_SHADOW_MATRIX_COUNT]; + glm::vec3 _shadowDistances; Environment _environment; diff --git a/interface/src/Environment.cpp b/interface/src/Environment.cpp index 1ba2f0501c..67337f963c 100644 --- a/interface/src/Environment.cpp +++ b/interface/src/Environment.cpp @@ -160,6 +160,11 @@ bool Environment::findCapsulePenetration(const glm::vec3& start, const glm::vec3 int Environment::parseData(const HifiSockAddr& senderAddress, const QByteArray& packet) { // push past the packet header int bytesRead = numBytesForPacketHeader(packet); + + // push past flags, sequence, timestamp + bytesRead += sizeof(OCTREE_PACKET_FLAGS); + bytesRead += sizeof(OCTREE_PACKET_SEQUENCE); + bytesRead += sizeof(OCTREE_PACKET_SENT_TIME); // get the lock for the duration of the call QMutexLocker locker(&_mutex); diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 45b4089a11..deb052e595 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -73,6 +73,11 @@ const int ONE_SECOND_OF_FRAMES = 60; const int FIVE_SECONDS_OF_FRAMES = 5 * ONE_SECOND_OF_FRAMES; const float MUTE_RADIUS = 50; +const QString CONSOLE_TITLE = "Scripting Console"; +const float CONSOLE_WINDOW_OPACITY = 0.95f; +const int CONSOLE_WIDTH = 800; +const int CONSOLE_HEIGHT = 200; + Menu::Menu() : _actionHash(), _audioJitterBufferSamples(0), @@ -81,6 +86,7 @@ Menu::Menu() : _faceshiftEyeDeflection(DEFAULT_FACESHIFT_EYE_DEFLECTION), _frustumDrawMode(FRUSTUM_DRAW_MODE_ALL), _viewFrustumOffset(DEFAULT_FRUSTUM_OFFSET), + _jsConsole(NULL), _octreeStatsDialog(NULL), _lodToolsDialog(NULL), _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), @@ -227,6 +233,12 @@ Menu::Menu() : _chatWindow = new ChatWindow(Application::getInstance()->getWindow()); #endif + addActionToQMenuAndActionHash(toolsMenu, + MenuOption::Console, + Qt::CTRL | Qt::ALT | Qt::Key_J, + this, + SLOT(toggleConsole())); + QMenu* viewMenu = addMenu("View"); addCheckableActionToQMenuAndActionHash(viewMenu, @@ -303,7 +315,12 @@ Menu::Menu() : appInstance->getGlowEffect(), SLOT(cycleRenderMode())); - addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Shadows, 0, false); + QMenu* shadowMenu = renderOptionsMenu->addMenu("Shadows"); + QActionGroup* shadowGroup = new QActionGroup(shadowMenu); + shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, "None", 0, true)); + shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::SimpleShadows, 0, false)); + shadowGroup->addAction(addCheckableActionToQMenuAndActionHash(shadowMenu, MenuOption::CascadedShadows, 0, false)); + addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, false); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); @@ -374,8 +391,18 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false); addDisabledActionAndSeparator(developerMenu, "Testing"); - + QMenu* timingMenu = developerMenu->addMenu("Timing and Statistics Tools"); + QMenu* perfTimerMenu = timingMenu->addMenu("Performance Timer"); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::DisplayTimingDetails, 0, true); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandDisplaySideTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandAvatarSimulateTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandAvatarUpdateTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandMiscAvatarTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandIdleTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandPaintGLTiming, 0, false); + addCheckableActionToQMenuAndActionHash(perfTimerMenu, MenuOption::ExpandUpdateTiming, 0, false); + addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::TestPing, 0, true); addCheckableActionToQMenuAndActionHash(timingMenu, MenuOption::FrameTimer); addActionToQMenuAndActionHash(timingMenu, MenuOption::RunTimingTests, 0, this, SLOT(runTests())); @@ -666,6 +693,10 @@ void Menu::scanMenu(QMenu* menu, settingsAction modifySetting, QSettings* set) { set->endGroup(); } +bool Menu::getShadowsEnabled() const { + return isOptionChecked(MenuOption::SimpleShadows) || isOptionChecked(MenuOption::CascadedShadows); +} + void Menu::handleViewFrustumOffsetKeyModifier(int key) { const float VIEW_FRUSTUM_OFFSET_DELTA = 0.5f; const float VIEW_FRUSTUM_OFFSET_UP_DELTA = 0.05f; @@ -827,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) { @@ -836,8 +868,8 @@ void Menu::setIsOptionChecked(const QString& menuOption, bool isChecked) { } } -bool Menu::isOptionChecked(const QString& menuOption) { - QAction* menu = _actionHash.value(menuOption); +bool Menu::isOptionChecked(const QString& menuOption) const { + const QAction* menu = _actionHash.value(menuOption); if (menu) { return menu->isChecked(); } @@ -1258,6 +1290,25 @@ void Menu::toggleChat() { #endif } +void Menu::toggleConsole() { + QMainWindow* mainWindow = Application::getInstance()->getWindow(); + if (!_jsConsole) { + QDialog* dialog = new QDialog(mainWindow, Qt::WindowStaysOnTopHint); + QVBoxLayout* layout = new QVBoxLayout(dialog); + dialog->setLayout(new QVBoxLayout(dialog)); + + dialog->resize(QSize(CONSOLE_WIDTH, CONSOLE_HEIGHT)); + layout->setMargin(0); + layout->setSpacing(0); + layout->addWidget(new JSConsole(dialog)); + dialog->setWindowOpacity(CONSOLE_WINDOW_OPACITY); + dialog->setWindowTitle(CONSOLE_TITLE); + + _jsConsole = dialog; + } + _jsConsole->setVisible(!_jsConsole->isVisible()); +} + void Menu::audioMuteToggled() { QAction *muteAction = _actionHash.value(MenuOption::MuteAudio); muteAction->setChecked(Application::getInstance()->getAudio()->getMuted()); @@ -1600,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) { @@ -1675,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 012dc1662c..020cd651d2 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -26,6 +26,7 @@ #include "location/LocationManager.h" #include "ui/PreferencesDialog.h" #include "ui/ChatWindow.h" +#include "ui/JSConsole.h" #include "ui/ScriptEditorWindow.h" const float ADJUST_LOD_DOWN_FPS = 40.0; @@ -102,6 +103,8 @@ public: int getMaxVoxels() const { return _maxVoxels; } QAction* getUseVoxelShader() const { return _useVoxelShader; } + bool getShadowsEnabled() const; + void handleViewFrustumOffsetKeyModifier(int key); // User Tweakable LOD Items @@ -166,11 +169,13 @@ 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 isOptionChecked(const QString& menuOption); + bool menuItemExists(const QString& menuName, const QString& menuitem); + bool isOptionChecked(const QString& menuOption) const; void setIsOptionChecked(const QString& menuOption, bool isChecked); private slots: @@ -189,6 +194,7 @@ private slots: void showMetavoxelEditor(); void showScriptEditor(); void showChat(); + void toggleConsole(); void toggleChat(); void audioMuteToggled(); void namedLocationCreated(LocationManager::NamedLocationCreateResponse response); @@ -243,6 +249,7 @@ private: QPointer _MetavoxelEditor; QPointer _ScriptEditor; QPointer _chatWindow; + QDialog* _jsConsole; OctreeStatsDialog* _octreeStatsDialog; LodToolsDialog* _lodToolsDialog; int _maxVoxels; @@ -301,6 +308,7 @@ namespace MenuOption { const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; const QString BuckyBalls = "Bucky Balls"; + const QString CascadedShadows = "Cascaded"; const QString Chat = "Chat..."; const QString ChatCircling = "Chat Circling"; const QString CollideWithAvatars = "Collide With Avatars"; @@ -308,6 +316,7 @@ namespace MenuOption { const QString CollideWithParticles = "Collide With Particles"; const QString CollideWithVoxels = "Collide With Voxels"; const QString Collisions = "Collisions"; + const QString Console = "Console..."; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DecreaseVoxelSize = "Decrease Voxel Size"; const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; @@ -317,10 +326,18 @@ namespace MenuOption { const QString DisplayModelBounds = "Display Model Bounds"; const QString DisplayModelElementProxy = "Display Model Element Bounds"; const QString DisplayModelElementChildProxies = "Display Model Element Children"; + const QString DisplayTimingDetails = "Display Timing Details"; const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes"; const QString EchoLocalAudio = "Echo Local Audio"; const QString EchoServerAudio = "Echo Server Audio"; const QString Enable3DTVMode = "Enable 3DTV Mode"; + const QString ExpandMiscAvatarTiming = "Expand Misc MyAvatar Timing"; + const QString ExpandAvatarUpdateTiming = "Expand MyAvatar update Timing"; + const QString ExpandAvatarSimulateTiming = "Expand MyAvatar simulate Timing"; + const QString ExpandDisplaySideTiming = "Expand Display Side Timing"; + const QString ExpandIdleTiming = "Expand Idle Timing"; + const QString ExpandPaintGLTiming = "Expand PaintGL Timing"; + const QString ExpandUpdateTiming = "Expand Update Timing"; const QString Faceplus = "Faceplus"; const QString Faceshift = "Faceshift"; const QString FilterSixense = "Smooth Sixense Movement"; @@ -376,7 +393,7 @@ namespace MenuOption { const QString ScriptEditor = "Script Editor..."; const QString SettingsExport = "Export Settings"; const QString SettingsImport = "Import Settings"; - const QString Shadows = "Shadows"; + const QString SimpleShadows = "Simple"; const QString ShowBordersVoxelNodes = "Show Voxel Nodes"; const QString ShowBordersModelNodes = "Show Model Nodes"; const QString ShowBordersParticleNodes = "Show Particle Nodes"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 306dc0194e..870d5b1a53 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -378,10 +378,10 @@ void Avatar::simulateAttachments(float deltaTime) { model->setLODDistance(getLODDistance()); } if (_skeletonModel.getJointPosition(jointIndex, jointPosition) && - _skeletonModel.getJointRotation(jointIndex, jointRotation)) { + _skeletonModel.getJointCombinedRotation(jointIndex, jointRotation)) { model->setTranslation(jointPosition + jointRotation * attachment.translation * _scale); model->setRotation(jointRotation * attachment.rotation); - model->setScale(_skeletonModel.getScale() * attachment.scale); + model->setScaleToFit(true, _scale * attachment.scale); model->simulate(deltaTime); } } @@ -705,6 +705,54 @@ QStringList Avatar::getJointNames() const { return _skeletonModel.isActive() ? _skeletonModel.getGeometry()->getFBXGeometry().getJointNames() : QStringList(); } +glm::vec3 Avatar::getJointPosition(int index) const { + if (QThread::currentThread() != thread()) { + glm::vec3 position; + QMetaObject::invokeMethod(const_cast(this), "getJointPosition", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(glm::vec3, position), Q_ARG(const int, index)); + return position; + } + glm::vec3 position; + _skeletonModel.getJointPosition(index, position); + return position; +} + +glm::vec3 Avatar::getJointPosition(const QString& name) const { + if (QThread::currentThread() != thread()) { + glm::vec3 position; + QMetaObject::invokeMethod(const_cast(this), "getJointPosition", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(glm::vec3, position), Q_ARG(const QString&, name)); + return position; + } + glm::vec3 position; + _skeletonModel.getJointPosition(getJointIndex(name), position); + return position; +} + +glm::quat Avatar::getJointCombinedRotation(int index) const { + if (QThread::currentThread() != thread()) { + glm::quat rotation; + QMetaObject::invokeMethod(const_cast(this), "getJointCombinedRotation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(glm::quat, rotation), Q_ARG(const int, index)); + return rotation; + } + glm::quat rotation; + _skeletonModel.getJointCombinedRotation(index, rotation); + return rotation; +} + +glm::quat Avatar::getJointCombinedRotation(const QString& name) const { + if (QThread::currentThread() != thread()) { + glm::quat rotation; + QMetaObject::invokeMethod(const_cast(this), "getJointCombinedRotation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(glm::quat, rotation), Q_ARG(const QString&, name)); + return rotation; + } + glm::quat rotation; + _skeletonModel.getJointCombinedRotation(getJointIndex(name), rotation); + return rotation; +} + void Avatar::setFaceModelURL(const QUrl& faceModelURL) { AvatarData::setFaceModelURL(faceModelURL); const QUrl DEFAULT_FACE_MODEL_URL = QUrl::fromLocalFile(Application::resourcesPath() + "meshes/defaultAvatar_head.fst"); @@ -734,6 +782,8 @@ void Avatar::setAttachmentData(const QVector& attachmentData) { // update the urls for (int i = 0; i < attachmentData.size(); i++) { + _attachmentModels[i]->setSnapModelToCenter(true); + _attachmentModels[i]->setScaleToFit(true, _scale * _attachmentData.at(i).scale); _attachmentModels[i]->setURL(attachmentData.at(i).modelURL); } } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index db13656546..f928881068 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -152,7 +152,12 @@ public: quint32 getCollisionGroups() const { return _collisionGroups; } virtual void setCollisionGroups(quint32 collisionGroups) { _collisionGroups = (collisionGroups & VALID_COLLISION_GROUPS); } - + + Q_INVOKABLE glm::vec3 getJointPosition(int index) const; + Q_INVOKABLE glm::vec3 getJointPosition(const QString& name) const; + Q_INVOKABLE glm::quat getJointCombinedRotation(int index) const; + Q_INVOKABLE glm::quat getJointCombinedRotation(const QString& name) const; + public slots: void updateCollisionGroups(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 582a3934d4..c13950e520 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -24,9 +24,9 @@ #include #include #include -#include - +#include #include +#include #include "Application.h" #include "Audio.h" @@ -76,8 +76,7 @@ MyAvatar::MyAvatar() : _lastFloorContactPoint(0.0f), _lookAtTargetAvatar(), _shouldRender(true), - _billboardValid(false), - _oculusYawOffset(0.0f) + _billboardValid(false) { for (int i = 0; i < MAX_DRIVE_KEYS; i++) { _driveKeys[i] = 0.0f; @@ -90,8 +89,7 @@ MyAvatar::~MyAvatar() { void MyAvatar::reset() { _skeletonModel.reset(); - getHead()->reset(); - _oculusYawOffset = 0.0f; + getHead()->reset(); setVelocity(glm::vec3(0.0f)); setThrust(glm::vec3(0.0f)); @@ -103,10 +101,15 @@ void MyAvatar::reset() { } void MyAvatar::update(float deltaTime) { + PerformanceTimer perfTimer("MyAvatar::update/"); Head* head = getHead(); head->relaxLean(deltaTime); - updateFromTrackers(deltaTime); + { + PerformanceTimer perfTimer("MyAvatar::update/updateFromTrackers"); + updateFromTrackers(deltaTime); + } if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) { + PerformanceTimer perfTimer("MyAvatar::update/moveWithLean"); // Faceshift drive is enabled, set the avatar drive based on the head position moveWithLean(); } @@ -117,13 +120,18 @@ void MyAvatar::update(float deltaTime) { head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) { + PerformanceTimer perfTimer("MyAvatar::update/gravityWork"); setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition())); } - simulate(deltaTime); + { + PerformanceTimer perfTimer("MyAvatar::update/simulate"); + simulate(deltaTime); + } } void MyAvatar::simulate(float deltaTime) { + PerformanceTimer perfTimer("MyAvatar::simulate"); if (_scale != _targetScale) { float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; @@ -134,34 +142,56 @@ void MyAvatar::simulate(float deltaTime) { // no extra movement of the hand here any more ... _handState = HAND_STATE_NULL; - updateOrientation(deltaTime); - updatePosition(deltaTime); - - // update avatar skeleton and simulate hand and head - getHand()->collideAgainstOurself(); - getHand()->simulate(deltaTime, true); - - _skeletonModel.simulate(deltaTime); - simulateAttachments(deltaTime); - - // copy out the skeleton joints from the model - _jointData.resize(_skeletonModel.getJointStateCount()); - for (int i = 0; i < _jointData.size(); i++) { - JointData& data = _jointData[i]; - data.valid = _skeletonModel.getJointState(i, data.rotation); + { + PerformanceTimer perfTimer("MyAvatar::simulate/updateOrientation"); + updateOrientation(deltaTime); + } + { + PerformanceTimer perfTimer("MyAvatar::simulate/updatePosition"); + updatePosition(deltaTime); } - Head* head = getHead(); - glm::vec3 headPosition; - if (!_skeletonModel.getHeadPosition(headPosition)) { - headPosition = _position; + { + PerformanceTimer perfTimer("MyAvatar::simulate/hand Collision,simulate"); + // update avatar skeleton and simulate hand and head + getHand()->collideAgainstOurself(); + getHand()->simulate(deltaTime, true); + } + + { + PerformanceTimer perfTimer("MyAvatar::simulate/_skeletonModel.simulate()"); + _skeletonModel.simulate(deltaTime); + } + { + PerformanceTimer perfTimer("MyAvatar::simulate/simulateAttachments"); + simulateAttachments(deltaTime); + } + + { + PerformanceTimer perfTimer("MyAvatar::simulate/copy joints"); + // copy out the skeleton joints from the model + _jointData.resize(_skeletonModel.getJointStateCount()); + for (int i = 0; i < _jointData.size(); i++) { + JointData& data = _jointData[i]; + data.valid = _skeletonModel.getJointState(i, data.rotation); + } + } + + { + PerformanceTimer perfTimer("MyAvatar::simulate/head Simulate"); + Head* head = getHead(); + glm::vec3 headPosition; + if (!_skeletonModel.getHeadPosition(headPosition)) { + headPosition = _position; + } + head->setPosition(headPosition); + head->setScale(_scale); + head->simulate(deltaTime, true); } - head->setPosition(headPosition); - head->setScale(_scale); - head->simulate(deltaTime, true); // now that we're done stepping the avatar forward in time, compute new collisions if (_collisionGroups != 0) { + PerformanceTimer perfTimer("MyAvatar::simulate/_collisionGroups"); Camera* myCamera = Application::getInstance()->getCamera(); float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE; @@ -171,14 +201,17 @@ void MyAvatar::simulate(float deltaTime) { } updateShapePositions(); if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) { + PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithEnvironment"); updateCollisionWithEnvironment(deltaTime, radius); } if (_collisionGroups & COLLISION_GROUP_VOXELS) { + PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithVoxels"); updateCollisionWithVoxels(deltaTime, radius); } else { _trapDuration = 0.0f; } if (_collisionGroups & COLLISION_GROUP_AVATARS) { + PerformanceTimer perfTimer("MyAvatar::simulate/updateCollisionWithAvatars"); updateCollisionWithAvatars(deltaTime); } } @@ -791,6 +824,7 @@ bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode rend } float MyAvatar::computeDistanceToFloor(const glm::vec3& startPoint) { + PerformanceTimer perfTimer("MyAvatar::computeDistanceToFloor()"); glm::vec3 direction = -_worldUpDirection; OctreeElement* elementHit; // output from findRayIntersection float distance = FLT_MAX; // output from findRayIntersection @@ -828,43 +862,8 @@ void MyAvatar::updateOrientation(float deltaTime) { OculusManager::getEulerAngles(yaw, pitch, roll); // ... so they need to be converted to degrees before we do math... - // The neck is limited in how much it can yaw, so we check its relative - // yaw from the body and yaw the body if necessary. - yaw *= DEGREES_PER_RADIAN; - float bodyToHeadYaw = yaw - _oculusYawOffset; - const float MAX_NECK_YAW = 85.0f; // degrees - if ((fabs(bodyToHeadYaw) > 2.0f * MAX_NECK_YAW) && (yaw * _oculusYawOffset < 0.0f)) { - // We've wrapped around the range for yaw so adjust - // the measured yaw to be relative to _oculusYawOffset. - if (yaw > 0.0f) { - yaw -= 360.0f; - } else { - yaw += 360.0f; - } - bodyToHeadYaw = yaw - _oculusYawOffset; - } - - float delta = fabs(bodyToHeadYaw) - MAX_NECK_YAW; - if (delta > 0.0f) { - yaw = MAX_NECK_YAW; - if (bodyToHeadYaw < 0.0f) { - delta *= -1.0f; - bodyToHeadYaw = -MAX_NECK_YAW; - } else { - bodyToHeadYaw = MAX_NECK_YAW; - } - // constrain _oculusYawOffset to be within range [-180,180] - _oculusYawOffset = fmod((_oculusYawOffset + delta) + 180.0f, 360.0f) - 180.0f; - - // We must adjust the body orientation using a delta rotation (rather than - // doing yaw math) because the body's yaw ranges are not the same - // as what the Oculus API provides. - glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), _worldUpDirection); - orientation = orientation * bodyCorrection; - } Head* head = getHead(); - head->setBaseYaw(bodyToHeadYaw); - + head->setBaseYaw(yaw * DEGREES_PER_RADIAN); head->setBasePitch(pitch * DEGREES_PER_RADIAN); head->setBaseRoll(roll * DEGREES_PER_RADIAN); } @@ -876,6 +875,7 @@ void MyAvatar::updateOrientation(float deltaTime) { const float NEARBY_FLOOR_THRESHOLD = 5.0f; void MyAvatar::updatePosition(float deltaTime) { + PerformanceTimer perfTimer("MyAvatar::updatePosition"); float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) + fabsf(_driveKeys[UP] - _driveKeys[DOWN]); diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 19f51d5db7..f60c38198a 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -424,7 +424,25 @@ bool SkeletonModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& seco return false; } const FBXGeometry& geometry = _geometry->getFBXGeometry(); - return getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) && - getJointPosition(geometry.rightEyeJointIndex, secondEyePosition); + if (getJointPosition(geometry.leftEyeJointIndex, firstEyePosition) && + getJointPosition(geometry.rightEyeJointIndex, secondEyePosition)) { + return true; + } + // no eye joints; try to estimate based on head/neck joints + glm::vec3 neckPosition, headPosition; + if (getJointPosition(geometry.neckJointIndex, neckPosition) && + getJointPosition(geometry.headJointIndex, headPosition)) { + const float EYE_PROPORTION = 0.6f; + glm::vec3 baseEyePosition = glm::mix(neckPosition, headPosition, EYE_PROPORTION); + glm::quat headRotation; + getJointRotation(geometry.headJointIndex, headRotation); + const float EYES_FORWARD = 0.25f; + const float EYE_SEPARATION = 0.1f; + float headHeight = glm::distance(neckPosition, headPosition); + firstEyePosition = baseEyePosition + headRotation * glm::vec3(EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; + secondEyePosition = baseEyePosition + headRotation * glm::vec3(-EYE_SEPARATION, 0.0f, EYES_FORWARD) * headHeight; + return true; + } + return false; } diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 8f71d38bdb..c8b2d0dcbc 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -50,7 +50,8 @@ void OculusManager::connect() { _sensorDevice = *_hmdDevice->GetSensor(); _sensorFusion = new SensorFusion; _sensorFusion->AttachToSensor(_sensorDevice); - + _sensorFusion->SetPredictionEnabled(true); + HMDInfo info; _hmdDevice->GetDeviceInfo(&info); _stereoConfig.SetHMDInfo(info); @@ -83,7 +84,9 @@ void OculusManager::display(Camera& whichCamera) { #ifdef HAVE_LIBOVR ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay(); // We only need to render the overlays to a texture once, then we just render the texture as a quad + // PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay() applicationOverlay.renderOverlay(true); + const bool displayOverlays = false; Application::getInstance()->getGlowEffect()->prepare(); @@ -104,7 +107,9 @@ void OculusManager::display(Camera& whichCamera) { Application::getInstance()->displaySide(whichCamera); - applicationOverlay.displayOverlayTextureOculus(whichCamera); + if (displayOverlays) { + applicationOverlay.displayOverlayTextureOculus(whichCamera); + } // and the right eye to the right side const StereoEyeParams& rightEyeParams = _stereoConfig.GetEyeRenderParams(StereoEye_Right); @@ -121,7 +126,9 @@ void OculusManager::display(Camera& whichCamera) { Application::getInstance()->displaySide(whichCamera); - applicationOverlay.displayOverlayTextureOculus(whichCamera); + if (displayOverlays) { + applicationOverlay.displayOverlayTextureOculus(whichCamera); + } glPopMatrix(); @@ -195,7 +202,7 @@ void OculusManager::reset() { void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) { #ifdef HAVE_LIBOVR - _sensorFusion->GetOrientation().GetEulerAngles(&yaw, &pitch, &roll); + _sensorFusion->GetPredictedOrientation().GetEulerAngles(&yaw, &pitch, &roll); #endif } diff --git a/interface/src/renderer/GlowEffect.cpp b/interface/src/renderer/GlowEffect.cpp index 1eceb71752..262a632df0 100644 --- a/interface/src/renderer/GlowEffect.cpp +++ b/interface/src/renderer/GlowEffect.cpp @@ -14,6 +14,8 @@ #include +#include + #include "Application.h" #include "GlowEffect.h" #include "ProgramObject.h" @@ -119,6 +121,8 @@ static void maybeRelease(QOpenGLFramebufferObject* fbo) { } QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) { + PerformanceTimer perfTimer("paintGL/glowEffect"); + QOpenGLFramebufferObject* primaryFBO = Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject(); primaryFBO->release(); glBindTexture(GL_TEXTURE_2D, primaryFBO->texture()); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 0335378a54..4ed0e2a4f2 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -65,6 +65,11 @@ ProgramObject Model::_shadowNormalMapProgram; ProgramObject Model::_shadowSpecularMapProgram; ProgramObject Model::_shadowNormalSpecularMapProgram; +ProgramObject Model::_cascadedShadowMapProgram; +ProgramObject Model::_cascadedShadowNormalMapProgram; +ProgramObject Model::_cascadedShadowSpecularMapProgram; +ProgramObject Model::_cascadedShadowNormalSpecularMapProgram; + ProgramObject Model::_shadowProgram; ProgramObject Model::_skinProgram; @@ -77,13 +82,25 @@ ProgramObject Model::_skinShadowNormalMapProgram; ProgramObject Model::_skinShadowSpecularMapProgram; ProgramObject Model::_skinShadowNormalSpecularMapProgram; +ProgramObject Model::_skinCascadedShadowMapProgram; +ProgramObject Model::_skinCascadedShadowNormalMapProgram; +ProgramObject Model::_skinCascadedShadowSpecularMapProgram; +ProgramObject Model::_skinCascadedShadowNormalSpecularMapProgram; + ProgramObject Model::_skinShadowProgram; int Model::_normalMapTangentLocation; int Model::_normalSpecularMapTangentLocation; int Model::_shadowNormalMapTangentLocation; int Model::_shadowNormalSpecularMapTangentLocation; +int Model::_cascadedShadowNormalMapTangentLocation; +int Model::_cascadedShadowNormalSpecularMapTangentLocation; +int Model::_cascadedShadowMapDistancesLocation; +int Model::_cascadedShadowNormalMapDistancesLocation; +int Model::_cascadedShadowSpecularMapDistancesLocation; +int Model::_cascadedShadowNormalSpecularMapDistancesLocation; + Model::SkinLocations Model::_skinLocations; Model::SkinLocations Model::_skinNormalMapLocations; Model::SkinLocations Model::_skinSpecularMapLocations; @@ -92,6 +109,10 @@ Model::SkinLocations Model::_skinShadowMapLocations; Model::SkinLocations Model::_skinShadowNormalMapLocations; Model::SkinLocations Model::_skinShadowSpecularMapLocations; Model::SkinLocations Model::_skinShadowNormalSpecularMapLocations; +Model::SkinLocations Model::_skinCascadedShadowMapLocations; +Model::SkinLocations Model::_skinCascadedShadowNormalMapLocations; +Model::SkinLocations Model::_skinCascadedShadowSpecularMapLocations; +Model::SkinLocations Model::_skinCascadedShadowNormalSpecularMapLocations; Model::SkinLocations Model::_skinShadowLocations; void Model::setScale(const glm::vec3& scale) { @@ -128,6 +149,7 @@ void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locati locations.clusterIndices = program.attributeLocation("clusterIndices"); locations.clusterWeights = program.attributeLocation("clusterWeights"); locations.tangent = program.attributeLocation("tangent"); + locations.shadowDistances = program.uniformLocation("shadowDistances"); program.setUniformValue("diffuseMap", 0); program.setUniformValue("normalMap", 1); program.setUniformValue("specularMap", specularTextureUnit); @@ -222,7 +244,7 @@ void Model::init() { _normalSpecularMapProgram.setUniformValue("diffuseMap", 0); _normalSpecularMapProgram.setUniformValue("normalMap", 1); _normalSpecularMapProgram.setUniformValue("specularMap", 2); - _normalSpecularMapTangentLocation = _normalMapProgram.attributeLocation("tangent"); + _normalSpecularMapTangentLocation = _normalSpecularMapProgram.attributeLocation("tangent"); _normalSpecularMapProgram.release(); @@ -272,10 +294,66 @@ void Model::init() { _shadowNormalSpecularMapProgram.setUniformValue("normalMap", 1); _shadowNormalSpecularMapProgram.setUniformValue("specularMap", 2); _shadowNormalSpecularMapProgram.setUniformValue("shadowMap", 3); - _shadowNormalSpecularMapTangentLocation = _normalMapProgram.attributeLocation("tangent"); + _shadowNormalSpecularMapTangentLocation = _shadowNormalSpecularMapProgram.attributeLocation("tangent"); _shadowNormalSpecularMapProgram.release(); + _cascadedShadowMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/model.vert"); + _cascadedShadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/model_cascaded_shadow_map.frag"); + _cascadedShadowMapProgram.link(); + + _cascadedShadowMapProgram.bind(); + _cascadedShadowMapProgram.setUniformValue("diffuseMap", 0); + _cascadedShadowMapProgram.setUniformValue("shadowMap", 1); + _cascadedShadowMapDistancesLocation = _cascadedShadowMapProgram.uniformLocation("shadowDistances"); + _cascadedShadowMapProgram.release(); + + _cascadedShadowNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model_normal_map.vert"); + _cascadedShadowNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_cascaded_shadow_normal_map.frag"); + _cascadedShadowNormalMapProgram.link(); + + _cascadedShadowNormalMapProgram.bind(); + _cascadedShadowNormalMapProgram.setUniformValue("diffuseMap", 0); + _cascadedShadowNormalMapProgram.setUniformValue("normalMap", 1); + _cascadedShadowNormalMapProgram.setUniformValue("shadowMap", 2); + _cascadedShadowNormalMapDistancesLocation = _cascadedShadowNormalMapProgram.uniformLocation("shadowDistances"); + _cascadedShadowNormalMapTangentLocation = _cascadedShadowNormalMapProgram.attributeLocation("tangent"); + _cascadedShadowNormalMapProgram.release(); + + _cascadedShadowSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model.vert"); + _cascadedShadowSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_cascaded_shadow_specular_map.frag"); + _cascadedShadowSpecularMapProgram.link(); + + _cascadedShadowSpecularMapProgram.bind(); + _cascadedShadowSpecularMapProgram.setUniformValue("diffuseMap", 0); + _cascadedShadowSpecularMapProgram.setUniformValue("specularMap", 1); + _cascadedShadowSpecularMapProgram.setUniformValue("shadowMap", 2); + _cascadedShadowSpecularMapDistancesLocation = _cascadedShadowSpecularMapProgram.uniformLocation("shadowDistances"); + _cascadedShadowSpecularMapProgram.release(); + + _cascadedShadowNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/model_normal_map.vert"); + _cascadedShadowNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_cascaded_shadow_normal_specular_map.frag"); + _cascadedShadowNormalSpecularMapProgram.link(); + + _cascadedShadowNormalSpecularMapProgram.bind(); + _cascadedShadowNormalSpecularMapProgram.setUniformValue("diffuseMap", 0); + _cascadedShadowNormalSpecularMapProgram.setUniformValue("normalMap", 1); + _cascadedShadowNormalSpecularMapProgram.setUniformValue("specularMap", 2); + _cascadedShadowNormalSpecularMapProgram.setUniformValue("shadowMap", 3); + _cascadedShadowNormalSpecularMapDistancesLocation = + _cascadedShadowNormalSpecularMapProgram.uniformLocation("shadowDistances"); + _cascadedShadowNormalSpecularMapTangentLocation = _cascadedShadowNormalSpecularMapProgram.attributeLocation("tangent"); + _cascadedShadowNormalSpecularMapProgram.release(); + + _shadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/model_shadow.vert"); _shadowProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + "shaders/model_shadow.frag"); @@ -346,6 +424,39 @@ void Model::init() { initSkinProgram(_skinShadowNormalSpecularMapProgram, _skinShadowNormalSpecularMapLocations, 2, 3); + _skinCascadedShadowMapProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + + "shaders/skin_model.vert"); + _skinCascadedShadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment, Application::resourcesPath() + + "shaders/model_cascaded_shadow_map.frag"); + _skinCascadedShadowMapProgram.link(); + + initSkinProgram(_skinCascadedShadowMapProgram, _skinCascadedShadowMapLocations); + + _skinCascadedShadowNormalMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model_normal_map.vert"); + _skinCascadedShadowNormalMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_cascaded_shadow_normal_map.frag"); + _skinCascadedShadowNormalMapProgram.link(); + + initSkinProgram(_skinCascadedShadowNormalMapProgram, _skinCascadedShadowNormalMapLocations, 1, 2); + + _skinCascadedShadowSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model.vert"); + _skinCascadedShadowSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_cascaded_shadow_specular_map.frag"); + _skinCascadedShadowSpecularMapProgram.link(); + + initSkinProgram(_skinCascadedShadowSpecularMapProgram, _skinCascadedShadowSpecularMapLocations, 1, 2); + + _skinCascadedShadowNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/skin_model_normal_map.vert"); + _skinCascadedShadowNormalSpecularMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/model_cascaded_shadow_normal_specular_map.frag"); + _skinCascadedShadowNormalSpecularMapProgram.link(); + + initSkinProgram(_skinCascadedShadowNormalSpecularMapProgram, _skinCascadedShadowNormalSpecularMapLocations, 2, 3); + + _skinShadowProgram.addShaderFromSourceFile(QGLShader::Vertex, Application::resourcesPath() + "shaders/skin_model_shadow.vert"); _skinShadowProgram.addShaderFromSourceFile(QGLShader::Fragment, @@ -501,7 +612,7 @@ bool Model::render(float alpha, RenderMode mode, bool receiveShadows) { glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.5f * alpha); - receiveShadows &= Menu::getInstance()->isOptionChecked(MenuOption::Shadows); + receiveShadows &= Menu::getInstance()->getShadowsEnabled(); renderMeshes(alpha, mode, false, receiveShadows); glDisable(GL_ALPHA_TEST); @@ -621,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); @@ -636,6 +747,14 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) return true; } +bool Model::getJointCombinedRotation(int jointIndex, glm::quat& rotation) const { + if (jointIndex == -1 || jointIndex >= _jointStates.size()) { + return false; + } + rotation = _jointStates[jointIndex]._combinedRotation; + return true; +} + QStringList Model::getJointNames() const { if (QThread::currentThread() != thread()) { QStringList result; @@ -1439,11 +1558,7 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); - if (receiveShadows) { - glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[0]); - glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[1]); - glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[2]); - } + bool cascadedShadows = Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows); for (int i = 0; i < networkMeshes.size(); i++) { // exit early if the translucency doesn't match what we're drawing const NetworkMesh& networkMesh = networkMeshes.at(i); @@ -1465,6 +1580,8 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re ProgramObject* program = &_program; ProgramObject* skinProgram = &_skinProgram; SkinLocations* skinLocations = &_skinLocations; + int tangentLocation = _normalMapTangentLocation; + int shadowDistancesLocation = _cascadedShadowMapDistancesLocation; GLenum specularTextureUnit = 0; GLenum shadowTextureUnit = 0; if (mode == SHADOW_RENDER_MODE) { @@ -1475,21 +1592,40 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re } else if (!mesh.tangents.isEmpty()) { if (mesh.hasSpecularTexture()) { if (receiveShadows) { - program = &_shadowNormalSpecularMapProgram; - skinProgram = &_skinShadowNormalSpecularMapProgram; - skinLocations = &_skinShadowNormalSpecularMapLocations; + if (cascadedShadows) { + program = &_cascadedShadowNormalSpecularMapProgram; + skinProgram = &_skinCascadedShadowNormalSpecularMapProgram; + skinLocations = &_skinCascadedShadowNormalSpecularMapLocations; + tangentLocation = _cascadedShadowNormalSpecularMapTangentLocation; + shadowDistancesLocation = _cascadedShadowNormalSpecularMapDistancesLocation; + } else { + program = &_shadowNormalSpecularMapProgram; + skinProgram = &_skinShadowNormalSpecularMapProgram; + skinLocations = &_skinShadowNormalSpecularMapLocations; + tangentLocation = _shadowNormalSpecularMapTangentLocation; + } shadowTextureUnit = GL_TEXTURE3; } else { program = &_normalSpecularMapProgram; skinProgram = &_skinNormalSpecularMapProgram; skinLocations = &_skinNormalSpecularMapLocations; + tangentLocation = _normalSpecularMapTangentLocation; } specularTextureUnit = GL_TEXTURE2; } else if (receiveShadows) { - program = &_shadowNormalMapProgram; - skinProgram = &_skinShadowNormalMapProgram; - skinLocations = &_skinShadowNormalMapLocations; + if (cascadedShadows) { + program = &_cascadedShadowNormalMapProgram; + skinProgram = &_skinCascadedShadowNormalMapProgram; + skinLocations = &_skinCascadedShadowNormalMapLocations; + tangentLocation = _cascadedShadowNormalMapTangentLocation; + shadowDistancesLocation = _cascadedShadowNormalMapDistancesLocation; + } else { + program = &_shadowNormalMapProgram; + skinProgram = &_skinShadowNormalMapProgram; + skinLocations = &_skinShadowNormalMapLocations; + tangentLocation = _shadowNormalMapTangentLocation; + } shadowTextureUnit = GL_TEXTURE2; } else { program = &_normalMapProgram; @@ -1498,9 +1634,16 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re } } else if (mesh.hasSpecularTexture()) { if (receiveShadows) { - program = &_shadowSpecularMapProgram; - skinProgram = &_skinShadowSpecularMapProgram; - skinLocations = &_skinShadowSpecularMapLocations; + if (cascadedShadows) { + program = &_cascadedShadowSpecularMapProgram; + skinProgram = &_skinCascadedShadowSpecularMapProgram; + skinLocations = &_skinCascadedShadowSpecularMapLocations; + shadowDistancesLocation = _cascadedShadowSpecularMapDistancesLocation; + } else { + program = &_shadowSpecularMapProgram; + skinProgram = &_skinShadowSpecularMapProgram; + skinLocations = &_skinShadowSpecularMapLocations; + } shadowTextureUnit = GL_TEXTURE2; } else { program = &_specularMapProgram; @@ -1510,15 +1653,20 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re specularTextureUnit = GL_TEXTURE1; } else if (receiveShadows) { - program = &_shadowMapProgram; - skinProgram = &_skinShadowMapProgram; - skinLocations = &_skinShadowMapLocations; + if (cascadedShadows) { + program = &_cascadedShadowMapProgram; + skinProgram = &_skinCascadedShadowMapProgram; + skinLocations = &_skinCascadedShadowMapLocations; + } else { + program = &_shadowMapProgram; + skinProgram = &_skinShadowMapProgram; + skinLocations = &_skinShadowMapLocations; + } shadowTextureUnit = GL_TEXTURE1; } const MeshState& state = _meshStates.at(i); ProgramObject* activeProgram = program; - int tangentLocation = _normalMapTangentLocation; glPushMatrix(); Application::getInstance()->loadTranslatedViewMatrix(_translation); @@ -1536,10 +1684,15 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool re skinProgram->enableAttributeArray(skinLocations->clusterWeights); activeProgram = skinProgram; tangentLocation = skinLocations->tangent; - + if (cascadedShadows) { + program->setUniform(skinLocations->shadowDistances, Application::getInstance()->getShadowDistances()); + } } else { glMultMatrixf((const GLfloat*)&state.clusterMatrices[0]); program->bind(); + if (cascadedShadows) { + program->setUniform(shadowDistancesLocation, Application::getInstance()->getShadowDistances()); + } } if (mesh.blendshapes.isEmpty()) { diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 6e38c7eed4..fed83ecaae 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -157,6 +157,7 @@ public: bool getJointPosition(int jointIndex, glm::vec3& position) const; bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const; + bool getJointCombinedRotation(int jointIndex, glm::quat& rotation) const; QStringList getJointNames() const; @@ -300,6 +301,11 @@ private: static ProgramObject _shadowSpecularMapProgram; static ProgramObject _shadowNormalSpecularMapProgram; + static ProgramObject _cascadedShadowMapProgram; + static ProgramObject _cascadedShadowNormalMapProgram; + static ProgramObject _cascadedShadowSpecularMapProgram; + static ProgramObject _cascadedShadowNormalSpecularMapProgram; + static ProgramObject _shadowProgram; static ProgramObject _skinProgram; @@ -312,12 +318,24 @@ private: static ProgramObject _skinShadowSpecularMapProgram; static ProgramObject _skinShadowNormalSpecularMapProgram; + static ProgramObject _skinCascadedShadowMapProgram; + static ProgramObject _skinCascadedShadowNormalMapProgram; + static ProgramObject _skinCascadedShadowSpecularMapProgram; + static ProgramObject _skinCascadedShadowNormalSpecularMapProgram; + static ProgramObject _skinShadowProgram; static int _normalMapTangentLocation; static int _normalSpecularMapTangentLocation; static int _shadowNormalMapTangentLocation; static int _shadowNormalSpecularMapTangentLocation; + static int _cascadedShadowNormalMapTangentLocation; + static int _cascadedShadowNormalSpecularMapTangentLocation; + + static int _cascadedShadowMapDistancesLocation; + static int _cascadedShadowNormalMapDistancesLocation; + static int _cascadedShadowSpecularMapDistancesLocation; + static int _cascadedShadowNormalSpecularMapDistancesLocation; class SkinLocations { public: @@ -325,6 +343,7 @@ private: int clusterIndices; int clusterWeights; int tangent; + int shadowDistances; }; static SkinLocations _skinLocations; @@ -335,6 +354,10 @@ private: static SkinLocations _skinShadowNormalMapLocations; static SkinLocations _skinShadowSpecularMapLocations; static SkinLocations _skinShadowNormalSpecularMapLocations; + static SkinLocations _skinCascadedShadowMapLocations; + static SkinLocations _skinCascadedShadowNormalMapLocations; + static SkinLocations _skinCascadedShadowSpecularMapLocations; + static SkinLocations _skinCascadedShadowNormalSpecularMapLocations; static SkinLocations _skinShadowLocations; static void initSkinProgram(ProgramObject& program, SkinLocations& locations, 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); diff --git a/interface/src/ui/JSConsole.cpp b/interface/src/ui/JSConsole.cpp new file mode 100644 index 0000000000..921c571d44 --- /dev/null +++ b/interface/src/ui/JSConsole.cpp @@ -0,0 +1,229 @@ +// +// JSConsole.cpp +// interface/src/ui +// +// Created by Ryan Huffman on 05/12/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include +#include + +#include "Application.h" +#include "ScriptHighlighting.h" + +#include "JSConsole.h" + +const int NO_CURRENT_HISTORY_COMMAND = -1; +const int MAX_HISTORY_SIZE = 64; + +const QString COMMAND_STYLE = "color: #266a9b;"; + +const QString RESULT_SUCCESS_STYLE = "color: #677373;"; +const QString RESULT_ERROR_STYLE = "color: #d13b22;"; + +const QString GUTTER_PREVIOUS_COMMAND = "<"; +const QString GUTTER_ERROR = "X"; + +JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) : + QWidget(parent), + _ui(new Ui::Console), + _currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND), + _commandHistory(), + _scriptEngine(scriptEngine) { + + _ui->setupUi(this); + _ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap); + _ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap); + _ui->promptTextEdit->installEventFilter(this); + + QFile styleSheet(Application::resourcesPath() + "styles/console.qss"); + if (styleSheet.open(QIODevice::ReadOnly)) { + QDir::setCurrent(Application::resourcesPath()); + setStyleSheet(styleSheet.readAll()); + } + + connect(_ui->scrollArea->verticalScrollBar(), SIGNAL(rangeChanged(int, int)), this, SLOT(scrollToBottom())); + connect(_ui->promptTextEdit, SIGNAL(textChanged()), this, SLOT(resizeTextInput())); + + + if (_scriptEngine == NULL) { + _scriptEngine = Application::getInstance()->loadScript(); + } + + connect(_scriptEngine, SIGNAL(evaluationFinished(QScriptValue, bool)), + this, SLOT(handleEvalutationFinished(QScriptValue, bool))); + connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&))); + + resizeTextInput(); +} + +JSConsole::~JSConsole() { + delete _ui; +} + +void JSConsole::executeCommand(const QString& command) { + _commandHistory.prepend(command); + if (_commandHistory.length() > MAX_HISTORY_SIZE) { + _commandHistory.removeLast(); + } + + _ui->promptTextEdit->setDisabled(true); + + appendMessage(">", "" + command.toHtmlEscaped() + ""); + + QMetaObject::invokeMethod(_scriptEngine, "evaluate", Q_ARG(const QString&, command)); + + resetCurrentCommandHistory(); +} + +void JSConsole::handleEvalutationFinished(QScriptValue result, bool isException) { + _ui->promptTextEdit->setDisabled(false); + + // Make sure focus is still on this window - some commands are blocking and can take awhile to execute. + if (window()->isActiveWindow()) { + _ui->promptTextEdit->setFocus(); + } + + QString gutter = (isException || result.isError()) ? GUTTER_ERROR : GUTTER_PREVIOUS_COMMAND; + QString resultColor = (isException || result.isError()) ? RESULT_ERROR_STYLE : RESULT_SUCCESS_STYLE; + QString resultStr = "" + result.toString().toHtmlEscaped() + ""; + appendMessage(gutter, resultStr); +} + +void JSConsole::handlePrint(const QString& message) { + appendMessage("", message); +} + +void JSConsole::mouseReleaseEvent(QMouseEvent* event) { + _ui->promptTextEdit->setFocus(); +} + +void JSConsole::showEvent(QShowEvent* event) { + _ui->promptTextEdit->setFocus(); +} + +bool JSConsole::eventFilter(QObject* sender, QEvent* event) { + if (sender == _ui->promptTextEdit) { + if (event->type() == QEvent::KeyPress) { + QKeyEvent* keyEvent = static_cast(event); + int key = keyEvent->key(); + + if ((key == Qt::Key_Return || key == Qt::Key_Enter)) { + if (keyEvent->modifiers() & Qt::ShiftModifier) { + // If the shift key is being used then treat it as a regular return/enter. If this isn't done, + // a new QTextBlock isn't created. + keyEvent->setModifiers(keyEvent->modifiers() & ~Qt::ShiftModifier); + } else { + QString command = _ui->promptTextEdit->toPlainText().trimmed(); + + if (!command.isEmpty()) { + QTextCursor cursor = _ui->promptTextEdit->textCursor(); + _ui->promptTextEdit->clear(); + + executeCommand(command); + } + + return true; + } + } else if (key == Qt::Key_Down) { + // Go to the next command in history if the cursor is at the last line of the current command. + int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); + int blockCount = _ui->promptTextEdit->document()->blockCount(); + if (blockNumber == blockCount - 1) { + setToNextCommandInHistory(); + return true; + } + } else if (key == Qt::Key_Up) { + // Go to the previous command in history if the cursor is at the first line of the current command. + int blockNumber = _ui->promptTextEdit->textCursor().blockNumber(); + if (blockNumber == 0) { + setToPreviousCommandInHistory(); + return true; + } + } + } + } + return false; +} + +void JSConsole::setToNextCommandInHistory() { + if (_currentCommandInHistory >= 0) { + _currentCommandInHistory--; + if (_currentCommandInHistory == NO_CURRENT_HISTORY_COMMAND) { + setAndSelectCommand(_rootCommand); + } else { + setAndSelectCommand(_commandHistory[_currentCommandInHistory]); + } + } +} + +void JSConsole::setToPreviousCommandInHistory() { + if (_currentCommandInHistory < (_commandHistory.length() - 1)) { + if (_currentCommandInHistory == NO_CURRENT_HISTORY_COMMAND) { + _rootCommand = _ui->promptTextEdit->toPlainText(); + } + _currentCommandInHistory++; + setAndSelectCommand(_commandHistory[_currentCommandInHistory]); + } +} + +void JSConsole::resetCurrentCommandHistory() { + _currentCommandInHistory = NO_CURRENT_HISTORY_COMMAND; +} + +void JSConsole::resizeTextInput() { + _ui->promptTextEdit->setFixedHeight(_ui->promptTextEdit->document()->size().height()); + _ui->promptTextEdit->updateGeometry(); +} + +void JSConsole::setAndSelectCommand(const QString& text) { + QTextCursor cursor = _ui->promptTextEdit->textCursor(); + cursor.select(QTextCursor::Document); + cursor.deleteChar(); + cursor.insertText(text); + cursor.movePosition(QTextCursor::End); +} + +void JSConsole::scrollToBottom() { + QScrollBar* scrollBar = _ui->scrollArea->verticalScrollBar(); + scrollBar->setValue(scrollBar->maximum()); +} + +void JSConsole::appendMessage(const QString& gutter, const QString& message) { + QWidget* logLine = new QWidget(_ui->logArea); + QHBoxLayout* layout = new QHBoxLayout(logLine); + layout->setMargin(0); + layout->setSpacing(4); + + QLabel* gutterLabel = new QLabel(logLine); + QLabel* messageLabel = new QLabel(logLine); + + gutterLabel->setFixedWidth(16); + gutterLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + messageLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + + gutterLabel->setStyleSheet("font-size: 14px; font-family: Inconsolata, Lucida Console, Andale Mono, Monaco;"); + messageLabel->setStyleSheet("font-size: 14px; font-family: Inconsolata, Lucida Console, Andale Mono, Monaco;"); + + gutterLabel->setText(gutter); + messageLabel->setText(message); + + layout->addWidget(gutterLabel); + layout->addWidget(messageLabel); + logLine->setLayout(layout); + + layout->setAlignment(gutterLabel, Qt::AlignTop); + + layout->setStretch(0, 0); + layout->setStretch(1, 1); + + _ui->logArea->layout()->addWidget(logLine); + + _ui->logArea->updateGeometry(); + scrollToBottom(); +} diff --git a/interface/src/ui/JSConsole.h b/interface/src/ui/JSConsole.h new file mode 100644 index 0000000000..f28132a1f5 --- /dev/null +++ b/interface/src/ui/JSConsole.h @@ -0,0 +1,62 @@ +// +// JSConsole.h +// interface/src/ui +// +// Created by Ryan Huffman on 05/12/14. +// Copyright 2014 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef hifi_JSConsole_h +#define hifi_JSConsole_h + +#include +#include +#include +#include + +#include "ui_console.h" +#include "ScriptEngine.h" + +class JSConsole : public QWidget { + Q_OBJECT +public: + JSConsole(QWidget* parent, ScriptEngine* scriptEngine = NULL); + ~JSConsole(); + +public slots: + void executeCommand(const QString& command); + +signals: + void commandExecuting(const QString& command); + void commandFinished(const QString& result); + +protected: + void setAndSelectCommand(const QString& command); + virtual bool eventFilter(QObject* sender, QEvent* event); + virtual void mouseReleaseEvent(QMouseEvent* event); + virtual void showEvent(QShowEvent* event); + +protected slots: + void scrollToBottom(); + void resizeTextInput(); + void handleEvalutationFinished(QScriptValue result, bool isException); + void handlePrint(const QString& message); + +private: + void appendMessage(const QString& gutter, const QString& message); + void setToNextCommandInHistory(); + void setToPreviousCommandInHistory(); + void resetCurrentCommandHistory(); + + Ui::Console* _ui; + int _currentCommandInHistory; + QList _commandHistory; + QString _rootCommand; + ScriptEngine* _scriptEngine; +}; + + +#endif // hifi_JSConsole_h diff --git a/interface/src/ui/OAuthWebViewHandler.cpp b/interface/src/ui/OAuthWebViewHandler.cpp index e93321212c..5415d5d9c3 100644 --- a/interface/src/ui/OAuthWebViewHandler.cpp +++ b/interface/src/ui/OAuthWebViewHandler.cpp @@ -82,7 +82,7 @@ void OAuthWebViewHandler::displayWebviewForAuthorizationURL(const QUrl& authoriz _activeWebView = new QWebView; // keep the window on top and delete it when it closes - _activeWebView->setWindowFlags(Qt::Sheet | Qt::WindowStaysOnTopHint); + _activeWebView->setWindowFlags(Qt::Sheet); _activeWebView->setAttribute(Qt::WA_DeleteOnClose); qDebug() << "Displaying QWebView for OAuth authorization at" << authorizationURL.toString(); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index ed2d7097db..d5c6079d4b 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -29,6 +29,8 @@ PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : F connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &PreferencesDialog::openHeadModelBrowser); connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &PreferencesDialog::openBodyModelBrowser); connect(ui.buttonBrowseLocation, &QPushButton::clicked, this, &PreferencesDialog::openSnapshotLocationBrowser); + connect(ui.buttonReloadDefaultScripts, &QPushButton::clicked, + Application::getInstance(), &Application::loadDefaultScripts); } void PreferencesDialog::accept() { diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 328974dc9d..9bef454829 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -106,36 +106,12 @@ void RunningScriptsWidget::setRunningScripts(const QStringList& list) { createRecentlyLoadedScriptsTable(); } -void RunningScriptsWidget::keyPressEvent(QKeyEvent* event) -{ - int loadScriptNumber = -1; - switch(event->key()) { - case Qt::Key_Escape: - Application::getInstance()->toggleRunningScriptsWidget(); - break; - - case Qt::Key_1: - case Qt::Key_2: - case Qt::Key_3: - case Qt::Key_4: - case Qt::Key_5: - case Qt::Key_6: - case Qt::Key_7: - case Qt::Key_8: - case Qt::Key_9: - loadScriptNumber = event->key() - Qt::Key_1; - break; - - default: - break; +void RunningScriptsWidget::keyPressEvent(QKeyEvent *keyEvent) { + if (keyEvent->key() == Qt::Key_Escape) { + return; + } else { + FramelessDialog::keyPressEvent(keyEvent); } - if (loadScriptNumber >= 0) { - if (_recentlyLoadedScripts.size() > loadScriptNumber) { - Application::getInstance()->loadScript(_recentlyLoadedScripts.at(loadScriptNumber)); - } - } - - FramelessDialog::keyPressEvent(event); } void RunningScriptsWidget::paintEvent(QPaintEvent* event) { diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp index 41732d18c6..3f63f0741b 100644 --- a/interface/src/ui/ScriptEditorWindow.cpp +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -27,6 +27,9 @@ #include "Application.h" #include "FlowLayout.h" +#include "JSConsole.h" + +const int CONSOLE_HEIGHT = 150; ScriptEditorWindow::ScriptEditorWindow() : _ScriptEditorWindowUI(new Ui::ScriptEditorWindow), @@ -48,6 +51,10 @@ ScriptEditorWindow::ScriptEditorWindow() : connect(new QShortcut(QKeySequence("Ctrl+S"), this), &QShortcut::activated, this,&ScriptEditorWindow::saveScriptClicked); connect(new QShortcut(QKeySequence("Ctrl+O"), this), &QShortcut::activated, this, &ScriptEditorWindow::loadScriptClicked); connect(new QShortcut(QKeySequence("F5"), this), &QShortcut::activated, this, &ScriptEditorWindow::toggleRunScriptClicked); + + QWidget* console = new JSConsole(this); + console->setFixedHeight(CONSOLE_HEIGHT); + this->layout()->addWidget(console); } ScriptEditorWindow::~ScriptEditorWindow() { diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index a391ed239c..fa62ecdb9b 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -18,6 +18,8 @@ #include #include +#include + #include "Stats.h" #include "InterfaceConfig.h" #include "Menu.h" @@ -158,6 +160,39 @@ void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int heigh glColor4f(1, 1, 1, 1); } +bool Stats::includeTimingRecord(const QString& name) { + bool included = false; + if (Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) { + + if (name == "idle/update") { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming) || + Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming); + } else if (name == "idle/updateGL") { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming); + } else if (name.startsWith("idle/update")) { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandUpdateTiming); + } else if (name.startsWith("idle/")) { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandIdleTiming); + } else if (name.startsWith("MyAvatar::simulate")) { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandAvatarSimulateTiming); + } else if (name.startsWith("MyAvatar::update/") || name.startsWith("updateMyAvatar")) { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandAvatarUpdateTiming); + } else if (name.startsWith("MyAvatar::")) { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandMiscAvatarTiming); + } else if (name == "paintGL/displaySide") { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandDisplaySideTiming) || + Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); + } else if (name.startsWith("paintGL/displaySide/")) { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandDisplaySideTiming); + } else if (name.startsWith("paintGL/")) { + included = Menu::getInstance()->isOptionChecked(MenuOption::ExpandPaintGLTiming); + } else { + included = true; // include everything else + } + } + return included; +} + // display expanded or contracted stats void Stats::display( const float* color, @@ -345,11 +380,25 @@ void Stats::display( VoxelSystem* voxels = Application::getInstance()->getVoxels(); - lines = _expanded ? 12 : 3; + lines = _expanded ? 11 : 3; if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) { lines += 9; // spatial audio processing adds 1 spacing line and 8 extra lines of info } + if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) { + // we will also include room for 1 line per timing record and a header + lines += 1; + + const QMap& allRecords = PerformanceTimer::getAllTimerRecords(); + QMapIterator i(allRecords); + while (i.hasNext()) { + i.next(); + if (includeTimingRecord(i.key())) { + lines++; + } + } + } + drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); horizontalOffset += 5; @@ -454,8 +503,6 @@ void Stats::display( } } - verticalOffset += (_expanded ? STATS_PELS_PER_LINE : 0); - QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' '); // Server Voxels @@ -508,6 +555,29 @@ void Stats::display( drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); } + // TODO: the display of these timing details should all be moved to JavaScript + if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::DisplayTimingDetails)) { + // Timing details... + const int TIMER_OUTPUT_LINE_LENGTH = 300; + char perfLine[TIMER_OUTPUT_LINE_LENGTH]; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, + "--------------------- Function -------------------- --msecs- -calls--", color); + + const QMap& allRecords = PerformanceTimer::getAllTimerRecords(); + QMapIterator i(allRecords); + while (i.hasNext()) { + i.next(); + if (includeTimingRecord(i.key())) { + sprintf(perfLine, "%50s: %8.4f [%6llu]", qPrintable(i.key()), + (float)i.value().getMovingAverage() / (float)USECS_PER_MSEC, + i.value().getCount()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, perfLine, color); + } + } + } if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) { verticalOffset += STATS_PELS_PER_LINE; // space one line... diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index 1ce0807ee8..66d6ec99f0 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -30,6 +30,7 @@ public: void checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseDragStartedY, int horizontalOffset); void resetWidth(int width, int horizontalOffset); void display(const float* color, int horizontalOffset, float fps, int packetsPerSecond, int bytesPerSecond, int voxelPacketsToProcess); + bool includeTimingRecord(const QString& name); private: static Stats* _sharedInstance; diff --git a/interface/src/voxels/VoxelPacketProcessor.cpp b/interface/src/voxels/VoxelPacketProcessor.cpp index c0f27264f6..095c378c04 100644 --- a/interface/src/voxels/VoxelPacketProcessor.cpp +++ b/interface/src/voxels/VoxelPacketProcessor.cpp @@ -39,7 +39,7 @@ void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, c } PacketType voxelPacketType = packetTypeForPacket(mutablePacket); - + // note: PacketType_OCTREE_STATS can have PacketType_VOXEL_DATA // immediately following them inside the same packet. So, we process the PacketType_OCTREE_STATS first // then process any remaining bytes as if it was another packet @@ -81,6 +81,7 @@ void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, c if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { + app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket); if (sendingNode) { diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index 8937cef7dd..2b86a58d9b 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -519,6 +519,17 @@ void VoxelSystem::initVoxelMemory() { _shadowMapProgram.bind(); _shadowMapProgram.setUniformValue("shadowMap", 0); _shadowMapProgram.release(); + + _cascadedShadowMapProgram.addShaderFromSourceFile(QGLShader::Vertex, + Application::resourcesPath() + "shaders/cascaded_shadow_map.vert"); + _cascadedShadowMapProgram.addShaderFromSourceFile(QGLShader::Fragment, + Application::resourcesPath() + "shaders/cascaded_shadow_map.frag"); + _cascadedShadowMapProgram.link(); + + _cascadedShadowMapProgram.bind(); + _cascadedShadowMapProgram.setUniformValue("shadowMap", 0); + _shadowDistancesLocation = _cascadedShadowMapProgram.uniformLocation("shadowDistances"); + _cascadedShadowMapProgram.release(); } } _renderer = new PrimitiveRenderer(_maxVoxels); @@ -1166,6 +1177,8 @@ glm::vec3 VoxelSystem::computeVoxelVertex(const glm::vec3& startVertex, float vo ProgramObject VoxelSystem::_perlinModulateProgram; ProgramObject VoxelSystem::_shadowMapProgram; +ProgramObject VoxelSystem::_cascadedShadowMapProgram; +int VoxelSystem::_shadowDistancesLocation; void VoxelSystem::init() { if (_initialized) { @@ -1486,14 +1499,15 @@ void VoxelSystem::render() { void VoxelSystem::applyScaleAndBindProgram(bool texture) { - if (Menu::getInstance()->isOptionChecked(MenuOption::Shadows)) { - _shadowMapProgram.bind(); + if (Menu::getInstance()->getShadowsEnabled()) { + if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + _cascadedShadowMapProgram.bind(); + _cascadedShadowMapProgram.setUniform(_shadowDistancesLocation, Application::getInstance()->getShadowDistances()); + } else { + _shadowMapProgram.bind(); + } glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getShadowDepthTextureID()); - glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[0]); - glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[1]); - glTexGenfv(GL_R, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[2]); - } else if (texture) { _perlinModulateProgram.bind(); glBindTexture(GL_TEXTURE_2D, Application::getInstance()->getTextureCache()->getPermutationNormalTextureID()); @@ -1507,11 +1521,14 @@ void VoxelSystem::removeScaleAndReleaseProgram(bool texture) { // scale back down to 1 so heads aren't massive glPopMatrix(); - if (Menu::getInstance()->isOptionChecked(MenuOption::Shadows)) { - _shadowMapProgram.release(); + if (Menu::getInstance()->getShadowsEnabled()) { + if (Menu::getInstance()->isOptionChecked(MenuOption::CascadedShadows)) { + _cascadedShadowMapProgram.release(); + } else { + _shadowMapProgram.release(); + } glBindTexture(GL_TEXTURE_2D, 0); - } else if (texture) { _perlinModulateProgram.release(); glBindTexture(GL_TEXTURE_2D, 0); diff --git a/interface/src/voxels/VoxelSystem.h b/interface/src/voxels/VoxelSystem.h index 15e2b20a75..71abc9ca93 100644 --- a/interface/src/voxels/VoxelSystem.h +++ b/interface/src/voxels/VoxelSystem.h @@ -233,6 +233,8 @@ private: static ProgramObject _perlinModulateProgram; static ProgramObject _shadowMapProgram; + static ProgramObject _cascadedShadowMapProgram; + static int _shadowDistancesLocation; int _hookID; std::vector _freeIndexes; diff --git a/interface/ui/console.ui b/interface/ui/console.ui new file mode 100644 index 0000000000..d4b0b2bc74 --- /dev/null +++ b/interface/ui/console.ui @@ -0,0 +1,258 @@ + + + Console + + + + 0 + 0 + 1055 + 205 + + + + Dialog + + + QDialog { background: white } + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOn + + + true + + + + + 0 + 0 + 1040 + 205 + + + + background-color: white; + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + background-color: white; + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 16 + 23 + + + + + 16 + 23 + + + + + 50 + false + + + + padding: 0px 0 0 0; + + + > + + + Qt::PlainText + + + + + + + + 0 + 0 + + + + + Inconsolata,Lucida Console,Andale Mono,Monaco + -1 + + + + QFrame::NoFrame + + + Qt::ScrollBarAlwaysOff + + + Qt::ScrollBarAlwaysOff + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui index bab431160d..a1c2073ab6 100644 --- a/interface/ui/preferencesDialog.ui +++ b/interface/ui/preferencesDialog.ui @@ -134,8 +134,8 @@ color: #0e7077 0 30 - 615 - 491 + 494 + 384 @@ -154,9 +154,9 @@ color: #0e7077 0 - -271 - 598 - 1018 + -204 + 494 + 1091 @@ -612,6 +612,78 @@ color: #0e7077 + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + Scripts + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + + + + 0 + 0 + + + + background: #0e7077; +color: #fff; +border-radius: 4px; +font: bold 14pt; +padding: 10px;margin-top:10px + + + Load Default Scripts + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui index cb0106cd48..6cb23f4c89 100644 --- a/interface/ui/runningScriptsWidget.ui +++ b/interface/ui/runningScriptsWidget.ui @@ -143,7 +143,7 @@ font: bold 14pt; font-size: 14pt; - (click a script or use the 1-9 keys to load and run it) + (click a script to load and run it) true diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 1fe9f1336f..129dc47bd0 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -88,6 +88,7 @@ void AudioInjector::injectAudio() { int currentSendPosition = 0; int numPreAudioDataBytes = injectAudioPacket.size(); + bool shouldLoop = _options.getLoop(); // loop to send off our audio in NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL byte chunks while (currentSendPosition < soundByteArray.size() && !_shouldStop) { @@ -120,6 +121,10 @@ void AudioInjector::injectAudio() { usleep(usecToSleep); } } + + if (shouldLoop && currentSendPosition == soundByteArray.size()) { + currentSendPosition = 0; + } } } diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index cd6b08f6c7..49f1571c98 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -15,6 +15,7 @@ AudioInjectorOptions::AudioInjectorOptions(QObject* parent) : QObject(parent), _position(0.0f, 0.0f, 0.0f), _volume(1.0f), + _loop(false), _orientation(glm::vec3(0.0f, 0.0f, 0.0f)), _loopbackAudioInterface(NULL) { @@ -24,6 +25,7 @@ AudioInjectorOptions::AudioInjectorOptions(QObject* parent) : AudioInjectorOptions::AudioInjectorOptions(const AudioInjectorOptions& other) { _position = other._position; _volume = other._volume; + _loop = other._loop; _orientation = other._orientation; _loopbackAudioInterface = other._loopbackAudioInterface; } diff --git a/libraries/audio/src/AudioInjectorOptions.h b/libraries/audio/src/AudioInjectorOptions.h index bbe3d57b08..b90deb93f1 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -26,6 +26,7 @@ class AudioInjectorOptions : public QObject { Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition) Q_PROPERTY(float volume READ getVolume WRITE setVolume) + Q_PROPERTY(bool loop READ getLoop WRITE setLoop) public: AudioInjectorOptions(QObject* parent = 0); AudioInjectorOptions(const AudioInjectorOptions& other); @@ -36,6 +37,9 @@ public: float getVolume() const { return _volume; } void setVolume(float volume) { _volume = volume; } + float getLoop() const { return _loop; } + void setLoop(float loop) { _loop = loop; } + const glm::quat& getOrientation() const { return _orientation; } void setOrientation(const glm::quat& orientation) { _orientation = orientation; } @@ -45,6 +49,7 @@ public: private: glm::vec3 _position; float _volume; + bool _loop; glm::quat _orientation; AbstractAudioInterface* _loopbackAudioInterface; }; diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index b76bd189bf..9e2920ae85 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -20,8 +20,8 @@ #include // degrees -const float MIN_HEAD_YAW = -110.f; -const float MAX_HEAD_YAW = 110.f; +const float MIN_HEAD_YAW = -180.f; +const float MAX_HEAD_YAW = 180.f; const float MIN_HEAD_PITCH = -60.f; const float MAX_HEAD_PITCH = 60.f; const float MIN_HEAD_ROLL = -50.f; diff --git a/libraries/models/src/ModelTree.cpp b/libraries/models/src/ModelTree.cpp index c492ed14ad..e88a969061 100644 --- a/libraries/models/src/ModelTree.cpp +++ b/libraries/models/src/ModelTree.cpp @@ -545,8 +545,8 @@ bool ModelTree::hasModelsDeletedSince(quint64 sinceTime) { } // sinceTime is an in/out parameter - it will be side effected with the last time sent out -bool ModelTree::encodeModelsDeletedSince(quint64& sinceTime, unsigned char* outputBuffer, size_t maxLength, - size_t& outputLength) { +bool ModelTree::encodeModelsDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, unsigned char* outputBuffer, + size_t maxLength, size_t& outputLength) { bool hasMoreToSend = true; @@ -555,6 +555,26 @@ bool ModelTree::encodeModelsDeletedSince(quint64& sinceTime, unsigned char* outp copyAt += numBytesPacketHeader; outputLength = numBytesPacketHeader; + // pack in flags + OCTREE_PACKET_FLAGS flags = 0; + OCTREE_PACKET_FLAGS* flagsAt = (OCTREE_PACKET_FLAGS*)copyAt; + *flagsAt = flags; + copyAt += sizeof(OCTREE_PACKET_FLAGS); + outputLength += sizeof(OCTREE_PACKET_FLAGS); + + // pack in sequence number + OCTREE_PACKET_SEQUENCE* sequenceAt = (OCTREE_PACKET_SEQUENCE*)copyAt; + *sequenceAt = sequenceNumber; + copyAt += sizeof(OCTREE_PACKET_SEQUENCE); + outputLength += sizeof(OCTREE_PACKET_SEQUENCE); + + // pack in timestamp + OCTREE_PACKET_SENT_TIME now = usecTimestampNow(); + OCTREE_PACKET_SENT_TIME* timeAt = (OCTREE_PACKET_SENT_TIME*)copyAt; + *timeAt = now; + copyAt += sizeof(OCTREE_PACKET_SENT_TIME); + outputLength += sizeof(OCTREE_PACKET_SENT_TIME); + uint16_t numberOfIds = 0; // placeholder for now unsigned char* numberOfIDsAt = copyAt; memcpy(copyAt, &numberOfIds, sizeof(numberOfIds)); @@ -642,6 +662,10 @@ void ModelTree::processEraseMessage(const QByteArray& dataByteArray, const Share size_t processedBytes = numBytesPacketHeader; dataAt += numBytesPacketHeader; + dataAt += sizeof(OCTREE_PACKET_FLAGS); + dataAt += sizeof(OCTREE_PACKET_SEQUENCE); + dataAt += sizeof(OCTREE_PACKET_SENT_TIME); + uint16_t numberOfIds = 0; // placeholder for now memcpy(&numberOfIds, dataAt, sizeof(numberOfIds)); dataAt += sizeof(numberOfIds); diff --git a/libraries/models/src/ModelTree.h b/libraries/models/src/ModelTree.h index c450f04e2d..a2a3c9cd28 100644 --- a/libraries/models/src/ModelTree.h +++ b/libraries/models/src/ModelTree.h @@ -74,7 +74,7 @@ public: bool hasAnyDeletedModels() const { return _recentlyDeletedModelItemIDs.size() > 0; } bool hasModelsDeletedSince(quint64 sinceTime); - bool encodeModelsDeletedSince(quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength); + bool encodeModelsDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength); void forgetModelsDeletedBefore(quint64 sinceTime); void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index c655a45b57..b87557d073 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -192,7 +192,7 @@ bool ModelTreeElement::findDetailedRayIntersection(const glm::vec3& origin, cons // if it's in our AABOX for our rotated extents, then check to see if it's in our non-AABox if (rotatedExtentsBox.findRayIntersection(origin, direction, localDistance, localFace)) { - + // extents is the model relative, scaled, centered extents of the model glm::mat4 rotation = glm::mat4_cast(model.getModelRotation()); glm::mat4 translation = glm::translate(model.getPosition()); @@ -202,7 +202,7 @@ bool ModelTreeElement::findDetailedRayIntersection(const glm::vec3& origin, cons AABox modelFrameBox(extents.minimum, (extents.maximum - extents.minimum)); glm::vec3 modelFrameOrigin = glm::vec3(worldToModelMatrix * glm::vec4(origin, 1.0f)); - glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 1.0f)); + glm::vec3 modelFrameDirection = glm::vec3(worldToModelMatrix * glm::vec4(direction, 0.0f)); // we can use the AABox's ray intersection by mapping our origin and direction into the model frame // and testing intersection there. diff --git a/libraries/networking/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp index 0f0b57635c..c56dba9cf1 100644 --- a/libraries/networking/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -52,7 +52,7 @@ PacketVersion versionForPacketType(PacketType type) { case PacketTypeAvatarIdentity: return 1; case PacketTypeEnvironmentData: - return 1; + return 2; case PacketTypeDomainList: case PacketTypeDomainListRequest: return 3; @@ -66,8 +66,12 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeParticleData: return 1; + case PacketTypeParticleErase: + return 1; case PacketTypeModelData: - return 2; + return 2; + case PacketTypeModelErase: + return 1; default: return 0; } diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 22320b5969..4e14f603e8 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -1308,11 +1308,6 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3 keepSearching = true; // assume that we will continue searching after this. - // by default, we only allow intersections with leaves with content - if (!canRayIntersect()) { - return false; // we don't intersect with non-leaves, and we keep searching - } - AACube cube = getAACube(); float localDistance; BoxFace localFace; @@ -1323,6 +1318,11 @@ bool OctreeElement::findRayIntersection(const glm::vec3& origin, const glm::vec3 return false; // we did not intersect } + // by default, we only allow intersections with leaves with content + if (!canRayIntersect()) { + return false; // we don't intersect with non-leaves, and we keep searching + } + // we did hit this element, so calculate appropriate distances localDistance *= TREE_SCALE; if (localDistance < distance) { @@ -1346,6 +1346,7 @@ bool OctreeElement::findDetailedRayIntersection(const glm::vec3& origin, const g if (intersectedObject) { *intersectedObject = this; } + keepSearching = false; return true; // we did intersect } return false; // we did not intersect diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index c08e723f89..9580ae6d13 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -858,7 +858,7 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, //bool packetIsCompressed = oneAtBit(flags, PACKET_IS_COMPRESSED_BIT); OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); - int flightTime = arrivedAt - sentAt + nodeClockSkewUsec; + qint64 flightTime = arrivedAt - sentAt + nodeClockSkewUsec; if (wantExtraDebugging) { qDebug() << "sentAt:" << sentAt << " usecs"; @@ -866,7 +866,7 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, qDebug() << "nodeClockSkewUsec:" << nodeClockSkewUsec << " usecs"; qDebug() << "flightTime:" << flightTime << " usecs"; } - + // Guard against possible corrupted packets... with bad timestamps const int MAX_RESONABLE_FLIGHT_TIME = 200 * USECS_PER_SECOND; // 200 seconds is more than enough time for a packet to arrive const int MIN_RESONABLE_FLIGHT_TIME = 0; @@ -985,6 +985,6 @@ void OctreeSceneStats::trackIncomingOctreePacket(const QByteArray& packet, } } } - + } diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index ef97c48c20..cd79ff303e 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -511,8 +511,8 @@ bool ParticleTree::hasParticlesDeletedSince(quint64 sinceTime) { } // sinceTime is an in/out parameter - it will be side effected with the last time sent out -bool ParticleTree::encodeParticlesDeletedSince(quint64& sinceTime, unsigned char* outputBuffer, size_t maxLength, - size_t& outputLength) { +bool ParticleTree::encodeParticlesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, unsigned char* outputBuffer, + size_t maxLength, size_t& outputLength) { bool hasMoreToSend = true; @@ -521,6 +521,24 @@ bool ParticleTree::encodeParticlesDeletedSince(quint64& sinceTime, unsigned char copyAt += numBytesPacketHeader; outputLength = numBytesPacketHeader; + // pack in flags + OCTREE_PACKET_FLAGS flags = 0; + memcpy(copyAt, &flags, sizeof(OCTREE_PACKET_FLAGS)); + copyAt += sizeof(OCTREE_PACKET_FLAGS); + outputLength += sizeof(OCTREE_PACKET_FLAGS); + + // pack in sequence number + memcpy(copyAt, &sequenceNumber, sizeof(OCTREE_PACKET_SEQUENCE)); + copyAt += sizeof(OCTREE_PACKET_SEQUENCE); + outputLength += sizeof(OCTREE_PACKET_SEQUENCE); + + // pack in timestamp + OCTREE_PACKET_SENT_TIME now = usecTimestampNow(); + memcpy(copyAt, &now, sizeof(OCTREE_PACKET_SENT_TIME)); + copyAt += sizeof(OCTREE_PACKET_SENT_TIME); + outputLength += sizeof(OCTREE_PACKET_SENT_TIME); + + uint16_t numberOfIds = 0; // placeholder for now unsigned char* numberOfIDsAt = copyAt; memcpy(copyAt, &numberOfIds, sizeof(numberOfIds)); @@ -609,6 +627,10 @@ void ParticleTree::processEraseMessage(const QByteArray& dataByteArray, const Sh size_t processedBytes = numBytesPacketHeader; dataAt += numBytesPacketHeader; + dataAt += sizeof(OCTREE_PACKET_FLAGS); + dataAt += sizeof(OCTREE_PACKET_SEQUENCE); + dataAt += sizeof(OCTREE_PACKET_SENT_TIME); + uint16_t numberOfIds = 0; // placeholder for now memcpy(&numberOfIds, dataAt, sizeof(numberOfIds)); dataAt += sizeof(numberOfIds); diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index 01ceb4425c..eba00d60da 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -67,7 +67,7 @@ public: bool hasAnyDeletedParticles() const { return _recentlyDeletedParticleIDs.size() > 0; } bool hasParticlesDeletedSince(quint64 sinceTime); - bool encodeParticlesDeletedSince(quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength); + bool encodeParticlesDeletedSince(OCTREE_PACKET_SEQUENCE sequenceNumber, quint64& sinceTime, unsigned char* packetData, size_t maxLength, size_t& outputLength); void forgetParticlesDeletedBefore(quint64 sinceTime); void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 2c17090914..a4aae61248 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -314,6 +314,18 @@ void ScriptEngine::evaluate() { } } +QScriptValue ScriptEngine::evaluate(const QString& program, const QString& fileName, int lineNumber) { + QScriptValue result = _engine.evaluate(program, fileName, lineNumber); + bool hasUncaughtException = _engine.hasUncaughtException(); + if (hasUncaughtException) { + int line = _engine.uncaughtExceptionLineNumber(); + qDebug() << "Uncaught exception at line" << line << ": " << result.toString(); + } + emit evaluationFinished(result, hasUncaughtException); + _engine.clearExceptions(); + return result; +} + void ScriptEngine::sendAvatarIdentityPacket() { if (_isAvatar && _avatarData) { _avatarData->sendIdentityPacket(); diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 96cc874453..bf2ac40568 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -92,6 +92,7 @@ public: public slots: void stop(); + QScriptValue evaluate(const QString& program, const QString& fileName = QString(), int lineNumber = 1); QObject* setInterval(const QScriptValue& function, int intervalMS); QObject* setTimeout(const QScriptValue& function, int timeoutMS); void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } @@ -107,6 +108,7 @@ signals: void printedMessage(const QString& message); void errorMessage(const QString& message); void runningStateChanged(); + void evaluationFinished(QScriptValue result, bool isException); protected: QString _scriptContents; diff --git a/libraries/shared/src/PerfStat.cpp b/libraries/shared/src/PerfStat.cpp index 9235cb3f25..4dca3f3d49 100644 --- a/libraries/shared/src/PerfStat.cpp +++ b/libraries/shared/src/PerfStat.cpp @@ -52,4 +52,22 @@ PerformanceWarning::~PerformanceWarning() { } }; +QMap PerformanceTimer::_records; + +PerformanceTimer::~PerformanceTimer() { + quint64 end = usecTimestampNow(); + quint64 elapsedusec = (end - _start); + PerformanceTimerRecord& namedRecord = _records[_name]; + namedRecord.recordResult(elapsedusec); +} + +void PerformanceTimer::dumpAllTimerRecords() { + QMapIterator i(_records); + while (i.hasNext()) { + i.next(); + qDebug() << i.key() << ": average " << i.value().getAverage() + << " [" << i.value().getMovingAverage() << "]" + << "usecs over" << i.value().getCount() << "calls"; + } +} diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index 22cf14f207..f849fb844c 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -17,6 +17,7 @@ #include #include "SharedUtil.h" +#include "SimpleMovingAverage.h" #include #include @@ -49,5 +50,41 @@ public: static void setSuppressShortTimings(bool suppressShortTimings) { _suppressShortTimings = suppressShortTimings; } }; +class PerformanceTimerRecord { +public: + PerformanceTimerRecord() : _runningTotal(0), _totalCalls(0) {} + + void recordResult(quint64 elapsed) { _runningTotal += elapsed; _totalCalls++; _movingAverage.updateAverage(elapsed); } + quint64 getAverage() const { return (_totalCalls == 0) ? 0 : _runningTotal / _totalCalls; } + quint64 getMovingAverage() const { return (_totalCalls == 0) ? 0 : _movingAverage.getAverage(); } + quint64 getCount() const { return _totalCalls; } + +private: + quint64 _runningTotal; + quint64 _totalCalls; + SimpleMovingAverage _movingAverage; +}; + +class PerformanceTimer { +public: + + PerformanceTimer(const QString& name) : + _start(usecTimestampNow()), + _name(name) { } + + quint64 elapsed() const { return (usecTimestampNow() - _start); }; + + ~PerformanceTimer(); + + static const PerformanceTimerRecord& getTimerRecord(const QString& name) { return _records[name]; }; + static const QMap& getAllTimerRecords() { return _records; }; + static void dumpAllTimerRecords(); + +private: + quint64 _start; + QString _name; + static QMap _records; +}; + #endif // hifi_PerfStat_h