Merge branch 'master' into vr-edit-a

This commit is contained in:
David Rowe 2017-08-05 20:45:12 +12:00
commit 5d1732c5aa
79 changed files with 3221 additions and 858 deletions

View file

@ -109,7 +109,7 @@ private:
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
AudioGate _audioGate;
bool _audioGateOpen { false };
bool _audioGateOpen { true };
bool _isNoiseGateEnabled { false };
CodecPluginPointer _codec;

View file

@ -27,21 +27,12 @@ macro(GENERATE_INSTALLERS)
endif ()
set(CPACK_PACKAGE_INSTALL_DIRECTORY ${_DISPLAY_NAME})
if (WIN32)
# include CMake module that will install compiler system libraries
# so that we have msvcr120 and msvcp120 installed with targets
set(CMAKE_INSTALL_SYSTEM_RUNTIME_DESTINATION ${INTERFACE_INSTALL_DIR})
# as long as we're including sixense plugin with installer
# we need re-distributables for VS 2011 as well
# this should be removed if/when sixense support is pulled
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS
"${EXTERNALS_BINARY_DIR}/sixense/project/src/sixense/samples/win64/msvcr100.dll"
"${EXTERNALS_BINARY_DIR}/sixense/project/src/sixense/samples/win64/msvcp100.dll"
)
# Do not install the Visual Studio C runtime libraries. The installer will do this automatically
set(CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS_SKIP TRUE)
include(InstallRequiredSystemLibraries)
set(CPACK_NSIS_MUI_ICON "${HF_CMAKE_DIR}/installer/installer.ico")
# install and reference the Add/Remove icon
@ -93,3 +84,4 @@ macro(GENERATE_INSTALLERS)
include(CPack)
endmacro()

View file

@ -22,9 +22,12 @@ macro(install_beside_console)
else ()
# setup install of executable and things copied by fixup/windeployqt
install(
FILES "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
DIRECTORY "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
DESTINATION ${COMPONENT_INSTALL_DIR}
COMPONENT ${SERVER_COMPONENT}
PATTERN "*.pdb" EXCLUDE
PATTERN "*.lib" EXCLUDE
PATTERN "*.exp" EXCLUDE
)
# on windows for PR and production builds, sign the executable

View file

@ -11,34 +11,28 @@
include(BundleUtilities)
# replace copy_resolved_item_into_bundle
#
# The official version of copy_resolved_item_into_bundle will print out a "warning:" when
# the resolved item matches the resolved embedded item. This not not really an issue that
# should rise to the level of a "warning" so we replace this message with a "status:"
#
function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item)
if (WIN32)
# ignore case on Windows
string(TOLOWER "${resolved_item}" resolved_item_compare)
string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare)
else()
set(resolved_item_compare "${resolved_item}")
set(resolved_embedded_item_compare "${resolved_embedded_item}")
function(gp_resolved_file_type_override resolved_file type_var)
if( file MATCHES ".*VCRUNTIME140.*" )
set(type "system" PARENT_SCOPE)
endif()
if ("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}")
# this is our only change from the original version
message(STATUS "status: resolved_item == resolved_embedded_item - not copying...")
else()
#message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}")
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}")
if(UNIX AND NOT APPLE)
file(RPATH_REMOVE FILE "${resolved_embedded_item}")
endif()
if( file MATCHES ".*concrt140.*" )
set(type "system" PARENT_SCOPE)
endif()
if( file MATCHES ".*msvcp140.*" )
set(type "system" PARENT_SCOPE)
endif()
if( file MATCHES ".*vcruntime140.*" )
set(type "system" PARENT_SCOPE)
endif()
if( file MATCHES ".*api-ms-win-crt-conio.*" )
set(type "system" PARENT_SCOPE)
endif()
if( file MATCHES ".*api-ms-win-core-winrt.*" )
set(type "system" PARENT_SCOPE)
endif()
endfunction()
message(STATUS "FIXUP_LIBS for fixup_bundle called for bundle ${BUNDLE_EXECUTABLE} are @FIXUP_LIBS@")
message(STATUS "Scanning for plugins from ${BUNDLE_PLUGIN_DIR}")
@ -52,3 +46,4 @@ endif()
file(GLOB EXTRA_PLUGINS "${BUNDLE_PLUGIN_DIR}/*.${PLUGIN_EXTENSION}")
fixup_bundle("${BUNDLE_EXECUTABLE}" "${EXTRA_PLUGINS}" "@FIXUP_LIBS@")

View file

@ -853,6 +853,8 @@ Section "-Core installation"
; Rename the incorrectly cased Raleway font
Rename "$INSTDIR\resources\qml\styles-uit\RalewaySemibold.qml" "$INSTDIR\resources\qml\styles-uit\RalewaySemiBold.qml"
ExecWait "$INSTDIR\vcredist_x64.exe /install /q /norestart"
; Remove the Old Interface directory and vcredist_x64.exe (from installs prior to Server Console)
RMDir /r "$INSTDIR\Interface"
Delete "$INSTDIR\vcredist_x64.exe"

View file

@ -309,9 +309,12 @@ else (APPLE)
# setup install of executable and things copied by fixup/windeployqt
install(
FILES "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
DIRECTORY "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
DESTINATION ${INTERFACE_INSTALL_DIR}
COMPONENT ${CLIENT_COMPONENT}
PATTERN "*.pdb" EXCLUDE
PATTERN "*.lib" EXCLUDE
PATTERN "*.exp" EXCLUDE
)
set(SCRIPTS_INSTALL_DIR "${INTERFACE_INSTALL_DIR}")

View file

@ -15,7 +15,7 @@
{ "comment" : "Mouse turn need to be small continuous increments",
"from": { "makeAxis" : [
[ "Keyboard.MouseMoveLeft" ],
[ "Keyboard.MouseMoveRight" ]
[ "Keyboard.MouseMoveRight" ]
]
},
"when": [ "Application.InHMD", "Application.SnapTurn", "Keyboard.RightMouseButton" ],
@ -31,8 +31,8 @@
{ "comment" : "Touchpad turn need to be small continuous increments, but without the RMB constraint",
"from": { "makeAxis" : [
[ "Keyboard.TouchpadLeft" ],
[ "Keyboard.TouchpadRight" ]
]
[ "Keyboard.TouchpadRight" ]
]
},
"when": [ "Application.InHMD", "Application.SnapTurn" ],
"to": "Actions.StepYaw",

View file

@ -109,6 +109,23 @@
{ "from": "Standard.Head", "to": "Actions.Head" },
{ "from": "Standard.LeftArm", "to": "Actions.LeftArm" },
{ "from": "Standard.RightArm", "to": "Actions.RightArm" }
{ "from": "Standard.RightArm", "to": "Actions.RightArm" },
{ "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" },
{ "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" },
{ "from": "Standard.TrackedObject02", "to" : "Actions.TrackedObject02" },
{ "from": "Standard.TrackedObject03", "to" : "Actions.TrackedObject03" },
{ "from": "Standard.TrackedObject04", "to" : "Actions.TrackedObject04" },
{ "from": "Standard.TrackedObject05", "to" : "Actions.TrackedObject05" },
{ "from": "Standard.TrackedObject06", "to" : "Actions.TrackedObject06" },
{ "from": "Standard.TrackedObject07", "to" : "Actions.TrackedObject07" },
{ "from": "Standard.TrackedObject08", "to" : "Actions.TrackedObject08" },
{ "from": "Standard.TrackedObject09", "to" : "Actions.TrackedObject09" },
{ "from": "Standard.TrackedObject10", "to" : "Actions.TrackedObject10" },
{ "from": "Standard.TrackedObject11", "to" : "Actions.TrackedObject11" },
{ "from": "Standard.TrackedObject12", "to" : "Actions.TrackedObject12" },
{ "from": "Standard.TrackedObject13", "to" : "Actions.TrackedObject13" },
{ "from": "Standard.TrackedObject14", "to" : "Actions.TrackedObject14" },
{ "from": "Standard.TrackedObject15", "to" : "Actions.TrackedObject15" }
]
}

View file

@ -77,6 +77,23 @@
{ "from": "Vive.Head", "to" : "Standard.Head"},
{ "from": "Vive.RightArm", "to" : "Standard.RightArm" },
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm" }
{ "from": "Vive.LeftArm", "to" : "Standard.LeftArm" },
{ "from": "Vive.TrackedObject00", "to" : "Standard.TrackedObject00" },
{ "from": "Vive.TrackedObject01", "to" : "Standard.TrackedObject01" },
{ "from": "Vive.TrackedObject02", "to" : "Standard.TrackedObject02" },
{ "from": "Vive.TrackedObject03", "to" : "Standard.TrackedObject03" },
{ "from": "Vive.TrackedObject04", "to" : "Standard.TrackedObject04" },
{ "from": "Vive.TrackedObject05", "to" : "Standard.TrackedObject05" },
{ "from": "Vive.TrackedObject06", "to" : "Standard.TrackedObject06" },
{ "from": "Vive.TrackedObject07", "to" : "Standard.TrackedObject07" },
{ "from": "Vive.TrackedObject08", "to" : "Standard.TrackedObject08" },
{ "from": "Vive.TrackedObject09", "to" : "Standard.TrackedObject09" },
{ "from": "Vive.TrackedObject10", "to" : "Standard.TrackedObject10" },
{ "from": "Vive.TrackedObject11", "to" : "Standard.TrackedObject11" },
{ "from": "Vive.TrackedObject12", "to" : "Standard.TrackedObject12" },
{ "from": "Vive.TrackedObject13", "to" : "Standard.TrackedObject13" },
{ "from": "Vive.TrackedObject14", "to" : "Standard.TrackedObject14" },
{ "from": "Vive.TrackedObject15", "to" : "Standard.TrackedObject15" }
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View file

@ -114,6 +114,7 @@ Item {
}
function clearMenus() {
topMenu = null
d.clear()
}

View file

@ -202,5 +202,11 @@ Item {
width: 480
height: 706
function setShown(value) {}
function setShown(value) {
if (value === true) {
HMD.openTablet()
} else {
HMD.closeTablet()
}
}
}

View file

