diff --git a/interface/resources/images/preview.png b/interface/resources/images/preview.png new file mode 100644 index 0000000000..faebbfce8f Binary files /dev/null and b/interface/resources/images/preview.png differ diff --git a/interface/src/ui/overlays/ModelOverlay.cpp b/interface/src/ui/overlays/ModelOverlay.cpp index adf08934f0..6b81ad7ff3 100644 --- a/interface/src/ui/overlays/ModelOverlay.cpp +++ b/interface/src/ui/overlays/ModelOverlay.cpp @@ -45,7 +45,7 @@ void ModelOverlay::update(float deltatime) { _updateModel = false; _model->setSnapModelToCenter(true); - _model->setScale(getDimensions()); + _model->setScaleToFit(true, getDimensions()); _model->setRotation(getRotation()); _model->setTranslation(getPosition()); _model->setURL(_url); @@ -100,7 +100,6 @@ void ModelOverlay::setProperties(const QVariantMap& properties) { if (newScale.x <= 0 || newScale.y <= 0 || newScale.z <= 0) { setDimensions(scale); } else { - _model->setScaleToFit(true, getDimensions()); _updateModel = true; } } diff --git a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp index 18f39cd3df..e0c87fbbed 100644 --- a/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/OpenGLDisplayPlugin.cpp @@ -16,6 +16,9 @@ #include #include +#if defined(Q_OS_MAC) +#include +#endif #include #include #include @@ -612,8 +615,14 @@ void OpenGLDisplayPlugin::enableVsync(bool enable) { if (!_vsyncSupported) { return; } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) wglSwapIntervalEXT(enable ? 1 : 0); +#elif defined(Q_OS_MAC) + GLint interval = enable ? 1 : 0; + CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); +#else + // TODO: Fill in for linux + return; #endif } @@ -621,9 +630,14 @@ bool OpenGLDisplayPlugin::isVsyncEnabled() { if (!_vsyncSupported) { return true; } -#ifdef Q_OS_WIN +#if defined(Q_OS_WIN) return wglGetSwapIntervalEXT() != 0; +#elif defined(Q_OS_MAC) + GLint interval; + CGLGetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval); + return interval != 0; #else + // TODO: Fill in for linux return true; #endif } diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp index f1aa1edc81..c2497e5740 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.cpp @@ -21,6 +21,11 @@ #include #include +#include +#include + +#include + #include "../Logging.h" #include "../CompositorHelper.h" @@ -58,9 +63,33 @@ bool HmdDisplayPlugin::internalActivate() { _eyeInverseProjections[eye] = glm::inverse(_eyeProjections[eye]); }); + if (_previewTextureID == 0) { + QImage previewTexture(PathUtils::resourcesPath() + "images/preview.png"); + if (!previewTexture.isNull()) { + glGenTextures(1, &_previewTextureID); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, previewTexture.width(), previewTexture.height(), 0, + GL_BGRA, GL_UNSIGNED_BYTE, previewTexture.mirrored(false, true).bits()); + using namespace oglplus; + Texture::MinFilter(TextureTarget::_2D, TextureMinFilter::Linear); + Texture::MagFilter(TextureTarget::_2D, TextureMagFilter::Linear); + glBindTexture(GL_TEXTURE_2D, 0); + _previewAspect = ((float)previewTexture.width())/((float)previewTexture.height()); + _firstPreview = true; + } + } + return Parent::internalActivate(); } +void HmdDisplayPlugin::internalDeactivate() { + if (_previewTextureID != 0) { + glDeleteTextures(1, &_previewTextureID); + _previewTextureID = 0; + } + Parent::internalDeactivate(); +} + static const char * REPROJECTION_VS = R"VS(#version 410 core in vec3 Position; @@ -196,6 +225,7 @@ static ProgramPtr getReprojectionProgram() { } #endif +static GLint PREVIEW_TEXTURE_LOCATION = -1; static const char * LASER_VS = R"VS(#version 410 core uniform mat4 mvp = mat4(1); @@ -227,14 +257,24 @@ void main() { void HmdDisplayPlugin::customizeContext() { Parent::customizeContext(); // Only enable mirroring if we know vsync is disabled + // On Mac, this won't work due to how the contexts are handled, so don't try +#if !defined(Q_OS_MAC) enableVsync(false); +#endif _enablePreview = !isVsyncEnabled(); _sphereSection = loadSphereSection(_program, CompositorHelper::VIRTUAL_UI_TARGET_FOV.y, CompositorHelper::VIRTUAL_UI_ASPECT_RATIO); - compileProgram(_laserProgram, LASER_VS, LASER_FS); - _laserGeometry = loadLaser(_laserProgram); - compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); using namespace oglplus; + if (!_enablePreview) { + const std::string version("#version 410 core\n"); + compileProgram(_previewProgram, version + DrawUnitQuadTexcoord_vert, version + DrawTexture_frag); + PREVIEW_TEXTURE_LOCATION = Uniform(*_previewProgram, "colorMap").Location(); + } + + compileProgram(_laserProgram, LASER_VS, LASER_FS); + _laserGeometry = loadLaser(_laserProgram); + + compileProgram(_reprojectionProgram, REPROJECTION_VS, REPROJECTION_FS); REPROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "reprojection").Location(); INVERSE_PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "inverseProjections").Location(); PROJECTION_MATRIX_LOCATION = Uniform(*_reprojectionProgram, "projections").Location(); @@ -243,6 +283,7 @@ void HmdDisplayPlugin::customizeContext() { void HmdDisplayPlugin::uncustomizeContext() { _sphereSection.reset(); _compositeFramebuffer.reset(); + _previewProgram.reset(); _reprojectionProgram.reset(); _laserProgram.reset(); _laserGeometry.reset(); @@ -335,30 +376,32 @@ void HmdDisplayPlugin::internalPresent() { hmdPresent(); // screen preview mirroring + auto window = _container->getPrimaryWidget(); + auto devicePixelRatio = window->devicePixelRatio(); + auto windowSize = toGlm(window->size()); + windowSize *= devicePixelRatio; + float windowAspect = aspect(windowSize); + float sceneAspect = _enablePreview ? aspect(_renderTargetSize) : _previewAspect; + if (_enablePreview && _monoPreview) { + sceneAspect /= 2.0f; + } + float aspectRatio = sceneAspect / windowAspect; + + uvec2 targetViewportSize = windowSize; + if (aspectRatio < 1.0f) { + targetViewportSize.x *= aspectRatio; + } else { + targetViewportSize.y /= aspectRatio; + } + + uvec2 targetViewportPosition; + if (targetViewportSize.x < windowSize.x) { + targetViewportPosition.x = (windowSize.x - targetViewportSize.x) / 2; + } else if (targetViewportSize.y < windowSize.y) { + targetViewportPosition.y = (windowSize.y - targetViewportSize.y) / 2; + } + if (_enablePreview) { - auto window = _container->getPrimaryWidget(); - auto windowSize = toGlm(window->size()); - float windowAspect = aspect(windowSize); - float sceneAspect = aspect(_renderTargetSize); - if (_monoPreview) { - sceneAspect /= 2.0f; - } - float aspectRatio = sceneAspect / windowAspect; - - uvec2 targetViewportSize = windowSize; - if (aspectRatio < 1.0f) { - targetViewportSize.x *= aspectRatio; - } else { - targetViewportSize.y /= aspectRatio; - } - - uvec2 targetViewportPosition; - if (targetViewportSize.x < windowSize.x) { - targetViewportPosition.x = (windowSize.x - targetViewportSize.x) / 2; - } else if (targetViewportSize.y < windowSize.y) { - targetViewportPosition.y = (windowSize.y - targetViewportSize.y) / 2; - } - using namespace oglplus; Context::Clear().ColorBuffer(); auto sourceSize = _compositeFramebuffer->size; @@ -373,6 +416,21 @@ void HmdDisplayPlugin::internalPresent() { BufferSelectBit::ColorBuffer, BlitFilter::Nearest); }); swapBuffers(); + } else if (_firstPreview || windowSize != _prevWindowSize || devicePixelRatio != _prevDevicePixelRatio) { + useProgram(_previewProgram); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA,GL_ONE_MINUS_SRC_ALPHA); + glClearColor(0, 0, 0, 1); + glClear(GL_COLOR_BUFFER_BIT); + glViewport(targetViewportPosition.x, targetViewportPosition.y, targetViewportSize.x, targetViewportSize.y); + glUniform1i(PREVIEW_TEXTURE_LOCATION, 0); + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, _previewTextureID); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + swapBuffers(); + _firstPreview = false; + _prevWindowSize = windowSize; + _prevDevicePixelRatio = devicePixelRatio; } postPreview(); diff --git a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h index fada15d864..8e48690fd1 100644 --- a/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h +++ b/libraries/display-plugins/src/display-plugins/hmd/HmdDisplayPlugin.h @@ -40,6 +40,7 @@ protected: virtual void updatePresentPose(); bool internalActivate() override; + void internalDeactivate() override; void compositeScene() override; void compositeOverlay() override; void compositePointer() override; @@ -89,8 +90,17 @@ private: bool _enablePreview { false }; bool _monoPreview { true }; bool _enableReprojection { true }; - ShapeWrapperPtr _sphereSection; + bool _firstPreview { true }; + + ProgramPtr _previewProgram; + float _previewAspect { 0 }; + GLuint _previewTextureID { 0 }; + glm::uvec2 _prevWindowSize { 0, 0 }; + qreal _prevDevicePixelRatio { 0 }; + ProgramPtr _reprojectionProgram; + ShapeWrapperPtr _sphereSection; + ProgramPtr _laserProgram; ShapeWrapperPtr _laserGeometry; }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index 56f6438e70..1ec934be92 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -291,7 +291,9 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { foreach(const EntityItemID& entityID, _currentEntitiesInside) { if (!entitiesContainingAvatar.contains(entityID)) { emit leaveEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } } } @@ -299,7 +301,9 @@ bool EntityTreeRenderer::checkEnterLeaveEntities() { foreach(const EntityItemID& entityID, entitiesContainingAvatar) { if (!_currentEntitiesInside.contains(entityID)) { emit enterEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "enterEntity"); + } } } _currentEntitiesInside = entitiesContainingAvatar; @@ -315,7 +319,9 @@ void EntityTreeRenderer::leaveAllEntities() { // for all of our previous containing entities, if they are no longer containing then send them a leave event foreach(const EntityItemID& entityID, _currentEntitiesInside) { emit leaveEntity(entityID); - _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(entityID, "leaveEntity"); + } } _currentEntitiesInside.clear(); forceRecheckEntities(); @@ -652,11 +658,15 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { } emit mousePressOnEntity(rayPickResult, event); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mousePressOnEntity", MouseEvent(*event)); + } _currentClickingOnEntityID = rayPickResult.entityID; emit clickDownOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "clickDownOnEntity", MouseEvent(*event)); + } } else { emit mousePressOffEntity(rayPickResult, event); } @@ -677,14 +687,18 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { if (rayPickResult.intersects) { //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; emit mouseReleaseOnEntity(rayPickResult, event); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseReleaseOnEntity", MouseEvent(*event)); + } } // Even if we're no longer intersecting with an entity, if we started clicking on it, and now // we're releasing the button, then this is considered a clickOn event if (!_currentClickingOnEntityID.isInvalidID()) { emit clickReleaseOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "clickReleaseOnEntity", MouseEvent(*event)); + } } // makes it the unknown ID, we just released so we can't be clicking on anything @@ -707,8 +721,10 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking); if (rayPickResult.intersects) { - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveEvent", MouseEvent(*event)); + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "mouseMoveOnEntity", MouseEvent(*event)); + } // handle the hover logic... @@ -716,19 +732,25 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // then we need to send the hover leave. if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + } } // If the new hover entity does not match the previous hover entity then we are entering the new one // this is true if the _currentHoverOverEntityID is known or unknown if (rayPickResult.entityID != _currentHoverOverEntityID) { - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverEnterEntity", MouseEvent(*event)); + } } // and finally, no matter what, if we're intersecting an entity then we're definitely hovering over it, and // we should send our hover over event emit hoverOverEntity(rayPickResult.entityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(rayPickResult.entityID, "hoverOverEntity", MouseEvent(*event)); + } // remember what we're hovering over _currentHoverOverEntityID = rayPickResult.entityID; @@ -739,7 +761,9 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // send the hover leave for our previous entity if (!_currentHoverOverEntityID.isInvalidID()) { emit hoverLeaveEntity(_currentHoverOverEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentHoverOverEntityID, "hoverLeaveEntity", MouseEvent(*event)); + } _currentHoverOverEntityID = UNKNOWN_ENTITY_ID; // makes it the unknown ID } } @@ -748,14 +772,16 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // not yet released the hold then this is still considered a holdingClickOnEntity event if (!_currentClickingOnEntityID.isInvalidID()) { emit holdingClickOnEntity(_currentClickingOnEntityID, MouseEvent(*event)); - _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event)); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(_currentClickingOnEntityID, "holdingClickOnEntity", MouseEvent(*event)); + } } _lastMouseEvent = MouseEvent(*event); _lastMouseEventValid = true; } void EntityTreeRenderer::deletingEntity(const EntityItemID& entityID) { - if (_tree && !_shuttingDown) { + if (_tree && !_shuttingDown && _entitiesScriptEngine) { _entitiesScriptEngine->unloadEntityScript(entityID); } @@ -801,7 +827,7 @@ void EntityTreeRenderer::entitySciptChanging(const EntityItemID& entityID, const void EntityTreeRenderer::checkAndCallPreload(const EntityItemID& entityID, const bool reload) { if (_tree && !_shuttingDown) { EntityItemPointer entity = getTree()->findEntityByEntityItemID(entityID); - if (entity && entity->shouldPreloadScript()) { + if (entity && entity->shouldPreloadScript() && _entitiesScriptEngine) { QString scriptUrl = entity->getScript(); scriptUrl = ResourceManager::normalizeURL(scriptUrl); ScriptEngine::loadEntityScript(_entitiesScriptEngine, entityID, scriptUrl, reload); @@ -910,12 +936,16 @@ void EntityTreeRenderer::entityCollisionWithEntity(const EntityItemID& idA, cons // And now the entity scripts if (isCollisionOwner(myNodeID, entityTree, idA, collision)) { emit collisionWithEntity(idA, idB, collision); - _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(idA, "collisionWithEntity", idB, collision); + } } if (isCollisionOwner(myNodeID, entityTree, idA, collision)) { emit collisionWithEntity(idB, idA, collision); - _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(idB, "collisionWithEntity", idA, collision); + } } } @@ -941,7 +971,9 @@ void EntityTreeRenderer::updateZone(const EntityItemID& id) { if (zone && zone->contains(_lastAvatarPosition)) { _currentEntitiesInside << id; emit enterEntity(id); - _entitiesScriptEngine->callEntityScriptMethod(id, "enterEntity"); + if (_entitiesScriptEngine) { + _entitiesScriptEngine->callEntityScriptMethod(id, "enterEntity"); + } if (zone->getVisible()) { _bestZone = std::dynamic_pointer_cast(zone); } diff --git a/libraries/gl/src/gl/Config.h b/libraries/gl/src/gl/Config.h index 593537a291..7947bd45df 100644 --- a/libraries/gl/src/gl/Config.h +++ b/libraries/gl/src/gl/Config.h @@ -20,6 +20,7 @@ #include #include +#include #endif diff --git a/libraries/gl/src/gl/GLWidget.cpp b/libraries/gl/src/gl/GLWidget.cpp index f113be1cfb..6fc9c41160 100644 --- a/libraries/gl/src/gl/GLWidget.cpp +++ b/libraries/gl/src/gl/GLWidget.cpp @@ -47,13 +47,16 @@ void GLWidget::initializeGL() { // Note, we *DO NOT* want Qt to automatically swap buffers for us. This results in the "ringing" bug mentioned in WL#19514 when we're throttling the framerate. setAutoBufferSwap(false); - // TODO: write the proper code for linux makeCurrent(); -#if defined(Q_OS_WIN) if (isValid() && context() && context()->contextHandle()) { - _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control");; - } +#if defined(Q_OS_WIN) + _vsyncSupported = context()->contextHandle()->hasExtension("WGL_EXT_swap_control"); +#elif defined(Q_OS_MAC) + _vsyncSupported = true; +#else + // TODO: write the proper code for linux #endif + } } void GLWidget::paintEvent(QPaintEvent* event) { diff --git a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp index 2c032f7005..6da842b7b9 100644 --- a/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp +++ b/plugins/oculusLegacy/src/OculusLegacyDisplayPlugin.cpp @@ -68,18 +68,48 @@ bool OculusLegacyDisplayPlugin::isSupported() const { } auto hmd = ovrHmd_Create(0); + + // The Oculus SDK seems to have trouble finding the right screen sometimes, so we have to guess + // Guesses, in order of best match: + // - resolution and position match + // - resolution and one component of position match + // - resolution matches + // - position matches + // If it still picks the wrong screen, you'll have to mess with your monitor configuration + QList matches({ -1, -1, -1, -1 }); if (hmd) { QPoint targetPosition{ hmd->WindowsPos.x, hmd->WindowsPos.y }; + QSize targetResolution{ hmd->Resolution.w, hmd->Resolution.h }; auto screens = qApp->screens(); for(int i = 0; i < screens.size(); ++i) { auto screen = screens[i]; QPoint position = screen->geometry().topLeft(); - if (position == targetPosition) { - _hmdScreen = i; - break; + QSize resolution = screen->geometry().size(); + + if (position == targetPosition && resolution == targetResolution) { + matches[0] = i; + } else if ((position.x() == targetPosition.x() || position.y() == targetPosition.y()) && + resolution == targetResolution) { + matches[1] = i; + } else if (resolution == targetResolution) { + matches[2] = i; + } else if (position == targetPosition) { + matches[3] = i; } } } + + for (int screen : matches) { + if (screen != -1) { + _hmdScreen = screen; + break; + } + } + + if (_hmdScreen == -1) { + qDebug() << "Could not find Rift screen"; + result = false; + } ovr_Shutdown(); return result; diff --git a/scripts/developer/tests/loadedMachine.js b/scripts/developer/tests/loadedMachine.js new file mode 100644 index 0000000000..375e3d8004 --- /dev/null +++ b/scripts/developer/tests/loadedMachine.js @@ -0,0 +1,192 @@ +"use strict"; +/*jslint vars: true, plusplus: true*/ +/*globals Script, MyAvatar, Quat, Render, ScriptDiscoveryService, Window, LODManager, Entities, print*/ +// +// loadedMachine.js +// scripts/developer/tests/ +// +// Created by Howard Stearns on 6/6/16. +// Copyright 2016 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 +// +// Characterises the performance of heavily loaded or struggling machines. +// There is no point in running this on a machine that producing the target 60 or 90 hz rendering rate. +// +// The script examines several source of load, including each running script and a couple of Entity types. +// It turns all of these off, as well as LOD management, and twirls in place to get a baseline render rate. +// Then it turns each load on, one at a time, and records a new render rate. +// At the end, it reports the ordered results (in a popup), restores the original loads and LOD management, and quits. +// +// Small performance changes are not meaningful. +// If you think you find something that is significant, you should probably run again to make sure that it isn't random variation. +// You can also compare (e.g., the baseline) for different conditions such as domain, version, server settings, etc. +// This does not profile scripts as they are used -- it merely measures the effect of having the script loaded and running quietly. + +var NUMBER_OF_TWIRLS_PER_LOAD = 10; +var MILLISECONDS_PER_SECOND = 1000; +var WAIT_TIME_MILLISECONDS = MILLISECONDS_PER_SECOND; +var accumulatedRotation = 0; +var start; +var frames = 0; +var config = Render.getConfig("Stats"); +var thisPath = Script.resolvePath(''); +var scriptData = ScriptDiscoveryService.getRunning(); +var scripts = scriptData.filter(function (datum) { return datum.name !== 'defaultScripts.js'; }).map(function (script) { return script.path; }); +// If defaultScripts.js is running, we leave it running, because restarting it safely is a mess. +var otherScripts = scripts.filter(function (path) { return path !== thisPath; }); +var numberLeftRunning = scriptData.length - otherScripts.length; +print('initially running', otherScripts.length, 'scripts. Leaving', numberLeftRunning, 'and stopping', otherScripts); +var typedEntities = {Light: [], ParticleEffect: []}; +var interestingTypes = Object.keys(typedEntities); +var propertiedEntities = {dynamic: []}; +var interestingProperties = Object.keys(propertiedEntities); +var loads = ['ignore', 'baseline'].concat(otherScripts, interestingTypes, interestingProperties); +var loadIndex = 0, nLoads = loads.length, load; +var results = []; +var initialLodIsAutomatic = LODManager.getAutomaticLODAdjust(); +var SEARCH_RADIUS = 17000; +var DEFAULT_LOD = 32768 * 400; +LODManager.setAutomaticLODAdjust(false); +LODManager.setOctreeSizeScale(DEFAULT_LOD); + +// Fill the typedEnties with the entityIDs that are already visible. It would be nice if this were more efficient. +var allEntities = Entities.findEntities(MyAvatar.position, SEARCH_RADIUS); +print('Searching', allEntities.length, 'entities for', interestingTypes, 'and', interestingProperties); +var propertiesToGet = ['type', 'visible'].concat(interestingProperties); +allEntities.forEach(function (entityID) { + var properties = Entities.getEntityProperties(entityID, propertiesToGet); + if (properties.visible) { + if (interestingTypes.indexOf(properties.type) >= 0) { + typedEntities[properties.type].push(entityID); + } else { + interestingProperties.forEach(function (property) { + if (entityID && properties[property]) { + propertiedEntities[property].push(entityID); + entityID = false; // Put in only one bin + } + }); + } + } +}); +allEntities = undefined; // free them +interestingTypes.forEach(function (type) { + print('There are', typedEntities[type].length, type, 'entities.'); +}); +interestingProperties.forEach(function (property) { + print('There are', propertiedEntities[property].length, property, 'entities.'); +}); +function toggleVisibility(type, on) { + typedEntities[type].forEach(function (entityID) { + Entities.editEntity(entityID, {visible: on}); + }); +} +function toggleProperty(property, on) { + propertiedEntities[property].forEach(function (entityID) { + var properties = {}; + properties[property] = on; + Entities.editEntity(entityID, properties); + }); +} +function restoreOneTest(load) { + print('restore', load); + switch (load) { + case 'baseline': + case 'ignore': // ignore is used to do a twirl to make sure everything is loaded. + break; + case 'Light': + case 'ParticleEffect': + toggleVisibility(load, true); + break; + case 'dynamic': + toggleProperty(load, 1); + break; + default: + Script.load(load); + } +} +function undoOneTest(load) { + print('stop', load); + switch (load) { + case 'baseline': + case 'ignore': + break; + case 'Light': + case 'ParticleEffect': + toggleVisibility(load, false); + break; + case 'dynamic': + toggleProperty(load, 0); + break; + default: + ScriptDiscoveryService.stopScript(load); + } +} +loads.forEach(undoOneTest); + +function finalReport() { + var baseline = results[0]; + results.forEach(function (result) { + result.ratio = (baseline.fps / result.fps); + }); + results.sort(function (a, b) { return b.ratio - a.ratio; }); + var report = 'Performance Report:\nBaseline: ' + baseline.fps.toFixed(1) + ' fps.'; + results.forEach(function (result) { + report += '\n' + result.ratio.toFixed(2) + ' (' + result.fps.toFixed(1) + ' fps over ' + result.elapsed + ' seconds) for ' + result.name; + }); + Window.alert(report); + LODManager.setAutomaticLODAdjust(initialLodIsAutomatic); + loads.forEach(restoreOneTest); + Script.stop(); +} + +function onNewStats() { // Accumulates frames on signal during load test + frames++; +} +var DEGREES_PER_FULL_TWIRL = 360; +var DEGREES_PER_UPDATE = 6; +function onUpdate() { // Spins on update signal during load test + MyAvatar.orientation = Quat.fromPitchYawRollDegrees(0, accumulatedRotation, 0); + accumulatedRotation += DEGREES_PER_UPDATE; + if (accumulatedRotation >= (DEGREES_PER_FULL_TWIRL * NUMBER_OF_TWIRLS_PER_LOAD)) { + tearDown(); + } +} +function startTest() { + print('start', load); + accumulatedRotation = frames = 0; + start = Date.now(); + Script.update.connect(onUpdate); + config.newStats.connect(onNewStats); +} + +function setup() { + if (loadIndex >= nLoads) { + return finalReport(); + } + load = loads[loadIndex]; + restoreOneTest(load); + Script.setTimeout(startTest, WAIT_TIME_MILLISECONDS); +} +function tearDown() { + var now = Date.now(); + var elapsed = (now - start) / MILLISECONDS_PER_SECOND; + Script.update.disconnect(onUpdate); + config.newStats.disconnect(onNewStats); + if (load !== 'ignore') { + results.push({name: load, fps: frames / elapsed, elapsed: elapsed}); + } + undoOneTest(load); + loadIndex++; + setup(); +} +function waitUntilStopped() { // Wait until all the scripts have stopped + var count = ScriptDiscoveryService.getRunning().length; + if (count === numberLeftRunning) { + return setup(); + } + print('Still', count, 'scripts running'); + Script.setTimeout(waitUntilStopped, WAIT_TIME_MILLISECONDS); +} +waitUntilStopped(); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index ecece8c4f7..373f203e80 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -221,6 +221,14 @@ function entityHasActions(entityID) { return Entities.getActionIDs(entityID).length > 0; } +function findRayIntersection(pickRay, precise, include, exclude) { + var entities = Entities.findRayIntersection(pickRay, precise, include, exclude); + var overlays = Overlays.findRayIntersection(pickRay); + if (!overlays.intersects || (entities.intersects && (entities.distance <= overlays.distance))) { + return entities; + } + return overlays; +} function entityIsGrabbedByOther(entityID) { // by convention, a distance grab sets the tag of its action to be grab-*owner-session-id*. var actionIDs = Entities.getActionIDs(entityID); @@ -251,6 +259,10 @@ function propsArePhysical(props) { // If another script is managing the reticle (as is done by HandControllerPointer), we should not be setting it here, // and we should not be showing lasers when someone else is using the Reticle to indicate a 2D minor mode. var EXTERNALLY_MANAGED_2D_MINOR_MODE = true; +var EDIT_SETTING = "io.highfidelity.isEditting"; +function isEditing() { + return EXTERNALLY_MANAGED_2D_MINOR_MODE && Settings.getValue(EDIT_SETTING); +} function isIn2DMode() { // In this version, we make our own determination of whether we're aimed a HUD element, // because other scripts (such as handControllerPointer) might be using some other visualization @@ -1069,20 +1081,15 @@ function MyController(hand) { var intersection; if (USE_BLACKLIST === true && blacklist.length !== 0) { - intersection = Entities.findRayIntersection(pickRayBacked, true, [], blacklist); + intersection = findRayIntersection(pickRayBacked, true, [], blacklist); } else { - intersection = Entities.findRayIntersection(pickRayBacked, true); - } - - var overlayIntersection = Overlays.findRayIntersection(pickRayBacked); - if (!intersection.intersects || - (overlayIntersection.intersects && (intersection.distance > overlayIntersection.distance))) { - intersection = overlayIntersection; + intersection = findRayIntersection(pickRayBacked, true); } if (intersection.intersects) { return { entityID: intersection.entityID, + overlayID: intersection.overlayID, searchRay: pickRay, distance: Vec3.distance(pickRay.origin, intersection.intersection) }; @@ -1326,6 +1333,8 @@ function MyController(hand) { if (this.entityIsGrabbable(rayPickInfo.entityID) && rayPickInfo.distance < NEAR_GRAB_PICK_RADIUS) { grabbableEntities.push(rayPickInfo.entityID); } + } else if (rayPickInfo.overlayID) { + this.intersectionDistance = rayPickInfo.distance; } else { this.intersectionDistance = 0; } @@ -1382,7 +1391,7 @@ function MyController(hand) { // TODO: highlight the far-triggerable object? } } else if (this.entityIsDistanceGrabbable(rayPickInfo.entityID, handPosition)) { - if (this.triggerSmoothedGrab()) { + if (this.triggerSmoothedGrab() && !isEditing()) { this.grabbedEntity = entity; this.setState(STATE_DISTANCE_HOLDING, "distance hold '" + name + "'"); return; @@ -1942,8 +1951,8 @@ function MyController(hand) { var now = Date.now(); if (now - this.lastPickTime > MSECS_PER_SEC / PICKS_PER_SECOND_PER_HAND) { - var intersection = Entities.findRayIntersection(pickRay, true); - if (intersection.accurate) { + var intersection = findRayIntersection(pickRay, true); + if (intersection.accurate || intersection.overlayID) { this.lastPickTime = now; if (intersection.entityID != this.grabbedEntity) { this.callEntityMethodOnGrabbed("stopFarTrigger"); diff --git a/scripts/system/edit.js b/scripts/system/edit.js index 6810bae590..4e87fcbf71 100644 --- a/scripts/system/edit.js +++ b/scripts/system/edit.js @@ -332,6 +332,8 @@ var toolBar = (function() { entityListTool.clearEntityList(); }; + var EDIT_SETTING = "io.highfidelity.isEditting"; // for communication with other scripts + Settings.setValue(EDIT_SETTING, false); that.setActive = function(active) { if (active != isActive) { if (active && !Entities.canRez() && !Entities.canRezTmp()) { @@ -341,6 +343,7 @@ var toolBar = (function() { enabled: active })); isActive = active; + Settings.setValue(EDIT_SETTING, active); if (!isActive) { entityListTool.setVisible(false); gridTool.setVisible(false); @@ -348,6 +351,7 @@ var toolBar = (function() { propertiesTool.setVisible(false); selectionManager.clearSelections(); cameraManager.disable(); + selectionDisplay.triggerMapping.disable(); } else { UserActivityLogger.enabledEdit(); hasShownPropertiesTool = false; @@ -356,6 +360,7 @@ var toolBar = (function() { grid.setEnabled(true); propertiesTool.setVisible(true); // Not sure what the following was meant to accomplish, but it currently causes + selectionDisplay.triggerMapping.enable(); // everybody else to think that Interface has lost focus overall. fogbugzid:558 // Window.setFocus(); } @@ -1438,6 +1443,9 @@ function pushCommandForSelections(createdEntityData, deletedEntityData) { var entityID = SelectionManager.selections[i]; var initialProperties = SelectionManager.savedProperties[entityID]; var currentProperties = Entities.getEntityProperties(entityID); + if (!initialProperties) { + continue; + } undoData.setProperties.push({ entityID: entityID, properties: { diff --git a/scripts/system/libraries/entitySelectionTool.js b/scripts/system/libraries/entitySelectionTool.js index e46306ca31..2003df3652 100644 --- a/scripts/system/libraries/entitySelectionTool.js +++ b/scripts/system/libraries/entitySelectionTool.js @@ -1005,6 +1005,56 @@ SelectionDisplay = (function() { var activeTool = null; var grabberTools = {}; + // We get mouseMoveEvents from the handControllers, via handControllerPointer. + // But we dont' get mousePressEvents. + that.triggerMapping = Controller.newMapping(Script.resolvePath('') + '-click'); + Script.scriptEnding.connect(that.triggerMapping.disable); + that.TRIGGER_GRAB_VALUE = 0.85; // From handControllerGrab/Pointer.js. Should refactor. + that.TRIGGER_ON_VALUE = 0.4; + that.TRIGGER_OFF_VALUE = 0.15; + that.triggered = false; + var activeHand = Controller.Standard.RightHand; + function makeTriggerHandler(hand) { + return function (value) { + if (!that.triggered && (value > that.TRIGGER_GRAB_VALUE)) { // should we smooth? + that.triggered = true; + if (activeHand !== hand) { + // No switching while the other is already triggered, so no need to release. + activeHand = (activeHand === Controller.Standard.RightHand) ? Controller.Standard.LeftHand : Controller.Standard.RightHand; + } + var eventResult = that.mousePressEvent({}); + if (!eventResult || (eventResult === 'selectionBox')) { + var pickRay = controllerComputePickRay(); + if (pickRay) { + var entityIntersection = Entities.findRayIntersection(pickRay, true); + var overlayIntersection = Overlays.findRayIntersection(pickRay); + if (entityIntersection.intersects && + (!overlayIntersection.intersects || (entityIntersection.distance < overlayIntersection.distance))) { + selectionManager.setSelections([entityIntersection.entityID]); + } + } + } + } else if (that.triggered && (value < that.TRIGGER_OFF_VALUE)) { + that.triggered = false; + that.mouseReleaseEvent({}); + } + }; + } + that.triggerMapping.from(Controller.Standard.RT).peek().to(makeTriggerHandler(Controller.Standard.RightHand)); + that.triggerMapping.from(Controller.Standard.LT).peek().to(makeTriggerHandler(Controller.Standard.LeftHand)); + function controllerComputePickRay() { + var controllerPose = Controller.getPoseValue(activeHand); + if (controllerPose.valid && that.triggered) { + var controllerPosition = Vec3.sum(Vec3.multiplyQbyV(MyAvatar.orientation, controllerPose.translation), + MyAvatar.position); + // This gets point direction right, but if you want general quaternion it would be more complicated: + var controllerDirection = Quat.getUp(Quat.multiply(MyAvatar.orientation, controllerPose.rotation)); + return {origin: controllerPosition, direction: controllerDirection}; + } + } + function generalComputePickRay(x, y) { + return controllerComputePickRay() || Camera.computePickRay(x, y); + } function addGrabberTool(overlay, tool) { grabberTools[overlay] = { mode: tool.mode, @@ -1047,7 +1097,7 @@ SelectionDisplay = (function() { lastCameraOrientation = Camera.getOrientation(); if (event !== false) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); var wantDebug = false; if (wantDebug) { @@ -2269,7 +2319,7 @@ SelectionDisplay = (function() { startPosition = SelectionManager.worldPosition; var dimensions = SelectionManager.worldDimensions; - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, { x: 0, y: 1, @@ -2312,7 +2362,7 @@ SelectionDisplay = (function() { }, onMove: function(event) { var wantDebug = false; - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, { x: 0, @@ -2422,6 +2472,9 @@ SelectionDisplay = (function() { for (var i = 0; i < SelectionManager.selections.length; i++) { var properties = SelectionManager.savedProperties[SelectionManager.selections[i]]; + if (!properties) { + continue; + } var newPosition = Vec3.sum(properties.position, { x: vector.x, y: 0, @@ -2448,7 +2501,7 @@ SelectionDisplay = (function() { addGrabberTool(grabberMoveUp, { mode: "TRANSLATE_UP_DOWN", onBegin: function(event) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); upDownPickNormal = Quat.getFront(lastCameraOrientation); // Remove y component so the y-axis lies along the plane we picking on - this will @@ -2481,7 +2534,7 @@ SelectionDisplay = (function() { pushCommandForSelections(duplicatedEntityIDs); }, onMove: function(event) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); // translate mode left/right based on view toward entity var newIntersection = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, upDownPickNormal); @@ -2690,7 +2743,7 @@ SelectionDisplay = (function() { } } planeNormal = Vec3.multiplyQbyV(rotation, planeNormal); - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); lastPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); @@ -2726,7 +2779,7 @@ SelectionDisplay = (function() { rotation = SelectionManager.worldRotation; } - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); newPick = rayPlaneIntersection(pickRay, pickRayPosition, planeNormal); @@ -2782,10 +2835,10 @@ SelectionDisplay = (function() { var wantDebug = false; if (wantDebug) { print(stretchMode); - Vec3.print(" newIntersection:", newIntersection); + //Vec3.print(" newIntersection:", newIntersection); Vec3.print(" vector:", vector); - Vec3.print(" oldPOS:", oldPOS); - Vec3.print(" newPOS:", newPOS); + //Vec3.print(" oldPOS:", oldPOS); + //Vec3.print(" newPOS:", newPOS); Vec3.print(" changeInDimensions:", changeInDimensions); Vec3.print(" newDimensions:", newDimensions); @@ -3350,7 +3403,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3520,7 +3573,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3682,7 +3735,7 @@ SelectionDisplay = (function() { pushCommandForSelections(); }, onMove: function(event) { - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); Overlays.editOverlay(selectionBox, { ignoreRayIntersection: true, visible: false @@ -3797,13 +3850,13 @@ SelectionDisplay = (function() { that.mousePressEvent = function(event) { var wantDebug = false; - if (!event.isLeftButton) { + if (!event.isLeftButton && !that.triggered) { // if another mouse button than left is pressed ignore it return false; } var somethingClicked = false; - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); // before we do a ray test for grabbers, disable the ray intersection for our selection box Overlays.editOverlay(selectionBox, { @@ -3837,7 +3890,7 @@ SelectionDisplay = (function() { if (tool) { activeTool = tool; mode = tool.mode; - somethingClicked = true; + somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); } @@ -3845,7 +3898,7 @@ SelectionDisplay = (function() { switch (result.overlayID) { case grabberMoveUp: mode = "TRANSLATE_UP_DOWN"; - somethingClicked = true; + somethingClicked = mode; // in translate mode, we hide our stretch handles... for (var i = 0; i < stretchHandles.length; i++) { @@ -3860,34 +3913,34 @@ SelectionDisplay = (function() { case grabberEdgeTN: // TODO: maybe this should be TOP+NEAR stretching? case grabberEdgeBN: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_NEAR"; - somethingClicked = true; + somethingClicked = mode; break; case grabberFAR: case grabberEdgeTF: // TODO: maybe this should be TOP+FAR stretching? case grabberEdgeBF: // TODO: maybe this should be BOTTOM+FAR stretching? mode = "STRETCH_FAR"; - somethingClicked = true; + somethingClicked = mode; break; case grabberTOP: mode = "STRETCH_TOP"; - somethingClicked = true; + somethingClicked = mode; break; case grabberBOTTOM: mode = "STRETCH_BOTTOM"; - somethingClicked = true; + somethingClicked = mode; break; case grabberRIGHT: case grabberEdgeTR: // TODO: maybe this should be TOP+RIGHT stretching? case grabberEdgeBR: // TODO: maybe this should be BOTTOM+RIGHT stretching? mode = "STRETCH_RIGHT"; - somethingClicked = true; + somethingClicked = mode; break; case grabberLEFT: case grabberEdgeTL: // TODO: maybe this should be TOP+LEFT stretching? case grabberEdgeBL: // TODO: maybe this should be BOTTOM+LEFT stretching? mode = "STRETCH_LEFT"; - somethingClicked = true; + somethingClicked = mode; break; default: @@ -3955,7 +4008,7 @@ SelectionDisplay = (function() { if (tool) { activeTool = tool; mode = tool.mode; - somethingClicked = true; + somethingClicked = 'tool'; if (activeTool && activeTool.onBegin) { activeTool.onBegin(event); } @@ -3963,7 +4016,7 @@ SelectionDisplay = (function() { switch (result.overlayID) { case yawHandle: mode = "ROTATE_YAW"; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = yawHandleRotation; overlayCenter = yawCenter; yawZero = result.intersection; @@ -3973,7 +4026,7 @@ SelectionDisplay = (function() { case pitchHandle: mode = "ROTATE_PITCH"; initialPosition = SelectionManager.worldPosition; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = pitchHandleRotation; overlayCenter = pitchCenter; pitchZero = result.intersection; @@ -3982,7 +4035,7 @@ SelectionDisplay = (function() { case rollHandle: mode = "ROTATE_ROLL"; - somethingClicked = true; + somethingClicked = mode; overlayOrientation = rollHandleRotation; overlayCenter = rollCenter; rollZero = result.intersection; @@ -4156,7 +4209,7 @@ SelectionDisplay = (function() { mode = translateXZTool.mode; activeTool.onBegin(event); - somethingClicked = true; + somethingClicked = 'selectionBox'; break; default: if (wantDebug) { @@ -4169,7 +4222,7 @@ SelectionDisplay = (function() { } if (somethingClicked) { - pickRay = Camera.computePickRay(event.x, event.y); + pickRay = generalComputePickRay(event.x, event.y); if (wantDebug) { print("mousePressEvent()...... " + overlayNames[result.overlayID]); } @@ -4201,7 +4254,7 @@ SelectionDisplay = (function() { } // if no tool is active, then just look for handles to highlight... - var pickRay = Camera.computePickRay(event.x, event.y); + var pickRay = generalComputePickRay(event.x, event.y); var result = Overlays.findRayIntersection(pickRay); var pickedColor; var pickedAlpha; @@ -4320,7 +4373,7 @@ SelectionDisplay = (function() { that.updateHandleSizes = function() { if (selectionManager.hasSelection()) { var diff = Vec3.subtract(selectionManager.worldPosition, Camera.getPosition()); - var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 2; + var grabberSize = Vec3.length(diff) * GRABBER_DISTANCE_TO_SIZE_RATIO * 5; var dimensions = SelectionManager.worldDimensions; var avgDimension = (dimensions.x + dimensions.y + dimensions.z) / 3; grabberSize = Math.min(grabberSize, avgDimension / 10);