mirror of
https://github.com/lubosz/overte.git
synced 2025-04-26 21:55:25 +02:00
Merge branch 'master' of git://github.com/highfidelity/hifi into 19644
Conflicts: interface/src/Application.cpp interface/ui/preferencesDialog.ui
This commit is contained in:
commit
4115e0fab2
128 changed files with 3747 additions and 1434 deletions
.gitignore
animation-server
assignment-client
cmake/modules
domain-server
examples
animatedModelExample.jsbot.jseditModels.jseditVoxels.jsgun.jshydraMove.jsplaceModelsWithHands.jssit.js
interface
CMakeLists.txt
external/priovr
resources/shaders
src
AbstractLoggerInterface.hApplication.cppApplication.hAudio.cppFileLogger.cppFileLogger.hMenu.cppMenu.h
avatar
devices
models
particles
renderer
scripting
ui
AnimationsDialog.cppAnimationsDialog.hLogDialog.cppModelsBrowser.cppNodeBounds.cppNodeBounds.hOAuthWebViewHandler.cppPreferencesDialog.cpp
overlays
voxels
ui
libraries
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -46,5 +46,9 @@ interface/resources/visage/*
|
|||
interface/external/faceplus/*
|
||||
!interface/external/faceplus/readme.txt
|
||||
|
||||
# Ignore PrioVR
|
||||
interface/external/priovr/*
|
||||
!interface/external/priovr/readme.txt
|
||||
|
||||
# Ignore interfaceCache for Linux users
|
||||
interface/interfaceCache/
|
||||
|
|
|
@ -32,15 +32,7 @@ link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
|||
# link the hifi networking library
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
find_package(GnuTLS REQUIRED)
|
||||
|
||||
# include the GnuTLS dir
|
||||
include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}")
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
# link GnuTLS
|
||||
target_link_libraries(${TARGET_NAME} "${GNUTLS_LIBRARY}")
|
||||
endif ()
|
|
@ -33,18 +33,10 @@ link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
find_package(GnuTLS REQUIRED)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}")
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})
|
||||
endif (UNIX)
|
||||
|
@ -53,4 +45,9 @@ IF (WIN32)
|
|||
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
|
||||
ENDIF(WIN32)
|
||||
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}")
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
|
@ -250,6 +250,12 @@ void Agent::run() {
|
|||
_particleViewer.init();
|
||||
_scriptEngine.getParticlesScriptingInterface()->setParticleTree(_particleViewer.getTree());
|
||||
|
||||
_scriptEngine.registerGlobalObject("ModelViewer", &_modelViewer);
|
||||
JurisdictionListener* modelJL = _scriptEngine.getModelsScriptingInterface()->getJurisdictionListener();
|
||||
_modelViewer.setJurisdictionListener(modelJL);
|
||||
_modelViewer.init();
|
||||
_scriptEngine.getModelsScriptingInterface()->setModelTree(_modelViewer.getTree());
|
||||
|
||||
_scriptEngine.setScriptContents(scriptContents);
|
||||
_scriptEngine.run();
|
||||
setFinished(true);
|
||||
|
|
|
@ -13,8 +13,6 @@
|
|||
#include <QtCore/QThread>
|
||||
#include <QtCore/QTimer>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <Assignment.h>
|
||||
#include <Logging.h>
|
||||
|
@ -39,8 +37,6 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
|
|||
QCoreApplication(argc, argv),
|
||||
_assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME)
|
||||
{
|
||||
DTLSClientSession::globalInit();
|
||||
|
||||
setOrganizationName("High Fidelity");
|
||||
setOrganizationDomain("highfidelity.io");
|
||||
setApplicationName("assignment-client");
|
||||
|
@ -71,9 +67,20 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
|
|||
if (argumentIndex != -1) {
|
||||
assignmentPool = argumentList[argumentIndex + 1];
|
||||
}
|
||||
|
||||
// setup our _requestAssignment member variable from the passed arguments
|
||||
_requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool);
|
||||
|
||||
// check if we were passed a wallet UUID on the command line
|
||||
// this would represent where the user running AC wants funds sent to
|
||||
|
||||
const QString ASSIGNMENT_WALLET_DESTINATION_ID_OPTION = "--wallet";
|
||||
if ((argumentIndex = argumentList.indexOf(ASSIGNMENT_WALLET_DESTINATION_ID_OPTION)) != -1) {
|
||||
QUuid walletUUID = QString(argumentList[argumentIndex + 1]);
|
||||
qDebug() << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
|
||||
_requestAssignment.setWalletUUID(walletUUID);
|
||||
}
|
||||
|
||||
// create a NodeList as an unassigned client
|
||||
NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned);
|
||||
|
||||
|
@ -106,10 +113,6 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
|
|||
this, &AssignmentClient::handleAuthenticationRequest);
|
||||
}
|
||||
|
||||
AssignmentClient::~AssignmentClient() {
|
||||
DTLSClientSession::globalDeinit();
|
||||
}
|
||||
|
||||
void AssignmentClient::sendAssignmentRequest() {
|
||||
if (!_currentAssignment) {
|
||||
NodeList::getInstance()->sendAssignment(_requestAssignment);
|
||||
|
|
|
@ -21,7 +21,6 @@ class AssignmentClient : public QCoreApplication {
|
|||
public:
|
||||
AssignmentClient(int &argc, char **argv);
|
||||
static const SharedAssignmentPointer& getCurrentAssignment() { return _currentAssignment; }
|
||||
~AssignmentClient();
|
||||
private slots:
|
||||
void sendAssignmentRequest();
|
||||
void readPendingDatagrams();
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
#
|
||||
# FindGnuTLS.cmake
|
||||
#
|
||||
# Try to find the GnuTLS library
|
||||
#
|
||||
# You can provide a GNUTLS_ROOT_DIR which contains lib and include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# GNUTLS_FOUND - system found GnuTLS
|
||||
# GNUTLS_INCLUDE_DIR - the GnuTLS include directory
|
||||
# GNUTLS_LIBRARY - Link this to use GnuTLS
|
||||
#
|
||||
# Created on 3/31/2014 by Stephen Birarda
|
||||
# Copyright (c) 2014 High Fidelity
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
if (GNUTLS_LIBRARY AND GNUTLS_INCLUDE_DIRS)
|
||||
# in cache already
|
||||
set(GNUTLS_FOUND TRUE)
|
||||
else ()
|
||||
set(GNUTLS_SEARCH_DIRS "${GNUTLS_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/gnutls")
|
||||
|
||||
find_path(GNUTLS_INCLUDE_DIR gnutls/gnutls.h PATH_SUFFIXES include HINTS ${GNUTLS_SEARCH_DIRS})
|
||||
|
||||
find_library(GNUTLS_LIBRARY NAMES gnutls libgnutls libgnutls-28 PATH_SUFFIXES lib HINTS ${GNUTLS_SEARCH_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(GNUTLS DEFAULT_MSG GNUTLS_INCLUDE_DIR GNUTLS_LIBRARY)
|
||||
|
||||
if (WIN32 AND NOT GNUTLS_FOUND)
|
||||
message(STATUS "If you're generating a MSVC environment, you'll need to run the command")
|
||||
message(STATUS "$GnuTLS-DIR\\bin>lib /def:libgnutls-28.def")
|
||||
message(STATUS "From the MSVC command prompt to generate the .lib file and copy it into")
|
||||
message(STATUS "the GnuTLS lib folder. Replace $GnuTLS-DIR in the command with the directory")
|
||||
message(STATUS "containing GnuTLS.")
|
||||
endif ()
|
||||
endif ()
|
|
@ -49,7 +49,7 @@ else (LIBOVR_LIBRARIES AND LIBOVR_INCLUDE_DIRS)
|
|||
set(WINDOWS_LIBOVR_NAME "libovr.lib")
|
||||
endif()
|
||||
|
||||
find_library(LIBOVR_LIBRARIES "Lib/Win32/VS2010/${LIBOVR_NAME}" HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
find_library(LIBOVR_LIBRARIES "Lib/Win32/${WINDOWS_LIBOVR_NAME}" HINTS ${LIBOVR_SEARCH_DIRS})
|
||||
endif ()
|
||||
|
||||
if (LIBOVR_INCLUDE_DIRS AND LIBOVR_LIBRARIES)
|
||||
|
|
42
cmake/modules/FindPrioVR.cmake
Normal file
42
cmake/modules/FindPrioVR.cmake
Normal file
|
@ -0,0 +1,42 @@
|
|||
# Try to find the PrioVT library
|
||||
#
|
||||
# You must provide a PRIOVR_ROOT_DIR which contains lib and include directories
|
||||
#
|
||||
# Once done this will define
|
||||
#
|
||||
# PRIOVR_FOUND - system found PrioVR
|
||||
# PRIOVR_INCLUDE_DIRS - the PrioVR include directory
|
||||
# PRIOVR_LIBRARIES - Link this to use PrioVR
|
||||
#
|
||||
# Created on 5/12/2014 by Andrzej Kapolka
|
||||
# Copyright (c) 2014 High Fidelity
|
||||
#
|
||||
|
||||
if (PRIOVR_LIBRARIES AND PRIOVR_INCLUDE_DIRS)
|
||||
# in cache already
|
||||
set(PRIOVR_FOUND TRUE)
|
||||
else (PRIOVR_LIBRARIES AND PRIOVR_INCLUDE_DIRS)
|
||||
find_path(PRIOVR_INCLUDE_DIRS yei_skeletal_api.h ${PRIOVR_ROOT_DIR}/include)
|
||||
|
||||
if (WIN32)
|
||||
find_library(PRIOVR_LIBRARIES Skeletal_API.lib ${PRIOVR_ROOT_DIR}/lib)
|
||||
endif (WIN32)
|
||||
|
||||
if (PRIOVR_INCLUDE_DIRS AND PRIOVR_LIBRARIES)
|
||||
set(PRIOVR_FOUND TRUE)
|
||||
endif (PRIOVR_INCLUDE_DIRS AND PRIOVR_LIBRARIES)
|
||||
|
||||
if (PRIOVR_FOUND)
|
||||
if (NOT PRIOVR_FIND_QUIETLY)
|
||||
message(STATUS "Found PrioVR... ${PRIOVR_LIBRARIES}")
|
||||
endif (NOT PRIOVR_FIND_QUIETLY)
|
||||
else ()
|
||||
if (PRIOVR_FIND_REQUIRED)
|
||||
message(FATAL_ERROR "Could not find PrioVR")
|
||||
endif (PRIOVR_FIND_REQUIRED)
|
||||
endif ()
|
||||
|
||||
# show the PRIOVR_INCLUDE_DIRS and PRIOVR_LIBRARIES variables only in the advanced view
|
||||
mark_as_advanced(PRIOVR_INCLUDE_DIRS PRIOVR_LIBRARIES)
|
||||
|
||||
endif (PRIOVR_LIBRARIES AND PRIOVR_INCLUDE_DIRS)
|
|
@ -17,7 +17,6 @@ include(${MACRO_DIR}/IncludeGLM.cmake)
|
|||
include_glm(${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
find_package(Qt5Network REQUIRED)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiProject.cmake)
|
||||
|
||||
|
@ -38,17 +37,14 @@ link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(embedded-webserver ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# include the GnuTLS dir
|
||||
include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}")
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
IF (WIN32)
|
||||
target_link_libraries(${TARGET_NAME} Winmm Ws2_32)
|
||||
ENDIF(WIN32)
|
||||
|
||||
# link QtNetwork and GnuTLS
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}")
|
||||
# link QtNetwork
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
|
@ -13,6 +13,7 @@
|
|||
<th>Public</th>
|
||||
<th>Local</th>
|
||||
<th>Uptime (s)</th>
|
||||
<th>Pending Credits</th>
|
||||
<th>Kill?</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
|
|
@ -42,6 +42,8 @@ $(document).ready(function(){
|
|||
var uptimeSeconds = (Date.now() - data.wake_timestamp) / 1000;
|
||||
nodesTableBody += "<td>" + uptimeSeconds.toLocaleString() + "</td>";
|
||||
|
||||
nodesTableBody += "<td>" + (typeof data.pending_credits == 'number' ? data.pending_credits.toLocaleString() : 'N/A') + "</td>";
|
||||
|
||||
nodesTableBody += "<td><span class='glyphicon glyphicon-remove' data-uuid=" + data.uuid + "></span></td>";
|
||||
nodesTableBody += "</tr>";
|
||||
});
|
||||
|
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// DTLSServerSession.cpp
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-01.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "DTLSServerSession.h"
|
||||
|
||||
DTLSServerSession::DTLSServerSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) :
|
||||
DTLSSession(GNUTLS_SERVER, dtlsSocket, destinationSocket)
|
||||
{
|
||||
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// DTLSServerSession.h
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-01.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_DTLSServerSession_h
|
||||
#define hifi_DTLSServerSession_h
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
#include <DTLSSession.h>
|
||||
|
||||
class DTLSServerSession : public DTLSSession {
|
||||
public:
|
||||
DTLSServerSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket);
|
||||
};
|
||||
|
||||
#endif // hifi_DTLSServerSession_h
|
|
@ -18,8 +18,6 @@
|
|||
#include <QtCore/QTimer>
|
||||
#include <QtCore/QUrlQuery>
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <HifiConfigVariantMap.h>
|
||||
#include <HTTPConnection.h>
|
||||
|
@ -28,7 +26,6 @@
|
|||
#include <UUID.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
#include "DummyDTLSSession.h"
|
||||
|
||||
#include "DomainServer.h"
|
||||
|
||||
|
@ -38,19 +35,14 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_httpsManager(NULL),
|
||||
_allAssignments(),
|
||||
_unfulfilledAssignments(),
|
||||
_pendingAssignedNodes(),
|
||||
_isUsingDTLS(false),
|
||||
_x509Credentials(NULL),
|
||||
_dhParams(NULL),
|
||||
_priorityCache(NULL),
|
||||
_dtlsSessions(),
|
||||
_oauthProviderURL(),
|
||||
_oauthClientID(),
|
||||
_hostname(),
|
||||
_networkReplyUUIDMap(),
|
||||
_sessionAuthenticationHash()
|
||||
{
|
||||
gnutls_global_init();
|
||||
|
||||
setOrganizationName("High Fidelity");
|
||||
setOrganizationDomain("highfidelity.io");
|
||||
setApplicationName("domain-server");
|
||||
|
@ -58,37 +50,17 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
|
||||
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth()) {
|
||||
// we either read a certificate and private key or were not passed one, good to load assignments
|
||||
// and set up the node list
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
|
||||
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
|
||||
// we either read a certificate and private key or were not passed one
|
||||
// and completed login or did not need to
|
||||
|
||||
qDebug() << "Setting up LimitedNodeList and assignments.";
|
||||
setupNodeListAndAssignments();
|
||||
|
||||
if (_isUsingDTLS) {
|
||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||
|
||||
// connect our socket to read datagrams received on the DTLS socket
|
||||
connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams);
|
||||
}
|
||||
|
||||
_networkAccessManager = new QNetworkAccessManager(this);
|
||||
}
|
||||
}
|
||||
|
||||
DomainServer::~DomainServer() {
|
||||
if (_x509Credentials) {
|
||||
gnutls_certificate_free_credentials(*_x509Credentials);
|
||||
gnutls_priority_deinit(*_priorityCache);
|
||||
gnutls_dh_params_deinit(*_dhParams);
|
||||
|
||||
delete _x509Credentials;
|
||||
delete _priorityCache;
|
||||
delete _dhParams;
|
||||
delete _cookieKey;
|
||||
}
|
||||
gnutls_global_deinit();
|
||||
}
|
||||
|
||||
bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
||||
const QString X509_CERTIFICATE_OPTION = "cert";
|
||||
const QString X509_PRIVATE_KEY_OPTION = "key";
|
||||
|
@ -100,28 +72,28 @@ bool DomainServer::optionallyReadX509KeyAndCertificate() {
|
|||
if (!certPath.isEmpty() && !keyPath.isEmpty()) {
|
||||
// the user wants to use DTLS to encrypt communication with nodes
|
||||
// let's make sure we can load the key and certificate
|
||||
_x509Credentials = new gnutls_certificate_credentials_t;
|
||||
gnutls_certificate_allocate_credentials(_x509Credentials);
|
||||
// _x509Credentials = new gnutls_certificate_credentials_t;
|
||||
// gnutls_certificate_allocate_credentials(_x509Credentials);
|
||||
|
||||
QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV);
|
||||
|
||||
qDebug() << "Reading certificate file at" << certPath << "for DTLS.";
|
||||
qDebug() << "Reading key file at" << keyPath << "for DTLS.";
|
||||
|
||||
int gnutlsReturn = gnutls_certificate_set_x509_key_file2(*_x509Credentials,
|
||||
certPath.toLocal8Bit().constData(),
|
||||
keyPath.toLocal8Bit().constData(),
|
||||
GNUTLS_X509_FMT_PEM,
|
||||
keyPassphraseString.toLocal8Bit().constData(),
|
||||
0);
|
||||
// int gnutlsReturn = gnutls_certificate_set_x509_key_file2(*_x509Credentials,
|
||||
// certPath.toLocal8Bit().constData(),
|
||||
// keyPath.toLocal8Bit().constData(),
|
||||
// GNUTLS_X509_FMT_PEM,
|
||||
// keyPassphraseString.toLocal8Bit().constData(),
|
||||
// 0);
|
||||
//
|
||||
// if (gnutlsReturn < 0) {
|
||||
// qDebug() << "Unable to load certificate or key file." << "Error" << gnutlsReturn << "- domain-server will now quit.";
|
||||
// QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
if (gnutlsReturn < 0) {
|
||||
qDebug() << "Unable to load certificate or key file." << "Error" << gnutlsReturn << "- domain-server will now quit.";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
qDebug() << "Successfully read certificate and private key.";
|
||||
// qDebug() << "Successfully read certificate and private key.";
|
||||
|
||||
// we need to also pass this certificate and private key to the HTTPS manager
|
||||
// this is used for Oauth callbacks when authorizing users against a data server
|
||||
|
@ -159,7 +131,7 @@ bool DomainServer::optionallySetupOAuth() {
|
|||
_oauthClientSecret = QProcessEnvironment::systemEnvironment().value(OAUTH_CLIENT_SECRET_ENV);
|
||||
_hostname = _argumentVariantMap.value(REDIRECT_HOSTNAME_OPTION).toString();
|
||||
|
||||
if (!_oauthProviderURL.isEmpty() || !_hostname.isEmpty() || !_oauthClientID.isEmpty()) {
|
||||
if (!_oauthClientID.isEmpty()) {
|
||||
if (_oauthProviderURL.isEmpty()
|
||||
|| _hostname.isEmpty()
|
||||
|| _oauthClientID.isEmpty()
|
||||
|
@ -176,39 +148,6 @@ bool DomainServer::optionallySetupOAuth() {
|
|||
return true;
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupDTLS() {
|
||||
if (_x509Credentials) {
|
||||
qDebug() << "Generating Diffie-Hellman parameters.";
|
||||
|
||||
// generate Diffie-Hellman parameters
|
||||
// When short bit length is used, it might be wise to regenerate parameters often.
|
||||
int dhBits = gnutls_sec_param_to_pk_bits(GNUTLS_PK_DH, GNUTLS_SEC_PARAM_LEGACY);
|
||||
|
||||
_dhParams = new gnutls_dh_params_t;
|
||||
gnutls_dh_params_init(_dhParams);
|
||||
gnutls_dh_params_generate2(*_dhParams, dhBits);
|
||||
|
||||
qDebug() << "Successfully generated Diffie-Hellman parameters.";
|
||||
|
||||
// set the D-H paramters on the X509 credentials
|
||||
gnutls_certificate_set_dh_params(*_x509Credentials, *_dhParams);
|
||||
|
||||
// setup the key used for cookie verification
|
||||
_cookieKey = new gnutls_datum_t;
|
||||
gnutls_key_generate(_cookieKey, GNUTLS_COOKIE_KEY_SIZE);
|
||||
|
||||
_priorityCache = new gnutls_priority_t;
|
||||
const char DTLS_PRIORITY_STRING[] = "PERFORMANCE:-VERS-TLS-ALL:+VERS-DTLS1.2:%SERVER_PRECEDENCE";
|
||||
gnutls_priority_init(_priorityCache, DTLS_PRIORITY_STRING, NULL);
|
||||
|
||||
_isUsingDTLS = true;
|
||||
|
||||
qDebug() << "Initial DTLS setup complete.";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
||||
|
||||
const QString CUSTOM_PORT_OPTION = "port";
|
||||
|
@ -250,6 +189,65 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) {
|
|||
addStaticAssignmentsToQueue();
|
||||
}
|
||||
|
||||
bool DomainServer::optionallySetupAssignmentPayment() {
|
||||
// check if we have a username and password set via env
|
||||
const QString PAY_FOR_ASSIGNMENTS_OPTION = "pay-for-assignments";
|
||||
const QString HIFI_USERNAME_ENV_KEY = "DOMAIN_SERVER_USERNAME";
|
||||
const QString HIFI_PASSWORD_ENV_KEY = "DOMAIN_SERVER_PASSWORD";
|
||||
|
||||
if (_argumentVariantMap.contains(PAY_FOR_ASSIGNMENTS_OPTION)) {
|
||||
if (!_oauthProviderURL.isEmpty()) {
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
accountManager.setAuthURL(_oauthProviderURL);
|
||||
|
||||
if (!accountManager.hasValidAccessToken()) {
|
||||
// we don't have a valid access token so we need to get one
|
||||
QString username = QProcessEnvironment::systemEnvironment().value(HIFI_USERNAME_ENV_KEY);
|
||||
QString password = QProcessEnvironment::systemEnvironment().value(HIFI_PASSWORD_ENV_KEY);
|
||||
|
||||
if (!username.isEmpty() && !password.isEmpty()) {
|
||||
accountManager.requestAccessToken(username, password);
|
||||
|
||||
// connect to loginFailed signal from AccountManager so we can quit if that is the case
|
||||
connect(&accountManager, &AccountManager::loginFailed, this, &DomainServer::loginFailed);
|
||||
} else {
|
||||
qDebug() << "Missing access-token or username and password combination. domain-server will now quit.";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
} else {
|
||||
qDebug() << "Missing OAuth provider URL, but assigned node payment was enabled. domain-server will now quit.";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DomainServer::loginFailed() {
|
||||
qDebug() << "Login to data server has failed. domain-server will now quit";
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes) {
|
||||
// check for configs from the command line, these take precedence
|
||||
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
|
||||
|
@ -402,10 +400,23 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
|||
QUuid packetUUID = uuidFromPacketHeader(packet);
|
||||
|
||||
// check if this connect request matches an assignment in the queue
|
||||
bool isFulfilledOrUnfulfilledAssignment = _allAssignments.contains(packetUUID);
|
||||
bool isAssignment = _pendingAssignedNodes.contains(packetUUID);
|
||||
SharedAssignmentPointer matchingQueuedAssignment = SharedAssignmentPointer();
|
||||
if (isFulfilledOrUnfulfilledAssignment) {
|
||||
matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(packetUUID, nodeType);
|
||||
PendingAssignedNodeData* pendingAssigneeData = NULL;
|
||||
|
||||
if (isAssignment) {
|
||||
pendingAssigneeData = _pendingAssignedNodes.take(packetUUID);
|
||||
|
||||
if (pendingAssigneeData) {
|
||||
matchingQueuedAssignment = matchingQueuedAssignmentForCheckIn(pendingAssigneeData->getAssignmentUUID(), nodeType);
|
||||
|
||||
if (matchingQueuedAssignment) {
|
||||
qDebug() << "Assignment deployed with" << uuidStringWithoutCurlyBraces(packetUUID)
|
||||
<< "matches unfulfilled assignment"
|
||||
<< uuidStringWithoutCurlyBraces(matchingQueuedAssignment->getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!matchingQueuedAssignment && !_oauthProviderURL.isEmpty() && _argumentVariantMap.contains(ALLOWED_ROLES_CONFIG_KEY)) {
|
||||
|
@ -434,8 +445,8 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
|||
}
|
||||
}
|
||||
|
||||
if ((!isFulfilledOrUnfulfilledAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
||||
|| (isFulfilledOrUnfulfilledAssignment && matchingQueuedAssignment)) {
|
||||
if ((!isAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType))
|
||||
|| (isAssignment && matchingQueuedAssignment)) {
|
||||
// this was either not a static assignment or it was and we had a matching one in the queue
|
||||
|
||||
// create a new session UUID for this node
|
||||
|
@ -447,8 +458,12 @@ void DomainServer::handleConnectRequest(const QByteArray& packet, const HifiSock
|
|||
// if this was a static assignment set the UUID, set the sendingSockAddr
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(newNode->getLinkedData());
|
||||
|
||||
if (isFulfilledOrUnfulfilledAssignment) {
|
||||
nodeData->setAssignmentUUID(packetUUID);
|
||||
if (isAssignment) {
|
||||
nodeData->setAssignmentUUID(matchingQueuedAssignment->getUUID());
|
||||
nodeData->setWalletUUID(pendingAssigneeData->getWalletUUID());
|
||||
|
||||
// now that we've pulled the wallet UUID and added the node to our list, delete the pending assignee data
|
||||
delete pendingAssigneeData;
|
||||
}
|
||||
|
||||
nodeData->setSendingSockAddr(senderSockAddr);
|
||||
|
@ -552,8 +567,8 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
|
||||
if (nodeInterestList.size() > 0) {
|
||||
|
||||
DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
|
||||
int dataMTU = dtlsSession ? (int)gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE;
|
||||
// DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL;
|
||||
int dataMTU = MAX_PACKET_SIZE;
|
||||
|
||||
if (nodeData->isAuthenticated()) {
|
||||
// if this authenticated node has any interest types, send back those nodes as well
|
||||
|
@ -589,11 +604,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
// we need to break here and start a new packet
|
||||
// so send the current one
|
||||
|
||||
if (!dtlsSession) {
|
||||
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
|
||||
} else {
|
||||
dtlsSession->writeDatagram(broadcastPacket);
|
||||
}
|
||||
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
|
||||
|
||||
// reset the broadcastPacket structure
|
||||
broadcastPacket.resize(numBroadcastPacketLeadBytes);
|
||||
|
@ -607,11 +618,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif
|
|||
}
|
||||
|
||||
// always write the last broadcastPacket
|
||||
if (!dtlsSession) {
|
||||
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
|
||||
} else {
|
||||
dtlsSession->writeDatagram(broadcastPacket);
|
||||
}
|
||||
nodeList->writeDatagram(broadcastPacket, node, senderSockAddr);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -635,14 +642,21 @@ void DomainServer::readAvailableDatagrams() {
|
|||
Assignment requestAssignment(receivedPacket);
|
||||
|
||||
// Suppress these for Assignment::AgentType to once per 5 seconds
|
||||
static quint64 lastNoisyMessage = usecTimestampNow();
|
||||
quint64 timeNow = usecTimestampNow();
|
||||
const quint64 NOISY_TIME_ELAPSED = 5 * USECS_PER_SECOND;
|
||||
bool noisyMessage = false;
|
||||
if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) {
|
||||
static QElapsedTimer noisyMessageTimer;
|
||||
static bool wasNoisyTimerStarted = false;
|
||||
|
||||
if (!wasNoisyTimerStarted) {
|
||||
noisyMessageTimer.start();
|
||||
wasNoisyTimerStarted = true;
|
||||
}
|
||||
|
||||
const quint64 NOISY_MESSAGE_INTERVAL_MSECS = 5 * 1000;
|
||||
|
||||
if (requestAssignment.getType() != Assignment::AgentType
|
||||
|| noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
|
||||
qDebug() << "Received a request for assignment type" << requestAssignment.getType()
|
||||
<< "from" << senderSockAddr;
|
||||
noisyMessage = true;
|
||||
<< "from" << senderSockAddr;
|
||||
noisyMessageTimer.restart();
|
||||
}
|
||||
|
||||
SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment);
|
||||
|
@ -653,23 +667,29 @@ void DomainServer::readAvailableDatagrams() {
|
|||
// give this assignment out, either the type matches or the requestor said they will take any
|
||||
assignmentPacket.resize(numAssignmentPacketHeaderBytes);
|
||||
|
||||
// setup a copy of this assignment that will have a unique UUID, for packaging purposes
|
||||
Assignment uniqueAssignment(*assignmentToDeploy.data());
|
||||
uniqueAssignment.setUUID(QUuid::createUuid());
|
||||
|
||||
QDataStream assignmentStream(&assignmentPacket, QIODevice::Append);
|
||||
|
||||
assignmentStream << *assignmentToDeploy.data();
|
||||
assignmentStream << uniqueAssignment;
|
||||
|
||||
nodeList->getNodeSocket().writeDatagram(assignmentPacket,
|
||||
senderSockAddr.getAddress(), senderSockAddr.getPort());
|
||||
|
||||
// add the information for that deployed assignment to the hash of pending assigned nodes
|
||||
PendingAssignedNodeData* pendingNodeData = new PendingAssignedNodeData(assignmentToDeploy->getUUID(),
|
||||
requestAssignment.getWalletUUID());
|
||||
_pendingAssignedNodes.insert(uniqueAssignment.getUUID(), pendingNodeData);
|
||||
} else {
|
||||
if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) {
|
||||
if (requestAssignment.getType() != Assignment::AgentType
|
||||
|| noisyMessageTimer.elapsed() > NOISY_MESSAGE_INTERVAL_MSECS) {
|
||||
qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType()
|
||||
<< "from" << senderSockAddr;
|
||||
noisyMessage = true;
|
||||
<< "from" << senderSockAddr;
|
||||
noisyMessageTimer.restart();
|
||||
}
|
||||
}
|
||||
|
||||
if (noisyMessage) {
|
||||
lastNoisyMessage = timeNow;
|
||||
}
|
||||
} else if (!_isUsingDTLS) {
|
||||
// not using DTLS, process datagram normally
|
||||
processDatagram(receivedPacket, senderSockAddr);
|
||||
|
@ -689,81 +709,92 @@ void DomainServer::readAvailableDatagrams() {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServer::readAvailableDTLSDatagrams() {
|
||||
LimitedNodeList* nodeList = LimitedNodeList::getInstance();
|
||||
|
||||
QUdpSocket& dtlsSocket = nodeList->getDTLSSocket();
|
||||
|
||||
static sockaddr senderSockAddr;
|
||||
static socklen_t sockAddrSize = sizeof(senderSockAddr);
|
||||
|
||||
while (dtlsSocket.hasPendingDatagrams()) {
|
||||
// check if we have an active DTLS session for this sender
|
||||
QByteArray peekDatagram(dtlsSocket.pendingDatagramSize(), 0);
|
||||
|
||||
recvfrom(dtlsSocket.socketDescriptor(), peekDatagram.data(), dtlsSocket.pendingDatagramSize(),
|
||||
MSG_PEEK, &senderSockAddr, &sockAddrSize);
|
||||
void DomainServer::setupPendingAssignmentCredits() {
|
||||
// enumerate the NodeList to find the assigned nodes
|
||||
foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) {
|
||||
DomainServerNodeData* nodeData = reinterpret_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
|
||||
HifiSockAddr senderHifiSockAddr(&senderSockAddr);
|
||||
DTLSServerSession* existingSession = _dtlsSessions.value(senderHifiSockAddr);
|
||||
|
||||
if (existingSession) {
|
||||
if (!existingSession->completedHandshake()) {
|
||||
// check if we have completed handshake with this user
|
||||
int handshakeReturn = gnutls_handshake(*existingSession->getGnuTLSSession());
|
||||
|
||||
if (handshakeReturn == 0) {
|
||||
existingSession->setCompletedHandshake(true);
|
||||
} else if (gnutls_error_is_fatal(handshakeReturn)) {
|
||||
// this was a fatal error handshaking, so remove this session
|
||||
qDebug() << "Fatal error -" << gnutls_strerror(handshakeReturn) << "- during DTLS handshake with"
|
||||
<< senderHifiSockAddr;
|
||||
_dtlsSessions.remove(senderHifiSockAddr);
|
||||
}
|
||||
} else {
|
||||
// pull the data from this user off the stack and process it
|
||||
int receivedBytes = gnutls_record_recv(*existingSession->getGnuTLSSession(),
|
||||
peekDatagram.data(), peekDatagram.size());
|
||||
if (receivedBytes > 0) {
|
||||
processDatagram(peekDatagram.left(receivedBytes), senderHifiSockAddr);
|
||||
} else if (gnutls_error_is_fatal(receivedBytes)) {
|
||||
qDebug() << "Fatal error -" << gnutls_strerror(receivedBytes) << "- during DTLS handshake with"
|
||||
<< senderHifiSockAddr;
|
||||
if (!nodeData->getAssignmentUUID().isNull() && !nodeData->getWalletUUID().isNull()) {
|
||||
// check if we have a non-finalized transaction for this node to add this amount to
|
||||
TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID());
|
||||
WalletTransaction* existingTransaction = NULL;
|
||||
|
||||
while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) {
|
||||
if (!i.value()->isFinalized()) {
|
||||
existingTransaction = i.value();
|
||||
break;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// first we verify the cookie
|
||||
// see http://gnutls.org/manual/html_node/DTLS-sessions.html for why this is required
|
||||
gnutls_dtls_prestate_st prestate;
|
||||
memset(&prestate, 0, sizeof(prestate));
|
||||
int cookieValid = gnutls_dtls_cookie_verify(_cookieKey, &senderSockAddr, sizeof(senderSockAddr),
|
||||
peekDatagram.data(), peekDatagram.size(), &prestate);
|
||||
|
||||
if (cookieValid < 0) {
|
||||
// the cookie sent by the client was not valid
|
||||
// send a valid one
|
||||
DummyDTLSSession tempServerSession(LimitedNodeList::getInstance()->getDTLSSocket(), senderHifiSockAddr);
|
||||
|
||||
gnutls_dtls_cookie_send(_cookieKey, &senderSockAddr, sizeof(senderSockAddr), &prestate,
|
||||
&tempServerSession, DTLSSession::socketPush);
|
||||
|
||||
// acutally pull the peeked data off the network stack so that it gets discarded
|
||||
dtlsSocket.readDatagram(peekDatagram.data(), peekDatagram.size());
|
||||
qint64 elapsedMsecsSinceLastPayment = nodeData->getPaymentIntervalTimer().elapsed();
|
||||
nodeData->getPaymentIntervalTimer().restart();
|
||||
|
||||
const float CREDITS_PER_HOUR = 3;
|
||||
const float CREDITS_PER_MSEC = CREDITS_PER_HOUR / (60 * 60 * 1000);
|
||||
|
||||
float pendingCredits = elapsedMsecsSinceLastPayment * CREDITS_PER_MSEC;
|
||||
|
||||
if (existingTransaction) {
|
||||
existingTransaction->incrementAmount(pendingCredits);
|
||||
} else {
|
||||
// cookie valid but no existing session - set up a new session now
|
||||
DTLSServerSession* newServerSession = new DTLSServerSession(LimitedNodeList::getInstance()->getDTLSSocket(),
|
||||
senderHifiSockAddr);
|
||||
gnutls_session_t* gnutlsSession = newServerSession->getGnuTLSSession();
|
||||
// create a fresh transaction to pay this node, there is no transaction to append to
|
||||
WalletTransaction* freshTransaction = new WalletTransaction(nodeData->getWalletUUID(), pendingCredits);
|
||||
_pendingAssignmentCredits.insert(nodeData->getWalletUUID(), freshTransaction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::sendPendingTransactionsToServer() {
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
if (accountManager.hasValidAccessToken()) {
|
||||
|
||||
// enumerate the pending transactions and send them to the server to complete payment
|
||||
TransactionHash::iterator i = _pendingAssignmentCredits.begin();
|
||||
|
||||
JSONCallbackParameters transactionCallbackParams;
|
||||
|
||||
transactionCallbackParams.jsonCallbackReceiver = this;
|
||||
transactionCallbackParams.jsonCallbackMethod = "transactionJSONCallback";
|
||||
|
||||
while (i != _pendingAssignmentCredits.end()) {
|
||||
accountManager.authenticatedRequest("api/v1/transactions", QNetworkAccessManager::PostOperation,
|
||||
transactionCallbackParams, i.value()->postJson().toJson());
|
||||
|
||||
// set this transaction to finalized so we don't add additional credits to it
|
||||
i.value()->setIsFinalized(true);
|
||||
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DomainServer::transactionJSONCallback(const QJsonObject& data) {
|
||||
// check if this was successful - if so we can remove it from our list of pending
|
||||
if (data.value("status").toString() == "success") {
|
||||
// create a dummy wallet transaction to unpack the JSON to
|
||||
WalletTransaction dummyTransaction;
|
||||
dummyTransaction.loadFromJson(data);
|
||||
|
||||
TransactionHash::iterator i = _pendingAssignmentCredits.find(dummyTransaction.getDestinationUUID());
|
||||
|
||||
while (i != _pendingAssignmentCredits.end() && i.key() == dummyTransaction.getDestinationUUID()) {
|
||||
if (i.value()->getUUID() == dummyTransaction.getUUID()) {
|
||||
// we have a match - we can remove this from the hash of pending credits
|
||||
// and delete it for clean up
|
||||
|
||||
gnutls_priority_set(*gnutlsSession, *_priorityCache);
|
||||
gnutls_credentials_set(*gnutlsSession, GNUTLS_CRD_CERTIFICATE, *_x509Credentials);
|
||||
gnutls_dtls_prestate_set(*gnutlsSession, &prestate);
|
||||
WalletTransaction* matchingTransaction = i.value();
|
||||
_pendingAssignmentCredits.erase(i);
|
||||
delete matchingTransaction;
|
||||
|
||||
// handshake to begin the session
|
||||
gnutls_handshake(*gnutlsSession);
|
||||
|
||||
qDebug() << "Beginning DTLS session with node at" << senderHifiSockAddr;
|
||||
_dtlsSessions[senderHifiSockAddr] = newServerSession;
|
||||
break;
|
||||
} else {
|
||||
++i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -818,6 +849,7 @@ const char JSON_KEY_TYPE[] = "type";
|
|||
const char JSON_KEY_PUBLIC_SOCKET[] = "public";
|
||||
const char JSON_KEY_LOCAL_SOCKET[] = "local";
|
||||
const char JSON_KEY_POOL[] = "pool";
|
||||
const char JSON_KEY_PENDING_CREDITS[] = "pending_credits";
|
||||
const char JSON_KEY_WAKE_TIMESTAMP[] = "wake_timestamp";
|
||||
|
||||
QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
||||
|
@ -846,6 +878,18 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) {
|
|||
SharedAssignmentPointer matchingAssignment = _allAssignments.value(nodeData->getAssignmentUUID());
|
||||
if (matchingAssignment) {
|
||||
nodeJson[JSON_KEY_POOL] = matchingAssignment->getPool();
|
||||
|
||||
if (!nodeData->getWalletUUID().isNull()) {
|
||||
TransactionHash::iterator i = _pendingAssignmentCredits.find(nodeData->getWalletUUID());
|
||||
double pendingCreditAmount = 0;
|
||||
|
||||
while (i != _pendingAssignmentCredits.end() && i.key() == nodeData->getWalletUUID()) {
|
||||
pendingCreditAmount += i.value()->getAmount();
|
||||
++i;
|
||||
}
|
||||
|
||||
nodeJson[JSON_KEY_PENDING_CREDITS] = pendingCreditAmount;
|
||||
}
|
||||
}
|
||||
|
||||
return nodeJson;
|
||||
|
@ -915,6 +959,24 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
connection->respond(HTTPConnection::StatusCode200, assignmentDocument.toJson(), qPrintable(JSON_MIME_TYPE));
|
||||
|
||||
// we've processed this request
|
||||
return true;
|
||||
} else if (url.path() == "/transactions.json") {
|
||||
// enumerate our pending transactions and display them in an array
|
||||
QJsonObject rootObject;
|
||||
QJsonArray transactionArray;
|
||||
|
||||
TransactionHash::iterator i = _pendingAssignmentCredits.begin();
|
||||
while (i != _pendingAssignmentCredits.end()) {
|
||||
transactionArray.push_back(i.value()->toJson());
|
||||
++i;
|
||||
}
|
||||
|
||||
rootObject["pending_transactions"] = transactionArray;
|
||||
|
||||
// print out the created JSON
|
||||
QJsonDocument transactionsDocument(rootObject);
|
||||
connection->respond(HTTPConnection::StatusCode200, transactionsDocument.toJson(), qPrintable(JSON_MIME_TYPE));
|
||||
|
||||
return true;
|
||||
} else if (url.path() == QString("%1.json").arg(URI_NODES)) {
|
||||
// setup the JSON
|
||||
|
@ -1210,22 +1272,18 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
|
|||
reinterpret_cast<DomainServerNodeData*>(otherNode->getLinkedData())->getSessionSecretHash().remove(node->getUUID());
|
||||
}
|
||||
}
|
||||
|
||||
if (_isUsingDTLS) {
|
||||
// check if we need to remove a DTLS session from our in-memory hash
|
||||
DTLSServerSession* existingSession = _dtlsSessions.take(nodeData->getSendingSockAddr());
|
||||
if (existingSession) {
|
||||
delete existingSession;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) {
|
||||
SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& assignmentUUID, NodeType_t nodeType) {
|
||||
QQueue<SharedAssignmentPointer>::iterator i = _unfulfilledAssignments.begin();
|
||||
|
||||
while (i != _unfulfilledAssignments.end()) {
|
||||
if (i->data()->getType() == Assignment::typeForNodeType(nodeType) && i->data()->getUUID() == checkInUUID) {
|
||||
if (i->data()->getType() == Assignment::typeForNodeType(nodeType)
|
||||
&& i->data()->getUUID() == assignmentUUID) {
|
||||
// we have an unfulfilled assignment to return
|
||||
|
||||
// return the matching assignment
|
||||
return _unfulfilledAssignments.takeAt(i - _unfulfilledAssignments.begin());
|
||||
} else {
|
||||
++i;
|
||||
|
|
|
@ -20,21 +20,21 @@
|
|||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QUrl>
|
||||
|
||||
#include <gnutls/gnutls.h>
|
||||
|
||||
#include <Assignment.h>
|
||||
#include <HTTPSConnection.h>
|
||||
#include <LimitedNodeList.h>
|
||||
|
||||
#include "DTLSServerSession.h"
|
||||
#include "WalletTransaction.h"
|
||||
|
||||
#include "PendingAssignedNodeData.h"
|
||||
|
||||
typedef QSharedPointer<Assignment> SharedAssignmentPointer;
|
||||
typedef QMultiHash<QUuid, WalletTransaction*> TransactionHash;
|
||||
|
||||
class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
||||
Q_OBJECT
|
||||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url);
|
||||
|
@ -47,15 +47,18 @@ public slots:
|
|||
/// Called by NodeList to inform us a node has been killed
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
|
||||
private slots:
|
||||
void transactionJSONCallback(const QJsonObject& data);
|
||||
|
||||
private slots:
|
||||
void loginFailed();
|
||||
void readAvailableDatagrams();
|
||||
void readAvailableDTLSDatagrams();
|
||||
void setupPendingAssignmentCredits();
|
||||
void sendPendingTransactionsToServer();
|
||||
private:
|
||||
void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid());
|
||||
bool optionallySetupOAuth();
|
||||
bool optionallySetupDTLS();
|
||||
bool optionallyReadX509KeyAndCertificate();
|
||||
bool optionallySetupAssignmentPayment();
|
||||
|
||||
void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr);
|
||||
|
||||
|
@ -92,16 +95,12 @@ private:
|
|||
|
||||
QHash<QUuid, SharedAssignmentPointer> _allAssignments;
|
||||
QQueue<SharedAssignmentPointer> _unfulfilledAssignments;
|
||||
QHash<QUuid, PendingAssignedNodeData*> _pendingAssignedNodes;
|
||||
TransactionHash _pendingAssignmentCredits;
|
||||
|
||||
QVariantMap _argumentVariantMap;
|
||||
|
||||
bool _isUsingDTLS;
|
||||
gnutls_certificate_credentials_t* _x509Credentials;
|
||||
gnutls_dh_params_t* _dhParams;
|
||||
gnutls_datum_t* _cookieKey;
|
||||
gnutls_priority_t* _priorityCache;
|
||||
|
||||
QHash<HifiSockAddr, DTLSServerSession*> _dtlsSessions;
|
||||
|
||||
QNetworkAccessManager* _networkAccessManager;
|
||||
|
||||
|
|
|
@ -20,11 +20,13 @@
|
|||
DomainServerNodeData::DomainServerNodeData() :
|
||||
_sessionSecretHash(),
|
||||
_assignmentUUID(),
|
||||
_walletUUID(),
|
||||
_paymentIntervalTimer(),
|
||||
_statsJSONObject(),
|
||||
_sendingSockAddr(),
|
||||
_isAuthenticated(true)
|
||||
{
|
||||
|
||||
_paymentIntervalTimer.start();
|
||||
}
|
||||
|
||||
void DomainServerNodeData::parseJSONStatsPacket(const QByteArray& statsPacket) {
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_DomainServerNodeData_h
|
||||
#define hifi_DomainServerNodeData_h
|
||||
|
||||
|
||||
#include <QtCore/QElapsedTimer>
|
||||
#include <QtCore/QHash>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
|
@ -30,6 +32,11 @@ public:
|
|||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
|
||||
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
|
||||
const QUuid& getWalletUUID() const { return _walletUUID; }
|
||||
|
||||
QElapsedTimer& getPaymentIntervalTimer() { return _paymentIntervalTimer; }
|
||||
|
||||
void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; }
|
||||
const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; }
|
||||
|
||||
|
@ -42,6 +49,8 @@ private:
|
|||
|
||||
QHash<QUuid, QUuid> _sessionSecretHash;
|
||||
QUuid _assignmentUUID;
|
||||
QUuid _walletUUID;
|
||||
QElapsedTimer _paymentIntervalTimer;
|
||||
QJsonObject _statsJSONObject;
|
||||
HifiSockAddr _sendingSockAddr;
|
||||
bool _isAuthenticated;
|
||||
|
|
19
domain-server/src/PendingAssignedNodeData.cpp
Normal file
19
domain-server/src/PendingAssignedNodeData.cpp
Normal file
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// PendingAssignedNodeData.cpp
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-05-20.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "PendingAssignedNodeData.h"
|
||||
|
||||
PendingAssignedNodeData::PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID) :
|
||||
_assignmentUUID(assignmentUUID),
|
||||
_walletUUID(walletUUID)
|
||||
{
|
||||
|
||||
}
|
33
domain-server/src/PendingAssignedNodeData.h
Normal file
33
domain-server/src/PendingAssignedNodeData.h
Normal file
|
@ -0,0 +1,33 @@
|
|||
//
|
||||
// PendingAssignedNodeData.h
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-05-20.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_PendingAssignedNodeData_h
|
||||
#define hifi_PendingAssignedNodeData_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
class PendingAssignedNodeData : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
PendingAssignedNodeData(const QUuid& assignmentUUID, const QUuid& walletUUID);
|
||||
|
||||
void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; }
|
||||
const QUuid& getAssignmentUUID() const { return _assignmentUUID; }
|
||||
|
||||
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
|
||||
const QUuid& getWalletUUID() const { return _walletUUID; }
|
||||
private:
|
||||
QUuid _assignmentUUID;
|
||||
QUuid _walletUUID;
|
||||
};
|
||||
|
||||
#endif // hifi_PendingAssignedNodeData_h
|
67
domain-server/src/WalletTransaction.cpp
Normal file
67
domain-server/src/WalletTransaction.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
//
|
||||
// WalletTransaction.cpp
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-05-20.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include <QtCore/QJsonObject>
|
||||
|
||||
#include <UUID.h>
|
||||
|
||||
#include "WalletTransaction.h"
|
||||
|
||||
WalletTransaction::WalletTransaction() :
|
||||
_uuid(),
|
||||
_destinationUUID(),
|
||||
_amount(),
|
||||
_isFinalized(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
WalletTransaction::WalletTransaction(const QUuid& destinationUUID, double amount) :
|
||||
_uuid(QUuid::createUuid()),
|
||||
_destinationUUID(destinationUUID),
|
||||
_amount(amount),
|
||||
_isFinalized(false)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
const QString TRANSACTION_ID_KEY = "id";
|
||||
const QString TRANSACTION_DESTINATION_WALLET_ID_KEY = "destination_wallet_id";
|
||||
const QString TRANSACTION_AMOUNT_KEY = "amount";
|
||||
|
||||
const QString ROOT_OBJECT_TRANSACTION_KEY = "transaction";
|
||||
|
||||
QJsonDocument WalletTransaction::postJson() {
|
||||
QJsonObject rootObject;
|
||||
|
||||
rootObject.insert(ROOT_OBJECT_TRANSACTION_KEY, toJson());
|
||||
|
||||
return QJsonDocument(rootObject);
|
||||
}
|
||||
|
||||
QJsonObject WalletTransaction::toJson() {
|
||||
QJsonObject transactionObject;
|
||||
|
||||
transactionObject.insert(TRANSACTION_ID_KEY, uuidStringWithoutCurlyBraces(_uuid));
|
||||
transactionObject.insert(TRANSACTION_DESTINATION_WALLET_ID_KEY, uuidStringWithoutCurlyBraces(_destinationUUID));
|
||||
transactionObject.insert(TRANSACTION_AMOUNT_KEY, _amount);
|
||||
|
||||
return transactionObject;
|
||||
}
|
||||
|
||||
void WalletTransaction::loadFromJson(const QJsonObject& jsonObject) {
|
||||
// pull the destination wallet and ID of the transaction to match it
|
||||
QJsonObject transactionObject = jsonObject.value("data").toObject().value(ROOT_OBJECT_TRANSACTION_KEY).toObject();
|
||||
|
||||
_uuid = QUuid(transactionObject.value(TRANSACTION_ID_KEY).toString());
|
||||
_destinationUUID = QUuid(transactionObject.value(TRANSACTION_DESTINATION_WALLET_ID_KEY).toString());
|
||||
_amount = transactionObject.value(TRANSACTION_AMOUNT_KEY).toDouble();
|
||||
}
|
46
domain-server/src/WalletTransaction.h
Normal file
46
domain-server/src/WalletTransaction.h
Normal file
|
@ -0,0 +1,46 @@
|
|||
//
|
||||
// WalletTransaction.h
|
||||
// domain-server/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-05-20.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_WalletTransaction_h
|
||||
#define hifi_WalletTransaction_h
|
||||
|
||||
#include <QtCore/QJsonDocument>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUuid>
|
||||
|
||||
class WalletTransaction : public QObject {
|
||||
public:
|
||||
WalletTransaction();
|
||||
WalletTransaction(const QUuid& destinationUUID, double amount);
|
||||
|
||||
const QUuid& getUUID() const { return _uuid; }
|
||||
|
||||
void setDestinationUUID(const QUuid& destinationUUID) { _destinationUUID = destinationUUID; }
|
||||
const QUuid& getDestinationUUID() const { return _destinationUUID; }
|
||||
|
||||
double getAmount() const { return _amount; }
|
||||
void setAmount(double amount) { _amount = amount; }
|
||||
void incrementAmount(double increment) { _amount += increment; }
|
||||
|
||||
bool isFinalized() const { return _isFinalized; }
|
||||
void setIsFinalized(bool isFinalized) { _isFinalized = isFinalized; }
|
||||
|
||||
QJsonDocument postJson();
|
||||
QJsonObject toJson();
|
||||
void loadFromJson(const QJsonObject& jsonObject);
|
||||
private:
|
||||
QUuid _uuid;
|
||||
QUuid _destinationUUID;
|
||||
double _amount;
|
||||
bool _isFinalized;
|
||||
};
|
||||
|
||||
#endif // hifi_WalletTransaction_h
|
124
examples/animatedModelExample.js
Normal file
124
examples/animatedModelExample.js
Normal file
|
@ -0,0 +1,124 @@
|
|||
//
|
||||
// animatedModelExample.js
|
||||
// examples
|
||||
//
|
||||
// Created by Brad Hefta-Gaub on 12/31/13.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// This is an example script that demonstrates creating and editing a model
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
var count = 0;
|
||||
var moveUntil = 6000;
|
||||
var stopAfter = moveUntil + 100;
|
||||
|
||||
var pitch = 0.0;
|
||||
var yaw = 0.0;
|
||||
var roll = 0.0;
|
||||
var rotation = Quat.fromPitchYawRollDegrees(pitch, yaw, roll)
|
||||
|
||||
var originalProperties = {
|
||||
position: { x: MyAvatar.position.x,
|
||||
y: MyAvatar.position.y,
|
||||
z: MyAvatar.position.z },
|
||||
|
||||
radius : 1,
|
||||
|
||||
color: { red: 0,
|
||||
green: 255,
|
||||
blue: 0 },
|
||||
|
||||
modelURL: "http://www.fungibleinsight.com/faces/beta.fst",
|
||||
modelRotation: rotation,
|
||||
animationURL: "http://www.fungibleinsight.com/faces/gangnam_style_2.fbx",
|
||||
animationIsPlaying: true,
|
||||
};
|
||||
|
||||
var modelID = Models.addModel(originalProperties);
|
||||
print("Models.addModel()... modelID.creatorTokenID = " + modelID.creatorTokenID);
|
||||
|
||||
var isPlaying = true;
|
||||
var playPauseEveryWhile = 360;
|
||||
var animationFPS = 30;
|
||||
var adjustFPSEveryWhile = 120;
|
||||
var resetFrameEveryWhile = 600;
|
||||
|
||||
function moveModel(deltaTime) {
|
||||
var somethingChanged = false;
|
||||
if (count % playPauseEveryWhile == 0) {
|
||||
isPlaying = !isPlaying;
|
||||
print("isPlaying=" + isPlaying);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (count % adjustFPSEveryWhile == 0) {
|
||||
if (animationFPS == 30) {
|
||||
animationFPS = 30;
|
||||
} else if (animationFPS == 10) {
|
||||
animationFPS = 10;
|
||||
} else if (animationFPS == 60) {
|
||||
animationFPS = 60;
|
||||
}
|
||||
print("animationFPS=" + animationFPS);
|
||||
isPlaying = true;
|
||||
print("always start playing if we change the FPS -- isPlaying=" + isPlaying);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (count % resetFrameEveryWhile == 0) {
|
||||
resetFrame = true;
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (count >= moveUntil) {
|
||||
|
||||
// delete it...
|
||||
if (count == moveUntil) {
|
||||
print("calling Models.deleteModel()");
|
||||
Models.deleteModel(modelID);
|
||||
}
|
||||
|
||||
// stop it...
|
||||
if (count >= stopAfter) {
|
||||
print("calling Script.stop()");
|
||||
Script.stop();
|
||||
}
|
||||
|
||||
count++;
|
||||
return; // break early
|
||||
}
|
||||
|
||||
count++;
|
||||
|
||||
//print("modelID.creatorTokenID = " + modelID.creatorTokenID);
|
||||
|
||||
if (somethingChanged) {
|
||||
var newProperties = {
|
||||
animationIsPlaying: isPlaying,
|
||||
animationFPS: animationFPS,
|
||||
};
|
||||
|
||||
if (resetFrame) {
|
||||
print("resetting the frame!");
|
||||
newProperties.animationFrameIndex = 0;
|
||||
resetFrame = false;
|
||||
}
|
||||
|
||||
Models.editModel(modelID, newProperties);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// register the call back so it fires before each data send
|
||||
Script.update.connect(moveModel);
|
||||
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
print("cleaning up...");
|
||||
print("modelID="+ modelID.creatorTokenID + ", id:" + modelID.id);
|
||||
Models.deleteModel(modelID);
|
||||
});
|
||||
|
|
@ -25,7 +25,7 @@ function printVector(string, vector) {
|
|||
}
|
||||
|
||||
var CHANCE_OF_MOVING = 0.005;
|
||||
var CHANCE_OF_SOUND = 0.000;
|
||||
var CHANCE_OF_SOUND = 0.005;
|
||||
var CHANCE_OF_HEAD_TURNING = 0.05;
|
||||
var CHANCE_OF_BIG_MOVE = 0.1;
|
||||
var CHANCE_OF_WAVING = 0.009;
|
||||
|
@ -41,11 +41,11 @@ var isWaving = false;
|
|||
var waveFrequency = 0.0;
|
||||
var waveAmplitude = 0.0;
|
||||
|
||||
var X_MIN = 20.0;
|
||||
var X_MAX = 25.0;
|
||||
var Z_MIN = 20.0;
|
||||
var Z_MAX = 25.0;
|
||||
var Y_PELVIS = 2.5;
|
||||
var X_MIN = 5.0;
|
||||
var X_MAX = 15.0;
|
||||
var Z_MIN = 5.0;
|
||||
var Z_MAX = 15.0;
|
||||
var Y_PELVIS = 1.0;
|
||||
var SPINE_JOINT_NUMBER = 13;
|
||||
var SHOULDER_JOINT_NUMBER = 17;
|
||||
var ELBOW_JOINT_NUMBER = 18;
|
||||
|
|
|
@ -76,50 +76,59 @@ function controller(wichSide) {
|
|||
this.oldModelRadius;
|
||||
|
||||
this.laser = Overlays.addOverlay("line3d", {
|
||||
position: this.palmPosition,
|
||||
end: this.tipPosition,
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
end: { x: 0, y: 0, z: 0 },
|
||||
color: LASER_COLOR,
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: LASER_WIDTH
|
||||
lineWidth: LASER_WIDTH,
|
||||
anchor: "MyAvatar"
|
||||
});
|
||||
|
||||
this.guideScale = 0.02;
|
||||
this.ball = Overlays.addOverlay("sphere", {
|
||||
position: this.palmPosition,
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
size: this.guideScale,
|
||||
solid: true,
|
||||
color: { red: 0, green: 255, blue: 0 },
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
anchor: "MyAvatar"
|
||||
});
|
||||
this.leftRight = Overlays.addOverlay("line3d", {
|
||||
position: this.palmPosition,
|
||||
end: this.tipPosition,
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
end: { x: 0, y: 0, z: 0 },
|
||||
color: { red: 0, green: 0, blue: 255 },
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: LASER_WIDTH
|
||||
lineWidth: LASER_WIDTH,
|
||||
anchor: "MyAvatar"
|
||||
});
|
||||
this.topDown = Overlays.addOverlay("line3d", {
|
||||
position: this.palmPosition,
|
||||
end: this.tipPosition,
|
||||
position: { x: 0, y: 0, z: 0 },
|
||||
end: { x: 0, y: 0, z: 0 },
|
||||
color: { red: 0, green: 0, blue: 255 },
|
||||
alpha: 1,
|
||||
visible: false,
|
||||
lineWidth: LASER_WIDTH
|
||||
lineWidth: LASER_WIDTH,
|
||||
anchor: "MyAvatar"
|
||||
});
|
||||
|
||||
|
||||
|
||||
this.grab = function (modelID, properties) {
|
||||
print("Grabbing " + modelID.id);
|
||||
this.grabbing = true;
|
||||
this.modelID = modelID;
|
||||
if (this.isLocked(properties)) {
|
||||
print("Model locked " + modelID.id);
|
||||
} else {
|
||||
print("Grabbing " + modelID.id);
|
||||
|
||||
this.oldModelPosition = properties.position;
|
||||
this.oldModelRotation = properties.modelRotation;
|
||||
this.oldModelRadius = properties.radius;
|
||||
this.grabbing = true;
|
||||
this.modelID = modelID;
|
||||
|
||||
this.oldModelPosition = properties.position;
|
||||
this.oldModelRotation = properties.modelRotation;
|
||||
this.oldModelRadius = properties.radius;
|
||||
}
|
||||
}
|
||||
|
||||
this.release = function () {
|
||||
|
@ -140,8 +149,23 @@ function controller(wichSide) {
|
|||
this.pressed = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.isLocked = function (properties) {
|
||||
// special case to lock the ground plane model in hq.
|
||||
if (location.hostname == "hq.highfidelity.io" &&
|
||||
properties.modelURL == "https://s3-us-west-1.amazonaws.com/highfidelity-public/ozan/Terrain_Reduce_forAlpha.fbx") {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
this.checkModel = function (properties) {
|
||||
// special case to lock the ground plane model in hq.
|
||||
if (this.isLocked(properties)) {
|
||||
return { valid: false };
|
||||
}
|
||||
|
||||
|
||||
// P P - Model
|
||||
// /| A - Palm
|
||||
// / | d B - unit vector toward tip
|
||||
|
@ -170,10 +194,17 @@ function controller(wichSide) {
|
|||
}
|
||||
|
||||
this.moveLaser = function () {
|
||||
var endPosition = Vec3.sum(this.palmPosition, Vec3.multiply(this.front, LASER_LENGTH_FACTOR));
|
||||
// the overlays here are anchored to the avatar, which means they are specified in the avatar's local frame
|
||||
|
||||
var inverseRotation = Quat.inverse(MyAvatar.orientation);
|
||||
var startPosition = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.palmPosition, MyAvatar.position));
|
||||
var direction = Vec3.multiplyQbyV(inverseRotation, Vec3.subtract(this.tipPosition, this.palmPosition));
|
||||
var distance = Vec3.length(direction);
|
||||
direction = Vec3.multiply(direction, LASER_LENGTH_FACTOR / distance);
|
||||
var endPosition = Vec3.sum(startPosition, direction);
|
||||
|
||||
Overlays.editOverlay(this.laser, {
|
||||
position: this.palmPosition,
|
||||
position: startPosition,
|
||||
end: endPosition,
|
||||
visible: true
|
||||
});
|
||||
|
@ -219,11 +250,11 @@ function controller(wichSide) {
|
|||
position: newPosition,
|
||||
modelRotation: newRotation
|
||||
});
|
||||
print("Moving " + this.modelID.id);
|
||||
// print("Moving " + this.modelID.id);
|
||||
// Vec3.print("Old Position: ", this.oldModelPosition);
|
||||
// Vec3.print("Sav Position: ", newPosition);
|
||||
Quat.print("Old Rotation: ", this.oldModelRotation);
|
||||
Quat.print("New Rotation: ", newRotation);
|
||||
// Quat.print("Old Rotation: ", this.oldModelRotation);
|
||||
// Quat.print("New Rotation: ", newRotation);
|
||||
|
||||
this.oldModelRotation = newRotation;
|
||||
this.oldModelPosition = newPosition;
|
||||
|
@ -274,15 +305,18 @@ function controller(wichSide) {
|
|||
}
|
||||
|
||||
var properties = Models.getModelProperties(foundModels[i]);
|
||||
print("Checking properties: " + properties.id + " " + properties.isKnownID);
|
||||
|
||||
var check = this.checkModel(properties);
|
||||
if (check.valid) {
|
||||
this.grab(foundModels[i], properties);
|
||||
this.x = check.x;
|
||||
this.y = check.y;
|
||||
this.z = check.z;
|
||||
return;
|
||||
if (this.isLocked(properties)) {
|
||||
print("Model locked " + properties.id);
|
||||
} else {
|
||||
print("Checking properties: " + properties.id + " " + properties.isKnownID);
|
||||
var check = this.checkModel(properties);
|
||||
if (check.valid) {
|
||||
this.grab(foundModels[i], properties);
|
||||
this.x = check.x;
|
||||
this.y = check.y;
|
||||
this.z = check.z;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -301,7 +335,7 @@ var rightController = new controller(RIGHT);
|
|||
|
||||
function moveModels() {
|
||||
if (leftController.grabbing && rightController.grabbing && rightController.modelID.id == leftController.modelID.id) {
|
||||
print("Both controllers");
|
||||
//print("Both controllers");
|
||||
var oldLeftPoint = Vec3.sum(leftController.oldPalmPosition, Vec3.multiply(leftController.oldFront, leftController.x));
|
||||
var oldRightPoint = Vec3.sum(rightController.oldPalmPosition, Vec3.multiply(rightController.oldFront, rightController.x));
|
||||
|
||||
|
@ -319,7 +353,7 @@ function moveModels() {
|
|||
|
||||
var newPosition = Vec3.sum(middle,
|
||||
Vec3.multiply(Vec3.subtract(leftController.oldModelPosition, oldMiddle), ratio));
|
||||
Vec3.print("Ratio : " + ratio + " New position: ", newPosition);
|
||||
//Vec3.print("Ratio : " + ratio + " New position: ", newPosition);
|
||||
var rotation = Quat.multiply(leftController.rotation,
|
||||
Quat.inverse(leftController.oldRotation));
|
||||
rotation = Quat.multiply(rotation, leftController.oldModelRotation);
|
||||
|
@ -452,51 +486,54 @@ function mousePressEvent(event) {
|
|||
}
|
||||
|
||||
var properties = Models.getModelProperties(foundModels[i]);
|
||||
print("Checking properties: " + properties.id + " " + properties.isKnownID);
|
||||
if (this.isLocked(properties)) {
|
||||
print("Model locked " + properties.id);
|
||||
} else {
|
||||
print("Checking properties: " + properties.id + " " + properties.isKnownID);
|
||||
// P P - Model
|
||||
// /| A - Palm
|
||||
// / | d B - unit vector toward tip
|
||||
// / | X - base of the perpendicular line
|
||||
// A---X----->B d - distance fom axis
|
||||
// x x - distance from A
|
||||
//
|
||||
// |X-A| = (P-A).B
|
||||
// X == A + ((P-A).B)B
|
||||
// d = |P-X|
|
||||
|
||||
// P P - Model
|
||||
// /| A - Palm
|
||||
// / | d B - unit vector toward tip
|
||||
// / | X - base of the perpendicular line
|
||||
// A---X----->B d - distance fom axis
|
||||
// x x - distance from A
|
||||
//
|
||||
// |X-A| = (P-A).B
|
||||
// X == A + ((P-A).B)B
|
||||
// d = |P-X|
|
||||
var A = pickRay.origin;
|
||||
var B = Vec3.normalize(pickRay.direction);
|
||||
var P = properties.position;
|
||||
|
||||
var A = pickRay.origin;
|
||||
var B = Vec3.normalize(pickRay.direction);
|
||||
var P = properties.position;
|
||||
var x = Vec3.dot(Vec3.subtract(P, A), B);
|
||||
var X = Vec3.sum(A, Vec3.multiply(B, x));
|
||||
var d = Vec3.length(Vec3.subtract(P, X));
|
||||
|
||||
var x = Vec3.dot(Vec3.subtract(P, A), B);
|
||||
var X = Vec3.sum(A, Vec3.multiply(B, x));
|
||||
var d = Vec3.length(Vec3.subtract(P, X));
|
||||
|
||||
if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) {
|
||||
modelSelected = true;
|
||||
selectedModelID = foundModels[i];
|
||||
selectedModelProperties = properties;
|
||||
if (d < properties.radius && 0 < x && x < LASER_LENGTH_FACTOR) {
|
||||
modelSelected = true;
|
||||
selectedModelID = foundModels[i];
|
||||
selectedModelProperties = properties;
|
||||
|
||||
selectedModelProperties.oldRadius = selectedModelProperties.radius;
|
||||
selectedModelProperties.oldPosition = {
|
||||
x: selectedModelProperties.position.x,
|
||||
y: selectedModelProperties.position.y,
|
||||
z: selectedModelProperties.position.z,
|
||||
};
|
||||
selectedModelProperties.oldRotation = {
|
||||
x: selectedModelProperties.modelRotation.x,
|
||||
y: selectedModelProperties.modelRotation.y,
|
||||
z: selectedModelProperties.modelRotation.z,
|
||||
w: selectedModelProperties.modelRotation.w,
|
||||
};
|
||||
selectedModelProperties.oldRadius = selectedModelProperties.radius;
|
||||
selectedModelProperties.oldPosition = {
|
||||
x: selectedModelProperties.position.x,
|
||||
y: selectedModelProperties.position.y,
|
||||
z: selectedModelProperties.position.z,
|
||||
};
|
||||
selectedModelProperties.oldRotation = {
|
||||
x: selectedModelProperties.modelRotation.x,
|
||||
y: selectedModelProperties.modelRotation.y,
|
||||
z: selectedModelProperties.modelRotation.z,
|
||||
w: selectedModelProperties.modelRotation.w,
|
||||
};
|
||||
|
||||
|
||||
orientation = MyAvatar.orientation;
|
||||
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
|
||||
print("Clicked on " + selectedModelID.id + " " + modelSelected);
|
||||
orientation = MyAvatar.orientation;
|
||||
intersection = rayPlaneIntersection(pickRay, P, Quat.getFront(orientation));
|
||||
|
||||
return;
|
||||
print("Clicked on " + selectedModelID.id + " " + modelSelected);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -630,7 +630,7 @@ var trackAsDelete = false;
|
|||
var trackAsRecolor = false;
|
||||
var trackAsEyedropper = false;
|
||||
|
||||
var voxelToolSelected = true;
|
||||
var voxelToolSelected = false;
|
||||
var recolorToolSelected = false;
|
||||
var eyedropperToolSelected = false;
|
||||
var pasteMode = false;
|
||||
|
@ -848,7 +848,7 @@ function showPreviewLines() {
|
|||
}
|
||||
|
||||
function showPreviewGuides() {
|
||||
if (editToolsOn && !isImporting) {
|
||||
if (editToolsOn && !isImporting && (voxelToolSelected || recolorToolSelected || eyedropperToolSelected)) {
|
||||
if (previewAsVoxel) {
|
||||
showPreviewVoxel();
|
||||
|
||||
|
@ -964,7 +964,7 @@ function mousePressEvent(event) {
|
|||
|
||||
if (clickedOverlay == voxelTool) {
|
||||
modeSwitchSound.play(0);
|
||||
voxelToolSelected = true;
|
||||
voxelToolSelected = !voxelToolSelected;
|
||||
recolorToolSelected = false;
|
||||
eyedropperToolSelected = false;
|
||||
moveTools();
|
||||
|
@ -972,7 +972,7 @@ function mousePressEvent(event) {
|
|||
} else if (clickedOverlay == recolorTool) {
|
||||
modeSwitchSound.play(1);
|
||||
voxelToolSelected = false;
|
||||
recolorToolSelected = true;
|
||||
recolorToolSelected = !recolorToolSelected;
|
||||
eyedropperToolSelected = false;
|
||||
moveTools();
|
||||
clickedOnSomething = true;
|
||||
|
@ -980,7 +980,7 @@ function mousePressEvent(event) {
|
|||
modeSwitchSound.play(2);
|
||||
voxelToolSelected = false;
|
||||
recolorToolSelected = false;
|
||||
eyedropperToolSelected = true;
|
||||
eyedropperToolSelected = !eyedropperToolSelected;
|
||||
moveTools();
|
||||
clickedOnSomething = true;
|
||||
} else if (scaleSelector.clicked(event.x, event.y)) {
|
||||
|
@ -1000,7 +1000,7 @@ function mousePressEvent(event) {
|
|||
}
|
||||
}
|
||||
}
|
||||
if (clickedOnSomething || isImporting) {
|
||||
if (clickedOnSomething || isImporting || (!voxelToolSelected && !recolorToolSelected && !eyedropperToolSelected)) {
|
||||
return; // no further processing
|
||||
}
|
||||
|
||||
|
@ -1344,7 +1344,7 @@ function moveTools() {
|
|||
recolorToolOffset = 2;
|
||||
} else if (eyedropperToolSelected) {
|
||||
eyedropperToolOffset = 2;
|
||||
} else {
|
||||
} else if (voxelToolSelected) {
|
||||
if (pasteMode) {
|
||||
voxelToolColor = pasteModeColor;
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ var impactSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-pub
|
|||
var targetHitSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/hit.raw");
|
||||
var targetLaunchSound = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Space%20Invaders/shoot.raw");
|
||||
|
||||
var gunModel = "http://highfidelity-public.s3-us-west-1.amazonaws.com/models/attachments/Raygun2.fst";
|
||||
var gunModel = "http://public.highfidelity.io/models/attachments/HaloGun.fst";
|
||||
|
||||
var audioOptions = new AudioInjectionOptions();
|
||||
audioOptions.volume = 0.9;
|
||||
|
@ -199,7 +199,7 @@ function playLoadSound() {
|
|||
Audio.playSound(loadSound, audioOptions);
|
||||
}
|
||||
|
||||
MyAvatar.attach(gunModel, "RightHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20);
|
||||
//MyAvatar.attach(gunModel, "RightHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20);
|
||||
MyAvatar.attach(gunModel, "LeftHand", {x: -0.02, y: -.14, z: 0.07}, Quat.fromPitchYawRollDegrees(-70, -151, 72), 0.20);
|
||||
|
||||
// Give a bit of time to load before playing sound
|
||||
|
@ -320,7 +320,6 @@ function scriptEnding() {
|
|||
Overlays.deleteOverlay(reticle);
|
||||
Overlays.deleteOverlay(text);
|
||||
MyAvatar.detachOne(gunModel);
|
||||
MyAvatar.detachOne(gunModel);
|
||||
}
|
||||
|
||||
Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel);
|
||||
|
|
|
@ -50,6 +50,13 @@ var LEFT_BUTTON_4 = 4;
|
|||
var RIGHT_PALM = 2;
|
||||
var RIGHT_BUTTON_4 = 10;
|
||||
|
||||
|
||||
function printVector(text, v, decimals) {
|
||||
print(text + " " + v.x.toFixed(decimals) + ", " + v.y.toFixed(decimals) + ", " + v.z.toFixed(decimals));
|
||||
}
|
||||
|
||||
var debug = false;
|
||||
|
||||
// Used by handleGrabBehavior() for managing the grab position changes
|
||||
function getAndResetGrabDelta() {
|
||||
var HAND_GRAB_SCALE_DISTANCE = 2.0;
|
||||
|
@ -60,24 +67,12 @@ function getAndResetGrabDelta() {
|
|||
return result;
|
||||
}
|
||||
|
||||
// Used by handleGrabBehavior() for managing the grab velocity feature
|
||||
function getAndResetGrabDeltaVelocity() {
|
||||
var HAND_GRAB_SCALE_VELOCITY = 50.0;
|
||||
var delta = Vec3.multiply(grabDeltaVelocity, (MyAvatar.scale * HAND_GRAB_SCALE_VELOCITY));
|
||||
grabDeltaVelocity = { x: 0, y: 0, z: 0};
|
||||
var avatarRotation = MyAvatar.orientation;
|
||||
var result = Quat.multiply(avatarRotation, Vec3.multiply(delta, -1));
|
||||
return result;
|
||||
}
|
||||
|
||||
// Used by handleGrabBehavior() for managing the grab rotation feature
|
||||
function getAndResetGrabRotation() {
|
||||
function getGrabRotation() {
|
||||
var quatDiff = Quat.multiply(grabCurrentRotation, Quat.inverse(grabStartRotation));
|
||||
grabStartRotation = grabCurrentRotation;
|
||||
return quatDiff;
|
||||
}
|
||||
|
||||
// handles all the grab related behavior: position (crawl), velocity (flick), and rotate (twist)
|
||||
// When move button is pressed, process results
|
||||
function handleGrabBehavior(deltaTime) {
|
||||
// check for and handle grab behaviors
|
||||
grabbingWithRightHand = Controller.isButtonPressed(RIGHT_BUTTON_4);
|
||||
|
@ -88,9 +83,11 @@ function handleGrabBehavior(deltaTime) {
|
|||
if (grabbingWithRightHand && !wasGrabbingWithRightHand) {
|
||||
// Just starting grab, capture starting rotation
|
||||
grabStartRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM);
|
||||
grabStartPosition = Controller.getSpatialControlPosition(RIGHT_PALM);
|
||||
if (debug) printVector("start position", grabStartPosition, 3);
|
||||
}
|
||||
if (grabbingWithRightHand) {
|
||||
grabDelta = Vec3.sum(grabDelta, Vec3.multiply(Controller.getSpatialControlVelocity(RIGHT_PALM), deltaTime));
|
||||
grabDelta = Vec3.subtract(Controller.getSpatialControlPosition(RIGHT_PALM), grabStartPosition);
|
||||
grabCurrentRotation = Controller.getSpatialControlRawRotation(RIGHT_PALM);
|
||||
}
|
||||
if (!grabbingWithRightHand && wasGrabbingWithRightHand) {
|
||||
|
@ -102,10 +99,12 @@ function handleGrabBehavior(deltaTime) {
|
|||
if (grabbingWithLeftHand && !wasGrabbingWithLeftHand) {
|
||||
// Just starting grab, capture starting rotation
|
||||
grabStartRotation = Controller.getSpatialControlRawRotation(LEFT_PALM);
|
||||
grabStartPosition = Controller.getSpatialControlPosition(LEFT_PALM);
|
||||
if (debug) printVector("start position", grabStartPosition, 3);
|
||||
}
|
||||
|
||||
if (grabbingWithLeftHand) {
|
||||
grabDelta = Vec3.sum(grabDelta, Vec3.multiply(Controller.getSpatialControlVelocity(LEFT_PALM), deltaTime));
|
||||
grabDelta = Vec3.subtract(Controller.getSpatialControlPosition(LEFT_PALM), grabStartPosition);
|
||||
grabCurrentRotation = Controller.getSpatialControlRawRotation(LEFT_PALM);
|
||||
}
|
||||
if (!grabbingWithLeftHand && wasGrabbingWithLeftHand) {
|
||||
|
@ -119,44 +118,57 @@ function handleGrabBehavior(deltaTime) {
|
|||
|
||||
if (grabbing) {
|
||||
|
||||
// move position
|
||||
var moveFromGrab = getAndResetGrabDelta();
|
||||
if (Vec3.length(moveFromGrab) > EPSILON) {
|
||||
MyAvatar.position = Vec3.sum(MyAvatar.position, moveFromGrab);
|
||||
velocity = { x: 0, y: 0, z: 0};
|
||||
}
|
||||
|
||||
// add some rotation...
|
||||
var deltaRotation = getAndResetGrabRotation();
|
||||
var GRAB_CONTROLLER_TURN_SCALING = 0.5;
|
||||
var euler = Vec3.multiply(Quat.safeEulerAngles(deltaRotation), GRAB_CONTROLLER_TURN_SCALING);
|
||||
var headOrientation = MyAvatar.headOrientation;
|
||||
var front = Quat.getFront(headOrientation);
|
||||
var right = Quat.getRight(headOrientation);
|
||||
var up = Quat.getUp(headOrientation);
|
||||
|
||||
// Adjust body yaw by yaw from controller
|
||||
var orientation = Quat.multiply(Quat.angleAxis(-euler.y, {x:0, y: 1, z:0}), MyAvatar.orientation);
|
||||
grabDelta = Vec3.multiplyQbyV(MyAvatar.orientation, Vec3.multiply(grabDelta, -1));
|
||||
|
||||
if (debug) {
|
||||
printVector("grabDelta: ", grabDelta, 3);
|
||||
}
|
||||
|
||||
var THRUST_GRAB_SCALING = 0.0;
|
||||
|
||||
var thrustFront = Vec3.multiply(front, MyAvatar.scale * grabDelta.z * THRUST_GRAB_SCALING * deltaTime);
|
||||
MyAvatar.addThrust(thrustFront);
|
||||
var thrustRight = Vec3.multiply(right, MyAvatar.scale * grabDelta.x * THRUST_GRAB_SCALING * deltaTime);
|
||||
MyAvatar.addThrust(thrustRight);
|
||||
var thrustUp = Vec3.multiply(up, MyAvatar.scale * grabDelta.y * THRUST_GRAB_SCALING * deltaTime);
|
||||
MyAvatar.addThrust(thrustUp);
|
||||
|
||||
|
||||
// add some rotation...
|
||||
var deltaRotation = getGrabRotation();
|
||||
var PITCH_SCALING = 2.0;
|
||||
var PITCH_DEAD_ZONE = 2.0;
|
||||
var YAW_SCALING = 2.0;
|
||||
var ROLL_SCALING = 2.0;
|
||||
|
||||
var euler = Quat.safeEulerAngles(deltaRotation);
|
||||
|
||||
// Adjust body yaw by roll from controller
|
||||
var orientation = Quat.multiply(Quat.angleAxis(((euler.y * YAW_SCALING) +
|
||||
(euler.z * ROLL_SCALING)) * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation);
|
||||
MyAvatar.orientation = orientation;
|
||||
|
||||
// Adjust head pitch from controller
|
||||
MyAvatar.headPitch = MyAvatar.headPitch - euler.x;
|
||||
var pitch = 0.0;
|
||||
if (Math.abs(euler.x) > PITCH_DEAD_ZONE) {
|
||||
pitch = (euler.x < 0.0) ? (euler.x + PITCH_DEAD_ZONE) : (euler.x - PITCH_DEAD_ZONE);
|
||||
}
|
||||
MyAvatar.headPitch = MyAvatar.headPitch + (pitch * PITCH_SCALING * deltaTime);
|
||||
|
||||
// TODO: Add some camera roll proportional to the rate of turn (so it feels like an airplane or roller coaster)
|
||||
|
||||
}
|
||||
|
||||
// add some velocity...
|
||||
if (stoppedGrabbing) {
|
||||
velocity = Vec3.sum(velocity, getAndResetGrabDeltaVelocity());
|
||||
}
|
||||
|
||||
// handle residual velocity
|
||||
if(Vec3.length(velocity) > EPSILON) {
|
||||
MyAvatar.position = Vec3.sum(MyAvatar.position, Vec3.multiply(velocity, deltaTime));
|
||||
// damp velocity
|
||||
velocity = Vec3.multiply(velocity, damping);
|
||||
}
|
||||
|
||||
|
||||
wasGrabbingWithRightHand = grabbingWithRightHand;
|
||||
wasGrabbingWithLeftHand = grabbingWithLeftHand;
|
||||
}
|
||||
|
||||
// Main update function that handles flying and grabbing behaviort
|
||||
// Update for joysticks and move button
|
||||
function flyWithHydra(deltaTime) {
|
||||
var thrustJoystickPosition = Controller.getJoystickPosition(THRUST_CONTROLLER);
|
||||
|
||||
|
|
|
@ -37,6 +37,7 @@ var radiusMinimum = 0.05;
|
|||
var radiusMaximum = 0.5;
|
||||
|
||||
var modelURLs = [
|
||||
"http://www.fungibleinsight.com/faces/beta.fst",
|
||||
"https://s3-us-west-1.amazonaws.com/highfidelity-public/models/attachments/topHat.fst",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/Feisar_Ship.FBX",
|
||||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/birarda/birarda_head.fbx",
|
||||
|
@ -48,6 +49,19 @@ var modelURLs = [
|
|||
"http://highfidelity-public.s3-us-west-1.amazonaws.com/meshes/slimer.fbx",
|
||||
];
|
||||
|
||||
var animationURLs = [
|
||||
"http://www.fungibleinsight.com/faces/gangnam_style_2.fbx",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
];
|
||||
|
||||
var currentModelURL = 1;
|
||||
var numModels = modelURLs.length;
|
||||
|
||||
|
@ -214,10 +228,19 @@ function checkControllerSide(whichSide) {
|
|||
modelRotation: palmRotation,
|
||||
modelURL: modelURLs[currentModelURL]
|
||||
};
|
||||
|
||||
if (animationURLs[currentModelURL] !== "") {
|
||||
properties.animationURL = animationURLs[currentModelURL];
|
||||
properties.animationIsPlaying = true;
|
||||
}
|
||||
|
||||
debugPrint("modelRadius=" +modelRadius);
|
||||
|
||||
newModel = Models.addModel(properties);
|
||||
|
||||
print("just added model... newModel=" + newModel.creatorTokenID);
|
||||
print("properties.animationURL=" + properties.animationURL);
|
||||
|
||||
if (whichSide == LEFT_PALM) {
|
||||
leftModelAlreadyInHand = true;
|
||||
leftHandModel = newModel;
|
||||
|
|
153
examples/sit.js
Normal file
153
examples/sit.js
Normal file
|
@ -0,0 +1,153 @@
|
|||
//
|
||||
// sit.js
|
||||
// examples
|
||||
//
|
||||
// Created by Mika Impola on February 8, 2014
|
||||
// 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
|
||||
//
|
||||
|
||||
|
||||
var buttonImageUrl = "https://worklist-prod.s3.amazonaws.com/attachment/0aca88e1-9bd8-5c1d.svg";
|
||||
|
||||
var windowDimensions = Controller.getViewportDimensions();
|
||||
|
||||
var buttonWidth = 37;
|
||||
var buttonHeight = 46;
|
||||
var buttonPadding = 10;
|
||||
|
||||
var buttonPositionX = windowDimensions.x - buttonPadding - buttonWidth;
|
||||
var buttonPositionY = (windowDimensions.y - buttonHeight) / 2 ;
|
||||
|
||||
var sitDownButton = Overlays.addOverlay("image", {
|
||||
x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight,
|
||||
subImage: { x: 0, y: buttonHeight, width: buttonWidth, height: buttonHeight},
|
||||
imageURL: buttonImageUrl,
|
||||
visible: true,
|
||||
alpha: 1.0
|
||||
});
|
||||
var standUpButton = Overlays.addOverlay("image", {
|
||||
x: buttonPositionX, y: buttonPositionY, width: buttonWidth, height: buttonHeight,
|
||||
subImage: { x: buttonWidth, y: buttonHeight, width: buttonWidth, height: buttonHeight},
|
||||
imageURL: buttonImageUrl,
|
||||
visible: false,
|
||||
alpha: 1.0
|
||||
});
|
||||
|
||||
var passedTime = 0.0;
|
||||
var startPosition = null;
|
||||
var animationLenght = 2.0;
|
||||
|
||||
// This is the pose we would like to end up
|
||||
var pose = [
|
||||
{joint:"RightUpLeg", rotation: {x:100.0, y:15.0, z:0.0}},
|
||||
{joint:"RightLeg", rotation: {x:-130.0, y:15.0, z:0.0}},
|
||||
{joint:"RightFoot", rotation: {x:30, y:15.0, z:0.0}},
|
||||
{joint:"LeftUpLeg", rotation: {x:100.0, y:-15.0, z:0.0}},
|
||||
{joint:"LeftLeg", rotation: {x:-130.0, y:-15.0, z:0.0}},
|
||||
{joint:"LeftFoot", rotation: {x:30, y:15.0, z:0.0}},
|
||||
|
||||
{joint:"Spine2", rotation: {x:20, y:0.0, z:0.0}},
|
||||
|
||||
{joint:"RightShoulder", rotation: {x:0.0, y:40.0, z:0.0}},
|
||||
{joint:"LeftShoulder", rotation: {x:0.0, y:-40.0, z:0.0}}
|
||||
|
||||
];
|
||||
|
||||
var startPoseAndTransition = [];
|
||||
|
||||
function storeStartPoseAndTransition() {
|
||||
for (var i = 0; i < pose.length; i++){
|
||||
var startRotation = Quat.safeEulerAngles(MyAvatar.getJointRotation(pose[i].joint));
|
||||
var transitionVector = Vec3.subtract( pose[i].rotation, startRotation );
|
||||
startPoseAndTransition.push({joint: pose[i].joint, start: startRotation, transition: transitionVector});
|
||||
}
|
||||
}
|
||||
|
||||
function updateJoints(factor){
|
||||
for (var i = 0; i < startPoseAndTransition.length; i++){
|
||||
var scaledTransition = Vec3.multiply(startPoseAndTransition[i].transition, factor);
|
||||
var rotation = Vec3.sum(startPoseAndTransition[i].start, scaledTransition);
|
||||
MyAvatar.setJointData(startPoseAndTransition[i].joint, Quat.fromVec3Degrees( rotation ));
|
||||
}
|
||||
}
|
||||
|
||||
var sittingDownAnimation = function(deltaTime) {
|
||||
|
||||
passedTime += deltaTime;
|
||||
var factor = passedTime/animationLenght;
|
||||
|
||||
if ( passedTime <= animationLenght ) {
|
||||
updateJoints(factor);
|
||||
|
||||
var pos = { x: startPosition.x - 0.3 * factor, y: startPosition.y - 0.5 * factor, z: startPosition.z};
|
||||
MyAvatar.position = pos;
|
||||
}
|
||||
}
|
||||
|
||||
var standingUpAnimation = function(deltaTime){
|
||||
|
||||
passedTime += deltaTime;
|
||||
var factor = 1 - passedTime/animationLenght;
|
||||
|
||||
if ( passedTime <= animationLenght ) {
|
||||
|
||||
updateJoints(factor);
|
||||
|
||||
var pos = { x: startPosition.x + 0.3 * (passedTime/animationLenght), y: startPosition.y + 0.5 * (passedTime/animationLenght), z: startPosition.z};
|
||||
MyAvatar.position = pos;
|
||||
}
|
||||
}
|
||||
|
||||
Controller.mousePressEvent.connect(function(event){
|
||||
|
||||
var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y});
|
||||
|
||||
if (clickedOverlay == sitDownButton) {
|
||||
passedTime = 0.0;
|
||||
startPosition = MyAvatar.position;
|
||||
storeStartPoseAndTransition();
|
||||
try{
|
||||
Script.update.disconnect(standingUpAnimation);
|
||||
} catch(e){
|
||||
// no need to handle. if it wasn't connected no harm done
|
||||
}
|
||||
Script.update.connect(sittingDownAnimation);
|
||||
Overlays.editOverlay(sitDownButton, { visible: false });
|
||||
Overlays.editOverlay(standUpButton, { visible: true });
|
||||
} else if (clickedOverlay == standUpButton) {
|
||||
passedTime = 0.0;
|
||||
startPosition = MyAvatar.position;
|
||||
try{
|
||||
Script.update.disconnect(sittingDownAnimation);
|
||||
} catch (e){}
|
||||
Script.update.connect(standingUpAnimation);
|
||||
Overlays.editOverlay(standUpButton, { visible: false });
|
||||
Overlays.editOverlay(sitDownButton, { visible: true });
|
||||
}
|
||||
})
|
||||
|
||||
function update(deltaTime){
|
||||
var newWindowDimensions = Controller.getViewportDimensions();
|
||||
if( newWindowDimensions.x != windowDimensions.x || newWindowDimensions.y != windowDimensions.y ){
|
||||
windowDimensions = newWindowDimensions;
|
||||
var newX = windowDimensions.x - buttonPadding - buttonWidth;
|
||||
var newY = (windowDimensions.y - buttonHeight) / 2 ;
|
||||
Overlays.editOverlay( standUpButton, {x: newX, y: newY} );
|
||||
Overlays.editOverlay( sitDownButton, {x: newX, y: newY} );
|
||||
}
|
||||
}
|
||||
|
||||
Script.update.connect(update);
|
||||
|
||||
Script.scriptEnding.connect(function() {
|
||||
|
||||
for (var i = 0; i < pose.length; i++){
|
||||
MyAvatar.clearJointData(pose[i][0]);
|
||||
}
|
||||
|
||||
Overlays.deleteOverlay(sitDownButton);
|
||||
Overlays.deleteOverlay(standUpButton);
|
||||
});
|
|
@ -15,6 +15,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake
|
|||
set(FACEPLUS_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/faceplus")
|
||||
set(FACESHIFT_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/faceshift")
|
||||
set(LIBOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/oculus")
|
||||
set(PRIOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/priovr")
|
||||
set(SIXENSE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense")
|
||||
set(VISAGE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/visage")
|
||||
|
||||
|
@ -68,7 +69,6 @@ foreach(EXTERNAL_SOURCE_SUBDIR ${EXTERNAL_SOURCE_SUBDIRS})
|
|||
endforeach(EXTERNAL_SOURCE_SUBDIR)
|
||||
|
||||
find_package(Qt5 COMPONENTS Core Gui Multimedia Network OpenGL Script Svg WebKit WebKitWidgets Xml UiTools)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
|
||||
# grab the ui files in resources/ui
|
||||
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
|
||||
|
@ -127,12 +127,15 @@ link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(models ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(script-engine ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# find any optional libraries
|
||||
find_package(Faceplus)
|
||||
find_package(Faceshift)
|
||||
find_package(LibOVR)
|
||||
find_package(PrioVR)
|
||||
find_package(SDL)
|
||||
find_package(Sixense)
|
||||
find_package(Visage)
|
||||
find_package(ZLIB)
|
||||
|
@ -183,6 +186,20 @@ if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
|
|||
target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}")
|
||||
endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
|
||||
|
||||
# and with PrioVR library
|
||||
if (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
|
||||
add_definitions(-DHAVE_PRIOVR)
|
||||
include_directories(SYSTEM "${PRIOVR_INCLUDE_DIRS}")
|
||||
target_link_libraries(${TARGET_NAME} "${PRIOVR_LIBRARIES}")
|
||||
endif (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
|
||||
|
||||
# and with SDL for joysticks
|
||||
if (SDL_FOUND AND NOT DISABLE_SDL)
|
||||
add_definitions(-DHAVE_SDL)
|
||||
include_directories(SYSTEM "${SDL_INCLUDE_DIR}")
|
||||
target_link_libraries(${TARGET_NAME} "${SDL_LIBRARY}")
|
||||
endif (SDL_FOUND AND NOT DISABLE_SDL)
|
||||
|
||||
# and with qxmpp for chat
|
||||
if (QXMPP_FOUND AND NOT DISABLE_QXMPP)
|
||||
add_definitions(-DHAVE_QXMPP -DQXMPP_STATIC)
|
||||
|
@ -194,14 +211,9 @@ endif (QXMPP_FOUND AND NOT DISABLE_QXMPP)
|
|||
# include headers for interface and InterfaceConfig.
|
||||
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
# include external library headers
|
||||
# use system flag so warnings are supressed
|
||||
include_directories(SYSTEM "${FACESHIFT_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
|
||||
include_directories(SYSTEM "${FACESHIFT_INCLUDE_DIRS}")
|
||||
|
||||
target_link_libraries(
|
||||
${TARGET_NAME}
|
||||
|
@ -209,9 +221,11 @@ target_link_libraries(
|
|||
"${ZLIB_LIBRARIES}"
|
||||
Qt5::Core Qt5::Gui Qt5::Multimedia Qt5::Network Qt5::OpenGL
|
||||
Qt5::Script Qt5::Svg Qt5::WebKit Qt5::WebKitWidgets Qt5::Xml Qt5::UiTools
|
||||
"${GNUTLS_LIBRARY}"
|
||||
)
|
||||
|
||||
# assume we are using a Qt build without bearer management
|
||||
add_definitions(-DQT_NO_BEARERMANAGEMENT)
|
||||
|
||||
if (APPLE)
|
||||
# link in required OS X frameworks and include the right GL headers
|
||||
find_library(AppKit AppKit)
|
||||
|
@ -271,6 +285,9 @@ else (APPLE)
|
|||
# we're using static GLEW, so define GLEW_STATIC
|
||||
add_definitions(-DGLEW_STATIC)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
add_definitions(-Dssize_t=long)
|
||||
|
||||
target_link_libraries(${TARGET_NAME} "${GLEW_LIBRARY}" wsock32.lib opengl32.lib)
|
||||
endif()
|
||||
endif (APPLE)
|
||||
|
|
16
interface/external/priovr/readme.txt
vendored
Normal file
16
interface/external/priovr/readme.txt
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
|
||||
Instructions for adding the PrioVR driver to Interface
|
||||
Andrzej Kapolka, May 12, 2014
|
||||
|
||||
1. Download and install the YEI drivers from https://www.yeitechnology.com/yei-3-space-sensor-software-suite. If using
|
||||
Window 8+, follow the workaround instructions at http://forum.yeitechnology.com/viewtopic.php?f=3&t=24.
|
||||
|
||||
2. Get the PrioVR skeleton API, open ts_c_api2_priovr2/visual_studio/ThreeSpace_API_2/ThreeSpace_API_2.sln
|
||||
in Visual Studio, and build it.
|
||||
|
||||
3. Copy ts_c_api2_priovr2/visual_studio/ThreeSpace_API_2/Skeletal_API/yei_skeletal_api.h to interface/external/priovr/include,
|
||||
ts_c_api2_priovr2/visual_studio/ThreeSpace_API_2/Debug/Skeletal_API.lib to interface/external/priovr/lib, and
|
||||
ts_c_api2_priovr2/visual_studio/ThreeSpace_API_2/Debug/*.dll to your path.
|
||||
|
||||
4. Delete your build directory, run cmake and build, and you should be all set.
|
||||
|
|
@ -20,11 +20,14 @@ varying vec4 normal;
|
|||
void main(void) {
|
||||
// compute the base color based on OpenGL lighting model
|
||||
vec4 normalizedNormal = normalize(normal);
|
||||
float diffuse = dot(normalizedNormal, gl_LightSource[0].position);
|
||||
float facingLight = step(0.0, diffuse);
|
||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position)));
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||
|
||||
// compute the specular component (sans exponent)
|
||||
float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), normalizedNormal));
|
||||
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)),
|
||||
normalizedNormal));
|
||||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||
|
|
|
@ -32,12 +32,15 @@ void main(void) {
|
|||
// compute the base color based on OpenGL lighting model
|
||||
vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
|
||||
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
|
||||
float diffuse = dot(viewNormal, gl_LightSource[0].position);
|
||||
float facingLight = step(0.0, diffuse);
|
||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(viewNormal, gl_LightSource[0].position)));
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||
|
||||
// compute the specular component (sans exponent)
|
||||
float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), viewNormal));
|
||||
|
||||
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)),
|
||||
viewNormal));
|
||||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) +
|
||||
vec4(pow(specular, gl_FrontMaterial.shininess) * gl_FrontLightProduct[0].specular.rgb, 0.0);
|
||||
|
|
|
@ -35,12 +35,15 @@ void main(void) {
|
|||
// compute the base color based on OpenGL lighting model
|
||||
vec4 viewNormal = vec4(normalizedTangent * localNormal.x +
|
||||
normalizedBitangent * localNormal.y + normalizedNormal * localNormal.z, 0.0);
|
||||
float diffuse = dot(viewNormal, gl_LightSource[0].position);
|
||||
float facingLight = step(0.0, diffuse);
|
||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(viewNormal, gl_LightSource[0].position)));
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||
|
||||
// compute the specular component (sans exponent)
|
||||
float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), viewNormal));
|
||||
|
||||
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)),
|
||||
viewNormal));
|
||||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||
|
|
|
@ -23,12 +23,15 @@ varying vec4 normal;
|
|||
void main(void) {
|
||||
// compute the base color based on OpenGL lighting model
|
||||
vec4 normalizedNormal = normalize(normal);
|
||||
float diffuse = dot(normalizedNormal, gl_LightSource[0].position);
|
||||
float facingLight = step(0.0, diffuse);
|
||||
vec4 base = gl_Color * (gl_FrontLightModelProduct.sceneColor + gl_FrontLightProduct[0].ambient +
|
||||
gl_FrontLightProduct[0].diffuse * max(0.0, dot(normalizedNormal, gl_LightSource[0].position)));
|
||||
gl_FrontLightProduct[0].diffuse * (diffuse * facingLight));
|
||||
|
||||
// compute the specular component (sans exponent)
|
||||
float specular = max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)), normalizedNormal));
|
||||
|
||||
float specular = facingLight * max(0.0, dot(normalize(gl_LightSource[0].position + vec4(0.0, 0.0, 1.0, 0.0)),
|
||||
normalizedNormal));
|
||||
|
||||
// modulate texture by base color and add specular contribution
|
||||
gl_FragColor = base * texture2D(diffuseMap, gl_TexCoord[0].st) + vec4(pow(specular, gl_FrontMaterial.shininess) *
|
||||
gl_FrontLightProduct[0].specular.rgb * texture2D(specularMap, gl_TexCoord[0].st).rgb, 0.0);
|
||||
|
|
|
@ -20,12 +20,12 @@ class AbstractLoggerInterface : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {};
|
||||
inline bool extraDebugging() { return _extraDebugging; };
|
||||
inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; };
|
||||
AbstractLoggerInterface(QObject* parent = NULL) : QObject(parent) {}
|
||||
inline bool extraDebugging() { return _extraDebugging; }
|
||||
inline void setExtraDebugging(bool debugging) { _extraDebugging = debugging; }
|
||||
|
||||
virtual void addMessage(QString) = 0;
|
||||
virtual QStringList getLogData() = 0;
|
||||
virtual QString getLogData() = 0;
|
||||
virtual void locateLog() = 0;
|
||||
|
||||
signals:
|
||||
|
|
|
@ -169,14 +169,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
_voxelHideShowThread(&_voxels),
|
||||
_packetsPerSecond(0),
|
||||
_bytesPerSecond(0),
|
||||
_nodeBoundsDisplay(this),
|
||||
_previousScriptLocation(),
|
||||
_logger(new FileLogger(this)),
|
||||
_runningScriptsWidget(NULL),
|
||||
_runningScriptsWidgetWasVisible(false)
|
||||
{
|
||||
// init GnuTLS for DTLS with domain-servers
|
||||
DTLSClientSession::globalInit();
|
||||
|
||||
// read the ApplicationInfo.ini file for Name/Version/Domain information
|
||||
QSettings applicationInfo(Application::resourcesPath() + "info/ApplicationInfo.ini", QSettings::IniFormat);
|
||||
|
||||
|
@ -190,6 +187,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
setOrganizationName(applicationInfo.value("organizationName").toString());
|
||||
setOrganizationDomain(applicationInfo.value("organizationDomain").toString());
|
||||
|
||||
_logger = new FileLogger(this); // After setting organization name in order to get correct directory
|
||||
|
||||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
|
||||
_myAvatar = _avatarManager.getMyAvatar();
|
||||
|
@ -239,6 +238,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
|
||||
connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&)));
|
||||
|
||||
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
|
||||
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f;
|
||||
|
||||
QTimer* locationUpdateTimer = new QTimer(this);
|
||||
connect(locationUpdateTimer, &QTimer::timeout, this, &Application::updateLocationInServer);
|
||||
locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
|
||||
|
||||
connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded);
|
||||
connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled);
|
||||
connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), SLOT(nodeKilled(SharedNodePointer)));
|
||||
|
@ -330,7 +336,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
|
||||
// when -url in command line, teleport to location
|
||||
urlGoTo(argc, constArgv);
|
||||
|
||||
|
||||
// For now we're going to set the PPS for outbound packets to be super high, this is
|
||||
// probably not the right long term solution. But for now, we're going to do this to
|
||||
// allow you to move a particle around in your hand
|
||||
|
@ -357,23 +363,23 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
|||
// clear the scripts, and set out script to our default scripts
|
||||
clearScriptsBeforeRunning();
|
||||
loadScript("http://public.highfidelity.io/scripts/defaultScripts.js");
|
||||
|
||||
|
||||
QMutexLocker locker(&_settingsMutex);
|
||||
_settings->setValue("firstRun",QVariant(false));
|
||||
} else {
|
||||
// do this as late as possible so that all required subsystems are inialized
|
||||
loadScripts();
|
||||
|
||||
|
||||
QMutexLocker locker(&_settingsMutex);
|
||||
_previousScriptLocation = _settings->value("LastScriptLocation", QVariant("")).toString();
|
||||
}
|
||||
|
||||
|
||||
connect(_window, &MainWindow::windowGeometryChanged,
|
||||
_runningScriptsWidget, &RunningScriptsWidget::setBoundary);
|
||||
|
||||
//When -url in command line, teleport to location
|
||||
urlGoTo(argc, constArgv);
|
||||
|
||||
|
||||
//When -url in command line, teleport to location
|
||||
urlGoTo(argc, constArgv);
|
||||
|
||||
// call the OAuthWebviewHandler static getter so that its instance lives in our thread
|
||||
OAuthWebViewHandler::getInstance();
|
||||
// make sure the High Fidelity root CA is in our list of trusted certs
|
||||
|
@ -386,11 +392,11 @@ Application::~Application() {
|
|||
|
||||
// make sure we don't call the idle timer any more
|
||||
delete idleTimer;
|
||||
|
||||
|
||||
_sharedVoxelSystem.changeTree(new VoxelTree);
|
||||
|
||||
|
||||
saveSettings();
|
||||
|
||||
|
||||
delete _voxelImporter;
|
||||
|
||||
// let the avatar mixer know we're out
|
||||
|
@ -424,14 +430,12 @@ Application::~Application() {
|
|||
delete _glWidget;
|
||||
|
||||
AccountManager::getInstance().destroy();
|
||||
|
||||
DTLSClientSession::globalDeinit();
|
||||
}
|
||||
|
||||
void Application::saveSettings() {
|
||||
Menu::getInstance()->saveSettings();
|
||||
_rearMirrorTools->saveSettings(_settings);
|
||||
|
||||
|
||||
if (_voxelImporter) {
|
||||
_voxelImporter->saveSettings(_settings);
|
||||
}
|
||||
|
@ -513,7 +517,7 @@ void Application::initializeGL() {
|
|||
_voxelHideShowThread.initialize(_enableProcessVoxelsThread);
|
||||
_particleEditSender.initialize(_enableProcessVoxelsThread);
|
||||
_modelEditSender.initialize(_enableProcessVoxelsThread);
|
||||
|
||||
|
||||
if (_enableProcessVoxelsThread) {
|
||||
qDebug("Voxel parsing thread created.");
|
||||
}
|
||||
|
@ -558,10 +562,9 @@ void Application::paintGL() {
|
|||
_myCamera.setUpShift(0.0f);
|
||||
_myCamera.setDistance(0.0f);
|
||||
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
|
||||
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
|
||||
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
|
||||
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
|
||||
}
|
||||
|
||||
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
|
||||
_myCamera.setTightness(0.0f); // In first person, camera follows (untweaked) head exactly without delay
|
||||
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
|
||||
_myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation());
|
||||
|
@ -578,7 +581,7 @@ void Application::paintGL() {
|
|||
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale() * _scaleMirror);
|
||||
_myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight + (_raiseMirror * _myAvatar->getScale()), 0));
|
||||
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
|
||||
|
||||
|
||||
// if the head would intersect the near clip plane, we must push the camera out
|
||||
glm::vec3 relativePosition = glm::inverse(_myCamera.getTargetRotation()) *
|
||||
(eyePosition - _myCamera.getTargetPosition());
|
||||
|
@ -587,7 +590,7 @@ void Application::paintGL() {
|
|||
pushback = relativePosition.z + pushbackRadius - _myCamera.getDistance();
|
||||
pushbackFocalLength = _myCamera.getDistance();
|
||||
}
|
||||
|
||||
|
||||
// handle pushback, if any
|
||||
if (pushbackFocalLength > 0.0f) {
|
||||
const float PUSHBACK_DECAY = 0.5f;
|
||||
|
@ -1272,7 +1275,7 @@ void Application::dropEvent(QDropEvent *event) {
|
|||
|
||||
void Application::sendPingPackets() {
|
||||
QByteArray pingPacket = NodeList::getInstance()->constructPingPacket();
|
||||
controlledBroadcastToNodes(pingPacket, NodeSet()
|
||||
controlledBroadcastToNodes(pingPacket, NodeSet()
|
||||
<< NodeType::VoxelServer << NodeType::ParticleServer << NodeType::ModelServer
|
||||
<< NodeType::AudioMixer << NodeType::AvatarMixer
|
||||
<< NodeType::MetavoxelServer);
|
||||
|
@ -1283,7 +1286,7 @@ void Application::timer() {
|
|||
if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) {
|
||||
sendPingPackets();
|
||||
}
|
||||
|
||||
|
||||
float diffTime = (float)_timerStart.nsecsElapsed() / 1000000000.0f;
|
||||
|
||||
_fps = (float)_frameCount / diffTime;
|
||||
|
@ -1687,7 +1690,7 @@ void Application::init() {
|
|||
connect(_rearMirrorTools, SIGNAL(restoreView()), SLOT(restoreMirrorView()));
|
||||
connect(_rearMirrorTools, SIGNAL(shrinkView()), SLOT(shrinkMirrorView()));
|
||||
connect(_rearMirrorTools, SIGNAL(resetView()), SLOT(resetSensors()));
|
||||
|
||||
|
||||
// set up our audio reflector
|
||||
_audioReflector.setMyAvatar(getAvatar());
|
||||
_audioReflector.setVoxels(_voxels.getTree());
|
||||
|
@ -1696,7 +1699,7 @@ void Application::init() {
|
|||
|
||||
connect(getAudio(), &Audio::processInboundAudio, &_audioReflector, &AudioReflector::processInboundAudio,Qt::DirectConnection);
|
||||
connect(getAudio(), &Audio::processLocalAudio, &_audioReflector, &AudioReflector::processLocalAudio,Qt::DirectConnection);
|
||||
connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector,
|
||||
connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector,
|
||||
&AudioReflector::preProcessOriginalInboundAudio,Qt::DirectConnection);
|
||||
|
||||
// save settings when avatar changes
|
||||
|
@ -1811,7 +1814,7 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
|
||||
|
||||
FaceTracker* tracker = getActiveFaceTracker();
|
||||
|
||||
|
||||
bool isLookingAtSomeone = false;
|
||||
glm::vec3 lookAtSpot;
|
||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||
|
@ -1846,7 +1849,7 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
glm::distance(_mouseRayOrigin, _myAvatar->getHead()->calculateAverageEyePosition()));
|
||||
lookAtSpot = _mouseRayOrigin + _mouseRayDirection * qMax(minEyeDistance, distance);
|
||||
*/
|
||||
|
||||
|
||||
}
|
||||
//
|
||||
// Deflect the eyes a bit to match the detected Gaze from 3D camera if active
|
||||
|
@ -1866,7 +1869,7 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
eyePitch * pitchSign * deflection, eyeYaw * deflection, 0.0f))) *
|
||||
glm::inverse(_myCamera.getRotation()) * (lookAtSpot - origin);
|
||||
}
|
||||
|
||||
|
||||
_myAvatar->getHead()->setLookAtPosition(lookAtSpot);
|
||||
}
|
||||
|
||||
|
@ -1918,7 +1921,7 @@ void Application::updateCamera(float deltaTime) {
|
|||
PerformanceWarning warn(showWarnings, "Application::updateCamera()");
|
||||
|
||||
if (!OculusManager::isConnected() && !TV3DManager::isConnected() &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) {
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) {
|
||||
FaceTracker* tracker = getActiveFaceTracker();
|
||||
if (tracker) {
|
||||
const float EYE_OFFSET_SCALE = 0.025f;
|
||||
|
@ -1984,6 +1987,8 @@ void Application::update(float deltaTime) {
|
|||
_myAvatar->updateLookAtTargetAvatar();
|
||||
updateMyAvatarLookAtPosition();
|
||||
_sixenseManager.update(deltaTime);
|
||||
_joystickManager.update();
|
||||
_prioVR.update(deltaTime);
|
||||
updateMyAvatar(deltaTime); // Sample hardware, update view frustum if needed, and send avatar data to mixer/nodes
|
||||
updateThreads(deltaTime); // If running non-threaded, then give the threads some time to process...
|
||||
_avatarManager.updateOtherAvatars(deltaTime); //loop through all the other avatars and simulate them...
|
||||
|
@ -2472,7 +2477,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// disable specular lighting for ground and voxels
|
||||
glMaterialfv(GL_FRONT, GL_SPECULAR, NO_SPECULAR_COLOR);
|
||||
|
||||
|
||||
// draw the audio reflector overlay
|
||||
_audioReflector.render();
|
||||
|
||||
|
@ -2519,6 +2524,9 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) {
|
|||
|
||||
// restore default, white specular
|
||||
glMaterialfv(GL_FRONT, GL_SPECULAR, WHITE_SPECULAR_COLOR);
|
||||
|
||||
_nodeBoundsDisplay.draw();
|
||||
|
||||
}
|
||||
|
||||
bool mirrorMode = (whichCamera.getInterpolatedMode() == CAMERA_MODE_MIRROR);
|
||||
|
@ -2637,7 +2645,7 @@ void Application::displayOverlay() {
|
|||
const float LOG2_LOUDNESS_FLOOR = 11.f;
|
||||
float audioLevel = 0.f;
|
||||
float loudness = _audio.getLastInputLoudness() + 1.f;
|
||||
|
||||
|
||||
_trailingAudioLoudness = AUDIO_METER_AVERAGING * _trailingAudioLoudness + (1.f - AUDIO_METER_AVERAGING) * loudness;
|
||||
float log2loudness = log(_trailingAudioLoudness) / LOG2;
|
||||
|
||||
|
@ -2650,7 +2658,7 @@ void Application::displayOverlay() {
|
|||
audioLevel = AUDIO_METER_SCALE_WIDTH;
|
||||
}
|
||||
bool isClipping = ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME));
|
||||
|
||||
|
||||
if ((_audio.getTimeSinceLastClip() > 0.f) && (_audio.getTimeSinceLastClip() < CLIPPING_INDICATOR_TIME)) {
|
||||
const float MAX_MAGNITUDE = 0.7f;
|
||||
float magnitude = MAX_MAGNITUDE * (1 - _audio.getTimeSinceLastClip() / CLIPPING_INDICATOR_TIME);
|
||||
|
@ -2752,6 +2760,10 @@ void Application::displayOverlay() {
|
|||
? 80 : 20;
|
||||
drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 0.0f, 0, frameTimer, WHITE_TEXT);
|
||||
}
|
||||
_nodeBoundsDisplay.drawOverlay();
|
||||
|
||||
// give external parties a change to hook in
|
||||
emit renderingOverlay();
|
||||
|
||||
_overlays.render2D();
|
||||
|
||||
|
@ -2828,7 +2840,7 @@ void Application::renderRearViewMirror(const QRect& region, bool billboard) {
|
|||
// save absolute translations
|
||||
glm::vec3 absoluteSkeletonTranslation = _myAvatar->getSkeletonModel().getTranslation();
|
||||
glm::vec3 absoluteFaceTranslation = _myAvatar->getHead()->getFaceModel().getTranslation();
|
||||
|
||||
|
||||
// get the eye positions relative to the neck and use them to set the face translation
|
||||
glm::vec3 leftEyePosition, rightEyePosition;
|
||||
_myAvatar->getHead()->getFaceModel().setTranslation(glm::vec3());
|
||||
|
@ -3061,6 +3073,8 @@ void Application::resetSensors() {
|
|||
OculusManager::reset();
|
||||
}
|
||||
|
||||
_prioVR.reset();
|
||||
|
||||
QCursor::setPos(_mouseX, _mouseY);
|
||||
_myAvatar->reset();
|
||||
|
||||
|
@ -3092,7 +3106,7 @@ void Application::uploadModel(ModelType modelType) {
|
|||
thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit()));
|
||||
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
|
||||
uploader->connect(thread, SIGNAL(started()), SLOT(send()));
|
||||
|
||||
|
||||
thread->start();
|
||||
}
|
||||
|
||||
|
@ -3108,6 +3122,34 @@ void Application::updateWindowTitle(){
|
|||
_window->setWindowTitle(title);
|
||||
}
|
||||
|
||||
void Application::updateLocationInServer() {
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
if (accountManager.isLoggedIn()) {
|
||||
|
||||
static QJsonObject lastLocationObject;
|
||||
|
||||
// construct a QJsonObject given the user's current address information
|
||||
QJsonObject updatedLocationObject;
|
||||
|
||||
QJsonObject addressObject;
|
||||
addressObject.insert("position", QString(createByteArray(_myAvatar->getPosition())));
|
||||
addressObject.insert("orientation", QString(createByteArray(glm::degrees(safeEulerAngles(_myAvatar->getOrientation())))));
|
||||
addressObject.insert("domain", NodeList::getInstance()->getDomainHandler().getHostname());
|
||||
|
||||
updatedLocationObject.insert("address", addressObject);
|
||||
|
||||
if (updatedLocationObject != lastLocationObject) {
|
||||
|
||||
accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation,
|
||||
JSONCallbackParameters(), QJsonDocument(updatedLocationObject).toJson());
|
||||
|
||||
lastLocationObject = updatedLocationObject;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Application::domainChanged(const QString& domainHostname) {
|
||||
updateWindowTitle();
|
||||
|
||||
|
@ -3127,7 +3169,7 @@ void Application::domainChanged(const QString& domainHostname) {
|
|||
|
||||
// reset the voxels renderer
|
||||
_voxels.killLocalVoxels();
|
||||
|
||||
|
||||
// reset the auth URL for OAuth web view handler
|
||||
OAuthWebViewHandler::getInstance().clearLastAuthorizationURL();
|
||||
}
|
||||
|
@ -3348,7 +3390,7 @@ void Application::loadScripts() {
|
|||
loadScript(string);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
QMutexLocker locker(&_settingsMutex);
|
||||
_settings->endArray();
|
||||
}
|
||||
|
@ -3376,8 +3418,9 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
|
|||
}
|
||||
|
||||
// start the script on a new thread...
|
||||
ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), &_controllerScriptingInterface);
|
||||
_scriptEnginesHash.insert(scriptName, scriptEngine);
|
||||
QUrl scriptUrl(scriptName);
|
||||
ScriptEngine* scriptEngine = new ScriptEngine(scriptUrl, &_controllerScriptingInterface);
|
||||
_scriptEnginesHash.insert(scriptUrl.toString(), scriptEngine);
|
||||
|
||||
if (!scriptEngine->hasScript()) {
|
||||
qDebug() << "Application::loadScript(), script failed to load...";
|
||||
|
@ -3573,9 +3616,12 @@ void Application::loadScriptURLDialog() {
|
|||
void Application::toggleLogDialog() {
|
||||
if (! _logDialog) {
|
||||
_logDialog = new LogDialog(_glWidget, getLogger());
|
||||
_logDialog->show();
|
||||
}
|
||||
|
||||
if (_logDialog->isVisible()) {
|
||||
_logDialog->hide();
|
||||
} else {
|
||||
_logDialog->close();
|
||||
_logDialog->show();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3609,7 +3655,7 @@ void Application::parseVersionXml() {
|
|||
QObject* sender = QObject::sender();
|
||||
|
||||
QXmlStreamReader xml(qobject_cast<QNetworkReply*>(sender));
|
||||
|
||||
|
||||
while (!xml.atEnd() && !xml.hasError()) {
|
||||
if (xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == operatingSystem) {
|
||||
while (!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == operatingSystem)) {
|
||||
|
@ -3626,7 +3672,7 @@ void Application::parseVersionXml() {
|
|||
}
|
||||
xml.readNext();
|
||||
}
|
||||
|
||||
|
||||
if (!shouldSkipVersion(latestVersion) && applicationVersion() != latestVersion) {
|
||||
new UpdateDialog(_glWidget, releaseNotes, latestVersion, downloadUrl);
|
||||
}
|
||||
|
@ -3678,24 +3724,24 @@ void Application::urlGoTo(int argc, const char * constArgv[]) {
|
|||
} else if (urlParts.count() > 1) {
|
||||
// if url has 2 or more parts, the first one is domain name
|
||||
QString domain = urlParts[0];
|
||||
|
||||
|
||||
// second part is either a destination coordinate or
|
||||
// a place name
|
||||
QString destination = urlParts[1];
|
||||
|
||||
|
||||
// any third part is an avatar orientation.
|
||||
QString orientation = urlParts.count() > 2 ? urlParts[2] : QString();
|
||||
|
||||
|
||||
Menu::goToDomain(domain);
|
||||
|
||||
|
||||
// goto either @user, #place, or x-xx,y-yy,z-zz
|
||||
// style co-ordinate.
|
||||
Menu::goTo(destination);
|
||||
|
||||
|
||||
if (!orientation.isEmpty()) {
|
||||
// location orientation
|
||||
Menu::goToOrientation(orientation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -58,6 +58,8 @@
|
|||
#include "avatar/MyAvatar.h"
|
||||
#include "devices/Faceplus.h"
|
||||
#include "devices/Faceshift.h"
|
||||
#include "devices/JoystickManager.h"
|
||||
#include "devices/PrioVR.h"
|
||||
#include "devices/SixenseManager.h"
|
||||
#include "devices/Visage.h"
|
||||
#include "models/ModelTreeRenderer.h"
|
||||
|
@ -72,6 +74,7 @@
|
|||
#include "ui/BandwidthDialog.h"
|
||||
#include "ui/BandwidthMeter.h"
|
||||
#include "ui/ModelsBrowser.h"
|
||||
#include "ui/NodeBounds.h"
|
||||
#include "ui/OctreeStatsDialog.h"
|
||||
#include "ui/RearMirrorTools.h"
|
||||
#include "ui/SnapshotShareDialog.h"
|
||||
|
@ -189,11 +192,15 @@ public:
|
|||
bool isMouseHidden() const { return _mouseHidden; }
|
||||
const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; }
|
||||
const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; }
|
||||
int getMouseX() const { return _mouseX; }
|
||||
int getMouseY() const { return _mouseY; }
|
||||
Faceplus* getFaceplus() { return &_faceplus; }
|
||||
Faceshift* getFaceshift() { return &_faceshift; }
|
||||
Visage* getVisage() { return &_visage; }
|
||||
FaceTracker* getActiveFaceTracker();
|
||||
SixenseManager* getSixenseManager() { return &_sixenseManager; }
|
||||
PrioVR* getPrioVR() { return &_prioVR; }
|
||||
JoystickManager* getJoystickManager() { return &_joystickManager; }
|
||||
BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; }
|
||||
QUndoStack* getUndoStack() { return &_undoStack; }
|
||||
|
||||
|
@ -210,6 +217,7 @@ public:
|
|||
|
||||
QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
|
||||
GeometryCache* getGeometryCache() { return &_geometryCache; }
|
||||
AnimationCache* getAnimationCache() { return &_animationCache; }
|
||||
TextureCache* getTextureCache() { return &_textureCache; }
|
||||
GlowEffect* getGlowEffect() { return &_glowEffect; }
|
||||
ControllerScriptingInterface* getControllerScriptingInterface() { return &_controllerScriptingInterface; }
|
||||
|
@ -242,7 +250,7 @@ public:
|
|||
void computeOffAxisFrustum(float& left, float& right, float& bottom, float& top, float& nearVal,
|
||||
float& farVal, glm::vec4& nearClipPlane, glm::vec4& farClipPlane) const;
|
||||
|
||||
|
||||
NodeBounds& getNodeBoundsDisplay() { return _nodeBoundsDisplay; }
|
||||
|
||||
VoxelShader& getVoxelShader() { return _voxelShader; }
|
||||
PointShader& getPointShader() { return _pointShader; }
|
||||
|
@ -267,12 +275,16 @@ signals:
|
|||
/// Fired when we're rendering in-world interface elements; allows external parties to hook in.
|
||||
void renderingInWorldInterface();
|
||||
|
||||
/// Fired when we're rendering the overlay.
|
||||
void renderingOverlay();
|
||||
|
||||
/// Fired when the import window is closed
|
||||
void importDone();
|
||||
|
||||
public slots:
|
||||
void domainChanged(const QString& domainHostname);
|
||||
void updateWindowTitle();
|
||||
void updateLocationInServer();
|
||||
void nodeAdded(SharedNodePointer node);
|
||||
void nodeKilled(SharedNodePointer node);
|
||||
void packetSent(quint64 length);
|
||||
|
@ -442,6 +454,8 @@ private:
|
|||
Visage _visage;
|
||||
|
||||
SixenseManager _sixenseManager;
|
||||
PrioVR _prioVR;
|
||||
JoystickManager _joystickManager;
|
||||
|
||||
Camera _myCamera; // My view onto the world
|
||||
Camera _viewFrustumOffsetCamera; // The camera we use to sometimes show the view frustum from an offset mode
|
||||
|
@ -516,6 +530,8 @@ private:
|
|||
NodeToOctreeSceneStats _octreeServerSceneStats;
|
||||
QReadWriteLock _octreeSceneStatsLock;
|
||||
|
||||
NodeBounds _nodeBoundsDisplay;
|
||||
|
||||
std::vector<VoxelFade> _voxelFades;
|
||||
ControllerScriptingInterface _controllerScriptingInterface;
|
||||
QPointer<LogDialog> _logDialog;
|
||||
|
|
|
@ -201,21 +201,26 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) {
|
|||
IMMDeviceEnumerator* pMMDeviceEnumerator = NULL;
|
||||
CoCreateInstance(__uuidof(MMDeviceEnumerator), NULL, CLSCTX_ALL, __uuidof(IMMDeviceEnumerator), (void**)&pMMDeviceEnumerator);
|
||||
IMMDevice* pEndpoint;
|
||||
pMMDeviceEnumerator->GetDefaultAudioEndpoint(mode == QAudio::AudioOutput ? eRender : eCapture, eMultimedia, &pEndpoint);
|
||||
IPropertyStore* pPropertyStore;
|
||||
pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore);
|
||||
pEndpoint->Release();
|
||||
pEndpoint = NULL;
|
||||
PROPVARIANT pv;
|
||||
PropVariantInit(&pv);
|
||||
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
|
||||
pPropertyStore->Release();
|
||||
pPropertyStore = NULL;
|
||||
//QAudio devices seems to only take the 31 first characters of the Friendly Device Name.
|
||||
const DWORD QT_WIN_MAX_AUDIO_DEVICENAME_LEN = 31;
|
||||
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal).left(QT_WIN_MAX_AUDIO_DEVICENAME_LEN);
|
||||
qDebug() << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName;
|
||||
PropVariantClear(&pv);
|
||||
hr = pMMDeviceEnumerator->GetDefaultAudioEndpoint(mode == QAudio::AudioOutput ? eRender : eCapture, eMultimedia, &pEndpoint);
|
||||
if (hr == E_NOTFOUND){
|
||||
printf("Audio Error: device not found\n");
|
||||
deviceName = QString("NONE");
|
||||
} else {
|
||||
IPropertyStore* pPropertyStore;
|
||||
pEndpoint->OpenPropertyStore(STGM_READ, &pPropertyStore);
|
||||
pEndpoint->Release();
|
||||
pEndpoint = NULL;
|
||||
PROPVARIANT pv;
|
||||
PropVariantInit(&pv);
|
||||
hr = pPropertyStore->GetValue(PKEY_Device_FriendlyName, &pv);
|
||||
pPropertyStore->Release();
|
||||
pPropertyStore = NULL;
|
||||
//QAudio devices seems to only take the 31 first characters of the Friendly Device Name.
|
||||
const DWORD QT_WIN_MAX_AUDIO_DEVICENAME_LEN = 31;
|
||||
deviceName = QString::fromWCharArray((wchar_t*)pv.pwszVal).left(QT_WIN_MAX_AUDIO_DEVICENAME_LEN);
|
||||
qDebug() << (mode == QAudio::AudioOutput ? "output" : "input") << " device:" << deviceName;
|
||||
PropVariantClear(&pv);
|
||||
}
|
||||
pMMDeviceEnumerator->Release();
|
||||
pMMDeviceEnumerator = NULL;
|
||||
CoUninitialize();
|
||||
|
|
|
@ -23,7 +23,7 @@ const QString LOGS_DIRECTORY = "Logs";
|
|||
|
||||
FileLogger::FileLogger(QObject* parent) :
|
||||
AbstractLoggerInterface(parent),
|
||||
_logData(NULL)
|
||||
_logData("")
|
||||
{
|
||||
setExtraDebugging(false);
|
||||
|
||||
|
@ -36,7 +36,7 @@ FileLogger::FileLogger(QObject* parent) :
|
|||
void FileLogger::addMessage(QString message) {
|
||||
QMutexLocker locker(&_mutex);
|
||||
emit logReceived(message);
|
||||
_logData.append(message);
|
||||
_logData += message;
|
||||
|
||||
QFile file(_fileName);
|
||||
if (file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {
|
||||
|
|
|
@ -22,11 +22,11 @@ public:
|
|||
FileLogger(QObject* parent = NULL);
|
||||
|
||||
virtual void addMessage(QString);
|
||||
virtual QStringList getLogData() { return _logData; };
|
||||
virtual QString getLogData() { return _logData; }
|
||||
virtual void locateLog();
|
||||
|
||||
private:
|
||||
QStringList _logData;
|
||||
QString _logData;
|
||||
QString _fileName;
|
||||
QMutex _mutex;
|
||||
|
||||
|
|
|
@ -37,11 +37,13 @@
|
|||
#include "Menu.h"
|
||||
#include "scripting/MenuScriptingInterface.h"
|
||||
#include "Util.h"
|
||||
#include "ui/AnimationsDialog.h"
|
||||
#include "ui/AttachmentsDialog.h"
|
||||
#include "ui/InfoView.h"
|
||||
#include "ui/MetavoxelEditor.h"
|
||||
#include "ui/ModelsBrowser.h"
|
||||
#include "ui/LoginDialog.h"
|
||||
#include "ui/NodeBounds.h"
|
||||
|
||||
|
||||
Menu* Menu::_instance = NULL;
|
||||
|
@ -192,6 +194,7 @@ Menu::Menu() :
|
|||
QAction::PreferencesRole);
|
||||
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::Attachments, 0, this, SLOT(editAttachments()));
|
||||
addActionToQMenuAndActionHash(editMenu, MenuOption::Animations, 0, this, SLOT(editAnimations()));
|
||||
|
||||
addDisabledActionAndSeparator(editMenu, "Physics");
|
||||
QObject* avatar = appInstance->getAvatar();
|
||||
|
@ -242,6 +245,19 @@ Menu::Menu() :
|
|||
SLOT(setEnable3DTVMode(bool)));
|
||||
|
||||
|
||||
QMenu* nodeBordersMenu = viewMenu->addMenu("Server Borders");
|
||||
NodeBounds& nodeBounds = appInstance->getNodeBoundsDisplay();
|
||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_1, false,
|
||||
&nodeBounds, SLOT(setShowVoxelNodes(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersModelNodes,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_2, false,
|
||||
&nodeBounds, SLOT(setShowModelNodes(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersParticleNodes,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_3, false,
|
||||
&nodeBounds, SLOT(setShowParticleNodes(bool)));
|
||||
|
||||
|
||||
QMenu* avatarSizeMenu = viewMenu->addMenu("Avatar Size");
|
||||
|
||||
addActionToQMenuAndActionHash(avatarSizeMenu,
|
||||
|
@ -331,7 +347,7 @@ Menu::Menu() :
|
|||
#endif
|
||||
|
||||
#ifdef HAVE_VISAGE
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, true,
|
||||
addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, false,
|
||||
appInstance->getVisage(), SLOT(updateEnabled()));
|
||||
#endif
|
||||
|
||||
|
@ -351,6 +367,7 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::HandsCollideWithSelf, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::ShowIKConstraints, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlignForearmsWithWrists, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::AlternateIK, 0, false);
|
||||
|
||||
addDisabledActionAndSeparator(developerMenu, "Testing");
|
||||
|
||||
|
@ -849,6 +866,15 @@ void Menu::editAttachments() {
|
|||
}
|
||||
}
|
||||
|
||||
void Menu::editAnimations() {
|
||||
if (!_animationsDialog) {
|
||||
_animationsDialog = new AnimationsDialog();
|
||||
_animationsDialog->show();
|
||||
} else {
|
||||
_animationsDialog->close();
|
||||
}
|
||||
}
|
||||
|
||||
void Menu::goToDomain(const QString newDomain) {
|
||||
if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) {
|
||||
// send a node kill request, indicating to other clients that they should play the "disappeared" effect
|
||||
|
|
|
@ -64,6 +64,7 @@ struct ViewFrustumOffset {
|
|||
|
||||
class QSettings;
|
||||
|
||||
class AnimationsDialog;
|
||||
class AttachmentsDialog;
|
||||
class BandwidthDialog;
|
||||
class LodToolsDialog;
|
||||
|
@ -182,6 +183,7 @@ private slots:
|
|||
void aboutApp();
|
||||
void editPreferences();
|
||||
void editAttachments();
|
||||
void editAnimations();
|
||||
void goToDomainDialog();
|
||||
void goToLocation();
|
||||
void nameLocation();
|
||||
|
@ -266,6 +268,7 @@ private:
|
|||
QAction* _loginAction;
|
||||
QPointer<PreferencesDialog> _preferencesDialog;
|
||||
QPointer<AttachmentsDialog> _attachmentsDialog;
|
||||
QPointer<AnimationsDialog> _animationsDialog;
|
||||
QAction* _chatAction;
|
||||
QString _snapshotsLocation;
|
||||
QString _scriptsLocation;
|
||||
|
@ -274,7 +277,9 @@ private:
|
|||
namespace MenuOption {
|
||||
const QString AboutApp = "About Interface";
|
||||
const QString AlignForearmsWithWrists = "Align Forearms with Wrists";
|
||||
const QString AlternateIK = "Alternate IK";
|
||||
const QString AmbientOcclusion = "Ambient Occlusion";
|
||||
const QString Animations = "Animations...";
|
||||
const QString Atmosphere = "Atmosphere";
|
||||
const QString Attachments = "Attachments...";
|
||||
const QString AudioNoiseReduction = "Audio Noise Reduction";
|
||||
|
@ -373,6 +378,9 @@ namespace MenuOption {
|
|||
const QString SettingsExport = "Export Settings";
|
||||
const QString SettingsImport = "Import Settings";
|
||||
const QString Shadows = "Shadows";
|
||||
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
|
||||
const QString ShowBordersModelNodes = "Show Model Nodes";
|
||||
const QString ShowBordersParticleNodes = "Show Particle Nodes";
|
||||
const QString ShowIKConstraints = "Show IK Constraints";
|
||||
const QString Stars = "Stars";
|
||||
const QString Stats = "Stats";
|
||||
|
|
|
@ -214,37 +214,37 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) {
|
|||
if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) {
|
||||
renderBody(renderMode, glowLevel);
|
||||
}
|
||||
if (renderMode != SHADOW_RENDER_MODE &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) {
|
||||
_skeletonModel.updateShapePositions();
|
||||
_skeletonModel.renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
if (renderMode != SHADOW_RENDER_MODE &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) {
|
||||
if (shouldRenderHead(cameraPosition, renderMode)) {
|
||||
getHead()->getFaceModel().updateShapePositions();
|
||||
if (renderMode != SHADOW_RENDER_MODE) {
|
||||
bool renderSkeleton = Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes);
|
||||
bool renderHead = Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes);
|
||||
bool renderBounding = Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes);
|
||||
if (renderSkeleton || renderHead || renderBounding) {
|
||||
updateShapePositions();
|
||||
}
|
||||
|
||||
if (renderSkeleton) {
|
||||
_skeletonModel.renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
|
||||
if (renderHead && shouldRenderHead(cameraPosition, renderMode)) {
|
||||
getHead()->getFaceModel().renderJointCollisionShapes(0.7f);
|
||||
}
|
||||
}
|
||||
if (renderMode != SHADOW_RENDER_MODE &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) {
|
||||
if (shouldRenderHead(cameraPosition, renderMode)) {
|
||||
getHead()->getFaceModel().updateShapePositions();
|
||||
if (renderBounding && shouldRenderHead(cameraPosition, renderMode)) {
|
||||
getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f);
|
||||
_skeletonModel.updateShapePositions();
|
||||
_skeletonModel.renderBoundingCollisionShapes(0.7f);
|
||||
}
|
||||
}
|
||||
// If this is the avatar being looked at, render a little ball above their head
|
||||
if (renderMode != SHADOW_RENDER_MODE &&_isLookAtTarget) {
|
||||
const float LOOK_AT_INDICATOR_RADIUS = 0.03f;
|
||||
const float LOOK_AT_INDICATOR_HEIGHT = 0.60f;
|
||||
const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f };
|
||||
glPushMatrix();
|
||||
glColor4fv(LOOK_AT_INDICATOR_COLOR);
|
||||
glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z);
|
||||
glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15);
|
||||
glPopMatrix();
|
||||
|
||||
// If this is the avatar being looked at, render a little ball above their head
|
||||
if (_isLookAtTarget) {
|
||||
const float LOOK_AT_INDICATOR_RADIUS = 0.03f;
|
||||
const float LOOK_AT_INDICATOR_HEIGHT = 0.60f;
|
||||
const float LOOK_AT_INDICATOR_COLOR[] = { 0.8f, 0.0f, 0.0f, 0.5f };
|
||||
glPushMatrix();
|
||||
glColor4fv(LOOK_AT_INDICATOR_COLOR);
|
||||
glTranslatef(_position.x, _position.y + (getSkeletonHeight() * LOOK_AT_INDICATOR_HEIGHT), _position.z);
|
||||
glutSolidSphere(LOOK_AT_INDICATOR_RADIUS, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
// quick check before falling into the code below:
|
||||
|
@ -585,6 +585,12 @@ void Avatar::updateShapePositions() {
|
|||
_skeletonModel.updateShapePositions();
|
||||
Model& headModel = getHead()->getFaceModel();
|
||||
headModel.updateShapePositions();
|
||||
/* KEEP FOR DEBUG: use this in rather than code above to see shapes
|
||||
* in their default positions where the bounding shape is computed.
|
||||
_skeletonModel.resetShapePositions();
|
||||
Model& headModel = getHead()->getFaceModel();
|
||||
headModel.resetShapePositions();
|
||||
*/
|
||||
}
|
||||
|
||||
bool Avatar::findCollisions(const QVector<const Shape*>& shapes, CollisionList& collisions) {
|
||||
|
|
|
@ -49,8 +49,6 @@ const float COLLISION_RADIUS_SCALE = 0.125f;
|
|||
const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f;
|
||||
const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
|
||||
|
||||
const float DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS = 5.0f * 1000.0f;
|
||||
|
||||
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
|
||||
// to properly follow avatar size.
|
||||
float DEFAULT_MOTOR_TIMESCALE = 0.25f;
|
||||
|
@ -83,11 +81,6 @@ MyAvatar::MyAvatar() :
|
|||
for (int i = 0; i < MAX_DRIVE_KEYS; i++) {
|
||||
_driveKeys[i] = 0.0f;
|
||||
}
|
||||
|
||||
// update our location every 5 seconds in the data-server, assuming that we are authenticated with one
|
||||
QTimer* locationUpdateTimer = new QTimer(this);
|
||||
connect(locationUpdateTimer, &QTimer::timeout, this, &MyAvatar::updateLocationInDataServer);
|
||||
locationUpdateTimer->start(DATA_SERVER_LOCATION_CHANGE_UPDATE_MSECS);
|
||||
}
|
||||
|
||||
MyAvatar::~MyAvatar() {
|
||||
|
@ -111,7 +104,7 @@ void MyAvatar::reset() {
|
|||
void MyAvatar::update(float deltaTime) {
|
||||
Head* head = getHead();
|
||||
head->relaxLean(deltaTime);
|
||||
updateFromFaceTracker(deltaTime);
|
||||
updateFromTrackers(deltaTime);
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) {
|
||||
// Faceshift drive is enabled, set the avatar drive based on the head position
|
||||
moveWithLean();
|
||||
|
@ -234,26 +227,33 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
}
|
||||
|
||||
// Update avatar head rotation with sensor data
|
||||
void MyAvatar::updateFromFaceTracker(float deltaTime) {
|
||||
void MyAvatar::updateFromTrackers(float deltaTime) {
|
||||
glm::vec3 estimatedPosition, estimatedRotation;
|
||||
|
||||
FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker();
|
||||
if (tracker) {
|
||||
estimatedPosition = tracker->getHeadTranslation();
|
||||
estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation()));
|
||||
|
||||
// Rotate the body if the head is turned beyond the screen
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) {
|
||||
const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f;
|
||||
const float TRACKER_MIN_YAW_TURN = 15.0f;
|
||||
const float TRACKER_MAX_YAW_TURN = 50.0f;
|
||||
if ( (fabs(estimatedRotation.y) > TRACKER_MIN_YAW_TURN) &&
|
||||
(fabs(estimatedRotation.y) < TRACKER_MAX_YAW_TURN) ) {
|
||||
if (estimatedRotation.y > 0.0f) {
|
||||
_bodyYawDelta += (estimatedRotation.y - TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
|
||||
} else {
|
||||
_bodyYawDelta += (estimatedRotation.y + TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
|
||||
}
|
||||
if (Application::getInstance()->getPrioVR()->hasHeadRotation()) {
|
||||
estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation()));
|
||||
estimatedRotation.x *= -1.0f;
|
||||
estimatedRotation.z *= -1.0f;
|
||||
|
||||
} else {
|
||||
FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker();
|
||||
if (tracker) {
|
||||
estimatedPosition = tracker->getHeadTranslation();
|
||||
estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation()));
|
||||
}
|
||||
}
|
||||
|
||||
// Rotate the body if the head is turned beyond the screen
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) {
|
||||
const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f;
|
||||
const float TRACKER_MIN_YAW_TURN = 15.0f;
|
||||
const float TRACKER_MAX_YAW_TURN = 50.0f;
|
||||
if ( (fabs(estimatedRotation.y) > TRACKER_MIN_YAW_TURN) &&
|
||||
(fabs(estimatedRotation.y) < TRACKER_MAX_YAW_TURN) ) {
|
||||
if (estimatedRotation.y > 0.0f) {
|
||||
_bodyYawDelta += (estimatedRotation.y - TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
|
||||
} else {
|
||||
_bodyYawDelta += (estimatedRotation.y + TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -264,13 +264,26 @@ void MyAvatar::updateFromFaceTracker(float deltaTime) {
|
|||
// their head only 30 degrees or so, this may correspond to a 90 degree field of view.
|
||||
// Note that roll is magnified by a constant because it is not related to field of view.
|
||||
|
||||
float magnifyFieldOfView = Menu::getInstance()->getFieldOfView() / Menu::getInstance()->getRealWorldFieldOfView();
|
||||
|
||||
|
||||
Head* head = getHead();
|
||||
head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView);
|
||||
head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView);
|
||||
if (OculusManager::isConnected()){
|
||||
head->setDeltaPitch(estimatedRotation.x);
|
||||
head->setDeltaYaw(estimatedRotation.y);
|
||||
} else {
|
||||
float magnifyFieldOfView = Menu::getInstance()->getFieldOfView() / Menu::getInstance()->getRealWorldFieldOfView();
|
||||
head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView);
|
||||
head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView);
|
||||
}
|
||||
head->setDeltaRoll(estimatedRotation.z);
|
||||
|
||||
// the priovr can give us exact lean
|
||||
if (Application::getInstance()->getPrioVR()->isActive()) {
|
||||
glm::vec3 eulers = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getTorsoRotation()));
|
||||
head->setLeanSideways(eulers.z);
|
||||
head->setLeanForward(eulers.x);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update torso lean distance based on accelerometer data
|
||||
const float TORSO_LENGTH = 0.5f;
|
||||
glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f);
|
||||
|
@ -415,6 +428,46 @@ void MyAvatar::setGravity(const glm::vec3& gravity) {
|
|||
}
|
||||
}
|
||||
|
||||
AnimationHandlePointer MyAvatar::addAnimationHandle() {
|
||||
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
|
||||
handle->setLoop(true);
|
||||
handle->start();
|
||||
_animationHandles.append(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) {
|
||||
handle->stop();
|
||||
_animationHandles.removeOne(handle);
|
||||
}
|
||||
|
||||
void MyAvatar::startAnimation(const QString& url, float fps, float priority, bool loop) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url),
|
||||
Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop));
|
||||
return;
|
||||
}
|
||||
AnimationHandlePointer handle = _skeletonModel.createAnimationHandle();
|
||||
handle->setURL(url);
|
||||
handle->setFPS(fps);
|
||||
handle->setPriority(priority);
|
||||
handle->setLoop(loop);
|
||||
handle->start();
|
||||
}
|
||||
|
||||
void MyAvatar::stopAnimation(const QString& url) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url));
|
||||
return;
|
||||
}
|
||||
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
|
||||
if (handle->getURL() == url) {
|
||||
handle->stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::saveData(QSettings* settings) {
|
||||
settings->beginGroup("Avatar");
|
||||
|
||||
|
@ -453,6 +506,16 @@ void MyAvatar::saveData(QSettings* settings) {
|
|||
}
|
||||
settings->endArray();
|
||||
|
||||
settings->beginWriteArray("animationHandles");
|
||||
for (int i = 0; i < _animationHandles.size(); i++) {
|
||||
settings->setArrayIndex(i);
|
||||
const AnimationHandlePointer& pointer = _animationHandles.at(i);
|
||||
settings->setValue("url", pointer->getURL());
|
||||
settings->setValue("fps", pointer->getFPS());
|
||||
settings->setValue("priority", pointer->getPriority());
|
||||
}
|
||||
settings->endArray();
|
||||
|
||||
settings->setValue("displayName", _displayName);
|
||||
|
||||
settings->endGroup();
|
||||
|
@ -503,6 +566,22 @@ void MyAvatar::loadData(QSettings* settings) {
|
|||
settings->endArray();
|
||||
setAttachmentData(attachmentData);
|
||||
|
||||
int animationCount = settings->beginReadArray("animationHandles");
|
||||
while (_animationHandles.size() > animationCount) {
|
||||
_animationHandles.takeLast()->stop();
|
||||
}
|
||||
while (_animationHandles.size() < animationCount) {
|
||||
addAnimationHandle();
|
||||
}
|
||||
for (int i = 0; i < animationCount; i++) {
|
||||
settings->setArrayIndex(i);
|
||||
const AnimationHandlePointer& handle = _animationHandles.at(i);
|
||||
handle->setURL(settings->value("url").toUrl());
|
||||
handle->setFPS(loadSetting(settings, "fps", 30.0f));
|
||||
handle->setPriority(loadSetting(settings, "priority", 1.0f));
|
||||
}
|
||||
settings->endArray();
|
||||
|
||||
setDisplayName(settings->value("displayName").toString());
|
||||
|
||||
settings->endGroup();
|
||||
|
@ -1419,29 +1498,6 @@ void MyAvatar::resetSize() {
|
|||
qDebug("Reseted scale to %f", _targetScale);
|
||||
}
|
||||
|
||||
static QByteArray createByteArray(const glm::vec3& vector) {
|
||||
return QByteArray::number(vector.x) + ',' + QByteArray::number(vector.y) + ',' + QByteArray::number(vector.z);
|
||||
}
|
||||
|
||||
void MyAvatar::updateLocationInDataServer() {
|
||||
// TODO: don't re-send this when it hasn't change or doesn't change by some threshold
|
||||
// This will required storing the last sent values and clearing them when the AccountManager rootURL changes
|
||||
|
||||
AccountManager& accountManager = AccountManager::getInstance();
|
||||
|
||||
if (accountManager.isLoggedIn()) {
|
||||
QString positionString(createByteArray(_position));
|
||||
QString orientationString(createByteArray(glm::degrees(safeEulerAngles(getOrientation()))));
|
||||
|
||||
// construct the json to put the user's location
|
||||
QString locationPutJson = QString() + "{\"address\":{\"position\":\""
|
||||
+ positionString + "\", \"orientation\":\"" + orientationString + "\"}}";
|
||||
|
||||
accountManager.authenticatedRequest("/api/v1/users/address", QNetworkAccessManager::PutOperation,
|
||||
JSONCallbackParameters(), locationPutJson.toUtf8());
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) {
|
||||
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
|
@ -1556,3 +1612,4 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe
|
|||
getHead()->addLeanDeltas(sideways, forward);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
void reset();
|
||||
void update(float deltaTime);
|
||||
void simulate(float deltaTime);
|
||||
void updateFromFaceTracker(float deltaTime);
|
||||
void updateFromTrackers(float deltaTime);
|
||||
void moveWithLean();
|
||||
|
||||
void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE);
|
||||
|
@ -62,6 +62,17 @@ public:
|
|||
glm::vec3 getUprightHeadPosition() const;
|
||||
bool getShouldRenderLocally() const { return _shouldRender; }
|
||||
|
||||
const QList<AnimationHandlePointer>& getAnimationHandles() const { return _animationHandles; }
|
||||
AnimationHandlePointer addAnimationHandle();
|
||||
void removeAnimationHandle(const AnimationHandlePointer& handle);
|
||||
|
||||
/// Allows scripts to run animations.
|
||||
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f,
|
||||
float priority = 1.0f, bool loop = false);
|
||||
|
||||
/// Stops an animation as identified by a URL.
|
||||
Q_INVOKABLE void stopAnimation(const QString& url);
|
||||
|
||||
// get/set avatar data
|
||||
void saveData(QSettings* settings);
|
||||
void loadData(QSettings* settings);
|
||||
|
@ -108,7 +119,6 @@ public slots:
|
|||
void decreaseSize();
|
||||
void resetSize();
|
||||
|
||||
void updateLocationInDataServer();
|
||||
void goToLocationFromResponse(const QJsonObject& jsonObject);
|
||||
|
||||
// Set/Get update the thrust that will move the avatar around
|
||||
|
@ -152,6 +162,8 @@ private:
|
|||
bool _billboardValid;
|
||||
float _oculusYawOffset;
|
||||
|
||||
QList<AnimationHandlePointer> _animationHandles;
|
||||
|
||||
// private methods
|
||||
void updateOrientation(float deltaTime);
|
||||
void updateMotorFromKeyboard(float deltaTime, bool walking);
|
||||
|
|
|
@ -33,14 +33,28 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) {
|
|||
return; // only simulate for own avatar
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
PrioVR* prioVR = Application::getInstance()->getPrioVR();
|
||||
if (prioVR->isActive()) {
|
||||
for (int i = 0; i < prioVR->getJointRotations().size(); i++) {
|
||||
int humanIKJointIndex = prioVR->getHumanIKJointIndices().at(i);
|
||||
if (humanIKJointIndex == -1) {
|
||||
continue;
|
||||
}
|
||||
int jointIndex = geometry.humanIKJointIndices.at(humanIKJointIndex);
|
||||
if (jointIndex != -1) {
|
||||
setJointRotation(jointIndex, _rotation * prioVR->getJointRotations().at(i), true);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// find the left and rightmost active palms
|
||||
int leftPalmIndex, rightPalmIndex;
|
||||
Hand* hand = _owningAvatar->getHand();
|
||||
hand->getLeftRightPalmIndices(leftPalmIndex, rightPalmIndex);
|
||||
|
||||
const float HAND_RESTORATION_RATE = 0.25f;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
const float HAND_RESTORATION_RATE = 0.25f;
|
||||
if (leftPalmIndex == -1) {
|
||||
// palms are not yet set, use mouse
|
||||
if (_owningAvatar->getHandState() == HAND_STATE_NULL) {
|
||||
|
@ -150,7 +164,8 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
|||
|
||||
// rotate palm to align with its normal (normal points out of hand's palm)
|
||||
glm::quat palmRotation;
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) {
|
||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK) &&
|
||||
Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) {
|
||||
getJointRotation(parentJointIndex, palmRotation, true);
|
||||
} else {
|
||||
getJointRotation(jointIndex, palmRotation, true);
|
||||
|
@ -162,7 +177,10 @@ void SkeletonModel::applyPalmData(int jointIndex, PalmData& palm) {
|
|||
palmRotation = rotationBetween(palmRotation * glm::vec3(-sign, 0.0f, 0.0f), direction) * palmRotation;
|
||||
|
||||
// set hand position, rotation
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) {
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::AlternateIK)) {
|
||||
setHandPosition(jointIndex, palm.getPosition(), palmRotation);
|
||||
|
||||
} else if (Menu::getInstance()->isOptionChecked(MenuOption::AlignForearmsWithWrists)) {
|
||||
glm::vec3 forearmVector = palmRotation * glm::vec3(sign, 0.0f, 0.0f);
|
||||
setJointPosition(parentJointIndex, palm.getPosition() + forearmVector *
|
||||
geometry.joints.at(jointIndex).distanceToParent * extractUniformScale(_scale));
|
||||
|
@ -186,7 +204,7 @@ void SkeletonModel::updateJointState(int index) {
|
|||
}
|
||||
|
||||
void SkeletonModel::maybeUpdateLeanRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||
if (!_owningAvatar->isMyAvatar()) {
|
||||
if (!_owningAvatar->isMyAvatar() || Application::getInstance()->getPrioVR()->isActive()) {
|
||||
return;
|
||||
}
|
||||
// get the rotation axes in joint space and use them to adjust the rotation
|
||||
|
@ -262,3 +280,66 @@ void SkeletonModel::renderJointConstraints(int jointIndex) {
|
|||
glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
void SkeletonModel::setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation) {
|
||||
// this algorithm is from sample code from sixense
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
int elbowJointIndex = geometry.joints.at(jointIndex).parentIndex;
|
||||
if (elbowJointIndex == -1) {
|
||||
return;
|
||||
}
|
||||
int shoulderJointIndex = geometry.joints.at(elbowJointIndex).parentIndex;
|
||||
glm::vec3 shoulderPosition;
|
||||
if (!getJointPosition(shoulderJointIndex, shoulderPosition)) {
|
||||
return;
|
||||
}
|
||||
// precomputed lengths
|
||||
float scale = extractUniformScale(_scale);
|
||||
float upperArmLength = geometry.joints.at(elbowJointIndex).distanceToParent * scale;
|
||||
float lowerArmLength = geometry.joints.at(jointIndex).distanceToParent * scale;
|
||||
|
||||
// first set wrist position
|
||||
glm::vec3 wristPosition = position;
|
||||
|
||||
glm::vec3 shoulderToWrist = wristPosition - shoulderPosition;
|
||||
float distanceToWrist = glm::length(shoulderToWrist);
|
||||
|
||||
// prevent gimbal lock
|
||||
if (distanceToWrist > upperArmLength + lowerArmLength - EPSILON) {
|
||||
distanceToWrist = upperArmLength + lowerArmLength - EPSILON;
|
||||
shoulderToWrist = glm::normalize(shoulderToWrist) * distanceToWrist;
|
||||
wristPosition = shoulderPosition + shoulderToWrist;
|
||||
}
|
||||
|
||||
// cosine of angle from upper arm to hand vector
|
||||
float cosA = (upperArmLength * upperArmLength + distanceToWrist * distanceToWrist - lowerArmLength * lowerArmLength) /
|
||||
(2 * upperArmLength * distanceToWrist);
|
||||
float mid = upperArmLength * cosA;
|
||||
float height = sqrt(upperArmLength * upperArmLength + mid * mid - 2 * upperArmLength * mid * cosA);
|
||||
|
||||
// direction of the elbow
|
||||
glm::vec3 handNormal = glm::cross(rotation * glm::vec3(0.0f, 1.0f, 0.0f), shoulderToWrist); // elbow rotating with wrist
|
||||
glm::vec3 relaxedNormal = glm::cross(glm::vec3(0.0f, 1.0f, 0.0f), shoulderToWrist); // elbow pointing straight down
|
||||
const float NORMAL_WEIGHT = 0.5f;
|
||||
glm::vec3 finalNormal = glm::mix(relaxedNormal, handNormal, NORMAL_WEIGHT);
|
||||
|
||||
bool rightHand = (jointIndex == geometry.rightHandJointIndex);
|
||||
if (rightHand ? (finalNormal.y > 0.0f) : (finalNormal.y < 0.0f)) {
|
||||
finalNormal.y = 0.0f; // dont allow elbows to point inward (y is vertical axis)
|
||||
}
|
||||
|
||||
glm::vec3 tangent = glm::normalize(glm::cross(shoulderToWrist, finalNormal));
|
||||
|
||||
// ik solution
|
||||
glm::vec3 elbowPosition = shoulderPosition + glm::normalize(shoulderToWrist) * mid - tangent * height;
|
||||
|
||||
glm::vec3 forwardVector(rightHand ? -1.0f : 1.0f, 0.0f, 0.0f);
|
||||
|
||||
glm::quat shoulderRotation = rotationBetween(forwardVector, elbowPosition - shoulderPosition);
|
||||
setJointRotation(shoulderJointIndex, shoulderRotation, true);
|
||||
|
||||
setJointRotation(elbowJointIndex, rotationBetween(shoulderRotation * forwardVector,
|
||||
wristPosition - elbowPosition) * shoulderRotation, true);
|
||||
|
||||
setJointRotation(jointIndex, rotation, true);
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ protected:
|
|||
private:
|
||||
|
||||
void renderJointConstraints(int jointIndex);
|
||||
void setHandPosition(int jointIndex, const glm::vec3& position, const glm::quat& rotation);
|
||||
|
||||
Avatar* _owningAvatar;
|
||||
};
|
||||
|
|
64
interface/src/devices/JoystickManager.cpp
Normal file
64
interface/src/devices/JoystickManager.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// JoystickManager.cpp
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/15/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include <limits>
|
||||
|
||||
#include <QtDebug>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "JoystickManager.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
JoystickManager::JoystickManager() {
|
||||
#ifdef HAVE_SDL
|
||||
SDL_Init(SDL_INIT_JOYSTICK);
|
||||
int joystickCount = SDL_NumJoysticks();
|
||||
for (int i = 0; i < joystickCount; i++) {
|
||||
SDL_Joystick* joystick = SDL_JoystickOpen(i);
|
||||
if (joystick) {
|
||||
JoystickState state = { SDL_JoystickName(i), QVector<float>(SDL_JoystickNumAxes(joystick)),
|
||||
QVector<bool>(SDL_JoystickNumButtons(joystick)) };
|
||||
_joystickStates.append(state);
|
||||
_joysticks.append(joystick);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
JoystickManager::~JoystickManager() {
|
||||
#ifdef HAVE_SDL
|
||||
foreach (SDL_Joystick* joystick, _joysticks) {
|
||||
SDL_JoystickClose(joystick);
|
||||
}
|
||||
SDL_Quit();
|
||||
#endif
|
||||
}
|
||||
|
||||
void JoystickManager::update() {
|
||||
#ifdef HAVE_SDL
|
||||
SDL_JoystickUpdate();
|
||||
|
||||
for (int i = 0; i < _joystickStates.size(); i++) {
|
||||
SDL_Joystick* joystick = _joysticks.at(i);
|
||||
JoystickState& state = _joystickStates[i];
|
||||
for (int j = 0; j < state.axes.size(); j++) {
|
||||
float value = glm::round(SDL_JoystickGetAxis(joystick, j) + 0.5f) / numeric_limits<short>::max();
|
||||
const float DEAD_ZONE = 0.1f;
|
||||
state.axes[j] = glm::abs(value) < DEAD_ZONE ? 0.0f : value;
|
||||
}
|
||||
for (int j = 0; j < state.buttons.size(); j++) {
|
||||
state.buttons[j] = SDL_JoystickGetButton(joystick, j);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
53
interface/src/devices/JoystickManager.h
Normal file
53
interface/src/devices/JoystickManager.h
Normal file
|
@ -0,0 +1,53 @@
|
|||
//
|
||||
// JoystickManager.h
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/15/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_JoystickManager_h
|
||||
#define hifi_JoystickManager_h
|
||||
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
#include <SDL.h>
|
||||
#undef main
|
||||
#endif
|
||||
|
||||
class JoystickState;
|
||||
|
||||
/// Handles joystick input through SDL.
|
||||
class JoystickManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
JoystickManager();
|
||||
virtual ~JoystickManager();
|
||||
|
||||
const QVector<JoystickState>& getJoystickStates() const { return _joystickStates; }
|
||||
|
||||
void update();
|
||||
|
||||
private:
|
||||
QVector<JoystickState> _joystickStates;
|
||||
|
||||
#ifdef HAVE_SDL
|
||||
QVector<SDL_Joystick*> _joysticks;
|
||||
#endif
|
||||
};
|
||||
|
||||
class JoystickState {
|
||||
public:
|
||||
QString name;
|
||||
QVector<float> axes;
|
||||
QVector<bool> buttons;
|
||||
};
|
||||
|
||||
#endif // hifi_JoystickManager_h
|
213
interface/src/devices/PrioVR.cpp
Normal file
213
interface/src/devices/PrioVR.cpp
Normal file
|
@ -0,0 +1,213 @@
|
|||
//
|
||||
// PrioVR.cpp
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/12/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include <QTimer>
|
||||
#include <QtDebug>
|
||||
|
||||
#include <FBXReader.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "PrioVR.h"
|
||||
#include "ui/TextRenderer.h"
|
||||
|
||||
#ifdef HAVE_PRIOVR
|
||||
const unsigned int SERIAL_LIST[] = { 0x00000001, 0x00000000, 0x00000008, 0x00000009, 0x0000000A,
|
||||
0x0000000C, 0x0000000D, 0x0000000E, 0x00000004, 0x00000005, 0x00000010, 0x00000011 };
|
||||
const unsigned char AXIS_LIST[] = { 9, 43, 37, 37, 37, 13, 13, 13, 52, 52, 28, 28 };
|
||||
const int LIST_LENGTH = sizeof(SERIAL_LIST) / sizeof(SERIAL_LIST[0]);
|
||||
|
||||
const char* JOINT_NAMES[] = { "Neck", "Spine", "LeftArm", "LeftForeArm", "LeftHand", "RightArm",
|
||||
"RightForeArm", "RightHand", "LeftUpLeg", "LeftLeg", "RightUpLeg", "RightLeg" };
|
||||
|
||||
static int indexOfHumanIKJoint(const char* jointName) {
|
||||
for (int i = 0;; i++) {
|
||||
QByteArray humanIKJoint = HUMANIK_JOINTS[i];
|
||||
if (humanIKJoint.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
if (humanIKJoint == jointName) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void setPalm(float deltaTime, int index) {
|
||||
MyAvatar* avatar = Application::getInstance()->getAvatar();
|
||||
Hand* hand = avatar->getHand();
|
||||
PalmData* palm;
|
||||
bool foundHand = false;
|
||||
for (size_t j = 0; j < hand->getNumPalms(); j++) {
|
||||
if (hand->getPalms()[j].getSixenseID() == index) {
|
||||
palm = &(hand->getPalms()[j]);
|
||||
foundHand = true;
|
||||
}
|
||||
}
|
||||
if (!foundHand) {
|
||||
PalmData newPalm(hand);
|
||||
hand->getPalms().push_back(newPalm);
|
||||
palm = &(hand->getPalms()[hand->getNumPalms() - 1]);
|
||||
palm->setSixenseID(index);
|
||||
}
|
||||
|
||||
palm->setActive(true);
|
||||
|
||||
// Read controller buttons and joystick into the hand
|
||||
if (!Application::getInstance()->getJoystickManager()->getJoystickStates().isEmpty()) {
|
||||
const JoystickState& state = Application::getInstance()->getJoystickManager()->getJoystickStates().at(0);
|
||||
if (state.axes.size() >= 4 && state.buttons.size() >= 4) {
|
||||
if (index == SIXENSE_CONTROLLER_ID_LEFT_HAND) {
|
||||
palm->setControllerButtons(state.buttons.at(1) ? BUTTON_FWD : 0);
|
||||
palm->setTrigger(state.buttons.at(0) ? 1.0f : 0.0f);
|
||||
palm->setJoystick(state.axes.at(0), -state.axes.at(1));
|
||||
|
||||
} else {
|
||||
palm->setControllerButtons(state.buttons.at(3) ? BUTTON_FWD : 0);
|
||||
palm->setTrigger(state.buttons.at(2) ? 1.0f : 0.0f);
|
||||
palm->setJoystick(state.axes.at(2), -state.axes.at(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
glm::vec3 position;
|
||||
glm::quat rotation;
|
||||
|
||||
Model* skeletonModel = &Application::getInstance()->getAvatar()->getSkeletonModel();
|
||||
int jointIndex;
|
||||
glm::quat inverseRotation = glm::inverse(Application::getInstance()->getAvatar()->getOrientation());
|
||||
if (index == SIXENSE_CONTROLLER_ID_LEFT_HAND) {
|
||||
jointIndex = skeletonModel->getLeftHandJointIndex();
|
||||
skeletonModel->getJointRotation(jointIndex, rotation, true);
|
||||
rotation = inverseRotation * rotation * glm::quat(glm::vec3(0.0f, PI_OVER_TWO, 0.0f));
|
||||
|
||||
} else {
|
||||
jointIndex = skeletonModel->getRightHandJointIndex();
|
||||
skeletonModel->getJointRotation(jointIndex, rotation, true);
|
||||
rotation = inverseRotation * rotation * glm::quat(glm::vec3(0.0f, -PI_OVER_TWO, 0.0f));
|
||||
}
|
||||
skeletonModel->getJointPosition(jointIndex, position);
|
||||
position = inverseRotation * (position - skeletonModel->getTranslation());
|
||||
|
||||
palm->setRawRotation(rotation);
|
||||
|
||||
// Compute current velocity from position change
|
||||
glm::vec3 rawVelocity;
|
||||
if (deltaTime > 0.f) {
|
||||
rawVelocity = (position - palm->getRawPosition()) / deltaTime;
|
||||
} else {
|
||||
rawVelocity = glm::vec3(0.0f);
|
||||
}
|
||||
palm->setRawVelocity(rawVelocity);
|
||||
palm->setRawPosition(position);
|
||||
|
||||
// Store the one fingertip in the palm structure so we can track velocity
|
||||
const float FINGER_LENGTH = 0.3f; // meters
|
||||
const glm::vec3 FINGER_VECTOR(0.0f, 0.0f, FINGER_LENGTH);
|
||||
const glm::vec3 newTipPosition = position + rotation * FINGER_VECTOR;
|
||||
glm::vec3 oldTipPosition = palm->getTipRawPosition();
|
||||
if (deltaTime > 0.f) {
|
||||
palm->setTipVelocity((newTipPosition - oldTipPosition) / deltaTime);
|
||||
} else {
|
||||
palm->setTipVelocity(glm::vec3(0.f));
|
||||
}
|
||||
palm->setTipPosition(newTipPosition);
|
||||
}
|
||||
#endif
|
||||
|
||||
PrioVR::PrioVR() {
|
||||
#ifdef HAVE_PRIOVR
|
||||
char jointsDiscovered[LIST_LENGTH];
|
||||
_skeletalDevice = yei_setUpPrioVRSensors(0x00000000, const_cast<unsigned int*>(SERIAL_LIST),
|
||||
const_cast<unsigned char*>(AXIS_LIST), jointsDiscovered, LIST_LENGTH, YEI_TIMESTAMP_SYSTEM);
|
||||
if (!_skeletalDevice) {
|
||||
return;
|
||||
}
|
||||
_jointRotations.resize(LIST_LENGTH);
|
||||
_lastJointRotations.resize(LIST_LENGTH);
|
||||
for (int i = 0; i < LIST_LENGTH; i++) {
|
||||
_humanIKJointIndices.append(jointsDiscovered[i] ? indexOfHumanIKJoint(JOINT_NAMES[i]) : -1);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
PrioVR::~PrioVR() {
|
||||
#ifdef HAVE_PRIOVR
|
||||
if (_skeletalDevice) {
|
||||
yei_stopStreaming(_skeletalDevice);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
const int HEAD_ROTATION_INDEX = 0;
|
||||
|
||||
bool PrioVR::hasHeadRotation() const {
|
||||
return _humanIKJointIndices.size() > HEAD_ROTATION_INDEX && _humanIKJointIndices.at(HEAD_ROTATION_INDEX) != -1;
|
||||
}
|
||||
|
||||
glm::quat PrioVR::getHeadRotation() const {
|
||||
return _jointRotations.size() > HEAD_ROTATION_INDEX ? _jointRotations.at(HEAD_ROTATION_INDEX) : glm::quat();
|
||||
}
|
||||
|
||||
glm::quat PrioVR::getTorsoRotation() const {
|
||||
const int TORSO_ROTATION_INDEX = 1;
|
||||
return _jointRotations.size() > TORSO_ROTATION_INDEX ? _jointRotations.at(TORSO_ROTATION_INDEX) : glm::quat();
|
||||
}
|
||||
|
||||
void PrioVR::update(float deltaTime) {
|
||||
#ifdef HAVE_PRIOVR
|
||||
if (!_skeletalDevice) {
|
||||
return;
|
||||
}
|
||||
unsigned int timestamp;
|
||||
yei_getLastStreamDataAll(_skeletalDevice, (char*)_jointRotations.data(),
|
||||
_jointRotations.size() * sizeof(glm::quat), ×tamp);
|
||||
|
||||
// convert to our expected coordinate system, average with last rotations to smooth
|
||||
for (int i = 0; i < _jointRotations.size(); i++) {
|
||||
_jointRotations[i].y *= -1.0f;
|
||||
_jointRotations[i].z *= -1.0f;
|
||||
|
||||
glm::quat lastRotation = _lastJointRotations.at(i);
|
||||
_lastJointRotations[i] = _jointRotations.at(i);
|
||||
_jointRotations[i] = safeMix(lastRotation, _jointRotations.at(i), 0.5f);
|
||||
}
|
||||
|
||||
// convert the joysticks into palm data
|
||||
setPalm(deltaTime, SIXENSE_CONTROLLER_ID_LEFT_HAND);
|
||||
setPalm(deltaTime, SIXENSE_CONTROLLER_ID_RIGHT_HAND);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PrioVR::reset() {
|
||||
#ifdef HAVE_PRIOVR
|
||||
if (!_skeletalDevice) {
|
||||
return;
|
||||
}
|
||||
connect(Application::getInstance(), SIGNAL(renderingOverlay()), SLOT(renderCalibrationCountdown()));
|
||||
_calibrationCountdownStarted = QDateTime::currentDateTime();
|
||||
#endif
|
||||
}
|
||||
|
||||
void PrioVR::renderCalibrationCountdown() {
|
||||
#ifdef HAVE_PRIOVR
|
||||
const int COUNTDOWN_SECONDS = 3;
|
||||
int secondsRemaining = COUNTDOWN_SECONDS - _calibrationCountdownStarted.secsTo(QDateTime::currentDateTime());
|
||||
if (secondsRemaining == 0) {
|
||||
yei_tareSensors(_skeletalDevice);
|
||||
Application::getInstance()->disconnect(this);
|
||||
return;
|
||||
}
|
||||
static TextRenderer textRenderer(MONO_FONT_FAMILY, 18, QFont::Bold, false, TextRenderer::OUTLINE_EFFECT, 2);
|
||||
QByteArray text = "Assume T-Pose in " + QByteArray::number(secondsRemaining) + "...";
|
||||
textRenderer.draw((Application::getInstance()->getGLWidget()->width() - textRenderer.computeWidth(text.constData())) / 2,
|
||||
Application::getInstance()->getGLWidget()->height() / 2,
|
||||
text);
|
||||
#endif
|
||||
}
|
65
interface/src/devices/PrioVR.h
Normal file
65
interface/src/devices/PrioVR.h
Normal file
|
@ -0,0 +1,65 @@
|
|||
//
|
||||
// PrioVR.h
|
||||
// interface/src/devices
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/12/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_PrioVR_h
|
||||
#define hifi_PrioVR_h
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
#ifdef HAVE_PRIOVR
|
||||
extern "C" {
|
||||
#include <yei_skeletal_api.h>
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Handles interaction with the PrioVR skeleton tracking suit.
|
||||
class PrioVR : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
PrioVR();
|
||||
virtual ~PrioVR();
|
||||
|
||||
bool isActive() const { return !_jointRotations.isEmpty(); }
|
||||
|
||||
bool hasHeadRotation() const;
|
||||
|
||||
glm::quat getHeadRotation() const;
|
||||
glm::quat getTorsoRotation() const;
|
||||
|
||||
const QVector<int>& getHumanIKJointIndices() const { return _humanIKJointIndices; }
|
||||
const QVector<glm::quat>& getJointRotations() const { return _jointRotations; }
|
||||
|
||||
void update(float deltaTime);
|
||||
void reset();
|
||||
|
||||
private slots:
|
||||
|
||||
void renderCalibrationCountdown();
|
||||
|
||||
private:
|
||||
#ifdef HAVE_PRIOVR
|
||||
YEI_Device_Id _skeletalDevice;
|
||||
#endif
|
||||
|
||||
QVector<int> _humanIKJointIndices;
|
||||
QVector<glm::quat> _jointRotations;
|
||||
QVector<glm::quat> _lastJointRotations;
|
||||
|
||||
QDateTime _calibrationCountdownStarted;
|
||||
};
|
||||
|
||||
#endif // hifi_PrioVR_h
|
|
@ -55,7 +55,8 @@ Visage::Visage() :
|
|||
Visage::~Visage() {
|
||||
#ifdef HAVE_VISAGE
|
||||
_tracker->stop();
|
||||
delete _tracker;
|
||||
// deleting the tracker crashes windows; disable for now
|
||||
//delete _tracker;
|
||||
delete _data;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -20,11 +20,16 @@ ModelTreeRenderer::ModelTreeRenderer() :
|
|||
}
|
||||
|
||||
ModelTreeRenderer::~ModelTreeRenderer() {
|
||||
// delete the models in _modelsItemModels
|
||||
foreach(Model* model, _modelsItemModels) {
|
||||
// delete the models in _knownModelsItemModels
|
||||
foreach(Model* model, _knownModelsItemModels) {
|
||||
delete model;
|
||||
}
|
||||
_modelsItemModels.clear();
|
||||
_knownModelsItemModels.clear();
|
||||
|
||||
foreach(Model* model, _unknownModelsItemModels) {
|
||||
delete model;
|
||||
}
|
||||
_unknownModelsItemModels.clear();
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::init() {
|
||||
|
@ -43,27 +48,38 @@ void ModelTreeRenderer::render(RenderMode renderMode) {
|
|||
OctreeRenderer::render(renderMode);
|
||||
}
|
||||
|
||||
Model* ModelTreeRenderer::getModel(const QString& url) {
|
||||
Model* ModelTreeRenderer::getModel(const ModelItem& modelItem) {
|
||||
Model* model = NULL;
|
||||
|
||||
// if we don't already have this model then create it and initialize it
|
||||
if (_modelsItemModels.find(url) == _modelsItemModels.end()) {
|
||||
model = new Model();
|
||||
model->init();
|
||||
model->setURL(QUrl(url));
|
||||
_modelsItemModels[url] = model;
|
||||
if (modelItem.isKnownID()) {
|
||||
if (_knownModelsItemModels.find(modelItem.getID()) != _knownModelsItemModels.end()) {
|
||||
model = _knownModelsItemModels[modelItem.getID()];
|
||||
} else {
|
||||
model = new Model();
|
||||
model->init();
|
||||
model->setURL(QUrl(modelItem.getModelURL()));
|
||||
_knownModelsItemModels[modelItem.getID()] = model;
|
||||
}
|
||||
} else {
|
||||
model = _modelsItemModels[url];
|
||||
if (_unknownModelsItemModels.find(modelItem.getCreatorTokenID()) != _unknownModelsItemModels.end()) {
|
||||
model = _unknownModelsItemModels[modelItem.getCreatorTokenID()];
|
||||
} else {
|
||||
model = new Model();
|
||||
model->init();
|
||||
model->setURL(QUrl(modelItem.getModelURL()));
|
||||
_unknownModelsItemModels[modelItem.getCreatorTokenID()] = model;
|
||||
}
|
||||
}
|
||||
return model;
|
||||
}
|
||||
|
||||
void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args) {
|
||||
args->_elementsTouched++;
|
||||
// actually render it here...
|
||||
// we need to iterate the actual modelItems of the element
|
||||
ModelTreeElement* modelTreeElement = (ModelTreeElement*)element;
|
||||
|
||||
const QList<ModelItem>& modelItems = modelTreeElement->getModels();
|
||||
QList<ModelItem>& modelItems = modelTreeElement->getModels();
|
||||
|
||||
uint16_t numberOfModels = modelItems.size();
|
||||
|
||||
|
@ -139,7 +155,7 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
|
|||
}
|
||||
|
||||
for (uint16_t i = 0; i < numberOfModels; i++) {
|
||||
const ModelItem& modelItem = modelItems[i];
|
||||
ModelItem& modelItem = modelItems[i];
|
||||
// render modelItem aspoints
|
||||
AABox modelBox = modelItem.getAABox();
|
||||
modelBox.scale(TREE_SCALE);
|
||||
|
@ -150,13 +166,13 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
|
|||
|
||||
bool drawAsModel = modelItem.hasModel();
|
||||
|
||||
args->_renderedItems++;
|
||||
args->_itemsRendered++;
|
||||
|
||||
if (drawAsModel) {
|
||||
glPushMatrix();
|
||||
const float alpha = 1.0f;
|
||||
|
||||
Model* model = getModel(modelItem.getModelURL());
|
||||
Model* model = getModel(modelItem);
|
||||
|
||||
model->setScaleToFit(true, radius * 2.0f);
|
||||
model->setSnapModelToCenter(true);
|
||||
|
@ -167,6 +183,21 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
|
|||
|
||||
// set the position
|
||||
model->setTranslation(position);
|
||||
|
||||
// handle animations..
|
||||
if (modelItem.hasAnimation()) {
|
||||
if (!modelItem.jointsMapped()) {
|
||||
QStringList modelJointNames = model->getJointNames();
|
||||
modelItem.mapJoints(modelJointNames);
|
||||
}
|
||||
|
||||
QVector<glm::quat> frameData = modelItem.getAnimationFrame();
|
||||
for (int i = 0; i < frameData.size(); i++) {
|
||||
model->setJointState(i, true, frameData[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// make sure to simulate so everything gets set up correctly for rendering
|
||||
model->simulate(0.0f);
|
||||
|
||||
// TODO: should we allow modelItems to have alpha on their models?
|
||||
|
@ -190,6 +221,8 @@ void ModelTreeRenderer::renderElement(OctreeElement* element, RenderArgs* args)
|
|||
glutSolidSphere(radius, 15, 15);
|
||||
glPopMatrix();
|
||||
}
|
||||
} else {
|
||||
args->_itemsOutOfView++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -49,9 +49,9 @@ public:
|
|||
virtual void render(RenderMode renderMode = DEFAULT_RENDER_MODE);
|
||||
|
||||
protected:
|
||||
Model* getModel(const QString& url);
|
||||
|
||||
QMap<QString, Model*> _modelsItemModels;
|
||||
Model* getModel(const ModelItem& modelItem);
|
||||
QMap<uint32_t, Model*> _knownModelsItemModels;
|
||||
QMap<uint32_t, Model*> _unknownModelsItemModels;
|
||||
};
|
||||
|
||||
#endif // hifi_ModelTreeRenderer_h
|
||||
|
|
|
@ -76,7 +76,7 @@ void ParticleTreeRenderer::renderElement(OctreeElement* element, RenderArgs* arg
|
|||
|
||||
bool drawAsModel = particle.hasModel();
|
||||
|
||||
args->_renderedItems++;
|
||||
args->_itemsRendered++;
|
||||
|
||||
if (drawAsModel) {
|
||||
glPushMatrix();
|
||||
|
|
|
@ -404,6 +404,22 @@ QSharedPointer<NetworkGeometry> NetworkGeometry::getLODOrFallback(float distance
|
|||
return lod;
|
||||
}
|
||||
|
||||
uint qHash(const QWeakPointer<Animation>& animation, uint seed = 0) {
|
||||
return qHash(animation.data(), seed);
|
||||
}
|
||||
|
||||
QVector<int> NetworkGeometry::getJointMappings(const AnimationPointer& animation) {
|
||||
QVector<int> mappings = _jointMappings.value(animation);
|
||||
if (mappings.isEmpty() && isLoaded() && animation && animation->isLoaded()) {
|
||||
const FBXGeometry& animationGeometry = animation->getGeometry();
|
||||
for (int i = 0; i < animationGeometry.joints.size(); i++) {
|
||||
mappings.append(_geometry.jointIndices.value(animationGeometry.joints.at(i).name) - 1);
|
||||
}
|
||||
_jointMappings.insert(animation, mappings);
|
||||
}
|
||||
return mappings;
|
||||
}
|
||||
|
||||
void NetworkGeometry::setLoadPriority(const QPointer<QObject>& owner, float priority) {
|
||||
Resource::setLoadPriority(owner, priority);
|
||||
|
||||
|
|
|
@ -22,6 +22,8 @@
|
|||
|
||||
#include <FBXReader.h>
|
||||
|
||||
#include <AnimationCache.h>
|
||||
|
||||
class Model;
|
||||
class NetworkGeometry;
|
||||
class NetworkMesh;
|
||||
|
@ -90,6 +92,8 @@ public:
|
|||
const FBXGeometry& getFBXGeometry() const { return _geometry; }
|
||||
const QVector<NetworkMesh>& getMeshes() const { return _meshes; }
|
||||
|
||||
QVector<int> getJointMappings(const AnimationPointer& animation);
|
||||
|
||||
virtual void setLoadPriority(const QPointer<QObject>& owner, float priority);
|
||||
virtual void setLoadPriorities(const QHash<QPointer<QObject>, float>& priorities);
|
||||
virtual void clearLoadPriority(const QPointer<QObject>& owner);
|
||||
|
@ -117,6 +121,8 @@ private:
|
|||
QVector<NetworkMesh> _meshes;
|
||||
|
||||
QWeakPointer<NetworkGeometry> _lodParent;
|
||||
|
||||
QHash<QWeakPointer<Animation>, QVector<int> > _jointMappings;
|
||||
};
|
||||
|
||||
/// The state associated with a single mesh part.
|
||||
|
|
|
@ -39,6 +39,7 @@ Model::Model(QObject* parent) :
|
|||
_scaledToFit(false),
|
||||
_snapModelToCenter(false),
|
||||
_snappedToCenter(false),
|
||||
_rootIndex(-1),
|
||||
_shapesAreDirty(true),
|
||||
_boundingRadius(0.f),
|
||||
_boundingShape(),
|
||||
|
@ -117,6 +118,7 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
|||
JointState state;
|
||||
state.translation = joint.translation;
|
||||
state.rotation = joint.rotation;
|
||||
state.animationDisabled = false;
|
||||
jointStates.append(state);
|
||||
}
|
||||
|
||||
|
@ -128,7 +130,6 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
|||
jointIsSet.fill(false, numJoints);
|
||||
int numJointsSet = 0;
|
||||
int lastNumJointsSet = -1;
|
||||
glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset);
|
||||
while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) {
|
||||
lastNumJointsSet = numJointsSet;
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
|
@ -139,6 +140,8 @@ QVector<Model::JointState> Model::createJointStates(const FBXGeometry& geometry)
|
|||
const FBXJoint& joint = geometry.joints[i];
|
||||
int parentIndex = joint.parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
_rootIndex = i;
|
||||
glm::mat4 baseTransform = glm::mat4_cast(_rotation) * glm::scale(_scale) * glm::translate(_offset);
|
||||
glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation;
|
||||
state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
|
@ -433,7 +436,12 @@ Extents Model::getMeshExtents() const {
|
|||
return Extents();
|
||||
}
|
||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||
Extents scaledExtents = { extents.minimum * _scale, extents.maximum * _scale };
|
||||
|
||||
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
|
||||
// is captured in the offset matrix
|
||||
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0));
|
||||
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0));
|
||||
Extents scaledExtents = { minimum * _scale, maximum * _scale };
|
||||
return scaledExtents;
|
||||
}
|
||||
|
||||
|
@ -441,8 +449,16 @@ Extents Model::getUnscaledMeshExtents() const {
|
|||
if (!isActive()) {
|
||||
return Extents();
|
||||
}
|
||||
|
||||
const Extents& extents = _geometry->getFBXGeometry().meshExtents;
|
||||
return extents;
|
||||
|
||||
// even though our caller asked for "unscaled" we need to include any fst scaling, translation, and rotation, which
|
||||
// is captured in the offset matrix
|
||||
glm::vec3 minimum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.minimum, 1.0));
|
||||
glm::vec3 maximum = glm::vec3(_geometry->getFBXGeometry().offset * glm::vec4(extents.maximum, 1.0));
|
||||
Extents scaledExtents = { minimum, maximum };
|
||||
|
||||
return scaledExtents;
|
||||
}
|
||||
|
||||
bool Model::getJointState(int index, glm::quat& rotation) const {
|
||||
|
@ -576,6 +592,27 @@ bool Model::getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind)
|
|||
return true;
|
||||
}
|
||||
|
||||
QStringList Model::getJointNames() const {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QStringList result;
|
||||
QMetaObject::invokeMethod(const_cast<Model*>(this), "getJointNames", Qt::BlockingQueuedConnection,
|
||||
Q_RETURN_ARG(QStringList, result));
|
||||
return result;
|
||||
}
|
||||
return isActive() ? _geometry->getFBXGeometry().getJointNames() : QStringList();
|
||||
}
|
||||
|
||||
uint qHash(const WeakAnimationHandlePointer& handle, uint seed) {
|
||||
return qHash(handle.data(), seed);
|
||||
}
|
||||
|
||||
AnimationHandlePointer Model::createAnimationHandle() {
|
||||
AnimationHandlePointer handle(new AnimationHandle(this));
|
||||
handle->_self = handle;
|
||||
_animationHandles.insert(handle);
|
||||
return handle;
|
||||
}
|
||||
|
||||
void Model::clearShapes() {
|
||||
for (int i = 0; i < _jointShapes.size(); ++i) {
|
||||
delete _jointShapes[i];
|
||||
|
@ -586,66 +623,20 @@ void Model::clearShapes() {
|
|||
void Model::rebuildShapes() {
|
||||
clearShapes();
|
||||
|
||||
if (!_geometry) {
|
||||
if (!_geometry || _rootIndex == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
if (geometry.joints.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int numJoints = geometry.joints.size();
|
||||
QVector<glm::mat4> transforms;
|
||||
transforms.fill(glm::mat4(), numJoints);
|
||||
QVector<glm::quat> combinedRotations;
|
||||
combinedRotations.fill(glm::quat(), numJoints);
|
||||
QVector<bool> shapeIsSet;
|
||||
shapeIsSet.fill(false, numJoints);
|
||||
int rootIndex = 0;
|
||||
|
||||
// We create the shapes with proper dimensions, but we set their transforms later.
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
int numShapesSet = 0;
|
||||
int lastNumShapesSet = -1;
|
||||
while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) {
|
||||
lastNumShapesSet = numShapesSet;
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
if (shapeIsSet[i]) {
|
||||
continue;
|
||||
}
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
int parentIndex = joint.parentIndex;
|
||||
if (parentIndex == -1) {
|
||||
rootIndex = i;
|
||||
glm::mat4 baseTransform = glm::mat4_cast(_rotation) * uniformScale * glm::translate(_offset);
|
||||
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
||||
transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
combinedRotations[i] = _rotation * combinedRotation;
|
||||
++numShapesSet;
|
||||
shapeIsSet[i] = true;
|
||||
} else if (shapeIsSet[parentIndex]) {
|
||||
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
||||
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation) * joint.preTransform *
|
||||
glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
combinedRotations[i] = combinedRotations[parentIndex] * combinedRotation;
|
||||
++numShapesSet;
|
||||
shapeIsSet[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// joint shapes
|
||||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
|
||||
glm::vec3 worldPosition = extractTranslation(transforms[i]);
|
||||
Extents shapeExtents;
|
||||
shapeExtents.reset();
|
||||
|
||||
float radius = uniformScale * joint.boneRadius;
|
||||
float halfHeight = 0.5f * uniformScale * joint.distanceToParent;
|
||||
Shape::Type type = joint.shapeType;
|
||||
|
@ -655,47 +646,150 @@ void Model::rebuildShapes() {
|
|||
}
|
||||
if (type == Shape::CAPSULE_SHAPE) {
|
||||
CapsuleShape* capsule = new CapsuleShape(radius, halfHeight);
|
||||
capsule->setPosition(worldPosition);
|
||||
capsule->setRotation(combinedRotations[i] * joint.shapeRotation);
|
||||
_jointShapes.push_back(capsule);
|
||||
|
||||
// add the two furthest surface points of the capsule
|
||||
glm::vec3 axis;
|
||||
capsule->computeNormalizedAxis(axis);
|
||||
axis = halfHeight * axis + glm::vec3(radius);
|
||||
shapeExtents.addPoint(worldPosition + axis);
|
||||
shapeExtents.addPoint(worldPosition - axis);
|
||||
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
} else if (type == Shape::SPHERE_SHAPE) {
|
||||
SphereShape* sphere = new SphereShape(radius, worldPosition);
|
||||
SphereShape* sphere = new SphereShape(radius, glm::vec3(0.0f));
|
||||
_jointShapes.push_back(sphere);
|
||||
|
||||
glm::vec3 axis = glm::vec3(radius);
|
||||
shapeExtents.addPoint(worldPosition + axis);
|
||||
shapeExtents.addPoint(worldPosition - axis);
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
} else {
|
||||
// this shape type is not handled and the joint shouldn't collide,
|
||||
// however we must have a shape for each joint,
|
||||
// so we make a bogus sphere with zero radius.
|
||||
// TODO: implement collision groups for more control over what collides with what
|
||||
SphereShape* sphere = new SphereShape(0.f, worldPosition);
|
||||
SphereShape* sphere = new SphereShape(0.f, glm::vec3(0.0f));
|
||||
_jointShapes.push_back(sphere);
|
||||
}
|
||||
}
|
||||
|
||||
// bounding shape
|
||||
// NOTE: we assume that the longest side of totalExtents is the yAxis
|
||||
// This method moves the shapes to their default positions in Model frame
|
||||
// which is where we compute the bounding shape's parameters.
|
||||
computeBoundingShape(geometry);
|
||||
|
||||
// finally sync shapes to joint positions
|
||||
_shapesAreDirty = true;
|
||||
updateShapePositions();
|
||||
}
|
||||
|
||||
void Model::computeBoundingShape(const FBXGeometry& geometry) {
|
||||
// compute default joint transforms and rotations
|
||||
// (in local frame, ignoring Model translation and rotation)
|
||||
int numJoints = geometry.joints.size();
|
||||
QVector<glm::mat4> transforms;
|
||||
transforms.fill(glm::mat4(), numJoints);
|
||||
QVector<glm::quat> finalRotations;
|
||||
finalRotations.fill(glm::quat(), numJoints);
|
||||
|
||||
QVector<bool> shapeIsSet;
|
||||
shapeIsSet.fill(false, numJoints);
|
||||
int numShapesSet = 0;
|
||||
int lastNumShapesSet = -1;
|
||||
glm::vec3 rootOffset(0.0f);
|
||||
while (numShapesSet < numJoints && numShapesSet != lastNumShapesSet) {
|
||||
lastNumShapesSet = numShapesSet;
|
||||
for (int i = 0; i < numJoints; i++) {
|
||||
const FBXJoint& joint = geometry.joints.at(i);
|
||||
int parentIndex = joint.parentIndex;
|
||||
|
||||
if (parentIndex == -1) {
|
||||
glm::mat4 baseTransform = glm::scale(_scale) * glm::translate(_offset);
|
||||
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
||||
transforms[i] = baseTransform * geometry.offset * glm::translate(joint.translation)
|
||||
* joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
rootOffset = extractTranslation(transforms[i]);
|
||||
finalRotations[i] = combinedRotation;
|
||||
++numShapesSet;
|
||||
shapeIsSet[i] = true;
|
||||
} else if (shapeIsSet[parentIndex]) {
|
||||
glm::quat combinedRotation = joint.preRotation * joint.rotation * joint.postRotation;
|
||||
transforms[i] = transforms[parentIndex] * glm::translate(joint.translation)
|
||||
* joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform;
|
||||
finalRotations[i] = finalRotations[parentIndex] * combinedRotation;
|
||||
++numShapesSet;
|
||||
shapeIsSet[i] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sync shapes to joints
|
||||
_boundingRadius = 0.0f;
|
||||
float uniformScale = extractUniformScale(_scale);
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
const FBXJoint& joint = geometry.joints[i];
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (finalRotations[i] * joint.shapePosition);
|
||||
glm::vec3 localPosition = extractTranslation(transforms[i]) + jointToShapeOffset- rootOffset;
|
||||
Shape* shape = _jointShapes[i];
|
||||
shape->setPosition(localPosition);
|
||||
shape->setRotation(finalRotations[i] * joint.shapeRotation);
|
||||
float distance = glm::length(localPosition) + shape->getBoundingRadius();
|
||||
if (distance > _boundingRadius) {
|
||||
_boundingRadius = distance;
|
||||
}
|
||||
}
|
||||
|
||||
// compute bounding box
|
||||
Extents totalExtents;
|
||||
totalExtents.reset();
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
Extents shapeExtents;
|
||||
shapeExtents.reset();
|
||||
|
||||
Shape* shape = _jointShapes[i];
|
||||
glm::vec3 localPosition = shape->getPosition();
|
||||
int type = shape->getType();
|
||||
if (type == Shape::CAPSULE_SHAPE) {
|
||||
// add the two furthest surface points of the capsule
|
||||
CapsuleShape* capsule = static_cast<CapsuleShape*>(shape);
|
||||
glm::vec3 axis;
|
||||
capsule->computeNormalizedAxis(axis);
|
||||
float radius = capsule->getRadius();
|
||||
float halfHeight = capsule->getHalfHeight();
|
||||
axis = halfHeight * axis + glm::vec3(radius);
|
||||
|
||||
shapeExtents.addPoint(localPosition + axis);
|
||||
shapeExtents.addPoint(localPosition - axis);
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
} else if (type == Shape::SPHERE_SHAPE) {
|
||||
float radius = shape->getBoundingRadius();
|
||||
glm::vec3 axis = glm::vec3(radius);
|
||||
shapeExtents.addPoint(localPosition + axis);
|
||||
shapeExtents.addPoint(localPosition - axis);
|
||||
totalExtents.addExtents(shapeExtents);
|
||||
}
|
||||
}
|
||||
|
||||
// compute bounding shape parameters
|
||||
// NOTE: we assume that the longest side of totalExtents is the yAxis...
|
||||
glm::vec3 diagonal = totalExtents.maximum - totalExtents.minimum;
|
||||
// the radius is half the RMS of the X and Z sides:
|
||||
// ... and assume the radius is half the RMS of the X and Z sides:
|
||||
float capsuleRadius = 0.5f * sqrtf(0.5f * (diagonal.x * diagonal.x + diagonal.z * diagonal.z));
|
||||
_boundingShape.setRadius(capsuleRadius);
|
||||
_boundingShape.setHalfHeight(0.5f * diagonal.y - capsuleRadius);
|
||||
_boundingShapeLocalOffset = 0.5f * (totalExtents.maximum + totalExtents.minimum);
|
||||
}
|
||||
|
||||
glm::quat inverseRotation = glm::inverse(_rotation);
|
||||
glm::vec3 rootPosition = extractTranslation(transforms[rootIndex]);
|
||||
_boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition);
|
||||
void Model::resetShapePositions() {
|
||||
// DEBUG method.
|
||||
// Moves shapes to the joint default locations for debug visibility into
|
||||
// how the bounding shape is computed.
|
||||
|
||||
if (!_geometry || _rootIndex == -1) {
|
||||
// geometry or joints have not yet been created
|
||||
return;
|
||||
}
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
if (geometry.joints.isEmpty() || _jointShapes.size() != geometry.joints.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The shapes are moved to their default positions in computeBoundingShape().
|
||||
computeBoundingShape(geometry);
|
||||
|
||||
// Then we move them into world frame for rendering at the Model's location.
|
||||
for (int i = 0; i < _jointShapes.size(); i++) {
|
||||
Shape* shape = _jointShapes[i];
|
||||
shape->setPosition(_translation + _rotation * shape->getPosition());
|
||||
shape->setRotation(_rotation * shape->getRotation());
|
||||
}
|
||||
_boundingShape.setPosition(_translation + _rotation * _boundingShapeLocalOffset);
|
||||
_boundingShape.setRotation(_rotation);
|
||||
}
|
||||
|
@ -711,17 +805,17 @@ void Model::updateShapePositions() {
|
|||
// shape position and rotation need to be in world-frame
|
||||
glm::vec3 jointToShapeOffset = uniformScale * (_jointStates[i].combinedRotation * joint.shapePosition);
|
||||
glm::vec3 worldPosition = extractTranslation(_jointStates[i].transform) + jointToShapeOffset + _translation;
|
||||
_jointShapes[i]->setPosition(worldPosition);
|
||||
_jointShapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
float distance2 = glm::distance2(worldPosition, _translation);
|
||||
if (distance2 > _boundingRadius) {
|
||||
_boundingRadius = distance2;
|
||||
Shape* shape = _jointShapes[i];
|
||||
shape->setPosition(worldPosition);
|
||||
shape->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation);
|
||||
float distance = glm::distance(worldPosition, _translation) + shape->getBoundingRadius();
|
||||
if (distance > _boundingRadius) {
|
||||
_boundingRadius = distance;
|
||||
}
|
||||
if (joint.parentIndex == -1) {
|
||||
rootPosition = worldPosition;
|
||||
}
|
||||
}
|
||||
_boundingRadius = sqrtf(_boundingRadius);
|
||||
_shapesAreDirty = false;
|
||||
_boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset);
|
||||
_boundingShape.setRotation(_rotation);
|
||||
|
@ -930,6 +1024,11 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
|||
}
|
||||
|
||||
void Model::simulateInternal(float deltaTime) {
|
||||
// update animations
|
||||
foreach (const AnimationHandlePointer& handle, _runningAnimations) {
|
||||
handle->simulate(deltaTime);
|
||||
}
|
||||
|
||||
// NOTE: this is a recursive call that walks all attachments, and their attachments
|
||||
// update the world space transforms for all joints
|
||||
for (int i = 0; i < _jointStates.size(); i++) {
|
||||
|
@ -937,9 +1036,8 @@ void Model::simulateInternal(float deltaTime) {
|
|||
}
|
||||
_shapesAreDirty = true;
|
||||
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
|
||||
// update the attachment transforms and simulate them
|
||||
const FBXGeometry& geometry = _geometry->getFBXGeometry();
|
||||
for (int i = 0; i < _attachments.size(); i++) {
|
||||
const FBXAttachment& attachment = geometry.attachments.at(i);
|
||||
Model* model = _attachments.at(i);
|
||||
|
@ -1110,6 +1208,7 @@ bool Model::setJointRotation(int jointIndex, const glm::quat& rotation, bool fro
|
|||
state.rotation = state.rotation * glm::inverse(state.combinedRotation) * rotation *
|
||||
glm::inverse(fromBind ? _geometry->getFBXGeometry().joints.at(jointIndex).inverseBindRotation :
|
||||
_geometry->getFBXGeometry().joints.at(jointIndex).inverseDefaultRotation);
|
||||
state.animationDisabled = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -1142,6 +1241,7 @@ bool Model::restoreJointPosition(int jointIndex, float percent) {
|
|||
const FBXJoint& joint = geometry.joints.at(index);
|
||||
state.rotation = safeMix(state.rotation, joint.rotation, percent);
|
||||
state.translation = glm::mix(state.translation, joint.translation, percent);
|
||||
state.animationDisabled = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
@ -1175,6 +1275,7 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons
|
|||
glm::quat newRotation = glm::quat(glm::clamp(eulers, joint.rotationMin, joint.rotationMax));
|
||||
state.combinedRotation = state.combinedRotation * glm::inverse(state.rotation) * newRotation;
|
||||
state.rotation = newRotation;
|
||||
state.animationDisabled = true;
|
||||
}
|
||||
|
||||
const int BALL_SUBDIVISIONS = 10;
|
||||
|
@ -1350,6 +1451,16 @@ void Model::deleteGeometry() {
|
|||
_meshStates.clear();
|
||||
clearShapes();
|
||||
|
||||
for (QSet<WeakAnimationHandlePointer>::iterator it = _animationHandles.begin(); it != _animationHandles.end(); ) {
|
||||
AnimationHandlePointer handle = it->toStrongRef();
|
||||
if (handle) {
|
||||
handle->_jointMappings.clear();
|
||||
it++;
|
||||
} else {
|
||||
it = _animationHandles.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
if (_geometry) {
|
||||
_geometry->clearLoadPriority(this);
|
||||
}
|
||||
|
@ -1545,3 +1656,103 @@ void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) {
|
|||
activeProgram->release();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationHandle::setURL(const QUrl& url) {
|
||||
if (_url != url) {
|
||||
_animation = Application::getInstance()->getAnimationCache()->getAnimation(_url = url);
|
||||
_jointMappings.clear();
|
||||
}
|
||||
}
|
||||
|
||||
static void insertSorted(QList<AnimationHandlePointer>& handles, const AnimationHandlePointer& handle) {
|
||||
for (QList<AnimationHandlePointer>::iterator it = handles.begin(); it != handles.end(); it++) {
|
||||
if (handle->getPriority() < (*it)->getPriority()) {
|
||||
handles.insert(it, handle);
|
||||
return;
|
||||
}
|
||||
}
|
||||
handles.append(handle);
|
||||
}
|
||||
|
||||
void AnimationHandle::setPriority(float priority) {
|
||||
if (_priority != priority) {
|
||||
_priority = priority;
|
||||
if (_running) {
|
||||
_model->_runningAnimations.removeOne(_self);
|
||||
insertSorted(_model->_runningAnimations, _self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationHandle::setRunning(bool running) {
|
||||
if ((_running = running)) {
|
||||
if (!_model->_runningAnimations.contains(_self)) {
|
||||
insertSorted(_model->_runningAnimations, _self);
|
||||
}
|
||||
_frameIndex = 0.0f;
|
||||
|
||||
} else {
|
||||
_model->_runningAnimations.removeOne(_self);
|
||||
}
|
||||
}
|
||||
|
||||
AnimationHandle::AnimationHandle(Model* model) :
|
||||
QObject(model),
|
||||
_model(model),
|
||||
_fps(30.0f),
|
||||
_priority(1.0f),
|
||||
_loop(false),
|
||||
_running(false) {
|
||||
}
|
||||
|
||||
void AnimationHandle::simulate(float deltaTime) {
|
||||
_frameIndex += deltaTime * _fps;
|
||||
|
||||
// update the joint mappings if necessary/possible
|
||||
if (_jointMappings.isEmpty()) {
|
||||
if (_model->isActive()) {
|
||||
_jointMappings = _model->getGeometry()->getJointMappings(_animation);
|
||||
}
|
||||
if (_jointMappings.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const FBXGeometry& animationGeometry = _animation->getGeometry();
|
||||
if (animationGeometry.animationFrames.isEmpty()) {
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
int ceilFrameIndex = (int)glm::ceil(_frameIndex);
|
||||
if (!_loop && ceilFrameIndex >= animationGeometry.animationFrames.size()) {
|
||||
// passed the end; apply the last frame
|
||||
const FBXAnimationFrame& frame = animationGeometry.animationFrames.last();
|
||||
for (int i = 0; i < _jointMappings.size(); i++) {
|
||||
int mapping = _jointMappings.at(i);
|
||||
if (mapping != -1) {
|
||||
Model::JointState& state = _model->_jointStates[mapping];
|
||||
if (!state.animationDisabled) {
|
||||
state.rotation = frame.rotations.at(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
stop();
|
||||
return;
|
||||
}
|
||||
// blend between the closest two frames
|
||||
const FBXAnimationFrame& ceilFrame = animationGeometry.animationFrames.at(
|
||||
ceilFrameIndex % animationGeometry.animationFrames.size());
|
||||
const FBXAnimationFrame& floorFrame = animationGeometry.animationFrames.at(
|
||||
(int)glm::floor(_frameIndex) % animationGeometry.animationFrames.size());
|
||||
float frameFraction = glm::fract(_frameIndex);
|
||||
for (int i = 0; i < _jointMappings.size(); i++) {
|
||||
int mapping = _jointMappings.at(i);
|
||||
if (mapping != -1) {
|
||||
Model::JointState& state = _model->_jointStates[mapping];
|
||||
if (!state.animationDisabled) {
|
||||
state.rotation = safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,18 +12,25 @@
|
|||
#ifndef hifi_Model_h
|
||||
#define hifi_Model_h
|
||||
|
||||
#include <QBitArray>
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
#include <CapsuleShape.h>
|
||||
|
||||
#include <AnimationCache.h>
|
||||
|
||||
#include "GeometryCache.h"
|
||||
#include "InterfaceConfig.h"
|
||||
#include "ProgramObject.h"
|
||||
#include "TextureCache.h"
|
||||
|
||||
class AnimationHandle;
|
||||
class Shape;
|
||||
|
||||
typedef QSharedPointer<AnimationHandle> AnimationHandlePointer;
|
||||
typedef QWeakPointer<AnimationHandle> WeakAnimationHandlePointer;
|
||||
|
||||
/// A generic 3D model displaying geometry loaded from a URL.
|
||||
class Model : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -182,9 +189,16 @@ public:
|
|||
|
||||
bool getJointPosition(int jointIndex, glm::vec3& position) const;
|
||||
bool getJointRotation(int jointIndex, glm::quat& rotation, bool fromBind = false) const;
|
||||
|
||||
QStringList getJointNames() const;
|
||||
|
||||
AnimationHandlePointer createAnimationHandle();
|
||||
|
||||
const QList<AnimationHandlePointer>& getRunningAnimations() const { return _runningAnimations; }
|
||||
|
||||
void clearShapes();
|
||||
void rebuildShapes();
|
||||
void resetShapePositions();
|
||||
void updateShapePositions();
|
||||
void renderJointCollisionShapes(float alpha);
|
||||
void renderBoundingCollisionShapes(float alpha);
|
||||
|
@ -232,6 +246,7 @@ protected:
|
|||
|
||||
bool _snapModelToCenter; /// is the model's offset automatically adjusted to center around 0,0,0 in model space
|
||||
bool _snappedToCenter; /// are we currently snapped to center
|
||||
int _rootIndex;
|
||||
|
||||
class JointState {
|
||||
public:
|
||||
|
@ -239,6 +254,7 @@ protected:
|
|||
glm::quat rotation; // rotation relative to parent
|
||||
glm::mat4 transform; // rotation to world frame + translation in model frame
|
||||
glm::quat combinedRotation; // rotation from joint local to world frame
|
||||
bool animationDisabled; // if true, animations do not affect this joint
|
||||
};
|
||||
|
||||
bool _shapesAreDirty;
|
||||
|
@ -291,8 +307,12 @@ protected:
|
|||
|
||||
void applyRotationDelta(int jointIndex, const glm::quat& delta, bool constrain = true);
|
||||
|
||||
void computeBoundingShape(const FBXGeometry& geometry);
|
||||
|
||||
private:
|
||||
|
||||
friend class AnimationHandle;
|
||||
|
||||
void applyNextGeometry();
|
||||
void deleteGeometry();
|
||||
void renderMeshes(float alpha, RenderMode mode, bool translucent);
|
||||
|
@ -316,6 +336,10 @@ private:
|
|||
|
||||
QVector<Model*> _attachments;
|
||||
|
||||
QSet<WeakAnimationHandlePointer> _animationHandles;
|
||||
|
||||
QList<AnimationHandlePointer> _runningAnimations;
|
||||
|
||||
static ProgramObject _program;
|
||||
static ProgramObject _normalMapProgram;
|
||||
static ProgramObject _specularMapProgram;
|
||||
|
@ -351,4 +375,48 @@ Q_DECLARE_METATYPE(QPointer<Model>)
|
|||
Q_DECLARE_METATYPE(QWeakPointer<NetworkGeometry>)
|
||||
Q_DECLARE_METATYPE(QVector<glm::vec3>)
|
||||
|
||||
/// Represents a handle to a model animation.
|
||||
class AnimationHandle : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
void setURL(const QUrl& url);
|
||||
const QUrl& getURL() const { return _url; }
|
||||
|
||||
void setFPS(float fps) { _fps = fps; }
|
||||
float getFPS() const { return _fps; }
|
||||
|
||||
void setPriority(float priority);
|
||||
float getPriority() const { return _priority; }
|
||||
|
||||
void setLoop(bool loop) { _loop = loop; }
|
||||
bool getLoop() const { return _loop; }
|
||||
|
||||
void setRunning(bool running);
|
||||
bool isRunning() const { return _running; }
|
||||
|
||||
void start() { setRunning(true); }
|
||||
void stop() { setRunning(false); }
|
||||
|
||||
private:
|
||||
|
||||
friend class Model;
|
||||
|
||||
AnimationHandle(Model* model);
|
||||
|
||||
void simulate(float deltaTime);
|
||||
|
||||
Model* _model;
|
||||
WeakAnimationHandlePointer _self;
|
||||
AnimationPointer _animation;
|
||||
QUrl _url;
|
||||
float _fps;
|
||||
float _priority;
|
||||
bool _loop;
|
||||
bool _running;
|
||||
QVector<int> _jointMappings;
|
||||
float _frameIndex;
|
||||
};
|
||||
|
||||
#endif // hifi_Model_h
|
||||
|
|
|
@ -74,7 +74,7 @@ bool ControllerScriptingInterface::isPrimaryButtonPressed() const {
|
|||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
130
interface/src/ui/AnimationsDialog.cpp
Normal file
130
interface/src/ui/AnimationsDialog.cpp
Normal file
|
@ -0,0 +1,130 @@
|
|||
//
|
||||
// AnimationsDialog.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/19/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include <QDialogButtonBox>
|
||||
#include <QDoubleSpinBox>
|
||||
#include <QFileDialog>
|
||||
#include <QFormLayout>
|
||||
#include <QLineEdit>
|
||||
#include <QPushButton>
|
||||
#include <QScrollArea>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "AnimationsDialog.h"
|
||||
#include "Application.h"
|
||||
|
||||
AnimationsDialog::AnimationsDialog() :
|
||||
QDialog(Application::getInstance()->getWindow()) {
|
||||
|
||||
setWindowTitle("Edit Animations");
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
|
||||
QVBoxLayout* layout = new QVBoxLayout();
|
||||
setLayout(layout);
|
||||
|
||||
QScrollArea* area = new QScrollArea();
|
||||
layout->addWidget(area);
|
||||
area->setWidgetResizable(true);
|
||||
QWidget* container = new QWidget();
|
||||
container->setLayout(_animations = new QVBoxLayout());
|
||||
container->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred);
|
||||
area->setWidget(container);
|
||||
_animations->addStretch(1);
|
||||
|
||||
foreach (const AnimationHandlePointer& handle, Application::getInstance()->getAvatar()->getAnimationHandles()) {
|
||||
_animations->insertWidget(_animations->count() - 1, new AnimationPanel(this, handle));
|
||||
}
|
||||
|
||||
QPushButton* newAnimation = new QPushButton("New Animation");
|
||||
connect(newAnimation, SIGNAL(clicked(bool)), SLOT(addAnimation()));
|
||||
layout->addWidget(newAnimation);
|
||||
|
||||
QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok);
|
||||
layout->addWidget(buttons);
|
||||
connect(buttons, SIGNAL(accepted()), SLOT(deleteLater()));
|
||||
_ok = buttons->button(QDialogButtonBox::Ok);
|
||||
|
||||
setMinimumSize(600, 600);
|
||||
}
|
||||
|
||||
void AnimationsDialog::setVisible(bool visible) {
|
||||
QDialog::setVisible(visible);
|
||||
|
||||
// un-default the OK button
|
||||
if (visible) {
|
||||
_ok->setDefault(false);
|
||||
}
|
||||
}
|
||||
|
||||
void AnimationsDialog::addAnimation() {
|
||||
_animations->insertWidget(_animations->count() - 1, new AnimationPanel(
|
||||
this, Application::getInstance()->getAvatar()->addAnimationHandle()));
|
||||
}
|
||||
|
||||
AnimationPanel::AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle) :
|
||||
_dialog(dialog),
|
||||
_handle(handle),
|
||||
_applying(false) {
|
||||
setFrameStyle(QFrame::StyledPanel);
|
||||
|
||||
QFormLayout* layout = new QFormLayout();
|
||||
layout->setFieldGrowthPolicy(QFormLayout::AllNonFixedFieldsGrow);
|
||||
setLayout(layout);
|
||||
|
||||
QHBoxLayout* urlBox = new QHBoxLayout();
|
||||
layout->addRow("URL:", urlBox);
|
||||
urlBox->addWidget(_url = new QLineEdit(handle->getURL().toString()), 1);
|
||||
connect(_url, SIGNAL(returnPressed()), SLOT(updateHandle()));
|
||||
QPushButton* chooseURL = new QPushButton("Choose");
|
||||
urlBox->addWidget(chooseURL);
|
||||
connect(chooseURL, SIGNAL(clicked(bool)), SLOT(chooseURL()));
|
||||
|
||||
layout->addRow("FPS:", _fps = new QDoubleSpinBox());
|
||||
_fps->setSingleStep(0.01);
|
||||
_fps->setMaximum(FLT_MAX);
|
||||
_fps->setValue(handle->getFPS());
|
||||
connect(_fps, SIGNAL(valueChanged(double)), SLOT(updateHandle()));
|
||||
|
||||
layout->addRow("Priority:", _priority = new QDoubleSpinBox());
|
||||
_priority->setSingleStep(0.01);
|
||||
_priority->setMinimum(-FLT_MAX);
|
||||
_priority->setMaximum(FLT_MAX);
|
||||
_priority->setValue(handle->getPriority());
|
||||
connect(_priority, SIGNAL(valueChanged(double)), SLOT(updateHandle()));
|
||||
|
||||
QPushButton* remove = new QPushButton("Delete");
|
||||
layout->addRow(remove);
|
||||
connect(remove, SIGNAL(clicked(bool)), SLOT(removeHandle()));
|
||||
}
|
||||
|
||||
void AnimationPanel::chooseURL() {
|
||||
QString directory = Application::getInstance()->lockSettings()->value("animation_directory").toString();
|
||||
Application::getInstance()->unlockSettings();
|
||||
QString filename = QFileDialog::getOpenFileName(this, "Choose Animation", directory, "Animation files (*.fbx)");
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Application::getInstance()->lockSettings()->setValue("animation_directory", QFileInfo(filename).path());
|
||||
Application::getInstance()->unlockSettings();
|
||||
_url->setText(QUrl::fromLocalFile(filename).toString());
|
||||
emit _url->returnPressed();
|
||||
}
|
||||
|
||||
void AnimationPanel::updateHandle() {
|
||||
_handle->setURL(_url->text());
|
||||
_handle->setFPS(_fps->value());
|
||||
_handle->setPriority(_priority->value());
|
||||
}
|
||||
|
||||
void AnimationPanel::removeHandle() {
|
||||
Application::getInstance()->getAvatar()->removeAnimationHandle(_handle);
|
||||
deleteLater();
|
||||
}
|
69
interface/src/ui/AnimationsDialog.h
Normal file
69
interface/src/ui/AnimationsDialog.h
Normal file
|
@ -0,0 +1,69 @@
|
|||
//
|
||||
// AnimationsDialog.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Andrzej Kapolka on 5/19/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimationsDialog_h
|
||||
#define hifi_AnimationsDialog_h
|
||||
|
||||
#include <QDialog>
|
||||
#include <QFrame>
|
||||
|
||||
#include "avatar/MyAvatar.h"
|
||||
|
||||
class QDoubleSpinner;
|
||||
class QLineEdit;
|
||||
class QPushButton;
|
||||
class QVBoxLayout;
|
||||
|
||||
/// Allows users to edit the avatar animations.
|
||||
class AnimationsDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AnimationsDialog();
|
||||
|
||||
virtual void setVisible(bool visible);
|
||||
|
||||
private slots:
|
||||
|
||||
void addAnimation();
|
||||
|
||||
private:
|
||||
|
||||
QVBoxLayout* _animations;
|
||||
QPushButton* _ok;
|
||||
};
|
||||
|
||||
/// A panel controlling a single animation.
|
||||
class AnimationPanel : public QFrame {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
|
||||
AnimationPanel(AnimationsDialog* dialog, const AnimationHandlePointer& handle);
|
||||
|
||||
private slots:
|
||||
|
||||
void chooseURL();
|
||||
void updateHandle();
|
||||
void removeHandle();
|
||||
|
||||
private:
|
||||
|
||||
AnimationsDialog* _dialog;
|
||||
AnimationHandlePointer _handle;
|
||||
QLineEdit* _url;
|
||||
QDoubleSpinBox* _fps;
|
||||
QDoubleSpinBox* _priority;
|
||||
bool _applying;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimationsDialog_h
|
|
@ -57,6 +57,8 @@ LogDialog::LogDialog(QWidget* parent, AbstractLoggerInterface* logger) : QDialog
|
|||
resize(INITIAL_WIDTH, static_cast<int>(screen.height() * INITIAL_HEIGHT_RATIO));
|
||||
move(screen.center() - rect().center());
|
||||
setMinimumWidth(MINIMAL_WIDTH);
|
||||
|
||||
connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
LogDialog::~LogDialog() {
|
||||
|
@ -105,7 +107,6 @@ void LogDialog::initControls() {
|
|||
}
|
||||
|
||||
void LogDialog::showEvent(QShowEvent*) {
|
||||
connect(_logger, SIGNAL(logReceived(QString)), this, SLOT(appendLogLine(QString)), Qt::QueuedConnection);
|
||||
showLogData();
|
||||
}
|
||||
|
||||
|
@ -122,7 +123,6 @@ void LogDialog::appendLogLine(QString logLine) {
|
|||
if (logLine.contains(_searchTerm, Qt::CaseInsensitive)) {
|
||||
_logTextBox->appendPlainText(logLine.simplified());
|
||||
}
|
||||
_logTextBox->ensureCursorVisible();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -146,10 +146,8 @@ void LogDialog::handleSearchTextChanged(const QString searchText) {
|
|||
|
||||
void LogDialog::showLogData() {
|
||||
_logTextBox->clear();
|
||||
QStringList _logData = _logger->getLogData();
|
||||
for (int i = 0; i < _logData.size(); ++i) {
|
||||
appendLogLine(_logData[i]);
|
||||
}
|
||||
_logTextBox->insertPlainText(_logger->getLogData());
|
||||
_logTextBox->ensureCursorVisible();
|
||||
}
|
||||
|
||||
KeywordHighlighter::KeywordHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), keywordFormat() {
|
||||
|
|
|
@ -351,6 +351,7 @@ bool ModelHandler::parseHeaders(QNetworkReply* reply) {
|
|||
|
||||
QList<QStandardItem*> items = _model.findItems(QFileInfo(reply->url().toString()).baseName());
|
||||
if (items.isEmpty() || items.first()->text() == DO_NOT_MODIFY_TAG) {
|
||||
_lock.unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
239
interface/src/ui/NodeBounds.cpp
Normal file
239
interface/src/ui/NodeBounds.cpp
Normal file
|
@ -0,0 +1,239 @@
|
|||
//
|
||||
// NodeBounds.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Ryan Huffman on 05/14/14.
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
//
|
||||
// This class draws a border around the different Voxel, Model, and Particle nodes on the current domain,
|
||||
// and a semi-transparent cube around the currently mouse-overed node.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "Application.h"
|
||||
#include "Util.h"
|
||||
|
||||
#include "NodeBounds.h"
|
||||
|
||||
NodeBounds::NodeBounds(QObject* parent) :
|
||||
QObject(parent),
|
||||
_showVoxelNodes(false),
|
||||
_showModelNodes(false),
|
||||
_showParticleNodes(false),
|
||||
_overlayText() {
|
||||
|
||||
}
|
||||
|
||||
void NodeBounds::draw() {
|
||||
if (!(_showVoxelNodes || _showModelNodes || _showParticleNodes)) {
|
||||
_overlayText[0] = '\0';
|
||||
return;
|
||||
}
|
||||
|
||||
NodeToJurisdictionMap& voxelServerJurisdictions = Application::getInstance()->getVoxelServerJurisdictions();
|
||||
NodeToJurisdictionMap& modelServerJurisdictions = Application::getInstance()->getModelServerJurisdictions();
|
||||
NodeToJurisdictionMap& particleServerJurisdictions = Application::getInstance()->getParticleServerJurisdictions();
|
||||
NodeToJurisdictionMap* serverJurisdictions;
|
||||
|
||||
// Compute ray to find selected nodes later on. We can't use the pre-computed ray in Application because it centers
|
||||
// itself after the cursor disappears.
|
||||
Application* application = Application::getInstance();
|
||||
QGLWidget* glWidget = application->getGLWidget();
|
||||
float mouseX = application->getMouseX() / (float)glWidget->width();
|
||||
float mouseY = application->getMouseY() / (float)glWidget->height();
|
||||
glm::vec3 mouseRayOrigin;
|
||||
glm::vec3 mouseRayDirection;
|
||||
application->getViewFrustum()->computePickRay(mouseX, mouseY, mouseRayOrigin, mouseRayDirection);
|
||||
|
||||
// Variables to keep track of the selected node and properties to draw the cube later if needed
|
||||
Node* selectedNode = NULL;
|
||||
float selectedDistance = FLT_MAX;
|
||||
bool selectedIsInside = true;
|
||||
glm::vec3 selectedCenter;
|
||||
float selectedScale = 0;
|
||||
|
||||
NodeList* nodeList = NodeList::getInstance();
|
||||
|
||||
foreach (const SharedNodePointer& node, nodeList->getNodeHash()) {
|
||||
NodeType_t nodeType = node->getType();
|
||||
|
||||
if (nodeType == NodeType::VoxelServer && _showVoxelNodes) {
|
||||
serverJurisdictions = &voxelServerJurisdictions;
|
||||
} else if (nodeType == NodeType::ModelServer && _showModelNodes) {
|
||||
serverJurisdictions = &modelServerJurisdictions;
|
||||
} else if (nodeType == NodeType::ParticleServer && _showParticleNodes) {
|
||||
serverJurisdictions = &particleServerJurisdictions;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
QUuid nodeUUID = node->getUUID();
|
||||
if (serverJurisdictions->find(nodeUUID) != serverJurisdictions->end()) {
|
||||
const JurisdictionMap& map = serverJurisdictions->value(nodeUUID);
|
||||
|
||||
unsigned char* rootCode = map.getRootOctalCode();
|
||||
|
||||
if (rootCode) {
|
||||
VoxelPositionSize rootDetails;
|
||||
voxelDetailsForCode(rootCode, rootDetails);
|
||||
glm::vec3 location(rootDetails.x, rootDetails.y, rootDetails.z);
|
||||
location *= (float)TREE_SCALE;
|
||||
|
||||
AABox serverBounds(location, rootDetails.s * TREE_SCALE);
|
||||
|
||||
glm::vec3 center = serverBounds.getVertex(BOTTOM_RIGHT_NEAR)
|
||||
+ ((serverBounds.getVertex(TOP_LEFT_FAR) - serverBounds.getVertex(BOTTOM_RIGHT_NEAR)) / 2.0f);
|
||||
|
||||
const float VOXEL_NODE_SCALE = 1.00f;
|
||||
const float MODEL_NODE_SCALE = 0.99f;
|
||||
const float PARTICLE_NODE_SCALE = 0.98f;
|
||||
|
||||
float scaleFactor = rootDetails.s * TREE_SCALE;
|
||||
|
||||
// Scale by 0.92 - 1.00 depending on the scale of the node. This allows smaller nodes to scale in
|
||||
// a bit and not overlap larger nodes.
|
||||
scaleFactor *= 0.92 + (rootDetails.s * 0.08);
|
||||
|
||||
// Scale different node types slightly differently because it's common for them to overlap.
|
||||
if (nodeType == NodeType::VoxelServer) {
|
||||
scaleFactor *= VOXEL_NODE_SCALE;
|
||||
} else if (nodeType == NodeType::ModelServer) {
|
||||
scaleFactor *= MODEL_NODE_SCALE;
|
||||
} else {
|
||||
scaleFactor *= PARTICLE_NODE_SCALE;
|
||||
}
|
||||
|
||||
float red, green, blue;
|
||||
getColorForNodeType(nodeType, red, green, blue);
|
||||
drawNodeBorder(center, scaleFactor, red, green, blue);
|
||||
|
||||
float distance;
|
||||
BoxFace face;
|
||||
bool inside = serverBounds.contains(mouseRayOrigin);
|
||||
bool colliding = serverBounds.findRayIntersection(mouseRayOrigin, mouseRayDirection, distance, face);
|
||||
|
||||
// If the camera is inside a node it will be "selected" if you don't have your cursor over another node
|
||||
// that you aren't inside.
|
||||
if (colliding && (!selectedNode || (!inside && (distance < selectedDistance || selectedIsInside)))) {
|
||||
selectedNode = node.data();
|
||||
selectedDistance = distance;
|
||||
selectedIsInside = inside;
|
||||
selectedCenter = center;
|
||||
selectedScale = scaleFactor;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (selectedNode) {
|
||||
glPushMatrix();
|
||||
|
||||
glTranslatef(selectedCenter.x, selectedCenter.y, selectedCenter.z);
|
||||
glScalef(selectedScale, selectedScale, selectedScale);
|
||||
|
||||
NodeType_t selectedNodeType = selectedNode->getType();
|
||||
float red, green, blue;
|
||||
getColorForNodeType(selectedNode->getType(), red, green, blue);
|
||||
|
||||
glColor4f(red, green, blue, 0.2);
|
||||
glutSolidCube(1.0);
|
||||
|
||||
glPopMatrix();
|
||||
|
||||
HifiSockAddr addr = selectedNode->getPublicSocket();
|
||||
QString overlay = QString("%1:%2 %3ms")
|
||||
.arg(addr.getAddress().toString())
|
||||
.arg(addr.getPort())
|
||||
.arg(selectedNode->getPingMs())
|
||||
.left(MAX_OVERLAY_TEXT_LENGTH);
|
||||
|
||||
// Ideally we'd just use a QString, but I ran into weird blinking issues using
|
||||
// constData() directly, as if the data was being overwritten.
|
||||
strcpy(_overlayText, overlay.toLocal8Bit().constData());
|
||||
} else {
|
||||
_overlayText[0] = '\0';
|
||||
}
|
||||
}
|
||||
|
||||
void NodeBounds::drawNodeBorder(const glm::vec3& center, float scale, float red, float green, float blue) {
|
||||
glPushMatrix();
|
||||
|
||||
glTranslatef(center.x, center.y, center.z);
|
||||
glScalef(scale, scale, scale);
|
||||
|
||||
glLineWidth(2.5);
|
||||
glColor3f(red, green, blue);
|
||||
glBegin(GL_LINES);
|
||||
|
||||
glVertex3f(-0.5, -0.5, -0.5);
|
||||
glVertex3f( 0.5, -0.5, -0.5);
|
||||
|
||||
glVertex3f(-0.5, -0.5, -0.5);
|
||||
glVertex3f(-0.5, 0.5, -0.5);
|
||||
|
||||
glVertex3f(-0.5, -0.5, -0.5);
|
||||
glVertex3f(-0.5, -0.5, 0.5);
|
||||
|
||||
glVertex3f(-0.5, 0.5, -0.5);
|
||||
glVertex3f( 0.5, 0.5, -0.5);
|
||||
|
||||
glVertex3f(-0.5, 0.5, -0.5);
|
||||
glVertex3f(-0.5, 0.5, 0.5);
|
||||
|
||||
glVertex3f( 0.5, 0.5, 0.5);
|
||||
glVertex3f(-0.5, 0.5, 0.5);
|
||||
|
||||
glVertex3f( 0.5, 0.5, 0.5);
|
||||
glVertex3f( 0.5, -0.5, 0.5);
|
||||
|
||||
glVertex3f( 0.5, 0.5, 0.5);
|
||||
glVertex3f( 0.5, 0.5, -0.5);
|
||||
|
||||
glVertex3f( 0.5, -0.5, 0.5);
|
||||
glVertex3f(-0.5, -0.5, 0.5);
|
||||
|
||||
glVertex3f( 0.5, -0.5, 0.5);
|
||||
glVertex3f( 0.5, -0.5, -0.5);
|
||||
|
||||
glVertex3f( 0.5, 0.5, -0.5);
|
||||
glVertex3f( 0.5, -0.5, -0.5);
|
||||
|
||||
glVertex3f(-0.5, 0.5, 0.5);
|
||||
glVertex3f(-0.5, -0.5, 0.5);
|
||||
|
||||
glEnd();
|
||||
|
||||
glPopMatrix();
|
||||
}
|
||||
|
||||
void NodeBounds::getColorForNodeType(NodeType_t nodeType, float& red, float& green, float& blue) {
|
||||
red = nodeType == NodeType::VoxelServer ? 1.0 : 0.0;
|
||||
green = nodeType == NodeType::ParticleServer ? 1.0 : 0.0;
|
||||
blue = nodeType == NodeType::ModelServer ? 1.0 : 0.0;
|
||||
}
|
||||
|
||||
void NodeBounds::drawOverlay() {
|
||||
if (strlen(_overlayText) > 0) {
|
||||
Application* application = Application::getInstance();
|
||||
|
||||
const float TEXT_COLOR[] = { 0.90f, 0.90f, 0.90f };
|
||||
const float TEXT_SCALE = 0.1f;
|
||||
const int TEXT_HEIGHT = 10;
|
||||
const float ROTATION = 0.0f;
|
||||
const int FONT = 2;
|
||||
const int PADDING = 10;
|
||||
const int MOUSE_OFFSET = 10;
|
||||
const int BACKGROUND_OFFSET_Y = -20;
|
||||
const int BACKGROUND_BEVEL = 3;
|
||||
|
||||
int mouseX = application->getMouseX(),
|
||||
mouseY = application->getMouseY(),
|
||||
textWidth = widthText(TEXT_SCALE, 0, _overlayText);
|
||||
glColor4f(0.4, 0.4, 0.4, 0.6);
|
||||
renderBevelCornersRect(mouseX + MOUSE_OFFSET, mouseY - TEXT_HEIGHT - PADDING,
|
||||
textWidth + (2 * PADDING), TEXT_HEIGHT + (2 * PADDING), BACKGROUND_BEVEL);
|
||||
drawText(mouseX + MOUSE_OFFSET + PADDING, mouseY, TEXT_SCALE, ROTATION, FONT, _overlayText, TEXT_COLOR);
|
||||
}
|
||||
}
|
50
interface/src/ui/NodeBounds.h
Normal file
50
interface/src/ui/NodeBounds.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
//
|
||||
// NodeBounds.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Ryan Huffman on 05/14/14.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_NodeBounds_h
|
||||
#define hifi_NodeBounds_h
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <NodeList.h>
|
||||
|
||||
const int MAX_OVERLAY_TEXT_LENGTH = 64;
|
||||
|
||||
class NodeBounds : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
NodeBounds(QObject* parent = NULL);
|
||||
|
||||
bool getShowVoxelNodes() { return _showVoxelNodes; }
|
||||
bool getShowModelNodes() { return _showModelNodes; }
|
||||
bool getShowParticleNodes() { return _showParticleNodes; }
|
||||
|
||||
void draw();
|
||||
void drawOverlay();
|
||||
|
||||
public slots:
|
||||
void setShowVoxelNodes(bool value) { _showVoxelNodes = value; }
|
||||
void setShowModelNodes(bool value) { _showModelNodes = value; }
|
||||
void setShowParticleNodes(bool value) { _showParticleNodes = value; }
|
||||
|
||||
protected:
|
||||
void drawNodeBorder(const glm::vec3& center, float scale, float red, float green, float blue);
|
||||
void getColorForNodeType(NodeType_t nodeType, float& red, float& green, float& blue);
|
||||
|
||||
private:
|
||||
bool _showVoxelNodes;
|
||||
bool _showModelNodes;
|
||||
bool _showParticleNodes;
|
||||
char _overlayText[MAX_OVERLAY_TEXT_LENGTH + 1];
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_NodeBounds_h
|
|
@ -28,12 +28,35 @@ OAuthWebViewHandler::OAuthWebViewHandler() :
|
|||
|
||||
}
|
||||
|
||||
const char HIGH_FIDELITY_CA[] = "-----BEGIN CERTIFICATE-----\n"
|
||||
"MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n"
|
||||
"VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n"
|
||||
"aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n"
|
||||
"YXRpb25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEW\n"
|
||||
"E29wc0BoaWdoZmlkZWxpdHkuaW8wHhcNMTQwMzI4MjIzMzM1WhcNMjQwMzI1MjIz\n"
|
||||
"MzM1WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV\n"
|
||||
"BAcTDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoTEkhpZ2ggRmlkZWxpdHksIEluYzET\n"
|
||||
"MBEGA1UECxMKT3BlcmF0aW9uczEYMBYGA1UEAxMPaGlnaGZpZGVsaXR5LmlvMSIw\n"
|
||||
"IAYJKoZIhvcNAQkBFhNvcHNAaGlnaGZpZGVsaXR5LmlvMIGfMA0GCSqGSIb3DQEB\n"
|
||||
"AQUAA4GNADCBiQKBgQDyo1euYiPPEdnvDZnIjWrrP230qUKMSj8SWoIkbTJF2hE8\n"
|
||||
"2eP3YOgbgSGBzZ8EJBxIOuNmj9g9Eg6691hIKFqy5W0BXO38P04Gg+pVBvpHFGBi\n"
|
||||
"wpqGbfsjaUDuYmBeJRcMO0XYkLCRQG+lAQNHoFDdItWAJfC3FwtP3OCDnz8cNwID\n"
|
||||
"AQABo4IBEzCCAQ8wHQYDVR0OBBYEFCSv2kmiGg6VFMnxXzLDNP304cPAMIHfBgNV\n"
|
||||
"HSMEgdcwgdSAFCSv2kmiGg6VFMnxXzLDNP304cPAoYGwpIGtMIGqMQswCQYDVQQG\n"
|
||||
"EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\n"
|
||||
"bzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVyYXRp\n"
|
||||
"b25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEWE29w\n"
|
||||
"c0BoaWdoZmlkZWxpdHkuaW+CCQDZX0ZEQ/QPGzAMBgNVHRMEBTADAQH/MA0GCSqG\n"
|
||||
"SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n"
|
||||
"FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n"
|
||||
"Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
void OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig() {
|
||||
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
|
||||
|
||||
// add the High Fidelity root CA to the list of trusted CA certificates
|
||||
QByteArray highFidelityCACertificate(reinterpret_cast<char*>(DTLSSession::highFidelityCADatum()->data),
|
||||
DTLSSession::highFidelityCADatum()->size);
|
||||
QByteArray highFidelityCACertificate(HIGH_FIDELITY_CA, sizeof(HIGH_FIDELITY_CA));
|
||||
sslConfig.setCaCertificates(sslConfig.caCertificates() + QSslCertificate::fromData(highFidelityCACertificate));
|
||||
|
||||
// set the modified configuration
|
||||
|
|
|
@ -145,7 +145,9 @@ void PreferencesDialog::loadPreferences() {
|
|||
ui.faceshiftEyeDeflectionSider->maximum());
|
||||
|
||||
ui.audioJitterSpin->setValue(menuInstance->getAudioJitterBufferSamples());
|
||||
|
||||
|
||||
ui.realWorldFieldOfViewSpin->setValue(menuInstance->getRealWorldFieldOfView());
|
||||
|
||||
ui.fieldOfViewSpin->setValue(menuInstance->getFieldOfView());
|
||||
|
||||
ui.leanScaleSpin->setValue(myAvatar->getLeanScale());
|
||||
|
@ -203,6 +205,8 @@ void PreferencesDialog::savePreferences() {
|
|||
Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(),
|
||||
Application::getInstance()->getGLWidget()->height());
|
||||
|
||||
Menu::getInstance()->setRealWorldFieldOfView(ui.realWorldFieldOfViewSpin->value());
|
||||
|
||||
Menu::getInstance()->setFieldOfView(ui.fieldOfViewSpin->value());
|
||||
|
||||
Menu::getInstance()->setFaceshiftEyeDeflection(ui.faceshiftEyeDeflectionSider->value() /
|
||||
|
|
|
@ -23,7 +23,8 @@ Overlay::Overlay() :
|
|||
_parent(NULL),
|
||||
_alpha(DEFAULT_ALPHA),
|
||||
_color(DEFAULT_BACKGROUND_COLOR),
|
||||
_visible(true)
|
||||
_visible(true),
|
||||
_anchor(NO_ANCHOR)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -51,8 +52,15 @@ void Overlay::setProperties(const QScriptValue& properties) {
|
|||
if (properties.property("alpha").isValid()) {
|
||||
setAlpha(properties.property("alpha").toVariant().toFloat());
|
||||
}
|
||||
|
||||
|
||||
if (properties.property("visible").isValid()) {
|
||||
setVisible(properties.property("visible").toVariant().toBool());
|
||||
}
|
||||
|
||||
if (properties.property("anchor").isValid()) {
|
||||
QString property = properties.property("anchor").toVariant().toString();
|
||||
if (property == "MyAvatar") {
|
||||
setAnchor(MY_AVATAR);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,11 @@ class Overlay : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Anchor {
|
||||
NO_ANCHOR,
|
||||
MY_AVATAR
|
||||
};
|
||||
|
||||
Overlay();
|
||||
~Overlay();
|
||||
void init(QGLWidget* parent);
|
||||
|
@ -38,11 +43,13 @@ public:
|
|||
bool getVisible() const { return _visible; }
|
||||
const xColor& getColor() const { return _color; }
|
||||
float getAlpha() const { return _alpha; }
|
||||
Anchor getAnchor() const { return _anchor; }
|
||||
|
||||
// setters
|
||||
void setVisible(bool visible) { _visible = visible; }
|
||||
void setColor(const xColor& color) { _color = color; }
|
||||
void setAlpha(float alpha) { _alpha = alpha; }
|
||||
void setAnchor(Anchor anchor) { _anchor = anchor; }
|
||||
|
||||
virtual void setProperties(const QScriptValue& properties);
|
||||
|
||||
|
@ -51,6 +58,7 @@ protected:
|
|||
float _alpha;
|
||||
xColor _color;
|
||||
bool _visible; // should the overlay be drawn at all
|
||||
Anchor _anchor;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <Application.h>
|
||||
|
||||
#include "Cube3DOverlay.h"
|
||||
#include "ImageOverlay.h"
|
||||
|
@ -57,8 +58,41 @@ void Overlays::render2D() {
|
|||
}
|
||||
|
||||
void Overlays::render3D() {
|
||||
if (_overlays3D.size() == 0) {
|
||||
return;
|
||||
}
|
||||
bool myAvatarComputed = false;
|
||||
MyAvatar* avatar;
|
||||
glm::quat myAvatarRotation;
|
||||
glm::vec3 myAvatarPosition;
|
||||
float angle;
|
||||
glm::vec3 axis;
|
||||
float myAvatarScale;
|
||||
|
||||
foreach(Overlay* thisOverlay, _overlays3D) {
|
||||
glPushMatrix();
|
||||
switch (thisOverlay->getAnchor()) {
|
||||
case Overlay::MY_AVATAR:
|
||||
if (!myAvatarComputed) {
|
||||
avatar = Application::getInstance()->getAvatar();
|
||||
myAvatarRotation = avatar->getOrientation();
|
||||
myAvatarPosition = avatar->getPosition();
|
||||
angle = glm::degrees(glm::angle(myAvatarRotation));
|
||||
axis = glm::axis(myAvatarRotation);
|
||||
myAvatarScale = avatar->getScale();
|
||||
|
||||
myAvatarComputed = true;
|
||||
}
|
||||
|
||||
glTranslatef(myAvatarPosition.x, myAvatarPosition.y, myAvatarPosition.z);
|
||||
glRotatef(angle, axis.x, axis.y, axis.z);
|
||||
glScalef(myAvatarScale, myAvatarScale, myAvatarScale);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
thisOverlay->render();
|
||||
glPopMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -61,6 +61,24 @@ void VoxelPacketProcessor::processPacket(const SharedNodePointer& sendingNode, c
|
|||
} // fall through to piggyback message
|
||||
|
||||
voxelPacketType = packetTypeForPacket(mutablePacket);
|
||||
PacketVersion packetVersion = mutablePacket[1];
|
||||
PacketVersion expectedVersion = versionForPacketType(voxelPacketType);
|
||||
|
||||
// check version of piggyback packet against expected version
|
||||
if (packetVersion != expectedVersion) {
|
||||
static QMultiMap<QUuid, PacketType> versionDebugSuppressMap;
|
||||
|
||||
QUuid senderUUID = uuidFromPacketHeader(packet);
|
||||
if (!versionDebugSuppressMap.contains(senderUUID, voxelPacketType)) {
|
||||
qDebug() << "Packet version mismatch on" << voxelPacketType << "- Sender"
|
||||
<< senderUUID << "sent" << (int)packetVersion << "but"
|
||||
<< (int)expectedVersion << "expected.";
|
||||
|
||||
versionDebugSuppressMap.insert(senderUUID, voxelPacketType);
|
||||
}
|
||||
return; // bail since piggyback version doesn't match
|
||||
}
|
||||
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) {
|
||||
app->trackIncomingVoxelPacket(mutablePacket, sendingNode, wasStatsPacket);
|
||||
|
|
|
@ -862,6 +862,94 @@ color: #0e7077</string>
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_11">
|
||||
<property name="spacing">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>10</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true"/>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Real world vertical field of view (angular size of monitor)</string>
|
||||
</property>
|
||||
<property name="indent">
|
||||
<number>15</number>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>fieldOfViewSpin</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_11">
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="realWorldFieldOfViewSpin">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Fixed" vsizetype="Fixed">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>95</width>
|
||||
<height>36</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="maximumSize">
|
||||
<size>
|
||||
<width>95</width>
|
||||
<height>36</height>
|
||||
</size>
|
||||
</property>
|
||||
<property name="font">
|
||||
<font>
|
||||
<family>Arial</family>
|
||||
</font>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>180</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
||||
<property name="spacing">
|
||||
|
|
35
libraries/animation/CMakeLists.txt
Normal file
35
libraries/animation/CMakeLists.txt
Normal file
|
@ -0,0 +1,35 @@
|
|||
cmake_minimum_required(VERSION 2.8)
|
||||
|
||||
if (WIN32)
|
||||
cmake_policy (SET CMP0020 NEW)
|
||||
endif (WIN32)
|
||||
|
||||
set(ROOT_DIR ../..)
|
||||
set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
|
||||
|
||||
# setup for find modules
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cmake/modules/")
|
||||
|
||||
set(TARGET_NAME animation)
|
||||
|
||||
find_package(Qt5Widgets REQUIRED)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
|
||||
setup_hifi_library(${TARGET_NAME})
|
||||
|
||||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
include_glm(${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
||||
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# link ZLIB
|
||||
find_package(ZLIB)
|
||||
|
||||
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
|
@ -36,7 +36,8 @@ QSharedPointer<Resource> AnimationCache::createResource(const QUrl& url, const Q
|
|||
}
|
||||
|
||||
Animation::Animation(const QUrl& url) :
|
||||
Resource(url) {
|
||||
Resource(url),
|
||||
_isValid(false) {
|
||||
}
|
||||
|
||||
class AnimationReader : public QRunnable {
|
||||
|
@ -93,6 +94,7 @@ QVector<FBXAnimationFrame> Animation::getFrames() const {
|
|||
void Animation::setGeometry(const FBXGeometry& geometry) {
|
||||
_geometry = geometry;
|
||||
finishedLoading(true);
|
||||
_isValid = true;
|
||||
}
|
||||
|
||||
void Animation::downloadFinished(QNetworkReply* reply) {
|
|
@ -53,6 +53,8 @@ public:
|
|||
Q_INVOKABLE QStringList getJointNames() const;
|
||||
|
||||
Q_INVOKABLE QVector<FBXAnimationFrame> getFrames() const;
|
||||
|
||||
bool isValid() const { return _isValid; }
|
||||
|
||||
protected:
|
||||
|
||||
|
@ -63,6 +65,7 @@ protected:
|
|||
private:
|
||||
|
||||
FBXGeometry _geometry;
|
||||
bool _isValid;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimationCache_h
|
|
@ -23,13 +23,7 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake)
|
|||
link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# link GnuTLS
|
||||
find_package(GnuTLS REQUIRED)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}")
|
||||
target_link_libraries(${TARGET_NAME} "${GNUTLS_LIBRARY}")
|
||||
endif ()
|
|
@ -28,13 +28,9 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
find_package(GnuTLS REQUIRED)
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Script)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
|
||||
include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}")
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Script "${GNUTLS_LIBRARY}")
|
||||
endif ()
|
|
@ -24,15 +24,13 @@ link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# link ZLIB and GnuTLS
|
||||
# link ZLIB
|
||||
find_package(ZLIB)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}")
|
||||
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
|
||||
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
|
||||
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")
|
||||
endif ()
|
|
@ -577,6 +577,26 @@ const char* FACESHIFT_BLENDSHAPES[] = {
|
|||
""
|
||||
};
|
||||
|
||||
const char* HUMANIK_JOINTS[] = {
|
||||
"RightHand",
|
||||
"RightForeArm",
|
||||
"RightArm",
|
||||
"Head",
|
||||
"LeftArm",
|
||||
"LeftForeArm",
|
||||
"LeftHand",
|
||||
"Neck",
|
||||
"Spine",
|
||||
"Hips",
|
||||
"RightUpLeg",
|
||||
"LeftUpLeg",
|
||||
"RightLeg",
|
||||
"LeftLeg",
|
||||
"RightFoot",
|
||||
"LeftFoot",
|
||||
""
|
||||
};
|
||||
|
||||
class FBXModel {
|
||||
public:
|
||||
QString name;
|
||||
|
@ -1012,10 +1032,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
QString jointHeadName = processID(getString(joints.value("jointHead", "jointHead")));
|
||||
QString jointLeftHandName = processID(getString(joints.value("jointLeftHand", "jointLeftHand")));
|
||||
QString jointRightHandName = processID(getString(joints.value("jointRightHand", "jointRightHand")));
|
||||
QVariantList jointLeftFingerNames = joints.values("jointLeftFinger");
|
||||
QVariantList jointRightFingerNames = joints.values("jointRightFinger");
|
||||
QVariantList jointLeftFingertipNames = joints.values("jointLeftFingertip");
|
||||
QVariantList jointRightFingertipNames = joints.values("jointRightFingertip");
|
||||
QString jointEyeLeftID;
|
||||
QString jointEyeRightID;
|
||||
QString jointNeckID;
|
||||
|
@ -1024,10 +1040,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
QString jointHeadID;
|
||||
QString jointLeftHandID;
|
||||
QString jointRightHandID;
|
||||
QVector<QString> jointLeftFingerIDs(jointLeftFingerNames.size());
|
||||
QVector<QString> jointRightFingerIDs(jointRightFingerNames.size());
|
||||
QVector<QString> jointLeftFingertipIDs(jointLeftFingertipNames.size());
|
||||
QVector<QString> jointRightFingertipIDs(jointRightFingertipNames.size());
|
||||
|
||||
QVector<QString> humanIKJointNames;
|
||||
for (int i = 0;; i++) {
|
||||
QByteArray jointName = HUMANIK_JOINTS[i];
|
||||
if (jointName.isEmpty()) {
|
||||
break;
|
||||
}
|
||||
humanIKJointNames.append(processID(getString(joints.value(jointName, jointName))));
|
||||
}
|
||||
QVector<QString> humanIKJointIDs(humanIKJointNames.size());
|
||||
|
||||
QVariantHash blendshapeMappings = mapping.value("bs").toHash();
|
||||
QMultiHash<QByteArray, WeightedIndex> blendshapeIndices;
|
||||
|
@ -1091,7 +1113,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
} else {
|
||||
name = getID(object.properties);
|
||||
}
|
||||
int index;
|
||||
if (name == jointEyeLeftName || name == "EyeL" || name == "joint_Leye") {
|
||||
jointEyeLeftID = getID(object.properties);
|
||||
|
||||
|
@ -1115,19 +1136,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
|
||||
} else if (name == jointRightHandName) {
|
||||
jointRightHandID = getID(object.properties);
|
||||
|
||||
} else if ((index = jointLeftFingerNames.indexOf(name)) != -1) {
|
||||
jointLeftFingerIDs[index] = getID(object.properties);
|
||||
|
||||
} else if ((index = jointRightFingerNames.indexOf(name)) != -1) {
|
||||
jointRightFingerIDs[index] = getID(object.properties);
|
||||
|
||||
} else if ((index = jointLeftFingertipNames.indexOf(name)) != -1) {
|
||||
jointLeftFingertipIDs[index] = getID(object.properties);
|
||||
|
||||
} else if ((index = jointRightFingertipNames.indexOf(name)) != -1) {
|
||||
jointRightFingertipIDs[index] = getID(object.properties);
|
||||
}
|
||||
int humanIKJointIndex = humanIKJointNames.indexOf(name);
|
||||
if (humanIKJointIndex != -1) {
|
||||
humanIKJointIDs[humanIKJointIndex] = getID(object.properties);
|
||||
}
|
||||
|
||||
glm::vec3 translation;
|
||||
// NOTE: the euler angles as supplied by the FBX file are in degrees
|
||||
glm::vec3 rotationOffset;
|
||||
|
@ -1352,7 +1366,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
} else if (type.contains("bump") || type.contains("normal")) {
|
||||
bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
|
||||
} else if (type.contains("specular")) {
|
||||
} else if (type.contains("specular") || type.contains("reflection")) {
|
||||
specularTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1));
|
||||
|
||||
} else if (type == "lcl rotation") {
|
||||
|
@ -1513,11 +1527,11 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
geometry.headJointIndex = modelIDs.indexOf(jointHeadID);
|
||||
geometry.leftHandJointIndex = modelIDs.indexOf(jointLeftHandID);
|
||||
geometry.rightHandJointIndex = modelIDs.indexOf(jointRightHandID);
|
||||
geometry.leftFingerJointIndices = getIndices(jointLeftFingerIDs, modelIDs);
|
||||
geometry.rightFingerJointIndices = getIndices(jointRightFingerIDs, modelIDs);
|
||||
geometry.leftFingertipJointIndices = getIndices(jointLeftFingertipIDs, modelIDs);
|
||||
geometry.rightFingertipJointIndices = getIndices(jointRightFingertipIDs, modelIDs);
|
||||
|
||||
|
||||
foreach (const QString& id, humanIKJointIDs) {
|
||||
geometry.humanIKJointIndices.append(modelIDs.indexOf(id));
|
||||
}
|
||||
|
||||
// extract the translation component of the neck transform
|
||||
if (geometry.neckJointIndex != -1) {
|
||||
const glm::mat4& transform = geometry.joints.at(geometry.neckJointIndex).transform;
|
||||
|
@ -1621,11 +1635,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
|||
setTangents(extracted.mesh, part.quadIndices.at(i + 2), part.quadIndices.at(i + 3));
|
||||
setTangents(extracted.mesh, part.quadIndices.at(i + 3), part.quadIndices.at(i));
|
||||
}
|
||||
for (int i = 0; i < part.triangleIndices.size(); i += 3) {
|
||||
// <= size - 3 in order to prevent overflowing triangleIndices when (i % 3) != 0
|
||||
// This is most likely evidence of a further problem in extractMesh()
|
||||
for (int i = 0; i <= part.triangleIndices.size() - 3; i += 3) {
|
||||
setTangents(extracted.mesh, part.triangleIndices.at(i), part.triangleIndices.at(i + 1));
|
||||
setTangents(extracted.mesh, part.triangleIndices.at(i + 1), part.triangleIndices.at(i + 2));
|
||||
setTangents(extracted.mesh, part.triangleIndices.at(i + 2), part.triangleIndices.at(i));
|
||||
}
|
||||
if ((part.triangleIndices.size() % 3) != 0){
|
||||
qDebug() << "Error in extractFBXGeometry part.triangleIndices.size() is not divisible by three ";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -30,6 +30,9 @@ typedef QList<FBXNode> FBXNodeList;
|
|||
/// The names of the blendshapes expected by Faceshift, terminated with an empty string.
|
||||
extern const char* FACESHIFT_BLENDSHAPES[];
|
||||
|
||||
/// The names of the joints in the Maya HumanIK rig, terminated with an empty string.
|
||||
extern const char* HUMANIK_JOINTS[];
|
||||
|
||||
class Extents {
|
||||
public:
|
||||
/// set minimum and maximum to FLT_MAX and -FLT_MAX respectively
|
||||
|
@ -199,11 +202,7 @@ public:
|
|||
int leftHandJointIndex;
|
||||
int rightHandJointIndex;
|
||||
|
||||
QVector<int> leftFingerJointIndices;
|
||||
QVector<int> rightFingerJointIndices;
|
||||
|
||||
QVector<int> leftFingertipJointIndices;
|
||||
QVector<int> rightFingertipJointIndices;
|
||||
QVector<int> humanIKJointIndices;
|
||||
|
||||
glm::vec3 palmDirection;
|
||||
|
||||
|
|
|
@ -27,12 +27,9 @@ link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
|||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
include_glm(${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
find_package(GnuTLS REQUIRED)
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}")
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}")
|
||||
endif ()
|
|
@ -28,18 +28,18 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}")
|
|||
link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
|
||||
link_hifi_library(animation ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# for streamable
|
||||
link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# link ZLIB and GnuTLS
|
||||
# link ZLIB
|
||||
find_package(ZLIB)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}")
|
||||
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
include_directories(SYSTEM "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}")
|
||||
target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}")
|
||||
endif ()
|
|
@ -85,6 +85,15 @@ ModelItem::ModelItem(const ModelItemID& modelItemID, const ModelItemProperties&
|
|||
_shouldDie = false;
|
||||
_modelURL = MODEL_DEFAULT_MODEL_URL;
|
||||
_modelRotation = MODEL_DEFAULT_MODEL_ROTATION;
|
||||
|
||||
// animation related
|
||||
_animationURL = MODEL_DEFAULT_ANIMATION_URL;
|
||||
_animationIsPlaying = false;
|
||||
_animationFrameIndex = 0.0f;
|
||||
_animationFPS = MODEL_DEFAULT_ANIMATION_FPS;
|
||||
|
||||
_jointMappingCompleted = false;
|
||||
_lastAnimated = now;
|
||||
|
||||
setProperties(properties);
|
||||
}
|
||||
|
@ -110,6 +119,14 @@ void ModelItem::init(glm::vec3 position, float radius, rgbColor color, uint32_t
|
|||
_shouldDie = false;
|
||||
_modelURL = MODEL_DEFAULT_MODEL_URL;
|
||||
_modelRotation = MODEL_DEFAULT_MODEL_ROTATION;
|
||||
|
||||
// animation related
|
||||
_animationURL = MODEL_DEFAULT_ANIMATION_URL;
|
||||
_animationIsPlaying = false;
|
||||
_animationFrameIndex = 0.0f;
|
||||
_animationFPS = MODEL_DEFAULT_ANIMATION_FPS;
|
||||
_jointMappingCompleted = false;
|
||||
_lastAnimated = now;
|
||||
}
|
||||
|
||||
bool ModelItem::appendModelData(OctreePacketData* packetData) const {
|
||||
|
@ -150,6 +167,31 @@ bool ModelItem::appendModelData(OctreePacketData* packetData) const {
|
|||
if (success) {
|
||||
success = packetData->appendValue(getModelRotation());
|
||||
}
|
||||
|
||||
// animationURL
|
||||
if (success) {
|
||||
uint16_t animationURLLength = _animationURL.size() + 1; // include NULL
|
||||
success = packetData->appendValue(animationURLLength);
|
||||
if (success) {
|
||||
success = packetData->appendRawData((const unsigned char*)qPrintable(_animationURL), animationURLLength);
|
||||
}
|
||||
}
|
||||
|
||||
// animationIsPlaying
|
||||
if (success) {
|
||||
success = packetData->appendValue(getAnimationIsPlaying());
|
||||
}
|
||||
|
||||
// animationFrameIndex
|
||||
if (success) {
|
||||
success = packetData->appendValue(getAnimationFrameIndex());
|
||||
}
|
||||
|
||||
// animationFPS
|
||||
if (success) {
|
||||
success = packetData->appendValue(getAnimationFPS());
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
@ -166,6 +208,7 @@ int ModelItem::expectedBytes() {
|
|||
}
|
||||
|
||||
int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftToRead, ReadBitstreamToTreeParams& args) {
|
||||
|
||||
int bytesRead = 0;
|
||||
if (bytesLeftToRead >= expectedBytes()) {
|
||||
int clockSkew = args.sourceNode ? args.sourceNode->getClockSkewUsec() : 0;
|
||||
|
@ -215,7 +258,7 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT
|
|||
dataAt += sizeof(modelURLLength);
|
||||
bytesRead += sizeof(modelURLLength);
|
||||
QString modelURLString((const char*)dataAt);
|
||||
_modelURL = modelURLString;
|
||||
setModelURL(modelURLString);
|
||||
dataAt += modelURLLength;
|
||||
bytesRead += modelURLLength;
|
||||
|
||||
|
@ -224,13 +267,37 @@ int ModelItem::readModelDataFromBuffer(const unsigned char* data, int bytesLeftT
|
|||
dataAt += bytes;
|
||||
bytesRead += bytes;
|
||||
|
||||
//printf("ModelItem::readModelDataFromBuffer()... "); debugDump();
|
||||
if (args.bitstreamVersion >= VERSION_MODELS_HAVE_ANIMATION) {
|
||||
// animationURL
|
||||
uint16_t animationURLLength;
|
||||
memcpy(&animationURLLength, dataAt, sizeof(animationURLLength));
|
||||
dataAt += sizeof(animationURLLength);
|
||||
bytesRead += sizeof(animationURLLength);
|
||||
QString animationURLString((const char*)dataAt);
|
||||
setAnimationURL(animationURLString);
|
||||
dataAt += animationURLLength;
|
||||
bytesRead += animationURLLength;
|
||||
|
||||
// animationIsPlaying
|
||||
memcpy(&_animationIsPlaying, dataAt, sizeof(_animationIsPlaying));
|
||||
dataAt += sizeof(_animationIsPlaying);
|
||||
bytesRead += sizeof(_animationIsPlaying);
|
||||
|
||||
// animationFrameIndex
|
||||
memcpy(&_animationFrameIndex, dataAt, sizeof(_animationFrameIndex));
|
||||
dataAt += sizeof(_animationFrameIndex);
|
||||
bytesRead += sizeof(_animationFrameIndex);
|
||||
|
||||
// animationFPS
|
||||
memcpy(&_animationFPS, dataAt, sizeof(_animationFPS));
|
||||
dataAt += sizeof(_animationFPS);
|
||||
bytesRead += sizeof(_animationFPS);
|
||||
}
|
||||
}
|
||||
return bytesRead;
|
||||
}
|
||||
|
||||
ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int& processedBytes, ModelTree* tree, bool& valid) {
|
||||
|
||||
ModelItem newModelItem; // id and _lastUpdated will get set here...
|
||||
const unsigned char* dataAt = data;
|
||||
processedBytes = 0;
|
||||
|
@ -340,12 +407,52 @@ ModelItem ModelItem::fromEditPacket(const unsigned char* data, int length, int&
|
|||
}
|
||||
|
||||
// modelRotation
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) {
|
||||
if (isNewModelItem || ((packetContainsBits &
|
||||
MODEL_PACKET_CONTAINS_MODEL_ROTATION) == MODEL_PACKET_CONTAINS_MODEL_ROTATION)) {
|
||||
int bytes = unpackOrientationQuatFromBytes(dataAt, newModelItem._modelRotation);
|
||||
dataAt += bytes;
|
||||
processedBytes += bytes;
|
||||
}
|
||||
|
||||
// animationURL
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) {
|
||||
uint16_t animationURLLength;
|
||||
memcpy(&animationURLLength, dataAt, sizeof(animationURLLength));
|
||||
dataAt += sizeof(animationURLLength);
|
||||
processedBytes += sizeof(animationURLLength);
|
||||
QString tempString((const char*)dataAt);
|
||||
newModelItem._animationURL = tempString;
|
||||
dataAt += animationURLLength;
|
||||
processedBytes += animationURLLength;
|
||||
}
|
||||
|
||||
// animationIsPlaying
|
||||
if (isNewModelItem || ((packetContainsBits &
|
||||
MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) {
|
||||
|
||||
memcpy(&newModelItem._animationIsPlaying, dataAt, sizeof(newModelItem._animationIsPlaying));
|
||||
dataAt += sizeof(newModelItem._animationIsPlaying);
|
||||
processedBytes += sizeof(newModelItem._animationIsPlaying);
|
||||
}
|
||||
|
||||
// animationFrameIndex
|
||||
if (isNewModelItem || ((packetContainsBits &
|
||||
MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) {
|
||||
|
||||
memcpy(&newModelItem._animationFrameIndex, dataAt, sizeof(newModelItem._animationFrameIndex));
|
||||
dataAt += sizeof(newModelItem._animationFrameIndex);
|
||||
processedBytes += sizeof(newModelItem._animationFrameIndex);
|
||||
}
|
||||
|
||||
// animationFPS
|
||||
if (isNewModelItem || ((packetContainsBits &
|
||||
MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) {
|
||||
|
||||
memcpy(&newModelItem._animationFPS, dataAt, sizeof(newModelItem._animationFPS));
|
||||
dataAt += sizeof(newModelItem._animationFPS);
|
||||
processedBytes += sizeof(newModelItem._animationFPS);
|
||||
}
|
||||
|
||||
const bool wantDebugging = false;
|
||||
if (wantDebugging) {
|
||||
qDebug("ModelItem::fromEditPacket()...");
|
||||
|
@ -363,6 +470,7 @@ void ModelItem::debugDump() const {
|
|||
qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z);
|
||||
qDebug(" radius:%f", getRadius());
|
||||
qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]);
|
||||
qDebug() << " modelURL:" << qPrintable(getModelURL());
|
||||
}
|
||||
|
||||
bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id, const ModelItemProperties& properties,
|
||||
|
@ -476,6 +584,47 @@ bool ModelItem::encodeModelEditMessageDetails(PacketType command, ModelItemID id
|
|||
sizeOut += bytes;
|
||||
}
|
||||
|
||||
// animationURL
|
||||
if (isNewModelItem || ((packetContainsBits & MODEL_PACKET_CONTAINS_ANIMATION_URL) == MODEL_PACKET_CONTAINS_ANIMATION_URL)) {
|
||||
uint16_t urlLength = properties.getAnimationURL().size() + 1;
|
||||
memcpy(copyAt, &urlLength, sizeof(urlLength));
|
||||
copyAt += sizeof(urlLength);
|
||||
sizeOut += sizeof(urlLength);
|
||||
memcpy(copyAt, qPrintable(properties.getAnimationURL()), urlLength);
|
||||
copyAt += urlLength;
|
||||
sizeOut += urlLength;
|
||||
}
|
||||
|
||||
// animationIsPlaying
|
||||
if (isNewModelItem || ((packetContainsBits &
|
||||
MODEL_PACKET_CONTAINS_ANIMATION_PLAYING) == MODEL_PACKET_CONTAINS_ANIMATION_PLAYING)) {
|
||||
|
||||
bool animationIsPlaying = properties.getAnimationIsPlaying();
|
||||
memcpy(copyAt, &animationIsPlaying, sizeof(animationIsPlaying));
|
||||
copyAt += sizeof(animationIsPlaying);
|
||||
sizeOut += sizeof(animationIsPlaying);
|
||||
}
|
||||
|
||||
// animationFrameIndex
|
||||
if (isNewModelItem || ((packetContainsBits &
|
||||
MODEL_PACKET_CONTAINS_ANIMATION_FRAME) == MODEL_PACKET_CONTAINS_ANIMATION_FRAME)) {
|
||||
|
||||
float animationFrameIndex = properties.getAnimationFrameIndex();
|
||||
memcpy(copyAt, &animationFrameIndex, sizeof(animationFrameIndex));
|
||||
copyAt += sizeof(animationFrameIndex);
|
||||
sizeOut += sizeof(animationFrameIndex);
|
||||
}
|
||||
|
||||
// animationFPS
|
||||
if (isNewModelItem || ((packetContainsBits &
|
||||
MODEL_PACKET_CONTAINS_ANIMATION_FPS) == MODEL_PACKET_CONTAINS_ANIMATION_FPS)) {
|
||||
|
||||
float animationFPS = properties.getAnimationFPS();
|
||||
memcpy(copyAt, &animationFPS, sizeof(animationFPS));
|
||||
copyAt += sizeof(animationFPS);
|
||||
sizeOut += sizeof(animationFPS);
|
||||
}
|
||||
|
||||
bool wantDebugging = false;
|
||||
if (wantDebugging) {
|
||||
qDebug("encodeModelItemEditMessageDetails()....");
|
||||
|
@ -521,9 +670,111 @@ void ModelItem::adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssi
|
|||
}
|
||||
}
|
||||
|
||||
void ModelItem::update(const quint64& now) {
|
||||
_lastUpdated = now;
|
||||
|
||||
QMap<QString, AnimationPointer> ModelItem::_loadedAnimations; // TODO: improve cleanup by leveraging the AnimationPointer(s)
|
||||
AnimationCache ModelItem::_animationCache;
|
||||
|
||||
// This class/instance will cleanup the animations once unloaded.
|
||||
class ModelAnimationsBookkeeper {
|
||||
public:
|
||||
~ModelAnimationsBookkeeper() {
|
||||
ModelItem::cleanupLoadedAnimations();
|
||||
}
|
||||
};
|
||||
|
||||
ModelAnimationsBookkeeper modelAnimationsBookkeeperInstance;
|
||||
|
||||
void ModelItem::cleanupLoadedAnimations() {
|
||||
foreach(AnimationPointer animation, _loadedAnimations) {
|
||||
animation.clear();
|
||||
}
|
||||
_loadedAnimations.clear();
|
||||
}
|
||||
|
||||
Animation* ModelItem::getAnimation(const QString& url) {
|
||||
AnimationPointer animation;
|
||||
|
||||
// if we don't already have this model then create it and initialize it
|
||||
if (_loadedAnimations.find(url) == _loadedAnimations.end()) {
|
||||
animation = _animationCache.getAnimation(url);
|
||||
_loadedAnimations[url] = animation;
|
||||
} else {
|
||||
animation = _loadedAnimations[url];
|
||||
}
|
||||
return animation.data();
|
||||
}
|
||||
|
||||
void ModelItem::mapJoints(const QStringList& modelJointNames) {
|
||||
// if we don't have animation, or we're already joint mapped then bail early
|
||||
if (!hasAnimation() || _jointMappingCompleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
Animation* myAnimation = getAnimation(_animationURL);
|
||||
|
||||
if (!_jointMappingCompleted) {
|
||||
QStringList animationJointNames = myAnimation->getJointNames();
|
||||
|
||||
if (modelJointNames.size() > 0 && animationJointNames.size() > 0) {
|
||||
_jointMapping.resize(modelJointNames.size());
|
||||
for (int i = 0; i < modelJointNames.size(); i++) {
|
||||
_jointMapping[i] = animationJointNames.indexOf(modelJointNames[i]);
|
||||
}
|
||||
_jointMappingCompleted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVector<glm::quat> ModelItem::getAnimationFrame() {
|
||||
QVector<glm::quat> frameData;
|
||||
if (hasAnimation() && _jointMappingCompleted) {
|
||||
Animation* myAnimation = getAnimation(_animationURL);
|
||||
QVector<FBXAnimationFrame> frames = myAnimation->getFrames();
|
||||
int frameCount = frames.size();
|
||||
|
||||
if (frameCount > 0) {
|
||||
int animationFrameIndex = (int)glm::floor(_animationFrameIndex) % frameCount;
|
||||
QVector<glm::quat> rotations = frames[animationFrameIndex].rotations;
|
||||
frameData.resize(_jointMapping.size());
|
||||
for (int j = 0; j < _jointMapping.size(); j++) {
|
||||
int rotationIndex = _jointMapping[j];
|
||||
if (rotationIndex != -1 && rotationIndex < rotations.size()) {
|
||||
frameData[j] = rotations[rotationIndex];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return frameData;
|
||||
}
|
||||
|
||||
void ModelItem::update(const quint64& updateTime) {
|
||||
_lastUpdated = updateTime;
|
||||
setShouldDie(getShouldDie());
|
||||
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// only advance the frame index if we're playing
|
||||
if (getAnimationIsPlaying()) {
|
||||
|
||||
float deltaTime = (float)(now - _lastAnimated) / (float)USECS_PER_SECOND;
|
||||
|
||||
const bool wantDebugging = false;
|
||||
if (wantDebugging) {
|
||||
qDebug() << "ModelItem::update() now=" << now;
|
||||
qDebug() << " updateTime=" << updateTime;
|
||||
qDebug() << " _lastAnimated=" << _lastAnimated;
|
||||
qDebug() << " deltaTime=" << deltaTime;
|
||||
}
|
||||
_lastAnimated = now;
|
||||
_animationFrameIndex += deltaTime * _animationFPS;
|
||||
|
||||
if (wantDebugging) {
|
||||
qDebug() << " _animationFrameIndex=" << _animationFrameIndex;
|
||||
}
|
||||
|
||||
} else {
|
||||
_lastAnimated = now;
|
||||
}
|
||||
}
|
||||
|
||||
void ModelItem::copyChangedProperties(const ModelItem& other) {
|
||||
|
@ -547,6 +798,10 @@ ModelItemProperties::ModelItemProperties() :
|
|||
_shouldDie(false),
|
||||
_modelURL(""),
|
||||
_modelRotation(MODEL_DEFAULT_MODEL_ROTATION),
|
||||
_animationURL(""),
|
||||
_animationIsPlaying(false),
|
||||
_animationFrameIndex(0.0),
|
||||
_animationFPS(MODEL_DEFAULT_ANIMATION_FPS),
|
||||
|
||||
_id(UNKNOWN_MODEL_ID),
|
||||
_idSet(false),
|
||||
|
@ -558,6 +813,10 @@ ModelItemProperties::ModelItemProperties() :
|
|||
_shouldDieChanged(false),
|
||||
_modelURLChanged(false),
|
||||
_modelRotationChanged(false),
|
||||
_animationURLChanged(false),
|
||||
_animationIsPlayingChanged(false),
|
||||
_animationFrameIndexChanged(false),
|
||||
_animationFPSChanged(false),
|
||||
_defaultSettings(true)
|
||||
{
|
||||
}
|
||||
|
@ -589,6 +848,22 @@ uint16_t ModelItemProperties::getChangedBits() const {
|
|||
changedBits += MODEL_PACKET_CONTAINS_MODEL_ROTATION;
|
||||
}
|
||||
|
||||
if (_animationURLChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_URL;
|
||||
}
|
||||
|
||||
if (_animationIsPlayingChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_PLAYING;
|
||||
}
|
||||
|
||||
if (_animationFrameIndexChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_FRAME;
|
||||
}
|
||||
|
||||
if (_animationFPSChanged) {
|
||||
changedBits += MODEL_PACKET_CONTAINS_ANIMATION_FPS;
|
||||
}
|
||||
|
||||
return changedBits;
|
||||
}
|
||||
|
||||
|
@ -611,6 +886,10 @@ QScriptValue ModelItemProperties::copyToScriptValue(QScriptEngine* engine) const
|
|||
QScriptValue modelRotation = quatToScriptValue(engine, _modelRotation);
|
||||
properties.setProperty("modelRotation", modelRotation);
|
||||
|
||||
properties.setProperty("animationURL", _animationURL);
|
||||
properties.setProperty("animationIsPlaying", _animationIsPlaying);
|
||||
properties.setProperty("animationFrameIndex", _animationFrameIndex);
|
||||
properties.setProperty("animationFPS", _animationFPS);
|
||||
|
||||
if (_idSet) {
|
||||
properties.setProperty("id", _id);
|
||||
|
@ -707,6 +986,46 @@ void ModelItemProperties::copyFromScriptValue(const QScriptValue &object) {
|
|||
}
|
||||
}
|
||||
|
||||
QScriptValue animationURL = object.property("animationURL");
|
||||
if (animationURL.isValid()) {
|
||||
QString newAnimationURL;
|
||||
newAnimationURL = animationURL.toVariant().toString();
|
||||
if (_defaultSettings || newAnimationURL != _animationURL) {
|
||||
_animationURL = newAnimationURL;
|
||||
_animationURLChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue animationIsPlaying = object.property("animationIsPlaying");
|
||||
if (animationIsPlaying.isValid()) {
|
||||
bool newIsAnimationPlaying;
|
||||
newIsAnimationPlaying = animationIsPlaying.toVariant().toBool();
|
||||
if (_defaultSettings || newIsAnimationPlaying != _animationIsPlaying) {
|
||||
_animationIsPlaying = newIsAnimationPlaying;
|
||||
_animationIsPlayingChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue animationFrameIndex = object.property("animationFrameIndex");
|
||||
if (animationFrameIndex.isValid()) {
|
||||
float newFrameIndex;
|
||||
newFrameIndex = animationFrameIndex.toVariant().toFloat();
|
||||
if (_defaultSettings || newFrameIndex != _animationFrameIndex) {
|
||||
_animationFrameIndex = newFrameIndex;
|
||||
_animationFrameIndexChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
QScriptValue animationFPS = object.property("animationFPS");
|
||||
if (animationFPS.isValid()) {
|
||||
float newFPS;
|
||||
newFPS = animationFPS.toVariant().toFloat();
|
||||
if (_defaultSettings || newFPS != _animationFPS) {
|
||||
_animationFPS = newFPS;
|
||||
_animationFPSChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
_lastEdited = usecTimestampNow();
|
||||
}
|
||||
|
||||
|
@ -741,7 +1060,27 @@ void ModelItemProperties::copyToModelItem(ModelItem& modelItem) const {
|
|||
modelItem.setModelRotation(_modelRotation);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
|
||||
if (_animationURLChanged) {
|
||||
modelItem.setAnimationURL(_animationURL);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (_animationIsPlayingChanged) {
|
||||
modelItem.setAnimationIsPlaying(_animationIsPlaying);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (_animationFrameIndexChanged) {
|
||||
modelItem.setAnimationFrameIndex(_animationFrameIndex);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (_animationFPSChanged) {
|
||||
modelItem.setAnimationFPS(_animationFPS);
|
||||
somethingChanged = true;
|
||||
}
|
||||
|
||||
if (somethingChanged) {
|
||||
bool wantDebug = false;
|
||||
if (wantDebug) {
|
||||
|
@ -761,6 +1100,10 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
|
|||
_shouldDie = modelItem.getShouldDie();
|
||||
_modelURL = modelItem.getModelURL();
|
||||
_modelRotation = modelItem.getModelRotation();
|
||||
_animationURL = modelItem.getAnimationURL();
|
||||
_animationIsPlaying = modelItem.getAnimationIsPlaying();
|
||||
_animationFrameIndex = modelItem.getAnimationFrameIndex();
|
||||
_animationFPS = modelItem.getAnimationFPS();
|
||||
|
||||
_id = modelItem.getID();
|
||||
_idSet = true;
|
||||
|
@ -772,6 +1115,10 @@ void ModelItemProperties::copyFromModelItem(const ModelItem& modelItem) {
|
|||
_shouldDieChanged = false;
|
||||
_modelURLChanged = false;
|
||||
_modelRotationChanged = false;
|
||||
_animationURLChanged = false;
|
||||
_animationIsPlayingChanged = false;
|
||||
_animationFrameIndexChanged = false;
|
||||
_animationFPSChanged = false;
|
||||
_defaultSettings = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <QtScript/QScriptEngine>
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include <AnimationCache.h>
|
||||
#include <CollisionInfo.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <OctreePacketData.h>
|
||||
|
@ -39,14 +40,23 @@ const uint32_t UNKNOWN_MODEL_ID = 0xFFFFFFFF;
|
|||
const uint16_t MODEL_PACKET_CONTAINS_RADIUS = 1;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_POSITION = 2;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_COLOR = 4;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 512;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 1024;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 2048;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_SHOULDDIE = 8;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_MODEL_URL = 16;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_MODEL_ROTATION = 32;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_URL = 64;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_PLAYING = 128;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_FRAME = 256;
|
||||
const uint16_t MODEL_PACKET_CONTAINS_ANIMATION_FPS = 512;
|
||||
|
||||
const float MODEL_DEFAULT_RADIUS = 0.1f / TREE_SCALE;
|
||||
const float MINIMUM_MODEL_ELEMENT_SIZE = (1.0f / 100000.0f) / TREE_SCALE; // smallest size container
|
||||
const QString MODEL_DEFAULT_MODEL_URL("");
|
||||
const glm::quat MODEL_DEFAULT_MODEL_ROTATION;
|
||||
const QString MODEL_DEFAULT_ANIMATION_URL("");
|
||||
const float MODEL_DEFAULT_ANIMATION_FPS = 30.0f;
|
||||
|
||||
const PacketVersion VERSION_MODELS_HAVE_ANIMATION = 1;
|
||||
const PacketVersion VERSION_ROOT_ELEMENT_HAS_DATA = 2;
|
||||
|
||||
/// A collection of properties of a model item used in the scripting API. Translates between the actual properties of a model
|
||||
/// and a JavaScript style hash/QScriptValue storing a set of properties. Used in scripting to set/get the complete set of
|
||||
|
@ -69,6 +79,10 @@ public:
|
|||
|
||||
const QString& getModelURL() const { return _modelURL; }
|
||||
const glm::quat& getModelRotation() const { return _modelRotation; }
|
||||
const QString& getAnimationURL() const { return _animationURL; }
|
||||
float getAnimationFrameIndex() const { return _animationFrameIndex; }
|
||||
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
|
||||
float getAnimationFPS() const { return _animationFPS; }
|
||||
|
||||
quint64 getLastEdited() const { return _lastEdited; }
|
||||
uint16_t getChangedBits() const;
|
||||
|
@ -82,6 +96,10 @@ public:
|
|||
// model related properties
|
||||
void setModelURL(const QString& url) { _modelURL = url; _modelURLChanged = true; }
|
||||
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; _modelRotationChanged = true; }
|
||||
void setAnimationURL(const QString& url) { _animationURL = url; _animationURLChanged = true; }
|
||||
void setAnimationFrameIndex(float value) { _animationFrameIndex = value; _animationFrameIndexChanged = true; }
|
||||
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; _animationIsPlayingChanged = true; }
|
||||
void setAnimationFPS(float value) { _animationFPS = value; _animationFPSChanged = true; }
|
||||
|
||||
/// used by ModelScriptingInterface to return ModelItemProperties for unknown models
|
||||
void setIsUnknownID() { _id = UNKNOWN_MODEL_ID; _idSet = true; }
|
||||
|
@ -97,6 +115,10 @@ private:
|
|||
|
||||
QString _modelURL;
|
||||
glm::quat _modelRotation;
|
||||
QString _animationURL;
|
||||
bool _animationIsPlaying;
|
||||
float _animationFrameIndex;
|
||||
float _animationFPS;
|
||||
|
||||
uint32_t _id;
|
||||
bool _idSet;
|
||||
|
@ -109,6 +131,10 @@ private:
|
|||
|
||||
bool _modelURLChanged;
|
||||
bool _modelRotationChanged;
|
||||
bool _animationURLChanged;
|
||||
bool _animationIsPlayingChanged;
|
||||
bool _animationFrameIndexChanged;
|
||||
bool _animationFPSChanged;
|
||||
bool _defaultSettings;
|
||||
};
|
||||
Q_DECLARE_METATYPE(ModelItemProperties);
|
||||
|
@ -178,6 +204,8 @@ public:
|
|||
bool hasModel() const { return !_modelURL.isEmpty(); }
|
||||
const QString& getModelURL() const { return _modelURL; }
|
||||
const glm::quat& getModelRotation() const { return _modelRotation; }
|
||||
bool hasAnimation() const { return !_animationURL.isEmpty(); }
|
||||
const QString& getAnimationURL() const { return _animationURL; }
|
||||
|
||||
ModelItemID getModelItemID() const { return ModelItemID(getID(), getCreatorTokenID(), getID() != UNKNOWN_MODEL_ID); }
|
||||
ModelItemProperties getProperties() const;
|
||||
|
@ -196,6 +224,7 @@ public:
|
|||
bool getShouldDie() const { return _shouldDie; }
|
||||
uint32_t getCreatorTokenID() const { return _creatorTokenID; }
|
||||
bool isNewlyCreated() const { return _newlyCreated; }
|
||||
bool isKnownID() const { return getID() != UNKNOWN_MODEL_ID; }
|
||||
|
||||
/// set position in domain scale units (0.0 - 1.0)
|
||||
void setPosition(const glm::vec3& value) { _position = value; }
|
||||
|
@ -215,6 +244,10 @@ public:
|
|||
// model related properties
|
||||
void setModelURL(const QString& url) { _modelURL = url; }
|
||||
void setModelRotation(const glm::quat& rotation) { _modelRotation = rotation; }
|
||||
void setAnimationURL(const QString& url) { _animationURL = url; }
|
||||
void setAnimationFrameIndex(float value) { _animationFrameIndex = value; }
|
||||
void setAnimationIsPlaying(bool value) { _animationIsPlaying = value; }
|
||||
void setAnimationFPS(float value) { _animationFPS = value; }
|
||||
|
||||
void setProperties(const ModelItemProperties& properties);
|
||||
|
||||
|
@ -239,6 +272,16 @@ public:
|
|||
static uint32_t getNextCreatorTokenID();
|
||||
static void handleAddModelResponse(const QByteArray& packet);
|
||||
|
||||
void mapJoints(const QStringList& modelJointNames);
|
||||
QVector<glm::quat> getAnimationFrame();
|
||||
bool jointsMapped() const { return _jointMappingCompleted; }
|
||||
|
||||
bool getAnimationIsPlaying() const { return _animationIsPlaying; }
|
||||
float getAnimationFrameIndex() const { return _animationFrameIndex; }
|
||||
float getAnimationFPS() const { return _animationFPS; }
|
||||
|
||||
static void cleanupLoadedAnimations();
|
||||
|
||||
protected:
|
||||
glm::vec3 _position;
|
||||
rgbColor _color;
|
||||
|
@ -256,10 +299,26 @@ protected:
|
|||
|
||||
quint64 _lastUpdated;
|
||||
quint64 _lastEdited;
|
||||
quint64 _lastAnimated;
|
||||
|
||||
QString _animationURL;
|
||||
float _animationFrameIndex; // we keep this as a float and round to int only when we need the exact index
|
||||
bool _animationIsPlaying;
|
||||
float _animationFPS;
|
||||
|
||||
bool _jointMappingCompleted;
|
||||
QVector<int> _jointMapping;
|
||||
|
||||
|
||||
// used by the static interfaces for creator token ids
|
||||
static uint32_t _nextCreatorTokenID;
|
||||
static std::map<uint32_t,uint32_t> _tokenIDsToIDs;
|
||||
|
||||
|
||||
static Animation* getAnimation(const QString& url);
|
||||
static QMap<QString, AnimationPointer> _loadedAnimations;
|
||||
static AnimationCache _animationCache;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_ModelItem_h
|
||||
|
|
|
@ -108,6 +108,7 @@ bool FindAndUpdateModelOperator::PostRecursion(OctreeElement* element) {
|
|||
return !_found; // if we haven't yet found it, keep looking
|
||||
}
|
||||
|
||||
// TODO: improve this to not use multiple recursions
|
||||
void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& senderNode) {
|
||||
// First, look for the existing model in the tree..
|
||||
FindAndUpdateModelOperator theOperator(model);
|
||||
|
@ -118,8 +119,13 @@ void ModelTree::storeModel(const ModelItem& model, const SharedNodePointer& send
|
|||
AABox modelBox = model.getAABox();
|
||||
ModelTreeElement* element = (ModelTreeElement*)getOrCreateChildElementContaining(model.getAABox());
|
||||
element->storeModel(model);
|
||||
|
||||
// In the case where we stored it, we also need to mark the entire "path" down to the model as
|
||||
// having changed. Otherwise viewers won't see this change. So we call this recursion now that
|
||||
// we know it will be found, this find/update will correctly mark the tree as changed.
|
||||
recurseTreeWithOperator(&theOperator);
|
||||
}
|
||||
// what else do we need to do here to get reaveraging to work
|
||||
|
||||
_isDirty = true;
|
||||
}
|
||||
|
||||
|
@ -491,11 +497,12 @@ void ModelTree::update() {
|
|||
lockForWrite();
|
||||
_isDirty = true;
|
||||
|
||||
ModelTreeUpdateArgs args = { };
|
||||
ModelTreeUpdateArgs args;
|
||||
recurseTreeWithOperation(updateOperation, &args);
|
||||
|
||||
// now add back any of the particles that moved elements....
|
||||
int movingModels = args._movingModels.size();
|
||||
|
||||
for (int i = 0; i < movingModels; i++) {
|
||||
bool shouldDie = args._movingModels[i].getShouldDie();
|
||||
|
||||
|
@ -553,7 +560,7 @@ bool ModelTree::encodeModelsDeletedSince(quint64& sinceTime, unsigned char* outp
|
|||
memcpy(copyAt, &numberOfIds, sizeof(numberOfIds));
|
||||
copyAt += sizeof(numberOfIds);
|
||||
outputLength += sizeof(numberOfIds);
|
||||
|
||||
|
||||
// we keep a multi map of model IDs to timestamps, we only want to include the model IDs that have been
|
||||
// deleted since we last sent to this node
|
||||
_recentlyDeletedModelsLock.lockForRead();
|
||||
|
@ -595,7 +602,6 @@ bool ModelTree::encodeModelsDeletedSince(quint64& sinceTime, unsigned char* outp
|
|||
|
||||
// replace the correct count for ids included
|
||||
memcpy(numberOfIDsAt, &numberOfIds, sizeof(numberOfIds));
|
||||
|
||||
return hasMoreToSend;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,10 +36,12 @@ public:
|
|||
// own definition. Implement these to allow your octree based server to support editing
|
||||
virtual bool getWantSVOfileVersions() const { return true; }
|
||||
virtual PacketType expectedDataPacketType() const { return PacketTypeModelData; }
|
||||
virtual bool canProcessVersion(PacketVersion thisVersion) const { return true; } // we support all versions
|
||||
virtual bool handlesEditPacketType(PacketType packetType) const;
|
||||
virtual int processEditPacketData(PacketType packetType, const unsigned char* packetData, int packetLength,
|
||||
const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode);
|
||||
|
||||
virtual bool rootElementHasData() const { return true; }
|
||||
virtual void update();
|
||||
|
||||
void storeModel(const ModelItem& model, const SharedNodePointer& senderNode = SharedNodePointer());
|
||||
|
|
|
@ -84,14 +84,17 @@ bool ModelTreeElement::appendElementData(OctreePacketData* packetData, EncodeBit
|
|||
}
|
||||
|
||||
bool ModelTreeElement::containsModelBounds(const ModelItem& model) const {
|
||||
return _box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint());
|
||||
glm::vec3 clampedMin = glm::clamp(model.getMinimumPoint(), 0.0f, 1.0f);
|
||||
glm::vec3 clampedMax = glm::clamp(model.getMaximumPoint(), 0.0f, 1.0f);
|
||||
return _box.contains(clampedMin) && _box.contains(clampedMax);
|
||||
}
|
||||
|
||||
bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const {
|
||||
if (_box.contains(model.getMinimumPoint()) && _box.contains(model.getMaximumPoint())) {
|
||||
int childForMinimumPoint = getMyChildContainingPoint(model.getMinimumPoint());
|
||||
int childForMaximumPoint = getMyChildContainingPoint(model.getMaximumPoint());
|
||||
|
||||
glm::vec3 clampedMin = glm::clamp(model.getMinimumPoint(), 0.0f, 1.0f);
|
||||
glm::vec3 clampedMax = glm::clamp(model.getMaximumPoint(), 0.0f, 1.0f);
|
||||
if (_box.contains(clampedMin) && _box.contains(clampedMax)) {
|
||||
int childForMinimumPoint = getMyChildContainingPoint(clampedMin);
|
||||
int childForMaximumPoint = getMyChildContainingPoint(clampedMax);
|
||||
// If I contain both the minimum and maximum point, but two different children of mine
|
||||
// contain those points, then I am the best fit for that model
|
||||
if (childForMinimumPoint != childForMaximumPoint) {
|
||||
|
@ -102,10 +105,16 @@ bool ModelTreeElement::bestFitModelBounds(const ModelItem& model) const {
|
|||
}
|
||||
|
||||
void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
|
||||
args._totalElements++;
|
||||
// update our contained models
|
||||
QList<ModelItem>::iterator modelItr = _modelItems->begin();
|
||||
while(modelItr != _modelItems->end()) {
|
||||
ModelItem& model = (*modelItr);
|
||||
args._totalItems++;
|
||||
|
||||
// TODO: this _lastChanged isn't actually changing because we're not marking this element as changed.
|
||||
// how do we want to handle this??? We really only want to consider an element changed when it is
|
||||
// edited... not just animated...
|
||||
model.update(_lastChanged);
|
||||
|
||||
// If the model wants to die, or if it's left our bounding box, then move it
|
||||
|
@ -115,6 +124,8 @@ void ModelTreeElement::update(ModelTreeUpdateArgs& args) {
|
|||
|
||||
// erase this model
|
||||
modelItr = _modelItems->erase(modelItr);
|
||||
|
||||
args._movingItems++;
|
||||
|
||||
// this element has changed so mark it...
|
||||
markWithChangedTime();
|
||||
|
@ -313,6 +324,14 @@ bool ModelTreeElement::removeModelWithID(uint32_t id) {
|
|||
int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int bytesLeftToRead,
|
||||
ReadBitstreamToTreeParams& args) {
|
||||
|
||||
// If we're the root, but this bitstream doesn't support root elements with data, then
|
||||
// return without reading any bytes
|
||||
if (this == _myTree->getRoot() && args.bitstreamVersion < VERSION_ROOT_ELEMENT_HAS_DATA) {
|
||||
qDebug() << "ROOT ELEMENT: no root data for "
|
||||
"bitstreamVersion=" << (int)args.bitstreamVersion << " bytesLeftToRead=" << bytesLeftToRead;
|
||||
return 0;
|
||||
}
|
||||
|
||||
const unsigned char* dataAt = data;
|
||||
int bytesRead = 0;
|
||||
uint16_t numberOfModels = 0;
|
||||
|
@ -324,7 +343,7 @@ int ModelTreeElement::readElementDataFromBuffer(const unsigned char* data, int b
|
|||
dataAt += sizeof(numberOfModels);
|
||||
bytesLeftToRead -= (int)sizeof(numberOfModels);
|
||||
bytesRead += sizeof(numberOfModels);
|
||||
|
||||
|
||||
if (bytesLeftToRead >= (int)(numberOfModels * expectedBytesPerModel)) {
|
||||
for (uint16_t i = 0; i < numberOfModels; i++) {
|
||||
ModelItem tempModel;
|
||||
|
|
|
@ -23,7 +23,16 @@ class ModelTreeElement;
|
|||
|
||||
class ModelTreeUpdateArgs {
|
||||
public:
|
||||
ModelTreeUpdateArgs() :
|
||||
_totalElements(0),
|
||||
_totalItems(0),
|
||||
_movingItems(0)
|
||||
{ }
|
||||
|
||||
QList<ModelItem> _movingModels;
|
||||
int _totalElements;
|
||||
int _totalItems;
|
||||
int _movingItems;
|
||||
};
|
||||
|
||||
class FindAndUpdateModelItemIDArgs {
|
||||
|
@ -63,7 +72,11 @@ public:
|
|||
|
||||
/// Should this element be considered to have content in it. This will be used in collision and ray casting methods.
|
||||
/// By default we assume that only leaves are actual content, but some octrees may have different semantics.
|
||||
virtual bool hasContent() const { return isLeaf(); }
|
||||
virtual bool hasContent() const { return hasModels(); }
|
||||
|
||||
/// Should this element be considered to have detailed content in it. Specifically should it be rendered.
|
||||
/// By default we assume that only leaves have detailed content, but some octrees may have different semantics.
|
||||
virtual bool hasDetailedContent() const { return hasModels(); }
|
||||
|
||||
/// Override this to break up large octree elements when an edit operation is performed on a smaller octree element.
|
||||
/// For example, if the octrees represent solid cubes and a delete of a smaller octree element is done then the
|
||||
|
@ -92,7 +105,7 @@ public:
|
|||
|
||||
const QList<ModelItem>& getModels() const { return *_modelItems; }
|
||||
QList<ModelItem>& getModels() { return *_modelItems; }
|
||||
bool hasModels() const { return _modelItems->size() > 0; }
|
||||
bool hasModels() const { return _modelItems ? _modelItems->size() > 0 : false; }
|
||||
|
||||
void update(ModelTreeUpdateArgs& args);
|
||||
void setTree(ModelTree* tree) { _myTree = tree; }
|
||||
|
|
|
@ -11,7 +11,6 @@ set(TARGET_NAME networking)
|
|||
project(${TARGET_NAME})
|
||||
|
||||
find_package(Qt5 COMPONENTS Network)
|
||||
find_package(GnuTLS REQUIRED)
|
||||
|
||||
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
|
||||
setup_hifi_library(${TARGET_NAME})
|
||||
|
@ -20,10 +19,9 @@ setup_hifi_library(${TARGET_NAME})
|
|||
include(${MACRO_DIR}/IncludeGLM.cmake)
|
||||
include_glm(${TARGET_NAME} "${ROOT_DIR}")
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail on gnutls.h
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network)
|
||||
|
||||
# add a definition for ssize_t so that windows doesn't bail
|
||||
if (WIN32)
|
||||
add_definitions(-Dssize_t=long)
|
||||
endif ()
|
||||
|
||||
include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}")
|
||||
target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}")
|
||||
endif ()
|
|
@ -136,7 +136,13 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
|
|||
QNetworkRequest authenticatedRequest;
|
||||
|
||||
QUrl requestURL = _authURL;
|
||||
requestURL.setPath(path);
|
||||
|
||||
if (path.startsWith("/")) {
|
||||
requestURL.setPath(path);
|
||||
} else {
|
||||
requestURL.setPath("/" + path);
|
||||
}
|
||||
|
||||
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
||||
|
||||
authenticatedRequest.setUrl(requestURL);
|
||||
|
|
|
@ -63,7 +63,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
|
|||
_pool(pool),
|
||||
_location(location),
|
||||
_payload(),
|
||||
_isStatic(false)
|
||||
_isStatic(false),
|
||||
_walletUUID()
|
||||
{
|
||||
if (_command == Assignment::CreateCommand) {
|
||||
// this is a newly created assignment, generate a random UUID
|
||||
|
@ -74,7 +75,8 @@ Assignment::Assignment(Assignment::Command command, Assignment::Type type, const
|
|||
Assignment::Assignment(const QByteArray& packet) :
|
||||
_pool(),
|
||||
_location(GlobalLocation),
|
||||
_payload()
|
||||
_payload(),
|
||||
_walletUUID()
|
||||
{
|
||||
PacketType packetType = packetTypeForPacket(packet);
|
||||
|
||||
|
@ -104,6 +106,7 @@ Assignment::Assignment(const Assignment& otherAssignment) {
|
|||
_location = otherAssignment._location;
|
||||
_pool = otherAssignment._pool;
|
||||
_payload = otherAssignment._payload;
|
||||
_walletUUID = otherAssignment._walletUUID;
|
||||
}
|
||||
|
||||
Assignment& Assignment::operator=(const Assignment& rhsAssignment) {
|
||||
|
@ -121,6 +124,7 @@ void Assignment::swap(Assignment& otherAssignment) {
|
|||
swap(_location, otherAssignment._location);
|
||||
swap(_pool, otherAssignment._pool);
|
||||
swap(_payload, otherAssignment._payload);
|
||||
swap(_walletUUID, otherAssignment._walletUUID);
|
||||
}
|
||||
|
||||
const char* Assignment::getTypeName() const {
|
||||
|
@ -156,6 +160,10 @@ QDebug operator<<(QDebug debug, const Assignment &assignment) {
|
|||
QDataStream& operator<<(QDataStream &out, const Assignment& assignment) {
|
||||
out << (quint8) assignment._type << assignment._uuid << assignment._pool << assignment._payload;
|
||||
|
||||
if (assignment._command == Assignment::RequestCommand) {
|
||||
out << assignment._walletUUID;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
@ -166,5 +174,9 @@ QDataStream& operator>>(QDataStream &in, Assignment& assignment) {
|
|||
|
||||
in >> assignment._uuid >> assignment._pool >> assignment._payload;
|
||||
|
||||
if (assignment._command == Assignment::RequestCommand) {
|
||||
in >> assignment._walletUUID;
|
||||
}
|
||||
|
||||
return in;
|
||||
}
|
||||
|
|
|
@ -81,6 +81,9 @@ public:
|
|||
void setIsStatic(bool isStatic) { _isStatic = isStatic; }
|
||||
bool isStatic() const { return _isStatic; }
|
||||
|
||||
void setWalletUUID(const QUuid& walletUUID) { _walletUUID = walletUUID; }
|
||||
const QUuid& getWalletUUID() const { return _walletUUID; }
|
||||
|
||||
const char* getTypeName() const;
|
||||
|
||||
// implement parseData to return 0 so we can be a subclass of NodeData
|
||||
|
@ -98,6 +101,7 @@ protected:
|
|||
Assignment::Location _location; /// the location of the assignment, allows a domain to preferentially use local ACs
|
||||
QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed
|
||||
bool _isStatic; /// defines if this assignment needs to be re-queued in the domain-server if it stops being fulfilled
|
||||
QUuid _walletUUID; /// the UUID for the wallet that should be paid for this assignment
|
||||
};
|
||||
|
||||
#endif // hifi_Assignment_h
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
//
|
||||
// DTLSClientSession.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-01.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include "DomainHandler.h"
|
||||
|
||||
#include "DTLSClientSession.h"
|
||||
|
||||
gnutls_certificate_credentials_t DTLSClientSession::_x509CACredentials;
|
||||
|
||||
void DTLSClientSession::globalInit() {
|
||||
static bool initialized = false;
|
||||
|
||||
if (!initialized) {
|
||||
gnutls_global_init();
|
||||
gnutls_certificate_allocate_credentials(&_x509CACredentials);
|
||||
|
||||
gnutls_certificate_set_x509_trust_mem(_x509CACredentials, DTLSSession::highFidelityCADatum(), GNUTLS_X509_FMT_PEM);
|
||||
gnutls_certificate_set_verify_function(_x509CACredentials, DTLSClientSession::verifyServerCertificate);
|
||||
}
|
||||
}
|
||||
|
||||
void DTLSClientSession::globalDeinit() {
|
||||
gnutls_certificate_free_credentials(_x509CACredentials);
|
||||
|
||||
gnutls_global_deinit();
|
||||
}
|
||||
|
||||
int DTLSClientSession::verifyServerCertificate(gnutls_session_t session) {
|
||||
unsigned int verifyStatus = 0;
|
||||
|
||||
// grab the hostname from the domain handler that this session is associated with
|
||||
DomainHandler* domainHandler = reinterpret_cast<DomainHandler*>(gnutls_session_get_ptr(session));
|
||||
qDebug() << "Checking for" << domainHandler->getHostname() << "from cert.";
|
||||
|
||||
int certReturn = gnutls_certificate_verify_peers3(session,
|
||||
domainHandler->getHostname().toLocal8Bit().constData(),
|
||||
&verifyStatus);
|
||||
|
||||
if (certReturn < 0) {
|
||||
return GNUTLS_E_CERTIFICATE_ERROR;
|
||||
}
|
||||
|
||||
gnutls_certificate_type_t typeReturn = gnutls_certificate_type_get(session);
|
||||
|
||||
gnutls_datum_t printOut;
|
||||
|
||||
certReturn = gnutls_certificate_verification_status_print(verifyStatus, typeReturn, &printOut, 0);
|
||||
|
||||
if (certReturn < 0) {
|
||||
return GNUTLS_E_CERTIFICATE_ERROR;
|
||||
}
|
||||
|
||||
qDebug() << "Gnutls certificate verification status:" << reinterpret_cast<char *>(printOut.data);
|
||||
|
||||
#ifdef WIN32
|
||||
free(printOut.data);
|
||||
#else
|
||||
gnutls_free(printOut.data);
|
||||
#endif
|
||||
|
||||
if (verifyStatus != 0) {
|
||||
qDebug() << "Server provided certificate for DTLS is not trusted. Can not complete handshake.";
|
||||
return GNUTLS_E_CERTIFICATE_ERROR;
|
||||
} else {
|
||||
// certificate is valid, continue handshaking as normal
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
DTLSClientSession::DTLSClientSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) :
|
||||
DTLSSession(GNUTLS_CLIENT, dtlsSocket, destinationSocket)
|
||||
{
|
||||
gnutls_priority_set_direct(_gnutlsSession, "PERFORMANCE", NULL);
|
||||
gnutls_credentials_set(_gnutlsSession, GNUTLS_CRD_CERTIFICATE, _x509CACredentials);
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
//
|
||||
// DTLSClientSession.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-01.
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_DTLSClientSession_h
|
||||
#define hifi_DTLSClientSession_h
|
||||
|
||||
#include "DTLSSession.h"
|
||||
|
||||
class DTLSClientSession : public DTLSSession {
|
||||
public:
|
||||
DTLSClientSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket);
|
||||
|
||||
static void globalInit();
|
||||
static void globalDeinit();
|
||||
|
||||
static int verifyServerCertificate(gnutls_session_t session);
|
||||
|
||||
static gnutls_certificate_credentials_t _x509CACredentials;
|
||||
static bool _wasGloballyInitialized;
|
||||
};
|
||||
|
||||
#endif // hifi_DTLSClientSession_h
|
|
@ -1,145 +0,0 @@
|
|||
//
|
||||
// DTLSSession.cpp
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by Stephen Birarda on 2014-04-01.
|
||||
// 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
|
||||
//
|
||||
|
||||
#include <gnutls/dtls.h>
|
||||
|
||||
#include "NodeList.h"
|
||||
#include "DTLSSession.h"
|
||||
|
||||
int DTLSSession::socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms) {
|
||||
DTLSSession* session = static_cast<DTLSSession*>(ptr);
|
||||
QUdpSocket& dtlsSocket = session->_dtlsSocket;
|
||||
|
||||
if (dtlsSocket.hasPendingDatagrams()) {
|
||||
// peek the data on stack to see if it belongs to this session
|
||||
static sockaddr senderSockAddr;
|
||||
static socklen_t sockAddrSize = sizeof(senderSockAddr);
|
||||
|
||||
QByteArray peekDatagram(dtlsSocket.pendingDatagramSize(), 0);
|
||||
|
||||
recvfrom(dtlsSocket.socketDescriptor(), peekDatagram.data(), dtlsSocket.pendingDatagramSize(),
|
||||
MSG_PEEK, &senderSockAddr, &sockAddrSize);
|
||||
|
||||
if (HifiSockAddr(&senderSockAddr) == session->_destinationSocket) {
|
||||
// there is data for this session ready to be read
|
||||
return 1;
|
||||
} else {
|
||||
// the next data from the dtlsSocket is not for this session
|
||||
return 0;
|
||||
}
|
||||
} else {
|
||||
// no data available on the dtlsSocket
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
ssize_t DTLSSession::socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size) {
|
||||
DTLSSession* session = static_cast<DTLSSession*>(ptr);
|
||||
QUdpSocket& dtlsSocket = session->_dtlsSocket;
|
||||
|
||||
HifiSockAddr pulledSockAddr;
|
||||
qint64 bytesReceived = dtlsSocket.readDatagram(reinterpret_cast<char*>(buffer), size,
|
||||
pulledSockAddr.getAddressPointer(), pulledSockAddr.getPortPointer());
|
||||
if (bytesReceived == -1) {
|
||||
// no data to pull, return -1
|
||||
#if DTLS_VERBOSE_DEBUG
|
||||
qDebug() << "Received no data on call to readDatagram";
|
||||
#endif
|
||||
return bytesReceived;
|
||||
}
|
||||
|
||||
if (pulledSockAddr == session->_destinationSocket) {
|
||||
// bytes received from the correct sender, return number of bytes received
|
||||
|
||||
#if DTLS_VERBOSE_DEBUG
|
||||
qDebug() << "Received" << bytesReceived << "on DTLS socket from" << pulledSockAddr;
|
||||
#endif
|
||||
|
||||
return bytesReceived;
|
||||
}
|
||||
|
||||
// we pulled a packet not matching this session, so output that
|
||||
qDebug() << "Denied connection from" << pulledSockAddr;
|
||||
|
||||
gnutls_transport_set_errno(session->_gnutlsSession, GNUTLS_E_AGAIN);
|
||||
return -1;
|
||||
}
|
||||
|
||||
gnutls_datum_t* DTLSSession::highFidelityCADatum() {
|
||||
static gnutls_datum_t hifiCADatum;
|
||||
static bool datumInitialized = false;
|
||||
|
||||
static unsigned char HIGHFIDELITY_ROOT_CA_CERT[] =
|
||||
"-----BEGIN CERTIFICATE-----\n"
|
||||
"MIID6TCCA1KgAwIBAgIJANlfRkRD9A8bMA0GCSqGSIb3DQEBBQUAMIGqMQswCQYD\n"
|
||||
"VQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5j\n"
|
||||
"aXNjbzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVy\n"
|
||||
"YXRpb25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEW\n"
|
||||
"E29wc0BoaWdoZmlkZWxpdHkuaW8wHhcNMTQwMzI4MjIzMzM1WhcNMjQwMzI1MjIz\n"
|
||||
"MzM1WjCBqjELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNV\n"
|
||||
"BAcTDVNhbiBGcmFuY2lzY28xGzAZBgNVBAoTEkhpZ2ggRmlkZWxpdHksIEluYzET\n"
|
||||
"MBEGA1UECxMKT3BlcmF0aW9uczEYMBYGA1UEAxMPaGlnaGZpZGVsaXR5LmlvMSIw\n"
|
||||
"IAYJKoZIhvcNAQkBFhNvcHNAaGlnaGZpZGVsaXR5LmlvMIGfMA0GCSqGSIb3DQEB\n"
|
||||
"AQUAA4GNADCBiQKBgQDyo1euYiPPEdnvDZnIjWrrP230qUKMSj8SWoIkbTJF2hE8\n"
|
||||
"2eP3YOgbgSGBzZ8EJBxIOuNmj9g9Eg6691hIKFqy5W0BXO38P04Gg+pVBvpHFGBi\n"
|
||||
"wpqGbfsjaUDuYmBeJRcMO0XYkLCRQG+lAQNHoFDdItWAJfC3FwtP3OCDnz8cNwID\n"
|
||||
"AQABo4IBEzCCAQ8wHQYDVR0OBBYEFCSv2kmiGg6VFMnxXzLDNP304cPAMIHfBgNV\n"
|
||||
"HSMEgdcwgdSAFCSv2kmiGg6VFMnxXzLDNP304cPAoYGwpIGtMIGqMQswCQYDVQQG\n"
|
||||
"EwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNj\n"
|
||||
"bzEbMBkGA1UEChMSSGlnaCBGaWRlbGl0eSwgSW5jMRMwEQYDVQQLEwpPcGVyYXRp\n"
|
||||
"b25zMRgwFgYDVQQDEw9oaWdoZmlkZWxpdHkuaW8xIjAgBgkqhkiG9w0BCQEWE29w\n"
|
||||
"c0BoaWdoZmlkZWxpdHkuaW+CCQDZX0ZEQ/QPGzAMBgNVHRMEBTADAQH/MA0GCSqG\n"
|
||||
"SIb3DQEBBQUAA4GBAEkQl3p+lH5vuoCNgyfa67nL0MsBEt+5RSBOgjwCjjASjzou\n"
|
||||
"FTv5w0he2OypgMQb8i/BYtS1lJSFqjPJcSM1Salzrm3xDOK5pOXJ7h6SQLPDVEyf\n"
|
||||
"Hy2/9d/to+99+SOUlvfzfgycgjOc+s/AV7Y+GBd7uzGxUdrN4egCZW1F6/mH\n"
|
||||
"-----END CERTIFICATE-----\n";
|
||||
|
||||
if (!datumInitialized) {
|
||||
hifiCADatum.data = HIGHFIDELITY_ROOT_CA_CERT;
|
||||
hifiCADatum.size = sizeof(HIGHFIDELITY_ROOT_CA_CERT);
|
||||
}
|
||||
|
||||
return &hifiCADatum;
|
||||
}
|
||||
|
||||
DTLSSession::DTLSSession(int end, QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket) :
|
||||
DummyDTLSSession(dtlsSocket, destinationSocket),
|
||||
_completedHandshake(false)
|
||||
{
|
||||
gnutls_init(&_gnutlsSession, end | GNUTLS_DATAGRAM | GNUTLS_NONBLOCK);
|
||||
|
||||
// see http://gnutls.org/manual/html_node/Datagram-TLS-API.html#gnutls_005fdtls_005fset_005fmtu
|
||||
const unsigned int DTLS_MAX_MTU = 1452;
|
||||
gnutls_dtls_set_mtu(_gnutlsSession, DTLS_MAX_MTU);
|
||||
|
||||
const unsigned int DTLS_HANDSHAKE_RETRANSMISSION_TIMEOUT = DOMAIN_SERVER_CHECK_IN_MSECS;
|
||||
const unsigned int DTLS_TOTAL_CONNECTION_TIMEOUT = 2 * NODE_SILENCE_THRESHOLD_MSECS;
|
||||
gnutls_dtls_set_timeouts(_gnutlsSession, DTLS_HANDSHAKE_RETRANSMISSION_TIMEOUT, DTLS_TOTAL_CONNECTION_TIMEOUT);
|
||||
|
||||
gnutls_transport_set_ptr(_gnutlsSession, this);
|
||||
gnutls_transport_set_push_function(_gnutlsSession, DummyDTLSSession::socketPush);
|
||||
gnutls_transport_set_pull_function(_gnutlsSession, socketPull);
|
||||
gnutls_transport_set_pull_timeout_function(_gnutlsSession, socketPullTimeout);
|
||||
}
|
||||
|
||||
DTLSSession::~DTLSSession() {
|
||||
gnutls_deinit(_gnutlsSession);
|
||||
}
|
||||
|
||||
void DTLSSession::setCompletedHandshake(bool completedHandshake) {
|
||||
_completedHandshake = completedHandshake;
|
||||
qDebug() << "Completed DTLS handshake with" << _destinationSocket;
|
||||
}
|
||||
|
||||
qint64 DTLSSession::writeDatagram(const QByteArray& datagram) {
|
||||
// we don't need to put a hash in this packet, so just send it off
|
||||
return gnutls_record_send(_gnutlsSession, datagram.data(), datagram.size());
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue