mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-04-07 23:12:36 +02:00
Merge branch 'master' into feature/secondary-pose-support
This commit is contained in:
commit
98bbe887b9
92 changed files with 3458 additions and 967 deletions
BIN
Test Plan 2.docx
Normal file
BIN
Test Plan 2.docx
Normal file
Binary file not shown.
|
@ -109,7 +109,7 @@ private:
|
|||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
|
||||
AudioGate _audioGate;
|
||||
bool _audioGateOpen { false };
|
||||
bool _audioGateOpen { true };
|
||||
bool _isNoiseGateEnabled { false };
|
||||
|
||||
CodecPluginPointer _codec;
|
||||
|
|
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi8.zip
|
||||
URL_MD5 b01510437ea15527156bc25cdf733bd9
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip
|
||||
URL_MD5 94f4765bdbcd53cd099f349ae031e769
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -22,23 +22,17 @@ macro(GENERATE_INSTALLERS)
|
|||
set(CPACK_PACKAGE_FILE_NAME "HighFidelity-Beta-${BUILD_VERSION}")
|
||||
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
|
||||
set(CPACK_NSIS_PACKAGE_NAME ${_DISPLAY_NAME})
|
||||
if (PR_BUILD)
|
||||
set(CPACK_NSIS_COMPRESSOR "/SOLID bzip2")
|
||||
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
|
||||
|
@ -90,3 +84,4 @@ macro(GENERATE_INSTALLERS)
|
|||
|
||||
include(CPack)
|
||||
endmacro()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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@")
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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}")
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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" }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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" }
|
||||
]
|
||||
}
|
||||
|
|
BIN
interface/resources/images/inspect-icon.png
Normal file
BIN
interface/resources/images/inspect-icon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 27 KiB |
|
@ -33,7 +33,7 @@ Rectangle {
|
|||
|
||||
// only show the title if loaded through a "loader"
|
||||
function showTitle() {
|
||||
return root.parent.objectName == "loader";
|
||||
return (root.parent !== null) && root.parent.objectName == "loader";
|
||||
}
|
||||
|
||||
Column {
|
||||
|
|
|
@ -27,7 +27,7 @@ Rectangle {
|
|||
|
||||
color: "#00000000";
|
||||
border {
|
||||
width: (standalone || Audio.muted || mouseArea.containsMouse) ? 2 : 0;
|
||||
width: mouseArea.containsMouse || mouseArea.containsPress ? 2 : 0;
|
||||
color: colors.border;
|
||||
}
|
||||
|
||||
|
@ -61,7 +61,7 @@ Rectangle {
|
|||
drag.target: dragTarget;
|
||||
}
|
||||
|
||||
Item {
|
||||
QtObject {
|
||||
id: colors;
|
||||
|
||||
readonly property string unmuted: "#FFF";
|
||||
|
@ -72,7 +72,7 @@ Rectangle {
|
|||
readonly property string red: colors.muted;
|
||||
readonly property string fill: "#55000000";
|
||||
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
|
||||
readonly property string icon: (Audio.muted && !mouseArea.containsMouse) ? muted : unmuted;
|
||||
readonly property string icon: Audio.muted ? muted : unmuted;
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -92,10 +92,8 @@ Rectangle {
|
|||
readonly property string unmutedIcon: "../../../icons/tablet-icons/mic-unmute-i.svg";
|
||||
readonly property string mutedIcon: "../../../icons/tablet-icons/mic-mute-i.svg";
|
||||
|
||||
function exclusiveOr(a, b) { return (a || b) && !(a && b); }
|
||||
|
||||
id: image;
|
||||
source: exclusiveOr(Audio.muted, mouseArea.containsMouse) ? mutedIcon : unmutedIcon;
|
||||
source: Audio.muted ? mutedIcon : unmutedIcon;
|
||||
|
||||
width: 30;
|
||||
height: 30;
|
||||
|
@ -118,9 +116,9 @@ Rectangle {
|
|||
Item {
|
||||
id: status;
|
||||
|
||||
readonly property string color: (Audio.muted && !mouseArea.containsMouse) ? colors.muted : colors.unmuted;
|
||||
readonly property string color: Audio.muted ? colors.muted : colors.unmuted;
|
||||
|
||||
visible: Audio.muted || mouseArea.containsMouse;
|
||||
visible: Audio.muted;
|
||||
|
||||
anchors {
|
||||
left: parent.left;
|
||||
|
@ -133,14 +131,14 @@ Rectangle {
|
|||
|
||||
Text {
|
||||
anchors {
|
||||
horizontalCenter: parent.horizontalCenter;
|
||||
horizontalCenter: parent.horizontalCenter;
|
||||
verticalCenter: parent.verticalCenter;
|
||||
}
|
||||
|
||||
color: parent.color;
|
||||
|
||||
text: Audio.muted ? (mouseArea.containsMouse ? "UNMUTE" : "MUTED") : "MUTE";
|
||||
font.pointSize: 12;
|
||||
text: Audio.muted ? "MUTED" : "MUTE";
|
||||
font.pointSize: 12;
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
|
@ -150,7 +148,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
width: 50;
|
||||
height: 4;
|
||||
height: 4;
|
||||
color: parent.color;
|
||||
}
|
||||
|
||||
|
@ -161,7 +159,7 @@ Rectangle {
|
|||
}
|
||||
|
||||
width: 50;
|
||||
height: 4;
|
||||
height: 4;
|
||||
color: parent.color;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -202,5 +202,11 @@ Item {
|
|||
width: 480
|
||||
height: 706
|
||||
|
||||
function setShown(value) {}
|
||||
function setShown(value) {
|
||||
if (value === true) {
|
||||
HMD.openTablet()
|
||||
} else {
|
||||
HMD.closeTablet()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()));
|
||||
|
@ -2323,7 +2342,7 @@ void Application::paintGL() {
|
|||
}
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
if (isHMDMode()) {
|
||||
auto mirrorBodyOrientation = myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f));
|
||||
auto mirrorBodyOrientation = myAvatar->getOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f));
|
||||
|
||||
glm::quat hmdRotation = extractRotation(myAvatar->getHMDSensorMatrix());
|
||||
// Mirror HMD yaw and roll
|
||||
|
@ -2345,7 +2364,7 @@ void Application::paintGL() {
|
|||
+ mirrorBodyOrientation * glm::vec3(0.0f, 0.0f, 1.0f) * MIRROR_FULLSCREEN_DISTANCE * _scaleMirror
|
||||
+ mirrorBodyOrientation * hmdOffset);
|
||||
} else {
|
||||
_myCamera.setOrientation(myAvatar->getWorldAlignedOrientation()
|
||||
_myCamera.setOrientation(myAvatar->getOrientation()
|
||||
* glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
|
||||
+ glm::vec3(0, _raiseMirror * myAvatar->getUniformScale(), 0)
|
||||
|
@ -4482,11 +4501,9 @@ void Application::cameraModeChanged() {
|
|||
void Application::cameraMenuChanged() {
|
||||
auto menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
|
||||
if (isHMDMode()) {
|
||||
menu->setIsOptionChecked(MenuOption::FullscreenMirror, false);
|
||||
menu->setIsOptionChecked(MenuOption::FirstPerson, true);
|
||||
} else if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_MIRROR) {
|
||||
_myCamera.setMode(CAMERA_MODE_MIRROR);
|
||||
getMyAvatar()->reset(false, false, false); // to reset any active MyAvatar::FollowHelpers
|
||||
}
|
||||
} else if (menu->isOptionChecked(MenuOption::FirstPerson)) {
|
||||
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
|
||||
|
@ -5422,6 +5439,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5885,7 +5913,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);
|
||||
|
||||
|
|
|
@ -1299,7 +1299,7 @@ eyeContactTarget MyAvatar::getEyeContactTarget() {
|
|||
}
|
||||
|
||||
glm::vec3 MyAvatar::getDefaultEyePosition() const {
|
||||
return getPosition() + getWorldAlignedOrientation() * Quaternions::Y_180 * _skeletonModel->getDefaultEyeModelPosition();
|
||||
return getPosition() + getOrientation() * Quaternions::Y_180 * _skeletonModel->getDefaultEyeModelPosition();
|
||||
}
|
||||
|
||||
const float SCRIPT_PRIORITY = 1.0f + 1.0f;
|
||||
|
@ -1478,9 +1478,14 @@ void MyAvatar::updateMotors() {
|
|||
motorRotation = getMyHead()->getHeadOrientation();
|
||||
} else {
|
||||
// non-hovering = walking: follow camera twist about vertical but not lift
|
||||
// so we decompose camera's rotation and store the twist part in motorRotation
|
||||
// we decompose camera's rotation and store the twist part in motorRotation
|
||||
// however, we need to perform the decomposition in the avatar-frame
|
||||
// using the local UP axis and then transform back into world-frame
|
||||
glm::quat orientation = getOrientation();
|
||||
glm::quat headOrientation = glm::inverse(orientation) * getMyHead()->getHeadOrientation(); // avatar-frame
|
||||
glm::quat liftRotation;
|
||||
swingTwistDecomposition(getMyHead()->getHeadOrientation(), _worldUpDirection, liftRotation, motorRotation);
|
||||
swingTwistDecomposition(headOrientation, Vectors::UNIT_Y, liftRotation, motorRotation);
|
||||
motorRotation = orientation * motorRotation;
|
||||
}
|
||||
const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
|
@ -1534,11 +1539,31 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
|||
_prePhysicsRoomPose = AnimPose(_sensorToWorldMatrix);
|
||||
}
|
||||
|
||||
// There are a number of possible strategies for this set of tools through endRender, below.
|
||||
void MyAvatar::nextAttitude(glm::vec3 position, glm::quat orientation) {
|
||||
bool success;
|
||||
Transform trans = getTransform(success);
|
||||
if (!success) {
|
||||
qCWarning(interfaceapp) << "Warning -- MyAvatar::nextAttitude failed";
|
||||
return;
|
||||
}
|
||||
trans.setTranslation(position);
|
||||
trans.setRotation(orientation);
|
||||
SpatiallyNestable::setTransform(trans, success);
|
||||
if (!success) {
|
||||
qCWarning(interfaceapp) << "Warning -- MyAvatar::nextAttitude failed";
|
||||
}
|
||||
updateAttitude(orientation);
|
||||
}
|
||||
|
||||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||
glm::vec3 position = getPosition();
|
||||
glm::quat orientation = getOrientation();
|
||||
glm::vec3 position;
|
||||
glm::quat orientation;
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
_characterController.getPositionAndOrientation(position, orientation);
|
||||
} else {
|
||||
position = getPosition();
|
||||
orientation = getOrientation();
|
||||
}
|
||||
nextAttitude(position, orientation);
|
||||
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
|
||||
|
@ -2733,7 +2758,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
|
|||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix,
|
||||
const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
||||
|
||||
if (myAvatar.getHMDLeanRecenterEnabled()) {
|
||||
if (myAvatar.getHMDLeanRecenterEnabled() &&
|
||||
qApp->getCamera().getMode() != CAMERA_MODE_MIRROR) {
|
||||
if (!isActive(Rotation) && (shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||
activate(Rotation);
|
||||
}
|
||||
|
|
|
@ -438,6 +438,7 @@ public:
|
|||
|
||||
void updateMotors();
|
||||
void prepareForPhysicsSimulation();
|
||||
void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
|
||||
void harvestResultsFromPhysicsSimulation(float deltaTime);
|
||||
|
||||
const QString& getCollisionSoundURL() { return _collisionSoundURL; }
|
||||
|
@ -520,7 +521,6 @@ public:
|
|||
Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up.
|
||||
Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; };
|
||||
|
||||
|
||||
public slots:
|
||||
void increaseSize();
|
||||
void decreaseSize();
|
||||
|
|
|
@ -29,7 +29,7 @@ MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) {
|
|||
glm::quat MyHead::getHeadOrientation() const {
|
||||
// NOTE: Head::getHeadOrientation() is not used for orienting the camera "view" while in Oculus mode, so
|
||||
// you may wonder why this code is here. This method will be called while in Oculus mode to determine how
|
||||
// to change the driving direction while in Oculus mode. It is used to support driving toward where you're
|
||||
// to change the driving direction while in Oculus mode. It is used to support driving toward where your
|
||||
// head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not
|
||||
// always the same.
|
||||
|
||||
|
@ -39,7 +39,7 @@ glm::quat MyHead::getHeadOrientation() const {
|
|||
return headPose.rotation * Quaternions::Y_180;
|
||||
}
|
||||
|
||||
return myAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
|
||||
return myAvatar->getOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
|
||||
}
|
||||
|
||||
void MyHead::simulate(float deltaTime) {
|
||||
|
|
271
interface/src/ui/overlays/ContextOverlayInterface.cpp
Normal file
271
interface/src/ui/overlays/ContextOverlayInterface.cpp
Normal 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);
|
||||
}
|
||||
}
|
85
interface/src/ui/overlays/ContextOverlayInterface.h
Normal file
85
interface/src/ui/overlays/ContextOverlayInterface.h
Normal 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
|
|
@ -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")
|
|
@ -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
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -192,6 +192,7 @@ void Web3DOverlay::loadSourceURL() {
|
|||
_webSurface->getSurfaceContext()->setContextProperty("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
|
||||
_webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../");
|
||||
|
||||
tabletScriptingInterface->setQmlTabletRoot("com.highfidelity.interface.tablet.system", _webSurface.data());
|
||||
|
||||
// mark the TabletProxy object as cpp ownership.
|
||||
|
@ -596,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();
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -364,7 +364,7 @@ private:
|
|||
AudioIOStats _stats;
|
||||
|
||||
AudioGate* _audioGate { nullptr };
|
||||
bool _audioGateOpen { false };
|
||||
bool _audioGateOpen { true };
|
||||
|
||||
AudioPositionGetter _positionGetter;
|
||||
AudioOrientationGetter _orientationGetter;
|
||||
|
|
|
@ -151,11 +151,6 @@ glm::vec3 Avatar::getNeckPosition() const {
|
|||
return _skeletonModel->getNeckPosition(neckPosition) ? neckPosition : getPosition();
|
||||
}
|
||||
|
||||
|
||||
glm::quat Avatar::getWorldAlignedOrientation () const {
|
||||
return computeRotationFromBodyToWorldUp() * getOrientation();
|
||||
}
|
||||
|
||||
AABox Avatar::getBounds() const {
|
||||
if (!_skeletonModel->isRenderable() || _skeletonModel->needsFixupInScene()) {
|
||||
// approximately 2m tall, scaled to user request.
|
||||
|
@ -436,6 +431,11 @@ void Avatar::slamPosition(const glm::vec3& newPosition) {
|
|||
_lastVelocity = glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void Avatar::updateAttitude(const glm::quat& orientation) {
|
||||
_skeletonModel->updateAttitude(orientation);
|
||||
_worldUpDirection = orientation * Vectors::UNIT_Y;
|
||||
}
|
||||
|
||||
void Avatar::applyPositionDelta(const glm::vec3& delta) {
|
||||
setPosition(getPosition() + delta);
|
||||
_positionDeltaAccumulator += delta;
|
||||
|
@ -628,22 +628,6 @@ void Avatar::render(RenderArgs* renderArgs) {
|
|||
}
|
||||
}
|
||||
|
||||
glm::quat Avatar::computeRotationFromBodyToWorldUp(float proportion) const {
|
||||
glm::quat orientation = getOrientation();
|
||||
glm::vec3 currentUp = orientation * IDENTITY_UP;
|
||||
float angle = acosf(glm::clamp(glm::dot(currentUp, _worldUpDirection), -1.0f, 1.0f));
|
||||
if (angle < EPSILON) {
|
||||
return glm::quat();
|
||||
}
|
||||
glm::vec3 axis;
|
||||
if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis
|
||||
axis = orientation * IDENTITY_RIGHT;
|
||||
} else {
|
||||
axis = glm::normalize(glm::cross(currentUp, _worldUpDirection));
|
||||
}
|
||||
return glm::angleAxis(angle * proportion, axis);
|
||||
}
|
||||
|
||||
void Avatar::fixupModelsInScene(const render::ScenePointer& scene) {
|
||||
_attachmentsToDelete.clear();
|
||||
|
||||
|
@ -915,17 +899,34 @@ glm::vec3 Avatar::getDefaultJointTranslation(int index) const {
|
|||
}
|
||||
|
||||
glm::quat Avatar::getAbsoluteDefaultJointRotationInObjectFrame(int index) const {
|
||||
glm::quat rotation;
|
||||
glm::quat rot = _skeletonModel->getRig().getAnimSkeleton()->getAbsoluteDefaultPose(index).rot();
|
||||
return Quaternions::Y_180 * rot;
|
||||
// To make this thread safe, we hold onto the model by smart ptr, which prevents it from being deleted while we are accessing it.
|
||||
auto model = getSkeletonModel();
|
||||
if (model) {
|
||||
auto skeleton = model->getRig().getAnimSkeleton();
|
||||
if (skeleton && index >= 0 && index < skeleton->getNumJoints()) {
|
||||
// The rotation part of the geometry-to-rig transform is always identity so we can skip it.
|
||||
// Y_180 is to convert from rig-frame into avatar-frame
|
||||
return Quaternions::Y_180 * skeleton->getAbsoluteDefaultPose(index).rot();
|
||||
}
|
||||
}
|
||||
return Quaternions::Y_180;
|
||||
}
|
||||
|
||||
glm::vec3 Avatar::getAbsoluteDefaultJointTranslationInObjectFrame(int index) const {
|
||||
glm::vec3 translation;
|
||||
const Rig& rig = _skeletonModel->getRig();
|
||||
glm::vec3 trans = rig.getAnimSkeleton()->getAbsoluteDefaultPose(index).trans();
|
||||
glm::mat4 y180Mat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3());
|
||||
return transformPoint(y180Mat * rig.getGeometryToRigTransform(), trans);
|
||||
// To make this thread safe, we hold onto the model by smart ptr, which prevents it from being deleted while we are accessing it.
|
||||
auto model = getSkeletonModel();
|
||||
if (model) {
|
||||
const Rig& rig = model->getRig();
|
||||
auto skeleton = rig.getAnimSkeleton();
|
||||
if (skeleton && index >= 0 && index < skeleton->getNumJoints()) {
|
||||
// trans is in geometry frame.
|
||||
glm::vec3 trans = skeleton->getAbsoluteDefaultPose(index).trans();
|
||||
// Y_180 is to convert from rig-frame into avatar-frame
|
||||
glm::mat4 geomToAvatarMat = Matrices::Y_180 * rig.getGeometryToRigTransform();
|
||||
return transformPoint(geomToAvatarMat, trans);
|
||||
}
|
||||
}
|
||||
return Vectors::ZERO;
|
||||
}
|
||||
|
||||
glm::quat Avatar::getAbsoluteJointRotationInObjectFrame(int index) const {
|
||||
|
@ -1401,14 +1402,14 @@ glm::quat Avatar::getUncachedRightPalmRotation() const {
|
|||
return rightPalmRotation;
|
||||
}
|
||||
|
||||
void Avatar::setPosition(const glm::vec3& position) {
|
||||
AvatarData::setPosition(position);
|
||||
updateAttitude();
|
||||
void Avatar::setPositionViaScript(const glm::vec3& position) {
|
||||
setPosition(position);
|
||||
updateAttitude(getOrientation());
|
||||
}
|
||||
|
||||
void Avatar::setOrientation(const glm::quat& orientation) {
|
||||
AvatarData::setOrientation(orientation);
|
||||
updateAttitude();
|
||||
void Avatar::setOrientationViaScript(const glm::quat& orientation) {
|
||||
setOrientation(orientation);
|
||||
updateAttitude(orientation);
|
||||
}
|
||||
|
||||
void Avatar::updatePalms() {
|
||||
|
|
|
@ -112,8 +112,6 @@ public:
|
|||
const Head* getHead() const { return static_cast<const Head*>(_headData); }
|
||||
Head* getHead() { return static_cast<Head*>(_headData); }
|
||||
|
||||
glm::quat getWorldAlignedOrientation() const;
|
||||
|
||||
AABox getBounds() const;
|
||||
|
||||
/// Returns the distance to use as a LOD parameter.
|
||||
|
@ -184,7 +182,7 @@ public:
|
|||
void scaleVectorRelativeToPosition(glm::vec3 &positionToScale) const;
|
||||
|
||||
void slamPosition(const glm::vec3& position);
|
||||
virtual void updateAttitude() override { _skeletonModel->updateAttitude(); }
|
||||
virtual void updateAttitude(const glm::quat& orientation) override;
|
||||
|
||||
// Call this when updating Avatar position with a delta. This will allow us to
|
||||
// _accurately_ measure position changes and compute the resulting velocity
|
||||
|
@ -197,10 +195,8 @@ public:
|
|||
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
|
||||
float computeMass();
|
||||
|
||||
using SpatiallyNestable::setPosition;
|
||||
virtual void setPosition(const glm::vec3& position) override;
|
||||
using SpatiallyNestable::setOrientation;
|
||||
virtual void setOrientation(const glm::quat& orientation) override;
|
||||
void setPositionViaScript(const glm::vec3& position) override;
|
||||
void setOrientationViaScript(const glm::quat& orientation) override;
|
||||
|
||||
// these call through to the SpatiallyNestable versions, but they are here to expose these to javascript.
|
||||
Q_INVOKABLE virtual const QUuid getParentID() const override { return SpatiallyNestable::getParentID(); }
|
||||
|
@ -240,7 +236,7 @@ public:
|
|||
bool hasNewJointData() const { return _hasNewJointData; }
|
||||
|
||||
float getBoundingRadius() const;
|
||||
|
||||
|
||||
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
||||
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
||||
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
|
||||
|
@ -303,7 +299,6 @@ protected:
|
|||
|
||||
glm::vec3 getBodyRightDirection() const { return getOrientation() * IDENTITY_RIGHT; }
|
||||
glm::vec3 getBodyUpDirection() const { return getOrientation() * IDENTITY_UP; }
|
||||
glm::quat computeRotationFromBodyToWorldUp(float proportion = 1.0f) const;
|
||||
void measureMotionDerivatives(float deltaTime);
|
||||
|
||||
float getSkeletonHeight() const;
|
||||
|
|
|
@ -118,16 +118,16 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
_rig.updateFromEyeParameters(eyeParams);
|
||||
}
|
||||
|
||||
void SkeletonModel::updateAttitude() {
|
||||
void SkeletonModel::updateAttitude(const glm::quat& orientation) {
|
||||
setTranslation(_owningAvatar->getSkeletonPosition());
|
||||
setRotation(_owningAvatar->getOrientation() * Quaternions::Y_180);
|
||||
setRotation(orientation * Quaternions::Y_180);
|
||||
setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningAvatar->getScale());
|
||||
}
|
||||
|
||||
// Called by Avatar::simulate after it has set the joint states (fullUpdate true if changed),
|
||||
// but just before head has been simulated.
|
||||
void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
||||
updateAttitude();
|
||||
updateAttitude(_owningAvatar->getOrientation());
|
||||
if (fullUpdate) {
|
||||
setBlendshapeCoefficients(_owningAvatar->getHead()->getSummedBlendshapeCoefficients());
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ public:
|
|||
|
||||
void simulate(float deltaTime, bool fullUpdate = true) override;
|
||||
void updateRig(float deltaTime, glm::mat4 parentTransform) override;
|
||||
void updateAttitude();
|
||||
void updateAttitude(const glm::quat& orientation);
|
||||
|
||||
/// Returns the index of the left hand joint, or -1 if not found.
|
||||
int getLeftHandJointIndex() const { return isActive() ? getFBXGeometry().leftHandJointIndex : -1; }
|
||||
|
|
|
@ -91,9 +91,6 @@ AvatarData::AvatarData() :
|
|||
_targetVelocity(0.0f),
|
||||
_density(DEFAULT_AVATAR_DENSITY)
|
||||
{
|
||||
setBodyPitch(0.0f);
|
||||
setBodyYaw(-90.0f);
|
||||
setBodyRoll(0.0f);
|
||||
}
|
||||
|
||||
AvatarData::~AvatarData() {
|
||||
|
@ -110,23 +107,6 @@ const QUrl& AvatarData::defaultFullAvatarModelUrl() {
|
|||
return _defaultFullAvatarModelUrl;
|
||||
}
|
||||
|
||||
// There are a number of possible strategies for this set of tools through endRender, below.
|
||||
void AvatarData::nextAttitude(glm::vec3 position, glm::quat orientation) {
|
||||
bool success;
|
||||
Transform trans = getTransform(success);
|
||||
if (!success) {
|
||||
qCWarning(avatars) << "Warning -- AvatarData::nextAttitude failed";
|
||||
return;
|
||||
}
|
||||
trans.setTranslation(position);
|
||||
trans.setRotation(orientation);
|
||||
SpatiallyNestable::setTransform(trans, success);
|
||||
if (!success) {
|
||||
qCWarning(avatars) << "Warning -- AvatarData::nextAttitude failed";
|
||||
}
|
||||
updateAttitude();
|
||||
}
|
||||
|
||||
void AvatarData::setTargetScale(float targetScale) {
|
||||
auto newValue = glm::clamp(targetScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
|
||||
if (_targetScale != newValue) {
|
||||
|
@ -2100,6 +2080,7 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
currentBasis = std::make_shared<Transform>(Transform::fromJson(json[JSON_AVATAR_BASIS]));
|
||||
}
|
||||
|
||||
glm::quat orientation;
|
||||
if (json.contains(JSON_AVATAR_RELATIVE)) {
|
||||
// During playback you can either have the recording basis set to the avatar current state
|
||||
// meaning that all playback is relative to this avatars starting position, or
|
||||
|
@ -2111,12 +2092,14 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
auto relativeTransform = Transform::fromJson(json[JSON_AVATAR_RELATIVE]);
|
||||
auto worldTransform = currentBasis->worldTransform(relativeTransform);
|
||||
setPosition(worldTransform.getTranslation());
|
||||
setOrientation(worldTransform.getRotation());
|
||||
orientation = worldTransform.getRotation();
|
||||
} else {
|
||||
// We still set the position in the case that there is no movement.
|
||||
setPosition(currentBasis->getTranslation());
|
||||
setOrientation(currentBasis->getRotation());
|
||||
orientation = currentBasis->getRotation();
|
||||
}
|
||||
setOrientation(orientation);
|
||||
updateAttitude(orientation);
|
||||
|
||||
// Do after avatar orientation because head look-at needs avatar orientation.
|
||||
if (json.contains(JSON_AVATAR_HEAD)) {
|
||||
|
@ -2234,11 +2217,11 @@ void AvatarData::setBodyRoll(float bodyRoll) {
|
|||
setOrientation(glm::quat(glm::radians(eulerAngles)));
|
||||
}
|
||||
|
||||
void AvatarData::setPosition(const glm::vec3& position) {
|
||||
void AvatarData::setPositionViaScript(const glm::vec3& position) {
|
||||
SpatiallyNestable::setPosition(position);
|
||||
}
|
||||
|
||||
void AvatarData::setOrientation(const glm::quat& orientation) {
|
||||
void AvatarData::setOrientationViaScript(const glm::quat& orientation) {
|
||||
SpatiallyNestable::setOrientation(orientation);
|
||||
}
|
||||
|
||||
|
|
|
@ -351,14 +351,14 @@ public:
|
|||
class AvatarData : public QObject, public SpatiallyNestable {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition)
|
||||
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPositionViaScript)
|
||||
Q_PROPERTY(float scale READ getTargetScale WRITE setTargetScale)
|
||||
Q_PROPERTY(glm::vec3 handPosition READ getHandPosition WRITE setHandPosition)
|
||||
Q_PROPERTY(float bodyYaw READ getBodyYaw WRITE setBodyYaw)
|
||||
Q_PROPERTY(float bodyPitch READ getBodyPitch WRITE setBodyPitch)
|
||||
Q_PROPERTY(float bodyRoll READ getBodyRoll WRITE setBodyRoll)
|
||||
|
||||
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientation)
|
||||
Q_PROPERTY(glm::quat orientation READ getOrientation WRITE setOrientationViaScript)
|
||||
Q_PROPERTY(glm::quat headOrientation READ getHeadOrientation WRITE setHeadOrientation)
|
||||
Q_PROPERTY(float headPitch READ getHeadPitch WRITE setHeadPitch)
|
||||
Q_PROPERTY(float headYaw READ getHeadYaw WRITE setHeadYaw)
|
||||
|
@ -440,13 +440,10 @@ public:
|
|||
float getBodyRoll() const;
|
||||
void setBodyRoll(float bodyRoll);
|
||||
|
||||
using SpatiallyNestable::setPosition;
|
||||
virtual void setPosition(const glm::vec3& position) override;
|
||||
using SpatiallyNestable::setOrientation;
|
||||
virtual void setOrientation(const glm::quat& orientation) override;
|
||||
virtual void setPositionViaScript(const glm::vec3& position);
|
||||
virtual void setOrientationViaScript(const glm::quat& orientation);
|
||||
|
||||
void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
|
||||
virtual void updateAttitude() {} // Tell skeleton mesh about changes
|
||||
virtual void updateAttitude(const glm::quat& orientation) {}
|
||||
|
||||
glm::quat getHeadOrientation() const {
|
||||
lazyInitHeadData();
|
||||
|
|
|
@ -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"),
|
||||
|
||||
|
|
|
@ -158,6 +158,23 @@ enum class Action {
|
|||
LEFT_TOE_BASE,
|
||||
RIGHT_TOE_BASE,
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -82,6 +82,80 @@ bool RenderableShapeEntityItem::isTransparent() {
|
|||
}
|
||||
}
|
||||
|
||||
void RenderableShapeEntityItem::computeShapeInfo(ShapeInfo& info) {
|
||||
|
||||
// This will be called whenever DIRTY_SHAPE flag (set by dimension change, etc)
|
||||
// is set.
|
||||
|
||||
const glm::vec3 entityDimensions = getDimensions();
|
||||
|
||||
switch (_shape){
|
||||
case entity::Shape::Quad:
|
||||
case entity::Shape::Cube: {
|
||||
_collisionShapeType = SHAPE_TYPE_BOX;
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Sphere: {
|
||||
|
||||
float diameter = entityDimensions.x;
|
||||
const float MIN_DIAMETER = 0.001f;
|
||||
const float MIN_RELATIVE_SPHERICAL_ERROR = 0.001f;
|
||||
if (diameter > MIN_DIAMETER
|
||||
&& fabsf(diameter - entityDimensions.y) / diameter < MIN_RELATIVE_SPHERICAL_ERROR
|
||||
&& fabsf(diameter - entityDimensions.z) / diameter < MIN_RELATIVE_SPHERICAL_ERROR) {
|
||||
|
||||
_collisionShapeType = SHAPE_TYPE_SPHERE;
|
||||
}
|
||||
else {
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Cylinder: {
|
||||
_collisionShapeType = SHAPE_TYPE_CYLINDER_Y;
|
||||
// TODO WL21389: determine if rotation is axis-aligned
|
||||
//const Transform::Quat & rot = _transform.getRotation();
|
||||
|
||||
// TODO WL21389: some way to tell apart SHAPE_TYPE_CYLINDER_Y, _X, _Z based on rotation and
|
||||
// hull ( or dimensions, need circular cross section)
|
||||
// Should allow for minor variance along axes?
|
||||
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Triangle:
|
||||
case entity::Shape::Hexagon:
|
||||
case entity::Shape::Octagon:
|
||||
case entity::Shape::Circle:
|
||||
case entity::Shape::Tetrahedron:
|
||||
case entity::Shape::Octahedron:
|
||||
case entity::Shape::Dodecahedron:
|
||||
case entity::Shape::Icosahedron:
|
||||
case entity::Shape::Cone: {
|
||||
//TODO WL21389: SHAPE_TYPE_SIMPLE_HULL and pointCollection (later)
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
break;
|
||||
case entity::Shape::Torus:
|
||||
{
|
||||
// Not in GeometryCache::buildShapes, unsupported.
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
//TODO WL21389: SHAPE_TYPE_SIMPLE_HULL and pointCollection (later if desired support)
|
||||
}
|
||||
break;
|
||||
default:{
|
||||
_collisionShapeType = SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
EntityItem::computeShapeInfo(info);
|
||||
}
|
||||
|
||||
// This value specifes how the shape should be treated by physics calculations.
|
||||
ShapeType RenderableShapeEntityItem::getShapeType() const {
|
||||
return _collisionShapeType;
|
||||
}
|
||||
|
||||
void RenderableShapeEntityItem::render(RenderArgs* args) {
|
||||
PerformanceTimer perfTimer("RenderableShapeEntityItem::render");
|
||||
//Q_ASSERT(getType() == EntityTypes::Shape);
|
||||
|
|
|
@ -28,9 +28,18 @@ public:
|
|||
|
||||
bool isTransparent() override;
|
||||
|
||||
virtual void computeShapeInfo(ShapeInfo& info) override;
|
||||
ShapeType getShapeType() const override;
|
||||
|
||||
|
||||
private:
|
||||
std::unique_ptr<Procedural> _procedural { nullptr };
|
||||
|
||||
//! This is SHAPE_TYPE_ELLIPSOID rather than SHAPE_TYPE_NONE to maintain
|
||||
//! prior functionality where new or unsupported shapes are treated as
|
||||
//! ellipsoids.
|
||||
ShapeType _collisionShapeType{ ShapeType::SHAPE_TYPE_ELLIPSOID };
|
||||
|
||||
SIMPLE_RENDERABLE();
|
||||
};
|
||||
|
||||
|
|
|
@ -54,6 +54,7 @@ void EntityEditPacketSender::queueEditAvatarEntityMessage(PacketType type,
|
|||
EntityItemProperties entityProperties = entity->getProperties();
|
||||
entityProperties.merge(properties);
|
||||
|
||||
std::lock_guard<std::mutex> lock(_mutex);
|
||||
QScriptValue scriptProperties = EntityItemNonDefaultPropertiesToScriptValue(&_scriptEngine, entityProperties);
|
||||
QVariant variantProperties = scriptProperties.toVariant();
|
||||
QJsonDocument jsonProperties = QJsonDocument::fromVariant(variantProperties);
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
|
||||
#include <OctreeEditPacketSender.h>
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include "EntityItem.h"
|
||||
#include "AvatarData.h"
|
||||
|
||||
|
@ -49,6 +51,7 @@ private:
|
|||
EntityItemID entityItemID, const EntityItemProperties& properties);
|
||||
|
||||
private:
|
||||
std::mutex _mutex;
|
||||
AvatarData* _myAvatar { nullptr };
|
||||
QScriptEngine _scriptEngine;
|
||||
};
|
||||
|
|
|
@ -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([&] {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
}
|
|
@ -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
|
|
@ -160,12 +160,6 @@ void ShapeEntityItem::appendSubclassData(OctreePacketData* packetData, EncodeBit
|
|||
APPEND_ENTITY_PROPERTY(PROP_ALPHA, getAlpha());
|
||||
}
|
||||
|
||||
// This value specifes how the shape should be treated by physics calculations.
|
||||
// For now, all polys will act as spheres
|
||||
ShapeType ShapeEntityItem::getShapeType() const {
|
||||
return (_shape == entity::Shape::Cube) ? SHAPE_TYPE_BOX : SHAPE_TYPE_ELLIPSOID;
|
||||
}
|
||||
|
||||
void ShapeEntityItem::setColor(const rgbColor& value) {
|
||||
memcpy(_color, value, sizeof(rgbColor));
|
||||
}
|
||||
|
@ -223,10 +217,12 @@ bool ShapeEntityItem::findDetailedRayIntersection(const glm::vec3& origin, const
|
|||
void ShapeEntityItem::debugDump() const {
|
||||
quint64 now = usecTimestampNow();
|
||||
qCDebug(entities) << "SHAPE EntityItem id:" << getEntityItemID() << "---------------------------------------------";
|
||||
qCDebug(entities) << " shape:" << stringFromShape(_shape);
|
||||
qCDebug(entities) << " name:" << _name;
|
||||
qCDebug(entities) << " shape:" << stringFromShape(_shape) << " (EnumId: " << _shape << " )";
|
||||
qCDebug(entities) << " color:" << _color[0] << "," << _color[1] << "," << _color[2];
|
||||
qCDebug(entities) << " position:" << debugTreeVector(getPosition());
|
||||
qCDebug(entities) << " dimensions:" << debugTreeVector(getDimensions());
|
||||
qCDebug(entities) << " getLastEdited:" << debugTime(getLastEdited(), now);
|
||||
qCDebug(entities) << "SHAPE EntityItem Ptr:" << this;
|
||||
}
|
||||
|
||||
|
|
|
@ -84,7 +84,6 @@ public:
|
|||
QColor getQColor() const;
|
||||
void setColor(const QColor& value);
|
||||
|
||||
ShapeType getShapeType() const override;
|
||||
bool shouldBePhysical() const override { return !isDead(); }
|
||||
|
||||
bool supportsDetailedRayIntersection() const override;
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -445,7 +445,7 @@ void CharacterController::handleChangedCollisionGroup() {
|
|||
|
||||
void CharacterController::updateUpAxis(const glm::quat& rotation) {
|
||||
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
|
||||
if (_state != State::Hover && _rigidBody) {
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
#include "BulletUtil.h"
|
||||
|
||||
// These are the same normalized directions used by the btShapeHull class.
|
||||
// 12 points for the face centers of a duodecohedron plus another 30 points
|
||||
// 12 points for the face centers of a dodecahedron plus another 30 points
|
||||
// for the midpoints the edges, for a total of 42.
|
||||
const uint32_t NUM_UNIT_SPHERE_DIRECTIONS = 42;
|
||||
static const btVector3 _unitSphereDirections[NUM_UNIT_SPHERE_DIRECTIONS] = {
|
||||
|
@ -288,6 +288,38 @@ const btCollisionShape* ShapeFactory::createShapeFromInfo(const ShapeInfo& info)
|
|||
shape = new btCapsuleShape(radius, height);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CAPSULE_X: {
|
||||
glm::vec3 halfExtents = info.getHalfExtents();
|
||||
float radius = halfExtents.y;
|
||||
float height = 2.0f * halfExtents.x;
|
||||
shape = new btCapsuleShapeX(radius, height);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CAPSULE_Z: {
|
||||
glm::vec3 halfExtents = info.getHalfExtents();
|
||||
float radius = halfExtents.x;
|
||||
float height = 2.0f * halfExtents.z;
|
||||
shape = new btCapsuleShapeZ(radius, height);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CYLINDER_X: {
|
||||
const glm::vec3 halfExtents = info.getHalfExtents();
|
||||
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
|
||||
shape = new btCylinderShapeX(btHalfExtents);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CYLINDER_Z: {
|
||||
const glm::vec3 halfExtents = info.getHalfExtents();
|
||||
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
|
||||
shape = new btCylinderShapeZ(btHalfExtents);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_CYLINDER_Y: {
|
||||
const glm::vec3 halfExtents = info.getHalfExtents();
|
||||
const btVector3 btHalfExtents(halfExtents.x, halfExtents.y, halfExtents.z);
|
||||
shape = new btCylinderShape(btHalfExtents);
|
||||
}
|
||||
break;
|
||||
case SHAPE_TYPE_COMPOUND:
|
||||
case SHAPE_TYPE_SIMPLE_HULL: {
|
||||
const ShapeInfo::PointCollection& pointCollection = info.getPointCollection();
|
||||
|
|
|
@ -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
|
||||
|
@ -112,6 +118,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;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ void ShapeInfo::clear() {
|
|||
}
|
||||
|
||||
void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString url) {
|
||||
//TODO WL21389: Does this need additional cases and handling added?
|
||||
_url = "";
|
||||
_type = type;
|
||||
setHalfExtents(halfExtents);
|
||||
|
@ -55,6 +56,9 @@ void ShapeInfo::setParams(ShapeType type, const glm::vec3& halfExtents, QString
|
|||
}
|
||||
|
||||
void ShapeInfo::setBox(const glm::vec3& halfExtents) {
|
||||
//TODO WL21389: Should this pointlist clearance added in case
|
||||
// this is a re-purposed instance?
|
||||
// See https://github.com/highfidelity/hifi/pull/11024#discussion_r128885491
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_BOX;
|
||||
setHalfExtents(halfExtents);
|
||||
|
@ -62,6 +66,7 @@ void ShapeInfo::setBox(const glm::vec3& halfExtents) {
|
|||
}
|
||||
|
||||
void ShapeInfo::setSphere(float radius) {
|
||||
//TODO WL21389: See comment in setBox regarding clearance
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_SPHERE;
|
||||
radius = glm::max(radius, MIN_HALF_EXTENT);
|
||||
|
@ -70,12 +75,14 @@ void ShapeInfo::setSphere(float radius) {
|
|||
}
|
||||
|
||||
void ShapeInfo::setPointCollection(const ShapeInfo::PointCollection& pointCollection) {
|
||||
//TODO WL21389: May need to skip resetting type here.
|
||||
_pointCollection = pointCollection;
|
||||
_type = (_pointCollection.size() > 0) ? SHAPE_TYPE_COMPOUND : SHAPE_TYPE_NONE;
|
||||
_doubleHashKey.clear();
|
||||
}
|
||||
|
||||
void ShapeInfo::setCapsuleY(float radius, float halfHeight) {
|
||||
//TODO WL21389: See comment in setBox regarding clearance
|
||||
_url = "";
|
||||
_type = SHAPE_TYPE_CAPSULE_Y;
|
||||
radius = glm::max(radius, MIN_HALF_EXTENT);
|
||||
|
@ -117,6 +124,7 @@ int ShapeInfo::getLargestSubshapePointCount() const {
|
|||
}
|
||||
|
||||
float ShapeInfo::computeVolume() const {
|
||||
//TODO WL21389: Add support for other ShapeTypes( CYLINDER_X, CYLINDER_Y, etc).
|
||||
const float DEFAULT_VOLUME = 1.0f;
|
||||
float volume = DEFAULT_VOLUME;
|
||||
switch(_type) {
|
||||
|
@ -136,7 +144,10 @@ float ShapeInfo::computeVolume() const {
|
|||
}
|
||||
case SHAPE_TYPE_CAPSULE_Y: {
|
||||
float radius = _halfExtents.x;
|
||||
volume = PI * radius * radius * (2.0f * (_halfExtents.y - _halfExtents.x) + 4.0f * radius / 3.0f);
|
||||
// Need to offset halfExtents.y by x to account for the system treating
|
||||
// the y extent of the capsule as the cylindrical height + spherical radius.
|
||||
float cylinderHeight = 2.0f * (_halfExtents.y - _halfExtents.x);
|
||||
volume = PI * radius * radius * (cylinderHeight + 4.0f * radius / 3.0f);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
@ -147,6 +158,7 @@ float ShapeInfo::computeVolume() const {
|
|||
}
|
||||
|
||||
bool ShapeInfo::contains(const glm::vec3& point) const {
|
||||
//TODO WL21389: Add support for other ShapeTypes like Ellipsoid/Compound.
|
||||
switch(_type) {
|
||||
case SHAPE_TYPE_SPHERE:
|
||||
return glm::length(point) <= _halfExtents.x;
|
||||
|
@ -191,6 +203,7 @@ bool ShapeInfo::contains(const glm::vec3& point) const {
|
|||
}
|
||||
|
||||
const DoubleHashKey& ShapeInfo::getHash() const {
|
||||
//TODO WL21389: Need to include the pointlist for SIMPLE_HULL in hash
|
||||
// NOTE: we cache the key so we only ever need to compute it once for any valid ShapeInfo instance.
|
||||
if (_doubleHashKey.isNull() && _type != SHAPE_TYPE_NONE) {
|
||||
bool useOffset = glm::length2(_offset) > MIN_SHAPE_OFFSET * MIN_SHAPE_OFFSET;
|
||||
|
|
|
@ -127,7 +127,7 @@ private:
|
|||
glm::mat4 _projection;
|
||||
|
||||
// derived
|
||||
glm::vec3 _position;
|
||||
glm::vec3 _position { 0.0f, 0.0f, 0.0f };
|
||||
glm::quat _orientation;
|
||||
bool _isKeepLookingAt{ false };
|
||||
glm::vec3 _lookingAt;
|
||||
|
|
|
@ -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();
|
||||
|
@ -228,16 +231,23 @@ void TabletProxy::setToolbarMode(bool toolbarMode) {
|
|||
connect(tabletRootWindow, &QmlWindowClass::fromQml, this, &TabletProxy::fromQml);
|
||||
} else {
|
||||
removeButtonsFromToolbar();
|
||||
addButtonsToHomeScreen();
|
||||
|
||||
if (_currentPathLoaded == TABLET_SOURCE_URL) {
|
||||
addButtonsToHomeScreen();
|
||||
} 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();
|
||||
_desktopWindow = nullptr;
|
||||
}
|
||||
}
|
||||
loadHomeScreen(true);
|
||||
emit screenChanged(QVariant("Home"), QVariant(TABLET_SOURCE_URL));
|
||||
}
|
||||
|
||||
static void addButtonProxyToQmlTablet(QQuickItem* qmlTablet, TabletButtonProxy* buttonProxy) {
|
||||
|
@ -313,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;
|
||||
|
@ -362,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;
|
||||
|
@ -463,14 +487,14 @@ void TabletProxy::loadQMLSource(const QVariant& path) {
|
|||
}
|
||||
|
||||
if (root) {
|
||||
if (_state != State::QML) {
|
||||
removeButtonsFromHomeScreen();
|
||||
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
|
||||
_state = State::QML;
|
||||
removeButtonsFromHomeScreen(); //works only in Tablet
|
||||
QMetaObject::invokeMethod(root, "loadSource", Q_ARG(const QVariant&, path));
|
||||
_state = State::QML;
|
||||
if (path != _currentPathLoaded) {
|
||||
emit screenChanged(QVariant("QML"), path);
|
||||
_currentPathLoaded = path;
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
}
|
||||
_currentPathLoaded = path;
|
||||
QMetaObject::invokeMethod(root, "setShown", Q_ARG(const QVariant&, QVariant(true)));
|
||||
} else {
|
||||
qCDebug(uiLogging) << "tablet cannot load QML because _qmlTabletRoot is null";
|
||||
}
|
||||
|
@ -483,6 +507,11 @@ bool TabletProxy::pushOntoStack(const QVariant& path) {
|
|||
return result;
|
||||
}
|
||||
|
||||
//set landscape off when pushing menu items while in Create mode
|
||||
if (_landscape) {
|
||||
setLandscape(false);
|
||||
}
|
||||
|
||||
QObject* root = nullptr;
|
||||
if (!_toolbarMode && _qmlTabletRoot) {
|
||||
root = _qmlTabletRoot;
|
||||
|
@ -501,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() {
|
||||
|
|
|
@ -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*);
|
||||
|
|
|
@ -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 ()
|
||||
|
|
30
scripts/developer/tests/basicEntityTest/shapeSpawner.js
Normal file
30
scripts/developer/tests/basicEntityTest/shapeSpawner.js
Normal file
|
@ -0,0 +1,30 @@
|
|||
// compute a position to create the object relative to avatar
|
||||
var forwardOffset = Vec3.multiply(2.0, Quat.getFront(MyAvatar.orientation));
|
||||
var objectPosition = Vec3.sum(MyAvatar.position, forwardOffset);
|
||||
|
||||
var LIFETIME = 1800; //seconds
|
||||
var DIM_HEIGHT = 1, DIM_WIDTH = 1, DIM_DEPTH = 1;
|
||||
var COLOR_R = 100, COLOR_G = 10, COLOR_B = 200;
|
||||
|
||||
var properties = {
|
||||
name: "ShapeSpawnTest",
|
||||
type: "Shape",
|
||||
shape: "Cylinder",
|
||||
dimensions: {x: DIM_WIDTH, y: DIM_HEIGHT, z: DIM_DEPTH},
|
||||
color: {red: COLOR_R, green: COLOR_G, blue: COLOR_B},
|
||||
position: objectPosition,
|
||||
lifetime: LIFETIME,
|
||||
};
|
||||
|
||||
// create the object
|
||||
var entityId = Entities.addEntity(properties);
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(entityId);
|
||||
}
|
||||
|
||||
// delete the object when this script is stopped
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
|
||||
|
|
@ -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
|
|
@ -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 {
|
||||
|
|
|
@ -43,6 +43,7 @@
|
|||
var speechBubbleOffset = {x: 0, y: 0.3, z: 0.0}; // The offset from the joint to whic the speech bubble is attached.
|
||||
var speechBubbleJointName = 'Head'; // The name of the joint to which the speech bubble is attached.
|
||||
var speechBubbleLineHeight = 0.05; // The height of a line of text in the speech bubble.
|
||||
var SPEECH_BUBBLE_MAX_WIDTH = 1; // meters
|
||||
|
||||
// Load the persistent variables from the Settings, with defaults.
|
||||
function loadSettings() {
|
||||
|
@ -645,8 +646,16 @@
|
|||
//print("updateSpeechBubble:", "speechBubbleMessage", speechBubbleMessage, "textSize", textSize.width, textSize.height);
|
||||
|
||||
var fudge = 0.02;
|
||||
|
||||
var width = textSize.width + fudge;
|
||||
var height = textSize.height + fudge;
|
||||
var height = speechBubbleLineHeight + fudge;
|
||||
|
||||
if (textSize.width >= SPEECH_BUBBLE_MAX_WIDTH) {
|
||||
var numLines = Math.ceil(width);
|
||||
height = speechBubbleLineHeight * numLines + fudge;
|
||||
width = SPEECH_BUBBLE_MAX_WIDTH;
|
||||
}
|
||||
|
||||
dimensions = {
|
||||
x: width,
|
||||
y: height,
|
||||
|
@ -672,6 +681,7 @@
|
|||
Vec3.sum(
|
||||
headPosition,
|
||||
rotatedOffset);
|
||||
position.y += height / 2; // offset based on half of bubble height
|
||||
speechBubbleParams.position = position;
|
||||
|
||||
if (!speechBubbleTextID) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -381,7 +381,13 @@ function getAvatarFootOffset() {
|
|||
}
|
||||
if (footJointIndex != -1) {
|
||||
// default vertical offset from foot to avatar root.
|
||||
return -MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(footJointIndex).y;
|
||||
var footPos = MyAvatar.getAbsoluteDefaultJointTranslationInObjectFrame(footJointIndex);
|
||||
if (footPos.x === 0 && footPos.y === 0 && footPos.z === 0.0) {
|
||||
// if footPos is exactly zero, it's probably wrong because avatar is currently loading, fall back to default.
|
||||
return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale;
|
||||
} else {
|
||||
return -footPos.y;
|
||||
}
|
||||
} else {
|
||||
return DEFAULT_ROOT_TO_FOOT_OFFSET * MyAvatar.scale;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
Settings, Entities, Tablet, Toolbars, Messages, Menu, Camera, progressDialog, tooltip, MyAvatar, Quat, Controller, Clipboard, HMD, UndoStack, ParticleExplorerTool */
|
||||
|
||||
(function() { // BEGIN LOCAL_SCOPE
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
var HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
||||
|
@ -26,11 +26,8 @@ Script.include([
|
|||
"libraries/stringHelpers.js",
|
||||
"libraries/dataViewHelpers.js",
|
||||
"libraries/progressDialog.js",
|
||||
|
||||
"libraries/entitySelectionTool.js",
|
||||
|
||||
"libraries/ToolTip.js",
|
||||
|
||||
"libraries/entityCameraTool.js",
|
||||
"libraries/gridTool.js",
|
||||
"libraries/entityList.js",
|
||||
|
@ -275,7 +272,8 @@ var toolBar = (function () {
|
|||
properties.userData = JSON.stringify({ grabbableKey: { grabbable: true } });
|
||||
}
|
||||
entityID = Entities.addEntity(properties);
|
||||
if (properties.type == "ParticleEffect") {
|
||||
|
||||
if (properties.type === "ParticleEffect") {
|
||||
selectParticleEntity(entityID);
|
||||
}
|
||||
|
||||
|
@ -396,7 +394,6 @@ var toolBar = (function () {
|
|||
|
||||
function initialize() {
|
||||
Script.scriptEnding.connect(cleanup);
|
||||
|
||||
Window.domainChanged.connect(function () {
|
||||
that.setActive(false);
|
||||
that.clearEntityList();
|
||||
|
@ -419,7 +416,7 @@ var toolBar = (function () {
|
|||
createButton = activeButton;
|
||||
tablet.screenChanged.connect(function (type, url) {
|
||||
if (isActive && (type !== "QML" || url !== "Edit.qml")) {
|
||||
that.toggle();
|
||||
that.setActive(false)
|
||||
}
|
||||
});
|
||||
tablet.fromQml.connect(fromQml);
|
||||
|
@ -624,6 +621,7 @@ var toolBar = (function () {
|
|||
};
|
||||
|
||||
that.setActive = function (active) {
|
||||
ContextOverlay.enabled = !active;
|
||||
Settings.setValue(EDIT_SETTING, active);
|
||||
if (active) {
|
||||
Controller.captureEntityClickEvents();
|
||||
|
@ -2196,6 +2194,7 @@ var PopupMenu = function () {
|
|||
};
|
||||
|
||||
function cleanup() {
|
||||
ContextOverlay.enabled = true;
|
||||
for (var i = 0; i < overlays.length; i++) {
|
||||
Overlays.deleteOverlay(overlays[i]);
|
||||
}
|
||||
|
@ -2229,10 +2228,9 @@ var particleExplorerTool = new ParticleExplorerTool();
|
|||
var selectedParticleEntity = 0;
|
||||
var selectedParticleEntityID = null;
|
||||
|
||||
|
||||
function selectParticleEntity(entityID) {
|
||||
var properties = Entities.getEntityProperties(entityID);
|
||||
|
||||
selectedParticleEntityID = entityID;
|
||||
if (properties.emitOrientation) {
|
||||
properties.emitOrientation = Quat.safeEulerAngles(properties.emitOrientation);
|
||||
}
|
||||
|
@ -2274,7 +2272,6 @@ entityListTool.webView.webEventReceived.connect(function (data) {
|
|||
return;
|
||||
}
|
||||
// Destroy the old particles web view first
|
||||
selectParticleEntity(ids[0]);
|
||||
} else {
|
||||
selectedParticleEntity = 0;
|
||||
particleExplorerTool.destroyWebView();
|
||||
|
|
|
@ -40,9 +40,8 @@ function updateControllerDisplay() {
|
|||
var button;
|
||||
var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
|
||||
|
||||
// Independent and Entity mode make people sick. Third Person and Mirror have traps that we need to work through.
|
||||
// Disable them in hmd.
|
||||
var desktopOnlyViews = ['Mirror', 'Independent Mode', 'Entity Mode'];
|
||||
// Independent and Entity mode make people sick; disable them in hmd.
|
||||
var desktopOnlyViews = ['Independent Mode', 'Entity Mode'];
|
||||
|
||||
function onHmdChanged(isHmd) {
|
||||
HMD.closeTablet();
|
||||
|
|
|
@ -475,6 +475,15 @@ function unbindAllInputs() {
|
|||
}
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
if(document.selection && document.selection.empty) {
|
||||
document.selection.empty();
|
||||
} else if(window.getSelection) {
|
||||
var sel = window.getSelection();
|
||||
sel.removeAllRanges();
|
||||
}
|
||||
}
|
||||
|
||||
function loaded() {
|
||||
openEventBridge(function() {
|
||||
|
||||
|
@ -1051,6 +1060,7 @@ function loaded() {
|
|||
activeElement.select();
|
||||
}
|
||||
}
|
||||
clearSelection();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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');
|
||||
|
|
1500
unpublishedScripts/marketplace/doppleganger-attachments/dist/app-doppleganger-marketplace.js
vendored
Normal file
1500
unpublishedScripts/marketplace/doppleganger-attachments/dist/app-doppleganger-marketplace.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -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) {
|
||||
|
|
|
@ -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
|
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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 $@
|
|
@ -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){}, {
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"webpack": "^3.0.0"
|
||||
}
|
||||
}
|
|
@ -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`
|
|
@ -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
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// pUtils.js
|
||||
//
|
||||
// Created by Patrick Gosch on 03/28/2017
|
||||
// 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
|
||||
//
|
||||
|
||||
getEntityTextures = function(id) {
|
||||
var results = null;
|
||||
var properties = Entities.getEntityProperties(id, "textures");
|
||||
if (properties.textures) {
|
||||
try {
|
||||
results = JSON.parse(properties.textures);
|
||||
} catch (err) {
|
||||
logDebug(err);
|
||||
logDebug(properties.textures);
|
||||
}
|
||||
}
|
||||
return results ? results : {};
|
||||
};
|
||||
|
||||
setEntityTextures = function(id, textureList) {
|
||||
var json = JSON.stringify(textureList);
|
||||
Entities.editEntity(id, {textures: json});
|
||||
};
|
||||
|
||||
editEntityTextures = function(id, textureName, textureURL) {
|
||||
var textureList = getEntityTextures(id);
|
||||
textureList[textureName] = textureURL;
|
||||
setEntityTextures(id, textureList);
|
||||
};
|
|
@ -9,12 +9,12 @@
|
|||
//
|
||||
|
||||
(function() {
|
||||
Script.include(Script.resolvePath("pUtils.js"));
|
||||
var TIMEOUT = 150;
|
||||
var TEXGRAY = Script.resolvePath("xylotex_bar_gray.png");
|
||||
var TEXBLACK = Script.resolvePath("xylotex_bar_black.png");
|
||||
var TIMEOUT = 50; // at 30 ms, the key's color sometimes fails to switch when hit
|
||||
var TEXTURE_GRAY = Script.resolvePath("xylotex_bar_gray.png");
|
||||
var TEXTURE_BLACK = Script.resolvePath("xylotex_bar_black.png");
|
||||
var IS_DEBUG = false;
|
||||
var _this;
|
||||
|
||||
|
||||
function XylophoneKey() {
|
||||
_this = this;
|
||||
}
|
||||
|
@ -22,7 +22,7 @@
|
|||
XylophoneKey.prototype = {
|
||||
sound: null,
|
||||
isWaiting: false,
|
||||
homePos: null,
|
||||
homePosition: null,
|
||||
injector: null,
|
||||
|
||||
preload: function(entityID) {
|
||||
|
@ -34,31 +34,66 @@
|
|||
|
||||
collisionWithEntity: function(thisEntity, otherEntity, collision) {
|
||||
if (collision.type === 0) {
|
||||
_this.hit();
|
||||
_this.hit(otherEntity);
|
||||
}
|
||||
},
|
||||
|
||||
clickDownOnEntity: function() {
|
||||
_this.hit();
|
||||
clickDownOnEntity: function(otherEntity) {
|
||||
_this.hit(otherEntity);
|
||||
},
|
||||
|
||||
hit: function() {
|
||||
hit: function(otherEntity) {
|
||||
if (!_this.isWaiting) {
|
||||
_this.isWaiting = true;
|
||||
_this.homePos = Entities.getEntityProperties(_this.entityID, ["position"]).position;
|
||||
_this.injector = Audio.playSound(_this.sound, {position: _this.homePos, volume: 1});
|
||||
editEntityTextures(_this.entityID, "file5", TEXGRAY);
|
||||
_this.homePosition = Entities.getEntityProperties(_this.entityID, ["position"]).position;
|
||||
_this.injector = Audio.playSound(_this.sound, {position: _this.homePosition, volume: 1});
|
||||
_this.editEntityTextures(_this.entityID, "file5", TEXTURE_GRAY);
|
||||
|
||||
var HAPTIC_STRENGTH = 1;
|
||||
var HAPTIC_DURATION = 20;
|
||||
var userData = JSON.parse(Entities.getEntityProperties(otherEntity, 'userData').userData);
|
||||
if (userData.hasOwnProperty('hand')){
|
||||
Controller.triggerHapticPulse(HAPTIC_STRENGTH, HAPTIC_DURATION, userData.hand);
|
||||
}
|
||||
|
||||
_this.timeout();
|
||||
}
|
||||
},
|
||||
|
||||
timeout: function() {
|
||||
Script.setTimeout(function() {
|
||||
editEntityTextures(_this.entityID, "file5", TEXBLACK);
|
||||
_this.editEntityTextures(_this.entityID, "file5", TEXTURE_BLACK);
|
||||
_this.isWaiting = false;
|
||||
}, TIMEOUT);
|
||||
},
|
||||
|
||||
getEntityTextures: function(id) {
|
||||
var results = null;
|
||||
var properties = Entities.getEntityProperties(id, "textures");
|
||||
if (properties.textures) {
|
||||
try {
|
||||
results = JSON.parse(properties.textures);
|
||||
} catch (err) {
|
||||
if (IS_DEBUG) {
|
||||
print(err);
|
||||
print(properties.textures);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results ? results : {};
|
||||
},
|
||||
|
||||
setEntityTextures: function(id, textureList) {
|
||||
var json = JSON.stringify(textureList);
|
||||
Entities.editEntity(id, {textures: json});
|
||||
},
|
||||
|
||||
editEntityTextures: function(id, textureName, textureURL) {
|
||||
var textureList = _this.getEntityTextures(id);
|
||||
textureList[textureName] = textureURL;
|
||||
_this.setEntityTextures(id, textureList);
|
||||
}
|
||||
};
|
||||
|
||||
return new XylophoneKey();
|
||||
|
||||
});
|
||||
|
|
25
unpublishedScripts/marketplace/xylophone/xylophoneMallet.js
Normal file
25
unpublishedScripts/marketplace/xylophone/xylophoneMallet.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// xylophoneMallet.js
|
||||
//
|
||||
// Created by Johnathan Franck on 07/30/2017
|
||||
// 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
|
||||
|
||||
(function() {
|
||||
function XylophoneMallet() {
|
||||
}
|
||||
|
||||
XylophoneMallet.prototype = {
|
||||
startEquip: function(entityID, args) {
|
||||
var LEFT_HAND = 0;
|
||||
var RIGHT_HAND = 1;
|
||||
var userData = JSON.parse(Entities.getEntityProperties(entityID, 'userData').userData);
|
||||
userData.hand = args[0] === "left" ? LEFT_HAND : RIGHT_HAND;
|
||||
Entities.editEntity(entityID, {userData: JSON.stringify(userData)});
|
||||
}
|
||||
};
|
||||
|
||||
return new XylophoneMallet();
|
||||
});
|
|
@ -8,65 +8,70 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var soundFiles = ["C4.wav", "D4.wav", "E4.wav", "F4.wav", "G4.wav", "A4.wav", "B4.wav", "C5.wav"];
|
||||
var keyModelURL = Script.resolvePath("xyloKey_2_a_e.fbx");
|
||||
var keyScriptURL = Script.resolvePath("xylophoneKey.js");
|
||||
var TEXBLACK = Script.resolvePath("xylotex_bar_black.png");
|
||||
var malletModelURL = Script.resolvePath("Mallet3-2pc.fbx");
|
||||
var malletModelColliderURL = Script.resolvePath("Mallet3-2bpc_phys.obj");
|
||||
var SOUND_FILES = ["C4.wav", "D4.wav", "E4.wav", "F4.wav", "G4.wav", "A4.wav", "B4.wav", "C5.wav"];
|
||||
var KEY_MODEL_URL = Script.resolvePath("xyloKey_2_a_e.fbx");
|
||||
var KEY_SCRIPT_URL = Script.resolvePath("xylophoneKey.js");
|
||||
var MALLET_SCRIPT_URL = Script.resolvePath("xylophoneMallet.js");
|
||||
var TEXTURE_BLACK = Script.resolvePath("xylotex_bar_black.png");
|
||||
var MALLET_MODEL_URL = Script.resolvePath("Mallet3-2pc.fbx");
|
||||
var MALLET_MODEL_COLLIDER_URL = Script.resolvePath("Mallet3-2bpc_phys.obj");
|
||||
var FORWARD = { x: 0, y: 0, z: -1 };
|
||||
var center = MyAvatar.position;
|
||||
var fwd = {x:0, y:0, z:-1};
|
||||
|
||||
var xyloFramePos = Vec3.sum(center, Vec3.multiply(fwd, 0.8));
|
||||
var xyloFrameID = Entities.addEntity( {
|
||||
var XYLOPHONE_FORWARD_OFFSET = 0.8;
|
||||
var xylophoneFramePosition = Vec3.sum(center, Vec3.multiply(FORWARD, XYLOPHONE_FORWARD_OFFSET));
|
||||
var xylophoneFrameID = Entities.addEntity({
|
||||
name: "Xylophone",
|
||||
type: "Model",
|
||||
modelURL: Script.resolvePath("xylophoneFrameWithWave.fbx"),
|
||||
position: xyloFramePos,
|
||||
rotation: Quat.fromVec3Radians({x:0, y:Math.PI, z:0}),
|
||||
position: xylophoneFramePosition,
|
||||
rotation: Quat.fromVec3Radians({ x: 0, y: Math.PI, z: 0 }),
|
||||
shapeType: "static-mesh"
|
||||
});
|
||||
|
||||
center.y += (0.45); // key Y offset from frame
|
||||
var keyPos, keyRot, ud, td, keyID;
|
||||
for (var i = 1; i <= soundFiles.length; i++) {
|
||||
var KEY_Y_OFFSET = 0.45;
|
||||
center.y += KEY_Y_OFFSET;
|
||||
var keyPosition, keyRotation, userData, textureData, keyID;
|
||||
var ROTATION_START = 0.9;
|
||||
var ROTATION_DELTA = 0.2;
|
||||
for (var i = 1; i <= SOUND_FILES.length; i++) {
|
||||
|
||||
keyRotation = Quat.fromVec3Radians({ x: 0, y: ROTATION_START - (i*ROTATION_DELTA), z: 0 });
|
||||
keyPosition = Vec3.sum(center, Vec3.multiplyQbyV(keyRotation, FORWARD));
|
||||
|
||||
keyRot = Quat.fromVec3Radians({x:0, y:(0.9 - (i*0.2)), z:0});
|
||||
keyPos = Vec3.sum(center, Vec3.multiplyQbyV(keyRot, fwd));
|
||||
|
||||
ud = {
|
||||
soundFile: soundFiles[i-1]
|
||||
userData = {
|
||||
soundFile: SOUND_FILES[i-1]
|
||||
};
|
||||
|
||||
td = {
|
||||
textureData = {
|
||||
"file4": Script.resolvePath("xylotex_bar" + i + ".png"),
|
||||
"file5": TEXBLACK
|
||||
"file5": TEXTURE_BLACK
|
||||
};
|
||||
|
||||
keyID = Entities.addEntity( {
|
||||
keyID = Entities.addEntity({
|
||||
name: ("XyloKey" + i),
|
||||
type: "Model",
|
||||
modelURL: keyModelURL,
|
||||
position: keyPos,
|
||||
rotation: keyRot,
|
||||
modelURL: KEY_MODEL_URL,
|
||||
position: keyPosition,
|
||||
rotation: keyRotation,
|
||||
shapeType: "static-mesh",
|
||||
script: keyScriptURL,
|
||||
textures: JSON.stringify(td),
|
||||
userData: JSON.stringify(ud),
|
||||
parentID: xyloFrameID
|
||||
} );
|
||||
script: KEY_SCRIPT_URL,
|
||||
textures: JSON.stringify(textureData),
|
||||
userData: JSON.stringify(userData),
|
||||
parentID: xylophoneFrameID
|
||||
});
|
||||
}
|
||||
|
||||
// if rezzed on/above something, wait until after model has loaded so you can read its dimensions then move object on to that surface.
|
||||
var pickRay = {origin: center, direction: {x:0, y:-1, z:0}};
|
||||
var pickRay = {origin: center, direction: {x: 0, y: -1, z: 0}};
|
||||
var intersection = Entities.findRayIntersection(pickRay, true);
|
||||
if (intersection.intersects && (intersection.distance < 10)) {
|
||||
var surfaceY = intersection.intersection.y;
|
||||
Script.setTimeout( function() {
|
||||
// should add loop to check for fbx loaded instead of delay
|
||||
var xyloDimensions = Entities.getEntityProperties(xyloFrameID, ["dimensions"]).dimensions;
|
||||
xyloFramePos.y = surfaceY + (xyloDimensions.y/2);
|
||||
Entities.editEntity(xyloFrameID, {position: xyloFramePos});
|
||||
var xylophoneDimensions = Entities.getEntityProperties(xylophoneFrameID, ["dimensions"]).dimensions;
|
||||
xylophoneFramePosition.y = surfaceY + (xylophoneDimensions.y/2);
|
||||
Entities.editEntity(xylophoneFrameID, {position: xylophoneFramePosition});
|
||||
rezMallets();
|
||||
}, 2000);
|
||||
} else {
|
||||
|
@ -75,28 +80,50 @@ if (intersection.intersects && (intersection.distance < 10)) {
|
|||
}
|
||||
|
||||
function rezMallets() {
|
||||
var malletProps = {
|
||||
var malletProperties = {
|
||||
name: "Xylophone Mallet",
|
||||
type: "Model",
|
||||
modelURL: malletModelURL,
|
||||
compoundShapeURL: malletModelColliderURL,
|
||||
collidesWith: "static,dynamic,kinematic,",
|
||||
modelURL: MALLET_MODEL_URL,
|
||||
compoundShapeURL: MALLET_MODEL_COLLIDER_URL,
|
||||
collidesWith: "static,dynamic,kinematic",
|
||||
collisionMask: 7,
|
||||
collisionsWillMove: 1,
|
||||
dynamic: 1,
|
||||
damping: 1,
|
||||
angularDamping: 1,
|
||||
shapeType: "compound",
|
||||
userData: "{\"grabbableKey\":{\"grabbable\":true}}",
|
||||
dimensions: {"x": 0.057845603674650192, "y": 0.057845607399940491, "z": 0.30429631471633911} // not being set from fbx for some reason.
|
||||
script: MALLET_SCRIPT_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
},
|
||||
wearable: {
|
||||
joints: {
|
||||
LeftHand: [
|
||||
{ x: 0, y: 0.2, z: 0.04 },
|
||||
Quat.fromVec3Degrees({ x: 0, y: 90, z: 90 })
|
||||
],
|
||||
RightHand: [
|
||||
{ x: 0, y: 0.2, z: 0.04 },
|
||||
Quat.fromVec3Degrees({ x: 0, y: 90, z: 90 })
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
dimensions: { "x": 0.057845603674650192, "y": 0.057845607399940491, "z": 0.30429631471633911 } // not being set from fbx for some reason.
|
||||
};
|
||||
|
||||
malletProps.position = Vec3.sum(xyloFramePos, {x: 0.1, y: 0.55, z: 0});
|
||||
malletProps.rotation = Quat.fromVec3Radians({x:0, y:Math.PI - 0.1, z:0});
|
||||
Entities.addEntity(malletProps);
|
||||
var LEFT_MALLET_POSITION = { x: 0.1, y: 0.55, z: 0 };
|
||||
var LEFT_MALLET_ROTATION = { x: 0, y: Math.PI - 0.1, z: 0 };
|
||||
var RIGHT_MALLET_POSITION = { x: -0.1, y: 0.55, z: 0 };
|
||||
var RIGHT_MALLET_ROTATION = { x: 0, y: Math.PI + 0.1, z: 0 };
|
||||
|
||||
malletProps.position = Vec3.sum(xyloFramePos, {x: -0.1, y: 0.55, z: 0});
|
||||
malletProps.rotation = Quat.fromVec3Radians({x:0, y:Math.PI + 0.1, z:0});
|
||||
Entities.addEntity(malletProps);
|
||||
malletProperties.position = Vec3.sum(xylophoneFramePosition, LEFT_MALLET_POSITION);
|
||||
malletProperties.rotation = Quat.fromVec3Radians(LEFT_MALLET_ROTATION);
|
||||
Entities.addEntity(malletProperties);
|
||||
|
||||
malletProperties.position = Vec3.sum(xylophoneFramePosition, RIGHT_MALLET_POSITION);
|
||||
malletProperties.rotation = Quat.fromVec3Radians(RIGHT_MALLET_ROTATION);
|
||||
Entities.addEntity(malletProperties);
|
||||
Script.stop();
|
||||
}
|
Loading…
Reference in a new issue