diff --git a/examples/editModels.js b/examples/editModels.js index d53b854003..30d1e4edf4 100644 --- a/examples/editModels.js +++ b/examples/editModels.js @@ -188,7 +188,7 @@ function controller(wichSide) { var X = Vec3.sum(A, Vec3.multiply(B, x)); var d = Vec3.length(Vec3.subtract(P, X)); - if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) { + if (0 < x && x < LASER_LENGTH_FACTOR) { return { valid: true, x: x, y: y, z: z }; } return { valid: false }; @@ -293,39 +293,43 @@ function controller(wichSide) { if (this.pressing) { Vec3.print("Looking at: ", this.palmPosition); - var foundModels = Models.findModels(this.palmPosition, LASER_LENGTH_FACTOR); + var pickRay = { origin: this.palmPosition, + direction: Vec3.normalize(Vec3.subtract(this.tipPosition, this.palmPosition)) }; + var foundIntersection = Models.findRayIntersection(pickRay); - for (var i = 0; i < foundModels.length; i++) { - - if (!foundModels[i].isKnownID) { - var identify = Models.identifyModel(foundModels[i]); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + "(update loop)"); - return; - } - foundModels[i] = identify; + if(!foundIntersection.accurate) { + return; + } + var foundModel = foundIntersection.modelID; + + if (!foundModel.isKnownID) { + var identify = Models.identifyModel(foundModel); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + " (update loop " + foundModel.id + ")"); + continue; } - - var properties = Models.getModelProperties(foundModels[i]); - print("foundModels["+i+"].modelURL=" + properties.modelURL); - - if (isLocked(properties)) { - print("Model locked " + properties.id); - } else { - print("Checking properties: " + properties.id + " " + properties.isKnownID); - var check = this.checkModel(properties); - if (check.valid) { - this.grab(foundModels[i], properties); - this.x = check.x; - this.y = check.y; - this.z = check.z; - return; - } + foundModel = identify; + } + + var properties = Models.getModelProperties(foundModel); + print("foundModel.modelURL=" + properties.modelURL); + + if (isLocked(properties)) { + print("Model locked " + properties.id); + } else { + print("Checking properties: " + properties.id + " " + properties.isKnownID); + var check = this.checkModel(properties); + if (check.valid) { + this.grab(foundModel, properties); + this.x = check.x; + this.y = check.y; + this.z = check.z; + return; } } } } - + this.cleanup = function () { Overlays.deleteOverlay(this.laser); Overlays.deleteOverlay(this.ball); @@ -478,93 +482,97 @@ function mousePressEvent(event) { } else { var pickRay = Camera.computePickRay(event.x, event.y); Vec3.print("[Mouse] Looking at: ", pickRay.origin); - var foundModels = Models.findModels(pickRay.origin, LASER_LENGTH_FACTOR); - var closest = -1.0; - for (var i = 0; i < foundModels.length; i++) { - if (!foundModels[i].isKnownID) { - var identify = Models.identifyModel(foundModels[i]); - if (!identify.isKnownID) { - print("Unknown ID " + identify.id + "(update loop)"); - continue; - } - foundModels[i] = identify; - } - - var properties = Models.getModelProperties(foundModels[i]); - if (isLocked(properties)) { - print("Model locked " + properties.id); - } else { - print("Checking properties: " + properties.id + " " + properties.isKnownID); - // P P - Model - // /| A - Palm - // / | d B - unit vector toward tip - // / | X - base of the perpendicular line - // A---X----->B d - distance fom axis - // x x - distance from A - // - // |X-A| = (P-A).B - // X == A + ((P-A).B)B - // d = |P-X| - - var A = pickRay.origin; - var B = Vec3.normalize(pickRay.direction); - var P = properties.position; - - var x = Vec3.dot(Vec3.subtract(P, A), B); - var X = Vec3.sum(A, Vec3.multiply(B, x)); - var d = Vec3.length(Vec3.subtract(P, X)); - - if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) { - if (closest < 0.0) { - closest = x; - } - - if (x <= closest) { - modelSelected = true; - selectedModelID = foundModels[i]; - selectedModelProperties = properties; - - orientation = MyAvatar.orientation; - intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); - } - } + var foundIntersection = Models.findRayIntersection(pickRay); + + if(!foundIntersection.accurate) { + return; + } + var foundModel = foundIntersection.modelID; + + if (!foundModel.isKnownID) { + var identify = Models.identifyModel(foundModel); + if (!identify.isKnownID) { + print("Unknown ID " + identify.id + " (update loop " + foundModel.id + ")"); + continue; } + foundModel = identify; } - if (modelSelected) { - selectedModelProperties.oldRadius = selectedModelProperties.radius; - selectedModelProperties.oldPosition = { - x: selectedModelProperties.position.x, - y: selectedModelProperties.position.y, - z: selectedModelProperties.position.z, - }; - selectedModelProperties.oldRotation = { - x: selectedModelProperties.modelRotation.x, - y: selectedModelProperties.modelRotation.y, - z: selectedModelProperties.modelRotation.z, - w: selectedModelProperties.modelRotation.w, - }; + var properties = Models.getModelProperties(foundModel); + if (isLocked(properties)) { + print("Model locked " + properties.id); + } else { + print("Checking properties: " + properties.id + " " + properties.isKnownID); + // P P - Model + // /| A - Palm + // / | d B - unit vector toward tip + // / | X - base of the perpendicular line + // A---X----->B d - distance fom axis + // x x - distance from A + // + // |X-A| = (P-A).B + // X == A + ((P-A).B)B + // d = |P-X| - selectedModelProperties.glowLevel = 0.1; - Models.editModel(selectedModelID, { glowLevel: selectedModelProperties.glowLevel}); + var A = pickRay.origin; + var B = Vec3.normalize(pickRay.direction); + var P = properties.position; - print("Clicked on " + selectedModelID.id + " " + modelSelected); + var x = Vec3.dot(Vec3.subtract(P, A), B); + var X = Vec3.sum(A, Vec3.multiply(B, x)); + var d = Vec3.length(Vec3.subtract(P, X)); + + if (0 < x && x < LASER_LENGTH_FACTOR) { + modelSelected = true; + selectedModelID = foundModel; + selectedModelProperties = properties; + + orientation = MyAvatar.orientation; + intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation)); + } } } + + if (modelSelected) { + selectedModelProperties.oldRadius = selectedModelProperties.radius; + selectedModelProperties.oldPosition = { + x: selectedModelProperties.position.x, + y: selectedModelProperties.position.y, + z: selectedModelProperties.position.z, + }; + selectedModelProperties.oldRotation = { + x: selectedModelProperties.modelRotation.x, + y: selectedModelProperties.modelRotation.y, + z: selectedModelProperties.modelRotation.z, + w: selectedModelProperties.modelRotation.w, + }; + selectedModelProperties.glowLevel = 0.0; + + print("Clicked on " + selectedModelID.id + " " + modelSelected); + } } -Controller.mouseReleaseEvent.connect(function() { - if (modelSelected) { - Models.editModel(selectedModelID, { glowLevel: 0.0 }); - modelSelected = false; - } - }); - +var glowedModelID = { id: -1, isKnownID: false }; var oldModifier = 0; var modifier = 0; var wasShifted = false; function mouseMoveEvent(event) { + var pickRay = Camera.computePickRay(event.x, event.y); + if (!modelSelected) { + var modelIntersection = Models.findRayIntersection(pickRay); + if (modelIntersection.accurate) { + if(glowedModelID.isKnownID && glowedModelID.id != modelIntersection.modelID.id) { + Models.editModel(glowedModelID, { glowLevel: 0.0 }); + glowedModelID.id = -1; + glowedModelID.isKnownID = false; + } + + if (modelIntersection.modelID.isKnownID) { + Models.editModel(modelIntersection.modelID, { glowLevel: 0.25 }); + glowedModelID = modelIntersection.modelID; + } + } return; } @@ -579,8 +587,7 @@ function mouseMoveEvent(event) { } else { modifier = 0; } - - var pickRay = Camera.computePickRay(event.x, event.y); + pickRay = Camera.computePickRay(event.x, event.y); if (wasShifted != event.isShifted || modifier != oldModifier) { selectedModelProperties.oldRadius = selectedModelProperties.radius; @@ -653,6 +660,15 @@ function mouseMoveEvent(event) { Models.editModel(selectedModelID, selectedModelProperties); } +function mouseReleaseEvent(event) { + modelSelected = false; + + glowedModelID.id = -1; + glowedModelID.isKnownID = false; +} + + + function setupModelMenus() { // add our menuitems Menu.addMenuItem({ menuName: "Edit", menuItemName: "Models", isSeparator: true, beforeItem: "Physics" }); @@ -677,6 +693,7 @@ Script.scriptEnding.connect(scriptEnding); Script.update.connect(checkController); Controller.mousePressEvent.connect(mousePressEvent); Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); setupModelMenus(); Menu.menuItemEvent.connect(function(menuItem){ diff --git a/interface/resources/shaders/model_shadow_map.frag b/interface/resources/shaders/model_shadow_map.frag index 4a2bb312dc..aa1df03b95 100644 --- a/interface/resources/shaders/model_shadow_map.frag +++ b/interface/resources/shaders/model_shadow_map.frag @@ -17,6 +17,9 @@ uniform sampler2D diffuseMap; // the shadow texture uniform sampler2DShadow shadowMap; +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + // the interpolated position varying vec4 position; @@ -27,7 +30,11 @@ void main(void) { // compute the base color based on OpenGL lighting model vec4 normalizedNormal = normalize(normal); float diffuse = dot(normalizedNormal, gl_LightSource[0].position); - float facingLight = step(0.0, diffuse) * shadow2D(shadowMap, gl_TexCoord[1].stp).r; + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); diff --git a/interface/resources/shaders/model_shadow_normal_map.frag b/interface/resources/shaders/model_shadow_normal_map.frag index d61b123fba..3461c1b5f3 100644 --- a/interface/resources/shaders/model_shadow_normal_map.frag +++ b/interface/resources/shaders/model_shadow_normal_map.frag @@ -20,6 +20,9 @@ uniform sampler2D normalMap; // the shadow texture uniform sampler2DShadow shadowMap; +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + // the interpolated position varying vec4 interpolatedPosition; @@ -39,7 +42,11 @@ void main(void) { vec4 viewNormal = vec4(normalizedTangent * localNormal.x + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); float diffuse = dot(viewNormal, gl_LightSource[0].position); - float facingLight = step(0.0, diffuse) * shadow2D(shadowMap, gl_TexCoord[1].stp).r; + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); diff --git a/interface/resources/shaders/model_shadow_normal_specular_map.frag b/interface/resources/shaders/model_shadow_normal_specular_map.frag index 9865056479..273d197fca 100644 --- a/interface/resources/shaders/model_shadow_normal_specular_map.frag +++ b/interface/resources/shaders/model_shadow_normal_specular_map.frag @@ -23,6 +23,9 @@ uniform sampler2D specularMap; // the shadow texture uniform sampler2DShadow shadowMap; +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + // the interpolated position varying vec4 interpolatedPosition; @@ -42,7 +45,11 @@ void main(void) { vec4 viewNormal = vec4(normalizedTangent * localNormal.x + normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0); float diffuse = dot(viewNormal, gl_LightSource[0].position); - float facingLight = step(0.0, diffuse) * shadow2D(shadowMap, gl_TexCoord[1].stp).r; + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); diff --git a/interface/resources/shaders/model_shadow_specular_map.frag b/interface/resources/shaders/model_shadow_specular_map.frag index 4be0f1636d..77cff1e04e 100644 --- a/interface/resources/shaders/model_shadow_specular_map.frag +++ b/interface/resources/shaders/model_shadow_specular_map.frag @@ -20,6 +20,9 @@ uniform sampler2D specularMap; // the shadow texture uniform sampler2DShadow shadowMap; +// the inverse of the size of the shadow map +const float shadowScale = 1.0 / 2048.0; + // the interpolated position in view space varying vec4 position; @@ -30,7 +33,11 @@ void main(void) { // compute the base color based on OpenGL lighting model vec4 normalizedNormal = normalize(normal); float diffuse = dot(normalizedNormal, gl_LightSource[0].position); - float facingLight = step(0.0, diffuse) * shadow2D(shadowMap, gl_TexCoord[1].stp).r; + float facingLight = step(0.0, diffuse) * 0.25 * + (shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(-shadowScale, shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, -shadowScale, 0.0)).r + + shadow2D(shadowMap, gl_TexCoord[1].stp + vec3(shadowScale, shadowScale, 0.0)).r); vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient + gl_FrontLightProduct[0].diffuse * (diffuse * facingLight)); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index c9c09b7a8f..a07b89a934 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -164,6 +164,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : _previousScriptLocation(), _runningScriptsWidget(new RunningScriptsWidget(_window)), _runningScriptsWidgetWasVisible(false), + _trayIcon(new QSystemTrayIcon(_window)), _overlayRenderer() { // read the ApplicationInfo.ini file for Name/Version/Domain information @@ -234,7 +235,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QTimer* locationUpdateTimer = new QTimer(this); connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer); locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS); - + connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer))); @@ -243,16 +244,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); connect(nodeList, SIGNAL(uuidChanged(const QUuid&)), _myAvatar, SLOT(setSessionUUID(const QUuid&))); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); - + // connect to appropriate slots on AccountManager AccountManager& accountManager = AccountManager::getInstance(); - + const qint64 BALANCE_UPDATE_INTERVAL_MSECS = 5 * 1000; - + QTimer* balanceUpdateTimer = new QTimer(this); connect(balanceUpdateTimer, &QTimer::timeout, &accountManager, &AccountManager::updateBalance); balanceUpdateTimer->start(BALANCE_UPDATE_INTERVAL_MSECS); - + connect(&accountManager, &AccountManager::balanceChanged, this, &Application::updateWindowTitle); connect(&accountManager, &AccountManager::authRequired, Menu::getInstance(), &Menu::loginForCurrentDomain); @@ -383,6 +384,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : OAuthWebViewHandler::getInstance(); // make sure the High Fidelity root CA is in our list of trusted certs OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig(); + + _trayIcon->show(); } Application::~Application() { @@ -554,7 +557,7 @@ void Application::paintGL() { PerformanceWarning warn(showWarnings, "Application::paintGL()"); glEnable(GL_LINE_SMOOTH); - + if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) { _myCamera.setTightness(0.0f); // In first person, camera follows (untweaked) head exactly without delay _myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition()); @@ -2319,10 +2322,15 @@ void Application::updateShadowMap() { // store view matrix without translation, which we'll use for precision-sensitive objects updateUntranslatedViewMatrix(); + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(1.1f, 4.0f); // magic numbers courtesy http://www.eecs.berkeley.edu/~ravir/6160/papers/shadowmaps.ppt + _avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE); _particles.render(OctreeRenderer::SHADOW_RENDER_MODE); _models.render(OctreeRenderer::SHADOW_RENDER_MODE); + glDisable(GL_POLYGON_OFFSET_FILL); + glPopMatrix(); glMatrixMode(GL_PROJECTION); @@ -2922,18 +2930,18 @@ void Application::updateWindowTitle(){ QString username = AccountManager::getInstance().getAccountInfo().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) + nodeList->getDomainHandler().getHostname() + buildVersion; - + AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.getAccountInfo().hasBalance()) { float creditBalance = accountManager.getAccountInfo().getBalance() / SATOSHIS_PER_CREDIT; - + QString creditBalanceString; creditBalanceString.sprintf("%.8f", creditBalance); - + title += " - ₵" + creditBalanceString; } - - qDebug("Application title set to: %s", title.toStdString().c_str()); + + qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } diff --git a/interface/src/Application.h b/interface/src/Application.h index aef67bd6e7..8b40ad522e 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include @@ -214,6 +215,7 @@ public: JoystickManager* getJoystickManager() { return &_joystickManager; } BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } QUndoStack* getUndoStack() { return &_undoStack; } + QSystemTrayIcon* getTrayIcon() { return _trayIcon; } OverlayRenderer& getOverlayRenderer() { return _overlayRenderer; } Overlays& getOverlays() { return _overlays; } @@ -484,7 +486,7 @@ private: glm::mat4 _untranslatedViewMatrix; glm::vec3 _viewMatrixTranslation; glm::mat4 _projectionMatrix; - + float _scaleMirror; float _rotateMirror; float _raiseMirror; @@ -573,6 +575,8 @@ private: RunningScriptsWidget* _runningScriptsWidget; QHash _scriptEnginesHash; bool _runningScriptsWidgetWasVisible; + + QSystemTrayIcon* _trayIcon; }; #endif // hifi_Application_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 856e1efae7..92121e719a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -34,6 +34,7 @@ #include #include "Application.h" +#include "AccountManager.h" #include "Menu.h" #include "scripting/MenuScriptingInterface.h" #include "Util.h" @@ -195,12 +196,12 @@ Menu::Menu() : addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments())); addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, this, SLOT(editAnimations())); - + addDisabledActionAndSeparator(editMenu, "Physics"); QObject* avatar = appInstance->getAvatar(); - addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, + addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, avatar, SLOT(updateMotionBehaviorsFromMenu())); - addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::StandOnNearbyFloors, 0, true, + addCheckableActionToQMenuAndActionHash(editMenu, MenuOption::StandOnNearbyFloors, 0, true, avatar, SLOT(updateMotionBehaviorsFromMenu())); addAvatarCollisionSubMenu(editMenu); @@ -332,6 +333,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::AllowOculusCameraModeChange, 0, false); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::AvatarsReceiveShadows, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes); @@ -1026,24 +1028,24 @@ void Menu::multipleDestinationsDecision(const QJsonObject& userData, const QJson void Menu::muteEnvironment() { int headerSize = numBytesForPacketHeaderGivenPacketType(PacketTypeMuteEnvironment); int packetSize = headerSize + sizeof(glm::vec3) + sizeof(float); - + glm::vec3 position = Application::getInstance()->getAvatar()->getPosition(); - + char* packet = (char*)malloc(packetSize); populatePacketHeader(packet, PacketTypeMuteEnvironment); memcpy(packet + headerSize, &position, sizeof(glm::vec3)); memcpy(packet + headerSize + sizeof(glm::vec3), &MUTE_RADIUS, sizeof(float)); - + QByteArray mutePacket(packet, packetSize); - + // grab our audio mixer from the NodeList, if it exists SharedNodePointer audioMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AudioMixer); - + if (audioMixer) { // send off this mute packet NodeList::getInstance()->writeDatagram(mutePacket, audioMixer); } - + free(packet); } @@ -1201,19 +1203,24 @@ void Menu::showScriptEditor() { } void Menu::showChat() { - QMainWindow* mainWindow = Application::getInstance()->getWindow(); - if (!_chatWindow) { - _chatWindow = new ChatWindow(mainWindow); - } - if (_chatWindow->isHidden()) { - _chatWindow->show(); + if (AccountManager::getInstance().isLoggedIn()) { + QMainWindow* mainWindow = Application::getInstance()->getWindow(); + if (!_chatWindow) { + _chatWindow = new ChatWindow(mainWindow); + } + + if (_chatWindow->isHidden()) { + _chatWindow->show(); + } + } else { + Application::getInstance()->getTrayIcon()->showMessage("Interface", "You need to login to be able to chat with others on this domain."); } } void Menu::toggleChat() { #ifdef HAVE_QXMPP _chatAction->setEnabled(XmppClient::getInstance().getXMPPClient().isConnected()); - if (!_chatAction->isEnabled() && _chatWindow) { + if (!_chatAction->isEnabled() && _chatWindow && AccountManager::getInstance().isLoggedIn()) { if (_chatWindow->isHidden()) { _chatWindow->show(); } else { diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 279d2151c9..70f4f62ce4 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -293,6 +293,7 @@ namespace MenuOption { const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation"; const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation"; const QString Avatars = "Avatars"; + const QString AvatarsReceiveShadows = "Avatars Receive Shadows"; const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; const QString BuckyBalls = "Bucky Balls"; diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index aff15f4e0b..306dc0194e 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -347,7 +347,6 @@ glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const { void Avatar::renderBody(RenderMode renderMode, float glowLevel) { Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; - { Glower glower(glowLevel); @@ -356,7 +355,8 @@ void Avatar::renderBody(RenderMode renderMode, float glowLevel) { renderBillboard(); return; } - _skeletonModel.render(1.0f, modelRenderMode); + + _skeletonModel.render(1.0f, modelRenderMode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)); renderAttachments(renderMode); getHand()->render(false, modelRenderMode); } @@ -390,8 +390,9 @@ void Avatar::simulateAttachments(float deltaTime) { void Avatar::renderAttachments(RenderMode renderMode) { Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + bool receiveShadows = Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows); foreach (Model* model, _attachmentModels) { - model->render(1.0f, modelRenderMode); + model->render(1.0f, modelRenderMode, receiveShadows); } } diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index 2d0599b31f..8382843c1e 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -176,7 +176,8 @@ void Head::relaxLean(float deltaTime) { } void Head::render(float alpha, Model::RenderMode mode) { - if (_faceModel.render(alpha, mode) && _renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) { + if (_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)) && + _renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) { renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition); } } diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index a598c55aaa..f47880056c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -773,7 +773,7 @@ void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { // Render the body's voxels and head Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; - _skeletonModel.render(1.0f, modelRenderMode); + _skeletonModel.render(1.0f, modelRenderMode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)); renderAttachments(renderMode); // Render head so long as the camera isn't inside it @@ -1695,10 +1695,11 @@ void MyAvatar::renderAttachments(RenderMode renderMode) { QString headJointName = (geometry.headJointIndex == -1) ? QString() : geometry.joints.at(geometry.headJointIndex).name; Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + bool receiveShadows = Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows); for (int i = 0; i < _attachmentData.size(); i++) { const QString& jointName = _attachmentData.at(i).jointName; if (jointName != headJointName && jointName != "Head") { - _attachmentModels.at(i)->render(1.0f, modelRenderMode); + _attachmentModels.at(i)->render(1.0f, modelRenderMode, receiveShadows); } } } diff --git a/interface/src/models/ModelTreeRenderer.cpp b/interface/src/models/ModelTreeRenderer.cpp index 47e3a1ea7d..68bebd7b99 100644 --- a/interface/src/models/ModelTreeRenderer.cpp +++ b/interface/src/models/ModelTreeRenderer.cpp @@ -11,6 +11,8 @@ #include +#include + #include "InterfaceConfig.h" #include "Menu.h" #include "ModelTreeRenderer.h" @@ -34,8 +36,13 @@ ModelTreeRenderer::~ModelTreeRenderer() { void ModelTreeRenderer::init() { OctreeRenderer::init(); + static_cast(_tree)->setFBXService(this); } +void ModelTreeRenderer::setTree(Octree* newTree) { + OctreeRenderer::setTree(newTree); + static_cast(_tree)->setFBXService(this); +} void ModelTreeRenderer::update() { if (_tree) { @@ -48,6 +55,16 @@ void ModelTreeRenderer::render(RenderMode renderMode) { OctreeRenderer::render(renderMode); } +const FBXGeometry* ModelTreeRenderer::getGeometryForModel(const ModelItem& modelItem) { + const FBXGeometry* result = NULL; + Model* model = getModel(modelItem); + if (model) { + result = &model->getGeometry()->getFBXGeometry(); + + } + return result; +} + Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) { Model* model = NULL; @@ -73,42 +90,6 @@ Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) { return model; } -void calculateRotatedExtents(Extents& extents, const glm::quat& rotation) { - glm::vec3 bottomLeftNear(extents.minimum.x, extents.minimum.y, extents.minimum.z); - glm::vec3 bottomRightNear(extents.maximum.x, extents.minimum.y, extents.minimum.z); - glm::vec3 bottomLeftFar(extents.minimum.x, extents.minimum.y, extents.maximum.z); - glm::vec3 bottomRightFar(extents.maximum.x, extents.minimum.y, extents.maximum.z); - glm::vec3 topLeftNear(extents.minimum.x, extents.maximum.y, extents.minimum.z); - glm::vec3 topRightNear(extents.maximum.x, extents.maximum.y, extents.minimum.z); - glm::vec3 topLeftFar(extents.minimum.x, extents.maximum.y, extents.maximum.z); - glm::vec3 topRightFar(extents.maximum.x, extents.maximum.y, extents.maximum.z); - - glm::vec3 bottomLeftNearRotated = rotation * bottomLeftNear; - glm::vec3 bottomRightNearRotated = rotation * bottomRightNear; - glm::vec3 bottomLeftFarRotated = rotation * bottomLeftFar; - glm::vec3 bottomRightFarRotated = rotation * bottomRightFar; - glm::vec3 topLeftNearRotated = rotation * topLeftNear; - glm::vec3 topRightNearRotated = rotation * topRightNear; - glm::vec3 topLeftFarRotated = rotation * topLeftFar; - glm::vec3 topRightFarRotated = rotation * topRightFar; - - extents.minimum = glm::min(bottomLeftNearRotated, - glm::min(bottomRightNearRotated, - glm::min(bottomLeftFarRotated, - glm::min(bottomRightFarRotated, - glm::min(topLeftNearRotated, - glm::min(topRightNearRotated, - glm::min(topLeftFarRotated,topRightFarRotated))))))); - - extents.maximum = glm::max(bottomLeftNearRotated, - glm::max(bottomRightNearRotated, - glm::max(bottomLeftFarRotated, - glm::max(bottomRightFarRotated, - glm::max(topLeftNearRotated, - glm::max(topRightNearRotated, - glm::max(topLeftFarRotated,topRightFarRotated))))))); -} - void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) { args->_elementsTouched++; // actually render it here... @@ -248,6 +229,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) } if (!isShadowMode && displayModelBounds) { + glm::vec3 unRotatedMinimum = model->getUnscaledMeshExtents().minimum; glm::vec3 unRotatedMaximum = model->getUnscaledMeshExtents().maximum; glm::vec3 unRotatedExtents = unRotatedMaximum - unRotatedMinimum; @@ -285,6 +267,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) glutWireCube(1.0); glPopMatrix(); + } glPopMatrix(); diff --git a/interface/src/models/ModelTreeRenderer.h b/interface/src/models/ModelTreeRenderer.h index e0b8d7d0a2..2d418b1a25 100644 --- a/interface/src/models/ModelTreeRenderer.h +++ b/interface/src/models/ModelTreeRenderer.h @@ -26,7 +26,7 @@ #include "renderer/Model.h" // Generic client side Octree renderer class. -class ModelTreeRenderer : public OctreeRenderer { +class ModelTreeRenderer : public OctreeRenderer, public ModelItemFBXService { public: ModelTreeRenderer(); virtual ~ModelTreeRenderer(); @@ -38,6 +38,7 @@ public: virtual void renderElement(OctreeElement* element, RenderArgs* args); virtual float getSizeScale() const; virtual int getBoundaryLevelAdjust() const; + virtual void setTree(Octree* newTree); void update(); @@ -48,6 +49,8 @@ public: virtual void init(); virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE); + virtual const FBXGeometry* getGeometryForModel(const ModelItem& modelItem); + protected: Model* getModel(const ModelItem& modelItem); QMap _knownModelsItemModels; diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 5337e845a1..db2f6e0664 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -470,7 +470,7 @@ bool Model::updateGeometry() { return needFullUpdate; } -bool Model::render(float alpha, RenderMode mode) { +bool Model::render(float alpha, RenderMode mode, bool receiveShadows) { // render the attachments foreach (Model* attachment, _attachments) { attachment->render(alpha, mode); @@ -498,6 +498,9 @@ bool Model::render(float alpha, RenderMode mode) { glDisable(GL_CULL_FACE); } else { glEnable(GL_CULL_FACE); + if (mode == SHADOW_RENDER_MODE) { + glCullFace(GL_FRONT); + } } // render opaque meshes with alpha testing @@ -505,16 +508,21 @@ bool Model::render(float alpha, RenderMode mode) { glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.5f * alpha); - renderMeshes(alpha, mode, false); + receiveShadows &= Menu::getInstance()->isOptionChecked(MenuOption::Shadows); + renderMeshes(alpha, mode, false, receiveShadows); glDisable(GL_ALPHA_TEST); // render translucent meshes afterwards - renderMeshes(alpha, mode, true); + renderMeshes(alpha, mode, true, receiveShadows); glDisable(GL_CULL_FACE); + if (mode == SHADOW_RENDER_MODE) { + glCullFace(GL_BACK); + } + // deactivate vertex arrays after drawing glDisableClientState(GL_NORMAL_ARRAY); glDisableClientState(GL_VERTEX_ARRAY); @@ -1589,11 +1597,10 @@ void Model::deleteGeometry() { } } -void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { +void Model::renderMeshes(float alpha, RenderMode mode, bool translucent, bool receiveShadows) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); - bool receiveShadows = Menu::getInstance()->isOptionChecked(MenuOption::Shadows); if (receiveShadows) { glTexGenfv(GL_S, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[0]); glTexGenfv(GL_T, GL_EYE_PLANE, (const GLfloat*)&Application::getInstance()->getShadowMatrix()[1]); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 5b1f6402d3..69ea700b49 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -79,7 +79,7 @@ public: enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE }; - bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE); + bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE, bool receiveShadows = true); /// Sets the URL of the model to render. /// \param fallback the URL of a fallback model to render if the requested model fails to load @@ -315,7 +315,7 @@ private: void applyNextGeometry(); void deleteGeometry(); - void renderMeshes(float alpha, RenderMode mode, bool translucent); + void renderMeshes(float alpha, RenderMode mode, bool translucent, bool receiveShadows); QVector createJointStates(const FBXGeometry& geometry); QSharedPointer _baseGeometry; ///< reference required to prevent collection of base diff --git a/libraries/fbx/src/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp index 713870dafb..60412cf0ce 100644 --- a/libraries/fbx/src/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -2052,3 +2052,40 @@ FBXGeometry readSVO(const QByteArray& model) { return geometry; } + +void calculateRotatedExtents(Extents& extents, const glm::quat& rotation) { + glm::vec3 bottomLeftNear(extents.minimum.x, extents.minimum.y, extents.minimum.z); + glm::vec3 bottomRightNear(extents.maximum.x, extents.minimum.y, extents.minimum.z); + glm::vec3 bottomLeftFar(extents.minimum.x, extents.minimum.y, extents.maximum.z); + glm::vec3 bottomRightFar(extents.maximum.x, extents.minimum.y, extents.maximum.z); + glm::vec3 topLeftNear(extents.minimum.x, extents.maximum.y, extents.minimum.z); + glm::vec3 topRightNear(extents.maximum.x, extents.maximum.y, extents.minimum.z); + glm::vec3 topLeftFar(extents.minimum.x, extents.maximum.y, extents.maximum.z); + glm::vec3 topRightFar(extents.maximum.x, extents.maximum.y, extents.maximum.z); + + glm::vec3 bottomLeftNearRotated = rotation * bottomLeftNear; + glm::vec3 bottomRightNearRotated = rotation * bottomRightNear; + glm::vec3 bottomLeftFarRotated = rotation * bottomLeftFar; + glm::vec3 bottomRightFarRotated = rotation * bottomRightFar; + glm::vec3 topLeftNearRotated = rotation * topLeftNear; + glm::vec3 topRightNearRotated = rotation * topRightNear; + glm::vec3 topLeftFarRotated = rotation * topLeftFar; + glm::vec3 topRightFarRotated = rotation * topRightFar; + + extents.minimum = glm::min(bottomLeftNearRotated, + glm::min(bottomRightNearRotated, + glm::min(bottomLeftFarRotated, + glm::min(bottomRightFarRotated, + glm::min(topLeftNearRotated, + glm::min(topRightNearRotated, + glm::min(topLeftFarRotated,topRightFarRotated))))))); + + extents.maximum = glm::max(bottomLeftNearRotated, + glm::max(bottomRightNearRotated, + glm::max(bottomLeftFarRotated, + glm::max(bottomRightFarRotated, + glm::max(topLeftNearRotated, + glm::max(topRightNearRotated, + glm::max(topLeftFarRotated,topRightFarRotated))))))); +} + diff --git a/libraries/fbx/src/FBXReader.h b/libraries/fbx/src/FBXReader.h index 8073ab36a9..4c93f3dc5e 100644 --- a/libraries/fbx/src/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -51,7 +51,8 @@ public: bool containsPoint(const glm::vec3& point) const; /// \return whether or not the extents are empty - bool isEmpty() { return minimum == maximum; } + bool isEmpty() const { return minimum == maximum; } + bool isValid() const { return !((minimum == glm::vec3(FLT_MAX)) && (maximum == glm::vec3(-FLT_MAX))); } glm::vec3 minimum; glm::vec3 maximum; @@ -238,4 +239,6 @@ FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping); /// Reads SVO geometry from the supplied model data. FBXGeometry readSVO(const QByteArray& model); +void calculateRotatedExtents(Extents& extents, const glm::quat& rotation); + #endif // hifi_FBXReader_h diff --git a/libraries/models/src/ModelTree.h b/libraries/models/src/ModelTree.h index 596ca9074d..c450f04e2d 100644 --- a/libraries/models/src/ModelTree.h +++ b/libraries/models/src/ModelTree.h @@ -20,6 +20,11 @@ public: virtual void modelCreated(const ModelItem& newModel, const SharedNodePointer& senderNode) = 0; }; +class ModelItemFBXService { +public: + virtual const FBXGeometry* getGeometryForModel(const ModelItem& modelItem) = 0; +}; + class ModelTree : public Octree { Q_OBJECT public: @@ -74,6 +79,11 @@ public: void processEraseMessage(const QByteArray& dataByteArray, const SharedNodePointer& sourceNode); void handleAddModelResponse(const QByteArray& packet); + + void setFBXService(ModelItemFBXService* service) { _fbxService = service; } + const FBXGeometry* getGeometryForModel(const ModelItem& modelItem) { + return _fbxService ? _fbxService->getGeometryForModel(modelItem) : NULL; + } private: @@ -96,6 +106,7 @@ private: QReadWriteLock _recentlyDeletedModelsLock; QMultiMap _recentlyDeletedModelItemIDs; + ModelItemFBXService* _fbxService; }; #endif // hifi_ModelTree_h diff --git a/libraries/models/src/ModelTreeElement.cpp b/libraries/models/src/ModelTreeElement.cpp index dcb3253ef4..dad592f838 100644 --- a/libraries/models/src/ModelTreeElement.cpp +++ b/libraries/models/src/ModelTreeElement.cpp @@ -9,6 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // +#include #include #include "ModelTree.h" @@ -158,7 +159,41 @@ bool ModelTreeElement::findDetailedRayIntersection(const glm::vec3& origin, cons // if the ray doesn't intersect with our cube, we can stop searching! if (modelCube.findRayIntersection(origin, direction, localDistance, localFace)) { - if (localDistance < distance) { + const FBXGeometry* fbxGeometry = _myTree->getGeometryForModel(model); + if (fbxGeometry && fbxGeometry->meshExtents.isValid()) { + Extents extents = fbxGeometry->meshExtents; + + // NOTE: these extents are model space, so we need to scale and center them accordingly + // size is our "target size in world space" + // we need to set our model scale so that the extents of the mesh, fit in a cube that size... + float maxDimension = glm::distance(extents.maximum, extents.minimum); + float scale = model.getSize() / maxDimension; + + glm::vec3 halfDimensions = (extents.maximum - extents.minimum) * 0.5f; + glm::vec3 offset = -extents.minimum - halfDimensions; + + extents.minimum += offset; + extents.maximum += offset; + + 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)); + + if (rotatedExtentsBox.findRayIntersection(origin, direction, localDistance, localFace)) { + if (localDistance < distance) { + distance = localDistance; + face = localFace; + *intersectedObject = (void*)(&model); + somethingIntersected = true; + } + } + } else if (localDistance < distance) { distance = localDistance; face = localFace; *intersectedObject = (void*)(&model);