mirror of
https://github.com/overte-org/overte.git
synced 2025-04-23 08:33:28 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into fix/resort-in-octree
This commit is contained in:
commit
d1324b2e7a
300 changed files with 9689 additions and 3353 deletions
assignment-client/src
cmake
macros
modules
domain-server
resources/web/settings/js
src
examples
away.js
controllers
defaultScripts.jsdepthReticle.jsedit.jsentityScripts
gridTest.jshomeContent/whiteboardV2
libraries
shaders
tests
toybox
flappyAvatars
flashlight
musicPlayer
ping_pong_gun
tutorials/fireworks
chapter1
chapter2
chapter3
utilities/tools/render
zones
ice-server
interface
CMakeLists.txt
resources
fonts
qml
controls-uit
Button.qmlCheckBox.qmlComboBox.qmlContentSection.qmlLabel.qmlSlider.qmlSpinBox.qmlStaticSection.qmlTable.qmlTextField.qmlTree.qmlVerticalSpacer.qml
dialogs
PreferencesDialog.qml
preferences
hifi/dialogs
AudioPreferencesDialog.qmlAvatarPreferencesDialog.qmlGeneralPreferencesDialog.qmlGraphicsPreferencesDialog.qmlLodPreferencesDialog.qmlRunningScripts.qml
styles-uit
windows-uit
src
|
@ -55,13 +55,7 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
{
|
||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
auto assetClient = DependencyManager::set<AssetClient>();
|
||||
|
||||
QThread* assetThread = new QThread;
|
||||
assetThread->setObjectName("Asset Thread");
|
||||
assetClient->moveToThread(assetThread);
|
||||
connect(assetThread, &QThread::started, assetClient.data(), &AssetClient::init);
|
||||
assetThread->start();
|
||||
ResourceManager::init();
|
||||
|
||||
DependencyManager::registerInheritance<SpatialParentFinder, AssignmentParentFinder>();
|
||||
|
||||
|
@ -471,11 +465,7 @@ void Agent::aboutToFinish() {
|
|||
// our entity tree is going to go away so tell that to the EntityScriptingInterface
|
||||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
|
||||
|
||||
// cleanup the AssetClient thread
|
||||
QThread* assetThread = DependencyManager::get<AssetClient>()->thread();
|
||||
DependencyManager::destroy<AssetClient>();
|
||||
assetThread->quit();
|
||||
assetThread->wait();
|
||||
ResourceManager::cleanup();
|
||||
|
||||
// cleanup the AudioInjectorManager (and any still running injectors)
|
||||
DependencyManager::destroy<AudioInjectorManager>();
|
||||
|
|
|
@ -139,13 +139,13 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
httpStatusPort = parser.value(httpStatusPortOption).toUShort();
|
||||
}
|
||||
|
||||
QDir logDirectory { "." };
|
||||
QString logDirectory;
|
||||
|
||||
if (parser.isSet(logDirectoryOption)) {
|
||||
logDirectory = parser.value(logDirectoryOption);
|
||||
} else {
|
||||
logDirectory = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
}
|
||||
|
||||
|
||||
Assignment::Type requestAssignmentType = Assignment::AllTypes;
|
||||
if (argumentVariantMap.contains(ASSIGNMENT_TYPE_OVERRIDE_OPTION)) {
|
||||
requestAssignmentType = (Assignment::Type) argumentVariantMap.value(ASSIGNMENT_TYPE_OVERRIDE_OPTION).toInt();
|
||||
|
|
|
@ -33,8 +33,7 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
|
|||
const unsigned int maxAssignmentClientForks,
|
||||
Assignment::Type requestAssignmentType, QString assignmentPool,
|
||||
quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname,
|
||||
quint16 assignmentServerPort, quint16 httpStatusServerPort, QDir logDirectory) :
|
||||
_logDirectory(logDirectory),
|
||||
quint16 assignmentServerPort, quint16 httpStatusServerPort, QString logDirectory) :
|
||||
_httpManager(QHostAddress::LocalHost, httpStatusServerPort, "", this),
|
||||
_numAssignmentClientForks(numAssignmentClientForks),
|
||||
_minAssignmentClientForks(minAssignmentClientForks),
|
||||
|
@ -48,6 +47,11 @@ AssignmentClientMonitor::AssignmentClientMonitor(const unsigned int numAssignmen
|
|||
{
|
||||
qDebug() << "_requestAssignmentType =" << _requestAssignmentType;
|
||||
|
||||
if (!logDirectory.isEmpty()) {
|
||||
_wantsChildFileLogging = true;
|
||||
_logDirectory = QDir(logDirectory);
|
||||
}
|
||||
|
||||
// start the Logging class with the parent's target name
|
||||
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_MONITOR_TARGET_NAME);
|
||||
|
||||
|
@ -159,52 +163,61 @@ void AssignmentClientMonitor::spawnChildClient() {
|
|||
_childArguments.append("--" + ASSIGNMENT_CLIENT_MONITOR_PORT_OPTION);
|
||||
_childArguments.append(QString::number(DependencyManager::get<NodeList>()->getLocalSockAddr().getPort()));
|
||||
|
||||
// Setup log files
|
||||
const QString DATETIME_FORMAT = "yyyyMMdd.hh.mm.ss.zzz";
|
||||
QString nowString, stdoutFilenameTemp, stderrFilenameTemp, stdoutPathTemp, stderrPathTemp;
|
||||
|
||||
if (!_logDirectory.exists()) {
|
||||
qDebug() << "Log directory (" << _logDirectory.absolutePath() << ") does not exist, creating.";
|
||||
_logDirectory.mkpath(_logDirectory.absolutePath());
|
||||
|
||||
if (_wantsChildFileLogging) {
|
||||
// Setup log files
|
||||
const QString DATETIME_FORMAT = "yyyyMMdd.hh.mm.ss.zzz";
|
||||
|
||||
if (!_logDirectory.exists()) {
|
||||
qDebug() << "Log directory (" << _logDirectory.absolutePath() << ") does not exist, creating.";
|
||||
_logDirectory.mkpath(_logDirectory.absolutePath());
|
||||
}
|
||||
|
||||
nowString = QDateTime::currentDateTime().toString(DATETIME_FORMAT);
|
||||
stdoutFilenameTemp = QString("ac-%1-stdout.txt").arg(nowString);
|
||||
stderrFilenameTemp = QString("ac-%1-stderr.txt").arg(nowString);
|
||||
stdoutPathTemp = _logDirectory.absoluteFilePath(stdoutFilenameTemp);
|
||||
stderrPathTemp = _logDirectory.absoluteFilePath(stderrFilenameTemp);
|
||||
|
||||
// reset our output and error files
|
||||
assignmentClient->setStandardOutputFile(stdoutPathTemp);
|
||||
assignmentClient->setStandardErrorFile(stderrPathTemp);
|
||||
}
|
||||
|
||||
auto nowString = QDateTime::currentDateTime().toString(DATETIME_FORMAT);
|
||||
auto stdoutFilenameTemp = QString("ac-%1-stdout.txt").arg(nowString);
|
||||
auto stderrFilenameTemp = QString("ac-%1-stderr.txt").arg(nowString);
|
||||
QString stdoutPathTemp = _logDirectory.absoluteFilePath(stdoutFilenameTemp);
|
||||
QString stderrPathTemp = _logDirectory.absoluteFilePath(stderrFilenameTemp);
|
||||
|
||||
// reset our output and error files
|
||||
assignmentClient->setStandardOutputFile(stdoutPathTemp);
|
||||
assignmentClient->setStandardErrorFile(stderrPathTemp);
|
||||
|
||||
// make sure that the output from the child process appears in our output
|
||||
assignmentClient->setProcessChannelMode(QProcess::ForwardedChannels);
|
||||
|
||||
assignmentClient->start(QCoreApplication::applicationFilePath(), _childArguments);
|
||||
|
||||
// Update log path to use PID in filename
|
||||
auto stdoutFilename = QString("ac-%1_%2-stdout.txt").arg(nowString).arg(assignmentClient->processId());
|
||||
auto stderrFilename = QString("ac-%1_%2-stderr.txt").arg(nowString).arg(assignmentClient->processId());
|
||||
QString stdoutPath = _logDirectory.absoluteFilePath(stdoutFilename);
|
||||
QString stderrPath = _logDirectory.absoluteFilePath(stderrFilename);
|
||||
QString stdoutPath, stderrPath;
|
||||
|
||||
qDebug() << "Renaming " << stdoutPathTemp << " to " << stdoutPath;
|
||||
if (!_logDirectory.rename(stdoutFilenameTemp, stdoutFilename)) {
|
||||
qDebug() << "Failed to rename " << stdoutFilenameTemp;
|
||||
stdoutPath = stdoutPathTemp;
|
||||
stdoutFilename = stdoutFilenameTemp;
|
||||
if (_wantsChildFileLogging) {
|
||||
|
||||
// Update log path to use PID in filename
|
||||
auto stdoutFilename = QString("ac-%1_%2-stdout.txt").arg(nowString).arg(assignmentClient->processId());
|
||||
auto stderrFilename = QString("ac-%1_%2-stderr.txt").arg(nowString).arg(assignmentClient->processId());
|
||||
stdoutPath = _logDirectory.absoluteFilePath(stdoutFilename);
|
||||
stderrPath = _logDirectory.absoluteFilePath(stderrFilename);
|
||||
|
||||
qDebug() << "Renaming " << stdoutPathTemp << " to " << stdoutPath;
|
||||
if (!_logDirectory.rename(stdoutFilenameTemp, stdoutFilename)) {
|
||||
qDebug() << "Failed to rename " << stdoutFilenameTemp;
|
||||
stdoutPath = stdoutPathTemp;
|
||||
stdoutFilename = stdoutFilenameTemp;
|
||||
}
|
||||
|
||||
qDebug() << "Renaming " << stderrPathTemp << " to " << stderrPath;
|
||||
if (!QFile::rename(stderrPathTemp, stderrPath)) {
|
||||
qDebug() << "Failed to rename " << stderrFilenameTemp;
|
||||
stderrPath = stderrPathTemp;
|
||||
stderrFilename = stderrFilenameTemp;
|
||||
}
|
||||
|
||||
qDebug() << "Child stdout being written to: " << stdoutFilename;
|
||||
qDebug() << "Child stderr being written to: " << stderrFilename;
|
||||
}
|
||||
|
||||
qDebug() << "Renaming " << stderrPathTemp << " to " << stderrPath;
|
||||
if (!QFile::rename(stderrPathTemp, stderrPath)) {
|
||||
qDebug() << "Failed to rename " << stderrFilenameTemp;
|
||||
stderrPath = stderrPathTemp;
|
||||
stderrFilename = stderrFilenameTemp;
|
||||
}
|
||||
|
||||
qDebug() << "Child stdout being written to: " << stdoutFilename;
|
||||
qDebug() << "Child stderr being written to: " << stderrFilename;
|
||||
|
||||
if (assignmentClient->processId() > 0) {
|
||||
auto pid = assignmentClient->processId();
|
||||
// make sure we hear that this process has finished when it does
|
||||
|
@ -212,6 +225,7 @@ void AssignmentClientMonitor::spawnChildClient() {
|
|||
this, [this, pid]() { childProcessFinished(pid); });
|
||||
|
||||
qDebug() << "Spawned a child client with PID" << assignmentClient->processId();
|
||||
|
||||
_childProcesses.insert(assignmentClient->processId(), { assignmentClient, stdoutPath, stderrPath });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
AssignmentClientMonitor(const unsigned int numAssignmentClientForks, const unsigned int minAssignmentClientForks,
|
||||
const unsigned int maxAssignmentClientForks, Assignment::Type requestAssignmentType,
|
||||
QString assignmentPool, quint16 listenPort, QUuid walletUUID, QString assignmentServerHostname,
|
||||
quint16 assignmentServerPort, quint16 httpStatusServerPort, QDir logDirectory);
|
||||
quint16 assignmentServerPort, quint16 httpStatusServerPort, QString logDirectory);
|
||||
~AssignmentClientMonitor();
|
||||
|
||||
void stopChildProcesses();
|
||||
|
@ -73,6 +73,8 @@ private:
|
|||
quint16 _assignmentServerPort;
|
||||
|
||||
QMap<qint64, ACProcess> _childProcesses;
|
||||
|
||||
bool _wantsChildFileLogging { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentClientMonitor_h
|
||||
|
|
|
@ -277,14 +277,17 @@ void EntityServer::readAdditionalConfiguration(const QJsonObject& settingsSectio
|
|||
// set of stats to have, but we'd probably want a different data structure if we keep it very long.
|
||||
// Since this version uses a single shared QMap for all senders, there could be some lock contention
|
||||
// on this QWriteLocker
|
||||
void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) {
|
||||
void EntityServer::trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) {
|
||||
QWriteLocker locker(&_viewerSendingStatsLock);
|
||||
_viewerSendingStats[viewerNode][dataID] = { usecTimestampNow(), dataLastEdited };
|
||||
_viewerSendingStats[sessionID][dataID] = { usecTimestampNow(), dataLastEdited };
|
||||
}
|
||||
|
||||
void EntityServer::trackViewerGone(const QUuid& viewerNode) {
|
||||
void EntityServer::trackViewerGone(const QUuid& sessionID) {
|
||||
QWriteLocker locker(&_viewerSendingStatsLock);
|
||||
_viewerSendingStats.remove(viewerNode);
|
||||
_viewerSendingStats.remove(sessionID);
|
||||
if (_entitySimulation) {
|
||||
_entitySimulation->clearOwnership(sessionID);
|
||||
}
|
||||
}
|
||||
|
||||
QString EntityServer::serverSubclassStats() {
|
||||
|
|
|
@ -27,6 +27,8 @@ struct ViewerSendingStats {
|
|||
quint64 lastEdited;
|
||||
};
|
||||
|
||||
class SimpleEntitySimulation;
|
||||
|
||||
class EntityServer : public OctreeServer, public NewlyCreatedEntityHook {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -52,8 +54,8 @@ public:
|
|||
virtual void readAdditionalConfiguration(const QJsonObject& settingsSectionObject) override;
|
||||
virtual QString serverSubclassStats() override;
|
||||
|
||||
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& viewerNode) override;
|
||||
virtual void trackViewerGone(const QUuid& viewerNode) override;
|
||||
virtual void trackSend(const QUuid& dataID, quint64 dataLastEdited, const QUuid& sessionID) override;
|
||||
virtual void trackViewerGone(const QUuid& sessionID) override;
|
||||
|
||||
public slots:
|
||||
void pruneDeletedEntities();
|
||||
|
@ -65,7 +67,7 @@ private slots:
|
|||
void handleEntityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
private:
|
||||
EntitySimulation* _entitySimulation;
|
||||
SimpleEntitySimulation* _entitySimulation;
|
||||
QTimer* _pruneDeletedEntitiesTimer = nullptr;
|
||||
|
||||
QReadWriteLock _viewerSendingStatsLock;
|
||||
|
|
|
@ -236,10 +236,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) :
|
|||
{
|
||||
_averageLoopTime.updateAverage(0);
|
||||
qDebug() << "Octree server starting... [" << this << "]";
|
||||
|
||||
// make sure the AccountManager has an Auth URL for payment redemptions
|
||||
|
||||
AccountManager::getInstance().setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
||||
}
|
||||
|
||||
OctreeServer::~OctreeServer() {
|
||||
|
|
|
@ -64,11 +64,10 @@ macro(install_beside_console)
|
|||
)
|
||||
endif()
|
||||
|
||||
# set variables used by manual ssleay library copy
|
||||
set(TARGET_INSTALL_DIR ${COMPONENT_INSTALL_DIR})
|
||||
set(TARGET_INSTALL_COMPONENT ${SERVER_COMPONENT})
|
||||
manually_install_openssl_for_qt()
|
||||
|
||||
endif ()
|
||||
|
||||
# set variables used by manual ssleay library copy
|
||||
set(TARGET_INSTALL_DIR ${COMPONENT_INSTALL_DIR})
|
||||
set(TARGET_INSTALL_COMPONENT ${SERVER_COMPONENT})
|
||||
manually_install_ssl_eay()
|
||||
|
||||
endmacro()
|
||||
|
|
34
cmake/macros/ManuallyInstallOpenSSLForQt.cmake
Normal file
34
cmake/macros/ManuallyInstallOpenSSLForQt.cmake
Normal file
|
@ -0,0 +1,34 @@
|
|||
#
|
||||
# ManuallyInstallOpenSSLforQt.cmake
|
||||
#
|
||||
# Created by Stephen Birarda on 1/15/16.
|
||||
# Copyright 2014 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(manually_install_openssl_for_qt)
|
||||
|
||||
# Qt dynamically links OpenSSL if it can find it on the user's machine
|
||||
# We want to avoid it being found somewhere random and have it not being a compatible version
|
||||
# So even though we don't need the dynamic version of OpenSSL for our direct-use purposes
|
||||
# we use this macro to include the two SSL DLLs with the targets using QtNetwork
|
||||
if (WIN32)
|
||||
# we have to call find_package(OpenSSL) here even though this target may not directly need it
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
install(
|
||||
FILES "${OPENSSL_DLL_PATH}/ssleay32.dll"
|
||||
DESTINATION ${TARGET_INSTALL_DIR}
|
||||
COMPONENT ${TARGET_INSTALL_COMPONENT}
|
||||
)
|
||||
|
||||
install(
|
||||
FILES "${OPENSSL_DLL_PATH}/libeay32.dll"
|
||||
DESTINATION ${TARGET_INSTALL_DIR}
|
||||
COMPONENT ${TARGET_INSTALL_COMPONENT}
|
||||
)
|
||||
endif()
|
||||
|
||||
endmacro()
|
|
@ -1,28 +0,0 @@
|
|||
#
|
||||
# ManuallyInstallSSLEay.cmake
|
||||
#
|
||||
# Created by Stephen Birarda on 1/15/16.
|
||||
# Copyright 2014 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(manually_install_ssl_eay)
|
||||
|
||||
# on windows we have had issues with targets missing ssleay library
|
||||
# not convinced we actually need it (I assume it would show up in the dependency tree)
|
||||
# but to be safe we install it manually beside the current target
|
||||
if (WIN32)
|
||||
# we need to find SSL_EAY_LIBRARY_* so we can install it beside this target
|
||||
# so we have to call find_package(OpenSSL) here even though this target may not specifically need it
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
install(
|
||||
FILES "${OPENSSL_DLL_PATH}/ssleay32.dll"
|
||||
DESTINATION ${TARGET_INSTALL_DIR}
|
||||
COMPONENT ${TARGET_INSTALL_COMPONENT}
|
||||
)
|
||||
endif()
|
||||
|
||||
endmacro()
|
|
@ -67,10 +67,7 @@ find_path(OPENSSL_INCLUDE_DIR NAMES openssl/ssl.h HINTS ${_OPENSSL_ROOT_HINTS_AN
|
|||
|
||||
if (WIN32 AND NOT CYGWIN)
|
||||
if (MSVC)
|
||||
# /MD and /MDd are the standard values - if someone wants to use
|
||||
# others, the libnames have to change here too
|
||||
# use also ssl and ssleay32 in debug as fallback for openssl < 0.9.8b
|
||||
# TODO: handle /MT and static lib
|
||||
|
||||
# In Visual C++ naming convention each of these four kinds of Windows libraries has it's standard suffix:
|
||||
# * MD for dynamic-release
|
||||
# * MDd for dynamic-debug
|
||||
|
@ -81,20 +78,23 @@ if (WIN32 AND NOT CYGWIN)
|
|||
# We are using the libraries located in the VC subdir instead of the parent directory eventhough :
|
||||
# libeay32MD.lib is identical to ../libeay32.lib, and
|
||||
# ssleay32MD.lib is identical to ../ssleay32.lib
|
||||
find_library(LIB_EAY_DEBUG NAMES libeay32MDd libeay32d
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC"
|
||||
|
||||
# The Kitware FindOpenSSL module has been modified here by High Fidelity to look specifically for static libraries
|
||||
|
||||
find_library(LIB_EAY_DEBUG NAMES libeay32MTd
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static"
|
||||
)
|
||||
|
||||
find_library(LIB_EAY_RELEASE NAMES libeay32MD libeay32
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC"
|
||||
find_library(LIB_EAY_RELEASE NAMES libeay32MT
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static"
|
||||
)
|
||||
|
||||
find_library(SSL_EAY_DEBUG NAMES ssleay32MDd ssleay32d
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC"
|
||||
find_library(SSL_EAY_DEBUG NAMES ssleay32MTd
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static"
|
||||
)
|
||||
|
||||
find_library(SSL_EAY_RELEASE NAMES ssleay32MD ssleay32 ssl
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "VC" "lib/VC"
|
||||
find_library(SSL_EAY_RELEASE NAMES ssleay32MT
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib/VC/static"
|
||||
)
|
||||
|
||||
set(LIB_EAY_LIBRARY_DEBUG "${LIB_EAY_DEBUG}")
|
||||
|
@ -109,37 +109,6 @@ if (WIN32 AND NOT CYGWIN)
|
|||
set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY})
|
||||
|
||||
find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS})
|
||||
|
||||
elseif (MINGW)
|
||||
# same player, for MinGW
|
||||
set(LIB_EAY_NAMES libeay32)
|
||||
set(SSL_EAY_NAMES ssleay32)
|
||||
|
||||
if (CMAKE_CROSSCOMPILING)
|
||||
list(APPEND LIB_EAY_NAMES crypto)
|
||||
list(APPEND SSL_EAY_NAMES ssl)
|
||||
endif ()
|
||||
|
||||
find_library(LIB_EAY NAMES ${LIB_EAY_NAMES}
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "lib/MinGW"
|
||||
)
|
||||
|
||||
find_library(SSL_EAY NAMES ${SSL_EAY_NAMES}
|
||||
${_OPENSSL_ROOT_HINTS_AND_PATHS} PATH_SUFFIXES "lib" "lib/MinGW"
|
||||
)
|
||||
|
||||
mark_as_advanced(SSL_EAY LIB_EAY)
|
||||
set(OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY})
|
||||
unset(LIB_EAY_NAMES)
|
||||
unset(SSL_EAY_NAMES)
|
||||
else ()
|
||||
# Not sure what to pick for -say- intel, let's use the toplevel ones and hope someone report issues:
|
||||
find_library(LIB_EAY NAMES libeay32 HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} ${_OPENSSL_LIBDIR} PATH_SUFFIXES lib)
|
||||
|
||||
find_library(SSL_EAY NAMES ssleay32 HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} ${_OPENSSL_LIBDIR} PATH_SUFFIXES lib)
|
||||
|
||||
mark_as_advanced(SSL_EAY LIB_EAY)
|
||||
set(OPENSSL_LIBRARIES ${SSL_EAY} ${LIB_EAY})
|
||||
endif()
|
||||
else()
|
||||
|
||||
|
@ -250,8 +219,4 @@ else ()
|
|||
)
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
add_paths_to_fixup_libs(${OPENSSL_DLL_PATH})
|
||||
endif ()
|
||||
|
||||
mark_as_advanced(OPENSSL_INCLUDE_DIR OPENSSL_LIBRARIES OPENSSL_SEARCH_DIRS)
|
||||
|
|
|
@ -778,7 +778,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
function createTemporaryDomain() {
|
||||
swal({
|
||||
title: 'Create temporary place name',
|
||||
text: "This will create a temporary place name and domain ID (valid for 30 days)"
|
||||
text: "This will create a temporary place name and domain ID"
|
||||
+ " so other users can easily connect to your domain.</br></br>"
|
||||
+ "In order to make your domain reachable, this will also enable full automatic networking.",
|
||||
showCancelButton: true,
|
||||
|
|
|
@ -258,9 +258,26 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
if (onlyEditorsAreRezzers) {
|
||||
canRez = isAllowedEditor;
|
||||
}
|
||||
|
||||
QUuid hintNodeID;
|
||||
|
||||
// in case this is a node that's failing to connect
|
||||
// double check we don't have a node whose sockets match exactly already in the list
|
||||
limitedNodeList->eachNodeBreakable([&nodeConnection, &hintNodeID](const SharedNodePointer& node){
|
||||
if (node->getPublicSocket() == nodeConnection.publicSockAddr
|
||||
&& node->getLocalSocket() == nodeConnection.localSockAddr) {
|
||||
// we have a node that already has these exact sockets - this occurs if a node
|
||||
// is unable to connect to the domain
|
||||
hintNodeID = node->getUUID();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
// add the new node
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
|
||||
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
|
||||
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection, hintNodeID);
|
||||
|
||||
// set the edit rights for this user
|
||||
newNode->setIsAllowedEditor(isAllowedEditor);
|
||||
|
@ -279,28 +296,29 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
return newNode;
|
||||
}
|
||||
|
||||
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) {
|
||||
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
QUuid nodeID) {
|
||||
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
|
||||
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
|
||||
|
||||
QUuid nodeUUID;
|
||||
|
||||
if (connectedPeer) {
|
||||
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
|
||||
nodeUUID = nodeConnection.connectUUID;
|
||||
nodeID = nodeConnection.connectUUID;
|
||||
|
||||
if (connectedPeer->getActiveSocket()) {
|
||||
// set their discovered socket to whatever the activated socket on the network peer object was
|
||||
discoveredSocket = *connectedPeer->getActiveSocket();
|
||||
}
|
||||
} else {
|
||||
// we got a connectUUID we didn't recognize, just add the node with a new UUID
|
||||
nodeUUID = QUuid::createUuid();
|
||||
// we got a connectUUID we didn't recognize, either use the hinted node ID or randomly generate a new one
|
||||
if (nodeID.isNull()) {
|
||||
nodeID = QUuid::createUuid();
|
||||
}
|
||||
}
|
||||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeUUID, nodeConnection.nodeType,
|
||||
SharedNodePointer newNode = limitedNodeList->addOrUpdateNode(nodeID, nodeConnection.nodeType,
|
||||
nodeConnection.publicSockAddr, nodeConnection.localSockAddr);
|
||||
|
||||
// So that we can send messages to this node at will - we need to activate the correct socket on this node now
|
||||
|
@ -331,7 +349,6 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
QCryptographicHash::Sha256);
|
||||
|
||||
if (rsaPublicKey) {
|
||||
QByteArray decryptedArray(RSA_size(rsaPublicKey), 0);
|
||||
int decryptResult = RSA_verify(NID_sha256,
|
||||
reinterpret_cast<const unsigned char*>(usernameWithToken.constData()),
|
||||
usernameWithToken.size(),
|
||||
|
|
|
@ -59,7 +59,8 @@ private:
|
|||
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
const QByteArray& usernameSignature);
|
||||
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
|
||||
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
QUuid nodeID = QUuid());
|
||||
|
||||
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr);
|
||||
|
|
|
@ -96,7 +96,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
// make sure we hear about newly connected nodes from our gatekeeper
|
||||
connect(&_gatekeeper, &DomainGatekeeper::connectedNode, this, &DomainServer::handleConnectedNode);
|
||||
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
||||
// we either read a certificate and private key or were not passed one
|
||||
// and completed login or did not need to
|
||||
|
||||
|
@ -198,7 +198,6 @@ bool DomainServer::optionallySetupOAuth() {
|
|||
}
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
accountManager.disableSettingsFilePersistence();
|
||||
accountManager.setAuthURL(_oauthProviderURL);
|
||||
|
||||
_oauthClientID = settingsMap.value(OAUTH_CLIENT_ID_OPTION).toString();
|
||||
|
@ -372,20 +371,12 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
|||
packetReceiver.registerListener(PacketType::ICEPing, &_gatekeeper, "processICEPingPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEPingReply, &_gatekeeper, "processICEPingReplyPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEServerPeerInformation, &_gatekeeper, "processICEPeerInformationPacket");
|
||||
packetReceiver.registerListener(PacketType::ICEServerHeartbeatDenied, this, "processICEServerHeartbeatDenialPacket");
|
||||
|
||||
// add whatever static assignments that have been parsed to the queue
|
||||
addStaticAssignmentsToQueue();
|
||||
}
|
||||
|
||||
bool DomainServer::didSetupAccountManagerWithAccessToken() {
|
||||
if (AccountManager::getInstance().hasValidAccessToken()) {
|
||||
// we already gave the account manager a valid access token
|
||||
return true;
|
||||
}
|
||||
|
||||
return resetAccountManagerAccessToken();
|
||||
}
|
||||
|
||||
const QString ACCESS_TOKEN_KEY_PATH = "metaverse.access_token";
|
||||
|
||||
bool DomainServer::resetAccountManagerAccessToken() {
|
||||
|
@ -401,9 +392,13 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
|
||||
accessToken = accessTokenVariant->toString();
|
||||
} else {
|
||||
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present."
|
||||
<< "Set an access token via the web interface, in your user or master config"
|
||||
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present.";
|
||||
qDebug() << "Set an access token via the web interface, in your user or master config"
|
||||
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
|
||||
|
||||
// clear any existing access token from AccountManager
|
||||
AccountManager::getInstance().setAccessTokenForCurrentAuthURL(QString());
|
||||
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
|
@ -429,34 +424,6 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
}
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupAssignmentPayment() {
|
||||
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
||||
const QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||
|
||||
if (settingsMap.contains(PAY_FOR_ASSIGNMENTS_OPTION) &&
|
||||
settingsMap.value(PAY_FOR_ASSIGNMENTS_OPTION).toBool() &&
|
||||
didSetupAccountManagerWithAccessToken()) {
|
||||
|
||||
qDebug() << "Assignments will be paid for via" << qPrintable(_oauthProviderURL.toString());
|
||||
|
||||
// assume that the fact we are authing against HF data server means we will pay for assignments
|
||||
// setup a timer to send transactions to pay assigned nodes every 30 seconds
|
||||
QTimer* creditSetupTimer = new QTimer(this);
|
||||
connect(creditSetupTimer, &QTimer::timeout, this, &DomainServer::setupPendingAssignmentCredits);
|
||||
|
||||
const qint64 CREDIT_CHECK_INTERVAL_MSECS = 5 * 1000;
|
||||
creditSetupTimer->start(CREDIT_CHECK_INTERVAL_MSECS);
|
||||
|
||||
QTimer* nodePaymentTimer = new QTimer(this);
|
||||
connect(nodePaymentTimer, &QTimer::timeout, this, &DomainServer::sendPendingTransactionsToServer);
|
||||
|
||||
const qint64 TRANSACTION_SEND_INTERVAL_MSECS = 30 * 1000;
|
||||
nodePaymentTimer->start(TRANSACTION_SEND_INTERVAL_MSECS);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DomainServer::setupAutomaticNetworking() {
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
|
@ -467,9 +434,9 @@ void DomainServer::setupAutomaticNetworking() {
|
|||
setupICEHeartbeatForFullNetworking();
|
||||
}
|
||||
|
||||
if (!didSetupAccountManagerWithAccessToken()) {
|
||||
qDebug() << "Cannot send heartbeat to data server without an access token.";
|
||||
qDebug() << "Add an access token to your config file or via the web interface.";
|
||||
if (!resetAccountManagerAccessToken()) {
|
||||
qDebug() << "Will not send heartbeat to Metaverse API without an access token.";
|
||||
qDebug() << "If this is not a temporary domain add an access token to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
|
@ -526,6 +493,19 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
|||
// we need this DS to know what our public IP is - start trying to figure that out now
|
||||
limitedNodeList->startSTUNPublicSocketUpdate();
|
||||
|
||||
// to send ICE heartbeats we'd better have a private key locally with an uploaded public key
|
||||
auto& accountManager = AccountManager::getInstance();
|
||||
auto domainID = accountManager.getAccountInfo().getDomainID();
|
||||
|
||||
// if we have an access token and we don't have a private key or the current domain ID has changed
|
||||
// we should generate a new keypair
|
||||
if (!accountManager.getAccountInfo().hasPrivateKey() || domainID != limitedNodeList->getSessionUUID()) {
|
||||
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||
}
|
||||
|
||||
// hookup to the signal from account manager that tells us when keypair is available
|
||||
connect(&accountManager, &AccountManager::newKeypair, this, &DomainServer::handleKeypairChange);
|
||||
|
||||
if (!_iceHeartbeatTimer) {
|
||||
// setup a timer to heartbeat with the ice-server every so often
|
||||
_iceHeartbeatTimer = new QTimer { this };
|
||||
|
@ -1082,11 +1062,76 @@ void DomainServer::sendHeartbeatToDataServer(const QString& networkAddress) {
|
|||
domainUpdateJSON.toUtf8());
|
||||
}
|
||||
|
||||
// TODO: have data-web respond with ice-server hostname to use
|
||||
|
||||
void DomainServer::sendHeartbeatToIceServer() {
|
||||
if (!_iceServerSocket.getAddress().isNull()) {
|
||||
DependencyManager::get<LimitedNodeList>()->sendHeartbeatToIceServer(_iceServerSocket);
|
||||
|
||||
auto& accountManager = AccountManager::getInstance();
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
if (!accountManager.getAccountInfo().hasPrivateKey()) {
|
||||
qWarning() << "Cannot send an ice-server heartbeat without a private key for signature.";
|
||||
qWarning() << "Waiting for keypair generation to complete before sending ICE heartbeat.";
|
||||
|
||||
if (!limitedNodeList->getSessionUUID().isNull()) {
|
||||
accountManager.generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||
} else {
|
||||
qWarning() << "Attempting to send ICE server heartbeat with no domain ID. This is not supported";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: I'd love to specify the correct size for the packet here, but it's a little trickey with
|
||||
// QDataStream and the possibility of IPv6 address for the sockets.
|
||||
if (!_iceServerHeartbeatPacket) {
|
||||
_iceServerHeartbeatPacket = NLPacket::create(PacketType::ICEServerHeartbeat);
|
||||
}
|
||||
|
||||
bool shouldRecreatePacket = false;
|
||||
|
||||
if (_iceServerHeartbeatPacket->getPayloadSize() > 0) {
|
||||
// if either of our sockets have changed we need to re-sign the heartbeat
|
||||
// first read the sockets out from the current packet
|
||||
_iceServerHeartbeatPacket->seek(0);
|
||||
QDataStream heartbeatStream(_iceServerHeartbeatPacket.get());
|
||||
|
||||
QUuid senderUUID;
|
||||
HifiSockAddr publicSocket, localSocket;
|
||||
heartbeatStream >> senderUUID >> publicSocket >> localSocket;
|
||||
|
||||
if (senderUUID != limitedNodeList->getSessionUUID()
|
||||
|| publicSocket != limitedNodeList->getPublicSockAddr()
|
||||
|| localSocket != limitedNodeList->getLocalSockAddr()) {
|
||||
shouldRecreatePacket = true;
|
||||
}
|
||||
} else {
|
||||
shouldRecreatePacket = true;
|
||||
}
|
||||
|
||||
if (shouldRecreatePacket) {
|
||||
// either we don't have a heartbeat packet yet or some combination of sockets, ID and keypair have changed
|
||||
// and we need to make a new one
|
||||
|
||||
// reset the position in the packet before writing
|
||||
_iceServerHeartbeatPacket->reset();
|
||||
|
||||
// write our plaintext data to the packet
|
||||
QDataStream heartbeatDataStream(_iceServerHeartbeatPacket.get());
|
||||
heartbeatDataStream << limitedNodeList->getSessionUUID()
|
||||
<< limitedNodeList->getPublicSockAddr() << limitedNodeList->getLocalSockAddr();
|
||||
|
||||
// setup a QByteArray that points to the plaintext data
|
||||
auto plaintext = QByteArray::fromRawData(_iceServerHeartbeatPacket->getPayload(), _iceServerHeartbeatPacket->getPayloadSize());
|
||||
|
||||
// generate a signature for the plaintext data in the packet
|
||||
auto signature = accountManager.getAccountInfo().signPlaintext(plaintext);
|
||||
|
||||
// pack the signature with the data
|
||||
heartbeatDataStream << signature;
|
||||
}
|
||||
|
||||
// send the heartbeat packet to the ice server now
|
||||
limitedNodeList->sendUnreliablePacket(*_iceServerHeartbeatPacket, _iceServerSocket);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1970,3 +2015,31 @@ void DomainServer::processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMes
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
static const int NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN = 3;
|
||||
|
||||
static int numHeartbeatDenials = 0;
|
||||
if (++numHeartbeatDenials > NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN) {
|
||||
qDebug() << "Received" << NUM_HEARTBEAT_DENIALS_FOR_KEYPAIR_REGEN << "heartbeat denials from ice-server"
|
||||
<< "- re-generating keypair now";
|
||||
|
||||
// we've hit our threshold of heartbeat denials, trigger a keypair re-generation
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
AccountManager::getInstance().generateNewDomainKeypair(limitedNodeList->getSessionUUID());
|
||||
|
||||
// reset our number of heartbeat denials
|
||||
numHeartbeatDenials = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::handleKeypairChange() {
|
||||
if (_iceServerHeartbeatPacket) {
|
||||
// reset the payload size of the ice-server heartbeat packet - this causes the packet to be re-generated
|
||||
// the next time we go to send an ice-server heartbeat
|
||||
_iceServerHeartbeatPacket->setPayloadSize(0);
|
||||
|
||||
// send a heartbeat to the ice server immediately
|
||||
sendHeartbeatToIceServer();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -61,6 +61,7 @@ public slots:
|
|||
void processNodeJSONStatsPacket(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer sendingNode);
|
||||
void processPathQueryPacket(QSharedPointer<ReceivedMessage> packet);
|
||||
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
@ -78,16 +79,16 @@ private slots:
|
|||
void handleTempDomainError(QNetworkReply& requestReply);
|
||||
|
||||
void queuedQuit(QString quitMessage, int exitCode);
|
||||
|
||||
void handleKeypairChange();
|
||||
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
bool optionallySetupAssignmentPayment();
|
||||
|
||||
void optionallyGetTemporaryName(const QStringList& arguments);
|
||||
|
||||
bool didSetupAccountManagerWithAccessToken();
|
||||
bool resetAccountManagerAccessToken();
|
||||
|
||||
void setupAutomaticNetworking();
|
||||
|
@ -153,6 +154,7 @@ private:
|
|||
DomainServerSettingsManager _settingsManager;
|
||||
|
||||
HifiSockAddr _iceServerSocket;
|
||||
std::unique_ptr<NLPacket> _iceServerHeartbeatPacket;
|
||||
|
||||
QTimer* _iceHeartbeatTimer { nullptr }; // this looks like it dangles when created but it's parented to the DomainServer
|
||||
|
||||
|
|
120
examples/away.js
120
examples/away.js
|
@ -13,13 +13,30 @@
|
|||
//
|
||||
// Goes into "paused" when the '.' key (and automatically when started in HMD), and normal when pressing any key.
|
||||
// See MAIN CONTROL, below, for what "paused" actually does.
|
||||
var OVERLAY_RATIO = 1920 / 1080;
|
||||
var OVERLAY_WIDTH = 1920;
|
||||
var OVERLAY_HEIGHT = 1080;
|
||||
var OVERLAY_RATIO = OVERLAY_WIDTH / OVERLAY_HEIGHT;
|
||||
var OVERLAY_DATA = {
|
||||
width: OVERLAY_WIDTH,
|
||||
height: OVERLAY_HEIGHT,
|
||||
imageURL: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png",
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
alpha: 1
|
||||
};
|
||||
|
||||
var lastOverlayPosition = { x: 0, y: 0, z: 0};
|
||||
var OVERLAY_DATA_HMD = {
|
||||
position: lastOverlayPosition,
|
||||
width: OVERLAY_WIDTH,
|
||||
height: OVERLAY_HEIGHT,
|
||||
url: "http://hifi-content.s3.amazonaws.com/alan/production/images/images/Overlay-Viz-blank.png",
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
alpha: 1,
|
||||
scale: 2,
|
||||
isFacingAvatar: true,
|
||||
drawInFront: true
|
||||
};
|
||||
|
||||
// ANIMATION
|
||||
// We currently don't have play/stopAnimation integrated with the animation graph, but we can get the same effect
|
||||
// using an animation graph with a state that we turn on and off through the animation var defined with that state.
|
||||
|
@ -64,29 +81,74 @@ function stopAwayAnimation() {
|
|||
|
||||
// OVERLAY
|
||||
var overlay = Overlays.addOverlay("image", OVERLAY_DATA);
|
||||
var overlayHMD = Overlays.addOverlay("image3d", OVERLAY_DATA_HMD);
|
||||
|
||||
function moveCloserToCamera(positionAtHUD) {
|
||||
// we don't actually want to render at the slerped look at... instead, we want to render
|
||||
// slightly closer to the camera than that.
|
||||
var MOVE_CLOSER_TO_CAMERA_BY = -0.25;
|
||||
var cameraFront = Quat.getFront(Camera.orientation);
|
||||
var closerToCamera = Vec3.multiply(cameraFront, MOVE_CLOSER_TO_CAMERA_BY); // slightly closer to camera
|
||||
var slightlyCloserPosition = Vec3.sum(positionAtHUD, closerToCamera);
|
||||
|
||||
return slightlyCloserPosition;
|
||||
}
|
||||
|
||||
function showOverlay() {
|
||||
var properties = {visible: true},
|
||||
// Update for current screen size, keeping overlay proportions constant.
|
||||
screen = Controller.getViewportDimensions(),
|
||||
screenRatio = screen.x / screen.y;
|
||||
if (screenRatio < OVERLAY_RATIO) {
|
||||
properties.width = screen.x;
|
||||
properties.height = screen.x / OVERLAY_RATIO;
|
||||
properties.x = 0;
|
||||
properties.y = (screen.y - properties.height) / 2;
|
||||
var properties = {visible: true};
|
||||
|
||||
if (HMD.active) {
|
||||
// make sure desktop version is hidden
|
||||
Overlays.editOverlay(overlay, { visible: false });
|
||||
|
||||
lastOverlayPosition = HMD.getHUDLookAtPosition3D();
|
||||
var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition);
|
||||
Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon });
|
||||
} else {
|
||||
properties.height = screen.y;
|
||||
properties.width = screen.y * OVERLAY_RATIO;
|
||||
properties.y = 0;
|
||||
properties.x = (screen.x - properties.width) / 2;
|
||||
// make sure HMD is hidden
|
||||
Overlays.editOverlay(overlayHMD, { visible: false });
|
||||
|
||||
// Update for current screen size, keeping overlay proportions constant.
|
||||
var screen = Controller.getViewportDimensions();
|
||||
|
||||
// keep the overlay it's natural size and always center it...
|
||||
Overlays.editOverlay(overlay, { visible: true,
|
||||
x: ((screen.x - OVERLAY_WIDTH) / 2),
|
||||
y: ((screen.y - OVERLAY_HEIGHT) / 2) });
|
||||
}
|
||||
Overlays.editOverlay(overlay, properties);
|
||||
}
|
||||
function hideOverlay() {
|
||||
Overlays.editOverlay(overlay, {visible: false});
|
||||
Overlays.editOverlay(overlayHMD, {visible: false});
|
||||
}
|
||||
hideOverlay();
|
||||
|
||||
function maybeMoveOverlay() {
|
||||
if (isAway) {
|
||||
// if we switched from HMD to Desktop, make sure to hide our HUD overlay and show the
|
||||
// desktop overlay
|
||||
if (!HMD.active) {
|
||||
showOverlay(); // this will also recenter appropriately
|
||||
}
|
||||
|
||||
if (HMD.active) {
|
||||
// Note: instead of moving it directly to the lookAt, we will move it slightly toward the
|
||||
// new look at. This will result in a more subtle slerp toward the look at and reduce jerkiness
|
||||
var EASE_BY_RATIO = 0.1;
|
||||
var lookAt = HMD.getHUDLookAtPosition3D();
|
||||
var lookAtChange = Vec3.subtract(lookAt, lastOverlayPosition);
|
||||
var halfWayBetweenOldAndLookAt = Vec3.multiply(lookAtChange, EASE_BY_RATIO);
|
||||
var newOverlayPosition = Vec3.sum(lastOverlayPosition, halfWayBetweenOldAndLookAt);
|
||||
lastOverlayPosition = newOverlayPosition;
|
||||
|
||||
var actualOverlayPositon = moveCloserToCamera(lastOverlayPosition);
|
||||
Overlays.editOverlay(overlayHMD, { visible: true, position: actualOverlayPositon });
|
||||
|
||||
// make sure desktop version is hidden
|
||||
Overlays.editOverlay(overlay, { visible: false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MAIN CONTROL
|
||||
var wasMuted, isAway;
|
||||
|
@ -106,6 +168,12 @@ function goAway() {
|
|||
MyAvatar.setEnableMeshVisible(false); // just for our own display, without changing point of view
|
||||
playAwayAnimation(); // animation is still seen by others
|
||||
showOverlay();
|
||||
|
||||
// tell the Reticle, we want to stop capturing the mouse until we come back
|
||||
Reticle.allowMouseCapture = false;
|
||||
if (HMD.active) {
|
||||
Reticle.visible = false;
|
||||
}
|
||||
}
|
||||
function goActive() {
|
||||
if (!isAway) {
|
||||
|
@ -119,20 +187,28 @@ function goActive() {
|
|||
MyAvatar.setEnableMeshVisible(true); // IWBNI we respected Developer->Avatar->Draw Mesh setting.
|
||||
stopAwayAnimation();
|
||||
hideOverlay();
|
||||
|
||||
// tell the Reticle, we are ready to capture the mouse again and it should be visible
|
||||
Reticle.allowMouseCapture = true;
|
||||
Reticle.visible = true;
|
||||
if (HMD.active) {
|
||||
Reticle.position = HMD.getHUDLookAtPosition2D();
|
||||
}
|
||||
}
|
||||
|
||||
function maybeGoActive(event) {
|
||||
if (event.isAutoRepeat) { // isAutoRepeat is true when held down (or when Windows feels like it)
|
||||
return;
|
||||
}
|
||||
if (!isAway && (event.text === '.')) {
|
||||
if (!isAway && (event.text == 'ESC')) {
|
||||
goAway();
|
||||
} else {
|
||||
goActive();
|
||||
}
|
||||
}
|
||||
var wasHmdActive = false;
|
||||
var wasMouseCaptured = false;
|
||||
var wasHmdActive = HMD.active;
|
||||
var wasMouseCaptured = Reticle.mouseCaptured;
|
||||
|
||||
function maybeGoAway() {
|
||||
if (HMD.active !== wasHmdActive) {
|
||||
wasHmdActive = !wasHmdActive;
|
||||
|
@ -141,10 +217,8 @@ function maybeGoAway() {
|
|||
}
|
||||
}
|
||||
|
||||
// If the mouse has gone from captured, to non-captured state,
|
||||
// then it likely means the person is still in the HMD, but has
|
||||
// tabbed away from the application (meaning they don't have mouse
|
||||
// control) and they likely want to go into an away state
|
||||
// If the mouse has gone from captured, to non-captured state, then it likely means the person is still in the HMD, but
|
||||
// tabbed away from the application (meaning they don't have mouse control) and they likely want to go into an away state
|
||||
if (Reticle.mouseCaptured !== wasMouseCaptured) {
|
||||
wasMouseCaptured = !wasMouseCaptured;
|
||||
if (!wasMouseCaptured) {
|
||||
|
@ -153,6 +227,8 @@ function maybeGoAway() {
|
|||
}
|
||||
}
|
||||
|
||||
Script.update.connect(maybeMoveOverlay);
|
||||
|
||||
Script.update.connect(maybeGoAway);
|
||||
Controller.mousePressEvent.connect(goActive);
|
||||
Controller.keyPressEvent.connect(maybeGoActive);
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
"use strict";
|
||||
// handControllerGrab.js
|
||||
//
|
||||
// Created by Eric Levin on 9/2/15
|
||||
|
@ -801,6 +802,8 @@ function MyController(hand) {
|
|||
this.isInitialGrab = false;
|
||||
this.doubleParentGrab = false;
|
||||
|
||||
this.checkForStrayChildren();
|
||||
|
||||
if (this.state == STATE_SEARCHING ? this.triggerSmoothedReleased() : this.bumperReleased()) {
|
||||
this.setState(STATE_RELEASE);
|
||||
return;
|
||||
|
@ -1046,9 +1049,14 @@ function MyController(hand) {
|
|||
}
|
||||
|
||||
this.distanceHolding = function() {
|
||||
var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition;
|
||||
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation);
|
||||
|
||||
// controller pose is in avatar frame
|
||||
var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand);
|
||||
|
||||
// transform it into world frame
|
||||
var controllerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation));
|
||||
var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
|
||||
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
||||
var now = Date.now();
|
||||
|
||||
|
@ -1056,12 +1064,10 @@ function MyController(hand) {
|
|||
this.currentObjectPosition = grabbedProperties.position;
|
||||
this.currentObjectRotation = grabbedProperties.rotation;
|
||||
this.currentObjectTime = now;
|
||||
this.handRelativePreviousPosition = Vec3.subtract(handControllerPosition, MyAvatar.position);
|
||||
this.handPreviousRotation = handRotation;
|
||||
this.currentCameraOrientation = Camera.orientation;
|
||||
|
||||
// compute a constant based on the initial conditions which we use below to exagerate hand motion onto the held object
|
||||
this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, handControllerPosition) + 1.0);
|
||||
this.radiusScalar = Math.log(Vec3.distance(this.currentObjectPosition, controllerPosition) + 1.0);
|
||||
if (this.radiusScalar < 1.0) {
|
||||
this.radiusScalar = 1.0;
|
||||
}
|
||||
|
@ -1091,10 +1097,10 @@ function MyController(hand) {
|
|||
this.callEntityMethodOnGrabbed("startDistanceGrab");
|
||||
}
|
||||
|
||||
this.currentAvatarPosition = MyAvatar.position;
|
||||
this.currentAvatarOrientation = MyAvatar.orientation;
|
||||
|
||||
this.turnOffVisualizations();
|
||||
|
||||
this.previousControllerPosition = controllerPosition;
|
||||
this.previousControllerRotation = controllerRotation;
|
||||
};
|
||||
|
||||
this.continueDistanceHolding = function() {
|
||||
|
@ -1106,10 +1112,13 @@ function MyController(hand) {
|
|||
|
||||
this.heartBeat(this.grabbedEntity);
|
||||
|
||||
var handPosition = this.getHandPosition();
|
||||
var handControllerPosition = (this.hand === RIGHT_HAND) ? MyAvatar.rightHandPosition : MyAvatar.leftHandPosition;
|
||||
var controllerHandInput = (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand;
|
||||
var handRotation = Quat.multiply(MyAvatar.orientation, Controller.getPoseValue(controllerHandInput).rotation);
|
||||
// controller pose is in avatar frame
|
||||
var avatarControllerPose = Controller.getPoseValue((this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand);
|
||||
|
||||
// transform it into world frame
|
||||
var controllerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, avatarControllerPose.translation));
|
||||
var controllerRotation = Quat.multiply(MyAvatar.orientation, avatarControllerPose.rotation);
|
||||
|
||||
var grabbedProperties = Entities.getEntityProperties(this.grabbedEntity, GRABBABLE_PROPERTIES);
|
||||
var grabbableData = getEntityCustomData(GRABBABLE_DATA_KEY, this.grabbedEntity, DEFAULT_GRABBABLE_DATA);
|
||||
|
||||
|
@ -1122,66 +1131,27 @@ function MyController(hand) {
|
|||
return;
|
||||
}
|
||||
|
||||
var now = Date.now();
|
||||
this.currentObjectTime = now;
|
||||
|
||||
// the action was set up on a previous call. update the targets.
|
||||
var radius = Vec3.distance(this.currentObjectPosition, handControllerPosition) *
|
||||
var radius = Vec3.distance(this.currentObjectPosition, controllerPosition) *
|
||||
this.radiusScalar * DISTANCE_HOLDING_RADIUS_FACTOR;
|
||||
if (radius < 1.0) {
|
||||
radius = 1.0;
|
||||
}
|
||||
|
||||
// how far did avatar move this timestep?
|
||||
var currentPosition = MyAvatar.position;
|
||||
var avatarDeltaPosition = Vec3.subtract(currentPosition, this.currentAvatarPosition);
|
||||
this.currentAvatarPosition = currentPosition;
|
||||
// scale delta controller hand movement by radius.
|
||||
var handMoved = Vec3.multiply(Vec3.subtract(controllerPosition, this.previousControllerPosition), radius);
|
||||
|
||||
// How far did the avatar turn this timestep?
|
||||
// Note: The following code is too long because we need a Quat.quatBetween() function
|
||||
// that returns the minimum quaternion between two quaternions.
|
||||
var currentOrientation = MyAvatar.orientation;
|
||||
if (Quat.dot(currentOrientation, this.currentAvatarOrientation) < 0.0) {
|
||||
var negativeCurrentOrientation = {
|
||||
x: -currentOrientation.x,
|
||||
y: -currentOrientation.y,
|
||||
z: -currentOrientation.z,
|
||||
w: -currentOrientation.w
|
||||
};
|
||||
var avatarDeltaOrientation = Quat.multiply(negativeCurrentOrientation, Quat.inverse(this.currentAvatarOrientation));
|
||||
} else {
|
||||
var avatarDeltaOrientation = Quat.multiply(currentOrientation, Quat.inverse(this.currentAvatarOrientation));
|
||||
}
|
||||
var handToAvatar = Vec3.subtract(handControllerPosition, this.currentAvatarPosition);
|
||||
var objectToAvatar = Vec3.subtract(this.currentObjectPosition, this.currentAvatarPosition);
|
||||
var handMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, handToAvatar), handToAvatar);
|
||||
var objectMovementFromTurning = Vec3.subtract(Quat.multiply(avatarDeltaOrientation, objectToAvatar), objectToAvatar);
|
||||
this.currentAvatarOrientation = currentOrientation;
|
||||
// double delta controller rotation
|
||||
var handChange = Quat.multiply(Quat.slerp(this.previousControllerRotation,
|
||||
controllerRotation,
|
||||
DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR),
|
||||
Quat.inverse(this.previousControllerRotation));
|
||||
|
||||
// how far did hand move this timestep?
|
||||
var handMoved = Vec3.subtract(handToAvatar, this.handRelativePreviousPosition);
|
||||
this.handRelativePreviousPosition = handToAvatar;
|
||||
|
||||
// magnify the hand movement but not the change from avatar movement & rotation
|
||||
handMoved = Vec3.subtract(handMoved, handMovementFromTurning);
|
||||
var superHandMoved = Vec3.multiply(handMoved, radius);
|
||||
|
||||
// Move the object by the magnified amount and then by amount from avatar movement & rotation
|
||||
var newObjectPosition = Vec3.sum(this.currentObjectPosition, superHandMoved);
|
||||
newObjectPosition = Vec3.sum(newObjectPosition, avatarDeltaPosition);
|
||||
newObjectPosition = Vec3.sum(newObjectPosition, objectMovementFromTurning);
|
||||
|
||||
var deltaPosition = Vec3.subtract(newObjectPosition, this.currentObjectPosition); // meters
|
||||
var now = Date.now();
|
||||
var deltaTime = (now - this.currentObjectTime) / MSEC_PER_SEC; // convert to seconds
|
||||
|
||||
this.currentObjectPosition = newObjectPosition;
|
||||
this.currentObjectTime = now;
|
||||
|
||||
// this doubles hand rotation
|
||||
var handChange = Quat.multiply(Quat.slerp(this.handPreviousRotation,
|
||||
handRotation,
|
||||
DISTANCE_HOLDING_ROTATION_EXAGGERATION_FACTOR),
|
||||
Quat.inverse(this.handPreviousRotation));
|
||||
this.handPreviousRotation = handRotation;
|
||||
// update the currentObject position and rotation.
|
||||
this.currentObjectPosition = Vec3.sum(this.currentObjectPosition, handMoved);
|
||||
this.currentObjectRotation = Quat.multiply(handChange, this.currentObjectRotation);
|
||||
|
||||
this.callEntityMethodOnGrabbed("continueDistantGrab");
|
||||
|
@ -1192,6 +1162,7 @@ function MyController(hand) {
|
|||
|
||||
var handControllerData = getEntityCustomData('handControllerKey', this.grabbedEntity, defaultMoveWithHeadData);
|
||||
|
||||
var objectToAvatar = Vec3.subtract(this.currentObjectPosition, MyAvatar.position);
|
||||
if (handControllerData.disableMoveWithHead !== true) {
|
||||
// mix in head motion
|
||||
if (MOVE_WITH_HEAD) {
|
||||
|
@ -1231,6 +1202,7 @@ function MyController(hand) {
|
|||
}
|
||||
}
|
||||
|
||||
var handPosition = this.getHandPosition();
|
||||
|
||||
//visualizations
|
||||
if (USE_ENTITY_LINES_FOR_MOVING === true) {
|
||||
|
@ -1262,6 +1234,9 @@ function MyController(hand) {
|
|||
} else {
|
||||
print("continueDistanceHolding -- updateAction failed");
|
||||
}
|
||||
|
||||
this.previousControllerPosition = controllerPosition;
|
||||
this.previousControllerRotation = controllerRotation;
|
||||
};
|
||||
|
||||
this.setupHoldAction = function() {
|
||||
|
@ -1444,6 +1419,13 @@ function MyController(hand) {
|
|||
this.heartBeat(this.grabbedEntity);
|
||||
|
||||
var props = Entities.getEntityProperties(this.grabbedEntity, ["localPosition", "parentID", "position"]);
|
||||
if (!props.position) {
|
||||
// server may have reset, taking our equipped entity with it. move back to "off" stte
|
||||
this.setState(STATE_RELEASE);
|
||||
this.callEntityMethodOnGrabbed("releaseGrab");
|
||||
return;
|
||||
}
|
||||
|
||||
if (props.parentID == MyAvatar.sessionUUID &&
|
||||
Vec3.length(props.localPosition) > NEAR_PICK_MAX_DISTANCE * 2.0) {
|
||||
// for whatever reason, the held/equipped entity has been pulled away. ungrab or unequip.
|
||||
|
@ -1751,6 +1733,17 @@ function MyController(hand) {
|
|||
return data;
|
||||
};
|
||||
|
||||
this.checkForStrayChildren = function() {
|
||||
// sometimes things can get parented to a hand and this script is unaware. Search for such entities and
|
||||
// unhook them.
|
||||
var handJointIndex = MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? "RightHand" : "LeftHand");
|
||||
var children = Entities.getChildrenIDsOfJoint(MyAvatar.sessionUUID, handJointIndex);
|
||||
children.forEach(function(childID) {
|
||||
print("disconnecting stray child of hand: (" + _this.hand + ") " + childID);
|
||||
Entities.editEntity(childID, {parentID: NULL_UUID});
|
||||
});
|
||||
}
|
||||
|
||||
this.deactivateEntity = function(entityID, noVelocity) {
|
||||
var data = getEntityCustomData(GRAB_USER_DATA_KEY, entityID, {});
|
||||
if (data && data["refCount"]) {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
//
|
||||
// reticleHandRotationTest.js
|
||||
// handControllerMouse.js
|
||||
// examples/controllers
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 2015/12/15
|
||||
|
@ -10,6 +10,9 @@
|
|||
//
|
||||
|
||||
var DEBUGGING = false;
|
||||
var angularVelocityTrailingAverage = 0.0; // Global trailing average used to decide whether to move reticle at all
|
||||
var lastX = 0;
|
||||
var lastY = 0;
|
||||
|
||||
Math.clamp=function(a,b,c) {
|
||||
return Math.max(b,Math.min(c,a));
|
||||
|
@ -31,12 +34,17 @@ function moveReticleAbsolute(x, y) {
|
|||
|
||||
var MAPPING_NAME = "com.highfidelity.testing.reticleWithHandRotation";
|
||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||
mapping.from(Controller.Hardware.Hydra.L3).peek().to(Controller.Actions.ReticleClick);
|
||||
mapping.from(Controller.Hardware.Hydra.R4).peek().to(Controller.Actions.ReticleClick);
|
||||
if (Controller.Hardware.Hydra !== undefined) {
|
||||
mapping.from(Controller.Hardware.Hydra.L3).peek().to(Controller.Actions.ReticleClick);
|
||||
mapping.from(Controller.Hardware.Hydra.R4).peek().to(Controller.Actions.ReticleClick);
|
||||
}
|
||||
if (Controller.Hardware.Vive !== undefined) {
|
||||
mapping.from(Controller.Hardware.Vive.LeftPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
|
||||
mapping.from(Controller.Hardware.Vive.RightPrimaryThumb).peek().to(Controller.Actions.ReticleClick);
|
||||
}
|
||||
|
||||
mapping.enable();
|
||||
|
||||
|
||||
|
||||
function debugPrint(message) {
|
||||
if (DEBUGGING) {
|
||||
print(message);
|
||||
|
@ -76,7 +84,7 @@ Script.update.connect(function(deltaTime) {
|
|||
}
|
||||
|
||||
// Velocity filter the hand rotation used to position reticle so that it is easier to target small things with the hand controllers
|
||||
var VELOCITY_FILTER_GAIN = 1.0;
|
||||
var VELOCITY_FILTER_GAIN = 0.5;
|
||||
filteredRotatedLeft = Vec3.mix(filteredRotatedLeft, rotatedLeft, Math.clamp(Vec3.length(poseLeft.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
|
||||
filteredRotatedRight = Vec3.mix(filteredRotatedRight, rotatedRight, Math.clamp(Vec3.length(poseRight.angularVelocity) * VELOCITY_FILTER_GAIN, 0.0, 1.0));
|
||||
var rotated = Vec3.mix(filteredRotatedLeft, filteredRotatedRight, leftRightBias);
|
||||
|
@ -96,11 +104,17 @@ Script.update.connect(function(deltaTime) {
|
|||
var y = screenSizeY * yRatio;
|
||||
|
||||
// don't move the reticle with the hand controllers unless the controllers are actually being moved
|
||||
var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.0001;
|
||||
// take a time average of angular velocity, and don't move mouse at all if it's below threshold
|
||||
|
||||
var AVERAGING_INTERVAL = 0.95;
|
||||
var MINIMUM_CONTROLLER_ANGULAR_VELOCITY = 0.03;
|
||||
var angularVelocityMagnitude = Vec3.length(poseLeft.angularVelocity) * (1.0 - leftRightBias) + Vec3.length(poseRight.angularVelocity) * leftRightBias;
|
||||
angularVelocityTrailingAverage = angularVelocityTrailingAverage * AVERAGING_INTERVAL + angularVelocityMagnitude * (1.0 - AVERAGING_INTERVAL);
|
||||
|
||||
if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityMagnitude > MINIMUM_CONTROLLER_ANGULAR_VELOCITY)) {
|
||||
if (!(xRatio == 0.5 && yRatio == 0) && (angularVelocityTrailingAverage > MINIMUM_CONTROLLER_ANGULAR_VELOCITY) && ((x != lastX) || (y != lastY))) {
|
||||
moveReticleAbsolute(x, y);
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
}
|
||||
});
|
||||
|
|
@ -22,3 +22,4 @@ Script.load("grab.js");
|
|||
Script.load("directory.js");
|
||||
Script.load("dialTone.js");
|
||||
Script.load("attachedEntitiesManager.js");
|
||||
Script.load("depthReticle.js");
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// When used in HMD, this script will make the reticle depth track to any clickable item in view.
|
||||
// This script also handles auto-hiding the reticle after inactivity, as well as having the reticle
|
||||
// seek the look at position upon waking up.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
@ -12,17 +14,104 @@
|
|||
|
||||
var APPARENT_2D_OVERLAY_DEPTH = 1.0;
|
||||
var APPARENT_MAXIMUM_DEPTH = 100.0; // this is a depth at which things all seem sufficiently distant
|
||||
var lastDepthCheckTime = 0;
|
||||
var lastDepthCheckTime = Date.now();
|
||||
var desiredDepth = APPARENT_2D_OVERLAY_DEPTH;
|
||||
var TIME_BETWEEN_DEPTH_CHECKS = 100;
|
||||
var MINIMUM_DEPTH_ADJUST = 0.01;
|
||||
var NON_LINEAR_DIVISOR = 2;
|
||||
var MINIMUM_SEEK_DISTANCE = 0.01;
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
var TIME_BETWEEN_DEPTH_CHECKS = 100;
|
||||
var timeSinceLastDepthCheck = Date.now() - lastDepthCheckTime;
|
||||
var lastMouseMove = Date.now();
|
||||
var lastMouseX = Reticle.position.x;
|
||||
var lastMouseY = Reticle.position.y;
|
||||
var HIDE_STATIC_MOUSE_AFTER = 3000; // 3 seconds
|
||||
var shouldSeekToLookAt = false;
|
||||
var fastMouseMoves = 0;
|
||||
var averageMouseVelocity = 0;
|
||||
var WEIGHTING = 1/20; // simple moving average over last 20 samples
|
||||
var ONE_MINUS_WEIGHTING = 1 - WEIGHTING;
|
||||
var AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO = 50;
|
||||
|
||||
Controller.mouseMoveEvent.connect(function(mouseEvent) {
|
||||
var now = Date.now();
|
||||
|
||||
// if the reticle is hidden, and we're not in away mode...
|
||||
if (!Reticle.visible && Reticle.allowMouseCapture) {
|
||||
Reticle.visible = true;
|
||||
if (HMD.active) {
|
||||
shouldSeekToLookAt = true;
|
||||
}
|
||||
} else {
|
||||
// even if the reticle is visible, if we're in HMD mode, and the person is moving their mouse quickly (shaking it)
|
||||
// then they are probably looking for it, and we should move into seekToLookAt mode
|
||||
if (HMD.active && !shouldSeekToLookAt && Reticle.allowMouseCapture) {
|
||||
var dx = Reticle.position.x - lastMouseX;
|
||||
var dy = Reticle.position.y - lastMouseY;
|
||||
var dt = Math.max(1, (now - lastMouseMove)); // mSecs since last mouse move
|
||||
var mouseMoveDistance = Math.sqrt((dx*dx) + (dy*dy));
|
||||
var mouseVelocity = mouseMoveDistance / dt;
|
||||
averageMouseVelocity = (ONE_MINUS_WEIGHTING * averageMouseVelocity) + (WEIGHTING * mouseVelocity);
|
||||
if (averageMouseVelocity > AVERAGE_MOUSE_VELOCITY_FOR_SEEK_TO) {
|
||||
shouldSeekToLookAt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
lastMouseMove = now;
|
||||
lastMouseX = mouseEvent.x;
|
||||
lastMouseY = mouseEvent.y;
|
||||
});
|
||||
|
||||
function seekToLookAt() {
|
||||
// if we're currently seeking the lookAt move the mouse toward the lookat
|
||||
if (shouldSeekToLookAt) {
|
||||
averageMouseVelocity = 0; // reset this, these never count for movement...
|
||||
var lookAt2D = HMD.getHUDLookAtPosition2D();
|
||||
var currentReticlePosition = Reticle.position;
|
||||
var distanceBetweenX = lookAt2D.x - Reticle.position.x;
|
||||
var distanceBetweenY = lookAt2D.y - Reticle.position.y;
|
||||
var moveX = distanceBetweenX / NON_LINEAR_DIVISOR;
|
||||
var moveY = distanceBetweenY / NON_LINEAR_DIVISOR;
|
||||
var newPosition = { x: Reticle.position.x + moveX, y: Reticle.position.y + moveY };
|
||||
var closeEnoughX = false;
|
||||
var closeEnoughY = false;
|
||||
if (moveX < MINIMUM_SEEK_DISTANCE) {
|
||||
newPosition.x = lookAt2D.x;
|
||||
closeEnoughX = true;
|
||||
}
|
||||
if (moveY < MINIMUM_SEEK_DISTANCE) {
|
||||
newPosition.y = lookAt2D.y;
|
||||
closeEnoughY = true;
|
||||
}
|
||||
Reticle.position = newPosition;
|
||||
if (closeEnoughX && closeEnoughY) {
|
||||
shouldSeekToLookAt = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function autoHideReticle() {
|
||||
// if we haven't moved in a long period of time, and we're not pointing at some
|
||||
// system overlay (like a window), then hide the reticle
|
||||
if (Reticle.visible && !Reticle.pointingAtSystemOverlay) {
|
||||
var now = Date.now();
|
||||
var timeSinceLastMouseMove = now - lastMouseMove;
|
||||
if (timeSinceLastMouseMove > HIDE_STATIC_MOUSE_AFTER) {
|
||||
Reticle.visible = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function checkReticleDepth() {
|
||||
var now = Date.now();
|
||||
var timeSinceLastDepthCheck = now - lastDepthCheckTime;
|
||||
if (timeSinceLastDepthCheck > TIME_BETWEEN_DEPTH_CHECKS) {
|
||||
var newDesiredDepth = desiredDepth;
|
||||
lastDepthCheckTime = now;
|
||||
var reticlePosition = Reticle.position;
|
||||
|
||||
// first check the 2D Overlays
|
||||
if (Reticle.pointingAtSystemOverlay || Overlays.getOverlayAtPoint(reticlePosition)) {
|
||||
Reticle.setDepth(APPARENT_2D_OVERLAY_DEPTH);
|
||||
newDesiredDepth = APPARENT_2D_OVERLAY_DEPTH;
|
||||
} else {
|
||||
var pickRay = Camera.computePickRay(reticlePosition.x, reticlePosition.y);
|
||||
|
||||
|
@ -37,11 +126,42 @@ Script.update.connect(function(deltaTime) {
|
|||
// If either the overlays or entities intersect, then set the reticle depth to
|
||||
// the distance of intersection
|
||||
if (result.intersects) {
|
||||
Reticle.setDepth(result.distance);
|
||||
newDesiredDepth = result.distance;
|
||||
} else {
|
||||
// if nothing intersects... set the depth to some sufficiently large depth
|
||||
Reticle.setDepth(APPARENT_MAXIMUM_DEPTH);
|
||||
newDesiredDepth = APPARENT_MAXIMUM_DEPTH;
|
||||
}
|
||||
}
|
||||
|
||||
// If the desired depth has changed, reset our fade start time
|
||||
if (desiredDepth != newDesiredDepth) {
|
||||
desiredDepth = newDesiredDepth;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function moveToDesiredDepth() {
|
||||
// move the reticle toward the desired depth
|
||||
if (desiredDepth != Reticle.depth) {
|
||||
|
||||
// cut distance between desiredDepth and current depth in half until we're close enough
|
||||
var distanceToAdjustThisCycle = (desiredDepth - Reticle.depth) / NON_LINEAR_DIVISOR;
|
||||
if (Math.abs(distanceToAdjustThisCycle) < MINIMUM_DEPTH_ADJUST) {
|
||||
newDepth = desiredDepth;
|
||||
} else {
|
||||
newDepth = Reticle.depth + distanceToAdjustThisCycle;
|
||||
}
|
||||
|
||||
Reticle.setDepth(newDepth);
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(function(deltaTime) {
|
||||
autoHideReticle(); // auto hide reticle for desktop or HMD mode
|
||||
if (HMD.active) {
|
||||
seekToLookAt(); // handle moving the reticle toward the look at
|
||||
checkReticleDepth(); // make sure reticle is at correct depth
|
||||
moveToDesiredDepth(); // move the fade the reticle to the desired depth
|
||||
}
|
||||
});
|
||||
|
|
|
@ -89,6 +89,9 @@ var SETTING_EASE_ON_FOCUS = "cameraEaseOnFocus";
|
|||
var SETTING_SHOW_LIGHTS_IN_EDIT_MODE = "showLightsInEditMode";
|
||||
var SETTING_SHOW_ZONES_IN_EDIT_MODE = "showZonesInEditMode";
|
||||
|
||||
|
||||
// marketplace info, etc. not quite ready yet.
|
||||
var SHOULD_SHOW_PROPERTY_MENU = false;
|
||||
var INSUFFICIENT_PERMISSIONS_ERROR_MSG = "You do not have the necessary permissions to edit on this domain."
|
||||
var INSUFFICIENT_PERMISSIONS_IMPORT_ERROR_MSG = "You do not have the necessary permissions to place items on this domain."
|
||||
|
||||
|
@ -711,13 +714,32 @@ var intersection;
|
|||
|
||||
var SCALE_FACTOR = 200.0;
|
||||
|
||||
function rayPlaneIntersection(pickRay, point, normal) {
|
||||
function rayPlaneIntersection(pickRay, point, normal) { //
|
||||
//
|
||||
// This version of the test returns the intersection of a line with a plane
|
||||
//
|
||||
var collides = Vec3.dot(pickRay.direction, normal);
|
||||
|
||||
var d = -Vec3.dot(point, normal);
|
||||
var t = -(Vec3.dot(pickRay.origin, normal) + d) / Vec3.dot(pickRay.direction, normal);
|
||||
var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides;
|
||||
|
||||
return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t));
|
||||
}
|
||||
|
||||
function rayPlaneIntersection2(pickRay, point, normal) {
|
||||
//
|
||||
// This version of the test returns false if the ray is directed away from the plane
|
||||
//
|
||||
var collides = Vec3.dot(pickRay.direction, normal);
|
||||
var d = -Vec3.dot(point, normal);
|
||||
var t = -(Vec3.dot(pickRay.origin, normal) + d) / collides;
|
||||
if (t < 0.0) {
|
||||
return false;
|
||||
} else {
|
||||
return Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction, t));
|
||||
}
|
||||
}
|
||||
|
||||
function findClickedEntity(event) {
|
||||
var pickZones = event.isControl;
|
||||
|
||||
|
@ -758,7 +780,8 @@ function findClickedEntity(event) {
|
|||
var foundEntity = result.entityID;
|
||||
return {
|
||||
pickRay: pickRay,
|
||||
entityID: foundEntity
|
||||
entityID: foundEntity,
|
||||
intersection: result.intersection
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -926,6 +949,7 @@ function mouseReleaseEvent(event) {
|
|||
}
|
||||
|
||||
function mouseClickEvent(event) {
|
||||
var wantDebug = false;
|
||||
if (isActive && event.isLeftButton) {
|
||||
var result = findClickedEntity(event);
|
||||
if (result === null) {
|
||||
|
@ -940,11 +964,15 @@ function mouseClickEvent(event) {
|
|||
|
||||
var properties = Entities.getEntityProperties(foundEntity);
|
||||
if (isLocked(properties)) {
|
||||
print("Model locked " + properties.id);
|
||||
if (wantDebug) {
|
||||
print("Model locked " + properties.id);
|
||||
}
|
||||
} else {
|
||||
var halfDiagonal = Vec3.length(properties.dimensions) / 2.0;
|
||||
|
||||
print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal);
|
||||
if (wantDebug) {
|
||||
print("Checking properties: " + properties.id + " " + " - Half Diagonal:" + halfDiagonal);
|
||||
}
|
||||
// P P - Model
|
||||
// /| A - Palm
|
||||
// / | d B - unit vector toward tip
|
||||
|
@ -981,8 +1009,9 @@ function mouseClickEvent(event) {
|
|||
} else {
|
||||
selectionManager.addEntity(foundEntity, true);
|
||||
}
|
||||
|
||||
print("Model selected: " + foundEntity);
|
||||
if (wantDebug) {
|
||||
print("Model selected: " + foundEntity);
|
||||
}
|
||||
selectionDisplay.select(selectedEntityID, event);
|
||||
|
||||
if (Menu.isOptionChecked(MENU_AUTO_FOCUS_ON_SELECT)) {
|
||||
|
@ -996,6 +1025,9 @@ function mouseClickEvent(event) {
|
|||
} else if (event.isRightButton) {
|
||||
var result = findClickedEntity(event);
|
||||
if (result) {
|
||||
if (SHOULD_SHOW_PROPERTY_MENU !== true) {
|
||||
return;
|
||||
}
|
||||
var properties = Entities.getEntityProperties(result.entityID);
|
||||
if (properties.marketplaceID) {
|
||||
propertyMenu.marketplaceID = properties.marketplaceID;
|
||||
|
@ -1869,9 +1901,11 @@ PopupMenu = function() {
|
|||
return this;
|
||||
};
|
||||
|
||||
|
||||
var propertyMenu = PopupMenu();
|
||||
|
||||
propertyMenu.onSelectMenuItem = function(name) {
|
||||
|
||||
if (propertyMenu.marketplaceID) {
|
||||
showMarketplace(propertyMenu.marketplaceID);
|
||||
}
|
||||
|
|
339
examples/entityScripts/lightningEntity.js
Normal file
339
examples/entityScripts/lightningEntity.js
Normal file
|
@ -0,0 +1,339 @@
|
|||
//
|
||||
// lightningEntity.js
|
||||
// examples/entityScripts
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 3/1/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example of an entity script which will randomly create a flash of lightning and a thunder sound
|
||||
// effect, as well as a background rain sound effect. It can be applied to any entity, although it works best
|
||||
// on a zone entity.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt, pointInExtents, vec3equal, setEntityCustomData, getEntityCustomData */
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Various configurable settings.
|
||||
//
|
||||
// You can change these values to change some of the various effects of the rain storm.
|
||||
// These values can also be controlled by setting user properties on the entity that you've attached this script to.
|
||||
// add a "lightning" section to a JSON encoded portion of the user data... for example:
|
||||
// {
|
||||
// "lightning": {
|
||||
// "flashMax": 20,
|
||||
// "flashMin": 0,
|
||||
// "flashMaxRandomness": 10,
|
||||
// "flashIntensityStepRandomeness": 2,
|
||||
// "averageLighteningStrikeGap": 120,
|
||||
// "extraRandomRangeLightningStrikeGap": 10,
|
||||
// "thunderURL": "atp:1336efe995398f5e0d46b37585785de8ba872fe9a9b718264db03748cd41c758.wav",
|
||||
// "thunderVolume": 0.1,
|
||||
// "rainURL": "atp:e0cc7438aca776636f6e6f731685781d9999b961c945e4e5760d937be5beecdd.wav",
|
||||
// "rainVolume": 0.05
|
||||
// }
|
||||
// // NOTE: you can have other user data here as well, so long as it's JSON encoded, it won't impact the lightning script
|
||||
// }
|
||||
//
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var MAX_FLASH_INTENSITY = 20; // this controls how bright the lightning effect appears
|
||||
var MIN_FLASH_INTENSITY = 0; // this is probably best at 0, but it could be higher, which will make the lightning not fade completely to darkness before going away.
|
||||
var MAX_FLASH_INTENSITY_RANDOMNESS = 10; // this will add some randomness to the max brightness of the lightning
|
||||
var FLASH_INTENSITY_STEP_RANDOMNESS = 2; // as the lightning goes from min to max back to min, this will make it more random in it's brightness
|
||||
var AVERAGE_LIGHTNING_STRIKE_GAP_IN_SECONDS = 120; // how long on average between lighting
|
||||
var EXTRA_RANDOM_RANGE_LIGHTNING_STRIKE_GAP_IN_SECONDS = 10; // some randomness to the lightning gap
|
||||
var THUNDER_SOUND_URL = "https://s3.amazonaws.com/hifi-public/brad/rainstorm/thunder-48k.wav"; // thunder sound effect, must be 48k 16bit PCM
|
||||
var THUNDER_VOLUME = 1; // adjust the volume of the thunder sound effect
|
||||
var RAIN_SOUND_URL = "https://s3.amazonaws.com/hifi-public/brad/rainstorm/rain.wav"; // the background rain, this will loop
|
||||
var RAIN_VOLUME = 0.05; // adjust the volume of the rain sound effect.
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Various constants and variables we need access to in all scopes
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
var MSECS_PER_SECOND = 1000; // number of milliseconds in a second, a universal truth, don't change this. :)
|
||||
|
||||
// This is a little trick for implementing entity scripts. Many of the entity callbacks will have the JavaScript
|
||||
// "this" variable set, but if you connect to a signal like update, "this" won't correctly point to the instance
|
||||
// of your entity script, and so we set "_this" and use it in cases we need to access "this". We need to define
|
||||
// the variable in this scope so that it's not share among all entities.
|
||||
|
||||
var _this; // this is important here... or else the _this will be globally scoped.
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Various helper functions...
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Helper function for returning either a value, or the default value if the value is undefined. This is
|
||||
// is handing in parsing JSON where you don't know if the values have been set or not.
|
||||
function valueOrDefault(value, defaultValue) {
|
||||
if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// return a random float between high and low
|
||||
function randFloat(low, high) {
|
||||
return low + Math.random() * (high - low);
|
||||
}
|
||||
|
||||
// the "constructor" for our class. pretty simple, it just sets our _this, so we can access it later.
|
||||
function Lightning() {
|
||||
_this = this;
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// The class prototype
|
||||
//
|
||||
// This is the class definition/prototype for our class. Funtions declared here will be accessible
|
||||
// via the instance of the entity
|
||||
//
|
||||
Lightning.prototype = {
|
||||
|
||||
// preload()
|
||||
// This is called by every viewer/interface instance that "sees" the entity you've attached this script to.
|
||||
// If this is a zone entity, then it will surely be called whenever someone enters the zone.
|
||||
preload: function (entityID) {
|
||||
|
||||
// we always make a point of remember our entityID, so that we can access our entity later
|
||||
_this.entityID = entityID;
|
||||
|
||||
// set up some of our time related state
|
||||
var now = Date.now();
|
||||
_this.lastUpdate = now;
|
||||
_this.lastStrike = now;
|
||||
|
||||
// some of our other state related items
|
||||
_this.lightningID = false; // this will be the entityID for any lightning that we create
|
||||
_this.lightningActive = false; // are we actively managing lightning
|
||||
|
||||
// Get the entities userData property, to see if someone has overridden any of our default settings
|
||||
var userDataText = Entities.getEntityProperties(entityID, ["userData"]).userData;
|
||||
var userData = {};
|
||||
if (userDataText !== "") {
|
||||
userData = JSON.parse(userDataText);
|
||||
}
|
||||
var lightningUserData = valueOrDefault(userData.lightning, {});
|
||||
_this.flashIntensityStepRandomeness = valueOrDefault(lightningUserData.flashIntensityStepRandomness, FLASH_INTENSITY_STEP_RANDOMNESS);
|
||||
_this.flashMax = valueOrDefault(lightningUserData.flashMax, MAX_FLASH_INTENSITY);
|
||||
_this.flashMin = valueOrDefault(lightningUserData.flashMin, MIN_FLASH_INTENSITY);
|
||||
_this.flashMaxRandomness = valueOrDefault(lightningUserData.flashMaxRandomness, MAX_FLASH_INTENSITY_RANDOMNESS);
|
||||
_this.averageLightningStrikeGap = valueOrDefault(lightningUserData.averageLightningStrikeGap, AVERAGE_LIGHTNING_STRIKE_GAP_IN_SECONDS);
|
||||
_this.extraRandomRangeLightningStrikeGap = valueOrDefault(lightningUserData.extraRandomRangeLightningStrikeGap, EXTRA_RANDOM_RANGE_LIGHTNING_STRIKE_GAP_IN_SECONDS);
|
||||
|
||||
var thunderURL = valueOrDefault(lightningUserData.thunderURL, THUNDER_SOUND_URL);
|
||||
_this.thunderSound = SoundCache.getSound(thunderURL); // start downloading the thunder into the cache in case we need it later
|
||||
_this.thunderVolume = valueOrDefault(lightningUserData.thunderVolume, THUNDER_VOLUME);
|
||||
|
||||
var rainURL = valueOrDefault(lightningUserData.rainURL, RAIN_SOUND_URL);
|
||||
_this.rainSound = SoundCache.getSound(rainURL); // start downloading the rain, we will be using it for sure
|
||||
_this.rainVolume = valueOrDefault(lightningUserData.rainVolume, RAIN_VOLUME);
|
||||
_this.rainPlaying = false;
|
||||
|
||||
Script.update.connect(_this.onUpdate); // connect our onUpdate to a regular update signal from the interface
|
||||
},
|
||||
|
||||
// unload()
|
||||
// This is called by every viewer/interface instance that "sees" the entity when you "stop knowing about" the
|
||||
// entity. Usually this means the user has left then domain, or maybe the entity has been deleted. We need to
|
||||
// clean up anything transient that we created here. In this case it means disconnecting from the update signal
|
||||
// and stopping the rain sound if we started it.
|
||||
unload: function ( /*entityID*/ ) {
|
||||
Script.update.disconnect(_this.onUpdate);
|
||||
if (_this.rainInjector !== undefined) {
|
||||
_this.rainInjector.stop();
|
||||
}
|
||||
},
|
||||
|
||||
// plays the rain sound effect. This is played locally, which means it doesn't provide spatialization, which
|
||||
// we don't really want for the ambient sound effect of rain. Also, since it's playing locally, we don't send
|
||||
// the stream to the audio mixer. Another subtle side effect of playing locally is that the sound isn't in sync
|
||||
// for all users in the domain, but that's ok for a sound effect like rain which is more of a white noise like
|
||||
// sound effect.
|
||||
playLocalRain: function () {
|
||||
var myPosition = Entities.getEntityProperties(_this.entityID, "position").position;
|
||||
_this.rainInjector = Audio.playSound(_this.rainSound, {
|
||||
position: myPosition,
|
||||
volume: _this.rainVolume,
|
||||
loop: true,
|
||||
localOnly: true
|
||||
});
|
||||
_this.rainPlaying = true;
|
||||
},
|
||||
|
||||
// this handles a single "step" of the lightning flash effect. It assumes a light entity has already been created,
|
||||
// and all it really does is change the intensity of the light based on the settings that are part of this entity's
|
||||
// userData.
|
||||
flashLightning: function (lightningID) {
|
||||
var lightningProperties = Entities.getEntityProperties(lightningID, ["userData", "intensity"]);
|
||||
var lightningParameters = JSON.parse(lightningProperties.userData);
|
||||
var currentIntensity = lightningProperties.intensity;
|
||||
var flashDirection = lightningParameters.flashDirection;
|
||||
var flashMax = lightningParameters.flashMax;
|
||||
var flashMin = lightningParameters.flashMin;
|
||||
var flashIntensityStepRandomeness = lightningParameters.flashIntensityStepRandomeness;
|
||||
var newIntensity = currentIntensity + flashDirection + randFloat(-flashIntensityStepRandomeness, flashIntensityStepRandomeness);
|
||||
|
||||
if (flashDirection > 0) {
|
||||
if (newIntensity >= flashMax) {
|
||||
flashDirection = -1; // reverse flash
|
||||
newIntensity = flashMax;
|
||||
}
|
||||
} else {
|
||||
if (newIntensity <= flashMin) {
|
||||
flashDirection = 1; // reverse flash
|
||||
newIntensity = flashMin;
|
||||
}
|
||||
}
|
||||
|
||||
// if we reached 0 intensity, then we're done with this strike...
|
||||
if (newIntensity === 0) {
|
||||
_this.lightningActive = false;
|
||||
Entities.deleteEntity(lightningID);
|
||||
}
|
||||
|
||||
// FIXME - we probably don't need to re-edit the userData of the light... we're only
|
||||
// changing direction, the rest are the same... we could just store direction in our
|
||||
// own local variable state
|
||||
var newLightningParameters = JSON.stringify({
|
||||
flashDirection: flashDirection,
|
||||
flashIntensityStepRandomeness: flashIntensityStepRandomeness,
|
||||
flashMax: flashMax,
|
||||
flashMin: flashMin
|
||||
});
|
||||
|
||||
// this is what actually creates the effect, changing the intensity of the light
|
||||
Entities.editEntity(lightningID, {intensity: newIntensity, userData: newLightningParameters});
|
||||
},
|
||||
|
||||
// findMyLightning() is designed to make the script more robust. Since we're an open editable platform
|
||||
// it's possible that from the time that we started the lightning effect until "now" when we're attempting
|
||||
// to change the light, some other client might have deleted our light. Before we proceed in editing
|
||||
// the light, we check to see if it exists.
|
||||
findMyLightning: function () {
|
||||
if (_this.lightningID !== false) {
|
||||
var lightningName = Entities.getEntityProperties(_this.lightningID, "name").name;
|
||||
if (lightningName !== undefined) {
|
||||
return _this.lightningID;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// findOtherLightning() is designed to allow this script to work in a "multi-user" environment, which we
|
||||
// must assume we are in. Since every user/viewer/client that connect to the domain and "sees" our entity
|
||||
// is going to run this script, any of them could be in charge of flashing the lightning. So before we
|
||||
// start to flash the lightning, we will first check to see if someone else already is.
|
||||
//
|
||||
// returns true if some other lightning exists... likely because some other viewer is flashing it
|
||||
// returns false if no other lightning exists...
|
||||
findOtherLightning: function () {
|
||||
var myPosition = Entities.getEntityProperties(_this.entityID, "position").position;
|
||||
|
||||
// find all entities near me...
|
||||
var entities = Entities.findEntities(myPosition, 1000);
|
||||
var checkEntityID;
|
||||
var checkProperties;
|
||||
var entity;
|
||||
for (entity = 0; entity < entities.length; entity++) {
|
||||
checkEntityID = entities[entity];
|
||||
checkProperties = Entities.getEntityProperties(checkEntityID, ["name", "type"]);
|
||||
|
||||
// check to see if they are lightning
|
||||
if (checkProperties.type === "Light" && checkProperties.name === "lightning for creator:" + _this.entityID) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
// createNewLightning() actually creates new lightning and plays the thunder sound
|
||||
createNewLightning: function () {
|
||||
var myPosition = Entities.getEntityProperties(_this.entityID, "position").position;
|
||||
_this.lightningID = Entities.addEntity({
|
||||
type: "Light",
|
||||
name: "lightning for creator:" + _this.entityID,
|
||||
userData: JSON.stringify({
|
||||
flashDirection: 1,
|
||||
flashIntensityStepRandomeness: _this.flashIntensityStepRandomeness,
|
||||
flashMax: _this.flashMax + randFloat(-_this.flashMaxRandomness, _this.flashMaxRandomness),
|
||||
flashMin: _this.flashMin
|
||||
}),
|
||||
falloffRadius: 1000,
|
||||
intensity: 0,
|
||||
position: myPosition,
|
||||
collisionless: true,
|
||||
dimensions: {x: 1000, y: 1000, z: 1000},
|
||||
color: {red: 255, green: 255, blue: 255},
|
||||
lifetime: 10 // lightning only lasts 10 seconds....
|
||||
});
|
||||
|
||||
// play the thunder...
|
||||
Audio.playSound(_this.thunderSound, {
|
||||
position: myPosition,
|
||||
volume: _this.thunderVolume
|
||||
});
|
||||
|
||||
return _this.lightningID;
|
||||
},
|
||||
|
||||
// onUpdate() this will be called regularly, approximately every frame of the simulation. We will use
|
||||
// it to determine if we need to do a lightning/thunder strike
|
||||
onUpdate: function () {
|
||||
var now = Date.now();
|
||||
|
||||
// check to see if rain is downloaded and not yet playing, if so start playing
|
||||
if (!_this.rainPlaying && _this.rainSound.downloaded) {
|
||||
_this.playLocalRain();
|
||||
}
|
||||
|
||||
// NOTE: _this.lightningActive will only be TRUE if we are the one who created
|
||||
// the lightning and we are in charge of flashing it...
|
||||
if (_this.lightningActive) {
|
||||
var lightningID = _this.findMyLightning();
|
||||
// if for some reason our lightning is gone... then just return to non-active state
|
||||
if (lightningID === false) {
|
||||
_this.lightningActive = false;
|
||||
_this.lightningID = false;
|
||||
} else {
|
||||
// otherwise, flash our lightning...
|
||||
_this.flashLightning(lightningID);
|
||||
}
|
||||
} else {
|
||||
// whether or not it's time for us to strike, we always keep an eye out for anyone else
|
||||
// striking... and if we see someone else striking, we will reset our lastStrike time
|
||||
if (_this.findOtherLightning()) {
|
||||
_this.lastStrike = now;
|
||||
}
|
||||
|
||||
var sinceLastStrike = now - _this.lastStrike;
|
||||
var nextRandomStrikeTime = _this.averageLightningStrikeGap
|
||||
+ randFloat(-_this.extraRandomRangeLightningStrikeGap,
|
||||
_this.extraRandomRangeLightningStrikeGap);
|
||||
|
||||
if (sinceLastStrike > nextRandomStrikeTime * MSECS_PER_SECOND) {
|
||||
|
||||
// so it's time for a strike... let's see if someone else has lightning...
|
||||
// if no one else is flashing lightning... then we create it...
|
||||
if (_this.findOtherLightning()) {
|
||||
_this.lightningActive = false;
|
||||
_this.lightningID = false;
|
||||
} else {
|
||||
_this.createNewLightning();
|
||||
_this.lightningActive = true;
|
||||
}
|
||||
|
||||
_this.lastStrike = now;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return new Lightning();
|
||||
});
|
|
@ -17,8 +17,8 @@ var SIZE = 10.0;
|
|||
var SEPARATION = 20.0;
|
||||
var ROWS_X = 30;
|
||||
var ROWS_Z = 30;
|
||||
var TYPE = "Sphere"; // Right now this can be "Box" or "Model" or "Sphere"
|
||||
var MODEL_URL = "https://hifi-public.s3.amazonaws.com/models/props/LowPolyIsland/CypressTreeGroup.fbx";
|
||||
var TYPE = "Model"; // Right now this can be "Box" or "Model" or "Sphere"
|
||||
var MODEL_URL = "http://hifi-content.s3.amazonaws.com/DomainContent/CellScience/Instances/vesicle.fbx";
|
||||
var MODEL_DIMENSION = { x: 33, y: 16, z: 49 };
|
||||
var RATE_PER_SECOND = 1000; // The entity server will drop data if we create things too fast.
|
||||
var SCRIPT_INTERVAL = 100;
|
||||
|
@ -38,6 +38,7 @@ Script.setInterval(function () {
|
|||
var numToCreate = RATE_PER_SECOND * (SCRIPT_INTERVAL / 1000.0);
|
||||
for (var i = 0; i < numToCreate; i++) {
|
||||
var position = { x: SIZE + (x * SEPARATION), y: SIZE, z: SIZE + (z * SEPARATION) };
|
||||
print('position:'+JSON.stringify(position))
|
||||
if (TYPE == "Model") {
|
||||
Entities.addEntity({
|
||||
type: TYPE,
|
||||
|
|
109
examples/homeContent/whiteboardV2/eraserEntityScript.js
Normal file
109
examples/homeContent/whiteboardV2/eraserEntityScript.js
Normal file
|
@ -0,0 +1,109 @@
|
|||
//
|
||||
// eraserEntityScript.js
|
||||
// examples/homeContent/eraserEntityScript
|
||||
//
|
||||
// Created by Eric Levin on 2/17/15.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This entity script provides logic for an object with attached script to erase nearby marker strokes
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var TRIGGER_CONTROLS = [
|
||||
Controller.Standard.LT,
|
||||
Controller.Standard.RT,
|
||||
];
|
||||
var _this;
|
||||
Eraser = function() {
|
||||
_this = this;
|
||||
|
||||
_this.ERASER_TRIGGER_THRESHOLD = 0.2;
|
||||
_this.STROKE_NAME = "hifi-marker-stroke";
|
||||
_this.ERASER_TO_STROKE_SEARCH_RADIUS = 0.7;
|
||||
_this.ERASER_RESET_WAIT_TIME = 3000;
|
||||
};
|
||||
|
||||
Eraser.prototype = {
|
||||
|
||||
startEquip: function(id, params) {
|
||||
_this.equipped = true;
|
||||
_this.hand = params[0] == "left" ? 0 : 1;
|
||||
// We really only need to grab position of marker strokes once, and then just check to see if eraser comes near enough to those strokes
|
||||
Overlays.editOverlay(_this.searchSphere, {
|
||||
visible: true
|
||||
});
|
||||
},
|
||||
continueEquip: function() {
|
||||
_this.eraserPosition = Entities.getEntityProperties(_this.entityID, "position").position;
|
||||
Overlays.editOverlay(_this.searchSphere, {
|
||||
position: _this.eraserPosition
|
||||
});
|
||||
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[_this.hand]);
|
||||
if (_this.triggerValue > _this.ERASER_TRIGGER_THRESHOLD) {
|
||||
_this.continueHolding();
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
continueHolding: function() {
|
||||
var strokeIDs = Entities.findEntities(_this.eraserPosition, _this.ERASER_TO_STROKE_SEARCH_RADIUS);
|
||||
// Create a map of stroke entities and their positions
|
||||
|
||||
strokeIDs.forEach(function(strokeID) {
|
||||
var strokeProps = Entities.getEntityProperties(strokeID, ["position", "name"]);
|
||||
if (strokeProps.name === _this.STROKE_NAME && Vec3.distance(_this.eraserPosition, strokeProps.position) < _this.ERASER_TO_STROKE_SEARCH_RADIUS) {
|
||||
Entities.deleteEntity(strokeID);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
releaseEquip: function() {
|
||||
Overlays.editOverlay(_this.searchSphere, {
|
||||
visible: false
|
||||
});
|
||||
|
||||
// Once user releases eraser, wait a bit then put marker back to its original position and rotation
|
||||
Script.setTimeout(function() {
|
||||
var userData = getEntityUserData(_this.entityID);
|
||||
Entities.editEntity(_this.entityID, {
|
||||
position: userData.originalPosition,
|
||||
rotation: userData.originalRotation,
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -0.01,
|
||||
z: 0
|
||||
}
|
||||
});
|
||||
}, _this.ERASER_RESET_WAIT_TIME);
|
||||
},
|
||||
|
||||
|
||||
|
||||
preload: function(entityID) {
|
||||
_this.entityID = entityID;
|
||||
_this.searchSphere = Overlays.addOverlay('sphere', {
|
||||
size: _this.ERASER_TO_STROKE_SEARCH_RADIUS,
|
||||
color: {
|
||||
red: 200,
|
||||
green: 10,
|
||||
blue: 10
|
||||
},
|
||||
alpha: 0.2,
|
||||
solid: true,
|
||||
visible: false
|
||||
})
|
||||
|
||||
},
|
||||
|
||||
unload: function() {
|
||||
Overlays.deleteOverlay(_this.searchSphere);
|
||||
}
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new Eraser();
|
||||
});
|
219
examples/homeContent/whiteboardV2/markerEntityScript.js
Normal file
219
examples/homeContent/whiteboardV2/markerEntityScript.js
Normal file
|
@ -0,0 +1,219 @@
|
|||
//
|
||||
// markerTipEntityScript.js
|
||||
// examples/homeContent/markerTipEntityScript
|
||||
//
|
||||
// Created by Eric Levin on 2/17/15.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This script provides the logic for an object to draw marker strokes on its associated whiteboard
|
||||
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var TRIGGER_CONTROLS = [
|
||||
Controller.Standard.LT,
|
||||
Controller.Standard.RT,
|
||||
];
|
||||
var MAX_POINTS_PER_STROKE = 40;
|
||||
var _this;
|
||||
MarkerTip = function() {
|
||||
_this = this;
|
||||
_this.MARKER_TEXTURE_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/textures/markerStroke.png";
|
||||
_this.strokeForwardOffset = 0.0001;
|
||||
_this.STROKE_WIDTH_RANGE = {
|
||||
min: 0.002,
|
||||
max: 0.01
|
||||
};
|
||||
_this.MAX_MARKER_TO_BOARD_DISTANCE = 1.4;
|
||||
_this.MIN_DISTANCE_BETWEEN_POINTS = 0.002;
|
||||
_this.MAX_DISTANCE_BETWEEN_POINTS = 0.1;
|
||||
_this.strokes = [];
|
||||
_this.PAINTING_TRIGGER_THRESHOLD = 0.2;
|
||||
_this.STROKE_NAME = "hifi-marker-stroke";
|
||||
_this.WHITEBOARD_SURFACE_NAME = "hifi-whiteboardDrawingSurface";
|
||||
_this.MARKER_RESET_WAIT_TIME = 3000;
|
||||
};
|
||||
|
||||
MarkerTip.prototype = {
|
||||
|
||||
startEquip: function(id, params) {
|
||||
_this.whiteboards = [];
|
||||
_this.equipped = true;
|
||||
_this.hand = params[0] == "left" ? 0 : 1;
|
||||
_this.markerColor = getEntityUserData(_this.entityID).markerColor;
|
||||
// search for whiteboards
|
||||
var markerPosition = Entities.getEntityProperties(_this.entityID, "position").position;
|
||||
var entities = Entities.findEntities(markerPosition, 10);
|
||||
entities.forEach(function(entity) {
|
||||
var entityName = Entities.getEntityProperties(entity, "name").name;
|
||||
if (entityName === _this.WHITEBOARD_SURFACE_NAME) {
|
||||
|
||||
_this.whiteboards.push(entity);
|
||||
}
|
||||
});
|
||||
|
||||
print("intersectable entities " + JSON.stringify(_this.whiteboards))
|
||||
},
|
||||
|
||||
releaseEquip: function() {
|
||||
_this.resetStroke();
|
||||
Overlays.editOverlay(_this.laserPointer, {
|
||||
visible: false
|
||||
});
|
||||
|
||||
// Once user releases marker, wait a bit then put marker back to its original position and rotation
|
||||
Script.setTimeout(function() {
|
||||
var userData = getEntityUserData(_this.entityID);
|
||||
Entities.editEntity(_this.entityID, {
|
||||
position: userData.originalPosition,
|
||||
rotation: userData.originalRotation,
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -0.01,
|
||||
z: 0
|
||||
}
|
||||
});
|
||||
}, _this.MARKER_RESET_WAIT_TIME);
|
||||
},
|
||||
|
||||
|
||||
continueEquip: function() {
|
||||
// cast a ray from marker and see if it hits anything
|
||||
var markerProps = Entities.getEntityProperties(_this.entityID, ["position", "rotation"]);
|
||||
|
||||
var pickRay = {
|
||||
origin: markerProps.position,
|
||||
direction: Quat.getFront(markerProps.rotation)
|
||||
}
|
||||
var intersection = Entities.findRayIntersectionBlocking(pickRay, true, _this.whiteboards);
|
||||
|
||||
if (intersection.intersects && Vec3.distance(intersection.intersection, markerProps.position) < _this.MAX_MARKER_TO_BOARD_DISTANCE) {
|
||||
_this.currentWhiteboard = intersection.entityID;
|
||||
var whiteboardRotation = Entities.getEntityProperties(_this.currentWhiteboard, "rotation").rotation;
|
||||
_this.whiteboardNormal = Quat.getFront(whiteboardRotation);
|
||||
Overlays.editOverlay(_this.laserPointer, {
|
||||
visible: true,
|
||||
position: intersection.intersection,
|
||||
rotation: whiteboardRotation
|
||||
})
|
||||
_this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[_this.hand]);
|
||||
if (_this.triggerValue > _this.PAINTING_TRIGGER_THRESHOLD) {
|
||||
_this.paint(intersection.intersection)
|
||||
} else {
|
||||
_this.resetStroke();
|
||||
}
|
||||
} else {
|
||||
if (_this.currentStroke) {
|
||||
_this.resetStroke();
|
||||
}
|
||||
|
||||
Overlays.editOverlay(_this.laserPointer, {
|
||||
visible: false
|
||||
});
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
newStroke: function(position) {
|
||||
_this.strokeBasePosition = position;
|
||||
_this.currentStroke = Entities.addEntity({
|
||||
type: "PolyLine",
|
||||
name: _this.STROKE_NAME,
|
||||
dimensions: {
|
||||
x: 10,
|
||||
y: 10,
|
||||
z: 10
|
||||
},
|
||||
position: position,
|
||||
textures: _this.MARKER_TEXTURE_URL,
|
||||
color: _this.markerColor,
|
||||
lifetime: 5000,
|
||||
});
|
||||
|
||||
_this.linePoints = [];
|
||||
_this.normals = [];
|
||||
_this.strokes.push(_this.currentStroke);
|
||||
},
|
||||
|
||||
paint: function(position) {
|
||||
var basePosition = position;
|
||||
if (!_this.currentStroke) {
|
||||
if (_this.oldPosition) {
|
||||
basePosition = _this.oldPosition;
|
||||
}
|
||||
_this.newStroke(basePosition);
|
||||
}
|
||||
|
||||
var localPoint = Vec3.subtract(basePosition, _this.strokeBasePosition);
|
||||
localPoint = Vec3.sum(localPoint, Vec3.multiply(_this.whiteboardNormal, _this.strokeForwardOffset));
|
||||
|
||||
if (_this.linePoints.length > 0) {
|
||||
var distance = Vec3.distance(localPoint, _this.linePoints[_this.linePoints.length - 1]);
|
||||
if (distance < _this.MIN_DISTANCE_BETWEEN_POINTS) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
_this.linePoints.push(localPoint);
|
||||
_this.normals.push(_this.whiteboardNormal);
|
||||
|
||||
var strokeWidths = [];
|
||||
for (var i = 0; i < _this.linePoints.length; i++) {
|
||||
// Create a temp array of stroke widths for calligraphy effect - start and end should be less wide
|
||||
var pointsFromCenter = Math.abs(_this.linePoints.length / 2 - i);
|
||||
var pointWidth = map(pointsFromCenter, 0, this.linePoints.length / 2, _this.STROKE_WIDTH_RANGE.max, this.STROKE_WIDTH_RANGE.min);
|
||||
strokeWidths.push(pointWidth);
|
||||
}
|
||||
|
||||
Entities.editEntity(_this.currentStroke, {
|
||||
linePoints: _this.linePoints,
|
||||
normals: _this.normals,
|
||||
strokeWidths: strokeWidths
|
||||
});
|
||||
|
||||
if (_this.linePoints.length > MAX_POINTS_PER_STROKE) {
|
||||
Entities.editEntity(_this.currentStroke, {
|
||||
parentID: _this.currentWhiteboard
|
||||
});
|
||||
_this.currentStroke = null;
|
||||
_this.oldPosition = position;
|
||||
}
|
||||
},
|
||||
resetStroke: function() {
|
||||
|
||||
Entities.editEntity(_this.currentStroke, {
|
||||
parentID: _this.currentWhiteboard
|
||||
});
|
||||
_this.currentStroke = null;
|
||||
|
||||
_this.oldPosition = null;
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
_this.laserPointer = Overlays.addOverlay("circle3d", {
|
||||
color: {
|
||||
red: 220,
|
||||
green: 35,
|
||||
blue: 53
|
||||
},
|
||||
solid: true,
|
||||
size: 0.01,
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
unload: function() {
|
||||
Overlays.deleteOverlay(_this.laserPointer);
|
||||
_this.strokes.forEach(function(stroke) {
|
||||
Entities.deleteEntity(stroke);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new MarkerTip();
|
||||
});
|
254
examples/homeContent/whiteboardV2/whiteboardSpawner.js
Normal file
254
examples/homeContent/whiteboardV2/whiteboardSpawner.js
Normal file
|
@ -0,0 +1,254 @@
|
|||
//
|
||||
// whiteboardSpawner.js
|
||||
// examples/homeContent/whiteboardV2
|
||||
//
|
||||
// Created by Eric Levina on 2/17/16
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Run this script to spawn a whiteboard, markers, and an eraser.
|
||||
// To draw on the whiteboard, equip a marker and hold down trigger with marker tip pointed at whiteboard
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
Script.include("../../libraries/utils.js")
|
||||
|
||||
var orientation = MyAvatar.orientation;
|
||||
orientation = Quat.safeEulerAngles(orientation);
|
||||
var markerRotation = Quat.fromVec3Degrees({
|
||||
x: orientation.x + 10,
|
||||
y: orientation.y - 90,
|
||||
z: orientation.z
|
||||
})
|
||||
orientation.x = 0;
|
||||
var whiteboardRotation = Quat.fromVec3Degrees({
|
||||
x: 0,
|
||||
y: orientation.y,
|
||||
z: 0
|
||||
});
|
||||
orientation = Quat.fromVec3Degrees(orientation);
|
||||
var markers = [];
|
||||
|
||||
|
||||
var whiteboardPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(2, Quat.getFront(orientation)));
|
||||
var WHITEBOARD_MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Whiteboard-4.fbx";
|
||||
var WHITEBOARD_COLLISION_HULL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/whiteboardCollisionHull.obj";
|
||||
var whiteboard = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "whiteboard",
|
||||
modelURL: WHITEBOARD_MODEL_URL,
|
||||
position: whiteboardPosition,
|
||||
rotation: whiteboardRotation,
|
||||
shapeType: 'compound',
|
||||
compoundShapeURL: WHITEBOARD_COLLISION_HULL_URL,
|
||||
dimensions: {
|
||||
x: 1.86,
|
||||
y: 2.7,
|
||||
z: 0.4636
|
||||
},
|
||||
});
|
||||
|
||||
var whiteboardSurfacePosition = Vec3.sum(whiteboardPosition, {
|
||||
x: 0.0,
|
||||
y: 0.45,
|
||||
z: 0.0
|
||||
});
|
||||
whiteboardSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(-0.02, Quat.getRight(whiteboardRotation)));
|
||||
var moveForwardDistance = 0.02;
|
||||
whiteboardFrontSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(-moveForwardDistance, Quat.getFront(whiteboardRotation)));
|
||||
var whiteboardSurfaceSettings = {
|
||||
type: "Box",
|
||||
name: "hifi-whiteboardDrawingSurface",
|
||||
dimensions: {
|
||||
x: 1.82,
|
||||
y: 1.8,
|
||||
z: 0.01
|
||||
},
|
||||
color: {
|
||||
red: 200,
|
||||
green: 10,
|
||||
blue: 200
|
||||
},
|
||||
position: whiteboardFrontSurfacePosition,
|
||||
rotation: whiteboardRotation,
|
||||
visible: false,
|
||||
parentID: whiteboard
|
||||
}
|
||||
var whiteboardFrontDrawingSurface = Entities.addEntity(whiteboardSurfaceSettings);
|
||||
|
||||
|
||||
whiteboardBackSurfacePosition = Vec3.sum(whiteboardSurfacePosition, Vec3.multiply(moveForwardDistance, Quat.getFront(whiteboardRotation)));
|
||||
whiteboardSurfaceSettings.position = whiteboardBackSurfacePosition;
|
||||
|
||||
var whiteboardBackDrawingSurface = Entities.addEntity(whiteboardSurfaceSettings);
|
||||
|
||||
|
||||
var WHITEBOARD_RACK_DEPTH = 1.9;
|
||||
|
||||
var ERASER_MODEL_URL = "http://hifi-content.s3.amazonaws.com/alan/dev/eraser-2.fbx";
|
||||
var ERASER_SCRIPT_URL = Script.resolvePath("eraserEntityScript.js?v43");
|
||||
var eraserPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(WHITEBOARD_RACK_DEPTH, Quat.getFront(whiteboardRotation)));
|
||||
eraserPosition = Vec3.sum(eraserPosition, Vec3.multiply(-0.5, Quat.getRight(whiteboardRotation)));
|
||||
var eraserRotation = markerRotation;
|
||||
|
||||
var eraser = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: ERASER_MODEL_URL,
|
||||
position: eraserPosition,
|
||||
script: ERASER_SCRIPT_URL,
|
||||
shapeType: "box",
|
||||
dimensions: {
|
||||
x: 0.0858,
|
||||
y: 0.0393,
|
||||
z: 0.2083
|
||||
},
|
||||
rotation: eraserRotation,
|
||||
dynamic: true,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -1,
|
||||
z: 0
|
||||
},
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -0.1,
|
||||
z: 0
|
||||
},
|
||||
userData: JSON.stringify({
|
||||
originalPosition: eraserPosition,
|
||||
originalRotation: eraserRotation,
|
||||
wearable: {
|
||||
joints: {
|
||||
RightHand: [{
|
||||
x: 0.020,
|
||||
y: 0.120,
|
||||
z: 0.049
|
||||
}, {
|
||||
x: 0.1004,
|
||||
y: 0.6424,
|
||||
z: 0.717,
|
||||
w: 0.250
|
||||
}],
|
||||
LeftHand: [{
|
||||
x: -0.005,
|
||||
y: 0.1101,
|
||||
z: 0.053
|
||||
}, {
|
||||
x: 0.723,
|
||||
y: 0.289,
|
||||
z: 0.142,
|
||||
w: 0.610
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
createMarkers();
|
||||
|
||||
function createMarkers() {
|
||||
var modelURLS = [
|
||||
"https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-blue.fbx",
|
||||
"https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-red.fbx",
|
||||
"https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/marker-black.fbx",
|
||||
];
|
||||
|
||||
var markerPosition = Vec3.sum(MyAvatar.position, Vec3.multiply(WHITEBOARD_RACK_DEPTH, Quat.getFront(orientation)));
|
||||
|
||||
createMarker(modelURLS[0], markerPosition, {
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 200
|
||||
});
|
||||
|
||||
markerPosition = Vec3.sum(markerPosition, Vec3.multiply(-0.2, Quat.getFront(markerRotation)));
|
||||
createMarker(modelURLS[1], markerPosition, {
|
||||
red: 200,
|
||||
green: 10,
|
||||
blue: 10
|
||||
});
|
||||
|
||||
markerPosition = Vec3.sum(markerPosition, Vec3.multiply(0.4, Quat.getFront(markerRotation)));
|
||||
createMarker(modelURLS[2], markerPosition, {
|
||||
red: 10,
|
||||
green: 10,
|
||||
blue: 10
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function createMarker(modelURL, markerPosition, markerColor) {
|
||||
var MARKER_SCRIPT_URL = Script.resolvePath("markerEntityScript.js?v1" + Math.random());
|
||||
var marker = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: modelURL,
|
||||
rotation: markerRotation,
|
||||
shapeType: "box",
|
||||
name: "marker",
|
||||
dynamic: true,
|
||||
gravity: {
|
||||
x: 0,
|
||||
y: -1,
|
||||
z: 0
|
||||
},
|
||||
velocity: {
|
||||
x: 0,
|
||||
y: -0.1,
|
||||
z: 0
|
||||
},
|
||||
position: markerPosition,
|
||||
dimensions: {
|
||||
x: 0.027,
|
||||
y: 0.027,
|
||||
z: 0.164
|
||||
},
|
||||
name: "marker",
|
||||
script: MARKER_SCRIPT_URL,
|
||||
userData: JSON.stringify({
|
||||
originalPosition: markerPosition,
|
||||
originalRotation: markerRotation,
|
||||
markerColor: markerColor,
|
||||
wearable: {
|
||||
joints: {
|
||||
RightHand: [{
|
||||
x: 0.001,
|
||||
y: 0.139,
|
||||
z: 0.050
|
||||
}, {
|
||||
x: -0.73,
|
||||
y: -0.043,
|
||||
z: -0.108,
|
||||
w: -0.666
|
||||
}],
|
||||
LeftHand: [{
|
||||
x: 0.007,
|
||||
y: 0.151,
|
||||
z: 0.061
|
||||
}, {
|
||||
x: -0.417,
|
||||
y: 0.631,
|
||||
z: -0.389,
|
||||
w: -0.525
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
markers.push(marker);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(whiteboard);
|
||||
Entities.deleteEntity(whiteboardFrontDrawingSurface);
|
||||
Entities.deleteEntity(whiteboardBackDrawingSurface);
|
||||
Entities.deleteEntity(eraser);
|
||||
markers.forEach(function(marker) {
|
||||
Entities.deleteEntity(marker);
|
||||
});
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
|
@ -16,6 +16,11 @@ HIFI_PUBLIC_BUCKET = "http://s3.amazonaws.com/hifi-public/";
|
|||
SPACE_LOCAL = "local";
|
||||
SPACE_WORLD = "world";
|
||||
|
||||
function objectTranslationPlanePoint(position, dimensions) {
|
||||
var newPosition = { x: position.x, y: position.y, z: position.z };
|
||||
newPosition.y -= dimensions.y / 2.0;
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
SelectionManager = (function() {
|
||||
var that = {};
|
||||
|
@ -2252,15 +2257,20 @@ SelectionDisplay = (function() {
|
|||
var constrainMajorOnly = false;
|
||||
var startPosition = null;
|
||||
var duplicatedEntityIDs = null;
|
||||
|
||||
var translateXZTool = {
|
||||
mode: 'TRANSLATE_XZ',
|
||||
pickPlanePosition: { x: 0, y: 0, z: 0 },
|
||||
greatestDimension: 0.0,
|
||||
startingDistance: 0.0,
|
||||
startingElevation: 0.0,
|
||||
onBegin: function(event) {
|
||||
SelectionManager.saveProperties();
|
||||
startPosition = SelectionManager.worldPosition;
|
||||
var dimensions = SelectionManager.worldDimensions;
|
||||
|
||||
var pickRay = Camera.computePickRay(event.x, event.y);
|
||||
initialXZPick = rayPlaneIntersection(pickRay, startPosition, {
|
||||
initialXZPick = rayPlaneIntersection(pickRay, translateXZTool.pickPlanePosition, {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
|
@ -2297,16 +2307,56 @@ SelectionDisplay = (function() {
|
|||
visible: false
|
||||
});
|
||||
},
|
||||
elevation: function(origin, intersection) {
|
||||
return (origin.y - intersection.y) / Vec3.distance(origin, intersection);
|
||||
},
|
||||
onMove: function(event) {
|
||||
var wantDebug = false;
|
||||
pickRay = Camera.computePickRay(event.x, event.y);
|
||||
|
||||
var pick = rayPlaneIntersection(pickRay, SelectionManager.worldPosition, {
|
||||
var pick = rayPlaneIntersection2(pickRay, translateXZTool.pickPlanePosition, {
|
||||
x: 0,
|
||||
y: 1,
|
||||
z: 0
|
||||
});
|
||||
|
||||
// If the pick ray doesn't hit the pick plane in this direction, do nothing.
|
||||
// this will happen when someone drags across the horizon from the side they started on.
|
||||
if (!pick) {
|
||||
if (wantDebug) {
|
||||
print("Pick ray does not intersect XZ plane.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var vector = Vec3.subtract(pick, initialXZPick);
|
||||
|
||||
// If the mouse is too close to the horizon of the pick plane, stop moving
|
||||
var MIN_ELEVATION = 0.02; // largest dimension of object divided by distance to it
|
||||
var elevation = translateXZTool.elevation(pickRay.origin, pick);
|
||||
if (wantDebug) {
|
||||
print("Start Elevation: " + translateXZTool.startingElevation + ", elevation: " + elevation);
|
||||
}
|
||||
if ((translateXZTool.startingElevation > 0.0 && elevation < MIN_ELEVATION) ||
|
||||
(translateXZTool.startingElevation < 0.0 && elevation > -MIN_ELEVATION)) {
|
||||
if (wantDebug) {
|
||||
print("too close to horizon!");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If the angular size of the object is too small, stop moving
|
||||
var MIN_ANGULAR_SIZE = 0.01; // Radians
|
||||
if (translateXZTool.greatestDimension > 0) {
|
||||
var angularSize = Math.atan(translateXZTool.greatestDimension / Vec3.distance(pickRay.origin, pick));
|
||||
if (wantDebug) {
|
||||
print("Angular size = " + angularSize);
|
||||
}
|
||||
if (angularSize < MIN_ANGULAR_SIZE) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// If shifted, constrain to one axis
|
||||
if (event.isShifted) {
|
||||
if (Math.abs(vector.x) > Math.abs(vector.z)) {
|
||||
|
@ -2368,7 +2418,7 @@ SelectionDisplay = (function() {
|
|||
grid.snapToGrid(Vec3.sum(cornerPosition, vector), constrainMajorOnly),
|
||||
cornerPosition);
|
||||
|
||||
var wantDebug = false;
|
||||
|
||||
|
||||
for (var i = 0; i < SelectionManager.selections.length; i++) {
|
||||
var properties = SelectionManager.savedProperties[SelectionManager.selections[i]];
|
||||
|
@ -2645,11 +2695,6 @@ SelectionDisplay = (function() {
|
|||
pickRayPosition,
|
||||
planeNormal);
|
||||
|
||||
// Overlays.editOverlay(normalLine, {
|
||||
// start: initialPosition,
|
||||
// end: Vec3.sum(Vec3.multiply(100000, planeNormal), initialPosition),
|
||||
// });
|
||||
|
||||
SelectionManager.saveProperties();
|
||||
};
|
||||
|
||||
|
@ -3751,7 +3796,7 @@ SelectionDisplay = (function() {
|
|||
};
|
||||
|
||||
that.mousePressEvent = function(event) {
|
||||
|
||||
var wantDebug = false;
|
||||
if (!event.isLeftButton) {
|
||||
// if another mouse button than left is pressed ignore it
|
||||
return false;
|
||||
|
@ -3777,7 +3822,7 @@ SelectionDisplay = (function() {
|
|||
|
||||
if (result.intersects) {
|
||||
|
||||
var wantDebug = false;
|
||||
|
||||
if (wantDebug) {
|
||||
print("something intersects... ");
|
||||
print(" result.overlayID:" + result.overlayID + "[" + overlayNames[result.overlayID] + "]");
|
||||
|
@ -3874,7 +3919,10 @@ SelectionDisplay = (function() {
|
|||
|
||||
if (!somethingClicked) {
|
||||
|
||||
print("rotate handle case...");
|
||||
if (wantDebug) {
|
||||
print("rotate handle case...");
|
||||
}
|
||||
|
||||
|
||||
// After testing our stretch handles, then check out rotate handles
|
||||
Overlays.editOverlay(yawHandle, {
|
||||
|
@ -3942,15 +3990,17 @@ SelectionDisplay = (function() {
|
|||
break;
|
||||
|
||||
default:
|
||||
print("mousePressEvent()...... " + overlayNames[result.overlayID]);
|
||||
if (wantDebug) {
|
||||
print("mousePressEvent()...... " + overlayNames[result.overlayID]);
|
||||
}
|
||||
mode = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
print(" somethingClicked:" + somethingClicked);
|
||||
print(" mode:" + mode);
|
||||
|
||||
if (wantDebug) {
|
||||
print(" somethingClicked:" + somethingClicked);
|
||||
print(" mode:" + mode);
|
||||
}
|
||||
|
||||
if (somethingClicked) {
|
||||
|
||||
|
@ -4093,12 +4143,25 @@ SelectionDisplay = (function() {
|
|||
switch (result.overlayID) {
|
||||
case selectionBox:
|
||||
activeTool = translateXZTool;
|
||||
translateXZTool.pickPlanePosition = result.intersection;
|
||||
translateXZTool.greatestDimension = Math.max(Math.max(SelectionManager.worldDimensions.x, SelectionManager.worldDimensions.y),
|
||||
SelectionManager.worldDimensions.z);
|
||||
if (wantDebug) {
|
||||
print("longest dimension: " + translateXZTool.greatestDimension);
|
||||
translateXZTool.startingDistance = Vec3.distance(pickRay.origin, SelectionManager.position);
|
||||
print("starting distance: " + translateXZTool.startingDistance);
|
||||
translateXZTool.startingElevation = translateXZTool.elevation(pickRay.origin, translateXZTool.pickPlanePosition);
|
||||
print(" starting elevation: " + translateXZTool.startingElevation);
|
||||
}
|
||||
|
||||
mode = translateXZTool.mode;
|
||||
activeTool.onBegin(event);
|
||||
somethingClicked = true;
|
||||
break;
|
||||
default:
|
||||
print("mousePressEvent()...... " + overlayNames[result.overlayID]);
|
||||
if (wantDebug) {
|
||||
print("mousePressEvent()...... " + overlayNames[result.overlayID]);
|
||||
}
|
||||
mode = "UNKNOWN";
|
||||
break;
|
||||
}
|
||||
|
|
125
examples/shaders/rainyDayNightSkybox.fs
Normal file
125
examples/shaders/rainyDayNightSkybox.fs
Normal file
|
@ -0,0 +1,125 @@
|
|||
// Created by inigo quilez - iq/2013
|
||||
// Turbulence and Day/Night cycle added by Michael Olson - OMGparticles/2015
|
||||
// rain effect adapted from Rainy London by David Hoskins. - https://www.shadertoy.com/view/XdSGDc
|
||||
// License Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
|
||||
#line 6
|
||||
const float PI = 3.14159;
|
||||
uniform float rotationSpeed = 0.005;
|
||||
uniform float gridLevel = 0.0;
|
||||
uniform float constellationLevel = 0.0;
|
||||
uniform float constellationBoundaryLevel = 0.0;
|
||||
|
||||
// Axial tilt
|
||||
const float axialTilt = 0.408407;
|
||||
const vec2 atsc = vec2(sin(axialTilt), cos(axialTilt));
|
||||
const mat3 axialTiltMatrix = mat3(
|
||||
vec3(atsc.y, -atsc.x, 0.0),
|
||||
vec3(atsc.x, atsc.y, 0.0),
|
||||
vec3(0.0, 0.0, 1.0));
|
||||
|
||||
vec2 directionToSpherical(in vec3 d) {
|
||||
return vec2(atan(d.x, -d.z), asin(d.y)) * -1.0f;
|
||||
}
|
||||
|
||||
vec2 directionToUv(in vec3 d) {
|
||||
vec2 uv = directionToSpherical(d);
|
||||
uv /= PI;
|
||||
uv.x /= 2.0;
|
||||
uv += 0.5;
|
||||
return uv;
|
||||
}
|
||||
|
||||
vec3 stars3(in vec3 d) {
|
||||
//return rd;
|
||||
vec3 dir = d * axialTiltMatrix;
|
||||
|
||||
float theta = rotationSpeed * iGlobalTime * 4.0;
|
||||
vec2 sc = vec2(sin(theta), cos(theta));
|
||||
dir = dir * mat3( vec3(sc.y, 0.0, sc.x),
|
||||
vec3(0.0, 1.0, 0.0),
|
||||
vec3(-sc.x, 0.0, sc.y));
|
||||
|
||||
vec2 uv = directionToUv(dir);
|
||||
vec3 starColor = texture2D( iChannel0, uv).xyz;
|
||||
starColor = pow(starColor, vec3(0.75));
|
||||
|
||||
if (gridLevel > 0.0) {
|
||||
starColor += texture2D( iChannel1, uv).xyz * gridLevel;
|
||||
}
|
||||
return starColor;
|
||||
}
|
||||
|
||||
//const vec3 vNightColor = vec3(.15, 0.3, 0.6);
|
||||
uniform vec3 uDayColor = vec3(0.9,0.7,0.7);
|
||||
const vec3 vNightColor = vec3(1.0, 1.0, 1.0);
|
||||
const vec3 vHorizonColor = vec3(0.6, 0.3, 0.4);
|
||||
const vec3 vSunColor = vec3(1.0,0.8,0.6);
|
||||
const vec3 vSunRimColor = vec3(1.0,0.66,0.33);
|
||||
|
||||
vec4 render( in vec3 ro, in vec3 rd )
|
||||
{
|
||||
float fSunSpeed = (rotationSpeed * 10.0 * iGlobalTime) + PI / 2.0 * 3.0;
|
||||
vec3 sundir = normalize( vec3(cos(fSunSpeed),sin(fSunSpeed),0.0) );
|
||||
sundir = sundir * axialTiltMatrix;
|
||||
float sun = clamp( dot(sundir,rd), 0.0, 1.0 );
|
||||
|
||||
float fSunHeight = sundir.y;
|
||||
|
||||
// below this height will be full night color
|
||||
float fNightHeight = -0.8;
|
||||
// above this height will be full day color
|
||||
float fDayHeight = 0.3;
|
||||
|
||||
float fHorizonLength = fDayHeight - fNightHeight;
|
||||
float fInverseHL = 1.0 / fHorizonLength;
|
||||
float fHalfHorizonLength = fHorizonLength / 2.0;
|
||||
float fInverseHHL = 1.0 / fHalfHorizonLength;
|
||||
float fMidPoint = fNightHeight + fHalfHorizonLength;
|
||||
|
||||
float fNightContrib = clamp((fSunHeight - fMidPoint) * (-fInverseHHL), 0.0, 1.0);
|
||||
float fHorizonContrib = -clamp(abs((fSunHeight - fMidPoint) * (-fInverseHHL)), 0.0, 1.0) + 1.0;
|
||||
float fDayContrib = clamp((fSunHeight - fMidPoint) * ( fInverseHHL), 0.0, 1.0);
|
||||
|
||||
// sky color
|
||||
vec3 vSkyColor = vec3(0.0);
|
||||
vSkyColor += mix(vec3(0.0), vNightColor, fNightContrib); // Night
|
||||
vSkyColor += mix(vec3(0.0), vHorizonColor, fHorizonContrib); // Horizon
|
||||
vSkyColor += mix(vec3(0.0), uDayColor, fDayContrib); // Day
|
||||
vec3 col = vSkyColor;
|
||||
|
||||
// atmosphere brighter near horizon
|
||||
col -= clamp(rd.y, 0.0, 0.5);
|
||||
|
||||
// draw sun
|
||||
col += 0.4 * vSunRimColor * pow( sun, 4.0 );
|
||||
col += 1.0 * vSunColor * pow( sun, 2000.0 );
|
||||
|
||||
// stars
|
||||
float fStarContrib = clamp((fSunHeight - fDayHeight) * (-fInverseHL), 0.0, 1.0);
|
||||
if (fStarContrib > 0.0) {
|
||||
vec3 vStarDir = rd;
|
||||
col = mix(col, stars3(vStarDir), fStarContrib);
|
||||
}
|
||||
|
||||
// Ten layers of rain sheets...
|
||||
float rainBrightness = 0.15;
|
||||
vec2 q = vec2(atan(rd.x, -rd.z), asin(rd.y));
|
||||
float dis = 1;
|
||||
int sheets = 12;
|
||||
for (int i = 0; i < sheets; i++) {
|
||||
float f = pow(dis, .45) + 0.25;
|
||||
vec2 st = f * (q * vec2(2.0, .05) + vec2(-iGlobalTime*.01 + q.y * .05, iGlobalTime * 0.12));
|
||||
f = texture2D(iChannel2, st * .5, -59.0).x;
|
||||
f += texture2D(iChannel2, st*.284, -99.0).y;
|
||||
f = clamp(pow(abs(f)*.5, 29.0) * 140.0, 0.00, q.y*.4+.05);
|
||||
vec3 bri = vec3(rainBrightness);
|
||||
col += bri*f;
|
||||
dis += 3.5;
|
||||
}
|
||||
|
||||
return vec4(clamp(col, 0.0, 1.0), 1.0);
|
||||
}
|
||||
|
||||
vec3 getSkyboxColor() {
|
||||
return render( vec3(0.0), normalize(_normal) ).rgb;
|
||||
}
|
|
@ -17,14 +17,14 @@
|
|||
function ToggleButtonBuddy(x, y, width, height, urls) {
|
||||
this.up = Overlays.addOverlay("image", {
|
||||
x: x, y: y, width: width, height: height,
|
||||
subImage: { x: 0, y: height, width: width, height: height},
|
||||
subImage: { x: 0, y: 0, width: width, height: height},
|
||||
imageURL: urls.up,
|
||||
visible: true,
|
||||
alpha: 1.0
|
||||
});
|
||||
this.down = Overlays.addOverlay("image", {
|
||||
x: x, y: y, width: width, height: height,
|
||||
subImage: { x: 0, y: height, width: width, height: height},
|
||||
subImage: { x: 0, y: 0, width: width, height: height},
|
||||
imageURL: urls.down,
|
||||
visible: false,
|
||||
alpha: 1.0
|
||||
|
@ -120,7 +120,6 @@ coatButton.addToggleHandler(function (isDown) {
|
|||
}
|
||||
});
|
||||
|
||||
|
||||
function wearAttachment(attachment) {
|
||||
MyAvatar.attach(attachment.modelURL,
|
||||
attachment.jointName,
|
||||
|
@ -144,5 +143,5 @@ function removeAttachment(attachment) {
|
|||
|
||||
Script.scriptEnding.connect(function() {
|
||||
hatButton.destroy();
|
||||
coatbutton.destroy();
|
||||
coatButton.destroy();
|
||||
});
|
||||
|
|
|
@ -46,7 +46,7 @@ function emitter(jointName) {
|
|||
x:0,
|
||||
y: 0,
|
||||
z: 0,
|
||||
w: Math.PI
|
||||
w: 1
|
||||
},
|
||||
emitRadiusStart: 0,
|
||||
polarStart: 0,
|
||||
|
@ -84,7 +84,7 @@ function emitter(jointName) {
|
|||
alpha: 1,
|
||||
alphaSpread: 0,
|
||||
alphaStart: 1,
|
||||
alphaFinish: 1
|
||||
alphaFinish: 1
|
||||
});
|
||||
return newEmitter;
|
||||
}
|
||||
|
|
689
examples/toybox/flappyAvatars/flappyAvatars.js
Normal file
689
examples/toybox/flappyAvatars/flappyAvatars.js
Normal file
|
@ -0,0 +1,689 @@
|
|||
//
|
||||
// flappyAvatars.js
|
||||
// examples/toybox
|
||||
//
|
||||
// Created by Clement 3/2/16
|
||||
// Copyright 2015 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() {
|
||||
// Constants
|
||||
var TRIGGER_CONTROLS = [
|
||||
Controller.Standard.LT,
|
||||
Controller.Standard.RT,
|
||||
];
|
||||
|
||||
var G = 4.0;
|
||||
|
||||
Number.prototype.clamp = function(min, max) {
|
||||
return Math.min(Math.max(this, min), max);
|
||||
};
|
||||
|
||||
var entityManager = new EntityManager();
|
||||
|
||||
// Class definitions
|
||||
function Avatar(DEFAULT_X, DEFAULT_Y, rotation, to3DPosition) {
|
||||
var DIMENSION = 0.15;
|
||||
var JUMP_VELOCITY = 1.0;
|
||||
var xPosition = DEFAULT_X;
|
||||
var color = { red: 0, green: 0, blue: 255 };
|
||||
|
||||
var dimensionsSet = false;
|
||||
var dimensions = { x: DIMENSION, y: DIMENSION, z: DIMENSION };
|
||||
var yPosition = DEFAULT_Y;
|
||||
var yVelocity = 0.0;
|
||||
var yAcceleration = -G;
|
||||
|
||||
var airSwipeSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/Air Swipe 05.wav");
|
||||
var injector = null;
|
||||
|
||||
this.position = function() {
|
||||
return { x: xPosition, y: yPosition };
|
||||
}
|
||||
this.dimensions = function() {
|
||||
return dimensions;
|
||||
}
|
||||
|
||||
var id = entityManager.add({
|
||||
type: "Model",
|
||||
modelURL: MyAvatar.skeletonModelURL,
|
||||
animation: {
|
||||
url: "http://hifi-content.s3.amazonaws.com/ozan/dev/anim/standard_anims_160127/fly.fbx",
|
||||
running: true,
|
||||
fps: 30,
|
||||
firstFrame: 1.0,
|
||||
lastFrame: 80.0,
|
||||
currentFrame: 1.0,
|
||||
loop: true,
|
||||
hold: false
|
||||
},
|
||||
position: to3DPosition(this.position()),
|
||||
rotation: rotation,
|
||||
dimensions: dimensions
|
||||
});
|
||||
|
||||
this.changeModel = function(modelURL) {
|
||||
dimensionsSet = false;
|
||||
dimensions = { x: 0.10, y: 0.10, z: 0.01 };
|
||||
|
||||
Entities.editEntity(id, {
|
||||
modelURL: modelURL,
|
||||
rotation: Quat.multiply(rotation, Quat.fromPitchYawRollDegrees(0, -90, 0)),
|
||||
dimensions: dimensions,
|
||||
animation: {running: false}
|
||||
});
|
||||
|
||||
airSwipeSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/8bit Jump 03.wav");
|
||||
injector = null;
|
||||
}
|
||||
|
||||
this.jump = function() {
|
||||
yVelocity = JUMP_VELOCITY;
|
||||
|
||||
if (airSwipeSound.downloaded && !injector) {
|
||||
injector = Audio.playSound(airSwipeSound, { position: to3DPosition(this.position()), volume: 0.05 });
|
||||
} else if (injector) {
|
||||
injector.restart();
|
||||
}
|
||||
}
|
||||
this.update = function(deltaTime) {
|
||||
if (!dimensionsSet) {
|
||||
var properties = Entities.getEntityProperties(id, ["naturalDimensions"]);
|
||||
var naturalDimensions = properties.naturalDimensions;
|
||||
if (naturalDimensions.x != 1 || naturalDimensions.y != 1 || naturalDimensions.z != 1) {
|
||||
var max = Math.max(naturalDimensions.x, Math.max(naturalDimensions.y, naturalDimensions.z));
|
||||
dimensions.x = naturalDimensions.x / max * dimensions.x;
|
||||
dimensions.y = naturalDimensions.y / max * dimensions.y;
|
||||
dimensions.z = naturalDimensions.z / max * dimensions.z;
|
||||
dimensionsSet = true;
|
||||
|
||||
Entities.editEntity(id, {
|
||||
dimensions: dimensions
|
||||
});
|
||||
}
|
||||
} else {
|
||||
dimensions = Entities.getEntityProperties(id, ["dimensions"]).dimensions;
|
||||
}
|
||||
|
||||
yPosition += deltaTime * (yVelocity + deltaTime * yAcceleration / 2.0);
|
||||
yVelocity += deltaTime * yAcceleration;
|
||||
}
|
||||
this.draw = function() {
|
||||
Entities.editEntity(id, {
|
||||
position: to3DPosition(this.position())
|
||||
});
|
||||
}
|
||||
this.reset = function() {
|
||||
yPosition = DEFAULT_Y;
|
||||
yVelocity = 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
function Coin(xPosition, yPosition, to3DPosition) {
|
||||
var velocity = 0.4;
|
||||
var dimensions = { x: 0.0625, y: 0.0625, z: 0.0088 };
|
||||
|
||||
this.position = function() {
|
||||
return { x: xPosition, y: yPosition };
|
||||
}
|
||||
|
||||
var id = entityManager.add({
|
||||
type: "Model",
|
||||
modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/coin.fbx",
|
||||
angularVelocity: { x: 0, y: 20, z: 0 },
|
||||
position: to3DPosition(this.position()),
|
||||
dimensions:dimensions
|
||||
});
|
||||
|
||||
this.update = function(deltaTime) {
|
||||
xPosition -= deltaTime * velocity;
|
||||
}
|
||||
this.isColliding = function(avatar) {
|
||||
var deltaX = Math.abs(this.position().x - avatar.position().x);
|
||||
var deltaY = Math.abs(this.position().Y - avatar.position().Y);
|
||||
if (deltaX < (avatar.dimensions().x + dimensions.x) / 2.0 &&
|
||||
deltaX < (avatar.dimensions().y + dimensions.y) / 2.0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
this.draw = function() {
|
||||
Entities.editEntity(id, { position: to3DPosition({ x: xPosition, y: yPosition }) });
|
||||
}
|
||||
this.clear = function() {
|
||||
entityManager.remove(id);
|
||||
}
|
||||
}
|
||||
|
||||
function Pipe(xPosition, yPosition, height, gap, to3DPosition) {
|
||||
var velocity = 0.4;
|
||||
var width = 0.1;
|
||||
|
||||
this.position = function() {
|
||||
return xPosition;
|
||||
}
|
||||
|
||||
var upHeight = yPosition - (height + gap);
|
||||
var upYPosition = height + gap + upHeight / 2.0;
|
||||
|
||||
var idUp = entityManager.add({
|
||||
type: "Model",
|
||||
modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/greenPipe.fbx",
|
||||
rotation: Quat.fromPitchYawRollDegrees(180, 0, 0),
|
||||
position: to3DPosition({ x: xPosition, y: upYPosition }),
|
||||
dimensions: { x: width, y: upHeight, z: width }
|
||||
});
|
||||
var idDown = entityManager.add({
|
||||
type: "Model",
|
||||
modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/greenPipe.fbx",
|
||||
position: to3DPosition({ x: xPosition, y: height / 2.0 }),
|
||||
dimensions: { x: width, y: height, z: width }
|
||||
});
|
||||
|
||||
this.update = function(deltaTime) {
|
||||
xPosition -= deltaTime * velocity;
|
||||
}
|
||||
this.isColliding = function(avatar) {
|
||||
var deltaX = Math.abs(this.position() - avatar.position().x);
|
||||
if (deltaX < (avatar.dimensions().z + width) / 2.0) {
|
||||
var factor = 0.8;
|
||||
var upDistance = (yPosition - upHeight) - (avatar.position().y + avatar.dimensions().y * factor);
|
||||
var downDistance = (avatar.position().y - avatar.dimensions().y * factor) - height;
|
||||
if (upDistance <= 0 || downDistance <= 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
this.draw = function() {
|
||||
Entities.editEntity(idUp, { position: to3DPosition({ x: xPosition, y: upYPosition }) });
|
||||
Entities.editEntity(idDown, { position: to3DPosition({ x: xPosition, y: height / 2.0 }) });
|
||||
}
|
||||
this.clear = function() {
|
||||
entityManager.remove(idUp);
|
||||
entityManager.remove(idDown);
|
||||
}
|
||||
}
|
||||
|
||||
function Pipes(newPipesPosition, newPipesHeight, to3DPosition, moveScore) {
|
||||
var lastPipe = 0;
|
||||
var pipesInterval = 2.0;
|
||||
|
||||
var pipes = new Array();
|
||||
var coins = new Array();
|
||||
|
||||
var coinsSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/Coin.wav");
|
||||
var injector = null;
|
||||
|
||||
this.update = function(deltaTime, gameTime, startedPlaying) {
|
||||
// Move pipes forward
|
||||
pipes.forEach(function(element) {
|
||||
element.update(deltaTime);
|
||||
});
|
||||
// Move coins forward
|
||||
coins.forEach(function(element) {
|
||||
element.update(deltaTime);
|
||||
});
|
||||
// Delete pipes over the end
|
||||
var count = 0;
|
||||
while(count < pipes.length && pipes[count].position() <= 0.0) {
|
||||
pipes[count].clear();
|
||||
count++;
|
||||
}
|
||||
if (count > 0) {
|
||||
pipes = pipes.splice(count);
|
||||
}
|
||||
// Delete coins over the end
|
||||
count = 0;
|
||||
while(count < coins.length && coins[count].position() <= 0.0) {
|
||||
coins[count].clear();
|
||||
count++;
|
||||
}
|
||||
if (count > 0) {
|
||||
coins = coins.splice(count);
|
||||
}
|
||||
// Make new pipes and coins
|
||||
if (startedPlaying && gameTime - lastPipe > pipesInterval) {
|
||||
var min = 0.4;
|
||||
var max = 0.7;
|
||||
var height = Math.random() * (max - min) + min;
|
||||
pipes.push(new Pipe(newPipesPosition, newPipesHeight, height, 0.5, to3DPosition));
|
||||
coins.push(new Coin(newPipesPosition, height + 0.5 / 2.0, to3DPosition));
|
||||
lastPipe = gameTime;
|
||||
}
|
||||
}
|
||||
this.isColliding = function(avatar) {
|
||||
// Check coin collection
|
||||
var collected = -1;
|
||||
coins.forEach(function(element, index) {
|
||||
if (element.isColliding(avatar)) {
|
||||
element.clear();
|
||||
collected = index;
|
||||
moveScore(1);
|
||||
|
||||
if (coinsSound.downloaded && !injector) {
|
||||
injector = Audio.playSound(coinsSound, { position: to3DPosition({ x: newPipesPosition, y: newPipesHeight }), volume: 0.1 });
|
||||
} else if (injector) {
|
||||
injector.restart();
|
||||
}
|
||||
}
|
||||
});
|
||||
if (collected > -1) {
|
||||
coins.splice(collected, 1);
|
||||
}
|
||||
|
||||
|
||||
// Check collisions
|
||||
var isColliding = false;
|
||||
|
||||
pipes.forEach(function(element) {
|
||||
isColliding |= element.isColliding(avatar);
|
||||
});
|
||||
|
||||
return isColliding;
|
||||
}
|
||||
this.draw = function() {
|
||||
// Drawing pipes
|
||||
pipes.forEach(function(element) {
|
||||
element.draw();
|
||||
});
|
||||
// Drawing coins
|
||||
coins.forEach(function(element) {
|
||||
element.draw();
|
||||
});
|
||||
}
|
||||
this.clear = function() {
|
||||
// Clearing pipes
|
||||
pipes.forEach(function(element) {
|
||||
element.clear();
|
||||
});
|
||||
pipes = new Array();
|
||||
|
||||
// Clearing coins
|
||||
coins.forEach(function(element) {
|
||||
element.clear();
|
||||
});
|
||||
coins = new Array();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Score(space, bestScore) {
|
||||
var score = 0;
|
||||
var highScore = bestScore;
|
||||
|
||||
var topOffset = Vec3.multiplyQbyV(space.orientation, { x: -0.1, y: 0.2, z: -0.2 });
|
||||
var topLeft = Vec3.sum(space.position, topOffset);
|
||||
var bottomOffset = Vec3.multiplyQbyV(space.orientation, { x: -0.1, y: 0.0, z: -0.2 });
|
||||
var bottomLeft = Vec3.sum(space.position, bottomOffset);
|
||||
|
||||
var numberDimensions = { x: 0.0660, y: 0.1050, z: 0.0048 };
|
||||
|
||||
function numberUrl(number) {
|
||||
return "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/" + number + ".fbx"
|
||||
}
|
||||
function digitPosition(digit) {
|
||||
return Vec3.multiplyQbyV(space.orientation, { x: 0.3778 + digit * (numberDimensions.x + 0.01), y: 0.0, z: 0.0 });
|
||||
}
|
||||
this.score = function() {
|
||||
return score;
|
||||
}
|
||||
this.highScore = function() {
|
||||
return highScore;
|
||||
}
|
||||
|
||||
var numDigits = 3;
|
||||
|
||||
var bestId = entityManager.add({
|
||||
type: "Model",
|
||||
modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/best.fbx",
|
||||
position: topLeft,
|
||||
rotation: Quat.multiply(space.orientation, Quat.fromPitchYawRollDegrees(90, 0, 0)),
|
||||
dimensions: { x: 0.2781, y: 0.0063, z: 0.1037 }
|
||||
});
|
||||
var bestDigitsId = []
|
||||
for (var i = 0; i < numDigits; i++) {
|
||||
bestDigitsId[i] = entityManager.add({
|
||||
type: "Model",
|
||||
modelURL: numberUrl(0),
|
||||
position: Vec3.sum(topLeft, digitPosition(i)),
|
||||
rotation: space.orientation,
|
||||
dimensions: numberDimensions
|
||||
});
|
||||
}
|
||||
|
||||
var scoreId = entityManager.add({
|
||||
type: "Model",
|
||||
modelURL: "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/score.fbx",
|
||||
position: bottomLeft,
|
||||
rotation: Quat.multiply(space.orientation, Quat.fromPitchYawRollDegrees(90, 0, 0)),
|
||||
dimensions: { x: 0.3678, y: 0.0063, z: 0.1037 }
|
||||
});
|
||||
var scoreDigitsId = []
|
||||
for (var i = 0; i < numDigits; i++) {
|
||||
scoreDigitsId[i] = entityManager.add({
|
||||
type: "Model",
|
||||
modelURL: numberUrl(0),
|
||||
position: Vec3.sum(bottomLeft, digitPosition(i)),
|
||||
rotation: space.orientation,
|
||||
dimensions: numberDimensions
|
||||
});
|
||||
}
|
||||
|
||||
this.moveScore = function(delta) {
|
||||
score += delta;
|
||||
if (score > highScore) {
|
||||
highScore = score;
|
||||
}
|
||||
}
|
||||
this.resetScore = function() {
|
||||
score = 0;
|
||||
}
|
||||
|
||||
this.draw = function() {
|
||||
for (var i = 0; i < numDigits; i++) {
|
||||
Entities.editEntity(bestDigitsId[i], { modelURL: numberUrl(Math.floor((highScore / Math.pow(10, numDigits- i - 1)) % 10)) });
|
||||
}
|
||||
|
||||
for (var i = 0; i < numDigits; i++) {
|
||||
Entities.editEntity(scoreDigitsId[i], { modelURL: numberUrl(Math.floor(score / Math.pow(10, numDigits - i - 1)) % 10) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Game(bestScore) {
|
||||
// public methods
|
||||
this.start = function() {
|
||||
if (!isRunning) {
|
||||
isRunning = true;
|
||||
setup();
|
||||
}
|
||||
}
|
||||
|
||||
this.stop = function() {
|
||||
if (isRunning) {
|
||||
cleanup();
|
||||
isRunning = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Game loop setup
|
||||
var timestamp = 0;
|
||||
this.idle = function(triggerValue) {
|
||||
var now = Date.now();
|
||||
var deltaTime = (now - timestamp) / 1000.0;
|
||||
if (timestamp === 0) {
|
||||
deltaTime = 0;
|
||||
}
|
||||
gameTime += deltaTime;
|
||||
|
||||
inputs(triggerValue);
|
||||
update(deltaTime);
|
||||
draw();
|
||||
timestamp = now;
|
||||
}
|
||||
|
||||
// Constants
|
||||
var spaceDimensions = { x: 2.0, y: 1.5, z: 0.01 };
|
||||
var spaceDistance = 1.5;
|
||||
var spaceYOffset = 0.6;
|
||||
|
||||
// Private game state
|
||||
var that = this;
|
||||
var isRunning = false;
|
||||
var startedPlaying = false;
|
||||
|
||||
var coolDown = 1.5;
|
||||
var lastLost = -coolDown;
|
||||
|
||||
var gameTime = 0;
|
||||
|
||||
var isJumping = false;
|
||||
var lastJumpValue = 0.0;
|
||||
var lastTriggerValue = 0.0;
|
||||
var TRIGGER_THRESHOLD = 0.9;
|
||||
|
||||
var space = null;
|
||||
var avatar = null;
|
||||
var pipes = null;
|
||||
var score = null;
|
||||
|
||||
var gameOverSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/Game Over.wav");
|
||||
var injector = null;
|
||||
|
||||
var directions = ["UP", "DOWN", "LEFT", "RIGHT"];
|
||||
var sequence = [directions[0], directions[0], directions[1], directions[1], directions[2], directions[3], directions[2], directions[3], "b", "a"];
|
||||
var current = 0;
|
||||
function keyPress(event) {
|
||||
if (event.text === sequence[current]) {
|
||||
++current;
|
||||
} else {
|
||||
current = 0;
|
||||
}
|
||||
if (current === sequence.length) {
|
||||
avatar.changeModel("https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/mario.fbx");
|
||||
current = 0;
|
||||
}
|
||||
}
|
||||
|
||||
var isBoardReset = true;
|
||||
|
||||
function moveScore(delta) {
|
||||
score.moveScore(delta);
|
||||
}
|
||||
|
||||
this.score = function() {
|
||||
return score.score();
|
||||
}
|
||||
this.highScore = function() {
|
||||
return score.highScore();
|
||||
}
|
||||
|
||||
function setup() {
|
||||
space = {
|
||||
position: getSpacePosition(),
|
||||
orientation: getSpaceOrientation(),
|
||||
dimensions: getSpaceDimensions()
|
||||
}
|
||||
|
||||
var rotation = Quat.multiply(space.orientation, Quat.fromPitchYawRollDegrees(0, 90, 0));
|
||||
avatar = new Avatar(space.dimensions.x / 2.0, space.dimensions.y / 2.0, rotation, to3DPosition);
|
||||
pipes = new Pipes(space.dimensions.x, space.dimensions.y, to3DPosition, moveScore);
|
||||
score = new Score(space, bestScore);
|
||||
|
||||
Controller.keyPressEvent.connect(keyPress);
|
||||
}
|
||||
function inputs(triggerValue) {
|
||||
if (!startedPlaying && !isBoardReset && (gameTime - lastLost) > coolDown) {
|
||||
score.resetScore();
|
||||
avatar.reset();
|
||||
pipes.clear();
|
||||
|
||||
isBoardReset = true;
|
||||
}
|
||||
|
||||
if (triggerValue > TRIGGER_THRESHOLD &&
|
||||
lastTriggerValue < TRIGGER_THRESHOLD &&
|
||||
(gameTime - lastLost) > coolDown) {
|
||||
isJumping = true;
|
||||
startedPlaying = true;
|
||||
}
|
||||
lastTriggerValue = triggerValue;
|
||||
}
|
||||
function update(deltaTime) {
|
||||
// Keep entities alive
|
||||
entityManager.update(deltaTime);
|
||||
|
||||
if (!startedPlaying && (gameTime - lastLost) < coolDown && !isBoardReset) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update Avatar
|
||||
if (!startedPlaying && avatar.position().y < spaceDimensions.y / 2.0) {
|
||||
isJumping = true;
|
||||
}
|
||||
// Apply jumps
|
||||
if (isJumping) {
|
||||
avatar.jump();
|
||||
isJumping = false;
|
||||
}
|
||||
avatar.update(deltaTime);
|
||||
|
||||
pipes.update(deltaTime, gameTime, startedPlaying);
|
||||
|
||||
// Check lost
|
||||
var hasLost = avatar.position().y < 0.0 ||
|
||||
avatar.position().y > space.dimensions.y ||
|
||||
pipes.isColliding(avatar);
|
||||
|
||||
|
||||
// Cleanup
|
||||
if (hasLost) {
|
||||
if (gameOverSound.downloaded && !injector) {
|
||||
injector = Audio.playSound(gameOverSound, { position: space.position, volume: 0.4 });
|
||||
} else if (injector) {
|
||||
injector.restart();
|
||||
}
|
||||
|
||||
isBoardReset = false;
|
||||
startedPlaying = false;
|
||||
lastLost = gameTime;
|
||||
}
|
||||
}
|
||||
function draw() {
|
||||
avatar.draw();
|
||||
pipes.draw();
|
||||
score.draw();
|
||||
}
|
||||
function cleanup() {
|
||||
entityManager.removeAll();
|
||||
|
||||
Controller.keyPressEvent.disconnect(keyPress);
|
||||
}
|
||||
|
||||
// Private methods
|
||||
function getSpacePosition() {
|
||||
var forward = Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.FRONT);
|
||||
var spacePosition = Vec3.sum(MyAvatar.position, Vec3.multiply(spaceDistance, forward));
|
||||
return Vec3.sum(spacePosition, Vec3.multiply(spaceYOffset, Vec3.UP));
|
||||
}
|
||||
function getSpaceOrientation() {
|
||||
return MyAvatar.orientation;
|
||||
}
|
||||
function getSpaceDimensions() {
|
||||
return spaceDimensions;
|
||||
}
|
||||
|
||||
function to3DPosition(position) {
|
||||
var position2D = {
|
||||
x: position.x - space.dimensions.x / 2.0,
|
||||
y: position.y - space.dimensions.y / 2.0,
|
||||
z: 0.0
|
||||
}
|
||||
return Vec3.sum(space.position, Vec3.multiplyQbyV(space.orientation, position2D));
|
||||
}
|
||||
}
|
||||
|
||||
function EntityManager() {
|
||||
var OBJECTS_LIFETIME = 1;
|
||||
var entities = new Array();
|
||||
var lifetime = OBJECTS_LIFETIME;
|
||||
|
||||
this.setLifetime = function(newLifetime) {
|
||||
lifetime = newLifetime;
|
||||
this.update();
|
||||
}
|
||||
this.add = function(properties) {
|
||||
// Add to scene
|
||||
properties.lifetime = lifetime;
|
||||
var entityID = Entities.addEntity(properties);
|
||||
// Add to array
|
||||
entities.push({ id: entityID, properties: properties });
|
||||
|
||||
return entityID;
|
||||
}
|
||||
this.update = function(deltaTime) {
|
||||
entities.forEach(function(element) {
|
||||
// Get entity's age
|
||||
var properties = Entities.getEntityProperties(element.id, ["age"]);
|
||||
// Update entity's lifetime
|
||||
Entities.editEntity(element.id, { lifetime: properties.age + lifetime });
|
||||
});
|
||||
}
|
||||
this.remove = function(entityID) {
|
||||
// Remove from scene
|
||||
Entities.deleteEntity(entityID);
|
||||
|
||||
// Remove from array
|
||||
entities = entities.filter(function(element) {
|
||||
return element.id !== entityID;
|
||||
});
|
||||
}
|
||||
this.removeAll = function() {
|
||||
// Remove all from scene
|
||||
entities.forEach(function(element) {
|
||||
Entities.deleteEntity(element.id);
|
||||
});
|
||||
// Remove all from array
|
||||
entities = new Array();
|
||||
}
|
||||
}
|
||||
|
||||
PartableGame = function() {
|
||||
this.entityID = null;
|
||||
this.equipped = false;
|
||||
this.triggerValue = 0.0;
|
||||
this.hand = 0;
|
||||
this.game = null;
|
||||
};
|
||||
|
||||
PartableGame.prototype = {
|
||||
preload: function(entityID) {
|
||||
this.entityID = entityID;
|
||||
},
|
||||
unload: function() {
|
||||
},
|
||||
startEquip: function(id, params) {
|
||||
this.equipped = true;
|
||||
this.hand = params[0] == "left" ? 0 : 1;
|
||||
|
||||
|
||||
var bestScore = 0;
|
||||
var properties = Entities.getEntityProperties(this.entityID, ["userData"]);
|
||||
var userData = JSON.parse(properties.userData);
|
||||
if (userData.highScore) {
|
||||
bestScore = userData.highScore;
|
||||
}
|
||||
|
||||
this.game = new Game(bestScore);
|
||||
this.game.start();
|
||||
},
|
||||
releaseEquip: function(id, params) {
|
||||
this.equipped = false;
|
||||
|
||||
var properties = Entities.getEntityProperties(this.entityID, ["userData"]);
|
||||
var userData = JSON.parse(properties.userData);
|
||||
userData.highScore = this.game.highScore();
|
||||
properties.userData = JSON.stringify(userData);
|
||||
Entities.editEntity(this.entityID, properties);
|
||||
|
||||
this.game.stop();
|
||||
delete this.game;
|
||||
},
|
||||
continueEquip: function(id, params) {
|
||||
if (!this.equipped) {
|
||||
return;
|
||||
}
|
||||
this.triggerValue = Controller.getValue(TRIGGER_CONTROLS[this.hand]);
|
||||
this.game.idle(this.triggerValue);
|
||||
},
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new PartableGame();
|
||||
});
|
34
examples/toybox/flappyAvatars/flappyAvatars.json
Normal file
34
examples/toybox/flappyAvatars/flappyAvatars.json
Normal file
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"Entities": [
|
||||
{
|
||||
"collisionsWillMove": 1,
|
||||
"created": "2016-03-03T19:00:10Z",
|
||||
"dimensions": {
|
||||
"x": 0.11497055739164352,
|
||||
"y": 0.11497056484222412,
|
||||
"z": 0.11497056484222412
|
||||
},
|
||||
"dynamic": 1,
|
||||
"id": "{ee5b25e6-aca2-4dc7-9462-51537d89c126}",
|
||||
"modelURL": "https://s3-us-west-1.amazonaws.com/hifi-content/clement/production/cube.fbx",
|
||||
"queryAACube": {
|
||||
"scale": 0.5974045991897583,
|
||||
"x": -5.1575918197631836,
|
||||
"y": 23.078603744506836,
|
||||
"z": 16.521066665649414
|
||||
},
|
||||
"rotation": {
|
||||
"w": 0.92288088798522949,
|
||||
"x": -0.10148775577545166,
|
||||
"y": -0.13279926776885986,
|
||||
"z": 0.34688329696655273
|
||||
},
|
||||
"script": "https://raw.githubusercontent.com/Atlante45/hifi/feat/hackaton/examples/toybox/flappyAvatars/flappyAvatars.js",
|
||||
"scriptTimestamp": 1457031937425,
|
||||
"shapeType": "box",
|
||||
"type": "Model",
|
||||
"userData": "{\"wearable\":{\"joints\":{\"RightHand\":[{\"x\":0.07079616189002991,\"y\":0.20177987217903137,\"z\":0.06374628841876984},{\"x\":-0.5863648653030396,\"y\":-0.46007341146469116,\"z\":0.46949487924575806,\"w\":-0.4733745753765106}],\"LeftHand\":[{\"x\":-0.018704339861869812,\"y\":0.20499876141548157,\"z\":0.08445858210325241},{\"x\":0.2061777561903,\"y\":-0.6629757881164551,\"z\":0.5865269303321838,\"w\":0.41706138849258423}]}},\"grabbableKey\":{\"invertSolidWhileHeld\":true},\"resetMe\":{\"resetMe\":true},\"highScore\":0}"
|
||||
}
|
||||
],
|
||||
"Version": 57
|
||||
}
|
|
@ -104,8 +104,9 @@
|
|||
green: 255,
|
||||
blue: 255
|
||||
},
|
||||
intensity: 2,
|
||||
exponent: 0.3,
|
||||
intensity: 1,
|
||||
falloffRadius:0.9,
|
||||
exponent: 0.5,
|
||||
cutoff: 20,
|
||||
lifetime: LIFETIME,
|
||||
position: lightTransform.p,
|
||||
|
@ -128,6 +129,8 @@
|
|||
blue: 255
|
||||
},
|
||||
exponent: 0,
|
||||
intensity:1.0,
|
||||
falloffRadius:0.3,
|
||||
lifetime: LIFETIME,
|
||||
cutoff: 90, // in degrees
|
||||
position: glowLightTransform.p,
|
||||
|
|
100
examples/toybox/musicPlayer/createNewMusicPlayerOnClick.js
Normal file
100
examples/toybox/musicPlayer/createNewMusicPlayerOnClick.js
Normal file
|
@ -0,0 +1,100 @@
|
|||
//
|
||||
// createNewMusicPlayerOnClick.js
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 3/3/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Entity Script that you can attach to any entity to have it spawn new "music players"
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
(function(){
|
||||
|
||||
var musicPlayerScript = Script.resolvePath("./musicPlayer.js");
|
||||
var imageShader = Script.resolvePath("./imageShader.fs");
|
||||
var defaultImage = Script.resolvePath("./defaultImage.jpg");
|
||||
|
||||
function getPositionToCreateEntity() {
|
||||
var distance = 0.5;
|
||||
var direction = Quat.getFront(Camera.orientation);
|
||||
var offset = Vec3.multiply(distance, direction);
|
||||
var placementPosition = Vec3.sum(Camera.position, offset);
|
||||
|
||||
var cameraPosition = Camera.position;
|
||||
|
||||
var HALF_TREE_SCALE = 16384;
|
||||
|
||||
var cameraOutOfBounds = Math.abs(cameraPosition.x) > HALF_TREE_SCALE || Math.abs(cameraPosition.y) > HALF_TREE_SCALE || Math.abs(cameraPosition.z) > HALF_TREE_SCALE;
|
||||
var placementOutOfBounds = Math.abs(placementPosition.x) > HALF_TREE_SCALE || Math.abs(placementPosition.y) > HALF_TREE_SCALE || Math.abs(placementPosition.z) > HALF_TREE_SCALE;
|
||||
|
||||
if (cameraOutOfBounds && placementOutOfBounds) {
|
||||
return null;
|
||||
}
|
||||
|
||||
placementPosition.x = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.x));
|
||||
placementPosition.y = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.y));
|
||||
placementPosition.z = Math.min(HALF_TREE_SCALE, Math.max(-HALF_TREE_SCALE, placementPosition.z));
|
||||
|
||||
return placementPosition;
|
||||
}
|
||||
|
||||
function createNewIPOD() {
|
||||
var iPodPosition = { x: 0, y: .5, z: 0};
|
||||
var iPodDimensions = { x: 0.15, y: 0.3, z: 0.03 };
|
||||
var overlayDimensions = { x: 0.13, y: 0.13, z: 0.001 };
|
||||
var boxOverlayDimensions = { x: 0.13, y: 0.13, z: 0.0001 };
|
||||
|
||||
var iPodID = Entities.addEntity({
|
||||
type: "Box",
|
||||
name: "music player",
|
||||
position: iPodPosition,
|
||||
dimensions: iPodDimensions,
|
||||
color: { red: 150, green: 150, blue: 150},
|
||||
script: musicPlayerScript,
|
||||
dynamic: true
|
||||
});
|
||||
print(iPodID);
|
||||
|
||||
var textID = Entities.addEntity({
|
||||
type: "Text",
|
||||
name: "now playing",
|
||||
position: { x: 0, y: (0.5+0.07), z: 0.0222},
|
||||
dimensions: overlayDimensions,
|
||||
color: { red: 150, green: 150, blue: 150},
|
||||
parentID: iPodID,
|
||||
lineHeight: 0.01,
|
||||
text: "Pause"
|
||||
});
|
||||
|
||||
|
||||
var newAlbumArt = JSON.stringify(
|
||||
{
|
||||
"ProceduralEntity": {
|
||||
"version":2,
|
||||
"shaderUrl":imageShader,
|
||||
"uniforms":{"iSpeed":0,"iShell":1},
|
||||
"channels":[defaultImage]
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
var albumArtID = Entities.addEntity({
|
||||
type: "Box",
|
||||
name: "album art",
|
||||
position: { x: 0, y: (0.5-0.07), z: 0.01506},
|
||||
dimensions: boxOverlayDimensions,
|
||||
color: { red: 255, green: 255, blue: 255},
|
||||
userData: newAlbumArt,
|
||||
parentID: iPodID
|
||||
});
|
||||
Entities.editEntity(iPodID, { position: getPositionToCreateEntity() });
|
||||
}
|
||||
|
||||
|
||||
this.clickDownOnEntity = function(myID, mouseEvent) {
|
||||
createNewIPOD();
|
||||
};
|
||||
})
|
||||
|
||||
|
BIN
examples/toybox/musicPlayer/defaultImage.jpg
Normal file
BIN
examples/toybox/musicPlayer/defaultImage.jpg
Normal file
Binary file not shown.
After ![]() (image error) Size: 57 KiB |
44
examples/toybox/musicPlayer/imageShader.fs
Normal file
44
examples/toybox/musicPlayer/imageShader.fs
Normal file
|
@ -0,0 +1,44 @@
|
|||
float aspect(vec2 v) {
|
||||
return v.x / v.y;
|
||||
}
|
||||
|
||||
vec3 indexedTexture() {
|
||||
vec2 uv = _position.xy;
|
||||
uv += 0.5;
|
||||
uv.y = 1.0 - uv.y;
|
||||
|
||||
float targetAspect = iWorldScale.x / iWorldScale.y;
|
||||
float sourceAspect = aspect(iChannelResolution[0].xy);
|
||||
float aspectCorrection = sourceAspect / targetAspect;
|
||||
if (aspectCorrection > 1.0) {
|
||||
float offset = aspectCorrection - 1.0;
|
||||
float halfOffset = offset / 2.0;
|
||||
uv.y -= halfOffset;
|
||||
uv.y *= aspectCorrection;
|
||||
} else {
|
||||
float offset = 1.0 - aspectCorrection;
|
||||
float halfOffset = offset / 2.0;
|
||||
uv.x -= halfOffset;
|
||||
uv.x /= aspectCorrection;
|
||||
}
|
||||
|
||||
if (any(lessThan(uv, vec2(0.0)))) {
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
if (any(greaterThan(uv, vec2(1.0)))) {
|
||||
return vec3(0.0);
|
||||
}
|
||||
|
||||
vec4 color = texture(iChannel0, uv);
|
||||
return color.rgb * max(0.5, sourceAspect) * max(0.9, fract(iWorldPosition.x));
|
||||
}
|
||||
|
||||
float getProceduralColors(inout vec3 diffuse, inout vec3 specular, inout float shininess) {
|
||||
if (_position.z > -0.49) {
|
||||
discard;
|
||||
}
|
||||
|
||||
specular = indexedTexture();
|
||||
return 1.0;
|
||||
}
|
330
examples/toybox/musicPlayer/musicPlayer.js
Normal file
330
examples/toybox/musicPlayer/musicPlayer.js
Normal file
|
@ -0,0 +1,330 @@
|
|||
//
|
||||
// musicPlayer.js
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 3/3/16.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
/*global print, MyAvatar, Entities, AnimationCache, SoundCache, Scene, Camera, Overlays, Audio, HMD, AvatarList, AvatarManager, Controller, UndoStack, Window, Account, GlobalServices, Script, ScriptDiscoveryService, LODManager, Menu, Vec3, Quat, AudioDevice, Paths, Clipboard, Settings, XMLHttpRequest, randFloat, randInt */
|
||||
|
||||
(function() {
|
||||
|
||||
var imageShader = Script.resolvePath("./imageShader.fs");
|
||||
var defaultImage = Script.resolvePath("./defaultImage.jpg");
|
||||
|
||||
var MAPPING_NAME = "com.highfidelity.musicPlayerEntity";
|
||||
|
||||
var PLAYLIST_URL = "https://spreadsheets.google.com/feeds/cells/1x-ceGPGHldkHadARABFWfujLPTOWzXJPhrf2bTwg2cQ/od6/public/basic?alt=json";
|
||||
var SONG_VOLUME = 0.1;
|
||||
var HEADPHONES_ATTACHMENT = {
|
||||
modelURL: "https://s3.amazonaws.com/hifi-public/brad/musicplayer/headphones2-v2.fbx",
|
||||
jointName: "Head",
|
||||
translation: {"x": 0, "y": 0.19, "z": 0.06},
|
||||
rotation: {"x":0,"y":0.7071067690849304,"z":0.7071067690849304,"w":0},
|
||||
scale: 0.435,
|
||||
isSoft: false
|
||||
};
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Various helper functions...
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Helper function for returning either a value, or the default value if the value is undefined. This is
|
||||
// is handing in parsing JSON where you don't know if the values have been set or not.
|
||||
function valueOrDefault(value, defaultValue) {
|
||||
if (value !== undefined) {
|
||||
return value;
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
// return a random float between high and low
|
||||
function randFloat(low, high) {
|
||||
return low + Math.random() * (high - low);
|
||||
}
|
||||
|
||||
// wears an attachment on MyAvatar
|
||||
function wearAttachment(attachment) {
|
||||
MyAvatar.attach(attachment.modelURL,
|
||||
attachment.jointName,
|
||||
attachment.translation,
|
||||
attachment.rotation,
|
||||
attachment.scale,
|
||||
attachment.isSoft);
|
||||
}
|
||||
|
||||
// un-wears an attachment from MyAvatar
|
||||
function removeAttachment(attachment) {
|
||||
var attachments = MyAvatar.attachmentData;
|
||||
var i, l = attachments.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
if (attachments[i].modelURL === attachment.modelURL) {
|
||||
attachments.splice(i, 1);
|
||||
MyAvatar.attachmentData = attachments;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var _this;
|
||||
MusicPlayer = function() {
|
||||
_this = this;
|
||||
this.equipped = false;
|
||||
};
|
||||
|
||||
MusicPlayer.prototype = {
|
||||
preload: function(entityID) {
|
||||
print("preload");
|
||||
//print("rotation:" + JSON.stringify(Quat.fromPitchYawRollDegrees(-90,180,0)));
|
||||
this.entityID = entityID;
|
||||
|
||||
// Get the entities userData property, to see if someone has overridden any of our default settings
|
||||
var userDataText = Entities.getEntityProperties(entityID, ["userData"]).userData;
|
||||
//print(userDataText);
|
||||
var userData = {};
|
||||
if (userDataText !== "") {
|
||||
//print("calling JSON.parse");
|
||||
userData = JSON.parse(userDataText);
|
||||
//print("userData:" + JSON.stringify(userData));
|
||||
}
|
||||
var musicPlayerUserData = valueOrDefault(userData.musicPlayer, {});
|
||||
this.headphonesAttachment = valueOrDefault(musicPlayerUserData.headphonesAttachment, HEADPHONES_ATTACHMENT);
|
||||
|
||||
this.track = 0; // start at the first track
|
||||
this.playlistURL = valueOrDefault(musicPlayerUserData.playlistURL, PLAYLIST_URL);
|
||||
this.songVolume = valueOrDefault(musicPlayerUserData.songVolume, SONG_VOLUME);
|
||||
this.songPlaying = false;
|
||||
|
||||
this.loadPlayList();
|
||||
|
||||
// Find my screen and any controlls
|
||||
var children = Entities.getChildrenIDsOfJoint(entityID, 65535);
|
||||
for (var child in children) {
|
||||
var childID = children[child];
|
||||
var childProperties = Entities.getEntityProperties(childID,["type", "name"]);
|
||||
if (childProperties.type == "Text" && childProperties.name == "now playing") {
|
||||
this.nowPlayingID = childID;
|
||||
}
|
||||
if (childProperties.type == "Box" && childProperties.name == "album art") {
|
||||
this.albumArt = childID;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
unload: function() {
|
||||
print("unload");
|
||||
if (_this.songInjector !== undefined) {
|
||||
_this.songInjector.stop();
|
||||
}
|
||||
},
|
||||
|
||||
loadPlayList: function() {
|
||||
print("loadPlayList");
|
||||
var req = new XMLHttpRequest();
|
||||
req.open("GET", _this.playlistURL, false);
|
||||
req.send();
|
||||
|
||||
var entries = JSON.parse(req.responseText).feed.entry;
|
||||
|
||||
for (entry in entries) {
|
||||
var cellDetails = entries[entry];
|
||||
var cellName = cellDetails.title.$t;
|
||||
var column = Number(cellName.charCodeAt(0)) - Number("A".charCodeAt(0));
|
||||
var row = Number(cellName.slice(1)) - 1;
|
||||
var cellContent = cellDetails.content.$t;
|
||||
//print(JSON.stringify(cellDetails));
|
||||
//print("["+column +"/"+ row +":"+cellContent+"]");
|
||||
if (_this.playList === undefined) {
|
||||
_this.playList = new Array();
|
||||
}
|
||||
if (_this.playList[row] === undefined) {
|
||||
_this.playList[row] = { };
|
||||
}
|
||||
switch (column) {
|
||||
case 0:
|
||||
_this.playList[row].title = cellContent;
|
||||
break;
|
||||
case 1:
|
||||
_this.playList[row].artist = cellContent;
|
||||
break;
|
||||
case 2:
|
||||
_this.playList[row].album = cellContent;
|
||||
break;
|
||||
case 3:
|
||||
_this.playList[row].url = cellContent;
|
||||
_this.playList[row].sound = SoundCache.getSound(cellContent);
|
||||
break;
|
||||
case 4:
|
||||
_this.playList[row].albumArtURL = cellContent;
|
||||
break;
|
||||
}
|
||||
}
|
||||
//print(req.responseText);
|
||||
print(JSON.stringify(_this.playList));
|
||||
},
|
||||
|
||||
startEquip: function(id, params) {
|
||||
var whichHand = params[0]; // "left" or "right"
|
||||
print("I am equipped in the " + whichHand + " hand....");
|
||||
this.equipped = true;
|
||||
this.hand = whichHand;
|
||||
|
||||
this.loadPlayList(); // reload the playlist in case...
|
||||
|
||||
this.mapHandButtons(whichHand);
|
||||
wearAttachment(HEADPHONES_ATTACHMENT);
|
||||
},
|
||||
|
||||
continueEquip: function(id, params) {
|
||||
if (!this.equipped) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
releaseEquip: function(id, params) {
|
||||
print("I am NO LONGER equipped....");
|
||||
this.hand = null;
|
||||
this.equipped = false;
|
||||
Controller.disableMapping(MAPPING_NAME);
|
||||
removeAttachment(HEADPHONES_ATTACHMENT);
|
||||
this.pause();
|
||||
},
|
||||
|
||||
mapHandButtons: function(hand) {
|
||||
var mapping = Controller.newMapping(MAPPING_NAME);
|
||||
if (hand === "left") {
|
||||
mapping.from(Controller.Standard.LS).peek().to(this.playOrPause);
|
||||
mapping.from(Controller.Standard.LX).peek().to(this.seek);
|
||||
mapping.from(Controller.Standard.LY).peek().to(this.volume);
|
||||
} else {
|
||||
mapping.from(Controller.Standard.RS).peek().to(this.playOrPause);
|
||||
mapping.from(Controller.Standard.RX).peek().to(this.seek);
|
||||
mapping.from(Controller.Standard.RY).peek().to(this.volume);
|
||||
}
|
||||
Controller.enableMapping(MAPPING_NAME);
|
||||
},
|
||||
|
||||
playOrPause: function(value) {
|
||||
print("[playOrPause: "+value+"]");
|
||||
if (value === 1) {
|
||||
if (!_this.songPlaying) {
|
||||
_this.play();
|
||||
} else {
|
||||
_this.pause();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
play: function() {
|
||||
print("play current track:" + _this.track);
|
||||
if (!_this.playList[_this.track].sound.downloaded) {
|
||||
print("still waiting on track to download....");
|
||||
return; // not yet ready
|
||||
}
|
||||
|
||||
var statusText = "Song:" + _this.playList[_this.track].title + "\n" +
|
||||
"Artist:" + _this.playList[_this.track].artist + "\n" +
|
||||
"Album:" + _this.playList[_this.track].album;
|
||||
|
||||
Entities.editEntity(_this.nowPlayingID, { text: statusText });
|
||||
|
||||
var newAlbumArt = JSON.stringify(
|
||||
{
|
||||
"ProceduralEntity": {
|
||||
"version":2,
|
||||
"shaderUrl":imageShader,
|
||||
"uniforms":{"iSpeed":0,"iShell":1},
|
||||
"channels":[_this.playList[_this.track].albumArtURL]
|
||||
}
|
||||
});
|
||||
|
||||
Entities.editEntity(_this.albumArt, { userData: newAlbumArt });
|
||||
|
||||
|
||||
_this.songInjector = Audio.playSound(_this.playList[_this.track].sound, {
|
||||
position: MyAvatar.position,
|
||||
volume: _this.songVolume,
|
||||
loop: false,
|
||||
localOnly: true
|
||||
});
|
||||
_this.songPlaying = true;
|
||||
},
|
||||
|
||||
pause: function() {
|
||||
print("pause");
|
||||
Entities.editEntity(_this.nowPlayingID, { text: "Paused" });
|
||||
if (_this.songInjector !== undefined) {
|
||||
_this.songInjector.stop();
|
||||
}
|
||||
var newAlbumArt = JSON.stringify(
|
||||
{
|
||||
"ProceduralEntity": {
|
||||
"version":2,
|
||||
"shaderUrl":imageShader,
|
||||
"uniforms":{"iSpeed":0,"iShell":1},
|
||||
"channels":[defaultImage]
|
||||
}
|
||||
});
|
||||
|
||||
Entities.editEntity(_this.albumArt, { userData: newAlbumArt });
|
||||
|
||||
|
||||
_this.songPlaying = false;
|
||||
},
|
||||
|
||||
seek: function(value) {
|
||||
print("[seek: " + value + "]");
|
||||
if (value > 0.9) {
|
||||
_this.next();
|
||||
} else if (value < -0.9) {
|
||||
_this.previous();
|
||||
}
|
||||
},
|
||||
|
||||
volume: function(value) {
|
||||
print("adjusting volume disabled because of a bug in audio injectors....");
|
||||
/*
|
||||
var scaledValue = value / 20;
|
||||
_this.songVolume += scaledValue;
|
||||
print("[volume: " + value + "] new volume:" + _this.songVolume);
|
||||
if (_this.songInjector !== undefined) {
|
||||
print("[volume: attempting to set options....");
|
||||
var newOptions = {
|
||||
position: MyAvatar.position,
|
||||
volume: _this.songVolume,
|
||||
loop: false,
|
||||
localOnly: true
|
||||
};
|
||||
|
||||
_this.songInjector.options = newOptions;
|
||||
print("[volume: attempting to set options.... RESULT:" + JSON.stringify(_this.songInjector.options));
|
||||
}
|
||||
*/
|
||||
},
|
||||
|
||||
previous: function() {
|
||||
print("[previous]");
|
||||
_this.pause();
|
||||
_this.track--;
|
||||
if (_this.track < 0) {
|
||||
_this.track = (_this.playList.length - 1);
|
||||
}
|
||||
_this.play();
|
||||
},
|
||||
|
||||
next: function() {
|
||||
print("[next]");
|
||||
_this.pause();
|
||||
_this.track++;
|
||||
if (_this.track >= _this.playList.length) {
|
||||
_this.track = 0;
|
||||
}
|
||||
_this.play();
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new MusicPlayer();
|
||||
});
|
|
@ -13,51 +13,61 @@ Script.include("../../libraries/utils.js");
|
|||
|
||||
var scriptURL = Script.resolvePath('pingPongGun.js');
|
||||
|
||||
var MODEL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun.fbx'
|
||||
var COLLISION_HULL_URL = 'http://hifi-public.s3.amazonaws.com/models/ping_pong_gun/ping_pong_gun_convex.obj';
|
||||
var MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Pingpong-Gun-New.fbx'
|
||||
var COLLISION_HULL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Pingpong-Gun-New.obj';
|
||||
var COLLISION_SOUND_URL = 'http://hifi-public.s3.amazonaws.com/sounds/Collisions-otherorganic/plastic_impact.L.wav';
|
||||
var center = Vec3.sum(Vec3.sum(MyAvatar.position, {
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
x: 0,
|
||||
y: 0.5,
|
||||
z: 0
|
||||
}), Vec3.multiply(0.5, Quat.getFront(Camera.getOrientation())));
|
||||
|
||||
var pingPongGun = Entities.addEntity({
|
||||
type: "Model",
|
||||
modelURL: MODEL_URL,
|
||||
shapeType: 'compound',
|
||||
compoundShapeURL: COLLISION_HULL_URL,
|
||||
script: scriptURL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.08,
|
||||
y: 0.21,
|
||||
z: 0.47
|
||||
type: "Model",
|
||||
modelURL: MODEL_URL,
|
||||
shapeType: 'compound',
|
||||
compoundShapeURL: COLLISION_HULL_URL,
|
||||
script: scriptURL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.125,
|
||||
y: 0.3875,
|
||||
z: 0.9931
|
||||
},
|
||||
dynamic: true,
|
||||
collisionSoundURL: COLLISION_SOUND_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
},
|
||||
dynamic: true,
|
||||
collisionSoundURL: COLLISION_SOUND_URL,
|
||||
userData: JSON.stringify({
|
||||
grabbableKey: {
|
||||
invertSolidWhileHeld: true
|
||||
},
|
||||
wearable:{joints:{RightHand:[{x:0.1177130937576294,
|
||||
y:0.12922893464565277,
|
||||
z:0.08307232707738876},
|
||||
{x:0.4934672713279724,
|
||||
y:0.3605862259864807,
|
||||
z:0.6394805908203125,
|
||||
w:-0.4664038419723511}],
|
||||
LeftHand:[{x:0.09151676297187805,
|
||||
y:0.13639454543590546,
|
||||
z:0.09354984760284424},
|
||||
{x:-0.19628101587295532,
|
||||
y:0.6418180465698242,
|
||||
z:0.2830369472503662,
|
||||
w:0.6851521730422974}]}}
|
||||
})
|
||||
wearable: {
|
||||
joints: {
|
||||
RightHand: [{
|
||||
x: 0.1177130937576294,
|
||||
y: 0.12922893464565277,
|
||||
z: 0.08307232707738876
|
||||
}, {
|
||||
x: 0.4934672713279724,
|
||||
y: 0.3605862259864807,
|
||||
z: 0.6394805908203125,
|
||||
w: -0.4664038419723511
|
||||
}],
|
||||
LeftHand: [{
|
||||
x: 0.09151676297187805,
|
||||
y: 0.13639454543590546,
|
||||
z: 0.09354984760284424
|
||||
}, {
|
||||
x: -0.19628101587295532,
|
||||
y: 0.6418180465698242,
|
||||
z: 0.2830369472503662,
|
||||
w: 0.6851521730422974
|
||||
}]
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
function cleanUp() {
|
||||
Entities.deleteEntity(pingPongGun);
|
||||
Entities.deleteEntity(pingPongGun);
|
||||
}
|
||||
Script.scriptEnding.connect(cleanUp);
|
||||
|
|
|
@ -22,8 +22,8 @@
|
|||
|
||||
//if the trigger value goes below this value, reload the gun.
|
||||
var RELOAD_THRESHOLD = 0.95;
|
||||
var GUN_TIP_FWD_OFFSET =-0.35;
|
||||
var GUN_TIP_UP_OFFSET = 0.040;
|
||||
var GUN_TIP_FWD_OFFSET = -0.35;
|
||||
var GUN_TIP_UP_OFFSET = 0.12;
|
||||
var GUN_FORCE = 9;
|
||||
var BALL_RESTITUTION = 0.6;
|
||||
var BALL_LINEAR_DAMPING = 0.4;
|
||||
|
@ -45,12 +45,12 @@
|
|||
green: 255,
|
||||
blue: 255
|
||||
};
|
||||
|
||||
|
||||
var TRIGGER_CONTROLS = [
|
||||
Controller.Standard.LT,
|
||||
Controller.Standard.RT,
|
||||
];
|
||||
|
||||
|
||||
|
||||
PingPongGun.prototype = {
|
||||
hand: null,
|
||||
|
@ -98,8 +98,8 @@
|
|||
var properties = {
|
||||
// type: 'Model',
|
||||
// modelURL:PING_PONG_BALL_URL,
|
||||
shapeType:'sphere',
|
||||
type:'Sphere',
|
||||
shapeType: 'sphere',
|
||||
type: 'Sphere',
|
||||
color: BALL_COLOR,
|
||||
dimensions: BALL_DIMENSIONS,
|
||||
damping: BALL_LINEAR_DAMPING,
|
||||
|
@ -125,7 +125,7 @@
|
|||
|
||||
Audio.playSound(this.SHOOTING_SOUND, audioProperties);
|
||||
},
|
||||
|
||||
|
||||
getGunTipPosition: function(properties) {
|
||||
//the tip of the gun is going to be in a different place than the center, so we move in space relative to the model to find that position
|
||||
var frontVector = Quat.getFront(properties.rotation);
|
||||
|
@ -148,4 +148,4 @@
|
|||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new PingPongGun();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,29 @@
|
|||
//
|
||||
// fireworksLaunchButtonEntityScript.js
|
||||
//
|
||||
// Created by Eric Levin on 3/7/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This is the chapter 1 entity script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial)
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var _this;
|
||||
Fireworks = function() {
|
||||
_this = this;
|
||||
};
|
||||
|
||||
Fireworks.prototype = {
|
||||
|
||||
preload: function(entityID) {
|
||||
_this.entityID = entityID;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new Fireworks();
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// fireworksLaunchButtonSpawner.js
|
||||
//
|
||||
// Created by Eric Levin on 3/7/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This is the chapter 1 interface script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial)
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
||||
var orientation = Camera.getOrientation();
|
||||
orientation = Quat.safeEulerAngles(orientation);
|
||||
orientation.x = 0;
|
||||
orientation = Quat.fromVec3Degrees(orientation);
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation)));
|
||||
|
||||
var SCRIPT_URL = Script.resolvePath("fireworksLaunchButtonEntityScript.js");
|
||||
var MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Launch-Button.fbx";
|
||||
var launchButton = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "hifi-launch-button",
|
||||
modelURL: MODEL_URL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.98,
|
||||
y: 1.16,
|
||||
z: 0.98
|
||||
},
|
||||
script: SCRIPT_URL,
|
||||
})
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(launchButton);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
|
@ -0,0 +1,92 @@
|
|||
//
|
||||
// fireworksLaunchButtonEntityScript.js
|
||||
//
|
||||
// Created by Eric Levin on 3/7/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This is the chapter 2 entity script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial)
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var _this;
|
||||
Fireworks = function() {
|
||||
_this = this;
|
||||
_this.launchSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/missle+launch.wav");
|
||||
};
|
||||
|
||||
Fireworks.prototype = {
|
||||
|
||||
startNearTrigger: function() {
|
||||
_this.shootFirework(_this.position);
|
||||
},
|
||||
|
||||
startFarTrigger: function() {
|
||||
_this.shootFirework(_this.position);
|
||||
},
|
||||
|
||||
clickReleaseOnEntity: function() {
|
||||
_this.shootFirework(_this.position);
|
||||
},
|
||||
|
||||
|
||||
|
||||
shootFirework: function(launchPosition) {
|
||||
Audio.playSound(_this.launchSound, {
|
||||
position: launchPosition,
|
||||
volume: 0.5
|
||||
});
|
||||
|
||||
|
||||
var smoke = Entities.addEntity({
|
||||
type: "ParticleEffect",
|
||||
position: _this.position,
|
||||
velocity: {x: 0, y: 3, z: 0},
|
||||
lifespan: 10,
|
||||
lifetime: 20,
|
||||
isEmitting: true,
|
||||
name: "Smoke Trail",
|
||||
maxParticles: 3000,
|
||||
emitRate: 80,
|
||||
emitSpeed: 0,
|
||||
speedSpread: 0,
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
azimuthStart: -3.14,
|
||||
azimuthFinish: 3.14,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 0.01,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0.01,
|
||||
y: 0,
|
||||
z: 0.01
|
||||
},
|
||||
radiusSpread: 0.03,
|
||||
particleRadius: 0.3,
|
||||
radiusStart: 0.06,
|
||||
radiusFinish: 0.9,
|
||||
alpha: 0.1,
|
||||
alphaSpread: 0,
|
||||
alphaStart: 0.7,
|
||||
alphaFinish: 0,
|
||||
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
|
||||
emitterShouldTrail: true,
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
_this.entityID = entityID;
|
||||
_this.position = Entities.getEntityProperties(_this.entityID, "position").position;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new Fireworks();
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// fireworksLaunchButtonSpawner.js
|
||||
//
|
||||
// Created by Eric Levin on 3/7/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This is the chapter 2 interface script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial)
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
||||
var orientation = Camera.getOrientation();
|
||||
orientation = Quat.safeEulerAngles(orientation);
|
||||
orientation.x = 0;
|
||||
orientation = Quat.fromVec3Degrees(orientation);
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation)));
|
||||
|
||||
var SCRIPT_URL = Script.resolvePath("fireworksLaunchButtonEntityScript.js");
|
||||
var MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Launch-Button.fbx";
|
||||
var launchButton = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "hifi-launch-button",
|
||||
modelURL: MODEL_URL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.98,
|
||||
y: 1.16,
|
||||
z: 0.98
|
||||
},
|
||||
script: SCRIPT_URL,
|
||||
})
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(launchButton);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
|
@ -0,0 +1,164 @@
|
|||
//
|
||||
// fireworksLaunchButtonEntityScript.js
|
||||
//
|
||||
// Created by Eric Levin on 3/7/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This is the chapter 3 entity script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial)
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
||||
(function() {
|
||||
Script.include("../../libraries/utils.js");
|
||||
var _this;
|
||||
Fireworks = function() {
|
||||
_this = this;
|
||||
_this.launchSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/missle+launch.wav");
|
||||
_this.explosionSound = SoundCache.getSound("https://s3-us-west-1.amazonaws.com/hifi-content/eric/Sounds/fireworksExplosion.wav");
|
||||
_this.TIME_TO_EXPLODE = 3000;
|
||||
};
|
||||
|
||||
Fireworks.prototype = {
|
||||
|
||||
startNearTrigger: function() {
|
||||
_this.shootFirework(_this.position);
|
||||
},
|
||||
|
||||
startFarTrigger: function() {
|
||||
_this.shootFirework(_this.position);
|
||||
},
|
||||
|
||||
clickReleaseOnEntity: function() {
|
||||
_this.shootFirework(_this.position);
|
||||
},
|
||||
|
||||
|
||||
|
||||
shootFirework: function(launchPosition) {
|
||||
Audio.playSound(_this.launchSound, {
|
||||
position: launchPosition,
|
||||
volume: 0.5
|
||||
});
|
||||
|
||||
|
||||
var smoke = Entities.addEntity({
|
||||
type: "ParticleEffect",
|
||||
position: _this.position,
|
||||
velocity: {x: 0, y: 3, z: 0},
|
||||
linearDamping: 0,
|
||||
lifespan: 10,
|
||||
lifetime: 20,
|
||||
isEmitting: true,
|
||||
name: "Smoke Trail",
|
||||
maxParticles: 3000,
|
||||
emitRate: 80,
|
||||
emitSpeed: 0,
|
||||
speedSpread: 0,
|
||||
polarStart: 0,
|
||||
polarFinish: 0,
|
||||
azimuthStart: -3.14,
|
||||
azimuthFinish: 3.14,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: 0.01,
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: 0.01,
|
||||
y: 0,
|
||||
z: 0.01
|
||||
},
|
||||
radiusSpread: 0.03,
|
||||
particleRadius: 0.3,
|
||||
radiusStart: 0.06,
|
||||
radiusFinish: 0.9,
|
||||
alpha: 0.1,
|
||||
alphaSpread: 0,
|
||||
alphaStart: 0.7,
|
||||
alphaFinish: 0,
|
||||
textures: "https://hifi-public.s3.amazonaws.com/alan/Particles/Particle-Sprite-Smoke-1.png",
|
||||
emitterShouldTrail: true,
|
||||
});
|
||||
|
||||
Script.setTimeout(function() {
|
||||
var explodePosition = Entities.getEntityProperties(smoke, "position").position;
|
||||
_this.explodeFirework(explodePosition);
|
||||
}, _this.TIME_TO_EXPLODE);
|
||||
|
||||
},
|
||||
|
||||
explodeFirework: function(explodePosition) {
|
||||
Audio.playSound(_this.explosionSound, {
|
||||
position: explodePosition
|
||||
});
|
||||
var firework = Entities.addEntity({
|
||||
name: "fireworks emitter",
|
||||
position: explodePosition,
|
||||
type: "ParticleEffect",
|
||||
colorStart: hslToRgb({
|
||||
h: Math.random(),
|
||||
s: 0.5,
|
||||
l: 0.7
|
||||
}),
|
||||
color: hslToRgb({
|
||||
h: Math.random(),
|
||||
s: 0.5,
|
||||
l: 0.5
|
||||
}),
|
||||
colorFinish: hslToRgb({
|
||||
h: Math.random(),
|
||||
s: 0.5,
|
||||
l: 0.7
|
||||
}),
|
||||
maxParticles: 10000,
|
||||
lifetime: 20,
|
||||
lifespan: randFloat(1.5, 3),
|
||||
emitRate: randInt(500, 5000),
|
||||
emitSpeed: randFloat(0.5, 2),
|
||||
speedSpread: 0.2,
|
||||
emitOrientation: Quat.fromPitchYawRollDegrees(randInt(0, 360), randInt(0, 360), randInt(0, 360)),
|
||||
polarStart: 1,
|
||||
polarFinish: randFloat(1.2, 3),
|
||||
azimuthStart: -Math.PI,
|
||||
azimuthFinish: Math.PI,
|
||||
emitAcceleration: {
|
||||
x: 0,
|
||||
y: randFloat(-1, -0.2),
|
||||
z: 0
|
||||
},
|
||||
accelerationSpread: {
|
||||
x: Math.random(),
|
||||
y: 0,
|
||||
z: Math.random()
|
||||
},
|
||||
particleRadius: randFloat(0.001, 0.1),
|
||||
radiusSpread: Math.random() * 0.1,
|
||||
radiusStart: randFloat(0.001, 0.1),
|
||||
radiusFinish: randFloat(0.001, 0.1),
|
||||
alpha: randFloat(0.8, 1.0),
|
||||
alphaSpread: randFloat(0.1, 0.2),
|
||||
alphaStart: randFloat(0.7, 1.0),
|
||||
alphaFinish: randFloat(0.7, 1.0),
|
||||
textures: "http://ericrius1.github.io/PlatosCave/assets/star.png",
|
||||
});
|
||||
|
||||
|
||||
Script.setTimeout(function() {
|
||||
Entities.editEntity(firework, {
|
||||
isEmitting: false
|
||||
});
|
||||
}, randInt(500, 1000));
|
||||
|
||||
},
|
||||
|
||||
preload: function(entityID) {
|
||||
_this.entityID = entityID;
|
||||
_this.position = Entities.getEntityProperties(_this.entityID, "position").position;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// entity scripts always need to return a newly constructed object of our type
|
||||
return new Fireworks();
|
||||
});
|
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// fireworksLaunchButtonSpawner.js
|
||||
//
|
||||
// Created by Eric Levin on 3/7/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// This is the chapter 3 interface script of the fireworks tutorial (https://docs.highfidelity.com/docs/fireworks-scripting-tutorial)
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
|
||||
var orientation = Camera.getOrientation();
|
||||
orientation = Quat.safeEulerAngles(orientation);
|
||||
orientation.x = 0;
|
||||
orientation = Quat.fromVec3Degrees(orientation);
|
||||
var center = Vec3.sum(MyAvatar.position, Vec3.multiply(3, Quat.getFront(orientation)));
|
||||
|
||||
var SCRIPT_URL = Script.resolvePath("fireworksLaunchButtonEntityScript.js");
|
||||
var MODEL_URL = "https://s3-us-west-1.amazonaws.com/hifi-content/eric/models/Launch-Button.fbx";
|
||||
var launchButton = Entities.addEntity({
|
||||
type: "Model",
|
||||
name: "hifi-launch-button",
|
||||
modelURL: MODEL_URL,
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 0.98,
|
||||
y: 1.16,
|
||||
z: 0.98
|
||||
},
|
||||
script: SCRIPT_URL,
|
||||
})
|
||||
|
||||
|
||||
function cleanup() {
|
||||
Entities.deleteEntity(launchButton);
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(cleanup);
|
51
examples/utilities/tools/render/debugFramebuffer.js
Normal file
51
examples/utilities/tools/render/debugFramebuffer.js
Normal file
|
@ -0,0 +1,51 @@
|
|||
//
|
||||
// ddebugFramBuffer.js
|
||||
// examples/utilities/tools/render
|
||||
//
|
||||
// Sam Gateau created on 2/18/2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var DDB = Render.RenderDeferredTask.DebugDeferredBuffer;
|
||||
oldConfig = DDB.toJSON();
|
||||
DDB.enabled = true;
|
||||
|
||||
|
||||
// Set up the qml ui
|
||||
var qml = Script.resolvePath('framebuffer.qml');
|
||||
var window = new OverlayWindow({
|
||||
title: 'Framebuffer Debug',
|
||||
source: qml,
|
||||
width: 400, height: 400,
|
||||
});
|
||||
window.setPosition(25, 50);
|
||||
window.closed.connect(function() { Script.stop(); });
|
||||
|
||||
// Debug buffer sizing
|
||||
var resizing = false;
|
||||
|
||||
Controller.mousePressEvent.connect(function (e) {
|
||||
if (shouldStartResizing(e.x)) {
|
||||
resizing = true;
|
||||
}
|
||||
});
|
||||
Controller.mouseReleaseEvent.connect(function() { resizing = false; });
|
||||
Controller.mouseMoveEvent.connect(function (e) { resizing && setDebugBufferSize(e.x); });
|
||||
|
||||
|
||||
function shouldStartResizing(eventX) {
|
||||
var x = Math.abs(eventX - Window.innerWidth * (1.0 + DDB.size.x) / 2.0);
|
||||
var mode = DDB.mode;
|
||||
return mode !== -1 && x < 20;
|
||||
}
|
||||
|
||||
function setDebugBufferSize(x) {
|
||||
x = (2.0 * (x / Window.innerWidth) - 1.0); // scale
|
||||
x = Math.min(Math.max(-1, x), 1); // clamp
|
||||
DDB.size = { x: x, y: -1, z: 1, w: 1 };
|
||||
}
|
||||
|
||||
Script.scriptEnding.connect(function () { DDB.fromJSON(oldConfig); });
|
52
examples/utilities/tools/render/framebuffer.qml
Normal file
52
examples/utilities/tools/render/framebuffer.qml
Normal file
|
@ -0,0 +1,52 @@
|
|||
//
|
||||
// main.qml
|
||||
// examples/utilities/tools/render
|
||||
//
|
||||
// Created by Zach Pomerantz on 2/8/2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or https://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
Column {
|
||||
spacing: 8
|
||||
Column {
|
||||
id: debug
|
||||
property var config: Render.getConfig("DebugDeferredBuffer")
|
||||
|
||||
function setDebugMode(mode) {
|
||||
debug.config.enabled = (mode != -1);
|
||||
debug.config.mode = mode;
|
||||
}
|
||||
|
||||
Label { text: qsTr("Debug Buffer") }
|
||||
ExclusiveGroup { id: bufferGroup }
|
||||
Repeater {
|
||||
model: [
|
||||
"Off",
|
||||
"Depth",
|
||||
"Albedo",
|
||||
"Normal",
|
||||
"Roughness",
|
||||
"Metallic",
|
||||
"Emissive",
|
||||
"Occlusion",
|
||||
"Lightmap",
|
||||
"Lighting",
|
||||
"Shadow",
|
||||
"Pyramid Depth",
|
||||
"Ambient Occlusion",
|
||||
"Custom Shader"
|
||||
]
|
||||
RadioButton {
|
||||
text: qsTr(modelData)
|
||||
exclusiveGroup: bufferGroup
|
||||
checked: index == 0
|
||||
onCheckedChanged: if (checked) debug.setDebugMode(index - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
examples/zones/RainyDayNightZone.json
Normal file
27
examples/zones/RainyDayNightZone.json
Normal file
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"Entities": [
|
||||
{
|
||||
"backgroundMode": "skybox",
|
||||
"dimensions": {
|
||||
"x": 10000,
|
||||
"y": 10000,
|
||||
"z": 10000
|
||||
},
|
||||
"name": "Rainy Day/Night Cycle",
|
||||
"rotation": {
|
||||
"w": 1,
|
||||
"x": 0,
|
||||
"y": 0,
|
||||
"z": 0
|
||||
},
|
||||
"script": "https://s3.amazonaws.com/hifi-public/brad/rainstorm/lightningEntity.js",
|
||||
"shapeType": "box",
|
||||
"skybox": {
|
||||
"url": "https://hifi-public.s3.amazonaws.com/images/SkyboxTextures/CloudyDay1.jpg"
|
||||
},
|
||||
"type": "Zone",
|
||||
"userData":"{\n\"ProceduralEntity\":{\n\"version\":2,\n\"shaderUrl\":\"https://s3.amazonaws.com/hifi-public/brad/rainstorm/rainyDayNightSkybox.fs\",\n\"channels\":[\n\"https://hifi-public.s3.amazonaws.com/austin/assets/images/skybox/starmap_8k.jpg\",\n\"https://hifi-public.s3.amazonaws.com/austin/assets/images/skybox/celestial_grid.jpg\",\n\"https://s3.amazonaws.com/hifi-public/brad/rainstorm/noise.jpg\",\n\"https://s3.amazonaws.com/hifi-public/brad/noise.jpg\"\n],\n\"uniforms\":{\n\"rotationSpeed\":0.001,\n\"uDayColor\":[0.4,0.3,0.3],\n\"constellationLevel\":0.0,\n\"constellationBoundaryLevel\":0.00,\n\"gridLevel\":0.0\n}\n},\n\"lightning\":{\n\"flashMax\":20,\n\"flashMin\":0,\n\"flashIntensityStepRandomeness\":2,\n\"flashMaxRandomness\":10,\n\"averageLightningStrikeGap\":120,\n\"extraRandomRangeLightningStrikeGap\":5,\n\"thunderURL\":\"https://s3.amazonaws.com/hifi-public/brad/rainstorm/thunder-48k.wav\",\n\"thunderVolume\":0.1,\n\"rainURL\":\"https://s3.amazonaws.com/hifi-public/brad/rainstorm/rain.wav\",\n\"rainVolume\":0.05\n}\n}"
|
||||
}
|
||||
],
|
||||
"Version": 57
|
||||
}
|
|
@ -6,3 +6,17 @@ setup_hifi_project(Network)
|
|||
# link the shared hifi libraries
|
||||
link_hifi_libraries(embedded-webserver networking shared)
|
||||
package_libraries_for_deployment()
|
||||
|
||||
# find OpenSSL
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include")
|
||||
# this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto
|
||||
message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings."
|
||||
"\nWe recommend you install a newer version (at least 1.0.1h) in a different directory and set OPENSSL_ROOT_DIR in your env so Cmake can find it.")
|
||||
endif ()
|
||||
|
||||
include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
||||
|
||||
# append OpenSSL to our list of libraries to link
|
||||
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES})
|
||||
|
|
|
@ -9,14 +9,21 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QTimer>
|
||||
#include "IceServer.h"
|
||||
|
||||
#include <openssl/rsa.h>
|
||||
#include <openssl/x509.h>
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
|
||||
#include <LimitedNodeList.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
#include "IceServer.h"
|
||||
|
||||
const int CLEAR_INACTIVE_PEERS_INTERVAL_MSECS = 1 * 1000;
|
||||
const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
||||
|
||||
|
@ -45,7 +52,6 @@ IceServer::IceServer(int argc, char* argv[]) :
|
|||
QTimer* inactivePeerTimer = new QTimer(this);
|
||||
connect(inactivePeerTimer, &QTimer::timeout, this, &IceServer::clearInactivePeers);
|
||||
inactivePeerTimer->start(CLEAR_INACTIVE_PEERS_INTERVAL_MSECS);
|
||||
|
||||
}
|
||||
|
||||
bool IceServer::packetVersionMatch(const udt::Packet& packet) {
|
||||
|
@ -70,9 +76,14 @@ void IceServer::processPacket(std::unique_ptr<udt::Packet> packet) {
|
|||
|
||||
if (nlPacket->getType() == PacketType::ICEServerHeartbeat) {
|
||||
SharedNetworkPeer peer = addOrUpdateHeartbeatingPeer(*nlPacket);
|
||||
|
||||
// so that we can send packets to the heartbeating peer when we need, we need to activate a socket now
|
||||
peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());
|
||||
if (peer) {
|
||||
// so that we can send packets to the heartbeating peer when we need, we need to activate a socket now
|
||||
peer->activateMatchingOrNewSymmetricSocket(nlPacket->getSenderSockAddr());
|
||||
} else {
|
||||
// we couldn't verify this peer - respond back to them so they know they may need to perform keypair re-generation
|
||||
static auto deniedPacket = NLPacket::create(PacketType::ICEServerHeartbeatDenied);
|
||||
_serverSocket.writePacket(*deniedPacket, nlPacket->getSenderSockAddr());
|
||||
}
|
||||
} else if (nlPacket->getType() == PacketType::ICEServerQuery) {
|
||||
QDataStream heartbeatStream(nlPacket.get());
|
||||
|
||||
|
@ -114,31 +125,135 @@ SharedNetworkPeer IceServer::addOrUpdateHeartbeatingPeer(NLPacket& packet) {
|
|||
// pull the UUID, public and private sock addrs for this peer
|
||||
QUuid senderUUID;
|
||||
HifiSockAddr publicSocket, localSocket;
|
||||
QByteArray signature;
|
||||
|
||||
QDataStream heartbeatStream(&packet);
|
||||
|
||||
heartbeatStream >> senderUUID;
|
||||
heartbeatStream >> publicSocket >> localSocket;
|
||||
heartbeatStream >> senderUUID >> publicSocket >> localSocket;
|
||||
|
||||
// make sure we have this sender in our peer hash
|
||||
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
|
||||
auto signedPlaintext = QByteArray::fromRawData(packet.getPayload(), heartbeatStream.device()->pos());
|
||||
heartbeatStream >> signature;
|
||||
|
||||
if (!matchingPeer) {
|
||||
// if we don't have this sender we need to create them now
|
||||
matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);
|
||||
_activePeers.insert(senderUUID, matchingPeer);
|
||||
// make sure this is a verified heartbeat before performing any more processing
|
||||
if (isVerifiedHeartbeat(senderUUID, signedPlaintext, signature)) {
|
||||
// make sure we have this sender in our peer hash
|
||||
SharedNetworkPeer matchingPeer = _activePeers.value(senderUUID);
|
||||
|
||||
qDebug() << "Added a new network peer" << *matchingPeer;
|
||||
if (!matchingPeer) {
|
||||
// if we don't have this sender we need to create them now
|
||||
matchingPeer = QSharedPointer<NetworkPeer>::create(senderUUID, publicSocket, localSocket);
|
||||
_activePeers.insert(senderUUID, matchingPeer);
|
||||
|
||||
qDebug() << "Added a new network peer" << *matchingPeer;
|
||||
} else {
|
||||
// we already had the peer so just potentially update their sockets
|
||||
matchingPeer->setPublicSocket(publicSocket);
|
||||
matchingPeer->setLocalSocket(localSocket);
|
||||
}
|
||||
|
||||
// update our last heard microstamp for this network peer to now
|
||||
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
|
||||
|
||||
return matchingPeer;
|
||||
} else {
|
||||
// we already had the peer so just potentially update their sockets
|
||||
matchingPeer->setPublicSocket(publicSocket);
|
||||
matchingPeer->setLocalSocket(localSocket);
|
||||
// not verified, return the empty peer object
|
||||
return SharedNetworkPeer();
|
||||
}
|
||||
}
|
||||
|
||||
bool IceServer::isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature) {
|
||||
// check if we have a private key for this domain ID - if we do not then fire off the request for it
|
||||
auto it = _domainPublicKeys.find(domainID);
|
||||
if (it != _domainPublicKeys.end()) {
|
||||
|
||||
// attempt to verify the signature for this heartbeat
|
||||
const unsigned char* publicKeyData = reinterpret_cast<const unsigned char*>(it->second.constData());
|
||||
|
||||
// first load up the public key into an RSA struct
|
||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, it->second.size());
|
||||
|
||||
if (rsaPublicKey) {
|
||||
auto hashedPlaintext = QCryptographicHash::hash(plaintext, QCryptographicHash::Sha256);
|
||||
int verificationResult = RSA_verify(NID_sha256,
|
||||
reinterpret_cast<const unsigned char*>(hashedPlaintext.constData()),
|
||||
hashedPlaintext.size(),
|
||||
reinterpret_cast<const unsigned char*>(signature.constData()),
|
||||
signature.size(),
|
||||
rsaPublicKey);
|
||||
|
||||
// free up the public key and remove connection token before we return
|
||||
RSA_free(rsaPublicKey);
|
||||
|
||||
if (verificationResult == 1) {
|
||||
// this is the only success case - we return true here to indicate that the heartbeat is verified
|
||||
return true;
|
||||
} else {
|
||||
qDebug() << "Failed to verify heartbeat for" << domainID << "- re-requesting public key from API.";
|
||||
}
|
||||
|
||||
} else {
|
||||
// we can't let this user in since we couldn't convert their public key to an RSA key we could use
|
||||
qWarning() << "Could not convert in-memory public key for" << domainID << "to usable RSA public key.";
|
||||
qWarning() << "Re-requesting public key from API";
|
||||
}
|
||||
}
|
||||
|
||||
// update our last heard microstamp for this network peer to now
|
||||
matchingPeer->setLastHeardMicrostamp(usecTimestampNow());
|
||||
// we could not verify this heartbeat (missing public key, could not load public key, bad actor)
|
||||
// ask the metaverse API for the right public key and return false to indicate that this is not verified
|
||||
requestDomainPublicKey(domainID);
|
||||
|
||||
return matchingPeer;
|
||||
return false;
|
||||
}
|
||||
|
||||
void IceServer::requestDomainPublicKey(const QUuid& domainID) {
|
||||
// send a request to the metaverse API for the public key for this domain
|
||||
QNetworkAccessManager* manager = new QNetworkAccessManager { this };
|
||||
connect(manager, &QNetworkAccessManager::finished, this, &IceServer::publicKeyReplyFinished);
|
||||
|
||||
QUrl publicKeyURL { NetworkingConstants::METAVERSE_SERVER_URL };
|
||||
QString publicKeyPath = QString("/api/v1/domains/%1/public_key").arg(uuidStringWithoutCurlyBraces(domainID));
|
||||
publicKeyURL.setPath(publicKeyPath);
|
||||
|
||||
QNetworkRequest publicKeyRequest { publicKeyURL };
|
||||
publicKeyRequest.setAttribute(QNetworkRequest::User, domainID);
|
||||
|
||||
qDebug() << "Requesting public key for domain with ID" << domainID;
|
||||
|
||||
manager->get(publicKeyRequest);
|
||||
}
|
||||
|
||||
void IceServer::publicKeyReplyFinished(QNetworkReply* reply) {
|
||||
// get the domain ID from the QNetworkReply attribute
|
||||
QUuid domainID = reply->request().attribute(QNetworkRequest::User).toUuid();
|
||||
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
// pull out the public key and store it for this domain
|
||||
|
||||
// the response should be JSON
|
||||
QJsonDocument responseDocument = QJsonDocument::fromJson(reply->readAll());
|
||||
|
||||
static const QString DATA_KEY = "data";
|
||||
static const QString PUBLIC_KEY_KEY = "public_key";
|
||||
static const QString STATUS_KEY = "status";
|
||||
static const QString SUCCESS_VALUE = "success";
|
||||
|
||||
auto responseObject = responseDocument.object();
|
||||
if (responseObject[STATUS_KEY].toString() == SUCCESS_VALUE) {
|
||||
auto dataObject = responseObject[DATA_KEY].toObject();
|
||||
if (dataObject.contains(PUBLIC_KEY_KEY)) {
|
||||
_domainPublicKeys[domainID] = QByteArray::fromBase64(dataObject[PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
} else {
|
||||
qWarning() << "There was no public key present in response for domain with ID" << domainID;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "The metaverse API did not return success for public key request for domain with ID" << domainID;
|
||||
}
|
||||
|
||||
} else {
|
||||
// there was a problem getting the public key for the domain
|
||||
// log it since it will be re-requested on the next heartbeat
|
||||
|
||||
qWarning() << "Error retreiving public key for domain with ID" << domainID << "-" << reply->errorString();
|
||||
}
|
||||
}
|
||||
|
||||
void IceServer::sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr) {
|
||||
|
|
|
@ -16,13 +16,15 @@
|
|||
#include <QtCore/QSharedPointer>
|
||||
#include <QUdpSocket>
|
||||
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include <NetworkPeer.h>
|
||||
#include <HTTPConnection.h>
|
||||
#include <HTTPManager.h>
|
||||
#include <NLPacket.h>
|
||||
#include <udt/Socket.h>
|
||||
|
||||
typedef QHash<QUuid, SharedNetworkPeer> NetworkPeerHash;
|
||||
class QNetworkReply;
|
||||
|
||||
class IceServer : public QCoreApplication, public HTTPRequestHandler {
|
||||
Q_OBJECT
|
||||
|
@ -31,6 +33,7 @@ public:
|
|||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
private slots:
|
||||
void clearInactivePeers();
|
||||
void publicKeyReplyFinished(QNetworkReply* reply);
|
||||
private:
|
||||
bool packetVersionMatch(const udt::Packet& packet);
|
||||
void processPacket(std::unique_ptr<udt::Packet> packet);
|
||||
|
@ -38,10 +41,19 @@ private:
|
|||
SharedNetworkPeer addOrUpdateHeartbeatingPeer(NLPacket& incomingPacket);
|
||||
void sendPeerInformationPacket(const NetworkPeer& peer, const HifiSockAddr* destinationSockAddr);
|
||||
|
||||
bool isVerifiedHeartbeat(const QUuid& domainID, const QByteArray& plaintext, const QByteArray& signature);
|
||||
void requestDomainPublicKey(const QUuid& domainID);
|
||||
|
||||
QUuid _id;
|
||||
udt::Socket _serverSocket;
|
||||
|
||||
using NetworkPeerHash = QHash<QUuid, SharedNetworkPeer>;
|
||||
NetworkPeerHash _activePeers;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
|
||||
using DomainPublicKeyHash = std::unordered_map<QUuid, QByteArray>;
|
||||
DomainPublicKeyHash _domainPublicKeys;
|
||||
};
|
||||
|
||||
#endif // hifi_IceServer_h
|
||||
|
|
|
@ -268,7 +268,7 @@ if (WIN32)
|
|||
|
||||
set(TARGET_INSTALL_DIR ${INTERFACE_INSTALL_DIR})
|
||||
set(TARGET_INSTALL_COMPONENT ${CLIENT_COMPONENT})
|
||||
manually_install_ssl_eay()
|
||||
manually_install_openssl_for_qt()
|
||||
|
||||
package_libraries_for_deployment()
|
||||
endif()
|
||||
|
|
Binary file not shown.
|
@ -18,7 +18,7 @@ Original.Button {
|
|||
id: button
|
||||
property int color: 0
|
||||
width: 120
|
||||
height: 30
|
||||
height: 28
|
||||
|
||||
style: ButtonStyle {
|
||||
|
||||
|
|
70
interface/resources/qml/controls-uit/CheckBox.qml
Normal file
70
interface/resources/qml/controls-uit/CheckBox.qml
Normal file
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// CheckBox.qml
|
||||
//
|
||||
// Created by David Rowe on 26 Feb 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4 as Original
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
Original.CheckBox {
|
||||
id: checkBox
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
|
||||
readonly property int boxSize: 14
|
||||
readonly property int boxRadius: 3
|
||||
readonly property int checkSize: 10
|
||||
readonly property int checkRadius: 2
|
||||
|
||||
style: CheckBoxStyle {
|
||||
indicator: Rectangle {
|
||||
id: box
|
||||
width: boxSize
|
||||
height: boxSize
|
||||
radius: boxRadius
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.2
|
||||
color: pressed || hovered
|
||||
? (checkBox.isLightColorScheme ? hifi.colors.checkboxDarkStart : hifi.colors.checkboxLightStart)
|
||||
: (checkBox.isLightColorScheme ? hifi.colors.checkboxLightStart : hifi.colors.checkboxDarkStart)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: pressed || hovered
|
||||
? (checkBox.isLightColorScheme ? hifi.colors.checkboxDarkFinish : hifi.colors.checkboxLightFinish)
|
||||
: (checkBox.isLightColorScheme ? hifi.colors.checkboxLightFinish : hifi.colors.checkboxDarkFinish)
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: check
|
||||
width: checkSize
|
||||
height: checkSize
|
||||
radius: checkRadius
|
||||
anchors.centerIn: parent
|
||||
color: hifi.colors.checkboxChecked
|
||||
border.width: 1
|
||||
border.color: hifi.colors.checkboxCheckedBorder
|
||||
visible: checked && !pressed || !checked && pressed
|
||||
}
|
||||
}
|
||||
|
||||
label: Label {
|
||||
text: control.text
|
||||
colorScheme: checkBox.colorScheme
|
||||
x: checkBox.boxSize / 2
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
}
|
||||
}
|
202
interface/resources/qml/controls-uit/ComboBox.qml
Normal file
202
interface/resources/qml/controls-uit/ComboBox.qml
Normal file
|
@ -0,0 +1,202 @@
|
|||
//
|
||||
// ComboBox.qml
|
||||
//
|
||||
// Created by Bradley Austin David on 27 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
import "." as VrControls
|
||||
|
||||
FocusScope {
|
||||
id: root
|
||||
|
||||
property alias model: comboBox.model;
|
||||
property alias comboBox: comboBox
|
||||
readonly property alias currentText: comboBox.currentText;
|
||||
property alias currentIndex: comboBox.currentIndex;
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
property string label: ""
|
||||
property real controlHeight: height + (comboBoxLabel.visible ? comboBoxLabel.height + comboBoxLabel.anchors.bottomMargin : 0)
|
||||
|
||||
readonly property ComboBox control: comboBox
|
||||
|
||||
implicitHeight: comboBox.height;
|
||||
focus: true
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.2
|
||||
color: popup.visible
|
||||
? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark)
|
||||
: (isLightColorScheme ? hifi.colors.dropDownLightStart : hifi.colors.dropDownDarkStart)
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: popup.visible
|
||||
? (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark)
|
||||
: (isLightColorScheme ? hifi.colors.dropDownLightFinish : hifi.colors.dropDownDarkFinish)
|
||||
}
|
||||
}
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
SystemPalette { id: palette }
|
||||
|
||||
ComboBox {
|
||||
id: comboBox
|
||||
anchors.fill: parent
|
||||
visible: false
|
||||
height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control.
|
||||
}
|
||||
|
||||
FiraSansSemiBold {
|
||||
id: textField
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.textPadding
|
||||
right: dropIcon.left
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
text: comboBox.currentText
|
||||
elide: Text.ElideRight
|
||||
color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText )
|
||||
}
|
||||
|
||||
Item {
|
||||
id: dropIcon
|
||||
anchors { right: parent.right; verticalCenter: parent.verticalCenter }
|
||||
height: background.height
|
||||
width: height
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
color: isLightColorScheme ? hifi.colors.faintGray : hifi.colors.baseGray
|
||||
}
|
||||
HiFiGlyphs {
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: -8
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
size: hifi.dimensions.spinnerSize
|
||||
text: hifi.glyphs.caratDn
|
||||
color: controlHover.containsMouse || popup.visible ? hifi.colors.baseGray : (isLightColorScheme ? hifi.colors.lightGray : hifi.colors.lightGrayText)
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: controlHover
|
||||
hoverEnabled: true
|
||||
anchors.fill: parent
|
||||
onClicked: toggleList();
|
||||
}
|
||||
|
||||
function toggleList() {
|
||||
if (popup.visible) {
|
||||
hideList();
|
||||
} else {
|
||||
showList();
|
||||
}
|
||||
}
|
||||
|
||||
function showList() {
|
||||
var r = desktop.mapFromItem(root, 0, 0, root.width, root.height);
|
||||
listView.currentIndex = root.currentIndex
|
||||
scrollView.x = r.x;
|
||||
scrollView.y = r.y + r.height;
|
||||
var bottom = scrollView.y + scrollView.height;
|
||||
if (bottom > desktop.height) {
|
||||
scrollView.y -= bottom - desktop.height + 8;
|
||||
}
|
||||
popup.visible = true;
|
||||
popup.forceActiveFocus();
|
||||
}
|
||||
|
||||
function hideList() {
|
||||
popup.visible = false;
|
||||
}
|
||||
|
||||
FocusScope {
|
||||
id: popup
|
||||
parent: desktop
|
||||
anchors.fill: parent
|
||||
z: desktop.zLevels.menu
|
||||
visible: false
|
||||
focus: true
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: hideList();
|
||||
}
|
||||
|
||||
function previousItem() { listView.currentIndex = (listView.currentIndex + listView.count - 1) % listView.count; }
|
||||
function nextItem() { listView.currentIndex = (listView.currentIndex + listView.count + 1) % listView.count; }
|
||||
function selectCurrentItem() { root.currentIndex = listView.currentIndex; hideList(); }
|
||||
function selectSpecificItem(index) { root.currentIndex = index; hideList(); }
|
||||
|
||||
Keys.onUpPressed: previousItem();
|
||||
Keys.onDownPressed: nextItem();
|
||||
Keys.onSpacePressed: selectCurrentItem();
|
||||
Keys.onRightPressed: selectCurrentItem();
|
||||
Keys.onReturnPressed: selectCurrentItem();
|
||||
Keys.onEscapePressed: hideList();
|
||||
|
||||
ScrollView {
|
||||
id: scrollView
|
||||
height: 480
|
||||
|
||||
ListView {
|
||||
id: listView
|
||||
height: textField.height * count * 1.4
|
||||
model: root.model
|
||||
delegate: Rectangle {
|
||||
width: root.width + 4
|
||||
height: popupText.implicitHeight * 1.4
|
||||
color: popupHover.containsMouse ? hifi.colors.primaryHighlight : (isLightColorScheme ? hifi.colors.dropDownPressedLight : hifi.colors.dropDownPressedDark)
|
||||
FiraSansSemiBold {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: hifi.dimensions.textPadding
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
id: popupText
|
||||
text: listView.model[index]
|
||||
size: hifi.fontSizes.textFieldInput
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
MouseArea {
|
||||
id: popupHover
|
||||
anchors.fill: parent;
|
||||
hoverEnabled: true
|
||||
onEntered: listView.currentIndex = index;
|
||||
onClicked: popup.selectSpecificItem(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Label {
|
||||
id: comboBoxLabel
|
||||
text: root.label
|
||||
colorScheme: root.colorScheme
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 4
|
||||
visible: label != ""
|
||||
}
|
||||
}
|
133
interface/resources/qml/controls-uit/ContentSection.qml
Normal file
133
interface/resources/qml/controls-uit/ContentSection.qml
Normal file
|
@ -0,0 +1,133 @@
|
|||
//
|
||||
// ContentSection.qml
|
||||
//
|
||||
// Created by David Rowe on 16 Feb 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtGraphicalEffects 1.0
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
Column {
|
||||
property string name: "Static Section"
|
||||
property bool isFirst: false
|
||||
property bool isCollapsible: false // Set at creation.
|
||||
property bool isCollapsed: false
|
||||
|
||||
spacing: 0 // Defer spacing decisions to individual controls.
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.contentMargin.x
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.contentMargin.x
|
||||
}
|
||||
|
||||
function toggleCollapsed() {
|
||||
if (isCollapsible) {
|
||||
isCollapsed = !isCollapsed;
|
||||
for (var i = 1; i < children.length; i++) {
|
||||
children[i].visible = !isCollapsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: sectionName
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: leadingSpace.height + topBar.height + heading.height + bottomBar.height
|
||||
|
||||
Item {
|
||||
id: leadingSpace
|
||||
width: 1
|
||||
height: isFirst ? hifi.dimensions.contentSpacing.y : hifi.dimensions.controlInterlineHeight
|
||||
anchors.top: parent.top
|
||||
}
|
||||
|
||||
Item {
|
||||
id: topBar
|
||||
visible: !isFirst
|
||||
height: visible ? 2 : 0
|
||||
anchors.top: leadingSpace.bottom
|
||||
|
||||
Rectangle {
|
||||
id: shadow
|
||||
width: frame.width
|
||||
height: 1
|
||||
color: hifi.colors.baseGrayShadow
|
||||
x: -hifi.dimensions.contentMargin.x
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: frame.width
|
||||
height: 1
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
x: -hifi.dimensions.contentMargin.x
|
||||
anchors.top: shadow.bottom
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: heading
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: topBar.bottom
|
||||
}
|
||||
height: (isCollapsible ? 3 : 2) * hifi.dimensions.contentSpacing.y
|
||||
|
||||
RalewayRegular {
|
||||
id: title
|
||||
anchors {
|
||||
left: parent.left
|
||||
top: parent.top
|
||||
topMargin: hifi.dimensions.contentSpacing.y
|
||||
}
|
||||
size: hifi.fontSizes.sectionName
|
||||
font.capitalization: Font.AllUppercase
|
||||
text: name
|
||||
color: hifi.colors.lightGrayText
|
||||
}
|
||||
|
||||
HiFiGlyphs {
|
||||
anchors {
|
||||
verticalCenter: title.verticalCenter
|
||||
right: parent.right
|
||||
rightMargin: -4
|
||||
}
|
||||
y: -2
|
||||
size: hifi.fontSizes.disclosureButton
|
||||
text: isCollapsed ? hifi.glyphs.disclosureButtonExpand : hifi.glyphs.disclosureButtonCollapse
|
||||
color: hifi.colors.lightGrayText
|
||||
visible: isCollapsible
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: toggleCollapsed()
|
||||
}
|
||||
}
|
||||
|
||||
LinearGradient {
|
||||
id: bottomBar
|
||||
visible: isCollapsible
|
||||
width: frame.width
|
||||
height: visible ? 4 : 0
|
||||
x: -hifi.dimensions.contentMargin.x
|
||||
anchors.top: heading.bottom
|
||||
start: Qt.point(0, 0)
|
||||
end: Qt.point(0, 4)
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: hifi.colors.darkGray }
|
||||
GradientStop { position: 1.0; color: hifi.colors.baseGray } // Equivalent of darkGray0 over baseGray background.
|
||||
}
|
||||
cached: true
|
||||
}
|
||||
}
|
||||
}
|
20
interface/resources/qml/controls-uit/Label.qml
Normal file
20
interface/resources/qml/controls-uit/Label.qml
Normal file
|
@ -0,0 +1,20 @@
|
|||
//
|
||||
// Label.qml
|
||||
//
|
||||
// Created by David Rowe on 26 Feb 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
RalewaySemibold {
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
|
||||
size: hifi.fontSizes.inputLabel
|
||||
color: colorScheme == hifi.colorSchemes.light ? hifi.colors.lightGray : hifi.colors.lightGrayText
|
||||
}
|
98
interface/resources/qml/controls-uit/Slider.qml
Normal file
98
interface/resources/qml/controls-uit/Slider.qml
Normal file
|
@ -0,0 +1,98 @@
|
|||
//
|
||||
// Slider.qml
|
||||
//
|
||||
// Created by David Rowe on 27 Feb 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
|
||||
Slider {
|
||||
id: slider
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
property string label: ""
|
||||
property real controlHeight: height + (sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0)
|
||||
|
||||
height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control.
|
||||
y: sliderLabel.visible ? sliderLabel.height + sliderLabel.anchors.bottomMargin : 0
|
||||
|
||||
style: SliderStyle {
|
||||
|
||||
groove: Rectangle {
|
||||
implicitWidth: 50
|
||||
implicitHeight: hifi.dimensions.sliderGrooveHeight
|
||||
radius: height / 2
|
||||
color: isLightColorScheme ? hifi.colors.sliderGutterLight : hifi.colors.sliderGutterDark
|
||||
|
||||
Rectangle {
|
||||
width: parent.height - 2
|
||||
height: slider.value * slider.width - 1
|
||||
radius: height / 2
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: width + 1
|
||||
left: parent.left
|
||||
leftMargin: 1
|
||||
}
|
||||
transformOrigin: Item.TopLeft
|
||||
rotation: -90
|
||||
gradient: Gradient {
|
||||
GradientStop { position: 0.0; color: hifi.colors.blueAccent }
|
||||
GradientStop { position: 1.0; color: hifi.colors.primaryHighlight }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
handle: Rectangle {
|
||||
implicitWidth: hifi.dimensions.sliderHandleSize
|
||||
implicitHeight: hifi.dimensions.sliderHandleSize
|
||||
radius: height / 2
|
||||
border.width: 1
|
||||
border.color: isLightColorScheme ? hifi.colors.sliderBorderLight : hifi.colors.sliderBorderDark
|
||||
gradient: Gradient {
|
||||
GradientStop {
|
||||
position: 0.0
|
||||
color: pressed || hovered
|
||||
? (isLightColorScheme ? hifi.colors.sliderDarkStart : hifi.colors.sliderLightStart )
|
||||
: (isLightColorScheme ? hifi.colors.sliderLightStart : hifi.colors.sliderDarkStart )
|
||||
}
|
||||
GradientStop {
|
||||
position: 1.0
|
||||
color: pressed || hovered
|
||||
? (isLightColorScheme ? hifi.colors.sliderDarkFinish : hifi.colors.sliderLightFinish )
|
||||
: (isLightColorScheme ? hifi.colors.sliderLightFinish : hifi.colors.sliderDarkFinish )
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
height: parent.height - 2
|
||||
width: height
|
||||
radius: height / 2
|
||||
anchors.centerIn: parent
|
||||
color: hifi.colors.transparent
|
||||
border.width: 1
|
||||
border.color: hifi.colors.black
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Label {
|
||||
id: sliderLabel
|
||||
text: slider.label
|
||||
colorScheme: slider.colorScheme
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 2
|
||||
visible: label != ""
|
||||
}
|
||||
}
|
79
interface/resources/qml/controls-uit/SpinBox.qml
Normal file
79
interface/resources/qml/controls-uit/SpinBox.qml
Normal file
|
@ -0,0 +1,79 @@
|
|||
//
|
||||
// SpinBox.qml
|
||||
//
|
||||
// Created by David Rowe on 26 Feb 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
|
||||
SpinBox {
|
||||
id: spinBox
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
property string label: ""
|
||||
property real controlHeight: height + (spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0)
|
||||
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
font.family: firaSansSemiBold.name
|
||||
font.pixelSize: hifi.fontSizes.textFieldInput
|
||||
height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control.
|
||||
|
||||
y: spinBoxLabel.visible ? spinBoxLabel.height + spinBoxLabel.anchors.bottomMargin : 0
|
||||
|
||||
style: SpinBoxStyle {
|
||||
background: Rectangle {
|
||||
color: isLightColorScheme
|
||||
? (spinBox.focus ? hifi.colors.white : hifi.colors.lightGray)
|
||||
: (spinBox.focus ? hifi.colors.black : hifi.colors.baseGrayShadow)
|
||||
border.color: hifi.colors.primaryHighlight
|
||||
border.width: spinBox.focus ? 1 : 0
|
||||
}
|
||||
|
||||
textColor: isLightColorScheme
|
||||
? (spinBox.focus ? hifi.colors.black : hifi.colors.lightGray)
|
||||
: (spinBox.focus ? hifi.colors.white : hifi.colors.lightGrayText)
|
||||
selectedTextColor: hifi.colors.black
|
||||
selectionColor: hifi.colors.primaryHighlight
|
||||
|
||||
horizontalAlignment: Qt.AlignLeft
|
||||
padding.left: hifi.dimensions.textPadding
|
||||
padding.right: hifi.dimensions.spinnerSize
|
||||
|
||||
incrementControl: HiFiGlyphs {
|
||||
id: incrementButton
|
||||
text: hifi.glyphs.caratUp
|
||||
x: 6
|
||||
y: 2
|
||||
size: hifi.dimensions.spinnerSize
|
||||
color: styleData.upPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
|
||||
}
|
||||
|
||||
decrementControl: HiFiGlyphs {
|
||||
text: hifi.glyphs.caratDn
|
||||
x: 6
|
||||
y: -3
|
||||
size: hifi.dimensions.spinnerSize
|
||||
color: styleData.downPressed ? (isLightColorScheme ? hifi.colors.black : hifi.colors.white) : hifi.colors.gray
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.Label {
|
||||
id: spinBoxLabel
|
||||
text: spinBox.label
|
||||
colorScheme: spinBox.colorScheme
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 4
|
||||
visible: label != ""
|
||||
}
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
//
|
||||
// StaticSection.qml
|
||||
//
|
||||
// Created by David Rowe on 16 Feb 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import "../styles-uit"
|
||||
|
||||
Column {
|
||||
property string name: "Static Section"
|
||||
property bool hasSeparator: false
|
||||
|
||||
spacing: hifi.dimensions.contentSpacing.y
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
leftMargin: hifi.dimensions.contentMargin.x
|
||||
right: parent.right
|
||||
rightMargin: hifi.dimensions.contentMargin.x
|
||||
}
|
||||
|
||||
VerticalSpacer { }
|
||||
|
||||
Item {
|
||||
visible: hasSeparator
|
||||
anchors.top: sectionName.top
|
||||
|
||||
Rectangle {
|
||||
width: frame.width
|
||||
height: 1
|
||||
color: hifi.colors.baseGrayShadow
|
||||
x: -hifi.dimensions.contentMargin.x
|
||||
anchors.bottom: highlight.top
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: highlight
|
||||
width: frame.width
|
||||
height: 1
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
x: -hifi.dimensions.contentMargin.x
|
||||
anchors.bottom: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
RalewayRegular {
|
||||
id: sectionName
|
||||
text: parent.name
|
||||
size: hifi.fontSizes.sectionName
|
||||
font.capitalization: Font.AllUppercase
|
||||
color: hifi.colors.lightGrayText
|
||||
verticalAlignment: Text.AlignBottom
|
||||
height: {
|
||||
if (hasSeparator) {
|
||||
hifi.dimensions.contentMargin.y
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -133,13 +133,14 @@ TableView {
|
|||
HiFiGlyphs {
|
||||
id: reloadButton
|
||||
text: hifi.glyphs.reloadSmall
|
||||
color: parent.color
|
||||
color: reloadButtonArea.pressed ? hifi.colors.white : parent.color
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: stopButton.left
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: reloadButtonArea
|
||||
anchors { fill: parent; margins: -2 }
|
||||
onClicked: reloadScript(model.url)
|
||||
}
|
||||
|
@ -149,13 +150,14 @@ TableView {
|
|||
HiFiGlyphs {
|
||||
id: stopButton
|
||||
text: hifi.glyphs.closeSmall
|
||||
color: parent.color
|
||||
color: stopButtonArea.pressed ? hifi.colors.white : parent.color
|
||||
anchors {
|
||||
top: parent.top
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
MouseArea {
|
||||
id: stopButtonArea
|
||||
anchors { fill: parent; margins: -2 }
|
||||
onClicked: stopScript(model.url)
|
||||
}
|
||||
|
|
|
@ -13,25 +13,31 @@ import QtQuick.Controls 1.4
|
|||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../styles-uit"
|
||||
import "../controls-uit" as HifiControls
|
||||
|
||||
TextField {
|
||||
id: textField
|
||||
|
||||
property int colorScheme: hifi.colorSchemes.light
|
||||
readonly property bool isLightColorScheme: colorScheme == hifi.colorSchemes.light
|
||||
property string label: ""
|
||||
property real controlHeight: height + (textFieldLabel.visible ? textFieldLabel.height : 0)
|
||||
|
||||
placeholderText: textField.placeholderText
|
||||
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
font.family: firaSansSemiBold.name
|
||||
font.pixelSize: hifi.fontSizes.textFieldInput
|
||||
height: implicitHeight + 4 // Make surrounding box higher so that highlight is vertically centered.
|
||||
placeholderText: textField.label // Instead of separate label (see below).
|
||||
|
||||
y: textFieldLabel.visible ? textFieldLabel.height + textFieldLabel.anchors.bottomMargin : 0
|
||||
|
||||
style: TextFieldStyle {
|
||||
textColor: textField.colorScheme == hifi.colorSchemes.light
|
||||
textColor: isLightColorScheme
|
||||
? (textField.focus ? hifi.colors.black : hifi.colors.lightGray)
|
||||
: (textField.focus ? hifi.colors.white : hifi.colors.lightGrayText)
|
||||
background: Rectangle {
|
||||
color: textField.colorScheme == hifi.colorSchemes.light
|
||||
color: isLightColorScheme
|
||||
? (textField.focus ? hifi.colors.white : hifi.colors.lightGray)
|
||||
: (textField.focus ? hifi.colors.black : hifi.colors.baseGrayShadow)
|
||||
border.color: hifi.colors.primaryHighlight
|
||||
|
@ -44,16 +50,13 @@ TextField {
|
|||
padding.right: hifi.dimensions.textPadding
|
||||
}
|
||||
|
||||
/*
|
||||
// Separate label instead of placeholderText.
|
||||
RalewaySemibold {
|
||||
HifiControls.Label {
|
||||
id: textFieldLabel
|
||||
text: textField.label
|
||||
size: hifi.fontSizes.inputLabel
|
||||
color: hifi.colors.lightGrayText
|
||||
colorScheme: textField.colorScheme
|
||||
anchors.left: parent.left
|
||||
anchors.bottom: parent.top
|
||||
anchors.bottomMargin: 4
|
||||
visible: label != ""
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -55,11 +55,11 @@ TreeView {
|
|||
alternateBackgroundColor: parent.isLightColorScheme ? hifi.colors.tableRowLightOdd : hifi.colors.tableRowDarkOdd
|
||||
|
||||
branchDelegate: HiFiGlyphs {
|
||||
text: styleData.isExpanded ? hifi.glyphs.disclosureCollapse : hifi.glyphs.disclosureExpand
|
||||
size: hifi.fontSizes.tableText * 2.5 // tableText is in points; proportionately scale to pixels
|
||||
text: styleData.isExpanded ? hifi.glyphs.caratDn : hifi.glyphs.caratR
|
||||
size: hifi.fontSizes.carat
|
||||
color: colorScheme == hifi.colorSchemes.light
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
? (styleData.selected ? hifi.colors.black : hifi.colors.baseGrayHighlight)
|
||||
: (styleData.selected ? hifi.colors.black : hifi.colors.lightGrayText)
|
||||
anchors {
|
||||
left: parent ? parent.left : undefined
|
||||
leftMargin: hifi.dimensions.tablePadding / 2
|
||||
|
|
|
@ -14,5 +14,5 @@ import "../styles-uit"
|
|||
|
||||
Item {
|
||||
width: 1 // Must be non-zero
|
||||
height: hifi.dimensions.contentSpacing.y
|
||||
height: hifi.dimensions.controlInterlineHeight
|
||||
}
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
//
|
||||
// PreferencesDialog.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 24 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Dialogs 1.2 as OriginalDialogs
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
import "../controls" as HifiControls
|
||||
import "../windows"
|
||||
import "../controls-uit" as HifiControls
|
||||
import "../styles-uit"
|
||||
import "../windows-uit"
|
||||
import "preferences"
|
||||
|
||||
Window {
|
||||
|
@ -16,6 +25,9 @@ Window {
|
|||
height: 577
|
||||
property var sections: []
|
||||
property var showCategories: []
|
||||
minSize: Qt.vector2d(400, 500)
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
function saveAll() {
|
||||
for (var i = 0; i < sections.length; ++i) {
|
||||
|
@ -33,10 +45,8 @@ Window {
|
|||
destroy();
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
color: "white"
|
||||
Column {
|
||||
width: pane.contentWidth
|
||||
|
||||
Component {
|
||||
id: sectionBuilder
|
||||
|
@ -64,45 +74,45 @@ Window {
|
|||
}
|
||||
|
||||
if (sections.length) {
|
||||
sections[0].expanded = true;
|
||||
// Default sections to expanded/collapsed as appropriate for dialog.
|
||||
if (sections.length === 1) {
|
||||
sections[0].collapsable = false
|
||||
sections[0].expanded = true
|
||||
} else {
|
||||
for (i = 0; i < sections.length; i++) {
|
||||
sections[i].collapsable = true;
|
||||
sections[i].expanded = true;
|
||||
}
|
||||
}
|
||||
sections[0].isFirst = true;
|
||||
sections[sections.length - 1].isLast = true;
|
||||
}
|
||||
}
|
||||
|
||||
Flickable {
|
||||
id: flickable
|
||||
clip: true
|
||||
interactive: true
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: dialogButtons.top
|
||||
anchors.bottomMargin: 8
|
||||
contentHeight: prefControls.height
|
||||
contentWidth: parent.width
|
||||
|
||||
Column {
|
||||
id: prefControls
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
Column {
|
||||
id: prefControls
|
||||
width: pane.contentWidth
|
||||
}
|
||||
Row {
|
||||
id: dialogButtons
|
||||
anchors { bottom: parent.bottom; right: parent.right; margins: 8 }
|
||||
}
|
||||
|
||||
Button {
|
||||
text: "Cancel";
|
||||
onClicked: root.restoreAll();
|
||||
}
|
||||
footer: Row {
|
||||
anchors {
|
||||
right: parent.right;
|
||||
rightMargin: hifi.dimensions.contentMargin.x
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
|
||||
Button {
|
||||
text: "Save all changes"
|
||||
onClicked: root.saveAll();
|
||||
}
|
||||
HifiControls.Button {
|
||||
text: "Save changes"
|
||||
color: hifi.buttons.blue
|
||||
onClicked: root.saveAll()
|
||||
}
|
||||
|
||||
HifiControls.Button {
|
||||
text: "Cancel"
|
||||
color: hifi.buttons.white
|
||||
onClicked: root.restoreAll()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
//
|
||||
// AvatarPreference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 22 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../../dialogs"
|
||||
import "../../controls-uit"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property alias buttonText: button.text
|
||||
property alias text: dataTextField.text
|
||||
property alias buttonText: button.text
|
||||
property alias placeholderText: dataTextField.placeholderText
|
||||
property real spacing: 8
|
||||
property var browser;
|
||||
height: labelText.height + Math.max(dataTextField.height, button.height) + spacing
|
||||
height: control.height + hifi.dimensions.controlInterlineHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
dataTextField.text = preference.value;
|
||||
|
@ -41,40 +51,48 @@ Preference {
|
|||
preference.save();
|
||||
}
|
||||
|
||||
Text {
|
||||
id: labelText
|
||||
color: enabled ? "black" : "gray"
|
||||
text: root.label
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: dataTextField
|
||||
placeholderText: root.placeholderText
|
||||
text: preference.value
|
||||
style: TextFieldStyle { renderType: Text.QtRendering }
|
||||
Item {
|
||||
id: control
|
||||
anchors {
|
||||
top: labelText.bottom
|
||||
left: parent.left
|
||||
right: button.left
|
||||
topMargin: root.spacing
|
||||
rightMargin: root.spacing
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
height: Math.max(dataTextField.controlHeight, button.height)
|
||||
|
||||
Component {
|
||||
id: avatarBrowserBuilder;
|
||||
AvatarBrowser { }
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
anchors { right: parent.right; verticalCenter: dataTextField.verticalCenter }
|
||||
text: "Browse"
|
||||
onClicked: {
|
||||
root.browser = avatarBrowserBuilder.createObject(desktop);
|
||||
root.browser.windowDestroyed.connect(function(){
|
||||
root.browser = null;
|
||||
})
|
||||
TextField {
|
||||
id: dataTextField
|
||||
placeholderText: root.placeholderText
|
||||
text: preference.value
|
||||
label: root.label
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: button.left
|
||||
rightMargin: hifi.dimensions.contentSpacing.x
|
||||
bottom: parent.bottom
|
||||
}
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
}
|
||||
|
||||
Component {
|
||||
id: avatarBrowserBuilder;
|
||||
AvatarBrowser { }
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
text: "Browse"
|
||||
anchors {
|
||||
right: parent.right
|
||||
verticalCenter: dataTextField.verticalCenter
|
||||
}
|
||||
onClicked: {
|
||||
root.browser = avatarBrowserBuilder.createObject(desktop);
|
||||
root.browser.windowDestroyed.connect(function(){
|
||||
root.browser = null;
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,23 @@
|
|||
//
|
||||
// BrowsablePreference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../../dialogs"
|
||||
import "../../controls-uit"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property alias buttonText: button.text
|
||||
property alias text: dataTextField.text
|
||||
property alias placeholderText: dataTextField.placeholderText
|
||||
property real spacing: 8
|
||||
height: labelText.height + Math.max(dataTextField.height, button.height) + spacing
|
||||
height: control.height + hifi.dimensions.controlInterlineHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
dataTextField.text = preference.value;
|
||||
|
@ -21,42 +28,49 @@ Preference {
|
|||
preference.save();
|
||||
}
|
||||
|
||||
Text {
|
||||
id: labelText
|
||||
color: enabled ? "black" : "gray"
|
||||
text: root.label
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: dataTextField
|
||||
placeholderText: root.placeholderText
|
||||
text: preference.value
|
||||
style: TextFieldStyle { renderType: Text.QtRendering }
|
||||
Item {
|
||||
id: control
|
||||
anchors {
|
||||
top: labelText.bottom
|
||||
left: parent.left
|
||||
right: button.left
|
||||
topMargin: root.spacing
|
||||
rightMargin: root.spacing
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
height: Math.max(dataTextField.controlHeight, button.height)
|
||||
|
||||
Component {
|
||||
id: fileBrowserBuilder;
|
||||
FileDialog { selectDirectory: true }
|
||||
}
|
||||
TextField {
|
||||
id: dataTextField
|
||||
|
||||
Button {
|
||||
id: button
|
||||
anchors { right: parent.right; verticalCenter: dataTextField.verticalCenter }
|
||||
text: preference.browseLabel
|
||||
onClicked: {
|
||||
var browser = fileBrowserBuilder.createObject(desktop, { selectDirectory: true, folder: fileDialogHelper.pathToUrl(preference.value) });
|
||||
browser.selectedFile.connect(function(fileUrl){
|
||||
console.log(fileUrl);
|
||||
dataTextField.text = fileDialogHelper.urlToPath(fileUrl);
|
||||
});
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: button.left
|
||||
rightMargin: hifi.dimensions.contentSpacing.x
|
||||
bottom: parent.bottom
|
||||
}
|
||||
|
||||
label: root.label
|
||||
placeholderText: root.placeholderText
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileBrowserBuilder;
|
||||
FileDialog { selectDirectory: true }
|
||||
}
|
||||
|
||||
Button {
|
||||
id: button
|
||||
text: preference.browseLabel
|
||||
anchors {
|
||||
right: parent.right
|
||||
verticalCenter: dataTextField.verticalCenter
|
||||
}
|
||||
onClicked: {
|
||||
var browser = fileBrowserBuilder.createObject(desktop, { selectDirectory: true, folder: fileDialogHelper.pathToUrl(preference.value) });
|
||||
browser.selectedFile.connect(function(fileUrl){
|
||||
console.log(fileUrl);
|
||||
dataTextField.text = fileDialogHelper.urlToPath(fileUrl);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,29 @@
|
|||
//
|
||||
// ButtonPreference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4 as Original
|
||||
|
||||
import "../../controls-uit"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
height: button.height
|
||||
height: button.height + hifi.dimensions.controlInterlineHeight
|
||||
|
||||
Component.onCompleted: button.text = preference.name;
|
||||
|
||||
function save() { }
|
||||
|
||||
Original.Button {
|
||||
Button {
|
||||
id: button
|
||||
onClicked: preference.trigger()
|
||||
width: 180
|
||||
anchors.bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,20 @@
|
|||
//
|
||||
// CheckBoxPreference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import "../../controls"
|
||||
|
||||
import "../../controls-uit"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
height: checkBox.implicitHeight
|
||||
height: spacer.height + Math.max(hifi.dimensions.controlLineHeight, checkBox.implicitHeight)
|
||||
|
||||
Component.onCompleted: {
|
||||
checkBox.checked = preference.value;
|
||||
|
@ -16,9 +26,25 @@ Preference {
|
|||
preference.save();
|
||||
}
|
||||
|
||||
Item {
|
||||
id: spacer
|
||||
anchors {
|
||||
top: parent.top
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
}
|
||||
height: isFirstCheckBox ? hifi.dimensions.controlInterlineHeight : 0
|
||||
}
|
||||
|
||||
CheckBox {
|
||||
id: checkBox
|
||||
anchors.fill: parent
|
||||
anchors {
|
||||
top: spacer.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
text: root.label
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
//
|
||||
// ComboBoxPreference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
import "../../controls-uit" as HiFiControls
|
||||
import "../../styles-uit"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property real spacing: 8
|
||||
height: labelText.height + dataComboBox.height + spacing
|
||||
height: control.height + hifi.dimensions.controlInterlineHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
dataComboBox.currentIndex = dataComboBox.find(preference.value);
|
||||
dataComboBox.currentIndex = dataComboBox.comboBox.find(preference.value);
|
||||
}
|
||||
|
||||
function save() {
|
||||
|
@ -16,22 +28,38 @@ Preference {
|
|||
preference.save();
|
||||
}
|
||||
|
||||
Text {
|
||||
id: labelText
|
||||
color: enabled ? "black" : "gray"
|
||||
text: root.label
|
||||
}
|
||||
|
||||
ComboBox {
|
||||
id: dataComboBox
|
||||
model: preference.items
|
||||
style: ComboBoxStyle { renderType: Text.QtRendering }
|
||||
Item {
|
||||
id: control
|
||||
anchors {
|
||||
top: labelText.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: root.spacing
|
||||
rightMargin: root.spacing
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: Math.max(labelText.height, dataComboBox.controlHeight)
|
||||
|
||||
HiFiControls.Label {
|
||||
id: labelText
|
||||
text: root.label + ":"
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: dataComboBox.left
|
||||
rightMargin: hifi.dimensions.labelPadding
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
horizontalAlignment: Text.AlignRight
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HiFiControls.ComboBox {
|
||||
id: dataComboBox
|
||||
model: preference.items
|
||||
width: 150
|
||||
anchors {
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,11 +1,21 @@
|
|||
//
|
||||
// EditablePreference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Controls.Styles 1.4
|
||||
|
||||
import "../../dialogs"
|
||||
import "../../controls-uit"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property real spacing: 8
|
||||
height: labelText.height + dataTextField.height + spacing
|
||||
height: dataTextField.controlHeight + hifi.dimensions.controlInterlineHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
dataTextField.text = preference.value;
|
||||
|
@ -16,22 +26,16 @@ Preference {
|
|||
preference.save();
|
||||
}
|
||||
|
||||
Text {
|
||||
id: labelText
|
||||
color: enabled ? "black" : "gray"
|
||||
text: root.label
|
||||
}
|
||||
|
||||
TextField {
|
||||
id: dataTextField
|
||||
placeholderText: preference.placeholderText
|
||||
style: TextFieldStyle { renderType: Text.QtRendering }
|
||||
label: root.label
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
|
||||
anchors {
|
||||
top: labelText.bottom
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
topMargin: root.spacing
|
||||
rightMargin: root.spacing
|
||||
bottom: parent.bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
//
|
||||
// Preference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
|
@ -6,6 +16,7 @@ Item {
|
|||
anchors { left: parent.left; right: parent.right }
|
||||
property var preference;
|
||||
property string label: preference ? preference.name : "";
|
||||
property bool isFirstCheckBox;
|
||||
Component.onCompleted: {
|
||||
if (preference) {
|
||||
preference.load();
|
||||
|
|
|
@ -1,20 +1,33 @@
|
|||
//
|
||||
// Section.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import Hifi 1.0
|
||||
|
||||
import "../../controls" as VrControls
|
||||
import "../../controls-uit" as HiFiControls
|
||||
import "../../styles-uit"
|
||||
import "."
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property bool collapsable: true
|
||||
property bool expanded: false
|
||||
property bool isFirst: false
|
||||
property bool isLast: false
|
||||
property string name: "Header"
|
||||
property real spacing: 8
|
||||
readonly property alias toggle: toggle
|
||||
readonly property alias header: header
|
||||
default property alias preferences: contentContainer.children
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
function saveAll() {
|
||||
for (var i = 0; i < d.preferences.length; ++i) {
|
||||
var preference = d.preferences[i];
|
||||
|
@ -29,47 +42,24 @@ Preference {
|
|||
}
|
||||
}
|
||||
|
||||
clip: true
|
||||
children: [ toggle, header, contentContainer ]
|
||||
height: expanded ? header.height + contentContainer.height + root.spacing * 3
|
||||
: Math.max(toggle.height, header.height) + root.spacing * 2
|
||||
Behavior on height { PropertyAnimation {} }
|
||||
children: [ contentContainer ]
|
||||
|
||||
height: contentContainer.height + (root.isLast ? 2 * hifi.dimensions.contentSpacing.y : 0)
|
||||
|
||||
Component.onCompleted: d.buildPreferences();
|
||||
|
||||
function toggleExpanded() {
|
||||
root.expanded = !root.expanded;
|
||||
}
|
||||
|
||||
VrControls.FontAwesome {
|
||||
id: toggle
|
||||
width: root.collapsable ? height : 0
|
||||
anchors { left: parent.left; top: parent.top; margins: root.spacing }
|
||||
visible: root.collapsable
|
||||
enabled: root.collapsable
|
||||
rotation: root.expanded ? 0 : -90
|
||||
text: "\uf078"
|
||||
Behavior on rotation { PropertyAnimation {} }
|
||||
MouseArea { anchors.fill: parent; onClicked: root.toggleExpanded() }
|
||||
}
|
||||
|
||||
Text {
|
||||
id: header
|
||||
anchors { left: toggle.right; top: parent.top; leftMargin: root.spacing * 2; margins: root.spacing }
|
||||
font.bold: true
|
||||
font.pointSize: 16
|
||||
color: "#0e7077"
|
||||
text: root.name
|
||||
MouseArea { anchors.fill: parent; onClicked: root.toggleExpanded() }
|
||||
}
|
||||
|
||||
Column {
|
||||
HiFiControls.ContentSection {
|
||||
id: contentContainer
|
||||
spacing: root.spacing
|
||||
anchors { left: toggle.right; top: header.bottom; topMargin: root.spacing; right: parent.right; margins: root.spacing }
|
||||
enabled: root.expanded
|
||||
visible: root.expanded
|
||||
clip: true
|
||||
name: root.name
|
||||
isFirst: root.isFirst
|
||||
isCollapsible: root.collapsable
|
||||
isCollapsed: !root.expanded
|
||||
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
margins: 0
|
||||
}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
|
@ -83,6 +73,7 @@ Preference {
|
|||
property var buttonBuilder: Component { ButtonPreference { } }
|
||||
property var comboBoxBuilder: Component { ComboBoxPreference { } }
|
||||
property var preferences: []
|
||||
property int checkBoxCount: 0
|
||||
|
||||
function buildPreferences() {
|
||||
var categoryPreferences = Preferences.preferencesByCategory[root.name];
|
||||
|
@ -99,40 +90,49 @@ Preference {
|
|||
var builder;
|
||||
switch (preference.type) {
|
||||
case Preference.Editable:
|
||||
checkBoxCount = 0;
|
||||
builder = editableBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Browsable:
|
||||
checkBoxCount = 0;
|
||||
builder = browsableBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Spinner:
|
||||
checkBoxCount = 0;
|
||||
builder = spinnerBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Slider:
|
||||
checkBoxCount = 0;
|
||||
builder = sliderBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Checkbox:
|
||||
checkBoxCount++;
|
||||
console.log("####### checkBoxCount = " + checkBoxCount);
|
||||
builder = checkboxBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Avatar:
|
||||
checkBoxCount = 0;
|
||||
builder = avatarBuilder;
|
||||
break;
|
||||
|
||||
case Preference.Button:
|
||||
checkBoxCount = 0;
|
||||
builder = buttonBuilder;
|
||||
break;
|
||||
|
||||
case Preference.ComboBox:
|
||||
checkBoxCount = 0;
|
||||
builder = comboBoxBuilder;
|
||||
break;
|
||||
};
|
||||
|
||||
if (builder) {
|
||||
preferences.push(builder.createObject(contentContainer, { preference: preference }));
|
||||
preferences.push(builder.createObject(contentContainer, { preference: preference, isFirstCheckBox: (checkBoxCount === 1) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,22 @@
|
|||
//
|
||||
// SpinBoxPreference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import "../../dialogs"
|
||||
import "../../controls-uit"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property alias slider: slider
|
||||
height: slider.height
|
||||
height: control.height + hifi.dimensions.controlInterlineHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
slider.value = preference.value;
|
||||
|
@ -15,16 +27,38 @@ Preference {
|
|||
preference.save();
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.label
|
||||
color: enabled ? "black" : "gray"
|
||||
anchors.verticalCenter: slider.verticalCenter
|
||||
}
|
||||
Item {
|
||||
id: control
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: Math.max(labelText.height, slider.height)
|
||||
|
||||
Slider {
|
||||
id: slider
|
||||
value: preference.value
|
||||
width: 130
|
||||
anchors { right: parent.right }
|
||||
Label {
|
||||
id: labelText
|
||||
text: root.label + ":"
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: slider.left
|
||||
rightMargin: hifi.dimensions.labelPadding
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
horizontalAlignment: Text.AlignRight
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Slider {
|
||||
id: slider
|
||||
value: preference.value
|
||||
width: 130
|
||||
anchors {
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,21 @@
|
|||
//
|
||||
// SpinBoxPreference.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 18 Jan 2016
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import "../../controls-uit"
|
||||
|
||||
Preference {
|
||||
id: root
|
||||
property alias spinner: spinner
|
||||
height: spinner.height
|
||||
height: control.height + hifi.dimensions.controlInterlineHeight
|
||||
|
||||
Component.onCompleted: {
|
||||
spinner.value = preference.value;
|
||||
|
@ -15,18 +26,40 @@ Preference {
|
|||
preference.save();
|
||||
}
|
||||
|
||||
Text {
|
||||
text: root.label
|
||||
color: root.enabled ? "black" : "gray"
|
||||
anchors.verticalCenter: spinner.verticalCenter
|
||||
}
|
||||
Item {
|
||||
id: control
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
bottom: parent.bottom
|
||||
}
|
||||
height: Math.max(spinnerLabel.height, spinner.controlHeight)
|
||||
|
||||
SpinBox {
|
||||
id: spinner
|
||||
decimals: preference.decimals
|
||||
minimumValue: preference.min
|
||||
maximumValue: preference.max
|
||||
width: 100
|
||||
anchors { right: parent.right }
|
||||
Label {
|
||||
id: spinnerLabel
|
||||
text: root.label + ":"
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: spinner.left
|
||||
rightMargin: hifi.dimensions.labelPadding
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
horizontalAlignment: Text.AlignRight
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
SpinBox {
|
||||
id: spinner
|
||||
decimals: preference.decimals
|
||||
minimumValue: preference.min
|
||||
maximumValue: preference.max
|
||||
width: 100
|
||||
anchors {
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import "../../dialogs"
|
|||
PreferencesDialog {
|
||||
id: root
|
||||
objectName: "AudioPreferencesDialog"
|
||||
title: "Audio Preferences"
|
||||
title: "Audio Settings"
|
||||
showCategories: ["Audio"]
|
||||
property var settings: Settings {
|
||||
category: root.objectName
|
||||
|
|
|
@ -6,7 +6,7 @@ import "../../dialogs"
|
|||
PreferencesDialog {
|
||||
id: root
|
||||
objectName: "AvatarPreferencesDialog"
|
||||
title: "Avatar Preferences"
|
||||
title: "Avatar Settings"
|
||||
showCategories: [ "Avatar Basics", "Avatar Tuning", "Avatar Camera" ]
|
||||
property var settings: Settings {
|
||||
category: root.objectName
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
//
|
||||
// PreferencesDialog.qml
|
||||
//
|
||||
// Created by Bradley Austin Davis on 24 Jan 2016
|
||||
// Copyright 2015 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
|
||||
//
|
||||
|
||||
import QtQuick 2.5
|
||||
import Qt.labs.settings 1.0
|
||||
|
||||
|
@ -6,7 +16,7 @@ import "../../dialogs"
|
|||
PreferencesDialog {
|
||||
id: root
|
||||
objectName: "GeneralPreferencesDialog"
|
||||
title: "General Preferences"
|
||||
title: "General Settings"
|
||||
showCategories: ["Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Sixense Controllers"]
|
||||
property var settings: Settings {
|
||||
category: root.objectName
|
||||
|
|
|
@ -6,7 +6,7 @@ import "../../dialogs"
|
|||
PreferencesDialog {
|
||||
id: root
|
||||
objectName: "GraphicsPreferencesDialog"
|
||||
title: "Graphics Preferences"
|
||||
title: "Graphics Settings"
|
||||
showCategories: ["Graphics"]
|
||||
property var settings: Settings {
|
||||
category: root.objectName
|
||||
|
|
|
@ -6,7 +6,7 @@ import "../../dialogs"
|
|||
PreferencesDialog {
|
||||
id: root
|
||||
objectName: "LodPreferencesDialog"
|
||||
title: "Level of Detail preferences"
|
||||
title: "LOD Settings"
|
||||
showCategories: ["Level of Detail Tuning"]
|
||||
property var settings: Settings {
|
||||
category: root.objectName
|
||||
|
|
|
@ -24,7 +24,7 @@ Window {
|
|||
resizable: true
|
||||
destroyOnInvisible: true
|
||||
x: 40; y: 40
|
||||
implicitWidth: 384; implicitHeight: 640
|
||||
implicitWidth: 400; implicitHeight: 695
|
||||
minSize: Qt.vector2d(200, 300)
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
@ -87,8 +87,11 @@ Window {
|
|||
Column {
|
||||
width: pane.contentWidth
|
||||
|
||||
HifiControls.StaticSection {
|
||||
HifiControls.ContentSection {
|
||||
name: "Currently Running"
|
||||
isFirst: true
|
||||
|
||||
HifiControls.VerticalSpacer {}
|
||||
|
||||
Row {
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
|
@ -106,6 +109,8 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
HifiControls.VerticalSpacer {}
|
||||
|
||||
HifiControls.Table {
|
||||
tableModel: runningScriptsModel
|
||||
height: 185
|
||||
|
@ -113,11 +118,16 @@ Window {
|
|||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
HifiControls.VerticalSpacer {
|
||||
height: 2 // Table view draws a little taller than it's height.
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.StaticSection {
|
||||
HifiControls.ContentSection {
|
||||
name: "Load Scripts"
|
||||
hasSeparator: true
|
||||
|
||||
HifiControls.VerticalSpacer {}
|
||||
|
||||
Row {
|
||||
spacing: hifi.dimensions.contentSpacing.x
|
||||
|
@ -161,18 +171,21 @@ Window {
|
|||
}
|
||||
}
|
||||
|
||||
HifiControls.VerticalSpacer {}
|
||||
|
||||
HifiControls.TextField {
|
||||
id: filterEdit
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
focus: true
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
//placeholderText: "filter"
|
||||
label: "Filter"
|
||||
placeholderText: "filter"
|
||||
onTextChanged: scriptsModel.filterRegExp = new RegExp("^.*" + text + ".*$", "i")
|
||||
Component.onCompleted: scriptsModel.filterRegExp = new RegExp("^.*$", "i")
|
||||
}
|
||||
|
||||
HifiControls.VerticalSpacer {}
|
||||
|
||||
HifiControls.Tree {
|
||||
id: treeView
|
||||
height: 155
|
||||
|
@ -182,6 +195,8 @@ Window {
|
|||
anchors.right: parent.right
|
||||
}
|
||||
|
||||
HifiControls.VerticalSpacer {}
|
||||
|
||||
HifiControls.TextField {
|
||||
id: selectedScript
|
||||
anchors.left: parent.left
|
||||
|
|
|
@ -48,11 +48,13 @@ Item {
|
|||
|
||||
// Other colors
|
||||
readonly property color white: "#ffffff"
|
||||
readonly property color gray: "#808080"
|
||||
readonly property color black: "#000000"
|
||||
// Semitransparent
|
||||
readonly property color white50: "#80ffffff"
|
||||
readonly property color white30: "#4dffffff"
|
||||
readonly property color white25: "#40ffffff"
|
||||
readonly property color transparent: "#00ffffff"
|
||||
|
||||
// Control specific colors
|
||||
readonly property color tableRowLightOdd: white50
|
||||
|
@ -61,6 +63,26 @@ Item {
|
|||
readonly property color tableRowDarkEven: "#a6181818"
|
||||
readonly property color tableScrollHandle: "#707070"
|
||||
readonly property color tableScrollBackground: "#323232"
|
||||
readonly property color checkboxLightStart: "#ffffff"
|
||||
readonly property color checkboxLightFinish: "#afafaf"
|
||||
readonly property color checkboxDarkStart: "#7d7d7d"
|
||||
readonly property color checkboxDarkFinish: "#6b6a6b"
|
||||
readonly property color checkboxChecked: primaryHighlight
|
||||
readonly property color checkboxCheckedBorder: "#36cdff"
|
||||
readonly property color sliderGutterLight: "#d4d4d4"
|
||||
readonly property color sliderGutterDark: "#252525"
|
||||
readonly property color sliderBorderLight: "#afafaf"
|
||||
readonly property color sliderBorderDark: "#7d7d7d"
|
||||
readonly property color sliderLightStart: "#ffffff"
|
||||
readonly property color sliderLightFinish: "#afafaf"
|
||||
readonly property color sliderDarkStart: "#7d7d7d"
|
||||
readonly property color sliderDarkFinish: "#6b6a6b"
|
||||
readonly property color dropDownPressedLight: "#d4d4d4"
|
||||
readonly property color dropDownPressedDark: "#afafaf"
|
||||
readonly property color dropDownLightStart: "#ffffff"
|
||||
readonly property color dropDownLightFinish: "#afafaf"
|
||||
readonly property color dropDownDarkStart: "#7d7d7d"
|
||||
readonly property color dropDownDarkFinish: "#6b6a6b"
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -76,39 +98,54 @@ Item {
|
|||
readonly property real borderWidth: largeScreen ? 2 : 1
|
||||
readonly property vector2d contentMargin: Qt.vector2d(12, 24)
|
||||
readonly property vector2d contentSpacing: Qt.vector2d(8, 12)
|
||||
readonly property real labelPadding: 40
|
||||
readonly property real textPadding: 8
|
||||
readonly property real sliderHandleSize: 18
|
||||
readonly property real sliderGrooveHeight: 8
|
||||
readonly property real spinnerSize: 42
|
||||
readonly property real tablePadding: 12
|
||||
readonly property real tableRowHeight: largeScreen ? 26 : 23
|
||||
readonly property vector2d modalDialogMargin: Qt.vector2d(50, 30)
|
||||
readonly property real modalDialogTitleHeight: 40
|
||||
readonly property real controlLineHeight: 29 // Height of spinbox control on 1920 x 1080 monitor
|
||||
readonly property real controlInterlineHeight: 22 // 75% of controlLineHeight
|
||||
}
|
||||
|
||||
Item {
|
||||
id: fontSizes // In pixels
|
||||
readonly property real overlayTitle: dimensions.largeScreen? 18 : 14
|
||||
readonly property real tabName: dimensions.largeScreen? 12 : 10
|
||||
readonly property real sectionName: dimensions.largeScreen? 12 : 10
|
||||
readonly property real inputLabel: dimensions.largeScreen? 14 : 10
|
||||
readonly property real textFieldInput: dimensions.largeScreen? 15 : 12
|
||||
readonly property real tableText: dimensions.largeScreen? 15 : 12
|
||||
readonly property real buttonLabel: dimensions.largeScreen? 13 : 9
|
||||
readonly property real iconButton: dimensions.largeScreen? 13 : 9
|
||||
readonly property real listItem: dimensions.largeScreen? 15 : 11
|
||||
readonly property real tabularData: dimensions.largeScreen? 15 : 11
|
||||
readonly property real logs: dimensions.largeScreen? 16 : 12
|
||||
readonly property real code: dimensions.largeScreen? 16 : 12
|
||||
readonly property real rootMenu: dimensions.largeScreen? 15 : 11
|
||||
readonly property real menuItem: dimensions.largeScreen? 15 : 11
|
||||
readonly property real shortcutText: dimensions.largeScreen? 13 : 9
|
||||
readonly property real overlayTitle: dimensions.largeScreen ? 18 : 14
|
||||
readonly property real tabName: dimensions.largeScreen ? 12 : 10
|
||||
readonly property real sectionName: dimensions.largeScreen ? 12 : 10
|
||||
readonly property real inputLabel: dimensions.largeScreen ? 14 : 10
|
||||
readonly property real textFieldInput: dimensions.largeScreen ? 15 : 12
|
||||
readonly property real tableText: dimensions.largeScreen ? 15 : 12
|
||||
readonly property real buttonLabel: dimensions.largeScreen ? 13 : 9
|
||||
readonly property real iconButton: dimensions.largeScreen ? 13 : 9
|
||||
readonly property real listItem: dimensions.largeScreen ? 15 : 11
|
||||
readonly property real tabularData: dimensions.largeScreen ? 15 : 11
|
||||
readonly property real logs: dimensions.largeScreen ? 16 : 12
|
||||
readonly property real code: dimensions.largeScreen ? 16 : 12
|
||||
readonly property real rootMenu: dimensions.largeScreen ? 15 : 11
|
||||
readonly property real menuItem: dimensions.largeScreen ? 15 : 11
|
||||
readonly property real shortcutText: dimensions.largeScreen ? 13 : 9
|
||||
readonly property real carat: dimensions.largeScreen ? 38 : 30
|
||||
readonly property real disclosureButton: dimensions.largeScreen ? 20 : 15
|
||||
}
|
||||
|
||||
Item {
|
||||
id: glyphs
|
||||
readonly property string backward: "E"
|
||||
readonly property string caratDn: "5"
|
||||
readonly property string caratR: "3"
|
||||
readonly property string caratUp: "6"
|
||||
readonly property string close: "w"
|
||||
readonly property string closeInverted: "x"
|
||||
readonly property string closeSmall: "C"
|
||||
readonly property string disclosureButtonCollapse: "M"
|
||||
readonly property string disclosureButtonExpand: "L"
|
||||
readonly property string disclosureCollapse: "Z"
|
||||
readonly property string disclosureExpand: "B"
|
||||
readonly property string forward: "D"
|
||||
readonly property string pin: "y"
|
||||
readonly property string pinInverted: "z"
|
||||
readonly property string reloadSmall: "a"
|
||||
|
|
|
@ -99,8 +99,8 @@ Frame {
|
|||
DropShadow {
|
||||
source: titleText
|
||||
anchors.fill: titleText
|
||||
horizontalOffset: 1
|
||||
verticalOffset: 1
|
||||
horizontalOffset: 2
|
||||
verticalOffset: 2
|
||||
samples: 2
|
||||
color: hifi.colors.baseGrayShadow60
|
||||
visible: (window && window.focus)
|
||||
|
|
|
@ -58,6 +58,8 @@ Fadable {
|
|||
// The content to place inside the window, determined by the client
|
||||
default property var content
|
||||
|
||||
property var footer: Item { } // Optional static footer at the bottom of the dialog.
|
||||
|
||||
function setDefaultFocus() {} // Default function; can be overridden by dialogs.
|
||||
|
||||
property var rectifier: Timer {
|
||||
|
@ -125,7 +127,8 @@ Fadable {
|
|||
// Scrollable window content.
|
||||
property var pane: Item {
|
||||
property bool isScrolling: scrollView.height < scrollView.contentItem.height
|
||||
property int contentWidth: scrollView.width - (isScrolling ? 11 : 0)
|
||||
property int contentWidth: scrollView.width - (isScrolling ? 10 : 0)
|
||||
property int scrollHeight: scrollView.height
|
||||
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: isScrolling ? 11 : 0
|
||||
|
@ -162,6 +165,7 @@ Fadable {
|
|||
verticalScrollBarPolicy: Qt.ScrollBarAsNeeded
|
||||
anchors.fill: parent
|
||||
anchors.rightMargin: parent.isScrolling ? 1 : 0
|
||||
anchors.bottomMargin: footer.height > 0 ? footerPane.height : 0
|
||||
|
||||
style: ScrollViewStyle {
|
||||
|
||||
|
@ -203,7 +207,46 @@ Fadable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
// Optional non-scrolling footer.
|
||||
id: footerPane
|
||||
anchors {
|
||||
left: parent.left
|
||||
bottom: parent.bottom
|
||||
}
|
||||
width: parent.contentWidth
|
||||
height: footer.height + 2 * hifi.dimensions.contentSpacing.y
|
||||
color: hifi.colors.baseGray
|
||||
visible: footer.height > 0
|
||||
|
||||
Item {
|
||||
// Horizontal rule.
|
||||
anchors.fill: parent
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
y: 1 // Stop displaying content just above horizontal rule/=.
|
||||
color: hifi.colors.baseGrayShadow
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
y: 2
|
||||
color: hifi.colors.baseGrayHighlight
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: 3 // Horizontal rule.
|
||||
children: [ footer ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
children: [ swallower, frame, pane, activator ]
|
||||
|
||||
Component.onCompleted: { raise(); setDefaultFocus(); }
|
||||
|
|
|
@ -370,7 +370,6 @@ bool setupEssentials(int& argc, char** argv) {
|
|||
DependencyManager::set<AutoUpdater>();
|
||||
DependencyManager::set<PathUtils>();
|
||||
DependencyManager::set<InterfaceActionFactory>();
|
||||
DependencyManager::set<AssetClient>();
|
||||
DependencyManager::set<AudioInjectorManager>();
|
||||
DependencyManager::set<MessagesClient>();
|
||||
DependencyManager::set<UserInputMapper>();
|
||||
|
@ -528,13 +527,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
audioThread->start();
|
||||
|
||||
// Setup AssetClient
|
||||
auto assetClient = DependencyManager::get<AssetClient>();
|
||||
QThread* assetThread = new QThread;
|
||||
assetThread->setObjectName("Asset Thread");
|
||||
assetClient->moveToThread(assetThread);
|
||||
connect(assetThread, &QThread::started, assetClient.data(), &AssetClient::init);
|
||||
assetThread->start();
|
||||
ResourceManager::init();
|
||||
|
||||
// Setup MessagesClient
|
||||
auto messagesClient = DependencyManager::get<MessagesClient>();
|
||||
|
@ -547,11 +540,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
|
||||
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
|
||||
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
|
||||
connect(&domainHandler, SIGNAL(resetting()), SLOT(resettingDomain()));
|
||||
connect(&domainHandler, SIGNAL(connectedToDomain(const QString&)), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(updateWindowTitle()));
|
||||
connect(&domainHandler, SIGNAL(disconnectedFromDomain()), SLOT(clearDomainOctreeDetails()));
|
||||
connect(&domainHandler, &DomainHandler::settingsReceived, this, &Application::domainSettingsReceived);
|
||||
|
||||
// update our location every 5 seconds in the metaverse server, assuming that we are authenticated with one
|
||||
const qint64 DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5 * 1000;
|
||||
|
@ -588,6 +580,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(&accountManager, &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
|
||||
|
||||
// set the account manager's root URL and trigger a login request if we don't have the access token
|
||||
accountManager.setIsAgent(true);
|
||||
accountManager.setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL);
|
||||
UserActivityLogger::getInstance().launch(applicationVersion());
|
||||
|
||||
|
@ -644,13 +637,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
connect(&identityPacketTimer, &QTimer::timeout, getMyAvatar(), &MyAvatar::sendIdentityPacket);
|
||||
identityPacketTimer.start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
||||
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkDiskCache* cache = new QNetworkDiskCache();
|
||||
cache->setMaximumCacheSize(MAXIMUM_CACHE_SIZE);
|
||||
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
|
||||
networkAccessManager.setCache(cache);
|
||||
|
||||
ResourceCache::setRequestLimit(3);
|
||||
|
||||
_glWidget = new GLCanvas();
|
||||
|
@ -661,15 +647,15 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
|
||||
_glWidget->setFocusPolicy(Qt::StrongFocus);
|
||||
_glWidget->setFocus();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
// OSX doesn't seem to provide for hiding the cursor only on the GL widget
|
||||
_window->setCursor(Qt::BlankCursor);
|
||||
auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget
|
||||
#else
|
||||
// On windows and linux, hiding the top level cursor also means it's invisible
|
||||
// when hovering over the window menu, which is a pain, so only hide it for
|
||||
// the GL surface
|
||||
_glWidget->setCursor(Qt::BlankCursor);
|
||||
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
|
||||
// window menu, which is a pain, so only hide it for the GL surface
|
||||
auto cursorTarget = _glWidget;
|
||||
#endif
|
||||
cursorTarget->setCursor(Qt::BlankCursor);
|
||||
|
||||
// enable mouse tracking; otherwise, we only get drag events
|
||||
_glWidget->setMouseTracking(true);
|
||||
|
@ -903,9 +889,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
SpacemouseManager::getInstance().init();
|
||||
#endif
|
||||
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::DomainConnectionDenied, this, "handleDomainConnectionDeniedPacket");
|
||||
|
||||
// If the user clicks an an entity, we will check that it's an unlocked web entity, and if so, set the focus to it
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
connect(entityScriptingInterface.data(), &EntityScriptingInterface::clickDownOnEntity,
|
||||
|
@ -981,6 +964,29 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
|||
_idleTimer->start(0);
|
||||
}
|
||||
|
||||
|
||||
void Application::checkChangeCursor() {
|
||||
QMutexLocker locker(&_changeCursorLock);
|
||||
if (_cursorNeedsChanging) {
|
||||
#ifdef Q_OS_MAC
|
||||
auto cursorTarget = _window; // OSX doesn't seem to provide for hiding the cursor only on the GL widget
|
||||
#else
|
||||
// On windows and linux, hiding the top level cursor also means it's invisible when hovering over the
|
||||
// window menu, which is a pain, so only hide it for the GL surface
|
||||
auto cursorTarget = _glWidget;
|
||||
#endif
|
||||
cursorTarget->setCursor(_desiredCursor);
|
||||
|
||||
_cursorNeedsChanging = false;
|
||||
}
|
||||
}
|
||||
|
||||
void Application::showCursor(const QCursor& cursor) {
|
||||
QMutexLocker locker(&_changeCursorLock);
|
||||
_desiredCursor = cursor;
|
||||
_cursorNeedsChanging = true;
|
||||
}
|
||||
|
||||
void Application::aboutToQuit() {
|
||||
emit beforeAboutToQuit();
|
||||
|
||||
|
@ -1062,13 +1068,6 @@ void Application::cleanupBeforeQuit() {
|
|||
DependencyManager::destroy<OffscreenUi>();
|
||||
}
|
||||
|
||||
void Application::emptyLocalCache() {
|
||||
if (auto cache = NetworkAccessManager::getInstance().cache()) {
|
||||
qDebug() << "DiskCacheEditor::clear(): Clearing disk cache.";
|
||||
cache->clear();
|
||||
}
|
||||
}
|
||||
|
||||
Application::~Application() {
|
||||
EntityTreePointer tree = getEntities()->getTree();
|
||||
tree->setSimulation(NULL);
|
||||
|
@ -1106,11 +1105,7 @@ Application::~Application() {
|
|||
DependencyManager::destroy<ScriptCache>();
|
||||
DependencyManager::destroy<SoundCache>();
|
||||
|
||||
// cleanup the AssetClient thread
|
||||
QThread* assetThread = DependencyManager::get<AssetClient>()->thread();
|
||||
DependencyManager::destroy<AssetClient>();
|
||||
assetThread->quit();
|
||||
assetThread->wait();
|
||||
ResourceManager::cleanup();
|
||||
|
||||
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
|
||||
|
||||
|
@ -2431,6 +2426,9 @@ void Application::idle(uint64_t now) {
|
|||
return; // bail early, nothing to do here.
|
||||
}
|
||||
|
||||
|
||||
checkChangeCursor();
|
||||
|
||||
Stats::getInstance()->updateStats();
|
||||
AvatarInputs::getInstance()->update();
|
||||
|
||||
|
@ -2946,9 +2944,9 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
} else {
|
||||
// I am not looking at anyone else, so just look forward
|
||||
if (isHMD) {
|
||||
glm::mat4 headPose = _avatarUpdate->getHeadPose() ;
|
||||
glm::mat4 headPose = _avatarUpdate->getHeadPose();
|
||||
glm::quat headRotation = glm::quat_cast(headPose);
|
||||
lookAtSpot = _myCamera.getPosition() +
|
||||
lookAtSpot = myAvatar->getPosition() +
|
||||
myAvatar->getOrientation() * (headRotation * glm::vec3(0.0f, 0.0f, -TREE_SCALE));
|
||||
} else {
|
||||
lookAtSpot = myAvatar->getHead()->getEyePosition() +
|
||||
|
@ -3055,7 +3053,7 @@ void Application::reloadResourceCaches() {
|
|||
_viewFrustum.setOrientation(glm::quat());
|
||||
queryOctree(NodeType::EntityServer, PacketType::EntityQuery, _entityServerJurisdictions);
|
||||
|
||||
emptyLocalCache();
|
||||
DependencyManager::get<AssetClient>()->clearCache();
|
||||
|
||||
DependencyManager::get<AnimationCache>()->refreshAll();
|
||||
DependencyManager::get<ModelCache>()->refreshAll();
|
||||
|
@ -3818,7 +3816,7 @@ void Application::displaySide(RenderArgs* renderArgs, Camera& theCamera, bool se
|
|||
// Setup the current Zone Entity lighting
|
||||
{
|
||||
auto stage = DependencyManager::get<SceneScriptingInterface>()->getSkyStage();
|
||||
DependencyManager::get<DeferredLightingEffect>()->setGlobalLight(stage->getSunLight(), stage->getSkybox()->getCubemap());
|
||||
DependencyManager::get<DeferredLightingEffect>()->setGlobalLight(stage->getSunLight());
|
||||
}
|
||||
|
||||
{
|
||||
|
@ -3954,37 +3952,13 @@ void Application::clearDomainOctreeDetails() {
|
|||
void Application::domainChanged(const QString& domainHostname) {
|
||||
updateWindowTitle();
|
||||
clearDomainOctreeDetails();
|
||||
_domainConnectionRefusals.clear();
|
||||
// disable physics until we have enough information about our new location to not cause craziness.
|
||||
_physicsEnabled = false;
|
||||
}
|
||||
|
||||
void Application::handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
// Read deny reason from packet
|
||||
quint16 reasonSize;
|
||||
message->readPrimitive(&reasonSize);
|
||||
QString reason = QString::fromUtf8(message->readWithoutCopy(reasonSize));
|
||||
|
||||
// output to the log so the user knows they got a denied connection request
|
||||
// and check and signal for an access token so that we can make sure they are logged in
|
||||
qCDebug(interfaceapp) << "The domain-server denied a connection request: " << reason;
|
||||
qCDebug(interfaceapp) << "You may need to re-log to generate a keypair so you can provide a username signature.";
|
||||
|
||||
if (!_domainConnectionRefusals.contains(reason)) {
|
||||
_domainConnectionRefusals.append(reason);
|
||||
emit domainConnectionRefused(reason);
|
||||
}
|
||||
|
||||
AccountManager::getInstance().checkAndSignalForAccessToken();
|
||||
}
|
||||
|
||||
void Application::connectedToDomain(const QString& hostname) {
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
const QUuid& domainID = DependencyManager::get<NodeList>()->getDomainHandler().getUUID();
|
||||
|
||||
if (accountManager.isLoggedIn() && !domainID.isNull()) {
|
||||
_notifiedPacketVersionMismatchThisDomain = false;
|
||||
}
|
||||
void Application::resettingDomain() {
|
||||
_notifiedPacketVersionMismatchThisDomain = false;
|
||||
}
|
||||
|
||||
void Application::nodeAdded(SharedNodePointer node) {
|
||||
|
@ -4521,33 +4495,6 @@ void Application::openUrl(const QUrl& url) {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::domainSettingsReceived(const QJsonObject& domainSettingsObject) {
|
||||
// from the domain-handler, figure out the satoshi cost per voxel and per meter cubed
|
||||
const QString VOXEL_SETTINGS_KEY = "voxels";
|
||||
const QString PER_VOXEL_COST_KEY = "per-voxel-credits";
|
||||
const QString PER_METER_CUBED_COST_KEY = "per-meter-cubed-credits";
|
||||
const QString VOXEL_WALLET_UUID = "voxel-wallet";
|
||||
|
||||
const QJsonObject& voxelObject = domainSettingsObject[VOXEL_SETTINGS_KEY].toObject();
|
||||
|
||||
qint64 satoshisPerVoxel = 0;
|
||||
qint64 satoshisPerMeterCubed = 0;
|
||||
QUuid voxelWalletUUID;
|
||||
|
||||
if (!domainSettingsObject.isEmpty()) {
|
||||
float perVoxelCredits = (float) voxelObject[PER_VOXEL_COST_KEY].toDouble();
|
||||
float perMeterCubedCredits = (float) voxelObject[PER_METER_CUBED_COST_KEY].toDouble();
|
||||
|
||||
satoshisPerVoxel = (qint64) floorf(perVoxelCredits * SATOSHIS_PER_CREDIT);
|
||||
satoshisPerMeterCubed = (qint64) floorf(perMeterCubedCredits * SATOSHIS_PER_CREDIT);
|
||||
|
||||
voxelWalletUUID = QUuid(voxelObject[VOXEL_WALLET_UUID].toString());
|
||||
}
|
||||
|
||||
qCDebug(interfaceapp) << "Octree edits costs are" << satoshisPerVoxel << "per octree cell and" << satoshisPerMeterCubed << "per meter cubed";
|
||||
qCDebug(interfaceapp) << "Destination wallet UUID for edit payments is" << voxelWalletUUID;
|
||||
}
|
||||
|
||||
void Application::loadDialog() {
|
||||
auto scriptEngines = DependencyManager::get<ScriptEngines>();
|
||||
QString fileNameString = OffscreenUi::getOpenFileName(
|
||||
|
|
|
@ -120,6 +120,8 @@ public:
|
|||
QSize getDeviceSize() const;
|
||||
bool hasFocus() const;
|
||||
|
||||
void showCursor(const QCursor& cursor);
|
||||
|
||||
bool isThrottleRendering() const;
|
||||
|
||||
Camera* getCamera() { return &_myCamera; }
|
||||
|
@ -225,7 +227,6 @@ signals:
|
|||
void svoImportRequested(const QString& url);
|
||||
|
||||
void checkBackgroundDownloads();
|
||||
void domainConnectionRefused(const QString& reason);
|
||||
|
||||
void fullAvatarURLChanged(const QString& newValue, const QString& modelName);
|
||||
|
||||
|
@ -288,16 +289,13 @@ private slots:
|
|||
void idle(uint64_t now);
|
||||
void aboutToQuit();
|
||||
|
||||
void connectedToDomain(const QString& hostname);
|
||||
void resettingDomain();
|
||||
|
||||
void audioMuteToggled();
|
||||
void faceTrackerMuteToggled();
|
||||
|
||||
void activeChanged(Qt::ApplicationState state);
|
||||
|
||||
void domainSettingsReceived(const QJsonObject& domainSettingsObject);
|
||||
void handleDomainConnectionDeniedPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void notifyPacketVersionMismatch();
|
||||
|
||||
void loadSettings();
|
||||
|
@ -328,8 +326,6 @@ private:
|
|||
|
||||
void cleanupBeforeQuit();
|
||||
|
||||
void emptyLocalCache();
|
||||
|
||||
void update(float deltaTime);
|
||||
|
||||
void setPalmData(Hand* hand, const controller::Pose& pose, float deltaTime, HandData::Hand whichHand, float triggerValue);
|
||||
|
@ -476,7 +472,6 @@ private:
|
|||
typedef bool (Application::* AcceptURLMethod)(const QString &);
|
||||
static const QHash<QString, AcceptURLMethod> _acceptedExtensions;
|
||||
|
||||
QList<QString> _domainConnectionRefusals;
|
||||
glm::uvec2 _renderResolution;
|
||||
|
||||
int _maxOctreePPS = DEFAULT_MAX_OCTREE_PPS;
|
||||
|
@ -515,6 +510,11 @@ private:
|
|||
QTimer* _idleTimer { nullptr };
|
||||
|
||||
bool _fakedMouseEvent { false };
|
||||
|
||||
void checkChangeCursor();
|
||||
mutable QMutex _changeCursorLock { QMutex::Recursive };
|
||||
QCursor _desiredCursor{ Qt::BlankCursor };
|
||||
bool _cursorNeedsChanging { false };
|
||||
};
|
||||
|
||||
#endif // hifi_Application_h
|
||||
|
|
|
@ -22,11 +22,8 @@
|
|||
#include <QStandardPaths>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "DataServerAccountInfo.h"
|
||||
#include "Menu.h"
|
||||
|
||||
Q_DECLARE_METATYPE(DataServerAccountInfo)
|
||||
|
||||
static const QString RUNNING_MARKER_FILENAME = "Interface.running";
|
||||
|
||||
void CrashHandler::checkForAndHandleCrash() {
|
||||
|
@ -57,7 +54,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
|||
layout->addWidget(label);
|
||||
|
||||
QRadioButton* option1 = new QRadioButton("Reset all my settings");
|
||||
QRadioButton* option2 = new QRadioButton("Reset my settings but retain login and avatar info.");
|
||||
QRadioButton* option2 = new QRadioButton("Reset my settings but retain avatar info.");
|
||||
QRadioButton* option3 = new QRadioButton("Continue with my current settings");
|
||||
option3->setChecked(true);
|
||||
layout->addWidget(option1);
|
||||
|
@ -79,7 +76,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
|||
return CrashHandler::DELETE_INTERFACE_INI;
|
||||
}
|
||||
if (option2->isChecked()) {
|
||||
return CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO;
|
||||
return CrashHandler::RETAIN_AVATAR_INFO;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,7 +85,7 @@ CrashHandler::Action CrashHandler::promptUserForAction() {
|
|||
}
|
||||
|
||||
void CrashHandler::handleCrash(CrashHandler::Action action) {
|
||||
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
||||
if (action != CrashHandler::DELETE_INTERFACE_INI && action != CrashHandler::RETAIN_AVATAR_INFO) {
|
||||
// CrashHandler::DO_NOTHING or unexpected value
|
||||
return;
|
||||
}
|
||||
|
@ -101,18 +98,13 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
const QString DISPLAY_NAME_KEY = "displayName";
|
||||
const QString FULL_AVATAR_URL_KEY = "fullAvatarURL";
|
||||
const QString FULL_AVATAR_MODEL_NAME_KEY = "fullAvatarModelName";
|
||||
const QString ACCOUNTS_GROUP = "accounts";
|
||||
QString displayName;
|
||||
QUrl fullAvatarURL;
|
||||
QString fullAvatarModelName;
|
||||
QUrl address;
|
||||
QMap<QString, DataServerAccountInfo> accounts;
|
||||
|
||||
if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
||||
// Read login and avatar info
|
||||
|
||||
qRegisterMetaType<DataServerAccountInfo>("DataServerAccountInfo");
|
||||
qRegisterMetaTypeStreamOperators<DataServerAccountInfo>("DataServerAccountInfo");
|
||||
if (action == CrashHandler::RETAIN_AVATAR_INFO) {
|
||||
// Read avatar info
|
||||
|
||||
// Location and orientation
|
||||
settings.beginGroup(ADDRESS_MANAGER_GROUP);
|
||||
|
@ -125,13 +117,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
fullAvatarURL = settings.value(FULL_AVATAR_URL_KEY).toUrl();
|
||||
fullAvatarModelName = settings.value(FULL_AVATAR_MODEL_NAME_KEY).toString();
|
||||
settings.endGroup();
|
||||
|
||||
// Accounts
|
||||
settings.beginGroup(ACCOUNTS_GROUP);
|
||||
foreach(const QString& key, settings.allKeys()) {
|
||||
accounts.insert(key, settings.value(key).value<DataServerAccountInfo>());
|
||||
}
|
||||
settings.endGroup();
|
||||
}
|
||||
|
||||
// Delete Interface.ini
|
||||
|
@ -140,8 +125,8 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
settingsFile.remove();
|
||||
}
|
||||
|
||||
if (action == CrashHandler::RETAIN_LOGIN_AND_AVATAR_INFO) {
|
||||
// Write login and avatar info
|
||||
if (action == CrashHandler::RETAIN_AVATAR_INFO) {
|
||||
// Write avatar info
|
||||
|
||||
// Location and orientation
|
||||
settings.beginGroup(ADDRESS_MANAGER_GROUP);
|
||||
|
@ -154,13 +139,6 @@ void CrashHandler::handleCrash(CrashHandler::Action action) {
|
|||
settings.setValue(FULL_AVATAR_URL_KEY, fullAvatarURL);
|
||||
settings.setValue(FULL_AVATAR_MODEL_NAME_KEY, fullAvatarModelName);
|
||||
settings.endGroup();
|
||||
|
||||
// Accounts
|
||||
settings.beginGroup(ACCOUNTS_GROUP);
|
||||
foreach(const QString& key, accounts.keys()) {
|
||||
settings.setValue(key, QVariant::fromValue(accounts.value(key)));
|
||||
}
|
||||
settings.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ public:
|
|||
private:
|
||||
enum Action {
|
||||
DELETE_INTERFACE_INI,
|
||||
RETAIN_LOGIN_AND_AVATAR_INFO,
|
||||
RETAIN_AVATAR_INFO,
|
||||
DO_NOTHING
|
||||
};
|
||||
|
||||
|
|
|
@ -31,7 +31,6 @@ MainWindow::MainWindow(QWidget* parent) :
|
|||
_windowState("WindowState", 0)
|
||||
{
|
||||
setAcceptDrops(true);
|
||||
_trayIcon.show();
|
||||
}
|
||||
|
||||
void MainWindow::restoreGeometry() {
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#define __hifi__MainWindow__
|
||||
|
||||
#include <QMainWindow>
|
||||
#include <QSystemTrayIcon>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
|
||||
|
@ -43,7 +42,6 @@ protected:
|
|||
private:
|
||||
Setting::Handle<QRect> _windowGeometry;
|
||||
Setting::Handle<int> _windowState;
|
||||
QSystemTrayIcon _trayIcon;
|
||||
};
|
||||
|
||||
#endif /* defined(__hifi__MainWindow__) */
|
||||
|
|
|
@ -342,9 +342,9 @@ void ModelPackager::populateBasicMapping(QVariantHash& mapping, QString filename
|
|||
void ModelPackager::listTextures() {
|
||||
_textures.clear();
|
||||
foreach (const FBXMaterial mat, _geometry->materials) {
|
||||
if (!mat.diffuseTexture.filename.isEmpty() && mat.diffuseTexture.content.isEmpty() &&
|
||||
!_textures.contains(mat.diffuseTexture.filename)) {
|
||||
_textures << mat.diffuseTexture.filename;
|
||||
if (!mat.albedoTexture.filename.isEmpty() && mat.albedoTexture.content.isEmpty() &&
|
||||
!_textures.contains(mat.albedoTexture.filename)) {
|
||||
_textures << mat.albedoTexture.filename;
|
||||
}
|
||||
if (!mat.normalTexture.filename.isEmpty() && mat.normalTexture.content.isEmpty() &&
|
||||
!_textures.contains(mat.normalTexture.filename)) {
|
||||
|
|
|
@ -18,8 +18,6 @@
|
|||
|
||||
#include "ModelSelector.h"
|
||||
|
||||
static const QString AVATAR_HEAD_STRING = "Avatar Head Only";
|
||||
static const QString AVATAR_BODY_STRING = "Avatar Body Only";
|
||||
static const QString AVATAR_HEAD_AND_BODY_STRING = "Avatar Body with Head";
|
||||
static const QString AVATAR_ATTACHEMENT_STRING = "Avatar Attachment";
|
||||
static const QString ENTITY_MODEL_STRING = "Entity Model";
|
||||
|
@ -35,8 +33,7 @@ ModelSelector::ModelSelector() {
|
|||
form->addRow("Model File:", _browseButton);
|
||||
|
||||
_modelType = new QComboBox(this);
|
||||
_modelType->addItem(AVATAR_HEAD_STRING);
|
||||
_modelType->addItem(AVATAR_BODY_STRING);
|
||||
|
||||
_modelType->addItem(AVATAR_HEAD_AND_BODY_STRING);
|
||||
_modelType->addItem(AVATAR_ATTACHEMENT_STRING);
|
||||
_modelType->addItem(ENTITY_MODEL_STRING);
|
||||
|
@ -55,11 +52,7 @@ QFileInfo ModelSelector::getFileInfo() const {
|
|||
FSTReader::ModelType ModelSelector::getModelType() const {
|
||||
QString text = _modelType->currentText();
|
||||
|
||||
if (text == AVATAR_HEAD_STRING) {
|
||||
return FSTReader::HEAD_MODEL;
|
||||
} else if (text == AVATAR_BODY_STRING) {
|
||||
return FSTReader::BODY_ONLY_MODEL;
|
||||
} else if (text == AVATAR_HEAD_AND_BODY_STRING) {
|
||||
if (text == AVATAR_HEAD_AND_BODY_STRING) {
|
||||
return FSTReader::HEAD_AND_BODY_MODEL;
|
||||
} else if (text == AVATAR_ATTACHEMENT_STRING) {
|
||||
return FSTReader::ATTACHMENT_MODEL;
|
||||
|
|
|
@ -400,7 +400,7 @@ void Avatar::render(RenderArgs* renderArgs, const glm::vec3& cameraPosition) {
|
|||
frustum = qApp->getDisplayViewFrustum();
|
||||
}
|
||||
|
||||
if (frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) {
|
||||
if (!frustum->sphereIntersectsFrustum(getPosition(), boundingRadius)) {
|
||||
endRender();
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -153,25 +153,6 @@ std::shared_ptr<Avatar> AvatarActionHold::getTarget(float deltaTimeStep, glm::qu
|
|||
palmPosition = holdingAvatar->getLeftPalmPosition();
|
||||
palmRotation = holdingAvatar->getLeftPalmRotation();
|
||||
}
|
||||
|
||||
// In this case we are simulating the grab of another avatar.
|
||||
// Because the hand controller velocity for their palms is not transmitted over the
|
||||
// network, we have to synthesize our own.
|
||||
|
||||
if (_previousSet) {
|
||||
// smooth linear velocity over two frames
|
||||
glm::vec3 positionalDelta = palmPosition - _previousPositionalTarget;
|
||||
linearVelocity = (positionalDelta + _previousPositionalDelta) / (deltaTimeStep + _previousDeltaTimeStep);
|
||||
glm::quat deltaRotation = palmRotation * glm::inverse(_previousRotationalTarget);
|
||||
float rotationAngle = glm::angle(deltaRotation);
|
||||
if (rotationAngle > EPSILON) {
|
||||
angularVelocity = glm::normalize(glm::axis(deltaRotation));
|
||||
angularVelocity *= (rotationAngle / deltaTimeStep);
|
||||
}
|
||||
|
||||
_previousPositionalDelta = positionalDelta;
|
||||
_previousDeltaTimeStep = deltaTimeStep;
|
||||
}
|
||||
}
|
||||
|
||||
rotation = palmRotation * _relativeRotation;
|
||||
|
@ -278,6 +259,7 @@ void AvatarActionHold::doKinematicUpdate(float deltaTimeStep) {
|
|||
});
|
||||
|
||||
forceBodyNonStatic();
|
||||
activateBody(true);
|
||||
}
|
||||
|
||||
bool AvatarActionHold::updateArguments(QVariantMap arguments) {
|
||||
|
|
|
@ -31,7 +31,14 @@ void AvatarUpdate::synchronousProcess() {
|
|||
// Keep our own updated value, so that our asynchronous code can consult it.
|
||||
_isHMDMode = qApp->isHMDMode();
|
||||
auto frameCount = qApp->getFrameCount();
|
||||
_headPose = qApp->getActiveDisplayPlugin()->getHeadPose(frameCount);
|
||||
|
||||
QSharedPointer<AvatarManager> manager = DependencyManager::get<AvatarManager>();
|
||||
MyAvatar* myAvatar = manager->getMyAvatar();
|
||||
assert(myAvatar);
|
||||
|
||||
// transform the head pose from the displayPlugin into avatar coordinates.
|
||||
glm::mat4 invAvatarMat = glm::inverse(createMatFromQuatAndPos(myAvatar->getOrientation(), myAvatar->getPosition()));
|
||||
_headPose = invAvatarMat * (myAvatar->getSensorToWorldMatrix() * qApp->getActiveDisplayPlugin()->getHeadPose(frameCount));
|
||||
|
||||
if (!isThreaded()) {
|
||||
process();
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#include <scripting/HMDScriptingInterface.h>
|
||||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <AudioClient.h>
|
||||
|
@ -849,7 +850,7 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
|||
avatar->setIsLookAtTarget(false);
|
||||
if (!avatar->isMyAvatar() && avatar->isInitialized() &&
|
||||
(distanceTo < GREATEST_LOOKING_AT_DISTANCE * getUniformScale())) {
|
||||
float angleTo = glm::angle(lookForward, glm::normalize(avatar->getHead()->getEyePosition() - cameraPosition));
|
||||
float angleTo = glm::angle(lookForward, glm::normalize(avatar->getHead()->getEyePosition() - getHead()->getEyePosition()));
|
||||
if (angleTo < (smallestAngleTo * (isCurrentTarget ? KEEP_LOOKING_AT_CURRENT_ANGLE_FACTOR : 1.0f))) {
|
||||
_lookAtTargetAvatar = avatarPointer;
|
||||
_targetAvatarPosition = avatarPointer->getPosition();
|
||||
|
@ -864,36 +865,29 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
|||
// Let's get everything to world space:
|
||||
glm::vec3 avatarLeftEye = getHead()->getLeftEyePosition();
|
||||
glm::vec3 avatarRightEye = getHead()->getRightEyePosition();
|
||||
// When not in HMD, these might both answer identity (i.e., the bridge of the nose). That's ok.
|
||||
// By my inpsection of the code and live testing, getEyeOffset and getEyePose are the same. (Application hands identity as offset matrix.)
|
||||
// This might be more work than needed for any given use, but as we explore different formulations, we go mad if we don't work in world space.
|
||||
glm::mat4 leftEye = qApp->getEyeOffset(Eye::Left);
|
||||
glm::mat4 rightEye = qApp->getEyeOffset(Eye::Right);
|
||||
glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]);
|
||||
glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]);
|
||||
auto humanSystem = qApp->getViewFrustum();
|
||||
glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal);
|
||||
glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal);
|
||||
|
||||
// First find out where (in world space) the person is looking relative to that bridge-of-the-avatar point.
|
||||
// (We will be adding that offset to the camera position, after making some other adjustments.)
|
||||
glm::vec3 gazeOffset = lookAtPosition - getHead()->getEyePosition();
|
||||
|
||||
// Scale by proportional differences between avatar and human.
|
||||
float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye);
|
||||
float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye);
|
||||
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation;
|
||||
// scale gazeOffset by IPD, if wearing an HMD.
|
||||
if (qApp->isHMDMode()) {
|
||||
glm::mat4 leftEye = qApp->getEyeOffset(Eye::Left);
|
||||
glm::mat4 rightEye = qApp->getEyeOffset(Eye::Right);
|
||||
glm::vec3 leftEyeHeadLocal = glm::vec3(leftEye[3]);
|
||||
glm::vec3 rightEyeHeadLocal = glm::vec3(rightEye[3]);
|
||||
auto humanSystem = qApp->getViewFrustum();
|
||||
glm::vec3 humanLeftEye = humanSystem->getPosition() + (humanSystem->getOrientation() * leftEyeHeadLocal);
|
||||
glm::vec3 humanRightEye = humanSystem->getPosition() + (humanSystem->getOrientation() * rightEyeHeadLocal);
|
||||
|
||||
// If the camera is also not oriented with the head, adjust by getting the offset in head-space...
|
||||
/* Not needed (i.e., code is a no-op), but I'm leaving the example code here in case something like this is needed someday.
|
||||
glm::quat avatarHeadOrientation = getHead()->getOrientation();
|
||||
glm::vec3 gazeOffsetLocalToHead = glm::inverse(avatarHeadOrientation) * gazeOffset;
|
||||
// ... and treat that as though it were in camera space, bringing it back to world space.
|
||||
// But camera is fudged to make the picture feel like the avatar's orientation.
|
||||
glm::quat humanOrientation = humanSystem->getOrientation(); // or just avatar getOrienation() ?
|
||||
gazeOffset = humanOrientation * gazeOffsetLocalToHead;
|
||||
glm::vec3 corrected = humanSystem->getPosition() + gazeOffset;
|
||||
*/
|
||||
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
float ipdScale = hmdInterface->getIPDScale();
|
||||
|
||||
// Scale by proportional differences between avatar and human.
|
||||
float humanEyeSeparationInModelSpace = glm::length(humanLeftEye - humanRightEye) * ipdScale;
|
||||
float avatarEyeSeparation = glm::length(avatarLeftEye - avatarRightEye);
|
||||
gazeOffset = gazeOffset * humanEyeSeparationInModelSpace / avatarEyeSeparation;
|
||||
}
|
||||
|
||||
// And now we can finally add that offset to the camera.
|
||||
glm::vec3 corrected = qApp->getViewFrustum()->getPosition() + gazeOffset;
|
||||
|
@ -1097,7 +1091,8 @@ void MyAvatar::prepareForPhysicsSimulation() {
|
|||
_characterController.setTargetVelocity(getTargetVelocity());
|
||||
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
|
||||
if (qApp->isHMDMode()) {
|
||||
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix);
|
||||
bool hasDriveInput = fabsf(_driveKeys[TRANSLATE_X]) > 0.0f || fabsf(_driveKeys[TRANSLATE_Z]) > 0.0f;
|
||||
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput);
|
||||
} else {
|
||||
_follow.deactivate();
|
||||
}
|
||||
|
@ -1339,11 +1334,11 @@ void MyAvatar::preRender(RenderArgs* renderArgs) {
|
|||
_prevShouldDrawHead = shouldDrawHead;
|
||||
}
|
||||
|
||||
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.6f;
|
||||
const float RENDER_HEAD_CUTOFF_DISTANCE = 0.3f;
|
||||
|
||||
bool MyAvatar::cameraInsideHead() const {
|
||||
const glm::vec3 cameraPosition = qApp->getCamera()->getPosition();
|
||||
return glm::length(cameraPosition - getDefaultEyePosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale());
|
||||
return glm::length(cameraPosition - getHeadPosition()) < (RENDER_HEAD_CUTOFF_DISTANCE * getUniformScale());
|
||||
}
|
||||
|
||||
bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
|
||||
|
@ -1776,25 +1771,6 @@ glm::quat MyAvatar::getWorldBodyOrientation() const {
|
|||
return glm::quat_cast(_sensorToWorldMatrix * _bodySensorMatrix);
|
||||
}
|
||||
|
||||
#if 0
|
||||
// derive avatar body position and orientation from the current HMD Sensor location.
|
||||
// results are in sensor space
|
||||
glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
|
||||
if (_rig) {
|
||||
// orientation
|
||||
const glm::quat hmdOrientation = getHMDSensorOrientation();
|
||||
const glm::quat yaw = cancelOutRollAndPitch(hmdOrientation);
|
||||
// position
|
||||
// we flip about yAxis when going from "root" to "avatar" frame
|
||||
// and we must also apply "yaw" to get into HMD frame
|
||||
glm::quat rotY180 = glm::angleAxis((float)M_PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||
glm::vec3 eyesInAvatarFrame = rotY180 * yaw * _rig->getEyesInRootFrame();
|
||||
glm::vec3 bodyPos = getHMDSensorPosition() - eyesInAvatarFrame;
|
||||
return createMatFromQuatAndPos(yaw, bodyPos);
|
||||
}
|
||||
return glm::mat4();
|
||||
}
|
||||
#else
|
||||
// old school meat hook style
|
||||
glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
|
||||
|
||||
|
@ -1835,7 +1811,6 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
|
|||
|
||||
return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos);
|
||||
}
|
||||
#endif
|
||||
|
||||
glm::vec3 MyAvatar::getPositionForAudio() {
|
||||
switch (_audioListenerMode) {
|
||||
|
@ -1884,58 +1859,132 @@ void MyAvatar::lateUpdatePalms() {
|
|||
|
||||
static const float FOLLOW_TIME = 0.5f;
|
||||
|
||||
void MyAvatar::FollowHelper::deactivate() {
|
||||
_timeRemaining = 0.0f;
|
||||
MyAvatar::FollowHelper::FollowHelper() {
|
||||
deactivate();
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::activate() {
|
||||
void MyAvatar::FollowHelper::deactivate() {
|
||||
for (int i = 0; i < NumFollowTypes; i++) {
|
||||
deactivate((FollowType)i);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::deactivate(FollowType type) {
|
||||
assert(type >= 0 && type < NumFollowTypes);
|
||||
_timeRemaining[(int)type] = 0.0f;
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::activate(FollowType type) {
|
||||
assert(type >= 0 && type < NumFollowTypes);
|
||||
// TODO: Perhaps, the follow time should be proportional to the displacement.
|
||||
_timeRemaining = FOLLOW_TIME;
|
||||
_timeRemaining[(int)type] = FOLLOW_TIME;
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::isActive(FollowType type) const {
|
||||
assert(type >= 0 && type < NumFollowTypes);
|
||||
return _timeRemaining[(int)type] > 0.0f;
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::isActive() const {
|
||||
return _timeRemaining > 0.0f;
|
||||
for (int i = 0; i < NumFollowTypes; i++) {
|
||||
if (isActive((FollowType)i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::shouldActivate(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
|
||||
float MyAvatar::FollowHelper::getMaxTimeRemaining() const {
|
||||
float max = 0.0f;
|
||||
for (int i = 0; i < NumFollowTypes; i++) {
|
||||
if (_timeRemaining[i] > max) {
|
||||
max = _timeRemaining[i];
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 4.0f);
|
||||
void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
|
||||
for (int i = 0; i < NumFollowTypes; i++) {
|
||||
_timeRemaining[i] -= dt;
|
||||
}
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
|
||||
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
|
||||
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
|
||||
if (glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD) {
|
||||
return glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
|
||||
|
||||
// -z axis of currentBodyMatrix in world space.
|
||||
glm::vec3 forward = glm::normalize(glm::vec3(-currentBodyMatrix[0][2], -currentBodyMatrix[1][2], -currentBodyMatrix[2][2]));
|
||||
// x axis of currentBodyMatrix in world space.
|
||||
glm::vec3 right = glm::normalize(glm::vec3(currentBodyMatrix[0][0], currentBodyMatrix[1][0], currentBodyMatrix[2][0]));
|
||||
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
|
||||
|
||||
float forwardLeanAmount = glm::dot(forward, offset);
|
||||
float lateralLeanAmount = glm::dot(right, offset);
|
||||
|
||||
const float MAX_LATERAL_LEAN = 0.3f;
|
||||
const float MAX_FORWARD_LEAN = 0.15f;
|
||||
const float MAX_BACKWARD_LEAN = 0.1f;
|
||||
|
||||
if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) {
|
||||
return true;
|
||||
} else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const float CYLINDER_TOP = 0.1f;
|
||||
const float CYLINDER_BOTTOM = -0.5f;
|
||||
const float CYLINDER_RADIUS = 0.15f;
|
||||
|
||||
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
|
||||
glm::vec3 radialOffset(offset.x, 0.0f, offset.z);
|
||||
float radialDistance = glm::length(radialOffset);
|
||||
|
||||
return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM) || (radialDistance > CYLINDER_RADIUS);
|
||||
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) {
|
||||
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
|
||||
|
||||
const float CYLINDER_TOP = 0.1f;
|
||||
const float CYLINDER_BOTTOM = -1.5f;
|
||||
|
||||
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
|
||||
return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
||||
_desiredBodyMatrix = desiredBodyMatrix;
|
||||
if (!isActive() && shouldActivate(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate();
|
||||
if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate(Rotation);
|
||||
}
|
||||
if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate(Horizontal);
|
||||
}
|
||||
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||
activate(Vertical);
|
||||
}
|
||||
|
||||
if (isActive()) {
|
||||
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix;
|
||||
myAvatar.getCharacterController()->setFollowParameters(desiredWorldMatrix, _timeRemaining);
|
||||
} else {
|
||||
glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix;
|
||||
myAvatar.getCharacterController()->setFollowParameters(currentWorldMatrix, 0.0f);
|
||||
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix;
|
||||
glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix;
|
||||
|
||||
AnimPose followWorldPose(currentWorldMatrix);
|
||||
if (isActive(Rotation)) {
|
||||
followWorldPose.rot = glmExtractRotation(desiredWorldMatrix);
|
||||
}
|
||||
if (isActive(Horizontal)) {
|
||||
glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix);
|
||||
followWorldPose.trans.x = desiredTranslation.x;
|
||||
followWorldPose.trans.z = desiredTranslation.z;
|
||||
}
|
||||
if (isActive(Vertical)) {
|
||||
glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix);
|
||||
followWorldPose.trans.y = desiredTranslation.y;
|
||||
}
|
||||
|
||||
myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining());
|
||||
}
|
||||
|
||||
glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) {
|
||||
if (isActive()) {
|
||||
float dt = myAvatar.getCharacterController()->getFollowTime();
|
||||
_timeRemaining -= dt;
|
||||
decrementTimeRemaining(dt);
|
||||
|
||||
// apply follow displacement to the body matrix.
|
||||
glm::vec3 worldLinearDisplacement = myAvatar.getCharacterController()->getFollowLinearDisplacement();
|
||||
|
|
|
@ -392,14 +392,29 @@ private:
|
|||
glm::mat4 _sensorToWorldMatrix;
|
||||
|
||||
struct FollowHelper {
|
||||
FollowHelper();
|
||||
|
||||
enum FollowType {
|
||||
Rotation = 0,
|
||||
Horizontal,
|
||||
Vertical,
|
||||
NumFollowTypes
|
||||
};
|
||||
glm::mat4 _desiredBodyMatrix;
|
||||
float _timeRemaining { 0.0f };
|
||||
float _timeRemaining[NumFollowTypes];
|
||||
|
||||
void deactivate();
|
||||
void deactivate(FollowType type);
|
||||
void activate();
|
||||
void activate(FollowType type);
|
||||
bool isActive() const;
|
||||
bool shouldActivate(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix);
|
||||
bool isActive(FollowType followType) const;
|
||||
float getMaxTimeRemaining() const;
|
||||
void decrementTimeRemaining(float dt);
|
||||
bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
|
||||
glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
|
||||
};
|
||||
FollowHelper _follow;
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
WindowScriptingInterface::WindowScriptingInterface() {
|
||||
const DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::connectedToDomain, this, &WindowScriptingInterface::domainChanged);
|
||||
connect(qApp, &Application::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
|
||||
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &WindowScriptingInterface::domainConnectionRefused);
|
||||
|
||||
connect(qApp, &Application::svoImportRequested, [this](const QString& urlString) {
|
||||
static const QMetaMethod svoImportRequestedSignal =
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue