diff --git a/examples/findParticleExample.js b/examples/findParticleExample.js new file mode 100644 index 0000000000..f582ee6469 --- /dev/null +++ b/examples/findParticleExample.js @@ -0,0 +1,128 @@ +// +// editParticleExample.js +// hifi +// +// Created by Brad Hefta-Gaub on 1/24/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// This is an example script that demonstrates "finding" particles +// + +var iteration = 0; + +var particleA = Particles.addParticle( + { + position: { x: 2, y: 0, z: 2 }, + velocity: { x: 0, y: 0, z: 0 }, + gravity: { x: 0, y: 0, z: 0 }, + radius : 0.1, + color: { red: 0, green: 255, blue: 0 } + }); + +var particleB = Particles.addParticle( + { + position: { x: 5, y: 0, z: 5 }, + velocity: { x: 0, y: 0, z: 0 }, + gravity: { x: 0, y: 0, z: 0 }, + radius : 0.1, + color: { red: 0, green: 255, blue: 255 } + }); + +var searchAt = { x: 0, y: 0, z: 0}; +var moveSearch = { x: 0.1, y: 0, z: 0.1}; +var searchRadius = 1; +var searchRadiusChange = 0; + +print("particleA.creatorTokenID = " + particleA.creatorTokenID); +print("particleB.creatorTokenID = " + particleB.creatorTokenID); + + +function scriptEnding() { + print("calling Particles.deleteParticle()"); + Particles.deleteParticle(particleA); + Particles.deleteParticle(particleB); +} + +function printProperties(properties) { + for (var property in properties) { + if (properties.hasOwnProperty(property)) { + if (property == "position" || + property == "gravity" || + property == "velocity") { + print(property +": " + properties[property].x + ", " + properties[property].y + ", " + properties[property].z); + } else if (property == "color") { + print(property +": " + properties[property].red + ", " + + properties[property].green + ", " + properties[property].blue); + } else { + print(property +": " + properties[property]) + } + } + } +} + +function findParticles() { + + // run for a while, then clean up + // stop it... + if (iteration >= 100) { + print("calling Agent.stop()"); + Agent.stop(); + } + + print("--------------------------"); + print("iteration =" + iteration); + iteration++; + + // Check to see if we've been notified of the actual identity of the particles we created + if (!particleA.isKnownID) { + var identifyA = Particles.identifyParticle(particleA); + if (identifyA.isKnownID) { + particleA = identifyA; + print(">>>> identified particleA.id = " + particleA.id); + } + } + if (!particleB.isKnownID) { + var identifyB = Particles.identifyParticle(particleB); + if (identifyB.isKnownID) { + particleB = identifyB; + print(">>>> identified particleB.id = " + particleB.id); + } + } + + // also check to see if we can "find" particles... + print("searching for particles at:" + searchAt.x + ", " + searchAt.y + ", " + searchAt.z + " radius:" + searchRadius); + var foundParticles = Particles.findParticles(searchAt, searchRadius); + print("found this many particles: "+ foundParticles.length); + for (var i = 0; i < foundParticles.length; i++) { + print(" particle[" + i + "].id:" + foundParticles[i].id); + if (foundParticles[i].id == particleA.id) { + print(">>>> found particleA!!"); + var propertiesA = Particles.getParticleProperties(particleA); + printProperties(propertiesA); + } + if (foundParticles[i].id == particleB.id) { + print(">>>> found particleB!!"); + } + } + // move search + searchAt.x += moveSearch.x; + searchAt.y += moveSearch.y; + searchAt.z += moveSearch.z; + searchRadius += searchRadiusChange; + + // after we've searched for 80 iterations, change our search mechanism to be from the center with expanding radius + if (iteration == 80) { + searchAt = { x: 3.5, y: 0, z: 3.5}; + moveSearch = { x: 0, y: 0, z: 0}; + searchRadius = 0.5; + searchRadiusChange = 0.5; + } + +} + + +// register the call back so it fires before each data send +Agent.willSendVisualDataCallback.connect(findParticles); + +// register our scriptEnding callback +Agent.scriptEnding.connect(scriptEnding); diff --git a/examples/particleBird.js b/examples/particleBird.js new file mode 100644 index 0000000000..6a4cf79a40 --- /dev/null +++ b/examples/particleBird.js @@ -0,0 +1,174 @@ +// +// particleBird.js +// hifi +// +// This sample script moves a voxel around like a bird and sometimes makes tweeting noises +// +function vLength(v) { + return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); +} +function printVector(v) { + print(v.x + ", " + v.y + ", " + v.z + "\n"); +} +// Create a random vector with individual lengths between a,b +function randVector(a, b) { + var rval = { x: a + Math.random() * (b - a), y: a + Math.random() * (b - a), z: a + Math.random() * (b - a) }; + return rval; +} +function vMinus(a, b) { + var rval = { x: a.x - b.x, y: a.y - b.y, z: a.z - b.z }; + return rval; +} +function vPlus(a, b) { + var rval = { x: a.x + b.x, y: a.y + b.y, z: a.z + b.z }; + return rval; +} +function vCopy(a, b) { + a.x = b.x; + a.y = b.y; + a.z = b.z; + return; +} +// Returns a vector which is fraction of the way between a and b +function vInterpolate(a, b, fraction) { + var rval = { x: a.x + (b.x - a.x) * fraction, y: a.y + (b.y - a.y) * fraction, z: a.z + (b.z - a.z) * fraction }; + return rval; +} + +// Decide what kind of bird we are +var tweet; +var color; +var size; +var which = Math.random(); +if (which < 0.2) { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); + color = { r: 100, g: 50, b: 120 }; + size = 0.08; +} else if (which < 0.4) { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/rosyfacedlovebird.raw"); + color = { r: 100, g: 150, b: 75 }; + size = 0.09; +} else if (which < 0.6) { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/saysphoebe.raw"); + color = { r: 84, g: 121, b: 36 }; + size = 0.05; +} else if (which < 0.8) { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/mexicanWhipoorwill.raw"); + color = { r: 23, g: 197, b: 230 }; + size = 0.12; +} else { + tweet = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/westernscreechowl.raw"); + color = { r: 50, g: 67, b: 144 }; + size = 0.15; +} + +var startTimeInSeconds = new Date().getTime() / 1000; + +var birdLifetime = 20; // lifetime of the bird in seconds! +var position = { x: 0, y: 0, z: 0 }; +var targetPosition = { x: 0, y: 0, z: 0 }; +var range = 1.0; // Over what distance in meters do you want your bird to fly around +var frame = 0; +var moving = false; +var tweeting = 0; +var moved = false; +var CHANCE_OF_MOVING = 0.05; +var CHANCE_OF_TWEETING = 0.05; +var START_HEIGHT_ABOVE_ME = 1.5; +var myPosition = MyAvatar.position; +var properties = { + lifetime: birdLifetime, + position: { x: myPosition.x, y: myPosition.y + START_HEIGHT_ABOVE_ME, z: myPosition.z }, + velocity: { x: 0, y: 0, z: 0 }, + gravity: { x: 0, y: 0, z: 0 }, + radius : 0.1, + color: { red: 0, + green: 255, + blue: 0 } +}; +var range = 1.0; // Distance around avatar where I can move +// Create the actual bird +var particleID = Particles.addParticle(properties); +function moveBird() { + + // check to see if we've been running long enough that our bird is dead + var nowTimeInSeconds = new Date().getTime() / 1000; + if ((nowTimeInSeconds - startTimeInSeconds) >= birdLifetime) { + print("our bird is dying, stop our script"); + Agent.stop(); + return; + } + + myPosition = MyAvatar.position; + frame++; + if (frame % 3 == 0) { + // Tweeting behavior + if (tweeting == 0) { + if (Math.random() < CHANCE_OF_TWEETING) { + //print("tweet!" + "\n"); + var options = new AudioInjectionOptions(); + options.position = position; + options.volume = 0.75; + Audio.playSound(tweet, options); + tweeting = 10; + } + } else { + tweeting -= 1; + } + // Moving behavior + if (moving == false) { + if (Math.random() < CHANCE_OF_MOVING) { + targetPosition = randVector(- range, range); + targetPosition = vPlus(targetPosition, myPosition); + if (targetPosition.x < 0) { + targetPosition.x = 0; + } + if (targetPosition.y < 0) { + targetPosition.y = 0; + } + if (targetPosition.z < 0) { + targetPosition.z = 0; + } + if (targetPosition.x > TREE_SCALE) { + targetPosition.x = TREE_SCALE; + } + if (targetPosition.y > TREE_SCALE) { + targetPosition.y = TREE_SCALE; + } + if (targetPosition.z > TREE_SCALE) { + targetPosition.z = TREE_SCALE; + } + //printVector(position); + moving = true; + } + } + if (moving) { + position = vInterpolate(position, targetPosition, 0.5); + if (vLength(vMinus(position, targetPosition)) < (size / 5.0)) { + moved = false; + moving = false; + } else { + moved = true; + } + } + if (moved || (tweeting > 0)) { + if (tweeting > 0) { + var newProperties = { + position: position, + radius : size * 1.5, + color: { red: Math.random() * 255, green: 0, blue: 0 } + }; + } else { + var newProperties = { + position: position, + radius : size, + color: { red: color.r, green: color.g, blue: color.b } + }; + } + Particles.editParticle(particleID, newProperties); + moved = false; + } + } +} +// register the call back so it fires before each data send +Agent.willSendVisualDataCallback.connect(moveBird); diff --git a/interface/resources/config/config.json b/interface/resources/config/config.json new file mode 100644 index 0000000000..8f7b3bb327 --- /dev/null +++ b/interface/resources/config/config.json @@ -0,0 +1,19 @@ +{ + "importFormats" : [ + { + "extension": "png", + "description": "Square PNG", + "icon": "raster.svg" + }, + { + "extension": "svo", + "description": "Sparse Voxel Octree Files", + "icon": "voxel.svg" + }, + { + "extension": "schematic", + "description": "Schematic Files", + "icon": "voxel.svg" + } + ] +} diff --git a/interface/resources/icons/computer.svg b/interface/resources/icons/computer.svg new file mode 100644 index 0000000000..f7e792afb5 --- /dev/null +++ b/interface/resources/icons/computer.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/interface/resources/icons/desktop.svg b/interface/resources/icons/desktop.svg new file mode 100644 index 0000000000..acf440f361 --- /dev/null +++ b/interface/resources/icons/desktop.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/documents.svg b/interface/resources/icons/documents.svg new file mode 100644 index 0000000000..e3a68301a6 --- /dev/null +++ b/interface/resources/icons/documents.svg @@ -0,0 +1,15 @@ + + + + + + + + + + diff --git a/interface/resources/icons/file.svg b/interface/resources/icons/file.svg new file mode 100644 index 0000000000..4f3799b100 --- /dev/null +++ b/interface/resources/icons/file.svg @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/interface/resources/icons/folder.svg b/interface/resources/icons/folder.svg new file mode 100644 index 0000000000..1c486211f4 --- /dev/null +++ b/interface/resources/icons/folder.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/interface/resources/icons/home.svg b/interface/resources/icons/home.svg new file mode 100644 index 0000000000..58983e71d5 --- /dev/null +++ b/interface/resources/icons/home.svg @@ -0,0 +1,16 @@ + + + + + + + + + + diff --git a/interface/resources/icons/raster.svg b/interface/resources/icons/raster.svg new file mode 100644 index 0000000000..c6117137a9 --- /dev/null +++ b/interface/resources/icons/raster.svg @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/interface/resources/icons/voxel.svg b/interface/resources/icons/voxel.svg new file mode 100644 index 0000000000..77cc82307f --- /dev/null +++ b/interface/resources/icons/voxel.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + diff --git a/interface/resources/sounds/snap.wav b/interface/resources/sounds/snap.wav new file mode 100644 index 0000000000..bb562e1db9 Binary files /dev/null and b/interface/resources/sounds/snap.wav differ diff --git a/interface/resources/styles/import_dialog.qss b/interface/resources/styles/import_dialog.qss new file mode 100644 index 0000000000..dd9761b7ed --- /dev/null +++ b/interface/resources/styles/import_dialog.qss @@ -0,0 +1,88 @@ +/* +* import_dialog.qss +* hifi +* +* Created by Stojce on 1/5/2014. +* Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +*/ + +* { + font-family: Helvetica, Arial, sans-serif; +} + +QLabel { + font-size: 16px; + color: #333333; +} + +QLabel#infoLabel { + padding: 7px 0 0 7px; + color: #666666; +} + +QPushButton { + border-width: 0; + border-radius: 9px; + font-size: 18px; + padding: 17px 0px 15px; + width: 107px; + margin-top: 20px; + margin-bottom: 18px; +} + +QPushButton#importButton { + color: #FFFFFF; + font-weight: bold; + margin-right: 6px; +} + +QPushButton#importButton:enabled { + background: #333333; +} + +QPushButton#importButton:!enabled { + background: rgba(50, 50, 50, 0.5); +} + +QPushButton#cancelButton { + color: #333333; + background-color: #FFFFFF; + width: 74px; + margin-right: 11px; +} + +QSidebar, QTreeView { + border: 1px solid #C5C5C5; + font-size: 14px; + margin: 0px; + selection-background-color: #BDE4E3; + selection-color: #333333; + +} + +QTreeView { + border-left: none; +} + +QSplitter::handle, QDialog { + background-color: white; +} + +QTreeView QHeaderView { + background: white; +} + +QTreeView QHeaderView:section { + border-left: none; + border-top: none; + border-bottom: 1px solid #C5C5C5; + border-right: 1px solid #C5C5C5; + background: white; + color: #666666; + padding: 10px 20px; +} + +QSidebar::item, +QTreeView::item { + padding: 5px 0 4px; +} diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 556bb40bcf..cda34f727c 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -47,6 +47,7 @@ #include #include #include +#include #include #include @@ -69,6 +70,7 @@ #include "renderer/ProgramObject.h" #include "ui/TextRenderer.h" #include "InfoView.h" +#include "ui/Snapshot.h" using namespace std; @@ -97,6 +99,8 @@ const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml"; const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; +const int STATS_PELS_PER_LINE = 20; + void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString &message) { QString messageWithNewLine = message + "\n"; fprintf(stdout, "%s", messageWithNewLine.toLocal8Bit().constData()); @@ -107,6 +111,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QApplication(argc, argv), _window(new QMainWindow(desktop())), _glWidget(new GLCanvas()), + _statsExpanded(false), _nodeThread(new QThread(this)), _datagramProcessor(), _frameCount(0), @@ -249,6 +254,10 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : restoreSizeAndPosition(); loadScripts(); + + QFontDatabase fontDatabase; + fontDatabase.addApplicationFont("resources/styles/Inconsolata.otf"); + _window->setVisible(true); _glWidget->setFocusPolicy(Qt::StrongFocus); _glWidget->setFocus(); @@ -764,6 +773,8 @@ void Application::keyPressEvent(QKeyEvent* event) { _voxels.collectStatsForTreesAndVBOs(); } else if (isShifted && isMeta) { Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); + } else if (!isShifted && isMeta) { + takeSnapshot(); } else if (_nudgeStarted) { if (_lookingAlongX) { if (_lookingAwayFromOrigin) { @@ -1258,6 +1269,9 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { _mouseY = event->y(); _mousePressed = false; checkBandwidthMeterClick(); + if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + checkStatsClick(); + } _pieMenu.mouseReleaseEvent(_mouseX, _mouseY); } @@ -1718,7 +1732,7 @@ void Application::init() { _sharedVoxelSystem.changeTree(&_clipboard); delete tmpTree; - _voxelImporter.init(); + _voxelImporter.init(_settings); _environment.init(); @@ -2962,9 +2976,11 @@ void Application::displayOverlay() { } if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + displayStatsBackground(0x33333399, 0, _glWidget->height() - 68, 296, 68); _audio.render(_glWidget->width(), _glWidget->height()); if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) { - _audioScope.render(45, _glWidget->height() - 200); + int oscilloscopeTop = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) ? 130 : 25; + _audioScope.render(25, oscilloscopeTop); } } @@ -3019,17 +3035,9 @@ void Application::displayOverlay() { displayStats(); // Bandwidth meter if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) { + displayStatsBackground(0x33333399, _glWidget->width() - 296, _glWidget->height() - 68, 296, 68); _bandwidthMeter.render(_glWidget->width(), _glWidget->height()); } - // Stats at upper right of screen about who domain server is telling us about - glPointSize(1.0f); - char nodes[100]; - - int totalAvatars = _avatarManager.size(); - int totalServers = NodeList::getInstance()->size(); - - sprintf(nodes, "Servers: %d, Avatars: %d\n", totalServers, totalAvatars); - drawtext(_glWidget->width() - 150, 20, 0.10f, 0, 1.0f, 0, nodes, 1, 0, 0); } // testing rendering coverage map @@ -3051,8 +3059,12 @@ void Application::displayOverlay() { char frameTimer[10]; uint64_t mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000)); - drawtext(_glWidget->width() - 100, _glWidget->height() - 20, 0.30f, 0, 1.0f, 0, frameTimer, 0, 0, 0); - drawtext(_glWidget->width() - 102, _glWidget->height() - 22, 0.30f, 0, 1.0f, 0, frameTimer, 1, 1, 1); + int timerBottom = + (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && + Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) + ? 80 : 20; + drawtext(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 0, 1.0f, 0, frameTimer, 0, 0, 0); + drawtext(_glWidget->width() - 102, _glWidget->height() - timerBottom - 2, 0.30f, 0, 1.0f, 0, frameTimer, 1, 1, 1); } _palette.render(_glWidget->width(), _glWidget->height()); @@ -3110,15 +3122,73 @@ void Application::displayOverlay() { glPopMatrix(); } +// translucent background box that makes stats more readable +void Application::displayStatsBackground(unsigned int rgba, int x, int y, int width, int height) { + glBegin(GL_QUADS); + glColor4f(((rgba >> 24) & 0xff) / 255.0f, + ((rgba >> 16) & 0xff) / 255.0f, + ((rgba >> 8) & 0xff) / 255.0f, + (rgba & 0xff) / 255.0f); + glVertex3f(x, y, 0); + glVertex3f(x + width, y, 0); + glVertex3f(x + width, y + height, 0); + glVertex3f(x , y + height, 0); + glEnd(); + glColor4f(1, 1, 1, 1); +} +// display expanded or contracted stats void Application::displayStats() { - int statsVerticalOffset = 8; - const int PELS_PER_LINE = 15; - char stats[200]; - statsVerticalOffset += PELS_PER_LINE; - sprintf(stats, "%3.0f FPS, %d Pkts/sec, %3.2f Mbps ", - _fps, _packetsPerSecond, (float)_bytesPerSecond * 8.f / 1000000.f); - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, stats); + unsigned int backgroundColor = 0x33333399; + int verticalOffset = 0, horizontalOffset = 0, lines = 0; + bool mirrorEnabled = Menu::getInstance()->isOptionChecked(MenuOption::Mirror); + + QLocale locale(QLocale::English); + std::stringstream voxelStats; + + glPointSize(1.0f); + + int totalAvatars = 0, totalServers = 0; + foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + node->getType() == NODE_TYPE_AGENT ? totalAvatars++ : totalServers++; + } + + if (mirrorEnabled) { + horizontalOffset += MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; + } + + lines = _statsExpanded ? 5 : 3; + displayStatsBackground(backgroundColor, horizontalOffset, 0, 165, lines * STATS_PELS_PER_LINE + 10); + horizontalOffset += 5; + + char serverNodes[30]; + sprintf(serverNodes, "Servers: %d", totalServers); + char avatarNodes[30]; + sprintf(avatarNodes, "Avatars: %d", totalAvatars); + char framesPerSecond[30]; + sprintf(framesPerSecond, "Framerate: %3.0f FPS", _fps); + + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0f, 2, serverNodes, .93f, .93f, .93f); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0f, 2, avatarNodes, .93f, .93f, .93f); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, framesPerSecond, .93f, .93f, .93f); + + if (_statsExpanded) { + char packetsPerSecond[30]; + sprintf(packetsPerSecond, "Pkts/sec: %d", _packetsPerSecond); + char averageMegabitsPerSecond[30]; + sprintf(averageMegabitsPerSecond, "Avg Mbps: %3.2f", (float)_bytesPerSecond * 8.f / 1000000.f); + + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, packetsPerSecond, .93f, .93f, .93f); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, averageMegabitsPerSecond, .93f, .93f, .93f); + } + + verticalOffset = 0; + horizontalOffset += 161; if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { int pingAudio = 0, pingAvatar = 0, pingVoxel = 0, pingVoxelMax = 0; @@ -3130,7 +3200,6 @@ void Application::displayStats() { pingAudio = audioMixerNode ? audioMixerNode->getPingMs() : 0; pingAvatar = avatarMixerNode ? avatarMixerNode->getPingMs() : 0; - // Now handle voxel servers, since there could be more than one, we average their ping times unsigned long totalPingVoxel = 0; int voxelServerCount = 0; @@ -3149,35 +3218,115 @@ void Application::displayStats() { pingVoxel = totalPingVoxel/voxelServerCount; } - char pingStats[200]; - statsVerticalOffset += PELS_PER_LINE; - sprintf(pingStats, "Ping audio/avatar/voxel: %d / %d / %d avg %d max ", pingAudio, pingAvatar, pingVoxel, pingVoxelMax); - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, pingStats); + lines = _statsExpanded ? 4 : 3; + displayStatsBackground(backgroundColor, horizontalOffset, 0, 175, lines * STATS_PELS_PER_LINE + 10); + horizontalOffset += 5; + + char audioPing[30]; + sprintf(audioPing, "Audio ping: %d", pingAudio); + char avatarPing[30]; + sprintf(avatarPing, "Avatar ping: %d", pingAvatar); + char voxelAvgPing[30]; + sprintf(voxelAvgPing, "Voxel avg ping: %d", pingVoxel); + + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, audioPing, .93f, .93f, .93f); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarPing, .93f, .93f, .93f); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, voxelAvgPing, .93f, .93f, .93f); + + if (_statsExpanded) { + char voxelMaxPing[30]; + sprintf(voxelMaxPing, "Voxel max ping: %d", pingVoxelMax); + + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, voxelMaxPing, .93f, .93f, .93f); + } + + verticalOffset = 0; + horizontalOffset += 171; } - char avatarStats[200]; - statsVerticalOffset += PELS_PER_LINE; glm::vec3 avatarPos = _myAvatar.getPosition(); - sprintf(avatarStats, "Avatar: pos %.3f, %.3f, %.3f, vel %.1f, yaw = %.2f", avatarPos.x, avatarPos.y, avatarPos.z, glm::length(_myAvatar.getVelocity()), _myAvatar.getBodyYaw()); - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarStats); - SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER); - char avatarMixerStats[200]; - if (avatarMixer) { - sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps", - roundf(avatarMixer->getAverageKilobitsPerSecond()), - roundf(avatarMixer->getAveragePacketsPerSecond())); + lines = _statsExpanded ? 4 : 3; + displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - (mirrorEnabled ? 301 : 411) - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); + horizontalOffset += 5; + + char avatarPosition[200]; + if (mirrorEnabled) { + // shorthand formatting + sprintf(avatarPosition, "Pos: %.0f,%.0f,%.0f", avatarPos.x, avatarPos.y, avatarPos.z); } else { - sprintf(avatarMixerStats, "No Avatar Mixer"); + // longhand way + sprintf(avatarPosition, "Position: %.3f, %.3f, %.3f", avatarPos.x, avatarPos.y, avatarPos.z); + } + char avatarVelocity[30]; + sprintf(avatarVelocity, "Velocity: %.1f", glm::length(_myAvatar.getVelocity())); + char avatarBodyYaw[30]; + sprintf(avatarBodyYaw, "Yaw: %.2f", _myAvatar.getBodyYaw()); + char avatarMixerStats[200]; + + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarPosition, .93f, .93f, .93f); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarVelocity, .93f, .93f, .93f); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarBodyYaw, .93f, .93f, .93f); + + if (_statsExpanded) { + SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NODE_TYPE_AVATAR_MIXER); + if (avatarMixer) { + sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps", + roundf(avatarMixer->getAverageKilobitsPerSecond()), + roundf(avatarMixer->getAveragePacketsPerSecond())); + } else { + sprintf(avatarMixerStats, "No Avatar Mixer"); + } + + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, avatarMixerStats, .93f, .93f, .93f); } - statsVerticalOffset += PELS_PER_LINE; - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, avatarMixerStats); + verticalOffset = 0; + horizontalOffset = _glWidget->width() - (mirrorEnabled ? 300 : 410); - // Used for formatting voxel stats details - statsVerticalOffset += PELS_PER_LINE; // skip a line for voxels - QLocale locale(QLocale::English); - std::stringstream voxelStats; + lines = _statsExpanded ? 11 : 3; + displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); + horizontalOffset += 5; + + if (_statsExpanded) { + // Local Voxel Memory Usage + voxelStats.str(""); + voxelStats << "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB"; + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); + + voxelStats.str(""); + voxelStats << + "Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB / " << + "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB"; + if (_voxels.hasVoxelMemoryUsageGPU()) { + voxelStats << " / GPU: " << _voxels.getVoxelMemoryUsageGPU() / 1000000.f << "MB"; + } + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); + + // Voxel Rendering + voxelStats.str(""); + voxelStats.precision(4); + voxelStats << "Voxel Rendering Slots Max: " << _voxels.getMaxVoxels() / 1000.f << "K"; + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); + } + + voxelStats.str(""); + voxelStats.precision(4); + voxelStats << "Drawn: " << _voxels.getVoxelsWritten() / 1000.f << "K " << + "Abandoned: " << _voxels.getAbandonedVoxels() / 1000.f << "K "; + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); // iterate all the current voxel stats, and list their sending modes, and total voxel counts std::stringstream sendingMode(""); @@ -3191,93 +3340,50 @@ void Application::displayStats() { //const QUuid& uuid = i->first; VoxelSceneStats& stats = i->second; serverCount++; - if (serverCount > 1) { - sendingMode << ","; - } - if (stats.isMoving()) { - sendingMode << "M"; - movingServerCount++; - } else { - sendingMode << "S"; + if (_statsExpanded) { + if (serverCount > 1) { + sendingMode << ","; + } + if (stats.isMoving()) { + sendingMode << "M"; + movingServerCount++; + } else { + sendingMode << "S"; + } } // calculate server node totals totalNodes += stats.getTotalElements(); - totalInternal += stats.getTotalInternal(); - totalLeaves += stats.getTotalLeaves(); + if (_statsExpanded) { + totalInternal += stats.getTotalInternal(); + totalLeaves += stats.getTotalLeaves(); + } } - if (serverCount == 0) { - sendingMode << "---"; + if (_statsExpanded) { + if (serverCount == 0) { + sendingMode << "---"; + } + sendingMode << "] " << serverCount << " servers"; + if (movingServerCount > 0) { + sendingMode << " "; + } else { + sendingMode << " "; + } + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)sendingMode.str().c_str(), .93f, .93f, .93f); } - sendingMode << "] " << serverCount << " servers"; - if (movingServerCount > 0) { - sendingMode << " "; - } else { - sendingMode << " "; - } - - QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' '); - QString serversInternalString = locale.toString((uint)totalInternal); - QString serversLeavesString = locale.toString((uint)totalLeaves); - - // Server Voxels - voxelStats.str(""); - voxelStats << - "Server Voxels Total: " << serversTotalString.toLocal8Bit().constData() << " / " << - "Internal: " << serversInternalString.toLocal8Bit().constData() << " / " << - "Leaves: " << serversLeavesString.toLocal8Bit().constData() << ""; - statsVerticalOffset += PELS_PER_LINE; - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str()); - - unsigned long localTotal = VoxelTreeElement::getNodeCount(); - unsigned long localInternal = VoxelTreeElement::getInternalNodeCount(); - unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount(); - QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' '); - QString localInternalString = locale.toString((uint)localInternal); - QString localLeavesString = locale.toString((uint)localLeaves); - - // Local Voxels - voxelStats.str(""); - voxelStats << - "Local Voxels Total: " << localTotalString.toLocal8Bit().constData() << " / " << - "Internal: " << localInternalString.toLocal8Bit().constData() << " / " << - "Leaves: " << localLeavesString.toLocal8Bit().constData() << ""; - statsVerticalOffset += PELS_PER_LINE; - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str()); - - // Local Voxel Memory Usage - voxelStats.str(""); - voxelStats << - "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB " - "Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB " << - "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB "; - if (_voxels.hasVoxelMemoryUsageGPU()) { - voxelStats << "GPU: " << _voxels.getVoxelMemoryUsageGPU() / 1000000.f << "MB "; - } - statsVerticalOffset += PELS_PER_LINE; - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str()); - - // Voxel Rendering - voxelStats.str(""); - voxelStats.precision(4); - voxelStats << "Voxel Rendering Slots " << - "Max: " << _voxels.getMaxVoxels() / 1000.f << "K " << - "Drawn: " << _voxels.getVoxelsWritten() / 1000.f << "K " << - "Abandoned: " << _voxels.getAbandonedVoxels() / 1000.f << "K "; - statsVerticalOffset += PELS_PER_LINE; - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str()); - - // draw Sending mode AFTER server node stats - statsVerticalOffset += PELS_PER_LINE; - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)sendingMode.str().c_str()); // Incoming packets - voxelStats.str(""); int voxelPacketsToProcess = _voxelProcessor.packetsToProcessCount(); - QString packetsString = locale.toString((int)voxelPacketsToProcess); - QString maxString = locale.toString((int)_recentMaxPackets); - voxelStats << "Voxel Packets to Process: " << packetsString.toLocal8Bit().constData() - << " [Recent Max: " << maxString.toLocal8Bit().constData() << "]"; + if (_statsExpanded) { + voxelStats.str(""); + QString packetsString = locale.toString((int)voxelPacketsToProcess); + QString maxString = locale.toString((int)_recentMaxPackets); + voxelStats << "Voxel Packets to Process: " << packetsString.toLocal8Bit().constData() + << " [Recent Max: " << maxString.toLocal8Bit().constData() << "]"; + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); + } if (_resetRecentMaxPacketsSoon && voxelPacketsToProcess > 0) { _recentMaxPackets = 0; @@ -3290,8 +3396,109 @@ void Application::displayStats() { _recentMaxPackets = voxelPacketsToProcess; } } - statsVerticalOffset += PELS_PER_LINE; - drawtext(10, statsVerticalOffset, 0.10f, 0, 1.0, 0, (char*)voxelStats.str().c_str()); + + verticalOffset += (_statsExpanded ? STATS_PELS_PER_LINE : 0); + + QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' '); + + // Server Voxels + voxelStats.str(""); + voxelStats << "Server voxels: " << serversTotalString.toLocal8Bit().constData(); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); + + if (_statsExpanded) { + QString serversInternalString = locale.toString((uint)totalInternal); + QString serversLeavesString = locale.toString((uint)totalLeaves); + + voxelStats.str(""); + voxelStats << + "Internal: " << serversInternalString.toLocal8Bit().constData() << " " << + "Leaves: " << serversLeavesString.toLocal8Bit().constData() << ""; + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); + } + + unsigned long localTotal = VoxelTreeElement::getNodeCount(); + QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' '); + + // Local Voxels + voxelStats.str(""); + voxelStats << "Local voxels: " << localTotalString.toLocal8Bit().constData(); + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); + + if (_statsExpanded) { + unsigned long localInternal = VoxelTreeElement::getInternalNodeCount(); + unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount(); + QString localInternalString = locale.toString((uint)localInternal); + QString localLeavesString = locale.toString((uint)localLeaves); + + voxelStats.str(""); + voxelStats << + "Internal: " << localInternalString.toLocal8Bit().constData() << " " << + "Leaves: " << localLeavesString.toLocal8Bit().constData() << ""; + verticalOffset += STATS_PELS_PER_LINE; + drawtext(horizontalOffset, verticalOffset, 0.10f, 0, 1.0, 2, (char*)voxelStats.str().c_str(), .93f, .93f, .93f); + } +} + +// called on mouse click release +// check for clicks over stats in order to expand or contract them +void Application::checkStatsClick() { + if (0 != glm::compMax(glm::abs(glm::ivec2(_mouseX - _mouseDragStartedX, _mouseY - _mouseDragStartedY)))) { + // not worried about dragging on stats + return; + } + + int statsHeight = 0, statsWidth = 0, statsX = 0, statsY = 0, lines = 0; + + if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + statsX += MIRROR_VIEW_WIDTH; + } + + // top-left stats click + lines = _statsExpanded ? 5 : 3; + statsHeight = lines * STATS_PELS_PER_LINE + 10; + statsWidth = 165; + if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) { + toggleStatsExpanded(); + return; + } + + // ping stats click + lines = _statsExpanded ? 4 : 3; + statsX += statsWidth; + statsHeight = lines * STATS_PELS_PER_LINE + 10; + statsWidth = 175; + if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) { + toggleStatsExpanded(); + return; + } + + // top-center stats panel click + lines = _statsExpanded ? 4 : 3; + statsX += statsWidth; + statsHeight = lines * STATS_PELS_PER_LINE + 10; + statsWidth = _glWidget->width() - 411 - statsX; + if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) { + toggleStatsExpanded(); + return; + } + + // top-right stats click + lines = _statsExpanded ? 11 : 3; + statsX = _glWidget->width() - 410; + statsHeight = lines * STATS_PELS_PER_LINE + 10; + statsWidth = _glWidget->width() - statsX; + if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) { + toggleStatsExpanded(); + return; + } +} + +void Application::toggleStatsExpanded() { + _statsExpanded = !_statsExpanded; } void Application::renderThrustAtVoxel(const glm::vec3& thrust) { @@ -4123,3 +4330,14 @@ void Application::skipVersion(QString latestVersion) { skipFile.seek(0); skipFile.write(latestVersion.toStdString().c_str()); } + +void Application::takeSnapshot() { + switchToResourcesParentIfRequired(); + QMediaPlayer* player = new QMediaPlayer(); + QFileInfo inf = QFileInfo("resources/sounds/snap.wav"); + player->setMedia(QUrl::fromLocalFile(inf.absoluteFilePath())); + player->play(); + + Snapshot::saveSnapshot(_glWidget, _profile.getUsername(), _myAvatar.getPosition()); +} + diff --git a/interface/src/Application.h b/interface/src/Application.h index db93f7c67f..3645355b89 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -305,7 +305,11 @@ private: void updateShadowMap(); void displayOverlay(); + void displayStatsBackground(unsigned int rgba, int x, int y, int width, int height); void displayStats(); + void checkStatsClick(); + void toggleStatsExpanded(); + void renderAvatars(bool forceRenderHead, bool selfAvatarOnly = false); void renderViewFrustum(ViewFrustum& viewFrustum); void checkBandwidthMeterClick(); @@ -326,6 +330,7 @@ private: QMainWindow* _window; QGLWidget* _glWidget; + bool _statsExpanded; BandwidthMeter _bandwidthMeter; QThread* _nodeThread; @@ -498,6 +503,7 @@ private: void checkVersion(); void displayUpdateDialog(); bool shouldSkipVersion(QString latestVersion); + void takeSnapshot(); }; #endif /* defined(__interface__Application__) */ diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 82c7360d77..752ba64910 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -39,8 +39,9 @@ static const float AUDIO_CALLBACK_MSECS = (float) NETWORK_BUFFER_LENGTH_SAMPLES_ // Mute icon configration static const int ICON_SIZE = 24; -static const int ICON_LEFT = 20; -static const int BOTTOM_PADDING = 110; +static const int ICON_LEFT = 0; +static const int ICON_TOP = 115; +static const int ICON_TOP_MIRROR = 220; Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* parent) : AbstractAudioInterface(parent), @@ -541,13 +542,13 @@ void Audio::render(int screenWidth, int screenHeight) { if (_audioInput) { glLineWidth(2.0); glBegin(GL_LINES); - glColor3f(1,1,1); + glColor3f(.93f, .93f, .93f); int startX = 20.0; int currentX = startX; - int topY = screenHeight - 40; - int bottomY = screenHeight - 20; - float frameWidth = 20.0; + int topY = screenHeight - 45; + int bottomY = screenHeight - 25; + float frameWidth = 23.0; float halfY = topY + ((bottomY - topY) / 2.0); // draw the lines for the base of the ring buffer @@ -576,9 +577,9 @@ void Audio::render(int screenWidth, int screenHeight) { float msLeftForAudioOutput = (secondsLeftForAudioOutput + secondsLeftForRingBuffer) * 1000; if (_numFramesDisplayStarve == 0) { - glColor3f(0, 1, 0); + glColor3f(0, .8f, .4f); } else { - glColor3f(0.5 + (_numFramesDisplayStarve / 20.0f), 0, 0); + glColor3f(0.5 + (_numFramesDisplayStarve / 20.0f), .2f, .4f); _numFramesDisplayStarve--; } @@ -589,44 +590,44 @@ void Audio::render(int screenWidth, int screenHeight) { } glBegin(GL_QUADS); - glVertex2f(startX, topY + 2); - glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth, topY + 2); - glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth, bottomY - 2); - glVertex2f(startX, bottomY - 2); + glVertex2f(startX + 1, topY + 2); + glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 1, topY + 2); + glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 1, bottomY - 2); + glVertex2f(startX + 1, bottomY - 2); glEnd(); // Show a yellow bar with the averaged msecs latency you are hearing (from time of packet receipt) - glColor3f(1,1,0); + glColor3f(1, .8f, 0); glBegin(GL_QUADS); - glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 2, topY - 2); - glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 2, topY - 2); - glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 2, bottomY + 2); - glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 2, bottomY + 2); + glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 1, topY - 2); + glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 3, topY - 2); + glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth + 3, bottomY + 2); + glVertex2f(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 1, bottomY + 2); glEnd(); char out[40]; sprintf(out, "%3.0f\n", _averagedLatency); - drawtext(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 10, topY - 9, 0.10f, 0, 1, 0, out, 1,1,0); + drawtext(startX + _averagedLatency / AUDIO_CALLBACK_MSECS * frameWidth - 10, topY - 9, 0.10f, 0, 1, 2, out, 1, .8f, 0); // Show a red bar with the 'start' point of one frame plus the jitter buffer - glColor3f(1, 0, 0); + glColor3f(1, .2f, .4f); int jitterBufferPels = (1.f + (float)getJitterBufferSamples() / (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL) * frameWidth; sprintf(out, "%.0f\n", getJitterBufferSamples() / SAMPLE_RATE * 1000.f); - drawtext(startX + jitterBufferPels - 5, topY - 9, 0.10f, 0, 1, 0, out, 1, 0, 0); + drawtext(startX + jitterBufferPels - 5, topY - 9, 0.10f, 0, 1, 2, out, 1, .2f, .4f); sprintf(out, "j %.1f\n", _measuredJitter); if (Menu::getInstance()->getAudioJitterBufferSamples() == 0) { - drawtext(startX + jitterBufferPels - 5, bottomY + 12, 0.10f, 0, 1, 0, out, 1, 0, 0); + drawtext(startX + jitterBufferPels - 5, bottomY + 12, 0.10f, 0, 1, 2, out, 1, .2f, .4f); } else { - drawtext(startX, bottomY + 12, 0.10f, 0, 1, 0, out, 1, 0, 0); + drawtext(startX, bottomY + 12, 0.10f, 0, 1, 2, out, 1, .2f, .4f); } glBegin(GL_QUADS); - glVertex2f(startX + jitterBufferPels - 2, topY - 2); - glVertex2f(startX + jitterBufferPels + 2, topY - 2); - glVertex2f(startX + jitterBufferPels + 2, bottomY + 2); - glVertex2f(startX + jitterBufferPels - 2, bottomY + 2); + glVertex2f(startX + jitterBufferPels - 1, topY - 2); + glVertex2f(startX + jitterBufferPels + 3, topY - 2); + glVertex2f(startX + jitterBufferPels + 3, bottomY + 2); + glVertex2f(startX + jitterBufferPels - 1, bottomY + 2); glEnd(); } @@ -732,11 +733,13 @@ void Audio::handleAudioByteArray(const QByteArray& audioByteArray) { void Audio::renderToolIcon(int screenHeight) { - _iconBounds = QRect(ICON_LEFT, screenHeight - BOTTOM_PADDING, ICON_SIZE, ICON_SIZE); + int iconTop = Menu::getInstance()->isOptionChecked(MenuOption::Mirror) ? ICON_TOP_MIRROR : ICON_TOP; + + _iconBounds = QRect(ICON_LEFT, iconTop, ICON_SIZE, ICON_SIZE); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_2D, _micTextureId); - glColor3f(1, 1, 1); + glColor3f(.93f, .93f, .93f); glBegin(GL_QUADS); glTexCoord2f(1, 1); diff --git a/interface/src/BandwidthMeter.cpp b/interface/src/BandwidthMeter.cpp index 94fb06a496..d8081e186f 100644 --- a/interface/src/BandwidthMeter.cpp +++ b/interface/src/BandwidthMeter.cpp @@ -14,21 +14,21 @@ namespace { // .cpp-local int const AREA_WIDTH = -280; // Width of the area used. Aligned to the right when negative. - int const AREA_HEIGHT = 40; // Height of the area used. Aligned to the bottom when negative. - int const BORDER_DISTANCE_HORIZ = -20; // Distance to edge of screen (use negative value when width is negative). - int const BORDER_DISTANCE_VERT = 40; // Distance to edge of screen (use negative value when height is negative). + int const AREA_HEIGHT = -40; // Height of the area used. Aligned to the bottom when negative. + int const BORDER_DISTANCE_HORIZ = -10; // Distance to edge of screen (use negative value when width is negative). + int const BORDER_DISTANCE_VERT = -15; // Distance to edge of screen (use negative value when height is negative). int SPACING_VERT_BARS = 2; // Vertical distance between input and output bar int SPACING_RIGHT_CAPTION_IN_OUT = 4; // IN/OUT <--> |######## : | int SPACING_LEFT_CAPTION_UNIT = 4; // |######## : | <--> UNIT int PADDING_HORIZ_VALUE = 2; // |<-->X.XX<:-># | - unsigned const COLOR_TEXT = 0xe0e0e0e0; // ^ ^ ^ ^ ^ ^ + unsigned const COLOR_TEXT = 0xedededff; // ^ ^ ^ ^ ^ ^ unsigned const COLOR_FRAME = 0xe0e0e0b0; // | | | unsigned const COLOR_INDICATOR = 0xc0c0c0b0; // | - char const* CAPTION_IN = "IN"; - char const* CAPTION_OUT = "OUT"; + char const* CAPTION_IN = "In"; + char const* CAPTION_OUT = "Out"; char const* CAPTION_UNIT = "Mbps"; double const UNIT_SCALE = 8000.0 / (1024.0 * 1024.0); // Bytes/ms -> Mbps @@ -38,13 +38,13 @@ namespace { // .cpp-local } BandwidthMeter::ChannelInfo BandwidthMeter::_CHANNELS[] = { - { "Audio" , "Kbps", 8000.0 / 1024.0, 0x40ff40d0 }, + { "Audio" , "Kbps", 8000.0 / 1024.0, 0x33cc99ff }, { "Avatars" , "Kbps", 8000.0 / 1024.0, 0xffef40c0 }, { "Voxels" , "Kbps", 8000.0 / 1024.0, 0xd0d0d0a0 } }; BandwidthMeter::BandwidthMeter() : - _textRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT), + _textRenderer(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false), _scaleMaxIndex(INITIAL_SCALE_MAXIMUM_INDEX) { _channels = static_cast( malloc(sizeof(_CHANNELS)) ); diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index 4c96dfcfa5..3c98b285a3 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -14,17 +14,17 @@ #include #include +const QString FILENAME_FORMAT = "hifi-log_%1_%2.txt"; +const QString DATETIME_FORMAT = "yyyy-MM-dd_hh.mm.ss"; +const QString LOGS_DIRECTORY = "Logs"; + FileLogger::FileLogger() : _logData(NULL) { setExtraDebugging(false); - _fileName = QStandardPaths::writableLocation(QStandardPaths::DataLocation); - QDir logDir(_fileName); - if (!logDir.exists(_fileName)) { - logDir.mkdir(_fileName); - } + _fileName = FileUtils::standardPath(LOGS_DIRECTORY); QHostAddress clientAddress = QHostAddress(getHostOrderLocalAddress()); QDateTime now = QDateTime::currentDateTime(); - _fileName.append(QString("/hifi-log_%1_%2.txt").arg(clientAddress.toString(), now.toString("yyyy-MM-dd_hh.mm.ss"))); + _fileName.append(QString(FILENAME_FORMAT).arg(clientAddress.toString(), now.toString(DATETIME_FORMAT))); } void FileLogger::addMessage(QString message) { @@ -40,5 +40,5 @@ void FileLogger::addMessage(QString message) { } void FileLogger::locateLog() { - FileUtils::LocateFile(_fileName); + FileUtils::locateFile(_fileName); } diff --git a/interface/src/ImportDialog.cpp b/interface/src/ImportDialog.cpp index 8851446f13..28e39f1abd 100644 --- a/interface/src/ImportDialog.cpp +++ b/interface/src/ImportDialog.cpp @@ -6,212 +6,148 @@ // Copyright (c) 2013 High Fidelity, Inc. All rights reserved. // #include "ImportDialog.h" -#include "Application.h" #include #include -#include +#include +#include +#include +#include -const QString WINDOW_NAME = QObject::tr("Import Voxels"); -const QString IMPORT_BUTTON_NAME = QObject::tr("Import"); -const QString IMPORT_TO_CLIPBOARD_CHECKBOX_STRING = QObject::tr("Import into clipboard"); -const QString PREVIEW_CHECKBOX_STRING = QObject::tr("Load preview"); -const QString IMPORT_FILE_TYPES = QObject::tr("Sparse Voxel Octree Files, " - "Square PNG, " - "Schematic Files " - "(*.svo *.png *.schematic)"); +const QString WINDOW_NAME = QObject::tr("Import Voxels"); +const QString IMPORT_BUTTON_NAME = QObject::tr("Import"); +const QString IMPORT_INFO = QObject::tr("Import %1 as voxels"); +const QString CANCEL_BUTTON_NAME = QObject::tr("Cancel"); +const QString INFO_LABEL_TEXT = QObject::tr("
" + "This will load the selected file into Hifi and allow you
" + "to place it with %1-V; you must be in select or
" + "add mode (S or V keys will toggle mode) to place.
"); const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); +const int SHORT_FILE_EXTENSION = 4; +const int SECOND_INDEX_LETTER = 1; +QIcon HiFiIconProvider::icon(QFileIconProvider::IconType type) const { -const glm::vec3 UP_VECT = glm::vec3(0, 1, 0); -const float ANGULAR_RATE = 0.02f; -const float VERTICAL_ANGLE = (float)M_PI_4 / 2.0f; -const float RETURN_RATE = 0.02f; -const float NEAR_CLIP = 0.5f; -const float FAR_CLIP = 10.0f; -const float FIELD_OF_VIEW = 60.0f; + switchToResourcesParentIfRequired(); -class GLWidget : public QGLWidget { -public: - GLWidget(QWidget* parent = NULL); - void setDraw(bool draw) {_draw = draw;} - void setTargetCenter(glm::vec3 targetCenter) { _targetCenter = targetCenter; } + // types + // Computer, Desktop, Trashcan, Network, Drive, Folder, File + QString typeString; -protected: - virtual void initializeGL(); - virtual void resizeGL(int width, int height); - virtual void paintGL(); + switch (type) { + case QFileIconProvider::Computer: + typeString = "computer"; + break; - void mousePressEvent(QMouseEvent* event); - void mouseMoveEvent(QMouseEvent* event); - void mouseReleaseEvent(QMouseEvent* event); + case QFileIconProvider::Desktop: + typeString = "desktop"; + break; -private: - VoxelSystem* _voxelSystem; + case QFileIconProvider::Trashcan: + case QFileIconProvider::Network: + case QFileIconProvider::Drive: + case QFileIconProvider::Folder: + typeString = "folder"; + break; - bool _draw; - - double _a; // horizontal angle of the camera to the center of the object - double _h; // vertical angle of the camera to the center of the object - glm::vec3 _targetCenter; - - bool _pressed; - int _mouseX; - int _mouseY; -}; - -GLWidget::GLWidget(QWidget *parent) - : QGLWidget(parent, Application::getInstance()->getGLWidget()), - _draw(false), - _a(0.0f), - _h(VERTICAL_ANGLE), - _targetCenter(0.5f, 0.5f, 0.5f), - _pressed(false), - _mouseX(0), - _mouseY(0) { - _voxelSystem = Application::getInstance()->getSharedVoxelSystem(); -} - -void GLWidget::initializeGL() { - glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - glShadeModel (GL_SMOOTH); - glEnable(GL_DEPTH_TEST); -} - -void GLWidget::resizeGL(int width, int height) { - glViewport(0, 0, width, height); - - glMatrixMode(GL_PROJECTION); - glLoadIdentity(); - gluPerspective(FIELD_OF_VIEW, - (float) width / height, - NEAR_CLIP, - FAR_CLIP); -} - -void GLWidget::paintGL() { - glEnable(GL_LINE_SMOOTH); - glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - if (!_pressed) { - _a += ANGULAR_RATE; - _h = (1.0f - RETURN_RATE) * _h + RETURN_RATE * VERTICAL_ANGLE; + default: + typeString = "file"; + break; } - gluLookAt(_targetCenter.x + (glm::length(_targetCenter) + NEAR_CLIP) * cos(_a), - _targetCenter.y + (glm::length(_targetCenter) + NEAR_CLIP) * sin(_h), - _targetCenter.z + (glm::length(_targetCenter) + NEAR_CLIP) * sin(_a), - _targetCenter.x, _targetCenter.y, _targetCenter.z, - UP_VECT.x, UP_VECT.y, UP_VECT.z); + return QIcon("resources/icons/" + typeString + ".svg"); +} +QIcon HiFiIconProvider::icon(const QFileInfo &info) const { + switchToResourcesParentIfRequired(); + const QString ext = info.suffix().toLower(); - if (_draw) { - glBegin(GL_LINES); - glColor3d(1, 1 ,1); - glVertex3d(0, 0, 0); - glVertex3d(1, 0, 0); - - glVertex3d(0, 0, 0); - glVertex3d(0, 1, 0); - - glVertex3d(0, 0, 0); - glVertex3d(0, 0, 1); - - - glColor3d(0.4f, 0.4f ,0.4f); - glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 2 * _targetCenter.z); - glVertex3d(0 , 2 * _targetCenter.y, 2 * _targetCenter.z); - - glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 2 * _targetCenter.z); - glVertex3d(2 * _targetCenter.x, 0 , 2 * _targetCenter.z); - - glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 2 * _targetCenter.z); - glVertex3d(2 * _targetCenter.x, 2 * _targetCenter.y, 0 ); - glEnd(); - - glScalef(1.0f / TREE_SCALE, 1.0f / TREE_SCALE, 1.0f / TREE_SCALE); - _voxelSystem->render(false); + if (info.isDir()) { + if (info.absoluteFilePath() == QDir::homePath()) { + return QIcon("resources/icons/home.svg"); + } else if (info.absoluteFilePath() == DESKTOP_LOCATION) { + return QIcon("resources/icons/desktop.svg"); + } else if (info.absoluteFilePath() == QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)) { + return QIcon("resources/icons/documents.svg"); + } + return QIcon("resources/icons/folder.svg"); } + + QFileInfo iconFile("resources/icons/" + iconsMap[ext]); + if (iconFile.exists() && iconFile.isFile()) { + return QIcon(iconFile.filePath()); + } + + return QIcon("resources/icons/file.svg"); } -void GLWidget::mousePressEvent(QMouseEvent* event) { - _pressed = true; - _mouseX = event->globalX(); - _mouseY = event->globalY(); +QString HiFiIconProvider::type(const QFileInfo &info) const { + if (info.isFile()) { + if (info.suffix().size() > SHORT_FILE_EXTENSION) { + // Capitalize extension + return info.suffix().left(SECOND_INDEX_LETTER).toUpper() + info.suffix().mid(SECOND_INDEX_LETTER); + } + return info.suffix().toUpper(); + } + + return QFileIconProvider::type(info); } -void GLWidget::mouseMoveEvent(QMouseEvent* event) { - _a += (M_PI * (event->globalX() - _mouseX)) / height(); - _h += (M_PI * (event->globalY() - _mouseY)) / height(); - _h = glm::clamp(_h, -M_PI_4, M_PI_4); +ImportDialog::ImportDialog(QWidget* parent) : + QFileDialog(parent, WINDOW_NAME, DESKTOP_LOCATION, NULL), + _importButton(IMPORT_BUTTON_NAME, this), + _cancelButton(CANCEL_BUTTON_NAME, this), + fileAccepted(false) { - _mouseX = event->globalX(); - _mouseY = event->globalY(); -} - -void GLWidget::mouseReleaseEvent(QMouseEvent* event) { - _pressed = false; -} - -ImportDialog::ImportDialog(QWidget *parent) - : QFileDialog(parent, WINDOW_NAME, DESKTOP_LOCATION, IMPORT_FILE_TYPES), - _importButton (IMPORT_BUTTON_NAME, this), - _clipboardImportBox(IMPORT_TO_CLIPBOARD_CHECKBOX_STRING, this), - _previewBox (PREVIEW_CHECKBOX_STRING, this), - _previewBar (this), - _glPreview (new GLWidget(this)) { setOption(QFileDialog::DontUseNativeDialog, true); setFileMode(QFileDialog::ExistingFile); setViewMode(QFileDialog::Detail); - QGridLayout* gridLayout = (QGridLayout*) layout(); - gridLayout->addWidget(&_importButton , 2, 2); - gridLayout->addWidget(&_clipboardImportBox, 2, 3); - gridLayout->addWidget(&_previewBox , 3, 3); - gridLayout->addWidget(&_previewBar , 0, 3); - gridLayout->addWidget(_glPreview , 1, 3); - gridLayout->setColumnStretch(3, 1); +#ifdef Q_OS_MAC + QString cmdString = ("Command"); +#else + QString cmdString = ("Control"); +#endif + QLabel* infoLabel = new QLabel(QString(INFO_LABEL_TEXT).arg(cmdString)); + infoLabel->setObjectName("infoLabel"); - _previewBar.setVisible(false); - _previewBar.setRange(0, 100); - _previewBar.setValue(0); + QGridLayout* gridLayout = (QGridLayout*) layout(); + gridLayout->addWidget(infoLabel, 2, 0, 2, 1); + gridLayout->addWidget(&_cancelButton, 2, 1, 2, 1); + gridLayout->addWidget(&_importButton, 2, 2, 2, 1); + + setImportTypes(); + setLayout(); connect(&_importButton, SIGNAL(pressed()), SLOT(import())); - connect(&_previewBox, SIGNAL(toggled(bool)), SIGNAL(previewToggled(bool))); - connect(&_previewBox, SIGNAL(toggled(bool)), SLOT(preview(bool))); - connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString))); - connect(&_glTimer, SIGNAL(timeout()), SLOT(timer())); + + connect(&_cancelButton, SIGNAL(pressed()), SLOT(close())); + connect(this, SIGNAL(currentChanged(QString)), SLOT(saveCurrentFile(QString))); } ImportDialog::~ImportDialog() { - delete _glPreview; -} - -void ImportDialog::init() { - VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem(); - connect(voxelSystem, SIGNAL(importSize(float,float,float)), SLOT(setGLCamera(float,float,float))); - connect(voxelSystem, SIGNAL(importProgress(int)), &_previewBar, SLOT(setValue(int))); + deleteLater(); } void ImportDialog::import() { - _importButton.setDisabled(true); - _clipboardImportBox.setDisabled(true); - _previewBox.setDisabled(true); - - _previewBar.setValue(0); - _previewBar.setVisible(true); - + fileAccepted = true; emit accepted(); } void ImportDialog::accept() { - QFileDialog::accept(); + // do nothing if import is not enable + if (!_importButton.isEnabled()) { + return; + } + + if (!fileAccepted) { + fileAccepted = true; + emit accepted(); + } else { + QFileDialog::accept(); + } } void ImportDialog::reject() { @@ -219,44 +155,141 @@ void ImportDialog::reject() { } int ImportDialog::exec() { + // deselect selected file + selectFile(" "); return QFileDialog::exec(); } -void ImportDialog::setGLCamera(float x, float y, float z) { - _glPreview->setTargetCenter(glm::vec3(x, y, z) / 2.0f); -} - void ImportDialog::reset() { - _previewBox.setChecked(false); - _previewBar.setVisible(false); - _previewBar.setValue(0); - _importButton.setEnabled(true); - _clipboardImportBox.setEnabled(true); - _previewBox.setEnabled(true); - - _glTimer.stop(); - _glPreview->setDraw(false); - _glPreview->updateGL(); -} - -void ImportDialog::preview(bool wantPreview) { - _previewBar.setValue(0); - _previewBar.setVisible(wantPreview); - _glPreview->setDraw(wantPreview); - - if (wantPreview) { - _glTimer.start(); - } else { - _glTimer.stop(); - _glPreview->updateGL(); - } + _importButton.setEnabled(false); } void ImportDialog::saveCurrentFile(QString filename) { - _currentFile = filename; + if (!filename.isEmpty() && QFileInfo(filename).isFile()) { + _currentFile = filename; + _importButton.setEnabled(true); + } else { + _currentFile.clear(); + _importButton.setEnabled(false); + } } -void ImportDialog::timer() { - _glPreview->updateGL(); - _glTimer.start(16); +void ImportDialog::setLayout() { + + // set ObjectName used in qss for styling + _importButton.setObjectName("importButton"); + _cancelButton.setObjectName("cancelButton"); + + // set fixed size + _importButton.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + _cancelButton.setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed); + + // hide unused embedded widgets in QFileDialog + QWidget* widget = findChild("lookInCombo"); + widget->hide(); + + widget = findChild("backButton"); + widget->hide(); + + widget = findChild("forwardButton"); + widget->hide(); + + widget = findChild("toParentButton"); + widget->hide(); + + widget = findChild("newFolderButton"); + widget->hide(); + + widget = findChild("listModeButton"); + widget->hide(); + + widget = findChild("detailModeButton"); + widget->hide(); + + widget = findChild("fileNameEdit"); + widget->hide(); + + widget = findChild("fileTypeCombo"); + widget->hide(); + + widget = findChild("fileTypeLabel"); + widget->hide(); + + widget = findChild("fileNameLabel"); + widget->hide(); + + widget = findChild("buttonBox"); + widget->hide(); + + QSplitter* splitter = findChild("splitter"); + splitter->setHandleWidth(0); + + // remove blue outline on Mac + widget = findChild("sidebar"); + widget->setAttribute(Qt::WA_MacShowFocusRect, false); + + widget = findChild("treeView"); + widget->setAttribute(Qt::WA_MacShowFocusRect, false); + + // remove reference to treeView + widget = NULL; + widget->deleteLater(); + + switchToResourcesParentIfRequired(); + QFile styleSheet("resources/styles/import_dialog.qss"); + if (styleSheet.open(QIODevice::ReadOnly)) { + setStyleSheet(styleSheet.readAll()); + } + +} + +void ImportDialog::setImportTypes() { + + switchToResourcesParentIfRequired(); + QFile config("resources/config/config.json"); + config.open(QFile::ReadOnly | QFile::Text); + QJsonDocument document = QJsonDocument::fromJson(config.readAll()); + if (!document.isNull() && !document.isEmpty()) { + + QString importFormatsInfo; + QString importFormatsFilterList; + QHash iconsMap; + + QJsonObject configObject = document.object(); + if (!configObject.isEmpty()) { + QJsonArray fileFormats = configObject["importFormats"].toArray(); + int formatsCounter = 0; + foreach (const QJsonValue& fileFormat, fileFormats) { + QJsonObject fileFormatObject = fileFormat.toObject(); + + QString ext(fileFormatObject["extension"].toString()); + QString icon(fileFormatObject.value("icon").toString()); + + if (formatsCounter > 0) { + importFormatsInfo.append(","); + } + + // set ' or' on last import type text + if (formatsCounter == fileFormats.count() - 1) { + importFormatsInfo.append(" or"); + } + + importFormatsFilterList.append(QString("*.%1 ").arg(ext)); + importFormatsInfo.append(" .").append(ext); + iconsMap[ext] = icon; + formatsCounter++; + } + } + + // set custom file icons + setIconProvider(new HiFiIconProvider(iconsMap)); + setNameFilter(importFormatsFilterList); + +#ifdef Q_OS_MAC + QString cmdString = ("Command"); +#else + QString cmdString = ("Control"); +#endif + setLabelText(QFileDialog::LookIn, QString(IMPORT_INFO).arg(importFormatsInfo)); + } } diff --git a/interface/src/ImportDialog.h b/interface/src/ImportDialog.h index 35102dfe4c..5cfc49e51e 100644 --- a/interface/src/ImportDialog.h +++ b/interface/src/ImportDialog.h @@ -9,54 +9,54 @@ #ifndef __hifi__ImportDialog__ #define __hifi__ImportDialog__ -#include - #include #include -#include -#include -#include -#include +#include +#include +#include -class GLWidget; +#include + +class HiFiIconProvider : public QFileIconProvider { +public: + HiFiIconProvider(const QHash map) { iconsMap = map; }; + virtual QIcon icon(IconType type) const; + virtual QIcon icon(const QFileInfo &info) const; + virtual QString type(const QFileInfo &info) const; + QHash iconsMap; +}; class ImportDialog : public QFileDialog { Q_OBJECT + public: ImportDialog(QWidget* parent = NULL); ~ImportDialog(); - void init(); void reset(); - bool getWantPreview() const { return _previewBox.isChecked(); } QString getCurrentFile() const { return _currentFile; } - bool getImportIntoClipboard() const { return _clipboardImportBox.isChecked(); } signals: - void previewToggled(bool); void accepted(); public slots: int exec(); - void setGLCamera(float x, float y, float z); void import(); void accept(); void reject(); private slots: - void preview(bool preview); void saveCurrentFile(QString); - void timer(); private: - QString _currentFile; - QPushButton _importButton; - QCheckBox _clipboardImportBox; - QCheckBox _previewBox; - QProgressBar _previewBar; - GLWidget* _glPreview; - QTimer _glTimer; + QString _currentFile; + QPushButton _importButton; + QPushButton _cancelButton; + + void setLayout(); + void setImportTypes(); + bool fileAccepted; }; #endif /* defined(__hifi__ImportDialog__) */ diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index ffee4c7879..a2e518b2d7 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -373,9 +373,18 @@ const glm::vec3 randVector() { } static TextRenderer* textRenderer(int mono) { - static TextRenderer* monoRenderer = new TextRenderer(MONO_FONT_FAMILY); - static TextRenderer* proportionalRenderer = new TextRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT); - return mono ? monoRenderer : proportionalRenderer; + static TextRenderer* monoRenderer = new TextRenderer(MONO_FONT_FAMILY); + static TextRenderer* proportionalRenderer = new TextRenderer(SANS_FONT_FAMILY, -1, -1, false, TextRenderer::SHADOW_EFFECT); + static TextRenderer* inconsolataRenderer = new TextRenderer(INCONSOLATA_FONT_FAMILY, -1, QFont::Bold, false); + switch (mono) { + case 1: + return monoRenderer; + case 2: + return inconsolataRenderer; + case 0: + default: + return proportionalRenderer; + } } int widthText(float scale, int mono, char const* string) { diff --git a/interface/src/Util.h b/interface/src/Util.h index 19509f8254..2a812120f0 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -25,6 +25,9 @@ // the standard mono font family #define MONO_FONT_FAMILY "Courier" +// the Inconsolata font family +#define INCONSOLATA_FONT_FAMILY "Inconsolata" + void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * fwd, glm::vec3 * left, glm::vec3 * up); float azimuth_to(glm::vec3 head_pos, glm::vec3 source_pos); diff --git a/interface/src/VoxelImporter.cpp b/interface/src/VoxelImporter.cpp index 570eb5043c..0011b0abb7 100644 --- a/interface/src/VoxelImporter.cpp +++ b/interface/src/VoxelImporter.cpp @@ -21,6 +21,9 @@ private: QString _filename; }; +const QString SETTINGS_GROUP_NAME = "VoxelImport"; +const QString IMPORT_DIALOG_SETTINGS_KEY = "ImportDialogSettings"; + VoxelImporter::VoxelImporter(QWidget* parent) : QObject(parent), _voxelTree(true), @@ -28,13 +31,20 @@ VoxelImporter::VoxelImporter(QWidget* parent) _currentTask(NULL), _nextTask(NULL) { - connect(&_importDialog, SIGNAL(previewToggled(bool)), SLOT(preImport())); connect(&_importDialog, SIGNAL(currentChanged(QString)), SLOT(preImport())); connect(&_importDialog, SIGNAL(accepted()), SLOT(import())); } -void VoxelImporter::init() { - _importDialog.init(); +void VoxelImporter::saveSettings(QSettings* settings) { + settings->beginGroup(SETTINGS_GROUP_NAME); + settings->setValue(IMPORT_DIALOG_SETTINGS_KEY, _importDialog.saveState()); + settings->endGroup(); +} + +void VoxelImporter::init(QSettings* settings) { + settings->beginGroup(SETTINGS_GROUP_NAME); + _importDialog.restoreState(settings->value(IMPORT_DIALOG_SETTINGS_KEY).toByteArray()); + settings->endGroup(); } VoxelImporter::~VoxelImporter() { @@ -74,15 +84,13 @@ int VoxelImporter::exec() { reset(); } else { _importDialog.reset(); - - if (_importDialog.getImportIntoClipboard()) { - VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem(); - - voxelSystem->copySubTreeIntoNewTree(voxelSystem->getTree()->getRoot(), - Application::getInstance()->getClipboard(), - true); - voxelSystem->changeTree(Application::getInstance()->getClipboard()); - } + + VoxelSystem* voxelSystem = Application::getInstance()->getSharedVoxelSystem(); + + voxelSystem->copySubTreeIntoNewTree(voxelSystem->getTree()->getRoot(), + Application::getInstance()->getClipboard(), + true); + voxelSystem->changeTree(Application::getInstance()->getClipboard()); } return ret; @@ -94,24 +102,22 @@ int VoxelImporter::preImport() { if (!QFileInfo(filename).isFile()) { return 0; } - - if (_importDialog.getWantPreview()) { - _filename = filename; - - if (_nextTask) { - delete _nextTask; - } - - _nextTask = new ImportTask(_filename); - connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask())); - - if (_currentTask != NULL) { - _voxelTree.cancelImport(); - } else { - launchTask(); - } + + _filename = filename; + + if (_nextTask) { + delete _nextTask; } - + + _nextTask = new ImportTask(_filename); + connect(_nextTask, SIGNAL(destroyed()), SLOT(launchTask())); + + if (_currentTask != NULL) { + _voxelTree.cancelImport(); + } else { + launchTask(); + } + return 1; } diff --git a/interface/src/VoxelImporter.h b/interface/src/VoxelImporter.h index a84955f2c9..43a3835e68 100644 --- a/interface/src/VoxelImporter.h +++ b/interface/src/VoxelImporter.h @@ -23,8 +23,9 @@ public: VoxelImporter(QWidget* parent = NULL); ~VoxelImporter(); - void init(); + void init(QSettings* settings); void reset(); + void saveSettings(QSettings* settings); VoxelTree* getVoxelTree() { return &_voxelTree; } diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index 8228ec9e64..df24b4c927 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -63,7 +63,7 @@ void AvatarManager::updateAvatars(float deltaTime) { // simulate avatars AvatarHash::iterator avatar = _avatarHash.begin(); - if (avatar != _avatarHash.end()) { + while (avatar != _avatarHash.end()) { if (avatar->data()->getOwningAvatarMixer()) { // this avatar's mixer is still around, go ahead and simulate it avatar->data()->simulate(deltaTime, NULL); @@ -72,6 +72,7 @@ void AvatarManager::updateAvatars(float deltaTime) { avatar->data()->setMouseRay(applicationInstance->getMouseRayOrigin(), applicationInstance->getMouseRayDirection()); + avatar++; } else { // the mixer that owned this avatar is gone, give it to the vector of fades and kill it avatar = removeAvatarAtHashIterator(avatar); @@ -225,4 +226,4 @@ void AvatarManager::clearHash() { while (removeAvatar != _avatarHash.end()) { removeAvatar = removeAvatarAtHashIterator(removeAvatar); } -} \ No newline at end of file +} diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index bdb3916694..73248b413f 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -12,6 +12,7 @@ #include #include +#include #include "Application.h" #include "GeometryCache.h" diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index 0ba8f84ad2..96fe84b305 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp new file mode 100644 index 0000000000..f0fef33cee --- /dev/null +++ b/interface/src/ui/Snapshot.cpp @@ -0,0 +1,46 @@ +// +// Snapshot.cpp +// hifi +// +// Created by Stojce Slavkovski on 1/26/14. +// +// + +#include "Snapshot.h" + +#include + +#include +#include +#include + +// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg +// %1 <= username, %2 <= date and time, %3 <= current location +const QString FILENAME_PATH_FORMAT = "hifi-snap-by-%1-on-%2@%3.jpg"; + +const QString DATETIME_FORMAT = "yyyy-MM-dd_hh-mm-ss"; +const QString SNAPSHOTS_DIRECTORY = "Snapshots"; + +void Snapshot::saveSnapshot(QGLWidget* widget, QString username, glm::vec3 location) { + QImage shot = widget->grabFrameBuffer(); + + // add metadata + shot.setText("location-x", QString::number(location.x)); + shot.setText("location-y", QString::number(location.y)); + shot.setText("location-z", QString::number(location.z)); + + QString formattedLocation = QString("%1_%2_%3").arg(location.x).arg(location.y).arg(location.z); + // replace decimal . with '-' + formattedLocation.replace('.', '-'); + + // normalize username, replace all non alphanumeric with '-' + username.replace(QRegExp("[^A-Za-z0-9_]"), "-"); + + QDateTime now = QDateTime::currentDateTime(); + + QString fileName = FileUtils::standardPath(SNAPSHOTS_DIRECTORY); + fileName.append(QString(FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation))); + shot.save(fileName, 0, 100); +} + + diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h new file mode 100644 index 0000000000..39548cdff8 --- /dev/null +++ b/interface/src/ui/Snapshot.h @@ -0,0 +1,27 @@ +// +// Snapshot.h +// hifi +// +// Created by Stojce Slavkovski on 1/26/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#ifndef __hifi__Snapshot__ +#define __hifi__Snapshot__ + +#import +#import +#import + +#include + +class Snapshot { + +public: + static void saveSnapshot(QGLWidget* widget, QString username, glm::vec3 location); + +private: + QString _username; +}; + +#endif /* defined(__hifi__Snapshot__) */ diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index aef62ccf07..340a560439 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -289,10 +289,7 @@ int Particle::readParticleDataFromBuffer(const unsigned char* data, int bytesLef return bytesRead; } - -Particle Particle::fromEditPacket(const unsigned char* data, int length, int& processedBytes, ParticleTree* tree) { - - //qDebug() << "Particle::fromEditPacket() length=" << length; +Particle Particle::fromEditPacket(const unsigned char* data, int length, int& processedBytes, ParticleTree* tree, bool& valid) { Particle newParticle; // id and _lastUpdated will get set here... const unsigned char* dataAt = data; @@ -302,9 +299,6 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr int octets = numberOfThreeBitSectionsInCode(data); int lengthOfOctcode = bytesRequiredForCodeLength(octets); - //qDebug() << "Particle::fromEditPacket() lengthOfOctcode=" << lengthOfOctcode; - //printOctalCode(data); - // we don't actually do anything with this octcode... dataAt += lengthOfOctcode; processedBytes += lengthOfOctcode; @@ -321,8 +315,6 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // special case for handling "new" particles if (isNewParticle) { - //qDebug() << "editID == NEW_PARTICLE"; - // If this is a NEW_PARTICLE, then we assume that there's an additional uint32_t creatorToken, that // we want to send back to the creator as an map to the actual id uint32_t creatorTokenID; @@ -330,11 +322,8 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr dataAt += sizeof(creatorTokenID); processedBytes += sizeof(creatorTokenID); - //qDebug() << "creatorTokenID:" << creatorTokenID; - newParticle.setCreatorTokenID(creatorTokenID); newParticle._newlyCreated = true; - newParticle.setAge(0); // this guy is new! } else { @@ -344,15 +333,19 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr // copy existing properties before over-writing with new properties if (existingParticle) { newParticle = *existingParticle; - //qDebug() << "newParticle = *existingParticle... calling debugDump()..."; - //existingParticle->debugDump(); + } else { + // the user attempted to edit a particle that doesn't exist + qDebug() << "user attempted to edit a particle that doesn't exist..."; + valid = false; + return newParticle; } - newParticle._id = editID; newParticle._newlyCreated = false; - - } + + // if we got this far, then our result will be valid + valid = true; + // lastEdited memcpy(&newParticle._lastEdited, dataAt, sizeof(newParticle._lastEdited)); @@ -838,6 +831,8 @@ ParticleProperties::ParticleProperties() : _inHand(false), _shouldDie(false), + _id(UNKNOWN_PARTICLE_ID), + _idSet(false), _lastEdited(usecTimestampNow()), _positionChanged(false), _colorChanged(false), @@ -923,6 +918,11 @@ QScriptValue ParticleProperties::copyToScriptValue(QScriptEngine* engine) const properties.setProperty("script", _script); properties.setProperty("inHand", _inHand); properties.setProperty("shouldDie", _shouldDie); + + if (_idSet) { + properties.setProperty("id", _id); + properties.setProperty("isKnownID", (_id == UNKNOWN_PARTICLE_ID)); + } return properties; } @@ -1116,6 +1116,9 @@ void ParticleProperties::copyFromParticle(const Particle& particle) { _inHand = particle.getInHand(); _shouldDie = particle.getShouldDie(); + _id = particle.getID(); + _idSet = true; + _positionChanged = false; _colorChanged = false; _radiusChanged = false; diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index fab528595d..6046ca39de 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -94,6 +94,9 @@ public: void setShouldDie(bool shouldDie) { _shouldDie = shouldDie; _shouldDieChanged = true; } void setLifetime(float value) { _lifetime = value; _lifetimeChanged = true; } void setScript(QString updateScript) { _script = updateScript; _scriptChanged = true; } + + /// used by ParticleScriptingInterface to return ParticleProperties for unknown particles + void setIsUnknownID() { _id = UNKNOWN_PARTICLE_ID; _idSet = true; } private: glm::vec3 _position; @@ -107,6 +110,8 @@ private: bool _inHand; bool _shouldDie; + uint32_t _id; + bool _idSet; uint64_t _lastEdited; bool _positionChanged; bool _colorChanged; @@ -145,6 +150,7 @@ public: }; Q_DECLARE_METATYPE(ParticleID); +Q_DECLARE_METATYPE(QVector); QScriptValue ParticleIDtoScriptValue(QScriptEngine* engine, const ParticleID& properties); void ParticleIDfromScriptValue(const QScriptValue &object, ParticleID& properties); @@ -161,8 +167,9 @@ public: glm::vec3 gravity = DEFAULT_GRAVITY, float damping = DEFAULT_DAMPING, float lifetime = DEFAULT_LIFETIME, bool inHand = NOT_IN_HAND, QString updateScript = DEFAULT_SCRIPT, uint32_t id = NEW_PARTICLE); + /// creates an NEW particle from an PacketType_PARTICLE_ADD_OR_EDIT edit data buffer - static Particle fromEditPacket(const unsigned char* data, int length, int& processedBytes, ParticleTree* tree); + static Particle fromEditPacket(const unsigned char* data, int length, int& processedBytes, ParticleTree* tree, bool& valid); virtual ~Particle(); virtual void init(glm::vec3 position, float radius, rgbColor color, glm::vec3 velocity, diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index 0c90a03573..a23c393f44 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -161,6 +161,42 @@ const Particle* ParticleTree::findClosestParticle(glm::vec3 position, float targ return args.closestParticle; } +class FindAllNearPointArgs { +public: + glm::vec3 position; + float targetRadius; + QVector particles; +}; + + +bool ParticleTree::findInSphereOperation(OctreeElement* element, void* extraData) { + FindAllNearPointArgs* args = static_cast(extraData); + ParticleTreeElement* particleTreeElement = static_cast(element); + + glm::vec3 penetration; + bool sphereIntersection = particleTreeElement->getAABox().findSpherePenetration(args->position, + args->targetRadius, penetration); + + // If this particleTreeElement contains the point, then search it... + if (sphereIntersection) { + QVector moreParticles = particleTreeElement->getParticles(args->position, args->targetRadius); + args->particles << moreParticles; + return true; // keep searching in case children have closer particles + } + + // if this element doesn't contain the point, then none of it's children can contain the point, so stop searching + return false; +} + +QVector ParticleTree::findParticles(const glm::vec3& center, float radius) { + QVector result; + FindAllNearPointArgs args = { center, radius }; + lockForRead(); + recurseTreeWithOperation(findInSphereOperation, &args); + unlock(); + return args.particles; +} + class FindByIDArgs { public: uint32_t id; @@ -215,23 +251,27 @@ int ParticleTree::processEditPacketData(PacketType packetType, const unsigned ch // we handle these types of "edit" packets switch (packetType) { case PacketTypeParticleAddOrEdit: { - //qDebug() << " got PacketType_PARTICLE_ADD_OR_EDIT... "; - Particle newParticle = Particle::fromEditPacket(editData, maxLength, processedBytes, this); - storeParticle(newParticle, senderNode); - if (newParticle.isNewlyCreated()) { - notifyNewlyCreatedParticle(newParticle, senderNode); + bool isValid; + Particle newParticle = Particle::fromEditPacket(editData, maxLength, processedBytes, this, isValid); + if (isValid) { + storeParticle(newParticle, senderNode); + if (newParticle.isNewlyCreated()) { + notifyNewlyCreatedParticle(newParticle, senderNode); + } } - //qDebug() << " DONE... PacketType_PARTICLE_ADD_OR_EDIT... "; - return processedBytes; - } + } break; - // TODO: wire in support here for server to get PacketType_PARTICLE_ERASE messages - // instead of using PacketType_PARTICLE_ADD_OR_EDIT messages to delete particles + // TODO: wire in support here for server to get PACKET_TYPE_PARTICLE_ERASE messages + // instead of using PACKET_TYPE_PARTICLE_ADD_OR_EDIT messages to delete particles case PacketTypeParticleErase: - return 0; + processedBytes = 0; + break; default: - return 0; + processedBytes = 0; + break; } + + return processedBytes; } void ParticleTree::notifyNewlyCreatedParticle(const Particle& newParticle, Node* senderNode) { diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index c89bd01b3b..af15666ff7 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -42,6 +42,7 @@ public: void storeParticle(const Particle& particle, Node* senderNode = NULL); const Particle* findClosestParticle(glm::vec3 position, float targetRadius); const Particle* findParticleByID(uint32_t id, bool alreadyLocked = false); + QVector findParticles(const glm::vec3& center, float radius); void addNewlyCreatedHook(NewlyCreatedParticleHook* hook); void removeNewlyCreatedHook(NewlyCreatedParticleHook* hook); @@ -58,6 +59,7 @@ private: static bool updateOperation(OctreeElement* element, void* extraData); static bool findAndUpdateOperation(OctreeElement* element, void* extraData); static bool findNearPointOperation(OctreeElement* element, void* extraData); + static bool findInSphereOperation(OctreeElement* element, void* extraData); static bool pruneOperation(OctreeElement* element, void* extraData); static bool findByIDOperation(OctreeElement* element, void* extraData); static bool findAndDeleteOperation(OctreeElement* element, void* extraData); diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index f767b56a49..1454eadcc9 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -185,6 +185,23 @@ const Particle* ParticleTreeElement::getClosestParticle(glm::vec3 position) cons return closestParticle; } +QVector ParticleTreeElement::getParticles(glm::vec3 searchPosition, float searchRadius) const { + QVector results; + uint16_t numberOfParticles = _particles->size(); + for (uint16_t i = 0; i < numberOfParticles; i++) { + const Particle* particle = &(*_particles)[i]; + glm::vec3 particlePosition = particle->getPosition(); + float particleRadius = particle->getRadius(); + glm::vec3 penetration; + + // check to see that the particle (penetrator) penetrates the search area + if (findSphereSpherePenetration(particlePosition, particleRadius, searchPosition, searchRadius, penetration)) { + results << particle; + } + } + return results; +} + const Particle* ParticleTreeElement::getParticleWithID(uint32_t id) const { // NOTE: this lookup is O(N) but maybe we don't care? (guaranteed that num particles per elemen is small?) const Particle* foundParticle = NULL; diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index 6d76594b53..2705ad4292 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -89,6 +89,7 @@ public: bool containsParticle(const Particle& particle) const; bool updateParticle(const Particle& particle); const Particle* getClosestParticle(glm::vec3 position) const; + QVector getParticles(glm::vec3 position, float radius) const; const Particle* getParticleWithID(uint32_t id) const; bool removeParticleWithID(uint32_t id); diff --git a/libraries/particles/src/ParticlesScriptingInterface.cpp b/libraries/particles/src/ParticlesScriptingInterface.cpp index 987fdf789c..b52ead87e1 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.cpp +++ b/libraries/particles/src/ParticlesScriptingInterface.cpp @@ -34,15 +34,44 @@ ParticleID ParticlesScriptingInterface::addParticle(const ParticleProperties& pr return id; } -void ParticlesScriptingInterface::editParticle(ParticleID particleID, const ParticleProperties& properties) { +ParticleID ParticlesScriptingInterface::identifyParticle(ParticleID particleID) { uint32_t actualID = particleID.id; if (!particleID.isKnownID) { actualID = Particle::getIDfromCreatorTokenID(particleID.creatorTokenID); - // hmmm... we kind of want to bail if someone attempts to edit an unknown if (actualID == UNKNOWN_PARTICLE_ID) { - //qDebug() << "ParticlesScriptingInterface::editParticle()... BAILING!!! particleID.creatorTokenID=" - // << particleID.creatorTokenID; - return; // bailing early + return particleID; // bailing early + } + + // found it! + particleID.id = actualID; + particleID.isKnownID = true; + } + return particleID; +} + +ParticleProperties ParticlesScriptingInterface::getParticleProperties(ParticleID particleID) { + ParticleProperties results; + ParticleID identity = identifyParticle(particleID); + if (!identity.isKnownID) { + results.setIsUnknownID(); + return results; + } + if (_particleTree) { + const Particle* particle = _particleTree->findParticleByID(identity.id); + results.copyFromParticle(*particle); + } + + return results; +} + + + +ParticleID ParticlesScriptingInterface::editParticle(ParticleID particleID, const ParticleProperties& properties) { + uint32_t actualID = particleID.id; + if (!particleID.isKnownID) { + actualID = Particle::getIDfromCreatorTokenID(particleID.creatorTokenID); + if (actualID == UNKNOWN_PARTICLE_ID) { + return particleID; // bailing early } } @@ -70,7 +99,9 @@ void ParticlesScriptingInterface::editParticle(ParticleID particleID, const Part qDebug() << "ParticlesScriptingInterface::editParticle()... properties.getInHand()=" << properties.getInHand(); } } + queueParticleMessage(PacketTypeParticleAddOrEdit, particleID, properties); + return particleID; } @@ -116,3 +147,17 @@ ParticleID ParticlesScriptingInterface::findClosestParticle(const glm::vec3& cen return result; } + +QVector ParticlesScriptingInterface::findParticles(const glm::vec3& center, float radius) const { + QVector result; + if (_particleTree) { + QVector particles = _particleTree->findParticles(center/(float)TREE_SCALE, radius/(float)TREE_SCALE); + + foreach (const Particle* particle, particles) { + ParticleID thisParticleID(particle->getID(), UNKNOWN_TOKEN, true); + result << thisParticleID; + } + } + return result; +} + diff --git a/libraries/particles/src/ParticlesScriptingInterface.h b/libraries/particles/src/ParticlesScriptingInterface.h index 624ce8a7b1..a85997ee05 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.h +++ b/libraries/particles/src/ParticlesScriptingInterface.h @@ -28,11 +28,33 @@ public: ParticleTree* getParticleTree(ParticleTree*) { return _particleTree; } public slots: + /// adds a particle with the specific properties ParticleID addParticle(const ParticleProperties& properties); - void editParticle(ParticleID particleID, const ParticleProperties& properties); + + /// identify a recently created particle to determine its true ID + ParticleID identifyParticle(ParticleID particleID); + + /// gets the current particle properties for a specific particle + /// this function will not find return results in script engine contexts which don't have access to particles + ParticleProperties getParticleProperties(ParticleID particleID); + + /// edits a particle updating only the included properties, will return the identified ParticleID in case of + /// successful edit, if the input particleID is for an unknown particle this function will have no effect + ParticleID editParticle(ParticleID particleID, const ParticleProperties& properties); + + /// deletes a particle void deleteParticle(ParticleID particleID); + + /// finds the closest particle to the center point, within the radius + /// will return a ParticleID.isKnownID = false if no particles are in the radius + /// this function will not find any particles in script engine contexts which don't have access to particles ParticleID findClosestParticle(const glm::vec3& center, float radius) const; + /// finds particles within the search sphere specified by the center point and radius + /// this function will not find any particles in script engine contexts which don't have access to particles + QVector findParticles(const glm::vec3& center, float radius) const; + + private: void queueParticleMessage(PacketType packetType, ParticleID particleID, const ParticleProperties& properties); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index f511299fcd..937162e0bf 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -118,6 +118,7 @@ void ScriptEngine::init() { registerMetaTypes(&_engine); qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); + qScriptRegisterSequenceMetaType >(&_engine); QScriptValue agentValue = _engine.newQObject(this); _engine.globalObject().setProperty("Agent", agentValue); diff --git a/libraries/shared/src/FileUtils.cpp b/libraries/shared/src/FileUtils.cpp index b58799104f..efbe1a189b 100644 --- a/libraries/shared/src/FileUtils.cpp +++ b/libraries/shared/src/FileUtils.cpp @@ -10,9 +10,9 @@ #include #include -void FileUtils::LocateFile(QString filePath) { +void FileUtils::locateFile(QString filePath) { - // adopted from + // adapted from // http://stackoverflow.com/questions/3490336/how-to-reveal-in-finder-or-show-in-explorer-with-qt // and // http://lynxline.com/show-in-finder-show-in-explorer/ @@ -54,3 +54,26 @@ void FileUtils::LocateFile(QString filePath) { QDesktopServices::openUrl(QUrl::fromLocalFile(folder)); } } + +QString FileUtils::standardPath(QString subfolder) { + // standard path + // Mac: ~/Library/Application Support/Interface + QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); + path.append("/Interface"); + + if (!subfolder.startsWith("/")) { + subfolder.prepend("/"); + } + + if (!subfolder.endsWith("/")) { + subfolder.append("/"); + } + + path.append(subfolder); + QDir logDir(path); + if (!logDir.exists(path)) { + logDir.mkpath(path); + } + + return path; +} diff --git a/libraries/shared/src/FileUtils.h b/libraries/shared/src/FileUtils.h index a725e72ca1..dd4605218e 100644 --- a/libraries/shared/src/FileUtils.h +++ b/libraries/shared/src/FileUtils.h @@ -14,7 +14,8 @@ class FileUtils { public: - static void LocateFile(QString); + static void locateFile(QString fileName); + static QString standardPath(QString subfolder); };