@ -70,7 +70,7 @@
#include <EntityScriptClient.h>
#include <EntityScriptServerLogClient.h>
#include <EntityScriptingInterface.h>
#include <HoverOverlayInterface.h>
#include "ui/overlays/ContextOverlayInterface.h"
#include <ErrorDialog.h>
#include <FileScriptingInterface.h>
#include <Finally.h>
@ -595,7 +595,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<Snapshot>();
DependencyManager::set<CloseEventSender>();
DependencyManager::set<ResourceManager>();
DependencyManager::set<HoverOverlayInterface>();
DependencyManager::set<ContextOverlayInterface>();
return previousSessionCrashed;
}
@ -1324,12 +1324,16 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Keyboard focus handling for Web overlays.
auto overlays = &(qApp->getOverlays());
connect(overlays, &Overlays::mousePressOnOverlay, [=](OverlayID overlayID, const PointerEvent& event) {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
setKeyboardFocusOverlay(overlayID);
connect(overlays, &Overlays::mousePressOnOverlay, [=](const OverlayID& overlayID, const PointerEvent& event) {
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(overlays->getOverlay(overlayID));
// Only Web overlays can have keyboard focus.
if (thisOverlay) {
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
setKeyboardFocusOverlay(overlayID);
}
});
connect(overlays, &Overlays::overlayDeleted, [=](OverlayID overlayID) {
connect(overlays, &Overlays::overlayDeleted, [=](const OverlayID& overlayID) {
if (overlayID == _keyboardFocusedOverlay.get()) {
setKeyboardFocusOverlay(UNKNOWN_OVERLAY_ID);
}
@ -1344,6 +1348,21 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
setKeyboardFocusEntity(UNKNOWN_ENTITY_ID);
});
connect(overlays,
SIGNAL(mousePressOnOverlay(const OverlayID&, const PointerEvent&)),
DependencyManager::get<ContextOverlayInterface>().data(),
SLOT(contextOverlays_mousePressOnOverlay(const OverlayID&, const PointerEvent&)));
connect(overlays,
SIGNAL(hoverEnterOverlay(const OverlayID&, const PointerEvent&)),
DependencyManager::get<ContextOverlayInterface>().data(),
SLOT(contextOverlays_hoverEnterOverlay(const OverlayID&, const PointerEvent&)));
connect(overlays,
SIGNAL(hoverLeaveOverlay(const OverlayID&, const PointerEvent&)),
DependencyManager::get<ContextOverlayInterface>().data(),
SLOT(contextOverlays_hoverLeaveOverlay(const OverlayID&, const PointerEvent&)));
// Add periodic checks to send user activity data
static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000;
static int NEARBY_AVATAR_RADIUS_METERS = 10;
@ -1470,7 +1489,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
properties["atp_mapping_requests"] = atpMappingRequests;
properties["throttled"] = _displayPlugin ? _displayPlugin->isThrottled() : false;
QJsonObject bytesDownloaded;
bytesDownloaded["atp"] = statTracker->getStat(STAT_ATP_RESOURCE_TOTAL_BYTES).toInt();
bytesDownloaded["http"] = statTracker->getStat(STAT_HTTP_RESOURCE_TOTAL_BYTES).toInt();
@ -2131,7 +2150,7 @@ void Application::initializeUi() {
surfaceContext->setContextProperty("ApplicationCompositor", &getApplicationCompositor());
surfaceContext->setContextProperty("AvatarInputs", AvatarInputs::getInstance());
surfaceContext->setContextProperty("HoverOverlay", DependencyManager::get<HoverOverlayInterface>().data());
surfaceContext->setContextProperty("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {
surfaceContext->setContextProperty("Steam", new SteamScriptingInterface(engine, steamClient.get()));
@ -2237,7 +2256,7 @@ void Application::paintGL() {
QMutexLocker viewLocker(&_viewMutex);
_viewFrustum.calculate();
}
renderArgs = RenderArgs(_gpuContext, getEntities(), lodManager->getOctreeSizeScale(),
renderArgs = RenderArgs(_gpuContext, lodManager->getOctreeSizeScale(),
lodManager->getBoundaryLevelAdjust(), RenderArgs::DEFAULT_RENDER_MODE,
RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE);
{
@ -2753,7 +2772,12 @@ bool Application::importSVOFromURL(const QString& urlString) {
return true;
}
bool _renderRequested { false };
void Application::onPresent(quint32 frameCount) {
if (shouldPaint()) {
postEvent(this, new QEvent(static_cast<QEvent::Type>(Idle)), Qt::HighEventPriority);
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
}
}
bool Application::event(QEvent* event) {
if (!Menu::getInstance()) {
@ -2769,23 +2793,9 @@ bool Application::event(QEvent* event) {
// Explicit idle keeps the idle running at a lower interval, but without any rendering
// see (windowMinimizedChanged)
case Event::Idle:
{
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
_lastTimeUpdated.start();
idle(nsecsElapsed);
}
return true;
case Event::Present:
if (!_renderRequested) {
float nsecsElapsed = (float)_lastTimeUpdated.nsecsElapsed();
if (shouldPaint(nsecsElapsed)) {
_renderRequested = true;
_lastTimeUpdated.start();
idle(nsecsElapsed);
postEvent(this, new QEvent(static_cast<QEvent::Type>(Paint)), Qt::HighEventPriority);
}
}
idle();
// Clear the event queue of pending idle calls
removePostedEvents(this, Idle);
return true;
case Event::Paint:
@ -2793,9 +2803,8 @@ bool Application::event(QEvent* event) {
// or AvatarInputs will mysteriously move to the bottom-right
AvatarInputs::getInstance()->update();
paintGL();
// wait for the next present event before starting idle / paint again
removePostedEvents(this, Present);
_renderRequested = false;
// Clear the event queue of pending paint calls
removePostedEvents(this, Paint);
return true;
default:
@ -3614,7 +3623,7 @@ bool Application::acceptSnapshot(const QString& urlString) {
static uint32_t _renderedFrameIndex { INVALID_FRAME };
bool Application::shouldPaint(float nsecsElapsed) {
bool Application::shouldPaint() {
if (_aboutToQuit) {
return false;
}
@ -3634,11 +3643,9 @@ bool Application::shouldPaint(float nsecsElapsed) {
(float)paintDelaySamples / paintDelayUsecs << "us";
}
#endif
float msecondsSinceLastUpdate = nsecsElapsed / NSECS_PER_USEC / USECS_PER_MSEC;
// Throttle if requested
if (displayPlugin->isThrottled() && (msecondsSinceLastUpdate < THROTTLED_SIM_FRAME_PERIOD_MS)) {
if (displayPlugin->isThrottled() && (_lastTimeUpdated.elapsed() < THROTTLED_SIM_FRAME_PERIOD_MS)) {
return false;
}
@ -3855,7 +3862,7 @@ void setupCpuMonitorThread() {
#endif
void Application::idle(float nsecsElapsed) {
void Application::idle() {
PerformanceTimer perfTimer("idle");
// Update the deadlock watchdog
@ -3912,7 +3919,8 @@ void Application::idle(float nsecsElapsed) {
steamClient->runCallbacks();
}
float secondsSinceLastUpdate = nsecsElapsed / NSECS_PER_MSEC / MSECS_PER_SECOND;
float secondsSinceLastUpdate = (float)_lastTimeUpdated.nsecsElapsed() / NSECS_PER_MSEC / MSECS_PER_SECOND;
_lastTimeUpdated.start();
// If the offscreen Ui has something active that is NOT the root, then assume it has keyboard focus.
if (_keyboardDeviceHasFocus && offscreenUi && offscreenUi->getWindow()->activeFocusItem() != offscreenUi->getRootItem()) {
@ -4146,6 +4154,7 @@ void Application::loadSettings() {
//DependencyManager::get<LODManager>()->setAutomaticLODAdjust(false);
Menu::getInstance()->loadSettings();
// If there is a preferred plugin, we probably messed it up with the menu settings, so fix it.
auto pluginManager = PluginManager::getInstance();
auto plugins = pluginManager->getPreferredDisplayPlugins();
@ -4159,24 +4168,44 @@ void Application::loadSettings() {
break;
}
}
} else {
}
Setting::Handle<bool> firstRun { Settings::firstRun, true };
bool isFirstPerson = false;
if (firstRun.get()) {
// If this is our first run, and no preferred devices were set, default to
// an HMD device if available.
Setting::Handle<bool> firstRun { Settings::firstRun, true };
if (firstRun.get()) {
auto displayPlugins = pluginManager->getDisplayPlugins();
for (auto& plugin : displayPlugins) {
if (plugin->isHmd()) {
if (auto action = menu->getActionForOption(plugin->getName())) {
action->setChecked(true);
action->trigger();
break;
}
auto displayPlugins = pluginManager->getDisplayPlugins();
for (auto& plugin : displayPlugins) {
if (plugin->isHmd()) {
if (auto action = menu->getActionForOption(plugin->getName())) {
action->setChecked(true);
action->trigger();
break;
}
}
}
isFirstPerson = (qApp->isHMDMode());
} else {
// if this is not the first run, the camera will be initialized differently depending on user settings
if (qApp->isHMDMode()) {
// if the HMD is active, use first-person camera, unless the appropriate setting is checked
isFirstPerson = menu->isOptionChecked(MenuOption::FirstPersonHMD);
} else {
// if HMD is not active, only use first person if the menu option is checked
isFirstPerson = menu->isOptionChecked(MenuOption::FirstPerson);
}
}
// finish initializing the camera, based on everything we checked above. Third person camera will be used if no settings
// dictated that we should be in first person
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, isFirstPerson);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !isFirstPerson);
_myCamera.setMode((isFirstPerson) ? CAMERA_MODE_FIRST_PERSON : CAMERA_MODE_THIRD_PERSON);
cameraMenuChanged();
auto inputs = pluginManager->getInputPlugins();
for (auto plugin : inputs) {
if (!plugin->isActive()) {
@ -4225,7 +4254,6 @@ void Application::init() {
DependencyManager::get<DeferredLightingEffect>()->init();
DependencyManager::get<AvatarManager>()->init();
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
_timerStart.start();
_lastTimeUpdated.start();
@ -5397,6 +5425,17 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
}
renderArgs->_debugFlags = renderDebugFlags;
//ViveControllerManager::getInstance().updateRendering(renderArgs, _main3DScene, transaction);
RenderArgs::OutlineFlags renderOutlineFlags = RenderArgs::RENDER_OUTLINE_NONE;
auto contextOverlayInterface = DependencyManager::get<ContextOverlayInterface>();
if (contextOverlayInterface->getEnabled()) {
if (DependencyManager::get<ContextOverlayInterface>()->getIsInMarketplaceInspectionMode()) {
renderOutlineFlags = RenderArgs::RENDER_OUTLINE_MARKETPLACE_MODE;
} else {
renderOutlineFlags = RenderArgs::RENDER_OUTLINE_WIREFRAMES;
}
}
renderArgs->_outlineFlags = renderOutlineFlags;
}
}
@ -5860,7 +5899,7 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEngine* scri
auto entityScriptServerLog = DependencyManager::get<EntityScriptServerLogClient>();
scriptEngine->registerGlobalObject("EntityScriptServerLog", entityScriptServerLog.data());
scriptEngine->registerGlobalObject("AvatarInputs", AvatarInputs::getInstance());
scriptEngine->registerGlobalObject("HoverOverlay", DependencyManager::get<HoverOverlayInterface>().data());
scriptEngine->registerGlobalObject("ContextOverlay", DependencyManager::get<ContextOverlayInterface>().data());
qScriptRegisterMetaType(scriptEngine, OverlayIDtoScriptValue, OverlayIDfromScriptValue);
@ -7056,6 +7095,7 @@ void Application::updateDisplayMode() {
auto oldDisplayPlugin = _displayPlugin;
if (_displayPlugin) {
disconnect(_displayPluginPresentConnection);
_displayPlugin->deactivate();
}
@ -7096,6 +7136,7 @@ void Application::updateDisplayMode() {
_offscreenContext->makeCurrent();
getApplicationCompositor().setDisplayPlugin(newDisplayPlugin);
_displayPlugin = newDisplayPlugin;
_displayPluginPresentConnection = connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent);
offscreenUi->getDesktop()->setProperty("repositionLocked", wasRepositionLocked);
}

View file

@ -129,8 +129,7 @@ public:
virtual DisplayPluginPointer getActiveDisplayPlugin() const override;
enum Event {
Present = DisplayPlugin::Present,
Paint,
Paint = QEvent::User + 1,
Idle,
Lambda
};
@ -409,6 +408,7 @@ private slots:
void clearDomainOctreeDetails();
void clearDomainAvatars();
void onAboutToQuit();
void onPresent(quint32 frameCount);
void resettingDomain();
@ -455,8 +455,8 @@ private:
void cleanupBeforeQuit();
bool shouldPaint(float nsecsElapsed);
void idle(float nsecsElapsed);
bool shouldPaint();
void idle();
void update(float deltaTime);
// Various helper functions called during update()
@ -518,6 +518,7 @@ private:
OffscreenGLCanvas* _offscreenContext { nullptr };
DisplayPluginPointer _displayPlugin;
QMetaObject::Connection _displayPluginPresentConnection;
mutable std::mutex _displayPluginLock;
InputPluginList _activeInputPlugins;

View file

@ -12,6 +12,7 @@
#include <map>
#include <shared/QtHelpers.h>
#include <plugins/DisplayPlugin.h>
#include "AudioDevices.h"

View file

@ -9,6 +9,8 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "JSConsole.h"
#include <QFuture>
#include <QKeyEvent>
#include <QLabel>
@ -20,7 +22,6 @@
#include <PathUtils.h>
#include "Application.h"
#include "JSConsole.h"
#include "ScriptHighlighting.h"
const int NO_CURRENT_HISTORY_COMMAND = -1;
@ -60,14 +61,12 @@ void _writeLines(const QString& filename, const QList<QString>& lines) {
QTextStream(&file) << json;
}
JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
JSConsole::JSConsole(QWidget* parent, const QSharedPointer<ScriptEngine>& scriptEngine) :
QWidget(parent),
_ui(new Ui::Console),
_currentCommandInHistory(NO_CURRENT_HISTORY_COMMAND),
_savedHistoryFilename(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + HISTORY_FILENAME),
_commandHistory(_readLines(_savedHistoryFilename)),
_ownScriptEngine(scriptEngine == NULL),
_scriptEngine(NULL) {
_commandHistory(_readLines(_savedHistoryFilename)) {
_ui->setupUi(this);
_ui->promptTextEdit->setLineWrapMode(QTextEdit::NoWrap);
_ui->promptTextEdit->setWordWrapMode(QTextOption::NoWrap);
@ -90,36 +89,37 @@ JSConsole::JSConsole(QWidget* parent, ScriptEngine* scriptEngine) :
}
JSConsole::~JSConsole() {
disconnect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
disconnect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
if (_ownScriptEngine) {
_scriptEngine->deleteLater();
if (_scriptEngine) {
disconnect(_scriptEngine.data(), SIGNAL(printedMessage(const QString&)), this, SLOT(handlePrint(const QString&)));
disconnect(_scriptEngine.data(), SIGNAL(errorMessage(const QString&)), this, SLOT(handleError(const QString&)));
_scriptEngine.reset();
}
delete _ui;
}
void JSConsole::setScriptEngine(ScriptEngine* scriptEngine) {
void JSConsole::setScriptEngine(const QSharedPointer<ScriptEngine>& scriptEngine) {
if (_scriptEngine == scriptEngine && scriptEngine != NULL) {
return;
}
if (_scriptEngine != NULL) {
disconnect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
disconnect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
disconnect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
disconnect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError);
if (_ownScriptEngine) {
_scriptEngine->deleteLater();
}
disconnect(_scriptEngine.data(), &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
disconnect(_scriptEngine.data(), &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
disconnect(_scriptEngine.data(), &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
disconnect(_scriptEngine.data(), &ScriptEngine::errorMessage, this, &JSConsole::handleError);
_scriptEngine.reset();
}
// if scriptEngine is NULL then create one and keep track of it using _ownScriptEngine
_ownScriptEngine = (scriptEngine == NULL);
_scriptEngine = _ownScriptEngine ? DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false) : scriptEngine;
if (scriptEngine.isNull()) {
_scriptEngine = QSharedPointer<ScriptEngine>(DependencyManager::get<ScriptEngines>()->loadScript(_consoleFileName, false), &QObject::deleteLater);
} else {
_scriptEngine = scriptEngine;
}
connect(_scriptEngine, &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
connect(_scriptEngine, &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
connect(_scriptEngine, &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
connect(_scriptEngine, &ScriptEngine::errorMessage, this, &JSConsole::handleError);
connect(_scriptEngine.data(), &ScriptEngine::printedMessage, this, &JSConsole::handlePrint);
connect(_scriptEngine.data(), &ScriptEngine::infoMessage, this, &JSConsole::handleInfo);
connect(_scriptEngine.data(), &ScriptEngine::warningMessage, this, &JSConsole::handleWarning);
connect(_scriptEngine.data(), &ScriptEngine::errorMessage, this, &JSConsole::handleError);
}
void JSConsole::executeCommand(const QString& command) {
@ -135,19 +135,22 @@ void JSConsole::executeCommand(const QString& command) {
appendMessage(">", "<span style='" + COMMAND_STYLE + "'>" + command.toHtmlEscaped() + "</span>");
QFuture<QScriptValue> future = QtConcurrent::run(this, &JSConsole::executeCommandInWatcher, command);
QWeakPointer<ScriptEngine> weakScriptEngine = _scriptEngine;
auto consoleFileName = _consoleFileName;
QFuture<QScriptValue> future = QtConcurrent::run([weakScriptEngine, consoleFileName, command]()->QScriptValue{
QScriptValue result;
auto scriptEngine = weakScriptEngine.lock();
if (scriptEngine) {
BLOCKING_INVOKE_METHOD(scriptEngine.data(), "evaluate",
Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, command),
Q_ARG(const QString&, consoleFileName));
}
return result;
});
_executeWatcher.setFuture(future);
}
QScriptValue JSConsole::executeCommandInWatcher(const QString& command) {
QScriptValue result;
BLOCKING_INVOKE_METHOD(_scriptEngine, "evaluate",
Q_RETURN_ARG(QScriptValue, result),
Q_ARG(const QString&, command),
Q_ARG(const QString&, _consoleFileName));
return result;
}
void JSConsole::commandFinished() {
QScriptValue result = _executeWatcher.result();

View file

@ -17,6 +17,7 @@
#include <QFutureWatcher>
#include <QObject>
#include <QWidget>
#include <QSharedPointer>
#include "ui_console.h"
#include "ScriptEngine.h"
@ -29,10 +30,10 @@ const int CONSOLE_HEIGHT = 200;
class JSConsole : public QWidget {
Q_OBJECT
public:
JSConsole(QWidget* parent, ScriptEngine* scriptEngine = NULL);
JSConsole(QWidget* parent, const QSharedPointer<ScriptEngine>& scriptEngine = QSharedPointer<ScriptEngine>());
~JSConsole();
void setScriptEngine(ScriptEngine* scriptEngine = NULL);
void setScriptEngine(const QSharedPointer<ScriptEngine>& scriptEngine = QSharedPointer<ScriptEngine>());
void clear();
public slots:
@ -58,17 +59,14 @@ private:
void setToNextCommandInHistory();
void setToPreviousCommandInHistory();
void resetCurrentCommandHistory();
QScriptValue executeCommandInWatcher(const QString& command);
QFutureWatcher<QScriptValue> _executeWatcher;
Ui::Console* _ui;
int _currentCommandInHistory;
QString _savedHistoryFilename;
QList<QString> _commandHistory;
// Keeps track if the script engine is created inside the JSConsole
bool _ownScriptEngine;
QString _rootCommand;
ScriptEngine* _scriptEngine;
QSharedPointer<ScriptEngine> _scriptEngine;
static const QString _consoleFileName;
};

View file

@ -8,7 +8,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
//#include "Application.h"
#include "ResourceImageItem.h"
#include <QOpenGLFramebufferObjectFormat>
@ -16,6 +15,8 @@
#include <QOpenGLExtraFunctions>
#include <QOpenGLContext>
#include <plugins/DisplayPlugin.h>
ResourceImageItem::ResourceImageItem() : QQuickFramebufferObject() {
auto textureCache = DependencyManager::get<TextureCache>();
connect(textureCache.data(), SIGNAL(spectatorCameraFramebufferReset()), this, SLOT(update()));

View file

@ -13,8 +13,9 @@
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtGui/QImage>
#include <QtConcurrent/QtConcurrentRun>
#include <QtConcurrent/qtconcurrentrun.h>
#include <plugins/DisplayPlugin.h>
#include "SnapshotAnimated.h"
QTimer* SnapshotAnimated::snapshotAnimatedTimer = NULL;

View file

@ -24,12 +24,12 @@ TestingDialog::TestingDialog(QWidget* parent) :
_console->setFixedHeight(TESTING_CONSOLE_HEIGHT);
auto _engines = DependencyManager::get<ScriptEngines>();
_engine = _engines->loadScript(qApp->applicationDirPath() + testRunnerRelativePath);
_engine.reset(_engines->loadScript(qApp->applicationDirPath() + testRunnerRelativePath));
_console->setScriptEngine(_engine);
connect(_engine, &ScriptEngine::finished, this, &TestingDialog::onTestingFinished);
connect(_engine.data(), &ScriptEngine::finished, this, &TestingDialog::onTestingFinished);
}
void TestingDialog::onTestingFinished(const QString& scriptPath) {
_engine = nullptr;
_console->setScriptEngine(nullptr);
_engine.reset();
_console->setScriptEngine();
}

View file

@ -29,7 +29,7 @@ public:
private:
std::unique_ptr<JSConsole> _console;
ScriptEngine* _engine;
QSharedPointer<ScriptEngine> _engine;
};
#endif

View file

@ -0,0 +1,271 @@
//
// ContextOverlayInterface.cpp
// interface/src/ui/overlays
//
// Created by Zach Fox on 2017-07-14.
// Copyright 2017 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 "ContextOverlayInterface.h"
#include "Application.h"
#include <EntityTreeRenderer.h>
static const float CONTEXT_OVERLAY_TABLET_OFFSET = 30.0f; // Degrees
static const float CONTEXT_OVERLAY_TABLET_ORIENTATION = 210.0f; // Degrees
static const float CONTEXT_OVERLAY_TABLET_DISTANCE = 0.65F; // Meters
ContextOverlayInterface::ContextOverlayInterface() {
// "context_overlay" debug log category disabled by default.
// Create your own "qtlogging.ini" file and set your "QT_LOGGING_CONF" environment variable
// if you'd like to enable/disable certain categories.
// More details: http://doc.qt.io/qt-5/qloggingcategory.html#configuring-categories
QLoggingCategory::setFilterRules(QStringLiteral("hifi.context_overlay.debug=false"));
_entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
_hmdScriptingInterface = DependencyManager::get<HMDScriptingInterface>();
_tabletScriptingInterface = DependencyManager::get<TabletScriptingInterface>();
_entityPropertyFlags += PROP_POSITION;
_entityPropertyFlags += PROP_ROTATION;
_entityPropertyFlags += PROP_MARKETPLACE_ID;
_entityPropertyFlags += PROP_DIMENSIONS;
_entityPropertyFlags += PROP_REGISTRATION_POINT;
// initially, set _enabled to match the switch. Later we enable/disable via the getter/setters
// if we are in edit or pal (for instance). Note this is temporary, as we expect to enable this all
// the time after getting edge highlighting, etc...
_enabled = _settingSwitch.get();
auto entityTreeRenderer = DependencyManager::get<EntityTreeRenderer>().data();
connect(entityTreeRenderer, SIGNAL(mousePressOnEntity(const EntityItemID&, const PointerEvent&)), this, SLOT(createOrDestroyContextOverlay(const EntityItemID&, const PointerEvent&)));
connect(entityTreeRenderer, SIGNAL(hoverEnterEntity(const EntityItemID&, const PointerEvent&)), this, SLOT(contextOverlays_hoverEnterEntity(const EntityItemID&, const PointerEvent&)));
connect(entityTreeRenderer, SIGNAL(hoverLeaveEntity(const EntityItemID&, const PointerEvent&)), this, SLOT(contextOverlays_hoverLeaveEntity(const EntityItemID&, const PointerEvent&)));
connect(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"), &TabletProxy::tabletShownChanged, this, [&]() {
if (_contextOverlayJustClicked && _hmdScriptingInterface->isMounted()) {
QUuid tabletFrameID = _hmdScriptingInterface->getCurrentTabletFrameID();
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
glm::quat cameraOrientation = qApp->getCamera().getOrientation();
QVariantMap props;
props.insert("position", vec3toVariant(myAvatar->getEyePosition() + glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_OFFSET, 0.0f))) * (CONTEXT_OVERLAY_TABLET_DISTANCE * (cameraOrientation * Vectors::FRONT))));
props.insert("orientation", quatToVariant(cameraOrientation * glm::quat(glm::radians(glm::vec3(0.0f, CONTEXT_OVERLAY_TABLET_ORIENTATION, 0.0f)))));
qApp->getOverlays().editOverlay(tabletFrameID, props);
_contextOverlayJustClicked = false;
}
});
}
static const uint32_t LEFT_HAND_HW_ID = 1;
static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 };
static const float CONTEXT_OVERLAY_INSIDE_DISTANCE = 1.0f; // in meters
static const float CONTEXT_OVERLAY_CLOSE_DISTANCE = 1.5f; // in meters
static const float CONTEXT_OVERLAY_CLOSE_SIZE = 0.12f; // in meters, same x and y dims
static const float CONTEXT_OVERLAY_FAR_SIZE = 0.08f; // in meters, same x and y dims
static const float CONTEXT_OVERLAY_CLOSE_OFFSET_ANGLE = 20.0f;
static const float CONTEXT_OVERLAY_UNHOVERED_ALPHA = 0.85f;
static const float CONTEXT_OVERLAY_HOVERED_ALPHA = 1.0f;
static const float CONTEXT_OVERLAY_UNHOVERED_PULSEMIN = 0.6f;
static const float CONTEXT_OVERLAY_UNHOVERED_PULSEMAX = 1.0f;
static const float CONTEXT_OVERLAY_UNHOVERED_PULSEPERIOD = 1.0f;
static const float CONTEXT_OVERLAY_UNHOVERED_COLORPULSE = 1.0f;
static const float CONTEXT_OVERLAY_FAR_OFFSET = 0.1f;
void ContextOverlayInterface::setEnabled(bool enabled) {
// only enable/disable if the setting in 'on'. If it is 'off',
// make sure _enabled is always false.
if (_settingSwitch.get()) {
_enabled = enabled;
} else {
_enabled = false;
}
}
bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_enabled && event.getButton() == PointerEvent::SecondaryButton) {
if (contextOverlayFilterPassed(entityItemID)) {
qCDebug(context_overlay) << "Creating Context Overlay on top of entity with ID: " << entityItemID;
// Add all necessary variables to the stack
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags);
glm::vec3 cameraPosition = qApp->getCamera().getPosition();
float distanceFromCameraToEntity = glm::distance(entityProperties.getPosition(), cameraPosition);
glm::vec3 entityDimensions = entityProperties.getDimensions();
glm::vec3 entityPosition = entityProperties.getPosition();
glm::vec3 contextOverlayPosition = entityProperties.getPosition();
glm::vec2 contextOverlayDimensions;
// Update the position of the overlay if the registration point of the entity
// isn't default
if (entityProperties.getRegistrationPoint() != glm::vec3(0.5f)) {
glm::vec3 adjustPos = entityProperties.getRegistrationPoint() - glm::vec3(0.5f);
entityPosition = entityPosition - (entityProperties.getRotation() * (adjustPos * entityProperties.getDimensions()));
}
enableEntityHighlight(entityItemID);
AABox boundingBox = AABox(entityPosition - (entityDimensions / 2.0f), entityDimensions * 2.0f);
// Update the cached Entity Marketplace ID
_entityMarketplaceID = entityProperties.getMarketplaceID();
if (!_currentEntityWithContextOverlay.isNull() && _currentEntityWithContextOverlay != entityItemID) {
disableEntityHighlight(_currentEntityWithContextOverlay);
}
// Update the cached "Current Entity with Context Overlay" variable
setCurrentEntityWithContextOverlay(entityItemID);
// Here, we determine the position and dimensions of the Context Overlay.
if (boundingBox.contains(cameraPosition)) {
// If the camera is inside the box...
// ...position the Context Overlay 1 meter in front of the camera.
contextOverlayPosition = cameraPosition + CONTEXT_OVERLAY_INSIDE_DISTANCE * (qApp->getCamera().getOrientation() * Vectors::FRONT);
contextOverlayDimensions = glm::vec2(CONTEXT_OVERLAY_CLOSE_SIZE, CONTEXT_OVERLAY_CLOSE_SIZE) * glm::distance(contextOverlayPosition, cameraPosition);
} else if (distanceFromCameraToEntity < CONTEXT_OVERLAY_CLOSE_DISTANCE) {
// Else if the entity is too close to the camera...
// ...rotate the Context Overlay to the right of the entity.
// This makes it easy to inspect things you're holding.
float offsetAngle = -CONTEXT_OVERLAY_CLOSE_OFFSET_ANGLE;
if (event.getID() == LEFT_HAND_HW_ID) {
offsetAngle *= -1;
}
contextOverlayPosition = (glm::quat(glm::radians(glm::vec3(0.0f, offsetAngle, 0.0f))) * (entityPosition - cameraPosition)) + cameraPosition;
contextOverlayDimensions = glm::vec2(CONTEXT_OVERLAY_CLOSE_SIZE, CONTEXT_OVERLAY_CLOSE_SIZE) * glm::distance(contextOverlayPosition, cameraPosition);
} else {
// Else, place the Context Overlay some offset away from the entity's bounding
// box in the direction of the camera.
glm::vec3 direction = glm::normalize(entityPosition - cameraPosition);
float distance;
BoxFace face;
glm::vec3 normal;
boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal);
contextOverlayPosition = (cameraPosition + direction * distance) - direction * CONTEXT_OVERLAY_FAR_OFFSET;
contextOverlayDimensions = glm::vec2(CONTEXT_OVERLAY_FAR_SIZE, CONTEXT_OVERLAY_FAR_SIZE) * glm::distance(contextOverlayPosition, cameraPosition);
}
// Finally, setup and draw the Context Overlay
if (_contextOverlayID == UNKNOWN_OVERLAY_ID || !qApp->getOverlays().isAddedOverlay(_contextOverlayID)) {
_contextOverlay = std::make_shared<Image3DOverlay>();
_contextOverlay->setAlpha(CONTEXT_OVERLAY_UNHOVERED_ALPHA);
_contextOverlay->setPulseMin(CONTEXT_OVERLAY_UNHOVERED_PULSEMIN);
_contextOverlay->setPulseMax(CONTEXT_OVERLAY_UNHOVERED_PULSEMAX);
_contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE);
_contextOverlay->setIgnoreRayIntersection(false);
_contextOverlay->setDrawInFront(true);
_contextOverlay->setURL(PathUtils::resourcesPath() + "images/inspect-icon.png");
_contextOverlay->setIsFacingAvatar(true);
_contextOverlayID = qApp->getOverlays().addOverlay(_contextOverlay);
}
_contextOverlay->setPosition(contextOverlayPosition);
_contextOverlay->setDimensions(contextOverlayDimensions);
_contextOverlay->setRotation(entityProperties.getRotation());
_contextOverlay->setVisible(true);
return true;
}
} else {
if (!_currentEntityWithContextOverlay.isNull()) {
return destroyContextOverlay(_currentEntityWithContextOverlay, event);
}
return false;
}
return false;
}
bool ContextOverlayInterface::contextOverlayFilterPassed(const EntityItemID& entityItemID) {
EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(entityItemID, _entityPropertyFlags);
return (entityProperties.getMarketplaceID().length() != 0);
}
bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) {
if (_contextOverlayID != UNKNOWN_OVERLAY_ID) {
qCDebug(context_overlay) << "Destroying Context Overlay on top of entity with ID: " << entityItemID;
disableEntityHighlight(entityItemID);
setCurrentEntityWithContextOverlay(QUuid());
_entityMarketplaceID.clear();
// Destroy the Context Overlay
qApp->getOverlays().deleteOverlay(_contextOverlayID);
_contextOverlay = NULL;
_contextOverlayID = UNKNOWN_OVERLAY_ID;
return true;
}
return false;
}
bool ContextOverlayInterface::destroyContextOverlay(const EntityItemID& entityItemID) {
return ContextOverlayInterface::destroyContextOverlay(entityItemID, PointerEvent());
}
void ContextOverlayInterface::contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) {
if (overlayID == _contextOverlayID && event.getButton() == PointerEvent::PrimaryButton) {
qCDebug(context_overlay) << "Clicked Context Overlay. Entity ID:" << _currentEntityWithContextOverlay << "Overlay ID:" << overlayID;
openMarketplace();
destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent());
_contextOverlayJustClicked = true;
}
}
void ContextOverlayInterface::contextOverlays_hoverEnterOverlay(const OverlayID& overlayID, const PointerEvent& event) {
if (_contextOverlayID != UNKNOWN_OVERLAY_ID && _contextOverlay) {
qCDebug(context_overlay) << "Started hovering over Context Overlay. Overlay ID:" << overlayID;
_contextOverlay->setColor(CONTEXT_OVERLAY_COLOR);
_contextOverlay->setColorPulse(0.0f); // pulse off
_contextOverlay->setPulsePeriod(0.0f); // pulse off
_contextOverlay->setAlpha(CONTEXT_OVERLAY_HOVERED_ALPHA);
}
}
void ContextOverlayInterface::contextOverlays_hoverLeaveOverlay(const OverlayID& overlayID, const PointerEvent& event) {
if (_contextOverlayID != UNKNOWN_OVERLAY_ID && _contextOverlay) {
qCDebug(context_overlay) << "Stopped hovering over Context Overlay. Overlay ID:" << overlayID;
_contextOverlay->setColor(CONTEXT_OVERLAY_COLOR);
_contextOverlay->setColorPulse(CONTEXT_OVERLAY_UNHOVERED_COLORPULSE);
_contextOverlay->setPulsePeriod(CONTEXT_OVERLAY_UNHOVERED_PULSEPERIOD);
_contextOverlay->setAlpha(CONTEXT_OVERLAY_UNHOVERED_ALPHA);
}
}
void ContextOverlayInterface::contextOverlays_hoverEnterEntity(const EntityItemID& entityID, const PointerEvent& event) {
if (contextOverlayFilterPassed(entityID)) {
enableEntityHighlight(entityID);
}
}
void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event) {
if (_currentEntityWithContextOverlay != entityID) {
disableEntityHighlight(entityID);
}
}
static const QString MARKETPLACE_BASE_URL = "https://metaverse.highfidelity.com/marketplace/items/";
void ContextOverlayInterface::openMarketplace() {
// lets open the tablet and go to the current item in
// the marketplace (if the current entity has a
// marketplaceID)
if (!_currentEntityWithContextOverlay.isNull() && _entityMarketplaceID.length() > 0) {
auto tablet = dynamic_cast<TabletProxy*>(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
// construct the url to the marketplace item
QString url = MARKETPLACE_BASE_URL + _entityMarketplaceID;
tablet->gotoWebScreen(url);
_hmdScriptingInterface->openTablet();
_isInMarketplaceInspectionMode = true;
}
}
void ContextOverlayInterface::enableEntityHighlight(const EntityItemID& entityItemID) {
if (!qApp->getEntities()->getTree()->findEntityByEntityItemID(entityItemID)->getShouldHighlight()) {
qCDebug(context_overlay) << "Setting 'shouldHighlight' to 'true' for Entity ID:" << entityItemID;
qApp->getEntities()->getTree()->findEntityByEntityItemID(entityItemID)->setShouldHighlight(true);
}
}
void ContextOverlayInterface::disableEntityHighlight(const EntityItemID& entityItemID) {
if (qApp->getEntities()->getTree()->findEntityByEntityItemID(entityItemID)->getShouldHighlight()) {
qCDebug(context_overlay) << "Setting 'shouldHighlight' to 'false' for Entity ID:" << entityItemID;
qApp->getEntities()->getTree()->findEntityByEntityItemID(entityItemID)->setShouldHighlight(false);
}
}

View file

@ -0,0 +1,85 @@
//
// ContextOverlayInterface.h
// interface/src/ui/overlays
//
// Created by Zach Fox on 2017-07-14.
// Copyright 2017 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
//
#pragma once
#ifndef hifi_ContextOverlayInterface_h
#define hifi_ContextOverlayInterface_h
#include <QtCore/QObject>
#include <QUuid>
#include <DependencyManager.h>
#include <PointerEvent.h>
#include <ui/TabletScriptingInterface.h>
#include "avatar/AvatarManager.h"
#include "EntityScriptingInterface.h"
#include "ui/overlays/Image3DOverlay.h"
#include "ui/overlays/Overlays.h"
#include "scripting/HMDScriptingInterface.h"
#include "EntityTree.h"
#include "ContextOverlayLogging.h"
/**jsdoc
* @namespace ContextOverlay
*/
class ContextOverlayInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(QUuid entityWithContextOverlay READ getCurrentEntityWithContextOverlay WRITE setCurrentEntityWithContextOverlay)
Q_PROPERTY(bool enabled READ getEnabled WRITE setEnabled)
Q_PROPERTY(bool isInMarketplaceInspectionMode READ getIsInMarketplaceInspectionMode WRITE setIsInMarketplaceInspectionMode)
QSharedPointer<EntityScriptingInterface> _entityScriptingInterface;
EntityPropertyFlags _entityPropertyFlags;
QSharedPointer<HMDScriptingInterface> _hmdScriptingInterface;
QSharedPointer<TabletScriptingInterface> _tabletScriptingInterface;
OverlayID _contextOverlayID { UNKNOWN_OVERLAY_ID };
std::shared_ptr<Image3DOverlay> _contextOverlay { nullptr };
public:
ContextOverlayInterface();
Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; }
void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; }
void setEnabled(bool enabled);
bool getEnabled() { return _enabled; }
bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; }
void setIsInMarketplaceInspectionMode(bool mode) { _isInMarketplaceInspectionMode = mode; }
public slots:
bool createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event);
bool destroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event);
bool destroyContextOverlay(const EntityItemID& entityItemID);
void contextOverlays_mousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event);
void contextOverlays_hoverEnterOverlay(const OverlayID& overlayID, const PointerEvent& event);
void contextOverlays_hoverLeaveOverlay(const OverlayID& overlayID, const PointerEvent& event);
void contextOverlays_hoverEnterEntity(const EntityItemID& entityID, const PointerEvent& event);
void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event);
bool contextOverlayFilterPassed(const EntityItemID& entityItemID);
private:
bool _verboseLogging { true };
bool _enabled { true };
QUuid _currentEntityWithContextOverlay{};
QString _entityMarketplaceID;
bool _contextOverlayJustClicked { false };
bool _isInMarketplaceInspectionMode { false };
Setting::Handle<bool> _settingSwitch { "inspectionMode", false };
void openMarketplace();
void enableEntityHighlight(const EntityItemID& entityItemID);
void disableEntityHighlight(const EntityItemID& entityItemID);
};
#endif // hifi_ContextOverlayInterface_h

View file

@ -1,6 +1,6 @@
//
// HoverOverlayLogging.cpp
// libraries/entities/src
// ContextOverlayLogging.cpp
// interface/src/ui/overlays
//
// Created by Zach Fox on 2017-07-17
// Copyright 2017 High Fidelity, Inc.
@ -9,6 +9,6 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "HoverOverlayLogging.h"
#include "ContextOverlayLogging.h"
Q_LOGGING_CATEGORY(hover_overlay, "hifi.hover_overlay")
Q_LOGGING_CATEGORY(context_overlay, "hifi.context_overlay")

View file

@ -1,6 +1,6 @@
//
// HoverOverlayLogging.h
// libraries/entities/src
// ContextOverlayLogging.h
// interface/src/ui/overlays
//
// Created by Zach Fox on 2017-07-17
// Copyright 2017 High Fidelity, Inc.
@ -10,11 +10,11 @@
//
#pragma once
#ifndef hifi_HoverOverlayLogging_h
#define hifi_HoverOverlayLogging_h
#ifndef hifi_ContextOverlayLogging_h
#define hifi_ContextOverlayLogging_h
#include <QLoggingCategory>
Q_DECLARE_LOGGING_CATEGORY(hover_overlay)
Q_DECLARE_LOGGING_CATEGORY(context_overlay)
#endif // hifi_HoverOverlayLogging_h
#endif // hifi_ContextOverlayLogging_h

View file

@ -20,7 +20,7 @@ Overlay::Overlay() :
_renderItemID(render::Item::INVALID_ITEM_ID),
_isLoaded(true),
_alpha(DEFAULT_ALPHA),
_pulse(0.0f),
_pulse(1.0f),
_pulseMax(0.0f),
_pulseMin(0.0f),
_pulsePeriod(1.0f),

View file

@ -116,7 +116,7 @@ void Overlays::renderHUD(RenderArgs* renderArgs) {
QMutexLocker locker(&_mutex);
foreach(Overlay::Pointer thisOverlay, _overlaysHUD) {
// Reset all batch pipeline settings between overlay
geometryCache->useSimpleDrawPipeline(batch);
batch.setResourceTexture(0, textureCache->getWhiteTexture()); // FIXME - do we really need to do this??
@ -136,7 +136,7 @@ void Overlays::enable() {
_enabled = true;
}
// Note, can't be invoked by scripts, but can be called by the InterfaceParentFinder
// Note, can't be invoked by scripts, but can be called by the InterfaceParentFinder
// class on packet processing threads
Overlay::Pointer Overlays::getOverlay(OverlayID id) const {
QMutexLocker locker(&_mutex);
@ -244,8 +244,8 @@ OverlayID Overlays::cloneOverlay(OverlayID id) {
bool Overlays::editOverlay(OverlayID id, const QVariant& properties) {
if (QThread::currentThread() != thread()) {
// NOTE editOverlay can be called very frequently in scripts and can't afford to
// block waiting on the main thread. Additionally, no script actually
// NOTE editOverlay can be called very frequently in scripts and can't afford to
// block waiting on the main thread. Additionally, no script actually
// examines the return value and does something useful with it, so use a non-blocking
// invoke and just always return true
QMetaObject::invokeMethod(this, "editOverlay", Q_ARG(OverlayID, id), Q_ARG(QVariant, properties));
@ -705,27 +705,27 @@ bool Overlays::isAddedOverlay(OverlayID id) {
return _overlaysHUD.contains(id) || _overlaysWorld.contains(id);
}
void Overlays::sendMousePressOnOverlay(OverlayID overlayID, const PointerEvent& event) {
void Overlays::sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event) {
QMetaObject::invokeMethod(this, "mousePressOnOverlay", Q_ARG(OverlayID, overlayID), Q_ARG(PointerEvent, event));
}
void Overlays::sendMouseReleaseOnOverlay(OverlayID overlayID, const PointerEvent& event) {
void Overlays::sendMouseReleaseOnOverlay(const OverlayID& overlayID, const PointerEvent& event) {
QMetaObject::invokeMethod(this, "mouseReleaseOnOverlay", Q_ARG(OverlayID, overlayID), Q_ARG(PointerEvent, event));
}
void Overlays::sendMouseMoveOnOverlay(OverlayID overlayID, const PointerEvent& event) {
void Overlays::sendMouseMoveOnOverlay(const OverlayID& overlayID, const PointerEvent& event) {
QMetaObject::invokeMethod(this, "mouseMoveOnOverlay", Q_ARG(OverlayID, overlayID), Q_ARG(PointerEvent, event));
}
void Overlays::sendHoverEnterOverlay(OverlayID id, PointerEvent event) {
void Overlays::sendHoverEnterOverlay(const OverlayID& id, const PointerEvent& event) {
QMetaObject::invokeMethod(this, "hoverEnterOverlay", Q_ARG(OverlayID, id), Q_ARG(PointerEvent, event));
}
void Overlays::sendHoverOverOverlay(OverlayID id, PointerEvent event) {
void Overlays::sendHoverOverOverlay(const OverlayID& id, const PointerEvent& event) {
QMetaObject::invokeMethod(this, "hoverOverOverlay", Q_ARG(OverlayID, id), Q_ARG(PointerEvent, event));
}
void Overlays::sendHoverLeaveOverlay(OverlayID id, PointerEvent event) {
void Overlays::sendHoverLeaveOverlay(const OverlayID& id, const PointerEvent& event) {
QMetaObject::invokeMethod(this, "hoverLeaveOverlay", Q_ARG(OverlayID, id), Q_ARG(PointerEvent, event));
}
@ -775,7 +775,7 @@ float Overlays::height() {
static const uint32_t MOUSE_POINTER_ID = 0;
static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay,
static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay,
const RayToOverlayIntersectionResult& rayPickResult) {
// Project the intersection point onto the local xy plane of the overlay.
@ -818,15 +818,20 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) {
}
}
PointerEvent Overlays::calculatePointerEvent(Overlay::Pointer overlay, PickRay ray,
PointerEvent Overlays::calculateOverlayPointerEvent(OverlayID overlayID, PickRay ray,
RayToOverlayIntersectionResult rayPickResult, QMouseEvent* event,
PointerEvent::EventType eventType) {
auto overlay = std::dynamic_pointer_cast<Planar3DOverlay>(getOverlay(overlayID));
if (getOverlayType(overlayID) == "web3d") {
overlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(overlayID));
}
if (!overlay) {
return PointerEvent();
}
glm::vec3 position = overlay->getPosition();
glm::quat rotation = overlay->getRotation();
glm::vec2 dimensions = overlay->getSize();
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(overlay);
auto position = thisOverlay->getPosition();
auto rotation = thisOverlay->getRotation();
auto dimensions = thisOverlay->getSize();
glm::vec2 pos2D = projectOntoOverlayXYPlane(position, rotation, dimensions, ray, rayPickResult);
@ -874,13 +879,9 @@ bool Overlays::mousePressEvent(QMouseEvent* event) {
if (rayPickResult.intersects) {
_currentClickingOnOverlayID = rayPickResult.overlayID;
// Only Web overlays can have focus.
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(_currentClickingOnOverlayID));
if (thisOverlay) {
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Press);
emit mousePressOnOverlay(_currentClickingOnOverlayID, pointerEvent);
return true;
}
PointerEvent pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press);
emit mousePressOnOverlay(_currentClickingOnOverlayID, pointerEvent);
return true;
}
emit mousePressOffOverlay();
return false;
@ -894,13 +895,9 @@ bool Overlays::mouseDoublePressEvent(QMouseEvent* event) {
if (rayPickResult.intersects) {
_currentClickingOnOverlayID = rayPickResult.overlayID;
// Only Web overlays can have focus.
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(_currentClickingOnOverlayID));
if (thisOverlay) {
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Press);
emit mouseDoublePressOnOverlay(_currentClickingOnOverlayID, pointerEvent);
return true;
}
auto pointerEvent = calculateOverlayPointerEvent(_currentClickingOnOverlayID, ray, rayPickResult, event, PointerEvent::Press);
emit mouseDoublePressOnOverlay(_currentClickingOnOverlayID, pointerEvent);
return true;
}
emit mouseDoublePressOffOverlay();
return false;
@ -912,13 +909,8 @@ bool Overlays::mouseReleaseEvent(QMouseEvent* event) {
PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray);
if (rayPickResult.intersects) {
// Only Web overlays can have focus.
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(rayPickResult.overlayID));
if (thisOverlay) {
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Release);
emit mouseReleaseOnOverlay(rayPickResult.overlayID, pointerEvent);
}
auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Release);
emit mouseReleaseOnOverlay(rayPickResult.overlayID, pointerEvent);
}
_currentClickingOnOverlayID = UNKNOWN_OVERLAY_ID;
@ -931,40 +923,29 @@ bool Overlays::mouseMoveEvent(QMouseEvent* event) {
PickRay ray = qApp->computePickRay(event->x(), event->y());
RayToOverlayIntersectionResult rayPickResult = findRayIntersectionForMouseEvent(ray);
if (rayPickResult.intersects) {
auto pointerEvent = calculateOverlayPointerEvent(rayPickResult.overlayID, ray, rayPickResult, event, PointerEvent::Move);
emit mouseMoveOnOverlay(rayPickResult.overlayID, pointerEvent);
// Only Web overlays can have focus.
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(rayPickResult.overlayID));
if (thisOverlay) {
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Move);
emit mouseMoveOnOverlay(rayPickResult.overlayID, pointerEvent);
// If previously hovering over a different overlay then leave hover on that overlay.
if (_currentHoverOverOverlayID != UNKNOWN_OVERLAY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) {
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(_currentHoverOverOverlayID));
if (thisOverlay) {
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Move);
emit hoverLeaveOverlay(_currentHoverOverOverlayID, pointerEvent);
}
}
// If hovering over a new overlay then enter hover on that overlay.
if (rayPickResult.overlayID != _currentHoverOverOverlayID) {
emit hoverEnterOverlay(rayPickResult.overlayID, pointerEvent);
}
// Hover over current overlay.
emit hoverOverOverlay(rayPickResult.overlayID, pointerEvent);
_currentHoverOverOverlayID = rayPickResult.overlayID;
// If previously hovering over a different overlay then leave hover on that overlay.
if (_currentHoverOverOverlayID != UNKNOWN_OVERLAY_ID && rayPickResult.overlayID != _currentHoverOverOverlayID) {
auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move);
emit hoverLeaveOverlay(_currentHoverOverOverlayID, pointerEvent);
}
// If hovering over a new overlay then enter hover on that overlay.
if (rayPickResult.overlayID != _currentHoverOverOverlayID) {
emit hoverEnterOverlay(rayPickResult.overlayID, pointerEvent);
}
// Hover over current overlay.
emit hoverOverOverlay(rayPickResult.overlayID, pointerEvent);
_currentHoverOverOverlayID = rayPickResult.overlayID;
} else {
// If previously hovering an overlay then leave hover.
if (_currentHoverOverOverlayID != UNKNOWN_OVERLAY_ID) {
auto thisOverlay = std::dynamic_pointer_cast<Web3DOverlay>(getOverlay(_currentHoverOverOverlayID));
if (thisOverlay) {
auto pointerEvent = calculatePointerEvent(thisOverlay, ray, rayPickResult, event, PointerEvent::Move);
emit hoverLeaveOverlay(_currentHoverOverOverlayID, pointerEvent);
}
auto pointerEvent = calculateOverlayPointerEvent(_currentHoverOverOverlayID, ray, rayPickResult, event, PointerEvent::Move);
emit hoverLeaveOverlay(_currentHoverOverOverlayID, pointerEvent);
_currentHoverOverOverlayID = UNKNOWN_OVERLAY_ID;
}

View file

@ -131,7 +131,7 @@ public slots:
OverlayID cloneOverlay(OverlayID id);
/**jsdoc
* Edit an overlay's properties.
* Edit an overlay's properties.
*
* @function Overlays.editOverlay
* @param {Overlays.OverlayID} overlayID The ID of the overlay to edit.
@ -288,13 +288,13 @@ public slots:
#endif
void sendMousePressOnOverlay(OverlayID overlayID, const PointerEvent& event);
void sendMouseReleaseOnOverlay(OverlayID overlayID, const PointerEvent& event);
void sendMouseMoveOnOverlay(OverlayID overlayID, const PointerEvent& event);
void sendMousePressOnOverlay(const OverlayID& overlayID, const PointerEvent& event);
void sendMouseReleaseOnOverlay(const OverlayID& overlayID, const PointerEvent& event);
void sendMouseMoveOnOverlay(const OverlayID& overlayID, const PointerEvent& event);
void sendHoverEnterOverlay(OverlayID id, PointerEvent event);
void sendHoverOverOverlay(OverlayID id, PointerEvent event);
void sendHoverLeaveOverlay(OverlayID id, PointerEvent event);
void sendHoverEnterOverlay(const OverlayID& id, const PointerEvent& event);
void sendHoverOverOverlay(const OverlayID& id, const PointerEvent& event);
void sendHoverLeaveOverlay(const OverlayID& id, const PointerEvent& event);
OverlayID getKeyboardFocusOverlay();
void setKeyboardFocusOverlay(OverlayID id);
@ -337,7 +337,7 @@ private:
#endif
bool _enabled = true;
PointerEvent calculatePointerEvent(Overlay::Pointer overlay, PickRay ray, RayToOverlayIntersectionResult rayPickResult,
PointerEvent calculateOverlayPointerEvent(OverlayID overlayID, PickRay ray, RayToOverlayIntersectionResult rayPickResult,
QMouseEvent* event, PointerEvent::EventType eventType);
OverlayID _currentClickingOnOverlayID { UNKNOWN_OVERLAY_ID };

View file

@ -21,6 +21,7 @@ public:
Planar3DOverlay(const Planar3DOverlay* planar3DOverlay);
virtual AABox getBounds() const override;
virtual glm::vec2 getSize() const { return _dimensions; };
glm::vec2 getDimensions() const { return _dimensions; }
void setDimensions(float value) { _dimensions = glm::vec2(value); }

View file

@ -597,7 +597,7 @@ void Web3DOverlay::setScriptURL(const QString& scriptURL) {
}
}
glm::vec2 Web3DOverlay::getSize() {
glm::vec2 Web3DOverlay::getSize() const {
return _resolution / _dpi * INCHES_TO_METERS * getDimensions();
};

View file

@ -50,7 +50,7 @@ public:
void setProperties(const QVariantMap& properties) override;
QVariant getProperty(const QString& property) override;
glm::vec2 getSize();
glm::vec2 getSize() const override;
virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance,
BoxFace& face, glm::vec3& surfaceNormal) override;

View file

@ -364,7 +364,7 @@ private:
AudioIOStats _stats;
AudioGate* _audioGate { nullptr };
bool _audioGateOpen { false };
bool _audioGateOpen { true };
AudioPositionGetter _positionGetter;
AudioOrientationGetter _orientationGetter;

View file

@ -101,6 +101,23 @@ namespace controller {
makePosePair(Action::RIGHT_HAND_PINKY3, "RightHandPinky3"),
makePosePair(Action::RIGHT_HAND_PINKY4, "RightHandPinky4"),
makePosePair(Action::TRACKED_OBJECT_00, "TrackedObject00"),
makePosePair(Action::TRACKED_OBJECT_01, "TrackedObject01"),
makePosePair(Action::TRACKED_OBJECT_02, "TrackedObject02"),
makePosePair(Action::TRACKED_OBJECT_03, "TrackedObject03"),
makePosePair(Action::TRACKED_OBJECT_04, "TrackedObject04"),
makePosePair(Action::TRACKED_OBJECT_05, "TrackedObject05"),
makePosePair(Action::TRACKED_OBJECT_06, "TrackedObject06"),
makePosePair(Action::TRACKED_OBJECT_07, "TrackedObject07"),
makePosePair(Action::TRACKED_OBJECT_08, "TrackedObject08"),
makePosePair(Action::TRACKED_OBJECT_09, "TrackedObject09"),
makePosePair(Action::TRACKED_OBJECT_10, "TrackedObject10"),
makePosePair(Action::TRACKED_OBJECT_11, "TrackedObject11"),
makePosePair(Action::TRACKED_OBJECT_12, "TrackedObject12"),
makePosePair(Action::TRACKED_OBJECT_13, "TrackedObject13"),
makePosePair(Action::TRACKED_OBJECT_14, "TrackedObject14"),
makePosePair(Action::TRACKED_OBJECT_15, "TrackedObject15"),
makeButtonPair(Action::LEFT_HAND_CLICK, "LeftHandClick"),
makeButtonPair(Action::RIGHT_HAND_CLICK, "RightHandClick"),

View file

@ -146,6 +146,23 @@ enum class Action {
RIGHT_HAND_PINKY3,
RIGHT_HAND_PINKY4,
TRACKED_OBJECT_00,
TRACKED_OBJECT_01,
TRACKED_OBJECT_02,
TRACKED_OBJECT_03,
TRACKED_OBJECT_04,
TRACKED_OBJECT_05,
TRACKED_OBJECT_06,
TRACKED_OBJECT_07,
TRACKED_OBJECT_08,
TRACKED_OBJECT_09,
TRACKED_OBJECT_10,
TRACKED_OBJECT_11,
TRACKED_OBJECT_12,
TRACKED_OBJECT_13,
TRACKED_OBJECT_14,
TRACKED_OBJECT_15,
NUM_ACTIONS,
};

View file

@ -166,6 +166,23 @@ Input::NamedVector StandardController::getAvailableInputs() const {
makePair(DD, "Down"),
makePair(DL, "Left"),
makePair(DR, "Right"),
makePair(TRACKED_OBJECT_00, "TrackedObject00"),
makePair(TRACKED_OBJECT_01, "TrackedObject01"),
makePair(TRACKED_OBJECT_02, "TrackedObject02"),
makePair(TRACKED_OBJECT_03, "TrackedObject03"),
makePair(TRACKED_OBJECT_04, "TrackedObject04"),
makePair(TRACKED_OBJECT_05, "TrackedObject05"),
makePair(TRACKED_OBJECT_06, "TrackedObject06"),
makePair(TRACKED_OBJECT_07, "TrackedObject07"),
makePair(TRACKED_OBJECT_08, "TrackedObject08"),
makePair(TRACKED_OBJECT_09, "TrackedObject09"),
makePair(TRACKED_OBJECT_10, "TrackedObject10"),
makePair(TRACKED_OBJECT_11, "TrackedObject11"),
makePair(TRACKED_OBJECT_12, "TrackedObject12"),
makePair(TRACKED_OBJECT_13, "TrackedObject13"),
makePair(TRACKED_OBJECT_14, "TrackedObject14"),
makePair(TRACKED_OBJECT_15, "TrackedObject15")
};
return availableInputs;
}

View file

@ -26,7 +26,6 @@
#include <PerfStat.h>
#include <SceneScriptingInterface.h>
#include <ScriptEngine.h>
#include <HoverOverlayInterface.h>
#include "RenderableEntityItem.h"
@ -453,8 +452,6 @@ RayToEntityIntersectionResult EntityTreeRenderer::findRayIntersectionWorker(cons
void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityScriptingInterface) {
auto hoverOverlayInterface = DependencyManager::get<HoverOverlayInterface>().data();
connect(this, &EntityTreeRenderer::mousePressOnEntity, entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity);
connect(this, &EntityTreeRenderer::mouseMoveOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseMoveOnEntity);
connect(this, &EntityTreeRenderer::mouseReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::mouseReleaseOnEntity);
@ -464,12 +461,8 @@ void EntityTreeRenderer::connectSignalsToSlots(EntityScriptingInterface* entityS
connect(this, &EntityTreeRenderer::clickReleaseOnEntity, entityScriptingInterface, &EntityScriptingInterface::clickReleaseOnEntity);
connect(this, &EntityTreeRenderer::hoverEnterEntity, entityScriptingInterface, &EntityScriptingInterface::hoverEnterEntity);
connect(this, SIGNAL(hoverEnterEntity(const EntityItemID&, const PointerEvent&)), hoverOverlayInterface, SLOT(createHoverOverlay(const EntityItemID&, const PointerEvent&)));
connect(this, &EntityTreeRenderer::hoverOverEntity, entityScriptingInterface, &EntityScriptingInterface::hoverOverEntity);
connect(this, &EntityTreeRenderer::hoverLeaveEntity, entityScriptingInterface, &EntityScriptingInterface::hoverLeaveEntity);
connect(this, SIGNAL(hoverLeaveEntity(const EntityItemID&, const PointerEvent&)), hoverOverlayInterface, SLOT(destroyHoverOverlay(const EntityItemID&, const PointerEvent&)));
connect(this, &EntityTreeRenderer::enterEntity, entityScriptingInterface, &EntityScriptingInterface::enterEntity);
connect(this, &EntityTreeRenderer::leaveEntity, entityScriptingInterface, &EntityScriptingInterface::leaveEntity);
@ -682,7 +675,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) {
PickRay ray = _viewState->computePickRay(event->x(), event->y());
bool precisionPicking = false; // for mouse moves we do not do precision picking
bool precisionPicking = true; // for mouse moves we do precision picking
RayToEntityIntersectionResult rayPickResult = findRayIntersectionWorker(ray, Octree::TryLock, precisionPicking);
if (rayPickResult.intersects) {

View file

@ -372,6 +372,21 @@ void RenderableModelEntityItem::render(RenderArgs* args) {
_model->updateRenderItems();
}
// this simple logic should say we set showingEntityHighlight to true whenever we are in marketplace mode and we have a marketplace id, or
// whenever we are not set to none and shouldHighlight is true.
bool showingEntityHighlight = ((bool)(args->_outlineFlags & (int)RenderArgs::RENDER_OUTLINE_MARKETPLACE_MODE) && getMarketplaceID().length() != 0) ||
(args->_outlineFlags != RenderArgs::RENDER_OUTLINE_NONE && getShouldHighlight());
if (showingEntityHighlight) {
static glm::vec4 yellowColor(1.0f, 1.0f, 0.0f, 1.0f);
gpu::Batch& batch = *args->_batch;
bool success;
auto shapeTransform = getTransformToCenter(success);
if (success) {
batch.setModelTransform(shapeTransform); // we want to include the scale as well
DependencyManager::get<GeometryCache>()->renderWireCubeInstance(args, batch, yellowColor);
}
}
if (!hasModel() || (_model && _model->didVisualGeometryRequestFail())) {
static glm::vec4 greenColor(0.0f, 1.0f, 0.0f, 1.0f);
gpu::Batch& batch = *args->_batch;

View file

@ -69,7 +69,7 @@ RenderableWebEntityItem::~RenderableWebEntityItem() {
}
}
bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer> renderer) {
bool RenderableWebEntityItem::buildWebSurface() {
if (_currentWebCount >= MAX_CONCURRENT_WEB_VIEWS) {
qWarning() << "Too many concurrent web views to create new view";
return false;
@ -132,6 +132,8 @@ bool RenderableWebEntityItem::buildWebSurface(QSharedPointer<EntityTreeRenderer>
handlePointerEvent(event);
}
};
auto renderer = DependencyManager::get<EntityTreeRenderer>();
_mousePressConnection = QObject::connect(renderer.data(), &EntityTreeRenderer::mousePressOnEntity, forwardPointerEvent);
_mouseReleaseConnection = QObject::connect(renderer.data(), &EntityTreeRenderer::mouseReleaseOnEntity, forwardPointerEvent);
_mouseMoveConnection = QObject::connect(renderer.data(), &EntityTreeRenderer::mouseMoveOnEntity, forwardPointerEvent);
@ -185,8 +187,7 @@ void RenderableWebEntityItem::render(RenderArgs* args) {
#endif
if (!_webSurface) {
auto renderer = qSharedPointerCast<EntityTreeRenderer>(args->_renderData);
if (!buildWebSurface(renderer)) {
if (!buildWebSurface()) {
return;
}
_fadeStartTime = usecTimestampNow();

View file

@ -57,7 +57,7 @@ public:
virtual QObject* getRootItem() override;
private:
bool buildWebSurface(QSharedPointer<EntityTreeRenderer> renderer);
bool buildWebSurface();
void destroyWebSurface();
glm::vec2 getWindowSize() const;

View file

@ -133,6 +133,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param
requestedProperties += PROP_LOCKED;
requestedProperties += PROP_USER_DATA;
requestedProperties += PROP_MARKETPLACE_ID;
requestedProperties += PROP_SHOULD_HIGHLIGHT;
requestedProperties += PROP_NAME;
requestedProperties += PROP_HREF;
requestedProperties += PROP_DESCRIPTION;
@ -278,6 +279,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet
APPEND_ENTITY_PROPERTY(PROP_LOCKED, getLocked());
APPEND_ENTITY_PROPERTY(PROP_USER_DATA, getUserData());
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, getMarketplaceID());
APPEND_ENTITY_PROPERTY(PROP_SHOULD_HIGHLIGHT, getShouldHighlight());
APPEND_ENTITY_PROPERTY(PROP_NAME, getName());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, getCollisionSoundURL());
APPEND_ENTITY_PROPERTY(PROP_HREF, getHref());
@ -829,6 +831,10 @@ int EntityItem::readEntityDataFromBuffer(const unsigned char* data, int bytesLef
READ_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
}
if (args.bitstreamVersion >= VERSION_ENTITIES_HAS_SHOULD_HIGHLIGHT) {
READ_ENTITY_PROPERTY(PROP_SHOULD_HIGHLIGHT, bool, setShouldHighlight);
}
READ_ENTITY_PROPERTY(PROP_NAME, QString, setName);
READ_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
READ_ENTITY_PROPERTY(PROP_HREF, QString, setHref);
@ -2803,6 +2809,20 @@ void EntityItem::setMarketplaceID(const QString& value) {
});
}
bool EntityItem::getShouldHighlight() const {
bool result;
withReadLock([&] {
result = _shouldHighlight;
});
return result;
}
void EntityItem::setShouldHighlight(const bool value) {
withWriteLock([&] {
_shouldHighlight = value;
});
}
uint32_t EntityItem::getDirtyFlags() const {
uint32_t result;
withReadLock([&] {

View file

@ -316,6 +316,9 @@ public:
QString getMarketplaceID() const;
void setMarketplaceID(const QString& value);
bool getShouldHighlight() const;
void setShouldHighlight(const bool value);
// TODO: get rid of users of getRadius()...
float getRadius() const;
@ -532,6 +535,7 @@ protected:
QString _userData;
SimulationOwner _simulationOwner;
QString _marketplaceID;
bool _shouldHighlight { false };
QString _name;
QString _href; //Hyperlink href
QString _description; //Hyperlink description

View file

@ -289,6 +289,7 @@ EntityPropertyFlags EntityItemProperties::getChangedProperties() const {
CHECK_PROPERTY_CHANGE(PROP_RADIUS_START, radiusStart);
CHECK_PROPERTY_CHANGE(PROP_RADIUS_FINISH, radiusFinish);
CHECK_PROPERTY_CHANGE(PROP_MARKETPLACE_ID, marketplaceID);
CHECK_PROPERTY_CHANGE(PROP_SHOULD_HIGHLIGHT, shouldHighlight);
CHECK_PROPERTY_CHANGE(PROP_NAME, name);
CHECK_PROPERTY_CHANGE(PROP_BACKGROUND_MODE, backgroundMode);
CHECK_PROPERTY_CHANGE(PROP_SOURCE_URL, sourceUrl);
@ -406,6 +407,7 @@ QScriptValue EntityItemProperties::copyToScriptValue(QScriptEngine* engine, bool
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_LOCKED, locked);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_USER_DATA, userData);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_MARKETPLACE_ID, marketplaceID);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_SHOULD_HIGHLIGHT, shouldHighlight);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_NAME, name);
COPY_PROPERTY_TO_QSCRIPTVALUE(PROP_COLLISION_SOUND_URL, collisionSoundURL);
@ -982,6 +984,7 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue
ADD_PROPERTY_TO_MAP(PROP_RADIUS_START, RadiusStart, radiusStart, float);
ADD_PROPERTY_TO_MAP(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float);
ADD_PROPERTY_TO_MAP(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString);
ADD_PROPERTY_TO_MAP(PROP_SHOULD_HIGHLIGHT, ShouldHighlight, shouldHighlight, bool);
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_COLOR, KeyLightColor, keyLightColor, xColor);
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_INTENSITY, KeyLightIntensity, keyLightIntensity, float);
ADD_PROPERTY_TO_MAP(PROP_KEYLIGHT_AMBIENT_INTENSITY, KeyLightAmbientIntensity, keyLightAmbientIntensity, float);
@ -1334,6 +1337,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem
APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape());
}
APPEND_ENTITY_PROPERTY(PROP_MARKETPLACE_ID, properties.getMarketplaceID());
APPEND_ENTITY_PROPERTY(PROP_SHOULD_HIGHLIGHT, properties.getShouldHighlight());
APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName());
APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL());
APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData());
@ -1632,6 +1636,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int
}
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_MARKETPLACE_ID, QString, setMarketplaceID);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SHOULD_HIGHLIGHT, bool, setShouldHighlight);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_NAME, QString, setName);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_COLLISION_SOUND_URL, QString, setCollisionSoundURL);
READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ACTION_DATA, QByteArray, setActionData);
@ -1746,6 +1751,7 @@ void EntityItemProperties::markAllChanged() {
//_alphaFinishChanged = true;
_marketplaceIDChanged = true;
_shouldHighlightChanged = true;
_keyLight.markAllChanged();

View file

@ -171,6 +171,7 @@ public:
DEFINE_PROPERTY(PROP_RADIUS_FINISH, RadiusFinish, radiusFinish, float, ParticleEffectEntityItem::DEFAULT_RADIUS_FINISH);
DEFINE_PROPERTY(PROP_EMITTER_SHOULD_TRAIL, EmitterShouldTrail, emitterShouldTrail, bool, ParticleEffectEntityItem::DEFAULT_EMITTER_SHOULD_TRAIL);
DEFINE_PROPERTY_REF(PROP_MARKETPLACE_ID, MarketplaceID, marketplaceID, QString, ENTITY_ITEM_DEFAULT_MARKETPLACE_ID);
DEFINE_PROPERTY_REF(PROP_SHOULD_HIGHLIGHT, ShouldHighlight, shouldHighlight, bool, ENTITY_ITEM_DEFAULT_SHOULD_HIGHLIGHT);
DEFINE_PROPERTY_GROUP(KeyLight, keyLight, KeyLightPropertyGroup);
DEFINE_PROPERTY_REF(PROP_VOXEL_VOLUME_SIZE, VoxelVolumeSize, voxelVolumeSize, glm::vec3, PolyVoxEntityItem::DEFAULT_VOXEL_VOLUME_SIZE);
DEFINE_PROPERTY_REF(PROP_VOXEL_DATA, VoxelData, voxelData, QByteArray, PolyVoxEntityItem::DEFAULT_VOXEL_DATA);

View file

@ -27,6 +27,7 @@ const glm::vec3 ENTITY_ITEM_HALF_VEC3 = glm::vec3(0.5f);
const bool ENTITY_ITEM_DEFAULT_LOCKED = false;
const QString ENTITY_ITEM_DEFAULT_USER_DATA = QString("");
const QString ENTITY_ITEM_DEFAULT_MARKETPLACE_ID = QString("");
const bool ENTITY_ITEM_DEFAULT_SHOULD_HIGHLIGHT = false;
const QUuid ENTITY_ITEM_DEFAULT_SIMULATOR_ID = QUuid();
const float ENTITY_ITEM_DEFAULT_ALPHA = 1.0f;

View file

@ -78,6 +78,7 @@ enum EntityPropertyList {
PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities
PROP_MARKETPLACE_ID, // all entities
PROP_SHOULD_HIGHLIGHT, // all entities
PROP_ACCELERATION, // all entities
PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID
PROP_NAME, // all entities

View file

@ -1,38 +0,0 @@
//
// HoverOverlayInterface.cpp
// libraries/entities/src
//
// Created by Zach Fox on 2017-07-14.
// Copyright 2017 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 "HoverOverlayInterface.h"
HoverOverlayInterface::HoverOverlayInterface() {
// "hover_overlay" debug log category disabled by default.
// Create your own "qtlogging.ini" file and set your "QT_LOGGING_CONF" environment variable
// if you'd like to enable/disable certain categories.
// More details: http://doc.qt.io/qt-5/qloggingcategory.html#configuring-categories
QLoggingCategory::setFilterRules(QStringLiteral("hifi.hover_overlay.debug=false"));
}
void HoverOverlayInterface::createHoverOverlay(const EntityItemID& entityItemID, const PointerEvent& event) {
qCDebug(hover_overlay) << "Creating Hover Overlay on top of entity with ID: " << entityItemID;
setCurrentHoveredEntity(entityItemID);
}
void HoverOverlayInterface::createHoverOverlay(const EntityItemID& entityItemID) {
HoverOverlayInterface::createHoverOverlay(entityItemID, PointerEvent());
}
void HoverOverlayInterface::destroyHoverOverlay(const EntityItemID& entityItemID, const PointerEvent& event) {
qCDebug(hover_overlay) << "Destroying Hover Overlay on top of entity with ID: " << entityItemID;
setCurrentHoveredEntity(QUuid());
}
void HoverOverlayInterface::destroyHoverOverlay(const EntityItemID& entityItemID) {
HoverOverlayInterface::destroyHoverOverlay(entityItemID, PointerEvent());
}

View file

@ -1,49 +0,0 @@
//
// HoverOverlayInterface.h
// libraries/entities/src
//
// Created by Zach Fox on 2017-07-14.
// Copyright 2017 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
//
#pragma once
#ifndef hifi_HoverOverlayInterface_h
#define hifi_HoverOverlayInterface_h
#include <QtCore/QObject>
#include <QUuid>
#include <DependencyManager.h>
#include <PointerEvent.h>
#include "EntityTree.h"
#include "HoverOverlayLogging.h"
/**jsdoc
* @namespace HoverOverlay
*/
class HoverOverlayInterface : public QObject, public Dependency {
Q_OBJECT
Q_PROPERTY(QUuid currentHoveredEntity READ getCurrentHoveredEntity WRITE setCurrentHoveredEntity)
public:
HoverOverlayInterface();
Q_INVOKABLE QUuid getCurrentHoveredEntity() { return _currentHoveredEntity; }
void setCurrentHoveredEntity(const QUuid& entityID) { _currentHoveredEntity = entityID; }
public slots:
void createHoverOverlay(const EntityItemID& entityItemID, const PointerEvent& event);
void createHoverOverlay(const EntityItemID& entityItemID);
void destroyHoverOverlay(const EntityItemID& entityItemID, const PointerEvent& event);
void destroyHoverOverlay(const EntityItemID& entityItemID);
private:
bool _verboseLogging { true };
QUuid _currentHoveredEntity{};
};
#endif // hifi_HoverOverlayInterface_h

View file

@ -236,6 +236,7 @@ controller::Input::NamedVector KeyboardMouseDevice::InputDevice::getAvailableInp
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Shift), "Shift"));
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageUp), QKeySequence(Qt::Key_PageUp).toString()));
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_PageDown), QKeySequence(Qt::Key_PageDown).toString()));
availableInputs.append(Input::NamedPair(makeInput(Qt::Key_Tab), QKeySequence(Qt::Key_Tab).toString()));
availableInputs.append(Input::NamedPair(makeInput(Qt::LeftButton), "LeftMouseButton"));
availableInputs.append(Input::NamedPair(makeInput(Qt::MiddleButton), "MiddleMouseButton"));

