diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8efdeed8a7..b5c2d00d3e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -312,6 +312,8 @@ void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configAr int numInstances = jsonObject[ASSIGNMENT_INSTANCES_KEY].toInt(); numInstances = (numInstances == 0 ? 1 : numInstances); + qDebug() << "Adding a static scripted assignment from" << assignmentURL; + for (int i = 0; i < numInstances; i++) { // add a scripted assignment to the queue for this instance Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, @@ -319,13 +321,8 @@ void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configAr assignmentPool); scriptAssignment->setPayload(assignmentURL.toUtf8()); - qDebug() << "Adding scripted assignment to queue -" << *scriptAssignment; - qDebug() << "URL for script is" << assignmentURL; - // scripts passed on CL or via JSON are static - so they are added back to the queue if the node dies - SharedAssignmentPointer sharedScriptAssignment(scriptAssignment); - _unfulfilledAssignments.enqueue(sharedScriptAssignment); - _allAssignments.insert(sharedScriptAssignment->getUUID(), sharedScriptAssignment); + addStaticAssignmentToAssignmentHash(scriptAssignment); } } } @@ -407,7 +404,7 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock PendingAssignedNodeData* pendingAssigneeData = NULL; if (isAssignment) { - pendingAssigneeData = _pendingAssignedNodes.take(packetUUID); + pendingAssigneeData = _pendingAssignedNodes.value(packetUUID); if (pendingAssigneeData) { matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(pendingAssigneeData->getAssignmentUUID(), nodeType); @@ -416,12 +413,21 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID) << "matches unfulfilled assignment" << uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID()); + + // remove this unique assignment deployment from the hash of pending assigned nodes + // cleanup of the PendingAssignedNodeData happens below after the node has been added to the LimitedNodeList + _pendingAssignedNodes.remove(packetUUID); + } else { + // this is a node connecting to fulfill an assignment that doesn't exist + // don't reply back to them so they cycle back and re-request an assignment + qDebug() << "No match for assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID); + return; } } } - if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) { + if (!isAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) { // this is an Agent, and we require authentication so we can compare the user's roles to our list of allowed ones if (_sessionAuthenticationHash.contains(packetUUID)) { if (!_sessionAuthenticationHash.value(packetUUID)) { diff --git a/examples/editModelExample.js b/examples/editModelExample.js index dbea1419ba..83491e2fb1 100644 --- a/examples/editModelExample.js +++ b/examples/editModelExample.js @@ -31,12 +31,12 @@ var originalProperties = { green: 255, blue: 0 }, - //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", + modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/pug.fbx", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/newInvader16x16-large-purple.svo", //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/minotaur/mino_full.fbx", - modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX", + //modelURL: "http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Combat_tank_V01.FBX", modelRotation: rotation }; diff --git a/examples/editVoxels.js b/examples/editVoxels.js index 9040306bf6..14bea50bb0 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -28,7 +28,7 @@ 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 = 1.0; +var MAX_VOXEL_SCALE = 16.0; var MIN_VOXEL_SCALE = 1.0 / Math.pow(2.0, 8.0); var WHITE_COLOR = { red: 255, green: 255, blue: 255 }; @@ -394,6 +394,9 @@ function ScaleSelector() { if (this.power < 13) { ++this.power; this.scale *= 2.0; + if (this.scale > MAX_VOXEL_SCALE) { + this.scale = MAX_VOXEL_SCALE; + } this.update(); rescaleImport(); resizeVoxelSound.play(voxelSizePlus); @@ -1056,6 +1059,9 @@ function mousePressEvent(event) { lastVoxelPosition = { x: voxelDetails.x, y: voxelDetails.y, z: voxelDetails.z }; lastVoxelColor = { red: newColor.red, green: newColor.green, blue: newColor.blue }; lastVoxelScale = voxelDetails.s; + if (lastVoxelScale > MAX_VOXEL_SCALE) { + lastVoxelScale = MAX_VOXEL_SCALE; + } addVoxelSound.playRandom(); 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/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 37aa23f317..4ee15bc949 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -102,18 +102,11 @@ const int STARTUP_JITTER_SAMPLES = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / 2 // Startup optimistically with small jitter buffer that // will start playback on the second received audio packet. -const int MIRROR_VIEW_TOP_PADDING = 5; -const int MIRROR_VIEW_LEFT_PADDING = 10; -const int MIRROR_VIEW_WIDTH = 265; -const int MIRROR_VIEW_HEIGHT = 215; -const float MIRROR_FULLSCREEN_DISTANCE = 0.35f; -const float MIRROR_REARVIEW_DISTANCE = 0.65f; -const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; -const float MIRROR_FIELD_OF_VIEW = 30.0f; - 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); @@ -171,6 +164,7 @@ 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)) @@ -370,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)); @@ -630,10 +624,12 @@ void Application::paintGL() { if (OculusManager::isConnected()) { OculusManager::display(whichCamera); + } else if (TV3DManager::isConnected()) { _glowEffect.prepare(); TV3DManager::display(whichCamera); _glowEffect.render(); + } else { _glowEffect.prepare(); @@ -652,7 +648,7 @@ void Application::paintGL() { _rearMirrorTools->render(true); } - displayOverlay(); + _applicationOverlay.renderOverlay(); } _frameCount++; @@ -2594,183 +2590,6 @@ void Application::computeOffAxisFrustum(float& left, float& right, float& bottom _viewFrustum.computeOffAxisFrustum(left, right, bottom, top, nearVal, farVal, nearClipPlane, farClipPlane); } -const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; - -void Application::displayOverlay() { - PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "Application::displayOverlay()"); - - // Render 2D overlay: I/O level bar graphs and text - glMatrixMode(GL_PROJECTION); - glPushMatrix(); - - glLoadIdentity(); - gluOrtho2D(0, _glWidget->width(), _glWidget->height(), 0); - glDisable(GL_DEPTH_TEST); - glDisable(GL_LIGHTING); - - // Display a single screen-size quad to create an alpha blended 'collision' flash - if (_audio.getCollisionFlashesScreen()) { - float collisionSoundMagnitude = _audio.getCollisionSoundMagnitude(); - const float VISIBLE_COLLISION_SOUND_MAGNITUDE = 0.5f; - if (collisionSoundMagnitude > VISIBLE_COLLISION_SOUND_MAGNITUDE) { - renderCollisionOverlay(_glWidget->width(), _glWidget->height(), _audio.getCollisionSoundMagnitude()); - } - } - - // Audio VU Meter and Mute Icon - const int MUTE_ICON_SIZE = 24; - const int AUDIO_METER_INSET = 2; - const int MUTE_ICON_PADDING = 10; - const int AUDIO_METER_WIDTH = MIRROR_VIEW_WIDTH - MUTE_ICON_SIZE - AUDIO_METER_INSET - MUTE_ICON_PADDING; - const int AUDIO_METER_SCALE_WIDTH = AUDIO_METER_WIDTH - 2 * AUDIO_METER_INSET; - const int AUDIO_METER_HEIGHT = 8; - const int AUDIO_METER_GAP = 5; - const int AUDIO_METER_X = MIRROR_VIEW_LEFT_PADDING + MUTE_ICON_SIZE + AUDIO_METER_INSET + AUDIO_METER_GAP; - - int audioMeterY; - if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { - audioMeterY = MIRROR_VIEW_HEIGHT + AUDIO_METER_GAP + MUTE_ICON_PADDING; - } else { - audioMeterY = AUDIO_METER_GAP + MUTE_ICON_PADDING; - } - - const float AUDIO_METER_BLUE[] = {0.0, 0.0, 1.0}; - const float AUDIO_METER_GREEN[] = {0.0, 1.0, 0.0}; - const float AUDIO_METER_RED[] = {1.0, 0.0, 0.0}; - const float AUDIO_GREEN_START = 0.25 * AUDIO_METER_SCALE_WIDTH; - const float AUDIO_RED_START = 0.80 * AUDIO_METER_SCALE_WIDTH; - const float CLIPPING_INDICATOR_TIME = 1.0f; - const float AUDIO_METER_AVERAGING = 0.5; - const float LOG2 = log(2.f); - const float METER_LOUDNESS_SCALE = 2.8f / 5.f; - const float LOG2_LOUDNESS_FLOOR = 11.f; - float audioLevel = 0.f; - float loudness = _audio.getLastInputLoudness() + 1.f; - - _trailingAudioLoudness = AUDIO_METER_AVERAGING * _trailingAudioLoudness + (1.f - AUDIO_METER_AVERAGING) * loudness; - float log2loudness = log(_trailingAudioLoudness) / LOG2; - - if (log2loudness <= LOG2_LOUDNESS_FLOOR) { - audioLevel = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; - } else { - audioLevel = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.f)) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; - } - if (audioLevel > AUDIO_METER_SCALE_WIDTH) { - audioLevel = AUDIO_METER_SCALE_WIDTH; - } - bool isClipping = ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)); - - if ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)) { - const float MAX_MAGNITUDE = 0.7f; - float magnitude = MAX_MAGNITUDE * (1 - _audio.getTimeSinceLastClip() / CLIPPING_INDICATOR_TIME); - renderCollisionOverlay(_glWidget->width(), _glWidget->height(), magnitude, 1.0f); - } - - _audio.renderToolBox(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP, - audioMeterY, - Menu::getInstance()->isOptionChecked(MenuOption::Mirror)); - - _audio.renderScope(_glWidget->width(), _glWidget->height()); - - glBegin(GL_QUADS); - if (isClipping) { - glColor3f(1, 0, 0); - } else { - glColor3f(0.475f, 0.475f, 0.475f); - } - - audioMeterY += AUDIO_METER_HEIGHT; - - glColor3f(0, 0, 0); - // Draw audio meter background Quad - glVertex2i(AUDIO_METER_X, audioMeterY); - glVertex2i(AUDIO_METER_X + AUDIO_METER_WIDTH, audioMeterY); - glVertex2i(AUDIO_METER_X + AUDIO_METER_WIDTH, audioMeterY + AUDIO_METER_HEIGHT); - glVertex2i(AUDIO_METER_X, audioMeterY + AUDIO_METER_HEIGHT); - - - if (audioLevel > AUDIO_RED_START) { - if (!isClipping) { - glColor3fv(AUDIO_METER_RED); - } else { - glColor3f(1, 1, 1); - } - // Draw Red Quad - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + AUDIO_RED_START, audioMeterY + AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + AUDIO_RED_START, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); - audioLevel = AUDIO_RED_START; - } - if (audioLevel > AUDIO_GREEN_START) { - if (!isClipping) { - glColor3fv(AUDIO_METER_GREEN); - } else { - glColor3f(1, 1, 1); - } - // Draw Green Quad - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + AUDIO_GREEN_START, audioMeterY + AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + AUDIO_GREEN_START, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); - audioLevel = AUDIO_GREEN_START; - } - // Draw Blue Quad - if (!isClipping) { - glColor3fv(AUDIO_METER_BLUE); - } else { - glColor3f(1, 1, 1); - } - // Draw Blue (low level) quad - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET, audioMeterY + AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); - glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); - glEnd(); - - - if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) { - _myAvatar->renderHeadMouse(_glWidget->width(), _glWidget->height()); - } - - // Display stats and log text onscreen - glLineWidth(1.0f); - glPointSize(1.0f); - - if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { - // let's set horizontal offset to give stats some margin to mirror - int horizontalOffset = MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; - int voxelPacketsToProcess = _voxelProcessor.packetsToProcessCount(); - // Onscreen text about position, servers, etc - Stats::getInstance()->display(WHITE_TEXT, horizontalOffset, _fps, _packetsPerSecond, _bytesPerSecond, voxelPacketsToProcess); - // Bandwidth meter - if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) { - Stats::drawBackground(0x33333399, _glWidget->width() - 296, _glWidget->height() - 68, 296, 68); - _bandwidthMeter.render(_glWidget->width(), _glWidget->height()); - } - } - - // Show on-screen msec timer - if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) { - char frameTimer[10]; - quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); - sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000)); - int timerBottom = - (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && - Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) - ? 80 : 20; - drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 0.0f, 0, frameTimer, WHITE_TEXT); - } - _nodeBoundsDisplay.drawOverlay(); - - // give external parties a change to hook in - emit renderingOverlay(); - - _overlays.render2D(); - - glPopMatrix(); -} - glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) { float horizontalScale = _glWidget->width() / 2.0f; float verticalScale = _glWidget->height() / 2.0f; @@ -3437,16 +3256,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. @@ -3545,6 +3369,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 2e3880b7fd..2d1a8c60f5 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -83,6 +83,7 @@ #include "ui/LogDialog.h" #include "ui/UpdateDialog.h" #include "ui/overlays/Overlays.h" +#include "ui/ApplicationOverlay.h" #include "ui/RunningScriptsWidget.h" #include "voxels/VoxelFade.h" #include "voxels/VoxelHideShowThread.h" @@ -116,6 +117,15 @@ static const QString CUSTOM_URL_SCHEME = "hifi:"; static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees static const float BILLBOARD_DISTANCE = 5.0f; // meters +static const int MIRROR_VIEW_TOP_PADDING = 5; +static const int MIRROR_VIEW_LEFT_PADDING = 10; +static const int MIRROR_VIEW_WIDTH = 265; +static const int MIRROR_VIEW_HEIGHT = 215; +static const float MIRROR_FULLSCREEN_DISTANCE = 0.35f; +static const float MIRROR_REARVIEW_DISTANCE = 0.65f; +static const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; +static const float MIRROR_FIELD_OF_VIEW = 30.0f; + class Application : public QApplication { Q_OBJECT @@ -182,6 +192,7 @@ public: ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; } VoxelSystem* getVoxels() { return &_voxels; } VoxelTree* getVoxelTree() { return _voxels.getTree(); } + const VoxelPacketProcessor& getVoxelPacketProcessor() const { return _voxelProcessor; } ParticleTreeRenderer* getParticles() { return &_particles; } MetavoxelSystem* getMetavoxels() { return &_metavoxels; } ModelTreeRenderer* getModels() { return &_models; } @@ -205,6 +216,13 @@ public: BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } QUndoStack* getUndoStack() { return &_undoStack; } QSystemTrayIcon* getTrayIcon() { return _trayIcon; } + ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; } + Overlays& getOverlays() { return _overlays; } + + float getFps() const { return _fps; } + float getPacketsPerSecond() const { return _packetsPerSecond; } + float getBytesPerSecond() const { return _bytesPerSecond; } + const glm::vec3& getViewMatrixTranslation() const { return _viewMatrixTranslation; } /// if you need to access the application settings, use lockSettings()/unlockSettings() QSettings* lockSettings() { _settingsMutex.lock(); return _settings; } @@ -305,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(); @@ -377,7 +396,6 @@ private: glm::vec3 getSunDirection(); void updateShadowMap(); - void displayOverlay(); void renderRearViewMirror(const QRect& region, bool billboard = false); void renderViewFrustum(ViewFrustum& viewFrustum); @@ -552,6 +570,7 @@ private: TouchEvent _lastTouchEvent; Overlays _overlays; + ApplicationOverlay _applicationOverlay; AudioReflector _audioReflector; RunningScriptsWidget* _runningScriptsWidget; diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 45b4089a11..7fd8f26006 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, @@ -1258,6 +1270,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()); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 012dc1662c..50e59320b9 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; @@ -189,6 +190,7 @@ private slots: void showMetavoxelEditor(); void showScriptEditor(); void showChat(); + void toggleConsole(); void toggleChat(); void audioMuteToggled(); void namedLocationCreated(LocationManager::NamedLocationCreateResponse response); @@ -243,6 +245,7 @@ private: QPointer _MetavoxelEditor; QPointer _ScriptEditor; QPointer _chatWindow; + QDialog* _jsConsole; OctreeStatsDialog* _octreeStatsDialog; LodToolsDialog* _lodToolsDialog; int _maxVoxels; @@ -308,6 +311,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"; diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index eaa9875641..6ecc45e1e3 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -49,9 +49,9 @@ void FaceModel::simulate(float deltaTime, bool fullUpdate) { void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // get the rotation axes in joint space and use them to adjust the rotation glm::mat3 axes = glm::mat3_cast(_rotation); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState.transform * glm::translate(state.translation) * + glm::mat3 inverse = glm::mat3(glm::inverse(parentState._transform * glm::translate(state._translation) * joint.preTransform * glm::mat4_cast(joint.preRotation))); - state.rotation = glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) + state._rotation = glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalRoll(), glm::normalize(inverse * axes[2])) * glm::angleAxis(RADIANS_PER_DEGREE * _owningHead->getFinalYaw(), glm::normalize(inverse * axes[1])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningHead->getFinalPitch(), glm::normalize(inverse * axes[0])) * joint.rotation; @@ -59,17 +59,34 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { // likewise with the eye joints - glm::mat4 inverse = glm::inverse(parentState.transform * glm::translate(state.translation) * + glm::mat4 inverse = glm::inverse(parentState._transform * glm::translate(state._translation) * joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation)); glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientation() * IDENTITY_FRONT, 0.0f)); glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() + _owningHead->getSaccade() - _translation, 1.0f)); glm::quat between = rotationBetween(front, lookAt); const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE; - state.rotation = glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * + state._rotation = glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) * joint.rotation; } +void FaceModel::updateJointState(int index) { + JointState& state = _jointStates[index]; + const FBXJoint& joint = state.getFBXJoint(); + if (joint.parentIndex != -1) { + const JointState& parentState = _jointStates.at(joint.parentIndex); + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + if (index == geometry.neckJointIndex) { + maybeUpdateNeckRotation(parentState, joint, state); + + } else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) { + maybeUpdateEyeRotation(parentState, joint, state); + } + } + + Model::updateJointState(index); +} + bool FaceModel::getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const { if (!isActive()) { return false; diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index c3462f42ac..c71bb9a8aa 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -28,7 +28,8 @@ public: virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state); - + virtual void updateJointState(int index); + /// Retrieve the positions of up to two eye meshes. /// \return whether or not both eye meshes were found bool getEyePositions(glm::vec3& firstEyePosition, glm::vec3& secondEyePosition) const; diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index 7333bfc395..3586e525ae 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -45,7 +45,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } int jointIndex = geometry.humanIKJointIndices.at(humanIKJointIndex); if (jointIndex != -1) { - setJointRotation(jointIndex, _rotation * prioVR->getJointRotations().at(i), true, PALM_PRIORITY); + setJointRotation(jointIndex, _rotation * prioVR->getJointRotations().at(i), PALM_PRIORITY); } } return; @@ -188,8 +188,8 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { setJointPosition(parentJointIndex, palm.getPosition() + forearmVector * geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale), glm::quat(), false, -1, false, glm::vec3(0.0f, -1.0f, 0.0f), PALM_PRIORITY); - setJointRotation(parentJointIndex, palmRotation, true, PALM_PRIORITY); - _jointStates[jointIndex].rotation = glm::quat(); + setJointRotation(parentJointIndex, palmRotation, PALM_PRIORITY); + _jointStates[jointIndex]._rotation = glm::quat(); } else { setJointPosition(jointIndex, palm.getPosition(), palmRotation, @@ -199,10 +199,10 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) { void SkeletonModel::updateJointState(int index) { JointState& state = _jointStates[index]; - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const FBXJoint& joint = geometry.joints.at(index); + const FBXJoint& joint = state.getFBXJoint(); if (joint.parentIndex != -1) { const JointState& parentState = _jointStates.at(joint.parentIndex); + const FBXGeometry& geometry = _geometry->getFBXGeometry(); if (index == geometry.leanJointIndex) { maybeUpdateLeanRotation(parentState, joint, state); @@ -217,9 +217,9 @@ void SkeletonModel::updateJointState(int index) { Model::updateJointState(index); if (index == _geometry->getFBXGeometry().rootJointIndex) { - state.transform[3][0] = 0.0f; - state.transform[3][1] = 0.0f; - state.transform[3][2] = 0.0f; + state._transform[3][0] = 0.0f; + state._transform[3][1] = 0.0f; + state._transform[3][2] = 0.0f; } } @@ -229,9 +229,9 @@ void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const } // get the rotation axes in joint space and use them to adjust the rotation glm::mat3 axes = glm::mat3_cast(_rotation); - glm::mat3 inverse = glm::mat3(glm::inverse(parentState.transform * glm::translate(state.translation) * + glm::mat3 inverse = glm::mat3(glm::inverse(parentState._transform * glm::translate(state._translation) * joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation))); - state.rotation = glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), + state._rotation = glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanSideways(), glm::normalize(inverse * axes[2])) * glm::angleAxis(- RADIANS_PER_DEGREE * _owningAvatar->getHead()->getFinalLeanForward(), glm::normalize(inverse * axes[0])) * joint.rotation; } @@ -255,11 +255,11 @@ void SkeletonModel::renderJointConstraints(int jointIndex) { do { const FBXJoint& joint = geometry.joints.at(jointIndex); const JointState& jointState = _jointStates.at(jointIndex); - glm::vec3 position = extractTranslation(jointState.transform) + _translation; + glm::vec3 position = extractTranslation(jointState._transform) + _translation; glPushMatrix(); glTranslatef(position.x, position.y, position.z); - glm::quat parentRotation = (joint.parentIndex == -1) ? _rotation : _jointStates.at(joint.parentIndex).combinedRotation; + glm::quat parentRotation = (joint.parentIndex == -1) ? _rotation : _jointStates.at(joint.parentIndex)._combinedRotation; glm::vec3 rotationAxis = glm::axis(parentRotation); glRotatef(glm::degrees(glm::angle(parentRotation)), rotationAxis.x, rotationAxis.y, rotationAxis.z); float fanScale = directionSize * 0.75f; @@ -292,7 +292,7 @@ void SkeletonModel::renderJointConstraints(int jointIndex) { } glPopMatrix(); - renderOrientationDirections(position, jointState.combinedRotation, directionSize); + renderOrientationDirections(position, jointState._combinedRotation, directionSize); jointIndex = joint.parentIndex; } while (jointIndex != -1 && geometry.joints.at(jointIndex).isFree); @@ -355,12 +355,12 @@ void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, c glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f); glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition); - setJointRotation(shoulderJointIndex, shoulderRotation, true, PALM_PRIORITY); + setJointRotation(shoulderJointIndex, shoulderRotation, PALM_PRIORITY); setJointRotation(elbowJointIndex, rotationBetween(shoulderRotation * forwardVector, - wristPosition - elbowPosition) * shoulderRotation, true, PALM_PRIORITY); + wristPosition - elbowPosition) * shoulderRotation, PALM_PRIORITY); - setJointRotation(jointIndex, rotation, true, PALM_PRIORITY); + setJointRotation(jointIndex, rotation, PALM_PRIORITY); } bool SkeletonModel::getLeftHandPosition(glm::vec3& position) const { diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index 854b19236d..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); @@ -81,7 +82,13 @@ void OculusManager::configureCamera(Camera& camera, int screenWidth, int screenH void OculusManager::display(Camera& whichCamera) { #ifdef HAVE_LIBOVR - Application::getInstance()->getGlowEffect()->prepare(); + 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(); // render the left eye view to the left side of the screen const StereoEyeParams& leftEyeParams = _stereoConfig.GetEyeRenderParams(StereoEye_Left); @@ -100,6 +107,10 @@ void OculusManager::display(Camera& whichCamera) { Application::getInstance()->displaySide(whichCamera); + if (displayOverlays) { + applicationOverlay.displayOverlayTextureOculus(whichCamera); + } + // and the right eye to the right side const StereoEyeParams& rightEyeParams = _stereoConfig.GetEyeRenderParams(StereoEye_Right); glMatrixMode(GL_PROJECTION); @@ -115,6 +126,10 @@ void OculusManager::display(Camera& whichCamera) { Application::getInstance()->displaySide(whichCamera); + if (displayOverlays) { + applicationOverlay.displayOverlayTextureOculus(whichCamera); + } + glPopMatrix(); // restore our normal viewport @@ -187,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/devices/PrioVR.cpp b/interface/src/devices/PrioVR.cpp index 3e19f1800e..ab29fc004b 100644 --- a/interface/src/devices/PrioVR.cpp +++ b/interface/src/devices/PrioVR.cpp @@ -79,7 +79,7 @@ static void setPalm(float deltaTime, int index) { glm::vec3 position; glm::quat rotation; - Model* skeletonModel = &Application::getInstance()->getAvatar()->getSkeletonModel(); + SkeletonModel* skeletonModel = &Application::getInstance()->getAvatar()->getSkeletonModel(); int jointIndex; glm::quat inverseRotation = glm::inverse(Application::getInstance()->getAvatar()->getOrientation()); if (index == LEFT_HAND_INDEX) { diff --git a/interface/src/renderer/GlowEffect.cpp b/interface/src/renderer/GlowEffect.cpp index b52e8d8531..1eceb71752 100644 --- a/interface/src/renderer/GlowEffect.cpp +++ b/interface/src/renderer/GlowEffect.cpp @@ -139,8 +139,7 @@ QOpenGLFramebufferObject* GlowEffect::render(bool toTexture) { if (_isEmpty && _renderMode != DIFFUSE_ADD_MODE) { // copy the primary to the screen if (QOpenGLFramebufferObject::hasOpenGLFramebufferBlit()) { - QOpenGLFramebufferObject::blitFramebuffer(destFBO, primaryFBO); - + QOpenGLFramebufferObject::blitFramebuffer(destFBO, primaryFBO); } else { maybeBind(destFBO); glEnable(GL_TEXTURE_2D); diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 5a93dbcf25..215678dedc 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -135,13 +135,11 @@ void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locati program.release(); } -QVector Model::createJointStates(const FBXGeometry& geometry) { +QVector Model::createJointStates(const FBXGeometry& geometry) { QVector jointStates; foreach (const FBXJoint& joint, geometry.joints) { JointState state; - state.translation = joint.translation; - state.rotation = joint.rotation; - state.animationPriority = 0.0f; + state.setFBXJoint(joint); jointStates.append(state); } @@ -160,23 +158,17 @@ QVector Model::createJointStates(const FBXGeometry& geometry) continue; } JointState& state = jointStates[i]; - const FBXJoint& joint = geometry.joints[i]; + const FBXJoint& joint = state.getFBXJoint(); int parentIndex = joint.parentIndex; if (parentIndex == -1) { _rootIndex = i; - glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); - glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; - state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; - state.combinedRotation = _rotation * combinedRotation; + glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; + state.updateWorldTransform(baseTransform, _rotation); ++numJointsSet; jointIsSet[i] = true; } else if (jointIsSet[parentIndex]) { const JointState& parentState = jointStates.at(parentIndex); - glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; - state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; - state.combinedRotation = parentState.combinedRotation * combinedRotation; + state.updateWorldTransform(parentState._transform, parentState._combinedRotation); ++numJointsSet; jointIsSet[i] = true; } @@ -372,7 +364,7 @@ void Model::reset() { } const FBXGeometry& geometry = _geometry->getFBXGeometry(); for (int i = 0; i < _jointStates.size(); i++) { - _jointStates[i].rotation = geometry.joints.at(i).rotation; + _jointStates[i]._rotation = geometry.joints.at(i).rotation; } } @@ -582,7 +574,7 @@ bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; } - rotation = _jointStates.at(index).rotation; + rotation = _jointStates.at(index)._rotation; const glm::quat& defaultRotation = _geometry->getFBXGeometry().joints.at(index).rotation; return glm::abs(rotation.x - defaultRotation.x) >= EPSILON || glm::abs(rotation.y - defaultRotation.y) >= EPSILON || @@ -593,13 +585,13 @@ bool Model::getJointState(int index, glm::quat& rotation) const { void Model::setJointState(int index, bool valid, const glm::quat& rotation, float priority) { if (index != -1 && index < _jointStates.size()) { JointState& state = _jointStates[index]; - if (priority >= state.animationPriority) { + if (priority >= state._animationPriority) { if (valid) { - state.rotation = rotation; - state.animationPriority = priority; - } else if (priority == state.animationPriority) { - state.rotation = _geometry->getFBXGeometry().joints.at(index).rotation; - state.animationPriority = 0.0f; + state._rotation = rotation; + state._animationPriority = priority; + } else if (priority == state._animationPriority) { + state._rotation = _geometry->getFBXGeometry().joints.at(index).rotation; + state._animationPriority = 0.0f; } } } @@ -632,7 +624,7 @@ bool Model::getJointPosition(int jointIndex, glm::vec3& position) const { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } - position = _translation + extractTranslation(_jointStates[jointIndex].transform); + position = _translation + extractTranslation(_jointStates[jointIndex]._transform); return true; } @@ -640,7 +632,7 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind) if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } - rotation = _jointStates[jointIndex].combinedRotation * + rotation = _jointStates[jointIndex]._combinedRotation * (fromBind ? _geometry->getFBXGeometry().joints[jointIndex].inverseBindRotation : _geometry->getFBXGeometry().joints[jointIndex].inverseDefaultRotation); return true; @@ -858,11 +850,11 @@ void Model::updateShapePositions() { for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; // shape position and rotation need to be in world-frame - glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition); - glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation; + glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i]._combinedRotation * joint.shapePosition); + glm::vec3 worldPosition = extractTranslation(_jointStates[i]._transform) + jointToShapeOffset + _translation; Shape* shape = _jointShapes[i]; shape->setPosition(worldPosition); - shape->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); + shape->setRotation(_jointStates[i]._combinedRotation * joint.shapeRotation); float distance = glm::distance(worldPosition, _translation) + shape->getBoundingRadius(); if (distance > _boundingRadius) { _boundingRadius = distance; @@ -884,12 +876,12 @@ bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direct float radiusScale = extractUniformScale(_scale); for (int i = 0; i < _jointStates.size(); i++) { const FBXJoint& joint = geometry.joints[i]; - glm::vec3 end = extractTranslation(_jointStates[i].transform); + glm::vec3 end = extractTranslation(_jointStates[i]._transform); float endRadius = joint.boneRadius * radiusScale; glm::vec3 start = end; float startRadius = joint.boneRadius * radiusScale; if (joint.parentIndex != -1) { - start = extractTranslation(_jointStates[joint.parentIndex].transform); + start = extractTranslation(_jointStates[joint.parentIndex]._transform); startRadius = geometry.joints[joint.parentIndex].boneRadius * radiusScale; } // for now, use average of start and end radii @@ -1115,7 +1107,7 @@ void Model::simulateInternal(float deltaTime) { const FBXMesh& mesh = geometry.meshes.at(i); for (int j = 0; j < mesh.clusters.size(); j++) { const FBXCluster& cluster = mesh.clusters.at(j); - state.clusterMatrices[j] = _jointStates[cluster.jointIndex].transform * cluster.inverseBindMatrix; + state.clusterMatrices[j] = _jointStates[cluster.jointIndex]._transform * cluster.inverseBindMatrix; } } @@ -1127,21 +1119,15 @@ void Model::simulateInternal(float deltaTime) { void Model::updateJointState(int index) { JointState& state = _jointStates[index]; - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const FBXJoint& joint = geometry.joints.at(index); + const FBXJoint& joint = state.getFBXJoint(); if (joint.parentIndex == -1) { - glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset); - glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; - state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; - state.combinedRotation = _rotation * combinedRotation; + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; + state.updateWorldTransform(baseTransform, _rotation); } else { const JointState& parentState = _jointStates.at(joint.parentIndex); - glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; - state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform * - glm::mat4_cast(combinedRotation) * joint.postTransform; - state.combinedRotation = parentState.combinedRotation * combinedRotation; + state.updateWorldTransform(parentState._transform, parentState._combinedRotation); } } @@ -1169,22 +1155,22 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat endRotation; if (useRotation) { getJointRotation(jointIndex, endRotation, true); - applyRotationDelta(jointIndex, rotation * glm::inverse(endRotation), priority); + applyRotationDelta(jointIndex, rotation * glm::inverse(endRotation), true, priority); getJointRotation(jointIndex, endRotation, true); } // then, we go from the joint upwards, rotating the end as close as possible to the target - glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex].transform); + glm::vec3 endPosition = extractTranslation(_jointStates[jointIndex]._transform); for (int j = 1; freeLineage.at(j - 1) != lastFreeIndex; j++) { int index = freeLineage.at(j); - const FBXJoint& joint = geometry.joints.at(index); + JointState& state = _jointStates[index]; + const FBXJoint& joint = state.getFBXJoint(); if (!(joint.isFree || allIntermediatesFree)) { continue; } - JointState& state = _jointStates[index]; - glm::vec3 jointPosition = extractTranslation(state.transform); + glm::vec3 jointPosition = extractTranslation(state._transform); glm::vec3 jointVector = endPosition - jointPosition; - glm::quat oldCombinedRotation = state.combinedRotation; + glm::quat oldCombinedRotation = state._combinedRotation; glm::quat combinedDelta; float combinedWeight; if (useRotation) { @@ -1202,7 +1188,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const for (int k = j - 1; k > 0; k--) { int index = freeLineage.at(k); updateJointState(index); - positionSum += extractTranslation(_jointStates.at(index).transform); + positionSum += extractTranslation(_jointStates.at(index)._transform); } glm::vec3 projectedCenterOfMass = glm::cross(jointVector, glm::cross(positionSum / (j - 1.0f) - jointPosition, jointVector)); @@ -1213,8 +1199,8 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const 1.0f / (combinedWeight + 1.0f)); } } - applyRotationDelta(index, combinedDelta, priority); - glm::quat actualDelta = state.combinedRotation * glm::inverse(oldCombinedRotation); + applyRotationDelta(index, combinedDelta, true, priority); + glm::quat actualDelta = state._combinedRotation * glm::inverse(oldCombinedRotation); endPosition = actualDelta * jointVector + jointPosition; if (useRotation) { endRotation = actualDelta * endRotation; @@ -1231,35 +1217,34 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& translation, const return true; } -bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind, float priority) { +bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, float priority) { if (jointIndex == -1 || _jointStates.isEmpty()) { return false; } JointState& state = _jointStates[jointIndex]; - if (priority >= state.animationPriority) { - state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation * - glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation : - _geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation); - state.animationPriority = priority; + if (priority >= state._animationPriority) { + state._rotation = state._rotation * glm::inverse(state._combinedRotation) * rotation * + glm::inverse(_geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation); + state._animationPriority = priority; } return true; } void Model::setJointTranslation(int jointIndex, const glm::vec3& translation) { - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - const FBXJoint& joint = geometry.joints.at(jointIndex); + JointState& state = _jointStates[jointIndex]; + const FBXJoint& joint = state.getFBXJoint(); glm::mat4 parentTransform; if (joint.parentIndex == -1) { + const FBXGeometry& geometry = _geometry->getFBXGeometry(); parentTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset) * geometry.offset; } else { - parentTransform = _jointStates.at(joint.parentIndex).transform; + parentTransform = _jointStates.at(joint.parentIndex)._transform; } - JointState& state = _jointStates[jointIndex]; glm::vec3 preTranslation = extractTranslation(joint.preTransform * glm::mat4_cast(joint.preRotation * - state.rotation * joint.postRotation) * joint.postTransform); - state.translation = glm::vec3(glm::inverse(parentTransform) * glm::vec4(translation, 1.0f)) - preTranslation; + state._rotation * joint.postRotation) * joint.postTransform); + state._translation = glm::vec3(glm::inverse(parentTransform) * glm::vec4(translation, 1.0f)) - preTranslation; } bool Model::restoreJointPosition(int jointIndex, float percent, float priority) { @@ -1271,11 +1256,11 @@ bool Model::restoreJointPosition(int jointIndex, float percent, float priority) foreach (int index, freeLineage) { JointState& state = _jointStates[index]; - if (priority == state.animationPriority) { + if (priority == state._animationPriority) { const FBXJoint& joint = geometry.joints.at(index); - state.rotation = safeMix(state.rotation, joint.rotation, percent); - state.translation = glm::mix(state.translation, joint.translation, percent); - state.animationPriority = 0.0f; + state._rotation = safeMix(state._rotation, joint.rotation, percent); + state._translation = glm::mix(state._translation, joint.translation, percent); + state._animationPriority = 0.0f; } } return true; @@ -1297,23 +1282,23 @@ float Model::getLimbLength(int jointIndex) const { void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain, float priority) { JointState& state = _jointStates[jointIndex]; - if (priority < state.animationPriority) { + if (priority < state._animationPriority) { return; } - state.animationPriority = priority; - const FBXJoint& joint = _geometry->getFBXGeometry().joints[jointIndex]; + state._animationPriority = priority; + const FBXJoint& joint = state.getFBXJoint(); if (!constrain || (joint.rotationMin == glm::vec3(-PI, -PI, -PI) && joint.rotationMax == glm::vec3(PI, PI, PI))) { // no constraints - state.rotation = state.rotation * glm::inverse(state.combinedRotation) * delta * state.combinedRotation; - state.combinedRotation = delta * state.combinedRotation; + state._rotation = state._rotation * glm::inverse(state._combinedRotation) * delta * state._combinedRotation; + state._combinedRotation = delta * state._combinedRotation; return; } - glm::quat targetRotation = delta * state.combinedRotation; - glm::vec3 eulers = safeEulerAngles(state.rotation * glm::inverse(state.combinedRotation) * targetRotation); + glm::quat targetRotation = delta * state._combinedRotation; + glm::vec3 eulers = safeEulerAngles(state._rotation * glm::inverse(state._combinedRotation) * targetRotation); glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax)); - state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation; - state.rotation = newRotation; + state._combinedRotation = state._combinedRotation * glm::inverse(state._rotation) * newRotation; + state._rotation = newRotation; } const int BALL_SUBDIVISIONS = 10; @@ -1851,10 +1836,10 @@ void AnimationHandle::simulate(float deltaTime) { for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { - Model::JointState& state = _model->_jointStates[mapping]; - if (_priority >= state.animationPriority) { - state.rotation = frame.rotations.at(i); - state.animationPriority = _priority; + JointState& state = _model->_jointStates[mapping]; + if (_priority >= state._animationPriority) { + state._rotation = frame.rotations.at(i); + state._animationPriority = _priority; } } } @@ -1875,10 +1860,10 @@ void AnimationHandle::simulate(float deltaTime) { for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { - Model::JointState& state = _model->_jointStates[mapping]; - if (_priority >= state.animationPriority) { - state.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); - state.animationPriority = _priority; + JointState& state = _model->_jointStates[mapping]; + if (_priority >= state._animationPriority) { + state._rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction); + state._animationPriority = _priority; } } } @@ -1888,10 +1873,32 @@ void AnimationHandle::replaceMatchingPriorities(float newPriority) { for (int i = 0; i < _jointMappings.size(); i++) { int mapping = _jointMappings.at(i); if (mapping != -1) { - Model::JointState& state = _model->_jointStates[mapping]; - if (_priority == state.animationPriority) { - state.animationPriority = newPriority; + JointState& state = _model->_jointStates[mapping]; + if (_priority == state._animationPriority) { + state._animationPriority = newPriority; } } } } + +// ---------------------------------------------------------------------------- +// JointState TODO: move this class to its own files +// ---------------------------------------------------------------------------- +JointState::JointState() : + _translation(0.0f), + _animationPriority(0.0f), + _fbxJoint(NULL) { +} + +void JointState::setFBXJoint(const FBXJoint& joint) { + assert(&joint != NULL); + _translation = joint.translation; + _rotation = joint.rotation; + _fbxJoint = &joint; +} + +void JointState::updateWorldTransform(const glm::mat4& baseTransform, const glm::quat& parentRotation) { + glm::quat combinedRotation = _fbxJoint->preRotation * _rotation * _fbxJoint->postRotation; + _transform = baseTransform * glm::translate(_translation) * _fbxJoint->preTransform * glm::mat4_cast(combinedRotation) * _fbxJoint->postTransform; + _combinedRotation = parentRotation * combinedRotation; +} diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index a4e45287dd..349e8305c4 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -30,6 +30,25 @@ class Shape; typedef QSharedPointer AnimationHandlePointer; typedef QWeakPointer WeakAnimationHandlePointer; + +class JointState { +public: + JointState(); + + void setFBXJoint(const FBXJoint& joint); + const FBXJoint& getFBXJoint() const { return *_fbxJoint; } + + void updateWorldTransform(const glm::mat4& baseTransform, const glm::quat& parentRotation); + + glm::vec3 _translation; // translation relative to parent + glm::quat _rotation; // rotation relative to parent + glm::mat4 _transform; // rotation to world frame + translation in model frame + glm::quat _combinedRotation; // rotation from joint local to world frame + float _animationPriority; // the priority of the animation affecting this joint + +private: + const FBXJoint* _fbxJoint; // JointState does not own its FBXJoint +}; /// A generic 3D model displaying geometry loaded from a URL. class Model : public QObject { @@ -182,15 +201,6 @@ protected: bool _snappedToCenter; /// are we currently snapped to center int _rootIndex; - class JointState { - public: - glm::vec3 translation; // translation relative to parent - glm::quat rotation; // rotation relative to parent - glm::mat4 transform; // rotation to world frame + translation in model frame - glm::quat combinedRotation; // rotation from joint local to world frame - float animationPriority; // the priority of the animation affecting this joint - }; - bool _shapesAreDirty; QVector _jointStates; QVector _jointShapes; @@ -221,7 +231,7 @@ protected: bool setJointPosition(int jointIndex, const glm::vec3& translation, const glm::quat& rotation = glm::quat(), bool useRotation = false, int lastFreeIndex = -1, bool allIntermediatesFree = false, const glm::vec3& alignment = glm::vec3(0.0f, -1.0f, 0.0f), float priority = 1.0f); - bool setJointRotation(int jointIndex, const glm::quat& rotation, bool fromBind = false, float priority = 1.0f); + bool setJointRotation(int jointIndex, const glm::quat& rotation, float priority = 1.0f); void setJointTranslation(int jointIndex, const glm::vec3& translation); diff --git a/interface/src/ui/ApplicationOverlay.cpp b/interface/src/ui/ApplicationOverlay.cpp new file mode 100644 index 0000000000..740b310ff2 --- /dev/null +++ b/interface/src/ui/ApplicationOverlay.cpp @@ -0,0 +1,343 @@ +// +// ApplicationOverlay.cpp +// interface/src/ui/overlays +// +// Created by Benjamin Arnold on 5/27/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 "InterfaceConfig.h" + +#include +#include + +#include "Application.h" +#include "ApplicationOverlay.h" + +#include "ui/Stats.h" + +ApplicationOverlay::ApplicationOverlay() : _framebufferObject(NULL) { + +} + +ApplicationOverlay::~ApplicationOverlay() { + if (_framebufferObject != NULL) { + delete _framebufferObject; + } +} + +const float WHITE_TEXT[] = { 0.93f, 0.93f, 0.93f }; + +// Renders the overlays either to a texture or to the screen +void ApplicationOverlay::renderOverlay(bool renderToTexture) { + PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "ApplicationOverlay::displayOverlay()"); + + Application* application = Application::getInstance(); + + Overlays& overlays = application->getOverlays(); + QGLWidget* glWidget = application->getGLWidget(); + MyAvatar* myAvatar = application->getAvatar(); + Audio* audio = application->getAudio(); + const VoxelPacketProcessor& voxelPacketProcessor = application->getVoxelPacketProcessor(); + BandwidthMeter* bandwidthMeter = application->getBandwidthMeter(); + NodeBounds& nodeBoundsDisplay = application->getNodeBoundsDisplay(); + + if (renderToTexture) { + getFramebufferObject()->bind(); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + } + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + // Render 2D overlay: I/O level bar graphs and text + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + + glLoadIdentity(); + gluOrtho2D(0, glWidget->width(), glWidget->height(), 0); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + + // Display a single screen-size quad to create an alpha blended 'collision' flash + if (audio->getCollisionFlashesScreen()) { + float collisionSoundMagnitude = audio->getCollisionSoundMagnitude(); + const float VISIBLE_COLLISION_SOUND_MAGNITUDE = 0.5f; + if (collisionSoundMagnitude > VISIBLE_COLLISION_SOUND_MAGNITUDE) { + renderCollisionOverlay(glWidget->width(), glWidget->height(), audio->getCollisionSoundMagnitude()); + } + } + + // Audio VU Meter and Mute Icon + const int MUTE_ICON_SIZE = 24; + const int AUDIO_METER_INSET = 2; + const int MUTE_ICON_PADDING = 10; + const int AUDIO_METER_WIDTH = MIRROR_VIEW_WIDTH - MUTE_ICON_SIZE - AUDIO_METER_INSET - MUTE_ICON_PADDING; + const int AUDIO_METER_SCALE_WIDTH = AUDIO_METER_WIDTH - 2 * AUDIO_METER_INSET; + const int AUDIO_METER_HEIGHT = 8; + const int AUDIO_METER_GAP = 5; + const int AUDIO_METER_X = MIRROR_VIEW_LEFT_PADDING + MUTE_ICON_SIZE + AUDIO_METER_INSET + AUDIO_METER_GAP; + + int audioMeterY; + if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + audioMeterY = MIRROR_VIEW_HEIGHT + AUDIO_METER_GAP + MUTE_ICON_PADDING; + } else { + audioMeterY = AUDIO_METER_GAP + MUTE_ICON_PADDING; + } + + const float AUDIO_METER_BLUE[] = { 0.0, 0.0, 1.0 }; + const float AUDIO_METER_GREEN[] = { 0.0, 1.0, 0.0 }; + const float AUDIO_METER_RED[] = { 1.0, 0.0, 0.0 }; + const float AUDIO_GREEN_START = 0.25 * AUDIO_METER_SCALE_WIDTH; + const float AUDIO_RED_START = 0.80 * AUDIO_METER_SCALE_WIDTH; + const float CLIPPING_INDICATOR_TIME = 1.0f; + const float AUDIO_METER_AVERAGING = 0.5; + const float LOG2 = log(2.f); + const float METER_LOUDNESS_SCALE = 2.8f / 5.f; + const float LOG2_LOUDNESS_FLOOR = 11.f; + float audioLevel = 0.f; + float loudness = audio->getLastInputLoudness() + 1.f; + + _trailingAudioLoudness = AUDIO_METER_AVERAGING * _trailingAudioLoudness + (1.f - AUDIO_METER_AVERAGING) * loudness; + float log2loudness = log(_trailingAudioLoudness) / LOG2; + + if (log2loudness <= LOG2_LOUDNESS_FLOOR) { + audioLevel = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; + } else { + audioLevel = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.f)) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; + } + if (audioLevel > AUDIO_METER_SCALE_WIDTH) { + audioLevel = AUDIO_METER_SCALE_WIDTH; + } + bool isClipping = ((audio->getTimeSinceLastClip() > 0.f) && (audio->getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)); + + if ((audio->getTimeSinceLastClip() > 0.f) && (audio->getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)) { + const float MAX_MAGNITUDE = 0.7f; + float magnitude = MAX_MAGNITUDE * (1 - audio->getTimeSinceLastClip() / CLIPPING_INDICATOR_TIME); + renderCollisionOverlay(glWidget->width(), glWidget->height(), magnitude, 1.0f); + } + + audio->renderToolBox(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP, + audioMeterY, + Menu::getInstance()->isOptionChecked(MenuOption::Mirror)); + + audio->renderScope(glWidget->width(), glWidget->height()); + + glBegin(GL_QUADS); + if (isClipping) { + glColor3f(1, 0, 0); + } else { + glColor3f(0.475f, 0.475f, 0.475f); + } + + audioMeterY += AUDIO_METER_HEIGHT; + + glColor3f(0, 0, 0); + // Draw audio meter background Quad + glVertex2i(AUDIO_METER_X, audioMeterY); + glVertex2i(AUDIO_METER_X + AUDIO_METER_WIDTH, audioMeterY); + glVertex2i(AUDIO_METER_X + AUDIO_METER_WIDTH, audioMeterY + AUDIO_METER_HEIGHT); + glVertex2i(AUDIO_METER_X, audioMeterY + AUDIO_METER_HEIGHT); + + if (audioLevel > AUDIO_RED_START) { + if (!isClipping) { + glColor3fv(AUDIO_METER_RED); + } else { + glColor3f(1, 1, 1); + } + // Draw Red Quad + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + AUDIO_RED_START, audioMeterY + AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + AUDIO_RED_START, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); + audioLevel = AUDIO_RED_START; + } + if (audioLevel > AUDIO_GREEN_START) { + if (!isClipping) { + glColor3fv(AUDIO_METER_GREEN); + } else { + glColor3f(1, 1, 1); + } + // Draw Green Quad + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + AUDIO_GREEN_START, audioMeterY + AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + AUDIO_GREEN_START, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); + audioLevel = AUDIO_GREEN_START; + } + // Draw Blue Quad + if (!isClipping) { + glColor3fv(AUDIO_METER_BLUE); + } else { + glColor3f(1, 1, 1); + } + // Draw Blue (low level) quad + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET, audioMeterY + AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET + audioLevel, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); + glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); + glEnd(); + + + if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) { + myAvatar->renderHeadMouse(glWidget->width(), glWidget->height()); + } + + // Display stats and log text onscreen + glLineWidth(1.0f); + glPointSize(1.0f); + + if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + // let's set horizontal offset to give stats some margin to mirror + int horizontalOffset = MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; + int voxelPacketsToProcess = voxelPacketProcessor.packetsToProcessCount(); + // Onscreen text about position, servers, etc + Stats::getInstance()->display(WHITE_TEXT, horizontalOffset, application->getFps(), application->getPacketsPerSecond(), application->getBytesPerSecond(), voxelPacketsToProcess); + // Bandwidth meter + if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) { + Stats::drawBackground(0x33333399, glWidget->width() - 296, glWidget->height() - 68, 296, 68); + bandwidthMeter->render(glWidget->width(), glWidget->height()); + } + } + + // Show on-screen msec timer + if (Menu::getInstance()->isOptionChecked(MenuOption::FrameTimer)) { + char frameTimer[10]; + quint64 mSecsNow = floor(usecTimestampNow() / 1000.0 + 0.5); + sprintf(frameTimer, "%d\n", (int)(mSecsNow % 1000)); + int timerBottom = + (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && + Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) + ? 80 : 20; + drawText(glWidget->width() - 100, glWidget->height() - timerBottom, 0.30f, 0.0f, 0, frameTimer, WHITE_TEXT); + } + nodeBoundsDisplay.drawOverlay(); + + // give external parties a change to hook in + emit application->renderingOverlay(); + + overlays.render2D(); + + glPopMatrix(); + + glMatrixMode(GL_MODELVIEW); + glEnable(GL_DEPTH_TEST); + glEnable(GL_LIGHTING); + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + + + if (renderToTexture) { + getFramebufferObject()->release(); + } +} + +// Draws the FBO texture for the screen +void ApplicationOverlay::displayOverlayTexture(Camera& whichCamera) { + + Application* application = Application::getInstance(); + QGLWidget* glWidget = application->getGLWidget(); + + glEnable(GL_TEXTURE_2D); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + + glLoadIdentity(); + gluOrtho2D(0, glWidget->width(), glWidget->height(), 0); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + + glBegin(GL_QUADS); + glTexCoord2f(0, 0); glVertex2i(0, glWidget->height()); + glTexCoord2f(1, 0); glVertex2i(glWidget->width(), glWidget->height()); + glTexCoord2f(1, 1); glVertex2i(glWidget->width(), 0); + glTexCoord2f(0, 1); glVertex2i(0, 0); + glEnd(); + + glPopMatrix(); + glDisable(GL_TEXTURE_2D); +} + +// Draws the FBO texture for Oculus rift. TODO: Draw a curved texture instead of plane. +void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) { + + Application* application = Application::getInstance(); + + QGLWidget* glWidget = application->getGLWidget(); + MyAvatar* myAvatar = application->getAvatar(); + const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation(); + + // Calculates the world space width and height of the texture based on a desired FOV + const float overlayFov = whichCamera.getFieldOfView() * PI / 180.0f; + const float overlayDistance = 1; + const float overlayAspectRatio = glWidget->width() / (float)glWidget->height(); + const float overlayHeight = overlayDistance * tan(overlayFov); + const float overlayWidth = overlayHeight * overlayAspectRatio; + const float halfOverlayWidth = overlayWidth / 2; + const float halfOverlayHeight = overlayHeight / 2; + + glActiveTexture(GL_TEXTURE0); + + glDepthMask(GL_FALSE); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture()); + glDisable(GL_DEPTH_TEST); + glDisable(GL_LIGHTING); + glEnable(GL_TEXTURE_2D); + + glMatrixMode(GL_MODELVIEW); + + glPushMatrix(); + glLoadIdentity(); + // Transform to world space + glm::quat rotation = whichCamera.getRotation(); + glm::vec3 axis2 = glm::axis(rotation); + glRotatef(-glm::degrees(glm::angle(rotation)), axis2.x, axis2.y, axis2.z); + glTranslatef(viewMatrixTranslation.x, viewMatrixTranslation.y, viewMatrixTranslation.z); + + // Translate to the front of the camera + glm::vec3 pos = whichCamera.getPosition(); + glm::quat rot = myAvatar->getOrientation(); + glm::vec3 axis = glm::axis(rot); + pos += rot * glm::vec3(0.0, 0.0, -overlayDistance); + + glTranslatef(pos.x, pos.y, pos.z); + glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z); + + glBegin(GL_QUADS); + glTexCoord2f(1, 0); glVertex3f(-halfOverlayWidth, halfOverlayHeight, 0); + glTexCoord2f(0, 0); glVertex3f(halfOverlayWidth, halfOverlayHeight, 0); + glTexCoord2f(0, 1); glVertex3f(halfOverlayWidth, -halfOverlayHeight, 0); + glTexCoord2f(1, 1); glVertex3f(-halfOverlayWidth, -halfOverlayHeight, 0); + glEnd(); + + glPopMatrix(); + + glEnable(GL_DEPTH_TEST); + glDepthMask(GL_TRUE); + glBindTexture(GL_TEXTURE_2D, 0); + glDisable(GL_TEXTURE_2D); + + glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE); + glEnable(GL_LIGHTING); + +} + +QOpenGLFramebufferObject* ApplicationOverlay::getFramebufferObject() { + if (!_framebufferObject) { + _framebufferObject = new QOpenGLFramebufferObject(Application::getInstance()->getGLWidget()->size()); + + glBindTexture(GL_TEXTURE_2D, _framebufferObject->texture()); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glBindTexture(GL_TEXTURE_2D, 0); + } + return _framebufferObject; +} + diff --git a/interface/src/ui/ApplicationOverlay.h b/interface/src/ui/ApplicationOverlay.h new file mode 100644 index 0000000000..cdc4065f45 --- /dev/null +++ b/interface/src/ui/ApplicationOverlay.h @@ -0,0 +1,38 @@ +// +// ApplicationOverlay.h +// interface/src/ui/overlays +// +// Created by Benjamin Arnold on 5/27/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_ApplicationOverlay_h +#define hifi_ApplicationOverlay_h + +class Overlays; +class QOpenGLFramebufferObject; + +// Handles the drawing of the overlays to the scree +class ApplicationOverlay { +public: + + ApplicationOverlay(); + ~ApplicationOverlay(); + + void renderOverlay(bool renderToTexture = false); + void displayOverlayTexture(Camera& whichCamera); + void displayOverlayTextureOculus(Camera& whichCamera); + + // Getters + QOpenGLFramebufferObject* getFramebufferObject(); + +private: + + QOpenGLFramebufferObject* _framebufferObject; + float _trailingAudioLoudness; +}; + +#endif // hifi_ApplicationOverlay_h \ No newline at end of file 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/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/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/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/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index dad592f838..c655a45b57 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -9,6 +9,8 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include + #include #include @@ -177,21 +179,41 @@ bool ModelTreeElement::findDetailedRayIntersection(const glm::vec3& origin, cons extents.minimum *= scale; extents.maximum *= scale; - - calculateRotatedExtents(extents, model.getModelRotation()); - - extents.minimum += model.getPosition(); - extents.maximum += model.getPosition(); - - AABox rotatedExtentsBox(extents.minimum, (extents.maximum - extents.minimum)); + Extents rotatedExtents = extents; + + calculateRotatedExtents(rotatedExtents, model.getModelRotation()); + + rotatedExtents.minimum += model.getPosition(); + rotatedExtents.maximum += model.getPosition(); + + + AABox rotatedExtentsBox(rotatedExtents.minimum, (rotatedExtents.maximum - rotatedExtents.minimum)); + + // 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)) { - if (localDistance < distance) { - distance = localDistance; - face = localFace; - *intersectedObject = (void*)(&model); - somethingIntersected = true; - } + + // 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()); + glm::mat4 modelToWorldMatrix = translation * rotation; + glm::mat4 worldToModelMatrix = glm::inverse(modelToWorldMatrix); + + 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)); + + // we can use the AABox's ray intersection by mapping our origin and direction into the model frame + // and testing intersection there. + if (modelFrameBox.findRayIntersection(modelFrameOrigin, modelFrameDirection, localDistance, localFace)) { + if (localDistance < distance) { + distance = localDistance; + face = localFace; + *intersectedObject = (void*)(&model); + somethingIntersected = true; + } + } } } else if (localDistance < distance) { distance = localDistance; 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;