View file

@ -62,7 +62,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
case PacketType::EntityEdit:
case PacketType::EntityData:
case PacketType::EntityPhysics:
return VERSION_ENTITIES_BULLET_DYNAMICS;
return VERSION_ENTITIES_HAS_SHOULD_HIGHLIGHT;
case PacketType::EntityQuery:
return static_cast<PacketVersion>(EntityQueryPacketVersion::JSONFilterWithFamilyTree);
case PacketType::AvatarIdentity:

View file

@ -218,6 +218,7 @@ const PacketVersion VERSION_ENTITIES_PHYSICS_PACKET = 67;
const PacketVersion VERSION_ENTITIES_ZONE_FILTERS = 68;
const PacketVersion VERSION_ENTITIES_HINGE_CONSTRAINT = 69;
const PacketVersion VERSION_ENTITIES_BULLET_DYNAMICS = 70;
const PacketVersion VERSION_ENTITIES_HAS_SHOULD_HIGHLIGHT = 71;
enum class EntityQueryPacketVersion: PacketVersion {
JSONFilter = 18,

View file

@ -18,6 +18,19 @@ void DisplayPlugin::incrementPresentCount() {
++_presentedFrameIndex;
// Alert the app that it needs to paint a new presentation frame
qApp->postEvent(qApp, new QEvent(static_cast<QEvent::Type>(Present)), Qt::HighEventPriority);
{
QMutexLocker locker(&_presentMutex);
_presentCondition.wakeAll();
}
emit presented(_presentedFrameIndex);
}
void DisplayPlugin::waitForPresent() {
QMutexLocker locker(&_presentMutex);
while (isActive()) {
if (_presentCondition.wait(&_presentMutex, MSECS_PER_SECOND)) {
break;
}
}
}

View file

@ -17,6 +17,8 @@
#include <QtCore/QPoint>
#include <QtCore/QElapsedTimer>
#include <QtCore/QJsonObject>
#include <QtCore/QMutex>
#include <QtCore/QWaitCondition>
#include <GLMHelpers.h>
#include <RegisteredMetaTypes.h>
@ -134,10 +136,6 @@ class DisplayPlugin : public Plugin, public HmdDisplay {
Q_OBJECT
using Parent = Plugin;
public:
enum Event {
Present = QEvent::User + 1
};
virtual int getRequiredThreadCount() const { return 0; }
virtual bool isHmd() const { return false; }
virtual int getHmdScreen() const { return -1; }
@ -221,12 +219,15 @@ public:
virtual void cycleDebugOutput() {}
void waitForPresent();
static const QString& MENU_PATH();
signals:
void recommendedFramebufferSizeChanged(const QSize& size);
void resetSensorsRequested();
void presented(quint32 frame);
protected:
void incrementPresentCount();
@ -234,6 +235,8 @@ protected:
gpu::ContextPointer _gpuContext;
private:
QMutex _presentMutex;
QWaitCondition _presentCondition;
std::atomic<uint32_t> _presentedFrameIndex;
mutable std::mutex _paintDelayMutex;
QElapsedTimer _paintDelayTimer;

View file

@ -63,6 +63,12 @@ namespace render {
public:
enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE, MIRROR_RENDER_MODE, SECONDARY_CAMERA_RENDER_MODE };
enum DisplayMode { MONO, STEREO_MONITOR, STEREO_HMD };
enum OutlineFlags {
RENDER_OUTLINE_NONE = 0,
RENDER_OUTLINE_WIREFRAMES = 1,
RENDER_OUTLINE_MARKETPLACE_MODE = 2,
RENDER_OUTLINE_SHADER = 4
};
enum DebugFlags {
RENDER_DEBUG_NONE = 0,
RENDER_DEBUG_HULLS = 1
@ -71,7 +77,6 @@ namespace render {
Args() {}
Args(const gpu::ContextPointer& context,
QSharedPointer<QObject> renderData = QSharedPointer<QObject>(nullptr),
float sizeScale = 1.0f,
int boundaryLevelAdjust = 0,
RenderMode renderMode = DEFAULT_RENDER_MODE,
@ -79,7 +84,6 @@ namespace render {
DebugFlags debugFlags = RENDER_DEBUG_NONE,
gpu::Batch* batch = nullptr) :
_context(context),
_renderData(renderData),
_sizeScale(sizeScale),
_boundaryLevelAdjust(boundaryLevelAdjust),
_renderMode(renderMode),
@ -104,7 +108,6 @@ namespace render {
std::shared_ptr<gpu::Context> _context;
std::shared_ptr<gpu::Framebuffer> _blitFramebuffer;
std::shared_ptr<render::ShapePipeline> _shapePipeline;
QSharedPointer<QObject> _renderData;
std::stack<ViewFrustum> _viewFrustums;
glm::ivec4 _viewport { 0.0f, 0.0f, 1.0f, 1.0f };
glm::vec3 _boomOffset { 0.0f, 0.0f, 1.0f };
@ -112,6 +115,7 @@ namespace render {
int _boundaryLevelAdjust { 0 };
RenderMode _renderMode { DEFAULT_RENDER_MODE };
DisplayMode _displayMode { MONO };
OutlineFlags _outlineFlags{ RENDER_OUTLINE_NONE };
DebugFlags _debugFlags { RENDER_DEBUG_NONE };
gpu::Batch* _batch = nullptr;

View file

@ -10,29 +10,71 @@
#include <QtCore/QDebug>
// Support for viewing the thread name in the debugger.
// Note, Qt actually does this for you but only in debug builds
// Code from https://msdn.microsoft.com/en-us/library/xcb2z8hs.aspx
// and matches logic in `qt_set_thread_name` in qthread_win.cpp
#ifdef Q_OS_WIN
#include <qt_windows.h>
#pragma pack(push,8)
struct THREADNAME_INFO {
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
};
#pragma pack(pop)
#endif
void setThreadName(const std::string& name) {
#ifdef Q_OS_WIN
static const DWORD MS_VC_EXCEPTION = 0x406D1388;
THREADNAME_INFO info{ 0x1000, name.c_str(), (DWORD)-1, 0 };
__try {
RaiseException(MS_VC_EXCEPTION, 0, sizeof(info) / sizeof(ULONG_PTR), (ULONG_PTR*)&info);
} __except (EXCEPTION_EXECUTE_HANDLER) { }
#endif
}
void moveToNewNamedThread(QObject* object, const QString& name, std::function<void(QThread*)> preStartCallback, std::function<void()> startCallback, QThread::Priority priority) {
Q_ASSERT(QThread::currentThread() == object->thread());
// Create the target thread
QThread* thread = new QThread();
thread->setObjectName(name);
// Execute any additional work to do before the thread starts like moving members to the target thread.
// This is required as QObject::moveToThread isn't virutal, so we can't override it on objects that contain
// an OpenGLContext and ensure that the context moves to the target thread as well.
preStartCallback(thread);
// Link the in-thread initialization code
QObject::connect(thread, &QThread::started, [name, startCallback] {
if (!name.isEmpty()) {
// Make it easy to spot our thread processes inside the debugger
setThreadName("Hifi_" + name.toStdString());
}
startCallback();
});
// Make sure the thread will be destroyed and cleaned up. The assumption here is that the incoming object
// will be destroyed and the thread will quit when that occurs.
QObject::connect(object, &QObject::destroyed, thread, &QThread::quit);
// When the thread itself stops running, it should also be deleted.
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
// put the object on the thread
object->moveToThread(thread);
thread->start();
if (priority != QThread::InheritPriority) {
thread->setPriority(priority);
}
}
void moveToNewNamedThread(QObject* object, const QString& name, std::function<void()> startCallback, QThread::Priority priority) {
Q_ASSERT(QThread::currentThread() == object->thread());
// setup a thread for the NodeList and its PacketReceiver
QThread* thread = new QThread();
thread->setObjectName(name);
QString tempName = name;
QObject::connect(thread, &QThread::started, [startCallback] {
startCallback();
});
// Make sure the thread will be destroyed and cleaned up
QObject::connect(object, &QObject::destroyed, thread, &QThread::quit);
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
// put the object on the thread
object->moveToThread(thread);
thread->start();
if (priority != QThread::InheritPriority) {
thread->setPriority(priority);
}
moveToNewNamedThread(object, name, [](QThread*){}, startCallback, priority);
}
void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority) {
moveToNewNamedThread(object, name, [] {}, priority);
moveToNewNamedThread(object, name, [](QThread*){}, []{}, priority);
}

View file

@ -32,8 +32,17 @@ void withLock(QMutex& lock, F function) {
function();
}
void moveToNewNamedThread(QObject* object, const QString& name, std::function<void()> startCallback, QThread::Priority priority = QThread::InheritPriority);
void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority = QThread::InheritPriority);
void moveToNewNamedThread(QObject* object, const QString& name,
std::function<void(QThread*)> preStartCallback,
std::function<void()> startCallback,
QThread::Priority priority = QThread::InheritPriority);
void moveToNewNamedThread(QObject* object, const QString& name,
std::function<void()> startCallback,
QThread::Priority priority = QThread::InheritPriority);
void moveToNewNamedThread(QObject* object, const QString& name,
QThread::Priority priority = QThread::InheritPriority);
class ConditionalGuard {
public:

View file

@ -11,11 +11,24 @@
#include <QtCore/QThread>
#include <QtCore/QCoreApplication>
#include <QtCore/QLoggingCategory>
#include <QtCore/QReadWriteLock>
#include "../Profile.h"
Q_LOGGING_CATEGORY(thread_safety, "hifi.thread_safety")
namespace hifi { namespace qt {
static QHash<QThread*, QString> threadHash;
static QReadWriteLock threadHashLock;
void addBlockingForbiddenThread(const QString& name, QThread* thread) {
if (!thread) {
thread = QThread::currentThread();
}
QWriteLocker locker(&threadHashLock);
threadHash[thread] = name;
}
bool blockingInvokeMethod(
const char* function,
QObject *obj, const char *member,
@ -30,9 +43,23 @@ bool blockingInvokeMethod(
QGenericArgument val7,
QGenericArgument val8,
QGenericArgument val9) {
if (QThread::currentThread() == qApp->thread()) {
auto currentThread = QThread::currentThread();
if (currentThread == qApp->thread()) {
qCWarning(thread_safety) << "BlockingQueuedConnection invoked on main thread from " << function;
return QMetaObject::invokeMethod(obj, member,
Qt::BlockingQueuedConnection, ret, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
}
{
QReadLocker locker(&threadHashLock);
for (const auto& thread : threadHash.keys()) {
if (currentThread == thread) {
qCWarning(thread_safety) << "BlockingQueuedConnection invoked on forbidden thread " << threadHash[thread];
}
}
}
PROFILE_RANGE(app, function);
return QMetaObject::invokeMethod(obj, member,
Qt::BlockingQueuedConnection, ret, val0, val1, val2, val3, val4, val5, val6, val7, val8, val9);
}

View file

@ -14,6 +14,7 @@
namespace hifi { namespace qt {
void addBlockingForbiddenThread(const QString& name, QThread* thread = nullptr);
bool blockingInvokeMethod(
const char* function,

View file

@ -187,6 +187,7 @@ TabletProxy::TabletProxy(QObject* parent, const QString& name) : QObject(parent)
if (QThread::currentThread() != qApp->thread()) {
qCWarning(uiLogging) << "Creating tablet proxy on wrong thread " << _name;
}
connect(this, &TabletProxy::tabletShownChanged, this, &TabletProxy::onTabletShown);
}
TabletProxy::~TabletProxy() {
@ -194,6 +195,7 @@ TabletProxy::~TabletProxy() {
if (QThread::currentThread() != thread()) {
qCWarning(uiLogging) << "Destroying tablet proxy on wrong thread" << _name;
}
disconnect(this, &TabletProxy::tabletShownChanged, this, &TabletProxy::onTabletShown);
}
void TabletProxy::setToolbarMode(bool toolbarMode) {
@ -208,12 +210,13 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
_toolbarMode = toolbarMode;
auto offscreenUi = DependencyManager::get<OffscreenUi>();
if (toolbarMode) {
removeButtonsFromHomeScreen();
addButtonsToToolbar();
// create new desktop window
auto offscreenUi = DependencyManager::get<OffscreenUi>();
auto tabletRootWindow = new TabletRootWindow();
tabletRootWindow->initQml(QVariantMap());
auto quickItem = tabletRootWindow->asQuickItem();
@ -234,7 +237,11 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
} else {
loadHomeScreen(true);
}
//check if running scripts window opened and save it for reopen in Tablet
if (offscreenUi->isVisible("RunningScripts")) {
offscreenUi->hide("RunningScripts");
_showRunningScripts = true;
}
// destroy desktop window
if (_desktopWindow) {
_desktopWindow->deleteLater();
@ -316,6 +323,13 @@ void TabletProxy::emitWebEvent(const QVariant& msg) {
emit webEventReceived(msg);
}
void TabletProxy::onTabletShown() {
if (_tabletShown && _showRunningScripts) {
_showRunningScripts = false;
pushOntoStack("../../hifi/dialogs/TabletRunningScripts.qml");
}
}
bool TabletProxy::isPathLoaded(const QVariant& path) {
if (QThread::currentThread() != thread()) {
bool result = false;
@ -365,9 +379,16 @@ void TabletProxy::setQmlTabletRoot(OffscreenQmlSurface* qmlOffscreenSurface) {
});
if (_initialScreen) {
pushOntoStack(_initialPath);
if (!_showRunningScripts) {
pushOntoStack(_initialPath);
}
_initialScreen = false;
}
if (_showRunningScripts) {
//show Tablet. Make sure, setShown implemented in TabletRoot.qml
QMetaObject::invokeMethod(_qmlTabletRoot, "setShown", Q_ARG(const QVariant&, QVariant(true)));
}
} else {
removeButtonsFromHomeScreen();
_state = State::Uninitialized;
@ -509,7 +530,7 @@ bool TabletProxy::pushOntoStack(const QVariant& path) {
qCDebug(uiLogging) << "tablet cannot push QML because _qmlTabletRoot or _desktopWindow is null";
}
return root;
return (root != nullptr);
}
void TabletProxy::popFromStack() {

View file

@ -232,6 +232,7 @@ protected slots:
void addButtonsToHomeScreen();
void desktopWindowClosed();
void emitWebEvent(const QVariant& msg);
void onTabletShown();
protected:
void removeButtonsFromHomeScreen();
void loadHomeScreen(bool forceOntoHomeScreen);
@ -252,6 +253,7 @@ protected:
enum class State { Uninitialized, Home, Web, Menu, QML };
State _state { State::Uninitialized };
bool _landscape { false };
bool _showRunningScripts { false };
};
Q_DECLARE_METATYPE(TabletProxy*);

View file

@ -6,9 +6,11 @@
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
#
if (NOT ANDROID)
set(TARGET_NAME hifiSixense)
setup_hifi_plugin(Script Qml Widgets)
link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins)
target_sixense()
endif ()
# FIXME if we want to re-enable this, we need to fix the mechanism for installing the
# dependency dlls `msvcr100` and `msvcp100` WITHOUT using CMAKE_INSTALL_SYSTEM_RUNTIME_LIBS
#if (NOT ANDROID)
# set(TARGET_NAME hifiSixense)
# setup_hifi_plugin(Script Qml Widgets)
# link_hifi_libraries(shared controllers ui plugins ui-plugins input-plugins)
# target_sixense()
#endif ()

View file

@ -1,21 +1,13 @@
//
// Created by Anthony J. Thibault on 2017/06/20
// Modified by Robbie Uvanni to support multiple pucks and easier placement of pucks on entities, on 2017/08/01
// Copyright 2017 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
//
// When this script is running, a new app button, named "PUCKTACH", will be added to the toolbar/tablet.
// Click this app to bring up the puck attachment panel. This panel contains the following fields.
//
// * Tracked Object - A drop down list of all the available pucks found. If no pucks are found this list will only have a single NONE entry.
// Closing and re-opening the app will refresh this list.
// * Model URL - A model url of the model you wish to be attached to the specified puck.
// * Position X, Y, Z - used to apply an offset between the puck and the attached model.
// * Rot X, Y, Z - used to apply euler angle offsets, in degrees, between the puck and the attached model.
// * Create Attachment - when this button is pressed a new Entity will be created at the specified puck's location.
// If a puck atttachment already exists, it will be destroyed before the new entity is created.
// * Destroy Attachmetn - destroies the entity attached to the puck.
// Click this app to bring up the puck attachment panel.
//
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
@ -25,7 +17,7 @@ Script.include("/~/system/libraries/Xform.js");
(function() { // BEGIN LOCAL_SCOPE
var TABLET_BUTTON_NAME = "PUCKTACH";
var HTML_URL = "https://s3.amazonaws.com/hifi-public/tony/html/puck-attach.html";
var TABLET_APP_URL = "https://hifi-content.s3.amazonaws.com/seefo/production/puck-attach/puck-attach.html";
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var tabletButton = tablet.addButton({
@ -34,32 +26,13 @@ var tabletButton = tablet.addButton({
activeIcon: "https://s3.amazonaws.com/hifi-public/tony/icons/puck-a.svg"
});
tabletButton.clicked.connect(function () {
if (shown) {
tablet.gotoHomeScreen();
} else {
tablet.gotoWebScreen(HTML_URL);
}
});
var shown = false;
var attachedEntity;
var attachedObj;
function onScreenChanged(type, url) {
if (type === "Web" && url === HTML_URL) {
if (type === "Web" && url === TABLET_APP_URL) {
tabletButton.editProperties({isActive: true});
if (!shown) {
// hook up to event bridge
tablet.webEventReceived.connect(onWebEventReceived);
Script.setTimeout(function () {
// send available tracked objects to the html running in the tablet.
var availableTrackedObjects = getAvailableTrackedObjects();
tablet.emitScriptEvent(JSON.stringify(availableTrackedObjects));
// print("PUCK-ATTACH: availableTrackedObjects = " + JSON.stringify(availableTrackedObjects));
}, 1000); // wait 1 sec before sending..
}
shown = true;
} else {
@ -71,104 +44,264 @@ function onScreenChanged(type, url) {
shown = false;
}
}
tablet.screenChanged.connect(onScreenChanged);
function pad(num, size) {
var tempString = "000000000" + num;
return tempString.substr(tempString.length - size);
}
function indexToTrackedObjectName(index) {
return "TrackedObject" + pad(index, 2);
}
function getAvailableTrackedObjects() {
var available = [];
var NUM_TRACKED_OBJECTS = 16;
var i;
for (i = 0; i < NUM_TRACKED_OBJECTS; i++) {
var key = indexToTrackedObjectName(i);
var pose = Controller.getPoseValue(Controller.Hardware.Vive[key]);
var pose = Controller.getPoseValue(Controller.Standard[key]);
if (pose && pose.valid) {
available.push(i);
}
}
return available;
}
function attach(obj) {
attachedEntity = Entities.addEntity({
type: "Model",
name: "puck-attach-entity",
modelURL: obj.modelurl
});
attachedObj = obj;
var localPos = {x: Number(obj.posx), y: Number(obj.posy), z: Number(obj.posz)};
var localRot = Quat.fromVec3Degrees({x: Number(obj.rotx), y: Number(obj.roty), z: Number(obj.rotz)});
attachedObj.localXform = new Xform(localRot, localPos);
var key = indexToTrackedObjectName(Number(attachedObj.puckno));
attachedObj.key = key;
// print("PUCK-ATTACH: attachedObj = " + JSON.stringify(attachedObj));
Script.update.connect(update);
update();
function sendAvailableTrackedObjects() {
tablet.emitScriptEvent(JSON.stringify({
pucks: getAvailableTrackedObjects(),
selectedPuck: ((lastPuck === undefined) ? -1 : lastPuck.name)
}));
}
function remove() {
if (attachedEntity) {
Script.update.disconnect(update);
Entities.deleteEntity(attachedEntity);
attachedEntity = undefined;
function getRelativePosition(origin, rotation, offset) {
var relativeOffset = Vec3.multiplyQbyV(rotation, offset);
var worldPosition = Vec3.sum(origin, relativeOffset);
return worldPosition;
}
function getPropertyForEntity(entityID, propertyName) {
return Entities.getEntityProperties(entityID, [propertyName])[propertyName];
}
function entityExists(entityID) {
return Object.keys(Entities.getEntityProperties(entityID)).length > 0;
}
var VIVE_PUCK_MODEL = "http://content.highfidelity.com/seefo/production/puck-attach/vive_tracker_puck.obj";
var VIVE_PUCK_DIMENSIONS = { x: 0.0945, y: 0.0921, z: 0.0423 }; // 1/1000th scale of model
var VIVE_PUCK_SEARCH_DISTANCE = 1.5; // metres
var VIVE_PUCK_SPAWN_DISTANCE = 0.5; // metres
var VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE = 10.0; // metres
var VIVE_PUCK_NAME = "Tracked Puck";
var trackedPucks = { };
var lastPuck;
function createPuck(puck) {
// create a puck entity and add it to our list of pucks
var action = indexToTrackedObjectName(puck.puckno);
var pose = Controller.getPoseValue(Controller.Standard[action]);
if (pose && pose.valid) {
var spawnOffset = Vec3.multiply(Vec3.FRONT, VIVE_PUCK_SPAWN_DISTANCE);
var spawnPosition = getRelativePosition(MyAvatar.position, MyAvatar.orientation, spawnOffset);
// should be an overlay
var puckEntityProperties = {
name: "Tracked Puck",
type: "Model",
modelURL: VIVE_PUCK_MODEL,
dimensions: VIVE_PUCK_DIMENSIONS,
position: spawnPosition,
userData: '{ "grabbableKey": { "grabbable": true, "kinematic": false } }'
};
var puckEntityID = Entities.addEntity(puckEntityProperties);
// if we've already created this puck, destroy it
if (trackedPucks.hasOwnProperty(puck.puckno)) {
destroyPuck(puck.puckno);
}
// if we had an unfinalized puck, destroy it
if (lastPuck !== undefined) {
destroyPuck(lastPuck.name);
}
// create our new unfinalized puck
trackedPucks[puck.puckno] = {
puckEntityID: puckEntityID,
trackedEntityID: ""
};
lastPuck = trackedPucks[puck.puckno];
lastPuck.name = Number(puck.puckno);
}
attachedObj = undefined;
}
function finalizePuck(puckName) {
// find nearest entity and change its parent to the puck
if (!trackedPucks.hasOwnProperty(puckName)) {
print('2');
return;
}
if (lastPuck === undefined) {
print('3');
return;
}
if (lastPuck.name !== Number(puckName)) {
print('1');
return;
}
var puckPosition = getPropertyForEntity(lastPuck.puckEntityID, "position");
var foundEntities = Entities.findEntities(puckPosition, VIVE_PUCK_SEARCH_DISTANCE);
function pad(num, size) {
var tempString = "000000000" + num;
return tempString.substr(tempString.length - size);
}
var foundEntity;
var leastDistance = Number.MAX_VALUE;
function update() {
if (attachedEntity && attachedObj && Controller.Hardware.Vive) {
var pose = Controller.getPoseValue(Controller.Hardware.Vive[attachedObj.key]);
var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position);
var puckXform = new Xform(pose.rotation, pose.translation);
var finalXform = Xform.mul(avatarXform, Xform.mul(puckXform, attachedObj.localXform));
if (pose && pose.valid) {
Entities.editEntity(attachedEntity, {
position: finalXform.pos,
rotation: finalXform.rot
});
} else {
if (pose) {
print("PUCK-ATTACH: WARNING: invalid pose for " + attachedObj.key);
} else {
print("PUCK-ATTACH: WARNING: could not find key " + attachedObj.key);
for (var i = 0; i < foundEntities.length; i++) {
var entity = foundEntities[i];
if (getPropertyForEntity(entity, "name") !== VIVE_PUCK_NAME) {
var entityPosition = getPropertyForEntity(entity, "position");
var d = Vec3.distance(entityPosition, puckPosition);
if (d < leastDistance) {
leastDistance = d;
foundEntity = entity;
}
}
}
if (foundEntity) {
lastPuck.trackedEntityID = foundEntity;
// remember the userdata and collisionless flag for the tracked entity since
// we're about to remove it and make it ungrabbable and collisionless
lastPuck.trackedEntityUserData = getPropertyForEntity(foundEntity, "userData");
lastPuck.trackedEntityCollisionFlag = getPropertyForEntity(foundEntity, "collisionless");
// update properties of the tracked entity
Entities.editEntity(lastPuck.trackedEntityID, {
"parentID": lastPuck.puckEntityID,
"userData": '{ "grabbableKey": { "grabbable": false } }',
"collisionless": 1
});
// remove reference to puck since it is now calibrated and finalized
lastPuck = undefined;
}
}
function updatePucks() {
// for each puck, update its position and orientation
for (var puckName in trackedPucks) {
if (!trackedPucks.hasOwnProperty(puckName)) {
continue;
}
var action = indexToTrackedObjectName(puckName);
var pose = Controller.getPoseValue(Controller.Standard[action]);
if (pose && pose.valid) {
var puck = trackedPucks[puckName];
if (puck.trackedEntityID) {
if (entityExists(puck.trackedEntityID)) {
var avatarXform = new Xform(MyAvatar.orientation, MyAvatar.position);
var puckXform = new Xform(pose.rotation, pose.translation);
var finalXform = Xform.mul(avatarXform, puckXform);
var d = Vec3.distance(MyAvatar.position, finalXform.pos);
if (d > VIVE_PUCK_TRACKED_OBJECT_MAX_DISTANCE) {
print('tried to move tracked object too far away: ' + d);
return;
}
Entities.editEntity(puck.puckEntityID, {
position: finalXform.pos,
rotation: finalXform.rot
});
// in case someone grabbed both entities and destroyed the
// child/parent relationship
Entities.editEntity(puck.trackedEntityID, {
parentID: puck.puckEntityID
});
} else {
destroyPuck(puckName);
}
}
}
}
}
function destroyPuck(puckName) {
// unparent entity and delete its parent
if (!trackedPucks.hasOwnProperty(puckName)) {
return;
}
var puck = trackedPucks[puckName];
var puckEntityID = puck.puckEntityID;
var trackedEntityID = puck.trackedEntityID;
// remove the puck as a parent entity and restore the tracked entities
// former userdata and collision flag
Entities.editEntity(trackedEntityID, {
"parentID": "{00000000-0000-0000-0000-000000000000}",
"userData": puck.trackedEntityUserData,
"collisionless": puck.trackedEntityCollisionFlag
});
delete trackedPucks[puckName];
// in some cases, the entity deletion may occur before the parent change
// has been processed, resulting in both the puck and the tracked entity
// to be deleted so we wait 100ms before deleting the puck, assuming
// that the parent change has occured
Script.setTimeout(function() {
// delete the puck
Entities.deleteEntity(puckEntityID);
}, 100);
}
function destroyPucks() {
// remove all pucks and unparent entities
for (var puckName in trackedPucks) {
if (trackedPucks.hasOwnProperty(puckName)) {
destroyPuck(puckName);
}
}
}
function onWebEventReceived(msg) {
var obj = {};
try {
try {
obj = JSON.parse(msg);
} catch (err) {
return;
} catch (err) {
return;
}
if (obj.cmd === "attach") {
remove();
attach(obj);
} else if (obj.cmd === "detach") {
remove();
switch (obj.cmd) {
case "ready":
sendAvailableTrackedObjects();
break;
case "create":
createPuck(obj);
break;
case "finalize":
finalizePuck(obj.puckno);
break;
case "destroy":
destroyPuck(obj.puckno);
break;
}
}
Script.update.connect(updatePucks);
Script.scriptEnding.connect(function () {
remove();
tablet.removeButton(tabletButton);
destroyPucks();
if (shown) {
tablet.webEventReceived.disconnect(onWebEventReceived);
tablet.gotoHomeScreen();
}
tablet.screenChanged.disconnect(onScreenChanged);
});
}()); // END LOCAL_SCOPE
tabletButton.clicked.connect(function () {
if (shown) {
tablet.gotoHomeScreen();
} else {
tablet.gotoWebScreen(TABLET_APP_URL);
}
});
}()); // END LOCAL_SCOPE

View file

@ -23,7 +23,7 @@ var BLUE = {x: 0, y: 0, z: 1, w: 1};
function update(dt) {
if (Controller.Hardware.Vive) {
TRACKED_OBJECT_POSES.forEach(function (key) {
var pose = Controller.getPoseValue(Controller.Hardware.Vive[key]);
var pose = Controller.getPoseValue(Controller.Standard[key]);
if (pose.valid) {
DebugDraw.addMyAvatarMarker(key, pose.rotation, pose.translation, BLUE);
} else {

View file

@ -187,7 +187,10 @@ var DEFAULT_GRABBABLE_DATA = {
var USE_BLACKLIST = true;
var blacklist = [];
var entitiesWithHoverOverlays = [];
var hoveredEntityID = false;
var contextOverlayTimer = false;
var entityWithContextOverlay = false;
var contextualHand = -1;
var FORBIDDEN_GRAB_NAMES = ["Grab Debug Entity", "grab pointer"];
var FORBIDDEN_GRAB_TYPES = ["Unknown", "Light", "PolyLine", "Zone"];
@ -224,6 +227,7 @@ CONTROLLER_STATE_MACHINE[STATE_OFF] = {
CONTROLLER_STATE_MACHINE[STATE_SEARCHING] = {
name: "searching",
enterMethod: "searchEnter",
exitMethod: "searchExit",
updateMethod: "search"
};
CONTROLLER_STATE_MACHINE[STATE_DISTANCE_HOLDING] = {
@ -351,7 +355,9 @@ function projectOntoXYPlane(worldPos, position, rotation, dimensions, registrati
function projectOntoEntityXYPlane(entityID, worldPos) {
var props = entityPropertiesCache.getProps(entityID);
return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint);
if (props) {
return projectOntoXYPlane(worldPos, props.position, props.rotation, props.dimensions, props.registrationPoint);
}
}
function projectOntoOverlayXYPlane(overlayID, worldPos) {
@ -369,7 +375,9 @@ function projectOntoOverlayXYPlane(overlayID, worldPos) {
dimensions = Vec3.multiplyVbyV(Vec3.multiply(resolution, INCHES_TO_METERS / dpi), scale);
} else {
dimensions = Overlays.getProperty(overlayID, "dimensions");
dimensions.z = 0.01; // overlay dimensions are 2D, not 3D.
if (dimensions.z) {
dimensions.z = 0.01; // overlay dimensions are 2D, not 3D.
}
}
return projectOntoXYPlane(worldPos, position, rotation, dimensions, DEFAULT_REGISTRATION_POINT);
@ -2191,6 +2199,14 @@ function MyController(hand) {
}
};
this.searchExit = function () {
contextualHand = -1;
if (hoveredEntityID) {
Entities.sendHoverLeaveEntity(hoveredEntityID, pointerEvent);
}
hoveredEntityID = false;
};
this.search = function(deltaTime, timestamp) {
var _this = this;
var name;
@ -2220,13 +2236,63 @@ function MyController(hand) {
entityPropertiesCache.addEntity(rayPickInfo.entityID);
}
if (rayPickInfo.entityID && entitiesWithHoverOverlays.indexOf(rayPickInfo.entityID) == -1) {
entitiesWithHoverOverlays.forEach(function (element) {
HoverOverlay.destroyHoverOverlay(element);
});
entitiesWithHoverOverlays = [];
HoverOverlay.createHoverOverlay(rayPickInfo.entityID);
entitiesWithHoverOverlays.push(rayPickInfo.entityID);
pointerEvent = {
type: "Move",
id: this.hand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(rayPickInfo.entityID, rayPickInfo.intersection),
pos3D: rayPickInfo.intersection,
normal: rayPickInfo.normal,
direction: rayPickInfo.searchRay.direction,
button: "None"
};
if (rayPickInfo.entityID) {
if (hoveredEntityID !== rayPickInfo.entityID) {
if (contextOverlayTimer) {
Script.clearTimeout(contextOverlayTimer);
contextOverlayTimer = false;
}
if (hoveredEntityID) {
Entities.sendHoverLeaveEntity(hoveredEntityID, pointerEvent);
}
hoveredEntityID = rayPickInfo.entityID;
Entities.sendHoverEnterEntity(hoveredEntityID, pointerEvent);
}
// If we already have a context overlay, we don't want to move it to
// another entity while we're searching.
if (!entityWithContextOverlay && !contextOverlayTimer) {
contextOverlayTimer = Script.setTimeout(function () {
if (rayPickInfo.entityID === hoveredEntityID &&
!entityWithContextOverlay &&
contextualHand !== -1 &&
contextOverlayTimer) {
var pointerEvent = {
type: "Move",
id: contextualHand + 1, // 0 is reserved for hardware mouse
pos2D: projectOntoEntityXYPlane(rayPickInfo.entityID, rayPickInfo.intersection),
pos3D: rayPickInfo.intersection,
normal: rayPickInfo.normal,
direction: rayPickInfo.searchRay.direction,
button: "Secondary"
};
if (ContextOverlay.createOrDestroyContextOverlay(rayPickInfo.entityID, pointerEvent)) {
entityWithContextOverlay = rayPickInfo.entityID;
hoveredEntityID = false;
}
}
contextOverlayTimer = false;
}, 500);
contextualHand = this.hand;
}
} else {
if (hoveredEntityID) {
Entities.sendHoverLeaveEntity(hoveredEntityID, pointerEvent);
hoveredEntityID = false;
}
if (contextOverlayTimer) {
Script.clearTimeout(contextOverlayTimer);
contextOverlayTimer = false;
}
}
var candidateHotSpotEntities = Entities.findEntities(handPosition, MAX_EQUIP_HOTSPOT_RADIUS);
@ -2433,8 +2499,11 @@ function MyController(hand) {
button: "None"
};
this.hoverEntity = entity;
Entities.sendHoverEnterEntity(entity, pointerEvent);
if (this.hoverEntity !== entity) {
Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent);
this.hoverEntity = entity;
Entities.sendHoverEnterEntity(this.hoverEntity, pointerEvent);
}
}
// send mouse events for button highlights and tooltips.
@ -2483,25 +2552,25 @@ function MyController(hand) {
var pointerEvent;
if (rayPickInfo.overlayID) {
var overlay = rayPickInfo.overlayID;
if (Overlays.getProperty(overlay, "type") != "web3d") {
return false;
}
if (Overlays.keyboardFocusOverlay != overlay) {
if ((Overlays.getProperty(overlay, "type") === "web3d") && Overlays.keyboardFocusOverlay != overlay) {
Entities.keyboardFocusEntity = null;
Overlays.keyboardFocusOverlay = overlay;
}
pointerEvent = {
type: "Move",
id: HARDWARE_MOUSE_ID,
pos2D: projectOntoOverlayXYPlane(overlay, rayPickInfo.intersection),
pos3D: rayPickInfo.intersection,
normal: rayPickInfo.normal,
direction: rayPickInfo.searchRay.direction,
button: "None"
};
pointerEvent = {
type: "Move",
id: HARDWARE_MOUSE_ID,
pos2D: projectOntoOverlayXYPlane(overlay, rayPickInfo.intersection),
pos3D: rayPickInfo.intersection,
normal: rayPickInfo.normal,
direction: rayPickInfo.searchRay.direction,
button: "None"
};
if (this.hoverOverlay !== overlay) {
Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent);
this.hoverOverlay = overlay;
Overlays.sendHoverEnterOverlay(overlay, pointerEvent);
Overlays.sendHoverEnterOverlay(this.hoverOverlay, pointerEvent);
}
// Send mouse events for button highlights and tooltips.
@ -3477,6 +3546,15 @@ function MyController(hand) {
var existingSearchDistance = this.searchSphereDistance;
this.release();
if (hoveredEntityID) {
Entities.sendHoverLeaveEntity(hoveredEntityID, pointerEvent);
hoveredEntityID = false;
}
if (entityWithContextOverlay) {
ContextOverlay.destroyContextOverlay(entityWithContextOverlay);
entityWithContextOverlay = false;
}
if (isInEditMode()) {
this.searchSphereDistance = existingSearchDistance;
}
@ -3597,7 +3675,7 @@ function MyController(hand) {
};
this.overlayLaserTouchingEnter = function () {
// Test for intersection between controller laser and Web overlay plane.
// Test for intersection between controller laser and overlay plane.
var controllerLocation = getControllerWorldLocation(this.handToController(), true);
var intersectInfo = handLaserIntersectOverlay(this.grabbedOverlay, controllerLocation);
if (intersectInfo) {
@ -3791,11 +3869,6 @@ function MyController(hand) {
this.release = function() {
this.turnOffVisualizations();
entitiesWithHoverOverlays.forEach(function (element) {
HoverOverlay.destroyHoverOverlay(element);
});
entitiesWithHoverOverlays = [];
if (this.grabbedThingID !== null) {
Messages.sendMessage('Hifi-Teleport-Ignore-Remove', this.grabbedThingID);

View file

@ -394,7 +394,6 @@ var toolBar = (function () {
function initialize() {
Script.scriptEnding.connect(cleanup);
Window.domainChanged.connect(function () {
that.setActive(false);
that.clearEntityList();
@ -622,6 +621,7 @@ var toolBar = (function () {
};
that.setActive = function (active) {
ContextOverlay.enabled = !active;
Settings.setValue(EDIT_SETTING, active);
if (active) {
Controller.captureEntityClickEvents();
@ -2194,6 +2194,7 @@ var PopupMenu = function () {
};
function cleanup() {
ContextOverlay.enabled = true;
for (var i = 0; i < overlays.length; i++) {
Overlays.deleteOverlay(overlays[i]);
}

View file

@ -43,17 +43,20 @@ var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
// Independent and Entity mode make people sick; disable them in hmd.
var desktopOnlyViews = ['Independent Mode', 'Entity Mode'];
var switchToVR = "ENTER VR";
var switchToDesktop = "EXIT VR";
function onHmdChanged(isHmd) {
HMD.closeTablet();
if (isHmd) {
button.editProperties({
icon: "icons/tablet-icons/switch-desk-i.svg",
text: "DESKTOP"
text: switchToDesktop
});
} else {
button.editProperties({
icon: "icons/tablet-icons/switch-vr-i.svg",
text: "VR"
text: switchToVR
});
}
desktopOnlyViews.forEach(function (view) {
@ -70,7 +73,7 @@ function onClicked() {
if (headset) {
button = tablet.addButton({
icon: HMD.active ? "icons/tablet-icons/switch-desk-i.svg" : "icons/tablet-icons/switch-vr-i.svg",
text: HMD.active ? "DESKTOP" : "VR",
text: HMD.active ? switchToDesktop : switchToVR,
sortOrder: 2
});
onHmdChanged(HMD.active);

View file

@ -11,135 +11,140 @@
/* global Tablet, Script, HMD, UserActivityLogger, Entities */
/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */
(function() { // BEGIN LOCAL_SCOPE
(function () { // BEGIN LOCAL_SCOPE
Script.include("../libraries/WebTablet.js");
Script.include("../libraries/WebTablet.js");
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
var MARKETPLACE_URL = "https://metaverse.highfidelity.com/marketplace";
var MARKETPLACE_URL_INITIAL = MARKETPLACE_URL + "?"; // Append "?" to signal injected script that it's the initial page.
var MARKETPLACES_URL = Script.resolvePath("../html/marketplaces.html");
var MARKETPLACES_INJECT_SCRIPT_URL = Script.resolvePath("../html/js/marketplacesInject.js");
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
var HOME_BUTTON_TEXTURE = "http://hifi-content.s3.amazonaws.com/alan/dev/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
// var HOME_BUTTON_TEXTURE = Script.resourcesPath() + "meshes/tablet-with-home-button.fbx/tablet-with-home-button.fbm/button-root.png";
// Event bridge messages.
var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
var CLARA_IO_STATUS = "CLARA.IO STATUS";
var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD";
var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD";
var GOTO_DIRECTORY = "GOTO_DIRECTORY";
var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
// Event bridge messages.
var CLARA_IO_DOWNLOAD = "CLARA.IO DOWNLOAD";
var CLARA_IO_STATUS = "CLARA.IO STATUS";
var CLARA_IO_CANCEL_DOWNLOAD = "CLARA.IO CANCEL DOWNLOAD";
var CLARA_IO_CANCELLED_DOWNLOAD = "CLARA.IO CANCELLED DOWNLOAD";
var GOTO_DIRECTORY = "GOTO_DIRECTORY";
var QUERY_CAN_WRITE_ASSETS = "QUERY_CAN_WRITE_ASSETS";
var CAN_WRITE_ASSETS = "CAN_WRITE_ASSETS";
var WARN_USER_NO_PERMISSIONS = "WARN_USER_NO_PERMISSIONS";
var CLARA_DOWNLOAD_TITLE = "Preparing Download";
var messageBox = null;
var isDownloadBeingCancelled = false;
var CLARA_DOWNLOAD_TITLE = "Preparing Download";
var messageBox = null;
var isDownloadBeingCancelled = false;
var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel
var NO_BUTTON = 0; // QMessageBox::NoButton
var CANCEL_BUTTON = 4194304; // QMessageBox::Cancel
var NO_BUTTON = 0; // QMessageBox::NoButton
var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server.";
var NO_PERMISSIONS_ERROR_MESSAGE = "Cannot download model because you can't write to \nthe domain's Asset Server.";
function onMessageBoxClosed(id, button) {
if (id === messageBox && button === CANCEL_BUTTON) {
isDownloadBeingCancelled = true;
messageBox = null;
tablet.emitScriptEvent(CLARA_IO_CANCEL_DOWNLOAD);
function onMessageBoxClosed(id, button) {
if (id === messageBox && button === CANCEL_BUTTON) {
isDownloadBeingCancelled = true;
messageBox = null;
tablet.emitScriptEvent(CLARA_IO_CANCEL_DOWNLOAD);
}
}
}
Window.messageBoxClosed.connect(onMessageBoxClosed);
Window.messageBoxClosed.connect(onMessageBoxClosed);
var onMarketplaceScreen = false;
var onMarketplaceScreen = false;
function showMarketplace() {
UserActivityLogger.openedMarketplace();
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
tablet.webEventReceived.connect(function (message) {
function showMarketplace() {
UserActivityLogger.openedMarketplace();
tablet.gotoWebScreen(MARKETPLACE_URL_INITIAL, MARKETPLACES_INJECT_SCRIPT_URL);
tablet.webEventReceived.connect(function (message) {
if (message === GOTO_DIRECTORY) {
tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL);
}
if (message === GOTO_DIRECTORY) {
tablet.gotoWebScreen(MARKETPLACES_URL, MARKETPLACES_INJECT_SCRIPT_URL);
}
if (message === QUERY_CAN_WRITE_ASSETS) {
tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
}
if (message === QUERY_CAN_WRITE_ASSETS) {
tablet.emitScriptEvent(CAN_WRITE_ASSETS + " " + Entities.canWriteAssets());
}
if (message === WARN_USER_NO_PERMISSIONS) {
Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
}
if (message === WARN_USER_NO_PERMISSIONS) {
Window.alert(NO_PERMISSIONS_ERROR_MESSAGE);
}
if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) {
if (isDownloadBeingCancelled) {
if (message.slice(0, CLARA_IO_STATUS.length) === CLARA_IO_STATUS) {
if (isDownloadBeingCancelled) {
return;
}
var text = message.slice(CLARA_IO_STATUS.length);
if (messageBox === null) {
messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
} else {
Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
}
return;
}
var text = message.slice(CLARA_IO_STATUS.length);
if (messageBox === null) {
messageBox = Window.openMessageBox(CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
} else {
Window.updateMessageBox(messageBox, CLARA_DOWNLOAD_TITLE, text, CANCEL_BUTTON, NO_BUTTON);
if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) {
if (messageBox !== null) {
Window.closeMessageBox(messageBox);
messageBox = null;
}
return;
}
return;
}
if (message.slice(0, CLARA_IO_DOWNLOAD.length) === CLARA_IO_DOWNLOAD) {
if (messageBox !== null) {
Window.closeMessageBox(messageBox);
messageBox = null;
if (message === CLARA_IO_CANCELLED_DOWNLOAD) {
isDownloadBeingCancelled = false;
}
return;
}
});
}
if (message === CLARA_IO_CANCELLED_DOWNLOAD) {
isDownloadBeingCancelled = false;
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var marketplaceButton = tablet.addButton({
icon: "icons/tablet-icons/market-i.svg",
activeIcon: "icons/tablet-icons/market-a.svg",
text: "MARKET",
sortOrder: 9
});
}
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
var marketplaceButton = tablet.addButton({
icon: "icons/tablet-icons/market-i.svg",
activeIcon: "icons/tablet-icons/market-a.svg",
text: "MARKET",
sortOrder: 9
});
function onCanWriteAssetsChanged() {
var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets();
tablet.emitScriptEvent(message);
}
function onClick() {
if (onMarketplaceScreen) {
// for toolbar-mode: go back to home screen, this will close the window.
tablet.gotoHomeScreen();
} else {
var entity = HMD.tabletID;
Entities.editEntity(entity, {textures: JSON.stringify({"tex.close": HOME_BUTTON_TEXTURE})});
showMarketplace();
function onCanWriteAssetsChanged() {
var message = CAN_WRITE_ASSETS + " " + Entities.canWriteAssets();
tablet.emitScriptEvent(message);
}
}
function onScreenChanged(type, url) {
onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL
// for toolbar mode: change button to active when window is first openend, false otherwise.
marketplaceButton.editProperties({isActive: onMarketplaceScreen});
}
marketplaceButton.clicked.connect(onClick);
tablet.screenChanged.connect(onScreenChanged);
Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
Script.scriptEnding.connect(function () {
if (onMarketplaceScreen) {
tablet.gotoHomeScreen();
function onClick() {
if (onMarketplaceScreen) {
// for toolbar-mode: go back to home screen, this will close the window.
tablet.gotoHomeScreen();
} else {
var entity = HMD.tabletID;
Entities.editEntity(entity, { textures: JSON.stringify({ "tex.close": HOME_BUTTON_TEXTURE }) });
showMarketplace();
}
}
tablet.removeButton(marketplaceButton);
tablet.screenChanged.disconnect(onScreenChanged);
Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
});
function onScreenChanged(type, url) {
onMarketplaceScreen = type === "Web" && url === MARKETPLACE_URL_INITIAL
// for toolbar mode: change button to active when window is first openend, false otherwise.
marketplaceButton.editProperties({ isActive: onMarketplaceScreen });
if (type === "Web" && url.indexOf(MARKETPLACE_URL) !== -1) {
ContextOverlay.isInMarketplaceInspectionMode = true;
} else {
ContextOverlay.isInMarketplaceInspectionMode = false;
}
}
marketplaceButton.clicked.connect(onClick);
tablet.screenChanged.connect(onScreenChanged);
Entities.canWriteAssetsChanged.connect(onCanWriteAssetsChanged);
Script.scriptEnding.connect(function () {
if (onMarketplaceScreen) {
tablet.gotoHomeScreen();
}
tablet.removeButton(marketplaceButton);
tablet.screenChanged.disconnect(onScreenChanged);
Entities.canWriteAssetsChanged.disconnect(onCanWriteAssetsChanged);
});
}()); // END LOCAL_SCOPE

View file

@ -710,6 +710,7 @@ function off() {
Controller.mouseMoveEvent.disconnect(handleMouseMoveEvent);
tablet.tabletShownChanged.disconnect(tabletVisibilityChanged);
isWired = false;
ContextOverlay.enabled = true
}
if (audioTimer) {
Script.clearInterval(audioTimer);
@ -722,6 +723,7 @@ function off() {
function tabletVisibilityChanged() {
if (!tablet.tabletShown) {
ContextOverlay.enabled = true;
tablet.gotoHomeScreen();
}
}
@ -732,7 +734,9 @@ function onTabletButtonClicked() {
if (onPalScreen) {
// for toolbar-mode: go back to home screen, this will close the window.
tablet.gotoHomeScreen();
ContextOverlay.enabled = true;
} else {
ContextOverlay.enabled = false;
tablet.loadQMLSource(PAL_QML_SOURCE);
tablet.tabletShownChanged.connect(tabletVisibilityChanged);
Users.requestsDomainListData = true;
@ -863,6 +867,7 @@ function avatarDisconnected(nodeID) {
function clearLocalQMLDataAndClosePAL() {
sendToQml({ method: 'clearLocalQMLData' });
if (onPalScreen) {
ContextOverlay.enabled = true;
tablet.gotoHomeScreen();
}
}

View file

@ -104,7 +104,6 @@
function showTabletUI() {
checkTablet()
gTablet.tabletShown = true;
if (!tabletRezzed || !tabletIsValid()) {
closeTabletUI();
@ -123,6 +122,7 @@
Overlays.editOverlay(HMD.tabletScreenID, { visible: true });
Overlays.editOverlay(HMD.tabletScreenID, { maxFPS: 90 });
}
gTablet.tabletShown = true;
}
function hideTabletUI() {

View file

@ -681,7 +681,7 @@ private:
_renderCount = _renderThread._presentCount.load();
update();
RenderArgs renderArgs(_renderThread._gpuContext, _octree, DEFAULT_OCTREE_SIZE_SCALE,
RenderArgs renderArgs(_renderThread._gpuContext, DEFAULT_OCTREE_SIZE_SCALE,
0, RenderArgs::DEFAULT_RENDER_MODE,
RenderArgs::MONO, RenderArgs::RENDER_DEBUG_NONE);

View file

@ -9,21 +9,44 @@
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
/* eslint-env commonjs */
/* global DriveKeys, require:true, console */
/* eslint-disable comma-dangle */
var require = Script.require;
// decomment next line for automatic require cache-busting
// var require = function require(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); };
if (typeof require !== 'function') {
require = Script.require;
}
var VERSION = '0.0.0';
var WANT_DEBUG = false;
var DEBUG_MOVE_AS_YOU_MOVE = false;
var ROTATE_AS_YOU_MOVE = false;
log(VERSION);
var DopplegangerClass = require('./doppleganger.js'),
DopplegangerAttachments = require('./doppleganger-attachments.js'),
modelHelper = require('./model-helper.js').modelHelper;
DebugControls = require('./doppleganger-debug.js'),
modelHelper = require('./model-helper.js').modelHelper,
utils = require('./utils.js');
// eslint-disable-next-line camelcase
var isWebpack = typeof __webpack_require__ === 'function';
var buttonConfig = utils.assign({
text: 'MIRROR',
}, !isWebpack ? {
icon: Script.resolvePath('./doppleganger-i.svg'),
activeIcon: Script.resolvePath('./doppleganger-a.svg'),
} : {
icon: require('./doppleganger-i.svg.json'),
activeIcon: require('./doppleganger-a.svg.json'),
});
var tablet = Tablet.getTablet('com.highfidelity.interface.tablet.system'),
button = tablet.addButton({
icon: Script.resolvePath('./doppleganger-i.svg'),
activeIcon: Script.resolvePath('./doppleganger-a.svg'),
text: 'MIRROR'
});
button = tablet.addButton(buttonConfig);
Script.scriptEnding.connect(function() {
tablet.removeButton(button);
@ -65,6 +88,59 @@ var doppleganger = new DopplegangerClass({
}
}
// add support for "move as you move"
{
var movementKeys = 'W,A,S,D,Up,Down,Right,Left'.split(',');
var controllerKeys = 'LX,LY,RY'.split(',');
var translationKeys = Object.keys(DriveKeys).filter(function(p) {
return /translate/i.test(p);
});
var startingPosition;
// returns an array of any active driving keys (eg: ['W', 'TRANSLATE_Z'])
function currentDrivers() {
return [].concat(
movementKeys.map(function(key) {
return Controller.getValue(Controller.Hardware.Keyboard[key]) && key;
})
).concat(
controllerKeys.map(function(key) {
return Controller.getValue(Controller.Standard[key]) !== 0.0 && key;
})
).concat(
translationKeys.map(function(key) {
return MyAvatar.getRawDriveKey(DriveKeys[key]) !== 0.0 && key;
})
).filter(Boolean);
}
doppleganger.jointsUpdated.connect(function(objectID) {
var drivers = currentDrivers(),
isDriving = drivers.length > 0;
if (isDriving) {
if (startingPosition) {
debugPrint('resetting startingPosition since drivers == ', drivers.join('|'));
startingPosition = null;
}
} else if (HMD.active || DEBUG_MOVE_AS_YOU_MOVE) {
startingPosition = startingPosition || MyAvatar.position;
var movement = Vec3.subtract(MyAvatar.position, startingPosition);
startingPosition = MyAvatar.position;
// Vec3.length(movement) > 0.0001 && Vec3.print('+avatarMovement', movement);
// "mirror" the relative translation vector
movement.x *= -1;
movement.z *= -1;
var props = {};
props.position = doppleganger.position = Vec3.sum(doppleganger.position, movement);
if (ROTATE_AS_YOU_MOVE) {
props.rotation = doppleganger.orientation = MyAvatar.orientation;
}
modelHelper.editObject(doppleganger.objectID, props);
}
});
}
// hide the doppleganger if this client script is unloaded
Script.scriptEnding.connect(doppleganger, 'stop');
@ -103,15 +179,21 @@ doppleganger.modelLoaded.connect(function(error, result) {
// add debug indicators, but only if the user has configured the settings value
if (Settings.getValue('debug.doppleganger', false)) {
WANT_DEBUG = true;
DopplegangerClass.addDebugControls(doppleganger);
WANT_DEBUG = WANT_DEBUG || true;
DopplegangerClass.WANT_DEBUG = WANT_DEBUG;
DopplegangerAttachments.WANT_DEBUG = WANT_DEBUG;
new DebugControls(doppleganger);
}
function log() {
// eslint-disable-next-line no-console
(typeof console === 'object' ? console.log : print)('app-doppleganger | ' + [].slice.call(arguments).join(' '));
}
function debugPrint() {
if (WANT_DEBUG) {
print('app-doppleganger | ' + [].slice.call(arguments).join(' '));
}
WANT_DEBUG && log.apply(this, arguments);
}
// ----------------------------------------------------------------------------
UserActivityLogger.logAction('doppleganger_app_load');

File diff suppressed because one or more lines are too long

View file

@ -1,10 +1,12 @@
"use strict";
/* eslint-env commonjs */
/* eslint-disable comma-dangle */
/* global console */
// var require = function(id) { return Script.require(id + '?'+new Date().getTime().toString(36)); }
module.exports = DopplegangerAttachments;
DopplegangerAttachments.version = '0.0.0';
DopplegangerAttachments.version = '0.0.1b';
DopplegangerAttachments.WANT_DEBUG = false;
var _modelHelper = require('./model-helper.js'),
@ -13,8 +15,10 @@ var _modelHelper = require('./model-helper.js'),
utils = require('./utils.js');
function log() {
print('doppleganger-attachments | ' + [].slice.call(arguments).join(' '));
// eslint-disable-next-line no-console
(typeof console === 'object' ? console.log : print)('doppleganger-attachments | ' + [].slice.call(arguments).join(' '));
}
log(DopplegangerAttachments.version);
function debugPrint() {
DopplegangerAttachments.WANT_DEBUG && log.apply(this, arguments);
@ -61,6 +65,9 @@ DopplegangerAttachments.prototype = {
},
// compare before / after attachment sets to determine which ones need to be (re)created
refreshAttachments: function() {
if (!this.doppleganger.objectID) {
return log('refreshAttachments -- canceling; !this.doppleganger.objectID');
}
var before = this.attachments || [],
beforeIndex = before.reduce(function(out, att, index) {
out[att.$hash] = index; return out;
@ -90,18 +97,19 @@ DopplegangerAttachments.prototype = {
var attachments = this.attachments,
parentID = this.doppleganger.objectID,
jointNames = this.doppleganger.jointNames,
properties = modelHelper.getProperties(this.doppleganger.objectID);
properties = modelHelper.getProperties(this.doppleganger.objectID),
modelType = properties && properties.type;
utils.assert(modelType === 'model' || modelType === 'Model', 'unrecognized doppleganger modelType:' + modelType);
debugPrint('DopplegangerAttachments..._createAttachmentObjects', JSON.stringify({
type: properties.type,
modelType: modelType,
attachments: attachments.length,
parentID: parentID,
jointNames: jointNames.join(' | '),
},0,2));
return attachments.map(utils.bind(this, function(attachment, i) {
var type = modelHelper.type(attachment.properties && attachment.properties.objectID);
if (type === 'overlay') {
debugPrint('skipping already-provisioned attachment object', type, attachment.properties && attachment.properties.name);
var objectType = modelHelper.type(attachment.properties && attachment.properties.objectID);
if (objectType === 'overlay') {
debugPrint('skipping already-provisioned attachment object', objectType, attachment.properties && attachment.properties.name);
return attachment;
}
var jointIndex = attachment.$jointIndex, // jointNames.indexOf(attachment.jointName),
@ -109,7 +117,7 @@ DopplegangerAttachments.prototype = {
attachment.properties = utils.assign({
name: attachment.toString(),
type: properties.type,
type: modelType,
modelURL: attachment.modelUrl,
scale: scale,
dimensions: modelHelper.type(parentID) === 'entity' ?
@ -119,14 +127,16 @@ DopplegangerAttachments.prototype = {
dynamic: false,
shapeType: 'none',
lifetime: 60,
grabbable: true,
}, !this.manualJointSync && {
parentID: parentID,
parentJointIndex: jointIndex,
localPosition: attachment.translation,
localRotation: Quat.fromVec3Degrees(attachment.rotation),
});
var objectID = attachment.properties.objectID = modelHelper.addObject(attachment.properties);
utils.assert(!Uuid.isNull(objectID), 'could not create attachment: ' + [objectID, JSON.stringify(attachment.properties,0,2)]);
attachment._resource = ModelCache.prefetch(attachment.properties.modelURL);
attachment._modelReadier = new ModelReadyWatcher( {
attachment._modelReadier = new ModelReadyWatcher({
resource: attachment._resource,
objectID: objectID,
});
@ -134,21 +144,23 @@ DopplegangerAttachments.prototype = {
attachment._modelReadier.modelReady.connect(this, function(err, result) {
if (err) {
log('>>>>> modelReady ERROR <<<<<: ' + err, attachment.modelUrl);
log('>>>>> modelReady ERROR <<<<<: ' + err, attachment.properties.modelURL);
modelHelper.deleteObject(objectID);
return objectID = null;
}
debugPrint('attachment model ('+modelHelper.type(result.objectID)+') is ready; # joints ==',
result.jointNames && result.jointNames.length, result.naturalDimensions, result.objectID);
result.jointNames && result.jointNames.length, JSON.stringify(result.naturalDimensions), result.objectID);
var properties = modelHelper.getProperties(result.objectID),
naturalDimensions = attachment.properties.naturalDimensions = properties.naturalDimensions;
modelHelper.editObject(result.objectID, {
naturalDimensions = attachment.properties.naturalDimensions = properties.naturalDimensions || result.naturalDimensions;
modelHelper.editObject(objectID, {
dimensions: naturalDimensions ? Vec3.multiply(attachment.scale, naturalDimensions) : undefined,
localRotation: Quat.normalize({}),
localPosition: Vec3.ZERO,
});
this.onJointsUpdated(parentID); // trigger once to initialize position/rotation
// give time for model overlay to "settle", then make it visible
Script.setTimeout(utils.bind(this, function() {
modelHelper.editObject(result.objectID, {
modelHelper.editObject(objectID, {
visible: true,
});
attachment._loaded = true;
@ -175,7 +187,7 @@ DopplegangerAttachments.prototype = {
}
},
onJointsUpdated: function onJointsUpdated(objectID) {
onJointsUpdated: function onJointsUpdated(objectID, jointUpdates) {
var jointOrientations = modelHelper.getJointOrientations(objectID),
jointPositions = modelHelper.getJointPositions(objectID),
parentID = objectID,
@ -195,8 +207,9 @@ DopplegangerAttachments.prototype = {
jointPosition = jointPositions[jointIndex];
var translation = Vec3.multiply(avatarScale, attachment.translation),
rotation = Quat.fromVec3Degrees(attachment.rotation),
localPosition = Vec3.multiplyQbyV(jointOrientation, translation),
rotation = Quat.fromVec3Degrees(attachment.rotation);
var localPosition = Vec3.multiplyQbyV(jointOrientation, translation),
localRotation = rotation;
updates[objectID] = manualJointSync ? {
@ -212,7 +225,6 @@ DopplegangerAttachments.prototype = {
localPosition: localPosition,
scale: attachment.scale,
};
onJointsUpdated[objectID] = updates[objectID];
return updates;
}, {});
modelHelper.editObjects(updatedObjects);
@ -221,7 +233,7 @@ DopplegangerAttachments.prototype = {
_initialize: function() {
var doppleganger = this.doppleganger;
if ('$attachmentControls' in doppleganger) {
throw new Error('only one set of debug controls can be added per doppleganger');
throw new Error('only one set of attachment controls can be added per doppleganger');
}
doppleganger.$attachmentControls = this;
doppleganger.activeChanged.connect(this, function(active) {

View file

@ -0,0 +1,158 @@
// -- ADVANCED DEBUGGING --
// @function - Add debug joint indicators / extra debugging info.
// @param {Doppleganger} - existing Doppleganger instance to add controls to
//
// @note:
// * rightclick toggles mirror mode on/off
// * shift-rightclick toggles the debug indicators on/off
// * clicking on an indicator displays the joint name and mirrored joint name in the debug log.
//
// Example use:
// var doppleganger = new DopplegangerClass();
// DopplegangerClass.addDebugControls(doppleganger);
"use strict";
/* eslint-env commonjs */
/* eslint-disable comma-dangle */
/* global console */
var DopplegangerClass = require('./doppleganger.js'),
modelHelper = require('./model-helper.js').modelHelper,
utils = require('./utils.js');
module.exports = DebugControls;
// mixin addDebugControls to DopplegangerClass for backwards-compatibility
DopplegangerClass.addDebugControls = function(doppleganger) {
new DebugControls(doppleganger);
return doppleganger;
};
DebugControls.version = '0.0.0';
DebugControls.COLOR_DEFAULT = { red: 255, blue: 255, green: 255 };
DebugControls.COLOR_SELECTED = { red: 0, blue: 255, green: 0 };
function log() {
// eslint-disable-next-line no-console
(typeof console === 'object' ? console.log : print)('doppleganger-debug | ' + [].slice.call(arguments).join(' '));
}
function DebugControls(doppleganger) {
this.enableIndicators = true;
this.selectedJointName = null;
this.debugOverlayIDs = undefined;
this.jointSelected = utils.signal(function(result) {});
this.doppleganger = doppleganger;
this._initialize();
}
DebugControls.prototype = {
start: function() {
if (!this.onMousePressEvent) {
this.onMousePressEvent = this._onMousePressEvent;
Controller.mousePressEvent.connect(this, 'onMousePressEvent');
this.doppleganger.jointsUpdated.connect(this, 'onJointsUpdated');
}
},
stop: function() {
this.removeIndicators();
if (this.onMousePressEvent) {
this.doppleganger.jointsUpdated.disconnect(this, 'onJointsUpdated');
Controller.mousePressEvent.disconnect(this, 'onMousePressEvent');
delete this.onMousePressEvent;
}
},
createIndicators: function(jointNames) {
this.jointNames = jointNames;
return jointNames.map(function(name, i) {
return Overlays.addOverlay('shape', {
shape: 'Icosahedron',
scale: 0.1,
solid: false,
alpha: 0.5
});
});
},
removeIndicators: function() {
if (this.debugOverlayIDs) {
this.debugOverlayIDs.forEach(Overlays.deleteOverlay);
this.debugOverlayIDs = undefined;
}
},
onJointsUpdated: function(overlayID) {
if (!this.enableIndicators) {
return;
}
var jointNames = Overlays.getProperty(overlayID, 'jointNames'),
jointOrientations = Overlays.getProperty(overlayID, 'jointOrientations'),
jointPositions = Overlays.getProperty(overlayID, 'jointPositions'),
selectedIndex = jointNames.indexOf(this.selectedJointName);
if (!this.debugOverlayIDs) {
this.debugOverlayIDs = this.createIndicators(jointNames);
}
// batch all updates into a single call (using the editOverlays({ id: {props...}, ... }) API)
var updatedOverlays = this.debugOverlayIDs.reduce(function(updates, id, i) {
updates[id] = {
position: jointPositions[i],
rotation: jointOrientations[i],
color: i === selectedIndex ? DebugControls.COLOR_SELECTED : DebugControls.COLOR_DEFAULT,
solid: i === selectedIndex
};
return updates;
}, {});
Overlays.editOverlays(updatedOverlays);
},
_onMousePressEvent: function(evt) {
if (evt.isLeftButton) {
if (!this.enableIndicators || !this.debugOverlayIDs) {
return;
}
var ray = Camera.computePickRay(evt.x, evt.y),
hit = Overlays.findRayIntersection(ray, true, this.debugOverlayIDs);
hit.jointIndex = this.debugOverlayIDs.indexOf(hit.overlayID);
hit.jointName = this.jointNames[hit.jointIndex];
this.jointSelected(hit);
} else if (evt.isRightButton) {
if (evt.isShifted) {
this.enableIndicators = !this.enableIndicators;
if (!this.enableIndicators) {
this.removeIndicators();
}
} else {
this.doppleganger.mirrored = !this.doppleganger.mirrored;
}
}
},
_initialize: function() {
if ('$debugControls' in this.doppleganger) {
throw new Error('only one set of debug controls can be added per doppleganger');
}
this.doppleganger.$debugControls = this;
this.doppleganger.activeChanged.connect(this, function(active) {
if (active) {
this.start();
} else {
this.stop();
}
});
this.jointSelected.connect(this, function(hit) {
this.selectedJointName = hit.jointName;
if (hit.jointIndex < 0) {
return;
}
hit.mirroredJointName = modelHelper.deriveMirroredJointNames([hit.jointName])[0];
log('selected joint:', JSON.stringify(hit, 0, 2));
});
Script.scriptEnding.connect(this, 'removeIndicators');
},
}; // DebugControls.prototype

View file

@ -10,6 +10,7 @@
//
/* eslint-env commonjs */
/* global console */
// @module doppleganger
//
// This module contains the `Doppleganger` class implementation for creating an inspectable replica of
@ -24,9 +25,13 @@
module.exports = Doppleganger;
Doppleganger.version = '0.0.1a';
log(Doppleganger.version);
var _modelHelper = require('./model-helper.js'),
modelHelper = _modelHelper.modelHelper,
ModelReadyWatcher = _modelHelper.ModelReadyWatcher;
ModelReadyWatcher = _modelHelper.ModelReadyWatcher,
utils = require('./utils.js');
// @property {bool} - toggle verbose debug logging on/off
Doppleganger.WANT_DEBUG = false;
@ -48,16 +53,16 @@ function Doppleganger(options) {
this.autoUpdate = 'autoUpdate' in options ? options.autoUpdate : true;
// @public
this.active = false; // whether doppleganger is currently being displayed/updated
this.active = false; // whether doppleganger is currently being displayed/updated
this.objectID = null; // current doppleganger's Overlay or Entity id
this.frame = 0; // current joint update frame
this.frame = 0; // current joint update frame
// @signal - emitted when .active state changes
this.activeChanged = signal(function(active, reason) {});
this.activeChanged = utils.signal(function(active, reason) {});
// @signal - emitted once model is either loaded or errors out
this.modelLoaded = signal(function(error, result){});
this.modelLoaded = utils.signal(function(error, result){});
// @signal - emitted each time the model's joint data has been synchronized
this.jointsUpdated = signal(function(objectID){});
this.jointsUpdated = utils.signal(function(objectID){});
}
Doppleganger.prototype = {
@ -118,11 +123,12 @@ Doppleganger.prototype = {
rotations = outRotations;
translations = outTranslations;
}
modelHelper.editObject(this.objectID, {
var jointUpdates = {
jointRotations: rotations,
jointTranslations: translations
});
this.jointsUpdated(this.objectID);
};
modelHelper.editObject(this.objectID, jointUpdates);
this.jointsUpdated(this.objectID, jointUpdates);
} catch (e) {
log('.update error: '+ e, index, e.stack);
this.stop('update_error');
@ -134,7 +140,7 @@ Doppleganger.prototype = {
// @param {vec3} [options.position=(in front of avatar)] - starting position
// @param {quat} [options.orientation=avatar.orientation] - starting orientation
start: function(options) {
options = assign(this.options, options);
options = utils.assign(this.options, options);
if (this.objectID) {
log('start() called but object model already exists', this.objectID);
return;
@ -194,11 +200,11 @@ Doppleganger.prototype = {
return this.stop(error);
}
debugPrint('model ('+modelHelper.type(this.objectID)+')' + ' is ready; # joints == ' + result.jointNames.length);
var naturalDimensions = modelHelper.getProperties(this.objectID, ['naturalDimensions']).naturalDimensions;
var naturalDimensions = this.naturalDimensions = modelHelper.getProperties(this.objectID, ['naturalDimensions']).naturalDimensions;
debugPrint('naturalDimensions:', JSON.stringify(naturalDimensions));
var props = { visible: true };
if (naturalDimensions) {
props.dimensions = Vec3.multiply(this.scale, naturalDimensions);
props.dimensions = this.dimensions = Vec3.multiply(this.scale, naturalDimensions);
}
debugPrint('scaledDimensions:', this.scale, JSON.stringify(props.dimensions));
modelHelper.editObject(this.objectID, props);
@ -296,220 +302,18 @@ Doppleganger.prototype = {
} else {
debugPrint('creating Script.setInterval thread @ ~', Doppleganger.TARGET_FPS +'fps');
var timeout = 1000 / Doppleganger.TARGET_FPS;
this._interval = Script.setInterval(bind(this, 'update'), timeout);
this._interval = Script.setInterval(utils.bind(this, 'update'), timeout);
}
}
};
// @function - bind a function to a `this` context
// @param {Object} - the `this` context
// @param {Function|String} - function or method name
function bind(thiz, method) {
method = thiz[method] || method;
return function() {
return method.apply(thiz, arguments);
};
}
// @function - Qt signal polyfill
function signal(template) {
var callbacks = [];
return Object.defineProperties(function() {
var args = [].slice.call(arguments);
callbacks.forEach(function(obj) {
obj.handler.apply(obj.scope, args);
});
}, {
connect: { value: function(scope, handler) {
var callback = {scope: scope, handler: scope[handler] || handler || scope};
if (!callback.handler || !callback.handler.apply) {
throw new Error('invalid arguments to connect:' + [template, scope, handler]);
}
callbacks.push({scope: scope, handler: scope[handler] || handler || scope});
}},
disconnect: { value: function(scope, handler) {
var match = {scope: scope, handler: scope[handler] || handler || scope};
callbacks = callbacks.filter(function(obj) {
return !(obj.scope === match.scope && obj.handler === match.handler);
});
}}
});
}
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
/* eslint-disable */
function assign(target, varArgs) { // .length of function is 2
'use strict';
if (target == null) { // TypeError if undefined or null
throw new TypeError('Cannot convert undefined or null to object');
}
var to = Object(target);
for (var index = 1; index < arguments.length; index++) {
var nextSource = arguments[index];
if (nextSource != null) { // Skip over if undefined or null
for (var nextKey in nextSource) {
// Avoid bugs when hasOwnProperty is shadowed
if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
to[nextKey] = nextSource[nextKey];
}
}
}
}
return to;
}
/* eslint-enable */
// //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign#Polyfill
// @function - debug logging
function log() {
print('doppleganger | ' + [].slice.call(arguments).join(' '));
// eslint-disable-next-line no-console
(typeof console === 'object' ? console.log : print)('doppleganger | ' + [].slice.call(arguments).join(' '));
}
function debugPrint() {
Doppleganger.WANT_DEBUG && log.apply(this, arguments);
}
// -- ADVANCED DEBUGGING --
// @function - Add debug joint indicators / extra debugging info.
// @param {Doppleganger} - existing Doppleganger instance to add controls to
//
// @note:
// * rightclick toggles mirror mode on/off
// * shift-rightclick toggles the debug indicators on/off
// * clicking on an indicator displays the joint name and mirrored joint name in the debug log.
//
// Example use:
// var doppleganger = new Doppleganger();
// Doppleganger.addDebugControls(doppleganger);
Doppleganger.addDebugControls = function(doppleganger) {
DebugControls.COLOR_DEFAULT = { red: 255, blue: 255, green: 255 };
DebugControls.COLOR_SELECTED = { red: 0, blue: 255, green: 0 };
function DebugControls() {
this.enableIndicators = true;
this.selectedJointName = null;
this.debugOverlayIDs = undefined;
this.jointSelected = signal(function(result) {});
}
DebugControls.prototype = {
start: function() {
if (!this.onMousePressEvent) {
this.onMousePressEvent = this._onMousePressEvent;
Controller.mousePressEvent.connect(this, 'onMousePressEvent');
}
},
stop: function() {
this.removeIndicators();
if (this.onMousePressEvent) {
Controller.mousePressEvent.disconnect(this, 'onMousePressEvent');
delete this.onMousePressEvent;
}
},
createIndicators: function(jointNames) {
this.jointNames = jointNames;
return jointNames.map(function(name, i) {
return Overlays.addOverlay('shape', {
shape: 'Icosahedron',
scale: 0.1,
solid: false,
alpha: 0.5
});
});
},
removeIndicators: function() {
if (this.debugOverlayIDs) {
this.debugOverlayIDs.forEach(Overlays.deleteOverlay);
this.debugOverlayIDs = undefined;
}
},
onJointsUpdated: function(overlayID) {
if (!this.enableIndicators) {
return;
}
var jointNames = Overlays.getProperty(overlayID, 'jointNames'),
jointOrientations = Overlays.getProperty(overlayID, 'jointOrientations'),
jointPositions = Overlays.getProperty(overlayID, 'jointPositions'),
selectedIndex = jointNames.indexOf(this.selectedJointName);
if (!this.debugOverlayIDs) {
this.debugOverlayIDs = this.createIndicators(jointNames);
}
// batch all updates into a single call (using the editOverlays({ id: {props...}, ... }) API)
var updatedOverlays = this.debugOverlayIDs.reduce(function(updates, id, i) {
updates[id] = {
position: jointPositions[i],
rotation: jointOrientations[i],
color: i === selectedIndex ? DebugControls.COLOR_SELECTED : DebugControls.COLOR_DEFAULT,
solid: i === selectedIndex
};
return updates;
}, {});
Overlays.editOverlays(updatedOverlays);
},
_onMousePressEvent: function(evt) {
if (!evt.isLeftButton || !this.enableIndicators || !this.debugOverlayIDs) {
return;
}
var ray = Camera.computePickRay(evt.x, evt.y),
hit = Overlays.findRayIntersection(ray, true, this.debugOverlayIDs);
hit.jointIndex = this.debugOverlayIDs.indexOf(hit.overlayID);
hit.jointName = this.jointNames[hit.jointIndex];
this.jointSelected(hit);
}
};
if ('$debugControls' in doppleganger) {
throw new Error('only one set of debug controls can be added per doppleganger');
}
var debugControls = new DebugControls();
doppleganger.$debugControls = debugControls;
function onMousePressEvent(evt) {
if (evt.isRightButton) {
if (evt.isShifted) {
debugControls.enableIndicators = !debugControls.enableIndicators;
if (!debugControls.enableIndicators) {
debugControls.removeIndicators();
}
} else {
doppleganger.mirrored = !doppleganger.mirrored;
}
}
}
doppleganger.activeChanged.connect(function(active) {
if (active) {
debugControls.start();
doppleganger.jointsUpdated.connect(debugControls, 'onJointsUpdated');
Controller.mousePressEvent.connect(onMousePressEvent);
} else {
Controller.mousePressEvent.disconnect(onMousePressEvent);
doppleganger.jointsUpdated.disconnect(debugControls, 'onJointsUpdated');
debugControls.stop();
}
});
debugControls.jointSelected.connect(function(hit) {
debugControls.selectedJointName = hit.jointName;
if (hit.jointIndex < 0) {
return;
}
hit.mirroredJointName = modelHelper.deriveMirroredJointNames([hit.jointName])[0];
log('selected joint:', JSON.stringify(hit, 0, 2));
});
Script.scriptEnding.connect(debugControls, 'removeIndicators');
return doppleganger;
};

View file

@ -0,0 +1,11 @@
all:
@echo "make dist"
dist: doppleganger-a.svg.json doppleganger-i.svg.json dist/app-doppleganger-marketplace.js
@echo "OK"
%.svg.json: %.svg
cat $< | jq -sR '"data:image/svg+xml;xml,"+.' > $@
dist/app-doppleganger-marketplace.js: *.js
./node_modules/.bin/webpack --verbose app-doppleganger-attachments.js $@

View file

@ -8,6 +8,7 @@
//
/* eslint-env commonjs */
/* global console */
// @module model-helper
//
// This module provides ModelReadyWatcher (a helper class for knowing when a model becomes usable inworld) and
@ -18,10 +19,16 @@ var utils = require('./utils.js'),
assert = utils.assert;
module.exports = {
version: '0.0.0',
version: '0.0.1',
ModelReadyWatcher: ModelReadyWatcher
};
function log() {
// eslint-disable-next-line no-console
(typeof console === 'object' ? console.log : print)('model-helper | ' + [].slice.call(arguments).join(' '));
}
log(module.exports.version);
var _objectDeleted = utils.signal(function objectDeleted(objectID){});
// proxy for _objectDeleted that only binds deletion tracking if script actually connects to the unified signal
var objectDeleted = utils.assign(function objectDeleted(objectID){}, {

View file

@ -0,0 +1,5 @@
{
"devDependencies": {
"webpack": "^3.0.0"
}
}

View file

@ -0,0 +1,4 @@
note: to rebuild webpack version:
* install `jq` https://stedolan.github.io/jq (used to encode the icon.svg's as Data URI JSON strings)
* `npm install`
* `make dist`

View file

@ -1,12 +1,20 @@
/* eslint-env commonjs */
/* global console */
module.exports = {
version: '0.0.1',
bind: bind,
signal: signal,
assign: assign,
assert: assert
};
function log() {
// eslint-disable-next-line no-console
(typeof console === 'object' ? console.log : print)('utils | ' + [].slice.call(arguments).join(' '));
}
log(module.exports.version);
// @function - bind a function to a `this` context
// @param {Object} - the `this` context
// @param {Function|String} - function or method name