diff --git a/.gitignore b/.gitignore index eb2f3ec303..c296a918af 100644 --- a/.gitignore +++ b/.gitignore @@ -42,5 +42,9 @@ interface/external/visage/* interface/resources/visage/* !interface/resources/visage/tracker.cfg +# Ignore Faceplus +interface/external/faceplus/* +!interface/external/faceplus/readme.txt + # Ignore interfaceCache for Linux users interface/interfaceCache/ diff --git a/BUILD.md b/BUILD.md index 6464f14d08..487bcb0eb1 100644 --- a/BUILD.md +++ b/BUILD.md @@ -1,10 +1,12 @@ Dependencies === -* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 2.8.11 +* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 2.8.12.2 * [Qt](http://qt-project.org/downloads) ~> 5.2.0 * [zLib](http://www.zlib.net/) ~> 1.2.8 * [glm](http://glm.g-truc.net/0.9.5/index.html) ~> 0.9.5.2 * [qxmpp](https://code.google.com/p/qxmpp/) ~> 0.7.6 +* [GnuTLS](http://gnutls.org/download.html) ~> 3.2.12 + * IMPORTANT: GnuTLS 3.2.12 is critical to avoid a security vulnerability. #####Linux only * [freeglut](http://freeglut.sourceforge.net/) ~> 2.8.0 @@ -50,12 +52,18 @@ Should you choose not to install Qt5 via a package manager that handles dependen libasound2 libxmu-dev libxi-dev freeglut3-dev libasound2-dev libjack-dev +#####GnuTLS + +If `libgnutls28-dev` 3.2.12 or higher is available via your package manager, it would be easiest to grab it from there. At the time of this writing that is not the case for any version of Ubuntu, so it will need to be built from source. + +`gmplib` is a dependency for GnuTLS. On Ubuntu, we were unable to build `hogweed` (part of `libnettle`) with `gmpib` 6.x.x. If nettle is not built with `hogweed`, GnuTLS will fail to build. If you run into this problem, try version 4.2.1 of `gmplib`. + ####OS X #####Package Managers [Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all hifi dependencies very simple. brew tap highfidelity/homebrew-formulas - brew install cmake glm zlib + brew install cmake glm zlib gnutls brew install highfidelity/formulas/qt5 brew link qt5 --force brew install highfidelity/formulas/qxmpp @@ -71,7 +79,7 @@ If Xcode is your editor of choice, you can ask CMake to generate Xcode project f After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run. -If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug directories`. +If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug` directories. Windows === @@ -100,11 +108,12 @@ Once Qt is installed, you need to manually configure the following: NOTE: zLib should configure itself correctly on install. However, sometimes zLib doesn't properly detect system components and fails to configure itself correctly. When it fails, it will not correctly set the #if HAVE_UNISTD_H at line 287 of zconf.h to #if 0... if it fails, you're build will have errors in the voxels target. You can correct this by setting the #if to 0 instead of 1, since Windows does not have unistd.h. ####External Libraries -We don't currently have a Windows installer, so before running Interface, you will need to ensure that all required resources are loadable. -CMake will need to know where the headers and libraries for required external dependencies are. If you installed ZLIB using the installer, the FindZLIB cmake module will be able to find it. This isn't the case for the others. +CMake will need to know where the headers and libraries for required external dependencies are. -The recommended route for CMake to find external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure: +If you installed zLib using the installer, the Cmake find module for zLib should locate it on your system. + +The recommended route for CMake to find the other external dependencies is to place all of the dependencies in one folder and set one ENV variable - HIFI_LIB_DIR. That ENV variable should point to a directory with the following structure: root_lib_dir -> glm @@ -121,15 +130,30 @@ The recommended route for CMake to find external dependencies is to place all of -> qxmpp -> include -> lib - -*NOTE: Be careful with glm. For the folder other libraries would normally call 'include', the folder containing the headers, glm opts to use 'glm'. You will have a glm folder nested inside the top-level glm folder.* + -> gnutls + -> bin + -> include + -> lib For many of the external libraries where precompiled binaries are readily available you should be able to simply copy the extracted folder that you get from the download links provided at the top of the guide. Otherwise you may need to build from source and install the built product to this directory. The `root_lib_dir` in the above example can be wherever you choose on your system - as long as the environment variable HIFI_LIB_DIR is set to it. +*NOTE: Be careful with glm. For the folder other libraries would normally call 'include', the folder containing the headers, glm opts to use 'glm'. You will have a glm folder nested inside the top-level glm folder.* + *NOTE: Qt does not support 64-bit builds on Windows 7, so you must use the 32-bit version of libraries for interface.exe to run. The 32-bit version of the static library is the one linked by our CMake find modules* -#### DLLs -As with the Qt libraries, you will need to make sure the directory containing dynamically-linked libraries is in your path. For example, for a dynamically linked build of freeglut, the directory to add to your path in which the DLL is found is `FREEGLUT_DIR/bin`. Where possible, you can use static builds of the external dependencies to avoid this requirement. +##### DLLs +As with the Qt libraries, you will need to make sure the directories containing dynamically-linked libraries is in your path. + +For example, for a dynamically linked build of freeglut, the directory to add to your path in which the DLL is found is `FREEGLUT_DIR/bin`. Where possible, you can use static builds of the external dependencies to avoid this requirement. + +#####GnuTLS +You can get a precompiled version of GnuTLS for Windows [here](http://gnutls.org/download.html). + +To use GnuTLS with Visual Studio, you will need to create `libgnutls-28.lib`, the import library for Visual Studio projects. this is done using the `lib` command in the `bin` folder of your GnuTLS download. Run the following in a Visual Studio Command Prompt (found in the tools menu of Visual Studio). + + $GNUTLS_DIR\bin> lib /def:libgnutls-28.def + +This will create `libgnutls-28.lib` in the `bin` folder. Copy that file to the `lib` sub-folder of your GnuTLS folder, and the Cmake FindGnuTLS module in this repo will find it during the Cmake run. ####Building in Visual Studio Follow the same build steps from the CMake section, but pass a different generator to CMake. diff --git a/LICENSE b/LICENSE index 1ecd0026f5..93c9a953d4 100644 --- a/LICENSE +++ b/LICENSE @@ -1 +1,13 @@ -All rights reserved. © High Fidelity, Inc. 2013 \ No newline at end of file +Copyright 2014 High Fidelity, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. \ No newline at end of file diff --git a/animation-server/CMakeLists.txt b/animation-server/CMakeLists.txt index 7d3222327a..5d9f3604bc 100644 --- a/animation-server/CMakeLists.txt +++ b/animation-server/CMakeLists.txt @@ -29,4 +29,18 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library 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 +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +# link GnuTLS +target_link_libraries(${TARGET_NAME} "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/animation-server/src/AnimationServer.cpp b/animation-server/src/AnimationServer.cpp index 1f0828ec17..32a95b48ea 100644 --- a/animation-server/src/AnimationServer.cpp +++ b/animation-server/src/AnimationServer.cpp @@ -1,9 +1,12 @@ // // AnimationServer.cpp -// hifi +// animation-server/src // // Created by Stephen Birarda on 12/5/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -168,7 +171,7 @@ static void renderMovingBug() { } } else { - //printf("bugPathCenter=(%f,%f,%f)\n", bugPathCenter.x, bugPathCenter.y, bugPathCenter.z); + //qDebug("bugPathCenter=(%f,%f,%f)", bugPathCenter.x, bugPathCenter.y, bugPathCenter.z); bugPathTheta += bugAngleDelta; // move slightly bugRotation -= bugAngleDelta; // rotate slightly @@ -184,12 +187,12 @@ static void renderMovingBug() { float y = bugPathCenter.y; bugPosition = glm::vec3(x, y, z); - //printf("bugPathTheta=%f\n", bugPathTheta); - //printf("bugRotation=%f\n", bugRotation); + //qDebug("bugPathTheta=%f", bugPathTheta); + //qDebug("bugRotation=%f", bugRotation); } - //printf("bugPosition=(%f,%f,%f)\n", bugPosition.x, bugPosition.y, bugPosition.z); - //printf("bugDirection=(%f,%f,%f)\n", bugDirection.x, bugDirection.y, bugDirection.z); + //qDebug("bugPosition=(%f,%f,%f)", bugPosition.x, bugPosition.y, bugPosition.z); + //qDebug("bugDirection=(%f,%f,%f)", bugDirection.x, bugDirection.y, bugDirection.z); // would be nice to add some randomness here... // Generate voxels for where bug is going to @@ -687,32 +690,32 @@ AnimationServer::AnimationServer(int &argc, char **argv) : // Handle Local Domain testing with the --local command line const char* NON_THREADED_PACKETSENDER = "--NonThreadedPacketSender"; ::nonThreadedPacketSender = cmdOptionExists(argc, (const char**) argv, NON_THREADED_PACKETSENDER); - printf("nonThreadedPacketSender=%s\n", debug::valueOf(::nonThreadedPacketSender)); + qDebug("nonThreadedPacketSender=%s", debug::valueOf(::nonThreadedPacketSender)); // Handle Local Domain testing with the --local command line const char* NO_BILLBOARD = "--NoBillboard"; ::includeBillboard = !cmdOptionExists(argc, (const char**) argv, NO_BILLBOARD); - printf("includeBillboard=%s\n", debug::valueOf(::includeBillboard)); + qDebug("includeBillboard=%s", debug::valueOf(::includeBillboard)); const char* NO_BORDER_TRACER = "--NoBorderTracer"; ::includeBorderTracer = !cmdOptionExists(argc, (const char**) argv, NO_BORDER_TRACER); - printf("includeBorderTracer=%s\n", debug::valueOf(::includeBorderTracer)); + qDebug("includeBorderTracer=%s", debug::valueOf(::includeBorderTracer)); const char* NO_MOVING_BUG = "--NoMovingBug"; ::includeMovingBug = !cmdOptionExists(argc, (const char**) argv, NO_MOVING_BUG); - printf("includeMovingBug=%s\n", debug::valueOf(::includeMovingBug)); + qDebug("includeMovingBug=%s", debug::valueOf(::includeMovingBug)); const char* INCLUDE_BLINKING_VOXEL = "--includeBlinkingVoxel"; ::includeBlinkingVoxel = cmdOptionExists(argc, (const char**) argv, INCLUDE_BLINKING_VOXEL); - printf("includeBlinkingVoxel=%s\n", debug::valueOf(::includeBlinkingVoxel)); + qDebug("includeBlinkingVoxel=%s", debug::valueOf(::includeBlinkingVoxel)); const char* NO_DANCE_FLOOR = "--NoDanceFloor"; ::includeDanceFloor = !cmdOptionExists(argc, (const char**) argv, NO_DANCE_FLOOR); - printf("includeDanceFloor=%s\n", debug::valueOf(::includeDanceFloor)); + qDebug("includeDanceFloor=%s", debug::valueOf(::includeDanceFloor)); const char* BUILD_STREET = "--BuildStreet"; ::buildStreet = cmdOptionExists(argc, (const char**) argv, BUILD_STREET); - printf("buildStreet=%s\n", debug::valueOf(::buildStreet)); + qDebug("buildStreet=%s", debug::valueOf(::buildStreet)); // Handle Local Domain testing with the --local command line const char* showPPS = "--showPPS"; @@ -722,20 +725,20 @@ AnimationServer::AnimationServer(int &argc, char **argv) : const char* local = "--local"; ::wantLocalDomain = cmdOptionExists(argc, (const char**) argv,local); if (::wantLocalDomain) { - printf("Local Domain MODE!\n"); - nodeList->getDomainInfo().setIPToLocalhost(); + qDebug("Local Domain MODE!"); + nodeList->getDomainHandler().setIPToLocalhost(); } const char* domainHostname = getCmdOption(argc, (const char**) argv, "--domain"); if (domainHostname) { - NodeList::getInstance()->getDomainInfo().setHostname(domainHostname); + NodeList::getInstance()->getDomainHandler().setHostname(domainHostname); } const char* packetsPerSecondCommand = getCmdOption(argc, (const char**) argv, "--pps"); if (packetsPerSecondCommand) { ::packetsPerSecond = atoi(packetsPerSecondCommand); } - printf("packetsPerSecond=%d\n",packetsPerSecond); + qDebug("packetsPerSecond=%d",packetsPerSecond); const char* animateFPSCommand = getCmdOption(argc, (const char**) argv, "--AnimateFPS"); const char* animateIntervalCommand = getCmdOption(argc, (const char**) argv, "--AnimateInterval"); @@ -750,8 +753,8 @@ AnimationServer::AnimationServer(int &argc, char **argv) : ::ANIMATE_VOXELS_INTERVAL_USECS = (ANIMATE_FPS_IN_MILLISECONDS * 1000.0); // converts from milliseconds to usecs } } - printf("ANIMATE_FPS=%d\n",ANIMATE_FPS); - printf("ANIMATE_VOXELS_INTERVAL_USECS=%llu\n",ANIMATE_VOXELS_INTERVAL_USECS); + qDebug("ANIMATE_FPS=%d",ANIMATE_FPS); + qDebug("ANIMATE_VOXELS_INTERVAL_USECS=%llu",ANIMATE_VOXELS_INTERVAL_USECS); const char* processingFPSCommand = getCmdOption(argc, (const char**) argv, "--ProcessingFPS"); const char* processingIntervalCommand = getCmdOption(argc, (const char**) argv, "--ProcessingInterval"); @@ -766,8 +769,8 @@ AnimationServer::AnimationServer(int &argc, char **argv) : ::PROCESSING_INTERVAL_USECS = (PROCESSING_FPS_IN_MILLISECONDS * 1000.0) - FUDGE_USECS; // converts from milliseconds to usecs } } - printf("PROCESSING_FPS=%d\n",PROCESSING_FPS); - printf("PROCESSING_INTERVAL_USECS=%llu\n",PROCESSING_INTERVAL_USECS); + qDebug("PROCESSING_FPS=%d",PROCESSING_FPS); + qDebug("PROCESSING_INTERVAL_USECS=%llu",PROCESSING_INTERVAL_USECS); nodeList->linkedDataCreateCallback = NULL; // do we need a callback? @@ -797,11 +800,11 @@ AnimationServer::AnimationServer(int &argc, char **argv) : QTimer* domainServerTimer = new QTimer(this); connect(domainServerTimer, SIGNAL(timeout()), nodeList, SLOT(sendDomainServerCheckIn())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); + domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readPendingDatagrams())); } diff --git a/animation-server/src/AnimationServer.h b/animation-server/src/AnimationServer.h index cda5f8fb24..58f05c32c5 100644 --- a/animation-server/src/AnimationServer.h +++ b/animation-server/src/AnimationServer.h @@ -1,13 +1,16 @@ // // AnimationServer.h -// hifi +// animation-server/src // // Created by Stephen Birarda on 12/5/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__AnimationServer__ -#define __hifi__AnimationServer__ +#ifndef hifi_AnimationServer_h +#define hifi_AnimationServer_h #include @@ -21,4 +24,4 @@ private slots: }; -#endif /* defined(__hifi__AnimationServer__) */ +#endif // hifi_AnimationServer_h diff --git a/animation-server/src/main.cpp b/animation-server/src/main.cpp index 3cdb7f04be..8acf3b8db2 100644 --- a/animation-server/src/main.cpp +++ b/animation-server/src/main.cpp @@ -1,9 +1,12 @@ // // main.cpp -// Animation Server +// animation-server/src // -// Created by Brad Hefta-Gaub on 05/16/2013 -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 05/16/2013. +// Copyright 2012 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 "AnimationServer.h" diff --git a/assignment-client/CMakeLists.txt b/assignment-client/CMakeLists.txt index 505708a899..b78d4f81f9 100644 --- a/assignment-client/CMakeLists.txt +++ b/assignment-client/CMakeLists.txt @@ -28,11 +28,22 @@ link_hifi_library(audio ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${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) @@ -41,4 +52,4 @@ IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script) +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}") diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index b5199e87e8..e39cb39307 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -1,15 +1,20 @@ // // Agent.cpp -// hifi +// assignment-client/src // // Created by Stephen Birarda on 7/1/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 #include +#include #include #include +#include #include #include @@ -17,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -27,7 +33,8 @@ Agent::Agent(const QByteArray& packet) : ThreadedAssignment(packet), _voxelEditSender(), _particleEditSender(), - _receivedAudioBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO) + _receivedAudioBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO), + _avatarHashMap() { // be the parent of the script engine so it gets moved when we do _scriptEngine.setParent(this); @@ -125,6 +132,16 @@ void Agent::readPendingDatagrams() { // let this continue through to the NodeList so it updates last heard timestamp // for the sending audio mixer NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); + } else if (datagramPacketType == PacketTypeBulkAvatarData + || datagramPacketType == PacketTypeAvatarIdentity + || datagramPacketType == PacketTypeAvatarBillboard + || datagramPacketType == PacketTypeKillAvatar) { + // let the avatar hash map process it + _avatarHashMap.processAvatarMixerDatagram(receivedPacket, nodeList->sendingNodeForPacket(receivedPacket)); + + // let this continue through to the NodeList so it updates last heard timestamp + // for the sending avatar-mixer + NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); } else { NodeList::getInstance()->processNodeData(senderSockAddr, receivedPacket); } @@ -145,22 +162,32 @@ void Agent::run() { << NodeType::ParticleServer); // figure out the URL for the script for this agent assignment - QString scriptURLString("http://%1:8080/assignment/%2"); - scriptURLString = scriptURLString.arg(NodeList::getInstance()->getDomainInfo().getIP().toString(), - uuidStringWithoutCurlyBraces(_uuid)); - + QUrl scriptURL; + if (_payload.isEmpty()) { + scriptURL = QUrl(QString("http://%1:8080/assignment/%2") + .arg(NodeList::getInstance()->getDomainHandler().getIP().toString(), + uuidStringWithoutCurlyBraces(_uuid))); + } else { + scriptURL = QUrl(_payload); + } + QNetworkAccessManager *networkManager = new QNetworkAccessManager(this); - QNetworkReply *reply = networkManager->get(QNetworkRequest(QUrl(scriptURLString))); + QNetworkReply *reply = networkManager->get(QNetworkRequest(scriptURL)); + QNetworkDiskCache* cache = new QNetworkDiskCache(networkManager); + QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation); + cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache"); + networkManager->setCache(cache); - qDebug() << "Downloading script at" << scriptURLString; + qDebug() << "Downloading script at" << scriptURL.toString(); QEventLoop loop; QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit())); loop.exec(); - // let the AvatarData class use our QNetworkAcessManager + // let the AvatarData and ResourceCache classes use our QNetworkAccessManager AvatarData::setNetworkAccessManager(networkManager); + ResourceCache::setNetworkAccessManager(networkManager); QString scriptContents(reply->readAll()); @@ -175,6 +202,7 @@ void Agent::run() { // give this AvatarData object to the script engine _scriptEngine.setAvatarData(&scriptedAvatar, "Avatar"); + _scriptEngine.setAvatarHashMap(&_avatarHashMap, "AvatarList"); // register ourselves to the script engine _scriptEngine.registerGlobalObject("Agent", this); diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index 9e5f31213c..9f6a8089cf 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -1,13 +1,16 @@ // // Agent.h -// hifi +// assignment-client/src // // Created by Stephen Birarda on 7/1/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__Agent__ -#define __hifi__Agent__ +#ifndef hifi_Agent_h +#define hifi_Agent_h #include @@ -15,6 +18,7 @@ #include #include +#include #include #include #include @@ -62,6 +66,7 @@ private: VoxelTreeHeadlessViewer _voxelViewer; MixedAudioRingBuffer _receivedAudioBuffer; + AvatarHashMap _avatarHashMap; }; -#endif /* defined(__hifi__Agent__) */ +#endif // hifi_Agent_h diff --git a/assignment-client/src/AssignmentClient.cpp b/assignment-client/src/AssignmentClient.cpp index aa20f2ff29..ff876596b0 100644 --- a/assignment-client/src/AssignmentClient.cpp +++ b/assignment-client/src/AssignmentClient.cpp @@ -1,15 +1,20 @@ // // AssignmentClient.cpp -// hifi +// assignment-client/src // // Created by Stephen Birarda on 11/25/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 #include #include +#include + #include #include #include @@ -17,6 +22,7 @@ #include #include + #include "AssignmentFactory.h" #include "AssignmentThread.h" @@ -25,68 +31,63 @@ const QString ASSIGNMENT_CLIENT_TARGET_NAME = "assignment-client"; const long long ASSIGNMENT_REQUEST_INTERVAL_MSECS = 1 * 1000; +SharedAssignmentPointer AssignmentClient::_currentAssignment; + int hifiSockAddrMeta = qRegisterMetaType("HifiSockAddr"); AssignmentClient::AssignmentClient(int &argc, char **argv) : QCoreApplication(argc, argv), - _currentAssignment() + _assignmentServerHostname(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME) { + DTLSClientSession::globalInit(); + setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("assignment-client"); QSettings::setDefaultFormat(QSettings::IniFormat); + QStringList argumentList = arguments(); + // register meta type is required for queued invoke method on Assignment subclasses // set the logging target to the the CHILD_TARGET_NAME Logging::setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME); - const char ASSIGNMENT_TYPE_OVVERIDE_OPTION[] = "-t"; - const char* assignmentTypeString = getCmdOption(argc, (const char**)argv, ASSIGNMENT_TYPE_OVVERIDE_OPTION); + const QString ASSIGNMENT_TYPE_OVVERIDE_OPTION = "-t"; + int argumentIndex = argumentList.indexOf(ASSIGNMENT_TYPE_OVVERIDE_OPTION); Assignment::Type requestAssignmentType = Assignment::AllTypes; - if (assignmentTypeString) { - // the user is asking to only be assigned to a particular type of assignment - // so set that as the ::overridenAssignmentType to be used in requests - requestAssignmentType = (Assignment::Type) atoi(assignmentTypeString); + if (argumentIndex != -1) { + requestAssignmentType = (Assignment::Type) argumentList[argumentIndex + 1].toInt(); } - const char ASSIGNMENT_POOL_OPTION[] = "--pool"; - const char* requestAssignmentPool = getCmdOption(argc, (const char**) argv, ASSIGNMENT_POOL_OPTION); + const QString ASSIGNMENT_POOL_OPTION = "--pool"; + argumentIndex = argumentList.indexOf(ASSIGNMENT_POOL_OPTION); + QString assignmentPool; + + if (argumentIndex != -1) { + assignmentPool = argumentList[argumentIndex + 1]; + } // setup our _requestAssignment member variable from the passed arguments - _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, requestAssignmentPool); + _requestAssignment = Assignment(Assignment::RequestCommand, requestAssignmentType, assignmentPool); // create a NodeList as an unassigned client NodeList* nodeList = NodeList::createInstance(NodeType::Unassigned); - const char CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION[] = "-a"; - const char CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION[] = "-p"; + // check for an overriden assignment server hostname + const QString CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION = "-a"; - // grab the overriden assignment-server hostname from argv, if it exists - const char* customAssignmentServerHostname = getCmdOption(argc, (const char**)argv, CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION); - const char* customAssignmentServerPortString = getCmdOption(argc,(const char**)argv, CUSTOM_ASSIGNMENT_SERVER_PORT_OPTION); + argumentIndex = argumentList.indexOf(CUSTOM_ASSIGNMENT_SERVER_HOSTNAME_OPTION); - HifiSockAddr customAssignmentSocket; - - if (customAssignmentServerHostname || customAssignmentServerPortString) { + if (argumentIndex != -1) { + _assignmentServerHostname = argumentList[argumentIndex + 1]; - // set the custom port or default if it wasn't passed - unsigned short assignmentServerPort = customAssignmentServerPortString - ? atoi(customAssignmentServerPortString) : DEFAULT_DOMAIN_SERVER_PORT; + // set the custom assignment socket on our NodeList + HifiSockAddr customAssignmentSocket = HifiSockAddr(_assignmentServerHostname, DEFAULT_DOMAIN_SERVER_PORT); - // set the custom hostname or default if it wasn't passed - if (!customAssignmentServerHostname) { - customAssignmentServerHostname = DEFAULT_ASSIGNMENT_SERVER_HOSTNAME; - } - - customAssignmentSocket = HifiSockAddr(customAssignmentServerHostname, assignmentServerPort); - } - - // set the custom assignment socket if we have it - if (!customAssignmentSocket.isNull()) { nodeList->setAssignmentServerSocket(customAssignmentSocket); } @@ -105,6 +106,10 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) : this, &AssignmentClient::handleAuthenticationRequest); } +AssignmentClient::~AssignmentClient() { + DTLSClientSession::globalDeinit(); +} + void AssignmentClient::sendAssignmentRequest() { if (!_currentAssignment) { NodeList::getInstance()->sendAssignment(_requestAssignment); @@ -130,12 +135,12 @@ void AssignmentClient::readPendingDatagrams() { if (_currentAssignment) { qDebug() << "Received an assignment -" << *_currentAssignment; - // switch our nodelist domain IP and port to whoever sent us the assignment + // switch our DomainHandler hostname and port to whoever sent us the assignment - nodeList->getDomainInfo().setSockAddr(senderSockAddr); - nodeList->getDomainInfo().setAssignmentUUID(_currentAssignment->getUUID()); + nodeList->getDomainHandler().setSockAddr(senderSockAddr, _assignmentServerHostname); + nodeList->getDomainHandler().setAssignmentUUID(_currentAssignment->getUUID()); - qDebug() << "Destination IP for assignment is" << nodeList->getDomainInfo().getIP().toString(); + qDebug() << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString(); // start the deployed assignment AssignmentThread* workerThread = new AssignmentThread(_currentAssignment, this); diff --git a/assignment-client/src/AssignmentClient.h b/assignment-client/src/AssignmentClient.h index c267c6238b..2df9f4ca40 100644 --- a/assignment-client/src/AssignmentClient.h +++ b/assignment-client/src/AssignmentClient.h @@ -1,13 +1,16 @@ // // AssignmentClient.h -// hifi +// assignment-client/src // // Created by Stephen Birarda on 11/25/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__AssignmentClient__ -#define __hifi__AssignmentClient__ +#ifndef hifi_AssignmentClient_h +#define hifi_AssignmentClient_h #include @@ -17,14 +20,18 @@ class AssignmentClient : public QCoreApplication { Q_OBJECT public: AssignmentClient(int &argc, char **argv); + static const SharedAssignmentPointer& getCurrentAssignment() { return _currentAssignment; } + ~AssignmentClient(); private slots: void sendAssignmentRequest(); void readPendingDatagrams(); void assignmentCompleted(); void handleAuthenticationRequest(); + private: Assignment _requestAssignment; - SharedAssignmentPointer _currentAssignment; + static SharedAssignmentPointer _currentAssignment; + QString _assignmentServerHostname; }; -#endif /* defined(__hifi__AssignmentClient__) */ +#endif // hifi_AssignmentClient_h diff --git a/assignment-client/src/AssignmentClientMonitor.cpp b/assignment-client/src/AssignmentClientMonitor.cpp index 64c9b31147..af09ff1535 100644 --- a/assignment-client/src/AssignmentClientMonitor.cpp +++ b/assignment-client/src/AssignmentClientMonitor.cpp @@ -1,9 +1,12 @@ // // AssignmentClientMonitor.cpp -// hifi +// assignment-client/src // // Created by Stephen Birarda on 1/10/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 @@ -53,4 +56,4 @@ void AssignmentClientMonitor::spawnChildClient() { void AssignmentClientMonitor::childProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { qDebug("Replacing dead child assignment client with a new one"); spawnChildClient(); -} \ No newline at end of file +} diff --git a/assignment-client/src/AssignmentClientMonitor.h b/assignment-client/src/AssignmentClientMonitor.h index 259a6d6db7..1df08a345e 100644 --- a/assignment-client/src/AssignmentClientMonitor.h +++ b/assignment-client/src/AssignmentClientMonitor.h @@ -1,13 +1,16 @@ // // AssignmentClientMonitor.h -// hifi +// assignment-client/src // // Created by Stephen Birarda on 1/10/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__AssignmentClientMonitor__ -#define __hifi__AssignmentClientMonitor__ +#ifndef hifi_AssignmentClientMonitor_h +#define hifi_AssignmentClientMonitor_h #include #include @@ -28,4 +31,4 @@ private: QStringList _childArguments; }; -#endif /* defined(__hifi__AssignmentClientMonitor__) */ +#endif // hifi_AssignmentClientMonitor_h diff --git a/assignment-client/src/AssignmentFactory.cpp b/assignment-client/src/AssignmentFactory.cpp index cde5666ab9..cdf5c591e1 100644 --- a/assignment-client/src/AssignmentFactory.cpp +++ b/assignment-client/src/AssignmentFactory.cpp @@ -1,9 +1,12 @@ // // AssignmentFactory.cpp -// hifi +// assignment-client/src // // Created by Stephen Birarda on 9/17/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/assignment-client/src/AssignmentFactory.h b/assignment-client/src/AssignmentFactory.h index 9eff29a468..9c197f5747 100644 --- a/assignment-client/src/AssignmentFactory.h +++ b/assignment-client/src/AssignmentFactory.h @@ -1,13 +1,16 @@ // // AssignmentFactory.h -// hifi +// assignment-client/src // // Created by Stephen Birarda on 9/17/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__AssignmentFactory__ -#define __hifi__AssignmentFactory__ +#ifndef hifi_AssignmentFactory_h +#define hifi_AssignmentFactory_h #include @@ -16,4 +19,4 @@ public: static ThreadedAssignment* unpackAssignment(const QByteArray& packet); }; -#endif /* defined(__hifi__AssignmentFactory__) */ +#endif // hifi_AssignmentFactory_h diff --git a/assignment-client/src/AssignmentThread.cpp b/assignment-client/src/AssignmentThread.cpp index dfe093aa7b..848fa7afae 100644 --- a/assignment-client/src/AssignmentThread.cpp +++ b/assignment-client/src/AssignmentThread.cpp @@ -1,9 +1,12 @@ // // AssignmentThread.cpp -// hifi +// assignment-client/src // -// Created by Stephen Birarda on 2014-03-28. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Stephen Birarda on 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 // #include "AssignmentThread.h" @@ -13,4 +16,4 @@ AssignmentThread::AssignmentThread(const SharedAssignmentPointer& assignment, QO _assignment(assignment) { -} \ No newline at end of file +} diff --git a/assignment-client/src/AssignmentThread.h b/assignment-client/src/AssignmentThread.h index b55ac10676..848c4614ba 100644 --- a/assignment-client/src/AssignmentThread.h +++ b/assignment-client/src/AssignmentThread.h @@ -1,13 +1,16 @@ // // AssignmentThread.h -// hifi +// assignment-client/src // -// Created by Stephen Birarda on 2014-03-28. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Stephen Birarda on 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 // -#ifndef __hifi__AssignmentThread__ -#define __hifi__AssignmentThread__ +#ifndef hifi_AssignmentThread_h +#define hifi_AssignmentThread_h #include @@ -20,4 +23,4 @@ private: SharedAssignmentPointer _assignment; }; -#endif /* defined(__hifi__AssignmentThread__) */ +#endif // hifi_AssignmentThread_h diff --git a/assignment-client/src/audio/AudioMixer.cpp b/assignment-client/src/audio/AudioMixer.cpp index f183abade9..5fd9efedd7 100644 --- a/assignment-client/src/audio/AudioMixer.cpp +++ b/assignment-client/src/audio/AudioMixer.cpp @@ -1,9 +1,12 @@ // // AudioMixer.cpp -// hifi +// assignment-client/src/audio // // Created by Stephen Birarda on 8/22/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -18,13 +21,10 @@ #include #ifdef _WIN32 -#include "Syssocket.h" -#include "Systime.h" #include #else #include #include -#include #include #endif //_WIN32 @@ -369,7 +369,7 @@ void AudioMixer::sendStatsPacket() { statsObject["average_mixes_per_listener"] = 0.0; } - ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); +// ThreadedAssignment::addPacketStatsAndSendStatsPacket(statsObject); _sumListeners = 0; _sumMixes = 0; @@ -387,9 +387,8 @@ void AudioMixer::run() { nodeList->linkedDataCreateCallback = attachNewBufferToNode; int nextFrame = 0; - timeval startTime; - - gettimeofday(&startTime, NULL); + QElapsedTimer timer; + timer.start(); char* clientMixBuffer = new char[NETWORK_BUFFER_LENGTH_BYTES_STEREO + numBytesForPacketHeaderGivenPacketType(PacketTypeMixedAudio)]; @@ -488,7 +487,7 @@ void AudioMixer::run() { break; } - usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); + usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000; // ns to us if (usecToSleep > 0) { usleep(usecToSleep); diff --git a/assignment-client/src/audio/AudioMixer.h b/assignment-client/src/audio/AudioMixer.h index 0ca241c5ed..659227dffb 100644 --- a/assignment-client/src/audio/AudioMixer.h +++ b/assignment-client/src/audio/AudioMixer.h @@ -1,13 +1,16 @@ // // AudioMixer.h -// hifi +// assignment-client/src/audio // // Created by Stephen Birarda on 8/22/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__AudioMixer__ -#define __hifi__AudioMixer__ +#ifndef hifi_AudioMixer_h +#define hifi_AudioMixer_h #include @@ -50,4 +53,4 @@ private: int _sumMixes; }; -#endif /* defined(__hifi__AudioMixer__) */ +#endif // hifi_AudioMixer_h diff --git a/assignment-client/src/audio/AudioMixerClientData.cpp b/assignment-client/src/audio/AudioMixerClientData.cpp index 4a9f1f84de..85b8dde690 100644 --- a/assignment-client/src/audio/AudioMixerClientData.cpp +++ b/assignment-client/src/audio/AudioMixerClientData.cpp @@ -1,9 +1,12 @@ // // AudioMixerClientData.cpp -// hifi +// assignment-client/src/audio // // Created by Stephen Birarda on 10/18/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/assignment-client/src/audio/AudioMixerClientData.h b/assignment-client/src/audio/AudioMixerClientData.h index 7f44390ec5..a5f03ebd15 100644 --- a/assignment-client/src/audio/AudioMixerClientData.h +++ b/assignment-client/src/audio/AudioMixerClientData.h @@ -1,13 +1,16 @@ // // AudioMixerClientData.h -// hifi +// assignment-client/src/audio // // Created by Stephen Birarda on 10/18/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__AudioMixerClientData__ -#define __hifi__AudioMixerClientData__ +#ifndef hifi_AudioMixerClientData_h +#define hifi_AudioMixerClientData_h #include @@ -31,4 +34,4 @@ private: std::vector _ringBuffers; }; -#endif /* defined(__hifi__AudioMixerClientData__) */ +#endif // hifi_AudioMixerClientData_h diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp index 4b1907efb8..9a7c2839d8 100644 --- a/assignment-client/src/audio/AvatarAudioRingBuffer.cpp +++ b/assignment-client/src/audio/AvatarAudioRingBuffer.cpp @@ -1,9 +1,12 @@ // // AvatarAudioRingBuffer.cpp -// hifi +// assignment-client/src/audio // // Created by Stephen Birarda on 6/5/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -18,4 +21,4 @@ AvatarAudioRingBuffer::AvatarAudioRingBuffer() : int AvatarAudioRingBuffer::parseData(const QByteArray& packet) { _shouldLoopbackForNode = (packetTypeForPacket(packet) == PacketTypeMicrophoneAudioWithEcho); return PositionalAudioRingBuffer::parseData(packet); -} \ No newline at end of file +} diff --git a/assignment-client/src/audio/AvatarAudioRingBuffer.h b/assignment-client/src/audio/AvatarAudioRingBuffer.h index 0d4f28467b..1e61a82f68 100644 --- a/assignment-client/src/audio/AvatarAudioRingBuffer.h +++ b/assignment-client/src/audio/AvatarAudioRingBuffer.h @@ -1,13 +1,16 @@ // // AvatarAudioRingBuffer.h -// hifi +// assignment-client/src/audio // // Created by Stephen Birarda on 6/5/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__AvatarAudioRingBuffer__ -#define __hifi__AvatarAudioRingBuffer__ +#ifndef hifi_AvatarAudioRingBuffer_h +#define hifi_AvatarAudioRingBuffer_h #include @@ -24,4 +27,4 @@ private: AvatarAudioRingBuffer& operator= (const AvatarAudioRingBuffer&); }; -#endif /* defined(__hifi__AvatarAudioRingBuffer__) */ +#endif // hifi_AvatarAudioRingBuffer_h diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index c4c0dcda24..93f38e3608 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -1,14 +1,13 @@ // // AvatarMixer.cpp -// hifi +// assignment-client/src/avatars // // Created by Stephen Birarda on 9/5/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Original avatar-mixer main created by Leonardo Murillo on 03/25/13. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// The avatar mixer receives head, hand and positional data from all connected -// nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms. #include #include diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index c9a145d2da..a69019427b 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -1,13 +1,19 @@ // // AvatarMixer.h -// hifi +// assignment-client/src/avatars // // Created by Stephen Birarda on 9/5/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. +// +// The avatar mixer receives head, hand and positional data from all connected +// nodes, and broadcasts that data back to them, every BROADCAST_INTERVAL ms. +// +// 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__AvatarMixer__ -#define __hifi__AvatarMixer__ +#ifndef hifi_AvatarMixer_h +#define hifi_AvatarMixer_h #include @@ -43,4 +49,4 @@ private: int _sumIdentityPackets; }; -#endif /* defined(__hifi__AvatarMixer__) */ +#endif // hifi_AvatarMixer_h diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index d1449e956e..f35285124f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -1,10 +1,15 @@ // // AvatarMixerClientData.cpp -// hifi +// assignment-client/src/avatars // // Created by Stephen Birarda on 2/4/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 #include "AvatarMixerClientData.h" diff --git a/assignment-client/src/avatars/AvatarMixerClientData.h b/assignment-client/src/avatars/AvatarMixerClientData.h index bc0a54f06b..18c29c4b0f 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.h +++ b/assignment-client/src/avatars/AvatarMixerClientData.h @@ -1,13 +1,16 @@ // // AvatarMixerClientData.h -// hifi +// assignment-client/src/avatars // // Created by Stephen Birarda on 2/4/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__AvatarMixerClientData__ -#define __hifi__AvatarMixerClientData__ +#ifndef hifi_AvatarMixerClientData_h +#define hifi_AvatarMixerClientData_h #include @@ -37,4 +40,4 @@ private: quint64 _identityChangeTimestamp; }; -#endif /* defined(__hifi__AvatarMixerClientData__) */ +#endif // hifi_AvatarMixerClientData_h diff --git a/assignment-client/src/main.cpp b/assignment-client/src/main.cpp index 0151f93319..7132b5c38a 100644 --- a/assignment-client/src/main.cpp +++ b/assignment-client/src/main.cpp @@ -1,9 +1,12 @@ // // main.cpp -// assignment-client +// assignment-client/src // // Created by Stephen Birarda on 8/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -14,6 +17,7 @@ #include "AssignmentClientMonitor.h" int main(int argc, char* argv[]) { + initialiseUsecTimestampNow(); #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); @@ -37,4 +41,4 @@ int main(int argc, char* argv[]) { AssignmentClient client(argc, argv); return client.exec(); } -} \ No newline at end of file +} diff --git a/assignment-client/src/metavoxels/MetavoxelServer.cpp b/assignment-client/src/metavoxels/MetavoxelServer.cpp index 98c91d44e7..3bf1632074 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.cpp +++ b/assignment-client/src/metavoxels/MetavoxelServer.cpp @@ -1,9 +1,12 @@ // // MetavoxelServer.cpp -// hifi +// assignment-client/src/metavoxels // // Created by Andrzej Kapolka on 12/18/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -95,6 +98,8 @@ MetavoxelSession::MetavoxelSession(MetavoxelServer* server, const SharedNodePoin connect(&_sequencer, SIGNAL(readyToRead(Bitstream&)), SLOT(readPacket(Bitstream&))); connect(&_sequencer, SIGNAL(sendAcknowledged(int)), SLOT(clearSendRecordsBefore(int))); connect(&_sequencer, SIGNAL(receivedHighPriorityMessage(const QVariant&)), SLOT(handleMessage(const QVariant&))); + connect(_sequencer.getReliableInputChannel(), SIGNAL(receivedMessage(const QVariant&)), + SLOT(handleMessage(const QVariant&))); // insert the baseline send record SendRecord record = { 0 }; diff --git a/assignment-client/src/metavoxels/MetavoxelServer.h b/assignment-client/src/metavoxels/MetavoxelServer.h index d178127ac7..b01bb9b412 100644 --- a/assignment-client/src/metavoxels/MetavoxelServer.h +++ b/assignment-client/src/metavoxels/MetavoxelServer.h @@ -1,13 +1,16 @@ // // MetavoxelServer.h -// hifi +// assignment-client/src/metavoxels // // Created by Andrzej Kapolka on 12/18/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__MetavoxelServer__ -#define __hifi__MetavoxelServer__ +#ifndef hifi_MetavoxelServer_h +#define hifi_MetavoxelServer_h #include #include @@ -92,4 +95,4 @@ private: QList _sendRecords; }; -#endif /* defined(__hifi__MetavoxelServer__) */ +#endif // hifi_MetavoxelServer_h diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 5e8de3a1b3..545c502036 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -1,11 +1,12 @@ // // OctreeInboundPacketProcessor.cpp -// voxel-server +// assignment-client/src/octree // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded network packet processor for the voxel-server +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include @@ -44,7 +45,7 @@ void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendin bool debugProcessPacket = _myServer->wantsVerboseDebug(); if (debugProcessPacket) { - printf("OctreeInboundPacketProcessor::processPacket() packetData=%p packetLength=%d\n", &packet, packet.size()); + qDebug("OctreeInboundPacketProcessor::processPacket() packetData=%p packetLength=%d", &packet, packet.size()); } int numBytesPacketHeader = numBytesForPacketHeader(packet); @@ -77,8 +78,8 @@ void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendin int maxSize = packet.size() - atByte; if (debugProcessPacket) { - printf("OctreeInboundPacketProcessor::processPacket() %c " - "packetData=%p packetLength=%d voxelData=%p atByte=%d maxSize=%d\n", + qDebug("OctreeInboundPacketProcessor::processPacket() %c " + "packetData=%p packetLength=%d voxelData=%p atByte=%d maxSize=%d", packetType, packetData, packet.size(), editData, atByte, maxSize); } @@ -104,8 +105,8 @@ void OctreeInboundPacketProcessor::processPacket(const SharedNodePointer& sendin } if (debugProcessPacket) { - printf("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %c " - "packetData=%p packetLength=%d voxelData=%p atByte=%d\n", + qDebug("OctreeInboundPacketProcessor::processPacket() DONE LOOPING FOR %c " + "packetData=%p packetLength=%d voxelData=%p atByte=%d", packetType, packetData, packet.size(), editData, atByte); } diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.h b/assignment-client/src/octree/OctreeInboundPacketProcessor.h index e2ec1b5c50..f637a9e7c9 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.h +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.h @@ -1,15 +1,18 @@ // // OctreeInboundPacketProcessor.h -// voxel-server +// assignment-client/src/octree // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded network packet processor for the voxel-server +// Threaded or non-threaded network packet processor for the voxel-server +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef __octree_server__OctreeInboundPacketProcessor__ -#define __octree_server__OctreeInboundPacketProcessor__ +#ifndef hifi_OctreeInboundPacketProcessor_h +#define hifi_OctreeInboundPacketProcessor_h #include @@ -80,4 +83,4 @@ private: NodeToSenderStatsMap _singleSenderStats; }; -#endif // __octree_server__OctreeInboundPacketProcessor__ +#endif // hifi_OctreeInboundPacketProcessor_h diff --git a/assignment-client/src/octree/OctreeQueryNode.cpp b/assignment-client/src/octree/OctreeQueryNode.cpp index 984ce42631..10d30ad1ae 100644 --- a/assignment-client/src/octree/OctreeQueryNode.cpp +++ b/assignment-client/src/octree/OctreeQueryNode.cpp @@ -1,9 +1,12 @@ // // OctreeQueryNode.cpp -// hifi +// assignment-client/src/octree // // Created by Stephen Birarda on 3/21/13. +// Copyright 2013 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 "PacketHeaders.h" @@ -44,41 +47,54 @@ OctreeQueryNode::OctreeQueryNode() : OctreeQueryNode::~OctreeQueryNode() { _isShuttingDown = true; - const bool extraDebugging = false; - if (extraDebugging) { - qDebug() << "OctreeQueryNode::~OctreeQueryNode()"; - } if (_octreeSendThread) { - if (extraDebugging) { - qDebug() << "OctreeQueryNode::~OctreeQueryNode()... calling _octreeSendThread->terminate()"; - } - _octreeSendThread->terminate(); - if (extraDebugging) { - qDebug() << "OctreeQueryNode::~OctreeQueryNode()... calling delete _octreeSendThread"; - } - delete _octreeSendThread; + forceNodeShutdown(); } - + delete[] _octreePacket; delete[] _lastOctreePacket; - if (extraDebugging) { - qDebug() << "OctreeQueryNode::~OctreeQueryNode()... DONE..."; - } } - -void OctreeQueryNode::deleteLater() { +void OctreeQueryNode::nodeKilled() { _isShuttingDown = true; + nodeBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications if (_octreeSendThread) { + // just tell our thread we want to shutdown, this is asynchronous, and fast, we don't need or want it to block + // while the thread actually shuts down _octreeSendThread->setIsShuttingDown(); } - OctreeQuery::deleteLater(); } +void OctreeQueryNode::forceNodeShutdown() { + _isShuttingDown = true; + nodeBag.unhookNotifications(); // if our node is shutting down, then we no longer need octree element notifications + if (_octreeSendThread) { + // we really need to force our thread to shutdown, this is synchronous, we will block while the thread actually + // shuts down because we really need it to shutdown, and it's ok if we wait for it to complete + OctreeSendThread* sendThread = _octreeSendThread; + _octreeSendThread = NULL; + sendThread->setIsShuttingDown(); + sendThread->terminate(); + delete sendThread; + } +} -void OctreeQueryNode::initializeOctreeSendThread(OctreeServer* octreeServer, SharedNodePointer node) { - // Create octree sending thread... - _octreeSendThread = new OctreeSendThread(octreeServer, node); +void OctreeQueryNode::sendThreadFinished() { + // We've been notified by our thread that it is shutting down. So we can clean up our reference to it, and + // delete the actual thread object. Cleaning up our thread will correctly unroll all refereces to shared + // pointers to our node as well as the octree server assignment + if (_octreeSendThread) { + OctreeSendThread* sendThread = _octreeSendThread; + _octreeSendThread = NULL; + delete sendThread; + } +} + +void OctreeQueryNode::initializeOctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node) { + _octreeSendThread = new OctreeSendThread(myAssignment, node); + + // we want to be notified when the thread finishes + connect(_octreeSendThread, &GenericThread::finished, this, &OctreeQueryNode::sendThreadFinished); _octreeSendThread->initialize(true); } diff --git a/assignment-client/src/octree/OctreeQueryNode.h b/assignment-client/src/octree/OctreeQueryNode.h index 17d6f6337c..7b42208f16 100644 --- a/assignment-client/src/octree/OctreeQueryNode.h +++ b/assignment-client/src/octree/OctreeQueryNode.h @@ -1,34 +1,37 @@ // // OctreeQueryNode.h -// hifi +// assignment-client/src/octree // // Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 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__OctreeQueryNode__ -#define __hifi__OctreeQueryNode__ +#ifndef hifi_OctreeQueryNode_h +#define hifi_OctreeQueryNode_h #include -#include -#include -#include + #include +#include #include #include +#include +#include #include +#include // for SharedAssignmentPointer class OctreeSendThread; -class OctreeServer; class OctreeQueryNode : public OctreeQuery { Q_OBJECT public: OctreeQueryNode(); virtual ~OctreeQueryNode(); - virtual void deleteLater(); - + void init(); // called after creation to set up some virtual items virtual PacketType getMyPacketType() const = 0; @@ -83,7 +86,7 @@ public: OctreeSceneStats stats; - void initializeOctreeSendThread(OctreeServer* octreeServer, SharedNodePointer node); + void initializeOctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node); bool isOctreeSendThreadInitalized() { return _octreeSendThread; } void dumpOutOfView(); @@ -93,8 +96,13 @@ public: unsigned int getlastOctreePacketLength() const { return _lastOctreePacketLength; } int getDuplicatePacketCount() const { return _duplicatePacketCount; } + void nodeKilled(); + void forceNodeShutdown(); bool isShuttingDown() const { return _isShuttingDown; } +private slots: + void sendThreadFinished(); + private: OctreeQueryNode(const OctreeQueryNode &); OctreeQueryNode& operator= (const OctreeQueryNode&); @@ -135,4 +143,4 @@ private: bool _isShuttingDown; }; -#endif /* defined(__hifi__OctreeQueryNode__) */ +#endif // hifi_OctreeQueryNode_h diff --git a/assignment-client/src/octree/OctreeSendThread.cpp b/assignment-client/src/octree/OctreeSendThread.cpp index 4105b21eb8..d8a9f3d1ea 100644 --- a/assignment-client/src/octree/OctreeSendThread.cpp +++ b/assignment-client/src/octree/OctreeSendThread.cpp @@ -1,8 +1,12 @@ // // OctreeSendThread.cpp +// assignment-client/src/octree // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 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 @@ -19,12 +23,13 @@ quint64 startSceneSleepTime = 0; quint64 endSceneSleepTime = 0; -OctreeSendThread::OctreeSendThread(OctreeServer* myServer, SharedNodePointer node) : - _myServer(myServer), +OctreeSendThread::OctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node) : + _myAssignment(myAssignment), + _myServer(static_cast(myAssignment.data())), + _node(node), _nodeUUID(node->getUUID()), _packetData(), _nodeMissingCount(0), - _processLock(), _isShuttingDown(false) { QString safeServerName("Octree"); @@ -37,22 +42,24 @@ OctreeSendThread::OctreeSendThread(OctreeServer* myServer, SharedNodePointer nod OctreeServer::clientConnected(); } -OctreeSendThread::~OctreeSendThread() { +OctreeSendThread::~OctreeSendThread() { QString safeServerName("Octree"); if (_myServer) { safeServerName = _myServer->getMyServerName(); } + qDebug() << qPrintable(safeServerName) << "server [" << _myServer << "]: client disconnected " "- ending sending thread [" << this << "]"; + OctreeServer::clientDisconnected(); + OctreeServer::stopTrackingThread(this); + + _node.clear(); + _myAssignment.clear(); } void OctreeSendThread::setIsShuttingDown() { _isShuttingDown = true; - OctreeServer::stopTrackingThread(this); - - // this will cause us to wait till the process loop is complete, we do this after we change _isShuttingDown - QMutexLocker locker(&_processLock); } @@ -61,47 +68,29 @@ bool OctreeSendThread::process() { return false; // exit early if we're shutting down } + // check that our server and assignment is still valid + if (!_myServer || !_myAssignment) { + return false; // exit early if it's not, it means the server is shutting down + } + OctreeServer::didProcess(this); - float lockWaitElapsedUsec = OctreeServer::SKIP_TIME; - quint64 lockWaitStart = usecTimestampNow(); - _processLock.lock(); - quint64 lockWaitEnd = usecTimestampNow(); - lockWaitElapsedUsec = (float)(lockWaitEnd - lockWaitStart); - OctreeServer::trackProcessWaitTime(lockWaitElapsedUsec); - quint64 start = usecTimestampNow(); // don't do any send processing until the initial load of the octree is complete... if (_myServer->isInitialLoadComplete()) { - SharedNodePointer node = NodeList::getInstance()->nodeWithUUID(_nodeUUID, false); - if (node) { + if (_node) { _nodeMissingCount = 0; - OctreeQueryNode* nodeData = static_cast(node->getLinkedData()); + OctreeQueryNode* nodeData = static_cast(_node->getLinkedData()); // Sometimes the node data has not yet been linked, in which case we can't really do anything if (nodeData && !nodeData->isShuttingDown()) { bool viewFrustumChanged = nodeData->updateCurrentViewFrustum(); - packetDistributor(node, nodeData, viewFrustumChanged); - } - } else { - _nodeMissingCount++; - const int MANY_FAILED_LOCKS = 1; - if (_nodeMissingCount >= MANY_FAILED_LOCKS) { - - QString safeServerName("Octree"); - if (_myServer) { - safeServerName = _myServer->getMyServerName(); - } - - qDebug() << qPrintable(safeServerName) << "server: sending thread [" << this << "]" - << "failed to get nodeWithUUID() " << _nodeUUID <<". Failed:" << _nodeMissingCount << "times"; + packetDistributor(nodeData, viewFrustumChanged); } } } - _processLock.unlock(); - if (_isShuttingDown) { return false; // exit early if we're shutting down } @@ -131,8 +120,7 @@ quint64 OctreeSendThread::_totalBytes = 0; quint64 OctreeSendThread::_totalWastedBytes = 0; quint64 OctreeSendThread::_totalPackets = 0; -int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, - OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { +int OctreeSendThread::handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent) { OctreeServer::didHandlePacketSend(this); @@ -192,12 +180,12 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, // actually send it OctreeServer::didCallWriteDatagram(this); - NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, SharedNodePointer(node)); + NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, _node); packetSent = true; } else { // not enough room in the packet, send two packets OctreeServer::didCallWriteDatagram(this); - NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, SharedNodePointer(node)); + NodeList::getInstance()->writeDatagram((char*) statsMessage, statsMessageLength, _node); // since a stats message is only included on end of scene, don't consider any of these bytes "wasted", since // there was nothing else to send. @@ -216,8 +204,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, packetsSent++; OctreeServer::didCallWriteDatagram(this); - NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), - SharedNodePointer(node)); + NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), _node); packetSent = true; @@ -237,8 +224,7 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, if (nodeData->isPacketWaiting() && !nodeData->isShuttingDown()) { // just send the voxel packet OctreeServer::didCallWriteDatagram(this); - NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), - SharedNodePointer(node)); + NodeList::getInstance()->writeDatagram((char*) nodeData->getPacket(), nodeData->getPacketLength(), _node); packetSent = true; int thisWastedBytes = MAX_PACKET_SIZE - nodeData->getPacketLength(); @@ -265,7 +251,8 @@ int OctreeSendThread::handlePacketSend(const SharedNodePointer& node, } /// Version of voxel distributor that sends the deepest LOD level at once -int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged) { +int OctreeSendThread::packetDistributor(OctreeQueryNode* nodeData, bool viewFrustumChanged) { + OctreeServer::didPacketDistributor(this); // if shutting down, exit early @@ -295,7 +282,7 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue // then let's just send that waiting packet. if (!nodeData->getCurrentPacketFormatMatches()) { if (nodeData->isPacketWaiting()) { - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); } else { nodeData->resetOctreePacket(); } @@ -336,7 +323,7 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue //unsigned long encodeTime = nodeData->stats.getTotalEncodeTime(); //unsigned long elapsedTime = nodeData->stats.getElapsedTime(); - int packetsJustSent = handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + int packetsJustSent = handlePacketSend(nodeData, trueBytesSent, truePacketsSent); packetsSentThisInterval += packetsJustSent; // If we're starting a full scene, then definitely we want to empty the nodeBag @@ -487,7 +474,7 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue if (writtenSize > nodeData->getAvailable()) { - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); } nodeData->writeToPacket(_packetData.getFinalizedData(), _packetData.getFinalizedSize()); @@ -509,7 +496,7 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue int targetSize = MAX_OCTREE_PACKET_DATA_SIZE; if (sendNow) { quint64 packetSendingStart = usecTimestampNow(); - packetsSentThisInterval += handlePacketSend(node, nodeData, trueBytesSent, truePacketsSent); + packetsSentThisInterval += handlePacketSend(nodeData, trueBytesSent, truePacketsSent); quint64 packetSendingEnd = usecTimestampNow(); packetSendingElapsedUsec = (float)(packetSendingEnd - packetSendingStart); @@ -542,8 +529,8 @@ int OctreeSendThread::packetDistributor(const SharedNodePointer& node, OctreeQue // Here's where we can/should allow the server to send other data... // send the environment packet // TODO: should we turn this into a while loop to better handle sending multiple special packets - if (_myServer->hasSpecialPacketToSend(node) && !nodeData->isShuttingDown()) { - trueBytesSent += _myServer->sendSpecialPacket(node); + if (_myServer->hasSpecialPacketToSend(_node) && !nodeData->isShuttingDown()) { + trueBytesSent += _myServer->sendSpecialPacket(_node); truePacketsSent++; packetsSentThisInterval++; } diff --git a/assignment-client/src/octree/OctreeSendThread.h b/assignment-client/src/octree/OctreeSendThread.h index 4b1b6d8c92..d8eed27802 100644 --- a/assignment-client/src/octree/OctreeSendThread.h +++ b/assignment-client/src/octree/OctreeSendThread.h @@ -1,27 +1,32 @@ // // OctreeSendThread.h +// assignment-client/src/octree // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 High Fidelity, Inc. // // Threaded or non-threaded object for sending voxels to a client // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef __octree_server__OctreeSendThread__ -#define __octree_server__OctreeSendThread__ +#ifndef hifi_OctreeSendThread_h +#define hifi_OctreeSendThread_h #include #include #include -#include "OctreeQueryNode.h" -#include "OctreeServer.h" +#include "OctreeQueryNode.h" + +class OctreeServer; /// Threaded processor for sending voxel packets to a single client class OctreeSendThread : public GenericThread { Q_OBJECT public: - OctreeSendThread(OctreeServer* myServer, SharedNodePointer node); + OctreeSendThread(const SharedAssignmentPointer& myAssignment, const SharedNodePointer& node); virtual ~OctreeSendThread(); void setIsShuttingDown(); @@ -38,17 +43,18 @@ protected: virtual bool process(); private: + SharedAssignmentPointer _myAssignment; OctreeServer* _myServer; + SharedNodePointer _node; QUuid _nodeUUID; - int handlePacketSend(const SharedNodePointer& node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent); - int packetDistributor(const SharedNodePointer& node, OctreeQueryNode* nodeData, bool viewFrustumChanged); + int handlePacketSend(OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent); + int packetDistributor(OctreeQueryNode* nodeData, bool viewFrustumChanged); OctreePacketData _packetData; int _nodeMissingCount; - QMutex _processLock; // don't allow us to have our nodeData, or our thread to be deleted while we're processing bool _isShuttingDown; }; -#endif // __octree_server__OctreeSendThread__ +#endif // hifi_OctreeSendThread_h diff --git a/assignment-client/src/octree/OctreeServer.cpp b/assignment-client/src/octree/OctreeServer.cpp index fd3f9e6cb7..bd04dd85d7 100644 --- a/assignment-client/src/octree/OctreeServer.cpp +++ b/assignment-client/src/octree/OctreeServer.cpp @@ -1,9 +1,12 @@ // // OctreeServer.cpp -// hifi +// assignment-client/src/octree // // Created by Brad Hefta-Gaub on 9/16/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -16,6 +19,8 @@ #include #include +#include "../AssignmentClient.h" + #include "OctreeServer.h" #include "OctreeServerConsts.h" @@ -203,7 +208,7 @@ void OctreeServer::trackProcessWaitTime(float time) { } void OctreeServer::attachQueryNodeToNode(Node* newNode) { - if (!newNode->getLinkedData()) { + if (!newNode->getLinkedData() && _instance) { OctreeQueryNode* newQueryNodeData = _instance->createOctreeQueryNode(); newQueryNodeData->init(); newNode->setLinkedData(newQueryNodeData); @@ -231,7 +236,13 @@ OctreeServer::OctreeServer(const QByteArray& packet) : _started(time(0)), _startedUSecs(usecTimestampNow()) { + if (_instance) { + qDebug() << "Octree Server starting... while old instance still running _instance=["<<_instance<<"] this=[" << this << "]"; + } + + qDebug() << "Octree Server starting... setting _instance to=[" << this << "]"; _instance = this; + _averageLoopTime.updateAverage(0); qDebug() << "Octree server starting... [" << this << "]"; } @@ -262,6 +273,16 @@ OctreeServer::~OctreeServer() { delete _jurisdiction; _jurisdiction = NULL; + + // cleanup our tree here... + qDebug() << qPrintable(_safeServerName) << "server START cleaning up octree... [" << this << "]"; + delete _tree; + _tree = NULL; + qDebug() << qPrintable(_safeServerName) << "server DONE cleaning up octree... [" << this << "]"; + + if (_instance == this) { + _instance = NULL; // we are gone + } qDebug() << qPrintable(_safeServerName) << "server DONE shutting down... [" << this << "]"; } @@ -809,33 +830,22 @@ void OctreeServer::readPendingDatagrams() { while (readAvailableDatagram(receivedPacket, senderSockAddr)) { if (nodeList->packetVersionAndHashMatch(receivedPacket)) { PacketType packetType = packetTypeForPacket(receivedPacket); - SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); - if (packetType == getMyQueryMessageType()) { - bool debug = false; - if (debug) { - if (matchingNode) { - qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow() << "node:" << *matchingNode; - } else { - qDebug() << "Got PacketTypeVoxelQuery at" << usecTimestampNow() << "node: ??????"; - } - } - + // If we got a PacketType_VOXEL_QUERY, then we're talking to an NodeType_t_AVATAR, and we // need to make sure we have it in our nodeList. if (matchingNode) { - if (debug) { - qDebug() << "calling updateNodeWithDataFromPacket()... node:" << *matchingNode; - } nodeList->updateNodeWithDataFromPacket(matchingNode, receivedPacket); - OctreeQueryNode* nodeData = (OctreeQueryNode*) matchingNode->getLinkedData(); if (nodeData && !nodeData->isOctreeSendThreadInitalized()) { - if (debug) { - qDebug() << "calling initializeOctreeSendThread()... node:" << *matchingNode; - } - nodeData->initializeOctreeSendThread(this, matchingNode); + + // NOTE: this is an important aspect of the proper ref counting. The send threads/node data need to + // know that the OctreeServer/Assignment will not get deleted on it while it's still active. The + // solution is to get the shared pointer for the current assignment. We need to make sure this is the + // same SharedAssignmentPointer that was ref counted by the assignment client. + SharedAssignmentPointer sharedAssignment = AssignmentClient::getCurrentAssignment(); + nodeData->initializeOctreeSendThread(sharedAssignment, matchingNode); } } } else if (packetType == PacketTypeJurisdictionRequest) { @@ -1039,23 +1049,46 @@ void OctreeServer::nodeAdded(SharedNodePointer node) { } void OctreeServer::nodeKilled(SharedNodePointer node) { + quint64 start = usecTimestampNow(); + qDebug() << qPrintable(_safeServerName) << "server killed node:" << *node; OctreeQueryNode* nodeData = static_cast(node->getLinkedData()); if (nodeData) { - qDebug() << qPrintable(_safeServerName) << "server resetting Linked Data for node:" << *node; - node->setLinkedData(NULL); // set this first in case another thread comes through and tryes to acces this - qDebug() << qPrintable(_safeServerName) << "server deleting Linked Data for node:" << *node; - nodeData->deleteLater(); + nodeData->nodeKilled(); // tell our node data and sending threads that we'd like to shut down } else { qDebug() << qPrintable(_safeServerName) << "server node missing linked data node:" << *node; } + + quint64 end = usecTimestampNow(); + quint64 usecsElapsed = (end - start); + if (usecsElapsed > 1000) { + qDebug() << qPrintable(_safeServerName) << "server nodeKilled() took: " << usecsElapsed << " usecs for node:" << *node; + } } +void OctreeServer::forceNodeShutdown(SharedNodePointer node) { + quint64 start = usecTimestampNow(); + + qDebug() << qPrintable(_safeServerName) << "server killed node:" << *node; + OctreeQueryNode* nodeData = static_cast(node->getLinkedData()); + if (nodeData) { + nodeData->forceNodeShutdown(); // tell our node data and sending threads that we'd like to shut down + } else { + qDebug() << qPrintable(_safeServerName) << "server node missing linked data node:" << *node; + } + + quint64 end = usecTimestampNow(); + quint64 usecsElapsed = (end - start); + qDebug() << qPrintable(_safeServerName) << "server forceNodeShutdown() took: " + << usecsElapsed << " usecs for node:" << *node; +} + + void OctreeServer::aboutToFinish() { qDebug() << qPrintable(_safeServerName) << "server STARTING about to finish..."; foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { qDebug() << qPrintable(_safeServerName) << "server about to finish while node still connected node:" << *node; - nodeKilled(node); + forceNodeShutdown(node); } qDebug() << qPrintable(_safeServerName) << "server ENDING about to finish..."; } diff --git a/assignment-client/src/octree/OctreeServer.h b/assignment-client/src/octree/OctreeServer.h index 63d43b6634..d7139b5c3d 100644 --- a/assignment-client/src/octree/OctreeServer.h +++ b/assignment-client/src/octree/OctreeServer.h @@ -1,14 +1,16 @@ // // OctreeServer.h -// voxel-server +// assignment-client/src/octree // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 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 __octree_server__OctreeServer__ -#define __octree_server__OctreeServer__ +#ifndef hifi_OctreeServer_h +#define hifi_OctreeServer_h #include #include @@ -115,6 +117,7 @@ public: bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); virtual void aboutToFinish(); + void forceNodeShutdown(SharedNodePointer node); public slots: /// runs the voxel server assignment @@ -217,4 +220,4 @@ protected: static QMutex _threadsDidCallWriteDatagramMutex; }; -#endif // __octree_server__OctreeServer__ +#endif // hifi_OctreeServer_h diff --git a/assignment-client/src/octree/OctreeServerConsts.h b/assignment-client/src/octree/OctreeServerConsts.h index fae03a73ca..a10c81494c 100644 --- a/assignment-client/src/octree/OctreeServerConsts.h +++ b/assignment-client/src/octree/OctreeServerConsts.h @@ -1,13 +1,16 @@ +// // OctreeServerConsts.h -// octree-server +// assignment-client/src/octree // -// Created by Brad Hefta-Gaub on 12/4/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 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 __octree_server__OctreeServerConsts__ -#define __octree_server__OctreeServerConsts__ +#ifndef hifi_OctreeServerConsts_h +#define hifi_OctreeServerConsts_h #include #include // for MAX_PACKET_SIZE @@ -18,4 +21,4 @@ const int INTERVALS_PER_SECOND = 60; const int OCTREE_SEND_INTERVAL_USECS = (1000 * 1000)/INTERVALS_PER_SECOND; const int SENDING_TIME_TO_SPARE = 5 * 1000; // usec of sending interval to spare for calculating voxels -#endif // __octree_server__OctreeServerConsts__ +#endif // hifi_OctreeServerConsts_h diff --git a/assignment-client/src/particles/ParticleNodeData.h b/assignment-client/src/particles/ParticleNodeData.h index 450767613e..340ce4844f 100644 --- a/assignment-client/src/particles/ParticleNodeData.h +++ b/assignment-client/src/particles/ParticleNodeData.h @@ -1,14 +1,16 @@ // // ParticleNodeData.h -// hifi +// assignment-client/src/particles // -// Created by Brad Hefta-Gaub on 12/4/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 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__ParticleNodeData__ -#define __hifi__ParticleNodeData__ +#ifndef hifi_ParticleNodeData_h +#define hifi_ParticleNodeData_h #include @@ -29,4 +31,4 @@ private: quint64 _lastDeletedParticlesSentAt; }; -#endif /* defined(__hifi__ParticleNodeData__) */ +#endif // hifi_ParticleNodeData_h diff --git a/assignment-client/src/particles/ParticleServer.cpp b/assignment-client/src/particles/ParticleServer.cpp index 1785ef46d6..649ee0cff9 100644 --- a/assignment-client/src/particles/ParticleServer.cpp +++ b/assignment-client/src/particles/ParticleServer.cpp @@ -1,9 +1,12 @@ // // ParticleServer.cpp -// hifi +// assignment-client/src/particles // -// Created by Brad Hefta-Gaub on 12/4/13 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/4/13. +// Copyright 2013 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 diff --git a/assignment-client/src/particles/ParticleServer.h b/assignment-client/src/particles/ParticleServer.h index 4e6a896475..495fc02c68 100644 --- a/assignment-client/src/particles/ParticleServer.h +++ b/assignment-client/src/particles/ParticleServer.h @@ -1,14 +1,16 @@ // // ParticleServer.h -// particle-server +// assignment-client/src/particles // -// Created by Brad Hefta-Gaub on 12/2/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/2/13. +// Copyright 2013 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 __particle_server__ParticleServer__ -#define __particle_server__ParticleServer__ +#ifndef hifi_ParticleServer_h +#define hifi_ParticleServer_h #include "../octree/OctreeServer.h" @@ -45,4 +47,4 @@ public slots: private: }; -#endif // __particle_server__ParticleServer__ +#endif // hifi_ParticleServer_h diff --git a/assignment-client/src/particles/ParticleServerConsts.h b/assignment-client/src/particles/ParticleServerConsts.h index 5ac2a1534b..49df2a181e 100644 --- a/assignment-client/src/particles/ParticleServerConsts.h +++ b/assignment-client/src/particles/ParticleServerConsts.h @@ -1,16 +1,19 @@ +// // ParticleServerConsts.h -// particle-server +// assignment-client/src/particles // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 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 __particle_server__ParticleServerConsts__ -#define __particle_server__ParticleServerConsts__ +#ifndef hifi_ParticleServerConsts_h +#define hifi_ParticleServerConsts_h extern const char* PARTICLE_SERVER_NAME; extern const char* PARTICLE_SERVER_LOGGING_TARGET_NAME; extern const char* LOCAL_PARTICLES_PERSIST_FILE; -#endif // __particle_server__ParticleServerConsts__ +#endif // hifi_ParticleServerConsts_h diff --git a/assignment-client/src/voxels/VoxelNodeData.h b/assignment-client/src/voxels/VoxelNodeData.h index c24bfad6ce..86d241852d 100644 --- a/assignment-client/src/voxels/VoxelNodeData.h +++ b/assignment-client/src/voxels/VoxelNodeData.h @@ -1,13 +1,16 @@ // // VoxelNodeData.h -// hifi +// assignment-client/src/voxels // // Created by Stephen Birarda on 3/21/13. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__VoxelNodeData__ -#define __hifi__VoxelNodeData__ +#ifndef hifi_VoxelNodeData_h +#define hifi_VoxelNodeData_h #include @@ -19,4 +22,4 @@ public: virtual PacketType getMyPacketType() const { return PacketTypeVoxelData; } }; -#endif /* defined(__hifi__VoxelNodeData__) */ +#endif // hifi_VoxelNodeData_h diff --git a/assignment-client/src/voxels/VoxelServer.cpp b/assignment-client/src/voxels/VoxelServer.cpp index 771c91570f..7747b8336b 100644 --- a/assignment-client/src/voxels/VoxelServer.cpp +++ b/assignment-client/src/voxels/VoxelServer.cpp @@ -1,9 +1,12 @@ // // VoxelServer.cpp -// hifi +// assignment-client/src/voxels // // Created by Brad Hefta-Gaub on 9/16/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/assignment-client/src/voxels/VoxelServer.h b/assignment-client/src/voxels/VoxelServer.h index 2e97736963..e81c16fc38 100644 --- a/assignment-client/src/voxels/VoxelServer.h +++ b/assignment-client/src/voxels/VoxelServer.h @@ -1,14 +1,16 @@ // // VoxelServer.h -// voxel-server +// assignment-client/src/voxels // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 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 __voxel_server__VoxelServer__ -#define __voxel_server__VoxelServer__ +#ifndef hifi_VoxelServer_h +#define hifi_VoxelServer_h #include #include @@ -53,4 +55,4 @@ private: unsigned char _tempOutputBuffer[MAX_PACKET_SIZE]; }; -#endif // __voxel_server__VoxelServer__ +#endif // hifi_VoxelServer_h diff --git a/assignment-client/src/voxels/VoxelServerConsts.h b/assignment-client/src/voxels/VoxelServerConsts.h index ce6fd86a32..5764e7d4cf 100644 --- a/assignment-client/src/voxels/VoxelServerConsts.h +++ b/assignment-client/src/voxels/VoxelServerConsts.h @@ -1,13 +1,16 @@ +// // VoxelServerConsts.h -// voxel-server +// assignment-client/src/voxels // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 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 __voxel_server__VoxelServerConsts__ -#define __voxel_server__VoxelServerConsts__ +#ifndef hifi_VoxelServerConsts_h +#define hifi_VoxelServerConsts_h extern const char* VOXEL_SERVER_NAME; extern const char* VOXEL_SERVER_LOGGING_TARGET_NAME; @@ -15,4 +18,4 @@ extern const char* LOCAL_VOXELS_PERSIST_FILE; const int ENVIRONMENT_SEND_INTERVAL_USECS = 1000000; -#endif // __voxel_server__VoxelServerConsts__ +#endif // hifi_VoxelServerConsts_h diff --git a/cmake/macros/AutoMTC.cmake b/cmake/macros/AutoMTC.cmake index 6619419b59..1682b9cd56 100644 --- a/cmake/macros/AutoMTC.cmake +++ b/cmake/macros/AutoMTC.cmake @@ -1,3 +1,13 @@ +# +# AutoMTC.cmake +# +# Created by Andrzej Kapolka on 12/31/13. +# Copyright 2013 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + macro(AUTO_MTC TARGET ROOT_DIR) if (NOT TARGET mtc) add_subdirectory("${ROOT_DIR}/tools/mtc" "${ROOT_DIR}/tools/mtc") @@ -8,6 +18,4 @@ macro(AUTO_MTC TARGET ROOT_DIR) file(GLOB INCLUDE_FILES src/*.h) add_custom_command(OUTPUT ${AUTOMTC_SRC} COMMAND mtc -o ${AUTOMTC_SRC} ${INCLUDE_FILES} DEPENDS mtc ${INCLUDE_FILES}) -endmacro() - - +endmacro() \ No newline at end of file diff --git a/cmake/macros/IncludeGLM.cmake b/cmake/macros/IncludeGLM.cmake index 7f5fe039c6..a31324993e 100644 --- a/cmake/macros/IncludeGLM.cmake +++ b/cmake/macros/IncludeGLM.cmake @@ -1,3 +1,12 @@ +# +# IncludeGLM.cmake +# +# Copyright 2013 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + macro(INCLUDE_GLM TARGET ROOT_DIR) find_package(GLM REQUIRED) diff --git a/cmake/macros/LinkHifiLibrary.cmake b/cmake/macros/LinkHifiLibrary.cmake index 02ebc983c2..6300e50c34 100644 --- a/cmake/macros/LinkHifiLibrary.cmake +++ b/cmake/macros/LinkHifiLibrary.cmake @@ -1,3 +1,12 @@ +# +# LinkHifiLibrary.cmake +# +# Copyright 2013 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + macro(LINK_HIFI_LIBRARY LIBRARY TARGET ROOT_DIR) if (NOT TARGET ${LIBRARY}) diff --git a/cmake/macros/SetupHifiLibrary.cmake b/cmake/macros/SetupHifiLibrary.cmake index 19428b6723..ff4ae3b4f3 100644 --- a/cmake/macros/SetupHifiLibrary.cmake +++ b/cmake/macros/SetupHifiLibrary.cmake @@ -1,3 +1,12 @@ +# +# SetupHifiLibrary.cmake +# +# Copyright 2013 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + macro(SETUP_HIFI_LIBRARY TARGET) project(${TARGET}) diff --git a/cmake/macros/SetupHifiProject.cmake b/cmake/macros/SetupHifiProject.cmake index a707626254..50cd3a4150 100644 --- a/cmake/macros/SetupHifiProject.cmake +++ b/cmake/macros/SetupHifiProject.cmake @@ -1,3 +1,12 @@ +# +# SetupHifiProject.cmake +# +# Copyright 2013 High Fidelity, Inc. +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# + macro(SETUP_HIFI_PROJECT TARGET INCLUDE_QT) project(${TARGET}) diff --git a/cmake/modules/FindFaceplus.cmake b/cmake/modules/FindFaceplus.cmake new file mode 100644 index 0000000000..2a2083d6e4 --- /dev/null +++ b/cmake/modules/FindFaceplus.cmake @@ -0,0 +1,42 @@ +# Try to find the Faceplus library +# +# You must provide a FACEPLUS_ROOT_DIR which contains lib and include directories +# +# Once done this will define +# +# FACEPLUS_FOUND - system found Faceplus +# FACEPLUS_INCLUDE_DIRS - the Faceplus include directory +# FACEPLUS_LIBRARIES - Link this to use Faceplus +# +# Created on 4/8/2014 by Andrzej Kapolka +# Copyright (c) 2014 High Fidelity +# + +if (FACEPLUS_LIBRARIES AND FACEPLUS_INCLUDE_DIRS) + # in cache already + set(FACEPLUS_FOUND TRUE) +else (FACEPLUS_LIBRARIES AND FACEPLUS_INCLUDE_DIRS) + find_path(FACEPLUS_INCLUDE_DIRS faceplus.h ${FACEPLUS_ROOT_DIR}/include) + + if (WIN32) + find_library(FACEPLUS_LIBRARIES faceplus.lib ${FACEPLUS_ROOT_DIR}/win32/) + endif (WIN32) + + if (FACEPLUS_INCLUDE_DIRS AND FACEPLUS_LIBRARIES) + set(FACEPLUS_FOUND TRUE) + endif (FACEPLUS_INCLUDE_DIRS AND FACEPLUS_LIBRARIES) + + if (FACEPLUS_FOUND) + if (NOT FACEPLUS_FIND_QUIETLY) + message(STATUS "Found Faceplus... ${FACEPLUS_LIBRARIES}") + endif (NOT FACEPLUS_FIND_QUIETLY) + else () + if (FACEPLUS_FIND_REQUIRED) + message(FATAL_ERROR "Could not find Faceplus") + endif (FACEPLUS_FIND_REQUIRED) + endif () + + # show the FACEPLUS_INCLUDE_DIRS and FACEPLUS_LIBRARIES variables only in the advanced view + mark_as_advanced(FACEPLUS_INCLUDE_DIRS FACEPLUS_LIBRARIES) + +endif (FACEPLUS_LIBRARIES AND FACEPLUS_INCLUDE_DIRS) diff --git a/cmake/modules/FindFaceshift.cmake b/cmake/modules/FindFaceshift.cmake index c89cbbab6f..faf556e06c 100644 --- a/cmake/modules/FindFaceshift.cmake +++ b/cmake/modules/FindFaceshift.cmake @@ -1,3 +1,6 @@ +# +# FindFaceshift.cmake +# # Try to find the Faceshift networking library # # You must provide a FACESHIFT_ROOT_DIR which contains lib and include directories @@ -9,8 +12,11 @@ # FACESHIFT_LIBRARIES - Link this to use Faceshift # # Created on 8/30/2013 by Andrzej Kapolka -# Copyright (c) 2013 High Fidelity +# Copyright 2013 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 +# if (FACESHIFT_LIBRARIES AND FACESHIFT_INCLUDE_DIRS) # in cache already diff --git a/cmake/modules/FindGLEW.cmake b/cmake/modules/FindGLEW.cmake index 684acca762..b621756885 100644 --- a/cmake/modules/FindGLEW.cmake +++ b/cmake/modules/FindGLEW.cmake @@ -1,16 +1,22 @@ # -# Try to find GLEW library and include path. -# Once done this will define -# -# GLEW_FOUND -# GLEW_INCLUDE_DIRS -# GLEW_LIBRARY +# FindGLEW.cmake # - -# Created on 2/6/2014 by Stephen Birarda +# Try to find GLEW library and include path. +# Once done this will define # -# Adapted from FindGLEW.cmake available in the nvidia-texture-tools repository -# (https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/cmake/FindGLEW.cmake?r=96) +# GLEW_FOUND +# GLEW_INCLUDE_DIRS +# GLEW_LIBRARY +# +# Created on 2/6/2014 by Stephen Birarda +# Copyright 2014 High Fidelity, Inc. +# +# Adapted from FindGLEW.cmake available in the nvidia-texture-tools repository +# (https://code.google.com/p/nvidia-texture-tools/source/browse/trunk/cmake/FindGLEW.cmake?r=96) +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +# if (GLEW_INCLUDE_DIRS AND GLEW_LIBRARY) set(GLEW_FOUND TRUE) @@ -20,7 +26,7 @@ else () find_path(GLEW_INCLUDE_DIRS GL/glew.h PATH_SUFFIXES include HINTS ${WIN_GLEW_SEARCH_DIRS}) - find_library(GLEW_LIBRARY glew32s PATH_SUFFIXES "lib/Release/Win32" HINTS ${WIN_GLEW_SEARCH_DIRS}) + find_library(GLEW_LIBRARY glew32s PATH_SUFFIXES "lib/Release/Win32" "lib" HINTS ${WIN_GLEW_SEARCH_DIRS}) endif () include(FindPackageHandleStandardArgs) diff --git a/cmake/modules/FindGLUT.cmake b/cmake/modules/FindGLUT.cmake index 173f5a24ae..280a6ac6db 100644 --- a/cmake/modules/FindGLUT.cmake +++ b/cmake/modules/FindGLUT.cmake @@ -1,15 +1,23 @@ -# Try to find GLUT library and include path. -# Once done this will define +# +# FindGLUT.cmake +# +# Try to find GLUT library and include path. +# Once done this will define # -# GLUT_FOUND -# GLUT_INCLUDE_DIR -# GLUT_LIBRARIES -# GLUT_DLL_PATH - Optionally defined for Win32, if not in path +# GLUT_FOUND +# GLUT_INCLUDE_DIR +# GLUT_LIBRARIES +# GLUT_DLL_PATH - Optionally defined for Win32, if not in path # # Created on 2/6/2014 by Stephen Birarda +# Copyright 2014 High Fidelity, Inc. +# +# Adapted from FindGLUT.cmake available in tlorach's OpenGLText Repository +# https://raw.github.com/tlorach/OpenGLText/master/cmake/FindGLUT.cmake +# +# Distributed under the Apache License, Version 2.0. +# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html # -# Adapted from FindGLUT.cmake available in tlorach's OpenGLText Repository -# https://raw.github.com/tlorach/OpenGLText/master/cmake/FindGLUT.cmake if (GLUT_INCLUDE_DIR AND GLUT_LIBRARIES) set(GLUT_FOUND TRUE) diff --git a/cmake/modules/FindGnuTLS.cmake b/cmake/modules/FindGnuTLS.cmake new file mode 100644 index 0000000000..0e1899864b --- /dev/null +++ b/cmake/modules/FindGnuTLS.cmake @@ -0,0 +1,41 @@ +# +# 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 () diff --git a/cmake/modules/FindLibOVR.cmake b/cmake/modules/FindLibOVR.cmake index 47b7d27015..c07b4c14c1 100644 --- a/cmake/modules/FindLibOVR.cmake +++ b/cmake/modules/FindLibOVR.cmake @@ -1,4 +1,7 @@ -# - Try to find the LibOVR library to use the Oculus +# +# FindLibOVR.cmake +# +# Try to find the LibOVR library to use the Oculus # # You must provide a LIBOVR_ROOT_DIR which contains Lib and Include directories # @@ -9,8 +12,11 @@ # LIBOVR_LIBRARIES - Link this to use LibOVR # # Created on 5/9/2013 by Stephen Birarda -# Copyright (c) 2013 High Fidelity +# Copyright 2013 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 +# if (LIBOVR_LIBRARIES AND LIBOVR_INCLUDE_DIRS) # in cache already diff --git a/cmake/modules/FindQt5LinguistToolsMacros.cmake b/cmake/modules/FindQt5LinguistToolsMacros.cmake index cd2d1eb74f..bd9d55cb16 100644 --- a/cmake/modules/FindQt5LinguistToolsMacros.cmake +++ b/cmake/modules/FindQt5LinguistToolsMacros.cmake @@ -30,8 +30,6 @@ # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #============================================================================= - - function(QT5_CREATE_TRANSLATION_CUSTOM _qm_files) set(options) set(oneValueArgs) diff --git a/cmake/modules/FindQxmpp.cmake b/cmake/modules/FindQxmpp.cmake index 415b184a65..420186d969 100644 --- a/cmake/modules/FindQxmpp.cmake +++ b/cmake/modules/FindQxmpp.cmake @@ -1,3 +1,6 @@ +# +# FindQxmpp.cmake +# # Try to find the qxmpp library # # You can provide a QXMPP_ROOT_DIR which contains lib and include directories @@ -6,11 +9,14 @@ # # QXMPP_FOUND - system found qxmpp # QXMPP_INCLUDE_DIRS - the qxmpp include directory -# QXMPP_LIBRARIES - Link this to use qxmpp +# QXMPP_LIBRARY - Link this to use qxmpp # # Created on 3/10/2014 by Stephen Birarda -# Copyright (c) 2014 High Fidelity +# 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 +# if (QXMPP_LIBRARIES AND QXMPP_INCLUDE_DIRS) # in cache already @@ -25,14 +31,4 @@ else () include(FindPackageHandleStandardArgs) find_package_handle_standard_args(QXMPP DEFAULT_MSG QXMPP_INCLUDE_DIR QXMPP_LIBRARY) - - if (QXMPP_FOUND) - if (NOT QXMPP_FIND_QUIETLY) - message(STATUS "Found qxmpp: ${QXMPP_LIBRARY}") - endif () - else () - if (QXMPP_FIND_REQUIRED) - message(FATAL_ERROR "Could not find qxmpp") - endif () - endif () endif () \ No newline at end of file diff --git a/cmake/modules/FindSixense.cmake b/cmake/modules/FindSixense.cmake index 1585095ba8..02b6e3fc21 100644 --- a/cmake/modules/FindSixense.cmake +++ b/cmake/modules/FindSixense.cmake @@ -1,3 +1,6 @@ +# +# FindSixense.cmake +# # Try to find the Sixense controller library # # You must provide a SIXENSE_ROOT_DIR which contains lib and include directories @@ -9,8 +12,11 @@ # SIXENSE_LIBRARIES - Link this to use Sixense # # Created on 11/15/2013 by Andrzej Kapolka -# Copyright (c) 2013 High Fidelity +# Copyright 2013 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 +# if (SIXENSE_LIBRARIES AND SIXENSE_INCLUDE_DIRS) # in cache already diff --git a/cmake/modules/FindVisage.cmake b/cmake/modules/FindVisage.cmake index aa7f0f9faf..0aa57f6508 100644 --- a/cmake/modules/FindVisage.cmake +++ b/cmake/modules/FindVisage.cmake @@ -1,3 +1,6 @@ +# +# FindVisage.cmake +# # Try to find the Visage controller library # # You must provide a VISAGE_ROOT_DIR which contains lib and include directories @@ -9,8 +12,11 @@ # VISAGE_LIBRARIES - Link this to use Visage # # Created on 2/11/2014 by Andrzej Kapolka -# Copyright (c) 2014 High Fidelity +# 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 +# if (VISAGE_LIBRARIES AND VISAGE_INCLUDE_DIRS) # in cache already diff --git a/domain-server/CMakeLists.txt b/domain-server/CMakeLists.txt index 16bdb0df6b..4736c2438b 100644 --- a/domain-server/CMakeLists.txt +++ b/domain-server/CMakeLists.txt @@ -17,6 +17,7 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") find_package(Qt5Network REQUIRED) +find_package(GnuTLS REQUIRED) include(${MACRO_DIR}/SetupHifiProject.cmake) @@ -33,11 +34,21 @@ add_custom_command(TARGET ${TARGET_NAME} POST_BUILD # link the shared hifi library include(${MACRO_DIR}/LinkHifiLibrary.cmake) +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) -target_link_libraries(${TARGET_NAME} Qt5::Network) \ No newline at end of file +# link QtNetwork and GnuTLS +target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/domain-server/resources/web/assignment/css/style.css b/domain-server/resources/web/assignment/css/style.css index b6c26ca9fd..51813a4d73 100644 --- a/domain-server/resources/web/assignment/css/style.css +++ b/domain-server/resources/web/assignment/css/style.css @@ -42,13 +42,13 @@ body { background-color: #28FF57; } -#instance-field { +#extra-fields { position: absolute; right: 20px; - top: 40px; + top: 30px; } -#instance-field input { +#extra-fields input { width: 80px; } diff --git a/domain-server/resources/web/assignment/index.shtml b/domain-server/resources/web/assignment/index.shtml index 64a6d8825f..27c84be985 100644 --- a/domain-server/resources/web/assignment/index.shtml +++ b/domain-server/resources/web/assignment/index.shtml @@ -14,8 +14,13 @@ Run -
- +
+
+ +
+
+ +
diff --git a/domain-server/resources/web/assignment/js/assignment.js b/domain-server/resources/web/assignment/js/assignment.js index a04c8192f4..71f4373251 100644 --- a/domain-server/resources/web/assignment/js/assignment.js +++ b/domain-server/resources/web/assignment/js/assignment.js @@ -22,9 +22,14 @@ $(document).ready(function(){ + '--' + boundary + '--\r\n'; var headers = {}; + if ($('#instance-field input').val()) { headers['ASSIGNMENT-INSTANCES'] = $('#instance-field input').val(); } + + if ($('#pool-field input').val()) { + headers['ASSIGNMENT-POOL'] = $('#pool-field input').val(); + } // post form to assignment in order to create an assignment $.ajax({ diff --git a/domain-server/resources/web/index.shtml b/domain-server/resources/web/index.shtml index bba4dc4d22..afd0af1679 100644 --- a/domain-server/resources/web/index.shtml +++ b/domain-server/resources/web/index.shtml @@ -12,6 +12,7 @@ Pool Public Local + Uptime (s) Kill? diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index 18e67ac6e7..a4884486c3 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -2,16 +2,47 @@ $(document).ready(function(){ // setup a function to grab the assignments function getNodesAndAssignments() { $.getJSON("nodes.json", function(json){ + + json.nodes.sort(function(a, b){ + if (a.type === b.type) { + if (a.wake_timestamp < b.wake_timestamp) { + return 1; + } else if (a.wake_timestamp > b.wake_timestamp) { + return -1; + } else { + return 0; + } + } + + if (a.type === "agent" && b.type !== "agent") { + return 1; + } else if (b.type === "agent" && a.type !== "agent") { + return -1; + } + + if (a.type > b.type) { + return 1; + } + + if (a.type < b.type) { + return -1; + } + }); + nodesTableBody = ""; - $.each(json.nodes, function (uuid, data) { + $.each(json.nodes, function(index, data) { nodesTableBody += ""; nodesTableBody += "" + data.type + ""; - nodesTableBody += "" + uuid + ""; + nodesTableBody += "" + data.uuid + ""; nodesTableBody += "" + (data.pool ? data.pool : "") + ""; nodesTableBody += "" + data.public.ip + ":" + data.public.port + ""; nodesTableBody += "" + data.local.ip + ":" + data.local.port + ""; - nodesTableBody += ""; + + var uptimeSeconds = (Date.now() - data.wake_timestamp) / 1000; + nodesTableBody += "" + uptimeSeconds.toLocaleString() + ""; + + nodesTableBody += ""; nodesTableBody += ""; }); diff --git a/domain-server/src/DTLSServerSession.cpp b/domain-server/src/DTLSServerSession.cpp new file mode 100644 index 0000000000..a80c54ee02 --- /dev/null +++ b/domain-server/src/DTLSServerSession.cpp @@ -0,0 +1,18 @@ +// +// 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) +{ + +} \ No newline at end of file diff --git a/domain-server/src/DTLSServerSession.h b/domain-server/src/DTLSServerSession.h new file mode 100644 index 0000000000..5fdc602df7 --- /dev/null +++ b/domain-server/src/DTLSServerSession.h @@ -0,0 +1,24 @@ +// +// 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 + +#include + +class DTLSServerSession : public DTLSSession { +public: + DTLSServerSession(QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket); +}; + +#endif // hifi_DTLSServerSession_h diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 035e6c9a20..e65f3968e0 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1,12 +1,13 @@ // // DomainServer.cpp -// hifi +// domain-server/src // // Created by Stephen Birarda on 9/26/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 #include #include @@ -16,13 +17,17 @@ #include #include +#include + #include +#include #include #include #include #include #include "DomainServerNodeData.h" +#include "DummyDTLSSession.h" #include "DomainServer.h" @@ -33,126 +38,168 @@ DomainServer::DomainServer(int argc, char* argv[]) : _HTTPManager(DOMAIN_SERVER_HTTP_PORT, QString("%1/resources/web/").arg(QCoreApplication::applicationDirPath()), this), _staticAssignmentHash(), _assignmentQueue(), - _nodeAuthenticationURL(), - _redeemedTokenResponses() + _isUsingDTLS(false), + _x509Credentials(NULL), + _dhParams(NULL), + _priorityCache(NULL), + _dtlsSessions() { + gnutls_global_init(); + setOrganizationName("High Fidelity"); setOrganizationDomain("highfidelity.io"); setApplicationName("domain-server"); QSettings::setDefaultFormat(QSettings::IniFormat); - _argumentList = arguments(); - int argumentIndex = 0; + _argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments()); - // check if this domain server should use no authentication or a custom hostname for authentication - const QString DEFAULT_AUTH_OPTION = "--defaultAuth"; - const QString CUSTOM_AUTH_OPTION = "--customAuth"; - if ((argumentIndex = _argumentList.indexOf(DEFAULT_AUTH_OPTION) != -1)) { - _nodeAuthenticationURL = QUrl(DEFAULT_NODE_AUTH_URL); - } else if ((argumentIndex = _argumentList.indexOf(CUSTOM_AUTH_OPTION)) != -1) { - _nodeAuthenticationURL = QUrl(_argumentList.value(argumentIndex + 1)); + if (optionallySetupDTLS()) { + // we either read a certificate and private key or were not passed one, good to load assignments + // and set up the node list + qDebug() << "Setting up LimitedNodeList and assignments."; + setupNodeListAndAssignments(); + + if (_isUsingDTLS) { + // we're using DTLS and our NodeList socket is good to go, so make the required DTLS changes + // DTLS requires that IP_DONTFRAG be set + // This is not accessible on some platforms (OS X) so we need to make sure DTLS still works without it + + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + // connect our socket to read datagrams received on the DTLS socket + connect(&nodeList->getDTLSSocket(), &QUdpSocket::readyRead, this, &DomainServer::readAvailableDTLSDatagrams); + } } +} + +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::optionallySetupDTLS() { + if (readX509KeyAndCertificate()) { + 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."; + } - if (!_nodeAuthenticationURL.isEmpty()) { - const QString DATA_SERVER_USERNAME_ENV = "HIFI_DS_USERNAME"; - const QString DATA_SERVER_PASSWORD_ENV = "HIFI_DS_PASSWORD"; + return true; + } else { + return false; + } +} + +bool DomainServer::readX509KeyAndCertificate() { + const QString X509_CERTIFICATE_OPTION = "cert"; + const QString X509_PRIVATE_KEY_OPTION = "key"; + const QString X509_KEY_PASSPHRASE_ENV = "DOMAIN_SERVER_KEY_PASSPHRASE"; + + QString certPath = _argumentVariantMap.value(X509_CERTIFICATE_OPTION).toString(); + QString keyPath = _argumentVariantMap.value(X509_PRIVATE_KEY_OPTION).toString(); + + 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); - // this node will be using an authentication server, let's make sure we have a username/password - QProcessEnvironment sysEnvironment = QProcessEnvironment::systemEnvironment(); + QString keyPassphraseString = QProcessEnvironment::systemEnvironment().value(X509_KEY_PASSPHRASE_ENV); - QString username = sysEnvironment.value(DATA_SERVER_USERNAME_ENV); - QString password = sysEnvironment.value(DATA_SERVER_PASSWORD_ENV); + qDebug() << "Reading certificate file at" << certPath << "for DTLS."; + qDebug() << "Reading key file at" << keyPath << "for DTLS."; - AccountManager& accountManager = AccountManager::getInstance(); - accountManager.setAuthURL(_nodeAuthenticationURL); + int gnutlsReturn = gnutls_certificate_set_x509_key_file2(*_x509Credentials, + certPath.toLocal8Bit().constData(), + keyPath.toLocal8Bit().constData(), + GNUTLS_X509_FMT_PEM, + keyPassphraseString.toLocal8Bit().constData(), + 0); - if (!username.isEmpty() && !password.isEmpty()) { - - connect(&accountManager, &AccountManager::loginComplete, this, &DomainServer::requestCreationFromDataServer); - - // ask the account manager to log us in from the env variables - accountManager.requestAccessToken(username, password); - } else { - qDebug() << "Authentication was requested against" << qPrintable(_nodeAuthenticationURL.toString()) - << "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV) - << "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Qutting!"; - - // bail out + 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; + return false; } - } else { - // auth is not requested for domain-server, setup NodeList and assignments now - setupNodeListAndAssignments(); + qDebug() << "Successfully read certificate and private key."; + + } else if (!certPath.isEmpty() || !keyPath.isEmpty()) { + qDebug() << "Missing certificate or private key. domain-server will now quit."; + QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection); + return false; } -} - -void DomainServer::requestCreationFromDataServer() { - // this slot is fired when we get a valid access token from the data-server - // now let's ask it to set us up with a UUID - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "processCreateResponseFromDataServer"; - AccountManager::getInstance().authenticatedRequest("/api/v1/domains/create", - QNetworkAccessManager::PostOperation, - callbackParams); -} - -void DomainServer::processCreateResponseFromDataServer(const QJsonObject& jsonObject) { - if (jsonObject["status"].toString() == "success") { - // pull out the UUID the data-server is telling us to use, and complete our setup with it - QUuid newSessionUUID = QUuid(jsonObject["data"].toObject()["uuid"].toString()); - setupNodeListAndAssignments(newSessionUUID); - } -} - -void DomainServer::processTokenRedeemResponse(const QJsonObject& jsonObject) { - // pull out the registration token this is associated with - QString registrationToken = jsonObject["data"].toObject()["registration_token"].toString(); - - // if we have a registration token add it to our hash of redeemed token responses - if (!registrationToken.isEmpty()) { - qDebug() << "Redeemed registration token" << registrationToken; - _redeemedTokenResponses.insert(registrationToken, jsonObject); - } + return true; } void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { - int argumentIndex = 0; - - const QString CUSTOM_PORT_OPTION = "-p"; + const QString CUSTOM_PORT_OPTION = "port"; unsigned short domainServerPort = DEFAULT_DOMAIN_SERVER_PORT; - if ((argumentIndex = _argumentList.indexOf(CUSTOM_PORT_OPTION)) != -1) { - domainServerPort = _argumentList.value(argumentIndex + 1).toUShort(); + if (_argumentVariantMap.contains(CUSTOM_PORT_OPTION)) { + domainServerPort = (unsigned short) _argumentVariantMap.value(CUSTOM_PORT_OPTION).toUInt(); } - QSet parsedTypes(QSet() << Assignment::AgentType); - parseCommandLineTypeConfigs(_argumentList, parsedTypes); + unsigned short domainServerDTLSPort = 0; - const QString CONFIG_FILE_OPTION = "--configFile"; - if ((argumentIndex = _argumentList.indexOf(CONFIG_FILE_OPTION)) != -1) { - QString configFilePath = _argumentList.value(argumentIndex + 1); - readConfigFile(configFilePath, parsedTypes); + if (_isUsingDTLS) { + domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT; + + const QString CUSTOM_DTLS_PORT_OPTION = "dtls-port"; + + if (_argumentVariantMap.contains(CUSTOM_DTLS_PORT_OPTION)) { + domainServerDTLSPort = (unsigned short) _argumentVariantMap.value(CUSTOM_DTLS_PORT_OPTION).toUInt(); + } } + QSet parsedTypes; + parseAssignmentConfigs(parsedTypes); + populateDefaultStaticAssignmentsExcludingTypes(parsedTypes); - NodeList* nodeList = NodeList::createInstance(NodeType::DomainServer, domainServerPort); + LimitedNodeList* nodeList = LimitedNodeList::createInstance(domainServerPort, domainServerDTLSPort); - // create a random UUID for this session for the domain-server - nodeList->setSessionUUID(sessionUUID); - - connect(nodeList, &NodeList::nodeAdded, this, &DomainServer::nodeAdded); - connect(nodeList, &NodeList::nodeKilled, this, &DomainServer::nodeKilled); + connect(nodeList, &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); + connect(nodeList, &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); QTimer* silentNodeTimer = new QTimer(this); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); connect(&nodeList->getNodeSocket(), SIGNAL(readyRead()), SLOT(readAvailableDatagrams())); @@ -160,137 +207,131 @@ void DomainServer::setupNodeListAndAssignments(const QUuid& sessionUUID) { addStaticAssignmentsToQueue(); } -void DomainServer::parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes) { +void DomainServer::parseAssignmentConfigs(QSet& excludedTypes) { // check for configs from the command line, these take precedence - const QString CONFIG_TYPE_OPTION = "--configType"; - int clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION); + const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)"; + QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING); - // enumerate all CL config overrides and parse them to files - while (clConfigIndex != -1) { - int clConfigType = argumentList.value(clConfigIndex + 1).toInt(); - if (clConfigType < Assignment::AllTypes && !excludedTypes.contains((Assignment::Type) clConfigIndex)) { - Assignment::Type assignmentType = (Assignment::Type) clConfigType; - createStaticAssignmentsForTypeGivenConfigString((Assignment::Type) assignmentType, - argumentList.value(clConfigIndex + 2)); - excludedTypes.insert(assignmentType); - } + // scan for assignment config keys + QStringList variantMapKeys = _argumentVariantMap.keys(); + int configIndex = variantMapKeys.indexOf(assignmentConfigRegex); + + while (configIndex != -1) { + // figure out which assignment type this matches + Assignment::Type assignmentType = (Assignment::Type) assignmentConfigRegex.cap(1).toInt(); - clConfigIndex = argumentList.indexOf(CONFIG_TYPE_OPTION, clConfigIndex + 1); - } -} - -// Attempts to read configuration from specified path -// returns true on success, false otherwise -void DomainServer::readConfigFile(const QString& path, QSet& excludedTypes) { - if (path.isEmpty()) { - // config file not specified - return; - } - - if (!QFile::exists(path)) { - qWarning("Specified configuration file does not exist!"); - return; - } - - QFile configFile(path); - if (!configFile.open(QIODevice::ReadOnly | QIODevice::Text)) { - qWarning("Can't open specified configuration file!"); - return; - } else { - qDebug() << "Reading configuration from" << path; - } - - QTextStream configStream(&configFile); - QByteArray configStringByteArray = configStream.readAll().toUtf8(); - QJsonObject configDocObject = QJsonDocument::fromJson(configStringByteArray).object(); - configFile.close(); - - QSet appendedExcludedTypes = excludedTypes; - - foreach (const QString& rootStringValue, configDocObject.keys()) { - int possibleConfigType = rootStringValue.toInt(); - - if (possibleConfigType < Assignment::AllTypes - && !excludedTypes.contains((Assignment::Type) possibleConfigType)) { - // this is an appropriate config type and isn't already in our excluded types - // we are good to parse it - Assignment::Type assignmentType = (Assignment::Type) possibleConfigType; - QString configString = readServerAssignmentConfig(configDocObject, rootStringValue); - createStaticAssignmentsForTypeGivenConfigString(assignmentType, configString); + if (assignmentType < Assignment::AllTypes && !excludedTypes.contains(assignmentType)) { + QVariant mapValue = _argumentVariantMap[variantMapKeys[configIndex]]; + QJsonArray assignmentArray; + + if (mapValue.type() == QVariant::String) { + QJsonDocument deserializedDocument = QJsonDocument::fromJson(mapValue.toString().toUtf8()); + assignmentArray = deserializedDocument.array(); + } else { + assignmentArray = mapValue.toJsonValue().toArray(); + } + + if (assignmentType != Assignment::AgentType) { + createStaticAssignmentsForType(assignmentType, assignmentArray); + } else { + createScriptedAssignmentsFromArray(assignmentArray); + } excludedTypes.insert(assignmentType); } + + configIndex = variantMapKeys.indexOf(assignmentConfigRegex, configIndex + 1); } } -// find assignment configurations on the specified node name and json object -// returns a string in the form of its equivalent cmd line params -QString DomainServer::readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName) { - QJsonArray nodeArray = jsonObject[nodeName].toArray(); - - QStringList serverConfig; - foreach (const QJsonValue& childValue, nodeArray) { - QString cmdParams; - QJsonObject childObject = childValue.toObject(); - QStringList keys = childObject.keys(); - for (int i = 0; i < keys.size(); i++) { - QString key = keys[i]; - QString value = childObject[key].toString(); - // both cmd line params and json keys are the same - cmdParams += QString("--%1 %2 ").arg(key, value); - } - serverConfig << cmdParams; - } - - // according to split() calls from DomainServer::prepopulateStaticAssignmentFile - // we shold simply join them with semicolons - return serverConfig.join(';'); -} - void DomainServer::addStaticAssignmentToAssignmentHash(Assignment* newAssignment) { qDebug() << "Inserting assignment" << *newAssignment << "to static assignment hash."; _staticAssignmentHash.insert(newAssignment->getUUID(), SharedAssignmentPointer(newAssignment)); } -void DomainServer::createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString) { - // we have a string for config for this type - qDebug() << "Parsing command line config for assignment type" << type; - - QStringList multiConfigList = configString.split(";", QString::SkipEmptyParts); - - const QString ASSIGNMENT_CONFIG_POOL_REGEX = "--pool\\s*([\\w-]+)"; - QRegExp poolRegex(ASSIGNMENT_CONFIG_POOL_REGEX); - - // read each config to a payload for this type of assignment - for (int i = 0; i < multiConfigList.size(); i++) { - QString config = multiConfigList.at(i); - - // check the config string for a pool - QString assignmentPool; - - int poolIndex = poolRegex.indexIn(config); - - if (poolIndex != -1) { - assignmentPool = poolRegex.cap(1); +void DomainServer::createScriptedAssignmentsFromArray(const QJsonArray &configArray) { + foreach(const QJsonValue& jsonValue, configArray) { + if (jsonValue.isObject()) { + QJsonObject jsonObject = jsonValue.toObject(); - // remove the pool from the config string, the assigned node doesn't need it - config.remove(poolIndex, poolRegex.matchedLength()); + // make sure we were passed a URL, otherwise this is an invalid scripted assignment + const QString ASSIGNMENT_URL_KEY = "url"; + QString assignmentURL = jsonObject[ASSIGNMENT_URL_KEY].toString(); + + if (!assignmentURL.isEmpty()) { + // check the json for a pool + const QString ASSIGNMENT_POOL_KEY = "pool"; + QString assignmentPool = jsonObject[ASSIGNMENT_POOL_KEY].toString(); + + // check for a number of instances, if not passed then default is 1 + const QString ASSIGNMENT_INSTANCES_KEY = "instances"; + int numInstances = jsonObject[ASSIGNMENT_INSTANCES_KEY].toInt(); + numInstances = (numInstances == 0 ? 1 : numInstances); + + for (int i = 0; i < numInstances; i++) { + // add a scripted assignment to the queue for this instance + Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, + Assignment::AgentType, + assignmentPool); + scriptAssignment->setPayload(assignmentURL.toUtf8()); + + qDebug() << "Adding scripted assignment to queue -" << *scriptAssignment; + qDebug() << "URL for script is" << assignmentURL; + + // scripts passed on CL or via JSON are static - so they are added back to the queue if the node dies + _assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment)); + } + } + } + } +} + +void DomainServer::createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray) { + // we have a string for config for this type + qDebug() << "Parsing config for assignment type" << type; + + int configCounter = 0; + + foreach(const QJsonValue& jsonValue, configArray) { + if (jsonValue.isObject()) { + QJsonObject jsonObject = jsonValue.toObject(); + + // check the config string for a pool + const QString ASSIGNMENT_POOL_KEY = "pool"; + QString assignmentPool; + + QJsonValue poolValue = jsonObject[ASSIGNMENT_POOL_KEY]; + if (!poolValue.isUndefined()) { + assignmentPool = poolValue.toString(); + + jsonObject.remove(ASSIGNMENT_POOL_KEY); + } + + ++configCounter; + qDebug() << "Type" << type << "config" << configCounter << "=" << jsonObject; + + Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool); + + // setup the payload as a semi-colon separated list of key = value + QStringList payloadStringList; + foreach(const QString& payloadKey, jsonObject.keys()) { + QString dashes = payloadKey.size() == 1 ? "-" : "--"; + payloadStringList << QString("%1%2 %3").arg(dashes).arg(payloadKey).arg(jsonObject[payloadKey].toString()); + } + + configAssignment->setPayload(payloadStringList.join(' ').toUtf8()); + + addStaticAssignmentToAssignmentHash(configAssignment); } - - qDebug("Type %d config[%d] = %s", type, i, config.toLocal8Bit().constData()); - - Assignment* configAssignment = new Assignment(Assignment::CreateCommand, type, assignmentPool); - - configAssignment->setPayload(config.toUtf8()); - - addStaticAssignmentToAssignmentHash(configAssignment); } } void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes) { // enumerate over all assignment types and see if we've already excluded it - for (int defaultedType = Assignment::AudioMixerType; defaultedType != Assignment::AllTypes; defaultedType++) { - if (!excludedTypes.contains((Assignment::Type) defaultedType)) { + for (Assignment::Type defaultedType = Assignment::AudioMixerType; + defaultedType != Assignment::AllTypes; + defaultedType = static_cast(static_cast(defaultedType) + 1)) { + if (!excludedTypes.contains(defaultedType) && defaultedType != Assignment::AgentType) { // type has not been set from a command line or config file config, use the default // by clearing whatever exists and writing a single default assignment with no payload Assignment* newAssignment = new Assignment(Assignment::CreateCommand, (Assignment::Type) defaultedType); @@ -299,28 +340,11 @@ void DomainServer::populateDefaultStaticAssignmentsExcludingTypes(const QSetgetNodeSocket().writeDatagram(authenticationRequestPacket, - senderSockAddr.getAddress(), senderSockAddr.getPort()); -} - const NodeSet STATICALLY_ASSIGNED_NODES = NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer << NodeType::VoxelServer << NodeType::ParticleServer << NodeType::MetavoxelServer; - -void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr, - const QJsonObject& authJsonObject) { +void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr) { NodeType_t nodeType; HifiSockAddr publicSockAddr, localSockAddr; @@ -328,35 +352,40 @@ void DomainServer::addNodeToNodeListAndConfirmConnection(const QByteArray& packe int numPreInterestBytes = parseNodeDataFromByteArray(nodeType, publicSockAddr, localSockAddr, packet, senderSockAddr); QUuid assignmentUUID = uuidFromPacketHeader(packet); - SharedAssignmentPointer matchingAssignment; + bool isStaticAssignment = _staticAssignmentHash.contains(assignmentUUID); + SharedAssignmentPointer matchingAssignment = SharedAssignmentPointer(); - if (!assignmentUUID.isNull() && (matchingAssignment = matchingStaticAssignmentForCheckIn(assignmentUUID, nodeType)) - && matchingAssignment) { - // this is an assigned node, make sure the UUID sent is for an assignment we're actually trying to give out + if (isStaticAssignment) { + // this is a static assignment, make sure the UUID sent is for an assignment we're actually trying to give out + matchingAssignment = matchingQueuedAssignmentForCheckIn(assignmentUUID, nodeType); - // remove the matching assignment from the assignment queue so we don't take the next check in - // (if it exists) - removeMatchingAssignmentFromQueue(matchingAssignment); + if (matchingAssignment) { + // remove the matching assignment from the assignment queue so we don't take the next check in + // (if it exists) + removeMatchingAssignmentFromQueue(matchingAssignment); + } } else { assignmentUUID = QUuid(); } - // create a new session UUID for this node - QUuid nodeUUID = QUuid::createUuid(); - - SharedNodePointer newNode = NodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, publicSockAddr, localSockAddr); - - // when the newNode is created the linked data is also created, if this was a static assignment set the UUID - reinterpret_cast(newNode->getLinkedData())->setStaticAssignmentUUID(assignmentUUID); - - if (!authJsonObject.isEmpty()) { - // pull the connection secret from the authJsonObject and set it as the connection secret for this node - QUuid connectionSecret(authJsonObject["data"].toObject()["connection_secret"].toString()); - newNode->setConnectionSecret(connectionSecret); + // make sure this was either not a static assignment or it was and we had a matching one in teh queue + if ((!isStaticAssignment && !STATICALLY_ASSIGNED_NODES.contains(nodeType)) || (isStaticAssignment && matchingAssignment)) { + // create a new session UUID for this node + QUuid nodeUUID = QUuid::createUuid(); + + SharedNodePointer newNode = LimitedNodeList::getInstance()->addOrUpdateNode(nodeUUID, nodeType, + publicSockAddr, localSockAddr); + + // when the newNode is created the linked data is also created + // if this was a static assignment set the UUID, set the sendingSockAddr + DomainServerNodeData* nodeData = reinterpret_cast(newNode->getLinkedData()); + + nodeData->setStaticAssignmentUUID(assignmentUUID); + nodeData->setSendingSockAddr(senderSockAddr); + + // reply back to the user with a PacketTypeDomainList + sendDomainListToNode(newNode, senderSockAddr, nodeInterestListFromPacket(packet, numPreInterestBytes)); } - - // reply back to the user with a PacketTypeDomainList - sendDomainListToNode(newNode, senderSockAddr, nodeInterestListFromPacket(packet, numPreInterestBytes)); } int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, @@ -365,18 +394,6 @@ int DomainServer::parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& QDataStream packetStream(packet); packetStream.skipRawData(numBytesForPacketHeader(packet)); - if (packetTypeForPacket(packet) == PacketTypeDomainConnectRequest) { - // we need to skip a quint8 that indicates if there is a registration token - // and potentially the registration token itself - quint8 hasRegistrationToken; - packetStream >> hasRegistrationToken; - - if (hasRegistrationToken) { - QByteArray registrationToken; - packetStream >> registrationToken; - } - } - packetStream >> nodeType; packetStream >> publicSockAddr >> localSockAddr; @@ -427,10 +444,13 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); - NodeList* nodeList = NodeList::getInstance(); - + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); if (nodeInterestList.size() > 0) { + + DTLSServerSession* dtlsSession = _isUsingDTLS ? _dtlsSessions[senderSockAddr] : NULL; + int dataMTU = dtlsSession ? (int)gnutls_dtls_get_data_mtu(*dtlsSession->getGnuTLSSession()) : MAX_PACKET_SIZE; + // if the node has any interest types, send back those nodes as well foreach (const SharedNodePointer& otherNode, nodeList->getNodeHash()) { @@ -460,11 +480,15 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif nodeDataStream << secretUUID; - if (broadcastPacket.size() + nodeByteArray.size() > MAX_PACKET_SIZE) { + if (broadcastPacket.size() + nodeByteArray.size() > dataMTU) { // we need to break here and start a new packet // so send the current one - nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + if (!dtlsSession) { + nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + } else { + dtlsSession->writeDatagram(broadcastPacket); + } // reset the broadcastPacket structure broadcastPacket.resize(numBroadcastPacketLeadBytes); @@ -475,78 +499,184 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif broadcastPacket.append(nodeByteArray); } } + + // always write the last broadcastPacket + if (!dtlsSession) { + nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); + } else { + dtlsSession->writeDatagram(broadcastPacket); + } } - - // always write the last broadcastPacket - nodeList->writeDatagram(broadcastPacket, node, senderSockAddr); } void DomainServer::readAvailableDatagrams() { - NodeList* nodeList = NodeList::getInstance(); - AccountManager& accountManager = AccountManager::getInstance(); + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); HifiSockAddr senderSockAddr; + QByteArray receivedPacket; static QByteArray assignmentPacket = byteArrayWithPopulatedHeader(PacketTypeCreateAssignment); static int numAssignmentPacketHeaderBytes = assignmentPacket.size(); - - QByteArray receivedPacket; while (nodeList->getNodeSocket().hasPendingDatagrams()) { receivedPacket.resize(nodeList->getNodeSocket().pendingDatagramSize()); nodeList->getNodeSocket().readDatagram(receivedPacket.data(), receivedPacket.size(), senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer()); - - if (nodeList->packetVersionAndHashMatch(receivedPacket)) { - PacketType requestType = packetTypeForPacket(receivedPacket); + if (packetTypeForPacket(receivedPacket) == PacketTypeRequestAssignment + && nodeList->packetVersionAndHashMatch(receivedPacket)) { + + // construct the requested assignment from the packet data + Assignment requestAssignment(receivedPacket); - if (requestType == PacketTypeDomainConnectRequest) { - QDataStream packetStream(receivedPacket); - packetStream.skipRawData(numBytesForPacketHeader(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) { + qDebug() << "Received a request for assignment type" << requestAssignment.getType() + << "from" << senderSockAddr; + noisyMessage = true; + } + + SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); + + if (assignmentToDeploy) { + qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << senderSockAddr; - quint8 hasRegistrationToken; - packetStream >> hasRegistrationToken; + // give this assignment out, either the type matches or the requestor said they will take any + assignmentPacket.resize(numAssignmentPacketHeaderBytes); - if (requiresAuthentication() && !hasRegistrationToken) { - // we need authentication and this node did not give us a registration token - tell it to auth - requestAuthenticationFromPotentialNode(senderSockAddr); - } else if (requiresAuthentication()) { - QByteArray registrationToken; - packetStream >> registrationToken; - - QString registrationTokenString(registrationToken.toHex()); - QJsonObject jsonForRedeemedToken = _redeemedTokenResponses.value(registrationTokenString); - - // check if we have redeemed this token and are ready to check the node in - if (jsonForRedeemedToken.isEmpty()) { - // make a request against the data-server to get information required to connect to this node - JSONCallbackParameters tokenCallbackParams; - tokenCallbackParams.jsonCallbackReceiver = this; - tokenCallbackParams.jsonCallbackMethod = "processTokenRedeemResponse"; - - QString redeemURLString = QString("/api/v1/nodes/redeem/%1.json").arg(registrationTokenString); - accountManager.authenticatedRequest(redeemURLString, QNetworkAccessManager::GetOperation, - tokenCallbackParams); - } else if (jsonForRedeemedToken["status"].toString() != "success") { - // we redeemed the token, but it was invalid - get the node to get another - requestAuthenticationFromPotentialNode(senderSockAddr); - } else { - // we've redeemed the token for this node and are ready to start communicating with it - // add the node to our NodeList - addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr, jsonForRedeemedToken); - } - - // if it exists, remove this response from the in-memory hash - _redeemedTokenResponses.remove(registrationTokenString); - - } else { - // we don't require authentication - add this node to our NodeList - // and send back session UUID right away - addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr); + QDataStream assignmentStream(&assignmentPacket, QIODevice::Append); + + assignmentStream << *assignmentToDeploy.data(); + + nodeList->getNodeSocket().writeDatagram(assignmentPacket, + senderSockAddr.getAddress(), senderSockAddr.getPort()); + } else { + if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { + qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() + << "from" << senderSockAddr; + noisyMessage = true; } + } + + if (noisyMessage) { + lastNoisyMessage = timeNow; + } + } else if (!_isUsingDTLS) { + // not using DTLS, process datagram normally + processDatagram(receivedPacket, senderSockAddr); + } else { + // we're using DTLS, so tell the sender to get back to us using DTLS + static QByteArray dtlsRequiredPacket = byteArrayWithPopulatedHeader(PacketTypeDomainServerRequireDTLS); + static int numBytesDTLSHeader = numBytesForPacketHeaderGivenPacketType(PacketTypeDomainServerRequireDTLS); + + if (dtlsRequiredPacket.size() == numBytesDTLSHeader) { + // pack the port that we accept DTLS traffic on + unsigned short dtlsPort = nodeList->getDTLSSocket().localPort(); + dtlsRequiredPacket.replace(numBytesDTLSHeader, sizeof(dtlsPort), reinterpret_cast(&dtlsPort)); + } + + nodeList->writeUnverifiedDatagram(dtlsRequiredPacket, senderSockAddr); + } + } +} + +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); + + 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()); - } else if (requestType == PacketTypeDomainListRequest) { - QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); + 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; + } + } + } 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()); + } 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(); + + gnutls_priority_set(*gnutlsSession, *_priorityCache); + gnutls_credentials_set(*gnutlsSession, GNUTLS_CRD_CERTIFICATE, *_x509Credentials); + gnutls_dtls_prestate_set(*gnutlsSession, &prestate); + + // handshake to begin the session + gnutls_handshake(*gnutlsSession); + + qDebug() << "Beginning DTLS session with node at" << senderHifiSockAddr; + _dtlsSessions[senderHifiSockAddr] = newServerSession; + } + } + } +} + +void DomainServer::processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr) { + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); + + if (nodeList->packetVersionAndHashMatch(receivedPacket)) { + PacketType requestType = packetTypeForPacket(receivedPacket); + + if (requestType == PacketTypeDomainConnectRequest) { + // add this node to our NodeList + // and send back session UUID right away + addNodeToNodeListAndConfirmConnection(receivedPacket, senderSockAddr); + } else if (requestType == PacketTypeDomainListRequest) { + QUuid nodeUUID = uuidFromPacketHeader(receivedPacket); + + if (!nodeUUID.isNull() && nodeList->nodeWithUUID(nodeUUID)) { NodeType_t throwawayNodeType; HifiSockAddr nodePublicAddress, nodeLocalAddress; @@ -554,60 +684,17 @@ void DomainServer::readAvailableDatagrams() { receivedPacket, senderSockAddr); SharedNodePointer checkInNode = nodeList->updateSocketsForNode(nodeUUID, nodePublicAddress, nodeLocalAddress); - + // update last receive to now quint64 timeNow = usecTimestampNow(); checkInNode->setLastHeardMicrostamp(timeNow); - - + sendDomainListToNode(checkInNode, senderSockAddr, nodeInterestListFromPacket(receivedPacket, numNodeInfoBytes)); - - } else if (requestType == PacketTypeRequestAssignment) { - - // construct the requested assignment from the packet data - 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) { - qDebug() << "Received a request for assignment type" << requestAssignment.getType() - << "from" << senderSockAddr; - noisyMessage = true; - } - - SharedAssignmentPointer assignmentToDeploy = deployableAssignmentForRequest(requestAssignment); - - if (assignmentToDeploy) { - qDebug() << "Deploying assignment -" << *assignmentToDeploy.data() << "- to" << senderSockAddr; - - // give this assignment out, either the type matches or the requestor said they will take any - assignmentPacket.resize(numAssignmentPacketHeaderBytes); - - QDataStream assignmentStream(&assignmentPacket, QIODevice::Append); - - assignmentStream << *assignmentToDeploy.data(); - - nodeList->getNodeSocket().writeDatagram(assignmentPacket, - senderSockAddr.getAddress(), senderSockAddr.getPort()); - } else { - if (requestAssignment.getType() != Assignment::AgentType || (timeNow - lastNoisyMessage) > NOISY_TIME_ELAPSED) { - qDebug() << "Unable to fulfill assignment request of type" << requestAssignment.getType() - << "from" << senderSockAddr; - noisyMessage = true; - } - } - - if (noisyMessage) { - lastNoisyMessage = timeNow; - } - } else if (requestType == PacketTypeNodeJsonStats) { - SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); - if (matchingNode) { - reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); - } + } + } else if (requestType == PacketTypeNodeJsonStats) { + SharedNodePointer matchingNode = nodeList->sendingNodeForPacket(receivedPacket); + if (matchingNode) { + reinterpret_cast(matchingNode->getLinkedData())->parseJSONStatsPacket(receivedPacket); } } } @@ -622,10 +709,12 @@ QJsonObject DomainServer::jsonForSocket(const HifiSockAddr& socket) { return socketJSON; } +const char JSON_KEY_UUID[] = "uuid"; 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_WAKE_TIMESTAMP[] = "wake_timestamp"; QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { QJsonObject nodeJson; @@ -635,6 +724,9 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { nodeTypeName = nodeTypeName.toLower(); nodeTypeName.replace(' ', '-'); + // add the node UUID + nodeJson[JSON_KEY_UUID] = uuidStringWithoutCurlyBraces(node->getUUID()); + // add the node type nodeJson[JSON_KEY_TYPE] = nodeTypeName; @@ -642,6 +734,9 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { nodeJson[JSON_KEY_PUBLIC_SOCKET] = jsonForSocket(node->getPublicSocket()); nodeJson[JSON_KEY_LOCAL_SOCKET] = jsonForSocket(node->getLocalSocket()); + // add the node uptime in our list + nodeJson[JSON_KEY_WAKE_TIMESTAMP] = QString::number(node->getWakeTimestamp()); + // if the node has pool information, add it SharedAssignmentPointer matchingAssignment = _staticAssignmentHash.value(node->getUUID()); if (matchingAssignment) { @@ -651,6 +746,16 @@ QJsonObject DomainServer::jsonObjectForNode(const SharedNodePointer& node) { return nodeJson; } +const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment"; + +QString pathForAssignmentScript(const QUuid& assignmentUUID) { + QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION); + newPath += "/"; + // append the UUID for this script as the new filename, remove the curly braces + newPath += uuidStringWithoutCurlyBraces(assignmentUUID); + return newPath; +} + bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url) { const QString JSON_MIME_TYPE = "application/json"; @@ -668,7 +773,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QJsonObject assignedNodesJSON; // enumerate the NodeList to find the assigned nodes - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { if (_staticAssignmentHash.value(node->getUUID())) { // add the node using the UUID as the key QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); @@ -707,18 +812,17 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url } else if (url.path() == QString("%1.json").arg(URI_NODES)) { // setup the JSON QJsonObject rootJSON; - QJsonObject nodesJSON; + QJsonArray nodesJSONArray; // enumerate the NodeList to find the assigned nodes - NodeList* nodeList = NodeList::getInstance(); + LimitedNodeList* nodeList = LimitedNodeList::getInstance(); foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { // add the node using the UUID as the key - QString uuidString = uuidStringWithoutCurlyBraces(node->getUUID()); - nodesJSON[uuidString] = jsonObjectForNode(node); + nodesJSONArray.append(jsonObjectForNode(node)); } - rootJSON["nodes"] = nodesJSON; + rootJSON["nodes"] = nodesJSONArray; // print out the created JSON QJsonDocument nodesDocument(rootJSON); @@ -735,7 +839,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url QUuid matchingUUID = QUuid(nodeShowRegex.cap(1)); // see if we have a node that matches this ID - SharedNodePointer matchingNode = NodeList::getInstance()->nodeWithUUID(matchingUUID); + SharedNodePointer matchingNode = LimitedNodeList::getInstance()->nodeWithUUID(matchingUUID); if (matchingNode) { // create a QJsonDocument with the stats QJsonObject QJsonObject statsObject = @@ -759,8 +863,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // this is a script upload - ask the HTTPConnection to parse the form data QList formData = connection->parseFormData(); - // check how many instances of this assignment the user wants by checking the ASSIGNMENT-INSTANCES header + // check optional headers for # of instances and pool const QString ASSIGNMENT_INSTANCES_HEADER = "ASSIGNMENT-INSTANCES"; + const QString ASSIGNMENT_POOL_HEADER = "ASSIGNMENT-POOL"; QByteArray assignmentInstancesValue = connection->requestHeaders().value(ASSIGNMENT_INSTANCES_HEADER.toLocal8Bit()); @@ -772,25 +877,30 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url numInstances = assignmentInstancesValue.toInt(); } - - const char ASSIGNMENT_SCRIPT_HOST_LOCATION[] = "resources/web/assignment"; + + QString assignmentPool = emptyPool; + QByteArray assignmentPoolValue = connection->requestHeaders().value(ASSIGNMENT_POOL_HEADER.toLocal8Bit()); + + if (!assignmentPoolValue.isEmpty()) { + // specific pool requested, set that on the created assignment + assignmentPool = QString(assignmentPoolValue); + } + for (int i = 0; i < numInstances; i++) { // create an assignment for this saved script - Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType); + Assignment* scriptAssignment = new Assignment(Assignment::CreateCommand, Assignment::AgentType, assignmentPool); - QString newPath(ASSIGNMENT_SCRIPT_HOST_LOCATION); - newPath += "/"; - // append the UUID for this script as the new filename, remove the curly braces - newPath += uuidStringWithoutCurlyBraces(scriptAssignment->getUUID()); + QString newPath = pathForAssignmentScript(scriptAssignment->getUUID()); - // create a file with the GUID of the assignment in the script host locaiton + // create a file with the GUID of the assignment in the script host location QFile scriptFile(newPath); scriptFile.open(QIODevice::WriteOnly); scriptFile.write(formData[0].second); - qDebug("Saved a script for assignment at %s", qPrintable(newPath)); + qDebug() << qPrintable(QString("Saved a script for assignment at %1%2") + .arg(newPath).arg(assignmentPool == emptyPool ? "" : " - pool is " + assignmentPool)); // add the script assigment to the assignment queue _assignmentQueue.enqueue(SharedAssignmentPointer(scriptAssignment)); @@ -814,14 +924,14 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url // pull the captured string, if it exists QUuid deleteUUID = QUuid(nodeDeleteRegex.cap(1)); - SharedNodePointer nodeToKill = NodeList::getInstance()->nodeWithUUID(deleteUUID); + SharedNodePointer nodeToKill = LimitedNodeList::getInstance()->nodeWithUUID(deleteUUID); if (nodeToKill) { // start with a 200 response connection->respond(HTTPConnection::StatusCode200); // we have a valid UUID and node - kill the node that has this assignment - QMetaObject::invokeMethod(NodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); + QMetaObject::invokeMethod(LimitedNodeList::getInstance(), "killNodeWithUUID", Q_ARG(const QUuid&, deleteUUID)); // successfully processed request return true; @@ -830,7 +940,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return true; } else if (allNodesDeleteRegex.indexIn(url.path()) != -1) { qDebug() << "Received request to kill all nodes."; - NodeList::getInstance()->eraseAllNodes(); + LimitedNodeList::getInstance()->eraseAllNodes(); return true; } @@ -847,6 +957,11 @@ void DomainServer::refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& qDebug() << "Reset UUID for assignment -" << *assignment.data() << "- and added to queue. Old UUID was" << uuidStringWithoutCurlyBraces(oldUUID); + if (assignment->getType() == Assignment::AgentType && assignment->getPayload().isEmpty()) { + // if this was an Agent without a script URL, we need to rename the old file so it can be retrieved at the new UUID + QFile::rename(pathForAssignmentScript(oldUUID), pathForAssignmentScript(assignment->getUUID())); + } + // add the static assignment back under the right UUID, and to the queue _staticAssignmentHash.insert(assignment->getUUID(), assignment); @@ -865,6 +980,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) { void DomainServer::nodeKilled(SharedNodePointer node) { DomainServerNodeData* nodeData = reinterpret_cast(node->getLinkedData()); + if (nodeData) { // if this node's UUID matches a static assignment we need to throw it back in the assignment queue if (!nodeData->getStaticAssignmentUUID().isNull()) { @@ -877,15 +993,23 @@ void DomainServer::nodeKilled(SharedNodePointer node) { // cleanup the connection secrets that we set up for this node (on the other nodes) foreach (const QUuid& otherNodeSessionUUID, nodeData->getSessionSecretHash().keys()) { - SharedNodePointer otherNode = NodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID); + SharedNodePointer otherNode = LimitedNodeList::getInstance()->nodeWithUUID(otherNodeSessionUUID); if (otherNode) { reinterpret_cast(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::matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) { +SharedAssignmentPointer DomainServer::matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType) { QQueue::iterator i = _assignmentQueue.begin(); while (i != _assignmentQueue.end()) { @@ -912,22 +1036,21 @@ SharedAssignmentPointer DomainServer::deployableAssignmentForRequest(const Assig bool assignmentPoolsMatch = assignment->getPool() == requestAssignment.getPool(); if ((requestIsAllTypes || assignmentTypesMatch) && (nietherHasPool || assignmentPoolsMatch)) { - - if (assignment->getType() == Assignment::AgentType) { - // if there is more than one instance to send out, simply decrease the number of instances - return _assignmentQueue.takeAt(sharedAssignment - _assignmentQueue.begin()); - } else { - // remove the assignment from the queue - SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment - - _assignmentQueue.begin()); - + + // remove the assignment from the queue + SharedAssignmentPointer deployableAssignment = _assignmentQueue.takeAt(sharedAssignment + - _assignmentQueue.begin()); + + if (deployableAssignment->getType() != Assignment::AgentType + || _staticAssignmentHash.contains(deployableAssignment->getUUID())) { + // this is a static assignment // until we get a check-in from that GUID // put assignment back in queue but stick it at the back so the others have a chance to go out _assignmentQueue.enqueue(deployableAssignment); - - // stop looping, we've handed out an assignment - return deployableAssignment; } + + // stop looping, we've handed out an assignment + return deployableAssignment; } else { // push forward the iterator to check the next assignment ++sharedAssignment; @@ -962,7 +1085,7 @@ void DomainServer::addStaticAssignmentsToQueue() { bool foundMatchingAssignment = false; // enumerate the nodes and check if there is one with an attached assignment with matching UUID - foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { + foreach (const SharedNodePointer& node, LimitedNodeList::getInstance()->getNodeHash()) { if (node->getUUID() == staticAssignment->data()->getUUID()) { foundMatchingAssignment = true; } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 597be7f50d..1bc9b71064 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -1,13 +1,16 @@ // // DomainServer.h -// hifi +// domain-server/src // // Created by Stephen Birarda on 9/26/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__DomainServer__ -#define __hifi__DomainServer__ +#ifndef hifi_DomainServer_h +#define hifi_DomainServer_h #include #include @@ -17,9 +20,13 @@ #include #include +#include + #include #include -#include +#include + +#include "DTLSServerSession.h" typedef QSharedPointer SharedAssignmentPointer; @@ -27,8 +34,7 @@ class DomainServer : public QCoreApplication, public HTTPRequestHandler { Q_OBJECT public: DomainServer(int argc, char* argv[]); - - bool requiresAuthentication() const { return !_nodeAuthenticationURL.isEmpty(); } + ~DomainServer(); bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url); @@ -40,26 +46,31 @@ public slots: /// Called by NodeList to inform us a node has been killed void nodeKilled(SharedNodePointer node); +private slots: + + void readAvailableDatagrams(); + void readAvailableDTLSDatagrams(); private: void setupNodeListAndAssignments(const QUuid& sessionUUID = QUuid::createUuid()); + bool optionallySetupDTLS(); + bool readX509KeyAndCertificate(); - void requestAuthenticationFromPotentialNode(const HifiSockAddr& senderSockAddr); - void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr, - const QJsonObject& authJsonObject = QJsonObject()); + void processDatagram(const QByteArray& receivedPacket, const HifiSockAddr& senderSockAddr); + + void addNodeToNodeListAndConfirmConnection(const QByteArray& packet, const HifiSockAddr& senderSockAddr); int parseNodeDataFromByteArray(NodeType_t& nodeType, HifiSockAddr& publicSockAddr, HifiSockAddr& localSockAddr, const QByteArray& packet, const HifiSockAddr& senderSockAddr); NodeSet nodeInterestListFromPacket(const QByteArray& packet, int numPreceedingBytes); void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr, const NodeSet& nodeInterestList); - void parseCommandLineTypeConfigs(const QStringList& argumentList, QSet& excludedTypes); - void readConfigFile(const QString& path, QSet& excludedTypes); - QString readServerAssignmentConfig(const QJsonObject& jsonObject, const QString& nodeName); + void parseAssignmentConfigs(QSet& excludedTypes); void addStaticAssignmentToAssignmentHash(Assignment* newAssignment); - void createStaticAssignmentsForTypeGivenConfigString(Assignment::Type type, const QString& configString); + void createScriptedAssignmentsFromArray(const QJsonArray& configArray); + void createStaticAssignmentsForType(Assignment::Type type, const QJsonArray& configArray); void populateDefaultStaticAssignmentsExcludingTypes(const QSet& excludedTypes); - SharedAssignmentPointer matchingStaticAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType); + SharedAssignmentPointer matchingQueuedAssignmentForCheckIn(const QUuid& checkInUUID, NodeType_t nodeType); SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment); void removeMatchingAssignmentFromQueue(const SharedAssignmentPointer& removableAssignment); void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment); @@ -73,17 +84,15 @@ private: QHash _staticAssignmentHash; QQueue _assignmentQueue; - QUrl _nodeAuthenticationURL; + QVariantMap _argumentVariantMap; - QStringList _argumentList; + bool _isUsingDTLS; + gnutls_certificate_credentials_t* _x509Credentials; + gnutls_dh_params_t* _dhParams; + gnutls_datum_t* _cookieKey; + gnutls_priority_t* _priorityCache; - QHash _redeemedTokenResponses; -private slots: - void requestCreationFromDataServer(); - void processCreateResponseFromDataServer(const QJsonObject& jsonObject); - void processTokenRedeemResponse(const QJsonObject& jsonObject); - - void readAvailableDatagrams(); + QHash _dtlsSessions; }; -#endif /* defined(__hifi__DomainServer__) */ +#endif // hifi_DomainServer_h diff --git a/domain-server/src/DomainServerNodeData.cpp b/domain-server/src/DomainServerNodeData.cpp index f1e08e3bc4..59d60659de 100644 --- a/domain-server/src/DomainServerNodeData.cpp +++ b/domain-server/src/DomainServerNodeData.cpp @@ -1,9 +1,12 @@ // // DomainServerNodeData.cpp -// hifi +// domain-server/src // // Created by Stephen Birarda on 2/6/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 @@ -17,7 +20,8 @@ DomainServerNodeData::DomainServerNodeData() : _sessionSecretHash(), _staticAssignmentUUID(), - _statsJSONObject() + _statsJSONObject(), + _sendingSockAddr() { } @@ -45,4 +49,4 @@ QJsonObject DomainServerNodeData::mergeJSONStatsFromNewObject(const QJsonObject& } return destinationObject; -} \ No newline at end of file +} diff --git a/domain-server/src/DomainServerNodeData.h b/domain-server/src/DomainServerNodeData.h index 20531839f4..6026e65f25 100644 --- a/domain-server/src/DomainServerNodeData.h +++ b/domain-server/src/DomainServerNodeData.h @@ -1,17 +1,21 @@ // // DomainServerNodeData.h -// hifi +// domain-server/src // // Created by Stephen Birarda on 2/6/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__DomainServerNodeData__ -#define __hifi__DomainServerNodeData__ +#ifndef hifi_DomainServerNodeData_h +#define hifi_DomainServerNodeData_h #include #include +#include #include class DomainServerNodeData : public NodeData { @@ -26,6 +30,9 @@ public: void setStaticAssignmentUUID(const QUuid& staticAssignmentUUID) { _staticAssignmentUUID = staticAssignmentUUID; } const QUuid& getStaticAssignmentUUID() const { return _staticAssignmentUUID; } + void setSendingSockAddr(const HifiSockAddr& sendingSockAddr) { _sendingSockAddr = sendingSockAddr; } + const HifiSockAddr& getSendingSockAddr() { return _sendingSockAddr; } + QHash& getSessionSecretHash() { return _sessionSecretHash; } private: QJsonObject mergeJSONStatsFromNewObject(const QJsonObject& newObject, QJsonObject destinationObject); @@ -33,6 +40,7 @@ private: QHash _sessionSecretHash; QUuid _staticAssignmentUUID; QJsonObject _statsJSONObject; + HifiSockAddr _sendingSockAddr; }; -#endif /* defined(__hifi__DomainServerNodeData__) */ +#endif // hifi_DomainServerNodeData_h diff --git a/domain-server/src/main.cpp b/domain-server/src/main.cpp index 970d1dad70..871c16a215 100644 --- a/domain-server/src/main.cpp +++ b/domain-server/src/main.cpp @@ -1,9 +1,9 @@ // // main.cpp -// Domain Server +// domain-server/src // // Created by Philip Rosedale on 11/20/12. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 High Fidelity, Inc. // // The Domain Server keeps a list of nodes that have connected to it, and echoes that list of // nodes out to nodes when they check in. @@ -11,19 +11,24 @@ // The connection is stateless... the domain server will set you inactive if it does not hear from // you in LOGOFF_CHECK_INTERVAL milliseconds, meaning your info will not be sent to other users. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// #include #include +#include #include "DomainServer.h" int main(int argc, char* argv[]) { + initialiseUsecTimestampNow(); #ifndef WIN32 setvbuf(stdout, NULL, _IOLBF, 0); #endif - + qInstallMessageHandler(Logging::verboseMessageHandler); DomainServer domainServer(argc, argv); diff --git a/examples/addVoxelOnMouseClickExample.js b/examples/addVoxelOnMouseClickExample.js index 244a017ae4..4fa175577c 100644 --- a/examples/addVoxelOnMouseClickExample.js +++ b/examples/addVoxelOnMouseClickExample.js @@ -1,13 +1,15 @@ // // addVoxelOnMouseClickExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/6/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Camera and Voxels class to implement // clicking on a voxel and adding a new voxel on the clicked on face // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // function mousePressEvent(event) { diff --git a/examples/audioBall.js b/examples/audioBall.js index 0889d9eb31..2a77a0999f 100644 --- a/examples/audioBall.js +++ b/examples/audioBall.js @@ -1,14 +1,17 @@ // // audioBall.js -// hifi +// examples // // Created by Athanasios Gaitatzes on 2/10/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This script creates a particle in front of the user that stays in front of // the user's avatar as they move, and animates it's radius and color // in response to the audio intensity. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var sound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/mexicanWhipoorwill.raw"); var CHANCE_OF_PLAYING_SOUND = 0.01; diff --git a/examples/audioDeviceExample.js b/examples/audioDeviceExample.js index 1ee00a1582..959b21f848 100644 --- a/examples/audioDeviceExample.js +++ b/examples/audioDeviceExample.js @@ -1,13 +1,15 @@ // // audioDeviceExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 3/22/14 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // This is an example script that demonstrates use of the Menu object // - +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var outputDevices = AudioDevice.getOutputDevices(); var defaultOutputDevice = AudioDevice.getDefaultOutputDevice(); diff --git a/examples/audioReflectorTools.js b/examples/audioReflectorTools.js new file mode 100644 index 0000000000..f299407e54 --- /dev/null +++ b/examples/audioReflectorTools.js @@ -0,0 +1,843 @@ +// +// audioReflectorTools.js +// hifi +// +// Created by Brad Hefta-Gaub on 2/14/14. +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// Tools for manipulating the attributes of the AudioReflector behavior +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + + +var delayScale = 100.0; +var fanoutScale = 10.0; +var speedScale = 20; +var factorScale = 5.0; +var localFactorScale = 1.0; +var reflectiveScale = 100.0; +var diffusionScale = 100.0; +var absorptionScale = 100.0; +var combFilterScale = 50.0; +var originalScale = 2.0; +var echoesScale = 2.0; + +// these three properties are bound together, if you change one, the others will also change +var reflectiveRatio = AudioReflector.getReflectiveRatio(); +var diffusionRatio = AudioReflector.getDiffusionRatio(); +var absorptionRatio = AudioReflector.getAbsorptionRatio(); + +var reflectiveThumbX; +var diffusionThumbX; +var absorptionThumbX; + +function setReflectiveRatio(reflective) { + var total = diffusionRatio + absorptionRatio + (reflective / reflectiveScale); + diffusionRatio = diffusionRatio / total; + absorptionRatio = absorptionRatio / total; + reflectiveRatio = (reflective / reflectiveScale) / total; + updateRatioValues(); +} + +function setDiffusionRatio(diffusion) { + var total = (diffusion / diffusionScale) + absorptionRatio + reflectiveRatio; + diffusionRatio = (diffusion / diffusionScale) / total; + absorptionRatio = absorptionRatio / total; + reflectiveRatio = reflectiveRatio / total; + updateRatioValues(); +} + +function setAbsorptionRatio(absorption) { + var total = diffusionRatio + (absorption / absorptionScale) + reflectiveRatio; + diffusionRatio = diffusionRatio / total; + absorptionRatio = (absorption / absorptionScale) / total; + reflectiveRatio = reflectiveRatio / total; + updateRatioValues(); +} + +function updateRatioSliders() { + reflectiveThumbX = reflectiveMinThumbX + ((reflectiveMaxThumbX - reflectiveMinThumbX) * reflectiveRatio); + diffusionThumbX = diffusionMinThumbX + ((diffusionMaxThumbX - diffusionMinThumbX) * diffusionRatio); + absorptionThumbX = absorptionMinThumbX + ((absorptionMaxThumbX - absorptionMinThumbX) * absorptionRatio); + + Overlays.editOverlay(reflectiveThumb, { x: reflectiveThumbX } ); + Overlays.editOverlay(diffusionThumb, { x: diffusionThumbX } ); + Overlays.editOverlay(absorptionThumb, { x: absorptionThumbX } ); +} + +function updateRatioValues() { + AudioReflector.setReflectiveRatio(reflectiveRatio); + AudioReflector.setDiffusionRatio(diffusionRatio); + AudioReflector.setAbsorptionRatio(absorptionRatio); +} + +var topY = 250; +var sliderHeight = 35; + +var delayY = topY; +topY += sliderHeight; +var delayLabel = Overlays.addOverlay("text", { + x: 40, + y: delayY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 12, + leftMargin: 5, + text: "Delay:" + }); + +var delaySlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: delayY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var delayMinThumbX = 110; +var delayMaxThumbX = delayMinThumbX + 110; +var delayThumbX = delayMinThumbX + ((delayMaxThumbX - delayMinThumbX) * (AudioReflector.getPreDelay() / delayScale)); +var delayThumb = Overlays.addOverlay("image", { + x: delayThumbX, + y: delayY + 9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 255, green: 0, blue: 0}, + alpha: 1 + }); + +var fanoutY = topY; +topY += sliderHeight; + +var fanoutLabel = Overlays.addOverlay("text", { + x: 40, + y: fanoutY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 12, + leftMargin: 5, + text: "Fanout:" + }); + +var fanoutSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: fanoutY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var fanoutMinThumbX = 110; +var fanoutMaxThumbX = fanoutMinThumbX + 110; +var fanoutThumbX = fanoutMinThumbX + ((fanoutMaxThumbX - fanoutMinThumbX) * (AudioReflector.getDiffusionFanout() / fanoutScale)); +var fanoutThumb = Overlays.addOverlay("image", { + x: fanoutThumbX, + y: fanoutY + 9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 255, green: 255, blue: 0}, + alpha: 1 + }); + + +var speedY = topY; +topY += sliderHeight; + +var speedLabel = Overlays.addOverlay("text", { + x: 40, + y: speedY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Speed\nin ms/m:" + }); + +var speedSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: speedY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var speedMinThumbX = 110; +var speedMaxThumbX = speedMinThumbX + 110; +var speedThumbX = speedMinThumbX + ((speedMaxThumbX - speedMinThumbX) * (AudioReflector.getSoundMsPerMeter() / speedScale)); +var speedThumb = Overlays.addOverlay("image", { + x: speedThumbX, + y: speedY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 0, green: 255, blue: 0}, + alpha: 1 + }); + +var factorY = topY; +topY += sliderHeight; + +var factorLabel = Overlays.addOverlay("text", { + x: 40, + y: factorY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Attenuation\nFactor:" + }); + + +var factorSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: factorY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var factorMinThumbX = 110; +var factorMaxThumbX = factorMinThumbX + 110; +var factorThumbX = factorMinThumbX + ((factorMaxThumbX - factorMinThumbX) * (AudioReflector.getDistanceAttenuationScalingFactor() / factorScale)); +var factorThumb = Overlays.addOverlay("image", { + x: factorThumbX, + y: factorY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 0, green: 0, blue: 255}, + alpha: 1 + }); + +var localFactorY = topY; +topY += sliderHeight; + +var localFactorLabel = Overlays.addOverlay("text", { + x: 40, + y: localFactorY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Local\nFactor:" + }); + + +var localFactorSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: localFactorY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var localFactorMinThumbX = 110; +var localFactorMaxThumbX = localFactorMinThumbX + 110; +var localFactorThumbX = localFactorMinThumbX + ((localFactorMaxThumbX - localFactorMinThumbX) * (AudioReflector.getLocalAudioAttenuationFactor() / localFactorScale)); +var localFactorThumb = Overlays.addOverlay("image", { + x: localFactorThumbX, + y: localFactorY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 0, green: 128, blue: 128}, + alpha: 1 + }); + +var combFilterY = topY; +topY += sliderHeight; + +var combFilterLabel = Overlays.addOverlay("text", { + x: 40, + y: combFilterY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Comb Filter\nWindow:" + }); + + +var combFilterSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: combFilterY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var combFilterMinThumbX = 110; +var combFilterMaxThumbX = combFilterMinThumbX + 110; +var combFilterThumbX = combFilterMinThumbX + ((combFilterMaxThumbX - combFilterMinThumbX) * (AudioReflector.getCombFilterWindow() / combFilterScale)); +var combFilterThumb = Overlays.addOverlay("image", { + x: combFilterThumbX, + y: combFilterY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 128, green: 128, blue: 0}, + alpha: 1 + }); + + +var reflectiveY = topY; +topY += sliderHeight; + +var reflectiveLabel = Overlays.addOverlay("text", { + x: 40, + y: reflectiveY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Reflective\nRatio:" + }); + + +var reflectiveSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: reflectiveY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var reflectiveMinThumbX = 110; +var reflectiveMaxThumbX = reflectiveMinThumbX + 110; +reflectiveThumbX = reflectiveMinThumbX + ((reflectiveMaxThumbX - reflectiveMinThumbX) * AudioReflector.getReflectiveRatio()); +var reflectiveThumb = Overlays.addOverlay("image", { + x: reflectiveThumbX, + y: reflectiveY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + +var diffusionY = topY; +topY += sliderHeight; + +var diffusionLabel = Overlays.addOverlay("text", { + x: 40, + y: diffusionY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Diffusion\nRatio:" + }); + + +var diffusionSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: diffusionY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var diffusionMinThumbX = 110; +var diffusionMaxThumbX = diffusionMinThumbX + 110; +diffusionThumbX = diffusionMinThumbX + ((diffusionMaxThumbX - diffusionMinThumbX) * AudioReflector.getDiffusionRatio()); +var diffusionThumb = Overlays.addOverlay("image", { + x: diffusionThumbX, + y: diffusionY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 0, green: 255, blue: 255}, + alpha: 1 + }); + +var absorptionY = topY; +topY += sliderHeight; + +var absorptionLabel = Overlays.addOverlay("text", { + x: 40, + y: absorptionY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Absorption\nRatio:" + }); + + +var absorptionSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: absorptionY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var absorptionMinThumbX = 110; +var absorptionMaxThumbX = absorptionMinThumbX + 110; +absorptionThumbX = absorptionMinThumbX + ((absorptionMaxThumbX - absorptionMinThumbX) * AudioReflector.getAbsorptionRatio()); +var absorptionThumb = Overlays.addOverlay("image", { + x: absorptionThumbX, + y: absorptionY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 255, green: 0, blue: 255}, + alpha: 1 + }); + +var originalY = topY; +topY += sliderHeight; + +var originalLabel = Overlays.addOverlay("text", { + x: 40, + y: originalY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Original\nMix:" + }); + + +var originalSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: originalY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var originalMinThumbX = 110; +var originalMaxThumbX = originalMinThumbX + 110; +var originalThumbX = originalMinThumbX + ((originalMaxThumbX - originalMinThumbX) * (AudioReflector.getOriginalSourceAttenuation() / originalScale)); +var originalThumb = Overlays.addOverlay("image", { + x: originalThumbX, + y: originalY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 128, green: 128, blue: 0}, + alpha: 1 + }); + +var echoesY = topY; +topY += sliderHeight; + +var echoesLabel = Overlays.addOverlay("text", { + x: 40, + y: echoesY, + width: 60, + height: sliderHeight, + color: { red: 0, green: 0, blue: 0}, + textColor: { red: 255, green: 255, blue: 255}, + topMargin: 6, + leftMargin: 5, + text: "Echoes\nMix:" + }); + + +var echoesSlider = Overlays.addOverlay("image", { + // alternate form of expressing bounds + bounds: { x: 100, y: echoesY, width: 150, height: sliderHeight}, + subImage: { x: 46, y: 0, width: 200, height: 71 }, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/slider.png", + color: { red: 255, green: 255, blue: 255}, + alpha: 1 + }); + + +var echoesMinThumbX = 110; +var echoesMaxThumbX = echoesMinThumbX + 110; +var echoesThumbX = echoesMinThumbX + ((echoesMaxThumbX - echoesMinThumbX) * (AudioReflector.getEchoesAttenuation() / echoesScale)); +var echoesThumb = Overlays.addOverlay("image", { + x: echoesThumbX, + y: echoesY+9, + width: 18, + height: 17, + imageURL: "https://s3-us-west-1.amazonaws.com/highfidelity-public/images/thumb.png", + color: { red: 128, green: 128, blue: 0}, + alpha: 1 + }); + + +// When our script shuts down, we should clean up all of our overlays +function scriptEnding() { + Overlays.deleteOverlay(factorLabel); + Overlays.deleteOverlay(factorThumb); + Overlays.deleteOverlay(factorSlider); + + Overlays.deleteOverlay(combFilterLabel); + Overlays.deleteOverlay(combFilterThumb); + Overlays.deleteOverlay(combFilterSlider); + + Overlays.deleteOverlay(localFactorLabel); + Overlays.deleteOverlay(localFactorThumb); + Overlays.deleteOverlay(localFactorSlider); + + Overlays.deleteOverlay(speedLabel); + Overlays.deleteOverlay(speedThumb); + Overlays.deleteOverlay(speedSlider); + + Overlays.deleteOverlay(delayLabel); + Overlays.deleteOverlay(delayThumb); + Overlays.deleteOverlay(delaySlider); + + Overlays.deleteOverlay(fanoutLabel); + Overlays.deleteOverlay(fanoutThumb); + Overlays.deleteOverlay(fanoutSlider); + + Overlays.deleteOverlay(reflectiveLabel); + Overlays.deleteOverlay(reflectiveThumb); + Overlays.deleteOverlay(reflectiveSlider); + + Overlays.deleteOverlay(diffusionLabel); + Overlays.deleteOverlay(diffusionThumb); + Overlays.deleteOverlay(diffusionSlider); + + Overlays.deleteOverlay(absorptionLabel); + Overlays.deleteOverlay(absorptionThumb); + Overlays.deleteOverlay(absorptionSlider); + + Overlays.deleteOverlay(echoesLabel); + Overlays.deleteOverlay(echoesThumb); + Overlays.deleteOverlay(echoesSlider); + + Overlays.deleteOverlay(originalLabel); + Overlays.deleteOverlay(originalThumb); + Overlays.deleteOverlay(originalSlider); + +} +Script.scriptEnding.connect(scriptEnding); + + +var count = 0; + +// Our update() function is called at approximately 60fps, and we will use it to animate our various overlays +function update(deltaTime) { + count++; +} +Script.update.connect(update); + + +// The slider is handled in the mouse event callbacks. +var movingSliderDelay = false; +var movingSliderFanout = false; +var movingSliderSpeed = false; +var movingSliderFactor = false; +var movingSliderCombFilter = false; +var movingSliderLocalFactor = false; +var movingSliderReflective = false; +var movingSliderDiffusion = false; +var movingSliderAbsorption = false; +var movingSliderOriginal = false; +var movingSliderEchoes = false; + +var thumbClickOffsetX = 0; +function mouseMoveEvent(event) { + if (movingSliderDelay) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < delayMinThumbX) { + newThumbX = delayMinThumbX; + } + if (newThumbX > delayMaxThumbX) { + newThumbX = delayMaxThumbX; + } + Overlays.editOverlay(delayThumb, { x: newThumbX } ); + var delay = ((newThumbX - delayMinThumbX) / (delayMaxThumbX - delayMinThumbX)) * delayScale; + AudioReflector.setPreDelay(delay); + } + if (movingSliderFanout) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < fanoutMinThumbX) { + newThumbX = fanoutMinThumbX; + } + if (newThumbX > fanoutMaxThumbX) { + newThumbX = fanoutMaxThumbX; + } + Overlays.editOverlay(fanoutThumb, { x: newThumbX } ); + var fanout = Math.round(((newThumbX - fanoutMinThumbX) / (fanoutMaxThumbX - fanoutMinThumbX)) * fanoutScale); + AudioReflector.setDiffusionFanout(fanout); + } + if (movingSliderSpeed) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < speedMinThumbX) { + newThumbX = speedMminThumbX; + } + if (newThumbX > speedMaxThumbX) { + newThumbX = speedMaxThumbX; + } + Overlays.editOverlay(speedThumb, { x: newThumbX } ); + var speed = ((newThumbX - speedMinThumbX) / (speedMaxThumbX - speedMinThumbX)) * speedScale; + AudioReflector.setSoundMsPerMeter(speed); + } + if (movingSliderFactor) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < factorMinThumbX) { + newThumbX = factorMminThumbX; + } + if (newThumbX > factorMaxThumbX) { + newThumbX = factorMaxThumbX; + } + Overlays.editOverlay(factorThumb, { x: newThumbX } ); + var factor = ((newThumbX - factorMinThumbX) / (factorMaxThumbX - factorMinThumbX)) * factorScale; + AudioReflector.setDistanceAttenuationScalingFactor(factor); + } + if (movingSliderCombFilter) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < combFilterMinThumbX) { + newThumbX = combFilterMminThumbX; + } + if (newThumbX > combFilterMaxThumbX) { + newThumbX = combFilterMaxThumbX; + } + Overlays.editOverlay(combFilterThumb, { x: newThumbX } ); + var combFilter = ((newThumbX - combFilterMinThumbX) / (combFilterMaxThumbX - combFilterMinThumbX)) * combFilterScale; + AudioReflector.setCombFilterWindow(combFilter); + } + if (movingSliderLocalFactor) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < localFactorMinThumbX) { + newThumbX = localFactorMminThumbX; + } + if (newThumbX > localFactorMaxThumbX) { + newThumbX = localFactorMaxThumbX; + } + Overlays.editOverlay(localFactorThumb, { x: newThumbX } ); + var localFactor = ((newThumbX - localFactorMinThumbX) / (localFactorMaxThumbX - localFactorMinThumbX)) * localFactorScale; + AudioReflector.setLocalAudioAttenuationFactor(localFactor); + } + + if (movingSliderAbsorption) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < absorptionMinThumbX) { + newThumbX = absorptionMminThumbX; + } + if (newThumbX > absorptionMaxThumbX) { + newThumbX = absorptionMaxThumbX; + } + Overlays.editOverlay(absorptionThumb, { x: newThumbX } ); + var absorption = ((newThumbX - absorptionMinThumbX) / (absorptionMaxThumbX - absorptionMinThumbX)) * absorptionScale; + setAbsorptionRatio(absorption); + } + + if (movingSliderReflective) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < reflectiveMinThumbX) { + newThumbX = reflectiveMminThumbX; + } + if (newThumbX > reflectiveMaxThumbX) { + newThumbX = reflectiveMaxThumbX; + } + Overlays.editOverlay(reflectiveThumb, { x: newThumbX } ); + var reflective = ((newThumbX - reflectiveMinThumbX) / (reflectiveMaxThumbX - reflectiveMinThumbX)) * reflectiveScale; + setReflectiveRatio(reflective); + } + + if (movingSliderDiffusion) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < diffusionMinThumbX) { + newThumbX = diffusionMminThumbX; + } + if (newThumbX > diffusionMaxThumbX) { + newThumbX = diffusionMaxThumbX; + } + Overlays.editOverlay(diffusionThumb, { x: newThumbX } ); + var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale; + setDiffusionRatio(diffusion); + } + if (movingSliderEchoes) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < echoesMinThumbX) { + newThumbX = echoesMminThumbX; + } + if (newThumbX > echoesMaxThumbX) { + newThumbX = echoesMaxThumbX; + } + Overlays.editOverlay(echoesThumb, { x: newThumbX } ); + var echoes = ((newThumbX - echoesMinThumbX) / (echoesMaxThumbX - echoesMinThumbX)) * echoesScale; + AudioReflector.setEchoesAttenuation(echoes); + } + if (movingSliderOriginal) { + newThumbX = event.x - thumbClickOffsetX; + if (newThumbX < originalMinThumbX) { + newThumbX = originalMminThumbX; + } + if (newThumbX > originalMaxThumbX) { + newThumbX = originalMaxThumbX; + } + Overlays.editOverlay(originalThumb, { x: newThumbX } ); + var original = ((newThumbX - originalMinThumbX) / (originalMaxThumbX - originalMinThumbX)) * originalScale; + AudioReflector.setOriginalSourceAttenuation(original); + } + +} + +// we also handle click detection in our mousePressEvent() +function mousePressEvent(event) { + var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); + if (clickedOverlay == delayThumb) { + movingSliderDelay = true; + thumbClickOffsetX = event.x - delayThumbX; + } + if (clickedOverlay == fanoutThumb) { + movingSliderFanout = true; + thumbClickOffsetX = event.x - fanoutThumbX; + } + if (clickedOverlay == speedThumb) { + movingSliderSpeed = true; + thumbClickOffsetX = event.x - speedThumbX; + } + if (clickedOverlay == factorThumb) { + movingSliderFactor = true; + thumbClickOffsetX = event.x - factorThumbX; + } + if (clickedOverlay == localFactorThumb) { + movingSliderLocalFactor = true; + thumbClickOffsetX = event.x - localFactorThumbX; + } + if (clickedOverlay == combFilterThumb) { + movingSliderCombFilter = true; + thumbClickOffsetX = event.x - combFilterThumbX; + } + if (clickedOverlay == diffusionThumb) { + movingSliderDiffusion = true; + thumbClickOffsetX = event.x - diffusionThumbX; + } + if (clickedOverlay == absorptionThumb) { + movingSliderAbsorption = true; + thumbClickOffsetX = event.x - absorptionThumbX; + } + if (clickedOverlay == reflectiveThumb) { + movingSliderReflective = true; + thumbClickOffsetX = event.x - reflectiveThumbX; + } + if (clickedOverlay == originalThumb) { + movingSliderOriginal = true; + thumbClickOffsetX = event.x - originalThumbX; + } + if (clickedOverlay == echoesThumb) { + movingSliderEchoes = true; + thumbClickOffsetX = event.x - echoesThumbX; + } +} + +function mouseReleaseEvent(event) { + if (movingSliderDelay) { + movingSliderDelay = false; + var delay = ((newThumbX - delayMinThumbX) / (delayMaxThumbX - delayMinThumbX)) * delayScale; + AudioReflector.setPreDelay(delay); + delayThumbX = newThumbX; + } + if (movingSliderFanout) { + movingSliderFanout = false; + var fanout = Math.round(((newThumbX - fanoutMinThumbX) / (fanoutMaxThumbX - fanoutMinThumbX)) * fanoutScale); + AudioReflector.setDiffusionFanout(fanout); + fanoutThumbX = newThumbX; + } + if (movingSliderSpeed) { + movingSliderSpeed = false; + var speed = ((newThumbX - speedMinThumbX) / (speedMaxThumbX - speedMinThumbX)) * speedScale; + AudioReflector.setSoundMsPerMeter(speed); + speedThumbX = newThumbX; + } + if (movingSliderFactor) { + movingSliderFactor = false; + var factor = ((newThumbX - factorMinThumbX) / (factorMaxThumbX - factorMinThumbX)) * factorScale; + AudioReflector.setDistanceAttenuationScalingFactor(factor); + factorThumbX = newThumbX; + } + if (movingSliderCombFilter) { + movingSliderCombFilter = false; + var combFilter = ((newThumbX - combFilterMinThumbX) / (combFilterMaxThumbX - combFilterMinThumbX)) * combFilterScale; + AudioReflector.setCombFilterWindow(combFilter); + combFilterThumbX = newThumbX; + } + if (movingSliderLocalFactor) { + movingSliderLocalFactor = false; + var localFactor = ((newThumbX - localFactorMinThumbX) / (localFactorMaxThumbX - localFactorMinThumbX)) * localFactorScale; + AudioReflector.setLocalAudioAttenuationFactor(localFactor); + localFactorThumbX = newThumbX; + } + if (movingSliderReflective) { + movingSliderReflective = false; + var reflective = ((newThumbX - reflectiveMinThumbX) / (reflectiveMaxThumbX - reflectiveMinThumbX)) * reflectiveScale; + setReflectiveRatio(reflective); + reflectiveThumbX = newThumbX; + updateRatioSliders(); + } + if (movingSliderDiffusion) { + movingSliderDiffusion = false; + var diffusion = ((newThumbX - diffusionMinThumbX) / (diffusionMaxThumbX - diffusionMinThumbX)) * diffusionScale; + setDiffusionRatio(diffusion); + diffusionThumbX = newThumbX; + updateRatioSliders(); + } + if (movingSliderAbsorption) { + movingSliderAbsorption = false; + var absorption = ((newThumbX - absorptionMinThumbX) / (absorptionMaxThumbX - absorptionMinThumbX)) * absorptionScale; + setAbsorptionRatio(absorption); + absorptionThumbX = newThumbX; + updateRatioSliders(); + } + if (movingSliderEchoes) { + movingSliderEchoes = false; + var echoes = ((newThumbX - echoesMinThumbX) / (echoesMaxThumbX - echoesMinThumbX)) * echoesScale; + AudioReflector.setEchoesAttenuation(echoes); + echoesThumbX = newThumbX; + } + if (movingSliderOriginal) { + movingSliderOriginal = false; + var original = ((newThumbX - originalMinThumbX) / (originalMaxThumbX - originalMinThumbX)) * originalScale; + AudioReflector.setOriginalSourceAttenuation(original); + originalThumbX = newThumbX; + } +} + +Controller.mouseMoveEvent.connect(mouseMoveEvent); +Controller.mousePressEvent.connect(mousePressEvent); +Controller.mouseReleaseEvent.connect(mouseReleaseEvent); + diff --git a/examples/avatarCollision.js b/examples/avatarCollision.js new file mode 100644 index 0000000000..5ade894365 --- /dev/null +++ b/examples/avatarCollision.js @@ -0,0 +1,68 @@ +// +// avatarCollision.js +// examples +// +// Created by Andrew Meadows on 2014-04-09 +// Copyright 2014 High Fidelity, Inc. +// +// Play a sound on collisions with your avatar +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var SOUND_TRIGGER_CLEAR = 1000; // milliseconds +var SOUND_TRIGGER_DELAY = 200; // milliseconds +var soundExpiry = 0; +var DateObj = new Date(); +var audioOptions = new AudioInjectionOptions(); +audioOptions.volume = 0.5; +audioOptions.position = { x: 0, y: 0, z: 0 }; + +var hitSounds = new Array(); +hitSounds[0] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit1.raw"); +hitSounds[1] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit2.raw"); +hitSounds[2] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit3.raw"); +hitSounds[3] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit4.raw"); +hitSounds[4] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit5.raw"); +hitSounds[5] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit6.raw"); +hitSounds[6] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit7.raw"); +hitSounds[7] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit8.raw"); +hitSounds[8] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit9.raw"); +hitSounds[9] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit10.raw"); +hitSounds[10] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit11.raw"); +hitSounds[11] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit12.raw"); +hitSounds[12] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit13.raw"); +hitSounds[13] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit14.raw"); +hitSounds[14] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit15.raw"); +hitSounds[15] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit16.raw"); +hitSounds[16] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit17.raw"); +hitSounds[17] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit18.raw"); +hitSounds[18] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit19.raw"); +hitSounds[19] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit20.raw"); +hitSounds[20] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit21.raw"); +hitSounds[21] = new Sound("http://highfidelity-public.s3-us-west-1.amazonaws.com/sounds/Collisions-hitsandslaps/Hit22.raw"); + +function playHitSound(mySessionID, theirSessionID, collision) { + var now = new Date(); + var msec = now.getTime(); + if (msec > soundExpiry) { + // this is a new contact --> play a new sound + var soundIndex = Math.floor((Math.random() * hitSounds.length) % hitSounds.length); + audioOptions.position = collision.contactPoint; + Audio.playSound(hitSounds[soundIndex], audioOptions); + + // bump the expiry + soundExpiry = msec + SOUND_TRIGGER_CLEAR; + + // log the collision info + Uuid.print("my sessionID = ", mySessionID); + Uuid.print(" their sessionID = ", theirSessionID); + Vec3.print(" penetration = ", collision.penetration); + Vec3.print(" contactPoint = ", collision.contactPoint); + } else { + // this is a recurring contact --> continue to delay sound trigger + soundExpiry = msec + SOUND_TRIGGER_DELAY; + } +} +MyAvatar.collisionWithAvatar.connect(playHitSound); diff --git a/examples/bot.js b/examples/bot.js index ccbc24bc3d..f7a0429c53 100644 --- a/examples/bot.js +++ b/examples/bot.js @@ -1,13 +1,15 @@ // // bot.js -// hifi +// examples // // Created by Stephen Birarda on 2/20/14. // Modified by Philip on 3/3/14 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates an NPC avatar. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // function getRandomFloat(min, max) { diff --git a/examples/cameraExample.js b/examples/cameraExample.js index 6e3c51a348..332cc0fbca 100644 --- a/examples/cameraExample.js +++ b/examples/cameraExample.js @@ -1,12 +1,14 @@ // // cameraExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/6/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Camera class // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var damping = 0.9; diff --git a/examples/clap.js b/examples/clap.js index ef8b61f05a..9da36ba094 100644 --- a/examples/clap.js +++ b/examples/clap.js @@ -1,6 +1,14 @@ // +// cameraExample.js +// examples +// +// Copyright 2014 High Fidelity, Inc. +// // This sample script watches your hydra hands and makes clapping sound when they come close together fast // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// function length(v) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); @@ -42,7 +50,7 @@ function maybePlaySound(deltaTime) { const CLAP_DISTANCE = 0.2; if (!clapping[palm] && (distanceBetween < CLAP_DISTANCE) && (speed > CLAP_SPEED)) { - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); options.position = palm1Position; options.volume = speed / 2.0; if (options.volume > 1.0) options.volume = 1.0; diff --git a/examples/clipboardExample.js b/examples/clipboardExample.js index 4972505dfe..e6db44054f 100644 --- a/examples/clipboardExample.js +++ b/examples/clipboardExample.js @@ -1,12 +1,14 @@ // // clipboardExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Clipboard class // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var selectedVoxel = { x: 0, y: 0, z: 0, s: 0 }; diff --git a/examples/collidingParticles.js b/examples/collidingParticles.js index 2d2cf4fecc..1f1899c8f2 100644 --- a/examples/collidingParticles.js +++ b/examples/collidingParticles.js @@ -1,14 +1,16 @@ // // collidingParticles.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 12/31/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // This is an example script that creates a couple particles, and sends them on a collision course. // One of the particles has a script that when it collides with another particle, it swaps colors with that particle. // The other particle has a script that when it collides with another particle it set's it's script to a suicide script. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var currentIteration = 0; @@ -30,9 +32,10 @@ var gravity = { var damping = 0.1; var scriptA = " " + - " function collisionWithParticle(other, penetration) { " + + " function collisionWithParticle(other, collision) { " + " print('collisionWithParticle(other.getID()=' + other.getID() + ')...'); " + - " Vec3.print('penetration=', penetration); " + + " Vec3.print('penetration=', collision.penetration); " + + " Vec3.print('contactPoint=', collision.contactPoint); " + " print('myID=' + Particle.getID() + '\\n'); " + " var colorBlack = { red: 0, green: 0, blue: 0 };" + " var otherColor = other.getColor();" + @@ -46,9 +49,10 @@ var scriptA = " " + " "; var scriptB = " " + - " function collisionWithParticle(other, penetration) { " + + " function collisionWithParticle(other, collision) { " + " print('collisionWithParticle(other.getID()=' + other.getID() + ')...'); " + - " Vec3.print('penetration=', penetration); " + + " Vec3.print('penetration=', collision.penetration); " + + " Vec3.print('contactPoint=', collision.contactPoint); " + " print('myID=' + Particle.getID() + '\\n'); " + " Particle.setScript('Particle.setShouldDie(true);'); " + " } " + diff --git a/examples/controllerExample.js b/examples/controllerExample.js index ebb013913e..66a9e40c56 100644 --- a/examples/controllerExample.js +++ b/examples/controllerExample.js @@ -1,12 +1,14 @@ // // controllerExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Controller class // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // // initialize our triggers diff --git a/examples/count.js b/examples/count.js index e04bc2c94b..2a8699ac55 100644 --- a/examples/count.js +++ b/examples/count.js @@ -1,12 +1,16 @@ // // count.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 12/31/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // This is an example script that runs in a loop and displays a counter to the log // +// 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; diff --git a/examples/crazylegs.js b/examples/crazylegs.js index 099387e000..b0f8e937bc 100644 --- a/examples/crazylegs.js +++ b/examples/crazylegs.js @@ -1,9 +1,12 @@ // // crazylegs.js -// hifi +// examples // // Created by Andrzej Kapolka on 3/6/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 FREQUENCY = 5.0; @@ -12,9 +15,12 @@ var AMPLITUDE = 45.0; var cumulativeTime = 0.0; -print("Joint List:"); var jointList = MyAvatar.getJointNames(); -print(jointList); +var jointMappings = "\n# Joint list start"; +for (var i = 0; i < jointList.length; i++) { + jointMappings = jointMappings + "\njointIndex = " + jointList[i] + " = " + i; +} +print(jointMappings + "\n# Joint list end"); Script.update.connect(function(deltaTime) { cumulativeTime += deltaTime; diff --git a/examples/dancing_bot.js b/examples/dancing_bot.js new file mode 100644 index 0000000000..2c0a35adb7 --- /dev/null +++ b/examples/dancing_bot.js @@ -0,0 +1,49 @@ +// +// dancing_bot.js +// examples +// +// Created by Andrzej Kapolka on 4/16/14. +// Copyright 2014 High Fidelity, Inc. +// +// This is an example script that demonstrates an NPC avatar running an FBX animation loop. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var animation = AnimationCache.getAnimation("http://www.fungibleinsight.com/faces/gangnam_style_2.fbx"); + +Avatar.skeletonModelURL = "http://www.fungibleinsight.com/faces/beta.fst"; + +Agent.isAvatar = true; + +var jointMapping; + +var frameIndex = 0.0; + +var FRAME_RATE = 30.0; // frames per second + +Script.update.connect(function(deltaTime) { + if (!jointMapping) { + var avatarJointNames = Avatar.jointNames; + var animationJointNames = animation.jointNames; + if (avatarJointNames.length === 0 || animationJointNames.length === 0) { + return; + } + jointMapping = new Array(avatarJointNames.length); + for (var i = 0; i < avatarJointNames.length; i++) { + jointMapping[i] = animationJointNames.indexOf(avatarJointNames[i]); + } + } + frameIndex += deltaTime * FRAME_RATE; + var frames = animation.frames; + var rotations = frames[Math.floor(frameIndex) % frames.length].rotations; + for (var j = 0; j < jointMapping.length; j++) { + var rotationIndex = jointMapping[j]; + if (rotationIndex != -1) { + Avatar.setJointData(j, rotations[rotationIndex]); + } + } +}); + + diff --git a/examples/defaultScripts.js b/examples/defaultScripts.js index 722b21844f..b3f9649883 100644 --- a/examples/defaultScripts.js +++ b/examples/defaultScripts.js @@ -1,5 +1,13 @@ +// // defaultScripts.js -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// examples +// +// 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 +// + Script.include("lookWithTouch.js"); Script.include("editVoxels.js"); Script.include("selectAudioDevice.js"); diff --git a/examples/drumStick.js b/examples/drumStick.js index 9b0a8ccbca..188661b000 100644 --- a/examples/drumStick.js +++ b/examples/drumStick.js @@ -1,6 +1,14 @@ // +// drumStick.js +// examples +// +// Copyright 2014 High Fidelity, Inc. +// // This example musical instrument script plays 'air drums' when you move your hands downward // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// function length(v) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); @@ -53,7 +61,7 @@ function checkSticks(deltaTime) { // Waiting for change in velocity direction or slowing to trigger drum sound if ((palmVelocity.y > 0.0) || (speed < STOP_SPEED)) { state[palm] = 0; - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); options.position = Controller.getSpatialControlPosition(palm * 2 + 1); if (strokeSpeed[palm] > 1.0) { strokeSpeed[palm] = 1.0; } options.volume = strokeSpeed[palm]; diff --git a/examples/editParticleExample.js b/examples/editParticleExample.js index b632e0229b..54c94d0ef3 100644 --- a/examples/editParticleExample.js +++ b/examples/editParticleExample.js @@ -1,12 +1,15 @@ // // editParticleExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 12/31/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates creating and editing a particle // +// 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 = 2000; diff --git a/examples/editVoxels.js b/examples/editVoxels.js index 4922caf0d8..12c571b09c 100644 --- a/examples/editVoxels.js +++ b/examples/editVoxels.js @@ -1,22 +1,28 @@ // // editVoxels.js -// hifi +// examples // // Created by Philip Rosedale on February 8, 2014 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // Captures mouse clicks and edits voxels accordingly. // // click = create a new voxel on this face, same color as old (default color picker state) -// right click or control + click = delete this voxel +// right click or control + click = delete this voxel // shift + click = recolor this voxel // 1 - 8 = pick new color from palette -// 9 = create a new voxel in front of the camera +// 9 = create a new voxel in front of the camera // // Click and drag to create more new voxels in the same direction // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +var editToolsOn = true; // starts out off var windowDimensions = Controller.getViewportDimensions(); +var WORLD_SCALE = Voxels.getTreeScale(); var NEW_VOXEL_SIZE = 1.0; var NEW_VOXEL_DISTANCE_FROM_CAMERA = 3.0; @@ -32,7 +38,8 @@ var MIN_PASTE_VOXEL_SCALE = .256; var zFightingSizeAdjust = 0.002; // used to adjust preview voxels to prevent z fighting var previewLineWidth = 1.5; -var isAdding = false; +var inspectJsIsRunning = false; +var isAdding = false; var isExtruding = false; var extrudeDirection = { x: 0, y: 0, z: 0 }; var extrudeScale = 0.0; @@ -44,7 +51,7 @@ var wheelPixelsMoved = 0; var mouseX = 0; -var mouseY = 0; +var mouseY = 0; // Create a table of the different colors you can choose var colors = new Array(); @@ -58,77 +65,147 @@ colors[6] = { red: 211, green: 115, blue: 0 }; colors[7] = { red: 48, green: 116, blue: 119 }; colors[8] = { red: 31, green: 64, blue: 64 }; var numColors = 9; -var whichColor = -1; // Starting color is 'Copy' mode +var whichColor = 0; // Starting color is 'Copy' mode -// Create sounds for adding, deleting, recoloring voxels -var addSound1 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+create+2.raw"); -var addSound2 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+create+3.raw"); -var addSound3 = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+create+4.raw"); - -var deleteSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+delete+2.raw"); -var changeColorSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Voxels/voxel+edit+2.raw"); -var clickSound = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Switches+and+sliders/toggle+switch+-+medium.raw"); +// Create sounds for for every script actions that require one var audioOptions = new AudioInjectionOptions(); - -audioOptions.volume = 0.5; +audioOptions.volume = 1.0; audioOptions.position = Vec3.sum(MyAvatar.position, { x: 0, y: 1, z: 0 } ); // start with audio slightly above the avatar -var editToolsOn = true; // starts out off +function SoundArray() { + this.audioOptions = audioOptions + this.sounds = new Array(); + this.addSound = function (soundURL) { + this.sounds[this.sounds.length] = new Sound(soundURL); + } + this.play = function (index) { + if (0 <= index && index < this.sounds.length) { + Audio.playSound(this.sounds[index], this.audioOptions); + } else { + print("[ERROR] editVoxels.js:randSound.play() : Index " + index + " out of range."); + } + } + this.playRandom = function () { + if (this.sounds.length > 0) { + rand = Math.floor(Math.random() * this.sounds.length); + Audio.playSound(this.sounds[rand], this.audioOptions); + } else { + print("[ERROR] editVoxels.js:randSound.playRandom() : Array is empty."); + } + } +} + +var addVoxelSound = new SoundArray(); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+1.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+2.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+3.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+4.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+5.raw"); +addVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Add/VA+6.raw"); + +var delVoxelSound = new SoundArray(); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A1.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A2.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+A3.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B1.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B2.raw"); +delVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Del/VD+B3.raw"); + +var resizeVoxelSound = new SoundArray(); +resizeVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Size/V+Size+Minus.raw"); +resizeVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Voxel+Size/V+Size+Plus.raw"); +var voxelSizeMinus = 0; +var voxelSizePlus = 1; + +var swatchesSound = new SoundArray(); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+1.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+2.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+3.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+4.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+5.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+6.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+7.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+8.raw"); +swatchesSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Swatches/Swatch+9.raw"); + +var undoSound = new SoundArray(); +undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+1.raw"); +undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+2.raw"); +undoSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Undo/Undo+3.raw"); + +var scriptInitSound = new SoundArray(); +scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+A.raw"); +scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+B.raw"); +scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+C.raw"); +scriptInitSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Script+Init/Script+Init+D.raw"); + +var modeSwitchSound = new SoundArray(); +modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+1.raw"); +modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+2.raw"); +modeSwitchSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Mode+Switch/Mode+3.raw"); + +var initialVoxelSound = new SoundArray(); +initialVoxelSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Initial+Voxel/Initial+V.raw"); + +var colorInheritSound = new SoundArray(); +colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+A.raw"); +colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+B.raw"); +colorInheritSound.addSound("https://highfidelity-public.s3.amazonaws.com/sounds/Voxel+Editing/Color+Inherit/Inherit+C.raw"); // previewAsVoxel - by default, we will preview adds/deletes/recolors as just 4 lines on the intersecting face. But if you // the preview to show a full voxel then set this to true and the voxel will be displayed for voxel editing -var previewAsVoxel = false; +var previewAsVoxel = false; var voxelPreview = Overlays.addOverlay("cube", { - position: { x: 0, y: 0, z: 0}, - size: 1, - color: { red: 255, green: 0, blue: 0}, - alpha: 1, - solid: false, - visible: false, - lineWidth: 4 - }); - + position: { x: 0, y: 0, z: 0}, + size: 1, + color: { red: 255, green: 0, blue: 0}, + alpha: 1, + solid: false, + visible: false, + lineWidth: 4 + }); + var linePreviewTop = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z: 0}, - end: { x: 0, y: 0, z: 0}, - color: { red: 255, green: 255, blue: 255}, - alpha: 1, - visible: false, - lineWidth: previewLineWidth - }); + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); var linePreviewBottom = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z: 0}, - end: { x: 0, y: 0, z: 0}, - color: { red: 255, green: 255, blue: 255}, - alpha: 1, - visible: false, - lineWidth: previewLineWidth - }); + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); var linePreviewLeft = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z: 0}, - end: { x: 0, y: 0, z: 0}, - color: { red: 255, green: 255, blue: 255}, - alpha: 1, - visible: false, - lineWidth: previewLineWidth - }); + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); var linePreviewRight = Overlays.addOverlay("line3d", { - position: { x: 0, y: 0, z: 0}, - end: { x: 0, y: 0, z: 0}, - color: { red: 255, green: 255, blue: 255}, - alpha: 1, - visible: false, - lineWidth: previewLineWidth - }); + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 255, blue: 255}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); // these will be used below -var sliderWidth = 154; -var sliderHeight = 37; +var scaleSelectorWidth = 144; +var scaleSelectorHeight = 37; // These will be our "overlay IDs" var swatches = new Array(); @@ -136,7 +213,7 @@ var swatchExtraPadding = 5; var swatchHeight = 37; var swatchWidth = 27; var swatchesWidth = swatchWidth * numColors + numColors + swatchExtraPadding * 2; -var swatchesX = (windowDimensions.x - (swatchesWidth + sliderWidth)) / 2; +var swatchesX = (windowDimensions.x - (swatchesWidth + scaleSelectorWidth)) / 2; var swatchesY = windowDimensions.y - swatchHeight + 1; var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/tools/"; @@ -144,33 +221,33 @@ var toolIconUrl = "http://highfidelity-public.s3-us-west-1.amazonaws.com/images/ // create the overlays, position them in a row, set their colors, and for the selected one, use a different source image // location so that it displays the "selected" marker for (s = 0; s < numColors; s++) { - + var extraWidth = 0; - + if (s == 0) { extraWidth = swatchExtraPadding; } - + var imageFromX = swatchExtraPadding - extraWidth + s * swatchWidth; var imageFromY = swatchHeight + 1; - + var swatchX = swatchExtraPadding - extraWidth + swatchesX + ((swatchWidth - 1) * s); - + if (s == (numColors - 1)) { extraWidth = swatchExtraPadding; } - + swatches[s] = Overlays.addOverlay("image", { - x: swatchX, - y: swatchesY, - width: swatchWidth + extraWidth, - height: swatchHeight, - subImage: { x: imageFromX, y: imageFromY, width: swatchWidth + extraWidth, height: swatchHeight }, - imageURL: toolIconUrl + "swatches.svg", - color: colors[s], - alpha: 1, - visible: editToolsOn - }); + x: swatchX, + y: swatchesY, + width: swatchWidth + extraWidth, + height: swatchHeight, + subImage: { x: imageFromX, y: imageFromY, width: swatchWidth + extraWidth, height: swatchHeight }, + imageURL: toolIconUrl + "swatches.svg", + color: colors[s], + alpha: 1, + visible: editToolsOn + }); } @@ -190,77 +267,173 @@ var eyedropperToolAt = 2; var pasteModeColor = { red: 132, green: 61, blue: 255 }; var voxelTool = Overlays.addOverlay("image", { - x: 0, y: 0, width: toolWidth, height: toolHeight, - subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, - imageURL: toolIconUrl + "voxel-tool.svg", - visible: false, - alpha: 0.9 - }); + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "voxel-tool.svg", + visible: editToolsOn, + alpha: 0.9 + }); var recolorTool = Overlays.addOverlay("image", { - x: 0, y: 0, width: toolWidth, height: toolHeight, - subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, - imageURL: toolIconUrl + "paint-tool.svg", - visible: false, - alpha: 0.9 - }); + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "paint-tool.svg", + visible: editToolsOn, + alpha: 0.9 + }); var eyedropperTool = Overlays.addOverlay("image", { - x: 0, y: 0, width: toolWidth, height: toolHeight, - subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, - imageURL: toolIconUrl + "eyedropper-tool.svg", - visible: false, - alpha: 0.9 - }); - -// This will create a couple of image overlays that make a "slider", we will demonstrate how to trap mouse messages to -// move the slider + x: 0, y: 0, width: toolWidth, height: toolHeight, + subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "eyedropper-tool.svg", + visible: editToolsOn, + alpha: 0.9 + }); -// see above... -//var sliderWidth = 158; -//var sliderHeight = 35; - -var sliderOffsetX = 17; -var sliderX = swatchesX - swatchWidth - sliderOffsetX; -var sliderY = windowDimensions.y - sliderHeight + 1; -var slider = Overlays.addOverlay("image", { - // alternate form of expressing bounds - bounds: { x: sliderX, y: sliderY, width: sliderWidth, height: sliderHeight}, - imageURL: toolIconUrl + "voxel-size-slider-bg.svg", - alpha: 1, - visible: false - }); - -// The slider is handled in the mouse event callbacks. -var isMovingSlider = false; -var thumbClickOffsetX = 0; - -// This is the thumb of our slider -var minThumbX = 20; // relative to the x of the slider -var maxThumbX = minThumbX + 90; -var thumbExtents = maxThumbX - minThumbX; -var thumbX = (minThumbX + maxThumbX) / 2; -var thumbOffsetY = 11; -var thumbY = sliderY + thumbOffsetY; -var thumb = Overlays.addOverlay("image", { - x: sliderX + thumbX, - y: thumbY, - width: 17, - height: 17, - imageURL: toolIconUrl + "voxel-size-slider-handle.svg", - alpha: 1, - visible: false - }); - -var pointerVoxelScale = Math.floor(MAX_VOXEL_SCALE + MIN_VOXEL_SCALE) / 2; // this is the voxel scale used for click to add or delete -var pointerVoxelScaleSet = false; // if voxel scale has not yet been set, we use the intersection size - -var pointerVoxelScaleSteps = 8; // the number of slider position steps -var pointerVoxelScaleOriginStep = 8; // the position of slider for the 1 meter size voxel -var pointerVoxelScaleMin = Math.pow(2, (1-pointerVoxelScaleOriginStep)); -var pointerVoxelScaleMax = Math.pow(2, (pointerVoxelScaleSteps-pointerVoxelScaleOriginStep)); -var thumbDeltaPerStep = thumbExtents / (pointerVoxelScaleSteps - 1); +var copyScale = true; +function ScaleSelector() { + this.x = swatchesX + swatchesWidth; + this.y = swatchesY; + this.width = scaleSelectorWidth; + this.height = scaleSelectorHeight; + + this.displayPower = false; + this.scale = 1.0; + this.power = 0; + + this.FIRST_PART = this.width * 40.0 / 100.0; + this.SECOND_PART = this.width * 37.0 / 100.0; + + this.buttonsOverlay = Overlays.addOverlay("image", { + x: this.x, y: this.y, + width: this.width, height: this.height, + //subImage: { x: 0, y: toolHeight, width: toolWidth, height: toolHeight }, + imageURL: toolIconUrl + "voxel-size-selector.svg", + alpha: 0.9, + visible: editToolsOn + }); + this.textOverlay = Overlays.addOverlay("text", { + x: this.x + this.FIRST_PART, y: this.y, + width: this.SECOND_PART, height: this.height, + topMargin: 13, + text: this.scale.toString(), + alpha: 0.0, + visible: editToolsOn + }); + this.powerOverlay = Overlays.addOverlay("text", { + x: this.x + this.FIRST_PART, y: this.y, + width: this.SECOND_PART, height: this.height, + leftMargin: 28, + text: this.power.toString(), + alpha: 0.0, + visible: false + }); + this.setScale = function(scale) { + this.scale = scale; + this.power = Math.floor(Math.log(scale)); + rescaleImport(); + } + + this.show = function(doShow) { + Overlays.editOverlay(this.buttonsOverlay, {visible: doShow}); + Overlays.editOverlay(this.textOverlay, {visible: doShow}); + Overlays.editOverlay(this.powerOverlay, {visible: doShow && this.displayPower}); + } + + this.move = function() { + this.x = swatchesX + swatchesWidth; + this.y = swatchesY; + + Overlays.editOverlay(this.buttonsOverlay, { + x: this.x, y: this.y, + }); + Overlays.editOverlay(this.textOverlay, { + x: this.x + this.FIRST_PART, y: this.y, + }); + Overlays.editOverlay(this.powerOverlay, { + x: this.x + this.FIRST_PART, y: this.y, + }); + } + + + this.switchDisplay = function() { + this.displayPower = !this.displayPower; + + if (this.displayPower) { + Overlays.editOverlay(this.textOverlay, { + leftMargin: 18, + text: "2" + }); + Overlays.editOverlay(this.powerOverlay, { + text: this.power.toString(), + visible: editToolsOn + }); + } else { + Overlays.editOverlay(this.textOverlay, { + leftMargin: 13, + text: this.scale.toString() + }); + Overlays.editOverlay(this.powerOverlay, { + visible: false + }); + } + } + + this.update = function() { + if (this.displayPower) { + Overlays.editOverlay(this.powerOverlay, {text: this.power.toString()}); + } else { + Overlays.editOverlay(this.textOverlay, {text: this.scale.toString()}); + } + } + + this.incrementScale = function() { + copyScale = false; + if (this.power < 13) { + ++this.power; + this.scale *= 2.0; + this.update(); + rescaleImport(); + resizeVoxelSound.play(voxelSizePlus); + } + } + + this.decrementScale = function() { + copyScale = false; + if (-4 < this.power) { + --this.power; + this.scale /= 2.0; + this.update(); + rescaleImport(); + resizeVoxelSound.play(voxelSizePlus); + } + } + + this.clicked = function(x, y) { + if (this.x < x && x < this.x + this.width && + this.y < y && y < this.y + this.height) { + + if (x < this.x + this.FIRST_PART) { + this.decrementScale(); + } else if (x < this.x + this.FIRST_PART + this.SECOND_PART) { + this.switchDisplay(); + } else { + this.incrementScale(); + } + return true; + } + return false; + } + + this.cleanup = function() { + Overlays.deleteOverlay(this.buttonsOverlay); + Overlays.deleteOverlay(this.textOverlay); + Overlays.deleteOverlay(this.powerOverlay); + } + +} +var scaleSelector = new ScaleSelector(); ///////////////////////////////////// IMPORT MODULE /////////////////////////////// @@ -268,9 +441,12 @@ var thumbDeltaPerStep = thumbExtents / (pointerVoxelScaleSteps - 1); var importTree; var importPreview; var importBoundaries; +var xImportGuide; +var yImportGuide; +var zImportGuide; var isImporting; var importPosition; -var importScale; +var importDistance; function initImport() { importPreview = Overlays.addOverlay("localvoxels", { @@ -281,49 +457,90 @@ function initImport() { }); importBoundaries = Overlays.addOverlay("cube", { position: { x: 0, y: 0, z: 0 }, - scale: 1, + size: 1, color: { red: 128, blue: 128, green: 128 }, + lineWIdth: 4, solid: false, visible: false - }) + }); + + xImportGuide = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 255, green: 0, blue: 0}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); + yImportGuide = Overlays.addOverlay("line3d", { + position: { x: 0, y: 0, z: 0}, + end: { x: 0, y: 0, z: 0}, + color: { red: 0, green: 255, blue: 0}, + alpha: 1, + visible: false, + lineWidth: previewLineWidth + }); + zImportGuide = Overlays.addOverlay("line3d", { + 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: previewLineWidth + }); + + isImporting = false; importPosition = { x: 0, y: 0, z: 0 }; - importScale = 0; } function importVoxels() { - if (Clipboard.importVoxels()) { - isImporting = true; - if (importScale <= 0) { - importScale = 1; - } - } else { - isImporting = false; - } - + isImporting = Clipboard.importVoxels(); return isImporting; } function moveImport(position) { - if (0 < position.x && 0 < position.y && 0 < position.z) { - importPosition = position; - Overlays.editOverlay(importPreview, { - position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } - }); - Overlays.editOverlay(importBoundaries, { - position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } - }); - } + importPosition = position; + Overlays.editOverlay(importPreview, { + position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } + }); + Overlays.editOverlay(importBoundaries, { + position: { x: importPosition.x, y: importPosition.y, z: importPosition.z } + }); + + + Overlays.editOverlay(xImportGuide, { + position: { x: importPosition.x, y: 0, z: importPosition.z }, + end: { x: importPosition.x + scaleSelector.scale, y: 0, z: importPosition.z } + }); + Overlays.editOverlay(yImportGuide, { + position: { x: importPosition.x, y: importPosition.y, z: importPosition.z }, + end: { x: importPosition.x, y: 0, z: importPosition.z } + }); + Overlays.editOverlay(zImportGuide, { + position: { x: importPosition.x, y: 0, z: importPosition.z }, + end: { x: importPosition.x, y: 0, z: importPosition.z + scaleSelector.scale } + }); + rescaleImport(); } -function rescaleImport(scale) { - if (0 < scale) { - importScale = scale; +function rescaleImport() { + if (0 < scaleSelector.scale) { Overlays.editOverlay(importPreview, { - scale: importScale + scale: scaleSelector.scale }); Overlays.editOverlay(importBoundaries, { - scale: importScale + size: scaleSelector.scale + }); + + Overlays.editOverlay(xImportGuide, { + end: { x: importPosition.x + scaleSelector.scale, y: 0, z: importPosition.z } + }); + Overlays.editOverlay(yImportGuide, { + end: { x: importPosition.x, y: 0, z: importPosition.z } + }); + Overlays.editOverlay(zImportGuide, { + end: { x: importPosition.x, y: 0, z: importPosition.z + scaleSelector.scale } }); } } @@ -335,11 +552,21 @@ function showImport(doShow) { Overlays.editOverlay(importBoundaries, { visible: doShow }); + + Overlays.editOverlay(xImportGuide, { + visible: doShow + }); + Overlays.editOverlay(yImportGuide, { + visible: doShow + }); + Overlays.editOverlay(zImportGuide, { + visible: doShow + }); } function placeImport() { if (isImporting) { - Clipboard.pasteVoxel(importPosition.x, importPosition.y, importPosition.z, importScale); + Clipboard.pasteVoxel(importPosition.x, importPosition.y, importPosition.z, scaleSelector.scale); isImporting = false; } } @@ -354,9 +581,11 @@ function cancelImport() { function cleanupImport() { Overlays.deleteOverlay(importPreview); Overlays.deleteOverlay(importBoundaries); + Overlays.deleteOverlay(xImportGuide); + Overlays.deleteOverlay(yImportGuide); + Overlays.deleteOverlay(zImportGuide); isImporting = false; importPostion = { x: 0, y: 0, z: 0 }; - importScale = 0; } /////////////////////////////////// END IMPORT MODULE ///////////////////////////// initImport(); @@ -365,40 +594,6 @@ if (editToolsOn) { moveTools(); } - -function calcThumbFromScale(scale) { - var scaleLog = Math.log(scale)/Math.log(2); - var thumbStep = scaleLog + pointerVoxelScaleOriginStep; - if (thumbStep < 1) { - thumbStep = 1; - } - if (thumbStep > pointerVoxelScaleSteps) { - thumbStep = pointerVoxelScaleSteps; - } - thumbX = (thumbDeltaPerStep * (thumbStep - 1)) + minThumbX; - Overlays.editOverlay(thumb, { x: thumbX + sliderX } ); -} - -function calcScaleFromThumb(newThumbX) { - // newThumbX is the pixel location relative to start of slider, - // we need to figure out the actual offset in the allowed slider area - thumbAt = newThumbX - minThumbX; - thumbStep = Math.floor((thumbAt/ thumbExtents) * (pointerVoxelScaleSteps-1)) + 1; - pointerVoxelScale = Math.pow(2, (thumbStep-pointerVoxelScaleOriginStep)); - - // if importing, rescale import ... - if (isImporting) { - var importScale = (pointerVoxelScale / MAX_VOXEL_SCALE) * MAX_PASTE_VOXEL_SCALE; - rescaleImport(importScale); - } - - // now reset the display accordingly... - calcThumbFromScale(pointerVoxelScale); - - // if the user moved the thumb, then they are fixing the voxel scale - pointerVoxelScaleSet = true; -} - function setAudioPosition() { var position = MyAvatar.position; var forwardVector = Quat.getFront(MyAvatar.orientation); @@ -406,18 +601,18 @@ function setAudioPosition() { } function getNewPasteVoxel(pickRay) { - - var voxelSize = MIN_PASTE_VOXEL_SCALE + (MAX_PASTE_VOXEL_SCALE - MIN_PASTE_VOXEL_SCALE) * pointerVoxelScale - 1; + + var voxelSize = scaleSelector.scale; var origin = { x: pickRay.direction.x, y: pickRay.direction.y, z: pickRay.direction.z }; - + origin.x += pickRay.origin.x; origin.y += pickRay.origin.y; origin.z += pickRay.origin.z; - + origin.x -= voxelSize / 2; origin.y -= voxelSize / 2; origin.z += voxelSize / 2; - + return {origin: origin, voxelSize: voxelSize}; } @@ -439,39 +634,24 @@ var recolorToolSelected = false; var eyedropperToolSelected = false; var pasteMode = false; -function playRandomAddSound(audioOptions) { - if (Math.random() < 0.33) { - Audio.playSound(addSound1, audioOptions); - } else if (Math.random() < 0.5) { - Audio.playSound(addSound2, audioOptions); - } else { - Audio.playSound(addSound3, audioOptions); - } -} function calculateVoxelFromIntersection(intersection, operation) { //print("calculateVoxelFromIntersection() operation="+operation); var resultVoxel; - + var wantDebug = false; if (wantDebug) { - print(">>>>> calculateVoxelFromIntersection().... intersection voxel.red/green/blue=" + intersection.voxel.red + ", " - + intersection.voxel.green + ", " + intersection.voxel.blue); - print(" intersection voxel.x/y/z/s=" + intersection.voxel.x + ", " - + intersection.voxel.y + ", " + intersection.voxel.z+ ": " + intersection.voxel.s); + print(">>>>> calculateVoxelFromIntersection().... intersection voxel.red/green/blue=" + intersection.voxel.red + ", " + + intersection.voxel.green + ", " + intersection.voxel.blue); + print(" intersection voxel.x/y/z/s=" + intersection.voxel.x + ", " + + intersection.voxel.y + ", " + intersection.voxel.z+ ": " + intersection.voxel.s); print(" intersection face=" + intersection.face); print(" intersection distance=" + intersection.distance); - print(" intersection intersection.x/y/z=" + intersection.intersection.x + ", " - + intersection.intersection.y + ", " + intersection.intersection.z); + print(" intersection intersection.x/y/z=" + intersection.intersection.x + ", " + + intersection.intersection.y + ", " + intersection.intersection.z); } - var voxelSize; - if (pointerVoxelScaleSet) { - voxelSize = pointerVoxelScale; - } else { - voxelSize = intersection.voxel.s; - } - + var voxelSize = scaleSelector.scale; var x; var y; var z; @@ -501,17 +681,17 @@ function calculateVoxelFromIntersection(intersection, operation) { } resultVoxel = { x: x, y: y, z: z, s: voxelSize }; highlightAt = { x: x, y: y, z: z, s: voxelSize }; - + // we only do the "add to the face we're pointing at" adjustment, if the operation is an add // operation, and the target voxel size is equal to or smaller than the intersecting voxel. var wantAddAdjust = (operation == "add" && (voxelSize <= intersection.voxel.s)); if (wantDebug) { print("wantAddAdjust="+wantAddAdjust); } - + // now we also want to calculate the "edge square" for the face for this voxel if (intersection.face == "MIN_X_FACE") { - + highlightAt.x = x - zFightingSizeAdjust; if (wantAddAdjust) { resultVoxel.x -= voxelSize; @@ -521,21 +701,21 @@ function calculateVoxelFromIntersection(intersection, operation) { resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust }; resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; - + } else if (intersection.face == "MAX_X_FACE") { - + highlightAt.x = x + voxelSize + zFightingSizeAdjust; if (wantAddAdjust) { resultVoxel.x += resultVoxel.s; } - + resultVoxel.bottomRight = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust }; resultVoxel.bottomLeft = {x: highlightAt.x, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; resultVoxel.topRight = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + zFightingSizeAdjust }; resultVoxel.topLeft = {x: highlightAt.x, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; - + } else if (intersection.face == "MIN_Y_FACE") { - + highlightAt.y = y - zFightingSizeAdjust; if (wantAddAdjust) { resultVoxel.y -= voxelSize; @@ -545,9 +725,9 @@ function calculateVoxelFromIntersection(intersection, operation) { resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust }; resultVoxel.bottomRight = {x: highlightAt.x + zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust , y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust }; - + } else if (intersection.face == "MAX_Y_FACE") { - + highlightAt.y = y + voxelSize + zFightingSizeAdjust; if (wantAddAdjust) { resultVoxel.y += voxelSize; @@ -557,9 +737,9 @@ function calculateVoxelFromIntersection(intersection, operation) { resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + zFightingSizeAdjust}; resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust}; resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y, z: highlightAt.z + voxelSize - zFightingSizeAdjust}; - + } else if (intersection.face == "MIN_Z_FACE") { - + highlightAt.z = z - zFightingSizeAdjust; if (wantAddAdjust) { resultVoxel.z -= voxelSize; @@ -569,19 +749,19 @@ function calculateVoxelFromIntersection(intersection, operation) { resultVoxel.bottomLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z}; resultVoxel.topRight = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z }; resultVoxel.topLeft = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z}; - + } else if (intersection.face == "MAX_Z_FACE") { - + highlightAt.z = z + voxelSize + zFightingSizeAdjust; if (wantAddAdjust) { resultVoxel.z += voxelSize; } - + resultVoxel.bottomLeft = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z }; resultVoxel.bottomRight = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + zFightingSizeAdjust, z: highlightAt.z}; resultVoxel.topLeft = {x: highlightAt.x + zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z }; resultVoxel.topRight = {x: highlightAt.x + voxelSize - zFightingSizeAdjust, y: highlightAt.y + voxelSize - zFightingSizeAdjust, z: highlightAt.z}; - + } return resultVoxel; @@ -589,26 +769,14 @@ function calculateVoxelFromIntersection(intersection, operation) { function showPreviewVoxel() { var voxelColor; - + var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY); var intersection = Voxels.findRayIntersection(pickRay); - - // if the user hasn't updated the - if (!pointerVoxelScaleSet) { - calcThumbFromScale(intersection.voxel.s); - } - - if (whichColor == -1) { - // Copy mode - use clicked voxel color - voxelColor = { red: intersection.voxel.red, - green: intersection.voxel.green, - blue: intersection.voxel.blue }; - } else { - voxelColor = { red: colors[whichColor].red, - green: colors[whichColor].green, - blue: colors[whichColor].blue }; - } - + + voxelColor = { red: colors[whichColor].red, + green: colors[whichColor].green, + blue: colors[whichColor].blue }; + var guidePosition; if (trackAsRecolor || recolorToolSelected || trackAsEyedropper || eyedropperToolSelected) { Overlays.editOverlay(voxelPreview, { visible: true }); @@ -620,55 +788,56 @@ function showPreviewVoxel() { } function showPreviewLines() { - + var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY); - + if (pasteMode) { // free voxel pasting - + Overlays.editOverlay(voxelPreview, { visible: false }); Overlays.editOverlay(linePreviewLeft, { visible: false }); - + var pasteVoxel = getNewPasteVoxel(pickRay); - + // X axis Overlays.editOverlay(linePreviewBottom, { - position: pasteVoxel.origin, - end: {x: pasteVoxel.origin.x + pasteVoxel.voxelSize, y: pasteVoxel.origin.y, z: pasteVoxel.origin.z }, - visible: true - }); - + position: pasteVoxel.origin, + end: {x: pasteVoxel.origin.x + pasteVoxel.voxelSize, y: pasteVoxel.origin.y, z: pasteVoxel.origin.z }, + visible: true + }); + // Y axis Overlays.editOverlay(linePreviewRight, { - position: pasteVoxel.origin, - end: {x: pasteVoxel.origin.x, y: pasteVoxel.origin.y + pasteVoxel.voxelSize, z: pasteVoxel.origin.z }, - visible: true - }); - + position: pasteVoxel.origin, + end: {x: pasteVoxel.origin.x, y: pasteVoxel.origin.y + pasteVoxel.voxelSize, z: pasteVoxel.origin.z }, + visible: true + }); + // Z axis Overlays.editOverlay(linePreviewTop, { - position: pasteVoxel.origin, - end: {x: pasteVoxel.origin.x, y: pasteVoxel.origin.y, z: pasteVoxel.origin.z - pasteVoxel.voxelSize }, - visible: true - }); - + position: pasteVoxel.origin, + end: {x: pasteVoxel.origin.x, y: pasteVoxel.origin.y, z: pasteVoxel.origin.z - pasteVoxel.voxelSize }, + visible: true + }); + return; } - + var intersection = Voxels.findRayIntersection(pickRay); if (intersection.intersects) { - - // if the user hasn't updated the - if (!pointerVoxelScaleSet) { - calcThumbFromScale(intersection.voxel.s); - } - resultVoxel = calculateVoxelFromIntersection(intersection,""); Overlays.editOverlay(voxelPreview, { visible: false }); Overlays.editOverlay(linePreviewTop, { position: resultVoxel.topLeft, end: resultVoxel.topRight, visible: true }); Overlays.editOverlay(linePreviewBottom, { position: resultVoxel.bottomLeft, end: resultVoxel.bottomRight, visible: true }); Overlays.editOverlay(linePreviewLeft, { position: resultVoxel.topLeft, end: resultVoxel.bottomLeft, visible: true }); Overlays.editOverlay(linePreviewRight, { position: resultVoxel.topRight, end: resultVoxel.bottomRight, visible: true }); + colors[0] = {red: intersection.voxel.red, green: intersection.voxel.green , blue: intersection.voxel.blue }; + + if (copyScale) { + scaleSelector.setScale(intersection.voxel.s); + scaleSelector.update(); + } + moveTools(); } else { Overlays.editOverlay(voxelPreview, { visible: false }); Overlays.editOverlay(linePreviewTop, { visible: false }); @@ -679,10 +848,10 @@ function showPreviewLines() { } function showPreviewGuides() { - if (editToolsOn) { + if (editToolsOn && !isImporting) { if (previewAsVoxel) { showPreviewVoxel(); - + // make sure alternative is hidden Overlays.editOverlay(linePreviewTop, { visible: false }); Overlays.editOverlay(linePreviewBottom, { visible: false }); @@ -714,7 +883,7 @@ function trackKeyPressEvent(event) { if (!editToolsOn) { return; } - + if (event.text == "CONTROL") { trackAsDelete = true; moveTools(); @@ -727,6 +896,9 @@ function trackKeyPressEvent(event) { trackAsEyedropper = true; moveTools(); } + if (event.text == "ALT") { + inspectJsIsRunning = true; + } showPreviewGuides(); } @@ -737,22 +909,24 @@ function trackKeyReleaseEvent(event) { moveTools(); setAudioPosition(); // make sure we set the audio position before playing sounds showPreviewGuides(); - Audio.playSound(clickSound, audioOptions); + scaleSelector.show(editToolsOn); + scriptInitSound.playRandom(); } - + + if (event.text == "ALT") { + inspectJsIsRunning = false; + } + if (editToolsOn) { if (event.text == "ESC") { - pointerVoxelScaleSet = false; pasteMode = false; moveTools(); } if (event.text == "-") { - thumbX -= thumbDeltaPerStep; - calcScaleFromThumb(thumbX); + scaleSelector.decrementScale(); } if (event.text == "+") { - thumbX += thumbDeltaPerStep; - calcScaleFromThumb(thumbX); + scaleSelector.incrementScale(); } if (event.text == "CONTROL") { trackAsDelete = false; @@ -771,60 +945,48 @@ function trackKeyReleaseEvent(event) { if (event.text == "F1") { previewAsVoxel = !previewAsVoxel; } - + showPreviewGuides(); - } + } } function mousePressEvent(event) { - // if our tools are off, then don't do anything if (!editToolsOn) { - return; + return; + } + if (inspectJsIsRunning) { + return; } var clickedOnSomething = false; var clickedOverlay = Overlays.getOverlayAtPoint({x: event.x, y: event.y}); - // If the user clicked on the thumb, handle the slider logic - if (clickedOverlay == thumb) { - isMovingSlider = true; - thumbClickOffsetX = event.x - (sliderX + thumbX); // this should be the position of the mouse relative to the thumb - clickedOnSomething = true; - - Overlays.editOverlay(thumb, { imageURL: toolIconUrl + "voxel-size-slider-handle.svg", }); - - } else if (clickedOverlay == voxelTool) { + if (clickedOverlay == voxelTool) { + modeSwitchSound.play(0); voxelToolSelected = true; recolorToolSelected = false; eyedropperToolSelected = false; moveTools(); clickedOnSomething = true; } else if (clickedOverlay == recolorTool) { + modeSwitchSound.play(1); voxelToolSelected = false; recolorToolSelected = true; eyedropperToolSelected = false; moveTools(); clickedOnSomething = true; } else if (clickedOverlay == eyedropperTool) { + modeSwitchSound.play(2); voxelToolSelected = false; recolorToolSelected = false; eyedropperToolSelected = true; moveTools(); clickedOnSomething = true; - } else if (clickedOverlay == slider) { - - if (event.x < sliderX + minThumbX) { - thumbX -= thumbDeltaPerStep; - calcScaleFromThumb(thumbX); + } else if (scaleSelector.clicked(event.x, event.y)) { + if (isImporting) { + rescaleImport(); } - - if (event.x > sliderX + maxThumbX) { - thumbX += thumbDeltaPerStep; - calcScaleFromThumb(thumbX); - } - - moveTools(); clickedOnSomething = true; } else { // if the user clicked on one of the color swatches, update the selectedSwatch @@ -833,14 +995,15 @@ function mousePressEvent(event) { whichColor = s; moveTools(); clickedOnSomething = true; + swatchesSound.play(whichColor); break; } } } - if (clickedOnSomething) { + if (clickedOnSomething || isImporting) { return; // no further processing } - + // TODO: does any of this stuff need to execute if we're panning or orbiting? trackMouseEvent(event); // used by preview support mouseX = event.x; @@ -848,15 +1011,7 @@ function mousePressEvent(event) { var pickRay = Camera.computePickRay(event.x, event.y); var intersection = Voxels.findRayIntersection(pickRay); audioOptions.position = Vec3.sum(pickRay.origin, pickRay.direction); - - if (isImporting) { - print("placing import..."); - placeImport(); - showImport(false); - moveTools(); - return; - } - + if (pasteMode) { var pasteVoxel = getNewPasteVoxel(pickRay); Clipboard.pasteVoxel(pasteVoxel.origin.x, pasteVoxel.origin.y, pasteVoxel.origin.z, pasteVoxel.voxelSize); @@ -864,66 +1019,50 @@ function mousePressEvent(event) { moveTools(); return; } - + if (intersection.intersects) { - // if the user hasn't updated the - if (!pointerVoxelScaleSet) { - calcThumbFromScale(intersection.voxel.s); - } - if (trackAsDelete || event.isRightButton && !trackAsEyedropper) { // Delete voxel voxelDetails = calculateVoxelFromIntersection(intersection,"delete"); Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s); - Audio.playSound(deleteSound, audioOptions); + delVoxelSound.playRandom(); Overlays.editOverlay(voxelPreview, { visible: false }); } else if (eyedropperToolSelected || trackAsEyedropper) { - if (whichColor != -1) { - colors[whichColor].red = intersection.voxel.red; - colors[whichColor].green = intersection.voxel.green; - colors[whichColor].blue = intersection.voxel.blue; - moveTools(); - } + colors[whichColor].red = intersection.voxel.red; + colors[whichColor].green = intersection.voxel.green; + colors[whichColor].blue = intersection.voxel.blue; + moveTools(); + swatchesSound.play(whichColor); } else if (recolorToolSelected || trackAsRecolor) { // Recolor Voxel voxelDetails = calculateVoxelFromIntersection(intersection,"recolor"); - + // doing this erase then set will make sure we only recolor just the target voxel - Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s); - Voxels.setVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s, + Voxels.setVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s, colors[whichColor].red, colors[whichColor].green, colors[whichColor].blue); - Audio.playSound(changeColorSound, audioOptions); + swatchesSound.play(whichColor); Overlays.editOverlay(voxelPreview, { visible: false }); } else if (voxelToolSelected) { // Add voxel on face - if (whichColor == -1) { - // Copy mode - use clicked voxel color - newColor = { - red: intersection.voxel.red, - green: intersection.voxel.green, - blue: intersection.voxel.blue }; - } else { - newColor = { - red: colors[whichColor].red, - green: colors[whichColor].green, - blue: colors[whichColor].blue }; - } - + newColor = { red: colors[whichColor].red, + green: colors[whichColor].green, + blue: colors[whichColor].blue + }; + voxelDetails = calculateVoxelFromIntersection(intersection,"add"); - Voxels.eraseVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s); Voxels.setVoxel(voxelDetails.x, voxelDetails.y, voxelDetails.z, voxelDetails.s, - newColor.red, newColor.green, newColor.blue); + newColor.red, newColor.green, newColor.blue); lastVoxelPosition = { x: voxelDetails.x, y: voxelDetails.y, z: voxelDetails.z }; lastVoxelColor = { red: newColor.red, green: newColor.green, blue: newColor.blue }; lastVoxelScale = voxelDetails.s; - - playRandomAddSound(audioOptions); + + addVoxelSound.playRandom(); Overlays.editOverlay(voxelPreview, { visible: false }); dragStart = { x: event.x, y: event.y }; isAdding = true; - } + } } } @@ -932,33 +1071,32 @@ function keyPressEvent(event) { if (editToolsOn) { var nVal = parseInt(event.text); if (event.text == "`") { - print("Color = Copy"); - whichColor = -1; - Audio.playSound(clickSound, audioOptions); - moveTools(); + copyScale = true; } else if ((nVal > 0) && (nVal <= numColors)) { whichColor = nVal - 1; print("Color = " + (whichColor + 1)); - Audio.playSound(clickSound, audioOptions); + swatchesSound.play(whichColor); moveTools(); } else if (event.text == "0") { - // Create a brand new 1 meter voxel in front of your avatar - var color = whichColor; - if (color == -1) color = 0; + // Create a brand new 1 meter voxel in front of your avatar var newPosition = getNewVoxelPosition(); - var newVoxel = { - x: newPosition.x, - y: newPosition.y , - z: newPosition.z, - s: NEW_VOXEL_SIZE, - red: colors[color].red, - green: colors[color].green, - blue: colors[color].blue }; + var newVoxel = { + x: newPosition.x, + y: newPosition.y , + z: newPosition.z, + s: NEW_VOXEL_SIZE, + red: colors[whichColor].red, + green: colors[whichColor].green, + blue: colors[whichColor].blue }; Voxels.eraseVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s); Voxels.setVoxel(newVoxel.x, newVoxel.y, newVoxel.z, newVoxel.s, newVoxel.red, newVoxel.green, newVoxel.blue); setAudioPosition(); - playRandomAddSound(audioOptions); + initialVoxelSound.playRandom(); + } else if (event.text == "z") { + undoSound.playRandom(); } + + } trackKeyPressEvent(event); // used by preview support @@ -971,7 +1109,7 @@ function keyReleaseEvent(event) { function setupMenus() { // hook up menus Menu.menuItemEvent.connect(menuItemEvent); - + // add our menuitems Menu.addMenuItem({ menuName: "Edit", menuItemName: "Voxels", isSeparator: true, beforeItem: "Physics" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Cut", shortcutKey: "CTRL+X", afterItem: "Voxels" }); @@ -979,7 +1117,7 @@ function setupMenus() { Menu.addMenuItem({ menuName: "Edit", menuItemName: "Paste", shortcutKey: "CTRL+V", afterItem: "Copy" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Nudge", shortcutKey: "CTRL+N", afterItem: "Paste" }); Menu.addMenuItem({ menuName: "Edit", menuItemName: "Delete", shortcutKeyEvent: { text: "backspace" }, afterItem: "Nudge" }); - + Menu.addMenuItem({ menuName: "File", menuItemName: "Voxels", isSeparator: true, beforeItem: "Settings" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Export Voxels", shortcutKey: "CTRL+E", afterItem: "Voxels" }); Menu.addMenuItem({ menuName: "File", menuItemName: "Import Voxels", shortcutKey: "CTRL+I", afterItem: "Export Voxels" }); @@ -999,10 +1137,10 @@ function cleanupMenus() { } function menuItemEvent(menuItem) { - + // handle clipboard items if (editToolsOn) { - + var pickRay = Camera.computePickRay(trackLastMouseX, trackLastMouseY); var intersection = Voxels.findRayIntersection(pickRay); selectedVoxel = calculateVoxelFromIntersection(intersection,"select"); @@ -1038,7 +1176,7 @@ function menuItemEvent(menuItem) { Clipboard.deleteVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s); } } - + if (menuItem == "Export Voxels") { print("export"); Clipboard.exportVoxel(selectedVoxel.x, selectedVoxel.y, selectedVoxel.z, selectedVoxel.s); @@ -1061,25 +1199,54 @@ function mouseMoveEvent(event) { if (!editToolsOn) { return; } - + if (inspectJsIsRunning) { + return; + } - if (isMovingSlider) { - thumbX = (event.x - thumbClickOffsetX) - sliderX; - if (thumbX < minThumbX) { - thumbX = minThumbX; - } - if (thumbX > maxThumbX) { - thumbX = maxThumbX; - } - calcScaleFromThumb(thumbX); + // Move Import Preview + if (isImporting) { + var pickRay = Camera.computePickRay(event.x, event.y); + var intersection = Voxels.findRayIntersection(pickRay); - } else if (isAdding) { + var distance = 2 * scaleSelector.scale; + + if (intersection.intersects) { + var intersectionDistance = Vec3.length(Vec3.subtract(pickRay.origin, intersection.intersection)); + if (intersectionDistance < distance) { + distance = intersectionDistance * 0.99; + } + + } + + var targetPosition = { x: pickRay.direction.x * distance, + y: pickRay.direction.y * distance, + z: pickRay.direction.z * distance + }; + targetPosition.x += pickRay.origin.x; + targetPosition.y += pickRay.origin.y; + targetPosition.z += pickRay.origin.z; + + if (targetPosition.x < 0) targetPosition.x = 0; + if (targetPosition.y < 0) targetPosition.y = 0; + if (targetPosition.z < 0) targetPosition.z = 0; + + var nudgeFactor = scaleSelector.scale; + var newPosition = { + x: Math.floor(targetPosition.x / nudgeFactor) * nudgeFactor, + y: Math.floor(targetPosition.y / nudgeFactor) * nudgeFactor, + z: Math.floor(targetPosition.z / nudgeFactor) * nudgeFactor + } + + moveImport(newPosition); + } + + if (isAdding) { // Watch the drag direction to tell which way to 'extrude' this voxel if (!isExtruding) { var pickRay = Camera.computePickRay(event.x, event.y); - var lastVoxelDistance = { x: pickRay.origin.x - lastVoxelPosition.x, - y: pickRay.origin.y - lastVoxelPosition.y, - z: pickRay.origin.z - lastVoxelPosition.z }; + var lastVoxelDistance = { x: pickRay.origin.x - lastVoxelPosition.x, + y: pickRay.origin.y - lastVoxelPosition.y, + z: pickRay.origin.z - lastVoxelPosition.z }; var distance = Vec3.length(lastVoxelDistance); var mouseSpot = { x: pickRay.direction.x * distance, y: pickRay.direction.y * distance, z: pickRay.direction.z * distance }; mouseSpot.x += pickRay.origin.x; @@ -1097,15 +1264,15 @@ function mouseMoveEvent(event) { else if (dy < -lastVoxelScale) extrudeDirection.y = -extrudeScale; else if (dz > lastVoxelScale) extrudeDirection.z = extrudeScale; else if (dz < -lastVoxelScale) extrudeDirection.z = -extrudeScale; - else isExtruding = false; + else isExtruding = false; } else { // We have got an extrusion direction, now look for mouse move beyond threshold to add new voxel - var dx = event.x - mouseX; + var dx = event.x - mouseX; var dy = event.y - mouseY; if (Math.sqrt(dx*dx + dy*dy) > PIXELS_PER_EXTRUDE_VOXEL) { lastVoxelPosition = Vec3.sum(lastVoxelPosition, extrudeDirection); - Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z, - extrudeScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue); + Voxels.setVoxel(lastVoxelPosition.x, lastVoxelPosition.y, lastVoxelPosition.z, + extrudeScale, lastVoxelColor.red, lastVoxelColor.green, lastVoxelColor.blue); mouseX = event.x; mouseY = event.y; } @@ -1119,61 +1286,61 @@ function mouseMoveEvent(event) { function mouseReleaseEvent(event) { // if our tools are off, then don't do anything if (!editToolsOn) { - return; + return; } - - if (isMovingSlider) { - isMovingSlider = false; + if (inspectJsIsRunning) { + return; } + isAdding = false; - isExtruding = false; + isExtruding = false; } function moveTools() { // move the swatches - swatchesX = (windowDimensions.x - (swatchesWidth + sliderWidth)) / 2; + swatchesX = (windowDimensions.x - (swatchesWidth + scaleSelectorWidth)) / 2; swatchesY = windowDimensions.y - swatchHeight + 1; - + // create the overlays, position them in a row, set their colors, and for the selected one, use a different source image // location so that it displays the "selected" marker for (s = 0; s < numColors; s++) { var extraWidth = 0; - + if (s == 0) { extraWidth = swatchExtraPadding; } - + var imageFromX = swatchExtraPadding - extraWidth + s * swatchWidth; var imageFromY = swatchHeight + 1; if (s == whichColor) { imageFromY = 0; } - + var swatchX = swatchExtraPadding - extraWidth + swatchesX + ((swatchWidth - 1) * s); - + if (s == (numColors - 1)) { extraWidth = swatchExtraPadding; } Overlays.editOverlay(swatches[s], { - x: swatchX, - y: swatchesY, - subImage: { x: imageFromX, y: imageFromY, width: swatchWidth + extraWidth, height: swatchHeight }, - color: colors[s], - alpha: 1, - visible: editToolsOn - }); + x: swatchX, + y: swatchesY, + subImage: { x: imageFromX, y: imageFromY, width: swatchWidth + extraWidth, height: swatchHeight }, + color: colors[s], + alpha: 1, + visible: editToolsOn + }); } - + // move the tools toolsY = (windowDimensions.y - toolsHeight) / 2; - + var voxelToolOffset = 1, - recolorToolOffset = 1, - eyedropperToolOffset = 1; - + recolorToolOffset = 1, + eyedropperToolOffset = 1; + var voxelToolColor = WHITE_COLOR; - + if (recolorToolSelected) { recolorToolOffset = 2; } else if (eyedropperToolSelected) { @@ -1184,52 +1351,27 @@ function moveTools() { } voxelToolOffset = 2; } - + Overlays.editOverlay(voxelTool, { - subImage: { x: 0, y: toolHeight * voxelToolOffset, width: toolWidth, height: toolHeight }, - x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * voxelToolAt), width: toolWidth, height: toolHeight, - color: voxelToolColor, - visible: editToolsOn - }); - + subImage: { x: 0, y: toolHeight * voxelToolOffset, width: toolWidth, height: toolHeight }, + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * voxelToolAt), width: toolWidth, height: toolHeight, + color: voxelToolColor, + visible: editToolsOn + }); + Overlays.editOverlay(recolorTool, { - subImage: { x: 0, y: toolHeight * recolorToolOffset, width: toolWidth, height: toolHeight }, - x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * recolorToolAt), width: toolWidth, height: toolHeight, - visible: editToolsOn - }); - + subImage: { x: 0, y: toolHeight * recolorToolOffset, width: toolWidth, height: toolHeight }, + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * recolorToolAt), width: toolWidth, height: toolHeight, + visible: editToolsOn + }); + Overlays.editOverlay(eyedropperTool, { - subImage: { x: 0, y: toolHeight * eyedropperToolOffset, width: toolWidth, height: toolHeight }, - x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * eyedropperToolAt), width: toolWidth, height: toolHeight, - visible: editToolsOn - }); - - sliderX = swatchesX + swatchesWidth - sliderOffsetX; - sliderY = windowDimensions.y - sliderHeight + 1; - thumbY = sliderY + thumbOffsetY; - Overlays.editOverlay(slider, { x: sliderX, y: sliderY, visible: editToolsOn }); - - // This is the thumb of our slider - Overlays.editOverlay(thumb, { x: sliderX + thumbX, y: thumbY, visible: editToolsOn }); - -} - -function touchBeginEvent(event) { - if (!editToolsOn) { - return; - } -} - -function touchUpdateEvent(event) { - if (!editToolsOn) { - return; - } -} - -function touchEndEvent(event) { - if (!editToolsOn) { - return; - } + subImage: { x: 0, y: toolHeight * eyedropperToolOffset, width: toolWidth, height: toolHeight }, + x: toolsX, y: toolsY + ((toolHeight + toolVerticalSpacing) * eyedropperToolAt), width: toolWidth, height: toolHeight, + visible: editToolsOn + }); + + scaleSelector.move(); } var lastFingerAddVoxel = { x: -1, y: -1, z: -1}; // off of the build-able area @@ -1238,8 +1380,8 @@ var lastFingerDeleteVoxel = { x: -1, y: -1, z: -1}; // off of the build-able are function checkControllers() { var controllersPerPalm = 2; // palm and finger for (var palm = 0; palm < 2; palm++) { - var palmController = palm * controllersPerPalm; - var fingerTipController = palmController + 1; + var palmController = palm * controllersPerPalm; + var fingerTipController = palmController + 1; var fingerTipPosition = Controller.getSpatialControlPosition(fingerTipController); var BUTTON_COUNT = 6; @@ -1247,18 +1389,14 @@ function checkControllers() { var BUTTON_1 = BUTTON_BASE + 1; var BUTTON_2 = BUTTON_BASE + 2; var FINGERTIP_VOXEL_SIZE = 0.05; - + if (Controller.isButtonPressed(BUTTON_1)) { if (Vec3.length(Vec3.subtract(fingerTipPosition,lastFingerAddVoxel)) > (FINGERTIP_VOXEL_SIZE / 2)) { - if (whichColor == -1) { - newColor = { red: colors[0].red, green: colors[0].green, blue: colors[0].blue }; - } else { - newColor = { red: colors[whichColor].red, green: colors[whichColor].green, blue: colors[whichColor].blue }; - } - + newColor = { red: colors[whichColor].red, green: colors[whichColor].green, blue: colors[whichColor].blue }; + Voxels.eraseVoxel(fingerTipPosition.x, fingerTipPosition.y, fingerTipPosition.z, FINGERTIP_VOXEL_SIZE); Voxels.setVoxel(fingerTipPosition.x, fingerTipPosition.y, fingerTipPosition.z, FINGERTIP_VOXEL_SIZE, - newColor.red, newColor.green, newColor.blue); + newColor.red, newColor.green, newColor.blue); lastFingerAddVoxel = fingerTipPosition; } @@ -1278,21 +1416,8 @@ function update(deltaTime) { windowDimensions = newWindowDimensions; moveTools(); } - + checkControllers(); - - // Move Import Preview - if (isImporting) { - var position = MyAvatar.position; - var forwardVector = Quat.getFront(MyAvatar.orientation); - var targetPosition = Vec3.sum(position, Vec3.multiply(forwardVector, importScale)); - var newPosition = { - x: Math.floor(targetPosition.x / importScale) * importScale, - y: Math.floor(targetPosition.y / importScale) * importScale, - z: Math.floor(targetPosition.z / importScale) * importScale - } - moveImport(newPosition); - } } } @@ -1300,28 +1425,17 @@ function wheelEvent(event) { wheelPixelsMoved += event.delta; if (Math.abs(wheelPixelsMoved) > WHEEL_PIXELS_PER_SCALE_CHANGE) { - if (!pointerVoxelScaleSet) { - pointerVoxelScale = 1.0; - pointerVoxelScaleSet = true; - } + if (wheelPixelsMoved > 0) { - pointerVoxelScale /= 2.0; - if (pointerVoxelScale < MIN_VOXEL_SCALE) { - pointerVoxelScale = MIN_VOXEL_SCALE; - } + scaleSelector.decrementScale(); } else { - pointerVoxelScale *= 2.0; - if (pointerVoxelScale > MAX_VOXEL_SCALE) { - pointerVoxelScale = MAX_VOXEL_SCALE; - } + scaleSelector.incrementScale(); } - calcThumbFromScale(pointerVoxelScale); trackMouseEvent(event); wheelPixelsMoved = 0; - + if (isImporting) { - var importScale = (pointerVoxelScale / MAX_VOXEL_SCALE) * MAX_PASTE_VOXEL_SCALE; - rescaleImport(importScale); + rescaleImport(); } } } @@ -1332,9 +1446,6 @@ Controller.mouseReleaseEvent.connect(mouseReleaseEvent); Controller.mouseMoveEvent.connect(mouseMoveEvent); Controller.keyPressEvent.connect(keyPressEvent); Controller.keyReleaseEvent.connect(keyReleaseEvent); -Controller.touchBeginEvent.connect(touchBeginEvent); -Controller.touchUpdateEvent.connect(touchUpdateEvent); -Controller.touchEndEvent.connect(touchEndEvent); Controller.captureKeyEvents({ text: "+" }); Controller.captureKeyEvents({ text: "-" }); @@ -1351,11 +1462,10 @@ function scriptEnding() { Overlays.deleteOverlay(voxelTool); Overlays.deleteOverlay(recolorTool); Overlays.deleteOverlay(eyedropperTool); - Overlays.deleteOverlay(slider); - Overlays.deleteOverlay(thumb); Controller.releaseKeyEvents({ text: "+" }); Controller.releaseKeyEvents({ text: "-" }); cleanupImport(); + scaleSelector.cleanup(); cleanupMenus(); } Script.scriptEnding.connect(scriptEnding); diff --git a/examples/findParticleExample.js b/examples/findParticleExample.js index 4a0e9b832a..635e30f344 100644 --- a/examples/findParticleExample.js +++ b/examples/findParticleExample.js @@ -1,12 +1,15 @@ // // findParticleExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/24/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates "finding" particles // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var iteration = 0; diff --git a/examples/flockingBirds.js b/examples/flockingBirds.js index 12b402ab40..17c60d88aa 100644 --- a/examples/flockingBirds.js +++ b/examples/flockingBirds.js @@ -1,9 +1,9 @@ // // flockingBirds.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 3/4/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that generates particles that act like flocking birds // @@ -13,12 +13,16 @@ // go back to gliding // birds don't like to be below a certain altitude // if they are below that altitude they will keep thrusting up, until they get ove - +// // flocking // try to align your velocity with velocity of other birds // try to fly toward center of flock // but dont get too close // +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var birdsInFlock = 40; diff --git a/examples/fountain.js b/examples/fountain.js index 8816dab09a..ab1b82cb76 100644 --- a/examples/fountain.js +++ b/examples/fountain.js @@ -1,4 +1,12 @@ - +// +// fountain.js +// examples +// +// 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 +// function vLength(v) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); diff --git a/examples/gameoflife.js b/examples/gameoflife.js index c0122c604a..3fcbfa8138 100644 --- a/examples/gameoflife.js +++ b/examples/gameoflife.js @@ -1,6 +1,14 @@ -// Add your JavaScript for assignment below this line - -// The following is an example of Conway's Game of Life (http://en.wikipedia.org/wiki/Conway's_Game_of_Life) +// +// gameoflife.js +// examples +// +// Copyright 2014 High Fidelity, Inc. +// +// The following is an example of Conway's Game of Life (http://en.wikipedia.org/wiki/Conway's_Game_of_Life) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var NUMBER_OF_CELLS_EACH_DIMENSION = 64; var NUMBER_OF_CELLS = NUMBER_OF_CELLS_EACH_DIMENSION * NUMBER_OF_CELLS_EACH_DIMENSION; diff --git a/examples/globalCollisionsExample.js b/examples/globalCollisionsExample.js index 266823f564..11d90cd15b 100644 --- a/examples/globalCollisionsExample.js +++ b/examples/globalCollisionsExample.js @@ -1,29 +1,33 @@ // // globalCollisionsExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/29/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Controller class // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // print("hello..."); -function particleCollisionWithVoxel(particle, voxel, penetration) { +function particleCollisionWithVoxel(particle, voxel, collision) { print("particleCollisionWithVoxel().."); print(" particle.getID()=" + particle.id); print(" voxel color...=" + voxel.red + ", " + voxel.green + ", " + voxel.blue); - Vec3.print('penetration=', penetration); + Vec3.print('penetration=', collision.penetration); + Vec3.print('contactPoint=', collision.contactPoint); } -function particleCollisionWithParticle(particleA, particleB, penetration) { +function particleCollisionWithParticle(particleA, particleB, collision) { print("particleCollisionWithParticle().."); print(" particleA.getID()=" + particleA.id); print(" particleB.getID()=" + particleB.id); - Vec3.print('penetration=', penetration); + Vec3.print('penetration=', collision.penetration); + Vec3.print('contactPoint=', collision.contactPoint); } Particles.particleCollisionWithVoxel.connect(particleCollisionWithVoxel); diff --git a/examples/gun.js b/examples/gun.js index e358e6b391..4bfdb140b8 100644 --- a/examples/gun.js +++ b/examples/gun.js @@ -1,18 +1,19 @@ // // gun.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 12/31/13. // Modified by Philip on 3/3/14 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // This is an example script that turns the hydra controllers and mouse into a particle gun. // It reads the controller, watches for trigger pulls, and launches particles. // When particles collide with voxels they blow little holes out of the voxels. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // - function getRandomFloat(min, max) { return Math.random() * (max - min) + min; } @@ -145,24 +146,20 @@ function shootTarget() { -function particleCollisionWithVoxel(particle, voxel, penetration) { +function particleCollisionWithVoxel(particle, voxel, collision) { var HOLE_SIZE = 0.125; var particleProperties = Particles.getParticleProperties(particle); var position = particleProperties.position; Particles.deleteParticle(particle); // Make a hole in this voxel - Vec3.print("penetration", penetration); - Vec3.print("position", position); - var pointOfEntry = Vec3.subtract(position, penetration); - Vec3.print("pointOfEntry", pointOfEntry); - Voxels.eraseVoxel(pointOfEntry.x, pointOfEntry.y, pointOfEntry.z, HOLE_SIZE); - Voxels.eraseVoxel(position.x, position.y, position.z, HOLE_SIZE); - //audioOptions.position = position; - audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); + //Vec3.print("voxel penetration", collision.penetration); + //Vec3.print("voxel contactPoint", collision.contactPoint); + Voxels.eraseVoxel(collision.contactPoint.x, collision.contactPoint.y, collision.contactPoint.z, HOLE_SIZE); + audioOptions.position = collision.contactPoint; Audio.playSound(impactSound, audioOptions); } -function particleCollisionWithParticle(particle1, particle2) { +function particleCollisionWithParticle(particle1, particle2, collision) { score++; if (showScore) { Overlays.editOverlay(text, { text: "Score: " + score } ); @@ -173,10 +170,12 @@ function particleCollisionWithParticle(particle1, particle2) { // Record shot time var endTime = new Date(); var msecs = endTime.valueOf() - shotTime.valueOf(); - print("hit, msecs = " + msecs); + //print("hit, msecs = " + msecs); + //Vec3.print("penetration = ", collision.penetration); + //Vec3.print("contactPoint = ", collision.contactPoint); Particles.deleteParticle(particle1); Particles.deleteParticle(particle2); - audioOptions.position = newPosition; + // play the sound near the camera so the shooter can hear it audioOptions.position = Vec3.sum(Camera.getPosition(), Quat.getFront(Camera.getOrientation())); Audio.playSound(targetHitSound, audioOptions); } diff --git a/examples/hideAvatarExample.js b/examples/hideAvatarExample.js index 7a0ece76eb..66d85becf1 100644 --- a/examples/hideAvatarExample.js +++ b/examples/hideAvatarExample.js @@ -1,12 +1,14 @@ // // hideAvatarExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates how to enable or disable local rendering of your own avatar // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // function keyReleaseEvent(event) { diff --git a/examples/hydraMove.js b/examples/hydraMove.js index 6268a38ba3..b73f990446 100644 --- a/examples/hydraMove.js +++ b/examples/hydraMove.js @@ -1,13 +1,15 @@ // // hydraMove.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/10/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Controller and MyAvatar classes to implement // avatar flying through the hydra/controller joysticks // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var damping = 0.9; diff --git a/examples/includeExample.js b/examples/includeExample.js index 489928d759..50a1234772 100644 --- a/examples/includeExample.js +++ b/examples/includeExample.js @@ -1,12 +1,15 @@ // // includeExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 3/24/14 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // This is an example script that demonstrates use of the Script.include() feature // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// // You can include scripts from URLs Script.include("http://public.highfidelity.io/scripts/lookWithTouch.js"); diff --git a/examples/inspect.js b/examples/inspect.js index 2443eadf9b..28db1e7735 100644 --- a/examples/inspect.js +++ b/examples/inspect.js @@ -1,9 +1,9 @@ // // inspect.js -// hifi +// examples // // Created by Clément Brisset on March 20, 2014 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // Allows you to inspect non moving objects (Voxels or Avatars) using Atl, Control (Command on Mac) and Shift // @@ -13,6 +13,9 @@ // Once you are in a mode left click on the object to inspect and hold the click // Dragging the mouse will move your camera according to the mode you are in. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var PI = 3.14 // No need for something more precise diff --git a/examples/localVoxelsExample.js b/examples/localVoxelsExample.js index 7d9007f590..d64138b214 100644 --- a/examples/localVoxelsExample.js +++ b/examples/localVoxelsExample.js @@ -1,3 +1,12 @@ +// +// localVoxelsExample.js +// examples +// +// 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 TREE_SCALE = 16384; var tree = LocalVoxels("tree"); diff --git a/examples/lookAtExample.js b/examples/lookAtExample.js index 46e0863231..1cf8aabb96 100644 --- a/examples/lookAtExample.js +++ b/examples/lookAtExample.js @@ -1,9 +1,9 @@ // // lookAtExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/6/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Camera class's lookAt(), keepLookingAt(), and stopLookingAt() // features. @@ -12,6 +12,8 @@ // on the face of the voxel that you clicked. Click again and it will stop looking at that point. While in this fixed mode // you can use the arrow keys to change the position of the camera. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var lookingAtSomething = false; diff --git a/examples/lookWithMouse.js b/examples/lookWithMouse.js index ef8f11ec16..256a3ea67c 100644 --- a/examples/lookWithMouse.js +++ b/examples/lookWithMouse.js @@ -1,12 +1,14 @@ // // lookWithMouse.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Controller class // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var alwaysLook = true; // if you want the mouse look to happen only when you click, change this to false diff --git a/examples/lookWithTouch.js b/examples/lookWithTouch.js index 4406b4567e..68b29c305b 100644 --- a/examples/lookWithTouch.js +++ b/examples/lookWithTouch.js @@ -1,12 +1,14 @@ // // lookWithTouch.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Controller class // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // var startedTouching = false; diff --git a/examples/menuExample.js b/examples/menuExample.js index 874d95ec31..00c6418269 100644 --- a/examples/menuExample.js +++ b/examples/menuExample.js @@ -1,12 +1,15 @@ // // menuExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/24/14 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // This is an example script that demonstrates use of the Menu object // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// function setupMenus() { diff --git a/examples/movingVoxel.js b/examples/movingVoxel.js index 0ddce48334..c55957cd04 100644 --- a/examples/movingVoxel.js +++ b/examples/movingVoxel.js @@ -1,6 +1,14 @@ // +// movingVoxel.js +// examples +// +// Copyright 2014 High Fidelity, Inc. +// // This sample script creates a voxel moving back and forth in a line // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var position = { x: 0, y: 0, z: 0 }; var oldPosition = { x: 0, y: 0, z:0 }; diff --git a/examples/multitouchExample.js b/examples/multitouchExample.js index 51bbcc2c20..9d27b7ae2a 100644 --- a/examples/multitouchExample.js +++ b/examples/multitouchExample.js @@ -1,9 +1,9 @@ // // multitouchExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/9/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Controller class's multi-touch features // @@ -11,6 +11,9 @@ // * Four finger rotate gesture will rotate your avatar. // * Three finger swipe up/down will adjust the pitch of your avatars head. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var lastX = 0; var lastY = 0; diff --git a/examples/overlaysExample.js b/examples/overlaysExample.js index 60f924338f..d0b3870803 100644 --- a/examples/overlaysExample.js +++ b/examples/overlaysExample.js @@ -1,12 +1,14 @@ // // overlaysExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/14/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Overlays class // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // diff --git a/examples/paintGun.js b/examples/paintGun.js index 6b6e78b43e..b5bb2a49da 100644 --- a/examples/paintGun.js +++ b/examples/paintGun.js @@ -1,9 +1,12 @@ // // paintGun.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 12/31/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 // // initialize our triggers @@ -62,9 +65,10 @@ function checkController(deltaTime) { // This is the script for the particles that this gun shoots. var script = - " function collisionWithVoxel(voxel, penetration) { " + + " function collisionWithVoxel(voxel, collision) { " + " print('collisionWithVoxel(voxel)... '); " + - " Vec3.print('penetration=', penetration); " + + " Vec3.print('penetration=', collision.penetration); " + + " Vec3.print('contactPoint=', collision.contactPoint); " + " print('myID=' + Particle.getID() + '\\n'); " + " var voxelColor = { red: voxel.red, green: voxel.green, blue: voxel.blue };" + " var voxelAt = { x: voxel.x, y: voxel.y, z: voxel.z };" + diff --git a/examples/particleBird.js b/examples/particleBird.js index 5cd068bd60..5241b3550b 100644 --- a/examples/particleBird.js +++ b/examples/particleBird.js @@ -1,9 +1,14 @@ // // particleBird.js -// hifi +// examples +// +// Copyright 2014 High Fidelity, Inc. // // This sample script moves a voxel around like a bird and sometimes makes tweeting noises // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// function vLength(v) { return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z); diff --git a/examples/particleModelExample.js b/examples/particleModelExample.js index c43956cd3e..b489e704c9 100644 --- a/examples/particleModelExample.js +++ b/examples/particleModelExample.js @@ -1,12 +1,15 @@ // // particleModelExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates creating and editing a particle // +// 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 stopAfter = 100; diff --git a/examples/playSound.js b/examples/playSound.js index 18857826ea..317581ba36 100644 --- a/examples/playSound.js +++ b/examples/playSound.js @@ -1,14 +1,20 @@ // +// playSound.js +// examples +// +// Copyright 2014 High Fidelity, Inc. // This sample script loads a sound file and plays it at the 'fingertip' of the // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // First, load the clap sound from a URL -var clap = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); +var clap = new Sound("https://s3-us-west-1.amazonaws.com/highfidelity-public/sounds/Animals/bushtit_1.raw"); function maybePlaySound(deltaTime) { if (Math.random() < 0.01) { // Set the location and other info for the sound to play - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); var palmPosition = Controller.getSpatialControlPosition(0); options.position = palmPosition; options.volume = 0.5; diff --git a/examples/rayPickExample.js b/examples/rayPickExample.js index 9c34cca1de..c09a7b1e57 100644 --- a/examples/rayPickExample.js +++ b/examples/rayPickExample.js @@ -1,12 +1,14 @@ // // rayPickExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/6/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Camera class // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // function mouseMoveEvent(event) { diff --git a/examples/ribbon.js b/examples/ribbon.js index a6e1edfa4a..dd3d46fbfc 100644 --- a/examples/ribbon.js +++ b/examples/ribbon.js @@ -1,9 +1,12 @@ // // ribbon.js -// hifi +// examples // // Created by Andrzej Kapolka on 2/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 // function vectorMultiply(vector, scalar) { diff --git a/examples/rideAlongWithAParticleExample.js b/examples/rideAlongWithAParticleExample.js index 1148b96b4d..552f6fb7fe 100644 --- a/examples/rideAlongWithAParticleExample.js +++ b/examples/rideAlongWithAParticleExample.js @@ -1,12 +1,15 @@ // // rideAlongWithAParticleExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/24/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates "finding" particles // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var iteration = 0; var lengthOfRide = 2000; // in iterations diff --git a/examples/seeingVoxelsExample.js b/examples/seeingVoxelsExample.js index 0cfe54d89e..35e2f95dd7 100644 --- a/examples/seeingVoxelsExample.js +++ b/examples/seeingVoxelsExample.js @@ -1,11 +1,12 @@ // // seeingVoxelsExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/26/14 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // -// This is an example script +// 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; diff --git a/examples/selectAudioDevice.js b/examples/selectAudioDevice.js index 958ca7babf..23606f774c 100644 --- a/examples/selectAudioDevice.js +++ b/examples/selectAudioDevice.js @@ -1,12 +1,15 @@ // // audioDeviceExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 3/22/14 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // This is an example script that demonstrates use of the Menu object // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// if (typeof String.prototype.startsWith != 'function') { String.prototype.startsWith = function (str){ diff --git a/examples/settingsExample.js b/examples/settingsExample.js index 0dcc5482b6..bef347ff07 100644 --- a/examples/settingsExample.js +++ b/examples/settingsExample.js @@ -1,15 +1,15 @@ // // settingsExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 3/22/14 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // This is an example script that demonstrates use of the Menu object // - - - +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// print("mySetting: " + Settings.getValue("mySetting")); Settings.setValue("mySetting", "spam"); diff --git a/examples/spaceInvadersExample.js b/examples/spaceInvadersExample.js index 61ff93fc8f..5d62102d71 100644 --- a/examples/spaceInvadersExample.js +++ b/examples/spaceInvadersExample.js @@ -1,12 +1,15 @@ // // spaceInvadersExample.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/30/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates a simple space invaders style of game // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// var iteration = 0; @@ -214,7 +217,7 @@ function update(deltaTime) { if (invaderStepOfCycle % stepsPerSound == 0) { // play the move sound - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, y: MyAvatar.position.y + 0.1, @@ -326,7 +329,7 @@ function fireMissile() { lifetime: 5 }); - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, y: MyAvatar.position.y + 0.1, @@ -376,7 +379,7 @@ function deleteIfInvader(possibleInvaderParticle) { Particles.deleteParticle(myMissile); // play the hit sound - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); if (soundInMyHead) { options.position = { x: MyAvatar.position.x + 0.0, y: MyAvatar.position.y + 0.1, @@ -392,9 +395,10 @@ function deleteIfInvader(possibleInvaderParticle) { } } -function particleCollisionWithParticle(particleA, particleB, penetration) { +function particleCollisionWithParticle(particleA, particleB, collision) { print("particleCollisionWithParticle() a.id="+particleA.id + " b.id=" + particleB.id); - Vec3.print('particleCollisionWithParticle() penetration=', penetration); + Vec3.print('particleCollisionWithParticle() penetration=', collision.penetration); + Vec3.print('particleCollisionWithParticle() contactPoint=', collision.contactPoint); if (missileFired) { myMissile = Particles.identifyParticle(myMissile); if (myMissile.id == particleA.id) { @@ -413,4 +417,3 @@ initializeInvaders(); // shut down the game after 1 minute var gameTimer = Script.setTimeout(endGame, itemLifetimes * 1000); - diff --git a/examples/testingVoxelViewerRestart.js b/examples/testingVoxelViewerRestart.js new file mode 100644 index 0000000000..c4ab67e8ba --- /dev/null +++ b/examples/testingVoxelViewerRestart.js @@ -0,0 +1,94 @@ +// +// testingVoxelSeeingRestart.js +// hifi +// +// Created by Brad Hefta-Gaub on 2/26/14 +// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// +// 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 yawDirection = -1; +var yaw = 45; +var yawMax = 70; +var yawMin = 20; +var vantagePoint = {x: 5000, y: 500, z: 5000}; + +var isLocal = false; + +// set up our VoxelViewer with a position and orientation +var orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0); + +function getRandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function init() { + if (isLocal) { + MyAvatar.position = vantagePoint; + MyAvatar.orientation = orientation; + } else { + VoxelViewer.setPosition(vantagePoint); + VoxelViewer.setOrientation(orientation); + VoxelViewer.queryOctree(); + Agent.isAvatar = true; + } +} + +function keepLooking(deltaTime) { + //print("count =" + count); + + if (count == 0) { + init(); + } + count++; + if (count % getRandomInt(5, 15) == 0) { + yaw += yawDirection; + orientation = Quat.fromPitchYawRollDegrees(0, yaw, 0); + if (yaw > yawMax || yaw < yawMin) { + yawDirection = yawDirection * -1; + } + + //if (count % 10000 == 0) { + // print("calling VoxelViewer.queryOctree()... count=" + count + " yaw=" + yaw); + //} + + if (isLocal) { + MyAvatar.orientation = orientation; + } else { + VoxelViewer.setOrientation(orientation); + VoxelViewer.queryOctree(); + + //if (count % 10000 == 0) { + // print("VoxelViewer.getOctreeElementsCount()=" + VoxelViewer.getOctreeElementsCount()); + //} + } + } + + // approximately every second, consider stopping + if (count % 60 == 0) { + print("considering stop.... elementCount:" + VoxelViewer.getOctreeElementsCount()); + var stopProbability = 0.05; // 5% chance of stopping + if (Math.random() < stopProbability) { + print("stopping.... elementCount:" + VoxelViewer.getOctreeElementsCount()); + Script.stop(); + } + } +} + +function scriptEnding() { + print("SCRIPT ENDNG!!!\n"); +} + +// register the call back so it fires before each data send +Script.update.connect(keepLooking); + +// register our scriptEnding callback +Script.scriptEnding.connect(scriptEnding); + + +// test for local... +Menu.isOptionChecked("Voxels"); +isLocal = true; // will only get here on local client diff --git a/examples/timer.js b/examples/timer.js index c7ad0290ab..b3af1d902c 100644 --- a/examples/timer.js +++ b/examples/timer.js @@ -1,3 +1,13 @@ +// +// timer.js +// examples +// +// 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 one_timer = Script.setTimeout(function() { print("One time timer fired!"); }, 10000); var multiple_timer = Script.setInterval(function() { print("Repeating timer fired!"); }, 1000); diff --git a/examples/toyball.js b/examples/toyball.js index 36f1aa11e5..d312c1bc94 100644 --- a/examples/toyball.js +++ b/examples/toyball.js @@ -1,9 +1,9 @@ // // toyball.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 1/20/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that turns the hydra controllers into a toy ball catch and throw game. // It reads the controller, watches for button presses and trigger pulls, and launches particles. @@ -11,6 +11,8 @@ // The particles it creates have a script that when they collide with Voxels, the // particle will change it's color to match the voxel it hits. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // // maybe we should make these constants... @@ -109,7 +111,7 @@ function checkControllerSide(whichSide) { velocity : { x: 0, y: 0, z: 0}, inHand: true }; Particles.editParticle(closestParticle, properties); - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); options.position = ballPosition; options.volume = 1.0; Audio.playSound(catchSound, options); @@ -150,7 +152,7 @@ function checkControllerSide(whichSide) { } // Play a new ball sound - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); options.position = ballPosition; options.volume = 1.0; Audio.playSound(catchSound, options); @@ -199,7 +201,7 @@ function checkControllerSide(whichSide) { rightHandParticle = false; } - var options = new AudioInjectionOptions();
 + var options = new AudioInjectionOptions(); options.position = ballPosition; options.volume = 1.0; Audio.playSound(throwSound, options); diff --git a/examples/voxelDrumming.js b/examples/voxelDrumming.js index 178c6734d8..1b320ed755 100644 --- a/examples/voxelDrumming.js +++ b/examples/voxelDrumming.js @@ -1,14 +1,17 @@ // // voxelDrumming.js -// hifi +// examples // // Created by Brad Hefta-Gaub on 2/14/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // This is an example script that demonstrates use of the Overlays, Controller, and Audio classes // // It adds Hydra controller "fingertip on voxels" drumming // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// Menu.addMenuItem({ menuName: "Developer > Hand Options", @@ -61,7 +64,7 @@ collisionBubble[1] = Overlays.addOverlay("sphere", visible: false }); -var audioOptions = new AudioInjectionOptions();
 +var audioOptions = new AudioInjectionOptions(); audioOptions.position = { x: MyAvatar.position.x, y: MyAvatar.position.y + 1, z: MyAvatar.position.z }; audioOptions.volume = 1; diff --git a/interface/CMakeLists.txt b/interface/CMakeLists.txt index f991212a6e..557af35e12 100644 --- a/interface/CMakeLists.txt +++ b/interface/CMakeLists.txt @@ -12,6 +12,7 @@ project(${TARGET_NAME}) # setup for find modules set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/") +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(SIXENSE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense") @@ -32,8 +33,8 @@ elseif (UNIX) # include the right GL headers for UNIX set(GL_HEADERS "#include \n#include \n#include ") elseif (WIN32) - add_definitions( -D_USE_MATH_DEFINES ) # apparently needed to get M_PI and other defines from cmath/math.h - add_definitions( -DWINDOWS_LEAN_AND_MEAN ) # needed to make sure windows doesn't go to crazy with its defines + add_definitions(-D_USE_MATH_DEFINES) # apparently needed to get M_PI and other defines from cmath/math.h + add_definitions(-DWINDOWS_LEAN_AND_MEAN) # needed to make sure windows doesn't go to crazy with its defines set(GL_HEADERS "#include \n#include \n#include ") endif () @@ -67,23 +68,20 @@ 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) # have qt5 wrap them and generate the appropriate header files qt5_wrap_ui(QT_UI_HEADERS "${QT_UI_FILES}") -# grab the resource files in resources -file (GLOB_RECURSE QT_RESOURCE_FILES resources/*.qrc) -# have qt5 wrap them and generate the appropriate source files -qt5_add_resources(QT_RESOURCES "${QT_RESOURCE_FILES}") - # add them to the interface source files set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}") -set(QM ${TARGET_NAME}_en.qm) -set(TS ${TARGET_NAME}_en.ts) -qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS}) +# translation disabled until we strip out the line numbers +# set(QM ${TARGET_NAME}_en.qm) +# set(TS ${TARGET_NAME}_en.ts) +# qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS}) if (APPLE) # configure CMake to use a custom Info.plist @@ -122,13 +120,16 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(metavoxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(avatars ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(audio ${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(Sixense) @@ -162,6 +163,13 @@ if (VISAGE_FOUND AND NOT DISABLE_VISAGE) target_link_libraries(${TARGET_NAME} "${VISAGE_LIBRARIES}") endif (VISAGE_FOUND AND NOT DISABLE_VISAGE) +# and with Faceplus library, also for webcam feature tracking +if (FACEPLUS_FOUND AND NOT DISABLE_FACEPLUS) + add_definitions(-DHAVE_FACEPLUS) + include_directories(SYSTEM "${FACEPLUS_INCLUDE_DIRS}") + target_link_libraries(${TARGET_NAME} "${FACEPLUS_LIBRARIES}") +endif (FACEPLUS_FOUND AND NOT DISABLE_FACEPLUS) + # and with LibOVR for Oculus Rift if (LIBOVR_FOUND AND NOT DISABLE_LIBOVR) add_definitions(-DHAVE_LIBOVR) @@ -185,9 +193,14 @@ 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}") +include_directories(SYSTEM "${FACESHIFT_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") target_link_libraries( ${TARGET_NAME} @@ -195,6 +208,7 @@ 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}" ) if (APPLE) diff --git a/interface/InterfaceConfig.h.in b/interface/InterfaceConfig.h.in index 0b570f622f..b2b1881d67 100644 --- a/interface/InterfaceConfig.h.in +++ b/interface/InterfaceConfig.h.in @@ -1,9 +1,12 @@ // // InterfaceConfig.h -// interface +// interface/src // // Created by Stephen Birarda on 2/8/13. -// Copyright (c) 2013 High Fidelity, Inc.. All rights reserved. +// Copyright 2013 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 interface__InterfaceConfig__ diff --git a/interface/InterfaceVersion.h.in b/interface/InterfaceVersion.h.in index 60e51b4ab6..2f902de41c 100644 --- a/interface/InterfaceVersion.h.in +++ b/interface/InterfaceVersion.h.in @@ -1,9 +1,14 @@ // // InterfaceVersion.h -// Declaration of version and build data +// interface/src // // Created by Leonardo Murillo on 12/16/13. -// Copyright (c) 2013 High Fidelity, Inc.. All rights reserved. +// Copyright 2013 High Fidelity, Inc. +// +// Declaration of version and build data +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // const QString BUILD_VERSION = "@BUILD_SEQ@"; diff --git a/interface/external/faceplus/readme.txt b/interface/external/faceplus/readme.txt new file mode 100644 index 0000000000..e98f8becdc --- /dev/null +++ b/interface/external/faceplus/readme.txt @@ -0,0 +1,11 @@ + +Instructions for adding the Faceplus driver to Interface +Andrzej Kapolka, April 8, 2014 + +1. Copy the Faceplus sdk folders (include, win32) into the interface/external/faceplus folder. + This readme.txt should be there as well. + +2. Copy the Faceplus DLLs from the win32 folder into your path. + +3. Delete your build directory, run cmake and build, and you should be all set. + diff --git a/interface/interface_en.ts b/interface/interface_en.ts index aa979781de..6c4426b2c6 100644 --- a/interface/interface_en.ts +++ b/interface/interface_en.ts @@ -4,22 +4,22 @@ Application - + Export Voxels - + Sparse Voxel Octree Files (*.svo) - + Open Script - + JavaScript Files (*.js) @@ -27,25 +27,25 @@ ChatWindow - - + + Chat - - + + Connecting to XMPP... - - + + online now: - + day %n day @@ -53,7 +53,7 @@ - + hour %n hour @@ -61,7 +61,7 @@ - + minute %n minute @@ -76,7 +76,7 @@ - + %1 online now: @@ -113,22 +113,135 @@ Menu - + Open .ini config file - - + + Text files (*.ini) - + Save .ini config file + + PreferencesDialog + + + + Cancel + + + + + + Save all changes + + + + + + + + Avatar + + + + + + <html><head/><body><p>Avatar display name <span style=" color:#909090;">(optional)</span></p></body></html> + + + + + + Not showing a name + + + + + + Head + + + + + + Body + + + + + + Advanced Tuning + + + + + + It's not recomended that you play with these settings unless you've looked into exactly what they do. + + + + + + Vertical field of view + + + + + + Lean scale (applies to Faceshift users) + + + + + + Avatar scale <span style=" color:#909090;">(default is 1.0)</span> + + + + + + Pupil dillation + + + + + + Audio Jitter Buffer Samples (0 for automatic) + + + + + + Faceshift eye detection + + + + + + Voxels + + + + + + Maximum voxels + + + + + + Max voxels sent each second + + + QObject diff --git a/interface/resources/icons/load-script.svg b/interface/resources/icons/load-script.svg new file mode 100644 index 0000000000..21be61c321 --- /dev/null +++ b/interface/resources/icons/load-script.svg @@ -0,0 +1,125 @@ + + + + + + + + + + image/svg+xml + + + + + T.Hofmeister + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/new-script.svg b/interface/resources/icons/new-script.svg new file mode 100644 index 0000000000..f68fcfa967 --- /dev/null +++ b/interface/resources/icons/new-script.svg @@ -0,0 +1,129 @@ + + + + + + + + + + image/svg+xml + + + + + T.Hofmeister + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/save-script.svg b/interface/resources/icons/save-script.svg new file mode 100644 index 0000000000..04d41b8302 --- /dev/null +++ b/interface/resources/icons/save-script.svg @@ -0,0 +1,674 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + T.Hofmeister + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/start-script.svg b/interface/resources/icons/start-script.svg new file mode 100644 index 0000000000..86354a555d --- /dev/null +++ b/interface/resources/icons/start-script.svg @@ -0,0 +1,557 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + Maximillian Merlin + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/icons/stop-script.svg b/interface/resources/icons/stop-script.svg new file mode 100644 index 0000000000..31cdcee749 --- /dev/null +++ b/interface/resources/icons/stop-script.svg @@ -0,0 +1,163 @@ + + + + + + + + + + image/svg+xml + + + + + Maximillian Merlin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/interface/resources/images/audio-box.svg b/interface/resources/images/audio-box.svg new file mode 100644 index 0000000000..0c0db4f51f --- /dev/null +++ b/interface/resources/images/audio-box.svg @@ -0,0 +1,12 @@ + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + \ No newline at end of file diff --git a/interface/resources/images/body.png b/interface/resources/images/body.png deleted file mode 100644 index dda65bad07..0000000000 Binary files a/interface/resources/images/body.png and /dev/null differ diff --git a/interface/resources/images/close.png b/interface/resources/images/close.png deleted file mode 100644 index 8090e3d3d0..0000000000 Binary files a/interface/resources/images/close.png and /dev/null differ diff --git a/interface/resources/images/close.svg b/interface/resources/images/close.svg index 8fe4bf4bdb..0cae08c590 100644 --- a/interface/resources/images/close.svg +++ b/interface/resources/images/close.svg @@ -1,14 +1,12 @@ - - - - - - - - - - + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + \ No newline at end of file diff --git a/interface/resources/images/close_down.svg b/interface/resources/images/close_down.svg new file mode 100644 index 0000000000..f36865fff1 --- /dev/null +++ b/interface/resources/images/close_down.svg @@ -0,0 +1,79 @@ + + + + + + image/svg+xml + + Slice 1 + + + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + diff --git a/interface/resources/images/head.png b/interface/resources/images/head.png deleted file mode 100644 index d21b453bee..0000000000 Binary files a/interface/resources/images/head.png and /dev/null differ diff --git a/interface/resources/images/mic-mute.svg b/interface/resources/images/mic-mute.svg new file mode 100644 index 0000000000..39dd811df4 --- /dev/null +++ b/interface/resources/images/mic-mute.svg @@ -0,0 +1,115 @@ + + + +image/svg+xml + + + + + + + + Mic + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/interface/resources/images/mic.svg b/interface/resources/images/mic.svg index a6dee50740..3329f19647 100644 --- a/interface/resources/images/mic.svg +++ b/interface/resources/images/mic.svg @@ -1,20 +1,105 @@ - - Mic - Created with Sketch (http://www.bohemiancoding.com/sketch) - - - - - - - - - - - - - - - + + +image/svg+xml + + + + + + + + Mic + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + + + + + + \ No newline at end of file diff --git a/interface/resources/images/minus.svg b/interface/resources/images/minus.svg new file mode 100644 index 0000000000..463b24aa4c --- /dev/null +++ b/interface/resources/images/minus.svg @@ -0,0 +1,11 @@ + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + \ No newline at end of file diff --git a/interface/resources/images/mute.svg b/interface/resources/images/mute.svg deleted file mode 100644 index 1f1b238463..0000000000 --- a/interface/resources/images/mute.svg +++ /dev/null @@ -1,20 +0,0 @@ - - - mute - Created with Sketch (http://www.bohemiancoding.com/sketch) - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/interface/resources/images/pin.svg b/interface/resources/images/pin.svg new file mode 100644 index 0000000000..ec968a1ec1 --- /dev/null +++ b/interface/resources/images/pin.svg @@ -0,0 +1,98 @@ + + + + + + image/svg+xml + + Slice 1 + + + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + diff --git a/interface/resources/images/pinned.svg b/interface/resources/images/pinned.svg new file mode 100644 index 0000000000..bda6f0e747 --- /dev/null +++ b/interface/resources/images/pinned.svg @@ -0,0 +1,106 @@ + + + + + + image/svg+xml + + Slice 1 + + + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + + diff --git a/interface/resources/images/plus.svg b/interface/resources/images/plus.svg new file mode 100644 index 0000000000..6efaa31abc --- /dev/null +++ b/interface/resources/images/plus.svg @@ -0,0 +1,12 @@ + + + Slice 1 + Created with Sketch (http://www.bohemiancoding.com/sketch) + + + + + + + + \ No newline at end of file diff --git a/interface/resources/resources.qrc b/interface/resources/resources.qrc deleted file mode 100644 index 35c0e40270..0000000000 --- a/interface/resources/resources.qrc +++ /dev/null @@ -1,8 +0,0 @@ - - - images/close.svg - images/kill-script.svg - images/reload.svg - images/stop.svg - - diff --git a/interface/resources/shaders/ambient_occlusion.frag b/interface/resources/shaders/ambient_occlusion.frag index 6e9ed82d95..512922ca43 100644 --- a/interface/resources/shaders/ambient_occlusion.frag +++ b/interface/resources/shaders/ambient_occlusion.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 7/5/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the depth texture diff --git a/interface/resources/shaders/ambient_occlusion.vert b/interface/resources/shaders/ambient_occlusion.vert index ca6af718bd..a95a0cda7d 100644 --- a/interface/resources/shaders/ambient_occlusion.vert +++ b/interface/resources/shaders/ambient_occlusion.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 8/16/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // void main(void) { diff --git a/interface/resources/shaders/diffuse.frag b/interface/resources/shaders/diffuse.frag index ebf11dace2..bc88b6598d 100644 --- a/interface/resources/shaders/diffuse.frag +++ b/interface/resources/shaders/diffuse.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 8/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the texture containing the original color diff --git a/interface/resources/shaders/glow_add.frag b/interface/resources/shaders/glow_add.frag index 0947292109..e8a1b504ea 100644 --- a/interface/resources/shaders/glow_add.frag +++ b/interface/resources/shaders/glow_add.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 8/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the texture containing the original color diff --git a/interface/resources/shaders/glow_add_separate.frag b/interface/resources/shaders/glow_add_separate.frag index 7b7f538a03..e798a3b11f 100644 --- a/interface/resources/shaders/glow_add_separate.frag +++ b/interface/resources/shaders/glow_add_separate.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 8/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the texture containing the original color diff --git a/interface/resources/shaders/grid.frag b/interface/resources/shaders/grid.frag index b9e3baccd4..17ad0fed5c 100644 --- a/interface/resources/shaders/grid.frag +++ b/interface/resources/shaders/grid.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 1/21/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // void main(void) { diff --git a/interface/resources/shaders/horizontal_blur.frag b/interface/resources/shaders/horizontal_blur.frag index 695de1a538..916fe4b52d 100644 --- a/interface/resources/shaders/horizontal_blur.frag +++ b/interface/resources/shaders/horizontal_blur.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 8/8/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the texture containing the original color diff --git a/interface/resources/shaders/metavoxel_point.vert b/interface/resources/shaders/metavoxel_point.vert index b0472088c0..fa585be099 100644 --- a/interface/resources/shaders/metavoxel_point.vert +++ b/interface/resources/shaders/metavoxel_point.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 12/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // uniform float pointScale; diff --git a/interface/resources/shaders/model.frag b/interface/resources/shaders/model.frag index e035f9bfe9..29579d07ac 100644 --- a/interface/resources/shaders/model.frag +++ b/interface/resources/shaders/model.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 10/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the diffuse texture diff --git a/interface/resources/shaders/model.vert b/interface/resources/shaders/model.vert index d07a657d25..904e3c2a8b 100644 --- a/interface/resources/shaders/model.vert +++ b/interface/resources/shaders/model.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 10/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the interpolated normal diff --git a/interface/resources/shaders/model_normal_map.frag b/interface/resources/shaders/model_normal_map.frag index 9a0e964500..2a4af2073a 100644 --- a/interface/resources/shaders/model_normal_map.frag +++ b/interface/resources/shaders/model_normal_map.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 10/29/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the diffuse texture diff --git a/interface/resources/shaders/model_normal_map.vert b/interface/resources/shaders/model_normal_map.vert index c367a30e06..3607501acf 100644 --- a/interface/resources/shaders/model_normal_map.vert +++ b/interface/resources/shaders/model_normal_map.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 10/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the tangent vector diff --git a/interface/resources/shaders/model_shadow.frag b/interface/resources/shaders/model_shadow.frag index bcb597b13c..50a4d3895d 100644 --- a/interface/resources/shaders/model_shadow.frag +++ b/interface/resources/shaders/model_shadow.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 3/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // void main(void) { diff --git a/interface/resources/shaders/model_shadow.vert b/interface/resources/shaders/model_shadow.vert index ae7e871887..a18f5fcd5d 100644 --- a/interface/resources/shaders/model_shadow.vert +++ b/interface/resources/shaders/model_shadow.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 3/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 // void main(void) { diff --git a/interface/resources/shaders/occlusion_blur.frag b/interface/resources/shaders/occlusion_blur.frag index 5b86aec68d..ab4b656e71 100644 --- a/interface/resources/shaders/occlusion_blur.frag +++ b/interface/resources/shaders/occlusion_blur.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 8/16/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the original texture diff --git a/interface/resources/shaders/oculus.frag b/interface/resources/shaders/oculus.frag index 3e7d1a4c3e..f2b066a974 100644 --- a/interface/resources/shaders/oculus.frag +++ b/interface/resources/shaders/oculus.frag @@ -5,11 +5,14 @@ // fragment shader // // Created by Andrzej Kapolka on 11/26/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// this shader is an adaptation (HLSL -> GLSL, removed conditional) of the one in the Oculus sample -// code (Samples/OculusRoomTiny/RenderTiny_D3D1X_Device.cpp), which is under the Apache license -// (http://www.apache.org/licenses/LICENSE-2.0) +// this shader is an adaptation (HLSL -> GLSL, removed conditional) of the one in the Oculus sample +// code (Samples/OculusRoomTiny/RenderTiny_D3D1X_Device.cpp), which is under the Apache license +// (http://www.apache.org/licenses/LICENSE-2.0) +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // uniform sampler2D texture; diff --git a/interface/resources/shaders/passthrough.vert b/interface/resources/shaders/passthrough.vert index f3d5c5b504..bb0a18eefa 100644 --- a/interface/resources/shaders/passthrough.vert +++ b/interface/resources/shaders/passthrough.vert @@ -1,4 +1,15 @@ #version 120 + +// +// passthrough.vert +// vertex shader +// +// Copyright 2013 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 +// attribute float voxelSizeIn; varying float voxelSize; diff --git a/interface/resources/shaders/perlin_modulate.frag b/interface/resources/shaders/perlin_modulate.frag index 8ead57c238..8693b14e1b 100644 --- a/interface/resources/shaders/perlin_modulate.frag +++ b/interface/resources/shaders/perlin_modulate.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 5/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the texture containing our permutations and normals diff --git a/interface/resources/shaders/perlin_modulate.vert b/interface/resources/shaders/perlin_modulate.vert index 5cf87fc5fd..428615017a 100644 --- a/interface/resources/shaders/perlin_modulate.vert +++ b/interface/resources/shaders/perlin_modulate.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 5/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the position in model space diff --git a/interface/resources/shaders/point_size.vert b/interface/resources/shaders/point_size.vert index 9c93385768..88c56bf0c4 100644 --- a/interface/resources/shaders/point_size.vert +++ b/interface/resources/shaders/point_size.vert @@ -1,4 +1,14 @@ #version 120 + +// +// point_size.vert +// vertex shader +// +// Copyright 2013 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 +// attribute float voxelSizeIn; varying float voxelSize; diff --git a/interface/resources/shaders/shadow_map.frag b/interface/resources/shaders/shadow_map.frag index b683ed5af2..70c459ecf8 100644 --- a/interface/resources/shaders/shadow_map.frag +++ b/interface/resources/shaders/shadow_map.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 11/21/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // uniform sampler2DShadow shadowMap; diff --git a/interface/resources/shaders/shadow_map.vert b/interface/resources/shaders/shadow_map.vert index 6809ca6e2b..abe5f99b44 100644 --- a/interface/resources/shaders/shadow_map.vert +++ b/interface/resources/shaders/shadow_map.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 3/27/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 // varying vec4 shadowColor; diff --git a/interface/resources/shaders/skin_model.vert b/interface/resources/shaders/skin_model.vert index 57e3928241..12320ba9a9 100644 --- a/interface/resources/shaders/skin_model.vert +++ b/interface/resources/shaders/skin_model.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 10/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // const int MAX_CLUSTERS = 128; diff --git a/interface/resources/shaders/skin_model_normal_map.vert b/interface/resources/shaders/skin_model_normal_map.vert index 97e21dae73..872cbe2fc3 100644 --- a/interface/resources/shaders/skin_model_normal_map.vert +++ b/interface/resources/shaders/skin_model_normal_map.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 10/29/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // const int MAX_CLUSTERS = 128; diff --git a/interface/resources/shaders/skin_model_shadow.vert b/interface/resources/shaders/skin_model_shadow.vert index b9ef05ad8a..6d9d5ebbf5 100644 --- a/interface/resources/shaders/skin_model_shadow.vert +++ b/interface/resources/shaders/skin_model_shadow.vert @@ -5,7 +5,10 @@ // vertex shader // // Created by Andrzej Kapolka on 3/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 // const int MAX_CLUSTERS = 128; diff --git a/interface/resources/shaders/vertical_blur.frag b/interface/resources/shaders/vertical_blur.frag index 96ab95ea9e..1b8c447cc7 100644 --- a/interface/resources/shaders/vertical_blur.frag +++ b/interface/resources/shaders/vertical_blur.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 8/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the texture containing the horizontally blurred color diff --git a/interface/resources/shaders/vertical_blur_add.frag b/interface/resources/shaders/vertical_blur_add.frag index 5cda2622b4..919e9d1268 100644 --- a/interface/resources/shaders/vertical_blur_add.frag +++ b/interface/resources/shaders/vertical_blur_add.frag @@ -5,7 +5,10 @@ // fragment shader // // Created by Andrzej Kapolka on 8/8/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // // the texture containing the original color diff --git a/interface/resources/shaders/voxel.geom b/interface/resources/shaders/voxel.geom index ca8f729542..4c850ed608 100644 --- a/interface/resources/shaders/voxel.geom +++ b/interface/resources/shaders/voxel.geom @@ -1,6 +1,16 @@ #version 120 #extension GL_ARB_geometry_shader4 : enable +// +// voxel.geom +// geometry shader +// +// Copyright 2013 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 +// + // // VOXEL GEOMETRY SHADER // diff --git a/interface/resources/styles/avatar.svg b/interface/resources/styles/avatar.svg new file mode 100644 index 0000000000..f9382edee4 --- /dev/null +++ b/interface/resources/styles/avatar.svg @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/interface/resources/styles/close.svg b/interface/resources/styles/close.svg new file mode 100644 index 0000000000..8fe4bf4bdb --- /dev/null +++ b/interface/resources/styles/close.svg @@ -0,0 +1,14 @@ + + + + + + + + + + diff --git a/interface/resources/styles/down.svg b/interface/resources/styles/down.svg new file mode 100644 index 0000000000..983ccd9597 --- /dev/null +++ b/interface/resources/styles/down.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/interface/resources/styles/global.qss b/interface/resources/styles/global.qss new file mode 100644 index 0000000000..2554f3b2c9 --- /dev/null +++ b/interface/resources/styles/global.qss @@ -0,0 +1,113 @@ +* { + padding: 0; + margin: 0; +} + +FramelessDialog { + font-family: Helvetica, Arial, sans-serif; + font-size: 16px; +} + +QLineEdit { + background-color: rgba(255, 255, 255, 1); + border-style: solid; + border-width: 1px; + border-color: #ccc; + padding: 8px; + font-size: 16px; + color: rgb(51, 51, 51); +} + +QLabel p { + color: #0e7077; + font-size: 23px; +} + +QPushButton { + border-width: 0; + border-radius: 9px; + font-family: Arial; + font-size: 18px; + color: #ffffff; + padding: 10px 0px; +} + +QSpinBox, QDoubleSpinBox { + padding: 5px; + border-width: 1; + font-size: 16px; + color: rgb(51, 51, 51); +} + +QDoubleSpinBox::up-arrow, +QSpinBox::up-arrow { + background-image: url(styles/up.svg); + background-repeat: no-repeat; + background-position: center center; +} + +QDoubleSpinBox::down-arrow, +QSpinBox::down-arrow { + background-image: url(styles/down.svg); + background-repeat: no-repeat; + background-position: center center; +} + +QDoubleSpinBox::up-button, +QSpinBox::up-button, +QDoubleSpinBox::down-button, +QSpinBox::down-button { + width: 26px; + height: 13px; + + background-color: #f2f2f2; + border-color: #ccc; + border-style: solid; + border-width: 1px; +} + +QDoubleSpinBox::up-button, +QSpinBox::up-button { + + margin-top:2px; + border-top-left-radius: 4px; + border-top-right-radius: 4px; +} + +QDoubleSpinBox::down-button, +QSpinBox::down-button { + margin-bottom:3px; + border-bottom-left-radius: 4px; + border-bottom-right-radius: 4px; +} + +QSlider { + width: 125px; + height: 18px; +} + +QSlider::groove:horizontal { + border: none; + background-image: url(styles/slider-bg.svg); + background-repeat: no-repeat; + background-position: center center; +} + +QSlider::handle:horizontal { + width: 18px; + height: 18px; + background-image: url(styles/slider-handle.svg); + background-repeat: no-repeat; + background-position: center center; +} + +QPushButton#closeButton { + border-color: #ccc; + border-style: solid; + border-width: 1px; + border-radius: 0; + background-color: #fff; + background-image: url(styles/close.svg); + background-repeat: no-repeat; + background-position: center center; +} diff --git a/interface/resources/styles/preferences.qss b/interface/resources/styles/preferences.qss new file mode 100644 index 0000000000..e678acd0c9 --- /dev/null +++ b/interface/resources/styles/preferences.qss @@ -0,0 +1,22 @@ +QLabel#avatarLabel { + background-image: url(styles/avatar.svg); + background-repeat: no-repeat; + background-position: left center; +} + +QLabel#advancedTuningLabel { + background-image: url(styles/wrench.svg); + background-repeat: no-repeat; + background-position: left center; +} + +QPushButton#buttonBrowseHead, +QPushButton#buttonBrowseBody, +QPushButton#buttonBrowseLocation { + background-image: url(styles/search.svg); + background-repeat: no-repeat; + background-position: center center; + background-color: #fff; + border-radius: 0; + padding: 0; +} diff --git a/interface/resources/styles/slider-bg.svg b/interface/resources/styles/slider-bg.svg new file mode 100644 index 0000000000..36c6478026 --- /dev/null +++ b/interface/resources/styles/slider-bg.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/interface/resources/styles/slider-handle.svg b/interface/resources/styles/slider-handle.svg new file mode 100644 index 0000000000..5d87e8599c --- /dev/null +++ b/interface/resources/styles/slider-handle.svg @@ -0,0 +1,10 @@ + + + + + + + + + diff --git a/interface/resources/styles/up.svg b/interface/resources/styles/up.svg new file mode 100644 index 0000000000..d122c4801e --- /dev/null +++ b/interface/resources/styles/up.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/interface/resources/styles/wrench.svg b/interface/resources/styles/wrench.svg new file mode 100644 index 0000000000..5019f92b18 --- /dev/null +++ b/interface/resources/styles/wrench.svg @@ -0,0 +1,9 @@ + + + + + + diff --git a/interface/src/AbstractLoggerInterface.h b/interface/src/AbstractLoggerInterface.h index cedab1fad2..f6cf136a71 100644 --- a/interface/src/AbstractLoggerInterface.h +++ b/interface/src/AbstractLoggerInterface.h @@ -1,13 +1,16 @@ // // AbstractLoggerInterface.h -// interface +// interface/src // // Created by Stojce Slavkovski on 12/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__AbstractLoggerInterface__ -#define __interface__AbstractLoggerInterface__ +#ifndef hifi_AbstractLoggerInterface_h +#define hifi_AbstractLoggerInterface_h #include #include @@ -32,4 +35,4 @@ private: bool _extraDebugging; }; -#endif /* defined(__interface__AbstractLoggerInterface__) */ +#endif // hifi_AbstractLoggerInterface_h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index df59b6574f..e1cdfe67b0 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1,13 +1,13 @@ // // Application.cpp -// interface +// interface/src // // Created by Andrzej Kapolka on 5/10/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. - -#ifdef WIN32 -#include -#endif +// Copyright 2013 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 @@ -62,7 +62,7 @@ #include #include #include -#include +#include #include "Application.h" #include "InterfaceVersion.h" @@ -80,6 +80,7 @@ #include "ui/InfoView.h" #include "ui/Snapshot.h" #include "ui/TextRenderer.h" +#include "ui/Stats.h" using namespace std; @@ -106,13 +107,9 @@ const float MIRROR_REARVIEW_DISTANCE = 0.65f; const float MIRROR_REARVIEW_BODY_DISTANCE = 2.3f; const float MIRROR_FIELD_OF_VIEW = 30.0f; -const QString CHECK_VERSION_URL = "http://highfidelity.io/latestVersion.xml"; +const QString CHECK_VERSION_URL = "https://highfidelity.io/latestVersion.xml"; const QString SKIP_FILENAME = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/hifi.skipversion"; -const int STATS_PELS_PER_LINE = 20; - -const QString CUSTOM_URL_SCHEME = "hifi:"; - void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) { if (message.size() > 0) { QString dateString = QDateTime::currentDateTime().toTimeSpec(Qt::LocalTime).toString(Qt::ISODate); @@ -132,11 +129,10 @@ QString& Application::resourcesPath() { return staticResourcePath; } -Application::Application(int& argc, char** argv, timeval &startup_time) : +Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) : QApplication(argc, argv), _window(new MainWindow(desktop())), _glWidget(new GLCanvas()), - _statsExpanded(false), _nodeThread(new QThread(this)), _datagramProcessor(), _frameCount(0), @@ -149,9 +145,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _viewFrustum(), _lastQueriedViewFrustum(), _lastQueriedTime(usecTimestampNow()), - _audioScope(256, 200, true), - _trailingAudioLoudness(0.f), _mirrorViewRect(QRect(MIRROR_VIEW_LEFT_PADDING, MIRROR_VIEW_TOP_PADDING, MIRROR_VIEW_WIDTH, MIRROR_VIEW_HEIGHT)), + _cameraPushback(0.0f), _mouseX(0), _mouseY(0), _lastMouseMove(usecTimestampNow()), @@ -161,19 +156,20 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : _touchAvgY(0.0f), _isTouchPressed(false), _mousePressed(false), - _audio(&_audioScope, STARTUP_JITTER_SAMPLES), + _audio(STARTUP_JITTER_SAMPLES), _enableProcessVoxelsThread(true), _voxelProcessor(), _voxelHideShowThread(&_voxels), _packetsPerSecond(0), _bytesPerSecond(0), - _recentMaxPackets(0), - _resetRecentMaxPacketsSoon(true), _previousScriptLocation(), _logger(new FileLogger(this)), _runningScriptsWidget(new RunningScriptsWidget), _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); @@ -231,8 +227,8 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : audioThread->start(); - connect(&nodeList->getDomainInfo(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); - connect(&nodeList->getDomainInfo(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); + connect(&nodeList->getDomainHandler(), SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&))); + connect(&nodeList->getDomainHandler(), SIGNAL(connectedToDomain(const QString&)), SLOT(connectedToDomain(const QString&))); connect(nodeList, &NodeList::nodeAdded, this, &Application::nodeAdded); connect(nodeList, &NodeList::nodeKilled, this, &Application::nodeKilled); @@ -240,6 +236,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : connect(nodeList, SIGNAL(nodeAdded(SharedNodePointer)), &_voxels, SLOT(nodeAdded(SharedNodePointer))); connect(nodeList, SIGNAL(nodeKilled(SharedNodePointer)), &_voxels, SLOT(nodeKilled(SharedNodePointer))); connect(nodeList, &NodeList::uuidChanged, this, &Application::updateWindowTitle); + connect(nodeList, SIGNAL(uuidChanged(const QUuid&)), _myAvatar, SLOT(setSessionUUID(const QUuid&))); connect(nodeList, &NodeList::limitOfSilentDomainCheckInsReached, nodeList, &NodeList::reset); // connect to appropriate slots on AccountManager @@ -254,6 +251,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection); _settings = new QSettings(this); + _numChangedSettings = 0; // Check to see if the user passed in a command line option for loading a local // Voxel File. @@ -277,7 +275,7 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : QTimer* silentNodeTimer = new QTimer(); connect(silentNodeTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); silentNodeTimer->moveToThread(_nodeThread); - silentNodeTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeTimer->start(NODE_SILENCE_THRESHOLD_MSECS); // send the identity packet for our avatar each second to our avatar mixer QTimer* identityPacketTimer = new QTimer(); @@ -346,15 +344,22 @@ Application::Application(int& argc, char** argv, timeval &startup_time) : 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); connect(_window, &MainWindow::windowShown, this, &Application::manageRunningScriptsWidgetVisibility); + + //When -url in command line, teleport to location + urlGoTo(argc, constArgv); } Application::~Application() { @@ -404,37 +409,50 @@ Application::~Application() { delete _glWidget; AccountManager::getInstance().destroy(); + + DTLSClientSession::globalDeinit(); } +void Application::saveSettings() { + Menu::getInstance()->saveSettings(); + _rearMirrorTools->saveSettings(_settings); + + if (_voxelImporter) { + _voxelImporter->saveSettings(_settings); + } + _settings->sync(); + _numChangedSettings = 0; +} + + void Application::restoreSizeAndPosition() { - QSettings* settings = new QSettings(this); QRect available = desktop()->availableGeometry(); - settings->beginGroup("Window"); + QMutexLocker locker(&_settingsMutex); + _settings->beginGroup("Window"); - int x = (int)loadSetting(settings, "x", 0); - int y = (int)loadSetting(settings, "y", 0); + int x = (int)loadSetting(_settings, "x", 0); + int y = (int)loadSetting(_settings, "y", 0); _window->move(x, y); - int width = (int)loadSetting(settings, "width", available.width()); - int height = (int)loadSetting(settings, "height", available.height()); + int width = (int)loadSetting(_settings, "width", available.width()); + int height = (int)loadSetting(_settings, "height", available.height()); _window->resize(width, height); - settings->endGroup(); + _settings->endGroup(); } void Application::storeSizeAndPosition() { - QSettings* settings = new QSettings(this); + QMutexLocker locker(&_settingsMutex); + _settings->beginGroup("Window"); - settings->beginGroup("Window"); + _settings->setValue("width", _window->rect().width()); + _settings->setValue("height", _window->rect().height()); - settings->setValue("width", _window->rect().width()); - settings->setValue("height", _window->rect().height()); + _settings->setValue("x", _window->pos().x()); + _settings->setValue("y", _window->pos().y()); - settings->setValue("x", _window->pos().x()); - settings->setValue("y", _window->pos().y()); - - settings->endGroup(); + _settings->endGroup(); } void Application::initializeGL() { @@ -495,7 +513,7 @@ void Application::initializeGL() { _idleLoopStdev.reset(); if (_justStarted) { - float startupTime = (usecTimestampNow() - usecTimestamp(&_applicationStartupTime)) / 1000000.0; + float startupTime = (float)_applicationStartupTime.elapsed() / 1000.0; _justStarted = false; qDebug("Startup time: %4.2f seconds.", startupTime); const char LOGSTASH_INTERFACE_START_TIME_KEY[] = "interface-start-time"; @@ -517,18 +535,55 @@ void Application::paintGL() { glEnable(GL_LINE_SMOOTH); + float pushback = 0.0f; + float pushbackFocalLength = 0.0f; if (OculusManager::isConnected()) { _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()->getOrientation()); + _myCamera.setTargetRotation(_myAvatar->getHead()->getCameraOrientation()); } else 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()); + glm::vec3 planeNormal = _myCamera.getTargetRotation() * IDENTITY_FRONT; + const float BASE_PUSHBACK_RADIUS = 0.25f; + float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS; + glm::vec4 plane(planeNormal, -glm::dot(planeNormal, _myCamera.getTargetPosition()) - pushbackRadius); + + // push camera out of any intersecting avatars + foreach (const AvatarSharedPointer& avatarData, _avatarManager.getAvatarHash()) { + Avatar* avatar = static_cast(avatarData.data()); + if (avatar->isMyAvatar()) { + continue; + } + if (glm::distance(avatar->getPosition(), _myCamera.getTargetPosition()) > + avatar->getBoundingRadius() + pushbackRadius) { + continue; + } + float angle = angleBetween(avatar->getPosition() - _myCamera.getTargetPosition(), planeNormal); + if (angle > PI_OVER_TWO) { + continue; + } + float scale = 1.0f - angle / PI_OVER_TWO; + scale = qMin(1.0f, scale * 2.5f); + static CollisionList collisions(64); + collisions.clear(); + if (!avatar->findPlaneCollisions(plane, collisions)) { + continue; + } + for (int i = 0; i < collisions.size(); i++) { + pushback = qMax(pushback, glm::length(collisions.getCollision(i)->_penetration) * scale); + } + } + const float MAX_PUSHBACK = 0.35f; + pushback = qMin(pushback, MAX_PUSHBACK * _myAvatar->getScale()); + const float BASE_PUSHBACK_FOCAL_LENGTH = 0.5f; + pushbackFocalLength = BASE_PUSHBACK_FOCAL_LENGTH * _myAvatar->getScale(); + } else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) { _myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing _myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition()); @@ -536,10 +591,35 @@ void Application::paintGL() { } else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) { _myCamera.setTightness(0.0f); - float headHeight = _myAvatar->getHead()->calculateAverageEyePosition().y - _myAvatar->getPosition().y; + glm::vec3 eyePosition = _myAvatar->getHead()->calculateAverageEyePosition(); + float headHeight = eyePosition.y - _myAvatar->getPosition().y; _myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _myAvatar->getScale()); _myCamera.setTargetPosition(_myAvatar->getPosition() + glm::vec3(0, headHeight, 0)); _myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI, 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()); + const float BASE_PUSHBACK_RADIUS = 0.2f; + float pushbackRadius = _myCamera.getNearClip() + _myAvatar->getScale() * BASE_PUSHBACK_RADIUS; + pushback = relativePosition.z + pushbackRadius - _myCamera.getDistance(); + pushbackFocalLength = _myCamera.getDistance(); + } + + // handle pushback, if any + if (pushbackFocalLength > 0.0f) { + const float PUSHBACK_DECAY = 0.5f; + _cameraPushback = qMax(pushback, _cameraPushback * PUSHBACK_DECAY); + if (_cameraPushback > EPSILON) { + _myCamera.setTargetPosition(_myCamera.getTargetPosition() + + _myCamera.getTargetRotation() * glm::vec3(0.0f, 0.0f, _cameraPushback)); + float enlargement = pushbackFocalLength / (pushbackFocalLength + _cameraPushback); + _myCamera.setFieldOfView(glm::degrees(2.0f * atanf(enlargement * tanf( + glm::radians(Menu::getInstance()->getFieldOfView() * 0.5f))))); + } else { + _myCamera.setFieldOfView(Menu::getInstance()->getFieldOfView()); + } + updateProjectionMatrix(_myCamera, true); } // Update camera position @@ -630,6 +710,13 @@ void Application::resizeGL(int width, int height) { _window->geometry().topLeft().y(), _runningScriptsWidget->width(), _window->height()); } + // update Stats width + int horizontalOffset = 0; + if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + // mirror is enabled, let's set horizontal offset to give stats some margin + horizontalOffset += MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; + } + Stats::getInstance()->resetWidth(width, horizontalOffset); } void Application::updateProjectionMatrix() { @@ -699,26 +786,9 @@ bool Application::event(QEvent* event) { // handle custom URL if (event->type() == QEvent::FileOpen) { QFileOpenEvent* fileEvent = static_cast(event); - if (!fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME)) { - QString destination = fileEvent->url().toLocalFile().remove(CUSTOM_URL_SCHEME); - QStringList urlParts = destination.split('/', QString::SkipEmptyParts); - - if (urlParts.count() > 1) { - // if url has 2 or more parts, the first one is domain name - Menu::getInstance()->goToDomain(urlParts[0]); - - // location coordinates - Menu::getInstance()->goToDestination(urlParts[1]); - if (urlParts.count() > 2) { - - // location orientation - Menu::getInstance()->goToOrientation(urlParts[2]); - } - } else if (urlParts.count() == 1) { - - // location coordinates - Menu::getInstance()->goToDestination(urlParts[0]); - } + bool isHifiSchemeURL = !fileEvent->url().isEmpty() && fileEvent->url().toLocalFile().startsWith(CUSTOM_URL_SCHEME); + if (isHifiSchemeURL) { + Menu::getInstance()->goTo(fileEvent->url().toString()); } return false; @@ -740,6 +810,7 @@ void Application::keyPressEvent(QKeyEvent* event) { if (activeWindow() == _window) { bool isShifted = event->modifiers().testFlag(Qt::ShiftModifier); bool isMeta = event->modifiers().testFlag(Qt::ControlModifier); + bool isOption = event->modifiers().testFlag(Qt::AltModifier); switch (event->key()) { break; case Qt::Key_BracketLeft: @@ -754,9 +825,6 @@ void Application::keyPressEvent(QKeyEvent* event) { case Qt::Key_Period: Menu::getInstance()->handleViewFrustumOffsetKeyModifier(event->key()); break; - case Qt::Key_Apostrophe: - _audioScope.inputPaused = !_audioScope.inputPaused; - break; case Qt::Key_L: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::LodTools); @@ -766,6 +834,7 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_E: + case Qt::Key_PageUp: if (!_myAvatar->getDriveKeys(UP)) { _myAvatar->jump(); } @@ -777,6 +846,7 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_C: + case Qt::Key_PageDown: _myAvatar->setDriveKeys(DOWN, 1.f); break; @@ -785,9 +855,11 @@ void Application::keyPressEvent(QKeyEvent* event) { break; case Qt::Key_S: - if (isShifted && isMeta) { + if (isShifted && isMeta && !isOption) { Menu::getInstance()->triggerOption(MenuOption::SuppressShortTimings); - } else if (!isShifted && isMeta) { + } else if (isOption && !isShifted && !isMeta) { + Menu::getInstance()->triggerOption(MenuOption::ScriptEditor); + } else if (!isOption && !isShifted && isMeta) { takeSnapshot(); } else { _myAvatar->setDriveKeys(BACK, 1.f); @@ -963,10 +1035,12 @@ void Application::keyReleaseEvent(QKeyEvent* event) { switch (event->key()) { case Qt::Key_E: + case Qt::Key_PageUp: _myAvatar->setDriveKeys(UP, 0.f); break; case Qt::Key_C: + case Qt::Key_PageDown: _myAvatar->setDriveKeys(DOWN, 0.f); break; @@ -1089,7 +1163,11 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { _mousePressed = false; checkBandwidthMeterClick(); if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { - checkStatsClick(); + int horizontalOffset = 0; + if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + horizontalOffset = MIRROR_VIEW_WIDTH; + } + Stats::getInstance()->checkClick(_mouseX, _mouseY, _mouseDragStartedX, _mouseDragStartedY, horizontalOffset); } } } @@ -1155,7 +1233,6 @@ void Application::touchEndEvent(QTouchEvent* event) { if (_controllerScriptingInterface.isTouchCaptured()) { return; } - // put any application specific touch behavior below here.. _touchDragStartedAvgX = _touchAvgX; _touchDragStartedAvgY = _touchAvgY; @@ -1209,26 +1286,24 @@ void Application::sendPingPackets() { // Every second, check the frame rates and other stuff void Application::timer() { - gettimeofday(&_timerEnd, NULL); - if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { sendPingPackets(); } - _fps = (float)_frameCount / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); + float diffTime = (float)_timerStart.nsecsElapsed() / 1000000000.0f; - _packetsPerSecond = (float) _datagramProcessor.getPacketCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); - _bytesPerSecond = (float) _datagramProcessor.getByteCount() / ((float)diffclock(&_timerStart, &_timerEnd) / 1000.f); + _fps = (float)_frameCount / diffTime; + + _packetsPerSecond = (float) _datagramProcessor.getPacketCount() / diffTime; + _bytesPerSecond = (float) _datagramProcessor.getByteCount() / diffTime; _frameCount = 0; _datagramProcessor.resetCounters(); - gettimeofday(&_timerStart, NULL); + _timerStart.start(); // ask the node list to check in with the domain server NodeList::getInstance()->sendDomainServerCheckIn(); - - } void Application::idle() { @@ -1238,13 +1313,11 @@ void Application::idle() { bool showWarnings = getLogger()->extraDebugging(); PerformanceWarning warn(showWarnings, "Application::idle()"); - timeval check; - gettimeofday(&check, NULL); - // Only run simulation code if more than IDLE_SIMULATE_MSECS have passed since last time we ran - double timeSinceLastUpdate = diffclock(&_lastTimeUpdated, &check); + double timeSinceLastUpdate = (double)_lastTimeUpdated.nsecsElapsed() / 1000000.0; if (timeSinceLastUpdate > IDLE_SIMULATE_MSECS) { + _lastTimeUpdated.start(); { PerformanceWarning warn(showWarnings, "Application::idle()... update()"); const float BIGGEST_DELTA_TIME_SECS = 0.25f; @@ -1256,7 +1329,6 @@ void Application::idle() { } { PerformanceWarning warn(showWarnings, "Application::idle()... rest of it"); - _lastTimeUpdated = check; _idleLoopStdev.addValue(timeSinceLastUpdate); // Record standard deviation and reset counter if needed @@ -1273,6 +1345,9 @@ void Application::idle() { // After finishing all of the above work, restart the idle timer, allowing 2ms to process events. idleTimer->start(2); } + if (_numChangedSettings > 0) { + saveSettings(); + } } } @@ -1353,6 +1428,12 @@ glm::vec3 Application::getMouseVoxelWorldCoordinates(const VoxelDetail& mouseVox (mouseVoxel.z + mouseVoxel.s / 2.f) * TREE_SCALE); } +FaceTracker* Application::getActiveFaceTracker() { + return _faceshift.isActive() ? static_cast(&_faceshift) : + (_faceplus.isActive() ? static_cast(&_faceplus) : + (_visage.isActive() ? static_cast(&_visage) : NULL)); +} + struct SendVoxelsOperationArgs { const unsigned char* newBaseOctCode; }; @@ -1563,8 +1644,8 @@ void Application::init() { Qt::QueuedConnection); } - gettimeofday(&_timerStart, NULL); - gettimeofday(&_lastTimeUpdated, NULL); + _timerStart.start(); + _lastTimeUpdated.start(); Menu::getInstance()->loadSettings(); if (Menu::getInstance()->getAudioJitterBufferSamples() != 0) { @@ -1572,8 +1653,9 @@ void Application::init() { } qDebug("Loaded settings"); - // initialize Visage and Faceshift after loading the menu settings + // initialize our face trackers after loading the menu settings _faceshift.init(); + _faceplus.init(); _visage.init(); // fire off an immediate domain-server check in now that settings are loaded @@ -1593,16 +1675,12 @@ void Application::init() { _particleCollisionSystem.init(&_particleEditSender, _particles.getTree(), _voxels.getTree(), &_audio, &_avatarManager); - // connect the _particleCollisionSystem to our script engine's ParticleScriptingInterface - connect(&_particleCollisionSystem, - SIGNAL(particleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&)), - ScriptEngine::getParticlesScriptingInterface(), - SLOT(forwardParticleCollisionWithVoxel(const ParticleID&, const VoxelDetail&, const glm::vec3&))); + // connect the _particleCollisionSystem to our script engine's ParticlesScriptingInterface + connect(&_particleCollisionSystem, &ParticleCollisionSystem::particleCollisionWithVoxel, + ScriptEngine::getParticlesScriptingInterface(), &ParticlesScriptingInterface::particleCollisionWithVoxel); - connect(&_particleCollisionSystem, - SIGNAL(particleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&)), - ScriptEngine::getParticlesScriptingInterface(), - SLOT(forwardParticleCollisionWithParticle(const ParticleID&, const ParticleID&, const glm::vec3&))); + connect(&_particleCollisionSystem, &ParticleCollisionSystem::particleCollisionWithParticle, + ScriptEngine::getParticlesScriptingInterface(), &ParticlesScriptingInterface::particleCollisionWithParticle); _audio.init(_glWidget); @@ -1612,6 +1690,20 @@ 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()); + _audioReflector.setAudio(getAudio()); + _audioReflector.setAvatarManager(&_avatarManager); + + connect(getAudio(), &Audio::processInboundAudio, &_audioReflector, &AudioReflector::processInboundAudio,Qt::DirectConnection); + connect(getAudio(), &Audio::processLocalAudio, &_audioReflector, &AudioReflector::processLocalAudio,Qt::DirectConnection); + connect(getAudio(), &Audio::preProcessOriginalInboundAudio, &_audioReflector, + &AudioReflector::preProcessOriginalInboundAudio,Qt::DirectConnection); + + // save settings when avatar changes + connect(_myAvatar, &MyAvatar::transformChanged, this, &Application::bumpSettings); } void Application::closeMirrorView() { @@ -1737,19 +1829,11 @@ void Application::updateMyAvatarLookAtPosition() { glm::distance(_mouseRayOrigin, _myAvatar->getHead()->calculateAverageEyePosition())); lookAtSpot = _mouseRayOrigin + _mouseRayDirection * qMax(minEyeDistance, distance); } - bool trackerActive = false; - float eyePitch, eyeYaw; - if (_faceshift.isActive()) { - eyePitch = _faceshift.getEstimatedEyePitch(); - eyeYaw = _faceshift.getEstimatedEyeYaw(); - trackerActive = true; + FaceTracker* tracker = getActiveFaceTracker(); + if (tracker) { + float eyePitch = tracker->getEstimatedEyePitch(); + float eyeYaw = tracker->getEstimatedEyeYaw(); - } else if (_visage.isActive()) { - eyePitch = _visage.getEstimatedEyePitch(); - eyeYaw = _visage.getEstimatedEyeYaw(); - trackerActive = true; - } - if (trackerActive) { // deflect using Faceshift gaze data glm::vec3 origin = _myAvatar->getHead()->calculateAverageEyePosition(); float pitchSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? -1.0f : 1.0f; @@ -1835,15 +1919,15 @@ void Application::updateCamera(float deltaTime) { bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings); PerformanceWarning warn(showWarnings, "Application::updateCamera()"); - if (!OculusManager::isConnected() && !TV3DManager::isConnected()) { - if (Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) { - float xSign = _myCamera.getMode() == CAMERA_MODE_MIRROR ? 1.0f : -1.0f; - if (_faceshift.isActive()) { - const float EYE_OFFSET_SCALE = 0.025f; - glm::vec3 position = _faceshift.getHeadTranslation() * EYE_OFFSET_SCALE; - _myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, position.y, -position.z)); - updateProjectionMatrix(); - } + if (!OculusManager::isConnected() && !TV3DManager::isConnected() && + Menu::getInstance()->isOptionChecked(MenuOption::OffAxisProjection)) { + FaceTracker* tracker = getActiveFaceTracker(); + if (tracker) { + const float EYE_OFFSET_SCALE = 0.025f; + glm::vec3 position = tracker->getHeadTranslation() * EYE_OFFSET_SCALE; + float xSign = (_myCamera.getMode() == CAMERA_MODE_MIRROR) ? 1.0f : -1.0f; + _myCamera.setEyeOffsetPosition(glm::vec3(position.x * xSign, position.y, -position.z)); + updateProjectionMatrix(); } } } @@ -2137,7 +2221,7 @@ void Application::queryOctree(NodeType_t serverType, PacketType packetType, Node int packetLength = endOfQueryPacket - queryPacket; // make sure we still have an active socket - nodeList->writeDatagram(reinterpret_cast(queryPacket), packetLength, node); + nodeList->writeUnverifiedDatagram(reinterpret_cast(queryPacket), packetLength, node); // Feed number of bytes to corresponding channel of the bandwidth meter _bandwidthMeter.outputStream(BandwidthMeter::VOXELS).updateValue(packetLength); @@ -2252,8 +2336,7 @@ void Application::updateShadowMap() { glRotatef(glm::degrees(glm::angle(inverseRotation)), axis.x, axis.y, axis.z); // store view matrix without translation, which we'll use for precision-sensitive objects - glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&_untranslatedViewMatrix); - _viewMatrixTranslation = glm::vec3(); + updateUntranslatedViewMatrix(); _avatarManager.renderAvatars(Avatar::SHADOW_RENDER_MODE); _particles.render(); @@ -2338,8 +2421,7 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { glRotatef(-glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); // store view matrix without translation, which we'll use for precision-sensitive objects - glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&_untranslatedViewMatrix); - _viewMatrixTranslation = -whichCamera.getPosition(); + updateUntranslatedViewMatrix(-whichCamera.getPosition()); glTranslatef(_viewMatrixTranslation.x, _viewMatrixTranslation.y, _viewMatrixTranslation.z); @@ -2394,6 +2476,9 @@ 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(); + // Draw voxels if (Menu::getInstance()->isOptionChecked(MenuOption::Voxels)) { PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), @@ -2470,6 +2555,11 @@ void Application::displaySide(Camera& whichCamera, bool selfAvatarOnly) { } } +void Application::updateUntranslatedViewMatrix(const glm::vec3& viewMatrixTranslation) { + glGetFloatv(GL_MODELVIEW_MATRIX, (GLfloat*)&_untranslatedViewMatrix); + _viewMatrixTranslation = viewMatrixTranslation; +} + void Application::loadTranslatedViewMatrix(const glm::vec3& translation) { glLoadMatrixf((const GLfloat*)&_untranslatedViewMatrix); glTranslatef(translation.x + _viewMatrixTranslation.x, translation.y + _viewMatrixTranslation.y, @@ -2514,32 +2604,22 @@ void Application::displayOverlay() { } } - // Audio Scope - const int AUDIO_SCOPE_Y_OFFSET = 135; - if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { - if (Menu::getInstance()->isOptionChecked(MenuOption::Oscilloscope)) { - int oscilloscopeTop = _glWidget->height() - AUDIO_SCOPE_Y_OFFSET; - _audioScope.render(MIRROR_VIEW_LEFT_PADDING, oscilloscopeTop); - } - } - // Audio VU Meter and Mute Icon const int MUTE_ICON_SIZE = 24; const int AUDIO_METER_INSET = 2; - const int AUDIO_METER_WIDTH = MIRROR_VIEW_WIDTH - MUTE_ICON_SIZE - AUDIO_METER_INSET; + const int MUTE_ICON_PADDING = 10; + const int AUDIO_METER_WIDTH = MIRROR_VIEW_WIDTH - MUTE_ICON_SIZE - AUDIO_METER_INSET - MUTE_ICON_PADDING; const int AUDIO_METER_SCALE_WIDTH = AUDIO_METER_WIDTH - 2 * AUDIO_METER_INSET; const int AUDIO_METER_HEIGHT = 8; - const int AUDIO_METER_Y_GAP = 8; - const int AUDIO_METER_X = MIRROR_VIEW_LEFT_PADDING + MUTE_ICON_SIZE + AUDIO_METER_INSET; + const int AUDIO_METER_GAP = 5; + const int AUDIO_METER_X = MIRROR_VIEW_LEFT_PADDING + MUTE_ICON_SIZE + AUDIO_METER_INSET + AUDIO_METER_GAP; int audioMeterY; if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { - audioMeterY = MIRROR_VIEW_HEIGHT + AUDIO_METER_Y_GAP; + audioMeterY = MIRROR_VIEW_HEIGHT + AUDIO_METER_GAP + MUTE_ICON_PADDING; } else { - audioMeterY = AUDIO_METER_Y_GAP; + audioMeterY = AUDIO_METER_GAP + MUTE_ICON_PADDING; } - _audio.renderMuteIcon(MIRROR_VIEW_LEFT_PADDING, audioMeterY); - const float AUDIO_METER_BLUE[] = {0.0, 0.0, 1.0}; const float AUDIO_METER_GREEN[] = {0.0, 1.0, 0.0}; @@ -2549,29 +2629,53 @@ void Application::displayOverlay() { const float CLIPPING_INDICATOR_TIME = 1.0f; const float AUDIO_METER_AVERAGING = 0.5; const float LOG2 = log(2.f); - const float MAX_LOG2_SAMPLE = 15.f; + const float METER_LOUDNESS_SCALE = 2.8f / 5.f; + 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; + _trailingAudioLoudness = AUDIO_METER_AVERAGING * _trailingAudioLoudness + (1.f - AUDIO_METER_AVERAGING) * loudness; float log2loudness = log(_trailingAudioLoudness) / LOG2; - audioLevel = log2loudness / MAX_LOG2_SAMPLE * AUDIO_METER_SCALE_WIDTH; - + if (log2loudness <= LOG2_LOUDNESS_FLOOR) { + audioLevel = (log2loudness / LOG2_LOUDNESS_FLOOR) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; + } else { + audioLevel = (log2loudness - (LOG2_LOUDNESS_FLOOR - 1.f)) * METER_LOUDNESS_SCALE * AUDIO_METER_SCALE_WIDTH; + } + if (audioLevel > AUDIO_METER_SCALE_WIDTH) { + 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); + renderCollisionOverlay(_glWidget->width(), _glWidget->height(), magnitude, 1.0f); + } + + _audio.renderToolBox(MIRROR_VIEW_LEFT_PADDING + AUDIO_METER_GAP, + audioMeterY, + Menu::getInstance()->isOptionChecked(MenuOption::Mirror)); + + _audio.renderScope(_glWidget->width(), _glWidget->height()); + glBegin(GL_QUADS); if (isClipping) { glColor3f(1, 0, 0); } else { - glColor3f(0, 0, 0); + glColor3f(0.475f, 0.475f, 0.475f); } + + audioMeterY += AUDIO_METER_HEIGHT; + + glColor3f(0, 0, 0); // Draw audio meter background Quad glVertex2i(AUDIO_METER_X, audioMeterY); glVertex2i(AUDIO_METER_X + AUDIO_METER_WIDTH, audioMeterY); glVertex2i(AUDIO_METER_X + AUDIO_METER_WIDTH, audioMeterY + AUDIO_METER_HEIGHT); glVertex2i(AUDIO_METER_X, audioMeterY + AUDIO_METER_HEIGHT); + if (audioLevel > AUDIO_RED_START) { if (!isClipping) { glColor3fv(AUDIO_METER_RED); @@ -2611,6 +2715,7 @@ void Application::displayOverlay() { glVertex2i(AUDIO_METER_X + AUDIO_METER_INSET, audioMeterY + AUDIO_METER_HEIGHT - AUDIO_METER_INSET); glEnd(); + if (Menu::getInstance()->isOptionChecked(MenuOption::HeadMouse)) { _myAvatar->renderHeadMouse(); } @@ -2620,11 +2725,17 @@ void Application::displayOverlay() { glPointSize(1.0f); if (Menu::getInstance()->isOptionChecked(MenuOption::Stats)) { + int horizontalOffset = 0; + if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { + // mirror is enabled, let's set horizontal offset to give stats some margin + horizontalOffset += MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; + } + int voxelPacketsToProcess = _voxelProcessor.packetsToProcessCount(); // Onscreen text about position, servers, etc - displayStats(); + Stats::getInstance()->display(WHITE_TEXT, horizontalOffset, _fps, _packetsPerSecond, _bytesPerSecond, voxelPacketsToProcess); // Bandwidth meter if (Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) { - displayStatsBackground(0x33333399, _glWidget->width() - 296, _glWidget->height() - 68, 296, 68); + Stats::drawBackground(0x33333399, _glWidget->width() - 296, _glWidget->height() - 68, 296, 68); _bandwidthMeter.render(_glWidget->width(), _glWidget->height()); } } @@ -2638,7 +2749,7 @@ void Application::displayOverlay() { (Menu::getInstance()->isOptionChecked(MenuOption::Stats) && Menu::getInstance()->isOptionChecked(MenuOption::Bandwidth)) ? 80 : 20; - drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 1.0f, 0.f, frameTimer, WHITE_TEXT); + drawText(_glWidget->width() - 100, _glWidget->height() - timerBottom, 0.30f, 0.0f, 0, frameTimer, WHITE_TEXT); } _overlays.render2D(); @@ -2646,415 +2757,6 @@ void Application::displayOverlay() { glPopMatrix(); } -// translucent background box that makes stats more readable -void Application::displayStatsBackground(unsigned int rgba, int x, int y, int width, int height) { - glBegin(GL_QUADS); - glColor4f(((rgba >> 24) & 0xff) / 255.0f, - ((rgba >> 16) & 0xff) / 255.0f, - ((rgba >> 8) & 0xff) / 255.0f, - (rgba & 0xff) / 255.0f); - glVertex3f(x, y, 0); - glVertex3f(x + width, y, 0); - glVertex3f(x + width, y + height, 0); - glVertex3f(x , y + height, 0); - glEnd(); - glColor4f(1, 1, 1, 1); -} - -// display expanded or contracted stats - -void Application::displayStats() { - unsigned int backgroundColor = 0x33333399; - int verticalOffset = 0, horizontalOffset = 0, lines = 0; - bool mirrorEnabled = Menu::getInstance()->isOptionChecked(MenuOption::Mirror); - - QLocale locale(QLocale::English); - std::stringstream voxelStats; - - glPointSize(1.0f); - - // we need to take one avatar out so we don't include ourselves - int totalAvatars = _avatarManager.size() - 1; - int totalServers = NodeList::getInstance()->size(); - - if (mirrorEnabled) { - horizontalOffset += MIRROR_VIEW_WIDTH + MIRROR_VIEW_LEFT_PADDING * 2; - } - - lines = _statsExpanded ? 5 : 3; - displayStatsBackground(backgroundColor, horizontalOffset, 0, 165, lines * STATS_PELS_PER_LINE + 10); - horizontalOffset += 5; - - char serverNodes[30]; - sprintf(serverNodes, "Servers: %d", totalServers); - char avatarNodes[30]; - sprintf(avatarNodes, "Avatars: %d", totalAvatars); - char framesPerSecond[30]; - sprintf(framesPerSecond, "Framerate: %3.0f FPS", _fps); - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, serverNodes, WHITE_TEXT); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarNodes, WHITE_TEXT); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, framesPerSecond, WHITE_TEXT); - - if (_statsExpanded) { - char packetsPerSecond[30]; - sprintf(packetsPerSecond, "Pkts/sec: %d", _packetsPerSecond); - char averageMegabitsPerSecond[30]; - sprintf(averageMegabitsPerSecond, "Mbps: %3.2f", (float)_bytesPerSecond * 8.f / 1000000.f); - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, packetsPerSecond, WHITE_TEXT); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, averageMegabitsPerSecond, WHITE_TEXT); - } - - verticalOffset = 0; - horizontalOffset += 161; - - if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { - int pingAudio = 0, pingAvatar = 0, pingVoxel = 0, pingVoxelMax = 0; - - NodeList* nodeList = NodeList::getInstance(); - SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); - SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); - - pingAudio = audioMixerNode ? audioMixerNode->getPingMs() : 0; - pingAvatar = avatarMixerNode ? avatarMixerNode->getPingMs() : 0; - - // Now handle voxel servers, since there could be more than one, we average their ping times - unsigned long totalPingVoxel = 0; - int voxelServerCount = 0; - - foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { - if (node->getType() == NodeType::VoxelServer) { - totalPingVoxel += node->getPingMs(); - voxelServerCount++; - if (pingVoxelMax < node->getPingMs()) { - pingVoxelMax = node->getPingMs(); - } - } - } - - if (voxelServerCount) { - pingVoxel = totalPingVoxel/voxelServerCount; - } - - lines = _statsExpanded ? 4 : 3; - displayStatsBackground(backgroundColor, horizontalOffset, 0, 175, lines * STATS_PELS_PER_LINE + 10); - horizontalOffset += 5; - - char audioJitter[30]; - sprintf(audioJitter, - "Buffer msecs %.1f", - (float) (_audio.getNetworkBufferLengthSamplesPerChannel() + (float) _audio.getJitterBufferSamples()) / - (float)_audio.getNetworkSampleRate() * 1000.f); - drawText(30, _glWidget->height() - 22, 0.10f, 0.f, 2.f, audioJitter, WHITE_TEXT); - - - char audioPing[30]; - sprintf(audioPing, "Audio ping: %d", pingAudio); - - - char avatarPing[30]; - sprintf(avatarPing, "Avatar ping: %d", pingAvatar); - char voxelAvgPing[30]; - sprintf(voxelAvgPing, "Voxel avg ping: %d", pingVoxel); - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, audioPing, WHITE_TEXT); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarPing, WHITE_TEXT); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, voxelAvgPing, WHITE_TEXT); - - if (_statsExpanded) { - char voxelMaxPing[30]; - sprintf(voxelMaxPing, "Voxel max ping: %d", pingVoxelMax); - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, voxelMaxPing, WHITE_TEXT); - } - - verticalOffset = 0; - horizontalOffset += 171; - } - - glm::vec3 avatarPos = _myAvatar->getPosition(); - - lines = _statsExpanded ? 5 : 3; - displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - (mirrorEnabled ? 301 : 411) - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); - horizontalOffset += 5; - - char avatarPosition[200]; - if (mirrorEnabled) { - // shorthand formatting - sprintf(avatarPosition, "Pos: %.0f,%.0f,%.0f", avatarPos.x, avatarPos.y, avatarPos.z); - } else { - // longhand way - sprintf(avatarPosition, "Position: %.1f, %.1f, %.1f", avatarPos.x, avatarPos.y, avatarPos.z); - } - char avatarVelocity[30]; - sprintf(avatarVelocity, "Velocity: %.1f", glm::length(_myAvatar->getVelocity())); - char avatarBodyYaw[30]; - sprintf(avatarBodyYaw, "Yaw: %.1f", _myAvatar->getBodyYaw()); - char avatarMixerStats[200]; - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarPosition, WHITE_TEXT); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarVelocity, WHITE_TEXT); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarBodyYaw, WHITE_TEXT); - - if (_statsExpanded) { - SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AvatarMixer); - if (avatarMixer) { - sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps", - roundf(avatarMixer->getAverageKilobitsPerSecond()), - roundf(avatarMixer->getAveragePacketsPerSecond())); - } else { - sprintf(avatarMixerStats, "No Avatar Mixer"); - } - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, avatarMixerStats, WHITE_TEXT); - - stringstream downloadStats; - downloadStats << "Downloads: "; - foreach (Resource* resource, ResourceCache::getLoadingRequests()) { - const float MAXIMUM_PERCENTAGE = 100.0f; - downloadStats << roundf(resource->getProgress() * MAXIMUM_PERCENTAGE) << "% "; - } - downloadStats << "(" << ResourceCache::getPendingRequestCount() << " pending)"; - - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, downloadStats.str().c_str(), WHITE_TEXT); - } - - verticalOffset = 0; - horizontalOffset = _glWidget->width() - (mirrorEnabled ? 300 : 410); - - lines = _statsExpanded ? 12 : 3; - displayStatsBackground(backgroundColor, horizontalOffset, 0, _glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); - horizontalOffset += 5; - - if (_statsExpanded) { - // Local Voxel Memory Usage - voxelStats.str(""); - voxelStats << "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB"; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - - voxelStats.str(""); - voxelStats << - "Geometry RAM: " << _voxels.getVoxelMemoryUsageRAM() / 1000000.f << "MB / " << - "VBO: " << _voxels.getVoxelMemoryUsageVBO() / 1000000.f << "MB"; - if (_voxels.hasVoxelMemoryUsageGPU()) { - voxelStats << " / GPU: " << _voxels.getVoxelMemoryUsageGPU() / 1000000.f << "MB"; - } - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - - // Voxel Rendering - voxelStats.str(""); - voxelStats.precision(4); - voxelStats << "Voxel Rendering Slots Max: " << _voxels.getMaxVoxels() / 1000.f << "K"; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - } - - voxelStats.str(""); - voxelStats.precision(4); - voxelStats << "Drawn: " << _voxels.getVoxelsWritten() / 1000.f << "K " << - "Abandoned: " << _voxels.getAbandonedVoxels() / 1000.f << "K "; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - - // iterate all the current voxel stats, and list their sending modes, and total voxel counts - std::stringstream sendingMode(""); - sendingMode << "Octree Sending Mode: ["; - int serverCount = 0; - int movingServerCount = 0; - unsigned long totalNodes = 0; - unsigned long totalInternal = 0; - unsigned long totalLeaves = 0; - for(NodeToOctreeSceneStatsIterator i = _octreeServerSceneStats.begin(); i != _octreeServerSceneStats.end(); i++) { - //const QUuid& uuid = i->first; - OctreeSceneStats& stats = i->second; - serverCount++; - if (_statsExpanded) { - if (serverCount > 1) { - sendingMode << ","; - } - if (stats.isMoving()) { - sendingMode << "M"; - movingServerCount++; - } else { - sendingMode << "S"; - } - } - - // calculate server node totals - totalNodes += stats.getTotalElements(); - if (_statsExpanded) { - totalInternal += stats.getTotalInternal(); - totalLeaves += stats.getTotalLeaves(); - } - } - if (_statsExpanded) { - if (serverCount == 0) { - sendingMode << "---"; - } - sendingMode << "] " << serverCount << " servers"; - if (movingServerCount > 0) { - sendingMode << " "; - } else { - sendingMode << " "; - } - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)sendingMode.str().c_str(), WHITE_TEXT); - } - - // Incoming packets - int voxelPacketsToProcess = _voxelProcessor.packetsToProcessCount(); - if (_statsExpanded) { - voxelStats.str(""); - QString packetsString = locale.toString((int)voxelPacketsToProcess); - QString maxString = locale.toString((int)_recentMaxPackets); - voxelStats << "Voxel Packets to Process: " << qPrintable(packetsString) - << " [Recent Max: " << qPrintable(maxString) << "]"; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - } - - if (_resetRecentMaxPacketsSoon && voxelPacketsToProcess > 0) { - _recentMaxPackets = 0; - _resetRecentMaxPacketsSoon = false; - } - if (voxelPacketsToProcess == 0) { - _resetRecentMaxPacketsSoon = true; - } else { - if (voxelPacketsToProcess > _recentMaxPackets) { - _recentMaxPackets = voxelPacketsToProcess; - } - } - - verticalOffset += (_statsExpanded ? STATS_PELS_PER_LINE : 0); - - QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' '); - - // Server Voxels - voxelStats.str(""); - voxelStats << "Server voxels: " << qPrintable(serversTotalString); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - - if (_statsExpanded) { - QString serversInternalString = locale.toString((uint)totalInternal); - QString serversLeavesString = locale.toString((uint)totalLeaves); - - voxelStats.str(""); - voxelStats << - "Internal: " << qPrintable(serversInternalString) << " " << - "Leaves: " << qPrintable(serversLeavesString) << ""; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - } - - unsigned long localTotal = VoxelTreeElement::getNodeCount(); - QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' '); - - // Local Voxels - voxelStats.str(""); - voxelStats << "Local voxels: " << qPrintable(localTotalString); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - - if (_statsExpanded) { - unsigned long localInternal = VoxelTreeElement::getInternalNodeCount(); - unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount(); - QString localInternalString = locale.toString((uint)localInternal); - QString localLeavesString = locale.toString((uint)localLeaves); - - voxelStats.str(""); - voxelStats << - "Internal: " << qPrintable(localInternalString) << " " << - "Leaves: " << qPrintable(localLeavesString) << ""; - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0, 2, (char*)voxelStats.str().c_str(), WHITE_TEXT); - } - - // LOD Details - if (_statsExpanded) { - voxelStats.str(""); - QString displayLODDetails = Menu::getInstance()->getLODFeedbackText(); - voxelStats << "LOD: You can see " << qPrintable(displayLODDetails.trimmed()); - verticalOffset += STATS_PELS_PER_LINE; - drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, (char*)voxelStats.str().c_str(), WHITE_TEXT); - } -} - -// called on mouse click release -// check for clicks over stats in order to expand or contract them -void Application::checkStatsClick() { - if (0 != glm::compMax(glm::abs(glm::ivec2(_mouseX - _mouseDragStartedX, _mouseY - _mouseDragStartedY)))) { - // not worried about dragging on stats - return; - } - - int statsHeight = 0, statsWidth = 0, statsX = 0, statsY = 0, lines = 0; - - if (Menu::getInstance()->isOptionChecked(MenuOption::Mirror)) { - statsX += MIRROR_VIEW_WIDTH; - } - - // top-left stats click - lines = _statsExpanded ? 5 : 3; - statsHeight = lines * STATS_PELS_PER_LINE + 10; - statsWidth = 165; - if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) { - toggleStatsExpanded(); - return; - } - - // ping stats click - lines = _statsExpanded ? 4 : 3; - statsX += statsWidth; - statsHeight = lines * STATS_PELS_PER_LINE + 10; - statsWidth = 175; - if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) { - toggleStatsExpanded(); - return; - } - - // top-center stats panel click - lines = _statsExpanded ? 4 : 3; - statsX += statsWidth; - statsHeight = lines * STATS_PELS_PER_LINE + 10; - statsWidth = _glWidget->width() - 411 - statsX; - if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) { - toggleStatsExpanded(); - return; - } - - // top-right stats click - lines = _statsExpanded ? 11 : 3; - statsX = _glWidget->width() - 410; - statsHeight = lines * STATS_PELS_PER_LINE + 10; - statsWidth = _glWidget->width() - statsX; - if (_mouseX > statsX && _mouseX < statsX + statsWidth && _mouseY > statsY && _mouseY < statsY + statsHeight) { - toggleStatsExpanded(); - return; - } -} - -void Application::toggleStatsExpanded() { - _statsExpanded = !_statsExpanded; -} - glm::vec2 Application::getScaledScreenPoint(glm::vec2 projectedPoint) { float horizontalScale = _glWidget->width() / 2.0f; float verticalScale = _glWidget->height() / 2.0f; @@ -3339,6 +3041,7 @@ void Application::resetSensors() { _mouseX = _glWidget->width() / 2; _mouseY = _glWidget->height() / 2; + _faceplus.reset(); _faceshift.reset(); _visage.reset(); @@ -3378,8 +3081,7 @@ void Application::updateWindowTitle(){ QString username = AccountManager::getInstance().getUsername(); QString title = QString() + (!username.isEmpty() ? username + " " : QString()) + nodeList->getSessionUUID().toString() - + " @ " + nodeList->getDomainInfo().getHostname() + buildVersion; - + + " @ " + nodeList->getDomainHandler().getHostname() + buildVersion; qDebug("Application title set to: %s", title.toStdString().c_str()); _window->setWindowTitle(title); } @@ -3431,7 +3133,7 @@ void Application::nodeKilled(SharedNodePointer node) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); - printf("voxel server going away...... v[%f, %f, %f, %f]\n", + qDebug("voxel server going away...... v[%f, %f, %f, %f]", rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); // Add the jurisditionDetails object to the list of "fade outs" @@ -3462,7 +3164,7 @@ void Application::nodeKilled(SharedNodePointer node) { VoxelPositionSize rootDetails; voxelDetailsForCode(rootCode, rootDetails); - printf("particle server going away...... v[%f, %f, %f, %f]\n", + qDebug("particle server going away...... v[%f, %f, %f, %f]", rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); // Add the jurisditionDetails object to the list of "fade outs" @@ -3543,7 +3245,7 @@ int Application::parseOctreeStats(const QByteArray& packet, const SharedNodePoin if (jurisdiction->find(nodeUUID) == jurisdiction->end()) { - printf("stats from new server... v[%f, %f, %f, %f]\n", + qDebug("stats from new server... v[%f, %f, %f, %f]", rootDetails.x, rootDetails.y, rootDetails.z, rootDetails.s); // Add the jurisditionDetails object to the list of "fade outs" @@ -3572,35 +3274,36 @@ void Application::packetSent(quint64 length) { void Application::loadScripts() { // loads all saved scripts - QSettings* settings = new QSettings(this); - int size = settings->beginReadArray("Settings"); - + int size = lockSettings()->beginReadArray("Settings"); + unlockSettings(); for (int i = 0; i < size; ++i){ - settings->setArrayIndex(i); - QString string = settings->value("script").toString(); - loadScript(string); + lockSettings()->setArrayIndex(i); + QString string = _settings->value("script").toString(); + unlockSettings(); + if (!string.isEmpty()) { + loadScript(string); + } } - settings->endArray(); + QMutexLocker locker(&_settingsMutex); + _settings->endArray(); } void Application::clearScriptsBeforeRunning() { // clears all scripts from the settings - QSettings* settings = new QSettings(this); - settings->beginWriteArray("Settings"); - settings->endArray(); + QMutexLocker locker(&_settingsMutex); + _settings->remove("Settings"); } void Application::saveScripts() { // saves all current running scripts - QSettings* settings = new QSettings(this); - settings->beginWriteArray("Settings"); + QMutexLocker locker(&_settingsMutex); + _settings->beginWriteArray("Settings"); for (int i = 0; i < getRunningScripts().size(); ++i){ - settings->setArrayIndex(i); - settings->setValue("script", getRunningScripts().at(i)); + _settings->setArrayIndex(i); + _settings->setValue("script", getRunningScripts().at(i)); } - - settings->endArray(); + _settings->endArray(); } void Application::stopAllScripts() { @@ -3611,14 +3314,17 @@ void Application::stopAllScripts() { } _scriptEnginesHash.clear(); _runningScriptsWidget->setRunningScripts(getRunningScripts()); + bumpSettings(); } -void Application::stopScript(const QString &scriptName) -{ - _scriptEnginesHash.value(scriptName)->stop(); - qDebug() << "stopping script..." << scriptName; - _scriptEnginesHash.remove(scriptName); - _runningScriptsWidget->setRunningScripts(getRunningScripts()); +void Application::stopScript(const QString &scriptName) { + if (_scriptEnginesHash.contains(scriptName)) { + _scriptEnginesHash.value(scriptName)->stop(); + qDebug() << "stopping script..." << scriptName; + _scriptEnginesHash.remove(scriptName); + _runningScriptsWidget->setRunningScripts(getRunningScripts()); + bumpSettings(); + } } void Application::reloadAllScripts() { @@ -3659,10 +3365,13 @@ void Application::toggleRunningScriptsWidget() } void Application::uploadFST(bool isHead) { - FstReader reader(isHead); - if (reader.zip()) { - reader.send(); - } + ModelUploader* uploader = new ModelUploader(isHead); + QThread* thread = new QThread(); + thread->connect(uploader, SIGNAL(destroyed()), SLOT(quit())); + thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); + uploader->connect(thread, SIGNAL(started()), SLOT(send())); + + thread->start(); } void Application::uploadHead() { @@ -3673,7 +3382,10 @@ void Application::uploadSkeleton() { uploadFST(false); } -void Application::loadScript(const QString& scriptName) { +ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScriptFromEditor) { + if(loadScriptFromEditor && _scriptEnginesHash.contains(scriptName) && !_scriptEnginesHash[scriptName]->isFinished()){ + return _scriptEnginesHash[scriptName]; + } // start the script on a new thread... ScriptEngine* scriptEngine = new ScriptEngine(QUrl(scriptName), &_controllerScriptingInterface); @@ -3681,7 +3393,7 @@ void Application::loadScript(const QString& scriptName) { if (!scriptEngine->hasScript()) { qDebug() << "Application::loadScript(), script failed to load..."; - return; + return NULL; } _runningScriptsWidget->setRunningScripts(getRunningScripts()); @@ -3689,6 +3401,7 @@ void Application::loadScript(const QString& scriptName) { // we can use the same ones from the application. scriptEngine->getVoxelsScriptingInterface()->setPacketSender(&_voxelEditSender); scriptEngine->getVoxelsScriptingInterface()->setVoxelTree(_voxels.getTree()); + scriptEngine->getVoxelsScriptingInterface()->setUndoStack(&_undoStack); scriptEngine->getParticlesScriptingInterface()->setPacketSender(&_particleEditSender); scriptEngine->getParticlesScriptingInterface()->setParticleTree(_particles.getTree()); @@ -3707,6 +3420,8 @@ void Application::loadScript(const QString& scriptName) { scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("Settings", SettingsScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("AudioDevice", AudioDeviceScriptingInterface::getInstance()); + scriptEngine->registerGlobalObject("AnimationCache", &_animationCache); + scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector); QThread* workerThread = new QThread(this); @@ -3726,7 +3441,12 @@ void Application::loadScript(const QString& scriptName) { workerThread->start(); // restore the main window's active state - _window->activateWindow(); + if (!loadScriptFromEditor) { + _window->activateWindow(); + } + bumpSettings(); + + return scriptEngine; } void Application::loadDialog() { @@ -3734,7 +3454,10 @@ void Application::loadDialog() { if (_previousScriptLocation.isEmpty()) { QString desktopLocation = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); +// Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475 +#ifdef __APPLE__ suggestedName = desktopLocation.append("/script.js"); +#endif } else { suggestedName = _previousScriptLocation; } @@ -3743,9 +3466,11 @@ void Application::loadDialog() { tr("JavaScript Files (*.js)")); if (!fileNameString.isEmpty()) { _previousScriptLocation = fileNameString; - } + QMutexLocker locker(&_settingsMutex); + _settings->setValue("LastScriptLocation", _previousScriptLocation); - loadScript(fileNameString); + loadScript(fileNameString); + } } void Application::loadScriptURLDialog() { @@ -3863,3 +3588,38 @@ void Application::takeSnapshot() { Snapshot::saveSnapshot(_glWidget, _myAvatar); } + +void Application::urlGoTo(int argc, const char * constArgv[]) { + //Gets the url (hifi://domain/destination/orientation) + QString customUrl = getCmdOption(argc, constArgv, "-url"); + + if (customUrl.startsWith("hifi://")) { + QStringList urlParts = customUrl.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + 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); + } + } else if (urlParts.count() == 1) { + // location coordinates or place name + QString destination = urlParts[0]; + Menu::goTo(destination); + } + } +} diff --git a/interface/src/Application.h b/interface/src/Application.h index 4196ed7ca8..d7a7150fe1 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -1,27 +1,32 @@ // // Application.h -// interface +// interface/src // // Created by Andrzej Kapolka on 5/10/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__Application__ -#define __interface__Application__ +#ifndef hifi_Application_h +#define hifi_Application_h #include #include #include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include @@ -35,6 +40,7 @@ #include "MainWindow.h" #include "Audio.h" +#include "AudioReflector.h" #include "BuckyBalls.h" #include "Camera.h" #include "DatagramProcessor.h" @@ -49,6 +55,7 @@ #include "avatar/Avatar.h" #include "avatar/AvatarManager.h" #include "avatar/MyAvatar.h" +#include "devices/Faceplus.h" #include "devices/Faceshift.h" #include "devices/SixenseManager.h" #include "devices/Visage.h" @@ -95,6 +102,7 @@ static const float NODE_KILLED_GREEN = 0.0f; static const float NODE_KILLED_BLUE = 0.0f; static const QString SNAPSHOT_EXTENSION = ".jpg"; +static const QString CUSTOM_URL_SCHEME = "hifi:"; static const float BILLBOARD_FIELD_OF_VIEW = 30.0f; // degrees static const float BILLBOARD_DISTANCE = 5.0f; // meters @@ -110,11 +118,11 @@ public: static Application* getInstance() { return static_cast(QCoreApplication::instance()); } static QString& resourcesPath(); - Application(int& argc, char** argv, timeval &startup_time); + Application(int& argc, char** argv, QElapsedTimer &startup_time); ~Application(); void restoreSizeAndPosition(); - void loadScript(const QString& fileNameString); + ScriptEngine* loadScript(const QString& fileNameString, bool loadScriptFromEditor = false); void loadScripts(); void storeSizeAndPosition(); void clearScriptsBeforeRunning(); @@ -122,6 +130,7 @@ public: void initializeGL(); void paintGL(); void resizeGL(int width, int height); + void urlGoTo(int argc, const char * constArgv[]); void keyPressEvent(QKeyEvent* event); void keyReleaseEvent(QKeyEvent* event); @@ -157,6 +166,7 @@ public: bool isThrottleRendering() const { return _glWidget->isThrottleRendering(); } MyAvatar* getAvatar() { return _myAvatar; } Audio* getAudio() { return &_audio; } + const AudioReflector* getAudioReflector() const { return &_audioReflector; } Camera* getCamera() { return &_myCamera; } ViewFrustum* getViewFrustum() { return &_viewFrustum; } ViewFrustum* getShadowViewFrustum() { return &_shadowViewFrustum; } @@ -172,15 +182,20 @@ public: bool isMouseHidden() const { return _mouseHidden; } const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; } const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; } + Faceplus* getFaceplus() { return &_faceplus; } Faceshift* getFaceshift() { return &_faceshift; } Visage* getVisage() { return &_visage; } + FaceTracker* getActiveFaceTracker(); SixenseManager* getSixenseManager() { return &_sixenseManager; } BandwidthMeter* getBandwidthMeter() { return &_bandwidthMeter; } + QUndoStack* getUndoStack() { return &_undoStack; } /// if you need to access the application settings, use lockSettings()/unlockSettings() QSettings* lockSettings() { _settingsMutex.lock(); return _settings; } void unlockSettings() { _settingsMutex.unlock(); } + void saveSettings(); + MainWindow* getWindow() { return _window; } NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); } @@ -203,6 +218,10 @@ public: void displaySide(Camera& whichCamera, bool selfAvatarOnly = false); + /// Stores the current modelview matrix as the untranslated view matrix to use for transforms and the supplied vector as + /// the view matrix translation. + void updateUntranslatedViewMatrix(const glm::vec3& viewMatrixTranslation = glm::vec3()); + /// Loads a view matrix that incorporates the specified model translation without the precision issues that can /// result from matrix multiplication at high translation magnitudes. void loadTranslatedViewMatrix(const glm::vec3& translation); @@ -230,6 +249,7 @@ public: void skipVersion(QString latestVersion); QStringList getRunningScripts() { return _scriptEnginesHash.keys(); } + ScriptEngine* getScriptEngine(QString scriptHash) { return _scriptEnginesHash.contains(scriptHash) ? _scriptEnginesHash[scriptHash] : NULL; } signals: @@ -272,6 +292,8 @@ public slots: void uploadHead(); void uploadSkeleton(); + void bumpSettings() { ++_numChangedSettings; } + private slots: void timer(); void idle(); @@ -309,6 +331,7 @@ private: // Various helper functions called during update() void updateLOD(); void updateMouseRay(); + void updateFaceplus(); void updateFaceshift(); void updateVisage(); void updateMyAvatarLookAtPosition(); @@ -335,10 +358,6 @@ private: void updateShadowMap(); void displayOverlay(); - void displayStatsBackground(unsigned int rgba, int x, int y, int width, int height); - void displayStats(); - void checkStatsClick(); - void toggleStatsExpanded(); void renderRearViewMirror(const QRect& region, bool billboard = false); void renderViewFrustum(ViewFrustum& viewFrustum); @@ -359,7 +378,6 @@ private: MainWindow* _window; GLCanvas* _glWidget; // our GLCanvas has a couple extra features - bool _statsExpanded; BandwidthMeter _bandwidthMeter; QThread* _nodeThread; @@ -368,6 +386,9 @@ private: QNetworkAccessManager* _networkAccessManager; QMutex _settingsMutex; QSettings* _settings; + int _numChangedSettings; + + QUndoStack _undoStack; glm::vec3 _gravity; @@ -375,9 +396,9 @@ private: int _frameCount; float _fps; - timeval _applicationStartupTime; - timeval _timerStart, _timerEnd; - timeval _lastTimeUpdated; + QElapsedTimer _applicationStartupTime; + QElapsedTimer _timerStart; + QElapsedTimer _lastTimeUpdated; bool _justStarted; Stars _stars; @@ -403,7 +424,6 @@ private: ViewFrustum _shadowViewFrustum; quint64 _lastQueriedTime; - Oscilloscope _audioScope; float _trailingAudioLoudness; OctreeQuery _octreeQuery; // NodeData derived class for querying voxels from voxel server @@ -411,6 +431,7 @@ private: AvatarManager _avatarManager; MyAvatar* _myAvatar; // TODO: move this and relevant code to AvatarManager (or MyAvatar as the case may be) + Faceplus _faceplus; Faceshift _faceshift; Visage _visage; @@ -422,6 +443,7 @@ private: QRect _mirrorViewRect; RearMirrorTools* _rearMirrorTools; + float _cameraPushback; glm::mat4 _untranslatedViewMatrix; glm::vec3 _viewMatrixTranslation; glm::mat4 _projectionMatrix; @@ -454,6 +476,7 @@ private: QSet _keysPressed; GeometryCache _geometryCache; + AnimationCache _animationCache; TextureCache _textureCache; GlowEffect _glowEffect; @@ -472,9 +495,6 @@ private: int _packetsPerSecond; int _bytesPerSecond; - int _recentMaxPackets; // recent max incoming voxel packets to process - bool _resetRecentMaxPacketsSoon; - StDev _idleLoopStdev; float _idleLoopMeasuredJitter; @@ -503,9 +523,10 @@ private: Overlays _overlays; + AudioReflector _audioReflector; RunningScriptsWidget* _runningScriptsWidget; QHash _scriptEnginesHash; bool _runningScriptsWidgetWasVisible; }; -#endif /* defined(__interface__Application__) */ +#endif // hifi_Application_h diff --git a/interface/src/Audio.cpp b/interface/src/Audio.cpp index 7261f14c6d..c182f6e842 100644 --- a/interface/src/Audio.cpp +++ b/interface/src/Audio.cpp @@ -1,9 +1,12 @@ // // Audio.cpp -// interface +// interface/src // // Created by Stephen Birarda on 1/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -15,6 +18,15 @@ #include #endif +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include +#endif + #include #include #include @@ -25,6 +37,7 @@ #include #include #include +#include #include "Application.h" #include "Audio.h" @@ -38,7 +51,7 @@ static const int NUMBER_OF_NOISE_SAMPLE_FRAMES = 300; // Mute icon configration static const int MUTE_ICON_SIZE = 24; -Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* parent) : +Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) : AbstractAudioInterface(parent), _audioInput(NULL), _desiredInputFormat(), @@ -55,7 +68,6 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p _proceduralOutputDevice(NULL), _inputRingBuffer(0), _ringBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL), - _scope(scope), _averagedLatency(0.0), _measuredJitter(0), _jitterBufferSamples(initialJitterBufferSamples), @@ -76,7 +88,18 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p _collisionSoundDuration(0.0f), _proceduralEffectSample(0), _numFramesDisplayStarve(0), - _muted(false) + _muted(false), + _processSpatialAudio(false), + _spatialAudioStart(0), + _spatialAudioFinish(0), + _spatialAudioRingBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, true), // random access mode + _scopeEnabled(false), + _scopeEnabledPause(false), + _scopeInputOffset(0), + _scopeOutputOffset(0), + _scopeInput(SAMPLES_PER_SCOPE_WIDTH * sizeof(int16_t), 0), + _scopeOutputLeft(SAMPLES_PER_SCOPE_WIDTH * sizeof(int16_t), 0), + _scopeOutputRight(SAMPLES_PER_SCOPE_WIDTH * sizeof(int16_t), 0) { // clear the array of locally injected samples memset(_localProceduralSamples, 0, NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL); @@ -86,7 +109,8 @@ Audio::Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* p void Audio::init(QGLWidget *parent) { _micTextureId = parent->bindTexture(QImage(Application::resourcesPath() + "images/mic.svg")); - _muteTextureId = parent->bindTexture(QImage(Application::resourcesPath() + "images/mute.svg")); + _muteTextureId = parent->bindTexture(QImage(Application::resourcesPath() + "images/mic-mute.svg")); + _boxTextureId = parent->bindTexture(QImage(Application::resourcesPath() + "images/audio-box.svg")); } void Audio::reset() { @@ -147,24 +171,56 @@ QAudioDeviceInfo defaultAudioDeviceForMode(QAudio::Mode mode) { #endif #ifdef WIN32 QString deviceName; - if (mode == QAudio::AudioInput) { - WAVEINCAPS wic; - // first use WAVE_MAPPER to get the default devices manufacturer ID - waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(wic)); - //Use the received manufacturer id to get the device's real name - waveInGetDevCaps(wic.wMid, &wic, sizeof(wic)); - qDebug() << "input device:" << wic.szPname; - deviceName = wic.szPname; + //Check for Windows Vista or higher, IMMDeviceEnumerator doesn't work below that. + OSVERSIONINFO osvi; + ZeroMemory(&osvi, sizeof(OSVERSIONINFO)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); + GetVersionEx(&osvi); + const DWORD VISTA_MAJOR_VERSION = 6; + if (osvi.dwMajorVersion < VISTA_MAJOR_VERSION) {// lower then vista + if (mode == QAudio::AudioInput) { + WAVEINCAPS wic; + // first use WAVE_MAPPER to get the default devices manufacturer ID + waveInGetDevCaps(WAVE_MAPPER, &wic, sizeof(wic)); + //Use the received manufacturer id to get the device's real name + waveInGetDevCaps(wic.wMid, &wic, sizeof(wic)); + qDebug() << "input device:" << wic.szPname; + deviceName = wic.szPname; + } else { + WAVEOUTCAPS woc; + // first use WAVE_MAPPER to get the default devices manufacturer ID + waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(woc)); + //Use the received manufacturer id to get the device's real name + waveOutGetDevCaps(woc.wMid, &woc, sizeof(woc)); + qDebug() << "output device:" << woc.szPname; + deviceName = woc.szPname; + } } else { - WAVEOUTCAPS woc; - // first use WAVE_MAPPER to get the default devices manufacturer ID - waveOutGetDevCaps(WAVE_MAPPER, &woc, sizeof(woc)); - //Use the received manufacturer id to get the device's real name - waveOutGetDevCaps(woc.wMid, &woc, sizeof(woc)); - qDebug() << "output device:" << woc.szPname; - deviceName = woc.szPname; + HRESULT hr = S_OK; + CoInitialize(NULL); + 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); + pMMDeviceEnumerator->Release(); + pMMDeviceEnumerator = NULL; + CoUninitialize(); } - qDebug() << "DEBUG [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]"; + qDebug() << "DEBUG [" << deviceName << "] [" << getNamedAudioDeviceForMode(mode, deviceName).deviceName() << "]"; return getNamedAudioDeviceForMode(mode, deviceName); #endif @@ -354,7 +410,7 @@ void Audio::handleAudioInput() { unsigned int inputSamplesRequired = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * inputToNetworkInputRatio; QByteArray inputByteArray = _inputDevice->readAll(); - + if (Menu::getInstance()->isOptionChecked(MenuOption::EchoLocalAudio) && !_muted && _audioOutput) { // if this person wants local loopback add that to the locally injected audio @@ -362,7 +418,7 @@ void Audio::handleAudioInput() { // we didn't have the loopback output device going so set that up now _loopbackOutputDevice = _loopbackAudioOutput->start(); } - + if (_inputFormat == _outputFormat) { if (_loopbackOutputDevice) { _loopbackOutputDevice->write(inputByteArray); @@ -424,7 +480,7 @@ void Audio::handleAudioInput() { float thisSample = 0; int samplesOverNoiseGate = 0; - const float NOISE_GATE_HEIGHT = 7.f; + const float NOISE_GATE_HEIGHT = 7.0f; const int NOISE_GATE_WIDTH = 5; const int NOISE_GATE_CLOSE_FRAME_DELAY = 5; const int NOISE_GATE_FRAMES_TO_AVERAGE = 5; @@ -434,7 +490,7 @@ void Audio::handleAudioInput() { // // Check clipping, adjust DC offset, and check if should open noise gate // - float measuredDcOffset = 0.f; + float measuredDcOffset = 0.0f; // Increment the time since the last clip if (_timeSinceLastClip >= 0.0f) { _timeSinceLastClip += (float) NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL / (float) SAMPLE_RATE; @@ -444,50 +500,59 @@ void Audio::handleAudioInput() { measuredDcOffset += monoAudioSamples[i]; monoAudioSamples[i] -= (int16_t) _dcOffset; thisSample = fabsf(monoAudioSamples[i]); - if (thisSample > (32767.f * CLIPPING_THRESHOLD)) { + if (thisSample >= (32767.0f * CLIPPING_THRESHOLD)) { _timeSinceLastClip = 0.0f; } loudness += thisSample; // Noise Reduction: Count peaks above the average loudness - if (thisSample > (_noiseGateMeasuredFloor * NOISE_GATE_HEIGHT)) { + if (_noiseGateEnabled && (thisSample > (_noiseGateMeasuredFloor * NOISE_GATE_HEIGHT))) { samplesOverNoiseGate++; } } measuredDcOffset /= NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; - if (_dcOffset == 0.f) { + if (_dcOffset == 0.0f) { // On first frame, copy over measured offset _dcOffset = measuredDcOffset; } else { - _dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.f - DC_OFFSET_AVERAGING) * measuredDcOffset; + _dcOffset = DC_OFFSET_AVERAGING * _dcOffset + (1.0f - DC_OFFSET_AVERAGING) * measuredDcOffset; } - // - _lastInputLoudness = fabs(loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - - float averageOfAllSampleFrames = 0.f; - _noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness; - if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) { - float smallestSample = FLT_MAX; - for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_FRAMES - NOISE_GATE_FRAMES_TO_AVERAGE; i+= NOISE_GATE_FRAMES_TO_AVERAGE) { - float thisAverage = 0.0f; - for (int j = i; j < i + NOISE_GATE_FRAMES_TO_AVERAGE; j++) { - thisAverage += _noiseSampleFrames[j]; - averageOfAllSampleFrames += _noiseSampleFrames[j]; - } - thisAverage /= NOISE_GATE_FRAMES_TO_AVERAGE; - - if (thisAverage < smallestSample) { - smallestSample = thisAverage; - } + // Add tone injection if enabled + const float TONE_FREQ = 220.0f / SAMPLE_RATE * TWO_PI; + const float QUARTER_VOLUME = 8192.0f; + if (_toneInjectionEnabled) { + loudness = 0.0f; + for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) { + monoAudioSamples[i] = QUARTER_VOLUME * sinf(TONE_FREQ * (float)(i + _proceduralEffectSample)); + loudness += fabsf(monoAudioSamples[i]); } - averageOfAllSampleFrames /= NUMBER_OF_NOISE_SAMPLE_FRAMES; - _noiseGateMeasuredFloor = smallestSample; - _noiseGateSampleCounter = 0; - } + _lastInputLoudness = fabs(loudness / NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); - if (_noiseGateEnabled) { + // If Noise Gate is enabled, check and turn the gate on and off + if (!_toneInjectionEnabled && _noiseGateEnabled) { + float averageOfAllSampleFrames = 0.0f; + _noiseSampleFrames[_noiseGateSampleCounter++] = _lastInputLoudness; + if (_noiseGateSampleCounter == NUMBER_OF_NOISE_SAMPLE_FRAMES) { + float smallestSample = FLT_MAX; + for (int i = 0; i <= NUMBER_OF_NOISE_SAMPLE_FRAMES - NOISE_GATE_FRAMES_TO_AVERAGE; i += NOISE_GATE_FRAMES_TO_AVERAGE) { + float thisAverage = 0.0f; + for (int j = i; j < i + NOISE_GATE_FRAMES_TO_AVERAGE; j++) { + thisAverage += _noiseSampleFrames[j]; + averageOfAllSampleFrames += _noiseSampleFrames[j]; + } + thisAverage /= NOISE_GATE_FRAMES_TO_AVERAGE; + + if (thisAverage < smallestSample) { + smallestSample = thisAverage; + } + } + averageOfAllSampleFrames /= NUMBER_OF_NOISE_SAMPLE_FRAMES; + _noiseGateMeasuredFloor = smallestSample; + _noiseGateSampleCounter = 0; + + } if (samplesOverNoiseGate > NOISE_GATE_WIDTH) { _noiseGateOpen = true; _noiseGateFramesToClose = NOISE_GATE_CLOSE_FRAME_DELAY; @@ -501,31 +566,30 @@ void Audio::handleAudioInput() { _lastInputLoudness = 0; } } - // - // Add tone injection if enabled - // - const float TONE_FREQ = 220.f / SAMPLE_RATE * TWO_PI; - const float QUARTER_VOLUME = 8192.f; - if (_toneInjectionEnabled) { - for (int i = 0; i < NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; i++) { - monoAudioSamples[i] = QUARTER_VOLUME * sinf(TONE_FREQ * (float)(i + _proceduralEffectSample)); - } - } - - // add input data just written to the scope - QMetaObject::invokeMethod(_scope, "addSamples", Qt::QueuedConnection, - Q_ARG(QByteArray, QByteArray((char*) monoAudioSamples, - NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL)), - Q_ARG(bool, false), Q_ARG(bool, true)); } else { // our input loudness is 0, since we're muted _lastInputLoudness = 0; } + // at this point we have clean monoAudioSamples, which match our target output... + // this is what we should send to our interested listeners + if (_processSpatialAudio && !_muted && _audioOutput) { + QByteArray monoInputData((char*)monoAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL * sizeof(int16_t)); + emit processLocalAudio(_spatialAudioStart, monoInputData, _desiredInputFormat); + } + if (_proceduralAudioOutput) { processProceduralAudio(monoAudioSamples, NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL); } + if (_scopeEnabled && !_scopeEnabledPause) { + unsigned int numMonoAudioChannels = 1; + unsigned int monoAudioChannel = 0; + addBufferToScope(_scopeInput, _scopeInputOffset, monoAudioSamples, monoAudioChannel, numMonoAudioChannels); + _scopeInputOffset += NETWORK_SAMPLES_PER_FRAME; + _scopeInputOffset %= SAMPLES_PER_SCOPE_WIDTH; + } + NodeList* nodeList = NodeList::getInstance(); SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer); @@ -579,12 +643,11 @@ void Audio::handleAudioInput() { void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { const int NUM_INITIAL_PACKETS_DISCARD = 3; const int STANDARD_DEVIATION_SAMPLE_COUNT = 500; - - timeval currentReceiveTime; - gettimeofday(¤tReceiveTime, NULL); + + _timeSinceLastRecieved.start(); _totalPacketsReceived++; - - double timeDiff = diffclock(&_lastReceiveTime, ¤tReceiveTime); + + double timeDiff = (double)_timeSinceLastRecieved.nsecsElapsed() / 1000000.0; // ns to ms // Discard first few received packets for computing jitter (often they pile up on start) if (_totalPacketsReceived > NUM_INITIAL_PACKETS_DISCARD) { @@ -596,9 +659,9 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { _stdev.reset(); // Set jitter buffer to be a multiple of the measured standard deviation const int MAX_JITTER_BUFFER_SAMPLES = _ringBuffer.getSampleCapacity() / 2; - const float NUM_STANDARD_DEVIATIONS = 3.f; + const float NUM_STANDARD_DEVIATIONS = 3.0f; if (Menu::getInstance()->getAudioJitterBufferSamples() == 0) { - float newJitterBufferSamples = (NUM_STANDARD_DEVIATIONS * _measuredJitter) / 1000.f * SAMPLE_RATE; + float newJitterBufferSamples = (NUM_STANDARD_DEVIATIONS * _measuredJitter) / 1000.0f * SAMPLE_RATE; setJitterBufferSamples(glm::clamp((int)newJitterBufferSamples, 0, MAX_JITTER_BUFFER_SAMPLES)); } } @@ -609,8 +672,69 @@ void Audio::addReceivedAudioToBuffer(const QByteArray& audioByteArray) { } Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::AUDIO).updateValue(audioByteArray.size()); +} - _lastReceiveTime = currentReceiveTime; +// NOTE: numSamples is the total number of single channel samples, since callers will always call this with stereo +// data we know that we will have 2x samples for each stereo time sample at the format's sample rate +void Audio::addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples) { + // Calculate the number of remaining samples available. The source spatial audio buffer will get + // clipped if there are insufficient samples available in the accumulation buffer. + unsigned int remaining = _spatialAudioRingBuffer.getSampleCapacity() - _spatialAudioRingBuffer.samplesAvailable(); + + // Locate where in the accumulation buffer the new samples need to go + if (sampleTime >= _spatialAudioFinish) { + if (_spatialAudioStart == _spatialAudioFinish) { + // Nothing in the spatial audio ring buffer yet, Just do a straight copy, clipping if necessary + unsigned int sampleCount = (remaining < numSamples) ? remaining : numSamples; + if (sampleCount) { + _spatialAudioRingBuffer.writeSamples((int16_t*)spatialAudio.data(), sampleCount); + } + _spatialAudioFinish = _spatialAudioStart + sampleCount / _desiredOutputFormat.channelCount(); + } else { + // Spatial audio ring buffer already has data, but there is no overlap with the new sample. + // Compute the appropriate time delay and pad with silence until the new start time. + unsigned int delay = sampleTime - _spatialAudioFinish; + unsigned int delayCount = delay * _desiredOutputFormat.channelCount(); + unsigned int silentCount = (remaining < delayCount) ? remaining : delayCount; + if (silentCount) { + _spatialAudioRingBuffer.addSilentFrame(silentCount); + } + + // Recalculate the number of remaining samples + remaining -= silentCount; + unsigned int sampleCount = (remaining < numSamples) ? remaining : numSamples; + + // Copy the new spatial audio to the accumulation ring buffer + if (sampleCount) { + _spatialAudioRingBuffer.writeSamples((int16_t*)spatialAudio.data(), sampleCount); + } + _spatialAudioFinish += (sampleCount + silentCount) / _desiredOutputFormat.channelCount(); + } + } else { + // There is overlap between the spatial audio buffer and the new sample, mix the overlap + // Calculate the offset from the buffer's current read position, which should be located at _spatialAudioStart + unsigned int offset = (sampleTime - _spatialAudioStart) * _desiredOutputFormat.channelCount(); + unsigned int mixedSamplesCount = (_spatialAudioFinish - sampleTime) * _desiredOutputFormat.channelCount(); + mixedSamplesCount = (mixedSamplesCount < numSamples) ? mixedSamplesCount : numSamples; + + const int16_t* spatial = reinterpret_cast(spatialAudio.data()); + for (unsigned int i = 0; i < mixedSamplesCount; i++) { + int existingSample = _spatialAudioRingBuffer[i + offset]; + int newSample = spatial[i]; + int sumOfSamples = existingSample + newSample; + _spatialAudioRingBuffer[i + offset] = static_cast(glm::clamp(sumOfSamples, + std::numeric_limits::min(), std::numeric_limits::max())); + } + + // Copy the remaining unoverlapped spatial audio to the spatial audio buffer, if any + unsigned int nonMixedSampleCount = numSamples - mixedSamplesCount; + nonMixedSampleCount = (remaining < nonMixedSampleCount) ? remaining : nonMixedSampleCount; + if (nonMixedSampleCount) { + _spatialAudioRingBuffer.writeSamples((int16_t*)spatialAudio.data() + mixedSamplesCount, nonMixedSampleCount); + // Extend the finish time by the amount of unoverlapped samples + _spatialAudioFinish += nonMixedSampleCount / _desiredOutputFormat.channelCount(); + } + } } bool Audio::mousePressEvent(int x, int y) { @@ -632,7 +756,7 @@ void Audio::toggleAudioNoiseReduction() { void Audio::processReceivedAudio(const QByteArray& audioByteArray) { _ringBuffer.parseData(audioByteArray); - + float networkOutputToOutputRatio = (_desiredOutputFormat.sampleRate() / (float) _outputFormat.sampleRate()) * (_desiredOutputFormat.channelCount() / (float) _outputFormat.channelCount()); @@ -663,13 +787,33 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { //qDebug() << "pushing " << numNetworkOutputSamples; _ringBuffer.setIsStarved(false); - // copy the samples we'll resample from the ring buffer - this also - // pushes the read pointer of the ring buffer forwards - int16_t* ringBufferSamples= new int16_t[numNetworkOutputSamples]; - _ringBuffer.readSamples(ringBufferSamples, numNetworkOutputSamples); - - // add the next numNetworkOutputSamples from each QByteArray - // in our _localInjectionByteArrays QVector to the localInjectedSamples + int16_t* ringBufferSamples = new int16_t[numNetworkOutputSamples]; + if (_processSpatialAudio) { + unsigned int sampleTime = _spatialAudioStart; + QByteArray buffer; + buffer.resize(numNetworkOutputSamples * sizeof(int16_t)); + + _ringBuffer.readSamples((int16_t*)buffer.data(), numNetworkOutputSamples); + // Accumulate direct transmission of audio from sender to receiver + if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal)) { + emit preProcessOriginalInboundAudio(sampleTime, buffer, _desiredOutputFormat); + addSpatialAudioToBuffer(sampleTime, buffer, numNetworkOutputSamples); + } + + // Send audio off for spatial processing + emit processInboundAudio(sampleTime, buffer, _desiredOutputFormat); + + // copy the samples we'll resample from the spatial audio ring buffer - this also + // pushes the read pointer of the spatial audio ring buffer forwards + _spatialAudioRingBuffer.readSamples(ringBufferSamples, numNetworkOutputSamples); + + // Advance the start point for the next packet of audio to arrive + _spatialAudioStart += numNetworkOutputSamples / _desiredOutputFormat.channelCount(); + } else { + // copy the samples we'll resample from the ring buffer - this also + // pushes the read pointer of the ring buffer forwards + _ringBuffer.readSamples(ringBufferSamples, numNetworkOutputSamples); + } // copy the packet from the RB to the output linearResampling(ringBufferSamples, @@ -680,12 +824,31 @@ void Audio::processReceivedAudio(const QByteArray& audioByteArray) { if (_outputDevice) { _outputDevice->write(outputBuffer); - - // add output (@speakers) data just written to the scope - QMetaObject::invokeMethod(_scope, "addSamples", Qt::QueuedConnection, - Q_ARG(QByteArray, QByteArray((char*) ringBufferSamples, numNetworkOutputSamples)), - Q_ARG(bool, true), Q_ARG(bool, false)); } + + if (_scopeEnabled && !_scopeEnabledPause) { + unsigned int numAudioChannels = _desiredOutputFormat.channelCount(); + int16_t* samples = ringBufferSamples; + for (int numSamples = numNetworkOutputSamples / numAudioChannels; numSamples > 0; numSamples -= NETWORK_SAMPLES_PER_FRAME) { + + unsigned int audioChannel = 0; + addBufferToScope( + _scopeOutputLeft, + _scopeOutputOffset, + samples, audioChannel, numAudioChannels); + + audioChannel = 1; + addBufferToScope( + _scopeOutputRight, + _scopeOutputOffset, + samples, audioChannel, numAudioChannels); + + _scopeOutputOffset += NETWORK_SAMPLES_PER_FRAME; + _scopeOutputOffset %= SAMPLES_PER_SCOPE_WIDTH; + samples += NETWORK_SAMPLES_PER_FRAME * numAudioChannels; + } + } + delete[] ringBufferSamples; } } @@ -724,14 +887,23 @@ void Audio::toggleToneInjection() { _toneInjectionEnabled = !_toneInjectionEnabled; } +void Audio::toggleAudioSpatialProcessing() { + _processSpatialAudio = !_processSpatialAudio; + if (_processSpatialAudio) { + _spatialAudioStart = 0; + _spatialAudioFinish = 0; + _spatialAudioRingBuffer.reset(); + } +} + // Take a pointer to the acquired microphone input samples and add procedural sounds void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) { float sample; const float COLLISION_SOUND_CUTOFF_LEVEL = 0.01f; - const float COLLISION_SOUND_MAX_VOLUME = 1000.f; + const float COLLISION_SOUND_MAX_VOLUME = 1000.0f; const float UP_MAJOR_FIFTH = powf(1.5f, 4.0f); - const float DOWN_TWO_OCTAVES = 4.f; - const float DOWN_FOUR_OCTAVES = 16.f; + const float DOWN_TWO_OCTAVES = 4.0f; + const float DOWN_FOUR_OCTAVES = 16.0f; float t; if (_collisionSoundMagnitude > COLLISION_SOUND_CUTOFF_LEVEL) { for (int i = 0; i < numSamples; i++) { @@ -761,12 +933,12 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) { _proceduralEffectSample += numSamples; // Add a drum sound - const float MAX_VOLUME = 32000.f; - const float MAX_DURATION = 2.f; + const float MAX_VOLUME = 32000.0f; + const float MAX_DURATION = 2.0f; const float MIN_AUDIBLE_VOLUME = 0.001f; const float NOISE_MAGNITUDE = 0.02f; float frequency = (_drumSoundFrequency / SAMPLE_RATE) * TWO_PI; - if (_drumSoundVolume > 0.f) { + if (_drumSoundVolume > 0.0f) { for (int i = 0; i < numSamples; i++) { t = (float) _drumSoundSample + (float) i; sample = sinf(t * frequency); @@ -786,12 +958,12 @@ void Audio::addProceduralSounds(int16_t* monoInput, int numSamples) { _localProceduralSamples[i] = glm::clamp(_localProceduralSamples[i] + collisionSample, MIN_SAMPLE_VALUE, MAX_SAMPLE_VALUE); - _drumSoundVolume *= (1.f - _drumSoundDecay); + _drumSoundVolume *= (1.0f - _drumSoundDecay); } _drumSoundSample += numSamples; - _drumSoundDuration = glm::clamp(_drumSoundDuration - (AUDIO_CALLBACK_MSECS / 1000.f), 0.f, MAX_DURATION); - if (_drumSoundDuration == 0.f || (_drumSoundVolume < MIN_AUDIBLE_VOLUME)) { - _drumSoundVolume = 0.f; + _drumSoundDuration = glm::clamp(_drumSoundDuration - (AUDIO_CALLBACK_MSECS / 1000.0f), 0.0f, MAX_DURATION); + if (_drumSoundDuration == 0.0f || (_drumSoundVolume < MIN_AUDIBLE_VOLUME)) { + _drumSoundVolume = 0.0f; } } } @@ -818,13 +990,52 @@ void Audio::handleAudioByteArray(const QByteArray& audioByteArray) { // or send to the mixer and use delayed loopback } -void Audio::renderMuteIcon(int x, int y) { +void Audio::renderToolBox(int x, int y, bool boxed) { - _iconBounds = QRect(x, y, MUTE_ICON_SIZE, MUTE_ICON_SIZE); glEnable(GL_TEXTURE_2D); - glBindTexture(GL_TEXTURE_2D, _micTextureId); - glColor3f(.93f, .93f, .93f); + if (boxed) { + + bool isClipping = ((getTimeSinceLastClip() > 0.0f) && (getTimeSinceLastClip() < 1.0f)); + const int BOX_LEFT_PADDING = 5; + const int BOX_TOP_PADDING = 10; + const int BOX_WIDTH = 266; + const int BOX_HEIGHT = 44; + + QRect boxBounds = QRect(x - BOX_LEFT_PADDING, y - BOX_TOP_PADDING, BOX_WIDTH, BOX_HEIGHT); + + glBindTexture(GL_TEXTURE_2D, _boxTextureId); + + if (isClipping) { + glColor3f(1.0f, 0.0f, 0.0f); + } else { + glColor3f(0.41f, 0.41f, 0.41f); + } + glBegin(GL_QUADS); + + glTexCoord2f(1, 1); + glVertex2f(boxBounds.left(), boxBounds.top()); + + glTexCoord2f(0, 1); + glVertex2f(boxBounds.right(), boxBounds.top()); + + glTexCoord2f(0, 0); + glVertex2f(boxBounds.right(), boxBounds.bottom()); + + glTexCoord2f(1, 0); + glVertex2f(boxBounds.left(), boxBounds.bottom()); + + glEnd(); + } + + _iconBounds = QRect(x, y, MUTE_ICON_SIZE, MUTE_ICON_SIZE); + if (!_muted) { + glBindTexture(GL_TEXTURE_2D, _micTextureId); + } else { + glBindTexture(GL_TEXTURE_2D, _muteTextureId); + } + + glColor3f(1,1,1); glBegin(GL_QUADS); glTexCoord2f(1, 1); @@ -841,28 +1052,140 @@ void Audio::renderMuteIcon(int x, int y) { glEnd(); - if (_muted) { - glBindTexture(GL_TEXTURE_2D, _muteTextureId); - glBegin(GL_QUADS); - - glTexCoord2f(1, 1); - glVertex2f(_iconBounds.left(), _iconBounds.top()); - - glTexCoord2f(0, 1); - glVertex2f(_iconBounds.right(), _iconBounds.top()); - - glTexCoord2f(0, 0); - glVertex2f(_iconBounds.right(), _iconBounds.bottom()); - - glTexCoord2f(1, 0); - glVertex2f(_iconBounds.left(), _iconBounds.bottom()); - - glEnd(); - } - glDisable(GL_TEXTURE_2D); } +void Audio::toggleScopePause() { + _scopeEnabledPause = !_scopeEnabledPause; +} + +void Audio::toggleScope() { + _scopeEnabled = !_scopeEnabled; + if (_scopeEnabled) { + static const int width = SAMPLES_PER_SCOPE_WIDTH; + _scopeInputOffset = 0; + _scopeOutputOffset = 0; + memset(_scopeInput.data(), 0, width * sizeof(int16_t)); + memset(_scopeOutputLeft.data(), 0, width * sizeof(int16_t)); + memset(_scopeOutputRight.data(), 0, width * sizeof(int16_t)); + } +} + +void Audio::addBufferToScope( + QByteArray& byteArray, unsigned int frameOffset, const int16_t* source, unsigned int sourceChannel, unsigned int sourceNumberOfChannels) { + + // Constant multiplier to map sample value to vertical size of scope + float multiplier = (float)MULTIPLIER_SCOPE_HEIGHT / logf(2.0f); + + // Temporary variable receives sample value + float sample; + + // Temporary variable receives mapping of sample value + int16_t value; + + // Short int pointer to mapped samples in byte array + int16_t* destination = (int16_t*) byteArray.data(); + + for (unsigned int i = 0; i < NETWORK_SAMPLES_PER_FRAME; i++) { + sample = (float)source[i * sourceNumberOfChannels + sourceChannel]; + if (sample > 0) { + value = (int16_t)(multiplier * logf(sample)); + } else if (sample < 0) { + value = (int16_t)(-multiplier * logf(-sample)); + } else { + value = 0; + } + destination[i + frameOffset] = value; + } +} + +void Audio::renderScope(int width, int height) { + + if (!_scopeEnabled) + return; + + static const float backgroundColor[4] = { 0.2f, 0.2f, 0.2f, 0.6f }; + static const float gridColor[4] = { 0.3f, 0.3f, 0.3f, 0.6f }; + static const float inputColor[4] = { 0.3f, .7f, 0.3f, 0.6f }; + static const float outputLeftColor[4] = { 0.7f, .3f, 0.3f, 0.6f }; + static const float outputRightColor[4] = { 0.3f, .3f, 0.7f, 0.6f }; + static const int gridRows = 2; + static const int gridCols = 5; + + int x = (width - SAMPLES_PER_SCOPE_WIDTH) / 2; + int y = (height - SAMPLES_PER_SCOPE_HEIGHT) / 2; + int w = SAMPLES_PER_SCOPE_WIDTH; + int h = SAMPLES_PER_SCOPE_HEIGHT; + + renderBackground(backgroundColor, x, y, w, h); + renderGrid(gridColor, x, y, w, h, gridRows, gridCols); + renderLineStrip(inputColor, x, y, w, _scopeInputOffset, _scopeInput); + renderLineStrip(outputLeftColor, x, y, w, _scopeOutputOffset, _scopeOutputLeft); + renderLineStrip(outputRightColor, x, y, w, _scopeOutputOffset, _scopeOutputRight); +} + +void Audio::renderBackground(const float* color, int x, int y, int width, int height) { + + glColor4fv(color); + glBegin(GL_QUADS); + + glVertex2i(x, y); + glVertex2i(x + width, y); + glVertex2i(x + width, y + height); + glVertex2i(x , y + height); + + glEnd(); + glColor4f(1, 1, 1, 1); +} + +void Audio::renderGrid(const float* color, int x, int y, int width, int height, int rows, int cols) { + + glColor4fv(color); + glBegin(GL_LINES); + + int dx = width / cols; + int dy = height / rows; + int tx = x; + int ty = y; + + // Draw horizontal grid lines + for (int i = rows + 1; --i >= 0; ) { + glVertex2i(x, ty); + glVertex2i(x + width, ty); + ty += dy; + } + // Draw vertical grid lines + for (int i = cols + 1; --i >= 0; ) { + glVertex2i(tx, y); + glVertex2i(tx, y + height); + tx += dx; + } + glEnd(); + glColor4f(1, 1, 1, 1); +} + +void Audio::renderLineStrip(const float* color, int x, int y, int n, int offset, const QByteArray& byteArray) { + + glColor4fv(color); + glBegin(GL_LINE_STRIP); + + int16_t sample; + int16_t* samples = ((int16_t*) byteArray.data()) + offset; + y += SAMPLES_PER_SCOPE_HEIGHT / 2; + for (int i = n - offset; --i >= 0; ) { + sample = *samples++; + glVertex2i(x++, y - sample); + } + samples = (int16_t*) byteArray.data(); + for (int i = offset; --i >= 0; ) { + sample = *samples++; + glVertex2i(x++, y - sample); + } + glEnd(); + glColor4f(1, 1, 1, 1); +} + + bool Audio::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) { bool supportedFormat = false; @@ -912,7 +1235,6 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) delete _audioOutput; _audioOutput = NULL; - _numInputCallbackBytes = 0; _loopbackOutputDevice = NULL; delete _loopbackAudioOutput; @@ -943,7 +1265,13 @@ bool Audio::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDeviceInfo) // setup a procedural audio output device _proceduralAudioOutput = new QAudioOutput(outputDeviceInfo, _outputFormat, this); - gettimeofday(&_lastReceiveTime, NULL); + _timeSinceLastRecieved.start(); + + // setup spatial audio ringbuffer + int numFrameSamples = _outputFormat.sampleRate() * _desiredOutputFormat.channelCount(); + _spatialAudioRingBuffer.resizeForFrameSize(numFrameSamples); + _spatialAudioStart = _spatialAudioFinish = 0; + supportedFormat = true; } } diff --git a/interface/src/Audio.h b/interface/src/Audio.h index 88488922f3..e1b8a7dddc 100644 --- a/interface/src/Audio.h +++ b/interface/src/Audio.h @@ -1,18 +1,16 @@ // // Audio.h -// interface +// interface/src // // Created by Stephen Birarda on 1/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__Audio__ -#define __interface__Audio__ - -#ifdef _WIN32 -#define WANT_TIMEVAL -#include -#endif +#ifndef hifi_Audio_h +#define hifi_Audio_h #include #include @@ -21,19 +19,18 @@ #include #include +#include #include #include #include #include #include +#include #include #include #include -#include "ui/Oscilloscope.h" - - static const int NUM_AUDIO_CHANNELS = 2; class QAudioInput; @@ -44,7 +41,7 @@ class Audio : public AbstractAudioInterface { Q_OBJECT public: // setup for audio I/O - Audio(Oscilloscope* scope, int16_t initialJitterBufferSamples, QObject* parent = 0); + Audio(int16_t initialJitterBufferSamples, QObject* parent = 0); float getLastInputLoudness() const { return glm::max(_lastInputLoudness - _noiseGateMeasuredFloor, 0.f); } float getTimeSinceLastClip() const { return _timeSinceLastClip; } @@ -55,8 +52,6 @@ public: void setJitterBufferSamples(int samples) { _jitterBufferSamples = samples; } int getJitterBufferSamples() { return _jitterBufferSamples; } - void lowPassFilter(int16_t* inputBuffer); - virtual void startCollisionSound(float magnitude, float frequency, float noise, float duration, bool flashScreen); virtual void startDrumSound(float volume, float frequency, float duration, float decay); @@ -69,20 +64,27 @@ public: void init(QGLWidget *parent = 0); bool mousePressEvent(int x, int y); - void renderMuteIcon(int x, int y); + void renderToolBox(int x, int y, bool boxed); + void renderScope(int width, int height); int getNetworkSampleRate() { return SAMPLE_RATE; } int getNetworkBufferLengthSamplesPerChannel() { return NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; } + bool getProcessSpatialAudio() const { return _processSpatialAudio; } + public slots: void start(); void stop(); void addReceivedAudioToBuffer(const QByteArray& audioByteArray); + void addSpatialAudioToBuffer(unsigned int sampleTime, const QByteArray& spatialAudio, unsigned int numSamples); void handleAudioInput(); void reset(); void toggleMute(); void toggleAudioNoiseReduction(); void toggleToneInjection(); + void toggleScope(); + void toggleScopePause(); + void toggleAudioSpatialProcessing(); virtual void handleAudioByteArray(const QByteArray& audioByteArray); @@ -98,6 +100,9 @@ public slots: signals: bool muteToggled(); + void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format); + void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); + void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); private: @@ -123,9 +128,8 @@ private: QString _inputAudioDeviceName; QString _outputAudioDeviceName; - Oscilloscope* _scope; StDev _stdev; - timeval _lastReceiveTime; + QElapsedTimer _timeSinceLastRecieved; float _averagedLatency; float _measuredJitter; int16_t _jitterBufferSamples; @@ -161,11 +165,18 @@ private: bool _localEcho; GLuint _micTextureId; GLuint _muteTextureId; + GLuint _boxTextureId; QRect _iconBounds; - // Audio callback in class context. + /// Audio callback in class context. inline void performIO(int16_t* inputLeft, int16_t* outputLeft, int16_t* outputRight); + + bool _processSpatialAudio; /// Process received audio by spatial audio hooks + unsigned int _spatialAudioStart; /// Start of spatial audio interval (in sample rate time base) + unsigned int _spatialAudioFinish; /// End of spatial audio interval (in sample rate time base) + AudioRingBuffer _spatialAudioRingBuffer; /// Spatially processed audio + // Process procedural audio by // 1. Echo to the local procedural output device // 2. Mix with the audio input @@ -186,7 +197,30 @@ private: int calculateNumberOfFrameSamples(int numBytes); float calculateDeviceToNetworkInputRatio(int numBytes); + // Audio scope methods for data acquisition + void addBufferToScope( + QByteArray& byteArray, unsigned int frameOffset, const int16_t* source, unsigned int sourceChannel, unsigned int sourceNumberOfChannels); + + // Audio scope methods for rendering + void renderBackground(const float* color, int x, int y, int width, int height); + void renderGrid(const float* color, int x, int y, int width, int height, int rows, int cols); + void renderLineStrip(const float* color, int x, int y, int n, int offset, const QByteArray& byteArray); + + // Audio scope data + static const unsigned int NETWORK_SAMPLES_PER_FRAME = NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL; + static const unsigned int FRAMES_PER_SCOPE = 5; + static const unsigned int SAMPLES_PER_SCOPE_WIDTH = FRAMES_PER_SCOPE * NETWORK_SAMPLES_PER_FRAME; + static const unsigned int MULTIPLIER_SCOPE_HEIGHT = 20; + static const unsigned int SAMPLES_PER_SCOPE_HEIGHT = 2 * 15 * MULTIPLIER_SCOPE_HEIGHT; + bool _scopeEnabled; + bool _scopeEnabledPause; + int _scopeInputOffset; + int _scopeOutputOffset; + QByteArray _scopeInput; + QByteArray _scopeOutputLeft; + QByteArray _scopeOutputRight; + }; -#endif /* defined(__interface__audio__) */ +#endif // hifi_Audio_h diff --git a/interface/src/AudioReflector.cpp b/interface/src/AudioReflector.cpp new file mode 100644 index 0000000000..e66735c403 --- /dev/null +++ b/interface/src/AudioReflector.cpp @@ -0,0 +1,858 @@ +// +// AudioReflector.cpp +// interface +// +// Created by Brad Hefta-Gaub on 4/2/2014 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// + +#include + +#include "AudioReflector.h" +#include "Menu.h" + +const float DEFAULT_PRE_DELAY = 20.0f; // this delay in msecs will always be added to all reflections +const float DEFAULT_MS_DELAY_PER_METER = 3.0f; +const float MINIMUM_ATTENUATION_TO_REFLECT = 1.0f / 256.0f; +const float DEFAULT_DISTANCE_SCALING_FACTOR = 2.0f; +const float MAXIMUM_DELAY_MS = 1000.0 * 20.0f; // stop reflecting after path is this long +const int DEFAULT_DIFFUSION_FANOUT = 5; +const int ABSOLUTE_MAXIMUM_BOUNCE_COUNT = 10; +const float DEFAULT_LOCAL_ATTENUATION_FACTOR = 0.125; +const float DEFAULT_COMB_FILTER_WINDOW = 0.05f; //ms delay differential to avoid + +const float SLIGHTLY_SHORT = 0.999f; // slightly inside the distance so we're on the inside of the reflection point + +const float DEFAULT_ABSORPTION_RATIO = 0.125; // 12.5% is absorbed +const float DEFAULT_DIFFUSION_RATIO = 0.125; // 12.5% is diffused +const float DEFAULT_ORIGINAL_ATTENUATION = 1.0f; +const float DEFAULT_ECHO_ATTENUATION = 1.0f; + +AudioReflector::AudioReflector(QObject* parent) : + QObject(parent), + _preDelay(DEFAULT_PRE_DELAY), + _soundMsPerMeter(DEFAULT_MS_DELAY_PER_METER), + _distanceAttenuationScalingFactor(DEFAULT_DISTANCE_SCALING_FACTOR), + _localAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR), + _combFilterWindow(DEFAULT_COMB_FILTER_WINDOW), + _diffusionFanout(DEFAULT_DIFFUSION_FANOUT), + _absorptionRatio(DEFAULT_ABSORPTION_RATIO), + _diffusionRatio(DEFAULT_DIFFUSION_RATIO), + _originalSourceAttenuation(DEFAULT_ORIGINAL_ATTENUATION), + _allEchoesAttenuation(DEFAULT_ECHO_ATTENUATION), + _withDiffusion(false), + _lastPreDelay(DEFAULT_PRE_DELAY), + _lastSoundMsPerMeter(DEFAULT_MS_DELAY_PER_METER), + _lastDistanceAttenuationScalingFactor(DEFAULT_DISTANCE_SCALING_FACTOR), + _lastLocalAudioAttenuationFactor(DEFAULT_LOCAL_ATTENUATION_FACTOR), + _lastDiffusionFanout(DEFAULT_DIFFUSION_FANOUT), + _lastAbsorptionRatio(DEFAULT_ABSORPTION_RATIO), + _lastDiffusionRatio(DEFAULT_DIFFUSION_RATIO), + _lastDontDistanceAttenuate(false), + _lastAlternateDistanceAttenuate(false) +{ + _reflections = 0; + _diffusionPathCount = 0; + _officialAverageAttenuation = _averageAttenuation = 0.0f; + _officialMaxAttenuation = _maxAttenuation = 0.0f; + _officialMinAttenuation = _minAttenuation = 0.0f; + _officialAverageDelay = _averageDelay = 0; + _officialMaxDelay = _maxDelay = 0; + _officialMinDelay = _minDelay = 0; + _inboundEchoesCount = 0; + _inboundEchoesSuppressedCount = 0; + _localEchoesCount = 0; + _localEchoesSuppressedCount = 0; +} + +bool AudioReflector::haveAttributesChanged() { + bool withDiffusion = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions); + bool dontDistanceAttenuate = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingDontDistanceAttenuate); + bool alternateDistanceAttenuate = Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate); + + bool attributesChange = (_withDiffusion != withDiffusion + || _lastPreDelay != _preDelay + || _lastSoundMsPerMeter != _soundMsPerMeter + || _lastDistanceAttenuationScalingFactor != _distanceAttenuationScalingFactor + || _lastDiffusionFanout != _diffusionFanout + || _lastAbsorptionRatio != _absorptionRatio + || _lastDiffusionRatio != _diffusionRatio + || _lastDontDistanceAttenuate != dontDistanceAttenuate + || _lastAlternateDistanceAttenuate != alternateDistanceAttenuate); + + if (attributesChange) { + _withDiffusion = withDiffusion; + _lastPreDelay = _preDelay; + _lastSoundMsPerMeter = _soundMsPerMeter; + _lastDistanceAttenuationScalingFactor = _distanceAttenuationScalingFactor; + _lastDiffusionFanout = _diffusionFanout; + _lastAbsorptionRatio = _absorptionRatio; + _lastDiffusionRatio = _diffusionRatio; + _lastDontDistanceAttenuate = dontDistanceAttenuate; + _lastAlternateDistanceAttenuate = alternateDistanceAttenuate; + } + + return attributesChange; +} + +void AudioReflector::render() { + + // if we're not set up yet, or we're not processing spatial audio, then exit early + if (!_myAvatar || !_audio->getProcessSpatialAudio()) { + return; + } + + // use this oportunity to calculate our reflections + calculateAllReflections(); + + // only render if we've been asked to do so + if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingRenderPaths)) { + drawRays(); + } +} + +// delay = 1ms per foot +// = 3ms per meter +float AudioReflector::getDelayFromDistance(float distance) { + float delay = (_soundMsPerMeter * distance); + if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingPreDelay)) { + delay += _preDelay; + } + return delay; +} + +// attenuation = from the Audio Mixer +float AudioReflector::getDistanceAttenuationCoefficient(float distance) { + + + bool doDistanceAttenuation = !Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingDontDistanceAttenuate); + + bool originalFormula = !Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate); + + + float distanceCoefficient = 1.0f; + + if (doDistanceAttenuation) { + + if (originalFormula) { + const float DISTANCE_SCALE = 2.5f; + const float GEOMETRIC_AMPLITUDE_SCALAR = 0.3f; + const float DISTANCE_LOG_BASE = 2.5f; + const float DISTANCE_SCALE_LOG = logf(DISTANCE_SCALE) / logf(DISTANCE_LOG_BASE); + + float distanceSquareToSource = distance * distance; + + // calculate the distance coefficient using the distance to this node + distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR, + DISTANCE_SCALE_LOG + + (0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1); + distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor()); + } else { + + // From Fred: If we wanted something that would produce a tail that could go up to 5 seconds in a + // really big room, that would suggest the sound still has to be in the audible after traveling about + // 1500 meters. If it’s a sound of average volume, we probably have about 30 db, or 5 base2 orders + // of magnitude we can drop down before the sound becomes inaudible. (That’s approximate headroom + // based on a few sloppy assumptions.) So we could try a factor like 1 / (2^(D/300)) for starters. + // 1 / (2^(D/300)) + const float DISTANCE_BASE = 2.0f; + const float DISTANCE_DENOMINATOR = 300.0f; + const float DISTANCE_NUMERATOR = 300.0f; + distanceCoefficient = DISTANCE_NUMERATOR / powf(DISTANCE_BASE, (distance / DISTANCE_DENOMINATOR )); + distanceCoefficient = std::min(1.0f, distanceCoefficient * getDistanceAttenuationScalingFactor()); + } + } + + return distanceCoefficient; +} + +glm::vec3 AudioReflector::getFaceNormal(BoxFace face) { + bool wantSlightRandomness = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingSlightlyRandomSurfaces); + glm::vec3 faceNormal; + const float MIN_RANDOM_LENGTH = 0.99f; + const float MAX_RANDOM_LENGTH = 1.0f; + const float NON_RANDOM_LENGTH = 1.0f; + float normalLength = wantSlightRandomness ? randFloatInRange(MIN_RANDOM_LENGTH, MAX_RANDOM_LENGTH) : NON_RANDOM_LENGTH; + float remainder = (1.0f - normalLength)/2.0f; + float remainderSignA = randomSign(); + float remainderSignB = randomSign(); + + if (face == MIN_X_FACE) { + faceNormal = glm::vec3(-normalLength, remainder * remainderSignA, remainder * remainderSignB); + } else if (face == MAX_X_FACE) { + faceNormal = glm::vec3(normalLength, remainder * remainderSignA, remainder * remainderSignB); + } else if (face == MIN_Y_FACE) { + faceNormal = glm::vec3(remainder * remainderSignA, -normalLength, remainder * remainderSignB); + } else if (face == MAX_Y_FACE) { + faceNormal = glm::vec3(remainder * remainderSignA, normalLength, remainder * remainderSignB); + } else if (face == MIN_Z_FACE) { + faceNormal = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, -normalLength); + } else if (face == MAX_Z_FACE) { + faceNormal = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, normalLength); + } + return faceNormal; +} + +// set up our buffers for our attenuated and delayed samples +const int NUMBER_OF_CHANNELS = 2; + +void AudioReflector::injectAudiblePoint(AudioSource source, const AudiblePoint& audiblePoint, + const QByteArray& samples, unsigned int sampleTime, int sampleRate) { + + bool wantEarSeparation = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingSeparateEars); + bool wantStereo = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingStereoSource); + glm::vec3 rightEarPosition = wantEarSeparation ? _myAvatar->getHead()->getRightEarPosition() : + _myAvatar->getHead()->getPosition(); + glm::vec3 leftEarPosition = wantEarSeparation ? _myAvatar->getHead()->getLeftEarPosition() : + _myAvatar->getHead()->getPosition(); + + int totalNumberOfSamples = samples.size() / sizeof(int16_t); + int totalNumberOfStereoSamples = samples.size() / (sizeof(int16_t) * NUMBER_OF_CHANNELS); + + const int16_t* originalSamplesData = (const int16_t*)samples.constData(); + QByteArray attenuatedLeftSamples; + QByteArray attenuatedRightSamples; + attenuatedLeftSamples.resize(samples.size()); + attenuatedRightSamples.resize(samples.size()); + + int16_t* attenuatedLeftSamplesData = (int16_t*)attenuatedLeftSamples.data(); + int16_t* attenuatedRightSamplesData = (int16_t*)attenuatedRightSamples.data(); + + // calculate the distance to the ears + float rightEarDistance = glm::distance(audiblePoint.location, rightEarPosition); + float leftEarDistance = glm::distance(audiblePoint.location, leftEarPosition); + + float rightEarDelayMsecs = getDelayFromDistance(rightEarDistance) + audiblePoint.delay; + float leftEarDelayMsecs = getDelayFromDistance(leftEarDistance) + audiblePoint.delay; + float averageEarDelayMsecs = (leftEarDelayMsecs + rightEarDelayMsecs) / 2.0f; + + bool safeToInject = true; // assume the best + + // check to see if this new injection point would be within the comb filter + // suppression window for any of the existing known delays + QMap& knownDelays = (source == INBOUND_AUDIO) ? _inboundAudioDelays : _localAudioDelays; + QMap::const_iterator lowerBound = knownDelays.lowerBound(averageEarDelayMsecs - _combFilterWindow); + if (lowerBound != knownDelays.end()) { + float closestFound = lowerBound.value(); + float deltaToClosest = (averageEarDelayMsecs - closestFound); + if (deltaToClosest > -_combFilterWindow && deltaToClosest < _combFilterWindow) { + safeToInject = false; + } + } + + // keep track of any of our suppressed echoes so we can report them in our statistics + if (!safeToInject) { + QVector& suppressedEchoes = (source == INBOUND_AUDIO) ? _inboundEchoesSuppressed : _localEchoesSuppressed; + suppressedEchoes << averageEarDelayMsecs; + } else { + knownDelays[averageEarDelayMsecs] = averageEarDelayMsecs; + + _totalDelay += rightEarDelayMsecs + leftEarDelayMsecs; + _delayCount += 2; + _maxDelay = std::max(_maxDelay,rightEarDelayMsecs); + _maxDelay = std::max(_maxDelay,leftEarDelayMsecs); + _minDelay = std::min(_minDelay,rightEarDelayMsecs); + _minDelay = std::min(_minDelay,leftEarDelayMsecs); + + int rightEarDelay = rightEarDelayMsecs * sampleRate / MSECS_PER_SECOND; + int leftEarDelay = leftEarDelayMsecs * sampleRate / MSECS_PER_SECOND; + + float rightEarAttenuation = audiblePoint.attenuation * + getDistanceAttenuationCoefficient(rightEarDistance + audiblePoint.distance); + + float leftEarAttenuation = audiblePoint.attenuation * + getDistanceAttenuationCoefficient(leftEarDistance + audiblePoint.distance); + + _totalAttenuation += rightEarAttenuation + leftEarAttenuation; + _attenuationCount += 2; + _maxAttenuation = std::max(_maxAttenuation,rightEarAttenuation); + _maxAttenuation = std::max(_maxAttenuation,leftEarAttenuation); + _minAttenuation = std::min(_minAttenuation,rightEarAttenuation); + _minAttenuation = std::min(_minAttenuation,leftEarAttenuation); + + // run through the samples, and attenuate them + for (int sample = 0; sample < totalNumberOfStereoSamples; sample++) { + int16_t leftSample = originalSamplesData[sample * NUMBER_OF_CHANNELS]; + int16_t rightSample = leftSample; + if (wantStereo) { + rightSample = originalSamplesData[(sample * NUMBER_OF_CHANNELS) + 1]; + } + + attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS] = + leftSample * leftEarAttenuation * _allEchoesAttenuation; + attenuatedLeftSamplesData[sample * NUMBER_OF_CHANNELS + 1] = 0; + + attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS] = 0; + attenuatedRightSamplesData[sample * NUMBER_OF_CHANNELS + 1] = + rightSample * rightEarAttenuation * _allEchoesAttenuation; + } + + // now inject the attenuated array with the appropriate delay + unsigned int sampleTimeLeft = sampleTime + leftEarDelay; + unsigned int sampleTimeRight = sampleTime + rightEarDelay; + + _audio->addSpatialAudioToBuffer(sampleTimeLeft, attenuatedLeftSamples, totalNumberOfSamples); + _audio->addSpatialAudioToBuffer(sampleTimeRight, attenuatedRightSamples, totalNumberOfSamples); + + _injectedEchoes++; + } +} + + +void AudioReflector::preProcessOriginalInboundAudio(unsigned int sampleTime, + QByteArray& samples, const QAudioFormat& format) { + + if (_originalSourceAttenuation != 1.0f) { + int numberOfSamples = (samples.size() / sizeof(int16_t)); + int16_t* sampleData = (int16_t*)samples.data(); + for (int i = 0; i < numberOfSamples; i++) { + sampleData[i] = sampleData[i] * _originalSourceAttenuation; + } + } + +} + +void AudioReflector::processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) { + if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) { + const int NUM_CHANNELS_INPUT = 1; + const int NUM_CHANNELS_OUTPUT = 2; + const int EXPECTED_SAMPLE_RATE = 24000; + if (format.channelCount() == NUM_CHANNELS_INPUT && format.sampleRate() == EXPECTED_SAMPLE_RATE) { + QAudioFormat outputFormat = format; + outputFormat.setChannelCount(NUM_CHANNELS_OUTPUT); + QByteArray stereoInputData(samples.size() * NUM_CHANNELS_OUTPUT, 0); + int numberOfSamples = (samples.size() / sizeof(int16_t)); + int16_t* monoSamples = (int16_t*)samples.data(); + int16_t* stereoSamples = (int16_t*)stereoInputData.data(); + + for (int i = 0; i < numberOfSamples; i++) { + stereoSamples[i* NUM_CHANNELS_OUTPUT] = monoSamples[i] * _localAudioAttenuationFactor; + stereoSamples[(i * NUM_CHANNELS_OUTPUT) + 1] = monoSamples[i] * _localAudioAttenuationFactor; + } + _localAudioDelays.clear(); + _localEchoesSuppressed.clear(); + echoAudio(LOCAL_AUDIO, sampleTime, stereoInputData, outputFormat); + _localEchoesCount = _localAudioDelays.size(); + _localEchoesSuppressedCount = _localEchoesSuppressed.size(); + } + } +} + +void AudioReflector::processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) { + _inboundAudioDelays.clear(); + _inboundEchoesSuppressed.clear(); + echoAudio(INBOUND_AUDIO, sampleTime, samples, format); + _inboundEchoesCount = _inboundAudioDelays.size(); + _inboundEchoesSuppressedCount = _inboundEchoesSuppressed.size(); +} + +void AudioReflector::echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format) { + QMutexLocker locker(&_mutex); + + _maxDelay = 0; + _maxAttenuation = 0.0f; + _minDelay = std::numeric_limits::max(); + _minAttenuation = std::numeric_limits::max(); + _totalDelay = 0.0f; + _delayCount = 0; + _totalAttenuation = 0.0f; + _attenuationCount = 0; + + // depending on if we're processing local or external audio, pick the correct points vector + QVector& audiblePoints = source == INBOUND_AUDIO ? _inboundAudiblePoints : _localAudiblePoints; + + int injectCalls = 0; + _injectedEchoes = 0; + foreach(const AudiblePoint& audiblePoint, audiblePoints) { + injectCalls++; + injectAudiblePoint(source, audiblePoint, samples, sampleTime, format.sampleRate()); + } + + /* + qDebug() << "injectCalls=" << injectCalls; + qDebug() << "_injectedEchoes=" << _injectedEchoes; + */ + + _averageDelay = _delayCount == 0 ? 0 : _totalDelay / _delayCount; + _averageAttenuation = _attenuationCount == 0 ? 0 : _totalAttenuation / _attenuationCount; + + if (_reflections == 0) { + _minDelay = 0.0f; + _minAttenuation = 0.0f; + } + + _officialMaxDelay = _maxDelay; + _officialMinDelay = _minDelay; + _officialMaxAttenuation = _maxAttenuation; + _officialMinAttenuation = _minAttenuation; + _officialAverageDelay = _averageDelay; + _officialAverageAttenuation = _averageAttenuation; + +} + +void AudioReflector::drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color) { + glDisable(GL_LIGHTING); + glLineWidth(2.0); + + // Draw the vector itself + glBegin(GL_LINES); + glColor3f(color.x,color.y,color.z); + glVertex3f(start.x, start.y, start.z); + glVertex3f(end.x, end.y, end.z); + glEnd(); + + glEnable(GL_LIGHTING); +} + + + +AudioPath::AudioPath(AudioSource source, const glm::vec3& origin, const glm::vec3& direction, + float attenuation, float delay, float distance,bool isDiffusion, int bounceCount) : + + source(source), + isDiffusion(isDiffusion), + startPoint(origin), + startDirection(direction), + startDelay(delay), + startAttenuation(attenuation), + + lastPoint(origin), + lastDirection(direction), + lastDistance(distance), + lastDelay(delay), + lastAttenuation(attenuation), + bounceCount(bounceCount), + + finalized(false), + reflections() +{ +} + +void AudioReflector::addAudioPath(AudioSource source, const glm::vec3& origin, const glm::vec3& initialDirection, + float initialAttenuation, float initialDelay, float initialDistance, bool isDiffusion) { + + AudioPath* path = new AudioPath(source, origin, initialDirection, initialAttenuation, initialDelay, + initialDistance, isDiffusion, 0); + + QVector& audioPaths = source == INBOUND_AUDIO ? _inboundAudioPaths : _localAudioPaths; + + audioPaths.push_back(path); +} + +// NOTE: This is a prototype of an eventual utility that will identify the speaking sources for the inbound audio +// stream. It's not currently called but will be added soon. +void AudioReflector::identifyAudioSources() { + // looking for audio sources.... + foreach (const AvatarSharedPointer& avatarPointer, _avatarManager->getAvatarHash()) { + Avatar* avatar = static_cast(avatarPointer.data()); + if (!avatar->isInitialized()) { + continue; + } + qDebug() << "avatar["<< avatar <<"] loudness:" << avatar->getAudioLoudness(); + } +} + +void AudioReflector::calculateAllReflections() { + // only recalculate when we've moved, or if the attributes have changed + // TODO: what about case where new voxels are added in front of us??? + bool wantHeadOrientation = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingHeadOriented); + glm::quat orientation = wantHeadOrientation ? _myAvatar->getHead()->getFinalOrientation() : _myAvatar->getOrientation(); + glm::vec3 origin = _myAvatar->getHead()->getPosition(); + glm::vec3 listenerPosition = _myAvatar->getHead()->getPosition(); + + bool shouldRecalc = _reflections == 0 + || !isSimilarPosition(origin, _origin) + || !isSimilarOrientation(orientation, _orientation) + || !isSimilarPosition(listenerPosition, _listenerPosition) + || haveAttributesChanged(); + + if (shouldRecalc) { + QMutexLocker locker(&_mutex); + quint64 start = usecTimestampNow(); + _origin = origin; + _orientation = orientation; + _listenerPosition = listenerPosition; + analyzePaths(); // actually does the work + quint64 end = usecTimestampNow(); + const bool wantDebugging = false; + if (wantDebugging) { + qDebug() << "newCalculateAllReflections() elapsed=" << (end - start); + } + } +} + +void AudioReflector::drawRays() { + const glm::vec3 RED(1,0,0); + const glm::vec3 GREEN(0,1,0); + const glm::vec3 BLUE(0,0,1); + const glm::vec3 CYAN(0,1,1); + + int diffusionNumber = 0; + + QMutexLocker locker(&_mutex); + + // draw the paths for inbound audio + foreach(AudioPath* const& path, _inboundAudioPaths) { + // if this is an original reflection, draw it in RED + if (path->isDiffusion) { + diffusionNumber++; + drawPath(path, GREEN); + } else { + drawPath(path, RED); + } + } + + if (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio)) { + // draw the paths for local audio + foreach(AudioPath* const& path, _localAudioPaths) { + // if this is an original reflection, draw it in RED + if (path->isDiffusion) { + diffusionNumber++; + drawPath(path, CYAN); + } else { + drawPath(path, BLUE); + } + } + } +} + +void AudioReflector::drawPath(AudioPath* path, const glm::vec3& originalColor) { + glm::vec3 start = path->startPoint; + glm::vec3 color = originalColor; + const float COLOR_ADJUST_PER_BOUNCE = 0.75f; + + foreach (glm::vec3 end, path->reflections) { + drawVector(start, end, color); + start = end; + color = color * COLOR_ADJUST_PER_BOUNCE; + } +} + +void AudioReflector::clearPaths() { + // clear our inbound audio paths + foreach(AudioPath* const& path, _inboundAudioPaths) { + delete path; + } + _inboundAudioPaths.clear(); + _inboundAudiblePoints.clear(); // clear our inbound audible points + + // clear our local audio paths + foreach(AudioPath* const& path, _localAudioPaths) { + delete path; + } + _localAudioPaths.clear(); + _localAudiblePoints.clear(); // clear our local audible points +} + +// Here's how this works: we have an array of AudioPaths, we loop on all of our currently calculating audio +// paths, and calculate one ray per path. If that ray doesn't reflect, or reaches a max distance/attenuation, then it +// is considered finalized. +// If the ray hits a surface, then, based on the characteristics of that surface, it will calculate the new +// attenuation, path length, and delay for the primary path. For surfaces that have diffusion, it will also create +// fanout number of new paths, those new paths will have an origin of the reflection point, and an initial attenuation +// of their diffusion ratio. Those new paths will be added to the active audio paths, and be analyzed for the next loop. +void AudioReflector::analyzePaths() { + clearPaths(); + + // add our initial paths + glm::vec3 right = glm::normalize(_orientation * IDENTITY_RIGHT); + glm::vec3 up = glm::normalize(_orientation * IDENTITY_UP); + glm::vec3 front = glm::normalize(_orientation * IDENTITY_FRONT); + glm::vec3 left = -right; + glm::vec3 down = -up; + glm::vec3 back = -front; + glm::vec3 frontRightUp = glm::normalize(front + right + up); + glm::vec3 frontLeftUp = glm::normalize(front + left + up); + glm::vec3 backRightUp = glm::normalize(back + right + up); + glm::vec3 backLeftUp = glm::normalize(back + left + up); + glm::vec3 frontRightDown = glm::normalize(front + right + down); + glm::vec3 frontLeftDown = glm::normalize(front + left + down); + glm::vec3 backRightDown = glm::normalize(back + right + down); + glm::vec3 backLeftDown = glm::normalize(back + left + down); + + float initialAttenuation = 1.0f; + + float preDelay = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingPreDelay) ? _preDelay : 0.0f; + + // NOTE: we're still calculating our initial paths based on the listeners position. But the analysis code has been + // updated to support individual sound sources (which is how we support diffusion), we can use this new paradigm to + // add support for individual sound sources, and more directional sound sources + + addAudioPath(INBOUND_AUDIO, _origin, front, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, right, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, up, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, down, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, back, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, left, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, frontRightUp, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, frontLeftUp, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, backRightUp, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, backLeftUp, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, frontRightDown, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, frontLeftDown, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, backRightDown, initialAttenuation, preDelay); + addAudioPath(INBOUND_AUDIO, _origin, backLeftDown, initialAttenuation, preDelay); + + // the original paths for the local audio are directional to the front of the origin + addAudioPath(LOCAL_AUDIO, _origin, front, initialAttenuation, preDelay); + addAudioPath(LOCAL_AUDIO, _origin, frontRightUp, initialAttenuation, preDelay); + addAudioPath(LOCAL_AUDIO, _origin, frontLeftUp, initialAttenuation, preDelay); + addAudioPath(LOCAL_AUDIO, _origin, frontRightDown, initialAttenuation, preDelay); + addAudioPath(LOCAL_AUDIO, _origin, frontLeftDown, initialAttenuation, preDelay); + + // loop through all our audio paths and keep analyzing them until they complete + int steps = 0; + int acitvePaths = _inboundAudioPaths.size() + _localAudioPaths.size(); // when we start, all paths are active + while(acitvePaths > 0) { + acitvePaths = analyzePathsSingleStep(); + steps++; + } + _reflections = _inboundAudiblePoints.size() + _localAudiblePoints.size(); + _diffusionPathCount = countDiffusionPaths(); +} + +int AudioReflector::countDiffusionPaths() { + int diffusionCount = 0; + + foreach(AudioPath* const& path, _inboundAudioPaths) { + if (path->isDiffusion) { + diffusionCount++; + } + } + foreach(AudioPath* const& path, _localAudioPaths) { + if (path->isDiffusion) { + diffusionCount++; + } + } + return diffusionCount; +} + +int AudioReflector::analyzePathsSingleStep() { + // iterate all the active sound paths, calculate one step per active path + int activePaths = 0; + + QVector* pathsLists[] = { &_inboundAudioPaths, &_localAudioPaths }; + + for(unsigned int i = 0; i < sizeof(pathsLists) / sizeof(pathsLists[0]); i++) { + + QVector& pathList = *pathsLists[i]; + + foreach(AudioPath* const& path, pathList) { + + glm::vec3 start = path->lastPoint; + glm::vec3 direction = path->lastDirection; + OctreeElement* elementHit; // output from findRayIntersection + float distance; // output from findRayIntersection + BoxFace face; // output from findRayIntersection + + if (!path->finalized) { + activePaths++; + + if (path->bounceCount > ABSOLUTE_MAXIMUM_BOUNCE_COUNT) { + path->finalized = true; + } else if (_voxels->findRayIntersection(start, direction, elementHit, distance, face)) { + // TODO: we need to decide how we want to handle locking on the ray intersection, if we force lock, + // we get an accurate picture, but it could prevent rendering of the voxels. If we trylock (default), + // we might not get ray intersections where they may exist, but we can't really detect that case... + // add last parameter of Octree::Lock to force locking + handlePathPoint(path, distance, elementHit, face); + + } else { + // If we didn't intersect, but this was a diffusion ray, then we will go ahead and cast a short ray out + // from our last known point, in the last known direction, and leave that sound source hanging there + if (path->isDiffusion) { + const float MINIMUM_RANDOM_DISTANCE = 0.25f; + const float MAXIMUM_RANDOM_DISTANCE = 0.5f; + float distance = randFloatInRange(MINIMUM_RANDOM_DISTANCE, MAXIMUM_RANDOM_DISTANCE); + handlePathPoint(path, distance, NULL, UNKNOWN_FACE); + } else { + path->finalized = true; // if it doesn't intersect, then it is finished + } + } + } + } + } + return activePaths; +} + +void AudioReflector::handlePathPoint(AudioPath* path, float distance, OctreeElement* elementHit, BoxFace face) { + glm::vec3 start = path->lastPoint; + glm::vec3 direction = path->lastDirection; + glm::vec3 end = start + (direction * (distance * SLIGHTLY_SHORT)); + + float currentReflectiveAttenuation = path->lastAttenuation; // only the reflective components + float currentDelay = path->lastDelay; // start with our delay so far + float pathDistance = path->lastDistance; + + pathDistance += glm::distance(start, end); + + float toListenerDistance = glm::distance(end, _listenerPosition); + + // adjust our current delay by just the delay from the most recent ray + currentDelay += getDelayFromDistance(distance); + + // now we know the current attenuation for the "perfect" reflection case, but we now incorporate + // our surface materials to determine how much of this ray is absorbed, reflected, and diffused + SurfaceCharacteristics material = getSurfaceCharacteristics(elementHit); + + float reflectiveAttenuation = currentReflectiveAttenuation * material.reflectiveRatio; + float totalDiffusionAttenuation = currentReflectiveAttenuation * material.diffusionRatio; + + bool wantDiffusions = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions); + int fanout = wantDiffusions ? _diffusionFanout : 0; + + float partialDiffusionAttenuation = fanout < 1 ? 0.0f : totalDiffusionAttenuation / (float)fanout; + + // total delay includes the bounce back to listener + float totalDelay = currentDelay + getDelayFromDistance(toListenerDistance); + float toListenerAttenuation = getDistanceAttenuationCoefficient(toListenerDistance + pathDistance); + + // if our resulting partial diffusion attenuation, is still above our minimum attenuation + // then we add new paths for each diffusion point + if ((partialDiffusionAttenuation * toListenerAttenuation) > MINIMUM_ATTENUATION_TO_REFLECT + && totalDelay < MAXIMUM_DELAY_MS) { + + // diffusions fan out from random places on the semisphere of the collision point + for(int i = 0; i < fanout; i++) { + glm::vec3 diffusion; + + // We're creating a random normal here. But we want it to be relatively dramatic compared to how we handle + // our slightly random surface normals. + const float MINIMUM_RANDOM_LENGTH = 0.5f; + const float MAXIMUM_RANDOM_LENGTH = 1.0f; + float randomness = randFloatInRange(MINIMUM_RANDOM_LENGTH, MAXIMUM_RANDOM_LENGTH); + float remainder = (1.0f - randomness)/2.0f; + float remainderSignA = randomSign(); + float remainderSignB = randomSign(); + + if (face == MIN_X_FACE) { + diffusion = glm::vec3(-randomness, remainder * remainderSignA, remainder * remainderSignB); + } else if (face == MAX_X_FACE) { + diffusion = glm::vec3(randomness, remainder * remainderSignA, remainder * remainderSignB); + } else if (face == MIN_Y_FACE) { + diffusion = glm::vec3(remainder * remainderSignA, -randomness, remainder * remainderSignB); + } else if (face == MAX_Y_FACE) { + diffusion = glm::vec3(remainder * remainderSignA, randomness, remainder * remainderSignB); + } else if (face == MIN_Z_FACE) { + diffusion = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, -randomness); + } else if (face == MAX_Z_FACE) { + diffusion = glm::vec3(remainder * remainderSignA, remainder * remainderSignB, randomness); + } else if (face == UNKNOWN_FACE) { + float randomnessX = randFloatInRange(MINIMUM_RANDOM_LENGTH, MAXIMUM_RANDOM_LENGTH); + float randomnessY = randFloatInRange(MINIMUM_RANDOM_LENGTH, MAXIMUM_RANDOM_LENGTH); + float randomnessZ = randFloatInRange(MINIMUM_RANDOM_LENGTH, MAXIMUM_RANDOM_LENGTH); + diffusion = glm::vec3(direction.x * randomnessX, direction.y * randomnessY, direction.z * randomnessZ); + } + + diffusion = glm::normalize(diffusion); + + // add new audio path for these diffusions, the new path's source is the same as the original source + addAudioPath(path->source, end, diffusion, partialDiffusionAttenuation, currentDelay, pathDistance, true); + } + } else { + const bool wantDebugging = false; + if (wantDebugging) { + if ((partialDiffusionAttenuation * toListenerAttenuation) <= MINIMUM_ATTENUATION_TO_REFLECT) { + qDebug() << "too quiet to diffuse"; + qDebug() << " partialDiffusionAttenuation=" << partialDiffusionAttenuation; + qDebug() << " toListenerAttenuation=" << toListenerAttenuation; + qDebug() << " result=" << (partialDiffusionAttenuation * toListenerAttenuation); + qDebug() << " MINIMUM_ATTENUATION_TO_REFLECT=" << MINIMUM_ATTENUATION_TO_REFLECT; + } + if (totalDelay > MAXIMUM_DELAY_MS) { + qDebug() << "too delayed to diffuse"; + qDebug() << " totalDelay=" << totalDelay; + qDebug() << " MAXIMUM_DELAY_MS=" << MAXIMUM_DELAY_MS; + } + } + } + + // if our reflective attenuation is above our minimum, then add our reflection point and + // allow our path to continue + if (((reflectiveAttenuation + totalDiffusionAttenuation) * toListenerAttenuation) > MINIMUM_ATTENUATION_TO_REFLECT + && totalDelay < MAXIMUM_DELAY_MS) { + + // add this location, as the reflective attenuation as well as the total diffusion attenuation + // NOTE: we add the delay to the audible point, not back to the listener. The additional delay + // and attenuation to the listener is recalculated at the point where we actually inject the + // audio so that it can be adjusted to ear position + AudiblePoint point = {end, currentDelay, (reflectiveAttenuation + totalDiffusionAttenuation), pathDistance}; + + QVector& audiblePoints = path->source == INBOUND_AUDIO ? _inboundAudiblePoints : _localAudiblePoints; + + audiblePoints.push_back(point); + + // add this location to the path points, so we can visualize it + path->reflections.push_back(end); + + // now, if our reflective attenuation is over our minimum then keep going... + if (reflectiveAttenuation * toListenerAttenuation > MINIMUM_ATTENUATION_TO_REFLECT) { + glm::vec3 faceNormal = getFaceNormal(face); + path->lastDirection = glm::normalize(glm::reflect(direction,faceNormal)); + path->lastPoint = end; + path->lastAttenuation = reflectiveAttenuation; + path->lastDelay = currentDelay; + path->lastDistance = pathDistance; + path->bounceCount++; + } else { + path->finalized = true; // if we're too quiet, then we're done + } + } else { + const bool wantDebugging = false; + if (wantDebugging) { + if (((reflectiveAttenuation + totalDiffusionAttenuation) * toListenerAttenuation) <= MINIMUM_ATTENUATION_TO_REFLECT) { + qDebug() << "too quiet to add audible point"; + qDebug() << " reflectiveAttenuation + totalDiffusionAttenuation=" << (reflectiveAttenuation + totalDiffusionAttenuation); + qDebug() << " toListenerAttenuation=" << toListenerAttenuation; + qDebug() << " result=" << ((reflectiveAttenuation + totalDiffusionAttenuation) * toListenerAttenuation); + qDebug() << " MINIMUM_ATTENUATION_TO_REFLECT=" << MINIMUM_ATTENUATION_TO_REFLECT; + } + if (totalDelay > MAXIMUM_DELAY_MS) { + qDebug() << "too delayed to add audible point"; + qDebug() << " totalDelay=" << totalDelay; + qDebug() << " MAXIMUM_DELAY_MS=" << MAXIMUM_DELAY_MS; + } + } + path->finalized = true; // if we're too quiet, then we're done + } +} + +// TODO: eventually we will add support for different surface characteristics based on the element +// that is hit, which is why we pass in the elementHit to this helper function. But for now, all +// surfaces have the same characteristics +SurfaceCharacteristics AudioReflector::getSurfaceCharacteristics(OctreeElement* elementHit) { + SurfaceCharacteristics result = { getReflectiveRatio(), _absorptionRatio, _diffusionRatio }; + return result; +} + +void AudioReflector::setReflectiveRatio(float ratio) { + float safeRatio = std::max(0.0f, std::min(ratio, 1.0f)); + float currentReflectiveRatio = (1.0f - (_absorptionRatio + _diffusionRatio)); + float halfDifference = (safeRatio - currentReflectiveRatio) / 2.0f; + + // evenly distribute the difference between the two other ratios + _absorptionRatio -= halfDifference; + _diffusionRatio -= halfDifference; +} + +void AudioReflector::setAbsorptionRatio(float ratio) { + float safeRatio = std::max(0.0f, std::min(ratio, 1.0f)); + _absorptionRatio = safeRatio; + const float MAX_COMBINED_RATIO = 1.0f; + if (_absorptionRatio + _diffusionRatio > MAX_COMBINED_RATIO) { + _diffusionRatio = MAX_COMBINED_RATIO - _absorptionRatio; + } +} + +void AudioReflector::setDiffusionRatio(float ratio) { + float safeRatio = std::max(0.0f, std::min(ratio, 1.0f)); + _diffusionRatio = safeRatio; + const float MAX_COMBINED_RATIO = 1.0f; + if (_absorptionRatio + _diffusionRatio > MAX_COMBINED_RATIO) { + _absorptionRatio = MAX_COMBINED_RATIO - _diffusionRatio; + } +} + diff --git a/interface/src/AudioReflector.h b/interface/src/AudioReflector.h new file mode 100644 index 0000000000..1bfb52ea09 --- /dev/null +++ b/interface/src/AudioReflector.h @@ -0,0 +1,254 @@ +// +// AudioReflector.h +// interface +// +// Created by Brad Hefta-Gaub on 4/2/2014 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#ifndef interface_AudioReflector_h +#define interface_AudioReflector_h + +#include + +#include + +#include "Audio.h" +#include "avatar/MyAvatar.h" +#include "avatar/AvatarManager.h" + +enum AudioSource { + LOCAL_AUDIO, + INBOUND_AUDIO +}; + +class AudioPath { +public: + AudioPath(AudioSource source = INBOUND_AUDIO, const glm::vec3& origin = glm::vec3(0.0f), + const glm::vec3& direction = glm::vec3(0.0f), float attenuation = 1.0f, + float delay = 0.0f, float distance = 0.0f, bool isDiffusion = false, int bounceCount = 0); + + AudioSource source; + bool isDiffusion; + glm::vec3 startPoint; + glm::vec3 startDirection; + float startDelay; + float startAttenuation; + + glm::vec3 lastPoint; + glm::vec3 lastDirection; + float lastDistance; + float lastDelay; + float lastAttenuation; + unsigned int bounceCount; + + bool finalized; + QVector reflections; +}; + +class AudiblePoint { +public: + glm::vec3 location; /// location of the audible point + float delay; /// includes total delay including pre delay to the point of the audible location, not to the listener's ears + float attenuation; /// only the reflective & diffusive portion of attenuation, doesn't include distance attenuation + float distance; /// includes total distance to the point of the audible location, not to the listener's ears +}; + +class SurfaceCharacteristics { +public: + float reflectiveRatio; + float absorptionRatio; + float diffusionRatio; +}; + +class AudioReflector : public QObject { + Q_OBJECT +public: + AudioReflector(QObject* parent = NULL); + + // setup functions to configure the resources used by the AudioReflector + void setVoxels(VoxelTree* voxels) { _voxels = voxels; } + void setMyAvatar(MyAvatar* myAvatar) { _myAvatar = myAvatar; } + void setAudio(Audio* audio) { _audio = audio; } + void setAvatarManager(AvatarManager* avatarManager) { _avatarManager = avatarManager; } + + void render(); /// must be called in the application render loop + + void preProcessOriginalInboundAudio(unsigned int sampleTime, QByteArray& samples, const QAudioFormat& format); + void processInboundAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); + void processLocalAudio(unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); + +public slots: + // statistics + int getReflections() const { return _reflections; } + float getAverageDelayMsecs() const { return _officialAverageDelay; } + float getAverageAttenuation() const { return _officialAverageAttenuation; } + float getMaxDelayMsecs() const { return _officialMaxDelay; } + float getMaxAttenuation() const { return _officialMaxAttenuation; } + float getMinDelayMsecs() const { return _officialMinDelay; } + float getMinAttenuation() const { return _officialMinAttenuation; } + float getDelayFromDistance(float distance); + int getDiffusionPathCount() const { return _diffusionPathCount; } + int getEchoesInjected() const { return _inboundEchoesCount + _localEchoesCount; } + int getEchoesSuppressed() const { return _inboundEchoesSuppressedCount + _localEchoesSuppressedCount; } + + /// ms of delay added to all echos + float getPreDelay() const { return _preDelay; } + void setPreDelay(float preDelay) { _preDelay = preDelay; } + + /// ms per meter that sound travels, larger means slower, which sounds bigger + float getSoundMsPerMeter() const { return _soundMsPerMeter; } + void setSoundMsPerMeter(float soundMsPerMeter) { _soundMsPerMeter = soundMsPerMeter; } + + /// scales attenuation to be louder or softer than the default distance attenuation + float getDistanceAttenuationScalingFactor() const { return _distanceAttenuationScalingFactor; } + void setDistanceAttenuationScalingFactor(float factor) { _distanceAttenuationScalingFactor = factor; } + + /// scales attenuation of local audio to be louder or softer than the default attenuation + float getLocalAudioAttenuationFactor() const { return _localAudioAttenuationFactor; } + void setLocalAudioAttenuationFactor(float factor) { _localAudioAttenuationFactor = factor; } + + /// ms window in which we will suppress echoes to reduce comb filter effects + float getCombFilterWindow() const { return _combFilterWindow; } + void setCombFilterWindow(float value) { _combFilterWindow = value; } + + /// number of points of diffusion from each reflection point, as fanout increases there are more chances for secondary + /// echoes, but each diffusion ray is quieter and therefore more likely to be below the sound floor + int getDiffusionFanout() const { return _diffusionFanout; } + void setDiffusionFanout(int fanout) { _diffusionFanout = fanout; } + + /// ratio 0.0 - 1.0 of amount of each ray that is absorbed upon hitting a surface + float getAbsorptionRatio() const { return _absorptionRatio; } + void setAbsorptionRatio(float ratio); + + // ratio 0.0 - 1.0 of amount of each ray that is diffused upon hitting a surface + float getDiffusionRatio() const { return _diffusionRatio; } + void setDiffusionRatio(float ratio); + + // remaining ratio 0.0 - 1.0 of amount of each ray that is cleanly reflected upon hitting a surface + float getReflectiveRatio() const { return (1.0f - (_absorptionRatio + _diffusionRatio)); } + void setReflectiveRatio(float ratio); + + // wet/dry mix - these don't affect any reflection calculations, only the final mix volumes + float getOriginalSourceAttenuation() const { return _originalSourceAttenuation; } + void setOriginalSourceAttenuation(float value) { _originalSourceAttenuation = value; } + float getEchoesAttenuation() const { return _allEchoesAttenuation; } + void setEchoesAttenuation(float value) { _allEchoesAttenuation = value; } + +signals: + +private: + VoxelTree* _voxels; // used to access voxel scene + MyAvatar* _myAvatar; // access to listener + Audio* _audio; // access to audio API + AvatarManager* _avatarManager; // access to avatar manager API + + // Helpers for drawing + void drawVector(const glm::vec3& start, const glm::vec3& end, const glm::vec3& color); + + // helper for generically calculating attenuation based on distance + float getDistanceAttenuationCoefficient(float distance); + + // statistics + int _reflections; + int _diffusionPathCount; + int _delayCount; + float _totalDelay; + float _averageDelay; + float _maxDelay; + float _minDelay; + float _officialAverageDelay; + float _officialMaxDelay; + float _officialMinDelay; + int _attenuationCount; + float _totalAttenuation; + float _averageAttenuation; + float _maxAttenuation; + float _minAttenuation; + float _officialAverageAttenuation; + float _officialMaxAttenuation; + float _officialMinAttenuation; + + + glm::vec3 _listenerPosition; + glm::vec3 _origin; + glm::quat _orientation; + + QVector _inboundAudioPaths; /// audio paths we're processing for inbound audio + QVector _inboundAudiblePoints; /// the audible points that have been calculated from the inbound audio paths + QMap _inboundAudioDelays; /// delay times for currently injected audio points + QVector _inboundEchoesSuppressed; /// delay times for currently injected audio points + int _inboundEchoesCount; + int _inboundEchoesSuppressedCount; + + QVector _localAudioPaths; /// audio paths we're processing for local audio + QVector _localAudiblePoints; /// the audible points that have been calculated from the local audio paths + QMap _localAudioDelays; /// delay times for currently injected audio points + QVector _localEchoesSuppressed; /// delay times for currently injected audio points + int _localEchoesCount; + int _localEchoesSuppressedCount; + + // adds a sound source to begin an audio path trace, these can be the initial sound sources with their directional properties, + // as well as diffusion sound sources + void addAudioPath(AudioSource source, const glm::vec3& origin, const glm::vec3& initialDirection, float initialAttenuation, + float initialDelay, float initialDistance = 0.0f, bool isDiffusion = false); + + // helper that handles audioPath analysis + int analyzePathsSingleStep(); + void handlePathPoint(AudioPath* path, float distance, OctreeElement* elementHit, BoxFace face); + void clearPaths(); + void analyzePaths(); + void drawRays(); + void drawPath(AudioPath* path, const glm::vec3& originalColor); + void calculateAllReflections(); + int countDiffusionPaths(); + glm::vec3 getFaceNormal(BoxFace face); + void identifyAudioSources(); + + void injectAudiblePoint(AudioSource source, const AudiblePoint& audiblePoint, const QByteArray& samples, unsigned int sampleTime, int sampleRate); + void echoAudio(AudioSource source, unsigned int sampleTime, const QByteArray& samples, const QAudioFormat& format); + + // return the surface characteristics of the element we hit + SurfaceCharacteristics getSurfaceCharacteristics(OctreeElement* elementHit = NULL); + + + QMutex _mutex; + + float _preDelay; + float _soundMsPerMeter; + float _distanceAttenuationScalingFactor; + float _localAudioAttenuationFactor; + float _combFilterWindow; + int _diffusionFanout; // number of points of diffusion from each reflection point + + // all elements have the same material for now... + float _absorptionRatio; + float _diffusionRatio; + float _reflectiveRatio; + + // wet/dry mix - these don't affect any reflection calculations, only the final mix volumes + float _originalSourceAttenuation; /// each sample of original signal will be multiplied by this + float _allEchoesAttenuation; /// each sample of all echo signals will be multiplied by this + + // remember the last known values at calculation + bool haveAttributesChanged(); + + bool _withDiffusion; + float _lastPreDelay; + float _lastSoundMsPerMeter; + float _lastDistanceAttenuationScalingFactor; + float _lastLocalAudioAttenuationFactor; + int _lastDiffusionFanout; + float _lastAbsorptionRatio; + float _lastDiffusionRatio; + bool _lastDontDistanceAttenuate; + bool _lastAlternateDistanceAttenuate; + + int _injectedEchoes; +}; + + +#endif // interface_AudioReflector_h diff --git a/interface/src/BuckyBalls.cpp b/interface/src/BuckyBalls.cpp index 8e489db74b..e1ec41dca1 100644 --- a/interface/src/BuckyBalls.cpp +++ b/interface/src/BuckyBalls.cpp @@ -1,9 +1,12 @@ // // BuckyBalls.cpp -// hifi +// interface/src // // Created by Philip on 1/2/14. +// Copyright 2013 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 "BuckyBalls.h" diff --git a/interface/src/BuckyBalls.h b/interface/src/BuckyBalls.h index 705f85517f..f7690408d3 100644 --- a/interface/src/BuckyBalls.h +++ b/interface/src/BuckyBalls.h @@ -1,13 +1,16 @@ // // BuckyBalls.h -// hifi +// interface/src // // Created by Philip on 1/2/14. +// Copyright 2013 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__BuckyBalls__ -#define __hifi__BuckyBalls__ +#ifndef hifi_BuckyBalls_h +#define hifi_BuckyBalls_h #include @@ -44,4 +47,4 @@ private: }; -#endif /* defined(__hifi__BuckyBalls__) */ +#endif // hifi_BuckyBalls_h diff --git a/interface/src/Camera.cpp b/interface/src/Camera.cpp index 252b3be685..f9ee5bdd25 100644 --- a/interface/src/Camera.cpp +++ b/interface/src/Camera.cpp @@ -1,8 +1,12 @@ // // Camera.cpp -// interface +// interface/src +// +// Copyright 2013 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 // -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #include @@ -41,7 +45,7 @@ Camera::Camera() : _idealPosition(0.0f, 0.0f, 0.0f), _targetPosition(0.0f, 0.0f, 0.0f), _fieldOfView(DEFAULT_FIELD_OF_VIEW_DEGREES), - _aspectRatio(16.f/9.f), + _aspectRatio(16.0f/9.0f), _nearClip(0.08f), // default _farClip(50.0f * TREE_SCALE), // default _upShift(0.0f), @@ -90,8 +94,8 @@ void Camera::updateFollowMode(float deltaTime) { // derive t from tightness float t = _tightness * _modeShift * deltaTime; - if (t > 1.0) { - t = 1.0; + if (t > 1.0f) { + t = 1.0f; } // handle keepLookingAt diff --git a/interface/src/Camera.h b/interface/src/Camera.h index 9973fd246e..5e189c1111 100644 --- a/interface/src/Camera.h +++ b/interface/src/Camera.h @@ -1,12 +1,15 @@ // // Camera.h -// interface +// interface/src // -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__camera__ -#define __interface__camera__ +#ifndef hifi_Camera_h +#define hifi_Camera_h #include #include @@ -54,6 +57,7 @@ public: const glm::quat& getRotation() const { return _rotation; } CameraMode getMode() const { return _mode; } float getModeShiftPeriod() const { return _modeShiftPeriod; } + float getDistance() const { return _distance; } const glm::vec3& getTargetPosition() const { return _targetPosition; } const glm::quat& getTargetRotation() const { return _targetRotation; } float getFieldOfView() const { return _fieldOfView; } @@ -150,4 +154,4 @@ private: Camera* _camera; ViewFrustum* _viewFrustum; }; -#endif +#endif // hifi_Camera_h diff --git a/interface/src/DatagramProcessor.cpp b/interface/src/DatagramProcessor.cpp index 50e84db7b5..6525e79cf2 100644 --- a/interface/src/DatagramProcessor.cpp +++ b/interface/src/DatagramProcessor.cpp @@ -1,9 +1,12 @@ // // DatagramProcessor.cpp -// hifi +// interface/src // // Created by Stephen Birarda on 1/23/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 @@ -75,7 +78,7 @@ void DatagramProcessor::processDatagrams() { OCTREE_PACKET_SENT_TIME arrivedAt = usecTimestampNow(); int flightTime = arrivedAt - sentAt; - printf("got PacketType_VOXEL_DATA, sequence:%d flightTime:%d\n", sequence, flightTime); + qDebug("got PacketType_VOXEL_DATA, sequence:%d flightTime:%d", sequence, flightTime); } SharedNodePointer matchedNode = NodeList::getInstance()->sendingNodeForPacket(incomingPacket); diff --git a/interface/src/DatagramProcessor.h b/interface/src/DatagramProcessor.h index 722e5a9d41..7d337ec02b 100644 --- a/interface/src/DatagramProcessor.h +++ b/interface/src/DatagramProcessor.h @@ -1,13 +1,16 @@ // // DatagramProcessor.h -// hifi +// interface/src // // Created by Stephen Birarda on 1/23/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__DatagramProcessor__ -#define __hifi__DatagramProcessor__ +#ifndef hifi_DatagramProcessor_h +#define hifi_DatagramProcessor_h #include @@ -28,4 +31,4 @@ private: int _byteCount; }; -#endif /* defined(__hifi__DatagramProcessor__) */ +#endif // hifi_DatagramProcessor_h diff --git a/interface/src/Environment.cpp b/interface/src/Environment.cpp index 096b8770fb..1ba2f0501c 100644 --- a/interface/src/Environment.cpp +++ b/interface/src/Environment.cpp @@ -1,9 +1,13 @@ // // Environment.cpp -// interface +// interface/src // // Created by Andrzej Kapolka on 5/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 "InterfaceConfig.h" diff --git a/interface/src/Environment.h b/interface/src/Environment.h index 1c9ee578c3..525f3b7eb4 100644 --- a/interface/src/Environment.h +++ b/interface/src/Environment.h @@ -1,13 +1,16 @@ // // Environment.h -// interface +// interface/src // // Created by Andrzej Kapolka on 5/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__Environment__ -#define __interface__Environment__ +#ifndef hifi_Environment_h +#define hifi_Environment_h #include #include @@ -75,4 +78,4 @@ private: QMutex _mutex; }; -#endif /* defined(__interface__Environment__) */ +#endif // hifi_Environment_h diff --git a/interface/src/FileLogger.cpp b/interface/src/FileLogger.cpp index 81c626a46e..c4e75b21b2 100644 --- a/interface/src/FileLogger.cpp +++ b/interface/src/FileLogger.cpp @@ -1,9 +1,12 @@ // // FileLogger.cpp -// hifi +// interface/src // // Created by Stojce Slavkovski on 12/22/13. +// Copyright 2013 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 "FileLogger.h" diff --git a/interface/src/FileLogger.h b/interface/src/FileLogger.h index 35cafa4db7..5da86044ab 100644 --- a/interface/src/FileLogger.h +++ b/interface/src/FileLogger.h @@ -1,9 +1,12 @@ // // FileLogger.h -// hifi +// interface/src // // Created by Stojce Slavkovski on 12/22/13. +// Copyright 2013 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_FileLogger_h @@ -29,4 +32,4 @@ private: }; -#endif +#endif // hifi_FileLogger_h diff --git a/interface/src/GLCanvas.cpp b/interface/src/GLCanvas.cpp index 73feb31042..49085e63df 100644 --- a/interface/src/GLCanvas.cpp +++ b/interface/src/GLCanvas.cpp @@ -1,9 +1,12 @@ // // GLCanvas.cpp -// hifi +// interface/src // // Created by Stephen Birarda on 8/14/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 "Application.h" diff --git a/interface/src/GLCanvas.h b/interface/src/GLCanvas.h index 5e31a1422e..024cd615ae 100644 --- a/interface/src/GLCanvas.h +++ b/interface/src/GLCanvas.h @@ -1,13 +1,16 @@ // // GLCanvas.h -// hifi +// interface/src // // Created by Stephen Birarda on 8/14/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__GLCanvas__ -#define __hifi__GLCanvas__ +#ifndef hifi_GLCanvas_h +#define hifi_GLCanvas_h #include #include @@ -49,4 +52,4 @@ private slots: void throttleRender(); }; -#endif /* defined(__hifi__GLCanvas__) */ +#endif // hifi_GLCanvas_h diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index bf23b25e7a..1798084b2a 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -1,9 +1,12 @@ // // Menu.cpp -// hifi +// interface/src // // Created by Stephen Birarda on 8/12/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -22,14 +25,12 @@ #include #include #include -#include #include #include #include #include #include -#include #include "Application.h" #include "Menu.h" @@ -37,7 +38,7 @@ #include "Util.h" #include "ui/InfoView.h" #include "ui/MetavoxelEditor.h" -#include "ui/ModelBrowser.h" +#include "ui/ModelsBrowser.h" Menu* Menu::_instance = NULL; @@ -77,6 +78,9 @@ Menu::Menu() : _lodToolsDialog(NULL), _maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM), _voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE), + _automaticAvatarLOD(true), + _avatarLODDecreaseFPS(DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS), + _avatarLODIncreaseFPS(ADJUST_LOD_UP_FPS), _avatarLODDistanceMultiplier(DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER), _boundaryLevelAdjust(0), _maxVoxelPacketsPerSecond(DEFAULT_MAX_VOXEL_PPS), @@ -84,7 +88,9 @@ Menu::Menu() : _lastAvatarDetailDrop(usecTimestampNow()), _fpsAverage(FIVE_SECONDS_OF_FRAMES), _fastFPSAverage(ONE_SECOND_OF_FRAMES), - _loginAction(NULL) + _loginAction(NULL), + _preferencesDialog(NULL), + _snapshotsLocation() { Application *appInstance = Application::getInstance(); @@ -163,6 +169,15 @@ Menu::Menu() : QMenu* editMenu = addMenu("Edit"); + QUndoStack* undoStack = Application::getInstance()->getUndoStack(); + QAction* undoAction = undoStack->createUndoAction(editMenu); + undoAction->setShortcut(Qt::CTRL | Qt::Key_Z); + addActionToQMenuAndActionHash(editMenu, undoAction); + + QAction* redoAction = undoStack->createRedoAction(editMenu); + redoAction->setShortcut(Qt::CTRL | Qt::SHIFT | Qt::Key_Z); + addActionToQMenuAndActionHash(editMenu, redoAction); + addActionToQMenuAndActionHash(editMenu, MenuOption::Preferences, Qt::CTRL | Qt::Key_Comma, @@ -180,19 +195,19 @@ Menu::Menu() : QMenu* toolsMenu = addMenu("Tools"); addActionToQMenuAndActionHash(toolsMenu, MenuOption::MetavoxelEditor, 0, this, SLOT(showMetavoxelEditor())); + addActionToQMenuAndActionHash(toolsMenu, MenuOption::ScriptEditor, Qt::ALT | Qt::Key_S, this, SLOT(showScriptEditor())); +#ifdef HAVE_QXMPP _chatAction = addActionToQMenuAndActionHash(toolsMenu, MenuOption::Chat, - Qt::Key_Return, + 0, this, SLOT(showChat())); -#ifdef HAVE_QXMPP + const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); toggleChat(); connect(&xmppClient, SIGNAL(connected()), this, SLOT(toggleChat())); connect(&xmppClient, SIGNAL(disconnected()), this, SLOT(toggleChat())); -#else - _chatAction->setEnabled(false); #endif QMenu* viewMenu = addMenu("View"); @@ -242,10 +257,12 @@ Menu::Menu() : addDisabledActionAndSeparator(viewMenu, "Stats"); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Stats, Qt::Key_Slash); addActionToQMenuAndActionHash(viewMenu, MenuOption::Log, Qt::CTRL | Qt::Key_L, appInstance, SLOT(toggleLogDialog())); - addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Oscilloscope, 0, false); addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::Bandwidth, 0, true); addActionToQMenuAndActionHash(viewMenu, MenuOption::BandwidthDetails, 0, this, SLOT(bandwidthDetails())); addActionToQMenuAndActionHash(viewMenu, MenuOption::OctreeStats, 0, this, SLOT(octreeStatsDetails())); + addCheckableActionToQMenuAndActionHash(viewMenu, MenuOption::AudioScope, 0, false, + appInstance->getAudio(), + SLOT(toggleScope())); QMenu* developerMenu = addMenu("Developer"); @@ -263,7 +280,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Metavoxels, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::BuckyBalls, 0, true); addCheckableActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::Particles, 0, true); - + addActionToQMenuAndActionHash(renderOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools())); QMenu* voxelOptionsMenu = developerMenu->addMenu("Voxel Options"); @@ -276,15 +293,15 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::VoxelTextures); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::AmbientOcclusion); - addActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::LodTools, Qt::SHIFT | Qt::Key_L, this, SLOT(lodTools())); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DontFadeOnVoxelServerChanges); addCheckableActionToQMenuAndActionHash(voxelOptionsMenu, MenuOption::DisableAutoAdjustLOD); QMenu* avatarOptionsMenu = developerMenu->addMenu("Avatar Options"); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Avatars, 0, true); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionProxies); - addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionProxies); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderSkeletonCollisionShapes); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderHeadCollisionShapes); + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::RenderBoundingCollisionShapes); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::LookAtVectors, 0, false); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, @@ -293,11 +310,17 @@ Menu::Menu() : true, appInstance->getFaceshift(), SLOT(setTCPEnabled(bool))); +#ifdef HAVE_FACEPLUS + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Faceplus, 0, true, + appInstance->getFaceplus(), SLOT(updateEnabled())); +#endif + #ifdef HAVE_VISAGE addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::Visage, 0, true, appInstance->getVisage(), SLOT(updateEnabled())); #endif + addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::GlowWhenSpeaking, 0, true); addCheckableActionToQMenuAndActionHash(avatarOptionsMenu, MenuOption::ChatCircling, 0, false); QMenu* handOptionsMenu = developerMenu->addMenu("Hand Options"); @@ -331,8 +354,8 @@ Menu::Menu() : QMenu* renderDebugMenu = developerMenu->addMenu("Render Debugging Tools"); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings, Qt::CTRL | Qt::SHIFT | Qt::Key_P); - addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings, Qt::CTRL | Qt::SHIFT | Qt::Key_S); + addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::PipelineWarnings); + addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::SuppressShortTimings); addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::CullSharedFaces, @@ -343,7 +366,7 @@ Menu::Menu() : addCheckableActionToQMenuAndActionHash(renderDebugMenu, MenuOption::ShowCulledSharedFaces, - Qt::CTRL | Qt::SHIFT | Qt::Key_X, + 0, false, appInstance->getVoxels(), SLOT(showCulledSharedFaces())); @@ -366,6 +389,63 @@ Menu::Menu() : false, appInstance->getAudio(), SLOT(toggleToneInjection())); + addCheckableActionToQMenuAndActionHash(audioDebugMenu, MenuOption::AudioScopePause, + Qt::CTRL | Qt::Key_P, + false, + appInstance->getAudio(), + SLOT(toggleScopePause())); + + QMenu* spatialAudioMenu = audioDebugMenu->addMenu("Spatial Audio"); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessing, + Qt::CTRL | Qt::SHIFT | Qt::Key_M, + false, + appInstance->getAudio(), + SLOT(toggleAudioSpatialProcessing())); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingIncludeOriginal, + Qt::CTRL | Qt::SHIFT | Qt::Key_O, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingSeparateEars, + Qt::CTRL | Qt::SHIFT | Qt::Key_E, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingPreDelay, + Qt::CTRL | Qt::SHIFT | Qt::Key_D, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingStereoSource, + Qt::CTRL | Qt::SHIFT | Qt::Key_S, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingHeadOriented, + Qt::CTRL | Qt::SHIFT | Qt::Key_H, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingWithDiffusions, + Qt::CTRL | Qt::SHIFT | Qt::Key_W, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingRenderPaths, + Qt::CTRL | Qt::SHIFT | Qt::Key_R, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingSlightlyRandomSurfaces, + Qt::CTRL | Qt::SHIFT | Qt::Key_X, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingProcessLocalAudio, + Qt::CTRL | Qt::SHIFT | Qt::Key_A, + true); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingDontDistanceAttenuate, + Qt::CTRL | Qt::SHIFT | Qt::Key_Y, + false); + + addCheckableActionToQMenuAndActionHash(spatialAudioMenu, MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate, + Qt::CTRL | Qt::SHIFT | Qt::Key_U, + false); addActionToQMenuAndActionHash(developerMenu, MenuOption::PasteToVoxel, Qt::CTRL | Qt::SHIFT | Qt::Key_V, @@ -399,7 +479,14 @@ void Menu::loadSettings(QSettings* settings) { _maxVoxels = loadSetting(settings, "maxVoxels", DEFAULT_MAX_VOXELS_PER_SYSTEM); _maxVoxelPacketsPerSecond = loadSetting(settings, "maxVoxelsPPS", DEFAULT_MAX_VOXEL_PPS); _voxelSizeScale = loadSetting(settings, "voxelSizeScale", DEFAULT_OCTREE_SIZE_SCALE); + _automaticAvatarLOD = settings->value("automaticAvatarLOD", true).toBool(); + _avatarLODDecreaseFPS = loadSetting(settings, "avatarLODDecreaseFPS", DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS); + _avatarLODIncreaseFPS = loadSetting(settings, "avatarLODIncreaseFPS", ADJUST_LOD_UP_FPS); + _avatarLODDistanceMultiplier = loadSetting(settings, "avatarLODDistanceMultiplier", + DEFAULT_AVATAR_LOD_DISTANCE_MULTIPLIER); _boundaryLevelAdjust = loadSetting(settings, "boundaryLevelAdjust", 0); + _snapshotsLocation = settings->value("snapshotsLocation", + QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)).toString(); settings->beginGroup("View Frustum Offset Camera"); // in case settings is corrupt or missing loadSetting() will check for NaN @@ -438,7 +525,12 @@ void Menu::saveSettings(QSettings* settings) { settings->setValue("maxVoxels", _maxVoxels); settings->setValue("maxVoxelsPPS", _maxVoxelPacketsPerSecond); settings->setValue("voxelSizeScale", _voxelSizeScale); + settings->setValue("automaticAvatarLOD", _automaticAvatarLOD); + settings->setValue("avatarLODDecreaseFPS", _avatarLODDecreaseFPS); + settings->setValue("avatarLODIncreaseFPS", _avatarLODIncreaseFPS); + settings->setValue("avatarLODDistanceMultiplier", _avatarLODDistanceMultiplier); settings->setValue("boundaryLevelAdjust", _boundaryLevelAdjust); + settings->setValue("snapshotsLocation", _snapshotsLocation); settings->beginGroup("View Frustum Offset Camera"); settings->setValue("viewFrustumOffsetYaw", _viewFrustumOffset.yaw); settings->setValue("viewFrustumOffsetPitch", _viewFrustumOffset.pitch); @@ -621,6 +713,41 @@ QAction* Menu::addActionToQMenuAndActionHash(QMenu* destinationMenu, return action; } +QAction* Menu::addActionToQMenuAndActionHash(QMenu* destinationMenu, + QAction* action, + const QString& actionName, + const QKeySequence& shortcut, + QAction::MenuRole role, + int menuItemLocation) { + QAction* actionBefore = NULL; + + if (menuItemLocation >= 0 && destinationMenu->actions().size() > menuItemLocation) { + actionBefore = destinationMenu->actions()[menuItemLocation]; + } + + if (!actionName.isEmpty()) { + action->setText(actionName); + } + + if (shortcut != 0) { + action->setShortcut(shortcut); + } + + if (role != QAction::NoRole) { + action->setMenuRole(role); + } + + if (!actionBefore) { + destinationMenu->addAction(action); + } else { + destinationMenu->insertAction(actionBefore, action); + } + + _actionHash.insert(action->text(), action); + + return action; +} + QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, const QString& actionName, const QKeySequence& shortcut, @@ -633,6 +760,7 @@ QAction* Menu::addCheckableActionToQMenuAndActionHash(QMenu* destinationMenu, QAction::NoRole, menuItemLocation); action->setCheckable(true); action->setChecked(checked); + connect(action, SIGNAL(changed()), Application::getInstance(), SLOT(bumpSettings())); return action; } @@ -657,7 +785,12 @@ bool Menu::isOptionChecked(const QString& menuOption) { } void Menu::triggerOption(const QString& menuOption) { - _actionHash.value(menuOption)->trigger(); + QAction* action = _actionHash.value(menuOption); + if (action) { + action->trigger(); + } else { + qDebug() << "NULL Action for menuOption '" << menuOption << "'"; + } } QAction* Menu::getActionForOption(const QString& menuOption) { @@ -716,176 +849,31 @@ void Menu::loginForCurrentDomain() { } void Menu::editPreferences() { - Application* applicationInstance = Application::getInstance(); - ModelBrowser headBrowser(Head); - ModelBrowser skeletonBrowser(Skeleton); - - const QString BROWSE_BUTTON_TEXT = "Browse"; - - QDialog dialog(applicationInstance->getWindow()); - dialog.setWindowTitle("Interface Preferences"); - - QBoxLayout* layout = new QBoxLayout(QBoxLayout::TopToBottom); - dialog.setLayout(layout); - - QFormLayout* form = new QFormLayout(); - layout->addLayout(form, 1); - - - QHBoxLayout headModelLayout; - QString faceURLString = applicationInstance->getAvatar()->getHead()->getFaceModel().getURL().toString(); - QLineEdit headURLEdit(faceURLString); - QPushButton headBrowseButton(BROWSE_BUTTON_TEXT); - connect(&headBrowseButton, SIGNAL(clicked()), &headBrowser, SLOT(browse())); - connect(&headBrowser, SIGNAL(selected(QString)), &headURLEdit, SLOT(setText(QString))); - headURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH); - headURLEdit.setPlaceholderText(DEFAULT_HEAD_MODEL_URL.toString()); - headModelLayout.addWidget(&headURLEdit); - headModelLayout.addWidget(&headBrowseButton); - form->addRow("Head URL:", &headModelLayout); - - QHBoxLayout skeletonModelLayout; - QString skeletonURLString = applicationInstance->getAvatar()->getSkeletonModel().getURL().toString(); - QLineEdit skeletonURLEdit(skeletonURLString); - QPushButton SkeletonBrowseButton(BROWSE_BUTTON_TEXT); - connect(&SkeletonBrowseButton, SIGNAL(clicked()), &skeletonBrowser, SLOT(browse())); - connect(&skeletonBrowser, SIGNAL(selected(QString)), &skeletonURLEdit, SLOT(setText(QString))); - skeletonURLEdit.setMinimumWidth(QLINE_MINIMUM_WIDTH); - skeletonURLEdit.setPlaceholderText(DEFAULT_BODY_MODEL_URL.toString()); - skeletonModelLayout.addWidget(&skeletonURLEdit); - skeletonModelLayout.addWidget(&SkeletonBrowseButton); - form->addRow("Skeleton URL:", &skeletonModelLayout); - - - QString displayNameString = applicationInstance->getAvatar()->getDisplayName(); - QLineEdit* displayNameEdit = new QLineEdit(displayNameString); - displayNameEdit->setMinimumWidth(QLINE_MINIMUM_WIDTH); - form->addRow("Display name:", displayNameEdit); - - QSlider* pupilDilation = new QSlider(Qt::Horizontal); - pupilDilation->setValue(applicationInstance->getAvatar()->getHead()->getPupilDilation() * pupilDilation->maximum()); - form->addRow("Pupil Dilation:", pupilDilation); - - QSlider* faceshiftEyeDeflection = new QSlider(Qt::Horizontal); - faceshiftEyeDeflection->setValue(_faceshiftEyeDeflection * faceshiftEyeDeflection->maximum()); - form->addRow("Faceshift Eye Deflection:", faceshiftEyeDeflection); - - QSpinBox* fieldOfView = new QSpinBox(); - fieldOfView->setMaximum(180.f); - fieldOfView->setMinimum(1.f); - fieldOfView->setValue(_fieldOfView); - form->addRow("Vertical Field of View (Degrees):", fieldOfView); - - QDoubleSpinBox* leanScale = new QDoubleSpinBox(); - leanScale->setValue(applicationInstance->getAvatar()->getLeanScale()); - form->addRow("Lean Scale:", leanScale); - - QDoubleSpinBox* avatarScale = new QDoubleSpinBox(); - avatarScale->setValue(applicationInstance->getAvatar()->getScale()); - form->addRow("Avatar Scale:", avatarScale); - - QSpinBox* audioJitterBufferSamples = new QSpinBox(); - audioJitterBufferSamples->setMaximum(10000); - audioJitterBufferSamples->setMinimum(-10000); - audioJitterBufferSamples->setValue(_audioJitterBufferSamples); - form->addRow("Audio Jitter Buffer Samples (0 for automatic):", audioJitterBufferSamples); - - QSpinBox* maxVoxels = new QSpinBox(); - const int MAX_MAX_VOXELS = 5000000; - const int MIN_MAX_VOXELS = 0; - const int STEP_MAX_VOXELS = 50000; - maxVoxels->setMaximum(MAX_MAX_VOXELS); - maxVoxels->setMinimum(MIN_MAX_VOXELS); - maxVoxels->setSingleStep(STEP_MAX_VOXELS); - maxVoxels->setValue(_maxVoxels); - form->addRow("Maximum Voxels:", maxVoxels); - - QSpinBox* maxVoxelsPPS = new QSpinBox(); - const int MAX_MAX_VOXELS_PPS = 6000; - const int MIN_MAX_VOXELS_PPS = 60; - const int STEP_MAX_VOXELS_PPS = 10; - maxVoxelsPPS->setMaximum(MAX_MAX_VOXELS_PPS); - maxVoxelsPPS->setMinimum(MIN_MAX_VOXELS_PPS); - maxVoxelsPPS->setSingleStep(STEP_MAX_VOXELS_PPS); - maxVoxelsPPS->setValue(_maxVoxelPacketsPerSecond); - form->addRow("Maximum Voxels Packets Per Second:", maxVoxelsPPS); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); - dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); - layout->addWidget(buttons); - - int ret = dialog.exec(); - if (ret == QDialog::Accepted) { - bool shouldDispatchIdentityPacket = false; - - if (headURLEdit.text() != faceURLString && !headURLEdit.text().isEmpty()) { - // change the faceModelURL in the profile, it will also update this user's BlendFace - applicationInstance->getAvatar()->setFaceModelURL(QUrl(headURLEdit.text())); - shouldDispatchIdentityPacket = true; - } - - if (skeletonURLEdit.text() != skeletonURLString && !skeletonURLEdit.text().isEmpty()) { - // change the skeletonModelURL in the profile, it will also update this user's Body - applicationInstance->getAvatar()->setSkeletonModelURL(QUrl(skeletonURLEdit.text())); - shouldDispatchIdentityPacket = true; - } - - QString displayNameStr(displayNameEdit->text()); - - if (displayNameStr != displayNameString) { - applicationInstance->getAvatar()->setDisplayName(displayNameStr); - shouldDispatchIdentityPacket = true; - } - - if (shouldDispatchIdentityPacket) { - applicationInstance->getAvatar()->sendIdentityPacket(); - } - - applicationInstance->getAvatar()->getHead()->setPupilDilation(pupilDilation->value() / (float)pupilDilation->maximum()); - - _maxVoxels = maxVoxels->value(); - applicationInstance->getVoxels()->setMaxVoxels(_maxVoxels); - - _maxVoxelPacketsPerSecond = maxVoxelsPPS->value(); - - applicationInstance->getAvatar()->setLeanScale(leanScale->value()); - applicationInstance->getAvatar()->setClampedTargetScale(avatarScale->value()); - - _audioJitterBufferSamples = audioJitterBufferSamples->value(); - - if (_audioJitterBufferSamples != 0) { - applicationInstance->getAudio()->setJitterBufferSamples(_audioJitterBufferSamples); - } - - _fieldOfView = fieldOfView->value(); - applicationInstance->resizeGL(applicationInstance->getGLWidget()->width(), applicationInstance->getGLWidget()->height()); - - _faceshiftEyeDeflection = faceshiftEyeDeflection->value() / (float)faceshiftEyeDeflection->maximum(); + if (!_preferencesDialog) { + _preferencesDialog = new PreferencesDialog(Application::getInstance()->getWindow()); + _preferencesDialog->show(); + } else { + _preferencesDialog->close(); } - QMetaObject::invokeMethod(applicationInstance->getAudio(), "reset", Qt::QueuedConnection); - - sendFakeEnterEvent(); } void Menu::goToDomain(const QString newDomain) { - if (NodeList::getInstance()->getDomainInfo().getHostname() != newDomain) { - + if (NodeList::getInstance()->getDomainHandler().getHostname() != newDomain) { // send a node kill request, indicating to other clients that they should play the "disappeared" effect Application::getInstance()->getAvatar()->sendKillAvatar(); // give our nodeList the new domain-server hostname - NodeList::getInstance()->getDomainInfo().setHostname(newDomain); + NodeList::getInstance()->getDomainHandler().setHostname(newDomain); } } void Menu::goToDomainDialog() { - QString currentDomainHostname = NodeList::getInstance()->getDomainInfo().getHostname(); + QString currentDomainHostname = NodeList::getInstance()->getDomainHandler().getHostname(); - if (NodeList::getInstance()->getDomainInfo().getPort() != DEFAULT_DOMAIN_SERVER_PORT) { + if (NodeList::getInstance()->getDomainHandler().getPort() != DEFAULT_DOMAIN_SERVER_PORT) { // add the port to the currentDomainHostname string if it is custom - currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainInfo().getPort())); + currentDomainHostname.append(QString(":%1").arg(NodeList::getInstance()->getDomainHandler().getPort())); } QInputDialog domainDialog(Application::getInstance()->getWindow()); @@ -918,19 +906,58 @@ bool Menu::goToDestination(QString destination) { return LocationManager::getInstance().goToDestination(destination); } +void Menu::goTo(QString destination) { + LocationManager::getInstance().goTo(destination); +} + void Menu::goTo() { QInputDialog gotoDialog(Application::getInstance()->getWindow()); gotoDialog.setWindowTitle("Go to"); - gotoDialog.setLabelText("Destination:"); + gotoDialog.setLabelText("Destination or URL:\n @user, #place, hifi://domain/location/orientation"); QString destination = QString(); + gotoDialog.setTextValue(destination); gotoDialog.setWindowFlags(Qt::Sheet); gotoDialog.resize(gotoDialog.parentWidget()->size().width() * DIALOG_RATIO_OF_WINDOW, gotoDialog.size().height()); int dialogReturn = gotoDialog.exec(); if (dialogReturn == QDialog::Accepted && !gotoDialog.textValue().isEmpty()) { - goToUser(gotoDialog.textValue()); + QString desiredDestination = gotoDialog.textValue(); + + if (desiredDestination.startsWith(CUSTOM_URL_SCHEME + "//")) { + QStringList urlParts = desiredDestination.remove(0, CUSTOM_URL_SCHEME.length() + 2).split('/', QString::SkipEmptyParts); + + 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(); + + goToDomain(domain); + + // goto either @user, #place, or x-xx,y-yy,z-zz + // style co-ordinate. + goTo(destination); + + if (!orientation.isEmpty()) { + // location orientation + goToOrientation(orientation); + } + } else if (urlParts.count() == 1) { + // location coordinates or place name + QString destination = urlParts[0]; + goTo(destination); + } + + } else { + goToUser(gotoDialog.textValue()); + } } sendFakeEnterEvent(); } @@ -966,7 +993,6 @@ void Menu::goToLocation() { QString currentLocation = QString("%1, %2, %3").arg(QString::number(avatarPos.x), QString::number(avatarPos.y), QString::number(avatarPos.z)); - QInputDialog coordinateDialog(Application::getInstance()->getWindow()); coordinateDialog.setWindowTitle("Go to Location"); coordinateDialog.setLabelText("Coordinate as x,y,z:"); @@ -1040,7 +1066,7 @@ void Menu::nameLocation() { connect(manager, &LocationManager::creationCompleted, this, &Menu::namedLocationCreated); NamedLocation* location = new NamedLocation(locationName, myAvatar->getPosition(), myAvatar->getOrientation(), - NodeList::getInstance()->getDomainInfo().getHostname()); + NodeList::getInstance()->getDomainHandler().getHostname()); manager->createNamedLocation(location); } } @@ -1107,29 +1133,25 @@ void Menu::showMetavoxelEditor() { _MetavoxelEditor->raise(); } -void Menu::showChat() { - if (!_chatAction->isEnabled()) { - // Don't do anything if chat is disabled (No - // QXMPP library or xmpp is disconnected). - return; +void Menu::showScriptEditor() { + if(!_ScriptEditor || !_ScriptEditor->isVisible()) { + _ScriptEditor = new ScriptEditorWindow(); } + _ScriptEditor->raise(); +} + +void Menu::showChat() { QMainWindow* mainWindow = Application::getInstance()->getWindow(); if (!_chatWindow) { - mainWindow->addDockWidget(Qt::NoDockWidgetArea, _chatWindow = new ChatWindow()); + mainWindow->addDockWidget(Qt::RightDockWidgetArea, _chatWindow = new ChatWindow()); } if (!_chatWindow->toggleViewAction()->isChecked()) { - int width = _chatWindow->width(); - int y = qMax((mainWindow->height() - _chatWindow->height()) / 2, 0); - _chatWindow->move(mainWindow->width(), y); + const QRect& windowGeometry = mainWindow->geometry(); + _chatWindow->move(windowGeometry.topRight().x() - _chatWindow->width(), + windowGeometry.topRight().y() + (windowGeometry.height() / 2) - (_chatWindow->height() / 2)); + _chatWindow->resize(0, _chatWindow->height()); _chatWindow->toggleViewAction()->trigger(); - - QPropertyAnimation* slideAnimation = new QPropertyAnimation(_chatWindow, "geometry", _chatWindow); - slideAnimation->setStartValue(_chatWindow->geometry()); - slideAnimation->setEndValue(QRect(mainWindow->width() - width, _chatWindow->y(), - width, _chatWindow->height())); - slideAnimation->setDuration(250); - slideAnimation->start(QAbstractAnimation::DeleteWhenStopped); } } @@ -1220,22 +1242,24 @@ void Menu::autoAdjustLOD(float currentFPS) { quint64 now = usecTimestampNow(); const quint64 ADJUST_AVATAR_LOD_DOWN_DELAY = 1000 * 1000; - if (_fastFPSAverage.getAverage() < ADJUST_LOD_DOWN_FPS) { - if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) { - // attempt to lower the detail in proportion to the fps difference - float targetFps = (ADJUST_LOD_DOWN_FPS + ADJUST_LOD_UP_FPS) * 0.5f; - float averageFps = _fastFPSAverage.getAverage(); - const float MAXIMUM_MULTIPLIER_SCALE = 2.0f; - _avatarLODDistanceMultiplier *= (averageFps < EPSILON) ? MAXIMUM_MULTIPLIER_SCALE : - qMin(MAXIMUM_MULTIPLIER_SCALE, targetFps / averageFps); - _lastAvatarDetailDrop = now; + if (_automaticAvatarLOD) { + if (_fastFPSAverage.getAverage() < _avatarLODDecreaseFPS) { + if (now - _lastAvatarDetailDrop > ADJUST_AVATAR_LOD_DOWN_DELAY) { + // attempt to lower the detail in proportion to the fps difference + float targetFps = (_avatarLODDecreaseFPS + _avatarLODIncreaseFPS) * 0.5f; + float averageFps = _fastFPSAverage.getAverage(); + const float MAXIMUM_MULTIPLIER_SCALE = 2.0f; + _avatarLODDistanceMultiplier = qMin(MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, _avatarLODDistanceMultiplier * + (averageFps < EPSILON ? MAXIMUM_MULTIPLIER_SCALE : + qMin(MAXIMUM_MULTIPLIER_SCALE, targetFps / averageFps))); + _lastAvatarDetailDrop = now; + } + } else if (_fastFPSAverage.getAverage() > _avatarLODIncreaseFPS) { + // let the detail level creep slowly upwards + const float DISTANCE_DECREASE_RATE = 0.05f; + _avatarLODDistanceMultiplier = qMax(MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, + _avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE); } - } else if (_fastFPSAverage.getAverage() > ADJUST_LOD_UP_FPS) { - // let the detail level creep slowly upwards - const float DISTANCE_DECREASE_RATE = 0.02f; - const float MINIMUM_DISTANCE_MULTIPLIER = 0.1f; - _avatarLODDistanceMultiplier = qMax(MINIMUM_DISTANCE_MULTIPLIER, - _avatarLODDistanceMultiplier - DISTANCE_DECREASE_RATE); } bool changed = false; @@ -1561,3 +1585,10 @@ void Menu::removeMenuItem(const QString& menu, const QString& menuitem) { QMenuBar::repaint(); }; +QString Menu::getSnapshotsLocation() const { + if (_snapshotsLocation.isNull() || _snapshotsLocation.isEmpty() || QDir(_snapshotsLocation).exists() == false) { + return QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); + } + return _snapshotsLocation; +} + diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 96da7908ec..384e2dd0ec 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -1,28 +1,36 @@ // // Menu.h -// hifi +// interface/src // // Created by Stephen Birarda on 8/12/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__Menu__ -#define __hifi__Menu__ +#ifndef hifi_Menu_h +#define hifi_Menu_h +#include #include #include #include #include +#include #include #include #include #include "location/LocationManager.h" +#include "ui/PreferencesDialog.h" #include "ui/ChatWindow.h" +#include "ui/ScriptEditorWindow.h" const float ADJUST_LOD_DOWN_FPS = 40.0; const float ADJUST_LOD_UP_FPS = 55.0; +const float DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS = 30.0f; const quint64 ADJUST_LOD_DOWN_DELAY = 1000 * 1000 * 5; const quint64 ADJUST_LOD_UP_DELAY = ADJUST_LOD_DOWN_DELAY * 2; @@ -33,6 +41,9 @@ const float ADJUST_LOD_UP_BY = 1.1f; const float ADJUST_LOD_MIN_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE * 0.25f; const float ADJUST_LOD_MAX_SIZE_SCALE = DEFAULT_OCTREE_SIZE_SCALE; +const float MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 0.1f; +const float MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER = 15.0f; + enum FrustumDrawMode { FRUSTUM_DRAW_MODE_ALL, FRUSTUM_DRAW_MODE_VECTORS, @@ -70,8 +81,14 @@ public: QAction* getActionForOption(const QString& menuOption); float getAudioJitterBufferSamples() const { return _audioJitterBufferSamples; } + void setAudioJitterBufferSamples(float audioJitterBufferSamples) { _audioJitterBufferSamples = audioJitterBufferSamples; } float getFieldOfView() const { return _fieldOfView; } + void setFieldOfView(float fieldOfView) { _fieldOfView = fieldOfView; } float getFaceshiftEyeDeflection() const { return _faceshiftEyeDeflection; } + void setFaceshiftEyeDeflection(float faceshiftEyeDeflection) { _faceshiftEyeDeflection = faceshiftEyeDeflection; } + QString getSnapshotsLocation() const; + void setSnapshotsLocation(QString snapshotsLocation) { _snapshotsLocation = snapshotsLocation; } + BandwidthDialog* getBandwidthDialog() const { return _bandwidthDialog; } FrustumDrawMode getFrustumDrawMode() const { return _frustumDrawMode; } ViewFrustumOffset getViewFrustumOffset() const { return _viewFrustumOffset; } @@ -88,12 +105,20 @@ public: void resetLODAdjust(); void setVoxelSizeScale(float sizeScale); float getVoxelSizeScale() const { return _voxelSizeScale; } + void setAutomaticAvatarLOD(bool automaticAvatarLOD) { _automaticAvatarLOD = automaticAvatarLOD; } + bool getAutomaticAvatarLOD() const { return _automaticAvatarLOD; } + void setAvatarLODDecreaseFPS(float avatarLODDecreaseFPS) { _avatarLODDecreaseFPS = avatarLODDecreaseFPS; } + float getAvatarLODDecreaseFPS() const { return _avatarLODDecreaseFPS; } + void setAvatarLODIncreaseFPS(float avatarLODIncreaseFPS) { _avatarLODIncreaseFPS = avatarLODIncreaseFPS; } + float getAvatarLODIncreaseFPS() const { return _avatarLODIncreaseFPS; } + void setAvatarLODDistanceMultiplier(float multiplier) { _avatarLODDistanceMultiplier = multiplier; } float getAvatarLODDistanceMultiplier() const { return _avatarLODDistanceMultiplier; } void setBoundaryLevelAdjust(int boundaryLevelAdjust); int getBoundaryLevelAdjust() const { return _boundaryLevelAdjust; } // User Tweakable PPS from Voxel Server int getMaxVoxelPacketsPerSecond() const { return _maxVoxelPacketsPerSecond; } + void setMaxVoxelPacketsPerSecond(int maxVoxelPacketsPerSecond) { _maxVoxelPacketsPerSecond = maxVoxelPacketsPerSecond; } QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, const QString& actionName, @@ -102,12 +127,19 @@ public: const char* member = NULL, QAction::MenuRole role = QAction::NoRole, int menuItemLocation = UNSPECIFIED_POSITION); + QAction* addActionToQMenuAndActionHash(QMenu* destinationMenu, + QAction* action, + const QString& actionName = QString(), + const QKeySequence& shortcut = 0, + QAction::MenuRole role = QAction::NoRole, + int menuItemLocation = UNSPECIFIED_POSITION); void removeAction(QMenu* menu, const QString& actionName); - bool goToDestination(QString destination); - void goToOrientation(QString orientation); - void goToDomain(const QString newDomain); + bool static goToDestination(QString destination); + void static goToOrientation(QString orientation); + void static goToDomain(const QString newDomain); + void static goTo(QString destination); public slots: @@ -146,6 +178,7 @@ private slots: void cycleFrustumRenderMode(); void runTests(); void showMetavoxelEditor(); + void showScriptEditor(); void showChat(); void toggleChat(); void audioMuteToggled(); @@ -197,11 +230,15 @@ private: FrustumDrawMode _frustumDrawMode; ViewFrustumOffset _viewFrustumOffset; QPointer _MetavoxelEditor; + QPointer _ScriptEditor; QPointer _chatWindow; OctreeStatsDialog* _octreeStatsDialog; LodToolsDialog* _lodToolsDialog; int _maxVoxels; float _voxelSizeScale; + bool _automaticAvatarLOD; + float _avatarLODDecreaseFPS; + float _avatarLODIncreaseFPS; float _avatarLODDistanceMultiplier; int _boundaryLevelAdjust; QAction* _useVoxelShader; @@ -212,104 +249,126 @@ private: SimpleMovingAverage _fpsAverage; SimpleMovingAverage _fastFPSAverage; QAction* _loginAction; + QPointer _preferencesDialog; QAction* _chatAction; + QString _snapshotsLocation; }; namespace MenuOption { const QString AboutApp = "About Interface"; const QString AmbientOcclusion = "Ambient Occlusion"; - const QString Avatars = "Avatars"; const QString Atmosphere = "Atmosphere"; - const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; + const QString AudioNoiseReduction = "Audio Noise Reduction"; + const QString AudioScope = "Audio Scope"; + const QString AudioScopePause = "Pause Audio Scope"; + const QString AudioToneInjection = "Inject Test Tone"; + const QString AudioSpatialProcessing = "Audio Spatial Processing"; + const QString AudioSpatialProcessingHeadOriented = "Head Oriented"; + const QString AudioSpatialProcessingIncludeOriginal = "Includes Network Original"; + const QString AudioSpatialProcessingPreDelay = "Add Pre-Delay"; + const QString AudioSpatialProcessingProcessLocalAudio = "Process Local Audio"; + const QString AudioSpatialProcessingRenderPaths = "Render Paths"; + const QString AudioSpatialProcessingSeparateEars = "Separate Ears"; + const QString AudioSpatialProcessingSlightlyRandomSurfaces = "Slightly Random Surfaces"; + const QString AudioSpatialProcessingStereoSource = "Stereo Source"; + const QString AudioSpatialProcessingWithDiffusions = "With Diffusions"; + const QString AudioSpatialProcessingDontDistanceAttenuate = "Don't calculate distance attenuation"; + const QString AudioSpatialProcessingAlternateDistanceAttenuate = "Alternate distance attenuation"; + + + + const QString Avatars = "Avatars"; const QString Bandwidth = "Bandwidth Display"; const QString BandwidthDetails = "Bandwidth Details"; const QString BuckyBalls = "Bucky Balls"; + const QString Chat = "Chat..."; const QString ChatCircling = "Chat Circling"; - const QString Collisions = "Collisions"; const QString CollideWithAvatars = "Collide With Avatars"; + const QString CollideWithEnvironment = "Collide With World Boundaries"; const QString CollideWithParticles = "Collide With Particles"; const QString CollideWithVoxels = "Collide With Voxels"; - const QString CollideWithEnvironment = "Collide With World Boundaries"; + const QString Collisions = "Collisions"; const QString CullSharedFaces = "Cull Shared Voxel Faces"; const QString DecreaseAvatarSize = "Decrease Avatar Size"; const QString DecreaseVoxelSize = "Decrease Voxel Size"; + const QString DisableAutoAdjustLOD = "Disable Automatically Adjusting LOD"; const QString DisplayFrustum = "Display Frustum"; const QString DisplayHands = "Display Hands"; const QString DisplayHandTargets = "Display Hand Targets"; - const QString FilterSixense = "Smooth Sixense Movement"; - const QString Enable3DTVMode = "Enable 3DTV Mode"; - const QString AudioNoiseReduction = "Audio Noise Reduction"; - const QString AudioToneInjection = "Inject Test Tone"; - const QString EchoServerAudio = "Echo Server Audio"; - const QString EchoLocalAudio = "Echo Local Audio"; - const QString MuteAudio = "Mute Microphone"; const QString DontFadeOnVoxelServerChanges = "Don't Fade In/Out on Voxel Server Changes"; - const QString HeadMouse = "Head Mouse"; - const QString HandsCollideWithSelf = "Collide With Self"; + const QString EchoLocalAudio = "Echo Local Audio"; + const QString EchoServerAudio = "Echo Server Audio"; + const QString Enable3DTVMode = "Enable 3DTV Mode"; + const QString Faceplus = "Faceplus"; const QString Faceshift = "Faceshift"; + const QString FilterSixense = "Smooth Sixense Movement"; const QString FirstPerson = "First Person"; const QString FrameTimer = "Show Timer"; const QString FrustumRenderMode = "Render Mode"; const QString Fullscreen = "Fullscreen"; const QString FullscreenMirror = "Fullscreen Mirror"; const QString GlowMode = "Cycle Glow Mode"; + const QString GlowWhenSpeaking = "Glow When Speaking"; + const QString GoHome = "Go Home"; + const QString GoTo = "Go To..."; const QString GoToDomain = "Go To Domain..."; const QString GoToLocation = "Go To Location..."; - const QString NameLocation = "Name this location"; - const QString GoTo = "Go To..."; + const QString Gravity = "Use Gravity"; + const QString HandsCollideWithSelf = "Collide With Self"; + const QString HeadMouse = "Head Mouse"; const QString IncreaseAvatarSize = "Increase Avatar Size"; const QString IncreaseVoxelSize = "Increase Voxel Size"; - const QString GoHome = "Go Home"; - const QString Gravity = "Use Gravity"; + const QString LoadScript = "Open and Run Script File..."; + const QString LoadScriptURL = "Open and Run Script from URL..."; const QString LodTools = "LOD Tools"; const QString Log = "Log"; const QString Login = "Login"; const QString Logout = "Logout"; const QString LookAtVectors = "Look-at Vectors"; const QString MetavoxelEditor = "Metavoxel Editor..."; - const QString Chat = "Chat..."; const QString Metavoxels = "Metavoxels"; const QString Mirror = "Mirror"; const QString MoveWithLean = "Move with Lean"; + const QString MuteAudio = "Mute Microphone"; + const QString NameLocation = "Name this location"; const QString NewVoxelCullingMode = "New Voxel Culling Mode"; + const QString OctreeStats = "Voxel and Particle Statistics"; const QString OffAxisProjection = "Off-Axis Projection"; const QString OldVoxelCullingMode = "Old Voxel Culling Mode"; - const QString TurnWithHead = "Turn using Head"; - const QString LoadScript = "Open and Run Script File..."; - const QString LoadScriptURL = "Open and Run Script from URL..."; - const QString Oscilloscope = "Audio Oscilloscope"; const QString Pair = "Pair"; const QString Particles = "Particles"; const QString PasteToVoxel = "Paste to Voxel..."; const QString PipelineWarnings = "Show Render Pipeline Warnings"; const QString PlaySlaps = "Play Slaps"; const QString Preferences = "Preferences..."; + const QString Quit = "Quit"; const QString ReloadAllScripts = "Reload All Scripts"; - const QString RenderSkeletonCollisionProxies = "Skeleton Collision Proxies"; - const QString RenderHeadCollisionProxies = "Head Collision Proxies"; + const QString RenderBoundingCollisionShapes = "Bounding Collision Shapes"; + const QString RenderHeadCollisionShapes = "Head Collision Shapes"; + const QString RenderSkeletonCollisionShapes = "Skeleton Collision Shapes"; const QString ResetAvatarSize = "Reset Avatar Size"; const QString RunningScripts = "Running Scripts"; const QString RunTimingTests = "Run Timing Tests"; + const QString ScriptEditor = "Script Editor..."; + const QString SettingsExport = "Export Settings"; const QString SettingsImport = "Import Settings"; const QString Shadows = "Shadows"; - const QString SettingsExport = "Export Settings"; const QString ShowCulledSharedFaces = "Show Culled Shared Voxel Faces"; - const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString Stars = "Stars"; const QString Stats = "Stats"; const QString StopAllScripts = "Stop All Scripts"; + const QString SuppressShortTimings = "Suppress Timings Less than 10ms"; const QString TestPing = "Test Ping"; const QString TransmitterDrive = "Transmitter Drive"; + const QString TurnWithHead = "Turn using Head"; const QString UploadHead = "Upload Head Model"; const QString UploadSkeleton = "Upload Skeleton Model"; const QString Visage = "Visage"; - const QString Quit = "Quit"; - const QString Voxels = "Voxels"; const QString VoxelMode = "Cycle Voxel Mode"; - const QString OctreeStats = "Voxel and Particle Statistics"; + const QString Voxels = "Voxels"; const QString VoxelTextures = "Voxel Textures"; } void sendFakeEnterEvent(); -#endif /* defined(__hifi__Menu__) */ +#endif // hifi_Menu_h diff --git a/interface/src/MetavoxelSystem.cpp b/interface/src/MetavoxelSystem.cpp index 042c9329f2..78c97c1703 100644 --- a/interface/src/MetavoxelSystem.cpp +++ b/interface/src/MetavoxelSystem.cpp @@ -1,9 +1,12 @@ // // MetavoxelSystem.cpp -// interface +// interface/src // // Created by Andrzej Kapolka on 12/10/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -69,13 +72,13 @@ SharedObjectPointer MetavoxelSystem::findFirstRaySpannerIntersection( return closestSpanner; } -void MetavoxelSystem::applyEdit(const MetavoxelEditMessage& edit) { +void MetavoxelSystem::applyEdit(const MetavoxelEditMessage& edit, bool reliable) { foreach (const SharedNodePointer& node, NodeList::getInstance()->getNodeHash()) { if (node->getType() == NodeType::MetavoxelServer) { QMutexLocker locker(&node->getMutex()); MetavoxelClient* client = static_cast(node->getLinkedData()); if (client) { - client->applyEdit(edit); + client->applyEdit(edit, reliable); } } } @@ -228,7 +231,7 @@ MetavoxelSystem::RenderVisitor::RenderVisitor() : } bool MetavoxelSystem::RenderVisitor::visit(Spanner* spanner, const glm::vec3& clipMinimum, float clipSize) { - spanner->getRenderer()->render(1.0f, clipMinimum, clipSize); + spanner->getRenderer()->render(1.0f, SpannerRenderer::DEFAULT_MODE, clipMinimum, clipSize); return true; } @@ -267,12 +270,17 @@ void MetavoxelClient::guide(MetavoxelVisitor& visitor) { _data.guide(visitor); } -void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit) { - // apply immediately to local tree - edit.apply(_data, _sequencer.getWeakSharedObjectHash()); +void MetavoxelClient::applyEdit(const MetavoxelEditMessage& edit, bool reliable) { + if (reliable) { + _sequencer.getReliableOutputChannel()->sendMessage(QVariant::fromValue(edit)); + + } else { + // apply immediately to local tree + edit.apply(_data, _sequencer.getWeakSharedObjectHash()); - // start sending it out - _sequencer.sendHighPriorityMessage(QVariant::fromValue(edit)); + // start sending it out + _sequencer.sendHighPriorityMessage(QVariant::fromValue(edit)); + } } void MetavoxelClient::simulate(float deltaTime) { @@ -290,11 +298,13 @@ void MetavoxelClient::simulate(float deltaTime) { int MetavoxelClient::parseData(const QByteArray& packet) { // process through sequencer QMetaObject::invokeMethod(&_sequencer, "receivedDatagram", Q_ARG(const QByteArray&, packet)); + Application::getInstance()->getBandwidthMeter()->inputStream(BandwidthMeter::METAVOXELS).updateValue(packet.size()); return packet.size(); } void MetavoxelClient::sendData(const QByteArray& data) { NodeList::getInstance()->writeDatagram(data, _node); + Application::getInstance()->getBandwidthMeter()->outputStream(BandwidthMeter::METAVOXELS).updateValue(data.size()); } void MetavoxelClient::readPacket(Bitstream& in) { @@ -340,9 +350,9 @@ static void enableClipPlane(GLenum plane, float x, float y, float z, float w) { glEnable(plane); } -void ClippedRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) { +void ClippedRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) { if (clipSize == 0.0f) { - renderUnclipped(alpha); + renderUnclipped(alpha, mode); return; } enableClipPlane(GL_CLIP_PLANE0, -1.0f, 0.0f, 0.0f, clipMinimum.x + clipSize); @@ -352,7 +362,7 @@ void ClippedRenderer::render(float alpha, const glm::vec3& clipMinimum, float cl enableClipPlane(GL_CLIP_PLANE4, 0.0f, 0.0f, -1.0f, clipMinimum.z + clipSize); enableClipPlane(GL_CLIP_PLANE5, 0.0f, 0.0f, 1.0f, -clipMinimum.z); - renderUnclipped(alpha); + renderUnclipped(alpha, mode); glDisable(GL_CLIP_PLANE0); glDisable(GL_CLIP_PLANE1); @@ -365,9 +375,9 @@ void ClippedRenderer::render(float alpha, const glm::vec3& clipMinimum, float cl SphereRenderer::SphereRenderer() { } -void SphereRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) { +void SphereRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) { if (clipSize == 0.0f) { - renderUnclipped(alpha); + renderUnclipped(alpha, mode); return; } // slight performance optimization: don't render if clip bounds are entirely within sphere @@ -376,13 +386,13 @@ void SphereRenderer::render(float alpha, const glm::vec3& clipMinimum, float cli for (int i = 0; i < Box::VERTEX_COUNT; i++) { const float CLIP_PROPORTION = 0.95f; if (glm::distance(sphere->getTranslation(), clipBox.getVertex(i)) >= sphere->getScale() * CLIP_PROPORTION) { - ClippedRenderer::render(alpha, clipMinimum, clipSize); + ClippedRenderer::render(alpha, mode, clipMinimum, clipSize); return; } } } -void SphereRenderer::renderUnclipped(float alpha) { +void SphereRenderer::renderUnclipped(float alpha, Mode mode) { Sphere* sphere = static_cast(parent()); const QColor& color = sphere->getColor(); glColor4f(color.redF(), color.greenF(), color.blueF(), color.alphaF() * alpha); @@ -390,7 +400,7 @@ void SphereRenderer::renderUnclipped(float alpha) { glPushMatrix(); const glm::vec3& translation = sphere->getTranslation(); glTranslatef(translation.x, translation.y, translation.z); - glm::quat rotation = glm::quat(glm::radians(sphere->getRotation())); + glm::quat rotation = sphere->getRotation(); glm::vec3 axis = glm::axis(rotation); glRotatef(glm::angle(rotation), axis.x, axis.y, axis.z); @@ -413,7 +423,7 @@ void StaticModelRenderer::init(Spanner* spanner) { applyURL(staticModel->getURL()); connect(spanner, SIGNAL(translationChanged(const glm::vec3&)), SLOT(applyTranslation(const glm::vec3&))); - connect(spanner, SIGNAL(rotationChanged(const glm::vec3&)), SLOT(applyRotation(const glm::vec3&))); + connect(spanner, SIGNAL(rotationChanged(const glm::quat&)), SLOT(applyRotation(const glm::quat&))); connect(spanner, SIGNAL(scaleChanged(float)), SLOT(applyScale(float))); connect(spanner, SIGNAL(urlChanged(const QUrl&)), SLOT(applyURL(const QUrl&))); } @@ -430,7 +440,20 @@ void StaticModelRenderer::simulate(float deltaTime) { _model->simulate(deltaTime); } -void StaticModelRenderer::renderUnclipped(float alpha) { +void StaticModelRenderer::renderUnclipped(float alpha, Mode mode) { + switch (mode) { + case DIFFUSE_MODE: + _model->render(alpha, Model::DIFFUSE_RENDER_MODE); + break; + + case NORMAL_MODE: + _model->render(alpha, Model::NORMAL_RENDER_MODE); + break; + + default: + _model->render(alpha); + break; + } _model->render(alpha); } @@ -443,8 +466,8 @@ void StaticModelRenderer::applyTranslation(const glm::vec3& translation) { _model->setTranslation(translation); } -void StaticModelRenderer::applyRotation(const glm::vec3& rotation) { - _model->setRotation(glm::quat(glm::radians(rotation))); +void StaticModelRenderer::applyRotation(const glm::quat& rotation) { + _model->setRotation(rotation); } void StaticModelRenderer::applyScale(float scale) { diff --git a/interface/src/MetavoxelSystem.h b/interface/src/MetavoxelSystem.h index 58f40dad37..f98a260ab1 100644 --- a/interface/src/MetavoxelSystem.h +++ b/interface/src/MetavoxelSystem.h @@ -1,13 +1,16 @@ // // MetavoxelSystem.h -// interface +// interface/src // // Created by Andrzej Kapolka on 12/10/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__MetavoxelSystem__ -#define __interface__MetavoxelSystem__ +#ifndef hifi_MetavoxelSystem_h +#define hifi_MetavoxelSystem_h #include #include @@ -38,7 +41,7 @@ public: SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction, const AttributePointer& attribute, float& distance); - void applyEdit(const MetavoxelEditMessage& edit); + Q_INVOKABLE void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false); void simulate(float deltaTime); void render(); @@ -98,7 +101,7 @@ public: void guide(MetavoxelVisitor& visitor); - void applyEdit(const MetavoxelEditMessage& edit); + void applyEdit(const MetavoxelEditMessage& edit, bool reliable = false); void simulate(float deltaTime); @@ -147,11 +150,11 @@ class ClippedRenderer : public SpannerRenderer { public: - virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize); + virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize); protected: - virtual void renderUnclipped(float alpha) = 0; + virtual void renderUnclipped(float alpha, Mode mode) = 0; }; /// Renders spheres. @@ -162,11 +165,11 @@ public: Q_INVOKABLE SphereRenderer(); - virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize); + virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize); protected: - virtual void renderUnclipped(float alpha); + virtual void renderUnclipped(float alpha, Mode mode); }; /// Renders static models. @@ -184,12 +187,12 @@ public: protected: - virtual void renderUnclipped(float alpha); + virtual void renderUnclipped(float alpha, Mode mode); private slots: void applyTranslation(const glm::vec3& translation); - void applyRotation(const glm::vec3& eulerAngles); // eulerAngles are in degrees + void applyRotation(const glm::quat& rotation); void applyScale(float scale); void applyURL(const QUrl& url); @@ -198,4 +201,4 @@ private: Model* _model; }; -#endif /* defined(__interface__MetavoxelSystem__) */ +#endif // hifi_MetavoxelSystem_h diff --git a/interface/src/ModelUploader.cpp b/interface/src/ModelUploader.cpp new file mode 100644 index 0000000000..048e13bdf2 --- /dev/null +++ b/interface/src/ModelUploader.cpp @@ -0,0 +1,427 @@ +// +// ModelUploader.cpp +// interface/src +// +// Created by Clément Brisset on 3/4/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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include "Application.h" +#include "ModelUploader.h" + + +static const QString NAME_FIELD = "name"; +static const QString FILENAME_FIELD = "filename"; +static const QString TEXDIR_FIELD = "texdir"; +static const QString LOD_FIELD = "lod"; + +static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; +static const QString DATA_SERVER_URL = "https://data-web.highfidelity.io"; +static const QString MODEL_URL = "/api/v1/models"; + +static const QString SETTING_NAME = "LastModelUploadLocation"; + +static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MB +static const int TIMEOUT = 1000; +static const int MAX_CHECK = 30; + +static const int QCOMPRESS_HEADER_POSITION = 0; +static const int QCOMPRESS_HEADER_SIZE = 4; + +ModelUploader::ModelUploader(bool isHead) : + _lodCount(-1), + _texturesCount(-1), + _totalSize(0), + _isHead(isHead), + _readyToSend(false), + _dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType)), + _numberOfChecks(MAX_CHECK) +{ + connect(&_timer, SIGNAL(timeout()), SLOT(checkS3())); +} + +ModelUploader::~ModelUploader() { + delete _dataMultiPart; +} + +bool ModelUploader::zip() { + // File Dialog + QSettings* settings = Application::getInstance()->lockSettings(); + QString lastLocation = settings->value(SETTING_NAME).toString(); + + if (lastLocation.isEmpty()) { + lastLocation = QStandardPaths::writableLocation(QStandardPaths::DownloadLocation); + // Temporary fix to Qt bug: http://stackoverflow.com/questions/16194475 +#ifdef __APPLE__ + lastLocation.append("/model.fst"); +#endif + } + + + QString filename = QFileDialog::getOpenFileName(NULL, "Select your .fst file ...", lastLocation, "*.fst"); + if (filename == "") { + // If the user canceled we return. + Application::getInstance()->unlockSettings(); + return false; + } + settings->setValue(SETTING_NAME, filename); + Application::getInstance()->unlockSettings(); + + bool _nameIsPresent = false; + QString texDir; + QString fbxFile; + + + // First we check the FST file + QFile fst(filename); + if (!fst.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("Could not open FST file."), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("Could not open FST file."); + return false; + } + qDebug() << "Reading FST file : " << QFileInfo(fst).filePath(); + + // Compress and copy the fst + if (!addPart(QFileInfo(fst).filePath(), QString("fst"))) { + return false; + } + + // Let's read through the FST file + QTextStream stream(&fst); + QList line; + while (!stream.atEnd()) { + line = stream.readLine().split(QRegExp("[ =]"), QString::SkipEmptyParts); + if (line.isEmpty()) { + continue; + } + + // according to what is read, we modify the command + if (line[0] == NAME_FIELD) { + QHttpPart textPart; + textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" + " name=\"model_name\""); + textPart.setBody(line[1].toUtf8()); + _dataMultiPart->append(textPart); + _url = S3_URL + ((_isHead)? "/models/heads/" : "/models/skeletons/") + line[1].toUtf8() + ".fst"; + _nameIsPresent = true; + } else if (line[0] == FILENAME_FIELD) { + fbxFile = QFileInfo(fst).path() + "/" + line[1]; + QFileInfo fbxInfo(fbxFile); + if (!fbxInfo.exists() || !fbxInfo.isFile()) { // Check existence + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbxInfo.fileName()); + return false; + } + // Compress and copy + if (!addPart(fbxInfo.filePath(), "fbx")) { + return false; + } + } else if (line[0] == TEXDIR_FIELD) { // Check existence + texDir = QFileInfo(fst).path() + "/" + line[1]; + QFileInfo texInfo(texDir); + if (!texInfo.exists() || !texInfo.isDir()) { + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("Texture directory could not be found."), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("Texture directory could not be found."); + return false; + } + } else if (line[0] == LOD_FIELD) { + QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]); + if (!lod.exists() || !lod.isFile()) { // Check existence + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("LOD file %1 could not be found.").arg(lod.fileName()), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName()); + } + // Compress and copy + if (!addPart(lod.filePath(), QString("lod%1").arg(++_lodCount))) { + return false; + } + } + } + + if (!addTextures(texDir, fbxFile)) { + return false; + } + + QHttpPart textPart; + textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" + " name=\"model_category\""); + if (_isHead) { + textPart.setBody("heads"); + } else { + textPart.setBody("skeletons"); + } + _dataMultiPart->append(textPart); + + if (!_nameIsPresent) { + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("Model name is missing in the .fst file."), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("Model name is missing in the .fst file."); + return false; + } + + _readyToSend = true; + return true; +} + +void ModelUploader::send() { + if (!zip()) { + deleteLater(); + return; + } + + JSONCallbackParameters callbackParams; + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "checkJSON"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "uploadFailed"; + + AccountManager::getInstance().authenticatedRequest(MODEL_URL + "/" + QFileInfo(_url).baseName(), + QNetworkAccessManager::GetOperation, + callbackParams); + + qDebug() << "Sending model..."; + _progressDialog = new QDialog(); + _progressBar = new QProgressBar(_progressDialog); + _progressBar->setRange(0, 100); + _progressBar->setValue(0); + + _progressDialog->setWindowTitle("Uploading model..."); + _progressDialog->setLayout(new QGridLayout(_progressDialog)); + _progressDialog->layout()->addWidget(_progressBar); + + _progressDialog->exec(); + + delete _progressDialog; + _progressDialog = NULL; + _progressBar = NULL; +} + +void ModelUploader::checkJSON(const QJsonObject& jsonResponse) { + if (jsonResponse.contains("status") && jsonResponse.value("status").toString() == "success") { + qDebug() << "status : success"; + JSONCallbackParameters callbackParams; + callbackParams.jsonCallbackReceiver = this; + callbackParams.jsonCallbackMethod = "uploadSuccess"; + callbackParams.errorCallbackReceiver = this; + callbackParams.errorCallbackMethod = "uploadFailed"; + callbackParams.updateReciever = this; + callbackParams.updateSlot = SLOT(uploadUpdate(qint64, qint64)); + + if (jsonResponse.contains("exists") && jsonResponse.value("exists").toBool()) { + qDebug() << "exists : true"; + if (jsonResponse.contains("can_update") && jsonResponse.value("can_update").toBool()) { + qDebug() << "can_update : true"; + + AccountManager::getInstance().authenticatedRequest(MODEL_URL + "/" + QFileInfo(_url).baseName(), + QNetworkAccessManager::PutOperation, + callbackParams, + QByteArray(), + _dataMultiPart); + _dataMultiPart = NULL; + } else { + qDebug() << "can_update : false"; + if (_progressDialog) { + _progressDialog->reject(); + } + QMessageBox::warning(NULL, + QString("ModelUploader::checkJSON()"), + QString("This model already exist and is own by someone else."), + QMessageBox::Ok); + deleteLater(); + } + } else { + qDebug() << "exists : false"; + AccountManager::getInstance().authenticatedRequest(MODEL_URL, + QNetworkAccessManager::PostOperation, + callbackParams, + QByteArray(), + _dataMultiPart); + _dataMultiPart = NULL; + } + } else { + qDebug() << "status : failed"; + if (_progressDialog) { + _progressDialog->reject(); + } + QMessageBox::warning(NULL, + QString("ModelUploader::checkJSON()"), + QString("Something went wrong with the data-server."), + QMessageBox::Ok); + deleteLater(); + } +} + +void ModelUploader::uploadUpdate(qint64 bytesSent, qint64 bytesTotal) { + if (_progressDialog) { + _progressBar->setRange(0, bytesTotal); + _progressBar->setValue(bytesSent); + } +} + +void ModelUploader::uploadSuccess(const QJsonObject& jsonResponse) { + if (_progressDialog) { + _progressDialog->accept(); + } + QMessageBox::information(NULL, + QString("ModelUploader::uploadSuccess()"), + QString("We are reading your model information."), + QMessageBox::Ok); + qDebug() << "Model sent with success"; + checkS3(); +} + +void ModelUploader::uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString) { + if (_progressDialog) { + _progressDialog->reject(); + } + qDebug() << "Model upload failed (" << errorCode << "): " << errorString; + QMessageBox::warning(NULL, + QString("ModelUploader::uploadFailed()"), + QString("There was a problem with your upload, please try again later."), + QMessageBox::Ok); + deleteLater(); +} + +void ModelUploader::checkS3() { + qDebug() << "Checking S3 for " << _url; + QNetworkRequest request(_url); + QNetworkReply* reply = _networkAccessManager.head(request); + connect(reply, SIGNAL(finished()), SLOT(processCheck())); +} + +void ModelUploader::processCheck() { + QNetworkReply* reply = static_cast(sender()); + _timer.stop(); + + switch (reply->error()) { + case QNetworkReply::NoError: + QMessageBox::information(NULL, + QString("ModelUploader::processCheck()"), + QString("Your model is now available in the browser."), + QMessageBox::Ok); + deleteLater(); + break; + case QNetworkReply::ContentNotFoundError: + if (--_numberOfChecks) { + _timer.start(TIMEOUT); + break; + } + default: + QMessageBox::warning(NULL, + QString("ModelUploader::processCheck()"), + QString("We could not verify that your model was sent sucessfully\n" + "but it may have. If you do not see it in the model browser, try to upload again."), + QMessageBox::Ok); + deleteLater(); + break; + } + + delete reply; +} + +bool ModelUploader::addTextures(const QString& texdir, const QString fbxFile) { + QFile fbx(fbxFile); + if (!fbx.open(QIODevice::ReadOnly)) { + return false; + } + + QByteArray buffer = fbx.readAll(); + QVariantHash variantHash = readMapping(buffer); + FBXGeometry geometry = readFBX(buffer, variantHash); + + foreach (FBXMesh mesh, geometry.meshes) { + foreach (FBXMeshPart part, mesh.parts) { + if (!part.diffuseTexture.filename.isEmpty()) { + if (!addPart(texdir + "/" + part.diffuseTexture.filename, + QString("texture%1").arg(++_texturesCount))) { + return false; + } + } + if (!part.normalTexture.filename.isEmpty()) { + if (!addPart(texdir + "/" + part.normalTexture.filename, + QString("texture%1").arg(++_texturesCount))) { + return false; + } + } + } + } + + return true; +} + +bool ModelUploader::addPart(const QString &path, const QString& name) { + QFile file(path); + if (!file.open(QIODevice::ReadOnly)) { + QMessageBox::warning(NULL, + QString("ModelUploader::addPart()"), + QString("Could not open %1").arg(path), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("Could not open %1").arg(path); + return false; + } + QByteArray buffer = qCompress(file.readAll()); + + // Qt's qCompress() default compression level (-1) is the standard zLib compression. + // Here remove Qt's custom header that prevent the data server from uncompressing the files with zLib. + buffer.remove(QCOMPRESS_HEADER_POSITION, QCOMPRESS_HEADER_SIZE); + + QHttpPart part; + part.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data;" + " name=\"" + name.toUtf8() + "\";" + " filename=\"" + QFileInfo(file).fileName().toUtf8() + "\"")); + part.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream")); + part.setBody(buffer); + _dataMultiPart->append(part); + + + qDebug() << "File " << QFileInfo(file).fileName() << " added to model."; + _totalSize += file.size(); + if (_totalSize > MAX_SIZE) { + QMessageBox::warning(NULL, + QString("ModelUploader::zip()"), + QString("Model too big, over %1 Bytes.").arg(MAX_SIZE), + QMessageBox::Ok); + qDebug() << "[Warning] " << QString("Model too big, over %1 Bytes.").arg(MAX_SIZE); + return false; + } + qDebug() << "Current model size: " << _totalSize; + + return true; +} + + + + + + diff --git a/interface/src/ModelUploader.h b/interface/src/ModelUploader.h new file mode 100644 index 0000000000..54702d6420 --- /dev/null +++ b/interface/src/ModelUploader.h @@ -0,0 +1,63 @@ +// +// ModelUploader.h +// interface/src +// +// Created by Clément Brisset on 3/4/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_ModelUploader_h +#define hifi_ModelUploader_h + +#include + +class QDialog; +class QFileInfo; +class QHttpMultiPart; +class QProgressBar; + +class ModelUploader : public QObject { + Q_OBJECT + +public: + ModelUploader(bool isHead); + ~ModelUploader(); + +public slots: + void send(); + +private slots: + void checkJSON(const QJsonObject& jsonResponse); + void uploadUpdate(qint64 bytesSent, qint64 bytesTotal); + void uploadSuccess(const QJsonObject& jsonResponse); + void uploadFailed(QNetworkReply::NetworkError errorCode, const QString& errorString); + void checkS3(); + void processCheck(); + +private: + QString _url; + int _lodCount; + int _texturesCount; + int _totalSize; + bool _isHead; + bool _readyToSend; + + QHttpMultiPart* _dataMultiPart; + QNetworkAccessManager _networkAccessManager; + + int _numberOfChecks; + QTimer _timer; + + QDialog* _progressDialog; + QProgressBar* _progressBar; + + + bool zip(); + bool addTextures(const QString& texdir, const QString fbxFile); + bool addPart(const QString& path, const QString& name); +}; + +#endif // hifi_ModelUploader_h diff --git a/interface/src/ParticleTreeRenderer.cpp b/interface/src/ParticleTreeRenderer.cpp index 59784c8c1f..aa498082d9 100644 --- a/interface/src/ParticleTreeRenderer.cpp +++ b/interface/src/ParticleTreeRenderer.cpp @@ -1,10 +1,12 @@ // // ParticleTreeRenderer.cpp -// hifi +// interface/src // // Created by Brad Hefta-Gaub on 12/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/interface/src/ParticleTreeRenderer.h b/interface/src/ParticleTreeRenderer.h index fe346ae730..7dc866c731 100644 --- a/interface/src/ParticleTreeRenderer.h +++ b/interface/src/ParticleTreeRenderer.h @@ -1,14 +1,16 @@ // // ParticleTreeRenderer.h -// hifi +// interface/src // // Created by Brad Hefta-Gaub on 12/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__ParticleTreeRenderer__ -#define __hifi__ParticleTreeRenderer__ +#ifndef hifi_ParticleTreeRenderer_h +#define hifi_ParticleTreeRenderer_h #include #include @@ -49,4 +51,4 @@ protected: QMap _particleModels; }; -#endif /* defined(__hifi__ParticleTreeRenderer__) */ \ No newline at end of file +#endif // hifi_ParticleTreeRenderer_h diff --git a/interface/src/Physics.cpp b/interface/src/Physics.cpp index 101087b1ef..30ea829c4e 100644 --- a/interface/src/Physics.cpp +++ b/interface/src/Physics.cpp @@ -1,10 +1,11 @@ // // Physics.cpp -// hifi +// interface/src // -// Created by Philip on July 11, 2013 +// Copyright 2013 High Fidelity, Inc. // -// Routines to help with doing virtual world physics +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include @@ -40,5 +41,4 @@ void applyDamping(float deltaTime, glm::vec3& velocity, float linearStrength, fl void applyDampedSpring(float deltaTime, glm::vec3& velocity, glm::vec3& position, glm::vec3& targetPosition, float k, float damping) { -} - +} \ No newline at end of file diff --git a/interface/src/Physics.h b/interface/src/Physics.h index 699497c187..97e873d920 100644 --- a/interface/src/Physics.h +++ b/interface/src/Physics.h @@ -1,9 +1,12 @@ // -// Balls.h -// hifi +// Physics.h +// interface/src // // Created by Philip on 4/25/13. +// Copyright 2013 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_Physics_h @@ -12,4 +15,4 @@ void applyStaticFriction(float deltaTime, glm::vec3& velocity, float maxVelocity, float strength); void applyDamping(float deltaTime, glm::vec3& velocity, float linearStrength, float squaredStrength); -#endif +#endif // hifi_Physics_h diff --git a/interface/src/ScriptHighlighting.cpp b/interface/src/ScriptHighlighting.cpp new file mode 100644 index 0000000000..3ab1394097 --- /dev/null +++ b/interface/src/ScriptHighlighting.cpp @@ -0,0 +1,95 @@ +// +// ScriptHighlighting.cpp +// interface/src +// +// Created by Thijs Wenker on 4/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 "ScriptHighlighting.h" +#include + +ScriptHighlighting::ScriptHighlighting(QTextDocument* parent) : + QSyntaxHighlighter(parent) +{ + _keywordRegex = QRegExp("\\b(break|case|catch|continue|debugger|default|delete|do|else|finally|for|function|if|in|instanceof|new|return|switch|this|throw|try|typeof|var|void|while|with)\\b"); + _qoutedTextRegex = QRegExp("\".*\""); + _multiLineCommentBegin = QRegExp("/\\*"); + _multiLineCommentEnd = QRegExp("\\*/"); + _numberRegex = QRegExp("[0-9]+(\\.[0-9]+){0,1}"); + _singleLineComment = QRegExp("//[^\n]*"); + _truefalseRegex = QRegExp("\\b(true|false)\\b"); +} + +void ScriptHighlighting::highlightBlock(const QString& text) { + this->highlightKeywords(text); + this->formatNumbers(text); + this->formatTrueFalse(text); + this->formatQoutedText(text); + this->formatComments(text); +} + +void ScriptHighlighting::highlightKeywords(const QString& text) { + int index = _keywordRegex.indexIn(text); + while (index >= 0) { + int length = _keywordRegex.matchedLength(); + setFormat(index, length, Qt::blue); + index = _keywordRegex.indexIn(text, index + length); + } +} + +void ScriptHighlighting::formatComments(const QString& text) { + + setCurrentBlockState(BlockStateClean); + + int start = (previousBlockState() != BlockStateInMultiComment) ? text.indexOf(_multiLineCommentBegin) : 0; + + while (start > -1) { + int end = text.indexOf(_multiLineCommentEnd, start); + int length = (end == -1 ? text.length() : (end + _multiLineCommentEnd.matchedLength())) - start; + setFormat(start, length, Qt::lightGray); + start = text.indexOf(_multiLineCommentBegin, start + length); + if (end == -1) { + setCurrentBlockState(BlockStateInMultiComment); + } + } + + int index = _singleLineComment.indexIn(text); + while (index >= 0) { + int length = _singleLineComment.matchedLength(); + setFormat(index, length, Qt::lightGray); + index = _singleLineComment.indexIn(text, index + length); + } +} + +void ScriptHighlighting::formatQoutedText(const QString& text){ + int index = _qoutedTextRegex.indexIn(text); + while (index >= 0) { + int length = _qoutedTextRegex.matchedLength(); + setFormat(index, length, Qt::red); + index = _qoutedTextRegex.indexIn(text, index + length); + } +} + +void ScriptHighlighting::formatNumbers(const QString& text){ + int index = _numberRegex.indexIn(text); + while (index >= 0) { + int length = _numberRegex.matchedLength(); + setFormat(index, length, Qt::green); + index = _numberRegex.indexIn(text, index + length); + } +} + +void ScriptHighlighting::formatTrueFalse(const QString& text){ + int index = _truefalseRegex.indexIn(text); + while (index >= 0) { + int length = _truefalseRegex.matchedLength(); + QFont* font = new QFont(this->document()->defaultFont()); + font->setBold(true); + setFormat(index, length, *font); + index = _truefalseRegex.indexIn(text, index + length); + } +} diff --git a/interface/src/ScriptHighlighting.h b/interface/src/ScriptHighlighting.h new file mode 100644 index 0000000000..d86d6f4d77 --- /dev/null +++ b/interface/src/ScriptHighlighting.h @@ -0,0 +1,46 @@ +// +// ScriptHighlighting.h +// interface/src +// +// Created by Thijs Wenker on 4/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_ScriptHighlighting_h +#define hifi_ScriptHighlighting_h + +#include + +class ScriptHighlighting : public QSyntaxHighlighter { + Q_OBJECT + +public: + ScriptHighlighting(QTextDocument* parent = NULL); + + enum BlockState { + BlockStateClean, + BlockStateInMultiComment + }; + +protected: + void highlightBlock(const QString& text); + void highlightKeywords(const QString& text); + void formatComments(const QString& text); + void formatQoutedText(const QString& text); + void formatNumbers(const QString& text); + void formatTrueFalse(const QString& text); + +private: + QRegExp _keywordRegex; + QRegExp _qoutedTextRegex; + QRegExp _multiLineCommentBegin; + QRegExp _multiLineCommentEnd; + QRegExp _numberRegex; + QRegExp _singleLineComment; + QRegExp _truefalseRegex; +}; + +#endif // hifi_ScriptHighlighting_h diff --git a/interface/src/Stars.cpp b/interface/src/Stars.cpp index dcfe726fff..e5662005d5 100755 --- a/interface/src/Stars.cpp +++ b/interface/src/Stars.cpp @@ -1,9 +1,12 @@ // -// Stars.cpp -// interface +// Stars.cpp +// interface/src // -// Created by Tobias Schwinger on 3/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/22/13. +// Copyright 2013 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 "InterfaceConfig.h" diff --git a/interface/src/Stars.h b/interface/src/Stars.h index f6fe7cfb80..08f218b9fa 100755 --- a/interface/src/Stars.h +++ b/interface/src/Stars.h @@ -1,13 +1,16 @@ // -// Stars.h -// interface +// Stars.h +// interface/src // -// Created by Tobias Schwinger on 3/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/22/13. +// Copyright 2013 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 __interface__Stars__ -#define __interface__Stars__ +#ifndef hifi_Stars_h +#define hifi_Stars_h #include @@ -49,5 +52,4 @@ private: }; -#endif - +#endif // hifi_Stars_h diff --git a/interface/src/Util.cpp b/interface/src/Util.cpp index 1921fe924b..79a2e31d80 100644 --- a/interface/src/Util.cpp +++ b/interface/src/Util.cpp @@ -1,9 +1,12 @@ // -// util.cpp -// interface +// Util.cpp +// interface/src // // Created by Philip Rosedale on 8/24/12. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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 @@ -60,7 +63,7 @@ void eulerToOrthonormals(glm::vec3 * angles, glm::vec3 * front, glm::vec3 * righ } void printVector(glm::vec3 vec) { - printf("%4.2f, %4.2f, %4.2f\n", vec.x, vec.y, vec.z); + qDebug("%4.2f, %4.2f, %4.2f", vec.x, vec.y, vec.z); } // Return the azimuth angle (in radians) between two points. @@ -75,112 +78,6 @@ float angle_to(glm::vec3 head_pos, glm::vec3 source_pos, float render_yaw, float return atan2(head_pos.x - source_pos.x, head_pos.z - source_pos.z) + render_yaw + head_yaw; } -// Helper function returns the positive angle (in radians) between two 3D vectors -float angleBetween(const glm::vec3& v1, const glm::vec3& v2) { - return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2))); -} - -// Helper function return the rotation from the first vector onto the second -glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { - float angle = angleBetween(v1, v2); - if (glm::isnan(angle) || angle < EPSILON) { - return glm::quat(); - } - glm::vec3 axis; - if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis - axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f)); - float axisLength = glm::length(axis); - if (axisLength < EPSILON) { // parallel to x; y will work - axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f))); - } else { - axis /= axisLength; - } - } else { - axis = glm::normalize(glm::cross(v1, v2)); - } - return glm::angleAxis(angle, axis); -} - - - -glm::vec3 extractTranslation(const glm::mat4& matrix) { - return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]); -} - -void setTranslation(glm::mat4& matrix, const glm::vec3& translation) { - matrix[3][0] = translation.x; - matrix[3][1] = translation.y; - matrix[3][2] = translation.z; -} - -glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { - // uses the iterative polar decomposition algorithm described by Ken Shoemake at - // http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf - // code adapted from Clyde, https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Matrix4f.java - - // start with the contents of the upper 3x3 portion of the matrix - glm::mat3 upper = glm::mat3(matrix); - if (!assumeOrthogonal) { - for (int i = 0; i < 10; i++) { - // store the results of the previous iteration - glm::mat3 previous = upper; - - // compute average of the matrix with its inverse transpose - float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2]; - float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2]; - float sd20 = previous[0][1] * previous[1][2] - previous[1][1] * previous[0][2]; - float det = previous[0][0] * sd00 + previous[2][0] * sd20 - previous[1][0] * sd10; - if (fabs(det) == 0.0f) { - // determinant is zero; matrix is not invertible - break; - } - float hrdet = 0.5f / det; - upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f; - upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f; - upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f; - - upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f; - upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f; - upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f; - - upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f; - upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f; - upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f; - - // compute the difference; if it's small enough, we're done - glm::mat3 diff = upper - previous; - if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] + - diff[1][1] * diff[1][1] + diff[2][1] * diff[2][1] + diff[0][2] * diff[0][2] + diff[1][2] * diff[1][2] + - diff[2][2] * diff[2][2] < EPSILON) { - break; - } - } - } - - // now that we have a nice orthogonal matrix, we can extract the rotation quaternion - // using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions - float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]); - float y2 = fabs(1.0f - upper[0][0] + upper[1][1] - upper[2][2]); - float z2 = fabs(1.0f - upper[0][0] - upper[1][1] + upper[2][2]); - float w2 = fabs(1.0f + upper[0][0] + upper[1][1] + upper[2][2]); - return glm::normalize(glm::quat(0.5f * sqrtf(w2), - 0.5f * sqrtf(x2) * (upper[1][2] >= upper[2][1] ? 1.0f : -1.0f), - 0.5f * sqrtf(y2) * (upper[2][0] >= upper[0][2] ? 1.0f : -1.0f), - 0.5f * sqrtf(z2) * (upper[0][1] >= upper[1][0] ? 1.0f : -1.0f))); -} - -glm::vec3 extractScale(const glm::mat4& matrix) { - return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2])); -} - -float extractUniformScale(const glm::mat4& matrix) { - return extractUniformScale(extractScale(matrix)); -} - -float extractUniformScale(const glm::vec3& scale) { - return (scale.x + scale.y + scale.z) / 3.0f; -} - // Draw a 3D vector floating in space void drawVector(glm::vec3 * vector) { glDisable(GL_LIGHTING); @@ -275,14 +172,6 @@ void renderWorldBox() { } -double diffclock(timeval *clock1,timeval *clock2) -{ - double diffms = (clock2->tv_sec - clock1->tv_sec) * 1000.0; - diffms += (clock2->tv_usec - clock1->tv_usec) / 1000.0; // us to ms - - return diffms; -} - // Return a random vector of average length 1 const glm::vec3 randVector() { return glm::vec3(randFloat() - 0.5f, randFloat() - 0.5f, randFloat() - 0.5f) * 2.f; @@ -348,10 +237,10 @@ void drawvec3(int x, int y, float scale, float radians, float thick, int mono, g glPopMatrix(); } -void renderCollisionOverlay(int width, int height, float magnitude) { +void renderCollisionOverlay(int width, int height, float magnitude, float red, float blue, float green) { const float MIN_VISIBLE_COLLISION = 0.01f; if (magnitude > MIN_VISIBLE_COLLISION) { - glColor4f(0, 0, 0, magnitude); + glColor4f(red, blue, green, magnitude); glBegin(GL_QUADS); glVertex2f(0, 0); glVertex2d(width, 0); @@ -514,69 +403,63 @@ void runTimingTests() { int iResults[numTests]; float fTest = 1.0; float fResults[numTests]; - timeval startTime, endTime; - float elapsedMsecs; - gettimeofday(&startTime, NULL); - for (int i = 1; i < numTests; i++) { - gettimeofday(&endTime, NULL); - } - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("gettimeofday() usecs: %f", 1000.0f * elapsedMsecs / (float) numTests); + QElapsedTimer startTime; + startTime.start(); + float elapsedUsecs; + + float NSEC_TO_USEC = 1.0f / 1000.0f; + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("QElapsedTimer::nsecElapsed() usecs: %f", elapsedUsecs / (float) numTests); // Random number generation - gettimeofday(&startTime, NULL); + startTime.start(); for (int i = 1; i < numTests; i++) { iResults[i] = rand(); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("rand() stored in array usecs: %f, first result:%d", 1000.0f * elapsedMsecs / (float) numTests, iResults[0]); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("rand() stored in array usecs: %f, first result:%d", elapsedUsecs / (float) numTests, iResults[0]); // Random number generation using randFloat() - gettimeofday(&startTime, NULL); + startTime.start(); for (int i = 1; i < numTests; i++) { fResults[i] = randFloat(); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("randFloat() stored in array usecs: %f, first result: %f", 1000.0f * elapsedMsecs / (float) numTests, fResults[0]); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("randFloat() stored in array usecs: %f, first result: %f", elapsedUsecs / (float) numTests, fResults[0]); // PowF function fTest = 1145323.2342f; - gettimeofday(&startTime, NULL); + startTime.start(); for (int i = 1; i < numTests; i++) { fTest = powf(fTest, 0.5f); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("powf(f, 0.5) usecs: %f", 1000.0f * elapsedMsecs / (float) numTests); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("powf(f, 0.5) usecs: %f", elapsedUsecs / (float) numTests); // Vector Math float distance; glm::vec3 pointA(randVector()), pointB(randVector()); - gettimeofday(&startTime, NULL); + startTime.start(); for (int i = 1; i < numTests; i++) { //glm::vec3 temp = pointA - pointB; //float distanceSquared = glm::dot(temp, temp); distance = glm::distance(pointA, pointB); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("vector math usecs: %f [%f msecs total for %d tests], last result:%f", - 1000.0f * elapsedMsecs / (float) numTests, elapsedMsecs, numTests, distance); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("vector math usecs: %f [%f usecs total for %d tests], last result:%f", + elapsedUsecs / (float) numTests, elapsedUsecs, numTests, distance); // Vec3 test glm::vec3 vecA(randVector()), vecB(randVector()); float result; - - gettimeofday(&startTime, NULL); + + startTime.start(); for (int i = 1; i < numTests; i++) { glm::vec3 temp = vecA-vecB; result = glm::dot(temp,temp); } - gettimeofday(&endTime, NULL); - elapsedMsecs = diffclock(&startTime, &endTime); - qDebug("vec3 assign and dot() usecs: %f, last result:%f", 1000.0f * elapsedMsecs / (float) numTests, result); + elapsedUsecs = (float)startTime.nsecsElapsed() * NSEC_TO_USEC; + qDebug("vec3 assign and dot() usecs: %f, last result:%f", elapsedUsecs / (float) numTests, result); } float loadSetting(QSettings* settings, const char* name, float defaultValue) { diff --git a/interface/src/Util.h b/interface/src/Util.h index 732a36b98d..02cfd99f9a 100644 --- a/interface/src/Util.h +++ b/interface/src/Util.h @@ -1,19 +1,16 @@ // // Util.h -// interface +// interface/src // // Created by Philip Rosedale on 8/24/12. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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 __interface__Util__ -#define __interface__Util__ - -#ifdef _WIN32 -#include "Systime.h" -#else -#include -#endif +#ifndef hifi_Util_h +#define hifi_Util_h #include #include @@ -41,25 +38,7 @@ void drawVector(glm::vec3* vector); void printVector(glm::vec3 vec); -float angleBetween(const glm::vec3& v1, const glm::vec3& v2); - -glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); - -glm::vec3 extractTranslation(const glm::mat4& matrix); - -void setTranslation(glm::mat4& matrix, const glm::vec3& translation); - -glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false); - -glm::vec3 extractScale(const glm::mat4& matrix); - -float extractUniformScale(const glm::mat4& matrix); - -float extractUniformScale(const glm::vec3& scale); - -double diffclock(timeval *clock1,timeval *clock2); - -void renderCollisionOverlay(int width, int height, float magnitude); +void renderCollisionOverlay(int width, int height, float magnitude, float red = 0, float blue = 0, float green = 0); void renderOrientationDirections( glm::vec3 position, const glm::quat& orientation, float size ); @@ -77,4 +56,4 @@ bool rayIntersectsSphere(const glm::vec3& rayStarting, const glm::vec3& rayNorma bool pointInSphere(glm::vec3& point, glm::vec3& sphereCenter, double sphereRadius); -#endif +#endif // hifi_Util_h diff --git a/interface/src/XmppClient.cpp b/interface/src/XmppClient.cpp index df8cf34874..d930c16b53 100644 --- a/interface/src/XmppClient.cpp +++ b/interface/src/XmppClient.cpp @@ -1,9 +1,12 @@ // // XmppClient.cpp -// interface +// interface/src // -// Created by Dimitar Dobrev on 10/3/14 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Dimitar Dobrev on 10/3/14. +// Copyright 2013 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 // #ifdef HAVE_QXMPP diff --git a/interface/src/XmppClient.h b/interface/src/XmppClient.h index 905d6e64fb..8af3204377 100644 --- a/interface/src/XmppClient.h +++ b/interface/src/XmppClient.h @@ -1,15 +1,18 @@ // // XmppClient.h -// interface +// interface/src // -// Created by Dimitar Dobrev on 10/3/14 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Dimitar Dobrev on 10/3/14. +// Copyright 2013 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 // #ifdef HAVE_QXMPP -#ifndef __interface__XmppClient__ -#define __interface__XmppClient__ +#ifndef hifi_XmppClient_h +#define hifi_XmppClient_h #include #include @@ -44,4 +47,4 @@ private: #endif // __interface__XmppClient__ -#endif +#endif // hifi_XmppClient_h diff --git a/interface/src/avatar/Avatar.cpp b/interface/src/avatar/Avatar.cpp index 16181f46b9..fe685b89f8 100644 --- a/interface/src/avatar/Avatar.cpp +++ b/interface/src/avatar/Avatar.cpp @@ -1,9 +1,13 @@ // // Avatar.cpp -// interface +// interface/src/avatar // // Created by Philip Rosedale on 9/11/12. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -46,18 +50,15 @@ Avatar::Avatar() : _mode(AVATAR_MODE_STANDING), _velocity(0.0f, 0.0f, 0.0f), _thrust(0.0f, 0.0f, 0.0f), - _speed(0.0f), _leanScale(0.5f), _scale(1.0f), _worldUpDirection(DEFAULT_UP_DIRECTION), _mouseRayOrigin(0.0f, 0.0f, 0.0f), _mouseRayDirection(0.0f, 0.0f, 0.0f), _moving(false), - _owningAvatarMixer(), _collisionFlags(0), _initialized(false), - _shouldRenderBillboard(true), - _modelsDirty(true) + _shouldRenderBillboard(true) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); @@ -118,24 +119,28 @@ void Avatar::simulate(float deltaTime) { getHand()->simulate(deltaTime, false); _skeletonModel.setLODDistance(getLODDistance()); - // copy joint data to skeleton - for (int i = 0; i < _jointData.size(); i++) { - const JointData& data = _jointData.at(i); - _skeletonModel.setJointState(i, data.valid, data.rotation); - } - glm::vec3 headPosition = _position; if (!_shouldRenderBillboard && inViewFrustum) { - _skeletonModel.simulate(deltaTime, _modelsDirty); - _modelsDirty = false; + if (_hasNewJointRotations) { + for (int i = 0; i < _jointData.size(); i++) { + const JointData& data = _jointData.at(i); + _skeletonModel.setJointState(i, data.valid, data.rotation); + } + _skeletonModel.simulate(deltaTime); + } + _skeletonModel.simulate(deltaTime, _hasNewJointRotations); + _hasNewJointRotations = false; + + glm::vec3 headPosition = _position; _skeletonModel.getHeadPosition(headPosition); + Head* head = getHead(); + head->setPosition(headPosition); + head->setScale(_scale); + head->simulate(deltaTime, false, _shouldRenderBillboard); } - Head* head = getHead(); - head->setPosition(headPosition); - head->setScale(_scale); - head->simulate(deltaTime, false, _shouldRenderBillboard); // use speed and angular velocity to determine walking vs. standing - if (_speed + fabs(_bodyYawDelta) > 0.2) { + float speed = glm::length(_velocity); + if (speed + fabs(_bodyYawDelta) > 0.2) { _mode = AVATAR_MODE_WALKING; } else { _mode = AVATAR_MODE_INTERACTING; @@ -204,37 +209,61 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { { // glow when moving far away const float GLOW_DISTANCE = 20.0f; - Glower glower(_moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE ? 1.0f : 0.0f); + const float GLOW_MAX_LOUDNESS = 2500.0f; + const float MAX_GLOW = 0.5f; + + float GLOW_FROM_AVERAGE_LOUDNESS = ((this == Application::getInstance()->getAvatar()) + ? 0.0f + : MAX_GLOW * getHeadData()->getAudioLoudness() / GLOW_MAX_LOUDNESS); + if (!Menu::getInstance()->isOptionChecked(MenuOption::GlowWhenSpeaking)) { + GLOW_FROM_AVERAGE_LOUDNESS = 0.0f; + } + + Glower glower(_moving && distanceToTarget > GLOW_DISTANCE && renderMode == NORMAL_RENDER_MODE + ? 1.0f + : GLOW_FROM_AVERAGE_LOUDNESS); // render body if (Menu::getInstance()->isOptionChecked(MenuOption::Avatars)) { renderBody(renderMode); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) { - _skeletonModel.renderCollisionProxies(0.7f); + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { + _skeletonModel.updateShapePositions(); + _skeletonModel.renderJointCollisionShapes(0.7f); } - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionProxies)) { - getHead()->getFaceModel().renderCollisionProxies(0.7f); + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderHeadCollisionShapes)) { + if (shouldRenderHead(cameraPosition, renderMode)) { + getHead()->getFaceModel().updateShapePositions(); + getHead()->getFaceModel().renderJointCollisionShapes(0.7f); + } + } + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderBoundingCollisionShapes)) { + if (shouldRenderHead(cameraPosition, renderMode)) { + getHead()->getFaceModel().updateShapePositions(); + getHead()->getFaceModel().renderBoundingCollisionShapes(0.7f); + _skeletonModel.updateShapePositions(); + _skeletonModel.renderBoundingCollisionShapes(0.7f); + } } // quick check before falling into the code below: // (a 10 degree breadth of an almost 2 meter avatar kicks in at about 12m) - const float MIN_VOICE_SPHERE_DISTANCE = 12.f; + const float MIN_VOICE_SPHERE_DISTANCE = 12.0f; if (distanceToTarget > MIN_VOICE_SPHERE_DISTANCE) { // render voice intensity sphere for avatars that are farther away - const float MAX_SPHERE_ANGLE = 10.f * RADIANS_PER_DEGREE; - const float MIN_SPHERE_ANGLE = 1.f * RADIANS_PER_DEGREE; + const float MAX_SPHERE_ANGLE = 10.0f * RADIANS_PER_DEGREE; + const float MIN_SPHERE_ANGLE = 1.0f * RADIANS_PER_DEGREE; const float MIN_SPHERE_SIZE = 0.01f; const float SPHERE_LOUDNESS_SCALING = 0.0005f; const float SPHERE_COLOR[] = { 0.5f, 0.8f, 0.8f }; float height = getSkeletonHeight(); - glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.f; + glm::vec3 delta = height * (getHead()->getCameraOrientation() * IDENTITY_UP) / 2.0f; float angle = abs(angleBetween(toTarget + delta, toTarget - delta)); float sphereRadius = getHead()->getAverageLoudness() * SPHERE_LOUDNESS_SCALING; if (renderMode == NORMAL_RENDER_MODE && (sphereRadius > MIN_SPHERE_SIZE) && (angle < MAX_SPHERE_ANGLE) && (angle > MIN_SPHERE_ANGLE)) { - glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.f - angle / MAX_SPHERE_ANGLE); + glColor4f(SPHERE_COLOR[0], SPHERE_COLOR[1], SPHERE_COLOR[2], 1.0f - angle / MAX_SPHERE_ANGLE); glPushMatrix(); glTranslatef(_position.x, _position.y, _position.z); glScalef(height, height, height); @@ -266,9 +295,9 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { glm::vec3 chatAxis = glm::axis(chatRotation); glRotatef(glm::degrees(glm::angle(chatRotation)), chatAxis.x, chatAxis.y, chatAxis.z); - glColor3f(0.f, 0.8f, 0.f); - glRotatef(180.f, 0.f, 1.f, 0.f); - glRotatef(180.f, 0.f, 0.f, 1.f); + glColor3f(0.0f, 0.8f, 0.0f); + glRotatef(180.0f, 0.0f, 1.0f, 0.0f); + glRotatef(180.0f, 0.0f, 0.0f, 1.0f); glScalef(_scale * CHAT_MESSAGE_SCALE, _scale * CHAT_MESSAGE_SCALE, 1.0f); glDisable(GL_LIGHTING); @@ -284,7 +313,7 @@ void Avatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { _chatMessage[lastIndex] = '\0'; textRenderer(CHAT)->draw(-width / 2.0f, 0, _chatMessage.c_str()); _chatMessage[lastIndex] = lastChar; - glColor3f(0.f, 1.f, 0.f); + glColor3f(0.0f, 1.0f, 0.0f); textRenderer(CHAT)->draw(width / 2.0f - lastWidth, 0, _chatMessage.c_str() + lastIndex); } glEnable(GL_LIGHTING); @@ -316,11 +345,21 @@ void Avatar::renderBody(RenderMode renderMode) { renderBillboard(); return; } - _skeletonModel.render(1.0f, renderMode == SHADOW_RENDER_MODE); - getHead()->render(1.0f, renderMode == SHADOW_RENDER_MODE); + Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? + Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + _skeletonModel.render(1.0f, modelRenderMode); + getHead()->render(1.0f, modelRenderMode); getHand()->render(false); } +bool Avatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { + return true; +} + +void Avatar::updateJointMappings() { + // no-op; joint mappings come from skeleton model +} + void Avatar::renderBillboard() { if (_billboard.isEmpty()) { return; @@ -502,6 +541,11 @@ bool Avatar::findSphereCollisions(const glm::vec3& penetratorCenter, float penet //return getHead()->getFaceModel().findSphereCollisions(penetratorCenter, penetratorRadius, collisions); } +bool Avatar::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) { + return _skeletonModel.findPlaneCollisions(plane, collisions) || + getHead()->getFaceModel().findPlaneCollisions(plane, collisions); +} + void Avatar::updateShapePositions() { _skeletonModel.updateShapePositions(); Model& headModel = getHead()->getFaceModel(); @@ -530,7 +574,7 @@ bool Avatar::findParticleCollisions(const glm::vec3& particleCenter, float parti const PalmData* palm = handData->getPalm(i); if (palm && palm->hasPaddle()) { // create a disk collision proxy where the hand is - glm::vec3 fingerAxis(0.f); + glm::vec3 fingerAxis(0.0f); for (size_t f = 0; f < palm->getNumFingers(); ++f) { const FingerData& finger = (palm->getFingers())[f]; if (finger.isActive()) { @@ -645,6 +689,11 @@ void Avatar::setBillboard(const QByteArray& billboard) { } int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) { + if (!_initialized) { + // now that we have data for this Avatar we are go for init + init(); + } + // change in position implies movement glm::vec3 oldPosition = _position; @@ -653,9 +702,6 @@ int Avatar::parseDataAtOffset(const QByteArray& packet, int offset) { const float MOVE_DISTANCE_THRESHOLD = 0.001f; _moving = glm::distance(oldPosition, _position) > MOVE_DISTANCE_THRESHOLD; - // note that we need to update our models - _modelsDirty = true; - return bytesRead; } @@ -675,8 +721,8 @@ void Avatar::renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, glm::vec3 perpCos = glm::normalize(glm::cross(axis, perpSin)); perpSin = glm::cross(perpCos, axis); - float anglea = 0.f; - float angleb = 0.f; + float anglea = 0.0f; + float angleb = 0.0f; for (int i = 0; i < NUM_BODY_CONE_SIDES; i ++) { @@ -726,8 +772,8 @@ void Avatar::updateCollisionFlags() { void Avatar::setScale(float scale) { _scale = scale; - if (_targetScale * (1.f - RESCALING_TOLERANCE) < _scale && - _scale < _targetScale * (1.f + RESCALING_TOLERANCE)) { + if (_targetScale * (1.0f - RESCALING_TOLERANCE) < _scale && + _scale < _targetScale * (1.0f + RESCALING_TOLERANCE)) { _scale = _targetScale; } } diff --git a/interface/src/avatar/Avatar.h b/interface/src/avatar/Avatar.h index f2ee400ba2..ecf1be4899 100755 --- a/interface/src/avatar/Avatar.h +++ b/interface/src/avatar/Avatar.h @@ -1,12 +1,15 @@ // // Avatar.h -// interface +// interface/src/avatar // -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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 __interface__avatar__ -#define __interface__avatar__ +#ifndef hifi_Avatar_h +#define hifi_Avatar_h #include #include @@ -61,7 +64,7 @@ enum ScreenTintLayer { // Where one's own Avatar begins in the world (will be overwritten if avatar data file is found) // this is basically in the center of the ground plane. Slightly adjusted. This was asked for by // Grayson as he's building a street around here for demo dinner 2 -const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.f, 0.5f * TREE_SCALE); +const glm::vec3 START_LOCATION(0.485f * TREE_SCALE, 0.0f, 0.5f * TREE_SCALE); class Texture; @@ -96,9 +99,6 @@ public: /// Returns the distance to use as a LOD parameter. float getLODDistance() const; - - Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); } - void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; @@ -116,6 +116,12 @@ public: bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions, int skeletonSkipIndex = -1); + /// Checks for penetration between the described plane and the avatar. + /// \param plane the penetration plane + /// \param collisions[out] a list to which collisions get appended + /// \return whether or not the plane penetrated + bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions); + /// Checks for collision between the a spherical particle and the avatar (including paddle hands) /// \param collisionCenter the center of particle's bounding sphere /// \param collisionRadius the radius of particle's bounding sphere @@ -140,8 +146,6 @@ public: static void renderJointConnectingCone(glm::vec3 position1, glm::vec3 position2, float radius1, float radius2); - - /// \return true if we expect the avatar would move as a result of the collision bool collisionWouldMoveAvatar(CollisionInfo& collision) const; @@ -154,13 +158,15 @@ public: public slots: void updateCollisionFlags(); +signals: + void collisionWithAvatar(const QUuid& myUUID, const QUuid& theirUUID, const CollisionInfo& collision); + protected: SkeletonModel _skeletonModel; float _bodyYawDelta; AvatarMode _mode; glm::vec3 _velocity; glm::vec3 _thrust; - float _speed; float _leanScale; float _scale; glm::vec3 _worldUpDirection; @@ -168,7 +174,6 @@ protected: glm::vec3 _mouseRayDirection; float _stringLength; bool _moving; ///< set when position is changing - QWeakPointer _owningAvatarMixer; uint32_t _collisionFlags; @@ -186,17 +191,19 @@ protected: void renderDisplayName(); virtual void renderBody(RenderMode renderMode); + virtual bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; + + virtual void updateJointMappings(); private: bool _initialized; QScopedPointer _billboardTexture; bool _shouldRenderBillboard; - bool _modelsDirty; void renderBillboard(); float getBillboardSize() const; }; -#endif +#endif // hifi_Avatar_h diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index c2ba28ac7b..59f31388f8 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -1,10 +1,14 @@ // // AvatarManager.cpp -// hifi +// interface/src/avatar // // Created by Stephen Birarda on 1/23/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 #include @@ -47,14 +51,16 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { // simulate avatars AvatarHash::iterator avatarIterator = _avatarHash.begin(); while (avatarIterator != _avatarHash.end()) { - Avatar* avatar = static_cast(avatarIterator.value().data()); - if (avatar == static_cast(_myAvatar.data()) || !avatar->isInitialized()) { + AvatarSharedPointer sharedAvatar = avatarIterator.value(); + Avatar* avatar = reinterpret_cast(sharedAvatar.data()); + + if (sharedAvatar == _myAvatar || !avatar->isInitialized()) { // DO NOT update _myAvatar! Its update has already been done earlier in the main loop. // DO NOT update uninitialized Avatars ++avatarIterator; continue; } - if (avatar->getOwningAvatarMixer()) { + if (!shouldKillAvatar(sharedAvatar)) { // this avatar's mixer is still around, go ahead and simulate it avatar->simulate(deltaTime); avatar->setMouseRay(mouseOrigin, mouseDirection); @@ -123,127 +129,12 @@ void AvatarManager::renderAvatarFades(const glm::vec3& cameraPosition, Avatar::R } } -AvatarSharedPointer AvatarManager::matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer& mixerWeakPointer) { - AvatarSharedPointer matchingAvatar = _avatarHash.value(nodeUUID); - - if (!matchingAvatar) { - // construct a new Avatar for this node - Avatar* avatar = new Avatar(); - avatar->setOwningAvatarMixer(mixerWeakPointer); - - // insert the new avatar into our hash - matchingAvatar = AvatarSharedPointer(avatar); - _avatarHash.insert(nodeUUID, matchingAvatar); - - qDebug() << "Adding avatar with UUID" << nodeUUID << "to AvatarManager hash."; - } - - return matchingAvatar; -} - -void AvatarManager::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { - switch (packetTypeForPacket(datagram)) { - case PacketTypeBulkAvatarData: - processAvatarDataPacket(datagram, mixerWeakPointer); - break; - case PacketTypeAvatarIdentity: - processAvatarIdentityPacket(datagram, mixerWeakPointer); - break; - case PacketTypeAvatarBillboard: - processAvatarBillboardPacket(datagram, mixerWeakPointer); - break; - case PacketTypeKillAvatar: - processKillAvatar(datagram); - break; - default: - break; - } -} - -void AvatarManager::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer &mixerWeakPointer) { - int bytesRead = numBytesForPacketHeader(datagram); - - // enumerate over all of the avatars in this packet - // only add them if mixerWeakPointer points to something (meaning that mixer is still around) - while (bytesRead < datagram.size() && mixerWeakPointer.data()) { - QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID)); - bytesRead += NUM_BYTES_RFC4122_UUID; - - AvatarSharedPointer matchingAvatarData = matchingOrNewAvatar(nodeUUID, mixerWeakPointer); - - // have the matching (or new) avatar parse the data from the packet - bytesRead += matchingAvatarData->parseDataAtOffset(datagram, bytesRead); - - Avatar* matchingAvatar = reinterpret_cast(matchingAvatarData.data()); - - if (!matchingAvatar->isInitialized()) { - // now that we have AvatarData for this Avatar we are go for init - matchingAvatar->init(); - } - } -} - -void AvatarManager::processAvatarIdentityPacket(const QByteArray &packet, const QWeakPointer& mixerWeakPointer) { - // setup a data stream to parse the packet - QDataStream identityStream(packet); - identityStream.skipRawData(numBytesForPacketHeader(packet)); - - QUuid nodeUUID; - - while (!identityStream.atEnd()) { - - QUrl faceMeshURL, skeletonURL; - QString displayName; - identityStream >> nodeUUID >> faceMeshURL >> skeletonURL >> displayName; - - // mesh URL for a UUID, find avatar in our list - AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(nodeUUID, mixerWeakPointer); - if (matchingAvatar) { - Avatar* avatar = static_cast(matchingAvatar.data()); - - if (avatar->getFaceModelURL() != faceMeshURL) { - avatar->setFaceModelURL(faceMeshURL); - } - - if (avatar->getSkeletonModelURL() != skeletonURL) { - avatar->setSkeletonModelURL(skeletonURL); - } - - if (avatar->getDisplayName() != displayName) { - avatar->setDisplayName(displayName); - } - } - } -} - -void AvatarManager::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer) { - int headerSize = numBytesForPacketHeader(packet); - QUuid nodeUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID)); - - AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(nodeUUID, mixerWeakPointer); - if (matchingAvatar) { - Avatar* avatar = static_cast(matchingAvatar.data()); - QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID); - if (avatar->getBillboard() != billboard) { - avatar->setBillboard(billboard); - } - } -} - -void AvatarManager::processKillAvatar(const QByteArray& datagram) { - // read the node id - QUuid nodeUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID)); - - // remove the avatar with that UUID from our hash, if it exists - AvatarHash::iterator matchedAvatar = _avatarHash.find(nodeUUID); - if (matchedAvatar != _avatarHash.end()) { - erase(matchedAvatar); - } +AvatarSharedPointer AvatarManager::newSharedAvatar() { + return AvatarSharedPointer(new Avatar()); } AvatarHash::iterator AvatarManager::erase(const AvatarHash::iterator& iterator) { if (iterator.key() != MY_AVATAR_KEY) { - qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarManager hash."; if (reinterpret_cast(iterator.value().data())->isInitialized()) { _avatarFades.push_back(iterator.value()); } diff --git a/interface/src/avatar/AvatarManager.h b/interface/src/avatar/AvatarManager.h index bd04dddb78..048844ddf2 100644 --- a/interface/src/avatar/AvatarManager.h +++ b/interface/src/avatar/AvatarManager.h @@ -1,13 +1,16 @@ // // AvatarManager.h -// hifi +// interface/src/avatar // // Created by Stephen Birarda on 1/23/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__AvatarManager__ -#define __hifi__AvatarManager__ +#ifndef hifi_AvatarManager_h +#define hifi_AvatarManager_h #include #include @@ -19,7 +22,7 @@ class MyAvatar; -class AvatarManager : public QObject, public AvatarHashMap { +class AvatarManager : public AvatarHashMap { Q_OBJECT public: AvatarManager(QObject* parent = 0); @@ -32,23 +35,15 @@ public: void renderAvatars(Avatar::RenderMode renderMode, bool selfAvatarOnly = false); void clearOtherAvatars(); - -public slots: - void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer); private: AvatarManager(const AvatarManager& other); - - AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer& mixerWeakPointer); - - void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); - void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); - void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); - void processKillAvatar(const QByteArray& datagram); void simulateAvatarFades(float deltaTime); void renderAvatarFades(const glm::vec3& cameraPosition, Avatar::RenderMode renderMode); + AvatarSharedPointer newSharedAvatar(); + // virtual override AvatarHash::iterator erase(const AvatarHash::iterator& iterator); @@ -56,4 +51,4 @@ private: QSharedPointer _myAvatar; }; -#endif /* defined(__hifi__AvatarManager__) */ +#endif // hifi_AvatarManager_h diff --git a/interface/src/avatar/FaceModel.cpp b/interface/src/avatar/FaceModel.cpp index 19120d10be..709a9fc79d 100644 --- a/interface/src/avatar/FaceModel.cpp +++ b/interface/src/avatar/FaceModel.cpp @@ -1,9 +1,12 @@ // // FaceModel.cpp -// interface +// interface/src/avatar // // Created by Andrzej Kapolka on 9/16/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -18,11 +21,8 @@ FaceModel::FaceModel(Head* owningHead) : { } -void FaceModel::simulate(float deltaTime) { - QVector newJointStates = updateGeometry(); - if (!isActive()) { - return; - } +void FaceModel::simulate(float deltaTime, bool fullUpdate) { + updateGeometry(); Avatar* owningAvatar = static_cast(_owningHead->_owningAvatar); glm::vec3 neckPosition; if (!owningAvatar->getSkeletonModel().getNeckPosition(neckPosition)) { @@ -37,12 +37,13 @@ void FaceModel::simulate(float deltaTime) { const float MODEL_SCALE = 0.0006f; setScale(glm::vec3(1.0f, 1.0f, 1.0f) * _owningHead->getScale() * MODEL_SCALE); - setOffset(-_geometry->getFBXGeometry().neckPivot); - setPupilDilation(_owningHead->getPupilDilation()); setBlendshapeCoefficients(_owningHead->getBlendshapeCoefficients()); - Model::simulate(deltaTime, true, newJointStates); + if (isActive()) { + setOffset(-_geometry->getFBXGeometry().neckPivot); + Model::simulateInternal(deltaTime); + } } void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) { diff --git a/interface/src/avatar/FaceModel.h b/interface/src/avatar/FaceModel.h index acf2d2baf4..fdf8ab1fd2 100644 --- a/interface/src/avatar/FaceModel.h +++ b/interface/src/avatar/FaceModel.h @@ -1,13 +1,16 @@ // // FaceModel.h -// interface +// interface/src/avatar // // Created by Andrzej Kapolka on 9/16/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__FaceModel__ -#define __interface__FaceModel__ +#ifndef hifi_FaceModel_h +#define hifi_FaceModel_h #include "renderer/Model.h" @@ -21,7 +24,7 @@ public: FaceModel(Head* owningHead); - void simulate(float deltaTime); + virtual void simulate(float deltaTime, bool fullUpdate = true); protected: @@ -33,4 +36,4 @@ private: Head* _owningHead; }; -#endif /* defined(__interface__FaceModel__) */ +#endif // hifi_FaceModel_h diff --git a/interface/src/avatar/Hand.cpp b/interface/src/avatar/Hand.cpp index daf82f2599..0f0df8a484 100644 --- a/interface/src/avatar/Hand.cpp +++ b/interface/src/avatar/Hand.cpp @@ -1,8 +1,12 @@ // // Hand.cpp -// interface +// interface/src/avatar +// +// Copyright 2013 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 // -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #include @@ -159,6 +163,11 @@ void Hand::collideAgainstAvatar(Avatar* avatar, bool isMyHand) { // TODO: submit collision info to MyAvatar which should lean accordingly averageContactPoint /= (float)handCollisions.size(); avatar->applyCollision(averageContactPoint, totalPenetration); + + CollisionInfo collision; + collision._penetration = totalPenetration; + collision._contactPoint = averageContactPoint; + emit avatar->collisionWithAvatar(avatar->getSessionUUID(), _owningAvatar->getSessionUUID(), collision); } } } @@ -254,7 +263,7 @@ void Hand::render(bool isMine) { _renderAlpha = 1.0; - if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionProxies)) { + if (Menu::getInstance()->isOptionChecked(MenuOption::RenderSkeletonCollisionShapes)) { // draw a green sphere at hand joint location, which is actually near the wrist) for (size_t i = 0; i < getNumPalms(); i++) { PalmData& palm = getPalms()[i]; diff --git a/interface/src/avatar/Hand.h b/interface/src/avatar/Hand.h index 3be3bc0f77..1c857a198a 100755 --- a/interface/src/avatar/Hand.h +++ b/interface/src/avatar/Hand.h @@ -1,8 +1,11 @@ // // Hand.h -// interface +// interface/src/avatar // -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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_Hand_h @@ -81,5 +84,4 @@ private: void playSlaps(PalmData& palm, Avatar* avatar); }; -#endif - +#endif // hifi_Hand_h diff --git a/interface/src/avatar/Head.cpp b/interface/src/avatar/Head.cpp index ffa0975ccb..a213603a89 100644 --- a/interface/src/avatar/Head.cpp +++ b/interface/src/avatar/Head.cpp @@ -1,8 +1,12 @@ // // Head.cpp -// interface +// interface/src/avatar +// +// Copyright 2013 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 // -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #include @@ -14,6 +18,7 @@ #include "Head.h" #include "Menu.h" #include "Util.h" +#include "devices/OculusManager.h" using namespace std; @@ -59,17 +64,11 @@ void Head::reset() { void Head::simulate(float deltaTime, bool isMine, bool billboard) { // Update audio trailing average for rendering facial animations - Faceshift* faceshift = Application::getInstance()->getFaceshift(); - Visage* visage = Application::getInstance()->getVisage(); if (isMine) { - _isFaceshiftConnected = false; - if (faceshift->isActive()) { - _blendshapeCoefficients = faceshift->getBlendshapeCoefficients(); - _isFaceshiftConnected = true; - - } else if (visage->isActive()) { - _blendshapeCoefficients = visage->getBlendshapeCoefficients(); - _isFaceshiftConnected = true; + FaceTracker* faceTracker = Application::getInstance()->getActiveFaceTracker(); + if ((_isFaceshiftConnected = faceTracker)) { + _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients(); + _isFaceshiftConnected = true; } } @@ -151,8 +150,9 @@ void Head::simulate(float deltaTime, bool isMine, bool billboard) { const float BROW_LIFT_SCALE = 500.0f; const float JAW_OPEN_SCALE = 0.01f; const float JAW_OPEN_DEAD_ZONE = 0.75f; - faceshift->updateFakeCoefficients(_leftEyeBlink, _rightEyeBlink, min(1.0f, _browAudioLift * BROW_LIFT_SCALE), - glm::clamp(sqrt(_averageLoudness * JAW_OPEN_SCALE) - JAW_OPEN_DEAD_ZONE, 0.0f, 1.0f), _blendshapeCoefficients); + Application::getInstance()->getFaceshift()->updateFakeCoefficients(_leftEyeBlink, _rightEyeBlink, + min(1.0f, _browAudioLift * BROW_LIFT_SCALE), glm::clamp(sqrt(_averageLoudness * JAW_OPEN_SCALE) - + JAW_OPEN_DEAD_ZONE, 0.0f, 1.0f), _blendshapeCoefficients); } if (!isMine) { @@ -179,8 +179,8 @@ void Head::relaxLean(float deltaTime) { _deltaLeanForward *= relaxationFactor; } -void Head::render(float alpha, bool forShadowMap) { - if (_faceModel.render(alpha, forShadowMap) && _renderLookatVectors) { +void Head::render(float alpha, Model::RenderMode mode) { + if (_faceModel.render(alpha, mode) && _renderLookatVectors) { renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition); } } @@ -198,6 +198,9 @@ glm::quat Head::getFinalOrientation() const { } glm::quat Head::getCameraOrientation () const { + if (OculusManager::isConnected()) { + return getOrientation(); + } Avatar* owningAvatar = static_cast(_owningAvatar); return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.f, 0.0f))); } diff --git a/interface/src/avatar/Head.h b/interface/src/avatar/Head.h index 8a03cfc7ad..8c58b73ebd 100644 --- a/interface/src/avatar/Head.h +++ b/interface/src/avatar/Head.h @@ -1,8 +1,11 @@ // // Head.h -// interface +// interface/src/avatar // -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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_Head_h @@ -27,6 +30,8 @@ enum eyeContactTargets { MOUTH }; +const float EYE_EAR_GAP = 0.08f; + class Avatar; class ProgramObject; @@ -37,7 +42,7 @@ public: void init(); void reset(); void simulate(float deltaTime, bool isMine, bool billboard = false); - void render(float alpha, bool forShadowMap); + void render(float alpha, Model::RenderMode mode); void setScale(float scale); void setPosition(glm::vec3 position) { _position = position; } void setGravity(glm::vec3 gravity) { _gravity = gravity; } @@ -70,12 +75,17 @@ public: glm::quat getEyeRotation(const glm::vec3& eyePosition) const; + const glm::vec3& getRightEyePosition() const { return _rightEyePosition; } + const glm::vec3& getLeftEyePosition() const { return _leftEyePosition; } + glm::vec3 getRightEarPosition() const { return _rightEyePosition + (getRightDirection() * EYE_EAR_GAP) + (getFrontDirection() * -EYE_EAR_GAP); } + glm::vec3 getLeftEarPosition() const { return _leftEyePosition + (getRightDirection() * -EYE_EAR_GAP) + (getFrontDirection() * -EYE_EAR_GAP); } + FaceModel& getFaceModel() { return _faceModel; } const FaceModel& getFaceModel() const { return _faceModel; } const bool getReturnToCenter() const { return _returnHeadToCenter; } // Do you want head to try to return to center (depends on interface detected) float getAverageLoudness() const { return _averageLoudness; } - glm::vec3 calculateAverageEyePosition() { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * ONE_HALF; } + glm::vec3 calculateAverageEyePosition() const { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * ONE_HALF; } /// \return the point about which scaling occurs. glm::vec3 getScalePivot() const; @@ -137,4 +147,4 @@ private: friend class FaceModel; }; -#endif +#endif // hifi_Head_h diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 62ba24a384..e1d7463aa3 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1,9 +1,12 @@ // // MyAvatar.cpp -// interface +// interface/src/avatar // // Created by Mark Peng on 8/16/13. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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 @@ -11,6 +14,7 @@ #include +#include #include #include @@ -20,6 +24,8 @@ #include #include +#include + #include "Application.h" #include "Audio.h" #include "Environment.h" @@ -56,10 +62,12 @@ MyAvatar::MyAvatar() : _isThrustOn(false), _thrustMultiplier(1.0f), _moveTarget(0,0,0), + _lastBodyPenetration(0.0f), _moveTargetStepCounter(0), _lookAtTargetAvatar(), _shouldRender(true), - _billboardValid(false) + _billboardValid(false), + _oculusYawOffset(0.0f) { for (int i = 0; i < MAX_DRIVE_KEYS; i++) { _driveKeys[i] = 0.0f; @@ -82,10 +90,11 @@ void MyAvatar::reset() { _skeletonModel.reset(); getHead()->reset(); getHand()->reset(); + _oculusYawOffset = 0.0f; - setVelocity(glm::vec3(0,0,0)); - setThrust(glm::vec3(0,0,0)); - setOrientation(glm::quat(glm::vec3(0,0,0))); + setVelocity(glm::vec3(0.0f)); + setThrust(glm::vec3(0.0f)); + setOrientation(glm::quat(glm::vec3(0.0f))); } void MyAvatar::setMoveTarget(const glm::vec3 moveTarget) { @@ -108,8 +117,8 @@ void MyAvatar::update(float deltaTime) { // TODO? resurrect headMouse stuff? //glm::vec3 headVelocity = faceshift->getHeadAngularVelocity(); //// sets how quickly head angular rotation moves the head mouse - //const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.f; - //const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.f; + //const float HEADMOUSE_FACESHIFT_YAW_SCALE = 40.0f; + //const float HEADMOUSE_FACESHIFT_PITCH_SCALE = 30.0f; //_headMouseX -= headVelocity.y * HEADMOUSE_FACESHIFT_YAW_SCALE; //_headMouseY -= headVelocity.x * HEADMOUSE_FACESHIFT_PITCH_SCALE; // @@ -118,16 +127,6 @@ void MyAvatar::update(float deltaTime) { //_headMouseY = glm::clamp(_headMouseY, 0, _glWidget->height()); } - if (OculusManager::isConnected()) { - float yaw, pitch, roll; // these angles will be in radians - OculusManager::getEulerAngles(yaw, pitch, roll); - - // but these euler angles are stored in degrees - head->setBaseYaw(yaw * DEGREES_PER_RADIAN); - head->setBasePitch(pitch * DEGREES_PER_RADIAN); - head->setBaseRoll(roll * DEGREES_PER_RADIAN); - } - // Get audio loudness data from audio input device Audio* audio = Application::getInstance()->getAudio(); head->setAudioLoudness(audio->getLastInputLoudness()); @@ -150,15 +149,15 @@ void MyAvatar::simulate(float deltaTime) { _elapsedTimeSinceCollision += deltaTime; const float VELOCITY_MOVEMENT_TIMER_THRESHOLD = 0.2f; if (glm::length(_velocity) < VELOCITY_MOVEMENT_TIMER_THRESHOLD) { - _elapsedTimeMoving = 0.f; + _elapsedTimeMoving = 0.0f; _elapsedTimeStopped += deltaTime; } else { - _elapsedTimeStopped = 0.f; + _elapsedTimeStopped = 0.0f; _elapsedTimeMoving += deltaTime; } if (_scale != _targetScale) { - float scale = (1.f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; + float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; setScale(scale); Application::getInstance()->getCamera()->setScale(scale); } @@ -166,12 +165,6 @@ void MyAvatar::simulate(float deltaTime) { // Collect thrust forces from keyboard and devices updateThrust(deltaTime); - // copy velocity so we can use it later for acceleration - glm::vec3 oldVelocity = getVelocity(); - - // calculate speed - _speed = glm::length(_velocity); - // update the movement of the hand and process handshaking with other avatars... updateHandMovementAndTouching(deltaTime); @@ -192,31 +185,31 @@ void MyAvatar::simulate(float deltaTime) { // decay body rotation momentum const float BODY_SPIN_FRICTION = 7.5f; - float bodySpinMomentum = 1.f - BODY_SPIN_FRICTION * deltaTime; + float bodySpinMomentum = 1.0f - BODY_SPIN_FRICTION * deltaTime; if (bodySpinMomentum < 0.0f) { bodySpinMomentum = 0.0f; } _bodyPitchDelta *= bodySpinMomentum; _bodyYawDelta *= bodySpinMomentum; _bodyRollDelta *= bodySpinMomentum; float MINIMUM_ROTATION_RATE = 2.0f; - if (fabs(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { _bodyYawDelta = 0.f; } - if (fabs(_bodyRollDelta) < MINIMUM_ROTATION_RATE) { _bodyRollDelta = 0.f; } - if (fabs(_bodyPitchDelta) < MINIMUM_ROTATION_RATE) { _bodyPitchDelta = 0.f; } + if (fabs(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { _bodyYawDelta = 0.0f; } + if (fabs(_bodyRollDelta) < MINIMUM_ROTATION_RATE) { _bodyRollDelta = 0.0f; } + if (fabs(_bodyPitchDelta) < MINIMUM_ROTATION_RATE) { _bodyPitchDelta = 0.0f; } - const float MAX_STATIC_FRICTION_VELOCITY = 0.5f; - const float STATIC_FRICTION_STRENGTH = _scale * 20.f; - applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_VELOCITY, STATIC_FRICTION_STRENGTH); + const float MAX_STATIC_FRICTION_SPEED = 0.5f; + const float STATIC_FRICTION_STRENGTH = _scale * 20.0f; + applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_SPEED, STATIC_FRICTION_STRENGTH); // Damp avatar velocity const float LINEAR_DAMPING_STRENGTH = 0.5f; const float SPEED_BRAKE_POWER = _scale * 10.0f; const float SQUARED_DAMPING_STRENGTH = 0.007f; - const float SLOW_NEAR_RADIUS = 5.f; + const float SLOW_NEAR_RADIUS = 5.0f; float linearDamping = LINEAR_DAMPING_STRENGTH; - const float NEAR_AVATAR_DAMPING_FACTOR = 50.f; + const float NEAR_AVATAR_DAMPING_FACTOR = 50.0f; if (_distanceToNearestAvatar < _scale * SLOW_NEAR_RADIUS) { - linearDamping *= 1.f + NEAR_AVATAR_DAMPING_FACTOR * + linearDamping *= 1.0f + NEAR_AVATAR_DAMPING_FACTOR * ((SLOW_NEAR_RADIUS - _distanceToNearestAvatar) / SLOW_NEAR_RADIUS); } if (_speedBrakes) { @@ -225,35 +218,61 @@ void MyAvatar::simulate(float deltaTime) { applyDamping(deltaTime, _velocity, linearDamping, SQUARED_DAMPING_STRENGTH); } + if (OculusManager::isConnected()) { + // these angles will be in radians + float yaw, pitch, roll; + OculusManager::getEulerAngles(yaw, pitch, roll); + // ... so they need to be converted to degrees before we do math... + + // The neck is limited in how much it can yaw, so we check its relative + // yaw from the body and yaw the body if necessary. + yaw *= DEGREES_PER_RADIAN; + float bodyToHeadYaw = yaw - _oculusYawOffset; + const float MAX_NECK_YAW = 85.0f; // degrees + if ((fabs(bodyToHeadYaw) > 2.0f * MAX_NECK_YAW) && (yaw * _oculusYawOffset < 0.0f)) { + // We've wrapped around the range for yaw so adjust + // the measured yaw to be relative to _oculusYawOffset. + if (yaw > 0.0f) { + yaw -= 360.0f; + } else { + yaw += 360.0f; + } + bodyToHeadYaw = yaw - _oculusYawOffset; + } + + float delta = fabs(bodyToHeadYaw) - MAX_NECK_YAW; + if (delta > 0.0f) { + yaw = MAX_NECK_YAW; + if (bodyToHeadYaw < 0.0f) { + delta *= -1.0f; + bodyToHeadYaw = -MAX_NECK_YAW; + } else { + bodyToHeadYaw = MAX_NECK_YAW; + } + // constrain _oculusYawOffset to be within range [-180,180] + _oculusYawOffset = fmod((_oculusYawOffset + delta) + 180.0f, 360.0f) - 180.0f; + + // We must adjust the body orientation using a delta rotation (rather than + // doing yaw math) because the body's yaw ranges are not the same + // as what the Oculus API provides. + glm::vec3 UP_AXIS = glm::vec3(0.0f, 1.0f, 0.0f); + glm::quat bodyCorrection = glm::angleAxis(glm::radians(delta), UP_AXIS); + orientation = orientation * bodyCorrection; + } + Head* head = getHead(); + head->setBaseYaw(bodyToHeadYaw); + + head->setBasePitch(pitch * DEGREES_PER_RADIAN); + head->setBaseRoll(roll * DEGREES_PER_RADIAN); + } + // update the euler angles setOrientation(orientation); - // Compute instantaneous acceleration - float forwardAcceleration = glm::length(glm::dot(getBodyFrontDirection(), getVelocity() - oldVelocity)) / deltaTime; - const float OCULUS_ACCELERATION_PULL_THRESHOLD = 1.0f; - const int OCULUS_YAW_OFFSET_THRESHOLD = 10; - - if (!Application::getInstance()->getFaceshift()->isActive() && OculusManager::isConnected() && - fabsf(forwardAcceleration) > OCULUS_ACCELERATION_PULL_THRESHOLD && - fabs(getHead()->getBaseYaw()) > OCULUS_YAW_OFFSET_THRESHOLD) { - - // if we're wearing the oculus - // and this acceleration is above the pull threshold - // and the head yaw if off the body by more than OCULUS_YAW_OFFSET_THRESHOLD - - // match the body yaw to the oculus yaw - _bodyYaw = getAbsoluteHeadYaw(); - - // set the head yaw to zero for this draw - getHead()->setBaseYaw(0); - - // correct the oculus yaw offset - OculusManager::updateYawOffset(); - } - const float WALKING_SPEED_THRESHOLD = 0.2f; // use speed and angular velocity to determine walking vs. standing - if (_speed + fabs(_bodyYawDelta) > WALKING_SPEED_THRESHOLD) { + float speed = glm::length(_velocity); + if (speed + fabs(_bodyYawDelta) > WALKING_SPEED_THRESHOLD) { _mode = AVATAR_MODE_WALKING; } else { _mode = AVATAR_MODE_INTERACTING; @@ -261,11 +280,11 @@ void MyAvatar::simulate(float deltaTime) { // update moving flag based on speed const float MOVING_SPEED_THRESHOLD = 0.01f; - _moving = _speed > MOVING_SPEED_THRESHOLD; + _moving = speed > MOVING_SPEED_THRESHOLD; // If a move target is set, update position explicitly const float MOVE_FINISHED_TOLERANCE = 0.1f; - const float MOVE_SPEED_FACTOR = 2.f; + const float MOVE_SPEED_FACTOR = 2.0f; const int MOVE_TARGET_MAX_STEPS = 250; if ((glm::length(_moveTarget) > EPSILON) && (_moveTargetStepCounter < MOVE_TARGET_MAX_STEPS)) { if (glm::length(_position - _moveTarget) > MOVE_FINISHED_TOLERANCE) { @@ -305,7 +324,7 @@ void MyAvatar::simulate(float deltaTime) { head->simulate(deltaTime, true); // Zero thrust out now that we've added it to velocity in this frame - _thrust = glm::vec3(0, 0, 0); + _thrust = glm::vec3(0.0f); // now that we're done stepping the avatar forward in time, compute new collisions if (_collisionFlags != 0) { @@ -313,18 +332,20 @@ void MyAvatar::simulate(float deltaTime) { float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE; if (myCamera->getMode() == CAMERA_MODE_FIRST_PERSON && !OculusManager::isConnected()) { - radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.f)); + radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f)); radius *= COLLISION_RADIUS_SCALAR; } - - if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) { - updateCollisionWithEnvironment(deltaTime, radius); - } - if (_collisionFlags & COLLISION_GROUP_VOXELS) { - updateCollisionWithVoxels(deltaTime, radius); - } - if (_collisionFlags & COLLISION_GROUP_AVATARS) { - updateCollisionWithAvatars(deltaTime); + if (_collisionFlags) { + updateShapePositions(); + if (_collisionFlags & COLLISION_GROUP_ENVIRONMENT) { + updateCollisionWithEnvironment(deltaTime, radius); + } + if (_collisionFlags & COLLISION_GROUP_VOXELS) { + updateCollisionWithVoxels(deltaTime, radius); + } + if (_collisionFlags & COLLISION_GROUP_AVATARS) { + updateCollisionWithAvatars(deltaTime); + } } } @@ -334,45 +355,35 @@ void MyAvatar::simulate(float deltaTime) { // Update avatar head rotation with sensor data void MyAvatar::updateFromGyros(float deltaTime) { - Faceshift* faceshift = Application::getInstance()->getFaceshift(); - Visage* visage = Application::getInstance()->getVisage(); glm::vec3 estimatedPosition, estimatedRotation; - bool trackerActive = false; - if (faceshift->isActive()) { - estimatedPosition = faceshift->getHeadTranslation(); - estimatedRotation = glm::degrees(safeEulerAngles(faceshift->getHeadRotation())); - trackerActive = true; - - } else if (visage->isActive()) { - estimatedPosition = visage->getHeadTranslation(); - estimatedRotation = glm::degrees(safeEulerAngles(visage->getHeadRotation())); - trackerActive = true; - } - - Head* head = getHead(); - if (trackerActive) { + 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.f; - const float TRACKER_MAX_YAW_TURN = 50.f; + 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.f) { + 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; } } } - } + } // Set the rotation of the avatar's head (as seen by others, not affecting view frustum) // to be scaled. Pitch is greater to emphasize nodding behavior / synchrony. const float AVATAR_HEAD_PITCH_MAGNIFY = 1.0f; const float AVATAR_HEAD_YAW_MAGNIFY = 1.0f; const float AVATAR_HEAD_ROLL_MAGNIFY = 1.0f; + Head* head = getHead(); head->setDeltaPitch(estimatedRotation.x * AVATAR_HEAD_PITCH_MAGNIFY); head->setDeltaYaw(estimatedRotation.y * AVATAR_HEAD_YAW_MAGNIFY); head->setDeltaRoll(estimatedRotation.z * AVATAR_HEAD_ROLL_MAGNIFY); @@ -397,20 +408,20 @@ void MyAvatar::moveWithLean() { float leanSideways = head->getLeanSideways(); // Degrees of 'dead zone' when leaning, and amount of acceleration to apply to lean angle - const float LEAN_FWD_DEAD_ZONE = 15.f; - const float LEAN_SIDEWAYS_DEAD_ZONE = 10.f; - const float LEAN_FWD_THRUST_SCALE = 4.f; - const float LEAN_SIDEWAYS_THRUST_SCALE = 3.f; + const float LEAN_FWD_DEAD_ZONE = 15.0f; + const float LEAN_SIDEWAYS_DEAD_ZONE = 10.0f; + const float LEAN_FWD_THRUST_SCALE = 4.0f; + const float LEAN_SIDEWAYS_THRUST_SCALE = 3.0f; if (fabs(leanForward) > LEAN_FWD_DEAD_ZONE) { - if (leanForward > 0.f) { + if (leanForward > 0.0f) { addThrust(front * -(leanForward - LEAN_FWD_DEAD_ZONE) * LEAN_FWD_THRUST_SCALE); } else { addThrust(front * -(leanForward + LEAN_FWD_DEAD_ZONE) * LEAN_FWD_THRUST_SCALE); } } if (fabs(leanSideways) > LEAN_SIDEWAYS_DEAD_ZONE) { - if (leanSideways > 0.f) { + if (leanSideways > 0.0f) { addThrust(right * -(leanSideways - LEAN_SIDEWAYS_DEAD_ZONE) * LEAN_SIDEWAYS_THRUST_SCALE); } else { addThrust(right * -(leanSideways + LEAN_SIDEWAYS_DEAD_ZONE) * LEAN_SIDEWAYS_THRUST_SCALE); @@ -423,7 +434,7 @@ void MyAvatar::renderDebugBodyPoints() { glm::vec3 headPosition(getHead()->getEyePosition()); float torsoToHead = glm::length(headPosition - torsoPosition); glm::vec3 position; - printf("head-above-torso %.2f, scale = %0.2f\n", torsoToHead, getScale()); + qDebug("head-above-torso %.2f, scale = %0.2f", torsoToHead, getScale()); // Torso Sphere position = torsoPosition; @@ -457,7 +468,7 @@ void MyAvatar::renderHeadMouse() const { // TODO? resurrect headMouse stuff? /* // Display small target box at center or head mouse target that can also be used to measure LOD - glColor3f(1.f, 1.f, 1.f); + glColor3f(1.0f, 1.0f, 1.0f); glDisable(GL_LINE_SMOOTH); const int PIXEL_BOX = 16; glBegin(GL_LINES); @@ -467,7 +478,7 @@ void MyAvatar::renderHeadMouse() const { glVertex2f(_headMouseX, _headMouseY + PIXEL_BOX/2); glEnd(); glEnable(GL_LINE_SMOOTH); - glColor3f(1.f, 0.f, 0.f); + glColor3f(1.0f, 0.0f, 0.0f); glPointSize(3.0f); glDisable(GL_POINT_SMOOTH); glBegin(GL_POINTS); @@ -479,7 +490,7 @@ void MyAvatar::renderHeadMouse() const { int eyeTargetX = (_glWidget->width() / 2) - _faceshift.getEstimatedEyeYaw() * EYE_TARGET_PIXELS_PER_DEGREE; int eyeTargetY = (_glWidget->height() / 2) - _faceshift.getEstimatedEyePitch() * EYE_TARGET_PIXELS_PER_DEGREE; - glColor3f(0.f, 1.f, 1.f); + glColor3f(0.0f, 1.0f, 1.0f); glDisable(GL_LINE_SMOOTH); glBegin(GL_LINES); glVertex2f(eyeTargetX - PIXEL_BOX/2, eyeTargetY); @@ -600,11 +611,6 @@ void MyAvatar::clearLookAtTargetAvatar() { _lookAtTargetAvatar.clear(); } -float MyAvatar::getAbsoluteHeadYaw() const { - const Head* head = static_cast(_headData); - return glm::yaw(head->getOrientation()); -} - glm::vec3 MyAvatar::getUprightHeadPosition() const { return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f); } @@ -639,18 +645,25 @@ void MyAvatar::renderBody(RenderMode renderMode) { } // Render the body's voxels and head - _skeletonModel.render(1.0f, renderMode == SHADOW_RENDER_MODE); + Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? + Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; + _skeletonModel.render(1.0f, modelRenderMode); // Render head so long as the camera isn't inside it - const float RENDER_HEAD_CUTOFF_DISTANCE = 0.40f; - Camera* myCamera = Application::getInstance()->getCamera(); - if (renderMode != NORMAL_RENDER_MODE || (glm::length(myCamera->getPosition() - getHead()->calculateAverageEyePosition()) > - RENDER_HEAD_CUTOFF_DISTANCE * _scale)) { - getHead()->render(1.0f, renderMode == SHADOW_RENDER_MODE); + if (shouldRenderHead(Application::getInstance()->getCamera()->getPosition(), renderMode)) { + getHead()->render(1.0f, modelRenderMode); } getHand()->render(true); } +const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; + +bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { + const Head* head = getHead(); + return (renderMode != NORMAL_RENDER_MODE) || + (glm::length(cameraPosition - head->calculateAverageEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale); +} + void MyAvatar::updateThrust(float deltaTime) { // // Gather thrust information from keyboard and sensors to apply to avatar motion @@ -661,11 +674,11 @@ void MyAvatar::updateThrust(float deltaTime) { glm::vec3 up = orientation * IDENTITY_UP; const float THRUST_MAG_UP = 800.0f; - const float THRUST_MAG_DOWN = 300.f; - const float THRUST_MAG_FWD = 500.f; - const float THRUST_MAG_BACK = 300.f; - const float THRUST_MAG_LATERAL = 250.f; - const float THRUST_JUMP = 120.f; + const float THRUST_MAG_DOWN = 300.0f; + const float THRUST_MAG_FWD = 500.0f; + const float THRUST_MAG_BACK = 300.0f; + const float THRUST_MAG_LATERAL = 250.0f; + const float THRUST_JUMP = 120.0f; // Add Thrusts from keyboard _thrust += _driveKeys[FWD] * _scale * THRUST_MAG_FWD * _thrustMultiplier * deltaTime * front; @@ -674,6 +687,28 @@ void MyAvatar::updateThrust(float deltaTime) { _thrust -= _driveKeys[LEFT] * _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right; _thrust += _driveKeys[UP] * _scale * THRUST_MAG_UP * _thrustMultiplier * deltaTime * up; _thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up; + + // attenuate thrust when in penetration + if (glm::dot(_thrust, _lastBodyPenetration) > EPSILON) { + const float MAX_BODY_PENETRATION_DEPTH = 0.6f * _skeletonModel.getBoundingShapeRadius(); + float penetrationFactor = glm::min(1.0f, glm::length(_lastBodyPenetration) / MAX_BODY_PENETRATION_DEPTH); + glm::vec3 penetrationDirection = glm::normalize(_lastBodyPenetration); + // attenuate parallel component + glm::vec3 parallelThrust = glm::dot(_thrust, penetrationDirection) * penetrationDirection; + // attenuate perpendicular component (friction) + glm::vec3 perpendicularThrust = _thrust - parallelThrust; + // recombine to get the final thrust + _thrust = (1.0f - penetrationFactor) * parallelThrust + (1.0f - penetrationFactor * penetrationFactor) * perpendicularThrust; + + // attenuate the growth of _thrustMultiplier when in penetration + // otherwise the avatar will eventually be able to tunnel through the obstacle + _thrustMultiplier *= (1.0f - penetrationFactor * penetrationFactor); + } else if (_thrustMultiplier < 1.0f) { + // rapid healing of attenuated thrustMultiplier after penetration event + _thrustMultiplier = 1.0f; + } + _lastBodyPenetration = glm::vec3(0.0f); + _bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime; _bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime; getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime); @@ -682,12 +717,12 @@ void MyAvatar::updateThrust(float deltaTime) { if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) { const float THRUST_INCREASE_RATE = 1.05f; const float MAX_THRUST_MULTIPLIER = 75.0f; - //printf("m = %.3f\n", _thrustMultiplier); - if (_thrustMultiplier < MAX_THRUST_MULTIPLIER) { - _thrustMultiplier *= 1.f + deltaTime * THRUST_INCREASE_RATE; + _thrustMultiplier *= 1.0f + deltaTime * THRUST_INCREASE_RATE; + if (_thrustMultiplier > MAX_THRUST_MULTIPLIER) { + _thrustMultiplier = MAX_THRUST_MULTIPLIER; } } else { - _thrustMultiplier = 1.f; + _thrustMultiplier = 1.0f; } // Add one time jumping force if requested @@ -703,11 +738,12 @@ void MyAvatar::updateThrust(float deltaTime) { if ((glm::length(_thrust) == 0.0f) && _isThrustOn && (glm::length(_velocity) > MIN_SPEED_BRAKE_VELOCITY)) { _speedBrakes = true; } + _isThrustOn = (glm::length(_thrust) > EPSILON); - if (_speedBrakes && (glm::length(_velocity) < MIN_SPEED_BRAKE_VELOCITY)) { + if (_isThrustOn || (_speedBrakes && (glm::length(_velocity) < MIN_SPEED_BRAKE_VELOCITY))) { _speedBrakes = false; } - _isThrustOn = (glm::length(_thrust) > EPSILON); + } void MyAvatar::updateHandMovementAndTouching(float deltaTime) { @@ -761,19 +797,21 @@ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { } } +static CollisionList myCollisions(64); + void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { - const float VOXEL_ELASTICITY = 0.4f; - const float VOXEL_DAMPING = 0.0f; - const float VOXEL_COLLISION_FREQUENCY = 0.5f; - glm::vec3 penetration; - float pelvisFloatingHeight = getPelvisFloatingHeight(); - if (Application::getInstance()->getVoxelTree()->findCapsulePenetration( - _position - glm::vec3(0.0f, pelvisFloatingHeight - radius, 0.0f), - _position + glm::vec3(0.0f, getSkeletonHeight() - pelvisFloatingHeight + radius, 0.0f), radius, penetration)) { - _lastCollisionPosition = _position; - updateCollisionSound(penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); - applyHardCollision(penetration, VOXEL_ELASTICITY, VOXEL_DAMPING); - } + myCollisions.clear(); + const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); + if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions)) { + const float VOXEL_ELASTICITY = 0.4f; + const float VOXEL_DAMPING = 0.0f; + for (int i = 0; i < myCollisions.size(); ++i) { + CollisionInfo* collision = myCollisions[i]; + applyHardCollision(collision->_penetration, VOXEL_ELASTICITY, VOXEL_DAMPING); + } + const float VOXEL_COLLISION_FREQUENCY = 0.5f; + updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); + } } void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity, float damping) { @@ -791,11 +829,11 @@ void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity if (penetrationLength > EPSILON) { _elapsedTimeSinceCollision = 0.0f; glm::vec3 direction = penetration / penetrationLength; - _velocity -= glm::dot(_velocity, direction) * direction * (1.f + elasticity); - _velocity *= glm::clamp(1.f - damping, 0.0f, 1.0f); - if ((glm::length(_velocity) < HALTING_VELOCITY) && (glm::length(_thrust) == 0.f)) { + _velocity -= glm::dot(_velocity, direction) * direction * (1.0f + elasticity); + _velocity *= glm::clamp(1.0f - damping, 0.0f, 1.0f); + if ((glm::length(_velocity) < HALTING_VELOCITY) && (glm::length(_thrust) == 0.0f)) { // If moving really slowly after a collision, and not applying forces, stop altogether - _velocity *= 0.f; + _velocity *= 0.0f; } } } @@ -803,7 +841,7 @@ void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) { // consider whether to have the collision make a sound const float AUDIBLE_COLLISION_THRESHOLD = 0.02f; - const float COLLISION_LOUDNESS = 1.f; + const float COLLISION_LOUDNESS = 1.0f; const float DURATION_SCALING = 0.004f; const float NOISE_SCALING = 0.1f; glm::vec3 velocity = _velocity; @@ -823,10 +861,10 @@ void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTim // Noise is a function of the angle of collision // Duration of the sound is a function of both base frequency and velocity of impact Application::getInstance()->getAudio()->startCollisionSound( - std::min(COLLISION_LOUDNESS * velocityTowardCollision, 1.f), - frequency * (1.f + velocityTangentToCollision / velocityTowardCollision), - std::min(velocityTangentToCollision / velocityTowardCollision * NOISE_SCALING, 1.f), - 1.f - DURATION_SCALING * powf(frequency, 0.5f) / velocityTowardCollision, true); + std::min(COLLISION_LOUDNESS * velocityTowardCollision, 1.0f), + frequency * (1.0f + velocityTangentToCollision / velocityTowardCollision), + std::min(velocityTangentToCollision / velocityTowardCollision * NOISE_SCALING, 1.0f), + 1.0f - DURATION_SCALING * powf(frequency, 0.5f) / velocityTowardCollision, true); } } @@ -839,8 +877,8 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float float halfHeights = 0.5 * (heightA + heightB); if (yDistance < halfHeights) { // cylinders collide - if (xzDistance > 0.f) { - positionBA.y = 0.f; + if (xzDistance > 0.0f) { + positionBA.y = 0.0f; // note, penetration should point from A into B penetration = positionBA * ((radiusA + radiusB - xzDistance) / xzDistance); return true; @@ -850,7 +888,7 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float } } else if (yDistance < halfHeights + radiusA + radiusB) { // caps collide - if (positionBA.y < 0.f) { + if (positionBA.y < 0.0f) { // A is above B positionBA.y += halfHeights; float BA = glm::length(positionBA); @@ -868,6 +906,8 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float return false; } +const float BODY_COLLISION_RESOLUTION_TIMESCALE = 0.5f; // seconds + void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // Reset detector for nearest avatar _distanceToNearestAvatar = std::numeric_limits::max(); @@ -876,17 +916,9 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // no need to compute a bunch of stuff if we have one or fewer avatars return; } - updateShapePositions(); float myBoundingRadius = getBoundingRadius(); - /* TODO: Andrew to fix Avatar-Avatar body collisions - // HACK: body-body collision uses two coaxial capsules with axes parallel to y-axis - // TODO: make the collision work without assuming avatar orientation - Extents myStaticExtents = _skeletonModel.getStaticExtents(); - glm::vec3 staticScale = myStaticExtents.maximum - myStaticExtents.minimum; - float myCapsuleRadius = 0.25f * (staticScale.x + staticScale.z); - float myCapsuleHeight = staticScale.y; - */ + const float BODY_COLLISION_RESOLUTION_FACTOR = glm::max(1.0f, deltaTime / BODY_COLLISION_RESOLUTION_TIMESCALE); foreach (const AvatarSharedPointer& avatarPointer, avatars) { Avatar* avatar = static_cast(avatarPointer.data()); @@ -901,23 +933,28 @@ void MyAvatar::updateCollisionWithAvatars(float deltaTime) { } float theirBoundingRadius = avatar->getBoundingRadius(); if (distance < myBoundingRadius + theirBoundingRadius) { - /* TODO: Andrew to fix Avatar-Avatar body collisions - Extents theirStaticExtents = _skeletonModel.getStaticExtents(); - glm::vec3 staticScale = theirStaticExtents.maximum - theirStaticExtents.minimum; - float theirCapsuleRadius = 0.25f * (staticScale.x + staticScale.z); - float theirCapsuleHeight = staticScale.y; + // collide our body against theirs + QVector myShapes; + _skeletonModel.getBodyShapes(myShapes); + QVector theirShapes; + avatar->getSkeletonModel().getBodyShapes(theirShapes); - glm::vec3 penetration(0.f); - if (findAvatarAvatarPenetration(_position, myCapsuleRadius, myCapsuleHeight, - avatar->getPosition(), theirCapsuleRadius, theirCapsuleHeight, penetration)) { - // move the avatar out by half the penetration - setPosition(_position - 0.5f * penetration); + CollisionInfo collision; + if (ShapeCollider::collideShapesCoarse(myShapes, theirShapes, collision)) { + float penetrationDepth = glm::length(collision._penetration); + if (penetrationDepth > myBoundingRadius) { + qDebug() << "WARNING: ignoring avatar-avatar penetration depth " << penetrationDepth; + } + else if (penetrationDepth > EPSILON) { + setPosition(getPosition() - BODY_COLLISION_RESOLUTION_FACTOR * collision._penetration); + _lastBodyPenetration += collision._penetration; + emit collisionWithAvatar(getSessionUUID(), avatar->getSessionUUID(), collision); + } } - */ // collide our hands against them // TODO: make this work when we can figure out when the other avatar won't yeild - // (for example, we're colling against their chest or leg) + // (for example, we're colliding against their chest or leg) //getHand()->collideAgainstAvatar(avatar, true); // collide their hands against us @@ -1069,15 +1106,15 @@ void MyAvatar::goHome() { } void MyAvatar::increaseSize() { - if ((1.f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) { - _targetScale *= (1.f + SCALING_RATIO); + if ((1.0f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) { + _targetScale *= (1.0f + SCALING_RATIO); qDebug("Changed scale to %f", _targetScale); } } void MyAvatar::decreaseSize() { - if (MIN_AVATAR_SCALE < (1.f - SCALING_RATIO) * _targetScale) { - _targetScale *= (1.f - SCALING_RATIO); + if (MIN_AVATAR_SCALE < (1.0f - SCALING_RATIO) * _targetScale) { + _targetScale *= (1.0f - SCALING_RATIO); qDebug("Changed scale to %f", _targetScale); } } @@ -1129,7 +1166,7 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { QStringList coordinateItems = positionString.split(','); QStringList orientationItems = orientationString.split(','); - NodeList::getInstance()->getDomainInfo().setHostname(domainHostnameString); + NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString); // orient the user to face the target glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), @@ -1143,6 +1180,7 @@ void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; setPosition(newPosition); + emit transformChanged(); } } @@ -1153,8 +1191,8 @@ void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& pe if (leverLength > EPSILON) { // compute lean perturbation angles glm::quat bodyRotation = getOrientation(); - glm::vec3 xAxis = bodyRotation * glm::vec3(1.f, 0.f, 0.f); - glm::vec3 zAxis = bodyRotation * glm::vec3(0.f, 0.f, 1.f); + glm::vec3 xAxis = bodyRotation * glm::vec3(1.0f, 0.0f, 0.0f); + glm::vec3 zAxis = bodyRotation * glm::vec3(0.0f, 0.0f, 1.0f); leverAxis = leverAxis / leverLength; glm::vec3 effectivePenetration = penetration - glm::dot(penetration, leverAxis) * leverAxis; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index 38edc5356e..71c74f7c91 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -1,13 +1,16 @@ // // MyAvatar.h -// interface +// interface/src/avatar // // Created by Mark Peng on 8/16/13. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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 __interface__myavatar__ -#define __interface__myavatar__ +#ifndef hifi_MyAvatar_h +#define hifi_MyAvatar_h #include @@ -38,6 +41,7 @@ public: void render(const glm::vec3& cameraPosition, RenderMode renderMode = NORMAL_RENDER_MODE); void renderBody(RenderMode renderMode); + bool shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const; void renderDebugBodyPoints(); void renderHeadMouse() const; @@ -50,12 +54,10 @@ public: void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; } // getters - float getSpeed() const { return _speed; } AvatarMode getMode() const { return _mode; } float getLeanScale() const { return _leanScale; } float getElapsedTimeStopped() const { return _elapsedTimeStopped; } float getElapsedTimeMoving() const { return _elapsedTimeMoving; } - float getAbsoluteHeadYaw() const; // degrees const glm::vec3& getMouseRayOrigin() const { return _mouseRayOrigin; } const glm::vec3& getMouseRayDirection() const { return _mouseRayDirection; } glm::vec3 getGravity() const { return _gravity; } @@ -105,6 +107,9 @@ public slots: glm::vec3 getThrust() { return _thrust; }; void setThrust(glm::vec3 newThrust) { _thrust = newThrust; } +signals: + void transformChanged(); + private: bool _mousePressed; float _bodyPitchDelta; // degrees @@ -121,12 +126,13 @@ private: bool _isThrustOn; float _thrustMultiplier; glm::vec3 _moveTarget; + glm::vec3 _lastBodyPenetration; int _moveTargetStepCounter; QWeakPointer _lookAtTargetAvatar; glm::vec3 _targetAvatarPosition; bool _shouldRender; - bool _billboardValid; + float _oculusYawOffset; // private methods void updateThrust(float deltaTime); @@ -140,4 +146,4 @@ private: void maybeUpdateBillboard(); }; -#endif +#endif // hifi_MyAvatar_h diff --git a/interface/src/avatar/SkeletonModel.cpp b/interface/src/avatar/SkeletonModel.cpp index ca18c62718..b9ac280711 100644 --- a/interface/src/avatar/SkeletonModel.cpp +++ b/interface/src/avatar/SkeletonModel.cpp @@ -1,9 +1,12 @@ // // SkeletonModel.cpp -// interface +// interface/src/avatar // // Created by Andrzej Kapolka on 10/17/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -14,7 +17,7 @@ #include "Menu.h" #include "SkeletonModel.h" -SkeletonModel::SkeletonModel(Avatar* owningAvatar) : +SkeletonModel::SkeletonModel(Avatar* owningAvatar) : _owningAvatar(owningAvatar) { } @@ -63,7 +66,7 @@ void SkeletonModel::simulate(float deltaTime, bool fullUpdate) { } void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) const { - if (jointIndex < 0 || jointIndex >= int(_shapes.size())) { + if (jointIndex < 0 || jointIndex >= int(_jointShapes.size())) { return; } if (jointIndex == getLeftHandJointIndex() @@ -75,16 +78,16 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) int parentIndex = joint.parentIndex; if (i == jointIndex) { // this shape is the hand - shapes.push_back(_shapes[i]); + shapes.push_back(_jointShapes[i]); if (parentIndex != -1) { // also add the forearm - shapes.push_back(_shapes[parentIndex]); + shapes.push_back(_jointShapes[parentIndex]); } } else { while (parentIndex != -1) { if (parentIndex == jointIndex) { // this shape is a child of the hand - shapes.push_back(_shapes[i]); + shapes.push_back(_jointShapes[i]); break; } parentIndex = geometry.joints[parentIndex].parentIndex; @@ -94,6 +97,12 @@ void SkeletonModel::getHandShapes(int jointIndex, QVector& shapes) } } +void SkeletonModel::getBodyShapes(QVector& shapes) const { + // for now we push a single bounding shape, + // but later we could push a subset of joint shapes + shapes.push_back(&_boundingShape); +} + class IndexValue { public: int index; diff --git a/interface/src/avatar/SkeletonModel.h b/interface/src/avatar/SkeletonModel.h index 213a53d9ed..2020ccf3b2 100644 --- a/interface/src/avatar/SkeletonModel.h +++ b/interface/src/avatar/SkeletonModel.h @@ -1,14 +1,16 @@ // // SkeletonModel.h -// interface +// interface/src/avatar // // Created by Andrzej Kapolka on 10/17/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__SkeletonModel__ -#define __interface__SkeletonModel__ - +#ifndef hifi_SkeletonModel_h +#define hifi_SkeletonModel_h #include "renderer/Model.h" @@ -28,8 +30,11 @@ public: /// \param shapes[out] list in which is stored pointers to hand shapes void getHandShapes(int jointIndex, QVector& shapes) const; + /// \param shapes[out] list of shapes for body collisions + void getBodyShapes(QVector& shapes) const; + protected: - + void applyHandPosition(int jointIndex, const glm::vec3& position); void applyPalmData(int jointIndex, const QVector& fingerJointIndices, @@ -45,4 +50,4 @@ private: Avatar* _owningAvatar; }; -#endif /* defined(__interface__SkeletonModel__) */ +#endif // hifi_SkeletonModel_h diff --git a/interface/src/devices/FaceTracker.cpp b/interface/src/devices/FaceTracker.cpp new file mode 100644 index 0000000000..52fe04de77 --- /dev/null +++ b/interface/src/devices/FaceTracker.cpp @@ -0,0 +1,17 @@ +// +// FaceTracker.cpp +// interface/src/devices +// +// Created by Andrzej Kapolka on 4/9/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 "FaceTracker.h" + +FaceTracker::FaceTracker() : + _estimatedEyePitch(0.0f), + _estimatedEyeYaw(0.0f) { +} diff --git a/interface/src/devices/FaceTracker.h b/interface/src/devices/FaceTracker.h new file mode 100644 index 0000000000..459f38cafc --- /dev/null +++ b/interface/src/devices/FaceTracker.h @@ -0,0 +1,46 @@ +// +// FaceTracker.h +// interface/src/devices +// +// Created by Andrzej Kapolka on 4/9/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_FaceTracker_h +#define hifi_FaceTracker_h + +#include +#include + +#include +#include + +/// Base class for face trackers (Faceshift, Visage, Faceplus). +class FaceTracker : public QObject { + Q_OBJECT + +public: + + FaceTracker(); + + const glm::vec3& getHeadTranslation() const { return _headTranslation; } + const glm::quat& getHeadRotation() const { return _headRotation; } + + float getEstimatedEyePitch() const { return _estimatedEyePitch; } + float getEstimatedEyeYaw() const { return _estimatedEyeYaw; } + + const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } + +protected: + + glm::vec3 _headTranslation; + glm::quat _headRotation; + float _estimatedEyePitch; + float _estimatedEyeYaw; + QVector _blendshapeCoefficients; +}; + +#endif // hifi_FaceTracker_h diff --git a/interface/src/devices/Faceplus.cpp b/interface/src/devices/Faceplus.cpp new file mode 100644 index 0000000000..f8eec434b7 --- /dev/null +++ b/interface/src/devices/Faceplus.cpp @@ -0,0 +1,259 @@ +// +// Faceplus.cpp +// interface/src/devices +// +// Created by Andrzej Kapolka on 4/9/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 + +#ifdef HAVE_FACEPLUS +#include +#endif + +#include + +#include "Application.h" +#include "Faceplus.h" + +static int floatVectorMetaTypeId = qRegisterMetaType >(); + +Faceplus::Faceplus() : + _enabled(false), + _active(false) { + +#ifdef HAVE_FACEPLUS + // these are ignored--any values will do + faceplus_log_in("username", "password"); +#endif +} + +Faceplus::~Faceplus() { + setEnabled(false); +} + +void Faceplus::init() { + connect(Application::getInstance()->getFaceshift(), SIGNAL(connectionStateChanged()), SLOT(updateEnabled())); + updateEnabled(); +} + +void Faceplus::reset() { + if (_enabled) { + QMetaObject::invokeMethod(_reader, "reset"); + } +} + +void Faceplus::setState(const glm::vec3& headTranslation, const glm::quat& headRotation, + float estimatedEyePitch, float estimatedEyeYaw, const QVector& blendshapeCoefficients) { + _headTranslation = headTranslation; + _headRotation = headRotation; + _estimatedEyePitch = estimatedEyePitch; + _estimatedEyeYaw = estimatedEyeYaw; + _blendshapeCoefficients = blendshapeCoefficients; + _active = true; +} + +void Faceplus::updateEnabled() { + setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Faceplus) && + !(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && + Application::getInstance()->getFaceshift()->isConnectedOrConnecting())); +} + +void Faceplus::setEnabled(bool enabled) { + if (_enabled == enabled) { + return; + } + if ((_enabled = enabled)) { + _reader = new FaceplusReader(); + QThread* readerThread = new QThread(this); + _reader->moveToThread(readerThread); + readerThread->start(); + QMetaObject::invokeMethod(_reader, "init"); + + } else { + QThread* readerThread = _reader->thread(); + QMetaObject::invokeMethod(_reader, "shutdown"); + readerThread->wait(); + delete readerThread; + _active = false; + } +} + +#ifdef HAVE_FACEPLUS +static QMultiHash > createChannelNameMap() { + QMultiHash > blendshapeMap; + blendshapeMap.insert("EyeBlink_L", QPair("Mix::Blink_Left", 1.0f)); + blendshapeMap.insert("EyeBlink_R", QPair("Mix::Blink_Right", 1.0f)); + blendshapeMap.insert("BrowsD_L", QPair("Mix::BrowsDown_Left", 1.0f)); + blendshapeMap.insert("BrowsD_R", QPair("Mix::BrowsDown_Right", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::BrowsIn_Left", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::BrowsIn_Right", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::BrowsOuterLower_Left", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::BrowsOuterLower_Right", 1.0f)); + blendshapeMap.insert("BrowsU_L", QPair("Mix::BrowsUp_Left", 10.0f)); + blendshapeMap.insert("BrowsU_R", QPair("Mix::BrowsUp_Right", 10.0f)); + blendshapeMap.insert("EyeOpen_L", QPair("Mix::EyesWide_Left", 1.0f)); + blendshapeMap.insert("EyeOpen_R", QPair("Mix::EyesWide_Right", 1.0f)); + blendshapeMap.insert("MouthFrown_L", QPair("Mix::Frown_Left", 1.0f)); + blendshapeMap.insert("MouthFrown_R", QPair("Mix::Frown_Right", 1.0f)); + blendshapeMap.insert("JawLeft", QPair("Mix::Jaw_RotateY_Left", 1.0f)); + blendshapeMap.insert("JawRight", QPair("Mix::Jaw_RotateY_Right", 1.0f)); + blendshapeMap.insert("LipsLowerDown", QPair("Mix::LowerLipDown_Left", 0.5f)); + blendshapeMap.insert("LipsLowerDown", QPair("Mix::LowerLipDown_Right", 0.5f)); + blendshapeMap.insert("...", QPair("Mix::LowerLipIn", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::LowerLipOut", 1.0f)); + blendshapeMap.insert("MouthLeft", QPair("Mix::Midmouth_Left", 1.0f)); + blendshapeMap.insert("MouthRight", QPair("Mix::Midmouth_Right", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::MouthDown", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::MouthNarrow_Left", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::MouthNarrow_Right", 1.0f)); + blendshapeMap.insert("JawOpen", QPair("Mix::MouthOpen", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::MouthUp", 1.0f)); + blendshapeMap.insert("LipsPucker", QPair("Mix::MouthWhistle_NarrowAdjust_Left", 0.5f)); + blendshapeMap.insert("LipsPucker", QPair("Mix::MouthWhistle_NarrowAdjust_Right", 0.5f)); + blendshapeMap.insert("Sneer", QPair("Mix::NoseScrunch_Left", 0.5f)); + blendshapeMap.insert("Sneer", QPair("Mix::NoseScrunch_Right", 0.5f)); + blendshapeMap.insert("MouthSmile_L", QPair("Mix::Smile_Left", 1.0f)); + blendshapeMap.insert("MouthSmile_R", QPair("Mix::Smile_Right", 1.0f)); + blendshapeMap.insert("EyeSquint_L", QPair("Mix::Squint_Left", 1.0f)); + blendshapeMap.insert("EyeSquint_R", QPair("Mix::Squint_Right", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::UpperLipIn", 1.0f)); + blendshapeMap.insert("...", QPair("Mix::UpperLipOut", 1.0f)); + blendshapeMap.insert("LipsUpperUp", QPair("Mix::UpperLipUp_Left", 0.5f)); + blendshapeMap.insert("LipsUpperUp", QPair("Mix::UpperLipUp_Right", 0.5f)); + + QMultiHash > channelNameMap; + for (int i = 0;; i++) { + QByteArray blendshape = FACESHIFT_BLENDSHAPES[i]; + if (blendshape.isEmpty()) { + break; + } + for (QMultiHash >::const_iterator it = blendshapeMap.constFind(blendshape); + it != blendshapeMap.constEnd() && it.key() == blendshape; it++) { + channelNameMap.insert(it.value().first, QPair(i, it.value().second)); + } + } + + return channelNameMap; +} + +static const QMultiHash >& getChannelNameMap() { + static QMultiHash > channelNameMap = createChannelNameMap(); + return channelNameMap; +} +#endif + +FaceplusReader::~FaceplusReader() { +#ifdef HAVE_FACEPLUS + if (faceplus_teardown()) { + qDebug() << "Faceplus torn down."; + } +#endif +} + +void FaceplusReader::init() { +#ifdef HAVE_FACEPLUS + if (!faceplus_init("hHD")) { + qDebug() << "Failed to initialized Faceplus."; + return; + } + qDebug() << "Faceplus initialized."; + + int channelCount = faceplus_output_channels_count(); + _outputVector.resize(channelCount); + + int maxIndex = -1; + _channelIndexMap.clear(); + for (int i = 0; i < channelCount; i++) { + QByteArray name = faceplus_output_channel_name(i); + if (name == "Head_Joint::Rotation_X") { + _headRotationIndices[0] = i; + + } else if (name == "Head_Joint::Rotation_Y") { + _headRotationIndices[1] = i; + + } else if (name == "Head_Joint::Rotation_Z") { + _headRotationIndices[2] = i; + + } else if (name == "Left_Eye_Joint::Rotation_X") { + _leftEyeRotationIndices[0] = i; + + } else if (name == "Left_Eye_Joint::Rotation_Y") { + _leftEyeRotationIndices[1] = i; + + } else if (name == "Right_Eye_Joint::Rotation_X") { + _rightEyeRotationIndices[0] = i; + + } else if (name == "Right_Eye_Joint::Rotation_Y") { + _rightEyeRotationIndices[1] = i; + } + for (QMultiHash >::const_iterator it = getChannelNameMap().constFind(name); + it != getChannelNameMap().constEnd() && it.key() == name; it++) { + _channelIndexMap.insert(i, it.value()); + maxIndex = qMax(maxIndex, it.value().first); + } + } + _blendshapeCoefficients.resize(maxIndex + 1); + _referenceInitialized = false; + + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); +#endif +} + +void FaceplusReader::shutdown() { + deleteLater(); + thread()->quit(); +} + +void FaceplusReader::update() { +#ifdef HAVE_FACEPLUS + float x, y, rotation, scale; + if (!(faceplus_synchronous_track() && faceplus_current_face_location(&x, &y, &rotation, &scale) && !glm::isnan(x) && + faceplus_current_output_vector(_outputVector.data()))) { + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); + return; + } + if (!_referenceInitialized) { + _referenceX = x; + _referenceY = y; + _referenceScale = scale; + _referenceInitialized = true; + } + const float TRANSLATION_SCALE = 10.0f; + const float REFERENCE_DISTANCE = 10.0f; + float depthScale = _referenceScale / scale; + float z = REFERENCE_DISTANCE * (depthScale - 1.0f); + glm::vec3 headTranslation((x - _referenceX) * depthScale * TRANSLATION_SCALE, + (y - _referenceY) * depthScale * TRANSLATION_SCALE, z); + glm::quat headRotation(glm::radians(glm::vec3(-_outputVector.at(_headRotationIndices[0]), + _outputVector.at(_headRotationIndices[1]), -_outputVector.at(_headRotationIndices[2])))); + float estimatedEyePitch = (_outputVector.at(_leftEyeRotationIndices[0]) + + _outputVector.at(_rightEyeRotationIndices[0])) * -0.5f; + float estimatedEyeYaw = (_outputVector.at(_leftEyeRotationIndices[1]) + + _outputVector.at(_rightEyeRotationIndices[1])) * 0.5f; + + qFill(_blendshapeCoefficients.begin(), _blendshapeCoefficients.end(), 0.0f); + for (int i = 0; i < _outputVector.size(); i++) { + for (QMultiHash >::const_iterator it = _channelIndexMap.constFind(i); + it != _channelIndexMap.constEnd() && it.key() == i; it++) { + _blendshapeCoefficients[it.value().first] += _outputVector.at(i) * it.value().second; + } + } + + QMetaObject::invokeMethod(Application::getInstance()->getFaceplus(), "setState", Q_ARG(const glm::vec3&, headTranslation), + Q_ARG(const glm::quat&, headRotation), Q_ARG(float, estimatedEyePitch), Q_ARG(float, estimatedEyeYaw), + Q_ARG(const QVector&, _blendshapeCoefficients)); + + QMetaObject::invokeMethod(this, "update", Qt::QueuedConnection); +#endif +} + +void FaceplusReader::reset() { +#ifdef HAVE_FACEPLUS + _referenceInitialized = false; +#endif +} diff --git a/interface/src/devices/Faceplus.h b/interface/src/devices/Faceplus.h new file mode 100644 index 0000000000..f3c680c2d6 --- /dev/null +++ b/interface/src/devices/Faceplus.h @@ -0,0 +1,85 @@ +// +// Faceplus.h +// interface/src/devices +// +// Created by Andrzej Kapolka on 4/9/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_Faceplus_h +#define hifi_Faceplus_h + +#include +#include +#include + +#include "FaceTracker.h" + +class FaceplusReader; + +/// Interface for Mixamo FacePlus. +class Faceplus : public FaceTracker { + Q_OBJECT + +public: + + Faceplus(); + virtual ~Faceplus(); + + void init(); + void reset(); + + bool isActive() const { return _active; } + + Q_INVOKABLE void setState(const glm::vec3& headTranslation, const glm::quat& headRotation, + float estimatedEyePitch, float estimatedEyeYaw, const QVector& blendshapeCoefficients); + +public slots: + + void updateEnabled(); + +private: + + void setEnabled(bool enabled); + + bool _enabled; + bool _active; + + FaceplusReader* _reader; +}; + +Q_DECLARE_METATYPE(QVector) + +/// The reader object that lives in its own thread. +class FaceplusReader : public QObject { + Q_OBJECT + +public: + + virtual ~FaceplusReader(); + + Q_INVOKABLE void init(); + Q_INVOKABLE void shutdown(); + Q_INVOKABLE void update(); + Q_INVOKABLE void reset(); + +private: + +#ifdef HAVE_FACEPLUS + QMultiHash > _channelIndexMap; + QVector _outputVector; + int _headRotationIndices[3]; + int _leftEyeRotationIndices[2]; + int _rightEyeRotationIndices[2]; + float _referenceX; + float _referenceY; + float _referenceScale; + bool _referenceInitialized; + QVector _blendshapeCoefficients; +#endif +}; + +#endif // hifi_Faceplus_h diff --git a/interface/src/devices/Faceshift.cpp b/interface/src/devices/Faceshift.cpp index 9f1734c7e5..59f2c245df 100644 --- a/interface/src/devices/Faceshift.cpp +++ b/interface/src/devices/Faceshift.cpp @@ -1,9 +1,12 @@ // // Faceshift.cpp -// interface +// interface/src/devices // // Created by Andrzej Kapolka on 9/3/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -42,9 +45,7 @@ Faceshift::Faceshift() : _jawOpenIndex(21), _longTermAverageEyePitch(0.0f), _longTermAverageEyeYaw(0.0f), - _longTermAverageInitialized(false), - _estimatedEyePitch(0.0f), - _estimatedEyeYaw(0.0f) + _longTermAverageInitialized(false) { connect(&_tcpSocket, SIGNAL(connected()), SLOT(noteConnected())); connect(&_tcpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(noteError(QAbstractSocket::SocketError))); diff --git a/interface/src/devices/Faceshift.h b/interface/src/devices/Faceshift.h index f878056b57..2fc1aaddb1 100644 --- a/interface/src/devices/Faceshift.h +++ b/interface/src/devices/Faceshift.h @@ -1,24 +1,26 @@ // // Faceshift.h -// interface +// interface/src/devices // // Created by Andrzej Kapolka on 9/3/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__Faceshift__ -#define __interface__Faceshift__ +#ifndef hifi_Faceshift_h +#define hifi_Faceshift_h #include #include -#include -#include - #include +#include "FaceTracker.h" + /// Handles interaction with the Faceshift software, which provides head position/orientation and facial features. -class Faceshift : public QObject { +class Faceshift : public FaceTracker { Q_OBJECT public: @@ -31,9 +33,7 @@ public: bool isActive() const; - const glm::quat& getHeadRotation() const { return _headRotation; } const glm::vec3& getHeadAngularVelocity() const { return _headAngularVelocity; } - const glm::vec3& getHeadTranslation() const { return _headTranslation; } // these pitch/yaw angles are in degrees float getEyeGazeLeftPitch() const { return _eyeGazeLeftPitch; } @@ -42,11 +42,6 @@ public: float getEyeGazeRightPitch() const { return _eyeGazeRightPitch; } float getEyeGazeRightYaw() const { return _eyeGazeRightYaw; } - float getEstimatedEyePitch() const { return _estimatedEyePitch; } - float getEstimatedEyeYaw() const { return _estimatedEyeYaw; } - - const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } - float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); } float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); } float getLeftEyeOpen() const { return getBlendshapeCoefficient(_leftEyeOpenIndex); } @@ -99,9 +94,7 @@ private: bool _tracking; quint64 _lastTrackingStateReceived; - glm::quat _headRotation; glm::vec3 _headAngularVelocity; - glm::vec3 _headTranslation; // degrees float _eyeGazeLeftPitch; @@ -109,8 +102,6 @@ private: float _eyeGazeRightPitch; float _eyeGazeRightYaw; - QVector _blendshapeCoefficients; - int _leftBlinkIndex; int _rightBlinkIndex; int _leftEyeOpenIndex; @@ -132,10 +123,6 @@ private: float _longTermAverageEyePitch; float _longTermAverageEyeYaw; bool _longTermAverageInitialized; - - // degrees - float _estimatedEyePitch; - float _estimatedEyeYaw; }; -#endif /* defined(__interface__Faceshift__) */ +#endif // hifi_Faceshift_h diff --git a/interface/src/devices/OculusManager.cpp b/interface/src/devices/OculusManager.cpp index c5377ce428..854b19236d 100644 --- a/interface/src/devices/OculusManager.cpp +++ b/interface/src/devices/OculusManager.cpp @@ -1,17 +1,16 @@ // // OculusManager.cpp -// hifi +// interface/src/devices // // Created by Stephen Birarda on 5/9/13. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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 "InterfaceConfig.h" -#ifdef WIN32 -#include -#endif - #include #include @@ -27,7 +26,6 @@ int OculusManager::_scaleLocation; int OculusManager::_scaleInLocation; int OculusManager::_hmdWarpParamLocation; bool OculusManager::_isConnected = false; -float OculusManager::_yawOffset = 0.0f; // radians #ifdef HAVE_LIBOVR using namespace OVR; @@ -187,18 +185,9 @@ void OculusManager::reset() { #endif } -void OculusManager::updateYawOffset() { -#ifdef HAVE_LIBOVR - float yaw, pitch, roll; - _sensorFusion->GetOrientation().GetEulerAngles(&yaw, &pitch, &roll); - _yawOffset = yaw; -#endif -} - void OculusManager::getEulerAngles(float& yaw, float& pitch, float& roll) { #ifdef HAVE_LIBOVR _sensorFusion->GetOrientation().GetEulerAngles(&yaw, &pitch, &roll); - yaw = yaw - _yawOffset; #endif } diff --git a/interface/src/devices/OculusManager.h b/interface/src/devices/OculusManager.h index b3cd400ac8..6376df05ca 100644 --- a/interface/src/devices/OculusManager.h +++ b/interface/src/devices/OculusManager.h @@ -1,13 +1,16 @@ // // OculusManager.h -// hifi +// interface/src/devices // // Created by Stephen Birarda on 5/9/13. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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__OculusManager__ -#define __hifi__OculusManager__ +#ifndef hifi_OculusManager_h +#define hifi_OculusManager_h #include @@ -48,7 +51,6 @@ private: static int _scaleInLocation; static int _hmdWarpParamLocation; static bool _isConnected; - static float _yawOffset; #ifdef HAVE_LIBOVR static OVR::Ptr _deviceManager; @@ -59,4 +61,4 @@ private: #endif }; -#endif /* defined(__hifi__OculusManager__) */ +#endif // hifi_OculusManager_h diff --git a/interface/src/devices/SixenseManager.cpp b/interface/src/devices/SixenseManager.cpp index 8e54e034ac..f7c00411c1 100644 --- a/interface/src/devices/SixenseManager.cpp +++ b/interface/src/devices/SixenseManager.cpp @@ -1,9 +1,12 @@ // -// Sixense.cpp -// interface +// SixenseManager.cpp +// interface/src/devices // // Created by Andrzej Kapolka on 11/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -93,7 +96,7 @@ void SixenseManager::update(float deltaTime) { hand->getPalms().push_back(newPalm); palm = &(hand->getPalms()[hand->getNumPalms() - 1]); palm->setSixenseID(data->controller_index); - printf("Found new Sixense controller, ID %i\n", data->controller_index); + qDebug("Found new Sixense controller, ID %i", data->controller_index); } palm->setActive(true); diff --git a/interface/src/devices/SixenseManager.h b/interface/src/devices/SixenseManager.h index de4cccb399..2fc6b3dcb3 100644 --- a/interface/src/devices/SixenseManager.h +++ b/interface/src/devices/SixenseManager.h @@ -1,13 +1,16 @@ // // SixenseManager.h -// interface +// interface/src/devices // // Created by Andrzej Kapolka on 11/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__SixenseManager__ -#define __interface__SixenseManager__ +#ifndef hifi_SixenseManager_h +#define hifi_SixenseManager_h #include @@ -63,5 +66,4 @@ private: quint64 _lastMovement; }; -#endif /* defined(__interface__SixenseManager__) */ - +#endif // hifi_SixenseManager_h diff --git a/interface/src/devices/TV3DManager.cpp b/interface/src/devices/TV3DManager.cpp index 07aeea92b4..b5cc28b07f 100644 --- a/interface/src/devices/TV3DManager.cpp +++ b/interface/src/devices/TV3DManager.cpp @@ -1,9 +1,12 @@ // // TV3DManager.cpp -// hifi +// interface/src/devices // // Created by Brad Hefta-Gaub on 12/24/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 "InterfaceConfig.h" @@ -12,11 +15,6 @@ #include - -#ifdef WIN32 -#include -#endif - #include "Application.h" #include "TV3DManager.h" diff --git a/interface/src/devices/TV3DManager.h b/interface/src/devices/TV3DManager.h index edea489745..91a78e9bce 100644 --- a/interface/src/devices/TV3DManager.h +++ b/interface/src/devices/TV3DManager.h @@ -1,13 +1,16 @@ // // TV3DManager.h -// hifi +// interface/src/devices // -// Created by Brad Hefta-Gaub on 12/24/2013 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/24/2013. +// Copyright 2013 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__TV3DManager__ -#define __hifi__TV3DManager__ +#ifndef hifi_TV3DManager_h +#define hifi_TV3DManager_h #include @@ -38,4 +41,4 @@ private: static eyeFrustum _rightEye; }; -#endif /* defined(__hifi__TV3DManager__) */ +#endif // hifi_TV3DManager_h diff --git a/interface/src/devices/Visage.cpp b/interface/src/devices/Visage.cpp index b96ef1ee72..a467d2d4a8 100644 --- a/interface/src/devices/Visage.cpp +++ b/interface/src/devices/Visage.cpp @@ -1,18 +1,22 @@ // // Visage.cpp -// interface +// interface/src/devices // // Created by Andrzej Kapolka on 2/11/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 #include +#include + #include "Application.h" #include "Visage.h" -#include "renderer/FBXReader.h" // this has to go after our normal includes, because its definition of HANDLE conflicts with Qt's #ifdef HAVE_VISAGE @@ -34,9 +38,7 @@ const glm::vec3 DEFAULT_HEAD_ORIGIN(0.0f, 0.0f, 0.7f); Visage::Visage() : _enabled(false), _active(false), - _headOrigin(DEFAULT_HEAD_ORIGIN), - _estimatedEyePitch(0.0f), - _estimatedEyeYaw(0.0f) { + _headOrigin(DEFAULT_HEAD_ORIGIN) { #ifdef HAVE_VISAGE QByteArray licensePath = Application::resourcesPath().toLatin1() + "visage/license.vlc"; @@ -161,6 +163,7 @@ void Visage::reset() { void Visage::updateEnabled() { setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::Visage) && + !Menu::getInstance()->isOptionChecked(MenuOption::Faceplus) && !(Menu::getInstance()->isOptionChecked(MenuOption::Faceshift) && Application::getInstance()->getFaceshift()->isConnectedOrConnecting())); } diff --git a/interface/src/devices/Visage.h b/interface/src/devices/Visage.h index 6e98abbb61..33e2f0a7e1 100644 --- a/interface/src/devices/Visage.h +++ b/interface/src/devices/Visage.h @@ -1,20 +1,22 @@ // // Visage.h -// interface +// interface/src/devices // // Created by Andrzej Kapolka on 2/11/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Visage__ -#define __interface__Visage__ +#ifndef hifi_Visage_h +#define hifi_Visage_h #include #include #include -#include -#include +#include "FaceTracker.h" namespace VisageSDK { class VisageTracker2; @@ -22,7 +24,7 @@ namespace VisageSDK { } /// Handles input from the Visage webcam feature tracking software. -class Visage : public QObject { +class Visage : public FaceTracker { Q_OBJECT public: @@ -34,14 +36,6 @@ public: bool isActive() const { return _active; } - const glm::quat& getHeadRotation() const { return _headRotation; } - const glm::vec3& getHeadTranslation() const { return _headTranslation; } - - float getEstimatedEyePitch() const { return _estimatedEyePitch; } - float getEstimatedEyeYaw() const { return _estimatedEyeYaw; } - - const QVector& getBlendshapeCoefficients() const { return _blendshapeCoefficients; } - void update(); void reset(); @@ -61,15 +55,8 @@ private: bool _enabled; bool _active; - glm::quat _headRotation; - glm::vec3 _headTranslation; - + glm::vec3 _headOrigin; - - float _estimatedEyePitch; - float _estimatedEyeYaw; - - QVector _blendshapeCoefficients; }; -#endif /* defined(__interface__Visage__) */ +#endif // hifi_Visage_h diff --git a/interface/src/location/LocationManager.cpp b/interface/src/location/LocationManager.cpp index b83094ba4c..8009551b6c 100644 --- a/interface/src/location/LocationManager.cpp +++ b/interface/src/location/LocationManager.cpp @@ -1,9 +1,12 @@ // // LocationManager.cpp -// hifi +// interface/src/location // // Created by Stojce Slavkovski on 2/7/14. +// Copyright 2013 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 "Application.h" @@ -115,8 +118,6 @@ void LocationManager::checkForMultipleDestinations() { Application::getInstance()->getAvatar()->goToLocationFromResponse(_placeData); return; } - - emit locationChanged(); } } @@ -175,6 +176,7 @@ void LocationManager::goToOrientation(QString orientation) { glm::quat avatarOrientation = myAvatar->getOrientation(); if (newAvatarOrientation != avatarOrientation) { myAvatar->setOrientation(newAvatarOrientation); + emit myAvatar->transformChanged(); } } } @@ -208,6 +210,7 @@ bool LocationManager::goToDestination(QString destination) { qDebug("Going To Location: %f, %f, %f...", x, y, z); myAvatar->setPosition(newAvatarPos); + emit myAvatar->transformChanged(); } return true; diff --git a/interface/src/location/LocationManager.h b/interface/src/location/LocationManager.h index a6bdaf66b4..ac66b3d08b 100644 --- a/interface/src/location/LocationManager.h +++ b/interface/src/location/LocationManager.h @@ -1,13 +1,16 @@ // // LocationManager.h -// hifi +// interface/src/location // // Created by Stojce Slavkovski on 2/7/14. +// Copyright 2013 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__LocationManager__ -#define __hifi__LocationManager__ +#ifndef hifi_LocationManager_h +#define hifi_LocationManager_h #include @@ -45,7 +48,6 @@ private: signals: void creationCompleted(LocationManager::NamedLocationCreateResponse response); void multipleDestinationsFound(const QJsonObject& userData, const QJsonObject& placeData); - void locationChanged(); private slots: void namedLocationDataReceived(const QJsonObject& data); @@ -55,4 +57,4 @@ private slots: }; -#endif /* defined(__hifi__LocationManager__) */ +#endif // hifi_LocationManager_h diff --git a/interface/src/location/NamedLocation.cpp b/interface/src/location/NamedLocation.cpp index c6daef4961..ed7701a391 100644 --- a/interface/src/location/NamedLocation.cpp +++ b/interface/src/location/NamedLocation.cpp @@ -1,9 +1,12 @@ // -// LocationManager.cpp -// hifi +// NamedLocation.cpp +// interface/src/location // // Created by Stojce Slavkovski on 2/1/14. +// Copyright 2013 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 "NamedLocation.h" diff --git a/interface/src/location/NamedLocation.h b/interface/src/location/NamedLocation.h index 81af03b45e..ffbd157263 100644 --- a/interface/src/location/NamedLocation.h +++ b/interface/src/location/NamedLocation.h @@ -1,13 +1,16 @@ // // NamedLocation.h -// hifi +// interface/src/location // // Created by Stojce Slavkovski on 2/1/14. +// Copyright 2013 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__NamedLocation__ -#define __hifi__NamedLocation__ +#ifndef hifi_NamedLocation_h +#define hifi_NamedLocation_h #include #include @@ -55,4 +58,4 @@ private: }; -#endif /* defined(__hifi__NamedLocation__) */ +#endif // hifi_NamedLocation_h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index f2c516792b..6f9dc5e3bd 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -1,18 +1,11 @@ -// -// Interface // -// Allows you to connect to and see/hear the shared 3D space. -// Optionally uses serialUSB connection to get gyro data for head movement. -// Optionally gets UDP stream from transmitter to animate controller/hand. -// -// Usage: The interface client first attempts to contact a domain server to -// discover the appropriate audio, voxel, and avatar servers to contact. -// Right now, the default domain server is "highfidelity.below92.com" -// You can change the domain server to use your own by editing the -// DOMAIN_HOSTNAME or DOMAIN_IP strings in the file NodeList.cpp +// main.cpp +// interface/src // +// Copyright 2013 High Fidelity, Inc. // -// Welcome Aboard! +// 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" @@ -23,8 +16,9 @@ #include int main(int argc, const char * argv[]) { - timeval startup_time; - gettimeofday(&startup_time, NULL); + initialiseUsecTimestampNow(); + QElapsedTimer startupTime; + startupTime.start(); // Debug option to demonstrate that the client's local time does not // need to be in sync with any other network node. This forces clock @@ -40,7 +34,7 @@ int main(int argc, const char * argv[]) { int exitCode; { QSettings::setDefaultFormat(QSettings::IniFormat); - Application app(argc, const_cast(argv), startup_time); + Application app(argc, const_cast(argv), startupTime); QTranslator translator; translator.load("interface_en"); diff --git a/interface/src/renderer/AmbientOcclusionEffect.cpp b/interface/src/renderer/AmbientOcclusionEffect.cpp index a492552e7c..635e6c9bd6 100644 --- a/interface/src/renderer/AmbientOcclusionEffect.cpp +++ b/interface/src/renderer/AmbientOcclusionEffect.cpp @@ -1,9 +1,12 @@ // // AmbientOcclusionEffect.cpp -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 7/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL diff --git a/interface/src/renderer/AmbientOcclusionEffect.h b/interface/src/renderer/AmbientOcclusionEffect.h index e56164bad4..3b22c7629a 100644 --- a/interface/src/renderer/AmbientOcclusionEffect.h +++ b/interface/src/renderer/AmbientOcclusionEffect.h @@ -1,13 +1,16 @@ // // AmbientOcclusionEffect.h -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 7/14/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__AmbientOcclusionEffect__ -#define __interface__AmbientOcclusionEffect__ +#ifndef hifi_AmbientOcclusionEffect_h +#define hifi_AmbientOcclusionEffect_h class ProgramObject; @@ -37,4 +40,4 @@ private: GLuint _rotationTextureID; }; -#endif /* defined(__interface__AmbientOcclusionEffect__) */ +#endif // hifi_AmbientOcclusionEffect_h diff --git a/interface/src/renderer/GeometryCache.cpp b/interface/src/renderer/GeometryCache.cpp index b835e91a12..3a410ac5e2 100644 --- a/interface/src/renderer/GeometryCache.cpp +++ b/interface/src/renderer/GeometryCache.cpp @@ -1,9 +1,13 @@ // // GeometryCache.cpp -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 6/21/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -539,14 +543,14 @@ void NetworkGeometry::setGeometry(const FBXGeometry& geometry) { int totalIndices = 0; foreach (const FBXMeshPart& part, mesh.parts) { NetworkMeshPart networkPart; - if (!part.diffuseFilename.isEmpty()) { + if (!part.diffuseTexture.filename.isEmpty()) { networkPart.diffuseTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.diffuseFilename)), false, mesh.isEye); + _textureBase.resolved(QUrl(part.diffuseTexture.filename)), false, mesh.isEye, part.diffuseTexture.content); networkPart.diffuseTexture->setLoadPriorities(_loadPriorities); } - if (!part.normalFilename.isEmpty()) { + if (!part.normalTexture.filename.isEmpty()) { networkPart.normalTexture = Application::getInstance()->getTextureCache()->getTexture( - _textureBase.resolved(QUrl(part.normalFilename)), true); + _textureBase.resolved(QUrl(part.normalTexture.filename)), true, false, part.normalTexture.content); networkPart.normalTexture->setLoadPriorities(_loadPriorities); } networkMesh.parts.append(networkPart); diff --git a/interface/src/renderer/GeometryCache.h b/interface/src/renderer/GeometryCache.h index 252a0c401b..0ad4f73904 100644 --- a/interface/src/renderer/GeometryCache.h +++ b/interface/src/renderer/GeometryCache.h @@ -1,13 +1,16 @@ // // GeometryCache.h -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 6/21/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__GeometryCache__ -#define __interface__GeometryCache__ +#ifndef hifi_GeometryCache_h +#define hifi_GeometryCache_h // include this before QOpenGLBuffer, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -17,7 +20,7 @@ #include -#include "FBXReader.h" +#include class Model; class NetworkGeometry; @@ -137,4 +140,4 @@ public: int getTranslucentPartCount() const; }; -#endif /* defined(__interface__GeometryCache__) */ +#endif // hifi_GeometryCache_h diff --git a/interface/src/renderer/GlowEffect.cpp b/interface/src/renderer/GlowEffect.cpp index f926a81941..b52e8d8531 100644 --- a/interface/src/renderer/GlowEffect.cpp +++ b/interface/src/renderer/GlowEffect.cpp @@ -1,9 +1,13 @@ // // GlowEffect.cpp -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 8/7/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL #include "InterfaceConfig.h" diff --git a/interface/src/renderer/GlowEffect.h b/interface/src/renderer/GlowEffect.h index 65d3d6c8ce..b675589526 100644 --- a/interface/src/renderer/GlowEffect.h +++ b/interface/src/renderer/GlowEffect.h @@ -1,13 +1,16 @@ // // GlowEffect.h -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 8/7/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__GlowEffect__ -#define __interface__GlowEffect__ +#ifndef hifi_GlowEffect_h +#define hifi_GlowEffect_h #include #include @@ -80,4 +83,4 @@ public: ~Glower(); }; -#endif /* defined(__interface__GlowEffect__) */ +#endif // hifi_GlowEffect_h diff --git a/interface/src/renderer/Model.cpp b/interface/src/renderer/Model.cpp index 36ac6bec99..ef0911f673 100644 --- a/interface/src/renderer/Model.cpp +++ b/interface/src/renderer/Model.cpp @@ -1,9 +1,12 @@ // // Model.cpp -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 10/18/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -32,9 +35,11 @@ Model::Model(QObject* parent) : QObject(parent), _scale(1.0f, 1.0f, 1.0f), _shapesAreDirty(true), + _boundingRadius(0.f), + _boundingShape(), + _boundingShapeLocalOffset(0.f), _lodDistance(0.0f), - _pupilDilation(0.0f), - _boundingRadius(0.f) { + _pupilDilation(0.0f) { // we may have been created in the network thread, but we live in the main thread moveToThread(Application::getInstance()->thread()); } @@ -54,6 +59,17 @@ Model::SkinLocations Model::_skinLocations; Model::SkinLocations Model::_skinNormalMapLocations; Model::SkinLocations Model::_skinShadowLocations; +void Model::setScale(const glm::vec3& scale) { + float scaleLength = glm::length(_scale); + float relativeDeltaScale = glm::length(_scale - scale) / scaleLength; + + const float ONE_PERCENT = 0.01f; + if (relativeDeltaScale > ONE_PERCENT || scaleLength < EPSILON) { + _scale = scale; + rebuildShapes(); + } +} + void Model::initSkinProgram(ProgramObject& program, Model::SkinLocations& locations) { program.bind(); locations.clusterMatrices = program.uniformLocation("clusterMatrices"); @@ -73,6 +89,44 @@ QVector Model::createJointStates(const FBXGeometry& geometry) state.rotation = joint.rotation; jointStates.append(state); } + + // compute transforms + // Unfortunately, the joints are not neccessarily in order from parents to children, + // so we must iterate over the list multiple times until all are set correctly. + QVector jointIsSet; + int numJoints = jointStates.size(); + jointIsSet.fill(false, numJoints); + int numJointsSet = 0; + int lastNumJointsSet = -1; + while (numJointsSet < numJoints && numJointsSet != lastNumJointsSet) { + lastNumJointsSet = numJointsSet; + for (int i = 0; i < numJoints; ++i) { + if (jointIsSet[i]) { + continue; + } + JointState& state = jointStates[i]; + const FBXJoint& joint = geometry.joints[i]; + int parentIndex = joint.parentIndex; + if (parentIndex == -1) { + 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; + state.combinedRotation = _rotation * combinedRotation; + ++numJointsSet; + jointIsSet[i] = true; + } else if (jointIsSet[parentIndex]) { + const JointState& parentState = jointStates.at(parentIndex); + glm::quat combinedRotation = joint.preRotation * state.rotation * joint.postRotation; + state.transform = parentState.transform * glm::translate(state.translation) * joint.preTransform * + glm::mat4_cast(combinedRotation) * joint.postTransform; + state.combinedRotation = parentState.combinedRotation * combinedRotation; + ++numJointsSet; + jointIsSet[i] = true; + } + } + } + return jointStates; } @@ -142,66 +196,101 @@ void Model::reset() { } } -void Model::clearShapes() { - for (int i = 0; i < _shapes.size(); ++i) { - delete _shapes[i]; - } - _shapes.clear(); -} - -void Model::createCollisionShapes() { - clearShapes(); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - float uniformScale = extractUniformScale(_scale); - for (int i = 0; i < _jointStates.size(); i++) { - const FBXJoint& joint = geometry.joints[i]; - glm::vec3 meshCenter = _jointStates[i].combinedRotation * joint.shapePosition; - glm::vec3 position = _rotation * (extractTranslation(_jointStates[i].transform) + uniformScale * meshCenter) + _translation; - - float radius = uniformScale * joint.boneRadius; - if (joint.shapeType == Shape::CAPSULE_SHAPE) { - float halfHeight = 0.5f * uniformScale * joint.distanceToParent; - CapsuleShape* shape = new CapsuleShape(radius, halfHeight); - shape->setPosition(position); - _shapes.push_back(shape); - } else { - SphereShape* shape = new SphereShape(radius, position); - _shapes.push_back(shape); +bool Model::updateGeometry() { + // NOTE: this is a recursive call that walks all attachments, and their attachments + bool needFullUpdate = false; + for (int i = 0; i < _attachments.size(); i++) { + Model* model = _attachments.at(i); + if (model->updateGeometry()) { + needFullUpdate = true; } } -} -void Model::updateShapePositions() { - if (_shapesAreDirty && _shapes.size() == _jointStates.size()) { - _boundingRadius = 0.f; - float uniformScale = extractUniformScale(_scale); - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - for (int i = 0; i < _jointStates.size(); i++) { - const FBXJoint& joint = geometry.joints[i]; - // 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; - _shapes[i]->setPosition(worldPosition); - _shapes[i]->setRotation(_jointStates[i].combinedRotation * joint.shapeRotation); - float distance2 = glm::distance2(worldPosition, _translation); - if (distance2 > _boundingRadius) { - _boundingRadius = distance2; + bool needToRebuild = false; + if (_nextGeometry) { + _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis); + _nextGeometry->setLoadPriority(this, -_lodDistance); + _nextGeometry->ensureLoading(); + if (_nextGeometry->isLoaded()) { + applyNextGeometry(); + needToRebuild = true; + } + } + if (!_geometry) { + // geometry is not ready + return false; + } + + QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); + if (_geometry != geometry) { + // NOTE: it is theoretically impossible to reach here after passing through the applyNextGeometry() call above. + // Which means we don't need to worry about calling deleteGeometry() below immediately after creating new geometry. + + const FBXGeometry& newGeometry = geometry->getFBXGeometry(); + QVector newJointStates = createJointStates(newGeometry); + if (! _jointStates.isEmpty()) { + // copy the existing joint states + const FBXGeometry& oldGeometry = _geometry->getFBXGeometry(); + for (QHash::const_iterator it = oldGeometry.jointIndices.constBegin(); + it != oldGeometry.jointIndices.constEnd(); it++) { + int oldIndex = it.value() - 1; + int newIndex = newGeometry.getJointIndex(it.key()); + if (newIndex != -1) { + newJointStates[newIndex] = _jointStates.at(oldIndex); + } } + } + deleteGeometry(); + _dilatedTextures.clear(); + _geometry = geometry; + _jointStates = newJointStates; + needToRebuild = true; + } else if (_jointStates.isEmpty()) { + const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); + if (fbxGeometry.joints.size() > 0) { + _jointStates = createJointStates(fbxGeometry); + needToRebuild = true; } - _boundingRadius = sqrtf(_boundingRadius); - _shapesAreDirty = false; } + _geometry->setLoadPriority(this, -_lodDistance); + _geometry->ensureLoading(); + + if (needToRebuild) { + const FBXGeometry& fbxGeometry = geometry->getFBXGeometry(); + foreach (const FBXMesh& mesh, fbxGeometry.meshes) { + MeshState state; + state.clusterMatrices.resize(mesh.clusters.size()); + _meshStates.append(state); + + QOpenGLBuffer buffer; + if (!mesh.blendshapes.isEmpty()) { + buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); + buffer.create(); + buffer.bind(); + buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); + buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3)); + buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(), + mesh.normals.size() * sizeof(glm::vec3)); + buffer.release(); + } + _blendedVertexBuffers.append(buffer); + } + foreach (const FBXAttachment& attachment, fbxGeometry.attachments) { + Model* model = new Model(this); + model->init(); + model->setURL(attachment.url); + _attachments.append(model); + } + rebuildShapes(); + needFullUpdate = true; + } + return needFullUpdate; } -void Model::simulate(float deltaTime, bool fullUpdate) { - // update our LOD, then simulate - simulate(deltaTime, fullUpdate, updateGeometry()); -} - -bool Model::render(float alpha, bool forShadowMap) { +bool Model::render(float alpha, RenderMode mode) { // render the attachments foreach (Model* attachment, _attachments) { - attachment->render(alpha); + attachment->render(alpha, mode); } if (_meshStates.isEmpty()) { return false; @@ -222,20 +311,24 @@ bool Model::render(float alpha, bool forShadowMap) { glDisable(GL_COLOR_MATERIAL); - glEnable(GL_CULL_FACE); + if (mode == DIFFUSE_RENDER_MODE || mode == NORMAL_RENDER_MODE) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + } // render opaque meshes with alpha testing glEnable(GL_ALPHA_TEST); glAlphaFunc(GL_GREATER, 0.5f * alpha); - renderMeshes(alpha, forShadowMap, false); + renderMeshes(alpha, mode, false); glDisable(GL_ALPHA_TEST); - // render translucent meshes afterwards, with back face culling + // render translucent meshes afterwards - renderMeshes(alpha, forShadowMap, true); + renderMeshes(alpha, mode, true); glDisable(GL_CULL_FACE); @@ -264,15 +357,6 @@ Extents Model::getBindExtents() const { return scaledExtents; } -Extents Model::getStaticExtents() const { - if (!isActive()) { - return Extents(); - } - const Extents& staticExtents = _geometry->getFBXGeometry().staticExtents; - Extents scaledExtents = { staticExtents.minimum * _scale, staticExtents.maximum * _scale }; - return scaledExtents; -} - bool Model::getJointState(int index, glm::quat& rotation) const { if (index == -1 || index >= _jointStates.size()) { return false; @@ -375,6 +459,166 @@ void Model::setURL(const QUrl& url, const QUrl& fallback, bool retainCurrent, bo } } +void Model::clearShapes() { + for (int i = 0; i < _jointShapes.size(); ++i) { + delete _jointShapes[i]; + } + _jointShapes.clear(); +} + +void Model::rebuildShapes() { + clearShapes(); + + if (!_geometry) { + return; + } + + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + + if (geometry.joints.isEmpty()) { + return; + } + + int numJoints = geometry.joints.size(); + QVector transforms; + transforms.fill(glm::mat4(), numJoints); + QVector combinedRotations; + combinedRotations.fill(glm::quat(), numJoints); + QVector shapeIsSet; + shapeIsSet.fill(false, numJoints); + int rootIndex = 0; + + 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; + if (type == Shape::CAPSULE_SHAPE && halfHeight < EPSILON) { + // this capsule is effectively a sphere + type = Shape::SPHERE_SHAPE; + } + if (type == Shape::CAPSULE_SHAPE) { + CapsuleShape* capsule = new CapsuleShape(radius, halfHeight); + capsule->setPosition(worldPosition); + capsule->setRotation(combinedRotations[i] * joint.shapeRotation); + _jointShapes.push_back(capsule); + + glm::vec3 endPoint; + capsule->getEndPoint(endPoint); + glm::vec3 startPoint; + capsule->getStartPoint(startPoint); + + // add some points that bound a sphere at the center of the capsule + glm::vec3 axis = glm::vec3(radius); + shapeExtents.addPoint(worldPosition + axis); + shapeExtents.addPoint(worldPosition - axis); + + // add the two furthest surface points of the capsule + axis = (halfHeight + radius) * glm::normalize(endPoint - startPoint); + shapeExtents.addPoint(worldPosition + axis); + shapeExtents.addPoint(worldPosition - axis); + + totalExtents.addExtents(shapeExtents); + } else if (type == Shape::SPHERE_SHAPE) { + SphereShape* sphere = new SphereShape(radius, worldPosition); + _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); + _jointShapes.push_back(sphere); + } + } + + // bounding shape + // 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: + 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); + + glm::quat inverseRotation = glm::inverse(_rotation); + glm::vec3 rootPosition = extractTranslation(transforms[rootIndex]); + _boundingShapeLocalOffset = inverseRotation * (0.5f * (totalExtents.maximum + totalExtents.minimum) - rootPosition); + _boundingShape.setPosition(_translation - _rotation * _boundingShapeLocalOffset); + _boundingShape.setRotation(_rotation); +} + +void Model::updateShapePositions() { + if (_shapesAreDirty && _jointShapes.size() == _jointStates.size()) { + glm::vec3 rootPosition(0.f); + _boundingRadius = 0.f; + float uniformScale = extractUniformScale(_scale); + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + for (int i = 0; i < _jointStates.size(); i++) { + const FBXJoint& joint = geometry.joints[i]; + // 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; + } + if (joint.parentIndex == -1) { + rootPosition = worldPosition; + } + } + _boundingRadius = sqrtf(_boundingRadius); + _shapesAreDirty = false; + _boundingShape.setPosition(rootPosition + _rotation * _boundingShapeLocalOffset); + _boundingShape.setRotation(_rotation); + } +} + bool Model::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const { const glm::vec3 relativeOrigin = origin - _translation; const FBXGeometry& geometry = _geometry->getFBXGeometry(); @@ -408,9 +652,9 @@ bool Model::findCollisions(const QVector shapes, CollisionList& co bool collided = false; for (int i = 0; i < shapes.size(); ++i) { const Shape* theirShape = shapes[i]; - for (int j = 0; j < _shapes.size(); ++j) { - const Shape* ourShape = _shapes[j]; - if (ShapeCollider::shapeShape(theirShape, ourShape, collisions)) { + for (int j = 0; j < _jointShapes.size(); ++j) { + const Shape* ourShape = _jointShapes[j]; + if (ShapeCollider::collideShapes(theirShape, ourShape, collisions)) { collided = true; } } @@ -421,10 +665,9 @@ bool Model::findCollisions(const QVector shapes, CollisionList& co bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadius, CollisionList& collisions, int skipIndex) { bool collided = false; - updateShapePositions(); SphereShape sphere(sphereRadius, sphereCenter); const FBXGeometry& geometry = _geometry->getFBXGeometry(); - for (int i = 0; i < _shapes.size(); i++) { + for (int i = 0; i < _jointShapes.size(); i++) { const FBXJoint& joint = geometry.joints[i]; if (joint.parentIndex != -1) { if (skipIndex != -1) { @@ -438,7 +681,7 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi } while (ancestorIndex != -1); } } - if (ShapeCollider::shapeShape(&sphere, _shapes[i], collisions)) { + if (ShapeCollider::collideShapes(&sphere, _jointShapes[i], collisions)) { CollisionInfo* collision = collisions.getLastCollision(); collision->_type = MODEL_COLLISION; collision->_data = (void*)(this); @@ -450,43 +693,19 @@ bool Model::findSphereCollisions(const glm::vec3& sphereCenter, float sphereRadi return collided; } -QVector Model::updateGeometry() { - QVector newJointStates; - if (_nextGeometry) { - _nextGeometry = _nextGeometry->getLODOrFallback(_lodDistance, _nextLODHysteresis); - _nextGeometry->setLoadPriority(this, -_lodDistance); - _nextGeometry->ensureLoading(); - if (_nextGeometry->isLoaded()) { - applyNextGeometry(); - return newJointStates; +bool Model::findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions) { + bool collided = false; + PlaneShape planeShape(plane); + for (int i = 0; i < _jointShapes.size(); i++) { + if (ShapeCollider::collideShapes(&planeShape, _jointShapes[i], collisions)) { + CollisionInfo* collision = collisions.getLastCollision(); + collision->_type = MODEL_COLLISION; + collision->_data = (void*)(this); + collision->_flags = i; + collided = true; } } - if (!_geometry) { - return newJointStates; - } - QSharedPointer geometry = _geometry->getLODOrFallback(_lodDistance, _lodHysteresis); - if (_geometry != geometry) { - if (!_jointStates.isEmpty()) { - // copy the existing joint states - const FBXGeometry& oldGeometry = _geometry->getFBXGeometry(); - const FBXGeometry& newGeometry = geometry->getFBXGeometry(); - newJointStates = createJointStates(newGeometry); - for (QHash::const_iterator it = oldGeometry.jointIndices.constBegin(); - it != oldGeometry.jointIndices.constEnd(); it++) { - int oldIndex = it.value() - 1; - int newIndex = newGeometry.getJointIndex(it.key()); - if (newIndex != -1) { - newJointStates[newIndex] = _jointStates.at(oldIndex); - } - } - } - deleteGeometry(); - _dilatedTextures.clear(); - _geometry = geometry; - } - _geometry->setLoadPriority(this, -_lodDistance); - _geometry->ensureLoading(); - return newJointStates; + return collided; } class Blender : public QRunnable { @@ -551,53 +770,23 @@ void Blender::run() { Q_ARG(const QVector&, vertices), Q_ARG(const QVector&, normals)); } -void Model::simulate(float deltaTime, bool fullUpdate, const QVector& newJointStates) { - if (!isActive()) { - return; +void Model::simulate(float deltaTime, bool fullUpdate) { + fullUpdate = updateGeometry() || fullUpdate; + if (isActive() && fullUpdate) { + simulateInternal(deltaTime); } - - // set up world vertices on first simulate after load - const FBXGeometry& geometry = _geometry->getFBXGeometry(); - if (_jointStates.isEmpty()) { - _jointStates = newJointStates.isEmpty() ? createJointStates(geometry) : newJointStates; - foreach (const FBXMesh& mesh, geometry.meshes) { - MeshState state; - state.clusterMatrices.resize(mesh.clusters.size()); - _meshStates.append(state); - - QOpenGLBuffer buffer; - if (!mesh.blendshapes.isEmpty()) { - buffer.setUsagePattern(QOpenGLBuffer::DynamicDraw); - buffer.create(); - buffer.bind(); - buffer.allocate((mesh.vertices.size() + mesh.normals.size()) * sizeof(glm::vec3)); - buffer.write(0, mesh.vertices.constData(), mesh.vertices.size() * sizeof(glm::vec3)); - buffer.write(mesh.vertices.size() * sizeof(glm::vec3), mesh.normals.constData(), - mesh.normals.size() * sizeof(glm::vec3)); - buffer.release(); - } - _blendedVertexBuffers.append(buffer); - } - foreach (const FBXAttachment& attachment, geometry.attachments) { - Model* model = new Model(this); - model->init(); - model->setURL(attachment.url); - _attachments.append(model); - } - fullUpdate = true; - createCollisionShapes(); - } - - // exit early if we don't have to perform a full update - if (!fullUpdate) { - return; - } - +} + +void Model::simulateInternal(float 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++) { updateJointState(i); } + _shapesAreDirty = true; + const FBXGeometry& geometry = _geometry->getFBXGeometry(); + // update the attachment transforms and simulate them for (int i = 0; i < _attachments.size(); i++) { const FBXAttachment& attachment = geometry.attachments.at(i); @@ -612,7 +801,9 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector model->setRotation(jointRotation * attachment.rotation); model->setScale(_scale * attachment.scale); - model->simulate(deltaTime); + if (model->isActive()) { + model->simulateInternal(deltaTime); + } } for (int i = 0; i < _meshStates.size(); i++) { @@ -631,7 +822,6 @@ void Model::simulate(float deltaTime, bool fullUpdate, const QVector } void Model::updateJointState(int index) { - _shapesAreDirty = true; JointState& state = _jointStates[index]; const FBXGeometry& geometry = _geometry->getFBXGeometry(); const FBXJoint& joint = geometry.joints.at(index); @@ -643,7 +833,7 @@ void Model::updateJointState(int index) { state.transform = baseTransform * geometry.offset * glm::translate(state.translation) * joint.preTransform * glm::mat4_cast(combinedRotation) * joint.postTransform; state.combinedRotation = _rotation * combinedRotation; - + } else { const JointState& parentState = _jointStates.at(joint.parentIndex); if (index == geometry.leanJointIndex) { @@ -749,6 +939,7 @@ bool Model::setJointPosition(int jointIndex, const glm::vec3& position, int last for (int j = freeLineage.size() - 1; j >= 0; j--) { updateJointState(freeLineage.at(j)); } + _shapesAreDirty = true; return true; } @@ -827,15 +1018,15 @@ void Model::applyRotationDelta(int jointIndex, const glm::quat& delta, bool cons state.rotation = newRotation; } -void Model::renderCollisionProxies(float alpha) { +const int BALL_SUBDIVISIONS = 10; + +void Model::renderJointCollisionShapes(float alpha) { glPushMatrix(); Application::getInstance()->loadTranslatedViewMatrix(_translation); - updateShapePositions(); - const int BALL_SUBDIVISIONS = 10; - for (int i = 0; i < _shapes.size(); i++) { + for (int i = 0; i < _jointShapes.size(); i++) { glPushMatrix(); - Shape* shape = _shapes[i]; + Shape* shape = _jointShapes[i]; if (shape->getType() == Shape::SPHERE_SHAPE) { // shapes are stored in world-frame, so we have to transform into model frame @@ -878,6 +1069,36 @@ void Model::renderCollisionProxies(float alpha) { glPopMatrix(); } +void Model::renderBoundingCollisionShapes(float alpha) { + glPushMatrix(); + + Application::getInstance()->loadTranslatedViewMatrix(_translation); + + // draw a blue sphere at the capsule endpoint + glm::vec3 endPoint; + _boundingShape.getEndPoint(endPoint); + endPoint = endPoint - _translation; + glTranslatef(endPoint.x, endPoint.y, endPoint.z); + glColor4f(0.6f, 0.6f, 0.8f, alpha); + glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); + + // draw a yellow sphere at the capsule startpoint + glm::vec3 startPoint; + _boundingShape.getStartPoint(startPoint); + startPoint = startPoint - _translation; + glm::vec3 axis = endPoint - startPoint; + glTranslatef(-axis.x, -axis.y, -axis.z); + glColor4f(0.8f, 0.8f, 0.6f, alpha); + glutSolidSphere(_boundingShape.getRadius(), BALL_SUBDIVISIONS, BALL_SUBDIVISIONS); + + // draw a green cylinder between the two points + glm::vec3 origin(0.f); + glColor4f(0.6f, 0.8f, 0.6f, alpha); + Avatar::renderJointConnectingCone( origin, axis, _boundingShape.getRadius(), _boundingShape.getRadius()); + + glPopMatrix(); +} + bool Model::collisionHitsMoveableJoint(CollisionInfo& collision) const { if (collision._type == MODEL_COLLISION) { // the joint is pokable by a collision if it exists and is free to move @@ -975,7 +1196,7 @@ void Model::deleteGeometry() { } } -void Model::renderMeshes(float alpha, bool forShadowMap, bool translucent) { +void Model::renderMeshes(float alpha, RenderMode mode, bool translucent) { const FBXGeometry& geometry = _geometry->getFBXGeometry(); const QVector& networkMeshes = _geometry->getMeshes(); @@ -1000,7 +1221,7 @@ void Model::renderMeshes(float alpha, bool forShadowMap, bool translucent) { ProgramObject* program = &_program; ProgramObject* skinProgram = &_skinProgram; SkinLocations* skinLocations = &_skinLocations; - if (forShadowMap) { + if (mode == SHADOW_RENDER_MODE) { program = &_shadowProgram; skinProgram = &_skinShadowProgram; skinLocations = &_skinShadowLocations; @@ -1038,7 +1259,7 @@ void Model::renderMeshes(float alpha, bool forShadowMap, bool translucent) { } if (mesh.blendshapes.isEmpty()) { - if (!(mesh.tangents.isEmpty() || forShadowMap)) { + if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, vertexCount * 2 * sizeof(glm::vec3), 3); activeProgram->enableAttributeArray(tangentLocation); } @@ -1048,7 +1269,7 @@ void Model::renderMeshes(float alpha, bool forShadowMap, bool translucent) { (mesh.tangents.size() + mesh.colors.size()) * sizeof(glm::vec3))); } else { - if (!(mesh.tangents.isEmpty() || forShadowMap)) { + if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { activeProgram->setAttributeBuffer(tangentLocation, GL_FLOAT, 0, 3); activeProgram->enableAttributeArray(tangentLocation); } @@ -1077,7 +1298,7 @@ void Model::renderMeshes(float alpha, bool forShadowMap, bool translucent) { continue; } // apply material properties - if (forShadowMap) { + if (mode == SHADOW_RENDER_MODE) { glBindTexture(GL_TEXTURE_2D, 0); } else { @@ -1118,7 +1339,7 @@ void Model::renderMeshes(float alpha, bool forShadowMap, bool translucent) { glDisableClientState(GL_TEXTURE_COORD_ARRAY); } - if (!(mesh.tangents.isEmpty() || forShadowMap)) { + if (!(mesh.tangents.isEmpty() || mode == SHADOW_RENDER_MODE)) { glActiveTexture(GL_TEXTURE1); glBindTexture(GL_TEXTURE_2D, 0); glActiveTexture(GL_TEXTURE0); diff --git a/interface/src/renderer/Model.h b/interface/src/renderer/Model.h index 3fdf1cd291..a14b58c11a 100644 --- a/interface/src/renderer/Model.h +++ b/interface/src/renderer/Model.h @@ -1,17 +1,22 @@ // // Model.h -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 10/18/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__Model__ -#define __interface__Model__ +#ifndef hifi_Model_h +#define hifi_Model_h #include #include +#include + #include "GeometryCache.h" #include "InterfaceConfig.h" #include "ProgramObject.h" @@ -34,7 +39,7 @@ public: void setRotation(const glm::quat& rotation) { _rotation = rotation; } const glm::quat& getRotation() const { return _rotation; } - void setScale(const glm::vec3& scale) { _scale = scale; } + void setScale(const glm::vec3& scale); const glm::vec3& getScale() const { return _scale; } void setOffset(const glm::vec3& offset) { _offset = offset; } @@ -54,12 +59,12 @@ public: void init(); void reset(); - void clearShapes(); - void createCollisionShapes(); - void updateShapePositions(); - void simulate(float deltaTime, bool fullUpdate = true); - bool render(float alpha = 1.0f, bool forShadowMap = false); + virtual void simulate(float deltaTime, bool fullUpdate = true); + enum RenderMode { DEFAULT_RENDER_MODE, SHADOW_RENDER_MODE, DIFFUSE_RENDER_MODE, NORMAL_RENDER_MODE }; + + bool render(float alpha = 1.0f, RenderMode mode = DEFAULT_RENDER_MODE); + /// Sets the URL of the model to render. /// \param fallback the URL of a fallback model to render if the requested model fails to load /// \param retainCurrent if true, keep rendering the current model until the new one is loaded @@ -75,9 +80,6 @@ public: /// Returns the extents of the model in its bind pose. Extents getBindExtents() const; - /// Returns the extents of the unmovable joints of the model. - Extents getStaticExtents() const; - /// Returns a reference to the shared geometry. const QSharedPointer& getGeometry() const { return _geometry; } @@ -159,6 +161,12 @@ public: /// Returns the extended length from the right hand to its first free ancestor. float getRightArmLength() const; + void clearShapes(); + void rebuildShapes(); + void updateShapePositions(); + void renderJointCollisionShapes(float alpha); + void renderBoundingCollisionShapes(float alpha); + bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, float& distance) const; /// \param shapes list of pointers shapes to test against Model @@ -168,9 +176,9 @@ public: bool findSphereCollisions(const glm::vec3& penetratorCenter, float penetratorRadius, CollisionList& collisions, int skipIndex = -1); - - void renderCollisionProxies(float alpha); + bool findPlaneCollisions(const glm::vec4& plane, CollisionList& collisions); + /// \param collision details about the collisions /// \return true if the collision is against a moveable joint bool collisionHitsMoveableJoint(CollisionInfo& collision) const; @@ -180,10 +188,13 @@ public: void applyCollision(CollisionInfo& collision); float getBoundingRadius() const { return _boundingRadius; } + float getBoundingShapeRadius() const { return _boundingShape.getRadius(); } /// Sets blended vertices computed in a separate thread. void setBlendedVertices(const QVector& vertices, const QVector& normals); + const CapsuleShape& getBoundingShape() const { return _boundingShape; } + protected: QSharedPointer _geometry; @@ -203,7 +214,11 @@ protected: bool _shapesAreDirty; QVector _jointStates; - QVector _shapes; + QVector _jointShapes; + + float _boundingRadius; + CapsuleShape _boundingShape; + glm::vec3 _boundingShapeLocalOffset; class MeshState { public: @@ -212,9 +227,11 @@ protected: QVector _meshStates; - QVector updateGeometry(); - void simulate(float deltaTime, bool fullUpdate, const QVector& newJointStates); - + // returns 'true' if needs fullUpdate after geometry change + bool updateGeometry(); + + void simulateInternal(float deltaTime); + /// Updates the state of the joint at the specified index. virtual void updateJointState(int index); @@ -247,7 +264,8 @@ private: void applyNextGeometry(); void deleteGeometry(); - void renderMeshes(float alpha, bool forShadowMap, bool translucent); + void renderMeshes(float alpha, RenderMode mode, bool translucent); + QVector createJointStates(const FBXGeometry& geometry); QSharedPointer _baseGeometry; ///< reference required to prevent collection of base QSharedPointer _nextBaseGeometry; @@ -267,8 +285,6 @@ private: QVector _attachments; - float _boundingRadius; - static ProgramObject _program; static ProgramObject _normalMapProgram; static ProgramObject _shadowProgram; @@ -291,11 +307,10 @@ private: static SkinLocations _skinShadowLocations; static void initSkinProgram(ProgramObject& program, SkinLocations& locations); - static QVector createJointStates(const FBXGeometry& geometry); }; Q_DECLARE_METATYPE(QPointer) Q_DECLARE_METATYPE(QWeakPointer) Q_DECLARE_METATYPE(QVector) -#endif /* defined(__interface__Model__) */ +#endif // hifi_Model_h diff --git a/interface/src/renderer/PointShader.cpp b/interface/src/renderer/PointShader.cpp index 6dd4aae485..68df69f284 100644 --- a/interface/src/renderer/PointShader.cpp +++ b/interface/src/renderer/PointShader.cpp @@ -1,9 +1,13 @@ // // PointShader.cpp -// interface +// interface/src/renderer // // Created by Brad Hefta-Gaub on 10/30/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL #include "InterfaceConfig.h" diff --git a/interface/src/renderer/PointShader.h b/interface/src/renderer/PointShader.h index b7e1b8c008..1db4f1b201 100644 --- a/interface/src/renderer/PointShader.h +++ b/interface/src/renderer/PointShader.h @@ -1,13 +1,16 @@ // // PointShader.h -// interface +// interface/src/renderer // // Created by Brad Hefta-Gaub on 10/30/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__PointShader__ -#define __interface__PointShader__ +#ifndef hifi_PointShader_h +#define hifi_PointShader_h #include @@ -42,4 +45,4 @@ private: ProgramObject* _program; }; -#endif /* defined(__interface__PointShader__) */ +#endif // hifi_PointShader_h diff --git a/interface/src/renderer/ProgramObject.cpp b/interface/src/renderer/ProgramObject.cpp index 5108c15068..b88be69f07 100644 --- a/interface/src/renderer/ProgramObject.cpp +++ b/interface/src/renderer/ProgramObject.cpp @@ -1,9 +1,13 @@ // // ProgramObject.cpp -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 5/7/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 "ProgramObject.h" diff --git a/interface/src/renderer/ProgramObject.h b/interface/src/renderer/ProgramObject.h index 78f07b7a1b..21e01ac8b3 100644 --- a/interface/src/renderer/ProgramObject.h +++ b/interface/src/renderer/ProgramObject.h @@ -1,13 +1,16 @@ // // ProgramObject.h -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 5/7/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__ProgramObject__ -#define __interface__ProgramObject__ +#ifndef hifi_ProgramObject_h +#define hifi_ProgramObject_h #include @@ -22,4 +25,4 @@ public: void setUniform(const char* name, const glm::vec3& value); }; -#endif /* defined(__interface__ProgramObject__) */ +#endif // hifi_ProgramObject_h diff --git a/interface/src/renderer/RenderUtil.cpp b/interface/src/renderer/RenderUtil.cpp index 11ec8b572c..0575ceaa88 100644 --- a/interface/src/renderer/RenderUtil.cpp +++ b/interface/src/renderer/RenderUtil.cpp @@ -1,9 +1,13 @@ // // RenderUtil.cpp -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 8/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 "InterfaceConfig.h" #include "RenderUtil.h" diff --git a/interface/src/renderer/RenderUtil.h b/interface/src/renderer/RenderUtil.h index 5e7e9b5d64..637596c78b 100644 --- a/interface/src/renderer/RenderUtil.h +++ b/interface/src/renderer/RenderUtil.h @@ -1,15 +1,18 @@ // // RenderUtil.h -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 8/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__RenderUtil__ -#define __interface__RenderUtil__ +#ifndef hifi_RenderUtil_h +#define hifi_RenderUtil_h /// Renders a quad from (-1, -1, 0) to (1, 1, 0) with texture coordinates from (sMin, 0) to (sMax, 1). void renderFullscreenQuad(float sMin = 0.0f, float sMax = 1.0f); -#endif /* defined(__interface__RenderUtil__) */ +#endif // hifi_RenderUtil_h diff --git a/interface/src/renderer/TextureCache.cpp b/interface/src/renderer/TextureCache.cpp index b3820abf25..f31e4f9060 100644 --- a/interface/src/renderer/TextureCache.cpp +++ b/interface/src/renderer/TextureCache.cpp @@ -1,9 +1,13 @@ // // TextureCache.cpp -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 8/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -101,13 +105,22 @@ GLuint TextureCache::getBlueTextureID() { return _blueTextureID; } -QSharedPointer TextureCache::getTexture(const QUrl& url, bool normalMap, bool dilatable) { +/// Extra data for creating textures. +class TextureExtra { +public: + bool normalMap; + const QByteArray& content; +}; + +QSharedPointer TextureCache::getTexture(const QUrl& url, bool normalMap, + bool dilatable, const QByteArray& content) { if (!dilatable) { - return ResourceCache::getResource(url, QUrl(), false, &normalMap).staticCast(); + TextureExtra extra = { normalMap, content }; + return ResourceCache::getResource(url, QUrl(), false, &extra).staticCast(); } QSharedPointer texture = _dilatableNetworkTextures.value(url); if (texture.isNull()) { - texture = QSharedPointer(new DilatableNetworkTexture(url), &Resource::allReferencesCleared); + texture = QSharedPointer(new DilatableNetworkTexture(url, content), &Resource::allReferencesCleared); texture->setSelf(texture); texture->setCache(this); _dilatableNetworkTextures.insert(url, texture); @@ -211,7 +224,9 @@ bool TextureCache::eventFilter(QObject* watched, QEvent* event) { QSharedPointer TextureCache::createResource(const QUrl& url, const QSharedPointer& fallback, bool delayLoad, const void* extra) { - return QSharedPointer(new NetworkTexture(url, *(const bool*)extra), &Resource::allReferencesCleared); + const TextureExtra* textureExtra = static_cast(extra); + return QSharedPointer(new NetworkTexture(url, textureExtra->normalMap, textureExtra->content), + &Resource::allReferencesCleared); } QOpenGLFramebufferObject* TextureCache::createFramebufferObject() { @@ -234,25 +249,31 @@ Texture::~Texture() { glDeleteTextures(1, &_id); } -NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap) : - Resource(url), +NetworkTexture::NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content) : + Resource(url, !content.isEmpty()), _translucent(false) { if (!url.isValid()) { _loaded = true; - return; } // default to white/blue glBindTexture(GL_TEXTURE_2D, getID()); loadSingleColorTexture(normalMap ? OPAQUE_BLUE : OPAQUE_WHITE); glBindTexture(GL_TEXTURE_2D, 0); + + // if we have content, load it after we have our self pointer + if (!content.isEmpty()) { + _startedLoading = true; + QMetaObject::invokeMethod(this, "loadContent", Qt::QueuedConnection, Q_ARG(const QByteArray&, content)); + } } class ImageReader : public QRunnable { public: - ImageReader(const QWeakPointer& texture, QNetworkReply* reply); + ImageReader(const QWeakPointer& texture, QNetworkReply* reply, const QUrl& url = QUrl(), + const QByteArray& content = QByteArray()); virtual void run(); @@ -260,27 +281,37 @@ private: QWeakPointer _texture; QNetworkReply* _reply; + QUrl _url; + QByteArray _content; }; -ImageReader::ImageReader(const QWeakPointer& texture, QNetworkReply* reply) : +ImageReader::ImageReader(const QWeakPointer& texture, QNetworkReply* reply, + const QUrl& url, const QByteArray& content) : _texture(texture), - _reply(reply) { + _reply(reply), + _url(url), + _content(content) { } void ImageReader::run() { QSharedPointer texture = _texture.toStrongRef(); if (texture.isNull()) { - _reply->deleteLater(); + if (_reply) { + _reply->deleteLater(); + } return; } - QUrl url = _reply->url(); - QImage image = QImage::fromData(_reply->readAll()); - _reply->deleteLater(); + if (_reply) { + _url = _reply->url(); + _content = _reply->readAll(); + _reply->deleteLater(); + } + QImage image = QImage::fromData(_content); // enforce a fixed maximum const int MAXIMUM_SIZE = 1024; if (image.width() > MAXIMUM_SIZE || image.height() > MAXIMUM_SIZE) { - qDebug() << "Image greater than maximum size:" << url << image.width() << image.height(); + qDebug() << "Image greater than maximum size:" << _url << image.width() << image.height(); image = image.scaled(MAXIMUM_SIZE, MAXIMUM_SIZE, Qt::KeepAspectRatio); } @@ -312,7 +343,7 @@ void ImageReader::run() { } int imageArea = image.width() * image.height(); if (opaquePixels == imageArea) { - qDebug() << "Image with alpha channel is completely opaque:" << url; + qDebug() << "Image with alpha channel is completely opaque:" << _url; image = image.convertToFormat(QImage::Format_RGB888); } QMetaObject::invokeMethod(texture.data(), "setImage", Q_ARG(const QImage&, image), @@ -324,6 +355,10 @@ void NetworkTexture::downloadFinished(QNetworkReply* reply) { QThreadPool::globalInstance()->start(new ImageReader(_self, reply)); } +void NetworkTexture::loadContent(const QByteArray& content) { + QThreadPool::globalInstance()->start(new ImageReader(_self, NULL, _url, content)); +} + void NetworkTexture::setImage(const QImage& image, bool translucent) { _translucent = translucent; @@ -345,8 +380,8 @@ void NetworkTexture::imageLoaded(const QImage& image) { // nothing by default } -DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url) : - NetworkTexture(url, false), +DilatableNetworkTexture::DilatableNetworkTexture(const QUrl& url, const QByteArray& content) : + NetworkTexture(url, false, content), _innerRadius(0), _outerRadius(0) { diff --git a/interface/src/renderer/TextureCache.h b/interface/src/renderer/TextureCache.h index dc874ab7b0..f4444b6dfc 100644 --- a/interface/src/renderer/TextureCache.h +++ b/interface/src/renderer/TextureCache.h @@ -1,13 +1,16 @@ // // TextureCache.h -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 8/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__TextureCache__ -#define __interface__TextureCache__ +#ifndef hifi_TextureCache_h +#define hifi_TextureCache_h #include #include @@ -41,7 +44,8 @@ public: GLuint getBlueTextureID(); /// Loads a texture from the specified URL. - QSharedPointer getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false); + QSharedPointer getTexture(const QUrl& url, bool normalMap = false, bool dilatable = false, + const QByteArray& content = QByteArray()); /// Returns a pointer to the primary framebuffer object. This render target includes a depth component, and is /// used for scene rendering. @@ -112,7 +116,7 @@ class NetworkTexture : public Resource, public Texture { public: - NetworkTexture(const QUrl& url, bool normalMap); + NetworkTexture(const QUrl& url, bool normalMap, const QByteArray& content); /// Checks whether it "looks like" this texture is translucent /// (majority of pixels neither fully opaque or fully transparent). @@ -121,10 +125,12 @@ public: protected: virtual void downloadFinished(QNetworkReply* reply); - virtual void imageLoaded(const QImage& image); - + + Q_INVOKABLE void loadContent(const QByteArray& content); Q_INVOKABLE void setImage(const QImage& image, bool translucent); + virtual void imageLoaded(const QImage& image); + private: bool _translucent; @@ -136,7 +142,7 @@ class DilatableNetworkTexture : public NetworkTexture { public: - DilatableNetworkTexture(const QUrl& url); + DilatableNetworkTexture(const QUrl& url, const QByteArray& content); /// Returns a pointer to a texture with the requested amount of dilation. QSharedPointer getDilatedTexture(float dilation); @@ -155,4 +161,4 @@ private: QMap > _dilatedTextures; }; -#endif /* defined(__interface__TextureCache__) */ +#endif // hifi_TextureCache_h diff --git a/interface/src/renderer/VoxelShader.cpp b/interface/src/renderer/VoxelShader.cpp index fab40fc338..0982304adf 100644 --- a/interface/src/renderer/VoxelShader.cpp +++ b/interface/src/renderer/VoxelShader.cpp @@ -1,9 +1,13 @@ // // VoxelShader.cpp -// interface +// interface/src/renderer // // Created by Brad Hefta-Gaub on 9/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL #include "InterfaceConfig.h" diff --git a/interface/src/renderer/VoxelShader.h b/interface/src/renderer/VoxelShader.h index 33e8ea0073..cfcd27bba7 100644 --- a/interface/src/renderer/VoxelShader.h +++ b/interface/src/renderer/VoxelShader.h @@ -1,13 +1,16 @@ // // VoxelShader.h -// interface +// interface/src/renderer // // Created by Brad Hefta-Gaub on 9/23/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__VoxelShader__ -#define __interface__VoxelShader__ +#ifndef hifi_VoxelShader_h +#define hifi_VoxelShader_h #include @@ -43,4 +46,4 @@ private: ProgramObject* _program; }; -#endif /* defined(__interface__VoxelShader__) */ +#endif // hifi_VoxelShader_h diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.cpp b/interface/src/scripting/AudioDeviceScriptingInterface.cpp index a184e8a2f6..688b0942d5 100644 --- a/interface/src/scripting/AudioDeviceScriptingInterface.cpp +++ b/interface/src/scripting/AudioDeviceScriptingInterface.cpp @@ -1,9 +1,12 @@ // // AudioDeviceScriptingInterface.cpp -// hifi +// interface/src/scripting // -// Created by Brad Hefta-Gaub on 3/23/14 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 3/23/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 "Application.h" diff --git a/interface/src/scripting/AudioDeviceScriptingInterface.h b/interface/src/scripting/AudioDeviceScriptingInterface.h index adc86cb15c..62f1153a0b 100644 --- a/interface/src/scripting/AudioDeviceScriptingInterface.h +++ b/interface/src/scripting/AudioDeviceScriptingInterface.h @@ -1,13 +1,16 @@ // // AudioDeviceScriptingInterface.h -// hifi +// interface/src/scripting // -// Created by Brad Hefta-Gaub on 3/22/14 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 3/22/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__AudioDeviceScriptingInterface__ -#define __hifi__AudioDeviceScriptingInterface__ +#ifndef hifi_AudioDeviceScriptingInterface_h +#define hifi_AudioDeviceScriptingInterface_h #include #include @@ -38,4 +41,4 @@ public slots: void setInputVolume(float volume); }; -#endif /* defined(__hifi__AudioDeviceScriptingInterface__) */ +#endif // hifi_AudioDeviceScriptingInterface_h diff --git a/interface/src/scripting/ClipboardScriptingInterface.cpp b/interface/src/scripting/ClipboardScriptingInterface.cpp index 2ddef524aa..e8fb545343 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.cpp +++ b/interface/src/scripting/ClipboardScriptingInterface.cpp @@ -1,8 +1,11 @@ // // ClipboardScriptingInterface.cpp -// interface +// interface/src/scripting // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 "Application.h" @@ -75,7 +78,7 @@ void ClipboardScriptingInterface::exportVoxel(float x, float y, float z, float s } bool ClipboardScriptingInterface::importVoxels() { - qDebug() << "[DEBUG] Importing ... "; + qDebug() << "Importing ... "; QEventLoop loop; connect(Application::getInstance(), SIGNAL(importDone()), &loop, SLOT(quit())); emit readyToImport(); @@ -96,4 +99,4 @@ void ClipboardScriptingInterface::nudgeVoxel(float x, float y, float z, float s, s / (float)TREE_SCALE }; Application::getInstance()->nudgeVoxelsByVector(sourceVoxel, nudgeVecInTreeSpace); -} \ No newline at end of file +} diff --git a/interface/src/scripting/ClipboardScriptingInterface.h b/interface/src/scripting/ClipboardScriptingInterface.h index 78e72d6de0..f0258b0cc7 100644 --- a/interface/src/scripting/ClipboardScriptingInterface.h +++ b/interface/src/scripting/ClipboardScriptingInterface.h @@ -1,14 +1,15 @@ // // ClipboardScriptingInterface.h -// interface +// interface/src/scripting // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // -// Scriptable interface for the Application clipboard +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef __interface__Clipboard__ -#define __interface__Clipboard__ +#ifndef hifi_ClipboardScriptingInterface_h +#define hifi_ClipboardScriptingInterface_h #include #include @@ -43,4 +44,4 @@ public slots: void nudgeVoxel(float x, float y, float z, float s, const glm::vec3& nudgeVec); }; -#endif // __interface__Clipboard__ +#endif // hifi_ClipboardScriptingInterface_h diff --git a/interface/src/scripting/ControllerScriptingInterface.cpp b/interface/src/scripting/ControllerScriptingInterface.cpp index f5ab1653f5..aa14f769de 100644 --- a/interface/src/scripting/ControllerScriptingInterface.cpp +++ b/interface/src/scripting/ControllerScriptingInterface.cpp @@ -1,9 +1,12 @@ // -// ControllerScriptingInterface.h -// hifi +// ControllerScriptingInterface.cpp +// interface/src/scripting // -// Created by Brad Hefta-Gaub on 12/17/13 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/17/13. +// Copyright 2013 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 diff --git a/interface/src/scripting/ControllerScriptingInterface.h b/interface/src/scripting/ControllerScriptingInterface.h index 6fe5a60fa4..c5d805dca5 100644 --- a/interface/src/scripting/ControllerScriptingInterface.h +++ b/interface/src/scripting/ControllerScriptingInterface.h @@ -1,14 +1,16 @@ - // // ControllerScriptingInterface.h -// hifi +// interface/src/scripting // -// Created by Brad Hefta-Gaub on 12/17/13 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/17/13. +// Copyright 2013 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__ControllerScriptingInterface__ -#define __hifi__ControllerScriptingInterface__ +#ifndef hifi_ControllerScriptingInterface_h +#define hifi_ControllerScriptingInterface_h #include @@ -96,4 +98,4 @@ const int NUMBER_OF_BUTTONS_PER_PALM = 6; const int PALM_SPATIALCONTROL = 0; const int TIP_SPATIALCONTROL = 1; -#endif /* defined(__hifi__ControllerScriptingInterface__) */ +#endif // hifi_ControllerScriptingInterface_h diff --git a/interface/src/scripting/MenuScriptingInterface.cpp b/interface/src/scripting/MenuScriptingInterface.cpp index 4f9003b288..11d681bfc7 100644 --- a/interface/src/scripting/MenuScriptingInterface.cpp +++ b/interface/src/scripting/MenuScriptingInterface.cpp @@ -1,9 +1,12 @@ // // MenuScriptingInterface.cpp -// hifi +// interface/src/scripting // -// Created by Brad Hefta-Gaub on 2/25/14 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/25/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 "Application.h" diff --git a/interface/src/scripting/MenuScriptingInterface.h b/interface/src/scripting/MenuScriptingInterface.h index a4496e3054..d127bd6edc 100644 --- a/interface/src/scripting/MenuScriptingInterface.h +++ b/interface/src/scripting/MenuScriptingInterface.h @@ -1,13 +1,16 @@ // // MenuScriptingInterface.h -// hifi +// interface/src/scripting // -// Created by Brad Hefta-Gaub on 2/25/14 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/25/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__MenuScriptingInterface__ -#define __hifi__MenuScriptingInterface__ +#ifndef hifi_MenuScriptingInterface_h +#define hifi_MenuScriptingInterface_h #include #include @@ -47,4 +50,4 @@ signals: void menuItemEvent(const QString& menuItem); }; -#endif /* defined(__hifi__MenuScriptingInterface__) */ +#endif // hifi_MenuScriptingInterface_h diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index 2a788c2776..ea9d645a94 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -1,9 +1,12 @@ // // SettingsScriptingInterface.cpp -// hifi +// interface/src/scripting // -// Created by Brad Hefta-Gaub on 2/25/14 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/25/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 "Application.h" diff --git a/interface/src/scripting/SettingsScriptingInterface.h b/interface/src/scripting/SettingsScriptingInterface.h index 12bda2173f..e3138be4a0 100644 --- a/interface/src/scripting/SettingsScriptingInterface.h +++ b/interface/src/scripting/SettingsScriptingInterface.h @@ -1,13 +1,16 @@ // // SettingsScriptingInterface.h -// hifi +// interface/src/scripting // -// Created by Brad Hefta-Gaub on 3/22/14 -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 3/22/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__SettingsScriptingInterface__ -#define __hifi__SettingsScriptingInterface__ +#ifndef hifi_SettingsScriptingInterface_h +#define hifi_SettingsScriptingInterface_h #include #include @@ -27,4 +30,4 @@ public slots: void setValue(const QString& setting, const QVariant& value); }; -#endif /* defined(__hifi__SettingsScriptingInterface__) */ +#endif // hifi_SettingsScriptingInterface_h diff --git a/interface/src/starfield/Config.h b/interface/src/starfield/Config.h index b92aec9a4c..7777c5207b 100755 --- a/interface/src/starfield/Config.h +++ b/interface/src/starfield/Config.h @@ -1,17 +1,16 @@ // -// starfield/Config.h -// interface +// Config.h +// interface/src/starfield // -// Created by Tobias Schwinger on 3/29/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/29/13. +// Copyright 2013 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 __interface__starfield__Config__ -#define __interface__starfield__Config__ - -// -// Dependencies: -// +#ifndef hifi_Config_h +#define hifi_Config_h #include "InterfaceConfig.h" #include "renderer/ProgramObject.h" @@ -59,5 +58,4 @@ namespace starfield { } -#endif - +#endif // hifi_Config_h diff --git a/interface/src/starfield/Controller.cpp b/interface/src/starfield/Controller.cpp index 0987a14dd5..2279a68422 100755 --- a/interface/src/starfield/Controller.cpp +++ b/interface/src/starfield/Controller.cpp @@ -1,31 +1,31 @@ // -// starfield/Controller.cpp -// interface +// Controller.cpp +// interface/src/starfield // -// Created by Chris Barnard on 10/16/13 -// Portions of code based on earlier work by Tobias Schwinger. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Chris Barnard on 10/16/13. +// Copyright 2013 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 // -#ifdef _WIN32 -#define WANT_TIMEVAL -#include -#endif +#include #include "starfield/Controller.h" using namespace starfield; bool Controller::computeStars(unsigned numStars, unsigned seed) { - timeval startTime; - gettimeofday(&startTime, NULL); + QElapsedTimer startTime; + startTime.start(); Generator::computeStarPositions(_inputSequence, numStars, seed); this->retile(numStars, _tileResolution); - qDebug() << "Total time to retile and generate stars: " - << ((usecTimestampNow() - usecTimestamp(&startTime)) / 1000) << "msec"; + double NSEC_TO_MSEC = 1.0 / 1000000.0; + double timeDiff = (double)startTime.nsecsElapsed() * NSEC_TO_MSEC; + qDebug() << "Total time to retile and generate stars: " << timeDiff << "msec"; return true; } diff --git a/interface/src/starfield/Controller.h b/interface/src/starfield/Controller.h index 5403d2fb75..06db5cb833 100755 --- a/interface/src/starfield/Controller.h +++ b/interface/src/starfield/Controller.h @@ -1,14 +1,17 @@ // -// starfield/Controller.h -// interface +// Controller.h +// interface/src/starfield // -// Created by Tobias Schwinger on 3/29/13. -// Modified by Chris Barnard 10/16/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/29/13. +// Modified by Chris Barnard on 10/16/13. +// Copyright 2013 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 __interface__starfield__Controller__ -#define __interface__starfield__Controller__ +#ifndef hifi_Controller_h +#define hifi_Controller_h #include @@ -38,4 +41,4 @@ namespace starfield { Renderer* _renderer; }; } -#endif +#endif // hifi_Controller_h diff --git a/interface/src/starfield/Generator.cpp b/interface/src/starfield/Generator.cpp index 6065140505..d9773e4452 100644 --- a/interface/src/starfield/Generator.cpp +++ b/interface/src/starfield/Generator.cpp @@ -1,15 +1,15 @@ // -// starfield/Generator.cpp -// interface +// Generator.cpp +// interface/src/starfield // -// Created by Chris Barnard on 10/13/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Chris Barnard on 10/13/13. +// Copyright 2013 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 // -#ifdef _WIN32 -#define WANT_TIMEVAL -#include -#endif +#include #include "starfield/Generator.h" @@ -22,8 +22,8 @@ void Generator::computeStarPositions(InputVertices& destination, unsigned limit, InputVertices* vertices = & destination; //_limit = limit; - timeval startTime; - gettimeofday(&startTime, NULL); + QElapsedTimer startTime; + startTime.start(); srand(seed); @@ -68,7 +68,8 @@ void Generator::computeStarPositions(InputVertices& destination, unsigned limit, vertices->push_back(InputVertex(azimuth, altitude, computeStarColor(STAR_COLORIZATION))); } - qDebug() << "Total time to generate stars: " << ((usecTimestampNow() - usecTimestamp(&startTime)) / 1000) << " msec"; + double timeDiff = (double)startTime.nsecsElapsed() / 1000000.0; // ns to ms + qDebug() << "Total time to generate stars: " << timeDiff << " msec"; } // computeStarColor diff --git a/interface/src/starfield/Generator.h b/interface/src/starfield/Generator.h index 1029bd6a52..5352b3daf1 100644 --- a/interface/src/starfield/Generator.h +++ b/interface/src/starfield/Generator.h @@ -1,13 +1,16 @@ // -// starfield/Generator.h -// interface +// Generator.h +// interface/src/starfield // -// Created by Chris Barnard on 10/13/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Chris Barnard on 10/13/13. +// Copyright 2013 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 __interface__starfield__Generator__ -#define __interface__starfield__Generator__ +#ifndef hifi_Generator_h +#define hifi_Generator_h #include #include @@ -34,4 +37,4 @@ namespace starfield { }; } -#endif \ No newline at end of file +#endif // hifi_Generator_h diff --git a/interface/src/starfield/data/GpuVertex.cpp b/interface/src/starfield/data/GpuVertex.cpp index 58db1d499a..b7fee1f2b1 100755 --- a/interface/src/starfield/data/GpuVertex.cpp +++ b/interface/src/starfield/data/GpuVertex.cpp @@ -1,11 +1,15 @@ // -// starfield/data/GpuVertex.cpp -// interface +// GpuVertex.cpp +// interface/src/starfield/data // -// Created by Chris Barnard on 10/17/13. -// Based on code by Tobias Schwinger on 3/29/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Chris Barnard on 10/17/13. +// Based on code by Tobias Schwinger on 3/29/13. +// Copyright 2013 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 "starfield/data/GpuVertex.h" using namespace starfield; diff --git a/interface/src/starfield/data/GpuVertex.h b/interface/src/starfield/data/GpuVertex.h index 39eeb19634..f3b3ccd451 100755 --- a/interface/src/starfield/data/GpuVertex.h +++ b/interface/src/starfield/data/GpuVertex.h @@ -1,14 +1,17 @@ // -// starfield/data/GpuVertex.h -// interface +// GpuVertex.h +// interface/src/starfield/data // -// Created by Tobias Schwinger on 3/29/13. -// Modified 10/17/13 Chris Barnard. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/29/13. +// Modified by Chris Barnard on 10/17/13. +// Copyright 2013 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 __interface__starfield__data__GpuVertex__ -#define __interface__starfield__data__GpuVertex__ +#ifndef hifi_GpuVertex_h +#define hifi_GpuVertex_h #include "starfield/data/InputVertex.h" @@ -31,5 +34,4 @@ namespace starfield { } -#endif - +#endif // hifi_GpuVertex_h diff --git a/interface/src/starfield/data/InputVertex.cpp b/interface/src/starfield/data/InputVertex.cpp index 28bffb0ec0..5dead34beb 100755 --- a/interface/src/starfield/data/InputVertex.cpp +++ b/interface/src/starfield/data/InputVertex.cpp @@ -1,10 +1,13 @@ // -// starfield/data/InputVertex.cpp -// interface +// InputVertex.cpp +// interface/src/starfield/data // -// Created by Chris Barnard on 10/17.13. -// Based on code by Tobias Schwinger 3/29/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Chris Barnard on 10/17/13. +// Based on code by Tobias Schwinger 3/29/13. +// Copyright 2013 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 "starfield/data/InputVertex.h" diff --git a/interface/src/starfield/data/InputVertex.h b/interface/src/starfield/data/InputVertex.h index d8c21a37a2..2eeda183ec 100755 --- a/interface/src/starfield/data/InputVertex.h +++ b/interface/src/starfield/data/InputVertex.h @@ -1,14 +1,17 @@ // -// starfield/data/InputVertex.h -// interface +// InputVertex.h +// interface/src/starfield/data // -// Created by Tobias Schwinger on 3/29/13. -// Modified by Chris Barnard 10/17/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/29/13. +// Modified by Chris Barnard on 10/17/13. +// Copyright 2013 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 __interface__starfield__data__InputVertex__ -#define __interface__starfield__data__InputVertex__ +#ifndef hifi_InputVertex_h +#define hifi_InputVertex_h #include "starfield/Config.h" @@ -33,5 +36,4 @@ namespace starfield { } -#endif - +#endif // hifi_InputVertex_h diff --git a/interface/src/starfield/data/Tile.h b/interface/src/starfield/data/Tile.h index bdef4e612f..7273394984 100755 --- a/interface/src/starfield/data/Tile.h +++ b/interface/src/starfield/data/Tile.h @@ -1,13 +1,16 @@ // -// starfield/data/Tile.h -// interface +// Tile.h +// interface/src/starfield/data // -// Created by Tobias Schwinger on 3/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/22/13. +// Copyright 2013 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 __interface__starfield__data__Tile__ -#define __interface__starfield__data__Tile__ +#ifndef hifi_Tile_h +#define hifi_Tile_h #include "starfield/Config.h" @@ -26,5 +29,4 @@ namespace starfield { } -#endif - +#endif // hifi_Tile_h diff --git a/interface/src/starfield/renderer/Renderer.cpp b/interface/src/starfield/renderer/Renderer.cpp index 0be9850338..034a571814 100755 --- a/interface/src/starfield/renderer/Renderer.cpp +++ b/interface/src/starfield/renderer/Renderer.cpp @@ -1,10 +1,13 @@ // -// starfield/renderer/Renderer.cpp -// interface +// Renderer.cpp +// interface/src/starfield/renderer // -// Created by Chris Barnard on 10/17/13. -// Based on earlier work by Tobias Schwinger 3/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Chris Barnard on 10/17/13. +// Based on earlier work by Tobias Schwinger 3/22/13. +// Copyright 2013 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 "starfield/renderer/Renderer.h" diff --git a/interface/src/starfield/renderer/Renderer.h b/interface/src/starfield/renderer/Renderer.h index 7504dd0182..1e271512d2 100755 --- a/interface/src/starfield/renderer/Renderer.h +++ b/interface/src/starfield/renderer/Renderer.h @@ -1,14 +1,17 @@ // -// starfield/renderer/Renderer.h -// interface +// Renderer.h +// interface/src/starfield/renderer // -// Created by Tobias Schwinger on 3/22/13. -// Modified 10/17/13 Chris Barnard. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/22/13. +// Modified by Chris Barnard on 10/17/13. +// Copyright 2013 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 __interface__starfield__renderer__Renderer__ -#define __interface__starfield__renderer__Renderer__ +#ifndef hifi_Renderer_h +#define hifi_Renderer_h #include "starfield/Config.h" #include "starfield/data/InputVertex.h" @@ -136,5 +139,4 @@ namespace starfield { } -#endif - +#endif // hifi_Renderer_h diff --git a/interface/src/starfield/renderer/Tiling.h b/interface/src/starfield/renderer/Tiling.h index d4a1446690..e299b0998f 100755 --- a/interface/src/starfield/renderer/Tiling.h +++ b/interface/src/starfield/renderer/Tiling.h @@ -1,13 +1,16 @@ // -// starfield/renderer/Tiling.h -// interface +// Tiling.h +// interface/src/starfield/renderer // -// Created by Tobias Schwinger on 3/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/22/13. +// Copyright 2013 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 __interface__starfield__renderer__Tiling__ -#define __interface__starfield__renderer__Tiling__ +#ifndef hifi_Tiling_h +#define hifi_Tiling_h #include "starfield/Config.h" @@ -42,5 +45,4 @@ namespace starfield { }; } -#endif - +#endif // hifi_Tiling_h diff --git a/interface/src/starfield/renderer/VertexOrder.cpp b/interface/src/starfield/renderer/VertexOrder.cpp index 78f5229f90..ec36f1eea2 100755 --- a/interface/src/starfield/renderer/VertexOrder.cpp +++ b/interface/src/starfield/renderer/VertexOrder.cpp @@ -1,11 +1,13 @@ // -// starfield/renderer/VertexOrder.cpp -// interface +// VertexOrder.cpp +// interface/src/starfield/renderer // -// Created by Chris Barnard on 10/17/13. -// Based on code by Tobias Schwinger on 3/22/13. +// Created by Chris Barnard on 10/17/13. +// Based on code by Tobias Schwinger on 3/22/13. +// Copyright 2013 High Fidelity, Inc. // -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "starfield/renderer/VertexOrder.h" @@ -15,4 +17,4 @@ using namespace starfield; bool VertexOrder::bit(InputVertex const& vertex, state_type const& state) const { unsigned key = _tiling.getTileIndex(vertex.getAzimuth(), vertex.getAltitude()); return base::bit(key, state); -} \ No newline at end of file +} diff --git a/interface/src/starfield/renderer/VertexOrder.h b/interface/src/starfield/renderer/VertexOrder.h index 85c738ce0d..e45ccdedb0 100755 --- a/interface/src/starfield/renderer/VertexOrder.h +++ b/interface/src/starfield/renderer/VertexOrder.h @@ -1,13 +1,16 @@ // -// starfield/renderer/VertexOrder.h -// interface +// VertexOrder.h +// interface/src/starfield/renderer // -// Created by Tobias Schwinger on 3/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/22/13. +// Copyright 2013 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 __interface__starfield__renderer__VertexOrder__ -#define __interface__starfield__renderer__VertexOrder__ +#ifndef hifi_VertexOrder_h +#define hifi_VertexOrder_h #include "starfield/Config.h" #include "starfield/data/InputVertex.h" @@ -35,5 +38,4 @@ namespace starfield { } // anonymous namespace -#endif - +#endif // hifi_VertexOrder_h diff --git a/interface/src/ui/BandwidthDialog.cpp b/interface/src/ui/BandwidthDialog.cpp index 8cefc9cf93..3e6d9d876b 100644 --- a/interface/src/ui/BandwidthDialog.cpp +++ b/interface/src/ui/BandwidthDialog.cpp @@ -1,9 +1,12 @@ // -// BandwidthDialog.h -// interface +// BandwidthDialog.cpp +// interface/src/ui // // Created by Tobias Schwinger on 6/21/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/interface/src/ui/BandwidthDialog.h b/interface/src/ui/BandwidthDialog.h index a702b1be23..c1e9c5b82e 100644 --- a/interface/src/ui/BandwidthDialog.h +++ b/interface/src/ui/BandwidthDialog.h @@ -1,13 +1,16 @@ // // BandwidthDialog.h -// interface +// interface/src/ui // // Created by Tobias Schwinger on 6/21/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__BandwidthDialog__ -#define __hifi__BandwidthDialog__ +#ifndef hifi_BandwidthDialog_h +#define hifi_BandwidthDialog_h #include #include @@ -43,5 +46,4 @@ private: QLabel* _labels[BandwidthMeter::N_STREAMS]; }; -#endif /* defined(__interface__BandwidthDialog__) */ - +#endif // hifi_BandwidthDialog_h diff --git a/interface/src/ui/BandwidthMeter.cpp b/interface/src/ui/BandwidthMeter.cpp index 64ff74b846..0f41a1a5cf 100644 --- a/interface/src/ui/BandwidthMeter.cpp +++ b/interface/src/ui/BandwidthMeter.cpp @@ -1,9 +1,12 @@ // -// BandwidthMeter.h -// interface +// BandwidthMeter.cpp +// interface/src/ui // // Created by Tobias Schwinger on 6/20/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -42,7 +45,8 @@ namespace { // .cpp-local BandwidthMeter::ChannelInfo BandwidthMeter::_CHANNELS[] = { { "Audio" , "Kbps", 8000.0 / 1024.0, 0x33cc99ff }, { "Avatars" , "Kbps", 8000.0 / 1024.0, 0xffef40c0 }, - { "Voxels" , "Kbps", 8000.0 / 1024.0, 0xd0d0d0a0 } + { "Voxels" , "Kbps", 8000.0 / 1024.0, 0xd0d0d0a0 }, + { "Metavoxels", "Kbps", 8000.0 / 1024.0, 0xd0d0d0a0 } }; BandwidthMeter::BandwidthMeter() : @@ -58,26 +62,21 @@ BandwidthMeter::~BandwidthMeter() { free(_channels); } -BandwidthMeter::Stream::Stream(float msToAverage) : - _value(0.0f), - _msToAverage(msToAverage) { - - gettimeofday(& _prevTime, NULL); +BandwidthMeter::Stream::Stream(float msToAverage) : _value(0.0f), _msToAverage(msToAverage) { + _prevTime.start(); } void BandwidthMeter::Stream::updateValue(double amount) { // Determine elapsed time - timeval now; - gettimeofday(& now, NULL); - double dt = diffclock(& _prevTime, & now); + double dt = (double)_prevTime.nsecsElapsed() / 1000000.0; // ns to ms // Ignore this value when timer imprecision yields dt = 0 if (dt == 0.0) { return; } - memcpy(& _prevTime, & now, sizeof(timeval)); + _prevTime.start(); // Compute approximate average _value = glm::mix(_value, amount / dt, diff --git a/interface/src/ui/BandwidthMeter.h b/interface/src/ui/BandwidthMeter.h index 5879b1546d..c6a28a21c3 100644 --- a/interface/src/ui/BandwidthMeter.h +++ b/interface/src/ui/BandwidthMeter.h @@ -1,18 +1,18 @@ // // BandwidthMeter.h -// interface +// interface/src/ui // // Created by Tobias Schwinger on 6/20/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__BandwidthMeter__ -#define __interface__BandwidthMeter__ +#ifndef hifi_BandwidthMeter_h +#define hifi_BandwidthMeter_h -#ifdef _WIN32 -#define WANT_TIMEVAL -#include -#endif +#include #include @@ -30,11 +30,11 @@ public: bool isWithinArea(int x, int y, int screenWidth, int screenHeight); // Number of channels / streams. - static size_t const N_CHANNELS = 3; + static size_t const N_CHANNELS = 4; static size_t const N_STREAMS = N_CHANNELS * 2; // Channel usage. - enum ChannelIndex { AUDIO, AVATARS, VOXELS }; + enum ChannelIndex { AUDIO, AVATARS, VOXELS, METAVOXELS }; // Meta information held for a communication channel (bidirectional). struct ChannelInfo { @@ -57,7 +57,7 @@ public: private: double _value; // Current value. double _msToAverage; // Milliseconds to average. - timeval _prevTime; // Time of last feed. + QElapsedTimer _prevTime; // Time of last feed. }; // Data model accessors @@ -84,5 +84,4 @@ private: int _scaleMaxIndex; }; -#endif /* defined(__interface__BandwidthMeter__) */ - +#endif // hifi_BandwidthMeter_h diff --git a/interface/src/ui/ChatMessageArea.cpp b/interface/src/ui/ChatMessageArea.cpp new file mode 100644 index 0000000000..f15b788990 --- /dev/null +++ b/interface/src/ui/ChatMessageArea.cpp @@ -0,0 +1,44 @@ +// +// ChatMessageArea.cpp +// interface/src/ui +// +// Created by Ryan Huffman on 4/11/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 "ChatMessageArea.h" +#include +#include + +ChatMessageArea::ChatMessageArea() : QTextBrowser() { + connect(document()->documentLayout(), &QAbstractTextDocumentLayout::documentSizeChanged, + this, &ChatMessageArea::updateLayout); +} + +void ChatMessageArea::setHtml(const QString& html) { + // Create format with updated line height + QTextBlockFormat format; + format.setLineHeight(CHAT_MESSAGE_LINE_HEIGHT, QTextBlockFormat::ProportionalHeight); + + // Possibly a bug in QT, the format won't take effect if `insertHtml` is used first. Inserting a space and deleting + // it after ensures the format is applied. + QTextCursor cursor = textCursor(); + cursor.setBlockFormat(format); + cursor.insertText(" "); + cursor.insertHtml(html); + cursor.setPosition(0); + cursor.deleteChar(); +} + +void ChatMessageArea::updateLayout() { + setFixedHeight(document()->size().height()); + updateGeometry(); +} + +void ChatMessageArea::wheelEvent(QWheelEvent* event) { + // Capture wheel events to stop Ctrl-WheelUp/Down zooming + event->ignore(); +} diff --git a/interface/src/ui/ChatMessageArea.h b/interface/src/ui/ChatMessageArea.h new file mode 100644 index 0000000000..1c49c60b08 --- /dev/null +++ b/interface/src/ui/ChatMessageArea.h @@ -0,0 +1,33 @@ +// +// ChatMessageArea.h +// interface/src/ui +// +// Created by Ryan Huffman on 4/11/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_ChatMessageArea_h +#define hifi_ChatMessageArea_h + +#include + +const int CHAT_MESSAGE_LINE_HEIGHT = 130; + +class ChatMessageArea : public QTextBrowser { + Q_OBJECT +public: + ChatMessageArea(); + virtual void setHtml(const QString& html); + +public slots: + void updateLayout(); + +protected: + virtual void wheelEvent(QWheelEvent* event); + +}; + +#endif // hifi_ChatMessageArea_h diff --git a/interface/src/ui/ChatWindow.cpp b/interface/src/ui/ChatWindow.cpp index 6963c208c5..635f1f3d10 100644 --- a/interface/src/ui/ChatWindow.cpp +++ b/interface/src/ui/ChatWindow.cpp @@ -1,24 +1,30 @@ // // ChatWindow.cpp -// interface +// interface/src/ui // // Created by Dimitar Dobrev on 3/6/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 #include #include +#include #include #include #include #include +#include #include "Application.h" #include "FlowLayout.h" #include "qtimespan.h" #include "ui_chatWindow.h" #include "XmppClient.h" +#include "ChatMessageArea.h" #include "ChatWindow.h" @@ -28,11 +34,15 @@ const QRegularExpression regexLinks("((?:(?:ftp)|(?:https?))://\\S+)"); ChatWindow::ChatWindow() : ui(new Ui::ChatWindow), - numMessagesAfterLastTimeStamp(0) + numMessagesAfterLastTimeStamp(0), + _mousePressed(false), + _mouseStartPosition() { ui->setupUi(this); - // remove the title bar (see the Qt docs on setTitleBarWidget) + // remove the title bar (see the Qt docs on setTitleBarWidget), but we keep it for undocking + // + titleBar = titleBarWidget(); setTitleBarWidget(new QWidget()); FlowLayout* flowLayout = new FlowLayout(0, 4, 4); @@ -42,6 +52,10 @@ ChatWindow::ChatWindow() : ui->messagesGridLayout->setColumnStretch(1, 3); ui->messagePlainTextEdit->installEventFilter(this); + + if (!AccountManager::getInstance().isLoggedIn()) { + ui->connectingToXMPPLabel->setText(tr("You must be logged in to chat with others.")); + } #ifdef HAVE_QXMPP const QXmppClient& xmppClient = XmppClient::getInstance().getXMPPClient(); @@ -75,6 +89,25 @@ ChatWindow::~ChatWindow() { delete ui; } +void ChatWindow::mousePressEvent(QMouseEvent *e) { + if (e->button() == Qt::LeftButton && isFloating()) { + _mousePressed = true; + _mouseStartPosition = e->pos(); + } +} + +void ChatWindow::mouseMoveEvent(QMouseEvent *e) { + if (_mousePressed) { + move(mapToParent(e->pos() - _mouseStartPosition)); + } +} + +void ChatWindow::mouseReleaseEvent( QMouseEvent *e ) { + if ( e->button() == Qt::LeftButton ) { + _mousePressed = false; + } +} + void ChatWindow::keyPressEvent(QKeyEvent* event) { QDockWidget::keyPressEvent(event); if (event->key() == Qt::Key_Escape) { @@ -132,7 +165,7 @@ QString ChatWindow::getParticipantName(const QString& participant) { void ChatWindow::addTimeStamp() { QTimeSpan timePassed = QDateTime::currentDateTime() - lastMessageStamp; int times[] = { timePassed.daysPart(), timePassed.hoursPart(), timePassed.minutesPart() }; - QString strings[] = { tr("day", 0, times[0]), tr("hour", 0, times[1]), tr("minute", 0, times[2]) }; + QString strings[] = { tr("%n day(s)", 0, times[0]), tr("%n hour(s)", 0, times[1]), tr("%n minute(s)", 0, times[2]) }; QString timeString = ""; for (int i = 0; i < 3; i++) { if (times[i] > 0) { @@ -147,8 +180,18 @@ void ChatWindow::addTimeStamp() { "padding: 4px;"); timeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); timeLabel->setAlignment(Qt::AlignHCenter); + + bool atBottom = isAtBottom(); + ui->messagesGridLayout->addWidget(timeLabel, ui->messagesGridLayout->rowCount(), 0, 1, 2); + ui->messagesGridLayout->parentWidget()->updateGeometry(); + + Application::processEvents(); numMessagesAfterLastTimeStamp = 0; + + if (atBottom) { + scrollToBottom(); + } } } @@ -224,25 +267,41 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { userLabel->setStyleSheet("padding: 2px; font-weight: bold"); userLabel->setAlignment(Qt::AlignTop | Qt::AlignRight); - QLabel* messageLabel = new QLabel(message.body().replace(regexLinks, "\\1")); - messageLabel->setWordWrap(true); - messageLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); - messageLabel->setOpenExternalLinks(true); - messageLabel->setStyleSheet("padding-bottom: 2px; padding-left: 2px; padding-top: 2px; padding-right: 20px"); - messageLabel->setAlignment(Qt::AlignTop | Qt::AlignLeft); + ChatMessageArea* messageArea = new ChatMessageArea(); - if (getParticipantName(message.from()) == AccountManager::getInstance().getUsername()) { + messageArea->setOpenLinks(true); + messageArea->setOpenExternalLinks(true); + messageArea->setWordWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere); + messageArea->setTextInteractionFlags(Qt::TextBrowserInteraction); + messageArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + messageArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + messageArea->setReadOnly(true); + + messageArea->setStyleSheet("padding-bottom: 2px;" + "padding-left: 2px;" + "padding-top: 2px;" + "padding-right: 20px;" + "background-color: rgba(0, 0, 0, 0%);" + "border: 0;"); + + bool fromSelf = getParticipantName(message.from()) == AccountManager::getInstance().getUsername(); + if (fromSelf) { userLabel->setStyleSheet(userLabel->styleSheet() + "; background-color: #e1e8ea"); - messageLabel->setStyleSheet(messageLabel->styleSheet() + "; background-color: #e1e8ea"); + messageArea->setStyleSheet(messageArea->styleSheet() + "; background-color: #e1e8ea"); } + messageArea->setHtml(message.body().replace(regexLinks, "\\1")); + + bool atBottom = isAtBottom(); ui->messagesGridLayout->addWidget(userLabel, ui->messagesGridLayout->rowCount(), 0); - ui->messagesGridLayout->addWidget(messageLabel, ui->messagesGridLayout->rowCount() - 1, 1); + ui->messagesGridLayout->addWidget(messageArea, ui->messagesGridLayout->rowCount() - 1, 1); + ui->messagesGridLayout->parentWidget()->updateGeometry(); Application::processEvents(); - QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); - verticalScrollBar->setSliderPosition(verticalScrollBar->maximum()); - messageLabel->updateGeometry(); + + if (atBottom || fromSelf) { + scrollToBottom(); + } ++numMessagesAfterLastTimeStamp; if (message.stamp().isValid()) { @@ -253,3 +312,27 @@ void ChatWindow::messageReceived(const QXmppMessage& message) { } #endif + +bool ChatWindow::isAtBottom() { + QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); + return verticalScrollBar->sliderPosition() == verticalScrollBar->maximum(); +} + +// Scroll chat message area to bottom. +void ChatWindow::scrollToBottom() { + QScrollBar* verticalScrollBar = ui->messagesScrollArea->verticalScrollBar(); + verticalScrollBar->setSliderPosition(verticalScrollBar->maximum()); +} + +void ChatWindow::togglePinned() { + QMainWindow* mainWindow = Application::getInstance()->getWindow(); + mainWindow->removeDockWidget(this); + if (ui->togglePinnedButton->isChecked()) { + mainWindow->addDockWidget(ui->togglePinnedButton->isChecked() ? Qt::RightDockWidgetArea : Qt::NoDockWidgetArea, this); + } + if (!this->toggleViewAction()->isChecked()) { + this->toggleViewAction()->trigger(); + } + this->setFloating(!ui->togglePinnedButton->isChecked()); + setTitleBarWidget(ui->togglePinnedButton->isChecked()?new QWidget():titleBar); +} diff --git a/interface/src/ui/ChatWindow.h b/interface/src/ui/ChatWindow.h index da8d423b9d..6a807f9b81 100644 --- a/interface/src/ui/ChatWindow.h +++ b/interface/src/ui/ChatWindow.h @@ -1,13 +1,16 @@ // // ChatWindow.h -// interface +// interface/src/ui // // Created by Dimitar Dobrev on 3/6/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__ChatWindow__ -#define __interface__ChatWindow__ +#ifndef hifi_ChatWindow_h +#define hifi_ChatWindow_h #include #include @@ -36,6 +39,10 @@ public: virtual void keyPressEvent(QKeyEvent *event); virtual void showEvent(QShowEvent* event); + virtual void mousePressEvent(QMouseEvent *e); + virtual void mouseMoveEvent(QMouseEvent *e); + virtual void mouseReleaseEvent(QMouseEvent *e); + protected: bool eventFilter(QObject* sender, QEvent* event); @@ -45,14 +52,20 @@ private: #endif void startTimerForTimeStamps(); void addTimeStamp(); + bool isAtBottom(); + void scrollToBottom(); Ui::ChatWindow* ui; + QWidget* titleBar; int numMessagesAfterLastTimeStamp; QDateTime lastMessageStamp; + bool _mousePressed; + QPoint _mouseStartPosition; private slots: void connected(); void timeout(); + void togglePinned(); #ifdef HAVE_QXMPP void error(QXmppClient::Error error); void participantsChanged(); @@ -60,4 +73,4 @@ private slots: #endif }; -#endif /* defined(__interface__ChatWindow__) */ +#endif // hifi_ChatWindow_h diff --git a/interface/src/ui/FramelessDialog.cpp b/interface/src/ui/FramelessDialog.cpp new file mode 100644 index 0000000000..18e3bca89a --- /dev/null +++ b/interface/src/ui/FramelessDialog.cpp @@ -0,0 +1,100 @@ +// +// FramelessDialog.cpp +// interface/src/ui +// +// Created by Stojce Slavkovski on 2/20/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 "Application.h" +#include "FramelessDialog.h" + +const int RESIZE_HANDLE_WIDTH = 7; + +FramelessDialog::FramelessDialog(QWidget *parent, Qt::WindowFlags flags) : +QDialog(parent, flags | Qt::FramelessWindowHint | Qt::WindowStaysOnTopHint) { + setAttribute(Qt::WA_DeleteOnClose); + + // handle rezize and move events + parentWidget()->installEventFilter(this); + + // handle minimize, restore and focus events + Application::getInstance()->installEventFilter(this); +} + +bool FramelessDialog::eventFilter(QObject* sender, QEvent* event) { + switch (event->type()) { + case QEvent::Move: + if (sender == parentWidget()) { + // move to upper left corner on app move + move(parentWidget()->geometry().topLeft()); + } + break; + case QEvent::Resize: + if (sender == parentWidget()) { + // keep full app height on resizing the app + setFixedHeight(parentWidget()->size().height()); + } + break; + case QEvent::WindowStateChange: + if (parentWidget()->isMinimized()) { + setHidden(true); + } else { + setHidden(false); + } + break; + case QEvent::ApplicationDeactivate: + // hide on minimize and focus lost + setHidden(true); + break; + case QEvent::ApplicationActivate: + setHidden(false); + break; + default: + break; + } + + return false; +} + +void FramelessDialog::setStyleSheetFile(const QString& fileName) { + QFile globalStyleSheet(Application::resourcesPath() + "styles/global.qss"); + QFile styleSheet(Application::resourcesPath() + fileName); + if (styleSheet.open(QIODevice::ReadOnly) && globalStyleSheet.open(QIODevice::ReadOnly) ) { + QDir::setCurrent(Application::resourcesPath()); + setStyleSheet(globalStyleSheet.readAll() + styleSheet.readAll()); + } +} + +void FramelessDialog::showEvent(QShowEvent* event) { + // move to upper left corner + move(parentWidget()->geometry().topLeft()); + + // keep full app height + setFixedHeight(parentWidget()->size().height()); + + // resize parrent if width is smaller than this dialog + if (parentWidget()->size().width() < size().width()) { + parentWidget()->resize(size().width(), parentWidget()->size().height()); + } +} +void FramelessDialog::mousePressEvent(QMouseEvent* mouseEvent) { + if (abs(mouseEvent->pos().x() - size().width()) < RESIZE_HANDLE_WIDTH && mouseEvent->button() == Qt::LeftButton) { + _isResizing = true; + QApplication::setOverrideCursor(Qt::SizeHorCursor); + } +} + +void FramelessDialog::mouseReleaseEvent(QMouseEvent* mouseEvent) { + QApplication::restoreOverrideCursor(); + _isResizing = false; +} + +void FramelessDialog::mouseMoveEvent(QMouseEvent* mouseEvent) { + if (_isResizing) { + resize(mouseEvent->pos().x(), size().height()); + } +} diff --git a/interface/src/ui/FramelessDialog.h b/interface/src/ui/FramelessDialog.h new file mode 100644 index 0000000000..db9f6dfd6c --- /dev/null +++ b/interface/src/ui/FramelessDialog.h @@ -0,0 +1,38 @@ +// +// FramelessDialog.h +// interface/src/ui +// +// Created by Stojce Slavkovski on 2/20/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_FramelessDialog_h +#define hifi_FramelessDialog_h + +#include + +class FramelessDialog : public QDialog { + Q_OBJECT + +public: + FramelessDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0); + void setStyleSheetFile(const QString& fileName); + +protected: + virtual void mouseMoveEvent(QMouseEvent* mouseEvent); + virtual void mousePressEvent(QMouseEvent* mouseEvent); + virtual void mouseReleaseEvent(QMouseEvent* mouseEvent); + virtual void showEvent(QShowEvent* event); + + bool eventFilter(QObject* sender, QEvent* event); + +private: + bool _isResizing; + +}; + +#endif // hifi_FramelessDialog_h diff --git a/interface/src/ui/ImportDialog.cpp b/interface/src/ui/ImportDialog.cpp index 2741fada0a..67b89773fb 100644 --- a/interface/src/ui/ImportDialog.cpp +++ b/interface/src/ui/ImportDialog.cpp @@ -1,9 +1,12 @@ // // ImportDialog.cpp -// hifi +// interface/src/ui // // Created by Clement Brisset on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 "InterfaceConfig.h" diff --git a/interface/src/ui/ImportDialog.h b/interface/src/ui/ImportDialog.h index 278fac932d..88cfda7a7c 100644 --- a/interface/src/ui/ImportDialog.h +++ b/interface/src/ui/ImportDialog.h @@ -1,13 +1,16 @@ // // ImportDialog.h -// hifi +// interface/src/ui // // Created by Clement Brisset on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__ImportDialog__ -#define __hifi__ImportDialog__ +#ifndef hifi_ImportDialog_h +#define hifi_ImportDialog_h #include "InterfaceConfig.h" @@ -67,4 +70,4 @@ private: void setImportTypes(); }; -#endif /* defined(__hifi__ImportDialog__) */ +#endif // hifi_ImportDialog_h diff --git a/interface/src/ui/InfoView.cpp b/interface/src/ui/InfoView.cpp index 8ed4da254c..06de3fdf2e 100644 --- a/interface/src/ui/InfoView.cpp +++ b/interface/src/ui/InfoView.cpp @@ -1,9 +1,12 @@ // -// InfoView -// hifi +// InfoView.cpp +// interface/src/ui // // Created by Stojce Slavkovski on 9/7/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 "InfoView.h" diff --git a/interface/src/ui/InfoView.h b/interface/src/ui/InfoView.h index 009587c1d5..94d18ff6a1 100644 --- a/interface/src/ui/InfoView.h +++ b/interface/src/ui/InfoView.h @@ -1,13 +1,16 @@ // // InfoView.h -// hifi +// interface/src/ui // // Created by Stojce Slavkovski on 9/7/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__InfoView__ -#define __hifi__InfoView__ +#ifndef hifi_InfoView_h +#define hifi_InfoView_h #include @@ -26,4 +29,4 @@ private slots: void loaded(bool ok); }; -#endif /* defined(__hifi__InfoView__) */ +#endif // hifi_InfoView_h diff --git a/interface/src/ui/LodToolsDialog.cpp b/interface/src/ui/LodToolsDialog.cpp index 564669757c..6df5121104 100644 --- a/interface/src/ui/LodToolsDialog.cpp +++ b/interface/src/ui/LodToolsDialog.cpp @@ -1,16 +1,21 @@ // // LodToolsDialog.cpp -// interface +// interface/src/ui // // Created by Brad Hefta-Gaub on 7/19/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 #include #include +#include #include +#include #include #include #include @@ -72,6 +77,27 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) : const int FEEDBACK_WIDTH = 350; _feedback->setFixedWidth(FEEDBACK_WIDTH); form->addRow("You can see... ", _feedback); + + form->addRow("Automatic Avatar LOD Adjustment:", _automaticAvatarLOD = new QCheckBox()); + _automaticAvatarLOD->setChecked(Menu::getInstance()->getAutomaticAvatarLOD()); + connect(_automaticAvatarLOD, SIGNAL(toggled(bool)), SLOT(updateAvatarLODControls())); + + form->addRow("Decrease Avatar LOD Below FPS:", _avatarLODDecreaseFPS = new QDoubleSpinBox()); + _avatarLODDecreaseFPS->setValue(Menu::getInstance()->getAvatarLODDecreaseFPS()); + _avatarLODDecreaseFPS->setDecimals(0); + connect(_avatarLODDecreaseFPS, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues())); + + form->addRow("Increase Avatar LOD Above FPS:", _avatarLODIncreaseFPS = new QDoubleSpinBox()); + _avatarLODIncreaseFPS->setValue(Menu::getInstance()->getAvatarLODIncreaseFPS()); + _avatarLODIncreaseFPS->setDecimals(0); + connect(_avatarLODIncreaseFPS, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues())); + + form->addRow("Avatar LOD:", _avatarLOD = new QDoubleSpinBox()); + _avatarLOD->setDecimals(3); + _avatarLOD->setRange(1.0 / MAXIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER, 1.0 / MINIMUM_AVATAR_LOD_DISTANCE_MULTIPLIER); + _avatarLOD->setSingleStep(0.001); + _avatarLOD->setValue(1.0 / Menu::getInstance()->getAvatarLODDistanceMultiplier()); + connect(_avatarLOD, SIGNAL(valueChanged(double)), SLOT(updateAvatarLODValues())); // Add a button to reset QPushButton* resetButton = new QPushButton("Reset"); @@ -79,6 +105,8 @@ LodToolsDialog::LodToolsDialog(QWidget* parent) : connect(resetButton,SIGNAL(clicked(bool)),this,SLOT(resetClicked(bool))); this->QDialog::setLayout(form); + + updateAvatarLODControls(); } LodToolsDialog::~LodToolsDialog() { @@ -93,6 +121,39 @@ void LodToolsDialog::reloadSliders() { _feedback->setText(Menu::getInstance()->getLODFeedbackText()); } +void LodToolsDialog::updateAvatarLODControls() { + QFormLayout* form = static_cast(layout()); + + Menu::getInstance()->setAutomaticAvatarLOD(_automaticAvatarLOD->isChecked()); + + _avatarLODDecreaseFPS->setVisible(_automaticAvatarLOD->isChecked()); + form->labelForField(_avatarLODDecreaseFPS)->setVisible(_automaticAvatarLOD->isChecked()); + + _avatarLODIncreaseFPS->setVisible(_automaticAvatarLOD->isChecked()); + form->labelForField(_avatarLODIncreaseFPS)->setVisible(_automaticAvatarLOD->isChecked()); + + _avatarLOD->setVisible(!_automaticAvatarLOD->isChecked()); + form->labelForField(_avatarLOD)->setVisible(!_automaticAvatarLOD->isChecked()); + + if (!_automaticAvatarLOD->isChecked()) { + _avatarLOD->setValue(1.0 / Menu::getInstance()->getAvatarLODDistanceMultiplier()); + } + + if (isVisible()) { + adjustSize(); + } +} + +void LodToolsDialog::updateAvatarLODValues() { + if (_automaticAvatarLOD->isChecked()) { + Menu::getInstance()->setAvatarLODDecreaseFPS(_avatarLODDecreaseFPS->value()); + Menu::getInstance()->setAvatarLODIncreaseFPS(_avatarLODIncreaseFPS->value()); + + } else { + Menu::getInstance()->setAvatarLODDistanceMultiplier(1.0 / _avatarLOD->value()); + } +} + void LodToolsDialog::sizeScaleValueChanged(int value) { float realValue = value * TREE_SCALE; Menu::getInstance()->setVoxelSizeScale(realValue); @@ -110,6 +171,9 @@ void LodToolsDialog::resetClicked(bool checked) { //sizeScaleValueChanged(sliderValue); _lodSize->setValue(sliderValue); _boundaryLevelAdjust->setValue(0); + _automaticAvatarLOD->setChecked(true); + _avatarLODDecreaseFPS->setValue(DEFAULT_ADJUST_AVATAR_LOD_DOWN_FPS); + _avatarLODIncreaseFPS->setValue(ADJUST_LOD_UP_FPS); } void LodToolsDialog::reject() { diff --git a/interface/src/ui/LodToolsDialog.h b/interface/src/ui/LodToolsDialog.h index 98a5fce5f8..5b34a5efd0 100644 --- a/interface/src/ui/LodToolsDialog.h +++ b/interface/src/ui/LodToolsDialog.h @@ -1,18 +1,24 @@ // // LodToolsDialog.h -// interface +// interface/src/ui // // Created by Brad Hefta-Gaub on 7/19/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__LodToolsDialog__ -#define __hifi__LodToolsDialog__ +#ifndef hifi_LodToolsDialog_h +#define hifi_LodToolsDialog_h #include #include #include +class QCheckBox; +class QDoubleSpinBox; + class LodToolsDialog : public QDialog { Q_OBJECT public: @@ -29,6 +35,8 @@ public slots: void boundaryLevelValueChanged(int value); void resetClicked(bool checked); void reloadSliders(); + void updateAvatarLODControls(); + void updateAvatarLODValues(); protected: @@ -38,8 +46,11 @@ protected: private: QSlider* _lodSize; QSlider* _boundaryLevelAdjust; + QCheckBox* _automaticAvatarLOD; + QDoubleSpinBox* _avatarLODDecreaseFPS; + QDoubleSpinBox* _avatarLODIncreaseFPS; + QDoubleSpinBox* _avatarLOD; QLabel* _feedback; }; -#endif /* defined(__interface__LodToolsDialog__) */ - +#endif // hifi_LodToolsDialog_h diff --git a/interface/src/ui/LogDialog.cpp b/interface/src/ui/LogDialog.cpp index d219c08698..5db704b230 100644 --- a/interface/src/ui/LogDialog.cpp +++ b/interface/src/ui/LogDialog.cpp @@ -1,9 +1,12 @@ // // LogDialog.cpp -// interface +// interface/src/ui // // Created by Stojce Slavkovski on 12/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 "InterfaceConfig.h" diff --git a/interface/src/ui/LogDialog.h b/interface/src/ui/LogDialog.h index 5138e231c7..914e1b4949 100644 --- a/interface/src/ui/LogDialog.h +++ b/interface/src/ui/LogDialog.h @@ -1,13 +1,16 @@ // // LogDialog.h -// interface +// interface/src/ui // // Created by Stojce Slavkovski on 12/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__LogDialog__ -#define __interface__LogDialog__ +#ifndef hifi_LogDialog_h +#define hifi_LogDialog_h #include "InterfaceConfig.h" @@ -71,5 +74,4 @@ private: void showLogData(); }; -#endif - +#endif // hifi_LogDialog_h diff --git a/interface/src/ui/MetavoxelEditor.cpp b/interface/src/ui/MetavoxelEditor.cpp index 19f5902336..a42c84470b 100644 --- a/interface/src/ui/MetavoxelEditor.cpp +++ b/interface/src/ui/MetavoxelEditor.cpp @@ -1,9 +1,16 @@ // // MetavoxelEditor.cpp -// interface +// interface/src/ui // // Created by Andrzej Kapolka on 1/21/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QOpenGLFramebufferObject, which includes an earlier version of OpenGL +#include "InterfaceConfig.h" #include #include @@ -13,8 +20,12 @@ #include #include #include +#include +#include #include +#include #include +#include #include #include @@ -565,7 +576,7 @@ void PlaceSpannerTool::render() { } Spanner* spanner = static_cast(_editor->getValue().value().data()); const float SPANNER_ALPHA = 0.25f; - spanner->getRenderer()->render(SPANNER_ALPHA, glm::vec3(), 0.0f); + spanner->getRenderer()->render(SPANNER_ALPHA, SpannerRenderer::DEFAULT_MODE, glm::vec3(), 0.0f); } bool PlaceSpannerTool::appliesTo(const AttributePointer& attribute) const { @@ -582,20 +593,18 @@ bool PlaceSpannerTool::eventFilter(QObject* watched, QEvent* event) { void PlaceSpannerTool::place() { AttributePointer attribute = AttributeRegistry::getInstance()->getAttribute(_editor->getSelectedAttribute()); - if (!attribute) { - return; + if (attribute) { + applyEdit(attribute, _editor->getValue().value()); } - SharedObjectPointer spanner = _editor->getValue().value(); - MetavoxelEditMessage message = { createEdit(attribute, spanner) }; - Application::getInstance()->getMetavoxels()->applyEdit(message); } InsertSpannerTool::InsertSpannerTool(MetavoxelEditor* editor) : PlaceSpannerTool(editor, "Insert Spanner", "Insert") { } -QVariant InsertSpannerTool::createEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { - return QVariant::fromValue(InsertSpannerEdit(attribute, spanner)); +void InsertSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { + MetavoxelEditMessage message = { QVariant::fromValue(InsertSpannerEdit(attribute, spanner)) }; + Application::getInstance()->getMetavoxels()->applyEdit(message); } RemoveSpannerTool::RemoveSpannerTool(MetavoxelEditor* editor) : @@ -654,6 +663,230 @@ bool SetSpannerTool::appliesTo(const AttributePointer& attribute) const { return attribute == AttributeRegistry::getInstance()->getSpannersAttribute(); } -QVariant SetSpannerTool::createEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { - return QVariant::fromValue(SetSpannerEdit(spanner)); +glm::quat DIRECTION_ROTATIONS[] = { + rotationBetween(glm::vec3(-1.0f, 0.0f, 0.0f), IDENTITY_FRONT), + rotationBetween(glm::vec3(1.0f, 0.0f, 0.0f), IDENTITY_FRONT), + rotationBetween(glm::vec3(0.0f, -1.0f, 0.0f), IDENTITY_FRONT), + rotationBetween(glm::vec3(0.0f, 1.0f, 0.0f), IDENTITY_FRONT), + rotationBetween(glm::vec3(0.0f, 0.0f, -1.0f), IDENTITY_FRONT), + rotationBetween(glm::vec3(0.0f, 0.0f, 1.0f), IDENTITY_FRONT) }; + +/// Represents a view from one direction of the spanner to be voxelized. +class DirectionImages { +public: + QImage color; + QVector depth; + glm::vec3 minima; + glm::vec3 maxima; + glm::vec3 scale; +}; + +class Voxelizer : public QRunnable { +public: + + Voxelizer(float size, const Box& bounds, float granularity, const QVector& directionImages); + + virtual void run(); + +private: + + void voxelize(const glm::vec3& center); + + float _size; + Box _bounds; + float _granularity; + QVector _directionImages; +}; + +Voxelizer::Voxelizer(float size, const Box& bounds, float granularity, const QVector& directionImages) : + _size(size), + _bounds(bounds), + _granularity(granularity), + _directionImages(directionImages) { +} + +void Voxelizer::run() { + // voxelize separately each cell within the bounds + float halfSize = _size * 0.5f; + for (float x = _bounds.minimum.x + halfSize; x < _bounds.maximum.x; x += _size) { + for (float y = _bounds.minimum.y + halfSize; y < _bounds.maximum.y; y += _size) { + for (float z = _bounds.minimum.z + halfSize; z < _bounds.maximum.z; z += _size) { + voxelize(glm::vec3(x, y, z)); + } + } + } +} + +class VoxelizationVisitor : public MetavoxelVisitor { +public: + + VoxelizationVisitor(const QVector& directionImages, const glm::vec3& center, float granularity); + + virtual int visit(MetavoxelInfo& info); + +private: + + QVector _directionImages; + glm::vec3 _center; + float _granularity; +}; + +VoxelizationVisitor::VoxelizationVisitor(const QVector& directionImages, + const glm::vec3& center, float granularity) : + MetavoxelVisitor(QVector(), QVector() << + AttributeRegistry::getInstance()->getColorAttribute()), + _directionImages(directionImages), + _center(center), + _granularity(granularity) { +} + +bool checkDisjoint(const DirectionImages& images, const glm::vec3& minimum, const glm::vec3& maximum, float extent) { + for (int x = qMax(0, (int)minimum.x), xmax = qMin(images.color.width(), (int)maximum.x); x < xmax; x++) { + for (int y = qMax(0, (int)minimum.y), ymax = qMin(images.color.height(), (int)maximum.y); y < ymax; y++) { + float depth = 1.0f - images.depth.at(y * images.color.width() + x); + if (depth - minimum.z >= -extent - EPSILON) { + return false; + } + } + } + return true; +} + +int VoxelizationVisitor::visit(MetavoxelInfo& info) { + float halfSize = info.size * 0.5f; + glm::vec3 center = info.minimum + _center + glm::vec3(halfSize, halfSize, halfSize); + const float EXTENT_SCALE = 2.0f; + if (info.size > _granularity) { + for (unsigned int i = 0; i < sizeof(DIRECTION_ROTATIONS) / sizeof(DIRECTION_ROTATIONS[0]); i++) { + glm::vec3 rotated = DIRECTION_ROTATIONS[i] * center; + const DirectionImages& images = _directionImages.at(i); + glm::vec3 relative = (rotated - images.minima) * images.scale; + glm::vec3 extents = images.scale * halfSize; + glm::vec3 minimum = relative - extents; + glm::vec3 maximum = relative + extents; + if (checkDisjoint(images, minimum, maximum, extents.z * EXTENT_SCALE)) { + info.outputValues[0] = AttributeValue(_outputs.at(0)); + return STOP_RECURSION; + } + } + return DEFAULT_ORDER; + } + QRgb closestColor; + float closestDistance = FLT_MAX; + for (unsigned int i = 0; i < sizeof(DIRECTION_ROTATIONS) / sizeof(DIRECTION_ROTATIONS[0]); i++) { + glm::vec3 rotated = DIRECTION_ROTATIONS[i] * center; + const DirectionImages& images = _directionImages.at(i); + glm::vec3 relative = (rotated - images.minima) * images.scale; + int x = qMax(qMin((int)glm::round(relative.x), images.color.width() - 1), 0); + int y = qMax(qMin((int)glm::round(relative.y), images.color.height() - 1), 0); + float depth = 1.0f - images.depth.at(y * images.color.width() + x); + float distance = depth - relative.z; + float extent = images.scale.z * halfSize * EXTENT_SCALE; + if (distance < -extent - EPSILON) { + info.outputValues[0] = AttributeValue(_outputs.at(0)); + return STOP_RECURSION; + } + QRgb color = images.color.pixel(x, y); + if (distance < extent + EPSILON) { + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(color)); + return STOP_RECURSION; + } + if (distance < closestDistance) { + closestColor = color; + closestDistance = distance; + } + } + info.outputValues[0] = AttributeValue(_outputs.at(0), encodeInline(closestColor)); + return STOP_RECURSION; +} + +void Voxelizer::voxelize(const glm::vec3& center) { + MetavoxelData data; + data.setSize(_size); + + VoxelizationVisitor visitor(_directionImages, center, _granularity); + data.guide(visitor); + + MetavoxelEditMessage edit = { QVariant::fromValue(SetDataEdit( + center - glm::vec3(_size, _size, _size) * 0.5f, data, true)) }; + QMetaObject::invokeMethod(Application::getInstance()->getMetavoxels(), "applyEdit", + Q_ARG(const MetavoxelEditMessage&, edit), Q_ARG(bool, true)); +} + +void SetSpannerTool::applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) { + Spanner* spannerData = static_cast(spanner.data()); + Box bounds = spannerData->getBounds(); + float longestSide(qMax(bounds.getLongestSide(), spannerData->getPlacementGranularity())); + float size = powf(2.0f, floorf(logf(longestSide) / logf(2.0f))); + Box cellBounds(glm::floor(bounds.minimum / size) * size, glm::ceil(bounds.maximum / size) * size); + + Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->bind(); + + glEnable(GL_SCISSOR_TEST); + glEnable(GL_LIGHTING); + glEnable(GL_DEPTH_TEST); + glDisable(GL_BLEND); + + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + + QVector directionImages; + + for (unsigned int i = 0; i < sizeof(DIRECTION_ROTATIONS) / sizeof(DIRECTION_ROTATIONS[0]); i++) { + glm::vec3 minima(FLT_MAX, FLT_MAX, FLT_MAX); + glm::vec3 maxima(-FLT_MAX, -FLT_MAX, -FLT_MAX); + for (int j = 0; j < Box::VERTEX_COUNT; j++) { + glm::vec3 rotated = DIRECTION_ROTATIONS[i] * cellBounds.getVertex(j); + minima = glm::min(minima, rotated); + maxima = glm::max(maxima, rotated); + } + float renderGranularity = spannerData->getVoxelizationGranularity() / 4.0f; + int width = glm::round((maxima.x - minima.x) / renderGranularity); + int height = glm::round((maxima.y - minima.y) / renderGranularity); + + glViewport(0, 0, width, height); + glScissor(0, 0, width, height); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glLoadIdentity(); + glOrtho(minima.x, maxima.x, minima.y, maxima.y, -maxima.z, -minima.z); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + glm::vec3 axis = glm::axis(DIRECTION_ROTATIONS[i]); + glRotatef(glm::degrees(glm::angle(DIRECTION_ROTATIONS[i])), axis.x, axis.y, axis.z); + + Application::getInstance()->setupWorldLight(); + + Application::getInstance()->updateUntranslatedViewMatrix(); + + spannerData->getRenderer()->render(1.0f, SpannerRenderer::DIFFUSE_MODE, glm::vec3(), 0.0f); + + DirectionImages images = { QImage(width, height, QImage::Format_ARGB32), + QVector(width * height), minima, maxima, glm::vec3(width / (maxima.x - minima.x), + height / (maxima.y - minima.y), 1.0f / (maxima.z - minima.z)) }; + glReadPixels(0, 0, width, height, GL_BGRA, GL_UNSIGNED_BYTE, images.color.bits()); + glReadPixels(0, 0, width, height, GL_DEPTH_COMPONENT, GL_FLOAT, images.depth.data()); + directionImages.append(images); + + glMatrixMode(GL_PROJECTION); + } + glPopMatrix(); + + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glEnable(GL_BLEND); + glDisable(GL_SCISSOR_TEST); + + Application::getInstance()->getTextureCache()->getPrimaryFramebufferObject()->release(); + + glViewport(0, 0, Application::getInstance()->getGLWidget()->width(), Application::getInstance()->getGLWidget()->height()); + + // send the images off to the lab for processing + QThreadPool::globalInstance()->start(new Voxelizer(size, cellBounds, + spannerData->getVoxelizationGranularity(), directionImages)); } diff --git a/interface/src/ui/MetavoxelEditor.h b/interface/src/ui/MetavoxelEditor.h index 76ef8baf6f..79ffd1e64c 100644 --- a/interface/src/ui/MetavoxelEditor.h +++ b/interface/src/ui/MetavoxelEditor.h @@ -1,13 +1,16 @@ // // MetavoxelEditor.h -// interface +// interface/src/ui // // Created by Andrzej Kapolka on 1/21/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__MetavoxelEditor__ -#define __interface__MetavoxelEditor__ +#ifndef hifi_MetavoxelEditor_h +#define hifi_MetavoxelEditor_h #include #include @@ -157,7 +160,7 @@ public: protected: - virtual QVariant createEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) = 0; + virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner) = 0; private slots: @@ -174,7 +177,7 @@ public: protected: - virtual QVariant createEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner); + virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner); }; /// Allows removing a spanner from the scene. @@ -217,7 +220,7 @@ public: protected: - virtual QVariant createEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner); + virtual void applyEdit(const AttributePointer& attribute, const SharedObjectPointer& spanner); }; -#endif /* defined(__interface__MetavoxelEditor__) */ +#endif // hifi_MetavoxelEditor_h diff --git a/interface/src/ui/ModelBrowser.cpp b/interface/src/ui/ModelBrowser.cpp deleted file mode 100644 index e03081273f..0000000000 --- a/interface/src/ui/ModelBrowser.cpp +++ /dev/null @@ -1,150 +0,0 @@ -// -// ModelBrowser.cpp -// hifi -// -// Created by Clement on 3/17/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "ModelBrowser.h" - -static const QString PREFIX_PARAMETER_NAME = "prefix"; -static const QString MARKER_PARAMETER_NAME = "marker"; -static const QString IS_TRUNCATED_NAME = "IsTruncated"; -static const QString CONTAINER_NAME = "Contents"; -static const QString KEY_NAME = "Key"; - -ModelBrowser::ModelBrowser(ModelType modelType, QWidget* parent) : QWidget(parent), _type(modelType) { - QUrl url(S3_URL); - QUrlQuery query; - - if (_type == Head) { - query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); - } else if (_type == Skeleton) { - query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); - } - url.setQuery(query); - - _downloader = new FileDownloader(url); - connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); -} - -ModelBrowser::~ModelBrowser() { - delete _downloader; -} - -void ModelBrowser::downloadFinished() { - parseXML(_downloader->getData()); -} - -void ModelBrowser::browse() { - QDialog dialog(this); - dialog.setWindowTitle("Browse models"); - - QGridLayout* layout = new QGridLayout(&dialog); - dialog.setLayout(layout); - - QLineEdit* searchBar = new QLineEdit(&dialog); - layout->addWidget(searchBar, 0, 0); - - ListView* listView = new ListView(&dialog); - listView->setEditTriggers(QAbstractItemView::NoEditTriggers); - layout->addWidget(listView, 1, 0); - listView->connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(keyboardSearch(const QString&))); - dialog.connect(listView, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept())); - - QStringListModel* model = new QStringListModel(_models.keys(), listView); - model->sort(0); - listView->setModel(model); - - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - layout->addWidget(buttons, 2, 0); - dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); - dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); - - if (dialog.exec() == QDialog::Rejected) { - return; - } - - QString selectedKey = model->data(listView->currentIndex(), Qt::DisplayRole).toString(); - - emit selected(_models[selectedKey]); -} - -bool ModelBrowser::parseXML(QByteArray xmlFile) { - QXmlStreamReader xml(xmlFile); - QRegExp rx(".*fst"); - bool truncated = false; - QString lastKey; - - // Read xml until the end or an error is detected - while(!xml.atEnd() && !xml.hasError()) { - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) { - while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) { - // Let's check if there is more - xml.readNext(); - if (xml.text().toString() == "True") { - truncated = true; - } - } - } - - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) { - while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) { - // If a file is find, process it - if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) { - xml.readNext(); - lastKey = xml.text().toString(); - if (rx.exactMatch(xml.text().toString())) { - // Add the found file to the list - _models.insert(QFileInfo(xml.text().toString()).baseName(), - S3_URL + "/" + xml.text().toString()); - } - } - xml.readNext(); - } - } - xml.readNext(); - } - - // Error handling - if(xml.hasError()) { - _models.clear(); - QMessageBox::critical(this, - "ModelBrowser::ModelBrowser()", - xml.errorString(), - QMessageBox::Ok); - return false; - } - - // If we didn't all the files, download the next ones - if (truncated) { - QUrl url(S3_URL); - QUrlQuery query; - - if (_type == Head) { - query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); - } else if (_type == Skeleton) { - query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); - } - query.addQueryItem(MARKER_PARAMETER_NAME, lastKey); - url.setQuery(query); - - delete _downloader; - _downloader = new FileDownloader(url); - connect(_downloader, SIGNAL(done(QNetworkReply::NetworkError)), SLOT(downloadFinished())); - } - - return true; -} \ No newline at end of file diff --git a/interface/src/ui/ModelBrowser.h b/interface/src/ui/ModelBrowser.h deleted file mode 100644 index 4628642e89..0000000000 --- a/interface/src/ui/ModelBrowser.h +++ /dev/null @@ -1,62 +0,0 @@ -// -// ModelBrowser.h -// hifi -// -// Created by Clement on 3/17/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#ifndef __hifi__ModelBrowser__ -#define __hifi__ModelBrowser__ - -#include - -#include -#include - -static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; -static const QString HEAD_MODELS_LOCATION = "models/heads/"; -static const QString SKELETON_MODELS_LOCATION = "models/skeletons/"; - -enum ModelType { - Head, - Skeleton -}; - -class ModelBrowser : public QWidget { - Q_OBJECT - -public: - ModelBrowser(ModelType modelType, QWidget* parent = NULL); - ~ModelBrowser(); - -signals: - void selected(QString filename); - -public slots: - void browse(); - -private slots: - void downloadFinished(); - -private: - ModelType _type; - FileDownloader* _downloader; - QHash _models; - - bool parseXML(QByteArray xmlFile); -}; - - - -class ListView : public QListView { - Q_OBJECT -public: - ListView(QWidget* parent) : QListView(parent) {} - public slots: - void keyboardSearch(const QString& text) { - QAbstractItemView::keyboardSearch(text); - } -}; - -#endif /* defined(__hifi__ModelBrowser__) */ diff --git a/interface/src/ui/ModelsBrowser.cpp b/interface/src/ui/ModelsBrowser.cpp new file mode 100644 index 0000000000..77e056bdd3 --- /dev/null +++ b/interface/src/ui/ModelsBrowser.cpp @@ -0,0 +1,374 @@ +// +// ModelsBrowser.cpp +// interface/src/ui +// +// Created by Clement on 3/17/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 +#include +#include +#include +#include +#include +#include +#include + +#include "Application.h" + +#include "ModelsBrowser.h" + +static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com"; +static const QString PUBLIC_URL = "http://public.highfidelity.io"; +static const QString HEAD_MODELS_LOCATION = "models/heads"; +static const QString SKELETON_MODELS_LOCATION = "models/skeletons/"; + +static const QString PREFIX_PARAMETER_NAME = "prefix"; +static const QString MARKER_PARAMETER_NAME = "marker"; +static const QString IS_TRUNCATED_NAME = "IsTruncated"; +static const QString CONTAINER_NAME = "Contents"; +static const QString KEY_NAME = "Key"; +static const QString LOADING_MSG = "Loading..."; +static const QString ERROR_MSG = "Error loading files"; + +static const QString DO_NOT_MODIFY_TAG = "DoNotModify"; + +enum ModelMetaData { + NAME, + CREATOR, + DATE_ADDED, + TOTAL_SIZE, + POLY_NUM, + TAGS, + + MODEL_METADATA_COUNT +}; +static const QString propertiesNames[MODEL_METADATA_COUNT] = { + "Name", + "Creator", + "Date Added", + "Total Size", + "Poly#", + "Tags" +}; +static const QString propertiesIds[MODEL_METADATA_COUNT] = { + DO_NOT_MODIFY_TAG, + "Creator", + "Date-Added", + "Total-Size", + "Poly-Num", + "Tags" +}; + +ModelsBrowser::ModelsBrowser(ModelType modelsType, QWidget* parent) : + QWidget(parent, Qt::WindowStaysOnTopHint), + _handler(new ModelHandler(modelsType)) +{ + connect(_handler, SIGNAL(doneDownloading()), SLOT(resizeView())); + connect(_handler, SIGNAL(updated()), SLOT(resizeView())); + + // Connect handler + _handler->connect(this, SIGNAL(startDownloading()), SLOT(download())); + _handler->connect(_handler, SIGNAL(doneDownloading()), SLOT(update())); + _handler->connect(this, SIGNAL(destroyed()), SLOT(exit())); + + // Setup and launch update thread + QThread* thread = new QThread(); + thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit())); + thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater())); + _handler->moveToThread(thread); + thread->start(); + emit startDownloading(); + + // Initialize the view + _view.setEditTriggers(QAbstractItemView::NoEditTriggers); + _view.setRootIsDecorated(false); + _view.setModel(_handler->getModel()); +} + +void ModelsBrowser::applyFilter(const QString &filter) { + QStringList filters = filter.split(" "); + + _handler->lockModel(); + QStandardItemModel* model = _handler->getModel(); + int rows = model->rowCount(); + + // Try and match every filter with each rows + for (int i = 0; i < rows; ++i) { + bool match = false; + for (int k = 0; k < filters.count(); ++k) { + match = false; + for (int j = 0; j < MODEL_METADATA_COUNT; ++j) { + if (model->item(i, j)->text().contains(filters.at(k), Qt::CaseInsensitive)) { + match = true; + break; + } + } + if (!match) { + break; + } + } + + // Hid the row if it doesn't match (Make sure it's not it it does) + if (match) { + _view.setRowHidden(i, QModelIndex(), false); + } else { + _view.setRowHidden(i, QModelIndex(), true); + } + } + _handler->unlockModel(); +} + +void ModelsBrowser::resizeView() { + for (int i = 0; i < MODEL_METADATA_COUNT; ++i) { + _view.resizeColumnToContents(i); + } +} + +void ModelsBrowser::browse() { + QDialog dialog; + dialog.setWindowTitle("Browse models"); + dialog.setMinimumSize(570, 500); + + QGridLayout* layout = new QGridLayout(&dialog); + dialog.setLayout(layout); + + QLineEdit* searchBar = new QLineEdit(&dialog); + layout->addWidget(searchBar, 0, 0); + + layout->addWidget(&_view, 1, 0); + dialog.connect(&_view, SIGNAL(doubleClicked(const QModelIndex&)), SLOT(accept())); + connect(searchBar, SIGNAL(textChanged(const QString&)), SLOT(applyFilter(const QString&))); + + QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + layout->addWidget(buttons, 2, 0); + dialog.connect(buttons, SIGNAL(accepted()), SLOT(accept())); + dialog.connect(buttons, SIGNAL(rejected()), SLOT(reject())); + + if (dialog.exec() == QDialog::Accepted) { + _handler->lockModel(); + QVariant selectedFile = _handler->getModel()->data(_view.currentIndex(), Qt::UserRole); + _handler->unlockModel(); + if (selectedFile.isValid()) { + emit selected(selectedFile.toString()); + } + } + + // So that we don't have to reconstruct the view + _view.setParent(NULL); +} + + +ModelHandler::ModelHandler(ModelType modelsType, QWidget* parent) : + QObject(parent), + _initiateExit(false), + _type(modelsType) +{ + // set headers data + QStringList headerData; + for (int i = 0; i < MODEL_METADATA_COUNT; ++i) { + headerData << propertiesNames[i]; + } + _model.setHorizontalHeaderLabels(headerData); +} + +void ModelHandler::download() { + _lock.lockForWrite(); + if (_initiateExit) { + _lock.unlock(); + return; + } + // Show loading message + QStandardItem* loadingItem = new QStandardItem(LOADING_MSG); + loadingItem->setEnabled(false); + _model.appendRow(loadingItem); + _lock.unlock(); + + // Query models list + queryNewFiles(); +} + +void ModelHandler::update() { + _lock.lockForWrite(); + if (_initiateExit) { + _lock.unlock(); + return; + } + for (int i = 0; i < _model.rowCount(); ++i) { + QUrl url(_model.item(i,0)->data(Qt::UserRole).toString()); + QNetworkAccessManager* accessManager = new QNetworkAccessManager(this); + QNetworkRequest request(url); + accessManager->head(request); + connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*))); + } + _lock.unlock(); +} + +void ModelHandler::exit() { + _lock.lockForWrite(); + _initiateExit = true; + + // Disconnect everything + disconnect(); + thread()->disconnect(); + + // Make sure the thread will exit correctly + thread()->connect(this, SIGNAL(destroyed()), SLOT(quit())); + thread()->connect(thread(), SIGNAL(finished()), SLOT(deleteLater())); + deleteLater(); + _lock.unlock(); +} + +void ModelHandler::downloadFinished(QNetworkReply* reply) { + QByteArray data = reply->readAll(); + + if (!data.isEmpty()) { + parseXML(data); + } else { + parseHeaders(reply); + } + reply->deleteLater(); + sender()->deleteLater(); +} + +void ModelHandler::queryNewFiles(QString marker) { + if (_initiateExit) { + return; + } + + // Build query + QUrl url(S3_URL); + QUrlQuery query; + if (_type == Head) { + query.addQueryItem(PREFIX_PARAMETER_NAME, HEAD_MODELS_LOCATION); + } else if (_type == Skeleton) { + query.addQueryItem(PREFIX_PARAMETER_NAME, SKELETON_MODELS_LOCATION); + } + + if (!marker.isEmpty()) { + query.addQueryItem(MARKER_PARAMETER_NAME, marker); + } + + // Download + url.setQuery(query); + QNetworkAccessManager* accessManager = new QNetworkAccessManager(this); + QNetworkRequest request(url); + accessManager->get(request); + connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*))); + +} + +bool ModelHandler::parseXML(QByteArray xmlFile) { + _lock.lockForWrite(); + if (_initiateExit) { + _lock.unlock(); + return false; + } + + QXmlStreamReader xml(xmlFile); + QRegExp rx(".*fst"); + bool truncated = false; + QString lastKey; + + // Remove loading indication + int oldLastRow = _model.rowCount() - 1; + delete _model.takeRow(oldLastRow).first(); + + // Read xml until the end or an error is detected + while(!xml.atEnd() && !xml.hasError()) { + if (_initiateExit) { + _lock.unlock(); + return false; + } + + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == IS_TRUNCATED_NAME) { + while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == IS_TRUNCATED_NAME)) { + // Let's check if there is more + xml.readNext(); + if (xml.text().toString() == "True") { + truncated = true; + } + } + } + + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == CONTAINER_NAME) { + while(!(xml.tokenType() == QXmlStreamReader::EndElement && xml.name() == CONTAINER_NAME)) { + // If a file is find, process it + if(xml.tokenType() == QXmlStreamReader::StartElement && xml.name() == KEY_NAME) { + xml.readNext(); + lastKey = xml.text().toString(); + if (rx.exactMatch(xml.text().toString())) { + // Add the found file to the list + QList model; + model << new QStandardItem(QFileInfo(xml.text().toString()).baseName()); + model.first()->setData(PUBLIC_URL + "/" + xml.text().toString(), Qt::UserRole); + for (int i = 1; i < MODEL_METADATA_COUNT; ++i) { + model << new QStandardItem(); + } + + _model.appendRow(model); + } + } + xml.readNext(); + } + } + xml.readNext(); + } + + // Error handling + if(xml.hasError()) { + _model.clear(); + QStandardItem* errorItem = new QStandardItem(ERROR_MSG); + errorItem->setEnabled(false); + _model.appendRow(errorItem); + + _lock.unlock(); + return false; + } + + // If we didn't all the files, download the next ones + if (truncated) { + // Indicate more files are being loaded + QStandardItem* loadingItem = new QStandardItem(LOADING_MSG); + loadingItem->setEnabled(false); + _model.appendRow(loadingItem); + + // query those files + queryNewFiles(lastKey); + } + _lock.unlock(); + + if (!truncated) { + emit doneDownloading(); + } + + return true; +} + +bool ModelHandler::parseHeaders(QNetworkReply* reply) { + _lock.lockForWrite(); + + QList items = _model.findItems(QFileInfo(reply->url().toString()).baseName()); + if (items.isEmpty() || items.first()->text() == DO_NOT_MODIFY_TAG) { + return false; + } + + for (int i = 0; i < MODEL_METADATA_COUNT; ++i) { + for (int k = 1; k < reply->rawHeaderPairs().count(); ++k) { + QString key = reply->rawHeaderPairs().at(k).first.data(); + QString item = reply->rawHeaderPairs().at(k).second.data(); + if (key == propertiesIds[i]) { + _model.item(_model.indexFromItem(items.first()).row(), i)->setText(item); + } + } + } + _lock.unlock(); + + emit updated(); + return true; +} + diff --git a/interface/src/ui/ModelsBrowser.h b/interface/src/ui/ModelsBrowser.h new file mode 100644 index 0000000000..81f64c6730 --- /dev/null +++ b/interface/src/ui/ModelsBrowser.h @@ -0,0 +1,81 @@ +// +// ModelsBrowser.h +// interface/src/ui +// +// Created by Clement on 3/17/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_ModelsBrowser_h +#define hifi_ModelsBrowser_h + +#include +#include +#include + + +enum ModelType { + Head, + Skeleton +}; + +class ModelHandler : public QObject { + Q_OBJECT +public: + ModelHandler(ModelType modelsType, QWidget* parent = NULL); + + void lockModel() { _lock.lockForRead(); } + QStandardItemModel* getModel() { return &_model; } + void unlockModel() { _lock.unlock(); } + +signals: + void doneDownloading(); + void updated(); + +public slots: + void download(); + void update(); + void exit(); + +private slots: + void downloadFinished(QNetworkReply* reply); + +private: + bool _initiateExit; + ModelType _type; + QReadWriteLock _lock; + QStandardItemModel _model; + + void queryNewFiles(QString marker = QString()); + bool parseXML(QByteArray xmlFile); + bool parseHeaders(QNetworkReply* reply); +}; + + +class ModelsBrowser : public QWidget { + Q_OBJECT +public: + + ModelsBrowser(ModelType modelsType, QWidget* parent = NULL); + +signals: + void startDownloading(); + void startUpdating(); + void selected(QString filename); + +public slots: + void browse(); + +private slots: + void applyFilter(const QString& filter); + void resizeView(); + +private: + ModelHandler* _handler; + QTreeView _view; +}; + +#endif // hifi_ModelsBrowser_h diff --git a/interface/src/ui/OctreeStatsDialog.cpp b/interface/src/ui/OctreeStatsDialog.cpp index daf5a68587..c56aa0b6ab 100644 --- a/interface/src/ui/OctreeStatsDialog.cpp +++ b/interface/src/ui/OctreeStatsDialog.cpp @@ -1,9 +1,12 @@ // // OctreeStatsDialog.cpp -// interface +// interface/src/ui // // Created by Brad Hefta-Gaub on 7/19/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/interface/src/ui/OctreeStatsDialog.h b/interface/src/ui/OctreeStatsDialog.h index ef190e6a52..2488667a09 100644 --- a/interface/src/ui/OctreeStatsDialog.h +++ b/interface/src/ui/OctreeStatsDialog.h @@ -1,13 +1,16 @@ // // OctreeStatsDialog.h -// interface +// interface/src/ui // // Created by Brad Hefta-Gaub on 7/19/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__OctreeStatsDialog__ -#define __hifi__OctreeStatsDialog__ +#ifndef hifi_OctreeStatsDialog_h +#define hifi_OctreeStatsDialog_h #include #include @@ -66,5 +69,4 @@ private: details _extraServerDetails[MAX_VOXEL_SERVERS]; }; -#endif /* defined(__interface__OctreeStatsDialog__) */ - +#endif // hifi_OctreeStatsDialog_h diff --git a/interface/src/ui/Oscilloscope.cpp b/interface/src/ui/Oscilloscope.cpp deleted file mode 100644 index a47586b436..0000000000 --- a/interface/src/ui/Oscilloscope.cpp +++ /dev/null @@ -1,189 +0,0 @@ -// -// Oscilloscope.cpp -// interface -// -// Created by Philip on 1/28/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// - -#include -#include -#include - -#include -#include - -#include "InterfaceConfig.h" - -#include "Oscilloscope.h" - -// Reimplemented 4/26/13 (tosh) - don't blame Philip for bugs - -using namespace std; - -namespace { // everything in here only exists while compiling this .cpp file - - // one sample buffer per channel - unsigned const MAX_SAMPLES = Oscilloscope::MAX_SAMPLES_PER_CHANNEL * Oscilloscope::MAX_CHANNELS; - - // adding an x-coordinate yields twice the amount of vertices - unsigned const MAX_COORDS_PER_CHANNEL = Oscilloscope::MAX_SAMPLES_PER_CHANNEL * 2; - // allocated once for each channel - unsigned const MAX_COORDS = MAX_COORDS_PER_CHANNEL * Oscilloscope::MAX_CHANNELS; - - // total amount of memory to allocate (in 16-bit integers) - unsigned const N_INT16_TO_ALLOC = MAX_SAMPLES + MAX_COORDS; -} - - -Oscilloscope::Oscilloscope(int w, int h, bool isEnabled) : - enabled(isEnabled), - inputPaused(false), - _width(w), - _height(h), - _samples(0l), - _vertices(0l), - // some filtering (see details in Log.h) - _lowPassCoeff(0.4f), - // three in -> one out - _downsampleRatio(3) { - - // allocate enough space for the sample data and to turn it into - // vertices and since they're all 'short', do so in one shot - _samples = new short[N_INT16_TO_ALLOC]; - memset(_samples, 0, N_INT16_TO_ALLOC * sizeof(short)); - _vertices = _samples + MAX_SAMPLES; - - // initialize write positions to start of each channel's region - for (unsigned ch = 0; ch < MAX_CHANNELS; ++ch) { - _writePos[ch] = MAX_SAMPLES_PER_CHANNEL * ch; - } - - _colors[0] = 0xffffff; - _colors[1] = 0x00ffff; - _colors[2] = 0x00ffff; -} - -Oscilloscope::~Oscilloscope() { - - delete[] _samples; -} - -void Oscilloscope::addSamples(const QByteArray& audioByteArray, bool isStereo, bool isInput) { - - if (! enabled || inputPaused) { - return; - } - - unsigned numSamplesPerChannel = audioByteArray.size() / (sizeof(int16_t) * (isStereo ? 2 : 1)); - int16_t* samples = (int16_t*) audioByteArray.data(); - - for (int channel = 0; channel < (isStereo ? 2 : 1); channel++) { - // add samples for each of the channels - - // determine start/end offset of this channel's region - unsigned baseOffs = MAX_SAMPLES_PER_CHANNEL * (channel + !isInput); - unsigned endOffs = baseOffs + MAX_SAMPLES_PER_CHANNEL; - - // fetch write position for this channel - unsigned writePos = _writePos[channel + !isInput]; - - // determine write position after adding the samples - unsigned newWritePos = writePos + numSamplesPerChannel; - unsigned n2 = 0; - if (newWritePos >= endOffs) { - // passed boundary of the circular buffer? -> we need to copy two blocks - n2 = newWritePos - endOffs; - newWritePos = baseOffs + n2; - numSamplesPerChannel -= n2; - } - - if (!isStereo) { - // copy data - memcpy(_samples + writePos, samples, numSamplesPerChannel * sizeof(int16_t)); - if (n2 > 0) { - memcpy(_samples + baseOffs, samples + numSamplesPerChannel, n2 * sizeof(int16_t)); - } - } else { - // we have interleaved samples we need to separate into two channels - for (unsigned i = 0; i < numSamplesPerChannel + n2; i++) { - if (i < numSamplesPerChannel - n2) { - _samples[writePos] = samples[(i * 2) + channel]; - } else { - _samples[baseOffs] = samples[(i * 2) + channel]; - } - } - } - - // set new write position for this channel - _writePos[channel + !isInput] = newWritePos; - } -} - -void Oscilloscope::render(int x, int y) { - - if (! enabled) { - return; - } - - // fetch low pass factor (and convert to fix point) / downsample factor - int lowPassFixPt = -(int)(std::numeric_limits::min()) * _lowPassCoeff; - unsigned downsample = _downsampleRatio; - // keep half of the buffer for writing and ensure an even vertex count - unsigned usedWidth = min(_width, MAX_SAMPLES_PER_CHANNEL / (downsample * 2)) & ~1u; - unsigned usedSamples = usedWidth * downsample; - - // expand samples to vertex data - for (unsigned ch = 0; ch < MAX_CHANNELS; ++ch) { - // for each channel: determine memory regions - short const* basePtr = _samples + MAX_SAMPLES_PER_CHANNEL * ch; - short const* endPtr = basePtr + MAX_SAMPLES_PER_CHANNEL; - short const* inPtr = _samples + _writePos[ch]; - short* outPtr = _vertices + MAX_COORDS_PER_CHANNEL * ch; - int sample = 0, x = usedWidth; - for (int i = (int)usedSamples; --i >= 0 ;) { - if (inPtr == basePtr) { - // handle boundary, reading the circular sample buffer - inPtr = endPtr; - } - // read and (eventually) filter sample - sample += ((*--inPtr - sample) * lowPassFixPt) >> 15; - // write every nth as y with a corresponding x-coordinate - if (i % downsample == 0) { - *outPtr++ = short(--x); - *outPtr++ = short(sample); - } - } - } - - // set up rendering state (vertex data lives at _vertices) - glLineWidth(1.0); - glDisable(GL_LINE_SMOOTH); - glPushMatrix(); - glTranslatef((float)x + 0.0f, (float)y + _height / 2.0f, 0.0f); - glScaled(1.0f, _height / 32767.0f, 1.0f); - glVertexPointer(2, GL_SHORT, 0, _vertices); - glEnableClientState(GL_VERTEX_ARRAY); - glDisableClientState(GL_COLOR_ARRAY); - glDisableClientState(GL_INDEX_ARRAY); - glDisableClientState(GL_NORMAL_ARRAY); - glDisableClientState(GL_TEXTURE_COORD_ARRAY); - - // render channel 0 - glColor3ub(GLubyte(_colors[0] >> 16), GLubyte((_colors[0] >> 8) & 0xff), GLubyte(_colors[0] & 0xff)); - glDrawArrays(GL_LINES, MAX_SAMPLES_PER_CHANNEL * 0, usedWidth); - - // render channel 1 - glColor3f(0.0f, 1.0f ,1.0f); - glColor3ub(GLubyte(_colors[1] >> 16), GLubyte((_colors[1] >> 8) & 0xff), GLubyte(_colors[1] & 0xff)); - glDrawArrays(GL_LINES, MAX_SAMPLES_PER_CHANNEL * 1, usedWidth); - - // render channel 2 - glColor3ub(GLubyte(_colors[2] >> 16), GLubyte((_colors[2] >> 8) & 0xff), GLubyte(_colors[2] & 0xff)); - glDrawArrays(GL_LINES, MAX_SAMPLES_PER_CHANNEL * 2, usedWidth); - - // reset rendering state - glDisableClientState(GL_VERTEX_ARRAY); - glPopMatrix(); -} - diff --git a/interface/src/ui/Oscilloscope.h b/interface/src/ui/Oscilloscope.h deleted file mode 100644 index a245f79f05..0000000000 --- a/interface/src/ui/Oscilloscope.h +++ /dev/null @@ -1,81 +0,0 @@ -// -// Oscilloscope.h -// interface -// -// Created by Philip on 1/28/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// - -#ifndef __interface__Oscilloscope__ -#define __interface__Oscilloscope__ - -#include - -#include - -class Oscilloscope : public QObject { - Q_OBJECT -public: - Oscilloscope(int width, int height, bool isEnabled); - ~Oscilloscope(); - - void render(int x, int y); - - // Switches: On/Off, Stop Time - volatile bool enabled; - volatile bool inputPaused; - - // Limits - static unsigned const MAX_CHANNELS = 3; - static unsigned const MAX_SAMPLES_PER_CHANNEL = 4096; - - // Sets the color for a specific channel. - void setColor(unsigned ch, unsigned rgb) { assert(ch < MAX_CHANNELS); if (! inputPaused) { _colors[ch] = rgb; } } - - // Controls a simple one pole IIR low pass filter that is provided to - // reduce high frequencies aliasing (to lower ones) when downsampling. - // - // The parameter sets the influence of the input in respect to the - // feed-back signal on the output. - // - // +---------+ - // in O--------------|+ ideal |--o--------------O out - // .---|- op amp | | - // | +---------+ | - // | | - // o-------||-------o - // | | - // | __V__ - // -------------|_____|-------+ - // : : | - // 0.0 - 1.0 (GND) - // - // The values in range 0.0 - 1.0 correspond to "all closed" (input has - // no influence on the output) to "all open" (feedback has no influence - // on the output) configurations. - void setLowpassOpenness(float w) { assert(w >= 0.0f && w <= 1.0f); _lowPassCoeff = w; } - - // Sets the number of input samples per output sample. Without filtering - // just uses every nTh sample. - void setDownsampleRatio(unsigned n) { assert(n > 0); _downsampleRatio = n; } -public slots: - void addSamples(const QByteArray& audioByteArray, bool isStereo, bool isInput); -private: - // don't copy/assign - Oscilloscope(Oscilloscope const&); // = delete; - Oscilloscope& operator=(Oscilloscope const&); // = delete; - - // state variables - - unsigned _width; - unsigned _height; - short* _samples; - short* _vertices; - unsigned _writePos[MAX_CHANNELS]; - - float _lowPassCoeff; - unsigned _downsampleRatio; - unsigned _colors[MAX_CHANNELS]; -}; - -#endif /* defined(__interface__oscilloscope__) */ diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp new file mode 100644 index 0000000000..36508e94d1 --- /dev/null +++ b/interface/src/ui/PreferencesDialog.cpp @@ -0,0 +1,182 @@ +// +// PreferencesDialog.cpp +// interface/src/ui +// +// Created by Stojce Slavkovski on 2/20/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 "Application.h" +#include "Menu.h" +#include "PreferencesDialog.h" +#include "ModelsBrowser.h" + +const int SCROLL_PANEL_BOTTOM_MARGIN = 30; +const int OK_BUTTON_RIGHT_MARGIN = 30; +const int BUTTONS_TOP_MARGIN = 24; + +PreferencesDialog::PreferencesDialog(QWidget* parent, Qt::WindowFlags flags) : FramelessDialog(parent, flags) { + + ui.setupUi(this); + setStyleSheetFile("styles/preferences.qss"); + loadPreferences(); + connect(ui.closeButton, &QPushButton::clicked, this, &QDialog::close); + + connect(ui.buttonBrowseHead, &QPushButton::clicked, this, &PreferencesDialog::openHeadModelBrowser); + connect(ui.buttonBrowseBody, &QPushButton::clicked, this, &PreferencesDialog::openBodyModelBrowser); + connect(ui.buttonBrowseLocation, &QPushButton::clicked, this, &PreferencesDialog::openSnapshotLocationBrowser); +} + +void PreferencesDialog::accept() { + savePreferences(); + close(); +} + +void PreferencesDialog::setHeadUrl(QString modelUrl) { + ui.faceURLEdit->setText(modelUrl); + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); +} + +void PreferencesDialog::setSkeletonUrl(QString modelUrl) { + ui.skeletonURLEdit->setText(modelUrl); + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); +} + +void PreferencesDialog::openHeadModelBrowser() { + setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + ModelsBrowser modelBrowser(Head); + connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setHeadUrl); + modelBrowser.browse(); +} + +void PreferencesDialog::openBodyModelBrowser() { + setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + ModelsBrowser modelBrowser(Skeleton); + connect(&modelBrowser, &ModelsBrowser::selected, this, &PreferencesDialog::setSkeletonUrl); + modelBrowser.browse(); +} + +void PreferencesDialog::openSnapshotLocationBrowser() { + setWindowFlags(windowFlags() & ~Qt::WindowStaysOnTopHint); + QString dir = QFileDialog::getExistingDirectory(this, tr("Snapshots Location"), + QStandardPaths::writableLocation(QStandardPaths::DesktopLocation), + QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks); + if (!dir.isNull() && !dir.isEmpty()) { + ui.snapshotLocationEdit->setText(dir); + } + setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint); +} + +void PreferencesDialog::resizeEvent(QResizeEvent *resizeEvent) { + + // keep buttons panel at the bottom + ui.buttonsPanel->setGeometry(0, size().height() - ui.buttonsPanel->height(), size().width(), ui.buttonsPanel->height()); + + // set width and height of srcollarea to match bottom panel and width + ui.scrollArea->setGeometry(ui.scrollArea->geometry().x(), ui.scrollArea->geometry().y(), + size().width(), + size().height() - ui.buttonsPanel->height() - + SCROLL_PANEL_BOTTOM_MARGIN - ui.scrollArea->geometry().y()); + + // move Save button to left position + ui.defaultButton->move(size().width() - OK_BUTTON_RIGHT_MARGIN - ui.defaultButton->size().width(), BUTTONS_TOP_MARGIN); + + // move Save button to left position + ui.cancelButton->move(ui.defaultButton->pos().x() - ui.cancelButton->size().width(), BUTTONS_TOP_MARGIN); + + // move close button + ui.closeButton->move(size().width() - OK_BUTTON_RIGHT_MARGIN - ui.closeButton->size().width(), ui.closeButton->pos().y()); +} + +void PreferencesDialog::loadPreferences() { + + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + Menu* menuInstance = Menu::getInstance(); + + _displayNameString = myAvatar->getDisplayName(); + ui.displayNameEdit->setText(_displayNameString); + + _faceURLString = myAvatar->getHead()->getFaceModel().getURL().toString(); + ui.faceURLEdit->setText(_faceURLString); + + _skeletonURLString = myAvatar->getSkeletonModel().getURL().toString(); + ui.skeletonURLEdit->setText(_skeletonURLString); + + ui.snapshotLocationEdit->setText(menuInstance->getSnapshotsLocation()); + + ui.pupilDilationSlider->setValue(myAvatar->getHead()->getPupilDilation() * + ui.pupilDilationSlider->maximum()); + + ui.faceshiftEyeDeflectionSider->setValue(menuInstance->getFaceshiftEyeDeflection() * + ui.faceshiftEyeDeflectionSider->maximum()); + + ui.audioJitterSpin->setValue(menuInstance->getAudioJitterBufferSamples()); + + ui.fieldOfViewSpin->setValue(menuInstance->getFieldOfView()); + + ui.leanScaleSpin->setValue(myAvatar->getLeanScale()); + + ui.avatarScaleSpin->setValue(myAvatar->getScale()); + + ui.maxVoxelsSpin->setValue(menuInstance->getMaxVoxels()); + + ui.maxVoxelsPPSSpin->setValue(menuInstance->getMaxVoxelPacketsPerSecond()); +} + +void PreferencesDialog::savePreferences() { + + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + bool shouldDispatchIdentityPacket = false; + + QString displayNameStr(ui.displayNameEdit->text()); + if (displayNameStr != _displayNameString) { + myAvatar->setDisplayName(displayNameStr); + shouldDispatchIdentityPacket = true; + } + + QUrl faceModelURL(ui.faceURLEdit->text()); + if (faceModelURL.toString() != _faceURLString) { + // change the faceModelURL in the profile, it will also update this user's BlendFace + myAvatar->setFaceModelURL(faceModelURL); + shouldDispatchIdentityPacket = true; + } + + QUrl skeletonModelURL(ui.skeletonURLEdit->text()); + if (skeletonModelURL.toString() != _skeletonURLString) { + // change the skeletonModelURL in the profile, it will also update this user's Body + myAvatar->setSkeletonModelURL(skeletonModelURL); + shouldDispatchIdentityPacket = true; + } + + if (shouldDispatchIdentityPacket) { + myAvatar->sendIdentityPacket(); + Application::getInstance()->bumpSettings(); + } + + if (!ui.snapshotLocationEdit->text().isEmpty() && QDir(ui.snapshotLocationEdit->text()).exists()) { + Menu::getInstance()->setSnapshotsLocation(ui.snapshotLocationEdit->text()); + } + + myAvatar->getHead()->setPupilDilation(ui.pupilDilationSlider->value() / (float)ui.pupilDilationSlider->maximum()); + myAvatar->setLeanScale(ui.leanScaleSpin->value()); + myAvatar->setClampedTargetScale(ui.avatarScaleSpin->value()); + + Application::getInstance()->getVoxels()->setMaxVoxels(ui.maxVoxelsSpin->value()); + Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(), + Application::getInstance()->getGLWidget()->height()); + + Menu::getInstance()->setFieldOfView(ui.fieldOfViewSpin->value()); + + Menu::getInstance()->setFaceshiftEyeDeflection(ui.faceshiftEyeDeflectionSider->value() / + (float)ui.faceshiftEyeDeflectionSider->maximum()); + Menu::getInstance()->setMaxVoxelPacketsPerSecond(ui.maxVoxelsPPSSpin->value()); + + Menu::getInstance()->setAudioJitterBufferSamples(ui.audioJitterSpin->value()); + + Application::getInstance()->resizeGL(Application::getInstance()->getGLWidget()->width(), + Application::getInstance()->getGLWidget()->height()); +} diff --git a/interface/src/ui/PreferencesDialog.h b/interface/src/ui/PreferencesDialog.h new file mode 100644 index 0000000000..c52986cc5c --- /dev/null +++ b/interface/src/ui/PreferencesDialog.h @@ -0,0 +1,48 @@ +// +// PreferencesDialog.h +// interface/src/ui +// +// Created by Stojce Slavkovski on 2/20/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_PreferencesDialog_h +#define hifi_PreferencesDialog_h + +#include "FramelessDialog.h" +#include "ui_preferencesDialog.h" + +#include + +class PreferencesDialog : public FramelessDialog { + Q_OBJECT + +public: + PreferencesDialog(QWidget* parent = 0, Qt::WindowFlags flags = 0); + +protected: + void resizeEvent(QResizeEvent* resizeEvent); + +private: + void loadPreferences(); + void savePreferences(); + void openHeadModelBrowser(); + void openBodyModelBrowser(); + + Ui_PreferencesDialog ui; + QString _faceURLString; + QString _skeletonURLString; + QString _displayNameString; + +private slots: + void accept(); + void setHeadUrl(QString modelUrl); + void setSkeletonUrl(QString modelUrl); + void openSnapshotLocationBrowser(); + +}; + +#endif // hifi_PreferencesDialog_h diff --git a/interface/src/ui/RearMirrorTools.cpp b/interface/src/ui/RearMirrorTools.cpp index 314fd7157b..9620469a22 100644 --- a/interface/src/ui/RearMirrorTools.cpp +++ b/interface/src/ui/RearMirrorTools.cpp @@ -1,9 +1,13 @@ // // RearMirrorTools.cpp -// interface +// interface/src/ui +// +// Created by Stojce Slavkovski on 10/23/2013. +// Copyright 2013 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 // -// Created by stojce on 23.10.2013. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. #include "InterfaceConfig.h" @@ -18,9 +22,8 @@ const char SETTINGS_GROUP_NAME[] = "Rear View Tools"; const char ZOOM_LEVEL_SETTINGS[] = "ZoomLevel"; -const int ICON_SIZE = 20; +const int ICON_SIZE = 24; const int ICON_PADDING = 5; -const int MID_ICON_PADDING = 70; RearMirrorTools::RearMirrorTools(QGLWidget* parent, QRect& bounds, QSettings* settings) : _parent(parent), @@ -29,16 +32,19 @@ RearMirrorTools::RearMirrorTools(QGLWidget* parent, QRect& bounds, QSettings* se _fullScreen(false) { _zoomLevel = HEAD; - _closeTextureId = _parent->bindTexture(QImage(Application::resourcesPath() + "images/close.png")); - _resetTextureId = _parent->bindTexture(QImage(Application::resourcesPath() + "images/reset.png")); - _zoomHeadTextureId = _parent->bindTexture(QImage(Application::resourcesPath() + "images/head.png")); - _zoomBodyTextureId = _parent->bindTexture(QImage(Application::resourcesPath() + "images/body.png")); + _closeTextureId = _parent->bindTexture(QImage(Application::resourcesPath() + "images/close.svg")); + + // Disabled for now https://worklist.net/19548 + // _resetTextureId = _parent->bindTexture(QImage(Application::resourcesPath() + "images/reset.png")); + + _zoomHeadTextureId = _parent->bindTexture(QImage(Application::resourcesPath() + "images/plus.svg")); + _zoomBodyTextureId = _parent->bindTexture(QImage(Application::resourcesPath() + "images/minus.svg")); _shrinkIconRect = QRect(ICON_PADDING, ICON_PADDING, ICON_SIZE, ICON_SIZE); _closeIconRect = QRect(_bounds.left() + ICON_PADDING, _bounds.top() + ICON_PADDING, ICON_SIZE, ICON_SIZE); _resetIconRect = QRect(_bounds.width() - ICON_SIZE - ICON_PADDING, _bounds.top() + ICON_PADDING, ICON_SIZE, ICON_SIZE); - _bodyZoomIconRect = QRect(_bounds.width() - MID_ICON_PADDING - ICON_SIZE, _bounds.bottom() - ICON_PADDING - ICON_SIZE, ICON_SIZE, ICON_SIZE); - _headZoomIconRect = QRect(_bounds.left() + MID_ICON_PADDING, _bounds.bottom() - ICON_PADDING - ICON_SIZE, ICON_SIZE, ICON_SIZE); + _bodyZoomIconRect = QRect(_bounds.width() - ICON_SIZE - ICON_PADDING, _bounds.bottom() - ICON_PADDING - ICON_SIZE, ICON_SIZE, ICON_SIZE); + _headZoomIconRect = QRect(_bounds.left() + ICON_PADDING, _bounds.bottom() - ICON_PADDING - ICON_SIZE, ICON_SIZE, ICON_SIZE); settings->beginGroup(SETTINGS_GROUP_NAME); _zoomLevel = loadSetting(settings, ZOOM_LEVEL_SETTINGS, 0) == HEAD ? HEAD : BODY; @@ -55,7 +61,10 @@ void RearMirrorTools::render(bool fullScreen) { _windowed = _bounds.contains(mousePosition.x(), mousePosition.y()); if (_windowed) { displayIcon(_bounds, _closeIconRect, _closeTextureId); - displayIcon(_bounds, _resetIconRect, _resetTextureId); + + // Disabled for now https://worklist.net/19548 + // displayIcon(_bounds, _resetIconRect, _resetTextureId); + displayIcon(_bounds, _headZoomIconRect, _zoomHeadTextureId, _zoomLevel == HEAD); displayIcon(_bounds, _bodyZoomIconRect, _zoomBodyTextureId, _zoomLevel == BODY); } @@ -69,11 +78,13 @@ bool RearMirrorTools::mousePressEvent(int x, int y) { emit closeView(); return true; } - + + /* Disabled for now https://worklist.net/19548 if (_resetIconRect.contains(x, y)) { emit resetView(); return true; } + */ if (_headZoomIconRect.contains(x, y)) { _zoomLevel = HEAD; @@ -84,7 +95,7 @@ bool RearMirrorTools::mousePressEvent(int x, int y) { _zoomLevel = BODY; return true; } - + if (_bounds.contains(x, y)) { _windowed = false; emit restoreView(); @@ -120,7 +131,7 @@ void RearMirrorTools::displayIcon(QRect bounds, QRect iconBounds, GLuint texture glEnable(GL_TEXTURE_2D); if (selected) { - glColor3f(1, 1, 0); + glColor3f(.5f, .5f, .5f); } else { glColor3f(1, 1, 1); } diff --git a/interface/src/ui/RearMirrorTools.h b/interface/src/ui/RearMirrorTools.h index 7f13eafbee..a595572e7a 100644 --- a/interface/src/ui/RearMirrorTools.h +++ b/interface/src/ui/RearMirrorTools.h @@ -1,13 +1,16 @@ // // RearMirrorTools.h -// interface +// interface/src/ui +// +// Created by Stojce Slavkovski on 10/23/2013. +// Copyright 2013 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 // -// Created by stojce on 23.10.2013. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. - -#ifndef __hifi__RearMirrorTools__ -#define __hifi__RearMirrorTools__ +#ifndef hifi_RearMirrorTools_h +#define hifi_RearMirrorTools_h #include "InterfaceConfig.h" @@ -55,4 +58,4 @@ private: void displayIcon(QRect bounds, QRect iconBounds, GLuint textureId, bool selected = false); }; -#endif /* defined(__hifi__RearMirrorTools__) */ +#endif // hifi_RearMirrorTools_h diff --git a/interface/src/ui/RunningScriptsWidget.cpp b/interface/src/ui/RunningScriptsWidget.cpp index 826aaf7a6a..94a53c4aa5 100644 --- a/interface/src/ui/RunningScriptsWidget.cpp +++ b/interface/src/ui/RunningScriptsWidget.cpp @@ -1,16 +1,19 @@ // // RunningScriptsWidget.cpp -// interface +// interface/src/ui // // Created by Mohammed Nafees on 03/28/2014. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 "ui_runningScriptsWidget.h" #include "RunningScriptsWidget.h" #include #include -#include #include #include @@ -26,6 +29,10 @@ RunningScriptsWidget::RunningScriptsWidget(QWidget *parent) : setWindowFlags(Qt::SubWindow | Qt::FramelessWindowHint); + ui->hideWidgetButton->setIcon(QIcon(Application::resourcesPath() + "images/close.svg")); + ui->reloadAllButton->setIcon(QIcon(Application::resourcesPath() + "images/reload.svg")); + ui->stopAllButton->setIcon(QIcon(Application::resourcesPath() + "images/stop.svg")); + _runningScriptsTable = new ScriptsTableWidget(ui->runningScriptsTableWidget); _runningScriptsTable->setColumnCount(2); _runningScriptsTable->setColumnWidth(0, 245); @@ -72,7 +79,7 @@ void RunningScriptsWidget::setRunningScripts(const QStringList& list) scriptName->setToolTip(list.at(i)); scriptName->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter); QTableWidgetItem *closeIcon = new QTableWidgetItem; - closeIcon->setIcon(QIcon(QPixmap(":/images/kill-script.svg").scaledToHeight(12))); + closeIcon->setIcon(QIcon(QPixmap(Application::resourcesPath() + "images/kill-script.svg").scaledToHeight(12))); _runningScriptsTable->setItem(i, 0, scriptName); _runningScriptsTable->setItem(i, 1, closeIcon); @@ -191,6 +198,7 @@ void RunningScriptsWidget::keyPressEvent(QKeyEvent *e) default: break; } + QWidget::keyPressEvent(e); } diff --git a/interface/src/ui/RunningScriptsWidget.h b/interface/src/ui/RunningScriptsWidget.h index 73dc8e3887..72f284fd2d 100644 --- a/interface/src/ui/RunningScriptsWidget.h +++ b/interface/src/ui/RunningScriptsWidget.h @@ -1,13 +1,18 @@ // // RunningScriptsWidget.h -// interface +// interface/src/ui // // Created by Mohammed Nafees on 03/28/2014. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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__RunningScriptsWidget__ -#define __hifi__RunningScriptsWidget__ +#ifndef hifi_RunningScriptsWidget_h +#define hifi_RunningScriptsWidget_h +// Qt #include #include "ScriptsTableWidget.h" @@ -56,4 +61,4 @@ private: void createRecentlyLoadedScriptsTable(); }; -#endif /* defined(__hifi__RunningScriptsWidget__) */ +#endif // hifi_RunningScriptsWidget_h diff --git a/interface/src/ui/ScriptEditorWidget.cpp b/interface/src/ui/ScriptEditorWidget.cpp new file mode 100644 index 0000000000..3f9b0137ef --- /dev/null +++ b/interface/src/ui/ScriptEditorWidget.cpp @@ -0,0 +1,159 @@ +// +// ScriptEditorWidget.cpp +// interface/src/ui +// +// Created by Thijs Wenker on 4/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 +// + +#include "ui_scriptEditorWidget.h" +#include "ScriptEditorWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "ScriptHighlighting.h" + +ScriptEditorWidget::ScriptEditorWidget() : + _scriptEditorWidgetUI(new Ui::ScriptEditorWidget), + _scriptEngine(NULL) +{ + _scriptEditorWidgetUI->setupUi(this); + + connect(_scriptEditorWidgetUI->scriptEdit->document(), SIGNAL(modificationChanged(bool)), this, SIGNAL(scriptModified())); + connect(_scriptEditorWidgetUI->scriptEdit->document(), SIGNAL(contentsChanged()), this, SLOT(onScriptModified())); + + // remove the title bar (see the Qt docs on setTitleBarWidget) + setTitleBarWidget(new QWidget()); + QFontMetrics fm(_scriptEditorWidgetUI->scriptEdit->font()); + _scriptEditorWidgetUI->scriptEdit->setTabStopWidth(fm.width('0') * 4); + // We create a new ScriptHighligting QObject and provide it with a parent so this is NOT a memory leak. + new ScriptHighlighting(_scriptEditorWidgetUI->scriptEdit->document()); + QTimer::singleShot(0, _scriptEditorWidgetUI->scriptEdit, SLOT(setFocus())); +} + +ScriptEditorWidget::~ScriptEditorWidget() { + delete _scriptEditorWidgetUI; +} + +void ScriptEditorWidget::onScriptModified() { + if(_scriptEditorWidgetUI->onTheFlyCheckBox->isChecked() && isRunning()) { + setRunning(false); + setRunning(true); + } +} + +bool ScriptEditorWidget::isModified() { + return _scriptEditorWidgetUI->scriptEdit->document()->isModified(); +} + +bool ScriptEditorWidget::isRunning() { + return (_scriptEngine != NULL) ? _scriptEngine->isRunning() : false; +} + +bool ScriptEditorWidget::setRunning(bool run) { + if (run && !save()) { + return false; + } + // Clean-up old connections. + disconnect(this, SLOT(onScriptError(const QString&))); + disconnect(this, SLOT(onScriptPrint(const QString&))); + + if (run) { + _scriptEngine = Application::getInstance()->loadScript(_currentScript, true); + connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged())); + + // Make new connections. + connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&))); + connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&))); + } else { + Application::getInstance()->stopScript(_currentScript); + _scriptEngine = NULL; + } + return true; +} + +bool ScriptEditorWidget::saveFile(const QString &scriptPath) { + QFile file(scriptPath); + if (!file.open(QFile::WriteOnly | QFile::Text)) { + QMessageBox::warning(this, tr("Interface"), tr("Cannot write script %1:\n%2.").arg(scriptPath) + .arg(file.errorString())); + return false; + } + + QTextStream out(&file); + out << _scriptEditorWidgetUI->scriptEdit->toPlainText(); + + setScriptFile(scriptPath); + return true; +} + +void ScriptEditorWidget::loadFile(const QString& scriptPath) { + QFile file(scriptPath); + if (!file.open(QFile::ReadOnly | QFile::Text)) { + QMessageBox::warning(this, tr("Interface"), tr("Cannot read script %1:\n%2.").arg(scriptPath).arg(file.errorString())); + return; + } + + QTextStream in(&file); + _scriptEditorWidgetUI->scriptEdit->setPlainText(in.readAll()); + + setScriptFile(scriptPath); + + disconnect(this, SLOT(onScriptError(const QString&))); + disconnect(this, SLOT(onScriptPrint(const QString&))); + + _scriptEngine = Application::getInstance()->getScriptEngine(scriptPath); + if (_scriptEngine != NULL) { + connect(_scriptEngine, SIGNAL(runningStateChanged()), this, SIGNAL(runningStateChanged())); + connect(_scriptEngine, SIGNAL(errorMessage(const QString&)), this, SLOT(onScriptError(const QString&))); + connect(_scriptEngine, SIGNAL(printedMessage(const QString&)), this, SLOT(onScriptPrint(const QString&))); + } +} + +bool ScriptEditorWidget::save() { + return _currentScript.isEmpty() ? saveAs() : saveFile(_currentScript); +} + +bool ScriptEditorWidget::saveAs() { + QString fileName = QFileDialog::getSaveFileName(this, tr("Save script"), QString(), tr("Javascript (*.js)")); + return !fileName.isEmpty() ? saveFile(fileName) : false; +} + +void ScriptEditorWidget::setScriptFile(const QString& scriptPath) { + _currentScript = scriptPath; + _scriptEditorWidgetUI->scriptEdit->document()->setModified(false); + setWindowModified(false); + + emit scriptnameChanged(); +} + +bool ScriptEditorWidget::questionSave() { + if (_scriptEditorWidgetUI->scriptEdit->document()->isModified()) { + QMessageBox::StandardButton button = QMessageBox::warning(this, tr("Interface"), + tr("The script has been modified.\nDo you want to save your changes?"), QMessageBox::Save | QMessageBox::Discard | + QMessageBox::Cancel, QMessageBox::Save); + return button == QMessageBox::Save ? save() : (button == QMessageBox::Cancel ? false : true); + } + return true; +} + +void ScriptEditorWidget::onScriptError(const QString& message) { + _scriptEditorWidgetUI->debugText->appendPlainText("ERROR: " + message); +} + +void ScriptEditorWidget::onScriptPrint(const QString& message) { + _scriptEditorWidgetUI->debugText->appendPlainText("> " + message); +} diff --git a/interface/src/ui/ScriptEditorWidget.h b/interface/src/ui/ScriptEditorWidget.h new file mode 100644 index 0000000000..3e50280a62 --- /dev/null +++ b/interface/src/ui/ScriptEditorWidget.h @@ -0,0 +1,57 @@ +// +// ScriptEditorWidget.h +// interface/src/ui +// +// Created by Thijs Wenker on 4/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_ScriptEditorWidget_h +#define hifi_ScriptEditorWidget_h + +#include +#include "ScriptEditorWidget.h" +#include "ScriptEngine.h" + +namespace Ui { + class ScriptEditorWidget; +} + +class ScriptEditorWidget : public QDockWidget { + Q_OBJECT + +public: + ScriptEditorWidget(); + ~ScriptEditorWidget(); + + bool isModified(); + bool isRunning(); + bool setRunning(bool run); + bool saveFile(const QString& scriptPath); + void loadFile(const QString& scriptPath); + void setScriptFile(const QString& scriptPath); + bool save(); + bool saveAs(); + bool questionSave(); + const QString getScriptName() const { return _currentScript; }; + +signals: + void runningStateChanged(); + void scriptnameChanged(); + void scriptModified(); + +private slots: + void onScriptError(const QString& message); + void onScriptPrint(const QString& message); + void onScriptModified(); + +private: + Ui::ScriptEditorWidget* _scriptEditorWidgetUI; + ScriptEngine* _scriptEngine; + QString _currentScript; +}; + +#endif // hifi_ScriptEditorWidget_h diff --git a/interface/src/ui/ScriptEditorWindow.cpp b/interface/src/ui/ScriptEditorWindow.cpp new file mode 100644 index 0000000000..0c34959353 --- /dev/null +++ b/interface/src/ui/ScriptEditorWindow.cpp @@ -0,0 +1,200 @@ +// +// ScriptEditorWindow.cpp +// interface/src/ui +// +// Created by Thijs Wenker on 4/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 +// + +#include "ui_scriptEditorWindow.h" +#include "ScriptEditorWindow.h" +#include "ScriptEditorWidget.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "FlowLayout.h" + +ScriptEditorWindow::ScriptEditorWindow() : + _ScriptEditorWindowUI(new Ui::ScriptEditorWindow), + _loadMenu(new QMenu), + _saveMenu(new QMenu) +{ + _ScriptEditorWindowUI->setupUi(this); + show(); + addScriptEditorWidget("New script"); + connect(_loadMenu, SIGNAL(aboutToShow()), this, SLOT(loadMenuAboutToShow())); + _ScriptEditorWindowUI->loadButton->setMenu(_loadMenu); + + _saveMenu->addAction("Save as..", this, SLOT(saveScriptAsClicked()), Qt::CTRL | Qt::SHIFT | Qt::Key_S); + + _ScriptEditorWindowUI->saveButton->setMenu(_saveMenu); + + connect(new QShortcut(QKeySequence("Ctrl+N"), this), SIGNAL(activated()), this, SLOT(newScriptClicked())); + connect(new QShortcut(QKeySequence("Ctrl+S"), this), SIGNAL(activated()), this, SLOT(saveScriptClicked())); + connect(new QShortcut(QKeySequence("Ctrl+O"), this), SIGNAL(activated()), this, SLOT(loadScriptClicked())); + connect(new QShortcut(QKeySequence("F5"), this), SIGNAL(activated()), this, SLOT(toggleRunScriptClicked())); +} + +ScriptEditorWindow::~ScriptEditorWindow() { + delete _ScriptEditorWindowUI; +} + +void ScriptEditorWindow::setRunningState(bool run) { + if (_ScriptEditorWindowUI->tabWidget->currentIndex() != -1) { + static_cast(_ScriptEditorWindowUI->tabWidget->currentWidget())->setRunning(run); + } + this->updateButtons(); +} + +void ScriptEditorWindow::updateButtons() { + _ScriptEditorWindowUI->toggleRunButton->setEnabled(_ScriptEditorWindowUI->tabWidget->currentIndex() != -1); + _ScriptEditorWindowUI->toggleRunButton->setIcon(_ScriptEditorWindowUI->tabWidget->currentIndex() != -1 && + static_cast(_ScriptEditorWindowUI->tabWidget->currentWidget())->isRunning() ? + QIcon("../resources/icons/stop-script.svg") : QIcon("../resources/icons/start-script.svg")); +} + +void ScriptEditorWindow::loadScriptMenu(const QString& scriptName) { + addScriptEditorWidget("loading...")->loadFile(scriptName); + updateButtons(); +} + +void ScriptEditorWindow::loadScriptClicked() { + QString scriptName = QFileDialog::getOpenFileName(this, tr("Interface"), QString(), tr("Javascript (*.js)")); + if (!scriptName.isEmpty()) { + addScriptEditorWidget("loading...")->loadFile(scriptName); + updateButtons(); + } +} + +void ScriptEditorWindow::loadMenuAboutToShow() { + _loadMenu->clear(); + QStringList runningScripts = Application::getInstance()->getRunningScripts(); + if (runningScripts.count() > 0) { + QSignalMapper* signalMapper = new QSignalMapper(this); + foreach (const QString& runningScript, runningScripts) { + QAction* runningScriptAction = new QAction(runningScript, _loadMenu); + connect(runningScriptAction, SIGNAL(triggered()), signalMapper, SLOT(map())); + signalMapper->setMapping(runningScriptAction, runningScript); + _loadMenu->addAction(runningScriptAction); + } + connect(signalMapper, SIGNAL(mapped(const QString &)), this, SLOT(loadScriptMenu(const QString&))); + } else { + QAction* naAction = new QAction("(no running scripts)", _loadMenu); + naAction->setDisabled(true); + _loadMenu->addAction(naAction); + } +} + +void ScriptEditorWindow::newScriptClicked() { + addScriptEditorWidget(QString("New script")); +} + +void ScriptEditorWindow::toggleRunScriptClicked() { + this->setRunningState(!(_ScriptEditorWindowUI->tabWidget->currentIndex() !=-1 + && static_cast(_ScriptEditorWindowUI->tabWidget->currentWidget())->isRunning())); +} + +void ScriptEditorWindow::saveScriptClicked() { + if (_ScriptEditorWindowUI->tabWidget->currentIndex() != -1) { + ScriptEditorWidget* currentScriptWidget = static_cast(_ScriptEditorWindowUI->tabWidget + ->currentWidget()); + currentScriptWidget->save(); + } +} + +void ScriptEditorWindow::saveScriptAsClicked() { + if (_ScriptEditorWindowUI->tabWidget->currentIndex() != -1) { + ScriptEditorWidget* currentScriptWidget = static_cast(_ScriptEditorWindowUI->tabWidget + ->currentWidget()); + currentScriptWidget->saveAs(); + } +} + +ScriptEditorWidget* ScriptEditorWindow::addScriptEditorWidget(QString title) { + ScriptEditorWidget* newScriptEditorWidget = new ScriptEditorWidget(); + connect(newScriptEditorWidget, SIGNAL(scriptnameChanged()), this, SLOT(updateScriptNameOrStatus())); + connect(newScriptEditorWidget, SIGNAL(scriptModified()), this, SLOT(updateScriptNameOrStatus())); + connect(newScriptEditorWidget, SIGNAL(runningStateChanged()), this, SLOT(updateButtons())); + _ScriptEditorWindowUI->tabWidget->addTab(newScriptEditorWidget, title); + _ScriptEditorWindowUI->tabWidget->setCurrentWidget(newScriptEditorWidget); + newScriptEditorWidget->setUpdatesEnabled(true); + newScriptEditorWidget->adjustSize(); + return newScriptEditorWidget; +} + +void ScriptEditorWindow::tabSwitched(int tabIndex) { + this->updateButtons(); + if (_ScriptEditorWindowUI->tabWidget->currentIndex() != -1) { + ScriptEditorWidget* currentScriptWidget = static_cast(_ScriptEditorWindowUI->tabWidget + ->currentWidget()); + QString modifiedStar = (currentScriptWidget->isModified() ? "*" : ""); + if (currentScriptWidget->getScriptName().length() > 0) { + this->setWindowTitle("Script Editor [" + currentScriptWidget->getScriptName() + modifiedStar + "]"); + } else { + this->setWindowTitle("Script Editor [New script" + modifiedStar + "]"); + } + } else { + this->setWindowTitle("Script Editor"); + } +} + +void ScriptEditorWindow::tabCloseRequested(int tabIndex) { + ScriptEditorWidget* closingScriptWidget = static_cast(_ScriptEditorWindowUI->tabWidget + ->widget(tabIndex)); + if(closingScriptWidget->questionSave()) { + _ScriptEditorWindowUI->tabWidget->removeTab(tabIndex); + } +} + +void ScriptEditorWindow::closeEvent(QCloseEvent *event) { + bool unsaved_docs_warning = false; + for (int i = 0; i < _ScriptEditorWindowUI->tabWidget->count(); i++){ + if(static_cast(_ScriptEditorWindowUI->tabWidget->widget(i))->isModified()){ + unsaved_docs_warning = true; + break; + } + } + + if (!unsaved_docs_warning || QMessageBox::warning(this, tr("Interface"), + tr("There are some unsaved scripts, are you sure you want to close the editor? Changes will be lost!"), + QMessageBox::Discard | QMessageBox::Cancel, QMessageBox::Cancel) == QMessageBox::Discard) { + event->accept(); + } else { + event->ignore(); + } +} + +void ScriptEditorWindow::updateScriptNameOrStatus() { + ScriptEditorWidget* source = static_cast(QObject::sender()); + QString modifiedStar = (source->isModified()? "*" : ""); + if (source->getScriptName().length() > 0) { + for (int i = 0; i < _ScriptEditorWindowUI->tabWidget->count(); i++){ + if (_ScriptEditorWindowUI->tabWidget->widget(i) == source) { + _ScriptEditorWindowUI->tabWidget->setTabText(i, modifiedStar + QFileInfo(source->getScriptName()).fileName()); + _ScriptEditorWindowUI->tabWidget->setTabToolTip(i, source->getScriptName()); + } + } + } + + if (_ScriptEditorWindowUI->tabWidget->currentWidget() == source) { + if (source->getScriptName().length() > 0) { + this->setWindowTitle("Script Editor [" + source->getScriptName() + modifiedStar + "]"); + } else { + this->setWindowTitle("Script Editor [New script" + modifiedStar + "]"); + } + } +} diff --git a/interface/src/ui/ScriptEditorWindow.h b/interface/src/ui/ScriptEditorWindow.h new file mode 100644 index 0000000000..0bf5015ccf --- /dev/null +++ b/interface/src/ui/ScriptEditorWindow.h @@ -0,0 +1,54 @@ +// +// ScriptEditorWindow.h +// interface/src/ui +// +// Created by Thijs Wenker on 4/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_ScriptEditorWindow_h +#define hifi_ScriptEditorWindow_h + +#include "ScriptEditorWidget.h" + +namespace Ui { + class ScriptEditorWindow; +} + +class ScriptEditorWindow : public QWidget { + Q_OBJECT + +public: + ScriptEditorWindow(); + ~ScriptEditorWindow(); + +protected: + void closeEvent(QCloseEvent* event); + +private: + Ui::ScriptEditorWindow* _ScriptEditorWindowUI; + QMenu* _loadMenu; + QMenu* _saveMenu; + + ScriptEditorWidget* addScriptEditorWidget(QString title); + void setRunningState(bool run); + void setScriptName(const QString& scriptName); + +private slots: + void loadScriptMenu(const QString& scriptName); + void loadScriptClicked(); + void newScriptClicked(); + void toggleRunScriptClicked(); + void saveScriptClicked(); + void saveScriptAsClicked(); + void loadMenuAboutToShow(); + void tabSwitched(int tabIndex); + void tabCloseRequested(int tabIndex); + void updateScriptNameOrStatus(); + void updateButtons(); +}; + +#endif // hifi_ScriptEditorWindow_h diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index a29a96f009..29c2d3c90f 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -1,9 +1,12 @@ // // Snapshot.cpp -// hifi +// interface/src/ui // // Created by Stojce Slavkovski on 1/26/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 @@ -13,6 +16,7 @@ #include #include "Snapshot.h" +#include "Menu.h" // filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg // %1 <= username, %2 <= date and time, %3 <= current location @@ -76,7 +80,7 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) { shot.setText(ORIENTATION_Z, QString::number(orientation.z)); shot.setText(ORIENTATION_W, QString::number(orientation.w)); - shot.setText(DOMAIN_KEY, NodeList::getInstance()->getDomainInfo().getHostname()); + shot.setText(DOMAIN_KEY, NodeList::getInstance()->getDomainHandler().getHostname()); QString formattedLocation = QString("%1_%2_%3").arg(location.x).arg(location.y).arg(location.z); // replace decimal . with '-' @@ -87,8 +91,12 @@ void Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) { username.replace(QRegExp("[^A-Za-z0-9_]"), "-"); QDateTime now = QDateTime::currentDateTime(); - - QString fileName = FileUtils::standardPath(SNAPSHOTS_DIRECTORY); + QString fileName = Menu::getInstance()->getSnapshotsLocation(); + + if (!fileName.endsWith(QDir::separator())) { + fileName.append(QDir::separator()); + } + fileName.append(QString(FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation))); shot.save(fileName, 0, 100); } diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 1b78e8328e..0fa9017b0a 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -1,13 +1,16 @@ // // Snapshot.h -// hifi +// interface/src/ui // // Created by Stojce Slavkovski on 1/26/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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__Snapshot__ -#define __hifi__Snapshot__ +#ifndef hifi_Snapshot_h +#define hifi_Snapshot_h #include "InterfaceConfig.h" @@ -42,4 +45,4 @@ public: static SnapshotMetaData* parseSnapshotData(QString snapshotPath); }; -#endif /* defined(__hifi__Snapshot__) */ +#endif // hifi_Snapshot_h diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp new file mode 100644 index 0000000000..2f1c055540 --- /dev/null +++ b/interface/src/ui/Stats.cpp @@ -0,0 +1,609 @@ +// +// Stats.cpp +// interface/src/ui +// +// Created by Lucas Crisman on 22/03/14. +// Copyright 2013 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 + +#include + +#include +#include +#include +#include + +#include "Stats.h" +#include "InterfaceConfig.h" +#include "Menu.h" +#include "Util.h" + +using namespace std; + +const int STATS_PELS_PER_LINE = 20; + +const int STATS_GENERAL_MIN_WIDTH = 165; +const int STATS_PING_MIN_WIDTH = 190; +const int STATS_GEO_MIN_WIDTH = 240; +const int STATS_VOXEL_MIN_WIDTH = 410; + +Stats* Stats::getInstance() { + static Stats stats; + return &stats; +} + +Stats::Stats(): + _expanded(false), + _recentMaxPackets(0), + _resetRecentMaxPacketsSoon(true), + _generalStatsWidth(STATS_GENERAL_MIN_WIDTH), + _pingStatsWidth(STATS_PING_MIN_WIDTH), + _geoStatsWidth(STATS_GEO_MIN_WIDTH), + _voxelStatsWidth(STATS_VOXEL_MIN_WIDTH), + _lastHorizontalOffset(0) +{ + QGLWidget* glWidget = Application::getInstance()->getGLWidget(); + resetWidth(glWidget->width(), 0); +} + +void Stats::toggleExpanded() { + _expanded = !_expanded; +} + +// called on mouse click release +// check for clicks over stats in order to expand or contract them +void Stats::checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseDragStartedY, int horizontalOffset) { + QGLWidget* glWidget = Application::getInstance()->getGLWidget(); + + if (0 != glm::compMax(glm::abs(glm::ivec2(mouseX - mouseDragStartedX, mouseY - mouseDragStartedY)))) { + // not worried about dragging on stats + return; + } + + int statsHeight = 0, + statsWidth = 0, + statsX = 0, + statsY = 0, + lines = 0; + + statsX = horizontalOffset; + + // top-left stats click + lines = _expanded ? 5 : 3; + statsHeight = lines * STATS_PELS_PER_LINE + 10; + if (mouseX > statsX && mouseX < statsX + _generalStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) { + toggleExpanded(); + return; + } + statsX += _generalStatsWidth; + + // ping stats click + if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { + lines = _expanded ? 4 : 3; + statsHeight = lines * STATS_PELS_PER_LINE + 10; + if (mouseX > statsX && mouseX < statsX + _pingStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) { + toggleExpanded(); + return; + } + statsX += _pingStatsWidth; + } + + // geo stats panel click + lines = _expanded ? 4 : 3; + statsHeight = lines * STATS_PELS_PER_LINE + 10; + if (mouseX > statsX && mouseX < statsX + _geoStatsWidth && mouseY > statsY && mouseY < statsY + statsHeight) { + toggleExpanded(); + return; + } + statsX += _geoStatsWidth; + + // top-right stats click + lines = _expanded ? 11 : 3; + statsHeight = lines * STATS_PELS_PER_LINE + 10; + statsWidth = glWidget->width() - statsX; + if (mouseX > statsX && mouseX < statsX + statsWidth && mouseY > statsY && mouseY < statsY + statsHeight) { + toggleExpanded(); + return; + } +} + +void Stats::resetWidth(int width, int horizontalOffset) { + QGLWidget* glWidget = Application::getInstance()->getGLWidget(); + int extraSpace = glWidget->width() - horizontalOffset -2 + - STATS_GENERAL_MIN_WIDTH + - (Menu::getInstance()->isOptionChecked(MenuOption::TestPing) ? STATS_PING_MIN_WIDTH -1 : 0) + - STATS_GEO_MIN_WIDTH + - STATS_VOXEL_MIN_WIDTH; + + int panels = 4; + + _generalStatsWidth = STATS_GENERAL_MIN_WIDTH; + if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { + _pingStatsWidth = STATS_PING_MIN_WIDTH; + } else { + _pingStatsWidth = 0; + panels = 3; + } + _geoStatsWidth = STATS_GEO_MIN_WIDTH; + _voxelStatsWidth = STATS_VOXEL_MIN_WIDTH; + + if (extraSpace > panels) { + _generalStatsWidth += (int) extraSpace / panels; + if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { + _pingStatsWidth += (int) extraSpace / panels; + } + _geoStatsWidth += (int) extraSpace / panels; + _voxelStatsWidth += glWidget->width() - (_generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3); + } +} + + +// translucent background box that makes stats more readable +void Stats::drawBackground(unsigned int rgba, int x, int y, int width, int height) { + glBegin(GL_QUADS); + glColor4f(((rgba >> 24) & 0xff) / 255.0f, + ((rgba >> 16) & 0xff) / 255.0f, + ((rgba >> 8) & 0xff) / 255.0f, + (rgba & 0xff) / 255.0f); + glVertex3f(x, y, 0); + glVertex3f(x + width, y, 0); + glVertex3f(x + width, y + height, 0); + glVertex3f(x , y + height, 0); + glEnd(); + glColor4f(1, 1, 1, 1); +} + +// display expanded or contracted stats +void Stats::display( + const float* color, + int horizontalOffset, + float fps, + int packetsPerSecond, + int bytesPerSecond, + int voxelPacketsToProcess) +{ + QGLWidget* glWidget = Application::getInstance()->getGLWidget(); + + unsigned int backgroundColor = 0x33333399; + int verticalOffset = 0, lines = 0; + float scale = 0.10f; + float rotation = 0.0f; + int font = 2; + + QLocale locale(QLocale::English); + std::stringstream voxelStats; + + if (_lastHorizontalOffset != horizontalOffset) { + resetWidth(glWidget->width(), horizontalOffset); + _lastHorizontalOffset = horizontalOffset; + } + + glPointSize(1.0f); + + // we need to take one avatar out so we don't include ourselves + int totalAvatars = Application::getInstance()->getAvatarManager().size() - 1; + int totalServers = NodeList::getInstance()->size(); + + lines = _expanded ? 5 : 3; + drawBackground(backgroundColor, horizontalOffset, 0, _generalStatsWidth, lines * STATS_PELS_PER_LINE + 10); + horizontalOffset += 5; + + char serverNodes[30]; + sprintf(serverNodes, "Servers: %d", totalServers); + char avatarNodes[30]; + sprintf(avatarNodes, "Avatars: %d", totalAvatars); + char framesPerSecond[30]; + sprintf(framesPerSecond, "Framerate: %3.0f FPS", fps); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, serverNodes, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarNodes, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, framesPerSecond, color); + + if (_expanded) { + char packetsPerSecondString[30]; + sprintf(packetsPerSecondString, "Pkts/sec: %d", packetsPerSecond); + char averageMegabitsPerSecond[30]; + sprintf(averageMegabitsPerSecond, "Mbps: %3.2f", (float)bytesPerSecond * 8.f / 1000000.f); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, packetsPerSecondString, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, averageMegabitsPerSecond, color); + } + + verticalOffset = 0; + horizontalOffset = _lastHorizontalOffset + _generalStatsWidth +1; + + if (Menu::getInstance()->isOptionChecked(MenuOption::TestPing)) { + int pingAudio = 0, pingAvatar = 0, pingVoxel = 0, pingVoxelMax = 0; + + NodeList* nodeList = NodeList::getInstance(); + SharedNodePointer audioMixerNode = nodeList->soloNodeOfType(NodeType::AudioMixer); + SharedNodePointer avatarMixerNode = nodeList->soloNodeOfType(NodeType::AvatarMixer); + + pingAudio = audioMixerNode ? audioMixerNode->getPingMs() : 0; + pingAvatar = avatarMixerNode ? avatarMixerNode->getPingMs() : 0; + + // Now handle voxel servers, since there could be more than one, we average their ping times + unsigned long totalPingVoxel = 0; + int voxelServerCount = 0; + + foreach (const SharedNodePointer& node, nodeList->getNodeHash()) { + if (node->getType() == NodeType::VoxelServer) { + totalPingVoxel += node->getPingMs(); + voxelServerCount++; + if (pingVoxelMax < node->getPingMs()) { + pingVoxelMax = node->getPingMs(); + } + } + } + + if (voxelServerCount) { + pingVoxel = totalPingVoxel/voxelServerCount; + } + + lines = _expanded ? 4 : 3; + drawBackground(backgroundColor, horizontalOffset, 0, _pingStatsWidth, lines * STATS_PELS_PER_LINE + 10); + horizontalOffset += 5; + + Audio* audio = Application::getInstance()->getAudio(); + + char audioJitter[30]; + sprintf(audioJitter, + "Buffer msecs %.1f", + (float) (audio->getNetworkBufferLengthSamplesPerChannel() + (float) audio->getJitterBufferSamples()) / + (float) audio->getNetworkSampleRate() * 1000.f); + drawText(30, glWidget->height() - 22, scale, rotation, font, audioJitter, color); + + + char audioPing[30]; + sprintf(audioPing, "Audio ping: %d", pingAudio); + + + char avatarPing[30]; + sprintf(avatarPing, "Avatar ping: %d", pingAvatar); + char voxelAvgPing[30]; + sprintf(voxelAvgPing, "Voxel avg ping: %d", pingVoxel); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, audioPing, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarPing, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelAvgPing, color); + + if (_expanded) { + char voxelMaxPing[30]; + sprintf(voxelMaxPing, "Voxel max ping: %d", pingVoxelMax); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, voxelMaxPing, color); + } + + verticalOffset = 0; + horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + 2; + } + + MyAvatar* myAvatar = Application::getInstance()->getAvatar(); + glm::vec3 avatarPos = myAvatar->getPosition(); + + lines = _expanded ? 5 : 3; + + drawBackground(backgroundColor, horizontalOffset, 0, _geoStatsWidth, lines * STATS_PELS_PER_LINE + 10); + horizontalOffset += 5; + + char avatarPosition[200]; + sprintf(avatarPosition, "Position: %.1f, %.1f, %.1f", avatarPos.x, avatarPos.y, avatarPos.z); + char avatarVelocity[30]; + sprintf(avatarVelocity, "Velocity: %.1f", glm::length(myAvatar->getVelocity())); + char avatarBodyYaw[30]; + sprintf(avatarBodyYaw, "Yaw: %.1f", myAvatar->getBodyYaw()); + char avatarMixerStats[200]; + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarPosition, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarVelocity, color); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarBodyYaw, color); + + if (_expanded) { + SharedNodePointer avatarMixer = NodeList::getInstance()->soloNodeOfType(NodeType::AvatarMixer); + if (avatarMixer) { + sprintf(avatarMixerStats, "Avatar Mixer: %.f kbps, %.f pps", + roundf(avatarMixer->getAverageKilobitsPerSecond()), + roundf(avatarMixer->getAveragePacketsPerSecond())); + } else { + sprintf(avatarMixerStats, "No Avatar Mixer"); + } + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, avatarMixerStats, color); + + stringstream downloads; + downloads << "Downloads: "; + foreach (Resource* resource, ResourceCache::getLoadingRequests()) { + downloads << (int)(resource->getProgress() * 100.0f) << "% "; + } + downloads << "(" << ResourceCache::getPendingRequestCount() << " pending)"; + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, downloads.str().c_str(), color); + } + + verticalOffset = 0; + horizontalOffset = _lastHorizontalOffset + _generalStatsWidth + _pingStatsWidth + _geoStatsWidth + 3; + + VoxelSystem* voxels = Application::getInstance()->getVoxels(); + + lines = _expanded ? 12 : 3; + if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) { + lines += 9; // spatial audio processing adds 1 spacing line and 8 extra lines of info + } + + drawBackground(backgroundColor, horizontalOffset, 0, glWidget->width() - horizontalOffset, lines * STATS_PELS_PER_LINE + 10); + horizontalOffset += 5; + + if (_expanded) { + // Local Voxel Memory Usage + voxelStats.str(""); + voxelStats << "Voxels Memory Nodes: " << VoxelTreeElement::getTotalMemoryUsage() / 1000000.f << "MB"; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + + voxelStats.str(""); + voxelStats << + "Geometry RAM: " << voxels->getVoxelMemoryUsageRAM() / 1000000.f << "MB / " << + "VBO: " << voxels->getVoxelMemoryUsageVBO() / 1000000.f << "MB"; + if (voxels->hasVoxelMemoryUsageGPU()) { + voxelStats << " / GPU: " << voxels->getVoxelMemoryUsageGPU() / 1000000.f << "MB"; + } + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + + // Voxel Rendering + voxelStats.str(""); + voxelStats.precision(4); + voxelStats << "Voxel Rendering Slots Max: " << voxels->getMaxVoxels() / 1000.f << "K"; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + } + + voxelStats.str(""); + voxelStats.precision(4); + voxelStats << "Drawn: " << voxels->getVoxelsWritten() / 1000.f << "K " << + "Abandoned: " << voxels->getAbandonedVoxels() / 1000.f << "K "; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + + // iterate all the current voxel stats, and list their sending modes, and total voxel counts + std::stringstream sendingMode(""); + sendingMode << "Octree Sending Mode: ["; + int serverCount = 0; + int movingServerCount = 0; + unsigned long totalNodes = 0; + unsigned long totalInternal = 0; + unsigned long totalLeaves = 0; + NodeToOctreeSceneStats* octreeServerSceneStats = Application::getInstance()->getOcteeSceneStats(); + for(NodeToOctreeSceneStatsIterator i = octreeServerSceneStats->begin(); i != octreeServerSceneStats->end(); i++) { + //const QUuid& uuid = i->first; + OctreeSceneStats& stats = i->second; + serverCount++; + if (_expanded) { + if (serverCount > 1) { + sendingMode << ","; + } + if (stats.isMoving()) { + sendingMode << "M"; + movingServerCount++; + } else { + sendingMode << "S"; + } + } + + // calculate server node totals + totalNodes += stats.getTotalElements(); + if (_expanded) { + totalInternal += stats.getTotalInternal(); + totalLeaves += stats.getTotalLeaves(); + } + } + if (_expanded) { + if (serverCount == 0) { + sendingMode << "---"; + } + sendingMode << "] " << serverCount << " servers"; + if (movingServerCount > 0) { + sendingMode << " "; + } else { + sendingMode << " "; + } + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)sendingMode.str().c_str(), color); + } + + // Incoming packets + if (_expanded) { + voxelStats.str(""); + QString packetsString = locale.toString((int)voxelPacketsToProcess); + QString maxString = locale.toString((int)_recentMaxPackets); + voxelStats << "Voxel Packets to Process: " << qPrintable(packetsString) + << " [Recent Max: " << qPrintable(maxString) << "]"; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + } + + if (_resetRecentMaxPacketsSoon && voxelPacketsToProcess > 0) { + _recentMaxPackets = 0; + _resetRecentMaxPacketsSoon = false; + } + if (voxelPacketsToProcess == 0) { + _resetRecentMaxPacketsSoon = true; + } else { + if (voxelPacketsToProcess > _recentMaxPackets) { + _recentMaxPackets = voxelPacketsToProcess; + } + } + + verticalOffset += (_expanded ? STATS_PELS_PER_LINE : 0); + + QString serversTotalString = locale.toString((uint)totalNodes); // consider adding: .rightJustified(10, ' '); + + // Server Voxels + voxelStats.str(""); + voxelStats << "Server voxels: " << qPrintable(serversTotalString); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + + if (_expanded) { + QString serversInternalString = locale.toString((uint)totalInternal); + QString serversLeavesString = locale.toString((uint)totalLeaves); + + voxelStats.str(""); + voxelStats << + "Internal: " << qPrintable(serversInternalString) << " " << + "Leaves: " << qPrintable(serversLeavesString) << ""; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + } + + unsigned long localTotal = VoxelTreeElement::getNodeCount(); + QString localTotalString = locale.toString((uint)localTotal); // consider adding: .rightJustified(10, ' '); + + // Local Voxels + voxelStats.str(""); + voxelStats << "Local voxels: " << qPrintable(localTotalString); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + + if (_expanded) { + unsigned long localInternal = VoxelTreeElement::getInternalNodeCount(); + unsigned long localLeaves = VoxelTreeElement::getLeafNodeCount(); + QString localInternalString = locale.toString((uint)localInternal); + QString localLeavesString = locale.toString((uint)localLeaves); + + voxelStats.str(""); + voxelStats << + "Internal: " << qPrintable(localInternalString) << " " << + "Leaves: " << qPrintable(localLeavesString) << ""; + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + } + + // LOD Details + if (_expanded) { + voxelStats.str(""); + QString displayLODDetails = Menu::getInstance()->getLODFeedbackText(); + voxelStats << "LOD: You can see " << qPrintable(displayLODDetails.trimmed()); + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, (char*)voxelStats.str().c_str(), color); + } + + + if (_expanded && Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessing)) { + verticalOffset += STATS_PELS_PER_LINE; // space one line... + + const AudioReflector* audioReflector = Application::getInstance()->getAudioReflector(); + + // add some reflection stats + char reflectionsStatus[128]; + + sprintf(reflectionsStatus, "Reflections: %d, Original: %s, Ears: %s, Source: %s, Normals: %s", + audioReflector->getReflections(), + (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingIncludeOriginal) + ? "included" : "silent"), + (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingSeparateEars) + ? "two" : "one"), + (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingStereoSource) + ? "stereo" : "mono"), + (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingSlightlyRandomSurfaces) + ? "random" : "regular") + ); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reflectionsStatus, color); + + float preDelay = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingPreDelay) ? + audioReflector->getPreDelay() : 0.0f; + + sprintf(reflectionsStatus, "Delay: pre: %6.3f, average %6.3f, max %6.3f, min %6.3f, speed: %6.3f", + preDelay, + audioReflector->getAverageDelayMsecs(), + audioReflector->getMaxDelayMsecs(), + audioReflector->getMinDelayMsecs(), + audioReflector->getSoundMsPerMeter()); + + verticalOffset += STATS_PELS_PER_LINE; + + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reflectionsStatus, color); + + bool distanceAttenuationDisabled = Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingDontDistanceAttenuate); + + bool alternateDistanceAttenuationEnabled = Menu::getInstance()->isOptionChecked( + MenuOption::AudioSpatialProcessingAlternateDistanceAttenuate); + + sprintf(reflectionsStatus, "Attenuation: average %5.3f, max %5.3f, min %5.3f, %s: %5.3f", + audioReflector->getAverageAttenuation(), + audioReflector->getMaxAttenuation(), + audioReflector->getMinAttenuation(), + (distanceAttenuationDisabled ? "Distance Factor [DISABLED]" : + alternateDistanceAttenuationEnabled ? "Distance Factor [ALTERNATE]" : "Distance Factor [STANARD]"), + audioReflector->getDistanceAttenuationScalingFactor()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reflectionsStatus, color); + + sprintf(reflectionsStatus, "Local Audio: %s Attenuation: %5.3f", + (Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingProcessLocalAudio) + ? "yes" : "no"), + audioReflector->getLocalAudioAttenuationFactor()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reflectionsStatus, color); + + bool diffusionEnabled = Menu::getInstance()->isOptionChecked(MenuOption::AudioSpatialProcessingWithDiffusions); + int fanout = diffusionEnabled ? audioReflector->getDiffusionFanout() : 0; + int diffusionPaths = diffusionEnabled ? audioReflector->getDiffusionPathCount() : 0; + sprintf(reflectionsStatus, "Diffusion: %s, Fanout: %d, Paths: %d", + (diffusionEnabled ? "yes" : "no"), fanout, diffusionPaths); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reflectionsStatus, color); + + const float AS_PERCENT = 100.0f; + float reflectiveRatio = audioReflector->getReflectiveRatio() * AS_PERCENT; + float diffusionRatio = audioReflector->getDiffusionRatio() * AS_PERCENT; + float absorptionRatio = audioReflector->getAbsorptionRatio() * AS_PERCENT; + sprintf(reflectionsStatus, "Ratios: Reflective: %5.3f, Diffusion: %5.3f, Absorption: %5.3f", + reflectiveRatio, diffusionRatio, absorptionRatio); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reflectionsStatus, color); + + sprintf(reflectionsStatus, "Comb Filter Window: %5.3f ms, Allowed: %d, Suppressed: %d", + audioReflector->getCombFilterWindow(), + audioReflector->getEchoesInjected(), + audioReflector->getEchoesSuppressed()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, scale, rotation, font, reflectionsStatus, color); + + sprintf(reflectionsStatus, "Wet/Dry Mix: Original: %5.3f Echoes: %5.3f", + audioReflector->getOriginalSourceAttenuation(), + audioReflector->getEchoesAttenuation()); + + verticalOffset += STATS_PELS_PER_LINE; + drawText(horizontalOffset, verticalOffset, 0.10f, 0.f, 2.f, reflectionsStatus, color); + + } + +} diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h new file mode 100644 index 0000000000..1ce0807ee8 --- /dev/null +++ b/interface/src/ui/Stats.h @@ -0,0 +1,49 @@ +// +// Stats.h +// interface/src/ui +// +// Created by Lucas Crisman on 22/03/14. +// Copyright 2013 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_Stats_h +#define hifi_Stats_h + +#include + +#include + +class Stats: public QObject { + Q_OBJECT + +public: + static Stats* getInstance(); + + Stats(); + + static void drawBackground(unsigned int rgba, int x, int y, int width, int height); + + void toggleExpanded(); + void checkClick(int mouseX, int mouseY, int mouseDragStartedX, int mouseDragStartedY, int horizontalOffset); + void resetWidth(int width, int horizontalOffset); + void display(const float* color, int horizontalOffset, float fps, int packetsPerSecond, int bytesPerSecond, int voxelPacketsToProcess); +private: + static Stats* _sharedInstance; + + bool _expanded; + + int _recentMaxPackets; // recent max incoming voxel packets to process + bool _resetRecentMaxPacketsSoon; + + int _generalStatsWidth; + int _pingStatsWidth; + int _geoStatsWidth; + int _voxelStatsWidth; + + int _lastHorizontalOffset; +}; + +#endif // hifi_Stats_h diff --git a/interface/src/ui/TextRenderer.cpp b/interface/src/ui/TextRenderer.cpp index cacd730fd6..2743e3e572 100644 --- a/interface/src/ui/TextRenderer.cpp +++ b/interface/src/ui/TextRenderer.cpp @@ -1,9 +1,13 @@ // // TextRenderer.cpp -// interface +// interface/src/ui // // Created by Andrzej Kapolka on 4/24/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 #include diff --git a/interface/src/ui/TextRenderer.h b/interface/src/ui/TextRenderer.h index d6c24c1ce8..813f15a5ac 100644 --- a/interface/src/ui/TextRenderer.h +++ b/interface/src/ui/TextRenderer.h @@ -1,13 +1,16 @@ // // TextRenderer.h -// interface +// interface/src/ui // // Created by Andrzej Kapolka on 4/26/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__TextRenderer__ -#define __interface__TextRenderer__ +#ifndef hifi_TextRenderer_h +#define hifi_TextRenderer_h #include #include @@ -111,4 +114,4 @@ private: int _width; }; -#endif /* defined(__interface__TextRenderer__) */ +#endif // hifi_TextRenderer_h diff --git a/interface/src/ui/UpdateDialog.cpp b/interface/src/ui/UpdateDialog.cpp index 9919cd0a7b..ace022b683 100644 --- a/interface/src/ui/UpdateDialog.cpp +++ b/interface/src/ui/UpdateDialog.cpp @@ -1,9 +1,11 @@ // // UpdateDialog.cpp -// interface +// interface/src/ui // -// Created by Leonardo Murillo on 1/8/14. -// Copyright (c) 2013, 2014 High Fidelity, Inc. All rights reserved. +// 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 "Application.h" // HACK ATTACK WARNING: for windows build to work, we need this ahead of QtGui @@ -49,4 +51,4 @@ void UpdateDialog::handleDownload() { void UpdateDialog::handleSkip() { Application::getInstance()->skipVersion(_latestVersion); this->close(); -} \ No newline at end of file +} diff --git a/interface/src/ui/UpdateDialog.h b/interface/src/ui/UpdateDialog.h index 14f4e6f39c..15a97bf024 100644 --- a/interface/src/ui/UpdateDialog.h +++ b/interface/src/ui/UpdateDialog.h @@ -1,13 +1,15 @@ // // UpdateDialog.h -// interface +// interface/src/ui // -// Created by Leonardo Murillo on 1/8/14. -// Copyright (c) 2013, 2014 High Fidelity, Inc. All rights reserved. +// 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__UpdateDialog__ -#define __hifi__UpdateDialog__ +#ifndef hifi_UpdateDialog_h +#define hifi_UpdateDialog_h #include @@ -26,4 +28,4 @@ private slots: void handleSkip(); }; -#endif /* defined(__hifi__UpdateDialog__) */ +#endif // hifi_UpdateDialog_h diff --git a/interface/src/ui/overlays/Base3DOverlay.cpp b/interface/src/ui/overlays/Base3DOverlay.cpp index bcd2ca1cd2..3500f400ab 100644 --- a/interface/src/ui/overlays/Base3DOverlay.cpp +++ b/interface/src/ui/overlays/Base3DOverlay.cpp @@ -1,8 +1,11 @@ // // Base3DOverlay.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/ui/overlays/Base3DOverlay.h b/interface/src/ui/overlays/Base3DOverlay.h index 286193393c..e2dcb82454 100644 --- a/interface/src/ui/overlays/Base3DOverlay.h +++ b/interface/src/ui/overlays/Base3DOverlay.h @@ -1,12 +1,15 @@ // // Base3DOverlay.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Base3DOverlay__ -#define __interface__Base3DOverlay__ +#ifndef hifi_Base3DOverlay_h +#define hifi_Base3DOverlay_h #include "Overlay.h" @@ -33,4 +36,4 @@ protected: }; -#endif /* defined(__interface__Base3DOverlay__) */ +#endif // hifi_Base3DOverlay_h diff --git a/interface/src/ui/overlays/Cube3DOverlay.cpp b/interface/src/ui/overlays/Cube3DOverlay.cpp index 992a18e451..e9a366c6dc 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.cpp +++ b/interface/src/ui/overlays/Cube3DOverlay.cpp @@ -1,8 +1,11 @@ // // Cube3DOverlay.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL @@ -37,6 +40,7 @@ void Cube3DOverlay::render() { if (_isSolid) { glutSolidCube(_size); } else { + glLineWidth(_lineWidth); glutWireCube(_size); } glPopMatrix(); diff --git a/interface/src/ui/overlays/Cube3DOverlay.h b/interface/src/ui/overlays/Cube3DOverlay.h index a1705d47d0..828048e697 100644 --- a/interface/src/ui/overlays/Cube3DOverlay.h +++ b/interface/src/ui/overlays/Cube3DOverlay.h @@ -1,12 +1,15 @@ // // Cube3DOverlay.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Cube3DOverlay__ -#define __interface__Cube3DOverlay__ +#ifndef hifi_Cube3DOverlay_h +#define hifi_Cube3DOverlay_h #include "Volume3DOverlay.h" @@ -20,4 +23,4 @@ public: }; -#endif /* defined(__interface__Cube3DOverlay__) */ +#endif // hifi_Cube3DOverlay_h diff --git a/interface/src/ui/overlays/ImageOverlay.cpp b/interface/src/ui/overlays/ImageOverlay.cpp index ac5c8ecefa..aa4766488a 100644 --- a/interface/src/ui/overlays/ImageOverlay.cpp +++ b/interface/src/ui/overlays/ImageOverlay.cpp @@ -1,8 +1,11 @@ // // ImageOverlay.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/ui/overlays/ImageOverlay.h b/interface/src/ui/overlays/ImageOverlay.h index d6165e388d..613cd95989 100644 --- a/interface/src/ui/overlays/ImageOverlay.h +++ b/interface/src/ui/overlays/ImageOverlay.h @@ -1,12 +1,15 @@ // // ImageOverlay.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__ImageOverlay__ -#define __interface__ImageOverlay__ +#ifndef hifi_ImageOverlay_h +#define hifi_ImageOverlay_h // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -59,4 +62,4 @@ private: }; -#endif /* defined(__interface__ImageOverlay__) */ +#endif // hifi_ImageOverlay_h diff --git a/interface/src/ui/overlays/Line3DOverlay.cpp b/interface/src/ui/overlays/Line3DOverlay.cpp index c357233329..6c036a898d 100644 --- a/interface/src/ui/overlays/Line3DOverlay.cpp +++ b/interface/src/ui/overlays/Line3DOverlay.cpp @@ -1,8 +1,11 @@ // // Line3DOverlay.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/ui/overlays/Line3DOverlay.h b/interface/src/ui/overlays/Line3DOverlay.h index d52b639d59..5b802f49c8 100644 --- a/interface/src/ui/overlays/Line3DOverlay.h +++ b/interface/src/ui/overlays/Line3DOverlay.h @@ -1,12 +1,15 @@ // // Line3DOverlay.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Line3DOverlay__ -#define __interface__Line3DOverlay__ +#ifndef hifi_Line3DOverlay_h +#define hifi_Line3DOverlay_h #include "Base3DOverlay.h" @@ -31,4 +34,4 @@ protected: }; -#endif /* defined(__interface__Line3DOverlay__) */ +#endif // hifi_Line3DOverlay_h diff --git a/interface/src/ui/overlays/LocalVoxelsOverlay.cpp b/interface/src/ui/overlays/LocalVoxelsOverlay.cpp index 460f4eadb6..93736ddccb 100644 --- a/interface/src/ui/overlays/LocalVoxelsOverlay.cpp +++ b/interface/src/ui/overlays/LocalVoxelsOverlay.cpp @@ -1,11 +1,14 @@ // // LocalVoxelsOverlay.cpp -// hifi +// interface/src/ui/overlays // // Created by Clément Brisset on 2/28/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" diff --git a/interface/src/ui/overlays/LocalVoxelsOverlay.h b/interface/src/ui/overlays/LocalVoxelsOverlay.h index 2b3ba1f535..cc23b05ff6 100644 --- a/interface/src/ui/overlays/LocalVoxelsOverlay.h +++ b/interface/src/ui/overlays/LocalVoxelsOverlay.h @@ -1,15 +1,16 @@ // // LocalVoxelsOverlay.h -// hifi +// interface/src/ui/overlays // // Created by Clément Brisset on 2/28/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // -// Scriptable interface for LocalVoxels +// 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__LocalVoxelsOverlay__ -#define __hifi__LocalVoxelsOverlay__ +#ifndef hifi_LocalVoxelsOverlay_h +#define hifi_LocalVoxelsOverlay_h // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -47,4 +48,4 @@ private: StrongVoxelSystemPointer _voxelSystem; }; -#endif /* defined(__hifi__LocalVoxelsOverlay__) */ +#endif // hifi_LocalVoxelsOverlay_h diff --git a/interface/src/ui/overlays/Overlay.cpp b/interface/src/ui/overlays/Overlay.cpp index 40da2253f4..3b38addb76 100644 --- a/interface/src/ui/overlays/Overlay.cpp +++ b/interface/src/ui/overlays/Overlay.cpp @@ -1,8 +1,11 @@ // // Overlay.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/ui/overlays/Overlay.h b/interface/src/ui/overlays/Overlay.h index ad1084e889..6feb159e05 100644 --- a/interface/src/ui/overlays/Overlay.h +++ b/interface/src/ui/overlays/Overlay.h @@ -1,12 +1,15 @@ // // Overlay.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Overlay__ -#define __interface__Overlay__ +#ifndef hifi_Overlay_h +#define hifi_Overlay_h // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -51,4 +54,4 @@ protected: }; -#endif /* defined(__interface__Overlay__) */ +#endif // hifi_Overlay_h diff --git a/interface/src/ui/overlays/Overlay2D.cpp b/interface/src/ui/overlays/Overlay2D.cpp index 0c459811c4..0bdb8790cc 100644 --- a/interface/src/ui/overlays/Overlay2D.cpp +++ b/interface/src/ui/overlays/Overlay2D.cpp @@ -1,8 +1,11 @@ // // Overlay2D.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/ui/overlays/Overlay2D.h b/interface/src/ui/overlays/Overlay2D.h index 3da8f8bca4..34028de89a 100644 --- a/interface/src/ui/overlays/Overlay2D.h +++ b/interface/src/ui/overlays/Overlay2D.h @@ -1,12 +1,15 @@ // // Overlay2D.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Overlay2D__ -#define __interface__Overlay2D__ +#ifndef hifi_Overlay2D_h +#define hifi_Overlay2D_h // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -48,4 +51,4 @@ protected: }; -#endif /* defined(__interface__Overlay2D__) */ +#endif // hifi_Overlay2D_h diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 0c9415fa73..4eb4f030ac 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -1,8 +1,11 @@ // // Overlays.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 // diff --git a/interface/src/ui/overlays/Overlays.h b/interface/src/ui/overlays/Overlays.h index c28f3ab83b..b3477be0c2 100644 --- a/interface/src/ui/overlays/Overlays.h +++ b/interface/src/ui/overlays/Overlays.h @@ -1,12 +1,15 @@ // // Overlays.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Overlays__ -#define __interface__Overlays__ +#ifndef hifi_Overlays_h +#define hifi_Overlays_h #include @@ -45,4 +48,4 @@ private: }; -#endif /* defined(__interface__Overlays__) */ +#endif // hifi_Overlays_h diff --git a/interface/src/ui/overlays/Sphere3DOverlay.cpp b/interface/src/ui/overlays/Sphere3DOverlay.cpp index 7fded5bedb..e5f31fa1be 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.cpp +++ b/interface/src/ui/overlays/Sphere3DOverlay.cpp @@ -1,8 +1,11 @@ // // Sphere3DOverlay.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/ui/overlays/Sphere3DOverlay.h b/interface/src/ui/overlays/Sphere3DOverlay.h index 58ed0d7776..9a2816611b 100644 --- a/interface/src/ui/overlays/Sphere3DOverlay.h +++ b/interface/src/ui/overlays/Sphere3DOverlay.h @@ -1,12 +1,15 @@ // // Sphere3DOverlay.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Sphere3DOverlay__ -#define __interface__Sphere3DOverlay__ +#ifndef hifi_Sphere3DOverlay_h +#define hifi_Sphere3DOverlay_h #include "Volume3DOverlay.h" @@ -20,4 +23,4 @@ public: }; -#endif /* defined(__interface__Sphere3DOverlay__) */ +#endif // hifi_Sphere3DOverlay_h diff --git a/interface/src/ui/overlays/TextOverlay.cpp b/interface/src/ui/overlays/TextOverlay.cpp index 1a6edb3ea2..e26c772b06 100644 --- a/interface/src/ui/overlays/TextOverlay.cpp +++ b/interface/src/ui/overlays/TextOverlay.cpp @@ -1,8 +1,11 @@ // // TextOverlay.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/ui/overlays/TextOverlay.h b/interface/src/ui/overlays/TextOverlay.h index d565aeb70d..fc04966d07 100644 --- a/interface/src/ui/overlays/TextOverlay.h +++ b/interface/src/ui/overlays/TextOverlay.h @@ -1,12 +1,15 @@ // // TextOverlay.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__TextOverlay__ -#define __interface__TextOverlay__ +#ifndef hifi_TextOverlay_h +#define hifi_TextOverlay_h // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -56,4 +59,4 @@ private: }; -#endif /* defined(__interface__TextOverlay__) */ +#endif // hifi_TextOverlay_h diff --git a/interface/src/ui/overlays/Volume3DOverlay.cpp b/interface/src/ui/overlays/Volume3DOverlay.cpp index dbc1582cc5..ee780d8329 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.cpp +++ b/interface/src/ui/overlays/Volume3DOverlay.cpp @@ -1,8 +1,11 @@ // // Volume3DOverlay.cpp -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/ui/overlays/Volume3DOverlay.h b/interface/src/ui/overlays/Volume3DOverlay.h index 8badbf2c33..b06aea27fd 100644 --- a/interface/src/ui/overlays/Volume3DOverlay.h +++ b/interface/src/ui/overlays/Volume3DOverlay.h @@ -1,12 +1,15 @@ // // Volume3DOverlay.h -// interface +// interface/src/ui/overlays // -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__Volume3DOverlay__ -#define __interface__Volume3DOverlay__ +#ifndef hifi_Volume3DOverlay_h +#define hifi_Volume3DOverlay_h // include this before QGLWidget, which includes an earlier version of OpenGL #include "InterfaceConfig.h" @@ -39,4 +42,4 @@ protected: }; -#endif /* defined(__interface__Volume3DOverlay__) */ +#endif // hifi_Volume3DOverlay_h diff --git a/interface/src/voxels/PrimitiveRenderer.cpp b/interface/src/voxels/PrimitiveRenderer.cpp index 7f3607e363..a212245289 100644 --- a/interface/src/voxels/PrimitiveRenderer.cpp +++ b/interface/src/voxels/PrimitiveRenderer.cpp @@ -1,10 +1,12 @@ -/// -/// @file PrimitiveRenderer.cpp -/// A geometric primitive renderer. -/// -/// @author: Norman Crafts -/// @copyright 2014, High Fidelity, Inc. All rights reserved. -/// +// +// PrimitiveRenderer.cpp +// interface/src/voxels +// +// 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 @@ -737,4 +739,4 @@ unsigned long PrimitiveRenderer::vGetMemoryUsage() { unsigned long PrimitiveRenderer::vGetMemoryUsageGPU() { return _gpuMemoryUsage; -} \ No newline at end of file +} diff --git a/interface/src/voxels/PrimitiveRenderer.h b/interface/src/voxels/PrimitiveRenderer.h index 51d96e0840..06dac119b0 100644 --- a/interface/src/voxels/PrimitiveRenderer.h +++ b/interface/src/voxels/PrimitiveRenderer.h @@ -1,13 +1,16 @@ -/// -/// @file PrimitiveRenderer.h -/// A geometric primitive renderer. -/// -/// @author: Norman Crafts -/// @copyright 2014, High Fidelity, Inc. All rights reserved. -/// +// +// PrimitiveRenderer.h +// interface/src/voxels +// +// Created by Norman Craft. +// 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 __interface__PrimitiveRenderer__ -#define __interface__PrimitiveRenderer__ +#ifndef hifi_PrimitiveRenderer_h +#define hifi_PrimitiveRenderer_h #include #include @@ -103,7 +106,7 @@ private: /// Copy constructor prohibited. /// Primitive( - const Primitive& prim + const Primitive& copy ); // SPI methods are defined here @@ -150,14 +153,14 @@ public: /// Configuration dependency injection constructor. /// Cube( - float x, - float y, - float z, - float s, - unsigned char r, - unsigned char g, - unsigned char b, - unsigned char faces + float x, ///< Cube location on X-axis + float y, ///< Cube location on Y-axis + float z, ///< Cube location on Z-axis + float s, ///< Cube size + unsigned char r, ///< Cube red color component + unsigned char g, ///< Cube green color component + unsigned char b, ///< Cube blue color component + unsigned char faces ///< Bitmask of faces of cube excluded from construction ); ~Cube(); @@ -169,36 +172,48 @@ private: const Cube& cube ); + /// Cube initialization + /// void init( - float x, - float y, - float z, - float s, - unsigned char r, - unsigned char g, - unsigned char b, - unsigned char faceExclusions + float x, ///< Cube location on X-axis + float y, ///< Cube location on Y-axis + float z, ///< Cube location on Z-axis + float s, ///< Cube size + unsigned char r, ///< Cube red color component + unsigned char g, ///< Cube green color component + unsigned char b, ///< Cube blue color component + unsigned char faceExclusions ///< Bitmask of faces of cube excluded from construction ); + /// Cube termination + /// void terminate(); + /// Initialize cube's vertex list + /// void initializeVertices( - float x, - float y, - float z, - float s, - unsigned char r, - unsigned char g, - unsigned char b, - unsigned char faceExclusions + float x, ///< Cube location on X-axis + float y, ///< Cube location on Y-axis + float z, ///< Cube location on Z-axis + float s, ///< Cube size + unsigned char r, ///< Cube red color component + unsigned char g, ///< Cube green color component + unsigned char b, ///< Cube blue color component + unsigned char faceExclusions ///< Bitmask of faces of cube excluded from construction ); + /// Terminate cube's vertex list + /// void terminateVertices(); + /// Initialize cube's triangle list + /// void initializeTris( unsigned char faceExclusions ); + /// Terminate cube's triangle list + /// void terminateTris(); // SPI virtual override methods go here @@ -216,11 +231,11 @@ private: unsigned long _cpuMemoryUsage; ///< Memory allocation of object - static const int _sNumFacesPerCube = 6; - static const int _sNumVerticesPerCube = 24; - static unsigned char _sFaceIndexToHalfSpaceMask[6]; - static float _sVertexIndexToConstructionVector[24][3]; - static float _sVertexIndexToNormalVector[6][3]; + static const int _sNumFacesPerCube = 6; ///< Number of faces per cube + static const int _sNumVerticesPerCube = 24; ///< Number of vertices per cube + static unsigned char _sFaceIndexToHalfSpaceMask[6]; ///< index to bitmask map + static float _sVertexIndexToConstructionVector[24][3]; ///< Vertex index to construction vector map + static float _sVertexIndexToNormalVector[6][3]; ///< Vertex index to normal vector map }; @@ -239,13 +254,13 @@ public: /// Add primitive to renderer database. /// int add( - Primitive* primitive ///< Pointer to primitive + Primitive* primitive ///< Primitive instance to be added ); /// Remove primitive from renderer database. /// void remove( - int id ///< Primitive id + int id ///< Primitive id to be removed ); /// Clear all primitives from renderer database @@ -275,7 +290,7 @@ private: /// Copy constructor prohibited. /// Renderer( - const Renderer& primitive + const Renderer& copy ); // SPI methods are defined here @@ -283,10 +298,10 @@ private: /// Add primitive to renderer database. /// Service implementer to provide private override for this method /// in derived class - /// @return primitive id + /// @return Primitive id /// virtual int vAdd( - Primitive* primitive ///< Pointer to primitive + Primitive* primitive ///< Primitive instance to be added ) = 0; /// Remove primitive from renderer database. @@ -294,7 +309,7 @@ private: /// in derived class /// virtual void vRemove( - int id ///< Primitive id + int id ///< Primitive id ) = 0; /// Clear all primitives from renderer database @@ -329,7 +344,7 @@ public: /// Configuration dependency injection constructor. /// PrimitiveRenderer( - int maxCount + int maxCount ///< Max count ); ~PrimitiveRenderer(); @@ -362,39 +377,39 @@ private: /// Construct the elements of the faces of the primitive. /// void constructElements( - Primitive* primitive + Primitive* primitive ///< Primitive instance ); /// Deconstruct the elements of the faces of the primitive. /// void deconstructElements( - Primitive* primitive + Primitive* primitive ///< Primitive instance ); /// Deconstruct the triangle element from the GL buffer. /// void deconstructTriElement( - int idx + int idx ///< Triangle element index ); /// Deconstruct the vertex element from the GL buffer. /// void deconstructVertexElement( - int idx + int idx ///< Vertex element index ); /// Transfer the vertex element to the GL buffer. /// void transferVertexElement( - int idx, - VertexElement *vertex + int idx, ///< Vertex element index + VertexElement *vertex ///< Vertex element instance ); /// Transfer the triangle element to the GL buffer. /// void transferTriElement( - int idx, - int tri[3] + int idx, ///< Triangle element index + int tri[3] ///< Triangle element data ); /// Get available primitive index. @@ -421,13 +436,13 @@ private: /// Add primitive to renderer database. /// int vAdd( - Primitive* primitive + Primitive* primitive ///< Primitive instance to be added ); /// Remove primitive from renderer database. /// void vRemove( - int id + int id ///< Primitive id to be removed ); /// Clear all primitives from renderer database @@ -448,7 +463,7 @@ private: private: - int _maxCount; + int _maxCount; ///< Maximum count of tris // GL related parameters @@ -476,8 +491,8 @@ private: // Statistics parameters, not necessary for proper operation - unsigned long _gpuMemoryUsage; - unsigned long _cpuMemoryUsage; + unsigned long _gpuMemoryUsage; ///< GPU memory used by this instance + unsigned long _cpuMemoryUsage; ///< CPU memory used by this instance static const int _sIndicesPerTri = 3; @@ -485,4 +500,4 @@ private: }; -#endif +#endif // hifi_PrimitiveRenderer_h diff --git a/interface/src/voxels/VoxelFade.cpp b/interface/src/voxels/VoxelFade.cpp index 8ce68c9724..c720717d7c 100644 --- a/interface/src/voxels/VoxelFade.cpp +++ b/interface/src/voxels/VoxelFade.cpp @@ -1,9 +1,12 @@ // // VoxelFade.cpp -// interface +// interface/src/voxels // // Created by Brad Hefta-Gaub on 8/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 "InterfaceConfig.h" diff --git a/interface/src/voxels/VoxelFade.h b/interface/src/voxels/VoxelFade.h index fd7a73135b..1843fa2d67 100644 --- a/interface/src/voxels/VoxelFade.h +++ b/interface/src/voxels/VoxelFade.h @@ -1,13 +1,16 @@ // // VoxelFade.h -// interface +// interface/src/voxels // // Created by Brad Hefta-Gaub on 8/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__VoxelFade__ -#define __interface__VoxelFade__ +#ifndef hifi_VoxelFade_h +#define hifi_VoxelFade_h #include // for VoxelPositionSize @@ -40,4 +43,4 @@ public: bool isDone() const; }; -#endif // __interface__VoxelFade__ +#endif // hifi_VoxelFade_h diff --git a/interface/src/voxels/VoxelHideShowThread.cpp b/interface/src/voxels/VoxelHideShowThread.cpp index d7a25b4b6d..089da0d1da 100644 --- a/interface/src/voxels/VoxelHideShowThread.cpp +++ b/interface/src/voxels/VoxelHideShowThread.cpp @@ -1,11 +1,12 @@ // // VoxelHideShowThread.cpp -// interface +// interface/src/voxels // -// Created by Brad Hefta-Gaub on 12/1/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/1/13. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded interface thread for hiding and showing voxels in the local tree. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/interface/src/voxels/VoxelHideShowThread.h b/interface/src/voxels/VoxelHideShowThread.h index dc1f2062c1..6aa3c108ba 100644 --- a/interface/src/voxels/VoxelHideShowThread.h +++ b/interface/src/voxels/VoxelHideShowThread.h @@ -1,15 +1,18 @@ // // VoxelHideShowThread.h -// voxel-server +// interface/src/voxels // -// Created by Brad Hefta-Gaub on 12/1/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/1/13. +// Copyright 2013 High Fidelity, Inc. // // Threaded or non-threaded voxel persistence // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef __interface__VoxelHideShowThread__ -#define __interface__VoxelHideShowThread__ +#ifndef hifi_VoxelHideShowThread_h +#define hifi_VoxelHideShowThread_h #include #include "VoxelSystem.h" @@ -29,4 +32,4 @@ private: VoxelSystem* _theSystem; }; -#endif // __interface__VoxelHideShowThread__ +#endif // hifi_VoxelHideShowThread_h diff --git a/interface/src/voxels/VoxelImporter.cpp b/interface/src/voxels/VoxelImporter.cpp index d3c1b259ae..f7d5562c06 100644 --- a/interface/src/voxels/VoxelImporter.cpp +++ b/interface/src/voxels/VoxelImporter.cpp @@ -1,9 +1,12 @@ // // VoxelImporter.cpp -// hifi +// interface/src/voxels // // Created by Clement Brisset on 8/9/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 this before QGLWidget, which includes an earlier version of OpenGL diff --git a/interface/src/voxels/VoxelImporter.h b/interface/src/voxels/VoxelImporter.h index 9ebfc2eef2..7da89c5a11 100644 --- a/interface/src/voxels/VoxelImporter.h +++ b/interface/src/voxels/VoxelImporter.h @@ -1,13 +1,16 @@ // // VoxelImporter.h -// hifi +// interface/src/voxels // // Created by Clement Brisset on 8/9/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__VoxelImporter__ -#define __hifi__VoxelImporter__ +#ifndef hifi_VoxelImporter_h +#define hifi_VoxelImporter_h #include #include @@ -44,4 +47,4 @@ private: void cleanupTask(); }; -#endif /* defined(__hifi__VoxelImporter__) */ +#endif // hifi_VoxelImporter_h diff --git a/interface/src/voxels/VoxelPacketProcessor.cpp b/interface/src/voxels/VoxelPacketProcessor.cpp index 142285664e..d9d001b644 100644 --- a/interface/src/voxels/VoxelPacketProcessor.cpp +++ b/interface/src/voxels/VoxelPacketProcessor.cpp @@ -1,11 +1,12 @@ // // VoxelPacketProcessor.cpp -// interface +// interface/src/voxels // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded voxel packet receiver for the Application +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/interface/src/voxels/VoxelPacketProcessor.h b/interface/src/voxels/VoxelPacketProcessor.h index 2acd347e99..36456c5cc2 100644 --- a/interface/src/voxels/VoxelPacketProcessor.h +++ b/interface/src/voxels/VoxelPacketProcessor.h @@ -1,15 +1,16 @@ // // VoxelPacketProcessor.h -// interface +// interface/src/voxels // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Voxel Packet Receiver +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef __shared__VoxelPacketProcessor__ -#define __shared__VoxelPacketProcessor__ +#ifndef hifi_VoxelPacketProcessor_h +#define hifi_VoxelPacketProcessor_h #include @@ -20,4 +21,4 @@ class VoxelPacketProcessor : public ReceivedPacketProcessor { protected: virtual void processPacket(const SharedNodePointer& sendingNode, const QByteArray& packet); }; -#endif // __shared__VoxelPacketProcessor__ +#endif // hifi_VoxelPacketProcessor_h diff --git a/interface/src/voxels/VoxelSystem.cpp b/interface/src/voxels/VoxelSystem.cpp index bb907c8a9a..9a54a08619 100644 --- a/interface/src/voxels/VoxelSystem.cpp +++ b/interface/src/voxels/VoxelSystem.cpp @@ -1,8 +1,12 @@ // // VoxelSystem.cpp +// interface/src/voxels // // Created by Philip on 12/31/12. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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 // @@ -113,7 +117,7 @@ void VoxelSystem::elementDeleted(OctreeElement* element) { forceRemoveNodeFromArrays(voxel); } else { if (Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings)) { - printf("VoxelSystem::elementDeleted() while _voxelsInWriteArrays==0, is that expected? \n"); + qDebug("VoxelSystem::elementDeleted() while _voxelsInWriteArrays==0, is that expected? "); } } } @@ -934,6 +938,8 @@ void VoxelSystem::copyWrittenDataSegmentToReadArrays(glBufferIndex segmentStart, } void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { + static unsigned int lockForReadAttempt = 0; + static unsigned int lockForWriteAttempt = 0; PerformanceWarning warn(Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings), "copyWrittenDataToReadArrays()"); @@ -942,7 +948,9 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { // time around, the only side effect is the VBOs won't be updated this frame const int WAIT_FOR_LOCK_IN_MS = 5; if (_readArraysLock.tryLockForWrite(WAIT_FOR_LOCK_IN_MS)) { + lockForWriteAttempt = 0; if (_writeArraysLock.tryLockForRead(WAIT_FOR_LOCK_IN_MS)) { + lockForReadAttempt = 0; if (_voxelsDirty && _voxelsUpdated) { if (fullVBOs) { copyWrittenDataToReadArraysFullVBOs(); @@ -952,11 +960,19 @@ void VoxelSystem::copyWrittenDataToReadArrays(bool fullVBOs) { } _writeArraysLock.unlock(); } else { - qDebug() << "couldn't get _writeArraysLock.LockForRead()..."; + lockForReadAttempt++; + // only report error of first failure + if (lockForReadAttempt == 1) { + qDebug() << "couldn't get _writeArraysLock.LockForRead()..."; + } } _readArraysLock.unlock(); } else { - qDebug() << "couldn't get _readArraysLock.LockForWrite()..."; + lockForWriteAttempt++; + // only report error of first failure + if (lockForWriteAttempt == 1) { + qDebug() << "couldn't get _readArraysLock.LockForWrite()..."; + } } } @@ -1679,11 +1695,12 @@ bool VoxelSystem::inspectForExteriorOcclusionsOperation(OctreeElement* element, //qDebug("Completely occupied voxel at %f %f %f size: %f", v.x, v.y, v.z, s); - // TODO: All of the exterior faces of this voxel element are - // occluders, which means that this element is completely - // occupied. Hence, the subtree from this node could be - // pruned and replaced by a leaf voxel, if the visible - // properties of the children are the same + // All of the exterior faces of this voxel element are + // occluders, which means that this element is completely + // occupied. Hence, the subtree from this node could be + // pruned and replaced by a leaf voxel, if the visible + // properties of the children are the same + } else if (exteriorOcclusions != OctreeElement::HalfSpace::None) { //const glm::vec3& v = voxel->getCorner(); //float s = voxel->getScale(); diff --git a/interface/src/voxels/VoxelSystem.h b/interface/src/voxels/VoxelSystem.h index bdc55450dd..b134fe1539 100644 --- a/interface/src/voxels/VoxelSystem.h +++ b/interface/src/voxels/VoxelSystem.h @@ -1,13 +1,16 @@ // // VoxelSystem.h -// interface +// interface/src/voxels // // Created by Philip on 12/31/12. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. +// Copyright 2012 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 __interface__VoxelSystem__ -#define __interface__VoxelSystem__ +#ifndef hifi_VoxelSystem_h +#define hifi_VoxelSystem_h #include "InterfaceConfig.h" #include @@ -271,4 +274,4 @@ private: }; -#endif +#endif // hifi_VoxelSystem_h diff --git a/interface/src/windowshacks.h b/interface/src/windowshacks.h index aae7b2f923..fcd2f5a7f2 100644 --- a/interface/src/windowshacks.h +++ b/interface/src/windowshacks.h @@ -1,15 +1,18 @@ // // windowshacks.h -// hifi +// interface/src // // Created by Brad Hefta-Gaub on 1/12/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // hacks to get windows to compile // +// 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__windowshacks__ -#define __hifi__windowshacks__ +#ifndef hifi_windowshacks_h +#define hifi_windowshacks_h #ifdef WIN32 #undef NOMINMAX @@ -54,4 +57,4 @@ inline int c99_snprintf(char* str, size_t size, const char* format, ...) { #endif // WIN32 -#endif // __hifi__windowshacks__ \ No newline at end of file +#endif // hifi_windowshacks_h diff --git a/interface/src/world.h b/interface/src/world.h index bc8dab9106..8e680f3d95 100644 --- a/interface/src/world.h +++ b/interface/src/world.h @@ -1,15 +1,18 @@ // // world.h -// interface +// interface/src // // Created by Philip Rosedale on 8/23/12. -// Copyright (c) 2012 High Fidelity, Inc. All rights reserved. -// +// Copyright 2012 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 __interface__world__ -#define __interface__world__ +#ifndef hifi_world_h +#define hifi_world_h const float GRAVITY_EARTH = 9.80665f; const float EDGE_SIZE_GROUND_PLANE = 20.f; -#endif +#endif // hifi_world_h diff --git a/interface/ui/chatWindow.ui b/interface/ui/chatWindow.ui index 60a0c6badd..0372e00c09 100644 --- a/interface/ui/chatWindow.ui +++ b/interface/ui/chatWindow.ui @@ -20,7 +20,7 @@ font-family: Helvetica, Arial, sans-serif; - QDockWidget::NoDockWidgetFeatures + QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable Qt::NoDockWidgetArea @@ -80,7 +80,7 @@ - + 0 @@ -100,8 +100,57 @@ - - :/images/close.svg:/images/close.svg + + ../resources/images/pin.svg + ../resources/images/pinned.svg../resources/images/pin.svg + + + true + + + true + + + false + + + true + + + + + + + + 0 + 0 + + + + + 16 + 16 + + + + Qt::NoFocus + + + QPushButton { + background-color: rgba( 0, 0, 0, 0% ); + border: none; + image: url(../resources/images/close.svg) +} + + +QPushButton:pressed { + background-color: rgba( 0, 0, 0, 0% ); + border: none; + image: url(../resources/images/close_down.svg) +} + + + true @@ -200,10 +249,24 @@ messagePlainTextEdit messagesScrollArea - - - + + + togglePinnedButton + clicked() + ChatWindow + togglePinned() + + + 390 + 42 + + + 550 + 42 + + + closeButton clicked() diff --git a/interface/ui/preferencesDialog.ui b/interface/ui/preferencesDialog.ui new file mode 100644 index 0000000000..278e3c5dab --- /dev/null +++ b/interface/ui/preferencesDialog.ui @@ -0,0 +1,1514 @@ + + + PreferencesDialog + + + + 0 + 0 + 638 + 652 + + + + + 0 + 0 + + + + + 610 + 0 + + + + 0.950000000000000 + + + + + 0 + 560 + 611 + 97 + + + + + 0 + 0 + + + + + 0 + 97 + + + + + Arial + + + + background-color: #0e7077 + + + + + 310 + 24 + 91 + 50 + + + + + 0 + 0 + + + + + 0 + 50 + + + + + Arial + 18 + 50 + false + + + + + + + Cancel + + + + + + 400 + 24 + 188 + 50 + + + + + 0 + 0 + + + + + 188 + 49 + + + + + Arial + 18 + + + + background-color: #fff; +color: #0e7077 + + + Save all changes + + + true + + + + + + + 0 + 30 + 615 + 491 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + true + + + + + 0 + 0 + 615 + 936 + + + + + 0 + + + 30 + + + 0 + + + 30 + + + 30 + + + + + + 0 + 0 + + + + + Arial + 24 + + + + color: #0e7077 + + + Avatar + + + 25 + + + + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + Arial + 16 + + + + color: #0e7077 + + + <html><head/><body><p>Avatar display name <span style=" color:#909090;">(optional)</span></p></body></html> + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + displayNameEdit + + + + + + + + 0 + 0 + + + + + 280 + 0 + + + + + Arial + + + + Qt::LeftToRight + + + + + + Not showing a name + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + Arial + 16 + + + + color: #0e7077 + + + Head + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + 0 + + + snapshotLocationEdit + + + + + + + + + + 0 + 0 + + + + + Arial + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + 30 + 30 + + + + + + + + + + + 30 + 30 + + + + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + Arial + 16 + + + + color: #0e7077 + + + Body + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + skeletonURLEdit + + + + + + + + + + 0 + 0 + + + + + Arial + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + 30 + 30 + + + + + + + + + + + 30 + 30 + + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + Snapshots + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + 0 + 0 + + + + + 0 + 30 + + + + + Arial + 16 + + + + color: #0e7077 + + + Place my Snapshots here: + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + 0 + + + snapshotLocationEdit + + + + + + + + + + 0 + 0 + + + + + Arial + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 0 + 0 + + + + + 30 + 30 + + + + + 30 + 30 + + + + + + + + + + + 30 + 30 + + + + + + + + + + Qt::Vertical + + + + 0 + 35 + + + + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + Arial + 24 + + + + color: #0e7077 + + + Advanced Tuning + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 25 + + + + + + + + 0 + 0 + + + + + Arial + 16 + + + + true + + + color: rgb(51, 51, 51); + + + It's not recomended that you play with these settings unless you've looked into exactly what they do. + + + false + + + true + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + Avatar + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + + + + Vertical field of view + + + 15 + + + fieldOfViewSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 0 + 0 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 95 + 36 + + + + + Arial + + + + 1 + + + 180 + + + + + + + + + 0 + + + 10 + + + 10 + + + + + + 0 + 0 + + + + + 0 + 0 + + + + + 16777215 + 25 + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Lean scale (applies to Faceshift users) + + + 15 + + + leanScaleSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + QSizePolicy::Expanding + + + + 40 + 10 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Avatar scale <span style=" color:#909090;">(default is 1.0)</span> + + + 15 + + + avatarScaleSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Pupil dillation + + + 15 + + + pupilDilationSlider + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 0 + + + + + Arial + + + + Qt::Horizontal + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Audio Jitter Buffer Samples (0 for automatic) + + + 15 + + + audioJitterSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + -10000 + + + 10000 + + + 1 + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Faceshift eye detection + + + 15 + + + faceshiftEyeDeflectionSider + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 0 + + + + + Arial + + + + Qt::Horizontal + + + + + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + Arial + 20 + 50 + false + + + + color: #0e7077 + + + Voxels + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + color: rgb(51, 51, 51) + + + Maximum voxels + + + 15 + + + maxVoxelsSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 125 + 36 + + + + + Arial + + + + 5000000 + + + 50000 + + + + + + + + + 0 + + + 10 + + + 0 + + + 10 + + + + + + Arial + + + + Max voxels sent each second + + + 15 + + + maxVoxelsPPSSpin + + + + + + + + Arial + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 95 + 36 + + + + + 70 + 16777215 + + + + + Arial + + + + 60 + + + 6000 + + + 10 + + + + + + + + + + + + 540 + 24 + 31 + 31 + + + + + PreferAntialias + + + + + + + + + + FramelessDialog + 1 + + + + + + cancelButton + clicked() + PreferencesDialog + close() + + + 495 + 749 + + + 528 + 0 + + + + + defaultButton + clicked() + PreferencesDialog + accept() + + + 504 + 749 + + + 20 + 20 + + + + + diff --git a/interface/ui/runningScriptsWidget.ui b/interface/ui/runningScriptsWidget.ui index e79496afb8..b8c690f16e 100644 --- a/interface/ui/runningScriptsWidget.ui +++ b/interface/ui/runningScriptsWidget.ui @@ -88,10 +88,6 @@ padding-top: 3px; Reload all - - - :/images/reload.svg:/images/reload.svg - @@ -115,10 +111,6 @@ padding-top: 3px; Stop all - - - :/images/stop.svg:/images/stop.svg - @@ -172,10 +164,6 @@ font-size: 14pt; - - - :/images/close.svg:/images/close.svg - 16 @@ -244,8 +232,6 @@ font-size: 14pt; reloadAllButton stopAllButton - - - + diff --git a/interface/ui/scriptEditorWidget.ui b/interface/ui/scriptEditorWidget.ui new file mode 100644 index 0000000000..88761c91c5 --- /dev/null +++ b/interface/ui/scriptEditorWidget.ui @@ -0,0 +1,148 @@ + + + ScriptEditorWidget + + + + 0 + 0 + 691 + 549 + + + + + 0 + 0 + + + + + 541 + 238 + + + + font-family: Helvetica, Arial, sans-serif; + + + QDockWidget::DockWidgetFloatable|QDockWidget::DockWidgetMovable + + + Qt::NoDockWidgetArea + + + Edit Script + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + Courier + 9 + 50 + false + false + + + + font: 9pt "Courier"; + + + + + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + Debug Log: + + + + + + + Run on the fly (Careful: Any valid change made to the code will run immediately) + + + + + + + Clear + + + + 16 + 16 + + + + + + + + + + font: 8pt "Courier"; + + + true + + + + + + + + + + clearButton + clicked() + debugText + clear() + + + 663 + 447 + + + 350 + 501 + + + + + diff --git a/interface/ui/scriptEditorWindow.ui b/interface/ui/scriptEditorWindow.ui new file mode 100644 index 0000000000..9e1b08de3e --- /dev/null +++ b/interface/ui/scriptEditorWindow.ui @@ -0,0 +1,331 @@ + + + ScriptEditorWindow + + + Qt::WindowModal + + + + 0 + 0 + 706 + 682 + + + + + 400 + 250 + + + + Script Editor + + + font-family: Helvetica, Arial, sans-serif; + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 3 + + + QLayout::SetNoConstraint + + + 0 + + + 0 + + + + + New Script (Ctrl+N) + + + New + + + + ../resources/icons/new-script.svg + ../resources/icons/new-script.svg../resources/icons/new-script.svg + + + + 32 + 32 + + + + + + + + + 30 + 0 + + + + + 25 + 0 + + + + Load Script (Ctrl+O) + + + Load + + + + ../resources/icons/load-script.svg../resources/icons/load-script.svg + + + + 32 + 32 + + + + false + + + QToolButton::MenuButtonPopup + + + Qt::ToolButtonIconOnly + + + + + + + + 30 + 0 + + + + + 32 + 0 + + + + Qt::NoFocus + + + Qt::NoContextMenu + + + Save Script (Ctrl+S) + + + Save + + + + ../resources/icons/save-script.svg../resources/icons/save-script.svg + + + + 32 + 32 + + + + 316 + + + QToolButton::MenuButtonPopup + + + + + + + Toggle Run Script (F5) + + + Run/Stop + + + + ../resources/icons/start-script.svg../resources/icons/start-script.svg + + + + 32 + 32 + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + true + + + + 250 + 80 + + + + QTabWidget::West + + + QTabWidget::Triangular + + + -1 + + + Qt::ElideNone + + + true + + + true + + + + + + + + + saveButton + clicked() + ScriptEditorWindow + saveScriptClicked() + + + 236 + 10 + + + 199 + 264 + + + + + toggleRunButton + clicked() + ScriptEditorWindow + toggleRunScriptClicked() + + + 330 + 10 + + + 199 + 264 + + + + + newButton + clicked() + ScriptEditorWindow + newScriptClicked() + + + 58 + 10 + + + 199 + 264 + + + + + loadButton + clicked() + ScriptEditorWindow + loadScriptClicked() + + + 85 + 10 + + + 199 + 264 + + + + + tabWidget + currentChanged(int) + ScriptEditorWindow + tabSwitched(int) + + + 352 + 360 + + + 352 + 340 + + + + + tabWidget + tabCloseRequested(int) + ScriptEditorWindow + tabCloseRequested(int) + + + 352 + 360 + + + 352 + 340 + + + + + diff --git a/libraries/audio/CMakeLists.txt b/libraries/audio/CMakeLists.txt index 8d2feb22a2..d946ae5b34 100644 --- a/libraries/audio/CMakeLists.txt +++ b/libraries/audio/CMakeLists.txt @@ -20,4 +20,16 @@ include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) -link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") \ No newline at end of file +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 +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/audio/src/AbstractAudioInterface.h b/libraries/audio/src/AbstractAudioInterface.h index dc3872efd5..f950eefcbd 100644 --- a/libraries/audio/src/AbstractAudioInterface.h +++ b/libraries/audio/src/AbstractAudioInterface.h @@ -1,14 +1,16 @@ // // AbstractAudioInterface.h -// hifi +// libraries/audio/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__AbstractAudioInterface__ -#define __hifi__AbstractAudioInterface__ +#ifndef hifi_AbstractAudioInterface_h +#define hifi_AbstractAudioInterface_h #include @@ -25,4 +27,4 @@ public slots: Q_DECLARE_METATYPE(AbstractAudioInterface*) -#endif /* defined(__hifi__AbstractAudioInterface__) */ \ No newline at end of file +#endif // hifi_AbstractAudioInterface_h diff --git a/libraries/audio/src/AudioInjector.cpp b/libraries/audio/src/AudioInjector.cpp index 7f0e5c7f1a..eed41ac849 100644 --- a/libraries/audio/src/AudioInjector.cpp +++ b/libraries/audio/src/AudioInjector.cpp @@ -1,9 +1,12 @@ // // AudioInjector.cpp -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 1/2/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 @@ -68,8 +71,8 @@ void AudioInjector::injectAudio() { quint8 volume = MAX_INJECTOR_VOLUME * _options.getVolume(); packetStream << volume; - timeval startTime = {}; - gettimeofday(&startTime, NULL); + QElapsedTimer timer; + timer.start(); int nextFrame = 0; int currentSendPosition = 0; @@ -101,7 +104,7 @@ void AudioInjector::injectAudio() { if (currentSendPosition != bytesToCopy && currentSendPosition < soundByteArray.size()) { // not the first packet and not done // sleep for the appropriate time - int usecToSleep = usecTimestamp(&startTime) + (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - usecTimestampNow(); + int usecToSleep = (++nextFrame * BUFFER_SEND_INTERVAL_USECS) - timer.nsecsElapsed() / 1000; if (usecToSleep > 0) { usleep(usecToSleep); diff --git a/libraries/audio/src/AudioInjector.h b/libraries/audio/src/AudioInjector.h index beff33a9ef..abaa804fb0 100644 --- a/libraries/audio/src/AudioInjector.h +++ b/libraries/audio/src/AudioInjector.h @@ -1,13 +1,16 @@ // // AudioInjector.h -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 1/2/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__AudioInjector__ -#define __hifi__AudioInjector__ +#ifndef hifi_AudioInjector_h +#define hifi_AudioInjector_h #include #include @@ -31,4 +34,4 @@ signals: void finished(); }; -#endif /* defined(__hifi__AudioInjector__) */ +#endif // hifi_AudioInjector_h diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index 30e8c3e490..cd6b08f6c7 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -1,9 +1,12 @@ // // AudioInjectorOptions.cpp -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 1/2/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 "AudioInjectorOptions.h" @@ -23,4 +26,4 @@ AudioInjectorOptions::AudioInjectorOptions(const AudioInjectorOptions& other) { _volume = other._volume; _orientation = other._orientation; _loopbackAudioInterface = other._loopbackAudioInterface; -} \ No newline at end of file +} diff --git a/libraries/audio/src/AudioInjectorOptions.h b/libraries/audio/src/AudioInjectorOptions.h index 0c70ad43cb..bbe3d57b08 100644 --- a/libraries/audio/src/AudioInjectorOptions.h +++ b/libraries/audio/src/AudioInjectorOptions.h @@ -1,13 +1,16 @@ // // AudioInjectorOptions.h -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 1/2/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__AudioInjectorOptions__ -#define __hifi__AudioInjectorOptions__ +#ifndef hifi_AudioInjectorOptions_h +#define hifi_AudioInjectorOptions_h #include @@ -46,4 +49,4 @@ private: AbstractAudioInterface* _loopbackAudioInterface; }; -#endif /* defined(__hifi__AudioInjectorOptions__) */ +#endif // hifi_AudioInjectorOptions_h diff --git a/libraries/audio/src/AudioRingBuffer.cpp b/libraries/audio/src/AudioRingBuffer.cpp index 376b60ffa1..9b50ed0bcb 100644 --- a/libraries/audio/src/AudioRingBuffer.cpp +++ b/libraries/audio/src/AudioRingBuffer.cpp @@ -1,9 +1,12 @@ // // AudioRingBuffer.cpp -// interface +// libraries/audio/src // // Created by Stephen Birarda on 2/1/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -15,15 +18,19 @@ #include "AudioRingBuffer.h" -AudioRingBuffer::AudioRingBuffer(int numFrameSamples) : +AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) : NodeData(), _sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES), _numFrameSamples(numFrameSamples), _isStarved(true), - _hasStarted(false) + _hasStarted(false), + _randomAccessMode(randomAccessMode) { if (numFrameSamples) { _buffer = new int16_t[_sampleCapacity]; + if (_randomAccessMode) { + memset(_buffer, 0, _sampleCapacity * sizeof(int16_t)); + } _nextOutput = _buffer; _endOfLastWrite = _buffer; } else { @@ -47,6 +54,9 @@ void AudioRingBuffer::resizeForFrameSize(qint64 numFrameSamples) { delete[] _buffer; _sampleCapacity = numFrameSamples * RING_BUFFER_LENGTH_FRAMES; _buffer = new int16_t[_sampleCapacity]; + if (_randomAccessMode) { + memset(_buffer, 0, _sampleCapacity * sizeof(int16_t)); + } _nextOutput = _buffer; _endOfLastWrite = _buffer; } @@ -65,18 +75,34 @@ qint64 AudioRingBuffer::readData(char *data, qint64 maxSize) { // only copy up to the number of samples we have available int numReadSamples = std::min((unsigned) (maxSize / sizeof(int16_t)), samplesAvailable()); + // If we're in random access mode, then we consider our number of available read samples slightly + // differently. Namely, if anything has been written, we say we have as many samples as they ask for + // otherwise we say we have nothing available + if (_randomAccessMode) { + numReadSamples = _endOfLastWrite ? (maxSize / sizeof(int16_t)) : 0; + } + if (_nextOutput + numReadSamples > _buffer + _sampleCapacity) { // we're going to need to do two reads to get this data, it wraps around the edge // read to the end of the buffer int numSamplesToEnd = (_buffer + _sampleCapacity) - _nextOutput; memcpy(data, _nextOutput, numSamplesToEnd * sizeof(int16_t)); + if (_randomAccessMode) { + memset(_nextOutput, 0, numSamplesToEnd * sizeof(int16_t)); // clear it + } // read the rest from the beginning of the buffer memcpy(data + (numSamplesToEnd * sizeof(int16_t)), _buffer, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); + if (_randomAccessMode) { + memset(_buffer, 0, (numReadSamples - numSamplesToEnd) * sizeof(int16_t)); // clear it + } } else { // read the data memcpy(data, _nextOutput, numReadSamples * sizeof(int16_t)); + if (_randomAccessMode) { + memset(_nextOutput, 0, numReadSamples * sizeof(int16_t)); // clear it + } } // push the position of _nextOutput by the number of samples read @@ -125,6 +151,10 @@ int16_t& AudioRingBuffer::operator[](const int index) { return *shiftedPositionAccomodatingWrap(_nextOutput, index); } +const int16_t& AudioRingBuffer::operator[] (const int index) const { + return *shiftedPositionAccomodatingWrap(_nextOutput, index); +} + void AudioRingBuffer::shiftReadPosition(unsigned int numSamples) { _nextOutput = shiftedPositionAccomodatingWrap(_nextOutput, numSamples); } diff --git a/libraries/audio/src/AudioRingBuffer.h b/libraries/audio/src/AudioRingBuffer.h index e55eeda40e..04cc67c8ac 100644 --- a/libraries/audio/src/AudioRingBuffer.h +++ b/libraries/audio/src/AudioRingBuffer.h @@ -1,13 +1,16 @@ // // AudioRingBuffer.h -// interface +// libraries/audio/src // // Created by Stephen Birarda on 2/1/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__AudioRingBuffer__ -#define __interface__AudioRingBuffer__ +#ifndef hifi_AudioRingBuffer_h +#define hifi_AudioRingBuffer_h #include #include @@ -36,7 +39,7 @@ const int MIN_SAMPLE_VALUE = std::numeric_limits::min(); class AudioRingBuffer : public NodeData { Q_OBJECT public: - AudioRingBuffer(int numFrameSamples); + AudioRingBuffer(int numFrameSamples, bool randomAccessMode = false); ~AudioRingBuffer(); void reset(); @@ -47,8 +50,8 @@ public: int parseData(const QByteArray& packet); // assume callers using this will never wrap around the end - const int16_t* getNextOutput() { return _nextOutput; } - const int16_t* getBuffer() { return _buffer; } + const int16_t* getNextOutput() const { return _nextOutput; } + const int16_t* getBuffer() const { return _buffer; } qint64 readSamples(int16_t* destination, qint64 maxSamples); qint64 writeSamples(const int16_t* source, qint64 maxSamples); @@ -57,6 +60,7 @@ public: qint64 writeData(const char* data, qint64 maxSize); int16_t& operator[](const int index); + const int16_t& operator[] (const int index) const; void shiftReadPosition(unsigned int numSamples); @@ -84,6 +88,7 @@ protected: int16_t* _buffer; bool _isStarved; bool _hasStarted; + bool _randomAccessMode; /// will this ringbuffer be used for random access? if so, do some special processing }; -#endif /* defined(__interface__AudioRingBuffer__) */ +#endif // hifi_AudioRingBuffer_h diff --git a/libraries/audio/src/AudioScriptingInterface.cpp b/libraries/audio/src/AudioScriptingInterface.cpp index 1fe2c19922..0d76a42757 100644 --- a/libraries/audio/src/AudioScriptingInterface.cpp +++ b/libraries/audio/src/AudioScriptingInterface.cpp @@ -1,9 +1,12 @@ // // AudioScriptingInterface.cpp -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 1/2/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 "AudioScriptingInterface.h" diff --git a/libraries/audio/src/AudioScriptingInterface.h b/libraries/audio/src/AudioScriptingInterface.h index f758923513..f2e9b02e9a 100644 --- a/libraries/audio/src/AudioScriptingInterface.h +++ b/libraries/audio/src/AudioScriptingInterface.h @@ -1,13 +1,16 @@ // // AudioScriptingInterface.h -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 1/2/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__AudioScriptingInterface__ -#define __hifi__AudioScriptingInterface__ +#ifndef hifi_AudioScriptingInterface_h +#define hifi_AudioScriptingInterface_h #include "AudioInjector.h" #include "Sound.h" @@ -22,4 +25,4 @@ public slots: const AudioInjectorOptions* injectorOptions = NULL); }; -#endif /* defined(__hifi__AudioScriptingInterface__) */ +#endif // hifi_AudioScriptingInterface_h diff --git a/libraries/audio/src/InjectedAudioRingBuffer.cpp b/libraries/audio/src/InjectedAudioRingBuffer.cpp index f8205a846f..2658b4c336 100644 --- a/libraries/audio/src/InjectedAudioRingBuffer.cpp +++ b/libraries/audio/src/InjectedAudioRingBuffer.cpp @@ -1,9 +1,12 @@ // // InjectedAudioRingBuffer.cpp -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 6/5/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/audio/src/InjectedAudioRingBuffer.h b/libraries/audio/src/InjectedAudioRingBuffer.h index d4dfb5e360..fd766e2848 100644 --- a/libraries/audio/src/InjectedAudioRingBuffer.h +++ b/libraries/audio/src/InjectedAudioRingBuffer.h @@ -1,13 +1,16 @@ // // InjectedAudioRingBuffer.h -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 6/5/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__InjectedAudioRingBuffer__ -#define __hifi__InjectedAudioRingBuffer__ +#ifndef hifi_InjectedAudioRingBuffer_h +#define hifi_InjectedAudioRingBuffer_h #include @@ -32,4 +35,4 @@ private: float _attenuationRatio; }; -#endif /* defined(__hifi__InjectedAudioRingBuffer__) */ +#endif // hifi_InjectedAudioRingBuffer_h diff --git a/libraries/audio/src/MixedAudioRingBuffer.cpp b/libraries/audio/src/MixedAudioRingBuffer.cpp index 0ffab23519..c975d7b68e 100644 --- a/libraries/audio/src/MixedAudioRingBuffer.cpp +++ b/libraries/audio/src/MixedAudioRingBuffer.cpp @@ -1,9 +1,12 @@ // // MixedAudioRingBuffer.cpp -// hifi +// libraries/audio/src // -// Created by Stephen Birarda on 2014-03-26. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Created by Stephen Birarda on 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 // #include "MixedAudioRingBuffer.h" @@ -46,4 +49,4 @@ qint64 MixedAudioRingBuffer::readSamples(int16_t* destination, qint64 maxSamples _lastReadFrameAverageLoudness = averageLoudness; return AudioRingBuffer::readSamples(destination, maxSamples); -} \ No newline at end of file +} diff --git a/libraries/audio/src/MixedAudioRingBuffer.h b/libraries/audio/src/MixedAudioRingBuffer.h index c116361689..25574a3ea6 100644 --- a/libraries/audio/src/MixedAudioRingBuffer.h +++ b/libraries/audio/src/MixedAudioRingBuffer.h @@ -1,13 +1,16 @@ // // MixedAudioRingBuffer.h -// hifi +// libraries/audio/src // -// Created by Stephen Birarda on 2014-03-26. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Created by Stephen Birarda on 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 // -#ifndef __hifi__MixedAudioRingBuffer__ -#define __hifi__MixedAudioRingBuffer__ +#ifndef hifi_MixedAudioRingBuffer_h +#define hifi_MixedAudioRingBuffer_h #include "AudioRingBuffer.h" @@ -23,4 +26,4 @@ private: float _lastReadFrameAverageLoudness; }; -#endif /* defined(__hifi__MixedAudioRingBuffer__) */ +#endif // hifi_MixedAudioRingBuffer_h diff --git a/libraries/audio/src/PositionalAudioRingBuffer.cpp b/libraries/audio/src/PositionalAudioRingBuffer.cpp index 7bdedfc793..70da363267 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.cpp +++ b/libraries/audio/src/PositionalAudioRingBuffer.cpp @@ -1,9 +1,12 @@ // // PositionalAudioRingBuffer.cpp -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 6/5/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/audio/src/PositionalAudioRingBuffer.h b/libraries/audio/src/PositionalAudioRingBuffer.h index a82df0b857..b130a9b216 100644 --- a/libraries/audio/src/PositionalAudioRingBuffer.h +++ b/libraries/audio/src/PositionalAudioRingBuffer.h @@ -1,13 +1,16 @@ // // PositionalAudioRingBuffer.h -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 6/5/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__PositionalAudioRingBuffer__ -#define __hifi__PositionalAudioRingBuffer__ +#ifndef hifi_PositionalAudioRingBuffer_h +#define hifi_PositionalAudioRingBuffer_h #include #include @@ -57,4 +60,4 @@ protected: float _nextOutputTrailingLoudness; }; -#endif /* defined(__hifi__PositionalAudioRingBuffer__) */ +#endif // hifi_PositionalAudioRingBuffer_h diff --git a/libraries/audio/src/Sound.cpp b/libraries/audio/src/Sound.cpp index 91a47b7d2c..5bd63b7959 100644 --- a/libraries/audio/src/Sound.cpp +++ b/libraries/audio/src/Sound.cpp @@ -1,10 +1,12 @@ // // Sound.cpp -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 1/2/2014. -// Modified by Athanasios Gaitatzes to add WAVE file support. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 @@ -18,6 +20,7 @@ #include #include +#include #include #include "AudioRingBuffer.h" @@ -64,21 +67,25 @@ Sound::Sound(float volume, float frequency, float duration, float decay, QObject } Sound::Sound(const QUrl& sampleURL, QObject* parent) : - QObject(parent) + QObject(parent), + _hasDownloaded(false) { // assume we have a QApplication or QCoreApplication instance and use the // QNetworkAccess manager to grab the raw audio file at the given URL QNetworkAccessManager *manager = new QNetworkAccessManager(this); - connect(manager, SIGNAL(finished(QNetworkReply*)), - this, SLOT(replyFinished(QNetworkReply*))); qDebug() << "Requesting audio file" << sampleURL.toDisplayString(); - manager->get(QNetworkRequest(sampleURL)); + + QNetworkReply* soundDownload = manager->get(QNetworkRequest(sampleURL)); + connect(soundDownload, &QNetworkReply::finished, this, &Sound::replyFinished); + connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError))); } -void Sound::replyFinished(QNetworkReply* reply) { +void Sound::replyFinished() { + QNetworkReply* reply = reinterpret_cast(sender()); + // replace our byte array with the downloaded data QByteArray rawAudioByteArray = reply->readAll(); @@ -105,6 +112,13 @@ void Sound::replyFinished(QNetworkReply* reply) { } else { qDebug() << "Network reply without 'Content-Type'."; } + + _hasDownloaded = true; +} + +void Sound::replyError(QNetworkReply::NetworkError code) { + QNetworkReply* reply = reinterpret_cast(sender()); + qDebug() << "Error downloading sound file at" << reply->url().toString() << "-" << reply->errorString(); } void Sound::downSample(const QByteArray& rawAudioByteArray) { @@ -152,20 +166,17 @@ void Sound::downSample(const QByteArray& rawAudioByteArray) { // Sample values are given above for a 16-bit stereo source. // -struct chunk -{ +struct chunk { char id[4]; quint32 size; }; -struct RIFFHeader -{ +struct RIFFHeader { chunk descriptor; // "RIFF" char type[4]; // "WAVE" }; -struct WAVEHeader -{ +struct WAVEHeader { chunk descriptor; quint16 audioFormat; // Format type: 1=PCM, 257=Mu-Law, 258=A-Law, 259=ADPCM quint16 numChannels; // Number of channels: 1=mono, 2=stereo @@ -175,13 +186,11 @@ struct WAVEHeader quint16 bitsPerSample; }; -struct DATAHeader -{ +struct DATAHeader { chunk descriptor; }; -struct CombinedHeader -{ +struct CombinedHeader { RIFFHeader riff; WAVEHeader wave; }; diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 7956343d46..c473cdff83 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -1,35 +1,42 @@ // // Sound.h -// hifi +// libraries/audio/src // // Created by Stephen Birarda on 1/2/2014. -// Modified by Athanasios Gaitatzes to add WAVE file support. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__Sound__ -#define __hifi__Sound__ +#ifndef hifi_Sound_h +#define hifi_Sound_h #include - -class QNetworkReply; +#include class Sound : public QObject { Q_OBJECT + + Q_PROPERTY(bool downloaded READ hasDownloaded) public: Sound(const QUrl& sampleURL, QObject* parent = NULL); Sound(float volume, float frequency, float duration, float decay, QObject* parent = NULL); + bool hasDownloaded() const { return _hasDownloaded; } + const QByteArray& getByteArray() { return _byteArray; } private: QByteArray _byteArray; - + bool _hasDownloaded; + void downSample(const QByteArray& rawAudioByteArray); void interpretAsWav(const QByteArray& inputAudioByteArray, QByteArray& outputAudioByteArray); private slots: - void replyFinished(QNetworkReply* reply); + void replyFinished(); + void replyError(QNetworkReply::NetworkError code); }; -#endif /* defined(__hifi__Sound__) */ +#endif // hifi_Sound_h diff --git a/libraries/avatars/CMakeLists.txt b/libraries/avatars/CMakeLists.txt index 4cc97e56a5..3fa1ebcfe2 100644 --- a/libraries/avatars/CMakeLists.txt +++ b/libraries/avatars/CMakeLists.txt @@ -26,5 +26,15 @@ link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -target_link_libraries(${TARGET_NAME} Qt5::Script) \ No newline at end of file +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}") +target_link_libraries(${TARGET_NAME} Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 31639b6836..2e716296ff 100644 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -1,9 +1,12 @@ // // AvatarData.cpp -// hifi +// libraries/avatars/src // // Created by Stephen Birarda on 4/9/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -32,6 +35,7 @@ using namespace std; QNetworkAccessManager* AvatarData::networkAccessManager = NULL; AvatarData::AvatarData() : + _sessionUUID(), _handPosition(0,0,0), _bodyYaw(-90.f), _bodyPitch(0.0f), @@ -40,13 +44,16 @@ AvatarData::AvatarData() : _handState(0), _keyState(NO_KEY_DOWN), _isChatCirclingEnabled(false), + _hasNewJointRotations(true), _headData(NULL), _handData(NULL), _displayNameBoundingRect(), _displayNameTargetAlpha(0.0f), _displayNameAlpha(0.0f), _billboard(), - _errorLogExpiry(0) + _errorLogExpiry(0), + _owningAvatarMixer(), + _lastUpdateTimer() { } @@ -188,6 +195,10 @@ bool AvatarData::shouldLogError(const quint64& now) { // read data in packet starting at byte offset and return number of bytes parsed int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { + + // reset the last heard timer since we have new data for this AvatarData + _lastUpdateTimer.restart(); + // lazily allocate memory for HeadData in case we're not an Avatar instance if (!_headData) { _headData = new HeadData(this); @@ -483,6 +494,7 @@ int AvatarData::parseDataAtOffset(const QByteArray& packet, int offset) { } } } // numJoints * 8 bytes + _hasNewJointRotations = true; return sourceBuffer - startPosition; } @@ -637,6 +649,8 @@ void AvatarData::setSkeletonModelURL(const QUrl& skeletonModelURL) { _skeletonModelURL = skeletonModelURL.isEmpty() ? DEFAULT_BODY_MODEL_URL : skeletonModelURL; qDebug() << "Changing skeleton model for avatar to" << _skeletonModelURL.toString(); + + updateJointMappings(); } void AvatarData::setDisplayName(const QString& displayName) { @@ -671,6 +685,40 @@ void AvatarData::setBillboardFromURL(const QString &billboardURL) { void AvatarData::setBillboardFromNetworkReply() { QNetworkReply* networkReply = reinterpret_cast(sender()); setBillboard(networkReply->readAll()); + networkReply->deleteLater(); +} + +void AvatarData::setJointMappingsFromNetworkReply() { + QNetworkReply* networkReply = static_cast(sender()); + + QByteArray line; + while (!(line = networkReply->readLine()).isEmpty()) { + if (!(line = line.trimmed()).startsWith("jointIndex")) { + continue; + } + int jointNameIndex = line.indexOf('=') + 1; + if (jointNameIndex == 0) { + continue; + } + int secondSeparatorIndex = line.indexOf('=', jointNameIndex); + if (secondSeparatorIndex == -1) { + continue; + } + QString jointName = line.mid(jointNameIndex, secondSeparatorIndex - jointNameIndex).trimmed(); + bool ok; + int jointIndex = line.mid(secondSeparatorIndex + 1).trimmed().toInt(&ok); + if (ok) { + while (_jointNames.size() < jointIndex + 1) { + _jointNames.append(QString()); + } + _jointNames[jointIndex] = jointName; + } + } + for (int i = 0; i < _jointNames.size(); i++) { + _jointIndices.insert(_jointNames.at(i), i + 1); + } + + networkReply->deleteLater(); } void AvatarData::setClampedTargetScale(float targetScale) { @@ -703,3 +751,13 @@ void AvatarData::sendBillboardPacket() { NodeList::getInstance()->broadcastToNodes(billboardPacket, NodeSet() << NodeType::AvatarMixer); } } + +void AvatarData::updateJointMappings() { + _jointIndices.clear(); + _jointNames.clear(); + + if (networkAccessManager && _skeletonModelURL.fileName().toLower().endsWith(".fst")) { + QNetworkReply* networkReply = networkAccessManager->get(QNetworkRequest(_skeletonModelURL)); + connect(networkReply, SIGNAL(finished()), this, SLOT(setJointMappingsFromNetworkReply())); + } +} diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index 2ea20c1041..250ae07b01 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -1,13 +1,16 @@ // // AvatarData.h -// hifi +// libraries/avatars/src // // Created by Stephen Birarda on 4/9/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__AvatarData__ -#define __hifi__AvatarData__ +#ifndef hifi_AvatarData_h +#define hifi_AvatarData_h #include /* VS2010 defines stdint.h, but not inttypes.h */ @@ -29,16 +32,20 @@ typedef unsigned long long quint64; #include #include +#include #include +#include #include #include #include #include #include #include +#include #include #include +#include #include "HeadData.h" #include "HandData.h" @@ -90,13 +97,20 @@ class AvatarData : public QObject { Q_PROPERTY(float audioLoudness READ getAudioLoudness WRITE setAudioLoudness) Q_PROPERTY(float audioAverageLoudness READ getAudioAverageLoudness WRITE setAudioAverageLoudness) + Q_PROPERTY(QString displayName READ getDisplayName WRITE setDisplayName) Q_PROPERTY(QString faceModelURL READ getFaceModelURLFromScript WRITE setFaceModelURLFromScript) Q_PROPERTY(QString skeletonModelURL READ getSkeletonModelURLFromScript WRITE setSkeletonModelURLFromScript) Q_PROPERTY(QString billboardURL READ getBillboardURL WRITE setBillboardFromURL) + + Q_PROPERTY(QStringList jointNames READ getJointNames) + + Q_PROPERTY(QUuid sessionUUID READ getSessionUUID) public: AvatarData(); virtual ~AvatarData(); + const QUuid& getSessionUUID() { return _sessionUUID; } + const glm::vec3& getPosition() const { return _position; } void setPosition(const glm::vec3 position) { _position = position; } @@ -160,9 +174,9 @@ public: Q_INVOKABLE glm::quat getJointRotation(const QString& name) const; /// Returns the index of the joint with the specified name, or -1 if not found/unknown. - Q_INVOKABLE virtual int getJointIndex(const QString& name) const { return -1; } + Q_INVOKABLE virtual int getJointIndex(const QString& name) const { return _jointIndices.value(name) - 1; } - Q_INVOKABLE virtual QStringList getJointNames() const { return QStringList(); } + Q_INVOKABLE virtual QStringList getJointNames() const { return _jointNames; } // key state void setKeyState(KeyState s) { _keyState = s; } @@ -209,6 +223,11 @@ public: QString getSkeletonModelURLFromScript() const { return _skeletonModelURL.toString(); } void setSkeletonModelURLFromScript(const QString& skeletonModelString) { setSkeletonModelURL(QUrl(skeletonModelString)); } + Node* getOwningAvatarMixer() { return _owningAvatarMixer.data(); } + void setOwningAvatarMixer(const QWeakPointer& owningAvatarMixer) { _owningAvatarMixer = owningAvatarMixer; } + + QElapsedTimer& getLastUpdateTimer() { return _lastUpdateTimer; } + virtual float getBoundingRadius() const { return 1.f; } static void setNetworkAccessManager(QNetworkAccessManager* sharedAccessManager) { networkAccessManager = sharedAccessManager; } @@ -217,7 +236,10 @@ public slots: void sendIdentityPacket(); void sendBillboardPacket(); void setBillboardFromNetworkReply(); + void setJointMappingsFromNetworkReply(); + void setSessionUUID(const QUuid& id) { _sessionUUID = id; } protected: + QUuid _sessionUUID; glm::vec3 _position; glm::vec3 _handPosition; @@ -242,6 +264,8 @@ protected: bool _isChatCirclingEnabled; + bool _hasNewJointRotations; // set in AvatarData, cleared in Avatar + HeadData* _headData; HandData* _handData; @@ -256,9 +280,18 @@ protected: QByteArray _billboard; QString _billboardURL; + QHash _jointIndices; ///< 1-based, since zero is returned for missing keys + QStringList _jointNames; ///< in order of depth-first traversal + static QNetworkAccessManager* networkAccessManager; quint64 _errorLogExpiry; ///< time in future when to log an error + + QWeakPointer _owningAvatarMixer; + QElapsedTimer _lastUpdateTimer; + + /// Loads the joint indices, names from the FST file (if any) + virtual void updateJointMappings(); private: // privatize the copy constructor and assignment operator so they cannot be called @@ -272,4 +305,4 @@ public: glm::quat rotation; }; -#endif /* defined(__hifi__AvatarData__) */ +#endif // hifi_AvatarData_h diff --git a/libraries/avatars/src/AvatarHashMap.cpp b/libraries/avatars/src/AvatarHashMap.cpp index 82485691c5..6b17a3fab8 100644 --- a/libraries/avatars/src/AvatarHashMap.cpp +++ b/libraries/avatars/src/AvatarHashMap.cpp @@ -1,23 +1,174 @@ // // AvatarHashMap.cpp -// hifi +// libraries/avatars/src // // Created by AndrewMeadows on 1/28/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 #include "AvatarHashMap.h" AvatarHashMap::AvatarHashMap() : _avatarHash() { + } void AvatarHashMap::insert(const QUuid& id, AvatarSharedPointer avatar) { _avatarHash.insert(id, avatar); + avatar->setSessionUUID(id); } AvatarHash::iterator AvatarHashMap::erase(const AvatarHash::iterator& iterator) { + qDebug() << "Removing Avatar with UUID" << iterator.key() << "from AvatarHashMap."; return _avatarHash.erase(iterator); } +const qint64 AVATAR_SILENCE_THRESHOLD_MSECS = 5 * 1000; + +bool AvatarHashMap::shouldKillAvatar(const AvatarSharedPointer& sharedAvatar) { + return (sharedAvatar->getOwningAvatarMixer() == NULL + || sharedAvatar->getLastUpdateTimer().elapsed() > AVATAR_SILENCE_THRESHOLD_MSECS); +} + +void AvatarHashMap::processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer) { + switch (packetTypeForPacket(datagram)) { + case PacketTypeBulkAvatarData: + processAvatarDataPacket(datagram, mixerWeakPointer); + break; + case PacketTypeAvatarIdentity: + processAvatarIdentityPacket(datagram, mixerWeakPointer); + break; + case PacketTypeAvatarBillboard: + processAvatarBillboardPacket(datagram, mixerWeakPointer); + break; + case PacketTypeKillAvatar: + processKillAvatar(datagram); + break; + default: + break; + } +} + +bool AvatarHashMap::containsAvatarWithDisplayName(const QString& displayName) { + + AvatarHash::iterator avatarIterator = _avatarHash.begin(); + while (avatarIterator != _avatarHash.end()) { + AvatarSharedPointer sharedAvatar = avatarIterator.value(); + if (avatarIterator.value()->getDisplayName() == displayName) { + // this is a match + // check if this avatar should still be around + if (!shouldKillAvatar(sharedAvatar)) { + // we have a match, return true + return true; + } else { + // we should remove this avatar, do that now + erase(avatarIterator); + } + + break; + } else { + ++avatarIterator; + } + } + + // return false, no match + return false; +} + +AvatarSharedPointer AvatarHashMap::newSharedAvatar() { + return AvatarSharedPointer(new AvatarData()); +} + +AvatarSharedPointer AvatarHashMap::matchingOrNewAvatar(const QUuid& sessionUUID, const QWeakPointer& mixerWeakPointer) { + AvatarSharedPointer matchingAvatar = _avatarHash.value(sessionUUID); + + if (!matchingAvatar) { + // insert the new avatar into our hash + matchingAvatar = newSharedAvatar(); + + qDebug() << "Adding avatar with sessionUUID " << sessionUUID << "to AvatarHashMap."; + _avatarHash.insert(sessionUUID, matchingAvatar); + + matchingAvatar->setOwningAvatarMixer(mixerWeakPointer); + } + + return matchingAvatar; +} + +void AvatarHashMap::processAvatarDataPacket(const QByteArray &datagram, const QWeakPointer &mixerWeakPointer) { + int bytesRead = numBytesForPacketHeader(datagram); + + // enumerate over all of the avatars in this packet + // only add them if mixerWeakPointer points to something (meaning that mixer is still around) + while (bytesRead < datagram.size() && mixerWeakPointer.data()) { + QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(bytesRead, NUM_BYTES_RFC4122_UUID)); + bytesRead += NUM_BYTES_RFC4122_UUID; + + AvatarSharedPointer matchingAvatarData = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); + + // have the matching (or new) avatar parse the data from the packet + bytesRead += matchingAvatarData->parseDataAtOffset(datagram, bytesRead); + } +} + +void AvatarHashMap::processAvatarIdentityPacket(const QByteArray &packet, const QWeakPointer& mixerWeakPointer) { + // setup a data stream to parse the packet + QDataStream identityStream(packet); + identityStream.skipRawData(numBytesForPacketHeader(packet)); + + QUuid sessionUUID; + + while (!identityStream.atEnd()) { + + QUrl faceMeshURL, skeletonURL; + QString displayName; + identityStream >> sessionUUID >> faceMeshURL >> skeletonURL >> displayName; + + // mesh URL for a UUID, find avatar in our list + AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); + if (matchingAvatar) { + + if (matchingAvatar->getFaceModelURL() != faceMeshURL) { + matchingAvatar->setFaceModelURL(faceMeshURL); + } + + if (matchingAvatar->getSkeletonModelURL() != skeletonURL) { + matchingAvatar->setSkeletonModelURL(skeletonURL); + } + + if (matchingAvatar->getDisplayName() != displayName) { + matchingAvatar->setDisplayName(displayName); + } + } + } +} + +void AvatarHashMap::processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer) { + int headerSize = numBytesForPacketHeader(packet); + QUuid sessionUUID = QUuid::fromRfc4122(QByteArray::fromRawData(packet.constData() + headerSize, NUM_BYTES_RFC4122_UUID)); + + AvatarSharedPointer matchingAvatar = matchingOrNewAvatar(sessionUUID, mixerWeakPointer); + if (matchingAvatar) { + QByteArray billboard = packet.mid(headerSize + NUM_BYTES_RFC4122_UUID); + if (matchingAvatar->getBillboard() != billboard) { + matchingAvatar->setBillboard(billboard); + } + } +} + +void AvatarHashMap::processKillAvatar(const QByteArray& datagram) { + // read the node id + QUuid sessionUUID = QUuid::fromRfc4122(datagram.mid(numBytesForPacketHeader(datagram), NUM_BYTES_RFC4122_UUID)); + + // remove the avatar with that UUID from our hash, if it exists + AvatarHash::iterator matchedAvatar = _avatarHash.find(sessionUUID); + if (matchedAvatar != _avatarHash.end()) { + erase(matchedAvatar); + } +} \ No newline at end of file diff --git a/libraries/avatars/src/AvatarHashMap.h b/libraries/avatars/src/AvatarHashMap.h index 19b6623402..542a2d62ab 100644 --- a/libraries/avatars/src/AvatarHashMap.h +++ b/libraries/avatars/src/AvatarHashMap.h @@ -1,24 +1,30 @@ // // AvatarHashMap.h -// hifi +// libraries/avatars/src // // Created by Stephen AndrewMeadows on 1/28/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__AvatarHashMap__ -#define __hifi__AvatarHashMap__ +#ifndef hifi_AvatarHashMap_h +#define hifi_AvatarHashMap_h #include #include #include +#include + #include "AvatarData.h" typedef QSharedPointer AvatarSharedPointer; typedef QHash AvatarHash; -class AvatarHashMap { +class AvatarHashMap : public QObject { + Q_OBJECT public: AvatarHashMap(); @@ -26,11 +32,25 @@ public: int size() const { return _avatarHash.size(); } virtual void insert(const QUuid& id, AvatarSharedPointer avatar); + +public slots: + void processAvatarMixerDatagram(const QByteArray& datagram, const QWeakPointer& mixerWeakPointer); + bool containsAvatarWithDisplayName(const QString& displayName); protected: virtual AvatarHash::iterator erase(const AvatarHash::iterator& iterator); + + bool shouldKillAvatar(const AvatarSharedPointer& sharedAvatar); + + virtual AvatarSharedPointer newSharedAvatar(); + AvatarSharedPointer matchingOrNewAvatar(const QUuid& nodeUUID, const QWeakPointer& mixerWeakPointer); + + void processAvatarDataPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); + void processAvatarIdentityPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); + void processAvatarBillboardPacket(const QByteArray& packet, const QWeakPointer& mixerWeakPointer); + void processKillAvatar(const QByteArray& datagram); AvatarHash _avatarHash; }; -#endif /* defined(__hifi__AvatarHashMap__) */ +#endif // hifi_AvatarHashMap_h diff --git a/libraries/avatars/src/HandData.cpp b/libraries/avatars/src/HandData.cpp index 201d17d3f5..c2e3b51cb3 100644 --- a/libraries/avatars/src/HandData.cpp +++ b/libraries/avatars/src/HandData.cpp @@ -1,9 +1,12 @@ // // HandData.cpp -// hifi +// libraries/avatars/src // // Created by Stephen Birarda on 5/20/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/avatars/src/HandData.h b/libraries/avatars/src/HandData.h index 9ebdc8dcf6..a37e3a5814 100755 --- a/libraries/avatars/src/HandData.h +++ b/libraries/avatars/src/HandData.h @@ -1,13 +1,16 @@ // // HandData.h -// hifi +// libraries/avatars/src // // Created by Eric Johnston on 6/26/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__HandData__ -#define __hifi__HandData__ +#ifndef hifi_HandData_h +#define hifi_HandData_h #include #include @@ -223,4 +226,4 @@ private: quint64 _collisionlessPaddleExpiry; /// Timestamp after which paddle starts colliding }; -#endif /* defined(__hifi__HandData__) */ +#endif // hifi_HandData_h diff --git a/libraries/avatars/src/HeadData.cpp b/libraries/avatars/src/HeadData.cpp index e74ac043fb..c691a2c28a 100644 --- a/libraries/avatars/src/HeadData.cpp +++ b/libraries/avatars/src/HeadData.cpp @@ -1,9 +1,12 @@ // // HeadData.cpp -// hifi +// libraries/avatars/src // // Created by Stephen Birarda on 5/20/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/avatars/src/HeadData.h b/libraries/avatars/src/HeadData.h index c60627e3f9..b76bd189bf 100644 --- a/libraries/avatars/src/HeadData.h +++ b/libraries/avatars/src/HeadData.h @@ -1,13 +1,16 @@ // // HeadData.h -// hifi +// libraries/avatars/src // // Created by Stephen Birarda on 5/20/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__HeadData__ -#define __hifi__HeadData__ +#ifndef hifi_HeadData_h +#define hifi_HeadData_h #include @@ -92,4 +95,4 @@ private: HeadData& operator= (const HeadData&); }; -#endif /* defined(__hifi__HeadData__) */ +#endif // hifi_HeadData_h diff --git a/libraries/embedded-webserver/src/HTTPConnection.cpp b/libraries/embedded-webserver/src/HTTPConnection.cpp index 8fc0a25dca..276b4e7f64 100755 --- a/libraries/embedded-webserver/src/HTTPConnection.cpp +++ b/libraries/embedded-webserver/src/HTTPConnection.cpp @@ -1,9 +1,12 @@ // // HTTPConnection.cpp -// hifi +// libraries/embedded-webserver/src // // Created by Stephen Birarda on 1/16/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 // diff --git a/libraries/embedded-webserver/src/HTTPConnection.h b/libraries/embedded-webserver/src/HTTPConnection.h index dbf1e31f47..a131a22a9e 100644 --- a/libraries/embedded-webserver/src/HTTPConnection.h +++ b/libraries/embedded-webserver/src/HTTPConnection.h @@ -1,17 +1,20 @@ // // HTTPConnection.h -// hifi +// libraries/embedded-webserver/src // // Created by Stephen Birarda on 1/16/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // Heavily based on Andrzej Kapolka's original HTTPConnection class // found from another one of his projects. // https://github.com/ey6es/witgap/tree/master/src/cpp/server/http // +// 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__HTTPConnection__ -#define __hifi__HTTPConnection__ +#ifndef hifi_HTTPConnection_h +#define hifi_HTTPConnection_h #include #include @@ -118,4 +121,4 @@ protected: QByteArray _requestContent; }; -#endif /* defined(__hifi__HTTPConnection__) */ +#endif // hifi_HTTPConnection_h diff --git a/libraries/embedded-webserver/src/HTTPManager.cpp b/libraries/embedded-webserver/src/HTTPManager.cpp index d106b6df59..1fc859014a 100755 --- a/libraries/embedded-webserver/src/HTTPManager.cpp +++ b/libraries/embedded-webserver/src/HTTPManager.cpp @@ -1,9 +1,12 @@ // // HTTPManager.cpp -// hifi +// libraries/embedded-webserver/src // // Created by Stephen Birarda on 1/16/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 diff --git a/libraries/embedded-webserver/src/HTTPManager.h b/libraries/embedded-webserver/src/HTTPManager.h index a8f9d723fa..1e3afca1b5 100755 --- a/libraries/embedded-webserver/src/HTTPManager.h +++ b/libraries/embedded-webserver/src/HTTPManager.h @@ -1,17 +1,20 @@ // // HTTPManager.h -// hifi +// libraries/embedded-webserver/src // // Created by Stephen Birarda on 1/16/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// Copyright 2014 High Fidelity, Inc. // // Heavily based on Andrzej Kapolka's original HTTPManager class // found from another one of his projects. // https://github.com/ey6es/witgap/tree/master/src/cpp/server/http // +// 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__HTTPManager__ -#define __hifi__HTTPManager__ +#ifndef hifi_HTTPManager_h +#define hifi_HTTPManager_h #include @@ -40,4 +43,4 @@ protected: HTTPRequestHandler* _requestHandler; }; -#endif /* defined(__hifi__HTTPManager__) */ +#endif // hifi_HTTPManager_h diff --git a/libraries/fbx/CMakeLists.txt b/libraries/fbx/CMakeLists.txt new file mode 100644 index 0000000000..10dd3f49f5 --- /dev/null +++ b/libraries/fbx/CMakeLists.txt @@ -0,0 +1,38 @@ +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 fbx) + +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(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 +find_package(ZLIB) +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 "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") diff --git a/interface/src/renderer/FBXReader.cpp b/libraries/fbx/src/FBXReader.cpp similarity index 90% rename from interface/src/renderer/FBXReader.cpp rename to libraries/fbx/src/FBXReader.cpp index 9a8ba43e34..7692d81eb9 100644 --- a/interface/src/renderer/FBXReader.cpp +++ b/libraries/fbx/src/FBXReader.cpp @@ -1,9 +1,12 @@ // // FBXReader.cpp -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 9/18/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -19,14 +22,14 @@ #include #include -#include - #include +#include #include +#include + #include #include "FBXReader.h" -#include "Util.h" using namespace std; @@ -41,6 +44,11 @@ bool Extents::containsPoint(const glm::vec3& point) const { && point.z >= minimum.z && point.z <= maximum.z); } +void Extents::addExtents(const Extents& extents) { + minimum = glm::min(minimum, extents.minimum); + maximum = glm::max(maximum, extents.maximum); +} + void Extents::addPoint(const glm::vec3& point) { minimum = glm::min(minimum, point); maximum = glm::max(maximum, point); @@ -64,6 +72,8 @@ bool FBXGeometry::hasBlendedMeshes() const { } static int fbxGeometryMetaTypeId = qRegisterMetaType(); +static int fbxAnimationFrameMetaTypeId = qRegisterMetaType(); +static int fbxAnimationFrameVectorMetaTypeId = qRegisterMetaType >(); template QVariant readBinaryArray(QDataStream& in) { quint32 arrayLength; @@ -436,6 +446,34 @@ QVector getIntVector(const QVariantList& properties, int index) { return vector; } +QVector getLongVector(const QVariantList& properties, int index) { + if (index >= properties.size()) { + return QVector(); + } + QVector vector = properties.at(index).value >(); + if (!vector.isEmpty()) { + return vector; + } + for (; index < properties.size(); index++) { + vector.append(properties.at(index).toLongLong()); + } + return vector; +} + +QVector getFloatVector(const QVariantList& properties, int index) { + if (index >= properties.size()) { + return QVector(); + } + QVector vector = properties.at(index).value >(); + if (!vector.isEmpty()) { + return vector; + } + for (; index < properties.size(); index++) { + vector.append(properties.at(index).toFloat()); + } + return vector; +} + QVector getDoubleVector(const QVariantList& properties, int index) { if (index >= properties.size()) { return QVector(); @@ -470,8 +508,7 @@ glm::vec3 parseVec3(const QString& string) { QString processID(const QString& id) { // Blender (at least) prepends a type to the ID, so strip it out - int index = id.indexOf("::"); - return (index == -1) ? id : id.mid(index + 2); + return id.mid(id.lastIndexOf(':') + 1); } QString getID(const QVariantList& properties, int index = 0) { @@ -893,6 +930,19 @@ public: float averageRadius; // average distance from mesh points to averageVertex }; +class AnimationCurve { +public: + QVector values; +}; + +FBXTexture getTexture(const QString& textureID, const QHash& textureFilenames, + const QHash& textureContent) { + FBXTexture texture; + texture.filename = textureFilenames.value(textureID); + texture.content = textureContent.value(texture.filename); + return texture; +} + FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) { QHash meshes; QVector blendshapes; @@ -900,10 +950,16 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QMultiHash childMap; QHash models; QHash clusters; + QHash animationCurves; QHash textureFilenames; + QHash textureContent; QHash materials; QHash diffuseTextures; QHash bumpTextures; + QHash localRotations; + QHash xComponents; + QHash yComponents; + QHash zComponents; QVariantHash joints = mapping.value("joint").toHash(); QString jointEyeLeftName = processID(getString(joints.value("jointEyeLeft", "jointEyeLeft"))); @@ -966,7 +1022,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) QString name; if (object.properties.size() == 3) { name = object.properties.at(1).toString(); - name = name.left(name.indexOf(QChar('\0'))); + name = processID(name.left(name.indexOf(QChar('\0')))); } else { name = getID(object.properties); @@ -1116,7 +1172,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) model.postRotation = glm::quat(glm::radians(postRotation)); model.postTransform = glm::translate(-rotationPivot) * glm::translate(scalePivot) * glm::scale(scale) * glm::translate(-scalePivot); - // NOTE: anbgles from the FBX file are in degrees + // NOTE: angles from the FBX file are in degrees // so we convert them to radians for the FBXModel class model.rotationMin = glm::radians(glm::vec3(rotationMinX ? rotationMin.x : -180.0f, rotationMinY ? rotationMin.y : -180.0f, rotationMinZ ? rotationMin.z : -180.0f)); @@ -1133,6 +1189,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) textureFilenames.insert(getID(object.properties), filename); } } + } else if (object.name == "Video") { + QByteArray filename; + QByteArray content; + foreach (const FBXNode& subobject, object.children) { + if (subobject.name == "RelativeFilename") { + filename = subobject.properties.at(0).toByteArray(); + filename = filename.mid(qMax(filename.lastIndexOf('\\'), filename.lastIndexOf('/')) + 1); + + } else if (subobject.name == "Content" && !subobject.properties.isEmpty()) { + content = subobject.properties.at(0).toByteArray(); + } + } + if (!content.isEmpty()) { + textureContent.insert(filename, content); + } } else if (object.name == "Material") { Material material = { glm::vec3(1.0f, 1.0f, 1.0f), glm::vec3(1.0f, 1.0f, 1.0f), 96.0f }; foreach (const FBXNode& subobject, object.children) { @@ -1196,6 +1267,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) blendshapeChannelIndices.insert(id, index); } } + } else if (object.name == "AnimationCurve") { + AnimationCurve curve; + foreach (const FBXNode& subobject, object.children) { + if (subobject.name == "KeyValueFloat") { + curve.values = getFloatVector(subobject.properties, 0); + } + } + animationCurves.insert(getID(object.properties), curve); } } } else if (child.name == "Connections") { @@ -1206,8 +1285,20 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) if (type.contains("diffuse")) { diffuseTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); - } else if (type.contains("bump")) { + } else if (type.contains("bump") || type.contains("normal")) { bumpTextures.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + + } else if (type == "lcl rotation") { + localRotations.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + + } else if (type == "d|x") { + xComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + + } else if (type == "d|y") { + yComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); + + } else if (type == "d|z") { + zComponents.insert(getID(connection.properties, 2), getID(connection.properties, 1)); } } parentMap.insert(getID(connection.properties, 1), getID(connection.properties, 2)); @@ -1231,7 +1322,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) glm::quat offsetRotation = glm::quat(glm::radians(glm::vec3(mapping.value("rx").toFloat(), mapping.value("ry").toFloat(), mapping.value("rz").toFloat()))); geometry.offset = glm::translate(glm::vec3(mapping.value("tx").toFloat(), mapping.value("ty").toFloat(), - mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) * glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale)); + mapping.value("tz").toFloat())) * glm::mat4_cast(offsetRotation) * + glm::scale(glm::vec3(offsetScale, offsetScale, offsetScale)); // get the list of models in depth-first traversal order QVector modelIDs; @@ -1269,6 +1361,17 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) appendModelIDs(parentMap.value(topID), childMap, models, remainingModels, modelIDs); } + // figure the number of animation frames from the curves + int frameCount = 0; + foreach (const AnimationCurve& curve, animationCurves) { + frameCount = qMax(frameCount, curve.values.size()); + } + for (int i = 0; i < frameCount; i++) { + FBXAnimationFrame frame; + frame.rotations.resize(modelIDs.size()); + geometry.animationFrames.append(frame); + } + // convert the models to joints QVariantList freeJoints = mapping.values("freeJoint"); foreach (const QString& modelID, modelIDs) { @@ -1278,7 +1381,8 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) joint.parentIndex = model.parentIndex; // get the indices of all ancestors starting with the first free one (if any) - joint.freeLineage.append(geometry.joints.size()); + int jointIndex = geometry.joints.size(); + joint.freeLineage.append(jointIndex); int lastFreeIndex = joint.isFree ? 0 : -1; for (int index = joint.parentIndex; index != -1; index = geometry.joints.at(index).parentIndex) { if (geometry.joints.at(index).isFree) { @@ -1317,6 +1421,18 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) joint.shapeType = Shape::UNKNOWN_SHAPE; geometry.joints.append(joint); geometry.jointIndices.insert(model.name, geometry.joints.size()); + + QString rotationID = localRotations.value(modelID); + AnimationCurve xCurve = animationCurves.value(xComponents.value(rotationID)); + AnimationCurve yCurve = animationCurves.value(yComponents.value(rotationID)); + AnimationCurve zCurve = animationCurves.value(zComponents.value(rotationID)); + glm::vec3 defaultValues = glm::degrees(safeEulerAngles(joint.rotation)); + for (int i = 0; i < frameCount; i++) { + geometry.animationFrames[i].rotations[jointIndex] = glm::quat(glm::radians(glm::vec3( + xCurve.values.isEmpty() ? defaultValues.x : xCurve.values.at(i % xCurve.values.size()), + yCurve.values.isEmpty() ? defaultValues.y : yCurve.values.at(i % yCurve.values.size()), + zCurve.values.isEmpty() ? defaultValues.z : zCurve.values.at(i % zCurve.values.size())))); + } } // for each joint we allocate a JointShapeInfo in which we'll store collision shape info QVector jointShapeInfos; @@ -1343,7 +1459,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } geometry.bindExtents.reset(); - geometry.staticExtents.reset(); geometry.meshExtents.reset(); for (QHash::iterator it = meshes.begin(); it != meshes.end(); it++) { @@ -1370,23 +1485,23 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) if (materials.contains(childID)) { Material material = materials.value(childID); - QByteArray diffuseFilename; + FBXTexture diffuseTexture; QString diffuseTextureID = diffuseTextures.value(childID); if (!diffuseTextureID.isNull()) { - diffuseFilename = textureFilenames.value(diffuseTextureID); - + diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent); + // FBX files generated by 3DSMax have an intermediate texture parent, apparently foreach (const QString& childTextureID, childMap.values(diffuseTextureID)) { if (textureFilenames.contains(childTextureID)) { - diffuseFilename = textureFilenames.value(childTextureID); + diffuseTexture = getTexture(diffuseTextureID, textureFilenames, textureContent); } } } - QByteArray normalFilename; + FBXTexture normalTexture; QString bumpTextureID = bumpTextures.value(childID); if (!bumpTextureID.isNull()) { - normalFilename = textureFilenames.value(bumpTextureID); + normalTexture = getTexture(bumpTextureID, textureFilenames, textureContent); generateTangents = true; } @@ -1396,21 +1511,21 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) part.diffuseColor = material.diffuse; part.specularColor = material.specular; part.shininess = material.shininess; - if (!diffuseFilename.isNull()) { - part.diffuseFilename = diffuseFilename; + if (!diffuseTexture.filename.isNull()) { + part.diffuseTexture = diffuseTexture; } - if (!normalFilename.isNull()) { - part.normalFilename = normalFilename; + if (!normalTexture.filename.isNull()) { + part.normalTexture = normalTexture; } } } materialIndex++; } else if (textureFilenames.contains(childID)) { - QByteArray filename = textureFilenames.value(childID); + FBXTexture texture = getTexture(childID, textureFilenames, textureContent); for (int j = 0; j < extracted.partMaterialTextures.size(); j++) { if (extracted.partMaterialTextures.at(j).second == textureIndex) { - extracted.mesh.parts[j].diffuseFilename = filename; + extracted.mesh.parts[j].diffuseTexture = texture; } } textureIndex++; @@ -1509,10 +1624,7 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } float radiusScale = extractUniformScale(joint.transform * fbxCluster.inverseBindMatrix); JointShapeInfo& jointShapeInfo = jointShapeInfos[jointIndex]; - jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd)); - bool jointIsStatic = joint.freeLineage.isEmpty(); - glm::vec3 jointTranslation = extractTranslation(geometry.offset * joint.bindTransform); float totalWeight = 0.0f; for (int j = 0; j < cluster.indices.size(); j++) { int oldIndex = cluster.indices.at(j); @@ -1534,10 +1646,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) jointShapeInfo.extents.addPoint(vertexInJointFrame); jointShapeInfo.averageVertex += vertexInJointFrame; ++jointShapeInfo.numVertices; - if (jointIsStatic) { - // expand the extents of static (nonmovable) joints - geometry.staticExtents.addPoint(vertex + jointTranslation); - } } // look for an unused slot in the weights vector @@ -1577,7 +1685,6 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) } } float radiusScale = extractUniformScale(joint.transform * firstFBXCluster.inverseBindMatrix); - jointShapeInfo.boneBegin = rotateMeshToJoint * (radiusScale * (boneBegin - boneEnd)); glm::vec3 averageVertex(0.f); foreach (const glm::vec3& vertex, extracted.mesh.vertices) { @@ -1613,6 +1720,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) FBXJoint& joint = geometry.joints[i]; JointShapeInfo& jointShapeInfo = jointShapeInfos[i]; + if (joint.parentIndex == -1) { + jointShapeInfo.boneBegin = glm::vec3(0.0f); + } else { + const FBXJoint& parentJoint = geometry.joints[joint.parentIndex]; + glm::quat inverseRotation = glm::inverse(extractRotation(joint.transform)); + jointShapeInfo.boneBegin = inverseRotation * (extractTranslation(parentJoint.transform) - extractTranslation(joint.transform)); + } + // we use a capsule if the joint ANY mesh vertices successfully projected onto the bone // AND its boneRadius is not too close to zero bool collideLikeCapsule = jointShapeInfo.numProjectedVertices > 0 @@ -1624,12 +1739,12 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) joint.shapeType = Shape::CAPSULE_SHAPE; } else { // collide the joint like a sphere + joint.shapeType = Shape::SPHERE_SHAPE; if (jointShapeInfo.numVertices > 0) { jointShapeInfo.averageVertex /= (float)jointShapeInfo.numVertices; joint.shapePosition = jointShapeInfo.averageVertex; } else { joint.shapePosition = glm::vec3(0.f); - joint.shapeType = Shape::SPHERE_SHAPE; } if (jointShapeInfo.numProjectedVertices == 0 && jointShapeInfo.numVertices > 0) { @@ -1638,6 +1753,15 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping) jointShapeInfo.averageRadius /= (float)jointShapeInfo.numVertices; joint.boneRadius = jointShapeInfo.averageRadius; } + + float distanceFromEnd = glm::length(joint.shapePosition); + float distanceFromBegin = glm::distance(joint.shapePosition, jointShapeInfo.boneBegin); + if (distanceFromEnd > joint.distanceToParent && distanceFromBegin > joint.distanceToParent) { + // The shape is further from both joint endpoints than the endpoints are from each other + // which probably means the model has a bad transform somewhere. We disable this shape + // by setting its type to UNKNOWN_SHAPE. + joint.shapeType = Shape::UNKNOWN_SHAPE; + } } } geometry.palmDirection = parseVec3(mapping.value("palmDirection", "0, -1, 0").toString()); diff --git a/interface/src/renderer/FBXReader.h b/libraries/fbx/src/FBXReader.h similarity index 82% rename from interface/src/renderer/FBXReader.h rename to libraries/fbx/src/FBXReader.h index 5f6a4f51ba..e437961385 100644 --- a/interface/src/renderer/FBXReader.h +++ b/libraries/fbx/src/FBXReader.h @@ -1,13 +1,16 @@ // // FBXReader.h -// interface +// interface/src/renderer // // Created by Andrzej Kapolka on 9/18/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__FBXReader__ -#define __interface__FBXReader__ +#ifndef hifi_FBXReader_h +#define hifi_FBXReader_h #include #include @@ -15,6 +18,8 @@ #include #include +#include + #include #include @@ -30,6 +35,10 @@ public: /// set minimum and maximum to FLT_MAX and -FLT_MAX respectively void reset(); + /// \param extents another intance of extents + /// expand current limits to contain other extents + void addExtents(const Extents& extents); + /// \param point new point to compare against existing limits /// compare point to current limits and expand them if necessary to contain point void addPoint(const glm::vec3& point); @@ -84,7 +93,7 @@ public: QString name; glm::vec3 shapePosition; // in joint frame glm::quat shapeRotation; // in joint frame - int shapeType; + Shape::Type shapeType; }; @@ -96,6 +105,14 @@ public: glm::mat4 inverseBindMatrix; }; +/// A texture map in an FBX document. +class FBXTexture { +public: + + QByteArray filename; + QByteArray content; +}; + /// A single part of a mesh (with the same material). class FBXMeshPart { public: @@ -107,8 +124,8 @@ public: glm::vec3 specularColor; float shininess; - QByteArray diffuseFilename; - QByteArray normalFilename; + FBXTexture diffuseTexture; + FBXTexture normalTexture; }; /// A single mesh (with optional blendshapes) extracted from an FBX document. @@ -132,6 +149,16 @@ public: QVector blendshapes; }; +/// A single animation frame extracted from an FBX document. +class FBXAnimationFrame { +public: + + QVector rotations; +}; + +Q_DECLARE_METATYPE(FBXAnimationFrame) +Q_DECLARE_METATYPE(QVector) + /// An attachment to an FBX document. class FBXAttachment { public: @@ -174,9 +201,10 @@ public: glm::vec3 neckPivot; Extents bindExtents; - Extents staticExtents; Extents meshExtents; + QVector animationFrames; + QVector attachments; int getJointIndex(const QString& name) const { return jointIndices.value(name) - 1; } @@ -197,4 +225,4 @@ FBXGeometry readFBX(const QByteArray& model, const QVariantHash& mapping); /// Reads SVO geometry from the supplied model data. FBXGeometry readSVO(const QByteArray& model); -#endif /* defined(__interface__FBXReader__) */ +#endif // hifi_FBXReader_h diff --git a/libraries/metavoxels/CMakeLists.txt b/libraries/metavoxels/CMakeLists.txt index acc060b270..e2a90cb085 100644 --- a/libraries/metavoxels/CMakeLists.txt +++ b/libraries/metavoxels/CMakeLists.txt @@ -20,8 +20,19 @@ auto_mtc(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/SetupHifiLibrary.cmake) setup_hifi_library(${TARGET_NAME} "${AUTOMTC_SRC}") +# link in the networking library +include(${MACRO_DIR}/LinkHifiLibrary.cmake) +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + include(${MACRO_DIR}/IncludeGLM.cmake) include_glm(${TARGET_NAME} "${ROOT_DIR}") -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script) +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}") +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/metavoxels/src/AttributeRegistry.cpp b/libraries/metavoxels/src/AttributeRegistry.cpp index fc448613f9..e7a7f41850 100644 --- a/libraries/metavoxels/src/AttributeRegistry.cpp +++ b/libraries/metavoxels/src/AttributeRegistry.cpp @@ -1,9 +1,12 @@ // // AttributeRegistry.cpp -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -144,6 +147,14 @@ void OwnedAttributeValue::mix(const AttributeValue& first, const AttributeValue& _value = _attribute->mix(first.getValue(), second.getValue(), alpha); } +void OwnedAttributeValue::blend(const AttributeValue& source, const AttributeValue& dest) { + if (_attribute) { + _attribute->destroy(_value); + } + _attribute = source.getAttribute(); + _value = _attribute->blend(source.getValue(), dest.getValue()); +} + OwnedAttributeValue& OwnedAttributeValue::operator=(const AttributeValue& other) { if (_attribute) { _attribute->destroy(_value); @@ -243,6 +254,19 @@ void* QRgbAttribute::mix(void* first, void* second, float alpha) const { glm::mix((float)qAlpha(firstValue), (float)qAlpha(secondValue), alpha))); } +const float EIGHT_BIT_MAXIMUM = 255.0f; + +void* QRgbAttribute::blend(void* source, void* dest) const { + QRgb sourceValue = decodeInline(source); + QRgb destValue = decodeInline(dest); + float alpha = qAlpha(sourceValue) / EIGHT_BIT_MAXIMUM; + return encodeInline(qRgba( + glm::mix((float)qRed(destValue), (float)qRed(sourceValue), alpha), + glm::mix((float)qGreen(destValue), (float)qGreen(sourceValue), alpha), + glm::mix((float)qBlue(destValue), (float)qBlue(sourceValue), alpha), + glm::mix((float)qAlpha(destValue), (float)qAlpha(sourceValue), alpha))); +} + void* QRgbAttribute::createFromScript(const QScriptValue& value, QScriptEngine* engine) const { return encodeInline((QRgb)value.toUInt32()); } @@ -287,6 +311,13 @@ void* PackedNormalAttribute::mix(void* first, void* second, float alpha) const { return encodeInline(packNormal(glm::normalize(glm::mix(firstNormal, secondNormal, alpha)))); } +void* PackedNormalAttribute::blend(void* source, void* dest) const { + QRgb sourceValue = decodeInline(source); + QRgb destValue = decodeInline(dest); + float alpha = qAlpha(sourceValue) / EIGHT_BIT_MAXIMUM; + return encodeInline(packNormal(glm::normalize(glm::mix(unpackNormal(destValue), unpackNormal(sourceValue), alpha)))); +} + const float CHAR_SCALE = 127.0f; const float INVERSE_CHAR_SCALE = 1.0f / CHAR_SCALE; diff --git a/libraries/metavoxels/src/AttributeRegistry.h b/libraries/metavoxels/src/AttributeRegistry.h index f7d8d955a5..00d974b8b6 100644 --- a/libraries/metavoxels/src/AttributeRegistry.h +++ b/libraries/metavoxels/src/AttributeRegistry.h @@ -1,13 +1,16 @@ // // AttributeRegistry.h -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__AttributeRegistry__ -#define __interface__AttributeRegistry__ +#ifndef hifi_AttributeRegistry_h +#define hifi_AttributeRegistry_h #include #include @@ -158,6 +161,9 @@ public: /// Sets this attribute to a mix of the first and second provided. void mix(const AttributeValue& first, const AttributeValue& second, float alpha); + /// Sets this attribute to a blend of the source and destination. + void blend(const AttributeValue& source, const AttributeValue& dest); + /// Destroys the current value, if any, and copies the specified other value. OwnedAttributeValue& operator=(const AttributeValue& other); @@ -218,6 +224,9 @@ public: /// Mixes the first and the second, returning a new value with the result. virtual void* mix(void* first, void* second, float alpha) const = 0; + /// Blends the source with the destination, returning a new value with the result. + virtual void* blend(void* source, void* dest) const = 0; + virtual void* getDefaultValue() const = 0; virtual void* createFromScript(const QScriptValue& value, QScriptEngine* engine) const { return create(); } @@ -249,6 +258,8 @@ public: virtual void* mix(void* first, void* second, float alpha) const { return create(alpha < 0.5f ? first : second); } + virtual void* blend(void* source, void* dest) const { return create(source); } + virtual void* getDefaultValue() const { return encodeInline(_defaultValue); } protected: @@ -315,6 +326,8 @@ public: virtual void* mix(void* first, void* second, float alpha) const; + virtual void* blend(void* source, void* dest) const; + virtual void* createFromScript(const QScriptValue& value, QScriptEngine* engine) const; virtual void* createFromVariant(const QVariant& value) const; @@ -333,6 +346,8 @@ public: virtual bool merge(void*& parent, void* children[], bool postRead = false) const; virtual void* mix(void* first, void* second, float alpha) const; + + virtual void* blend(void* source, void* dest) const; }; /// Packs a normal into an RGB value. @@ -449,4 +464,4 @@ public: virtual void writeMetavoxelSubdivision(const MetavoxelNode& root, MetavoxelStreamState& state); }; -#endif /* defined(__interface__AttributeRegistry__) */ +#endif // hifi_AttributeRegistry_h diff --git a/libraries/metavoxels/src/Bitstream.cpp b/libraries/metavoxels/src/Bitstream.cpp index 077e6c1c69..ad929e533c 100644 --- a/libraries/metavoxels/src/Bitstream.cpp +++ b/libraries/metavoxels/src/Bitstream.cpp @@ -1,9 +1,12 @@ // // Bitstream.cpp -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/2/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -35,6 +38,7 @@ REGISTER_SIMPLE_TYPE_STREAMER(SharedObjectPointer) // some types don't quite work with our macro static int vec3Streamer = Bitstream::registerTypeStreamer(qMetaTypeId(), new SimpleTypeStreamer()); +static int quatStreamer = Bitstream::registerTypeStreamer(qMetaTypeId(), new SimpleTypeStreamer()); static int metaObjectStreamer = Bitstream::registerTypeStreamer(qMetaTypeId(), new SimpleTypeStreamer()); @@ -352,6 +356,14 @@ Bitstream& Bitstream::operator>>(glm::vec3& value) { return *this >> value.x >> value.y >> value.z; } +Bitstream& Bitstream::operator<<(const glm::quat& value) { + return *this << value.w << value.x << value.y << value.z; +} + +Bitstream& Bitstream::operator>>(glm::quat& value) { + return *this >> value.w >> value.x >> value.y >> value.z; +} + Bitstream& Bitstream::operator<<(const QByteArray& string) { *this << string.size(); return write(string.constData(), string.size() * BITS_IN_BYTE); @@ -869,7 +881,11 @@ Bitstream& Bitstream::operator>(SharedObjectPointer& object) { *this >> rawObject; } pointer = static_cast(rawObject); - pointer->setRemoteID(id); + if (pointer) { + pointer->setRemoteID(id); + } else { + qDebug() << "Null object" << pointer << reference; + } } object = static_cast(pointer.data()); return *this; diff --git a/libraries/metavoxels/src/Bitstream.h b/libraries/metavoxels/src/Bitstream.h index 760154a928..34b66eb9f2 100644 --- a/libraries/metavoxels/src/Bitstream.h +++ b/libraries/metavoxels/src/Bitstream.h @@ -1,13 +1,16 @@ // // Bitstream.h -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/2/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__Bitstream__ -#define __interface__Bitstream__ +#ifndef hifi_Bitstream_h +#define hifi_Bitstream_h #include #include @@ -312,6 +315,9 @@ public: Bitstream& operator<<(const glm::vec3& value); Bitstream& operator>>(glm::vec3& value); + Bitstream& operator<<(const glm::quat& value); + Bitstream& operator>>(glm::quat& value); + Bitstream& operator<<(const QByteArray& string); Bitstream& operator>>(QByteArray& string); @@ -920,6 +926,13 @@ public: _Pragma(STRINGIFY(unused(_TypePtr##X))) #endif +/// Registers a simple type and its streamer. +template int registerSimpleMetaType() { + int type = qRegisterMetaType(); + Bitstream::registerTypeStreamer(type, new SimpleTypeStreamer()); + return type; +} + /// Registers a streamable type and its streamer. template int registerStreamableMetaType() { int type = qRegisterMetaType(); @@ -940,4 +953,4 @@ template int registerStreamableMetaType() { /// Flags a field or base class as streaming. #define STREAM -#endif /* defined(__interface__Bitstream__) */ +#endif // hifi_Bitstream_h diff --git a/libraries/metavoxels/src/DatagramSequencer.cpp b/libraries/metavoxels/src/DatagramSequencer.cpp index 5c605dc0f4..f1f60e4d87 100644 --- a/libraries/metavoxels/src/DatagramSequencer.cpp +++ b/libraries/metavoxels/src/DatagramSequencer.cpp @@ -1,16 +1,19 @@ // // DatagramSequencer.cpp -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/20/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 #include -#include +#include #include "DatagramSequencer.h" #include "MetavoxelMessages.h" diff --git a/libraries/metavoxels/src/DatagramSequencer.h b/libraries/metavoxels/src/DatagramSequencer.h index 30a131390d..ce9f36ba33 100644 --- a/libraries/metavoxels/src/DatagramSequencer.h +++ b/libraries/metavoxels/src/DatagramSequencer.h @@ -1,13 +1,16 @@ // // DatagramSequencer.h -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/20/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__DatagramSequencer__ -#define __interface__DatagramSequencer__ +#ifndef hifi_DatagramSequencer_h +#define hifi_DatagramSequencer_h #include #include @@ -318,4 +321,4 @@ private: bool _messagesEnabled; }; -#endif /* defined(__interface__DatagramSequencer__) */ +#endif // hifi_DatagramSequencer_h diff --git a/libraries/metavoxels/src/MetavoxelData.cpp b/libraries/metavoxels/src/MetavoxelData.cpp index e548de46c7..0d52fc5ed6 100644 --- a/libraries/metavoxels/src/MetavoxelData.cpp +++ b/libraries/metavoxels/src/MetavoxelData.cpp @@ -1,9 +1,12 @@ // // MetavoxelData.cpp -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -24,6 +27,8 @@ REGISTER_META_OBJECT(Spanner) REGISTER_META_OBJECT(Sphere) REGISTER_META_OBJECT(StaticModel) +static int metavoxelDataTypeId = registerSimpleMetaType(); + MetavoxelLOD::MetavoxelLOD(const glm::vec3& position, float threshold) : position(position), threshold(threshold) { @@ -327,6 +332,100 @@ const int Y_MAXIMUM_FLAG = 2; const int Z_MAXIMUM_FLAG = 4; const int MAXIMUM_FLAG_MASK = X_MAXIMUM_FLAG | Y_MAXIMUM_FLAG | Z_MAXIMUM_FLAG; +static glm::vec3 getNextMinimum(const glm::vec3& minimum, float nextSize, int index) { + return minimum + glm::vec3( + (index & X_MAXIMUM_FLAG) ? nextSize : 0.0f, + (index & Y_MAXIMUM_FLAG) ? nextSize : 0.0f, + (index & Z_MAXIMUM_FLAG) ? nextSize : 0.0f); +} + +static void setNode(const AttributeValue& value, MetavoxelNode*& node, MetavoxelNode* other, bool blend) { + if (!blend) { + // if we're not blending, we can just make a shallow copy + if (node) { + node->decrementReferenceCount(value.getAttribute()); + } + (node = other)->incrementReferenceCount(); + return; + } + if (node) { + MetavoxelNode* oldNode = node; + node = new MetavoxelNode(value.getAttribute(), oldNode); + oldNode->decrementReferenceCount(value.getAttribute()); + + } else { + node = new MetavoxelNode(value); + } + OwnedAttributeValue oldValue = node->getAttributeValue(value.getAttribute()); + node->blendAttributeValues(other->getAttributeValue(value.getAttribute()), oldValue); + if (!other->isLeaf()) { + for (int i = 0; i < MetavoxelNode::CHILD_COUNT; i++) { + MetavoxelNode* child = node->getChild(i); + setNode(oldValue, child, other->getChild(i), true); + node->setChild(i, child); + } + } + node->mergeChildren(value.getAttribute()); +} + +static void setNode(const AttributeValue& value, MetavoxelNode*& node, const glm::vec3& minimum, float size, + MetavoxelNode* other, const glm::vec3& otherMinimum, float otherSize, bool blend) { + if (otherSize >= size) { + setNode(value, node, other, blend); + return; + } + if (node) { + MetavoxelNode* oldNode = node; + node = new MetavoxelNode(value.getAttribute(), oldNode); + oldNode->decrementReferenceCount(value.getAttribute()); + + } else { + node = new MetavoxelNode(value); + } + int index = 0; + float otherHalfSize = otherSize * 0.5f; + float nextSize = size * 0.5f; + if (otherMinimum.x + otherHalfSize >= minimum.x + nextSize) { + index |= X_MAXIMUM_FLAG; + } + if (otherMinimum.y + otherHalfSize >= minimum.y + nextSize) { + index |= Y_MAXIMUM_FLAG; + } + if (otherMinimum.z + otherHalfSize >= minimum.z + nextSize) { + index |= Z_MAXIMUM_FLAG; + } + if (node->isLeaf()) { + for (int i = 1; i < MetavoxelNode::CHILD_COUNT; i++) { + node->setChild((index + i) % MetavoxelNode::CHILD_COUNT, new MetavoxelNode( + node->getAttributeValue(value.getAttribute()))); + } + } + MetavoxelNode* nextNode = node->getChild(index); + setNode(node->getAttributeValue(value.getAttribute()), nextNode, getNextMinimum(minimum, nextSize, index), + nextSize, other, otherMinimum, otherSize, blend); + node->setChild(index, nextNode); + node->mergeChildren(value.getAttribute()); +} + +void MetavoxelData::set(const glm::vec3& minimum, const MetavoxelData& data, bool blend) { + // expand to fit the entire data + Box bounds(minimum, minimum + glm::vec3(data.getSize(), data.getSize(), data.getSize())); + while (!getBounds().contains(bounds)) { + expand(); + } + + // set/mix each attribute separately + for (QHash::const_iterator it = data._roots.constBegin(); + it != data._roots.constEnd(); it++) { + MetavoxelNode*& root = _roots[it.key()]; + setNode(it.key(), root, getMinimum(), getSize(), it.value(), minimum, data.getSize(), blend); + if (root->isLeaf() && root->getAttributeValue(it.key()).isDefault()) { + _roots.remove(it.key()); + root->decrementReferenceCount(it.key()); + } + } +} + static int getOppositeIndex(int index) { return index ^ MAXIMUM_FLAG_MASK; } @@ -511,6 +610,14 @@ MetavoxelNode* MetavoxelData::createRoot(const AttributePointer& attribute) { return root = new MetavoxelNode(attribute); } +bool MetavoxelData::operator==(const MetavoxelData& other) const { + return _size == other._size && _roots == other._roots; +} + +bool MetavoxelData::operator!=(const MetavoxelData& other) const { + return _size != other._size || _roots != other._roots; +} + void MetavoxelData::incrementRootReferenceCounts() { for (QHash::const_iterator it = _roots.constBegin(); it != _roots.constEnd(); it++) { it.value()->incrementReferenceCount(); @@ -523,11 +630,22 @@ void MetavoxelData::decrementRootReferenceCounts() { } } -static glm::vec3 getNextMinimum(const glm::vec3& minimum, float nextSize, int index) { - return minimum + glm::vec3( - (index & X_MAXIMUM_FLAG) ? nextSize : 0.0f, - (index & Y_MAXIMUM_FLAG) ? nextSize : 0.0f, - (index & Z_MAXIMUM_FLAG) ? nextSize : 0.0f); +Bitstream& operator<<(Bitstream& out, const MetavoxelData& data) { + data.write(out); + return out; +} + +Bitstream& operator>>(Bitstream& in, MetavoxelData& data) { + data.read(in); + return in; +} + +template<> void Bitstream::writeDelta(const MetavoxelData& value, const MetavoxelData& reference) { + value.writeDelta(reference, MetavoxelLOD(), *this, MetavoxelLOD()); +} + +template<> void Bitstream::readDelta(MetavoxelData& value, const MetavoxelData& reference) { + value.readDelta(reference, MetavoxelLOD(), *this, MetavoxelLOD()); } bool MetavoxelStreamState::shouldSubdivide() const { @@ -577,7 +695,11 @@ MetavoxelNode::MetavoxelNode(const AttributePointer& attribute, const MetavoxelN void MetavoxelNode::setAttributeValue(const AttributeValue& attributeValue) { attributeValue.getAttribute()->destroy(_attributeValue); _attributeValue = attributeValue.copy(); - clearChildren(attributeValue.getAttribute()); +} + +void MetavoxelNode::blendAttributeValues(const AttributeValue& source, const AttributeValue& dest) { + source.getAttribute()->destroy(_attributeValue); + _attributeValue = source.getAttribute()->blend(source.getValue(), dest.getValue()); } AttributeValue MetavoxelNode::getAttributeValue(const AttributePointer& attribute) const { @@ -1408,7 +1530,7 @@ void SpannerRenderer::simulate(float deltaTime) { // nothing by default } -void SpannerRenderer::render(float alpha, const glm::vec3& clipMinimum, float clipSize) { +void SpannerRenderer::render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize) { // nothing by default } @@ -1426,7 +1548,7 @@ void Transformable::setTranslation(const glm::vec3& translation) { } } -void Transformable::setRotation(const glm::vec3& rotation) { +void Transformable::setRotation(const glm::quat& rotation) { if (_rotation != rotation) { emit rotationChanged(_rotation = rotation); } diff --git a/libraries/metavoxels/src/MetavoxelData.h b/libraries/metavoxels/src/MetavoxelData.h index fa408aafb7..2e6f6c4437 100644 --- a/libraries/metavoxels/src/MetavoxelData.h +++ b/libraries/metavoxels/src/MetavoxelData.h @@ -1,13 +1,16 @@ // // MetavoxelData.h -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__MetavoxelData__ -#define __interface__MetavoxelData__ +#ifndef hifi_MetavoxelData_h +#define hifi_MetavoxelData_h #include #include @@ -61,6 +64,7 @@ public: MetavoxelData& operator=(const MetavoxelData& other); + void setSize(float size) { _size = size; } float getSize() const { return _size; } glm::vec3 getMinimum() const { return glm::vec3(_size, _size, _size) * -0.5f; } @@ -90,6 +94,9 @@ public: SharedObjectPointer findFirstRaySpannerIntersection(const glm::vec3& origin, const glm::vec3& direction, const AttributePointer& attribute, float& distance, const MetavoxelLOD& lod = MetavoxelLOD()); + /// Sets part of the data. + void set(const glm::vec3& minimum, const MetavoxelData& data, bool blend = false); + /// Expands the tree, increasing its capacity in all dimensions. void expand(); @@ -103,6 +110,9 @@ public: MetavoxelNode* getRoot(const AttributePointer& attribute) const { return _roots.value(attribute); } MetavoxelNode* createRoot(const AttributePointer& attribute); + bool operator==(const MetavoxelData& other) const; + bool operator!=(const MetavoxelData& other) const; + private: friend class MetavoxelVisitation; @@ -114,6 +124,16 @@ private: QHash _roots; }; +Bitstream& operator<<(Bitstream& out, const MetavoxelData& data); + +Bitstream& operator>>(Bitstream& in, MetavoxelData& data); + +template<> void Bitstream::writeDelta(const MetavoxelData& value, const MetavoxelData& reference); + +template<> void Bitstream::readDelta(MetavoxelData& value, const MetavoxelData& reference); + +Q_DECLARE_METATYPE(MetavoxelData) + /// Holds the state used in streaming metavoxel data. class MetavoxelStreamState { public: @@ -142,6 +162,8 @@ public: void setAttributeValue(const AttributeValue& attributeValue); + void blendAttributeValues(const AttributeValue& source, const AttributeValue& dest); + AttributeValue getAttributeValue(const AttributePointer& attribute) const; void* getAttributeValue() const { return _attributeValue; } @@ -174,13 +196,13 @@ public: void destroy(const AttributePointer& attribute); + void clearChildren(const AttributePointer& attribute); + private: Q_DISABLE_COPY(MetavoxelNode) friend class MetavoxelVisitation; - void clearChildren(const AttributePointer& attribute); - int _referenceCount; void* _attributeValue; MetavoxelNode* _children[CHILD_COUNT]; @@ -499,11 +521,13 @@ class SpannerRenderer : public QObject { public: + enum Mode { DEFAULT_MODE, DIFFUSE_MODE, NORMAL_MODE }; + Q_INVOKABLE SpannerRenderer(); virtual void init(Spanner* spanner); virtual void simulate(float deltaTime); - virtual void render(float alpha, const glm::vec3& clipMinimum, float clipSize); + virtual void render(float alpha, Mode mode, const glm::vec3& clipMinimum, float clipSize); virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& clipMinimum, float clipSize, float& distance) const; }; @@ -512,7 +536,7 @@ public: class Transformable : public Spanner { Q_OBJECT Q_PROPERTY(glm::vec3 translation MEMBER _translation WRITE setTranslation NOTIFY translationChanged) - Q_PROPERTY(glm::vec3 rotation MEMBER _rotation WRITE setRotation NOTIFY rotationChanged) + Q_PROPERTY(glm::quat rotation MEMBER _rotation WRITE setRotation NOTIFY rotationChanged) Q_PROPERTY(float scale MEMBER _scale WRITE setScale NOTIFY scaleChanged) public: @@ -522,8 +546,8 @@ public: void setTranslation(const glm::vec3& translation); const glm::vec3& getTranslation() const { return _translation; } - void setRotation(const glm::vec3& rotation); - const glm::vec3& getRotation() const { return _rotation; } + void setRotation(const glm::quat& rotation); + const glm::quat& getRotation() const { return _rotation; } void setScale(float scale); float getScale() const { return _scale; } @@ -531,13 +555,13 @@ public: signals: void translationChanged(const glm::vec3& translation); - void rotationChanged(const glm::vec3& rotation); + void rotationChanged(const glm::quat& rotation); void scaleChanged(float scale); private: glm::vec3 _translation; - glm::vec3 _rotation; // Euler Angles in degrees + glm::quat _rotation; float _scale; }; @@ -592,7 +616,7 @@ public: const QUrl& getURL() const { return _url; } virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, - const glm::vec3& clipMinimum, float clipSize,float& distance) const; + const glm::vec3& clipMinimum, float clipSize, float& distance) const; signals: @@ -607,4 +631,4 @@ private: QUrl _url; }; -#endif /* defined(__interface__MetavoxelData__) */ +#endif // hifi_MetavoxelData_h diff --git a/libraries/metavoxels/src/MetavoxelMessages.cpp b/libraries/metavoxels/src/MetavoxelMessages.cpp index ce0d01ccf2..dba9bc9c5c 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.cpp +++ b/libraries/metavoxels/src/MetavoxelMessages.cpp @@ -1,9 +1,12 @@ // // MetavoxelMessages.cpp -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 1/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 "MetavoxelMessages.h" @@ -305,3 +308,13 @@ void SetSpannerEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& obje setIntersectingMasked(spanner->getBounds(), data); } + +SetDataEdit::SetDataEdit(const glm::vec3& minimum, const MetavoxelData& data, bool blend) : + minimum(minimum), + data(data), + blend(blend) { +} + +void SetDataEdit::apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const { + data.set(minimum, this->data, blend); +} diff --git a/libraries/metavoxels/src/MetavoxelMessages.h b/libraries/metavoxels/src/MetavoxelMessages.h index 25db8e5464..8f819fe3d8 100644 --- a/libraries/metavoxels/src/MetavoxelMessages.h +++ b/libraries/metavoxels/src/MetavoxelMessages.h @@ -1,13 +1,16 @@ // // MetavoxelMessages.h -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/31/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__MetavoxelMessages__ -#define __interface__MetavoxelMessages__ +#ifndef hifi_MetavoxelMessages_h +#define hifi_MetavoxelMessages_h #include "MetavoxelData.h" @@ -176,4 +179,21 @@ public: DECLARE_STREAMABLE_METATYPE(SetSpannerEdit) -#endif /* defined(__interface__MetavoxelMessages__) */ +/// An edit that directly sets part of the metavoxel data. +class SetDataEdit : public MetavoxelEdit { + STREAMABLE + +public: + + STREAM glm::vec3 minimum; + STREAM MetavoxelData data; + STREAM bool blend; + + SetDataEdit(const glm::vec3& minimum = glm::vec3(), const MetavoxelData& data = MetavoxelData(), bool blend = false); + + virtual void apply(MetavoxelData& data, const WeakSharedObjectHash& objects) const; +}; + +DECLARE_STREAMABLE_METATYPE(SetDataEdit) + +#endif // hifi_MetavoxelMessages_h diff --git a/libraries/metavoxels/src/MetavoxelUtil.cpp b/libraries/metavoxels/src/MetavoxelUtil.cpp index 7995809f1c..f2f434b24d 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.cpp +++ b/libraries/metavoxels/src/MetavoxelUtil.cpp @@ -1,9 +1,12 @@ // // MetavoxelUtil.cpp -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/30/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -52,6 +55,7 @@ public: DoubleEditor::DoubleEditor(QWidget* parent) : QDoubleSpinBox(parent) { setMinimum(-FLT_MAX); setMaximum(FLT_MAX); + setSingleStep(0.01); } DelegatingItemEditorFactory::DelegatingItemEditorFactory() : @@ -127,6 +131,12 @@ static QItemEditorCreatorBase* createVec3EditorCreator() { return creator; } +static QItemEditorCreatorBase* createQuatEditorCreator() { + QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); + getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); + return creator; +} + static QItemEditorCreatorBase* createParameterizedURLEditorCreator() { QItemEditorCreatorBase* creator = new LazyItemEditorCreator(); getItemEditorFactory()->registerEditor(qMetaTypeId(), creator); @@ -138,6 +148,7 @@ static QItemEditorCreatorBase* qMetaObjectEditorCreator = createQMetaObjectEdito static QItemEditorCreatorBase* qColorEditorCreator = createQColorEditorCreator(); static QItemEditorCreatorBase* qUrlEditorCreator = createQUrlEditorCreator(); static QItemEditorCreatorBase* vec3EditorCreator = createVec3EditorCreator(); +static QItemEditorCreatorBase* quatEditorCreator = createQuatEditorCreator(); static QItemEditorCreatorBase* parameterizedURLEditorCreator = createParameterizedURLEditorCreator(); QByteArray signal(const char* signature) { @@ -380,7 +391,7 @@ void QUrlEditor::updateSettings() { QSettings().setValue("editorURLs", urls); } -Vec3Editor::Vec3Editor(QWidget* parent) : QWidget(parent) { +BaseVec3Editor::BaseVec3Editor(QWidget* parent) : QWidget(parent) { QHBoxLayout* layout = new QHBoxLayout(); layout->setContentsMargins(QMargins()); setLayout(layout); @@ -390,26 +401,64 @@ Vec3Editor::Vec3Editor(QWidget* parent) : QWidget(parent) { layout->addWidget(_z = createComponentBox()); } -void Vec3Editor::setVector(const glm::vec3& vector) { - _vector = vector; - _x->setValue(vector.x); - _y->setValue(vector.y); - _z->setValue(vector.z); -} - -void Vec3Editor::updateVector() { - emit vectorChanged(_vector = glm::vec3(_x->value(), _y->value(), _z->value())); -} - -QDoubleSpinBox* Vec3Editor::createComponentBox() { +QDoubleSpinBox* BaseVec3Editor::createComponentBox() { QDoubleSpinBox* box = new QDoubleSpinBox(); box->setMinimum(-FLT_MAX); box->setMaximum(FLT_MAX); box->setMinimumWidth(50); - connect(box, SIGNAL(valueChanged(double)), SLOT(updateVector())); + connect(box, SIGNAL(valueChanged(double)), SLOT(updateValue())); return box; } +Vec3Editor::Vec3Editor(QWidget* parent) : BaseVec3Editor(parent) { + _x->setSingleStep(0.01); + _y->setSingleStep(0.01); + _z->setSingleStep(0.01); +} + +static void setComponentValue(QDoubleSpinBox* box, double value) { + box->blockSignals(true); + box->setValue(value); + box->blockSignals(false); +} + +void Vec3Editor::setValue(const glm::vec3& value) { + _value = value; + setComponentValue(_x, value.x); + setComponentValue(_y, value.y); + setComponentValue(_z, value.z); +} + +void Vec3Editor::updateValue() { + emit valueChanged(_value = glm::vec3(_x->value(), _y->value(), _z->value())); +} + +QuatEditor::QuatEditor(QWidget* parent) : BaseVec3Editor(parent) { + _x->setRange(-179.0, 180.0); + _y->setRange(-179.0, 180.0); + _z->setRange(-179.0, 180.0); + + _x->setWrapping(true); + _y->setWrapping(true); + _z->setWrapping(true); +} + +void QuatEditor::setValue(const glm::quat& value) { + if (_value != value) { + glm::vec3 eulers = glm::degrees(safeEulerAngles(_value = value)); + setComponentValue(_x, eulers.x); + setComponentValue(_y, eulers.y); + setComponentValue(_z, eulers.z); + } +} + +void QuatEditor::updateValue() { + glm::quat value(glm::radians(glm::vec3(_x->value(), _y->value(), _z->value()))); + if (_value != value) { + emit valueChanged(_value = value); + } +} + ParameterizedURL::ParameterizedURL(const QUrl& url, const ScriptHash& parameters) : _url(url), _parameters(parameters) { diff --git a/libraries/metavoxels/src/MetavoxelUtil.h b/libraries/metavoxels/src/MetavoxelUtil.h index 9972981bc7..34c2bac6be 100644 --- a/libraries/metavoxels/src/MetavoxelUtil.h +++ b/libraries/metavoxels/src/MetavoxelUtil.h @@ -1,13 +1,16 @@ // // MetavoxelUtil.h -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 12/30/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__MetavoxelUtil__ -#define __interface__MetavoxelUtil__ +#ifndef hifi_MetavoxelUtil_h +#define hifi_MetavoxelUtil_h #include #include @@ -142,10 +145,31 @@ private: QUrl _url; }; -/// Editor for vector values. -class Vec3Editor : public QWidget { +/// Base class for Vec3Editor and QuatEditor. +class BaseVec3Editor : public QWidget { Q_OBJECT - Q_PROPERTY(glm::vec3 vector MEMBER _vector WRITE setVector NOTIFY vectorChanged USER true) + +public: + + BaseVec3Editor(QWidget* parent); + +protected slots: + + virtual void updateValue() = 0; + +protected: + + QDoubleSpinBox* createComponentBox(); + + QDoubleSpinBox* _x; + QDoubleSpinBox* _y; + QDoubleSpinBox* _z; +}; + +/// Editor for vector values. +class Vec3Editor : public BaseVec3Editor { + Q_OBJECT + Q_PROPERTY(glm::vec3 value MEMBER _value WRITE setValue NOTIFY valueChanged USER true) public: @@ -153,24 +177,45 @@ public: signals: - void vectorChanged(const glm::vec3& vector); + void valueChanged(const glm::vec3& vector); public slots: - void setVector(const glm::vec3& vector); + void setValue(const glm::vec3& vector); -private slots: +protected: - void updateVector(); + virtual void updateValue(); private: - QDoubleSpinBox* createComponentBox(); + glm::vec3 _value; +}; + +/// Editor for quaternion values. +class QuatEditor : public BaseVec3Editor { + Q_OBJECT + Q_PROPERTY(glm::quat value MEMBER _value WRITE setValue NOTIFY valueChanged USER true) + +public: - QDoubleSpinBox* _x; - QDoubleSpinBox* _y; - QDoubleSpinBox* _z; - glm::vec3 _vector; + QuatEditor(QWidget* parent); + +signals: + + void valueChanged(const glm::quat& value); + +public slots: + + void setValue(const glm::quat& value); + +protected: + + virtual void updateValue(); + +private: + + glm::quat _value; }; typedef QHash ScriptHash; @@ -238,4 +283,4 @@ private: QUrlEditor _urlEditor; }; -#endif /* defined(__interface__MetavoxelUtil__) */ +#endif // hifi_MetavoxelUtil_h diff --git a/libraries/metavoxels/src/ScriptCache.cpp b/libraries/metavoxels/src/ScriptCache.cpp index cabf075563..dd090613b7 100644 --- a/libraries/metavoxels/src/ScriptCache.cpp +++ b/libraries/metavoxels/src/ScriptCache.cpp @@ -1,9 +1,12 @@ // // ScriptCache.cpp -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 2/4/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 diff --git a/libraries/metavoxels/src/ScriptCache.h b/libraries/metavoxels/src/ScriptCache.h index ac53e602f8..f393d0e0a8 100644 --- a/libraries/metavoxels/src/ScriptCache.h +++ b/libraries/metavoxels/src/ScriptCache.h @@ -1,13 +1,16 @@ // // ScriptCache.h -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 2/4/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__ScriptCache__ -#define __interface__ScriptCache__ +#ifndef hifi_ScriptCache_h +#define hifi_ScriptCache_h #include #include @@ -142,4 +145,4 @@ private: ScriptHash _parameters; }; -#endif /* defined(__interface__ScriptCache__) */ +#endif // hifi_ScriptCache_h diff --git a/libraries/metavoxels/src/SharedObject.cpp b/libraries/metavoxels/src/SharedObject.cpp index 36257a740f..b578d70959 100644 --- a/libraries/metavoxels/src/SharedObject.cpp +++ b/libraries/metavoxels/src/SharedObject.cpp @@ -1,9 +1,12 @@ // // SharedObject.cpp -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 2/5/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 @@ -20,18 +23,17 @@ REGISTER_META_OBJECT(SharedObject) SharedObject::SharedObject() : _id(++_lastID), - _remoteID(0), - _referenceCount(0) { + _remoteID(0) { _weakHash.insert(_id, this); } void SharedObject::incrementReferenceCount() { - _referenceCount++; + _referenceCount.ref(); } void SharedObject::decrementReferenceCount() { - if (--_referenceCount == 0) { + if (!_referenceCount.deref()) { _weakHash.remove(_id); delete this; } diff --git a/libraries/metavoxels/src/SharedObject.h b/libraries/metavoxels/src/SharedObject.h index 435127fffd..aba6b86bea 100644 --- a/libraries/metavoxels/src/SharedObject.h +++ b/libraries/metavoxels/src/SharedObject.h @@ -1,14 +1,18 @@ // // SharedObject.h -// metavoxels +// libraries/metavoxels/src // // Created by Andrzej Kapolka on 2/5/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__SharedObject__ -#define __interface__SharedObject__ +#ifndef hifi_SharedObject_h +#define hifi_SharedObject_h +#include #include #include #include @@ -42,7 +46,7 @@ public: void setRemoteID(int remoteID) { _remoteID = remoteID; } - int getReferenceCount() const { return _referenceCount; } + int getReferenceCount() const { return _referenceCount.load(); } void incrementReferenceCount(); void decrementReferenceCount(); @@ -62,7 +66,7 @@ private: int _id; int _remoteID; - int _referenceCount; + QAtomicInt _referenceCount; static int _lastID; static WeakSharedObjectHash _weakHash; @@ -214,4 +218,4 @@ private: SharedObjectPointer _object; }; -#endif /* defined(__interface__SharedObject__) */ +#endif // hifi_SharedObject_h diff --git a/libraries/networking/CMakeLists.txt b/libraries/networking/CMakeLists.txt new file mode 100644 index 0000000000..6d805cae6c --- /dev/null +++ b/libraries/networking/CMakeLists.txt @@ -0,0 +1,29 @@ +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") + +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}) + +# include GLM +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 +if (WIN32) + add_definitions(-Dssize_t=long) +endif () + +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} Qt5::Network "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/shared/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp similarity index 90% rename from libraries/shared/src/AccountManager.cpp rename to libraries/networking/src/AccountManager.cpp index 955ba779d1..d1e4edf2ea 100644 --- a/libraries/shared/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -1,9 +1,12 @@ // // AccountManager.cpp -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 @@ -34,6 +37,16 @@ Q_DECLARE_METATYPE(JSONCallbackParameters) const QString ACCOUNTS_GROUP = "accounts"; +JSONCallbackParameters::JSONCallbackParameters() : + jsonCallbackReceiver(NULL), + jsonCallbackMethod(), + errorCallbackReceiver(NULL), + errorCallbackMethod(), + updateReciever(NULL), + updateSlot() +{ +} + AccountManager::AccountManager() : _authURL(), _networkAccessManager(NULL), @@ -170,18 +183,31 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager:: if (!callbackParams.isEmpty()) { // if we have information for a callback, insert the callbackParams into our local map _pendingCallbackMap.insert(networkReply, callbackParams); + + if (callbackParams.updateReciever && !callbackParams.updateSlot.isEmpty()) { + callbackParams.updateReciever->connect(networkReply, SIGNAL(uploadProgress(qint64, qint64)), + callbackParams.updateSlot.toStdString().c_str()); + } } // if we ended up firing of a request, hook up to it now - connect(networkReply, SIGNAL(finished()), this, SLOT(passSuccessToCallback())); - connect(networkReply, SIGNAL(error(QNetworkReply::NetworkError)), - this, SLOT(passErrorToCallback(QNetworkReply::NetworkError))); + connect(networkReply, SIGNAL(finished()), SLOT(processReply())); } } } -void AccountManager::passSuccessToCallback() { +void AccountManager::processReply() { QNetworkReply* requestReply = reinterpret_cast(sender()); + + if (requestReply->error() == QNetworkReply::NoError) { + passSuccessToCallback(requestReply); + } else { + passErrorToCallback(requestReply); + } + delete requestReply; +} + +void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll()); JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply); @@ -200,17 +226,15 @@ void AccountManager::passSuccessToCallback() { qDebug() << jsonResponse; } } - delete requestReply; } -void AccountManager::passErrorToCallback(QNetworkReply::NetworkError errorCode) { - QNetworkReply* requestReply = reinterpret_cast(sender()); +void AccountManager::passErrorToCallback(QNetworkReply* requestReply) { JSONCallbackParameters callbackParams = _pendingCallbackMap.value(requestReply); if (callbackParams.errorCallbackReceiver) { // invoke the right method on the callback receiver QMetaObject::invokeMethod(callbackParams.errorCallbackReceiver, qPrintable(callbackParams.errorCallbackMethod), - Q_ARG(QNetworkReply::NetworkError, errorCode), + Q_ARG(QNetworkReply::NetworkError, requestReply->error()), Q_ARG(const QString&, requestReply->errorString())); // remove the related reply-callback group from the map @@ -218,10 +242,9 @@ void AccountManager::passErrorToCallback(QNetworkReply::NetworkError errorCode) } else { if (VERBOSE_HTTP_REQUEST_DEBUGGING) { qDebug() << "Received error response from data-server that has no matching callback."; - qDebug() << "Error" << errorCode << "-" << requestReply->errorString(); + qDebug() << "Error" << requestReply->error() << "-" << requestReply->errorString(); } } - delete requestReply; } bool AccountManager::hasValidAccessToken() { diff --git a/libraries/shared/src/AccountManager.h b/libraries/networking/src/AccountManager.h similarity index 83% rename from libraries/shared/src/AccountManager.h rename to libraries/networking/src/AccountManager.h index bfe84f392e..cb76786f4e 100644 --- a/libraries/shared/src/AccountManager.h +++ b/libraries/networking/src/AccountManager.h @@ -1,13 +1,16 @@ // // AccountManager.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__AccountManager__ -#define __hifi__AccountManager__ +#ifndef hifi_AccountManager_h +#define hifi_AccountManager_h #include #include @@ -19,9 +22,7 @@ class JSONCallbackParameters { public: - JSONCallbackParameters() : - jsonCallbackReceiver(NULL), jsonCallbackMethod(), - errorCallbackReceiver(NULL), errorCallbackMethod() {}; + JSONCallbackParameters(); bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; } @@ -29,6 +30,8 @@ public: QString jsonCallbackMethod; QObject* errorCallbackReceiver; QString errorCallbackMethod; + QObject* updateReciever; + QString updateSlot; }; class AccountManager : public QObject { @@ -70,13 +73,15 @@ signals: void loginComplete(const QUrl& authURL); void logoutComplete(); private slots: - void passSuccessToCallback(); - void passErrorToCallback(QNetworkReply::NetworkError errorCode); + void processReply(); private: AccountManager(); AccountManager(AccountManager const& other); // not implemented void operator=(AccountManager const& other); // not implemented + void passSuccessToCallback(QNetworkReply* reply); + void passErrorToCallback(QNetworkReply* reply); + Q_INVOKABLE void invokedRequest(const QString& path, QNetworkAccessManager::Operation operation, const JSONCallbackParameters& callbackParams, const QByteArray& dataByteArray, @@ -89,4 +94,4 @@ private: DataServerAccountInfo _accountInfo; }; -#endif /* defined(__hifi__AccountManager__) */ +#endif // hifi_AccountManager_h diff --git a/libraries/shared/src/Assignment.cpp b/libraries/networking/src/Assignment.cpp similarity index 95% rename from libraries/shared/src/Assignment.cpp rename to libraries/networking/src/Assignment.cpp index 992ec96a67..dd318aad8e 100644 --- a/libraries/shared/src/Assignment.cpp +++ b/libraries/networking/src/Assignment.cpp @@ -1,9 +1,12 @@ // // Assignment.cpp -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 8/22/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 "PacketHeaders.h" diff --git a/libraries/shared/src/Assignment.h b/libraries/networking/src/Assignment.h similarity index 91% rename from libraries/shared/src/Assignment.h rename to libraries/networking/src/Assignment.h index a8ab3be4a8..f0f7e8db1a 100644 --- a/libraries/shared/src/Assignment.h +++ b/libraries/networking/src/Assignment.h @@ -1,19 +1,16 @@ // // Assignment.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 8/22/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__Assignment__ -#define __hifi__Assignment__ - -#ifdef _WIN32 -#include "Systime.h" -#else -#include -#endif +#ifndef hifi_Assignment_h +#define hifi_Assignment_h #include @@ -98,4 +95,4 @@ protected: QByteArray _payload; /// an optional payload attached to this assignment, a maximum for 1024 bytes will be packed }; -#endif /* defined(__hifi__Assignment__) */ +#endif // hifi_Assignment_h diff --git a/libraries/networking/src/DTLSClientSession.cpp b/libraries/networking/src/DTLSClientSession.cpp new file mode 100644 index 0000000000..72384fbd10 --- /dev/null +++ b/libraries/networking/src/DTLSClientSession.cpp @@ -0,0 +1,83 @@ +// +// 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(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(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); +} \ No newline at end of file diff --git a/libraries/networking/src/DTLSClientSession.h b/libraries/networking/src/DTLSClientSession.h new file mode 100644 index 0000000000..eeca1aaa68 --- /dev/null +++ b/libraries/networking/src/DTLSClientSession.h @@ -0,0 +1,30 @@ +// +// 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 diff --git a/libraries/networking/src/DTLSSession.cpp b/libraries/networking/src/DTLSSession.cpp new file mode 100644 index 0000000000..7d375ec327 --- /dev/null +++ b/libraries/networking/src/DTLSSession.cpp @@ -0,0 +1,145 @@ +// +// 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 + +#include "NodeList.h" +#include "DTLSSession.h" + +int DTLSSession::socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms) { + DTLSSession* session = static_cast(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(ptr); + QUdpSocket& dtlsSocket = session->_dtlsSocket; + + HifiSockAddr pulledSockAddr; + qint64 bytesReceived = dtlsSocket.readDatagram(reinterpret_cast(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-----" + "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-----"; + + 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()); +} \ No newline at end of file diff --git a/libraries/networking/src/DTLSSession.h b/libraries/networking/src/DTLSSession.h new file mode 100644 index 0000000000..9e9542e147 --- /dev/null +++ b/libraries/networking/src/DTLSSession.h @@ -0,0 +1,44 @@ +// +// DTLSSession.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_DTLSSession_h +#define hifi_DTLSSession_h + +#include + +#include + +#include "DummyDTLSSession.h" +#include "HifiSockAddr.h" + +class DTLSSession : public DummyDTLSSession { + Q_OBJECT +public: + DTLSSession(int end, QUdpSocket& dtlsSocket, HifiSockAddr& destinationSocket); + ~DTLSSession(); + + static int socketPullTimeout(gnutls_transport_ptr_t ptr, unsigned int ms); + static ssize_t socketPull(gnutls_transport_ptr_t ptr, void* buffer, size_t size); + + static gnutls_datum_t* highFidelityCADatum(); + + qint64 writeDatagram(const QByteArray& datagram); + + gnutls_session_t* getGnuTLSSession() { return &_gnutlsSession; } + + bool completedHandshake() const { return _completedHandshake; } + void setCompletedHandshake(bool completedHandshake); +protected: + gnutls_session_t _gnutlsSession; + bool _completedHandshake; +}; + +#endif // hifi_DTLSSession_h diff --git a/libraries/shared/src/DataServerAccountInfo.cpp b/libraries/networking/src/DataServerAccountInfo.cpp similarity index 89% rename from libraries/shared/src/DataServerAccountInfo.cpp rename to libraries/networking/src/DataServerAccountInfo.cpp index 1884517515..87d3b694a7 100644 --- a/libraries/shared/src/DataServerAccountInfo.cpp +++ b/libraries/networking/src/DataServerAccountInfo.cpp @@ -1,9 +1,12 @@ // // DataServerAccountInfo.cpp -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 diff --git a/libraries/shared/src/DataServerAccountInfo.h b/libraries/networking/src/DataServerAccountInfo.h similarity index 76% rename from libraries/shared/src/DataServerAccountInfo.h rename to libraries/networking/src/DataServerAccountInfo.h index c840468319..21380c0855 100644 --- a/libraries/shared/src/DataServerAccountInfo.h +++ b/libraries/networking/src/DataServerAccountInfo.h @@ -1,13 +1,16 @@ // // DataServerAccountInfo.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/21/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__DataServerAccountInfo__ -#define __hifi__DataServerAccountInfo__ +#ifndef hifi_DataServerAccountInfo_h +#define hifi_DataServerAccountInfo_h #include @@ -39,4 +42,4 @@ private: QString _xmppPassword; }; -#endif /* defined(__hifi__DataServerAccountInfo__) */ +#endif // hifi_DataServerAccountInfo_h diff --git a/libraries/networking/src/DomainHandler.cpp b/libraries/networking/src/DomainHandler.cpp new file mode 100644 index 0000000000..6c2fb0274a --- /dev/null +++ b/libraries/networking/src/DomainHandler.cpp @@ -0,0 +1,183 @@ +// +// DomainHandler.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/18/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 +// + +#include + +#include "NodeList.h" +#include "PacketHeaders.h" + +#include "DomainHandler.h" + +DomainHandler::DomainHandler(QObject* parent) : + QObject(parent), + _uuid(), + _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), + _assignmentUUID(), + _isConnected(false), + _dtlsSession(NULL), + _handshakeTimer(NULL) +{ + +} + +DomainHandler::~DomainHandler() { + delete _dtlsSession; +} + +void DomainHandler::clearConnectionInfo() { + _uuid = QUuid(); + _isConnected = false; + + if (_handshakeTimer) { + _handshakeTimer->stop(); + delete _handshakeTimer; + _handshakeTimer = NULL; + } + + delete _dtlsSession; + _dtlsSession = NULL; +} + +void DomainHandler::reset() { + clearConnectionInfo(); + _hostname = QString(); + _sockAddr.setAddress(QHostAddress::Null); + + delete _dtlsSession; + _dtlsSession = NULL; +} + +const unsigned int DTLS_HANDSHAKE_INTERVAL_MSECS = 100; + +void DomainHandler::initializeDTLSSession() { + if (!_dtlsSession) { + _dtlsSession = new DTLSClientSession(NodeList::getInstance()->getDTLSSocket(), _sockAddr); + + gnutls_session_set_ptr(*_dtlsSession->getGnuTLSSession(), this); + + // start a timer to complete the handshake process + _handshakeTimer = new QTimer(this); + connect(_handshakeTimer, &QTimer::timeout, this, &DomainHandler::completeDTLSHandshake); + + // start the handshake right now + completeDTLSHandshake(); + + // start the timer to finish off the handshake + _handshakeTimer->start(DTLS_HANDSHAKE_INTERVAL_MSECS); + } +} + +void DomainHandler::setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname) { + if (_sockAddr != sockAddr) { + // we should reset on a sockAddr change + reset(); + // change the sockAddr + _sockAddr = sockAddr; + } + + // some callers may pass a hostname, this is not to be used for lookup but for DTLS certificate verification + _hostname = hostname; +} + +void DomainHandler::setHostname(const QString& hostname) { + + if (hostname != _hostname) { + // re-set the domain info so that auth information is reloaded + reset(); + + int colonIndex = hostname.indexOf(':'); + + if (colonIndex > 0) { + // the user has included a custom DS port with the hostname + + // the new hostname is everything up to the colon + _hostname = hostname.left(colonIndex); + + // grab the port by reading the string after the colon + _sockAddr.setPort(atoi(hostname.mid(colonIndex + 1, hostname.size()).toLocal8Bit().constData())); + + qDebug() << "Updated hostname to" << _hostname << "and port to" << _sockAddr.getPort(); + + } else { + // no port included with the hostname, simply set the member variable and reset the domain server port to default + _hostname = hostname; + _sockAddr.setPort(DEFAULT_DOMAIN_SERVER_PORT); + } + + // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname + qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); + QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); + + emit hostnameChanged(_hostname); + } +} + +void DomainHandler::completeDTLSHandshake() { + int handshakeReturn = gnutls_handshake(*_dtlsSession->getGnuTLSSession()); + + if (handshakeReturn == 0) { + // we've shaken hands, so we're good to go now + _dtlsSession->setCompletedHandshake(true); + + _handshakeTimer->stop(); + delete _handshakeTimer; + _handshakeTimer = NULL; + + // emit a signal so NodeList can handle incoming DTLS packets + emit completedDTLSHandshake(); + + } 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 DS at" + << qPrintable((_hostname.isEmpty() ? _sockAddr.getAddress().toString() : _hostname)); + + clearConnectionInfo(); + } +} + +void DomainHandler::completedHostnameLookup(const QHostInfo& hostInfo) { + for (int i = 0; i < hostInfo.addresses().size(); i++) { + if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { + _sockAddr.setAddress(hostInfo.addresses()[i]); + qDebug("DS at %s is at %s", _hostname.toLocal8Bit().constData(), + _sockAddr.getAddress().toString().toLocal8Bit().constData()); + return; + } + } + + // if we got here then we failed to lookup the address + qDebug("Failed domain server lookup"); +} + +void DomainHandler::setIsConnected(bool isConnected) { + if (_isConnected != isConnected) { + _isConnected = isConnected; + + if (_isConnected) { + emit connectedToDomain(_hostname); + } + } +} + +void DomainHandler::parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket) { + // figure out the port that the DS wants us to use for us to talk to them with DTLS + int numBytesPacketHeader = numBytesForPacketHeader(dtlsRequirementPacket); + + unsigned short dtlsPort = 0; + memcpy(&dtlsPort, dtlsRequirementPacket.data() + numBytesPacketHeader, sizeof(dtlsPort)); + + qDebug() << "domain-server DTLS port changed to" << dtlsPort << "- Enabling DTLS."; + + _sockAddr.setPort(dtlsPort); + + initializeDTLSSession(); +} \ No newline at end of file diff --git a/libraries/shared/src/DomainInfo.h b/libraries/networking/src/DomainHandler.h similarity index 60% rename from libraries/shared/src/DomainInfo.h rename to libraries/networking/src/DomainHandler.h index da65525f9d..2cc520991c 100644 --- a/libraries/shared/src/DomainInfo.h +++ b/libraries/networking/src/DomainHandler.h @@ -1,33 +1,38 @@ // -// DomainInfo.h -// hifi +// DomainHandler.h +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__DomainInfo__ -#define __hifi__DomainInfo__ +#ifndef hifi_DomainHandler_h +#define hifi_DomainHandler_h #include +#include #include #include #include +#include "DTLSClientSession.h" #include "HifiSockAddr.h" const QString DEFAULT_DOMAIN_HOSTNAME = "alpha.highfidelity.io"; const unsigned short DEFAULT_DOMAIN_SERVER_PORT = 40102; +const unsigned short DEFAULT_DOMAIN_SERVER_DTLS_PORT = 40103; -class DomainInfo : public QObject { +class DomainHandler : public QObject { Q_OBJECT public: - DomainInfo(); + DomainHandler(QObject* parent = 0); + ~DomainHandler(); void clearConnectionInfo(); - void parseAuthInformationFromJsonObject(const QJsonObject& jsonObject); - const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } @@ -38,43 +43,40 @@ public: void setIPToLocalhost() { _sockAddr.setAddress(QHostAddress(QHostAddress::LocalHost)); } const HifiSockAddr& getSockAddr() { return _sockAddr; } - void setSockAddr(const HifiSockAddr& sockAddr); + void setSockAddr(const HifiSockAddr& sockAddr, const QString& hostname); unsigned short getPort() const { return _sockAddr.getPort(); } const QUuid& getAssignmentUUID() const { return _assignmentUUID; } void setAssignmentUUID(const QUuid& assignmentUUID) { _assignmentUUID = assignmentUUID; } - const QUuid& getConnectionSecret() const { return _connectionSecret; } - void setConnectionSecret(const QUuid& connectionSecret) { _connectionSecret = connectionSecret; } - - const QByteArray& getRegistrationToken() const { return _registrationToken; } - - const QUrl& getRootAuthenticationURL() const { return _rootAuthenticationURL; } - void setRootAuthenticationURL(const QUrl& rootAuthenticationURL) { _rootAuthenticationURL = rootAuthenticationURL; } - bool isConnected() const { return _isConnected; } void setIsConnected(bool isConnected); -private slots: - void completedHostnameLookup(const QHostInfo& hostInfo); + DTLSClientSession* getDTLSSession() { return _dtlsSession; } - void logout(); + void parseDTLSRequirementPacket(const QByteArray& dtlsRequirementPacket); + +private slots: + void completeDTLSHandshake(); + void completedHostnameLookup(const QHostInfo& hostInfo); signals: void hostnameChanged(const QString& hostname); void connectedToDomain(const QString& hostname); + void completedDTLSHandshake(); + void DTLSConnectionLost(); + private: void reset(); + void initializeDTLSSession(); QUuid _uuid; QString _hostname; HifiSockAddr _sockAddr; QUuid _assignmentUUID; - QUuid _connectionSecret; - QByteArray _registrationToken; - QUrl _rootAuthenticationURL; - QString _publicKey; bool _isConnected; + DTLSClientSession* _dtlsSession; + QTimer* _handshakeTimer; }; -#endif /* defined(__hifi__DomainInfo__) */ +#endif // hifi_DomainHandler_h diff --git a/libraries/networking/src/DummyDTLSSession.cpp b/libraries/networking/src/DummyDTLSSession.cpp new file mode 100644 index 0000000000..a42e3f1599 --- /dev/null +++ b/libraries/networking/src/DummyDTLSSession.cpp @@ -0,0 +1,31 @@ +// +// DummyDTLSSession.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-04. +// 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 "DummyDTLSSession.h" + +ssize_t DummyDTLSSession::socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size) { + DummyDTLSSession* session = static_cast(ptr); + QUdpSocket& dtlsSocket = session->_dtlsSocket; + +#if DTLS_VERBOSE_DEBUG + qDebug() << "Pushing a message of size" << size << "to" << session->_destinationSocket; +#endif + + return dtlsSocket.writeDatagram(reinterpret_cast(buffer), size, + session->_destinationSocket.getAddress(), session->_destinationSocket.getPort()); +} + +DummyDTLSSession::DummyDTLSSession(QUdpSocket& dtlsSocket, const HifiSockAddr& destinationSocket) : + _dtlsSocket(dtlsSocket), + _destinationSocket(destinationSocket) +{ + +} \ No newline at end of file diff --git a/libraries/networking/src/DummyDTLSSession.h b/libraries/networking/src/DummyDTLSSession.h new file mode 100644 index 0000000000..352b2c23e2 --- /dev/null +++ b/libraries/networking/src/DummyDTLSSession.h @@ -0,0 +1,34 @@ +// +// DummyDTLSSession.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2014-04-04. +// 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_DummyDTLSSession_h +#define hifi_DummyDTLSSession_h + +#include + +#include + +#include "HifiSockAddr.h" + +#define DTLS_VERBOSE_DEBUG 0 + +class DummyDTLSSession : public QObject { + Q_OBJECT +public: + DummyDTLSSession(QUdpSocket& dtlsSocket, const HifiSockAddr& destinationSocket); + + static ssize_t socketPush(gnutls_transport_ptr_t ptr, const void* buffer, size_t size); +protected: + QUdpSocket& _dtlsSocket; + HifiSockAddr _destinationSocket; +}; + +#endif // hifi_DummyDTLSSession_h diff --git a/libraries/shared/src/HifiSockAddr.cpp b/libraries/networking/src/HifiSockAddr.cpp similarity index 71% rename from libraries/shared/src/HifiSockAddr.cpp rename to libraries/networking/src/HifiSockAddr.cpp index cc031525d8..d30f7944d7 100644 --- a/libraries/shared/src/HifiSockAddr.cpp +++ b/libraries/networking/src/HifiSockAddr.cpp @@ -1,17 +1,20 @@ // // HifiSockAddr.cpp -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 11/26/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 "HifiSockAddr.h" #include #include #include +#include "HifiSockAddr.h" + static int hifiSockAddrMetaTypeId = qMetaTypeId(); HifiSockAddr::HifiSockAddr() : @@ -44,6 +47,16 @@ HifiSockAddr::HifiSockAddr(const QString& hostname, quint16 hostOrderPort) { } } +HifiSockAddr::HifiSockAddr(const sockaddr* sockaddr) { + _address = QHostAddress(sockaddr); + + if (sockaddr->sa_family == AF_INET) { + _port = ntohs(reinterpret_cast(sockaddr)->sin_port); + } else { + _port = ntohs(reinterpret_cast(sockaddr)->sin6_port); + } +} + HifiSockAddr& HifiSockAddr::operator=(const HifiSockAddr& rhsSockAddr) { _address = rhsSockAddr._address; _port = rhsSockAddr._port; @@ -82,13 +95,13 @@ quint32 getHostOrderLocalAddress() { static int localAddress = 0; if (localAddress == 0) { - foreach(const QNetworkInterface &interface, QNetworkInterface::allInterfaces()) { - if (interface.flags() & QNetworkInterface::IsUp - && interface.flags() & QNetworkInterface::IsRunning - && interface.flags() & ~QNetworkInterface::IsLoopBack) { + foreach(const QNetworkInterface &networkInterface, QNetworkInterface::allInterfaces()) { + if (networkInterface.flags() & QNetworkInterface::IsUp + && networkInterface.flags() & QNetworkInterface::IsRunning + && networkInterface.flags() & ~QNetworkInterface::IsLoopBack) { // we've decided that this is the active NIC // enumerate it's addresses to grab the IPv4 address - foreach(const QNetworkAddressEntry &entry, interface.addressEntries()) { + foreach(const QNetworkAddressEntry &entry, networkInterface.addressEntries()) { // make sure it's an IPv4 address that isn't the loopback if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol && !entry.ip().isLoopback()) { qDebug("Node's local address is %s", entry.ip().toString().toLocal8Bit().constData()); @@ -109,3 +122,8 @@ quint32 getHostOrderLocalAddress() { // return the looked up local address return localAddress; } + +uint qHash(const HifiSockAddr& key, uint seed) { + // use the existing QHostAddress and quint16 hash functions to get our hash + return qHash(key.getAddress(), seed) + qHash(key.getPort(), seed); +} diff --git a/libraries/shared/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h similarity index 76% rename from libraries/shared/src/HifiSockAddr.h rename to libraries/networking/src/HifiSockAddr.h index e8f928c36d..8a591a60b8 100644 --- a/libraries/shared/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -1,13 +1,23 @@ // // HifiSockAddr.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 11/26/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__HifiSockAddr__ -#define __hifi__HifiSockAddr__ +#ifndef hifi_HifiSockAddr_h +#define hifi_HifiSockAddr_h + +#ifdef WIN32 +#include +#include +#else +#include +#endif #include @@ -17,6 +27,7 @@ public: HifiSockAddr(const QHostAddress& address, quint16 port); HifiSockAddr(const HifiSockAddr& otherSockAddr); HifiSockAddr(const QString& hostname, quint16 hostOrderPort); + HifiSockAddr(const sockaddr* sockaddr); bool isNull() const { return _address.isNull() && _port == 0; } @@ -45,8 +56,10 @@ private: quint16 _port; }; +uint qHash(const HifiSockAddr& key, uint seed); + quint32 getHostOrderLocalAddress(); Q_DECLARE_METATYPE(HifiSockAddr) -#endif /* defined(__hifi__HifiSockAddr__) */ +#endif // hifi_HifiSockAddr_h diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp new file mode 100644 index 0000000000..ce78ec2d10 --- /dev/null +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -0,0 +1,486 @@ +// +// LimitedNodeList.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 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 +#include +#include + +#include +#include +#include +#include +#include + +#include "AccountManager.h" +#include "Assignment.h" +#include "HifiSockAddr.h" +#include "Logging.h" +#include "LimitedNodeList.h" +#include "PacketHeaders.h" +#include "SharedUtil.h" +#include "UUID.h" + +const char SOLO_NODE_TYPES[2] = { + NodeType::AvatarMixer, + NodeType::AudioMixer +}; + +const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data-web.highfidelity.io"); + +LimitedNodeList* LimitedNodeList::_sharedInstance = NULL; + +LimitedNodeList* LimitedNodeList::createInstance(unsigned short socketListenPort, unsigned short dtlsPort) { + if (!_sharedInstance) { + NodeType::init(); + + _sharedInstance = new LimitedNodeList(socketListenPort, dtlsPort); + + // register the SharedNodePointer meta-type for signals/slots + qRegisterMetaType(); + } else { + qDebug("LimitedNodeList createInstance called with existing instance."); + } + + return _sharedInstance; +} + +LimitedNodeList* LimitedNodeList::getInstance() { + if (!_sharedInstance) { + qDebug("LimitedNodeList getInstance called before call to createInstance. Returning NULL pointer."); + } + + return _sharedInstance; +} + + +LimitedNodeList::LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort) : + _sessionUUID(), + _nodeHash(), + _nodeHashMutex(QMutex::Recursive), + _nodeSocket(this), + _dtlsSocket(NULL), + _numCollectedPackets(0), + _numCollectedBytes(0), + _packetStatTimer() +{ + _nodeSocket.bind(QHostAddress::AnyIPv4, socketListenPort); + qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort(); + + if (dtlsListenPort > 0) { + // only create the DTLS socket during constructor if a custom port is passed + _dtlsSocket = new QUdpSocket(this); + + _dtlsSocket->bind(QHostAddress::AnyIPv4, dtlsListenPort); + qDebug() << "NodeList DTLS socket is listening on" << _dtlsSocket->localPort(); + } + + const int LARGER_SNDBUF_SIZE = 1048576; + changeSendSocketBufferSize(LARGER_SNDBUF_SIZE); + + _packetStatTimer.start(); +} + +void LimitedNodeList::setSessionUUID(const QUuid& sessionUUID) { + QUuid oldUUID = _sessionUUID; + _sessionUUID = sessionUUID; + + if (sessionUUID != oldUUID) { + qDebug() << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID) + << "to" << uuidStringWithoutCurlyBraces(_sessionUUID); + emit uuidChanged(sessionUUID); + } +} + +QUdpSocket& LimitedNodeList::getDTLSSocket() { + if (!_dtlsSocket) { + // DTLS socket getter called but no DTLS socket exists, create it now + _dtlsSocket = new QUdpSocket(this); + + _dtlsSocket->bind(QHostAddress::AnyIPv4, 0, QAbstractSocket::DontShareAddress); + +#if defined(IP_DONTFRAG) || defined(IP_MTU_DISCOVER) + qDebug() << "Making required DTLS changes to LimitedNodeList DTLS socket."; + + int socketHandle = _dtlsSocket->socketDescriptor(); +#if defined(IP_DONTFRAG) + int optValue = 1; + setsockopt(socketHandle, IPPROTO_IP, IP_DONTFRAG, reinterpret_cast(&optValue), sizeof(optValue)); +#elif defined(IP_MTU_DISCOVER) + int optValue = 1; + setsockopt(socketHandle, IPPROTO_IP, IP_MTU_DISCOVER, reinterpret_cast(&optValue), sizeof(optValue)); +#endif +#endif + + qDebug() << "LimitedNodeList DTLS socket is listening on" << _dtlsSocket->localPort(); + } + + return *_dtlsSocket; +} + +void LimitedNodeList::changeSendSocketBufferSize(int numSendBytes) { + // change the socket send buffer size to be 1MB + int oldBufferSize = 0; + +#ifdef Q_OS_WIN + int sizeOfInt = sizeof(oldBufferSize); +#else + unsigned int sizeOfInt = sizeof(oldBufferSize); +#endif + + getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&oldBufferSize), &sizeOfInt); + + setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&numSendBytes), + sizeof(numSendBytes)); + + int newBufferSize = 0; + getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&newBufferSize), &sizeOfInt); + + qDebug() << "Changed socket send buffer size from" << oldBufferSize << "to" << newBufferSize << "bytes"; +} + +bool LimitedNodeList::packetVersionAndHashMatch(const QByteArray& packet) { + PacketType checkType = packetTypeForPacket(packet); + if (packet[1] != versionForPacketType(checkType) + && checkType != PacketTypeStunResponse) { + PacketType mismatchType = packetTypeForPacket(packet); + int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data()); + + static QMultiMap versionDebugSuppressMap; + + QUuid senderUUID = uuidFromPacketHeader(packet); + if (!versionDebugSuppressMap.contains(senderUUID, checkType)) { + qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender" + << uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but" + << qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected."; + + versionDebugSuppressMap.insert(senderUUID, checkType); + } + + return false; + } + + if (!NON_VERIFIED_PACKETS.contains(checkType)) { + // figure out which node this is from + SharedNodePointer sendingNode = sendingNodeForPacket(packet); + if (sendingNode) { + // check if the md5 hash in the header matches the hash we would expect + if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) { + return true; + } else { + qDebug() << "Packet hash mismatch on" << checkType << "- Sender" + << uuidFromPacketHeader(packet); + } + } else { + qDebug() << "Packet of type" << checkType << "received from unknown node with UUID" + << uuidFromPacketHeader(packet); + } + } else { + return true; + } + + return false; +} + +qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr, + const QUuid& connectionSecret) { + QByteArray datagramCopy = datagram; + + if (!connectionSecret.isNull()) { + // setup the MD5 hash for source verification in the header + replaceHashInPacketGivenConnectionUUID(datagramCopy, connectionSecret); + } + + // stat collection for packets + ++_numCollectedPackets; + _numCollectedBytes += datagram.size(); + + qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, + destinationSockAddr.getAddress(), destinationSockAddr.getPort()); + + if (bytesWritten < 0) { + qDebug() << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString(); + } + + return bytesWritten; +} + +qint64 LimitedNodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + if (destinationNode) { + // if we don't have an ovveriden address, assume they want to send to the node's active socket + const HifiSockAddr* destinationSockAddr = &overridenSockAddr; + if (overridenSockAddr.isNull()) { + if (destinationNode->getActiveSocket()) { + // use the node's active socket as the destination socket + destinationSockAddr = destinationNode->getActiveSocket(); + } else { + // we don't have a socket to send to, return 0 + return 0; + } + } + + return writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret()); + } + + // didn't have a destinationNode to send to, return 0 + return 0; +} + +qint64 LimitedNodeList::writeUnverifiedDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + if (destinationNode) { + // if we don't have an ovveriden address, assume they want to send to the node's active socket + const HifiSockAddr* destinationSockAddr = &overridenSockAddr; + if (overridenSockAddr.isNull()) { + if (destinationNode->getActiveSocket()) { + // use the node's active socket as the destination socket + destinationSockAddr = destinationNode->getActiveSocket(); + } else { + // we don't have a socket to send to, return 0 + return 0; + } + } + + // don't use the node secret! + return writeDatagram(datagram, *destinationSockAddr, QUuid()); + } + + // didn't have a destinationNode to send to, return 0 + return 0; +} + +qint64 LimitedNodeList::writeUnverifiedDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr) { + return writeDatagram(datagram, destinationSockAddr, QUuid()); +} + +qint64 LimitedNodeList::writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); +} + +qint64 LimitedNodeList::writeUnverifiedDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr) { + return writeUnverifiedDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); +} + +void LimitedNodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { + // the node decided not to do anything with this packet + // if it comes from a known source we should keep that node alive + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + if (matchingNode) { + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + } +} + +int LimitedNodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray &packet) { + QMutexLocker locker(&matchingNode->getMutex()); + + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + matchingNode->recordBytesReceived(packet.size()); + + if (!matchingNode->getLinkedData() && linkedDataCreateCallback) { + linkedDataCreateCallback(matchingNode.data()); + } + + QMutexLocker linkedDataLocker(&matchingNode->getLinkedData()->getMutex()); + + return matchingNode->getLinkedData()->parseData(packet); +} + +int LimitedNodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + + if (matchingNode) { + updateNodeWithDataFromPacket(matchingNode, packet); + } + + // we weren't able to match the sender address to the address we have for this node, unlock and don't parse + return 0; +} + +SharedNodePointer LimitedNodeList::nodeWithUUID(const QUuid& nodeUUID, bool blockingLock) { + const int WAIT_TIME = 10; // wait up to 10ms in the try lock case + SharedNodePointer node; + // if caller wants us to block and guarantee the correct answer, then honor that request + if (blockingLock) { + // this will block till we can get access + QMutexLocker locker(&_nodeHashMutex); + node = _nodeHash.value(nodeUUID); + } else if (_nodeHashMutex.tryLock(WAIT_TIME)) { // some callers are willing to get wrong answers but not block + node = _nodeHash.value(nodeUUID); + _nodeHashMutex.unlock(); + } + return node; + } + +SharedNodePointer LimitedNodeList::sendingNodeForPacket(const QByteArray& packet) { + QUuid nodeUUID = uuidFromPacketHeader(packet); + + // return the matching node, or NULL if there is no match + return nodeWithUUID(nodeUUID); +} + +NodeHash LimitedNodeList::getNodeHash() { + QMutexLocker locker(&_nodeHashMutex); + return NodeHash(_nodeHash); +} + +void LimitedNodeList::eraseAllNodes() { + qDebug() << "Clearing the NodeList. Deleting all nodes in list."; + + QMutexLocker locker(&_nodeHashMutex); + + NodeHash::iterator nodeItem = _nodeHash.begin(); + + // iterate the nodes in the list + while (nodeItem != _nodeHash.end()) { + nodeItem = killNodeAtHashIterator(nodeItem); + } +} + +void LimitedNodeList::reset() { + eraseAllNodes(); +} + +void LimitedNodeList::killNodeWithUUID(const QUuid& nodeUUID) { + QMutexLocker locker(&_nodeHashMutex); + + NodeHash::iterator nodeItemToKill = _nodeHash.find(nodeUUID); + if (nodeItemToKill != _nodeHash.end()) { + killNodeAtHashIterator(nodeItemToKill); + } +} + +NodeHash::iterator LimitedNodeList::killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill) { + qDebug() << "Killed" << *nodeItemToKill.value(); + emit nodeKilled(nodeItemToKill.value()); + return _nodeHash.erase(nodeItemToKill); +} + +void LimitedNodeList::processKillNode(const QByteArray& dataByteArray) { + // read the node id + QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader(dataByteArray), NUM_BYTES_RFC4122_UUID)); + + // kill the node with this UUID, if it exists + killNodeWithUUID(nodeUUID); +} + +SharedNodePointer LimitedNodeList::addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { + _nodeHashMutex.lock(); + + if (!_nodeHash.contains(uuid)) { + + // we didn't have this node, so add them + Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); + SharedNodePointer newNodeSharedPointer(newNode, &QObject::deleteLater); + + _nodeHash.insert(newNode->getUUID(), newNodeSharedPointer); + + _nodeHashMutex.unlock(); + + qDebug() << "Added" << *newNode; + + emit nodeAdded(newNodeSharedPointer); + + return newNodeSharedPointer; + } else { + _nodeHashMutex.unlock(); + + return updateSocketsForNode(uuid, publicSocket, localSocket); + } +} + +SharedNodePointer LimitedNodeList::updateSocketsForNode(const QUuid& uuid, + const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { + + SharedNodePointer matchingNode = nodeWithUUID(uuid); + + if (matchingNode) { + // perform appropriate updates to this node + QMutexLocker locker(&matchingNode->getMutex()); + + // check if we need to change this node's public or local sockets + if (publicSocket != matchingNode->getPublicSocket()) { + matchingNode->setPublicSocket(publicSocket); + qDebug() << "Public socket change for node" << *matchingNode; + } + + if (localSocket != matchingNode->getLocalSocket()) { + matchingNode->setLocalSocket(localSocket); + qDebug() << "Local socket change for node" << *matchingNode; + } + } + + return matchingNode; +} + +unsigned LimitedNodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) { + unsigned n = 0; + + foreach (const SharedNodePointer& node, getNodeHash()) { + // only send to the NodeTypes we are asked to send to. + if (destinationNodeTypes.contains(node->getType())) { + writeDatagram(packet, node); + ++n; + } + } + + return n; +} + +SharedNodePointer LimitedNodeList::soloNodeOfType(char nodeType) { + + if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { + foreach (const SharedNodePointer& node, getNodeHash()) { + if (node->getType() == nodeType) { + return node; + } + } + } + return SharedNodePointer(); +} + +void LimitedNodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) { + packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f); + bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f); +} + +void LimitedNodeList::resetPacketStats() { + _numCollectedPackets = 0; + _numCollectedBytes = 0; + _packetStatTimer.restart(); +} + +void LimitedNodeList::removeSilentNodes() { + + _nodeHashMutex.lock(); + + NodeHash::iterator nodeItem = _nodeHash.begin(); + + while (nodeItem != _nodeHash.end()) { + SharedNodePointer node = nodeItem.value(); + + node->getMutex().lock(); + + if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > (NODE_SILENCE_THRESHOLD_MSECS * 1000)) { + // call our private method to kill this node (removes it and emits the right signal) + nodeItem = killNodeAtHashIterator(nodeItem); + } else { + // we didn't kill this node, push the iterator forwards + ++nodeItem; + } + + node->getMutex().unlock(); + } + + _nodeHashMutex.unlock(); +} diff --git a/libraries/shared/src/NodeList.h b/libraries/networking/src/LimitedNodeList.h similarity index 50% rename from libraries/shared/src/NodeList.h rename to libraries/networking/src/LimitedNodeList.h index 34078b6a94..2a98dc536f 100644 --- a/libraries/shared/src/NodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -1,19 +1,17 @@ // -// NodeList.h -// hifi +// LimitedNodeList.h +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__NodeList__ -#define __hifi__NodeList__ +#ifndef hifi_LimitedNodeList_h +#define hifi_LimitedNodeList_h -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif #include #include @@ -29,12 +27,14 @@ #include #include -#include "DomainInfo.h" +#include + +#include "DomainHandler.h" #include "Node.h" -const quint64 NODE_SILENCE_THRESHOLD_USECS = 2 * 1000 * 1000; -const quint64 DOMAIN_SERVER_CHECK_IN_USECS = 1 * 1000000; -const quint64 PING_INACTIVE_NODE_INTERVAL_USECS = 1 * 1000 * 1000; +const int MAX_PACKET_SIZE = 1500; + +const quint64 NODE_SILENCE_THRESHOLD_MSECS = 2 * 1000; extern const char SOLO_NODE_TYPES[2]; @@ -42,9 +42,6 @@ extern const QUrl DEFAULT_NODE_AUTH_URL; const char DEFAULT_ASSIGNMENT_SERVER_HOSTNAME[] = "localhost"; -const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; - -class Assignment; class HifiSockAddr; typedef QSet NodeSet; @@ -53,61 +50,42 @@ typedef QSharedPointer SharedNodePointer; typedef QHash NodeHash; Q_DECLARE_METATYPE(SharedNodePointer) -typedef quint8 PingType_t; -namespace PingType { - const PingType_t Agnostic = 0; - const PingType_t Local = 1; - const PingType_t Public = 2; - const PingType_t Symmetric = 3; -} - -class NodeList : public QObject { +class LimitedNodeList : public QObject { Q_OBJECT public: - static NodeList* createInstance(char ownerType, unsigned short int socketListenPort = 0); - static NodeList* getInstance(); - NodeType_t getOwnerType() const { return _ownerType; } - void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } + static LimitedNodeList* createInstance(unsigned short socketListenPort = 0, unsigned short dtlsPort = 0); + static LimitedNodeList* getInstance(); const QUuid& getSessionUUID() const { return _sessionUUID; } void setSessionUUID(const QUuid& sessionUUID); - + QUdpSocket& getNodeSocket() { return _nodeSocket; } + QUdpSocket& getDTLSSocket(); bool packetVersionAndHashMatch(const QByteArray& packet); qint64 writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, const HifiSockAddr& overridenSockAddr = HifiSockAddr()); + + qint64 writeUnverifiedDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr = HifiSockAddr()); + + qint64 writeUnverifiedDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr); qint64 writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, const HifiSockAddr& overridenSockAddr = HifiSockAddr()); - qint64 sendStatsToDomainServer(const QJsonObject& statsObject); + + qint64 writeUnverifiedDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, + const HifiSockAddr& overridenSockAddr = HifiSockAddr()); void(*linkedDataCreateCallback)(Node *); NodeHash getNodeHash(); int size() const { return _nodeHash.size(); } - int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; } - DomainInfo& getDomainInfo() { return _domainInfo; } - - const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; } - void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd); - void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes); - void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); } - - int processDomainServerList(const QByteArray& packet); - - void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } - void sendAssignment(Assignment& assignment); - - QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); - QByteArray constructPingReplyPacket(const QByteArray& pingPacket); - void pingPunchForInactiveNode(const SharedNodePointer& node); - SharedNodePointer nodeWithUUID(const QUuid& nodeUUID, bool blockingLock = true); SharedNodePointer sendingNodeForPacket(const QByteArray& packet); - SharedNodePointer addOrUpdateNode(const QUuid& uuid, char nodeType, + SharedNodePointer addOrUpdateNode(const QUuid& uuid, NodeType_t nodeType, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); SharedNodePointer updateSocketsForNode(const QUuid& uuid, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); @@ -123,15 +101,10 @@ public: void getPacketStats(float &packetsPerSecond, float &bytesPerSecond); void resetPacketStats(); - - void loadData(QSettings* settings); - void saveData(QSettings* settings); public slots: void reset(); void eraseAllNodes(); - void sendDomainServerCheckIn(); - void pingInactiveNodes(); void removeSilentNodes(); void killNodeWithUUID(const QUuid& nodeUUID); @@ -139,45 +112,29 @@ signals: void uuidChanged(const QUuid& ownerUUID); void nodeAdded(SharedNodePointer); void nodeKilled(SharedNodePointer); - void limitOfSilentDomainCheckInsReached(); -private slots: - void domainServerAuthReply(const QJsonObject& jsonObject); -private: - static NodeList* _sharedInstance; +protected: + static LimitedNodeList* _sharedInstance; - NodeList(char ownerType, unsigned short int socketListenPort); - NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton - void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton - void sendSTUNRequest(); - void processSTUNResponse(const QByteArray& packet); + LimitedNodeList(unsigned short socketListenPort, unsigned short dtlsListenPort); + LimitedNodeList(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton + void operator=(LimitedNodeList const&); // Don't implement, needed to avoid copies of singleton qint64 writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr, const QUuid& connectionSecret); NodeHash::iterator killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill); - void processDomainServerAuthRequest(const QByteArray& packet); - void requestAuthForDomainServer(); - void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); - void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); void changeSendSocketBufferSize(int numSendBytes); + QUuid _sessionUUID; NodeHash _nodeHash; QMutex _nodeHashMutex; QUdpSocket _nodeSocket; - NodeType_t _ownerType; - NodeSet _nodeTypesOfInterest; - DomainInfo _domainInfo; - QUuid _sessionUUID; - int _numNoReplyDomainCheckIns; - HifiSockAddr _assignmentServerSocket; - HifiSockAddr _publicSockAddr; - bool _hasCompletedInitialSTUNFailure; - unsigned int _stunRequestsSinceSuccess; + QUdpSocket* _dtlsSocket; int _numCollectedPackets; int _numCollectedBytes; QElapsedTimer _packetStatTimer; }; -#endif /* defined(__hifi__NodeList__) */ +#endif // hifi_LimitedNodeList_h diff --git a/libraries/shared/src/Logging.cpp b/libraries/networking/src/Logging.cpp similarity index 91% rename from libraries/shared/src/Logging.cpp rename to libraries/networking/src/Logging.cpp index f0dcd7b67b..f42f1bda58 100644 --- a/libraries/shared/src/Logging.cpp +++ b/libraries/networking/src/Logging.cpp @@ -1,9 +1,12 @@ // // Logging.cpp -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 6/11/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -43,7 +46,7 @@ const HifiSockAddr& Logging::socket() { // use the first IP address _logstashSocket.setAddress(hostInfo.addresses().first()); } else { - printf("Failed to lookup logstash IP - will try again on next log attempt.\n"); + qDebug("Failed to lookup logstash IP - will try again on next log attempt."); } } diff --git a/libraries/shared/src/Logging.h b/libraries/networking/src/Logging.h similarity index 84% rename from libraries/shared/src/Logging.h rename to libraries/networking/src/Logging.h index abd5bfaab1..c52812bd33 100644 --- a/libraries/shared/src/Logging.h +++ b/libraries/networking/src/Logging.h @@ -1,19 +1,16 @@ // // Logging.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 6/11/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__Logging__ -#define __hifi__Logging__ - -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif +#ifndef hifi_Logging_h +#define hifi_Logging_h #include @@ -54,4 +51,4 @@ private: static QString _targetName; }; -#endif /* defined(__hifi__Logstash__) */ +#endif // hifi_Logging_h diff --git a/libraries/shared/src/NetworkPacket.cpp b/libraries/networking/src/NetworkPacket.cpp similarity index 86% rename from libraries/shared/src/NetworkPacket.cpp rename to libraries/networking/src/NetworkPacket.cpp index 1671dc2435..bf18aa9b37 100644 --- a/libraries/shared/src/NetworkPacket.cpp +++ b/libraries/networking/src/NetworkPacket.cpp @@ -1,11 +1,12 @@ // // NetworkPacket.cpp -// shared +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/9/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// A really simple class that stores a network packet between being received and being processed +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/libraries/shared/src/NetworkPacket.h b/libraries/networking/src/NetworkPacket.h similarity index 80% rename from libraries/shared/src/NetworkPacket.h rename to libraries/networking/src/NetworkPacket.h index 0875b9f131..94ddf8d56e 100644 --- a/libraries/shared/src/NetworkPacket.h +++ b/libraries/networking/src/NetworkPacket.h @@ -1,15 +1,18 @@ // // NetworkPacket.h -// shared +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/9/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // A really simple class that stores a network packet between being received and being processed // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef __shared_NetworkPacket__ -#define __shared_NetworkPacket__ +#ifndef hifi_NetworkPacket_h +#define hifi_NetworkPacket_h #include @@ -43,4 +46,4 @@ private: QByteArray _byteArray; }; -#endif /* defined(__shared_NetworkPacket__) */ +#endif // hifi_NetworkPacket_h diff --git a/libraries/shared/src/Node.cpp b/libraries/networking/src/Node.cpp similarity index 90% rename from libraries/shared/src/Node.cpp rename to libraries/networking/src/Node.cpp index a4491fb707..15ee443e1f 100644 --- a/libraries/shared/src/Node.cpp +++ b/libraries/networking/src/Node.cpp @@ -1,24 +1,22 @@ // // Node.cpp -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 #include -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include // not available on windows, apparently not needed on mac -#endif - #include "Node.h" #include "SharedUtil.h" #include +#include #include const QString UNKNOWN_NodeType_t_NAME = "Unknown"; @@ -44,10 +42,10 @@ const QString& NodeType::getNodeTypeName(NodeType_t nodeType) { return matchedTypeName != TypeNameHash.end() ? matchedTypeName.value() : UNKNOWN_NodeType_t_NAME; } -Node::Node(const QUuid& uuid, char type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : +Node::Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) : _type(type), _uuid(uuid), - _wakeMicrostamp(usecTimestampNow()), + _wakeTimestamp(QDateTime::currentMSecsSinceEpoch()), _lastHeardMicrostamp(usecTimestampNow()), _publicSocket(publicSocket), _localSocket(localSocket), @@ -60,6 +58,7 @@ Node::Node(const QUuid& uuid, char type, const HifiSockAddr& publicSocket, const _clockSkewUsec(0), _mutex() { + } Node::~Node() { diff --git a/libraries/shared/src/Node.h b/libraries/networking/src/Node.h similarity index 86% rename from libraries/shared/src/Node.h rename to libraries/networking/src/Node.h index 79d75629a6..0f63d01525 100644 --- a/libraries/shared/src/Node.h +++ b/libraries/networking/src/Node.h @@ -1,23 +1,20 @@ // // Node.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__Node__ -#define __hifi__Node__ +#ifndef hifi_Node_h +#define hifi_Node_h #include #include -#ifdef _WIN32 -#include "Syssocket.h" -#else -#include -#endif - #include #include #include @@ -48,7 +45,7 @@ namespace NodeType { class Node : public QObject { Q_OBJECT public: - Node(const QUuid& uuid, char type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); + Node(const QUuid& uuid, NodeType_t type, const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket); ~Node(); bool operator==(const Node& otherNode) const { return _uuid == otherNode._uuid; } @@ -60,8 +57,8 @@ public: const QUuid& getUUID() const { return _uuid; } void setUUID(const QUuid& uuid) { _uuid = uuid; } - quint64 getWakeMicrostamp() const { return _wakeMicrostamp; } - void setWakeMicrostamp(quint64 wakeMicrostamp) { _wakeMicrostamp = wakeMicrostamp; } + quint64 getWakeTimestamp() const { return _wakeTimestamp; } + void setWakeTimestamp(quint64 wakeTimestamp) { _wakeTimestamp = wakeTimestamp; } quint64 getLastHeardMicrostamp() const { return _lastHeardMicrostamp; } void setLastHeardMicrostamp(quint64 lastHeardMicrostamp) { _lastHeardMicrostamp = lastHeardMicrostamp; } @@ -109,7 +106,7 @@ private: NodeType_t _type; QUuid _uuid; - quint64 _wakeMicrostamp; + quint64 _wakeTimestamp; quint64 _lastHeardMicrostamp; HifiSockAddr _publicSocket; HifiSockAddr _localSocket; @@ -126,4 +123,4 @@ private: QDebug operator<<(QDebug debug, const Node &message); -#endif /* defined(__hifi__Node__) */ +#endif // hifi_Node_h diff --git a/libraries/networking/src/NodeData.cpp b/libraries/networking/src/NodeData.cpp new file mode 100644 index 0000000000..2e6fa9e8e1 --- /dev/null +++ b/libraries/networking/src/NodeData.cpp @@ -0,0 +1,22 @@ +// +// NodeData.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/19/13. +// Copyright 2013 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 "NodeData.h" + +NodeData::NodeData() : + _mutex() +{ + +} + +NodeData::~NodeData() { + +} diff --git a/libraries/shared/src/NodeData.h b/libraries/networking/src/NodeData.h similarity index 63% rename from libraries/shared/src/NodeData.h rename to libraries/networking/src/NodeData.h index b6b75443a2..b865e444a9 100644 --- a/libraries/shared/src/NodeData.h +++ b/libraries/networking/src/NodeData.h @@ -1,9 +1,12 @@ // // NodeData.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/19/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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_NodeData_h @@ -27,4 +30,4 @@ private: QMutex _mutex; }; -#endif +#endif // hifi_NodeData_h diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp new file mode 100644 index 0000000000..fbf2655269 --- /dev/null +++ b/libraries/networking/src/NodeList.cpp @@ -0,0 +1,596 @@ +// +// NodeList.cpp +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 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 +#include +#include +#include +#include + +#include + +#include "AccountManager.h" +#include "Assignment.h" +#include "HifiSockAddr.h" +#include "Logging.h" +#include "NodeList.h" +#include "PacketHeaders.h" +#include "SharedUtil.h" +#include "UUID.h" + +NodeList* NodeList::_sharedInstance = NULL; + +NodeList* NodeList::createInstance(char ownerType, unsigned short socketListenPort, unsigned short dtlsPort) { + if (!_sharedInstance) { + NodeType::init(); + + _sharedInstance = new NodeList(ownerType, socketListenPort, dtlsPort); + LimitedNodeList::_sharedInstance = _sharedInstance; + + // register the SharedNodePointer meta-type for signals/slots + qRegisterMetaType(); + } else { + qDebug("NodeList createInstance called with existing instance."); + } + + return _sharedInstance; +} + +NodeList* NodeList::getInstance() { + if (!_sharedInstance) { + qDebug("NodeList getInstance called before call to createInstance. Returning NULL pointer."); + } + + return _sharedInstance; +} + +NodeList::NodeList(char newOwnerType, unsigned short socketListenPort, unsigned short dtlsListenPort) : + LimitedNodeList(socketListenPort, dtlsListenPort), + _ownerType(newOwnerType), + _nodeTypesOfInterest(), + _domainHandler(this), + _numNoReplyDomainCheckIns(0), + _assignmentServerSocket(), + _publicSockAddr(), + _hasCompletedInitialSTUNFailure(false), + _stunRequestsSinceSuccess(0) +{ + // clear our NodeList when the domain changes + connect(&_domainHandler, &DomainHandler::hostnameChanged, this, &NodeList::reset); + + // clear our NodeList when logout is requested + connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); + + // perform a function when DTLS handshake is completed + connect(&_domainHandler, &DomainHandler::completedDTLSHandshake, this, &NodeList::completedDTLSHandshake); +} + +qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) { + QByteArray statsPacket = byteArrayWithPopulatedHeader(PacketTypeNodeJsonStats); + QDataStream statsPacketStream(&statsPacket, QIODevice::Append); + + statsPacketStream << statsObject.toVariantMap(); + + return writeDatagram(statsPacket, _domainHandler.getSockAddr(), QUuid()); +} + +void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) { + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + quint8 pingType; + quint64 ourOriginalTime, othersReplyTime; + + packetStream >> pingType >> ourOriginalTime >> othersReplyTime; + + quint64 now = usecTimestampNow(); + int pingTime = now - ourOriginalTime; + int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight + + // The other node's expected time should be our original time plus the one way flight time + // anything other than that is clock skew + quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime; + int clockSkew = othersReplyTime - othersExprectedReply; + + sendingNode->setPingMs(pingTime / 1000); + sendingNode->setClockSkewUsec(clockSkew); + + const bool wantDebug = false; + + if (wantDebug) { + qDebug() << "PING_REPLY from node " << *sendingNode << "\n" << + " now: " << now << "\n" << + " ourTime: " << ourOriginalTime << "\n" << + " pingTime: " << pingTime << "\n" << + " oneWayFlightTime: " << oneWayFlightTime << "\n" << + " othersReplyTime: " << othersReplyTime << "\n" << + " othersExprectedReply: " << othersExprectedReply << "\n" << + " clockSkew: " << clockSkew; + } +} + +void NodeList::completedDTLSHandshake() { + // at this point, we've got a DTLS socket + // make this NodeList the handler of DTLS packets + connect(_dtlsSocket, &QUdpSocket::readyRead, this, &NodeList::processAvailableDTLSDatagrams); +} + +void NodeList::processAvailableDTLSDatagrams() { + while (_dtlsSocket->hasPendingDatagrams()) { + QByteArray dtlsPacket(_dtlsSocket->pendingDatagramSize(), 0); + + // pull the data from this user off the stack and process it + int receivedBytes = gnutls_record_recv(*_domainHandler.getDTLSSession()->getGnuTLSSession(), + dtlsPacket.data(), dtlsPacket.size()); + if (receivedBytes > 0) { + // successful data receive, hand this off to processNodeData + processNodeData(_domainHandler.getSockAddr(), dtlsPacket.left(receivedBytes)); + } else if (gnutls_error_is_fatal(receivedBytes)) { + qDebug() << "Fatal error -" << gnutls_strerror(receivedBytes) << "- receiving DTLS packet from domain-server."; + } else { + qDebug() << "non fatal receive" << receivedBytes; + } + } +} + +void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { + switch (packetTypeForPacket(packet)) { + case PacketTypeDomainList: { + processDomainServerList(packet); + break; + } + case PacketTypeDomainServerRequireDTLS: { + _domainHandler.parseDTLSRequirementPacket(packet); + break; + } + case PacketTypePing: { + // send back a reply + SharedNodePointer matchingNode = sendingNodeForPacket(packet); + if (matchingNode) { + matchingNode->setLastHeardMicrostamp(usecTimestampNow()); + QByteArray replyPacket = constructPingReplyPacket(packet); + writeDatagram(replyPacket, matchingNode, senderSockAddr); + + // If we don't have a symmetric socket for this node and this socket doesn't match + // what we have for public and local then set it as the symmetric. + // This allows a server on a reachable port to communicate with nodes on symmetric NATs + if (matchingNode->getSymmetricSocket().isNull()) { + if (senderSockAddr != matchingNode->getLocalSocket() && senderSockAddr != matchingNode->getPublicSocket()) { + matchingNode->setSymmetricSocket(senderSockAddr); + } + } + } + + break; + } + case PacketTypePingReply: { + SharedNodePointer sendingNode = sendingNodeForPacket(packet); + + if (sendingNode) { + sendingNode->setLastHeardMicrostamp(usecTimestampNow()); + + // activate the appropriate socket for this node, if not yet updated + activateSocketFromNodeCommunication(packet, sendingNode); + + // set the ping time for this node for stat collection + timePingReply(packet, sendingNode); + } + + break; + } + case PacketTypeStunResponse: { + // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch + // pass it along so it can be processed into our public address and port + processSTUNResponse(packet); + break; + } + default: + LimitedNodeList::processNodeData(senderSockAddr, packet); + break; + } +} + +void NodeList::reset() { + LimitedNodeList::reset(); + + _numNoReplyDomainCheckIns = 0; + + // refresh the owner UUID to the NULL UUID + setSessionUUID(QUuid()); + + // clear the domain connection information + _domainHandler.clearConnectionInfo(); + + // if we setup the DTLS socket, also disconnect from the DTLS socket readyRead() so it can handle handshaking + if (_dtlsSocket) { + disconnect(_dtlsSocket, 0, this, 0); + } +} + +void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { + _nodeTypesOfInterest << nodeTypeToAdd; +} + +void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) { + _nodeTypesOfInterest.unite(setOfNodeTypes); +} + +const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; +const int NUM_BYTES_STUN_HEADER = 20; +const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5; + +void NodeList::sendSTUNRequest() { + const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io"; + const unsigned short STUN_SERVER_PORT = 3478; + + unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER]; + + int packetIndex = 0; + + const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); + + // leading zeros + message type + const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001); + memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE)); + packetIndex += sizeof(REQUEST_MESSAGE_TYPE); + + // message length (no additional attributes are included) + uint16_t messageLength = 0; + memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength)); + packetIndex += sizeof(messageLength); + + memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)); + packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); + + // transaction ID (random 12-byte unsigned integer) + const uint NUM_TRANSACTION_ID_BYTES = 12; + QUuid randomUUID = QUuid::createUuid(); + memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); + + // lookup the IP for the STUN server + static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); + + if (!_hasCompletedInitialSTUNFailure) { + qDebug("Sending intial stun request to %s", stunSockAddr.getAddress().toString().toLocal8Bit().constData()); + } + + _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), + stunSockAddr.getAddress(), stunSockAddr.getPort()); + + _stunRequestsSinceSuccess++; + + if (_stunRequestsSinceSuccess >= NUM_STUN_REQUESTS_BEFORE_FALLBACK) { + if (!_hasCompletedInitialSTUNFailure) { + // if we're here this was the last failed STUN request + // use our DS as our stun server + qDebug("Failed to lookup public address via STUN server at %s:%hu. Using DS for STUN.", + STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); + + _hasCompletedInitialSTUNFailure = true; + } + + // reset the public address and port + // use 0 so the DS knows to act as out STUN server + _publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort()); + } +} + +void NodeList::processSTUNResponse(const QByteArray& packet) { + // check the cookie to make sure this is actually a STUN response + // and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS + const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4; + const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020); + + const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); + + int attributeStartIndex = NUM_BYTES_STUN_HEADER; + + if (memcmp(packet.data() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH, + &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, + sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) { + + // enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE + while (attributeStartIndex < packet.size()) { + if (memcmp(packet.data() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) { + const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4; + const int NUM_BYTES_FAMILY_ALIGN = 1; + const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8; + + // reset the number of failed STUN requests since last success + _stunRequestsSinceSuccess = 0; + + int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; + + uint8_t addressFamily = 0; + memcpy(&addressFamily, packet.data(), sizeof(addressFamily)); + + byteIndex += sizeof(addressFamily); + + if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) { + // grab the X-Port + uint16_t xorMappedPort = 0; + memcpy(&xorMappedPort, packet.data() + byteIndex, sizeof(xorMappedPort)); + + uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16); + + byteIndex += sizeof(xorMappedPort); + + // grab the X-Address + uint32_t xorMappedAddress = 0; + memcpy(&xorMappedAddress, packet.data() + byteIndex, sizeof(xorMappedAddress)); + + uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); + + QHostAddress newPublicAddress = QHostAddress(stunAddress); + + if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { + _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); + + qDebug("New public socket received from STUN server is %s:%hu", + _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), + _publicSockAddr.getPort()); + + } + + _hasCompletedInitialSTUNFailure = true; + + break; + } + } else { + // push forward attributeStartIndex by the length of this attribute + const int NUM_BYTES_ATTRIBUTE_TYPE = 2; + + uint16_t attributeLength = 0; + memcpy(&attributeLength, packet.data() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE, + sizeof(attributeLength)); + attributeLength = ntohs(attributeLength); + + attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength; + } + } + } +} + +void NodeList::sendDomainServerCheckIn() { + if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) { + // we don't know our public socket and we need to send it to the domain server + // send a STUN request to figure it out + sendSTUNRequest(); + } else if (!_domainHandler.getIP().isNull()) { + + DTLSClientSession* dtlsSession = _domainHandler.getDTLSSession(); + bool isUsingDTLS = false; + + if (dtlsSession) { + if (!dtlsSession->completedHandshake()) { + // if the handshake process is not complete then we can't check in, so return + return; + } else { + isUsingDTLS = true; + } + } + + PacketType domainPacketType = _sessionUUID.isNull() + ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; + + // construct the DS check in packet + QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest + ? _sessionUUID : _domainHandler.getAssignmentUUID()); + + QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); + QDataStream packetStream(&domainServerPacket, QIODevice::Append); + + // pack our data to send to the domain-server + packetStream << _ownerType << _publicSockAddr + << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) + << (quint8) _nodeTypesOfInterest.size(); + + // copy over the bytes for node types of interest, if required + foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { + packetStream << nodeTypeOfInterest; + } + + if (!isUsingDTLS) { + writeDatagram(domainServerPacket, _domainHandler.getSockAddr(), QUuid()); + } else { + dtlsSession->writeDatagram(domainServerPacket); + } + + + const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; + static unsigned int numDomainCheckins = 0; + + // send a STUN request every Nth domain server check in so we update our public socket, if required + if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { + sendSTUNRequest(); + } + + if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { + // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS + // so emit our signal that indicates that + emit limitOfSilentDomainCheckInsReached(); + } + + // increment the count of un-replied check-ins + _numNoReplyDomainCheckIns++; + } +} + +int NodeList::processDomainServerList(const QByteArray& packet) { + // this is a packet from the domain server, reset the count of un-replied check-ins + _numNoReplyDomainCheckIns = 0; + + // if this was the first domain-server list from this domain, we've now connected + _domainHandler.setIsConnected(true); + + int readNodes = 0; + + // setup variables to read into from QDataStream + qint8 nodeType; + + QUuid nodeUUID, connectionUUID; + + HifiSockAddr nodePublicSocket; + HifiSockAddr nodeLocalSocket; + + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + // pull our owner UUID from the packet, it's always the first thing + QUuid newUUID; + packetStream >> newUUID; + setSessionUUID(newUUID); + + // pull each node in the packet + while(packetStream.device()->pos() < packet.size()) { + packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket; + + // if the public socket address is 0 then it's reachable at the same IP + // as the domain server + if (nodePublicSocket.getAddress().isNull()) { + nodePublicSocket.setAddress(_domainHandler.getIP()); + } + + SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket); + + packetStream >> connectionUUID; + node->setConnectionSecret(connectionUUID); + } + + // ping inactive nodes in conjunction with receipt of list from domain-server + // this makes it happen every second and also pings any newly added nodes + pingInactiveNodes(); + + return readNodes; +} + +void NodeList::sendAssignment(Assignment& assignment) { + + PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand + ? PacketTypeCreateAssignment + : PacketTypeRequestAssignment; + + QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType); + QDataStream packetStream(&packet, QIODevice::Append); + + packetStream << assignment; + + static HifiSockAddr DEFAULT_ASSIGNMENT_SOCKET(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, DEFAULT_DOMAIN_SERVER_PORT); + + const HifiSockAddr* assignmentServerSocket = _assignmentServerSocket.isNull() + ? &DEFAULT_ASSIGNMENT_SOCKET + : &_assignmentServerSocket; + + _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); +} + +QByteArray NodeList::constructPingPacket(PingType_t pingType) { + QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); + + QDataStream packetStream(&pingPacket, QIODevice::Append); + + packetStream << pingType; + packetStream << usecTimestampNow(); + + return pingPacket; +} + +QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { + QDataStream pingPacketStream(pingPacket); + pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); + + PingType_t typeFromOriginalPing; + pingPacketStream >> typeFromOriginalPing; + + quint64 timeFromOriginalPing; + pingPacketStream >> timeFromOriginalPing; + + QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); + QDataStream packetStream(&replyPacket, QIODevice::Append); + + packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); + + return replyPacket; +} + +void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { + + // send the ping packet to the local and public sockets for this node + QByteArray localPingPacket = constructPingPacket(PingType::Local); + writeDatagram(localPingPacket, node, node->getLocalSocket()); + + QByteArray publicPingPacket = constructPingPacket(PingType::Public); + writeDatagram(publicPingPacket, node, node->getPublicSocket()); + + if (!node->getSymmetricSocket().isNull()) { + QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric); + writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket()); + } +} + +void NodeList::pingInactiveNodes() { + foreach (const SharedNodePointer& node, getNodeHash()) { + if (!node->getActiveSocket()) { + // we don't have an active link to this node, ping it to set that up + pingPunchForInactiveNode(node); + } + } +} + +void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { + // deconstruct this ping packet to see if it is a public or local reply + QDataStream packetStream(packet); + packetStream.skipRawData(numBytesForPacketHeader(packet)); + + quint8 pingType; + packetStream >> pingType; + + // if this is a local or public ping then we can activate a socket + // we do nothing with agnostic pings, those are simply for timing + if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) { + sendingNode->activateLocalSocket(); + } else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) { + sendingNode->activatePublicSocket(); + } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { + sendingNode->activateSymmetricSocket(); + } +} + +const QString QSETTINGS_GROUP_NAME = "NodeList"; +const QString DOMAIN_SERVER_SETTING_KEY = "domainServerHostname"; + +void NodeList::loadData(QSettings *settings) { + settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); + + QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString(); + + if (domainServerHostname.size() > 0) { + _domainHandler.setHostname(domainServerHostname); + } else { + _domainHandler.setHostname(DEFAULT_DOMAIN_HOSTNAME); + } + + settings->endGroup(); +} + +void NodeList::saveData(QSettings* settings) { + settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); + + if (_domainHandler.getHostname() != DEFAULT_DOMAIN_HOSTNAME) { + // the user is using a different hostname, store it + settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainHandler.getHostname())); + } else { + // the user has switched back to default, remove the current setting + settings->remove(DOMAIN_SERVER_SETTING_KEY); + } + + settings->endGroup(); +} diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h new file mode 100644 index 0000000000..c55f08e7f0 --- /dev/null +++ b/libraries/networking/src/NodeList.h @@ -0,0 +1,114 @@ +// +// NodeList.h +// libraries/networking/src +// +// Created by Stephen Birarda on 2/15/13. +// Copyright 2013 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_NodeList_h +#define hifi_NodeList_h + +#include +#include + +#ifndef _WIN32 +#include // not on windows, not needed for mac or windows +#endif + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "DomainHandler.h" +#include "LimitedNodeList.h" +#include "Node.h" + +const quint64 DOMAIN_SERVER_CHECK_IN_MSECS = 1 * 1000; + +const int MAX_SILENT_DOMAIN_SERVER_CHECK_INS = 5; + +class Assignment; + +typedef quint8 PingType_t; +namespace PingType { + const PingType_t Agnostic = 0; + const PingType_t Local = 1; + const PingType_t Public = 2; + const PingType_t Symmetric = 3; +} + +class NodeList : public LimitedNodeList { + Q_OBJECT +public: + static NodeList* createInstance(char ownerType, unsigned short socketListenPort = 0, unsigned short dtlsPort = 0); + static NodeList* getInstance(); + NodeType_t getOwnerType() const { return _ownerType; } + void setOwnerType(NodeType_t ownerType) { _ownerType = ownerType; } + + qint64 sendStatsToDomainServer(const QJsonObject& statsObject); + + int getNumNoReplyDomainCheckIns() const { return _numNoReplyDomainCheckIns; } + DomainHandler& getDomainHandler() { return _domainHandler; } + + const NodeSet& getNodeInterestSet() const { return _nodeTypesOfInterest; } + void addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd); + void addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes); + void resetNodeInterestSet() { _nodeTypesOfInterest.clear(); } + + void processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet); + + int processDomainServerList(const QByteArray& packet); + + void setAssignmentServerSocket(const HifiSockAddr& serverSocket) { _assignmentServerSocket = serverSocket; } + void sendAssignment(Assignment& assignment); + + QByteArray constructPingPacket(PingType_t pingType = PingType::Agnostic); + QByteArray constructPingReplyPacket(const QByteArray& pingPacket); + + void pingPunchForInactiveNode(const SharedNodePointer& node); + + void loadData(QSettings* settings); + void saveData(QSettings* settings); +public slots: + void reset(); + void sendDomainServerCheckIn(); + void pingInactiveNodes(); + void completedDTLSHandshake(); + void processAvailableDTLSDatagrams(); +signals: + void limitOfSilentDomainCheckInsReached(); +private: + static NodeList* _sharedInstance; + + NodeList(char ownerType, unsigned short socketListenPort, unsigned short dtlsListenPort); + NodeList(NodeList const&); // Don't implement, needed to avoid copies of singleton + void operator=(NodeList const&); // Don't implement, needed to avoid copies of singleton + void sendSTUNRequest(); + void processSTUNResponse(const QByteArray& packet); + + void processDomainServerAuthRequest(const QByteArray& packet); + void requestAuthForDomainServer(); + void activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode); + void timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode); + + NodeType_t _ownerType; + NodeSet _nodeTypesOfInterest; + DomainHandler _domainHandler; + int _numNoReplyDomainCheckIns; + HifiSockAddr _assignmentServerSocket; + HifiSockAddr _publicSockAddr; + bool _hasCompletedInitialSTUNFailure; + unsigned int _stunRequestsSinceSuccess; +}; + +#endif // hifi_NodeList_h diff --git a/libraries/shared/src/OAuthAccessToken.cpp b/libraries/networking/src/OAuthAccessToken.cpp similarity index 88% rename from libraries/shared/src/OAuthAccessToken.cpp rename to libraries/networking/src/OAuthAccessToken.cpp index 20a9dc3aa5..365b07a935 100644 --- a/libraries/shared/src/OAuthAccessToken.cpp +++ b/libraries/networking/src/OAuthAccessToken.cpp @@ -1,9 +1,12 @@ // // OAuthAccessToken.cpp -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 diff --git a/libraries/shared/src/OAuthAccessToken.h b/libraries/networking/src/OAuthAccessToken.h similarity index 73% rename from libraries/shared/src/OAuthAccessToken.h rename to libraries/networking/src/OAuthAccessToken.h index e2a5eb4ce2..36859b79f8 100644 --- a/libraries/shared/src/OAuthAccessToken.h +++ b/libraries/networking/src/OAuthAccessToken.h @@ -1,13 +1,16 @@ // // OAuthAccessToken.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 2/18/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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__OAuthAccessToken__ -#define __hifi__OAuthAccessToken__ +#ifndef hifi_OAuthAccessToken_h +#define hifi_OAuthAccessToken_h #include #include @@ -34,4 +37,4 @@ private: void swap(OAuthAccessToken& otherToken); }; -#endif /* defined(__hifi__OAuthAccessToken__) */ +#endif // hifi_OAuthAccessToken_h diff --git a/libraries/shared/src/PacketHeaders.cpp b/libraries/networking/src/PacketHeaders.cpp similarity index 77% rename from libraries/shared/src/PacketHeaders.cpp rename to libraries/networking/src/PacketHeaders.cpp index c7518708ce..29238220e1 100644 --- a/libraries/shared/src/PacketHeaders.cpp +++ b/libraries/networking/src/PacketHeaders.cpp @@ -1,9 +1,12 @@ // // PacketHeaders.cpp -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 6/28/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -52,15 +55,10 @@ PacketVersion versionForPacketType(PacketType type) { return 1; case PacketTypeDomainList: case PacketTypeDomainListRequest: - return 1; + return 3; case PacketTypeCreateAssignment: case PacketTypeRequestAssignment: - return 1; - case PacketTypeDataServerGet: - case PacketTypeDataServerPut: - case PacketTypeDataServerConfirm: - case PacketTypeDataServerSend: - return 1; + return 2; case PacketTypeVoxelSet: case PacketTypeVoxelSetDestructive: return 1; @@ -89,15 +87,17 @@ int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionU char* position = packet + numTypeBytes + sizeof(PacketVersion); - QUuid packUUID = connectionUUID.isNull() ? NodeList::getInstance()->getSessionUUID() : connectionUUID; + QUuid packUUID = connectionUUID.isNull() ? LimitedNodeList::getInstance()->getSessionUUID() : connectionUUID; QByteArray rfcUUID = packUUID.toRfc4122(); memcpy(position, rfcUUID.constData(), NUM_BYTES_RFC4122_UUID); position += NUM_BYTES_RFC4122_UUID; - // pack 16 bytes of zeros where the md5 hash will be placed one data is packed - memset(position, 0, NUM_BYTES_MD5_HASH); - position += NUM_BYTES_MD5_HASH; + if (!NON_VERIFIED_PACKETS.contains(type)) { + // pack 16 bytes of zeros where the md5 hash will be placed one data is packed + memset(position, 0, NUM_BYTES_MD5_HASH); + position += NUM_BYTES_MD5_HASH; + } // return the number of bytes written for pointer pushing return position - packet; @@ -105,16 +105,26 @@ int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionU int numBytesForPacketHeader(const QByteArray& packet) { // returns the number of bytes used for the type, version, and UUID - return numBytesArithmeticCodingFromBuffer(packet.data()) + NUM_STATIC_HEADER_BYTES; + return numBytesArithmeticCodingFromBuffer(packet.data()) + + numHashBytesInPacketHeaderGivenPacketType(packetTypeForPacket(packet)) + + NUM_STATIC_HEADER_BYTES; } int numBytesForPacketHeader(const char* packet) { // returns the number of bytes used for the type, version, and UUID - return numBytesArithmeticCodingFromBuffer(packet) + NUM_STATIC_HEADER_BYTES; + return numBytesArithmeticCodingFromBuffer(packet) + + numHashBytesInPacketHeaderGivenPacketType(packetTypeForPacket(packet)) + + NUM_STATIC_HEADER_BYTES; } int numBytesForPacketHeaderGivenPacketType(PacketType type) { - return (int) ceilf((float)type / 255) + NUM_STATIC_HEADER_BYTES; + return (int) ceilf((float)type / 255) + + numHashBytesInPacketHeaderGivenPacketType(type) + + NUM_STATIC_HEADER_BYTES; +} + +int numHashBytesInPacketHeaderGivenPacketType(PacketType type) { + return (NON_VERIFIED_PACKETS.contains(type) ? 0 : NUM_BYTES_MD5_HASH); } QUuid uuidFromPacketHeader(const QByteArray& packet) { diff --git a/libraries/shared/src/PacketHeaders.h b/libraries/networking/src/PacketHeaders.h similarity index 71% rename from libraries/shared/src/PacketHeaders.h rename to libraries/networking/src/PacketHeaders.h index a9bc5d3763..b7535e5064 100644 --- a/libraries/shared/src/PacketHeaders.h +++ b/libraries/networking/src/PacketHeaders.h @@ -1,18 +1,19 @@ // // PacketHeaders.h -// hifi +// libraries/networking/src // // Created by Stephen Birarda on 4/8/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 // -// The packet headers below refer to the first byte of a received UDP packet transmitted between -// any two Hifi components. For example, a packet whose first byte is 'P' is always a ping packet. -// #ifndef hifi_PacketHeaders_h #define hifi_PacketHeaders_h #include +#include #include #include "UUID.h" @@ -37,9 +38,9 @@ enum PacketType { PacketTypeDomainListRequest, PacketTypeRequestAssignment, PacketTypeCreateAssignment, - PacketTypeDataServerPut, - PacketTypeDataServerGet, - PacketTypeDataServerSend, + PacketTypeDataServerPut, // reusable + PacketTypeDataServerGet, // reusable + PacketTypeDataServerSend, // reusable PacketTypeDataServerConfirm, PacketTypeVoxelQuery, PacketTypeVoxelData, @@ -58,15 +59,21 @@ enum PacketType { PacketTypeAvatarIdentity, PacketTypeAvatarBillboard, PacketTypeDomainConnectRequest, - PacketTypeDomainServerAuthRequest, - PacketTypeNodeJsonStats + PacketTypeDomainServerRequireDTLS, + PacketTypeNodeJsonStats, }; typedef char PacketVersion; +const QSet NON_VERIFIED_PACKETS = QSet() + << PacketTypeDomainServerRequireDTLS << PacketTypeDomainConnectRequest + << PacketTypeDomainList << PacketTypeDomainListRequest + << PacketTypeCreateAssignment << PacketTypeRequestAssignment << PacketTypeStunResponse + << PacketTypeNodeJsonStats << PacketTypeVoxelQuery << PacketTypeParticleQuery; + const int NUM_BYTES_MD5_HASH = 16; -const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID + NUM_BYTES_MD5_HASH; -const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + NUM_STATIC_HEADER_BYTES; +const int NUM_STATIC_HEADER_BYTES = sizeof(PacketVersion) + NUM_BYTES_RFC4122_UUID; +const int MAX_PACKET_HEADER_BYTES = sizeof(PacketType) + NUM_BYTES_MD5_HASH + NUM_STATIC_HEADER_BYTES; PacketVersion versionForPacketType(PacketType type); @@ -76,6 +83,8 @@ QByteArray byteArrayWithPopulatedHeader(PacketType type, const QUuid& connection int populatePacketHeader(QByteArray& packet, PacketType type, const QUuid& connectionUUID = nullUUID); int populatePacketHeader(char* packet, PacketType type, const QUuid& connectionUUID = nullUUID); +int numHashBytesInPacketHeaderGivenPacketType(PacketType type); + int numBytesForPacketHeader(const QByteArray& packet); int numBytesForPacketHeader(const char* packet); int numBytesForPacketHeaderGivenPacketType(PacketType type); @@ -92,4 +101,4 @@ PacketType packetTypeForPacket(const char* packet); int arithmeticCodingValueFromBuffer(const char* checkValue); int numBytesArithmeticCodingFromBuffer(const char* checkValue); -#endif +#endif // hifi_PacketHeaders_h diff --git a/libraries/shared/src/PacketSender.cpp b/libraries/networking/src/PacketSender.cpp similarity index 98% rename from libraries/shared/src/PacketSender.cpp rename to libraries/networking/src/PacketSender.cpp index 9fac115a39..5f7502a738 100644 --- a/libraries/shared/src/PacketSender.cpp +++ b/libraries/networking/src/PacketSender.cpp @@ -1,11 +1,12 @@ // // PacketSender.cpp -// shared +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded packet sender. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include @@ -281,4 +282,4 @@ bool PacketSender::nonThreadedProcess() { _lastSendTime = now; } return isStillRunning(); -} \ No newline at end of file +} diff --git a/libraries/shared/src/PacketSender.h b/libraries/networking/src/PacketSender.h similarity index 93% rename from libraries/shared/src/PacketSender.h rename to libraries/networking/src/PacketSender.h index bd8de9a1b1..7d2c0dc8aa 100644 --- a/libraries/shared/src/PacketSender.h +++ b/libraries/networking/src/PacketSender.h @@ -1,15 +1,18 @@ // // PacketSender.h -// shared +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // Threaded or non-threaded packet sender. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef __shared__PacketSender__ -#define __shared__PacketSender__ +#ifndef hifi_PacketSender_h +#define hifi_PacketSender_h #include @@ -121,4 +124,4 @@ private: QMutex _waitingOnPacketsMutex; }; -#endif // __shared__PacketSender__ +#endif // hifi_PacketSender_h diff --git a/libraries/shared/src/ReceivedPacketProcessor.cpp b/libraries/networking/src/ReceivedPacketProcessor.cpp similarity index 88% rename from libraries/shared/src/ReceivedPacketProcessor.cpp rename to libraries/networking/src/ReceivedPacketProcessor.cpp index df7bfad165..d54e165285 100644 --- a/libraries/shared/src/ReceivedPacketProcessor.cpp +++ b/libraries/networking/src/ReceivedPacketProcessor.cpp @@ -1,11 +1,12 @@ // // ReceivedPacketProcessor.cpp -// shared +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded packet receiver. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "NodeList.h" diff --git a/libraries/shared/src/ReceivedPacketProcessor.h b/libraries/networking/src/ReceivedPacketProcessor.h similarity index 83% rename from libraries/shared/src/ReceivedPacketProcessor.h rename to libraries/networking/src/ReceivedPacketProcessor.h index f88512639b..f8306b4896 100644 --- a/libraries/shared/src/ReceivedPacketProcessor.h +++ b/libraries/networking/src/ReceivedPacketProcessor.h @@ -1,15 +1,16 @@ // // ReceivedPacketProcessor.h -// shared +// libraries/networking/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded received packet processor. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef __shared__ReceivedPacketProcessor__ -#define __shared__ReceivedPacketProcessor__ +#ifndef hifi_ReceivedPacketProcessor_h +#define hifi_ReceivedPacketProcessor_h #include @@ -55,4 +56,4 @@ private: QMutex _waitingOnPacketsMutex; }; -#endif // __shared__PacketReceiver__ +#endif // hifi_ReceivedPacketProcessor_h diff --git a/libraries/shared/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp similarity index 90% rename from libraries/shared/src/ThreadedAssignment.cpp rename to libraries/networking/src/ThreadedAssignment.cpp index fdf2d91c36..4b92f8ba38 100644 --- a/libraries/shared/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -1,9 +1,12 @@ // // ThreadedAssignment.cpp -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 12/3/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -41,11 +44,11 @@ void ThreadedAssignment::commonInit(const QString& targetName, NodeType_t nodeTy QTimer* domainServerTimer = new QTimer(this); connect(domainServerTimer, SIGNAL(timeout()), this, SLOT(checkInWithDomainServerOrExit())); - domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_USECS / 1000); + domainServerTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS); QTimer* silentNodeRemovalTimer = new QTimer(this); connect(silentNodeRemovalTimer, SIGNAL(timeout()), nodeList, SLOT(removeSilentNodes())); - silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_USECS / 1000); + silentNodeRemovalTimer->start(NODE_SILENCE_THRESHOLD_MSECS); if (shouldSendStats) { // send a stats packet every 1 second diff --git a/libraries/shared/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h similarity index 75% rename from libraries/shared/src/ThreadedAssignment.h rename to libraries/networking/src/ThreadedAssignment.h index f9652dd98d..e9241d0272 100644 --- a/libraries/shared/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -1,13 +1,16 @@ // // ThreadedAssignment.h -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 12/3/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__ThreadedAssignment__ -#define __hifi__ThreadedAssignment__ +#ifndef hifi_ThreadedAssignment_h +#define hifi_ThreadedAssignment_h #include @@ -39,4 +42,4 @@ signals: typedef QSharedPointer SharedAssignmentPointer; -#endif /* defined(__hifi__ThreadedAssignment__) */ +#endif // hifi_ThreadedAssignment_h diff --git a/libraries/octree/CMakeLists.txt b/libraries/octree/CMakeLists.txt index 422089a57d..88554ed5f8 100644 --- a/libraries/octree/CMakeLists.txt +++ b/libraries/octree/CMakeLists.txt @@ -22,9 +22,17 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -# link ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# 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 "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/libraries/octree/src/AABox.cpp b/libraries/octree/src/AABox.cpp index 97fd706c21..51b31d4466 100644 --- a/libraries/octree/src/AABox.cpp +++ b/libraries/octree/src/AABox.cpp @@ -1,11 +1,12 @@ // -// AABox.h - Axis Aligned Boxes -// hifi +// AABox.cpp +// libraries/octree/src // -// Added by Brad Hefta-Gaub on 04/11/13. -// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Created by Brad Hefta-Gaub on 04/11/13. +// Copyright 2013 High Fidelity, Inc. // -// Simple axis aligned box class. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "SharedUtil.h" diff --git a/libraries/octree/src/AABox.h b/libraries/octree/src/AABox.h index 731b82be4c..1aa0849b70 100644 --- a/libraries/octree/src/AABox.h +++ b/libraries/octree/src/AABox.h @@ -1,15 +1,19 @@ // -// AABox.h - Axis Aligned Boxes -// hifi +// AABox.h +// libraries/octree/src +// +// Created by Brad Hefta-Gaub on 04/11/13. +// Copyright 2013 High Fidelity, Inc. // -// Added by Brad Hefta-Gaub on 04/11/13. // Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards -// // Simple axis aligned box class. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef _AABOX_ -#define _AABOX_ +#ifndef hifi_AABox_h +#define hifi_AABox_h #include @@ -19,10 +23,10 @@ enum BoxFace { MIN_Y_FACE, MAX_Y_FACE, MIN_Z_FACE, - MAX_Z_FACE + MAX_Z_FACE, + UNKNOWN_FACE }; - enum BoxVertex { BOTTOM_LEFT_NEAR = 0, BOTTOM_RIGHT_NEAR = 1, @@ -79,4 +83,4 @@ private: float _scale; }; -#endif +#endif // hifi_AABox_h diff --git a/libraries/octree/src/CoverageMap.cpp b/libraries/octree/src/CoverageMap.cpp index a7717b8107..65d5734b68 100644 --- a/libraries/octree/src/CoverageMap.cpp +++ b/libraries/octree/src/CoverageMap.cpp @@ -1,9 +1,12 @@ // -// CoverageMap.cpp - -// hifi +// CoverageMap.cpp +// libraries/octree/src // -// Added by Brad Hefta-Gaub on 06/11/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 06/11/13. +// Copyright 2013 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 @@ -555,4 +558,4 @@ CoverageMapStorageResult CoverageRegion::checkRegion(OctreeProjectedPolygon* pol } } return result; -} +} \ No newline at end of file diff --git a/libraries/octree/src/CoverageMap.h b/libraries/octree/src/CoverageMap.h index 9cdda579e8..bff6bb1078 100644 --- a/libraries/octree/src/CoverageMap.h +++ b/libraries/octree/src/CoverageMap.h @@ -1,13 +1,18 @@ // -// CoverageMap.h - 2D CoverageMap Quad tree for storage of OctreeProjectedPolygons -// hifi +// CoverageMap.h +// libraries/octree/src // -// Added by Brad Hefta-Gaub on 06/11/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 06/11/13. +// Copyright 2013 High Fidelity, Inc. +// +// 2D CoverageMap Quad tree for storage of OctreeProjectedPolygons +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef _COVERAGE_MAP_ -#define _COVERAGE_MAP_ +#ifndef hifi_CoverageMap_h +#define hifi_CoverageMap_h #include #include "OctreeProjectedPolygon.h" @@ -112,4 +117,4 @@ private: }; -#endif // _COVERAGE_MAP_ +#endif // hifi_CoverageMap_h diff --git a/libraries/octree/src/CoverageMapV2.cpp b/libraries/octree/src/CoverageMapV2.cpp index afb341fcba..1ad800ab6b 100644 --- a/libraries/octree/src/CoverageMapV2.cpp +++ b/libraries/octree/src/CoverageMapV2.cpp @@ -1,9 +1,12 @@ // -// CoverageMapV2.cpp - -// hifi +// CoverageMapV2.cpp +// libraries/octree/src // -// Added by Brad Hefta-Gaub on 06/11/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 06/11/13. +// Copyright 2013 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 diff --git a/libraries/octree/src/CoverageMapV2.h b/libraries/octree/src/CoverageMapV2.h index e7c2eed4fe..fc9a3ea70e 100644 --- a/libraries/octree/src/CoverageMapV2.h +++ b/libraries/octree/src/CoverageMapV2.h @@ -1,13 +1,16 @@ // -// CoverageMapV2.h - 2D CoverageMapV2 Quad tree for storage of OctreeProjectedPolygons -// hifi +// CoverageMapV2.h +// libraries/octree/src // -// Added by Brad Hefta-Gaub on 06/11/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 06/11/13. +// Copyright 2013 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 _COVERAGE_MAP_V2_ -#define _COVERAGE_MAP_V2_ +#ifndef hifi_CoverageMapV2_h +#define hifi_CoverageMapV2_h #include @@ -66,4 +69,4 @@ private: }; -#endif // _COVERAGE_MAP_V2_ +#endif // hifi_CoverageMapV2_h diff --git a/libraries/octree/src/JurisdictionListener.cpp b/libraries/octree/src/JurisdictionListener.cpp index c280b48c99..453ff10a42 100644 --- a/libraries/octree/src/JurisdictionListener.cpp +++ b/libraries/octree/src/JurisdictionListener.cpp @@ -1,11 +1,12 @@ // // JurisdictionListener.cpp -// shared +// libraries/octree/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded jurisdiction Sender for the Application +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/libraries/octree/src/JurisdictionListener.h b/libraries/octree/src/JurisdictionListener.h index b3dc9664d7..01f0392796 100644 --- a/libraries/octree/src/JurisdictionListener.h +++ b/libraries/octree/src/JurisdictionListener.h @@ -1,21 +1,23 @@ // // JurisdictionListener.h -// shared +// libraries/octree/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // Voxel Packet Sender // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef __shared__JurisdictionListener__ -#define __shared__JurisdictionListener__ +#ifndef hifi_JurisdictionListener_h +#define hifi_JurisdictionListener_h #include #include #include - #include "JurisdictionMap.h" /// Sends out PacketType_JURISDICTION_REQUEST packets to all voxel servers and then listens for and processes @@ -59,4 +61,4 @@ private: PacketSender _packetSender; }; -#endif // __shared__JurisdictionListener__ +#endif // hifi_JurisdictionListener_h diff --git a/libraries/octree/src/JurisdictionMap.cpp b/libraries/octree/src/JurisdictionMap.cpp index 0271c77012..747a05e90a 100644 --- a/libraries/octree/src/JurisdictionMap.cpp +++ b/libraries/octree/src/JurisdictionMap.cpp @@ -1,9 +1,12 @@ // // JurisdictionMap.cpp -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 8/1/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/octree/src/JurisdictionMap.h b/libraries/octree/src/JurisdictionMap.h index 06305254d1..b4174e6432 100644 --- a/libraries/octree/src/JurisdictionMap.h +++ b/libraries/octree/src/JurisdictionMap.h @@ -1,13 +1,16 @@ // // JurisdictionMap.h -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 8/1/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__JurisdictionMap__ -#define __hifi__JurisdictionMap__ +#ifndef hifi_JurisdictionMap_h +#define hifi_JurisdictionMap_h #include #include @@ -83,6 +86,4 @@ typedef QMap NodeToJurisdictionMap; typedef QMap::iterator NodeToJurisdictionMapIterator; -#endif /* defined(__hifi__JurisdictionMap__) */ - - +#endif // hifi_JurisdictionMap_h diff --git a/libraries/octree/src/JurisdictionSender.cpp b/libraries/octree/src/JurisdictionSender.cpp index 1d26e4a53a..c151999305 100644 --- a/libraries/octree/src/JurisdictionSender.cpp +++ b/libraries/octree/src/JurisdictionSender.cpp @@ -1,11 +1,12 @@ // // JurisdictionSender.cpp -// shared +// libraries/octree/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded jurisdiction Sender for the Application +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/libraries/octree/src/JurisdictionSender.h b/libraries/octree/src/JurisdictionSender.h index 18365fda63..bfa370c6ca 100644 --- a/libraries/octree/src/JurisdictionSender.h +++ b/libraries/octree/src/JurisdictionSender.h @@ -1,15 +1,16 @@ // // JurisdictionSender.h -// shared +// libraries/octree/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Jurisdiction Sender +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef __shared__JurisdictionSender__ -#define __shared__JurisdictionSender__ +#ifndef hifi_JurisdictionSender_h +#define hifi_JurisdictionSender_h #include #include @@ -54,4 +55,4 @@ private: PacketSender _packetSender; }; -#endif // __shared__JurisdictionSender__ +#endif // hifi_JurisdictionSender_h diff --git a/libraries/octree/src/Octree.cpp b/libraries/octree/src/Octree.cpp index 58cfd9b15d..ebfb954bd8 100644 --- a/libraries/octree/src/Octree.cpp +++ b/libraries/octree/src/Octree.cpp @@ -1,9 +1,12 @@ // // Octree.cpp -// hifi +// libraries/octree/src // // Created by Stephen Birarda on 3/13/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 // #ifdef _WIN32 @@ -17,18 +20,20 @@ #include -#include "CoverageMap.h" #include -#include "OctalCode.h" +#include #include #include +#include +#include //#include "Tags.h" -#include "ViewFrustum.h" +#include "CoverageMap.h" #include "OctreeConstants.h" #include "OctreeElementBag.h" #include "Octree.h" +#include "ViewFrustum.h" float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale) { return voxelSizeScale / powf(2, renderLevel); @@ -468,8 +473,8 @@ void Octree::processRemoveOctreeElementsBitstream(const unsigned char* bitstream int codeLength = numberOfThreeBitSectionsInCode(voxelCode, maxSize); if (codeLength == OVERFLOWED_OCTCODE_BUFFER) { - printf("WARNING! Got remove voxel bitstream that would overflow buffer in numberOfThreeBitSectionsInCode(), "); - printf("bailing processing of packet!\n"); + qDebug("WARNING! Got remove voxel bitstream that would overflow buffer in numberOfThreeBitSectionsInCode()," + " bailing processing of packet!"); break; } int voxelDataSize = bytesRequiredForCodeLength(codeLength) + SIZE_OF_COLOR_DATA; @@ -479,7 +484,7 @@ void Octree::processRemoveOctreeElementsBitstream(const unsigned char* bitstream voxelCode += voxelDataSize; atByte += voxelDataSize; } else { - printf("WARNING! Got remove voxel bitstream that would overflow buffer, bailing processing!\n"); + qDebug("WARNING! Got remove voxel bitstream that would overflow buffer, bailing processing!"); break; } } @@ -673,6 +678,13 @@ public: bool found; }; +class ShapeArgs { +public: + const Shape* shape; + CollisionList& collisions; + bool found; +}; + bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) { CapsuleArgs* args = static_cast(extraData); @@ -694,6 +706,27 @@ bool findCapsulePenetrationOp(OctreeElement* node, void* extraData) { return false; } +bool findShapeCollisionsOp(OctreeElement* node, void* extraData) { + ShapeArgs* args = static_cast(extraData); + + // coarse check against bounds + AABox cube = node->getAABox(); + cube.scale(TREE_SCALE); + if (!cube.expandedContains(args->shape->getPosition(), args->shape->getBoundingRadius())) { + return false; + } + if (!node->isLeaf()) { + return true; // recurse on children + } + if (node->hasContent()) { + if (ShapeCollider::collideShapeWithAACube(args->shape, cube.calcCenter(), cube.getScale(), args->collisions)) { + args->found = true; + return true; + } + } + return false; +} + bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration, Octree::lockType lockType) { @@ -724,6 +757,29 @@ bool Octree::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end return args.found; } +bool Octree::findShapeCollisions(const Shape* shape, CollisionList& collisions, Octree::lockType lockType) { + + ShapeArgs args = { shape, collisions, false }; + + bool gotLock = false; + if (lockType == Octree::Lock) { + lockForRead(); + gotLock = true; + } else if (lockType == Octree::TryLock) { + gotLock = tryLockForRead(); + if (!gotLock) { + return args.found; // if we wanted to tryLock, and we couldn't then just bail... + } + } + + recurseTreeWithOperation(findShapeCollisionsOp, &args); + + if (gotLock) { + unlock(); + } + return args.found; +} + class GetElementEnclosingArgs { public: OctreeElement* element; @@ -1401,7 +1457,7 @@ int Octree::encodeTreeBitstreamRecursion(OctreeElement* node, outputBits(childrenExistInPacketBits, false, true); printf(" childrenColored:"); outputBits(childrenColoredBits, false, true); - printf("\n"); + qDebug(""); **/ // if we were unable to fit this level in our packet, then rewind and add it to the node bag for @@ -1617,7 +1673,7 @@ void Octree::copyFromTreeIntoSubTree(Octree* sourceTree, OctreeElement* destinat } void dumpSetContents(const char* name, std::set set) { - printf("set %s has %ld elements\n", name, set.size()); + qDebug("set %s has %ld elements", name, set.size()); /* for (std::set::iterator i = set.begin(); i != set.end(); ++i) { printOctalCode(*i); diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index f029431d87..0b9f1657ee 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -1,13 +1,16 @@ // // Octree.h -// hifi +// libraries/octree/src // // Created by Stephen Birarda on 3/13/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__Octree__ -#define __hifi__Octree__ +#ifndef hifi_Octree_h +#define hifi_Octree_h #include #include @@ -18,6 +21,7 @@ class Octree; class OctreeElement; class OctreeElementBag; class OctreePacketData; +class Shape; #include "JurisdictionMap.h" @@ -27,6 +31,8 @@ class OctreePacketData; #include "OctreePacketData.h" #include "OctreeSceneStats.h" +#include + #include #include @@ -126,17 +132,17 @@ public: printf("StopReason: "); switch (stopReason) { default: - case UNKNOWN: printf("UNKNOWN\n"); break; + case UNKNOWN: qDebug("UNKNOWN"); break; - case DIDNT_FIT: printf("DIDNT_FIT\n"); break; - case NULL_NODE: printf("NULL_NODE\n"); break; - case TOO_DEEP: printf("TOO_DEEP\n"); break; - case OUT_OF_JURISDICTION: printf("OUT_OF_JURISDICTION\n"); break; - case LOD_SKIP: printf("LOD_SKIP\n"); break; - case OUT_OF_VIEW: printf("OUT_OF_VIEW\n"); break; - case WAS_IN_VIEW: printf("WAS_IN_VIEW\n"); break; - case NO_CHANGE: printf("NO_CHANGE\n"); break; - case OCCLUDED: printf("OCCLUDED\n"); break; + case DIDNT_FIT: qDebug("DIDNT_FIT"); break; + case NULL_NODE: qDebug("NULL_NODE"); break; + case TOO_DEEP: qDebug("TOO_DEEP"); break; + case OUT_OF_JURISDICTION: qDebug("OUT_OF_JURISDICTION"); break; + case LOD_SKIP: qDebug("LOD_SKIP"); break; + case OUT_OF_VIEW: qDebug("OUT_OF_VIEW"); break; + case WAS_IN_VIEW: qDebug("WAS_IN_VIEW"); break; + case NO_CHANGE: qDebug("NO_CHANGE"); break; + case OCCLUDED: qDebug("OCCLUDED"); break; } } }; @@ -243,6 +249,8 @@ public: bool findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration, Octree::lockType lockType = Octree::TryLock); + bool findShapeCollisions(const Shape* shape, CollisionList& collisions, Octree::lockType = Octree::TryLock); + OctreeElement* getElementEnclosingPoint(const glm::vec3& point, Octree::lockType lockType = Octree::TryLock); // Note: this assumes the fileFormat is the HIO individual voxels code files @@ -311,4 +319,4 @@ protected: float boundaryDistanceForRenderLevel(unsigned int renderLevel, float voxelSizeScale); -#endif /* defined(__hifi__Octree__) */ +#endif // hifi_Octree_h diff --git a/libraries/octree/src/OctreeConstants.h b/libraries/octree/src/OctreeConstants.h index a78d778f70..18c5eb0108 100644 --- a/libraries/octree/src/OctreeConstants.h +++ b/libraries/octree/src/OctreeConstants.h @@ -1,16 +1,16 @@ // // OctreeConstants.h -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 4/29/13. +// Copyright 2013 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 // -// Various important constants used throughout the system related to voxels -// -// -#ifndef __hifi_OctreeConstants_h__ -#define __hifi_OctreeConstants_h__ +#ifndef hifi_OctreeConstants_h +#define hifi_OctreeConstants_h #include #include @@ -44,4 +44,4 @@ const int DANGEROUSLY_DEEP_RECURSION = 200; // use this for something that needs const int DEFAULT_MAX_OCTREE_PPS = 600; // the default maximum PPS we think any octree based server should send to a client -#endif \ No newline at end of file +#endif // hifi_OctreeConstants_h diff --git a/libraries/octree/src/OctreeEditPacketSender.cpp b/libraries/octree/src/OctreeEditPacketSender.cpp index bb8ff9d8a0..7e59b3ae7f 100644 --- a/libraries/octree/src/OctreeEditPacketSender.cpp +++ b/libraries/octree/src/OctreeEditPacketSender.cpp @@ -1,11 +1,12 @@ // // OctreeEditPacketSender.cpp -// interface +// libraries/octree/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded packet Sender for the Application +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include @@ -36,7 +37,7 @@ OctreeEditPacketSender::OctreeEditPacketSender() : _serverJurisdictions(NULL), _sequenceNumber(0), _maxPacketSize(MAX_PACKET_SIZE) { - //printf("OctreeEditPacketSender::OctreeEditPacketSender() [%p] created... \n", this); + //qDebug("OctreeEditPacketSender::OctreeEditPacketSender() [%p] created... ", this); } OctreeEditPacketSender::~OctreeEditPacketSender() { @@ -52,7 +53,7 @@ OctreeEditPacketSender::~OctreeEditPacketSender() { _preServerPackets.erase(_preServerPackets.begin()); } _pendingPacketsLock.unlock(); - //printf("OctreeEditPacketSender::~OctreeEditPacketSender() [%p] destroyed... \n", this); + //qDebug("OctreeEditPacketSender::~OctreeEditPacketSender() [%p] destroyed... ", this); } diff --git a/libraries/octree/src/OctreeEditPacketSender.h b/libraries/octree/src/OctreeEditPacketSender.h index 75ad02a1c6..f9326b6ed3 100644 --- a/libraries/octree/src/OctreeEditPacketSender.h +++ b/libraries/octree/src/OctreeEditPacketSender.h @@ -1,15 +1,16 @@ // // OctreeEditPacketSender.h -// shared +// libraries/octree/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Octree Edit Packet Sender +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef __shared__OctreeEditPacketSender__ -#define __shared__OctreeEditPacketSender__ +#ifndef hifi_OctreeEditPacketSender_h +#define hifi_OctreeEditPacketSender_h #include #include @@ -114,4 +115,4 @@ protected: unsigned short int _sequenceNumber; int _maxPacketSize; }; -#endif // __shared__OctreeEditPacketSender__ +#endif // hifi_OctreeEditPacketSender_h diff --git a/libraries/octree/src/OctreeElement.cpp b/libraries/octree/src/OctreeElement.cpp index 7697338a28..846cf564c2 100644 --- a/libraries/octree/src/OctreeElement.cpp +++ b/libraries/octree/src/OctreeElement.cpp @@ -1,9 +1,12 @@ // // OctreeElement.cpp -// hifi +// libraries/octree/src // // Created by Stephen Birarda on 3/13/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -18,10 +21,10 @@ #include "AABox.h" #include "OctalCode.h" -#include "SharedUtil.h" #include "OctreeConstants.h" #include "OctreeElement.h" #include "Octree.h" +#include "SharedUtil.h" quint64 OctreeElement::_voxelMemoryUsage = 0; quint64 OctreeElement::_octcodeMemoryUsage = 0; @@ -201,7 +204,7 @@ void OctreeElement::calculateAABox() { void OctreeElement::deleteChildAtIndex(int childIndex) { OctreeElement* childAt = getChildAtIndex(childIndex); if (childAt) { - //printf("deleteChildAtIndex()... about to call delete childAt=%p\n",childAt); + //qDebug("deleteChildAtIndex()... about to call delete childAt=%p",childAt); delete childAt; setChildAtIndex(childIndex, NULL); _isDirty = true; @@ -1312,7 +1315,7 @@ OctreeElement* OctreeElement::getOrCreateChildElementAt(float x, float y, float float halfOurScale = ourScale / 2.0f; if(s > ourScale) { - printf("UNEXPECTED -- OctreeElement::getOrCreateChildElementAt() s=[%f] > ourScale=[%f] \n", s, ourScale); + qDebug("UNEXPECTED -- OctreeElement::getOrCreateChildElementAt() s=[%f] > ourScale=[%f] ", s, ourScale); } if (s > halfOurScale) { diff --git a/libraries/octree/src/OctreeElement.h b/libraries/octree/src/OctreeElement.h index a37866bdbe..c5eec1c9e2 100644 --- a/libraries/octree/src/OctreeElement.h +++ b/libraries/octree/src/OctreeElement.h @@ -1,13 +1,16 @@ // // OctreeElement.h -// hifi +// libraries/octree/src // // Created by Stephen Birarda on 3/13/13. +// Copyright 2013 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__OctreeElement__ -#define __hifi__OctreeElement__ +#ifndef hifi_OctreeElement_h +#define hifi_OctreeElement_h //#define HAS_AUDIT_CHILDREN //#define SIMPLE_CHILD_ARRAY @@ -16,6 +19,7 @@ #include #include + #include "AABox.h" #include "ViewFrustum.h" #include "OctreeConstants.h" @@ -316,4 +320,4 @@ protected: static quint64 _childrenCount[NUMBER_OF_CHILDREN + 1]; }; -#endif /* defined(__hifi__OctreeElement__) */ \ No newline at end of file +#endif // hifi_OctreeElement_h diff --git a/libraries/octree/src/OctreeElementBag.cpp b/libraries/octree/src/OctreeElementBag.cpp index 3ecdfaf2e3..92a8fe5bff 100644 --- a/libraries/octree/src/OctreeElementBag.cpp +++ b/libraries/octree/src/OctreeElementBag.cpp @@ -1,9 +1,12 @@ // // OctreeElementBag.cpp -// hifi +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 4/25/2013 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 4/25/2013. +// Copyright 2013 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 "OctreeElementBag.h" @@ -13,13 +16,21 @@ OctreeElementBag::OctreeElementBag() : _bagElements() { OctreeElement::addDeleteHook(this); + _hooked = true; }; OctreeElementBag::~OctreeElementBag() { - OctreeElement::removeDeleteHook(this); + unhookNotifications(); deleteAll(); } +void OctreeElementBag::unhookNotifications() { + if (_hooked) { + OctreeElement::removeDeleteHook(this); + _hooked = false; + } +} + void OctreeElementBag::elementDeleted(OctreeElement* element) { remove(element); // note: remove can safely handle nodes that aren't in it, so we don't need to check contains() } diff --git a/libraries/octree/src/OctreeElementBag.h b/libraries/octree/src/OctreeElementBag.h index ba74a05516..8c18ece773 100644 --- a/libraries/octree/src/OctreeElementBag.h +++ b/libraries/octree/src/OctreeElementBag.h @@ -1,18 +1,21 @@ // // OctreeElementBag.h -// hifi +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 4/25/2013 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 4/25/2013. +// Copyright 2013 High Fidelity, Inc. // // This class is used by the VoxelTree:encodeTreeBitstream() functions to store extra nodes that need to be sent // it's a generic bag style storage mechanism. But It has the property that you can't put the same node into the bag // more than once (in other words, it de-dupes automatically), also, it supports collapsing it's several peer nodes // into a parent node in cases where you add enough peers that it makes more sense to just add the parent. // +// 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__OctreeElementBag__ -#define __hifi__OctreeElementBag__ +#ifndef hifi_OctreeElementBag_h +#define hifi_OctreeElementBag_h #include "OctreeElement.h" @@ -33,8 +36,11 @@ public: void deleteAll(); virtual void elementDeleted(OctreeElement* element); + void unhookNotifications(); + private: QSet _bagElements; + bool _hooked; }; -#endif /* defined(__hifi__OctreeElementBag__) */ +#endif // hifi_OctreeElementBag_h diff --git a/libraries/octree/src/OctreeHeadlessViewer.cpp b/libraries/octree/src/OctreeHeadlessViewer.cpp index 5d2ce2d287..5574b376cb 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.cpp +++ b/libraries/octree/src/OctreeHeadlessViewer.cpp @@ -1,10 +1,12 @@ // // OctreeHeadlessViewer.cpp -// hifi +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 2/26/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/26/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 @@ -219,7 +221,7 @@ void OctreeHeadlessViewer::queryOctree() { int packetLength = endOfQueryPacket - queryPacket; // make sure we still have an active socket - nodeList->writeDatagram(reinterpret_cast(queryPacket), packetLength, node); + nodeList->writeUnverifiedDatagram(reinterpret_cast(queryPacket), packetLength, node); } } } diff --git a/libraries/octree/src/OctreeHeadlessViewer.h b/libraries/octree/src/OctreeHeadlessViewer.h index fc32dbc682..ebabf1dbad 100644 --- a/libraries/octree/src/OctreeHeadlessViewer.h +++ b/libraries/octree/src/OctreeHeadlessViewer.h @@ -1,14 +1,16 @@ // // OctreeHeadlessViewer.h -// hifi +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 2/26/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/26/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__OctreeHeadlessViewer__ -#define __hifi__OctreeHeadlessViewer__ +#ifndef hifi_OctreeHeadlessViewer_h +#define hifi_OctreeHeadlessViewer_h #include #include @@ -70,4 +72,4 @@ private: int _maxPacketsPerSecond; }; -#endif /* defined(__hifi__OctreeHeadlessViewer__) */ \ No newline at end of file +#endif // hifi_OctreeHeadlessViewer_h diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 5bd4fdce49..b54a87bef8 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -1,9 +1,12 @@ // // OctreePacketData.cpp -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 11/19/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -122,7 +125,7 @@ const unsigned char* OctreePacketData::getFinalizedData() { if (_dirty) { if (_debug) { - printf("getFinalizedData() _compressedBytes=%d _bytesInUse=%d\n",_compressedBytes, _bytesInUse); + qDebug("getFinalizedData() _compressedBytes=%d _bytesInUse=%d",_compressedBytes, _bytesInUse); } compressContent(); } @@ -136,7 +139,7 @@ int OctreePacketData::getFinalizedSize() { if (_dirty) { if (_debug) { - printf("getFinalizedSize() _compressedBytes=%d _bytesInUse=%d\n",_compressedBytes, _bytesInUse); + qDebug("getFinalizedSize() _compressedBytes=%d _bytesInUse=%d",_compressedBytes, _bytesInUse); } compressContent(); } @@ -184,7 +187,7 @@ void OctreePacketData::discardLevel(LevelDetails key) { _totalBytesOfColor -= reduceBytesOfColor; if (_debug) { - printf("discardLevel() BEFORE _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d\n", + qDebug("discardLevel() BEFORE _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d", debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse); } @@ -193,7 +196,7 @@ void OctreePacketData::discardLevel(LevelDetails key) { _dirty = true; if (_debug) { - printf("discardLevel() AFTER _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d\n", + qDebug("discardLevel() AFTER _dirty=%s bytesInLevel=%d _compressedBytes=%d _bytesInUse=%d", debug::valueOf(_dirty), bytesInLevel, _compressedBytes, _bytesInUse); } } @@ -410,13 +413,13 @@ void OctreePacketData::loadFinalizedContent(const unsigned char* data, int lengt } } else { if (_debug) { - printf("OctreePacketData::loadCompressedContent()... length = 0, nothing to do...\n"); + qDebug("OctreePacketData::loadCompressedContent()... length = 0, nothing to do..."); } } } void OctreePacketData::debugContent() { - printf("OctreePacketData::debugContent()... COMPRESSED DATA.... size=%d\n",_compressedBytes); + qDebug("OctreePacketData::debugContent()... COMPRESSED DATA.... size=%d",_compressedBytes); int perline=0; for (int i = 0; i < _compressedBytes; i++) { printf("%.2x ",_compressed[i]); @@ -428,7 +431,7 @@ void OctreePacketData::debugContent() { } printf("\n"); - printf("OctreePacketData::debugContent()... UNCOMPRESSED DATA.... size=%d\n",_bytesInUse); + qDebug("OctreePacketData::debugContent()... UNCOMPRESSED DATA.... size=%d",_bytesInUse); perline=0; for (int i = 0; i < _bytesInUse; i++) { printf("%.2x ",_uncompressed[i]); diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index 5d7a904a4c..d802f8e808 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -1,11 +1,11 @@ // // OctreePacketData.h -// hifi +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 11/19/2013 +// Created by Brad Hefta-Gaub on 11/19/2013. +// Copyright 2013 High Fidelity, Inc. // // TO DO: -// // * add stats tracking for number of unique colors and consecutive identical colors. // (as research for color dictionaries and RLE) // @@ -15,9 +15,12 @@ // but wouldn't work with RLE because the colors in the levels would get reordered and RLE would need // to be recalculated // +// 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__OctreePacketData__ -#define __hifi__OctreePacketData__ +#ifndef hifi_OctreePacketData_h +#define hifi_OctreePacketData_h #include #include "OctreeConstants.h" @@ -224,4 +227,4 @@ private: static quint64 _totalBytesOfRawData; }; -#endif /* defined(__hifi__OctreePacketData__) */ +#endif // hifi_OctreePacketData_h diff --git a/libraries/octree/src/OctreePersistThread.cpp b/libraries/octree/src/OctreePersistThread.cpp index 29715f9d90..7613e2332b 100644 --- a/libraries/octree/src/OctreePersistThread.cpp +++ b/libraries/octree/src/OctreePersistThread.cpp @@ -1,11 +1,12 @@ // // OctreePersistThread.cpp -// Octree-server +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded Octree persistence +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/libraries/octree/src/OctreePersistThread.h b/libraries/octree/src/OctreePersistThread.h index ce6190b0e6..2f86320b2e 100644 --- a/libraries/octree/src/OctreePersistThread.h +++ b/libraries/octree/src/OctreePersistThread.h @@ -1,15 +1,18 @@ // // OctreePersistThread.h -// Octree-server +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 8/21/13 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 8/21/13. +// Copyright 2013 High Fidelity, Inc. // // Threaded or non-threaded Octree persistence // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef __Octree_server__OctreePersistThread__ -#define __Octree_server__OctreePersistThread__ +#ifndef hifi_OctreePersistThread_h +#define hifi_OctreePersistThread_h #include #include @@ -42,4 +45,4 @@ private: quint64 _lastCheck; }; -#endif // __Octree_server__OctreePersistThread__ +#endif // hifi_OctreePersistThread_h diff --git a/libraries/octree/src/OctreeProjectedPolygon.cpp b/libraries/octree/src/OctreeProjectedPolygon.cpp index 6e5cea28cd..92a5b2ac18 100644 --- a/libraries/octree/src/OctreeProjectedPolygon.cpp +++ b/libraries/octree/src/OctreeProjectedPolygon.cpp @@ -1,8 +1,12 @@ // -// OctreeProjectedPolygon.cpp - The projected shadow (on the 2D view plane) for a voxel -// hifi +// OctreeProjectedPolygon.cpp +// libraries/octree/src // -// Added by Brad Hefta-Gaub on 06/11/13. +// Created by Brad Hefta-Gaub on 06/11/13. +// Copyright 2013 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 @@ -259,12 +263,12 @@ bool OctreeProjectedPolygon::pointInside(const glm::vec2& point, bool* matchesVe } void OctreeProjectedPolygon::printDebugDetails() const { - printf("OctreeProjectedPolygon..."); - printf(" minX=%f maxX=%f minY=%f maxY=%f\n", getMinX(), getMaxX(), getMinY(), getMaxY()); - printf(" vertex count=%d distance=%f\n", getVertexCount(), getDistance()); + qDebug("OctreeProjectedPolygon..." + " minX=%f maxX=%f minY=%f maxY=%f", getMinX(), getMaxX(), getMinY(), getMaxY()); + qDebug(" vertex count=%d distance=%f", getVertexCount(), getDistance()); for (int i = 0; i < getVertexCount(); i++) { glm::vec2 point = getVertex(i); - printf(" vertex[%d] = %f, %f \n", i, point.x, point.y); + qDebug(" vertex[%d] = %f, %f ", i, point.x, point.y); } } diff --git a/libraries/octree/src/OctreeProjectedPolygon.h b/libraries/octree/src/OctreeProjectedPolygon.h index 94e55a11d5..5a2a36f280 100644 --- a/libraries/octree/src/OctreeProjectedPolygon.h +++ b/libraries/octree/src/OctreeProjectedPolygon.h @@ -1,12 +1,18 @@ // -// OctreeProjectedPolygon.h - The projected shadow (on the 2D view plane) for a voxel -// hifi +// OctreeProjectedPolygon.h +// libraries/octree/src // -// Added by Brad Hefta-Gaub on 06/11/13. +// Created by Brad Hefta-Gaub on 06/11/13. +// Copyright 2013 High Fidelity, Inc. +// +// The projected shadow (on the 2D view plane) for a voxel +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef _VOXEL_PROJECTED_SHADOW_ -#define _VOXEL_PROJECTED_SHADOW_ +#ifndef hifi_OctreeProjectedPolygon_h +#define hifi_OctreeProjectedPolygon_h #include @@ -126,4 +132,4 @@ private: }; -#endif // _VOXEL_PROJECTED_SHADOW_ +#endif // hifi_OctreeProjectedPolygon_h diff --git a/libraries/octree/src/OctreeQuery.cpp b/libraries/octree/src/OctreeQuery.cpp index 51e235c4e2..687dd18037 100644 --- a/libraries/octree/src/OctreeQuery.cpp +++ b/libraries/octree/src/OctreeQuery.cpp @@ -1,9 +1,12 @@ // // OctreeQuery.cpp -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 10/24/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/octree/src/OctreeQuery.h b/libraries/octree/src/OctreeQuery.h index 3b53aaa501..8c4460bc73 100644 --- a/libraries/octree/src/OctreeQuery.h +++ b/libraries/octree/src/OctreeQuery.h @@ -1,13 +1,16 @@ // // OctreeQuery.h -// hifi +// libraries/octree/src // // Created by Stephen Birarda on 4/9/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__OctreeQuery__ -#define __hifi__OctreeQuery__ +#ifndef hifi_OctreeQuery_h +#define hifi_OctreeQuery_h /* VS2010 defines stdint.h, but not inttypes.h */ #if defined(_MSC_VER) @@ -113,4 +116,4 @@ private: OctreeQuery& operator= (const OctreeQuery&); }; -#endif /* defined(__hifi__OctreeQuery__) */ +#endif // hifi_OctreeQuery_h diff --git a/libraries/octree/src/OctreeRenderer.cpp b/libraries/octree/src/OctreeRenderer.cpp index 5d4543e3aa..cde1f0923d 100644 --- a/libraries/octree/src/OctreeRenderer.cpp +++ b/libraries/octree/src/OctreeRenderer.cpp @@ -1,10 +1,12 @@ // // OctreeRenderer.cpp -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 12/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/octree/src/OctreeRenderer.h b/libraries/octree/src/OctreeRenderer.h index 2e9762a883..fbd82fad8f 100644 --- a/libraries/octree/src/OctreeRenderer.h +++ b/libraries/octree/src/OctreeRenderer.h @@ -1,14 +1,16 @@ // // OctreeRenderer.h -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 12/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__OctreeRenderer__ -#define __hifi__OctreeRenderer__ +#ifndef hifi_OctreeRenderer_h +#define hifi_OctreeRenderer_h #include #include @@ -69,4 +71,4 @@ protected: ViewFrustum* _viewFrustum; }; -#endif /* defined(__hifi__OctreeRenderer__) */ \ No newline at end of file +#endif // hifi_OctreeRenderer_h diff --git a/libraries/octree/src/OctreeSceneStats.cpp b/libraries/octree/src/OctreeSceneStats.cpp index a21ab60493..ad8702adc2 100644 --- a/libraries/octree/src/OctreeSceneStats.cpp +++ b/libraries/octree/src/OctreeSceneStats.cpp @@ -1,10 +1,12 @@ // // OctreeSceneStats.cpp -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 7/18/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/octree/src/OctreeSceneStats.h b/libraries/octree/src/OctreeSceneStats.h index 866c5d0b85..140e33aedf 100644 --- a/libraries/octree/src/OctreeSceneStats.h +++ b/libraries/octree/src/OctreeSceneStats.h @@ -1,14 +1,16 @@ // // OctreeSceneStats.h -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 7/18/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__OctreeSceneStats__ -#define __hifi__OctreeSceneStats__ +#ifndef hifi_OctreeSceneStats_h +#define hifi_OctreeSceneStats_h #include #include @@ -290,4 +292,4 @@ private: typedef std::map NodeToOctreeSceneStats; typedef std::map::iterator NodeToOctreeSceneStatsIterator; -#endif /* defined(__hifi__OctreeSceneStats__) */ +#endif // hifi_OctreeSceneStats_h diff --git a/libraries/octree/src/OctreeScriptingInterface.cpp b/libraries/octree/src/OctreeScriptingInterface.cpp index 982496bc58..97e7f67ff5 100644 --- a/libraries/octree/src/OctreeScriptingInterface.cpp +++ b/libraries/octree/src/OctreeScriptingInterface.cpp @@ -1,9 +1,12 @@ // // OctreeScriptingInterface.cpp -// hifi +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 12/6/13 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 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 diff --git a/libraries/octree/src/OctreeScriptingInterface.h b/libraries/octree/src/OctreeScriptingInterface.h index 3003950bbf..52d7c8be34 100644 --- a/libraries/octree/src/OctreeScriptingInterface.h +++ b/libraries/octree/src/OctreeScriptingInterface.h @@ -1,13 +1,16 @@ // // OctreeScriptingInterface.h -// hifi +// libraries/octree/src // -// Created by Brad Hefta-Gaub on 12/6/13 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 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__OctreeScriptingInterface__ -#define __hifi__OctreeScriptingInterface__ +#ifndef hifi_OctreeScriptingInterface_h +#define hifi_OctreeScriptingInterface_h #include @@ -96,4 +99,4 @@ protected: bool _initialized; }; -#endif /* defined(__hifi__OctreeScriptingInterface__) */ +#endif // hifi_OctreeScriptingInterface_h diff --git a/libraries/octree/src/Plane.cpp b/libraries/octree/src/Plane.cpp index 6a356784b7..d9e5633233 100755 --- a/libraries/octree/src/Plane.cpp +++ b/libraries/octree/src/Plane.cpp @@ -1,15 +1,21 @@ // -// Plane.h -// hifi +// Plane.cpp +// libraries/octree/src/ // // Created by Brad Hefta-Gaub on 04/11/13. -// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Copyright 2013 High Fidelity, Inc. // +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards // Simple plane class. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// #include "Plane.h" +#include + #include void Plane::set3Points(const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3) { @@ -57,6 +63,6 @@ float Plane::distance(const glm::vec3 &point) const { } void Plane::print() const { - printf("Plane - point (x=%f y=%f z=%f) normal (x=%f y=%f z=%f) d=%f\n", + qDebug("Plane - point (x=%f y=%f z=%f) normal (x=%f y=%f z=%f) d=%f", _point.x, _point.y, _point.z, _normal.x, _normal.y, _normal.z, _dCoefficient); } diff --git a/libraries/octree/src/Plane.h b/libraries/octree/src/Plane.h index 8ce0e5042e..0f481dee57 100755 --- a/libraries/octree/src/Plane.h +++ b/libraries/octree/src/Plane.h @@ -1,20 +1,23 @@ // // Plane.h -// hifi +// libraries/octree/src/ // // Created by Brad Hefta-Gaub on 04/11/13. -// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards +// Copyright 2013 High Fidelity, Inc. // +// Originally from lighthouse3d. Modified to utilize glm::vec3 and clean up to our coding standards // Simple plane class. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef _PLANE_ -#define _PLANE_ +#ifndef hifi_Plane_h +#define hifi_Plane_h #include -class Plane -{ +class Plane { public: Plane(const glm::vec3 &v1, const glm::vec3 &v2, const glm::vec3 &v3) { set3Points(v1,v2,v3); } Plane() : _normal(0,0,0), _point(0,0,0), _dCoefficient(0) {}; @@ -41,4 +44,4 @@ private: }; -#endif \ No newline at end of file +#endif // hifi_Plane_h \ No newline at end of file diff --git a/libraries/octree/src/ViewFrustum.cpp b/libraries/octree/src/ViewFrustum.cpp index fa6873b093..c2e38b73c9 100644 --- a/libraries/octree/src/ViewFrustum.cpp +++ b/libraries/octree/src/ViewFrustum.cpp @@ -1,11 +1,12 @@ // // ViewFrustum.cpp -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 04/11/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Simple view frustum class. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include @@ -129,9 +130,11 @@ void ViewFrustum::calculate() { // Also calculate our projection matrix in case people want to project points... // Projection matrix : Field of View, ratio, display range : near to far - glm::mat4 projection = glm::perspective(_fieldOfView, _aspectRatio, _nearClip, _farClip); - glm::vec3 lookAt = _position + _direction; - glm::mat4 view = glm::lookAt(_position, lookAt, _up); + const float CLIP_NUDGE = 1.0f; + float farClip = (_farClip != _nearClip) ? _farClip : _nearClip + CLIP_NUDGE; // don't allow near and far to be equal + glm::mat4 projection = glm::perspective(_fieldOfView, _aspectRatio, _nearClip, farClip); + glm::vec3 lookAt = _position + _direction; + glm::mat4 view = glm::lookAt(_position, lookAt, _up); // Our ModelViewProjection : multiplication of our 3 matrices (note: model is identity, so we can drop it) _ourModelViewProjectionMatrix = projection * view; // Remember, matrix multiplication is the other way around @@ -424,10 +427,6 @@ bool ViewFrustum::matches(const ViewFrustum& compareTo, bool debug) const { return result; } -bool isNaN(float f) { - return f != f; -} - bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const { // Compute distance between the two positions @@ -449,7 +448,7 @@ bool ViewFrustum::isVerySimilar(const ViewFrustum& compareTo, bool debug) const float angleEyeOffsetOrientation = compareTo._eyeOffsetOrientation == _eyeOffsetOrientation ? 0.0f : glm::degrees(glm::angle(dQEyeOffsetOrientation)); if (isNaN(angleEyeOffsetOrientation)) { - angleOrientation = 0.0f; + angleEyeOffsetOrientation = 0.0f; } bool result = diff --git a/libraries/octree/src/ViewFrustum.h b/libraries/octree/src/ViewFrustum.h index 7a1c3b49ba..acd5c639f7 100644 --- a/libraries/octree/src/ViewFrustum.h +++ b/libraries/octree/src/ViewFrustum.h @@ -1,22 +1,24 @@ // // ViewFrustum.h -// hifi +// libraries/octree/src // // Created by Brad Hefta-Gaub on 04/11/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // Simple view frustum class. // +// 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__ViewFrustum__ -#define __hifi__ViewFrustum__ +#ifndef hifi_ViewFrustum_h +#define hifi_ViewFrustum_h #include #include #include "AABox.h" #include "Plane.h" - #include "OctreeConstants.h" #include "OctreeProjectedPolygon.h" @@ -172,4 +174,4 @@ private: }; -#endif /* defined(__hifi__ViewFrustum__) */ +#endif // hifi_ViewFrustum_h diff --git a/libraries/particles/CMakeLists.txt b/libraries/particles/CMakeLists.txt index b483e3e479..1cb60756a2 100644 --- a/libraries/particles/CMakeLists.txt +++ b/libraries/particles/CMakeLists.txt @@ -23,9 +23,17 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) 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 ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# 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 "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets "${GNUTLS_LIBRARY}") diff --git a/libraries/particles/src/Particle.cpp b/libraries/particles/src/Particle.cpp index f4a5785bad..59265c00dc 100644 --- a/libraries/particles/src/Particle.cpp +++ b/libraries/particles/src/Particle.cpp @@ -1,10 +1,12 @@ // // Particle.cpp -// hifi +// libraries/particles/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -144,7 +146,7 @@ bool Particle::appendParticleData(OctreePacketData* packetData) const { bool success = packetData->appendValue(getID()); - //printf("Particle::appendParticleData()... getID()=%d\n", getID()); + //qDebug("Particle::appendParticleData()... getID()=%d", getID()); if (success) { success = packetData->appendValue(getAge()); @@ -539,15 +541,15 @@ Particle Particle::fromEditPacket(const unsigned char* data, int length, int& pr } void Particle::debugDump() const { - printf("Particle id :%u\n", _id); - printf(" age:%f\n", getAge()); - printf(" edited ago:%f\n", getEditedAgo()); - printf(" should die:%s\n", debug::valueOf(getShouldDie())); - printf(" position:%f,%f,%f\n", _position.x, _position.y, _position.z); - printf(" radius:%f\n", getRadius()); - printf(" velocity:%f,%f,%f\n", _velocity.x, _velocity.y, _velocity.z); - printf(" gravity:%f,%f,%f\n", _gravity.x, _gravity.y, _gravity.z); - printf(" color:%d,%d,%d\n", _color[0], _color[1], _color[2]); + qDebug("Particle id :%u", _id); + qDebug(" age:%f", getAge()); + qDebug(" edited ago:%f", getEditedAgo()); + qDebug(" should die:%s", debug::valueOf(getShouldDie())); + qDebug(" position:%f,%f,%f", _position.x, _position.y, _position.z); + qDebug(" radius:%f", getRadius()); + qDebug(" velocity:%f,%f,%f", _velocity.x, _velocity.y, _velocity.z); + qDebug(" gravity:%f,%f,%f", _gravity.x, _gravity.y, _gravity.z); + qDebug(" color:%d,%d,%d", _color[0], _color[1], _color[2]); } bool Particle::encodeParticleEditMessageDetails(PacketType command, ParticleID id, const ParticleProperties& properties, @@ -730,9 +732,9 @@ bool Particle::encodeParticleEditMessageDetails(PacketType command, ParticleID i bool wantDebugging = false; if (wantDebugging) { - printf("encodeParticleEditMessageDetails()....\n"); - printf("Particle id :%u\n", id.id); - printf(" nextID:%u\n", _nextID); + qDebug("encodeParticleEditMessageDetails()...."); + qDebug("Particle id :%u", id.id); + qDebug(" nextID:%u", _nextID); } // cleanup @@ -842,7 +844,7 @@ void Particle::update(const quint64& now) { // handle damping glm::vec3 dampingResistance = _velocity * _damping; _velocity -= dampingResistance * timeElapsed; - //printf("applying damping to Particle timeElapsed=%f\n",timeElapsed); + //qDebug("applying damping to Particle timeElapsed=%f",timeElapsed); } } diff --git a/libraries/particles/src/Particle.h b/libraries/particles/src/Particle.h index dbe98c5bf6..c243363241 100644 --- a/libraries/particles/src/Particle.h +++ b/libraries/particles/src/Particle.h @@ -1,14 +1,16 @@ // // Particle.h -// hifi +// libraries/particles/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__Particle__ -#define __hifi__Particle__ +#ifndef hifi_Particle_h +#define hifi_Particle_h #include #include @@ -428,4 +430,4 @@ private: -#endif /* defined(__hifi__Particle__) */ +#endif // hifi_Particle_h diff --git a/libraries/particles/src/ParticleCollisionSystem.cpp b/libraries/particles/src/ParticleCollisionSystem.cpp index c827e28c78..57a339d81f 100644 --- a/libraries/particles/src/ParticleCollisionSystem.cpp +++ b/libraries/particles/src/ParticleCollisionSystem.cpp @@ -1,10 +1,12 @@ // // ParticleCollisionSystem.cpp -// hifi +// libraries/particles/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -72,17 +74,17 @@ void ParticleCollisionSystem::checkParticle(Particle* particle) { } void ParticleCollisionSystem::emitGlobalParticleCollisionWithVoxel(Particle* particle, - VoxelDetail* voxelDetails, const glm::vec3& penetration) { + VoxelDetail* voxelDetails, const CollisionInfo& collision) { ParticleID particleID = particle->getParticleID(); - emit particleCollisionWithVoxel(particleID, *voxelDetails, penetration); + emit particleCollisionWithVoxel(particleID, *voxelDetails, collision); } void ParticleCollisionSystem::emitGlobalParticleCollisionWithParticle(Particle* particleA, - Particle* particleB, const glm::vec3& penetration) { + Particle* particleB, const CollisionInfo& collision) { ParticleID idA = particleA->getParticleID(); ParticleID idB = particleB->getParticleID(); - emit particleCollisionWithParticle(idA, idB, penetration); + emit particleCollisionWithParticle(idA, idB, collision); } void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { @@ -100,11 +102,17 @@ void ParticleCollisionSystem::updateCollisionWithVoxels(Particle* particle) { // let the particles run their collision scripts if they have them particle->collisionWithVoxel(voxelDetails, collisionInfo._penetration); - // let the global script run their collision scripts for particles if they have them - emitGlobalParticleCollisionWithVoxel(particle, voxelDetails, collisionInfo._penetration); - + // findSpherePenetration() only computes the penetration but we also want some other collision info + // so we compute it ourselves here. Note that we must multiply scale by TREE_SCALE when feeding + // the results to systems outside of this octree reference frame. updateCollisionSound(particle, collisionInfo._penetration, COLLISION_FREQUENCY); + collisionInfo._contactPoint = (float)TREE_SCALE * (particle->getPosition() + particle->getRadius() * glm::normalize(collisionInfo._penetration)); + // let the global script run their collision scripts for particles if they have them + emitGlobalParticleCollisionWithVoxel(particle, voxelDetails, collisionInfo); + + // we must scale back down to the octree reference frame before updating the particle properties collisionInfo._penetration /= (float)(TREE_SCALE); + collisionInfo._contactPoint /= (float)(TREE_SCALE); particle->applyHardCollision(collisionInfo); queueParticlePropertiesUpdate(particle); @@ -121,8 +129,7 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) glm::vec3 penetration; Particle* particleB; if (_particles->findSpherePenetration(center, radius, penetration, (void**)&particleB, Octree::NoLock)) { - // NOTE: 'penetration' is the depth that 'particleA' overlaps 'particleB'. - // That is, it points from A into B. + // NOTE: 'penetration' is the depth that 'particleA' overlaps 'particleB'. It points from A into B. // Even if the particles overlap... when the particles are already moving appart // we don't want to count this as a collision. @@ -130,7 +137,12 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) if (glm::dot(relativeVelocity, penetration) > 0.0f) { particleA->collisionWithParticle(particleB, penetration); particleB->collisionWithParticle(particleA, penetration * -1.0f); // the penetration is reversed - emitGlobalParticleCollisionWithParticle(particleA, particleB, penetration); + + CollisionInfo collision; + collision._penetration = penetration; + // for now the contactPoint is the average between the the two paricle centers + collision._contactPoint = (0.5f * (float)TREE_SCALE) * (particleA->getPosition() + particleB->getPosition()); + emitGlobalParticleCollisionWithParticle(particleA, particleB, collision); glm::vec3 axis = glm::normalize(penetration); glm::vec3 axialVelocity = glm::dot(relativeVelocity, axis) * axis; @@ -142,25 +154,25 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA) float massB = (particleB->getInHand()) ? MAX_MASS : particleB->getMass(); float totalMass = massA + massB; - // handle A particle + // handle particle A particleA->setVelocity(particleA->getVelocity() - axialVelocity * (2.0f * massB / totalMass)); particleA->setPosition(particleA->getPosition() - 0.5f * penetration); ParticleProperties propertiesA; - ParticleID particleAid(particleA->getID()); + ParticleID idA(particleA->getID()); propertiesA.copyFromParticle(*particleA); propertiesA.setVelocity(particleA->getVelocity() * (float)TREE_SCALE); propertiesA.setPosition(particleA->getPosition() * (float)TREE_SCALE); - _packetSender->queueParticleEditMessage(PacketTypeParticleAddOrEdit, particleAid, propertiesA); + _packetSender->queueParticleEditMessage(PacketTypeParticleAddOrEdit, idA, propertiesA); - // handle B particle + // handle particle B particleB->setVelocity(particleB->getVelocity() + axialVelocity * (2.0f * massA / totalMass)); particleA->setPosition(particleB->getPosition() + 0.5f * penetration); ParticleProperties propertiesB; - ParticleID particleBid(particleB->getID()); + ParticleID idB(particleB->getID()); propertiesB.copyFromParticle(*particleB); propertiesB.setVelocity(particleB->getVelocity() * (float)TREE_SCALE); propertiesB.setPosition(particleB->getPosition() * (float)TREE_SCALE); - _packetSender->queueParticleEditMessage(PacketTypeParticleAddOrEdit, particleBid, propertiesB); + _packetSender->queueParticleEditMessage(PacketTypeParticleAddOrEdit, idB, propertiesB); _packetSender->releaseQueuedMessages(); diff --git a/libraries/particles/src/ParticleCollisionSystem.h b/libraries/particles/src/ParticleCollisionSystem.h index 1b30fd31ac..cf9c4b938c 100644 --- a/libraries/particles/src/ParticleCollisionSystem.h +++ b/libraries/particles/src/ParticleCollisionSystem.h @@ -1,14 +1,16 @@ // // ParticleCollisionSystem.h -// hifi +// libraries/particles/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__ParticleCollisionSystem__ -#define __hifi__ParticleCollisionSystem__ +#ifndef hifi_ParticleCollisionSystem_h +#define hifi_ParticleCollisionSystem_h #include #include @@ -53,13 +55,13 @@ public: void updateCollisionSound(Particle* particle, const glm::vec3 &penetration, float frequency); signals: - void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel, const glm::vec3& penetration); - void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB, const glm::vec3& penetration); + void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel, const CollisionInfo& penetration); + void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB, const CollisionInfo& penetration); private: static bool updateOperation(OctreeElement* element, void* extraData); - void emitGlobalParticleCollisionWithVoxel(Particle* particle, VoxelDetail* voxelDetails, const glm::vec3& penetration); - void emitGlobalParticleCollisionWithParticle(Particle* particleA, Particle* particleB, const glm::vec3& penetration); + void emitGlobalParticleCollisionWithVoxel(Particle* particle, VoxelDetail* voxelDetails, const CollisionInfo& penetration); + void emitGlobalParticleCollisionWithParticle(Particle* particleA, Particle* particleB, const CollisionInfo& penetration); ParticleEditPacketSender* _packetSender; ParticleTree* _particles; @@ -69,4 +71,4 @@ private: CollisionList _collisions; }; -#endif /* defined(__hifi__ParticleCollisionSystem__) */ +#endif // hifi_ParticleCollisionSystem_h diff --git a/libraries/particles/src/ParticleEditPacketSender.cpp b/libraries/particles/src/ParticleEditPacketSender.cpp index f06b19aa2d..21a910ff16 100644 --- a/libraries/particles/src/ParticleEditPacketSender.cpp +++ b/libraries/particles/src/ParticleEditPacketSender.cpp @@ -1,11 +1,12 @@ // // ParticleEditPacketSender.cpp -// interface +// libraries/particles/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded voxel packet Sender for the Application +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/libraries/particles/src/ParticleEditPacketSender.h b/libraries/particles/src/ParticleEditPacketSender.h index 3169c5629d..15e3196785 100644 --- a/libraries/particles/src/ParticleEditPacketSender.h +++ b/libraries/particles/src/ParticleEditPacketSender.h @@ -1,15 +1,16 @@ // // ParticleEditPacketSender.h -// shared +// libraries/particles/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Voxel Packet Sender +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -#ifndef __shared__ParticleEditPacketSender__ -#define __shared__ParticleEditPacketSender__ +#ifndef hifi_ParticleEditPacketSender_h +#define hifi_ParticleEditPacketSender_h #include #include "Particle.h" @@ -32,4 +33,4 @@ public: virtual unsigned char getMyNodeType() const { return NodeType::ParticleServer; } virtual void adjustEditPacketForClockSkew(unsigned char* codeColorBuffer, ssize_t length, int clockSkew); }; -#endif // __shared__ParticleEditPacketSender__ +#endif // hifi_ParticleEditPacketSender_h diff --git a/libraries/particles/src/ParticleTree.cpp b/libraries/particles/src/ParticleTree.cpp index 57f6a6e665..aeaf25e23c 100644 --- a/libraries/particles/src/ParticleTree.cpp +++ b/libraries/particles/src/ParticleTree.cpp @@ -1,9 +1,12 @@ // // ParticleTree.cpp -// hifi +// libraries/particles/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 "ParticleTree.h" diff --git a/libraries/particles/src/ParticleTree.h b/libraries/particles/src/ParticleTree.h index f3b8f5183d..a31c2d38aa 100644 --- a/libraries/particles/src/ParticleTree.h +++ b/libraries/particles/src/ParticleTree.h @@ -1,13 +1,16 @@ // // ParticleTree.h -// hifi +// libraries/particles/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__ParticleTree__ -#define __hifi__ParticleTree__ +#ifndef hifi_ParticleTree_h +#define hifi_ParticleTree_h #include #include "ParticleTreeElement.h" @@ -92,4 +95,4 @@ private: QMultiMap _recentlyDeletedParticleIDs; }; -#endif /* defined(__hifi__ParticleTree__) */ +#endif // hifi_ParticleTree_h diff --git a/libraries/particles/src/ParticleTreeElement.cpp b/libraries/particles/src/ParticleTreeElement.cpp index ee916dcf2f..d28ccf2f5e 100644 --- a/libraries/particles/src/ParticleTreeElement.cpp +++ b/libraries/particles/src/ParticleTreeElement.cpp @@ -1,9 +1,12 @@ // // ParticleTreeElement.cpp -// hifi +// libraries/particles/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -139,7 +142,7 @@ bool ParticleTreeElement::updateParticle(const Particle& particle) { bool localOlder = thisParticle.getLastUpdated() < particle.getLastUpdated(); if (changedOnServer || localOlder) { if (wantDebug) { - printf("local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s\n", + qDebug("local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s", particle.getID(), (changedOnServer ? "CHANGED" : "same"), (localOlder ? "OLDER" : "NEWER"), difference, debug::valueOf(particle.isNewlyCreated()) ); @@ -147,8 +150,8 @@ bool ParticleTreeElement::updateParticle(const Particle& particle) { thisParticle.copyChangedProperties(particle); } else { if (wantDebug) { - printf(">>> IGNORING SERVER!!! Would've caused jutter! <<< " - "local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s\n", + qDebug(">>> IGNORING SERVER!!! Would've caused jutter! <<< " + "local particle [id:%d] %s and %s than server particle by %d, particle.isNewlyCreated()=%s", particle.getID(), (changedOnServer ? "CHANGED" : "same"), (localOlder ? "OLDER" : "NEWER"), difference, debug::valueOf(particle.isNewlyCreated()) ); diff --git a/libraries/particles/src/ParticleTreeElement.h b/libraries/particles/src/ParticleTreeElement.h index 2854066f3b..59f80d588a 100644 --- a/libraries/particles/src/ParticleTreeElement.h +++ b/libraries/particles/src/ParticleTreeElement.h @@ -1,14 +1,16 @@ // // ParticleTreeElement.h -// hifi +// libraries/particles/src // // Created by Brad Hefta-Gaub on 12/4/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__ParticleTreeElement__ -#define __hifi__ParticleTreeElement__ +#ifndef hifi_ParticleTreeElement_h +#define hifi_ParticleTreeElement_h //#include @@ -127,4 +129,4 @@ protected: QList* _particles; }; -#endif /* defined(__hifi__ParticleTreeElement__) */ +#endif // hifi_ParticleTreeElement_h diff --git a/libraries/particles/src/ParticleTreeHeadlessViewer.cpp b/libraries/particles/src/ParticleTreeHeadlessViewer.cpp index f5384c3fec..1c47447b1a 100644 --- a/libraries/particles/src/ParticleTreeHeadlessViewer.cpp +++ b/libraries/particles/src/ParticleTreeHeadlessViewer.cpp @@ -1,10 +1,12 @@ // // ParticleTreeHeadlessViewer.cpp -// hifi +// libraries/particles/src // -// Created by Brad Hefta-Gaub on 2/26/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/26/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 "ParticleTreeHeadlessViewer.h" diff --git a/libraries/particles/src/ParticleTreeHeadlessViewer.h b/libraries/particles/src/ParticleTreeHeadlessViewer.h index 08aa12280c..1d539bd46f 100644 --- a/libraries/particles/src/ParticleTreeHeadlessViewer.h +++ b/libraries/particles/src/ParticleTreeHeadlessViewer.h @@ -1,14 +1,16 @@ // // ParticleTreeHeadlessViewer.h -// hifi +// libraries/particles/src // -// Created by Brad Hefta-Gaub on 2/26/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/26/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__ParticleTreeHeadlessViewer__ -#define __hifi__ParticleTreeHeadlessViewer__ +#ifndef hifi_ParticleTreeHeadlessViewer_h +#define hifi_ParticleTreeHeadlessViewer_h #include #include @@ -39,4 +41,4 @@ public: virtual void init(); }; -#endif /* defined(__hifi__ParticleTreeHeadlessViewer__) */ \ No newline at end of file +#endif // hifi_ParticleTreeHeadlessViewer_h diff --git a/libraries/particles/src/ParticlesScriptingInterface.cpp b/libraries/particles/src/ParticlesScriptingInterface.cpp index a25dde1b9e..4adfed358e 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.cpp +++ b/libraries/particles/src/ParticlesScriptingInterface.cpp @@ -1,9 +1,12 @@ // // ParticlesScriptingInterface.cpp -// hifi +// libraries/particles/src // -// Created by Brad Hefta-Gaub on 12/6/13 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 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 "ParticlesScriptingInterface.h" diff --git a/libraries/particles/src/ParticlesScriptingInterface.h b/libraries/particles/src/ParticlesScriptingInterface.h index af5f76a6af..e1d628f45a 100644 --- a/libraries/particles/src/ParticlesScriptingInterface.h +++ b/libraries/particles/src/ParticlesScriptingInterface.h @@ -1,16 +1,21 @@ // // ParticlesScriptingInterface.h -// hifi +// libraries/particles/src // -// Created by Brad Hefta-Gaub on 12/6/13 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/6/13. +// Copyright 2013 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__ParticlesScriptingInterface__ -#define __hifi__ParticlesScriptingInterface__ +#ifndef hifi_ParticlesScriptingInterface_h +#define hifi_ParticlesScriptingInterface_h #include +#include + #include #include "ParticleEditPacketSender.h" @@ -27,17 +32,6 @@ public: void setParticleTree(ParticleTree* particleTree) { _particleTree = particleTree; } ParticleTree* getParticleTree(ParticleTree*) { return _particleTree; } -private slots: - /// inbound slots for external collision systems - void forwardParticleCollisionWithVoxel(const ParticleID& particleID, - const VoxelDetail& voxel, const glm::vec3& penetration) { - emit particleCollisionWithVoxel(particleID, voxel, penetration); - } - - void forwardParticleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB, const glm::vec3& penetration) { - emit particleCollisionWithParticle(idA, idB, penetration); - } - public slots: /// adds a particle with the specific properties ParticleID addParticle(const ParticleProperties& properties); @@ -66,8 +60,8 @@ public slots: QVector findParticles(const glm::vec3& center, float radius) const; signals: - void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel, const glm::vec3& penetration); - void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB, const glm::vec3& penetration); + void particleCollisionWithVoxel(const ParticleID& particleID, const VoxelDetail& voxel, const CollisionInfo& collision); + void particleCollisionWithParticle(const ParticleID& idA, const ParticleID& idB, const CollisionInfo& collision); private: void queueParticleMessage(PacketType packetType, ParticleID particleID, const ParticleProperties& properties); @@ -76,4 +70,4 @@ private: ParticleTree* _particleTree; }; -#endif /* defined(__hifi__ParticlesScriptingInterface__) */ +#endif // hifi_ParticlesScriptingInterface_h diff --git a/libraries/script-engine/CMakeLists.txt b/libraries/script-engine/CMakeLists.txt index 615000e260..48d13e7742 100644 --- a/libraries/script-engine/CMakeLists.txt +++ b/libraries/script-engine/CMakeLists.txt @@ -24,10 +24,17 @@ include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(fbx ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(particles ${TARGET_NAME} "${ROOT_DIR}") # link ZLIB find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets) +# 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 "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets) diff --git a/libraries/script-engine/src/AbstractControllerScriptingInterface.h b/libraries/script-engine/src/AbstractControllerScriptingInterface.h index 1878edd4d6..677ca27e04 100644 --- a/libraries/script-engine/src/AbstractControllerScriptingInterface.h +++ b/libraries/script-engine/src/AbstractControllerScriptingInterface.h @@ -1,13 +1,16 @@ // // AbstractControllerScriptingInterface.h -// hifi +// libraries/script-engine/src // -// Created by Brad Hefta-Gaub on 12/17/13 -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 12/17/13. +// Copyright 2013 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__AbstractControllerScriptingInterface__ -#define __hifi__AbstractControllerScriptingInterface__ +#ifndef hifi_AbstractControllerScriptingInterface_h +#define hifi_AbstractControllerScriptingInterface_h #include @@ -73,4 +76,4 @@ signals: }; -#endif /* defined(__hifi__AbstractControllerScriptingInterface__) */ +#endif // hifi_AbstractControllerScriptingInterface_h diff --git a/libraries/script-engine/src/AnimationCache.cpp b/libraries/script-engine/src/AnimationCache.cpp new file mode 100644 index 0000000000..8e1493f075 --- /dev/null +++ b/libraries/script-engine/src/AnimationCache.cpp @@ -0,0 +1,102 @@ +// +// AnimationCache.cpp +// libraries/script-engine/src/ +// +// Created by Andrzej Kapolka on 4/14/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include +#include + +#include "AnimationCache.h" + +static int animationPointerMetaTypeId = qRegisterMetaType(); + +AnimationCache::AnimationCache(QObject* parent) : + ResourceCache(parent) { +} + +AnimationPointer AnimationCache::getAnimation(const QUrl& url) { + if (QThread::currentThread() != thread()) { + AnimationPointer result; + QMetaObject::invokeMethod(this, "getAnimation", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(AnimationPointer, result), Q_ARG(const QUrl&, url)); + return result; + } + return getResource(url).staticCast(); +} + +QSharedPointer AnimationCache::createResource(const QUrl& url, const QSharedPointer& fallback, + bool delayLoad, const void* extra) { + return QSharedPointer(new Animation(url), &Resource::allReferencesCleared); +} + +Animation::Animation(const QUrl& url) : + Resource(url) { +} + +class AnimationReader : public QRunnable { +public: + + AnimationReader(const QWeakPointer& animation, QNetworkReply* reply); + + virtual void run(); + +private: + + QWeakPointer _animation; + QNetworkReply* _reply; +}; + +AnimationReader::AnimationReader(const QWeakPointer& animation, QNetworkReply* reply) : + _animation(animation), + _reply(reply) { +} + +void AnimationReader::run() { + QSharedPointer animation = _animation.toStrongRef(); + if (!animation.isNull()) { + QMetaObject::invokeMethod(animation.data(), "setGeometry", + Q_ARG(const FBXGeometry&, readFBX(_reply->readAll(), QVariantHash()))); + } + _reply->deleteLater(); +} + +QStringList Animation::getJointNames() const { + if (QThread::currentThread() != thread()) { + QStringList result; + QMetaObject::invokeMethod(const_cast(this), "getJointNames", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QStringList, result)); + return result; + } + QStringList names; + foreach (const FBXJoint& joint, _geometry.joints) { + names.append(joint.name); + } + return names; +} + +QVector Animation::getFrames() const { + if (QThread::currentThread() != thread()) { + QVector result; + QMetaObject::invokeMethod(const_cast(this), "getFrames", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QVector, result)); + return result; + } + return _geometry.animationFrames; +} + +void Animation::setGeometry(const FBXGeometry& geometry) { + _geometry = geometry; + finishedLoading(true); +} + +void Animation::downloadFinished(QNetworkReply* reply) { + // send the reader off to the thread pool + QThreadPool::globalInstance()->start(new AnimationReader(_self, reply)); +} + diff --git a/libraries/script-engine/src/AnimationCache.h b/libraries/script-engine/src/AnimationCache.h new file mode 100644 index 0000000000..23183adf10 --- /dev/null +++ b/libraries/script-engine/src/AnimationCache.h @@ -0,0 +1,68 @@ +// +// AnimationCache.h +// libraries/script-engine/src/ +// +// Created by Andrzej Kapolka on 4/14/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// 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_AnimationCache_h +#define hifi_AnimationCache_h + +#include + +#include + +class Animation; + +typedef QSharedPointer AnimationPointer; + +/// Scriptable interface for FBX animation loading. +class AnimationCache : public ResourceCache { + Q_OBJECT + +public: + + AnimationCache(QObject* parent = NULL); + + Q_INVOKABLE AnimationPointer getAnimation(const QString& url) { return getAnimation(QUrl(url)); } + + Q_INVOKABLE AnimationPointer getAnimation(const QUrl& url); + +protected: + + virtual QSharedPointer createResource(const QUrl& url, + const QSharedPointer& fallback, bool delayLoad, const void* extra); +}; + +Q_DECLARE_METATYPE(AnimationPointer) + +/// An animation loaded from the network. +class Animation : public Resource { + Q_OBJECT + +public: + + Animation(const QUrl& url); + + const FBXGeometry& getGeometry() const { return _geometry; } + + Q_INVOKABLE QStringList getJointNames() const; + + Q_INVOKABLE QVector getFrames() const; + +protected: + + Q_INVOKABLE void setGeometry(const FBXGeometry& geometry); + + virtual void downloadFinished(QNetworkReply* reply); + +private: + + FBXGeometry _geometry; +}; + +#endif // hifi_AnimationCache_h diff --git a/libraries/script-engine/src/AnimationObject.cpp b/libraries/script-engine/src/AnimationObject.cpp new file mode 100644 index 0000000000..ede1e82623 --- /dev/null +++ b/libraries/script-engine/src/AnimationObject.cpp @@ -0,0 +1,36 @@ +// +// AnimationObject.cpp +// libraries/script-engine/src/ +// +// Created by Andrzej Kapolka on 4/17/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "AnimationCache.h" +#include "AnimationObject.h" + +QStringList AnimationObject::getJointNames() const { + return qscriptvalue_cast(thisObject())->getJointNames(); +} + +QVector AnimationObject::getFrames() const { + return qscriptvalue_cast(thisObject())->getFrames(); +} + +QVector AnimationFrameObject::getRotations() const { + return qscriptvalue_cast(thisObject()).rotations; +} + +void registerAnimationTypes(QScriptEngine* engine) { + qScriptRegisterSequenceMetaType >(engine); + engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + new AnimationFrameObject(), QScriptEngine::ScriptOwnership)); + engine->setDefaultPrototype(qMetaTypeId(), engine->newQObject( + new AnimationObject(), QScriptEngine::ScriptOwnership)); +} + diff --git a/libraries/script-engine/src/AnimationObject.h b/libraries/script-engine/src/AnimationObject.h new file mode 100644 index 0000000000..078fc31fb3 --- /dev/null +++ b/libraries/script-engine/src/AnimationObject.h @@ -0,0 +1,47 @@ +// +// AnimationObject.h +// libraries/script-engine/src/ +// +// Created by Andrzej Kapolka on 4/17/14. +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// 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_AnimationObject_h +#define hifi_AnimationObject_h + +#include +#include + +#include + +class QScriptEngine; + +/// Scriptable wrapper for animation pointers. +class AnimationObject : public QObject, protected QScriptable { + Q_OBJECT + Q_PROPERTY(QStringList jointNames READ getJointNames) + Q_PROPERTY(QVector frames READ getFrames) + +public: + + Q_INVOKABLE QStringList getJointNames() const; + + Q_INVOKABLE QVector getFrames() const; +}; + +/// Scriptable wrapper for animation frames. +class AnimationFrameObject : public QObject, protected QScriptable { + Q_OBJECT + Q_PROPERTY(QVector rotations READ getRotations) + +public: + + Q_INVOKABLE QVector getRotations() const; +}; + +void registerAnimationTypes(QScriptEngine* engine); + +#endif // hifi_AnimationObject_h diff --git a/libraries/script-engine/src/EventTypes.cpp b/libraries/script-engine/src/EventTypes.cpp index 9885b8d9dc..e785f53c19 100644 --- a/libraries/script-engine/src/EventTypes.cpp +++ b/libraries/script-engine/src/EventTypes.cpp @@ -1,12 +1,13 @@ // // EventTypes.cpp -// hifi +// libraries/script-engine/src // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 // -// Used to register meta-types with Qt for very various event types so that they can be exposed to our -// scripting engine #include #include diff --git a/libraries/script-engine/src/EventTypes.h b/libraries/script-engine/src/EventTypes.h index f5a4fa5c9b..6994225fd7 100644 --- a/libraries/script-engine/src/EventTypes.h +++ b/libraries/script-engine/src/EventTypes.h @@ -1,13 +1,16 @@ // // EventTypes.h -// hifi +// libraries/script-engine/src // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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_EventTypes_h__ -#define __hifi_EventTypes_h__ +#ifndef hifi_EventTypes_h +#define hifi_EventTypes_h #include @@ -123,4 +126,4 @@ void touchEventFromScriptValue(const QScriptValue& object, TouchEvent& event); QScriptValue wheelEventToScriptValue(QScriptEngine* engine, const WheelEvent& event); void wheelEventFromScriptValue(const QScriptValue& object, WheelEvent& event); -#endif // __hifi_EventTypes_h__ +#endif // hifi_EventTypes_h diff --git a/libraries/script-engine/src/LocalVoxels.cpp b/libraries/script-engine/src/LocalVoxels.cpp index 075dfb9e9a..1645c229d6 100644 --- a/libraries/script-engine/src/LocalVoxels.cpp +++ b/libraries/script-engine/src/LocalVoxels.cpp @@ -1,9 +1,12 @@ // // LocalVoxels.cpp -// hifi +// libraries/script-engine/src // // Created by Clément Brisset on 2/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 "LocalVoxels.h" diff --git a/libraries/script-engine/src/LocalVoxels.h b/libraries/script-engine/src/LocalVoxels.h index c64379ab27..c382d35c09 100644 --- a/libraries/script-engine/src/LocalVoxels.h +++ b/libraries/script-engine/src/LocalVoxels.h @@ -1,13 +1,16 @@ // // LocalVoxels.h -// hifi +// libraries/script-engine/src // // Created by Clément Brisset on 2/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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__LocalVoxels__ -#define __hifi__LocalVoxels__ +#ifndef hifi_LocalVoxels_h +#define hifi_LocalVoxels_h #include @@ -87,4 +90,4 @@ private: -#endif /* defined(__hifi__LocalVoxels__) */ +#endif // hifi_LocalVoxels_h diff --git a/libraries/script-engine/src/MenuItemProperties.cpp b/libraries/script-engine/src/MenuItemProperties.cpp index f071723233..c1f3e92447 100644 --- a/libraries/script-engine/src/MenuItemProperties.cpp +++ b/libraries/script-engine/src/MenuItemProperties.cpp @@ -1,12 +1,13 @@ // // MenuItemProperties.cpp -// hifi +// libraries/script-engine/src // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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 // -// Used to register meta-types with Qt for very various event types so that they can be exposed to our -// scripting engine #include #include diff --git a/libraries/script-engine/src/MenuItemProperties.h b/libraries/script-engine/src/MenuItemProperties.h index 82f00a2bc4..9dd3c6107c 100644 --- a/libraries/script-engine/src/MenuItemProperties.h +++ b/libraries/script-engine/src/MenuItemProperties.h @@ -1,13 +1,16 @@ // // MenuItemProperties.h -// hifi +// libraries/script-engine/src // // Created by Brad Hefta-Gaub on 1/28/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. +// 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_MenuItemProperties_h__ -#define __hifi_MenuItemProperties_h__ +#ifndef hifi_MenuItemProperties_h +#define hifi_MenuItemProperties_h #include @@ -48,4 +51,4 @@ void registerMenuItemProperties(QScriptEngine* engine); -#endif // __hifi_MenuItemProperties_h__ +#endif // hifi_MenuItemProperties_h diff --git a/libraries/script-engine/src/Quat.cpp b/libraries/script-engine/src/Quat.cpp index c939811db4..4acc60e7b4 100644 --- a/libraries/script-engine/src/Quat.cpp +++ b/libraries/script-engine/src/Quat.cpp @@ -1,12 +1,12 @@ // // Quat.cpp -// hifi +// libraries/script-engine/src // -// Created by Brad Hefta-Gaub on 1/29/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. -// -// Scriptable Quaternion class library. +// Created by Brad Hefta-Gaub on 1/29/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 diff --git a/libraries/script-engine/src/Quat.h b/libraries/script-engine/src/Quat.h index 02c0a3e147..c97ccf9a1e 100644 --- a/libraries/script-engine/src/Quat.h +++ b/libraries/script-engine/src/Quat.h @@ -1,16 +1,18 @@ // // Quat.h -// hifi +// libraries/script-engine/src // -// Created by Brad Hefta-Gaub on 1/29/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 1/29/14. +// Copyright 2014 High Fidelity, Inc. // // Scriptable Quaternion class library. // +// 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__Quat__ -#define __hifi__Quat__ +#ifndef hifi_Quat_h +#define hifi_Quat_h #include @@ -37,4 +39,4 @@ public slots: void print(const QString& lable, const glm::quat& q); }; -#endif /* defined(__hifi__Quat__) */ +#endif // hifi_Quat_h diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 7572638a30..9c034f6f69 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -1,9 +1,12 @@ // // ScriptEngine.cpp -// hifi +// libraries/script-engine/src // // Created by Brad Hefta-Gaub on 12/14/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -25,6 +28,7 @@ #include +#include "AnimationObject.h" #include "MenuItemProperties.h" #include "LocalVoxels.h" #include "ScriptEngine.h" @@ -39,6 +43,11 @@ static QScriptValue soundConstructor(QScriptContext* context, QScriptEngine* eng return soundScriptValue; } +static QScriptValue debugPrint(QScriptContext* context, QScriptEngine* engine){ + qDebug() << "script:print()<<" << context->argument(0).toString(); + engine->evaluate("Script.print('" + context->argument(0).toString() + "')"); + return QScriptValue(); +} ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNameString, AbstractControllerScriptingInterface* controllerScriptingInterface) : @@ -60,7 +69,9 @@ ScriptEngine::ScriptEngine(const QString& scriptContents, const QString& fileNam _scriptName(), _fileNameString(fileNameString), _quatLibrary(), - _vec3Library() + _vec3Library(), + _uuidLibrary(), + _animationCache(this) { } @@ -83,15 +94,18 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, _scriptName(), _fileNameString(), _quatLibrary(), - _vec3Library() + _vec3Library(), + _uuidLibrary(), + _animationCache(this) { QString scriptURLString = scriptURL.toString(); _fileNameString = scriptURLString; QUrl url(scriptURL); - // if the scheme is empty, maybe they typed in a file, let's try - if (url.scheme().isEmpty()) { + // if the scheme length is one or lower, maybe they typed in a file, let's try + const int WINDOWS_DRIVE_LETTER_SIZE = 1; + if (url.scheme().size() <= WINDOWS_DRIVE_LETTER_SIZE) { url = QUrl::fromLocalFile(scriptURLString); } @@ -106,6 +120,7 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL, _scriptContents = in.readAll(); } else { qDebug() << "ERROR Loading file:" << fileName; + emit errorMessage("ERROR Loading file:" + fileName); } } else { QNetworkAccessManager* networkManager = new QNetworkAccessManager(this); @@ -147,6 +162,14 @@ void ScriptEngine::setAvatarData(AvatarData* avatarData, const QString& objectNa registerGlobalObject(objectName, _avatarData); } +void ScriptEngine::setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName) { + // remove the old Avatar property, if it exists + _engine.globalObject().setProperty(objectName, QScriptValue()); + + // give the script engine the new avatar hash map + registerGlobalObject(objectName, avatarHashMap); +} + bool ScriptEngine::setScriptContents(const QString& scriptContents, const QString& fileNameString) { if (_isRunning) { return false; @@ -174,13 +197,18 @@ void ScriptEngine::init() { registerVoxelMetaTypes(&_engine); registerEventTypes(&_engine); registerMenuItemProperties(&_engine); + registerAnimationTypes(&_engine); qScriptRegisterMetaType(&_engine, ParticlePropertiesToScriptValue, ParticlePropertiesFromScriptValue); qScriptRegisterMetaType(&_engine, ParticleIDtoScriptValue, ParticleIDfromScriptValue); qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); + qScriptRegisterSequenceMetaType >(&_engine); qScriptRegisterSequenceMetaType >(&_engine); + QScriptValue printConstructorValue = _engine.newFunction(debugPrint); + _engine.globalObject().setProperty("print", printConstructorValue); + QScriptValue soundConstructorValue = _engine.newFunction(soundConstructor); QScriptValue soundMetaObject = _engine.newQMetaObject(&Sound::staticMetaObject, soundConstructorValue); _engine.globalObject().setProperty("Sound", soundMetaObject); @@ -197,6 +225,8 @@ void ScriptEngine::init() { registerGlobalObject("Particles", &_particlesScriptingInterface); registerGlobalObject("Quat", &_quatLibrary); registerGlobalObject("Vec3", &_vec3Library); + registerGlobalObject("Uuid", &_uuidLibrary); + registerGlobalObject("AnimationCache", &_animationCache); registerGlobalObject("Voxels", &_voxelsScriptingInterface); @@ -225,6 +255,7 @@ void ScriptEngine::evaluate() { if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at line" << line << ":" << result.toString(); + emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString()); } } @@ -245,15 +276,18 @@ void ScriptEngine::run() { init(); } _isRunning = true; + emit runningStateChanged(); QScriptValue result = _engine.evaluate(_scriptContents); if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); + qDebug() << "Uncaught exception at line" << line << ":" << result.toString(); + emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + result.toString()); } - timeval startTime; - gettimeofday(&startTime, NULL); + QElapsedTimer startTime; + startTime.start(); int thisFrame = 0; @@ -262,7 +296,7 @@ void ScriptEngine::run() { qint64 lastUpdate = usecTimestampNow(); while (!_isFinished) { - int usecToSleep = usecTimestamp(&startTime) + (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - usecTimestampNow(); + int usecToSleep = (thisFrame++ * SCRIPT_DATA_CALLBACK_USECS) - startTime.nsecsElapsed() / 1000; // nsec to usec if (usecToSleep > 0) { usleep(usecToSleep); } @@ -380,6 +414,7 @@ void ScriptEngine::run() { if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at line" << line << ":" << _engine.uncaughtException().toString(); + emit errorMessage("Uncaught exception at line" + QString::number(line) + ":" + _engine.uncaughtException().toString()); } } emit scriptEnding(); @@ -415,10 +450,12 @@ void ScriptEngine::run() { emit finished(_fileNameString); _isRunning = false; + emit runningStateChanged(); } void ScriptEngine::stop() { _isFinished = true; + emit runningStateChanged(); } void ScriptEngine::timerFired() { @@ -489,6 +526,10 @@ QUrl ScriptEngine::resolveInclude(const QString& include) const { return url; } +void ScriptEngine::print(const QString& message) { + emit printedMessage(message); +} + void ScriptEngine::include(const QString& includeFile) { QUrl url = resolveInclude(includeFile); QString includeContents; @@ -502,6 +543,7 @@ void ScriptEngine::include(const QString& includeFile) { includeContents = in.readAll(); } else { qDebug() << "ERROR Loading file:" << fileName; + emit errorMessage("ERROR Loading file:" + fileName); } } else { QNetworkAccessManager* networkManager = new QNetworkAccessManager(this); @@ -517,5 +559,6 @@ void ScriptEngine::include(const QString& includeFile) { if (_engine.hasUncaughtException()) { int line = _engine.uncaughtExceptionLineNumber(); qDebug() << "Uncaught exception at (" << includeFile << ") line" << line << ":" << result.toString(); + emit errorMessage("Uncaught exception at (" + includeFile + ") line" + QString::number(line) + ":" + result.toString()); } } diff --git a/libraries/script-engine/src/ScriptEngine.h b/libraries/script-engine/src/ScriptEngine.h index 964f64a005..9ea99276d3 100644 --- a/libraries/script-engine/src/ScriptEngine.h +++ b/libraries/script-engine/src/ScriptEngine.h @@ -1,13 +1,16 @@ // // ScriptEngine.h -// hifi +// libraries/script-engine/src // // Created by Brad Hefta-Gaub on 12/14/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__ScriptEngine__ -#define __hifi__ScriptEngine__ +#ifndef hifi_ScriptEngine_h +#define hifi_ScriptEngine_h #include @@ -19,9 +22,12 @@ #include #include +#include +#include "AnimationCache.h" #include "AbstractControllerScriptingInterface.h" #include "Quat.h" +#include "ScriptUUID.h" #include "Vec3.h" class ParticlesScriptingInterface; @@ -58,6 +64,7 @@ public: bool isAvatar() const { return _isAvatar; } void setAvatarData(AvatarData* avatarData, const QString& objectName); + void setAvatarHashMap(AvatarHashMap* avatarHashMap, const QString& objectName); bool isListeningToAudioStream() const { return _isListeningToAudioStream; } void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; } @@ -73,6 +80,9 @@ public: bool hasScript() const { return !_scriptContents.isEmpty(); } + bool isFinished() const { return _isFinished; } + bool isRunning() const { return _isRunning; } + public slots: void stop(); @@ -81,12 +91,16 @@ public slots: void clearInterval(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void clearTimeout(QObject* timer) { stopTimer(reinterpret_cast(timer)); } void include(const QString& includeFile); + void print(const QString& message); signals: void update(float deltaTime); void scriptEnding(); void finished(const QString& fileNameString); void cleanupMenuItem(const QString& menuItemString); + void printedMessage(const QString& message); + void errorMessage(const QString& message); + void runningStateChanged(); protected: QString _scriptContents; @@ -112,7 +126,6 @@ private: static VoxelsScriptingInterface _voxelsScriptingInterface; static ParticlesScriptingInterface _particlesScriptingInterface; - static int _scriptNumber; AbstractControllerScriptingInterface* _controllerScriptingInterface; AudioScriptingInterface _audioScriptingInterface; @@ -121,6 +134,8 @@ private: QString _fileNameString; Quat _quatLibrary; Vec3 _vec3Library; + ScriptUUID _uuidLibrary; + AnimationCache _animationCache; }; -#endif /* defined(__hifi__ScriptEngine__) */ +#endif // hifi_ScriptEngine_h diff --git a/libraries/script-engine/src/ScriptUUID.cpp b/libraries/script-engine/src/ScriptUUID.cpp new file mode 100644 index 0000000000..9e00f9c095 --- /dev/null +++ b/libraries/script-engine/src/ScriptUUID.cpp @@ -0,0 +1,40 @@ +// +// ScriptUUID.cpp +// libraries/script-engine/src/ +// +// Created by Andrew Meadows on 2014-04-07 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +#include + +#include "ScriptUUID.h" + +QUuid ScriptUUID::fromString(const QString& s) { + return QUuid(s); +} + +QString ScriptUUID::toString(const QUuid& id) { + return id.toString(); +} + +QUuid ScriptUUID::generate() { + return QUuid::createUuid(); +} + +bool ScriptUUID::isEqual(const QUuid& idA, const QUuid& idB) { + return idA == idB; +} + +bool ScriptUUID::isNull(const QUuid& id) { + return id.isNull(); +} + +void ScriptUUID::print(const QString& lable, const QUuid& id) { + qDebug() << qPrintable(lable) << id.toString(); +} diff --git a/libraries/script-engine/src/ScriptUUID.h b/libraries/script-engine/src/ScriptUUID.h new file mode 100644 index 0000000000..db94b5082b --- /dev/null +++ b/libraries/script-engine/src/ScriptUUID.h @@ -0,0 +1,32 @@ +// +// ScriptUUID.h +// libraries/script-engine/src/ +// +// Created by Andrew Meadows on 2014-04-07 +// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// +// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API +// +// 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_ScriptUUID_h +#define hifi_ScriptUUID_h + +#include + +/// Scriptable interface for a UUID helper class object. Used exclusively in the JavaScript API +class ScriptUUID : public QObject { + Q_OBJECT + +public slots: + QUuid fromString(const QString& string); + QString toString(const QUuid& id); + QUuid generate(); + bool isEqual(const QUuid& idA, const QUuid& idB); + bool isNull(const QUuid& id); + void print(const QString& lable, const QUuid& id); +}; + +#endif // hifi_ScriptUUID_h diff --git a/libraries/script-engine/src/Vec3.cpp b/libraries/script-engine/src/Vec3.cpp index dc5dcd9773..bab529df1f 100644 --- a/libraries/script-engine/src/Vec3.cpp +++ b/libraries/script-engine/src/Vec3.cpp @@ -1,12 +1,12 @@ // // Vec3.cpp -// hifi +// libraries/script-engine/src // -// Created by Brad Hefta-Gaub on 1/29/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. -// -// Scriptable Vec3 class library. +// Created by Brad Hefta-Gaub on 1/29/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 diff --git a/libraries/script-engine/src/Vec3.h b/libraries/script-engine/src/Vec3.h index cbec55b992..b30e274d89 100644 --- a/libraries/script-engine/src/Vec3.h +++ b/libraries/script-engine/src/Vec3.h @@ -1,16 +1,18 @@ // // Vec3.h -// hifi +// libraries/script-engine/src // -// Created by Brad Hefta-Gaub on 1/29/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 1/29/14. +// Copyright 2014 High Fidelity, Inc. // // Scriptable Vec3 class library. // +// 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__Vec3__ -#define __hifi__Vec3__ +#ifndef hifi_Vec3_h +#define hifi_Vec3_h #include #include @@ -35,4 +37,4 @@ public slots: -#endif /* defined(__hifi__Vec3__) */ +#endif // hifi_Vec3_h diff --git a/libraries/shared/CMakeLists.txt b/libraries/shared/CMakeLists.txt index 3af7272cc1..7f9a34492d 100644 --- a/libraries/shared/CMakeLists.txt +++ b/libraries/shared/CMakeLists.txt @@ -32,4 +32,4 @@ if (UNIX AND NOT APPLE) target_link_libraries(${TARGET_NAME} "${CMAKE_THREAD_LIBS_INIT}") endif (UNIX AND NOT APPLE) -target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets) +target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets) \ No newline at end of file diff --git a/libraries/shared/src/AngleUtil.h b/libraries/shared/src/AngleUtil.h index cecee31188..d700e41f3d 100644 --- a/libraries/shared/src/AngleUtil.h +++ b/libraries/shared/src/AngleUtil.h @@ -1,13 +1,16 @@ // -// AngleUtils.h -// hifi +// AngleUtil.h +// libraries/shared/src // -// Created by Tobias Schwinger on 3/23/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/23/13. +// Copyright 2013 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__AngleUtils__ -#define __hifi__AngleUtils__ +#ifndef hifi_AngleUtil_h +#define hifi_AngleUtil_h #include @@ -95,5 +98,4 @@ void angleHorizontalPolar(float& azimuth, float& altitude) { azimuth = angleUnsignedNormal(azimuth); } -#endif - +#endif // hifi_AngleUtil_h diff --git a/libraries/shared/src/CapsuleShape.cpp b/libraries/shared/src/CapsuleShape.cpp index bae5f201ca..5055b3636e 100644 --- a/libraries/shared/src/CapsuleShape.cpp +++ b/libraries/shared/src/CapsuleShape.cpp @@ -1,9 +1,12 @@ // // CapsuleShape.cpp -// hifi +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.20 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/20/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 // #include @@ -14,9 +17,9 @@ // default axis of CapsuleShape is Y-axis -const glm::vec3 localAxis(0.f, 1.f, 0.f); +const glm::vec3 localAxis(0.0f, 1.0f, 0.0f); -CapsuleShape::CapsuleShape() : Shape(Shape::CAPSULE_SHAPE) {} +CapsuleShape::CapsuleShape() : Shape(Shape::CAPSULE_SHAPE), _radius(0.0f), _halfHeight(0.0f) {} CapsuleShape::CapsuleShape(float radius, float halfHeight) : Shape(Shape::CAPSULE_SHAPE), _radius(radius), _halfHeight(halfHeight) { @@ -29,13 +32,13 @@ CapsuleShape::CapsuleShape(float radius, float halfHeight, const glm::vec3& posi } CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm::vec3& endPoint) : - Shape(Shape::CAPSULE_SHAPE), _radius(radius), _halfHeight(0.f) { + Shape(Shape::CAPSULE_SHAPE), _radius(radius), _halfHeight(0.0f) { glm::vec3 axis = endPoint - startPoint; float height = glm::length(axis); if (height > EPSILON) { _halfHeight = 0.5f * height; axis /= height; - glm::vec3 yAxis(0.f, 1.f, 0.f); + glm::vec3 yAxis(0.0f, 1.0f, 0.0f); float angle = glm::angle(axis, yAxis); if (angle > EPSILON) { axis = glm::normalize(glm::cross(yAxis, axis)); @@ -47,17 +50,17 @@ CapsuleShape::CapsuleShape(float radius, const glm::vec3& startPoint, const glm: /// \param[out] startPoint is the center of start cap void CapsuleShape::getStartPoint(glm::vec3& startPoint) const { - startPoint = getPosition() - _rotation * glm::vec3(0.f, _halfHeight, 0.f); + startPoint = getPosition() - _rotation * glm::vec3(0.0f, _halfHeight, 0.0f); } /// \param[out] endPoint is the center of the end cap void CapsuleShape::getEndPoint(glm::vec3& endPoint) const { - endPoint = getPosition() + _rotation * glm::vec3(0.f, _halfHeight, 0.f); + endPoint = getPosition() + _rotation * glm::vec3(0.0f, _halfHeight, 0.0f); } void CapsuleShape::computeNormalizedAxis(glm::vec3& axis) const { // default axis of a capsule is along the yAxis - axis = _rotation * glm::vec3(0.f, 1.f, 0.f); + axis = _rotation * glm::vec3(0.0f, 1.0f, 0.0f); } void CapsuleShape::setRadius(float radius) { diff --git a/libraries/shared/src/CapsuleShape.h b/libraries/shared/src/CapsuleShape.h index 6d7e0a50be..9421bf1789 100644 --- a/libraries/shared/src/CapsuleShape.h +++ b/libraries/shared/src/CapsuleShape.h @@ -1,13 +1,16 @@ // // CapsuleShape.h -// hifi +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.20 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/20/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 // -#ifndef __hifi__CapsuleShape__ -#define __hifi__CapsuleShape__ +#ifndef hifi_CapsuleShape_h +#define hifi_CapsuleShape_h #include "Shape.h" @@ -43,4 +46,4 @@ protected: float _halfHeight; }; -#endif /* defined(__hifi__CapsuleShape__) */ +#endif // hifi_CapsuleShape_h diff --git a/libraries/shared/src/CollisionInfo.cpp b/libraries/shared/src/CollisionInfo.cpp index f6c0d057a1..5d97842530 100644 --- a/libraries/shared/src/CollisionInfo.cpp +++ b/libraries/shared/src/CollisionInfo.cpp @@ -1,9 +1,12 @@ // // CollisionInfo.cpp -// hifi +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/14/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 // #include "CollisionInfo.h" @@ -45,3 +48,6 @@ void CollisionList::clear() { _size = 0; } +CollisionInfo* CollisionList::operator[](int index) { + return (index > -1 && index < _size) ? &(_collisions[index]) : NULL; +} diff --git a/libraries/shared/src/CollisionInfo.h b/libraries/shared/src/CollisionInfo.h index 868d259ce3..7db965fe64 100644 --- a/libraries/shared/src/CollisionInfo.h +++ b/libraries/shared/src/CollisionInfo.h @@ -1,13 +1,16 @@ // // CollisionInfo.h -// hifi +// libraries/shared/src // -// Created by Andrew Meadows on 2014.01.13 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/14/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 // -#ifndef __hifi__CollisionInfo__ -#define __hifi__CollisionInfo__ +#ifndef hifi_CollisionInfo_h +#define hifi_CollisionInfo_h #include #include @@ -92,10 +95,12 @@ public: /// Clear valid collisions. void clear(); + CollisionInfo* operator[](int index); + private: int _maxSize; // the container cannot get larger than this int _size; // the current number of valid collisions in the list QVector _collisions; }; -#endif /* defined(__hifi__CollisionInfo__) */ +#endif // hifi_CollisionInfo_h diff --git a/libraries/shared/src/DomainInfo.cpp b/libraries/shared/src/DomainInfo.cpp deleted file mode 100644 index bed638bf59..0000000000 --- a/libraries/shared/src/DomainInfo.cpp +++ /dev/null @@ -1,126 +0,0 @@ -// -// DomainInfo.cpp -// hifi -// -// Created by Stephen Birarda on 2/18/2014. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// - -#include - -#include "AccountManager.h" - -#include "DomainInfo.h" - -DomainInfo::DomainInfo() : - _uuid(), - _sockAddr(HifiSockAddr(QHostAddress::Null, DEFAULT_DOMAIN_SERVER_PORT)), - _assignmentUUID(), - _connectionSecret(), - _registrationToken(), - _rootAuthenticationURL(), - _publicKey(), - _isConnected(false) -{ - // clear appropriate variables after a domain-server logout - connect(&AccountManager::getInstance(), &AccountManager::logoutComplete, this, &DomainInfo::logout); -} - -void DomainInfo::clearConnectionInfo() { - _uuid = QUuid(); - _connectionSecret = QUuid(); - _registrationToken = QByteArray(); - _rootAuthenticationURL = QUrl(); - _publicKey = QString(); - _isConnected = false; -} - -void DomainInfo::reset() { - clearConnectionInfo(); - _hostname = QString(); - _sockAddr.setAddress(QHostAddress::Null); -} - -void DomainInfo::parseAuthInformationFromJsonObject(const QJsonObject& jsonObject) { - QJsonObject dataObject = jsonObject["data"].toObject(); - _connectionSecret = QUuid(dataObject["connection_secret"].toString()); - _registrationToken = QByteArray::fromHex(dataObject["registration_token"].toString().toUtf8()); - _publicKey = dataObject["public_key"].toString(); -} - -void DomainInfo::setSockAddr(const HifiSockAddr& sockAddr) { - if (_sockAddr != sockAddr) { - // we should reset on a sockAddr change - reset(); - // change the sockAddr - _sockAddr = sockAddr; - } -} - -void DomainInfo::setHostname(const QString& hostname) { - - if (hostname != _hostname) { - // re-set the domain info so that auth information is reloaded - reset(); - - int colonIndex = hostname.indexOf(':'); - - if (colonIndex > 0) { - // the user has included a custom DS port with the hostname - - // the new hostname is everything up to the colon - _hostname = hostname.left(colonIndex); - - // grab the port by reading the string after the colon - _sockAddr.setPort(atoi(hostname.mid(colonIndex + 1, hostname.size()).toLocal8Bit().constData())); - - qDebug() << "Updated hostname to" << _hostname << "and port to" << _sockAddr.getPort(); - - } else { - // no port included with the hostname, simply set the member variable and reset the domain server port to default - _hostname = hostname; - _sockAddr.setPort(DEFAULT_DOMAIN_SERVER_PORT); - } - - // re-set the sock addr to null and fire off a lookup of the IP address for this domain-server's hostname - qDebug("Looking up DS hostname %s.", _hostname.toLocal8Bit().constData()); - QHostInfo::lookupHost(_hostname, this, SLOT(completedHostnameLookup(const QHostInfo&))); - - emit hostnameChanged(_hostname); - } -} - -void DomainInfo::completedHostnameLookup(const QHostInfo& hostInfo) { - for (int i = 0; i < hostInfo.addresses().size(); i++) { - if (hostInfo.addresses()[i].protocol() == QAbstractSocket::IPv4Protocol) { - _sockAddr.setAddress(hostInfo.addresses()[i]); - qDebug("DS at %s is at %s", _hostname.toLocal8Bit().constData(), - _sockAddr.getAddress().toString().toLocal8Bit().constData()); - return; - } - } - - // if we got here then we failed to lookup the address - qDebug("Failed domain server lookup"); -} - -void DomainInfo::setIsConnected(bool isConnected) { - if (_isConnected != isConnected) { - _isConnected = isConnected; - - if (_isConnected) { - emit connectedToDomain(_hostname); - } - } -} - -void DomainInfo::logout() { - // clear any information related to auth for this domain, assuming it had requested auth - if (!_rootAuthenticationURL.isEmpty()) { - _rootAuthenticationURL = QUrl(); - _connectionSecret = QUuid(); - _registrationToken = QByteArray(); - _publicKey = QString(); - _isConnected = false; - } -} diff --git a/libraries/shared/src/FileDownloader.cpp b/libraries/shared/src/FileDownloader.cpp deleted file mode 100644 index 2b65bbd8df..0000000000 --- a/libraries/shared/src/FileDownloader.cpp +++ /dev/null @@ -1,65 +0,0 @@ -// -// FileDownloader.cpp -// hifi -// -// Created by Clement Brisset on 3/14/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// -// - -#include -#include -#include -#include - -#include "FileDownloader.h" - -FileDownloader::FileDownloader(const QUrl dataURL, QObject* parent) : - QObject(parent), - _done(false) -{ - connect(&_networkAccessManager, SIGNAL(finished(QNetworkReply*)), this, SLOT(processReply(QNetworkReply*))); - - QNetworkRequest request(dataURL); - _networkAccessManager.get(request); -} - -void FileDownloader::processReply(QNetworkReply *reply) { - if (reply->error() == QNetworkReply::NoError) { - _downloadedData = reply->readAll(); - } - - reply->deleteLater(); - _done = true; - emit done(reply->error()); -} - -void FileDownloader::waitForFile(int timeout) { - QTimer timer; - QEventLoop loop; - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit())); - connect(this, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit())); - - if (!_done) { - if (timeout > 0) { - timer.start(timeout); - } - loop.exec(); - } -} - -QByteArray FileDownloader::download(const QUrl dataURL, int timeout) { - QTimer timer; - QEventLoop loop; - connect(&timer, SIGNAL(timeout()), &loop, SLOT(quit)); - - FileDownloader downloader(dataURL); - connect(&downloader, SIGNAL(done(QNetworkReply::NetworkError)), &loop, SLOT(quit())); - - if (timeout > 0) { - timer.start(timeout); - } - loop.exec(); - - return downloader.getData(); -} \ No newline at end of file diff --git a/libraries/shared/src/FileDownloader.h b/libraries/shared/src/FileDownloader.h deleted file mode 100644 index 593b39b013..0000000000 --- a/libraries/shared/src/FileDownloader.h +++ /dev/null @@ -1,44 +0,0 @@ -// -// FileDownloader.h -// hifi -// -// Created by Clement Brisset on 3/14/14. -// Copyright (c) 2014 HighFidelity, Inc. All rights reserved. -// -// - -#ifndef __hifi__FileDownloader__ -#define __hifi__FileDownloader__ - -#include -#include -#include - -class FileDownloader : public QObject { - Q_OBJECT - -public: - FileDownloader(const QUrl dataURL, QObject* parent = NULL); - - void waitForFile(int timeout = 0); - - QByteArray getData() const { return _downloadedData; } - bool done() { return _done; } - - static QByteArray download(const QUrl dataURL, int timeout = 0); - -signals: - void done(QNetworkReply::NetworkError error); - -private slots: - void processReply(QNetworkReply* reply); - -private: - QNetworkAccessManager _networkAccessManager; - QByteArray _downloadedData; - - bool _done; -}; - - -#endif /* defined(__hifi__FileDownloader__) */ diff --git a/libraries/shared/src/FileUtils.cpp b/libraries/shared/src/FileUtils.cpp index efbe1a189b..fe79f6c527 100644 --- a/libraries/shared/src/FileUtils.cpp +++ b/libraries/shared/src/FileUtils.cpp @@ -1,9 +1,12 @@ // // FileUtils.cpp -// hifi +// libraries/shared/src // // Created by Stojce Slavkovski on 12/23/13. +// Copyright 2013 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 "FileUtils.h" @@ -58,8 +61,7 @@ void FileUtils::locateFile(QString filePath) { QString FileUtils::standardPath(QString subfolder) { // standard path // Mac: ~/Library/Application Support/Interface - QString path = QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation); - path.append("/Interface"); + QString path = QStandardPaths::writableLocation(QStandardPaths::DataLocation); if (!subfolder.startsWith("/")) { subfolder.prepend("/"); diff --git a/libraries/shared/src/FileUtils.h b/libraries/shared/src/FileUtils.h index dd4605218e..8b31d1473b 100644 --- a/libraries/shared/src/FileUtils.h +++ b/libraries/shared/src/FileUtils.h @@ -1,9 +1,12 @@ // // FileUtils.h -// hifi +// libraries/shared/src // // Created by Stojce Slavkovski on 12/23/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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_FileUtils_h @@ -19,4 +22,4 @@ public: }; -#endif +#endif // hifi_FileUtils_h diff --git a/libraries/shared/src/FloodFill.h b/libraries/shared/src/FloodFill.h index 40a89bfd1b..cd77534697 100644 --- a/libraries/shared/src/FloodFill.h +++ b/libraries/shared/src/FloodFill.h @@ -1,13 +1,15 @@ // // FloodFill.h -// hifi +// libraries/shared/src // -// Created by Tobias Schwinger 3/26/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__FloodFill__ -#define __hifi__FloodFill__ +#ifndef hifi_FloodFill_h +#define hifi_FloodFill_h // // Line scanning, iterative flood fill algorithm. @@ -104,5 +106,4 @@ void floodFill(Cursor const& p, Strategy const& s) { } -#endif /* defined(__hifi__FloodFill__) */ - +#endif // hifi_FloodFill_h diff --git a/libraries/shared/src/FstReader.cpp b/libraries/shared/src/FstReader.cpp deleted file mode 100644 index 14cff957b1..0000000000 --- a/libraries/shared/src/FstReader.cpp +++ /dev/null @@ -1,277 +0,0 @@ -// -// FstReader.cpp -// hifi -// -// Created by Clément Brisset on 3/4/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. -// -// - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "AccountManager.h" -#include "FstReader.h" - - -static const QString NAME_FIELD = "name"; -static const QString FILENAME_FIELD = "filename"; -static const QString TEXDIR_FIELD = "texdir"; -static const QString LOD_FIELD = "lod"; - -static const QString MODEL_URL = "/api/v1/models"; - -static const int MAX_SIZE = 10 * 1024 * 1024; // 10 MB - -// Class providing the QObject parent system to QTemporaryDir -class TemporaryDir : public QTemporaryDir, public QObject { -public: - virtual ~TemporaryDir() { - // ensuring the entire object gets deleted by the QObject parent. - } -}; - -FstReader::FstReader(bool isHead) : - _zipDir(new TemporaryDir()), - _lodCount(-1), - _texturesCount(-1), - _totalSize(0), - _isHead(isHead), - _readyToSend(false), - _dataMultiPart(new QHttpMultiPart(QHttpMultiPart::FormDataType)) -{ - _zipDir->setParent(_dataMultiPart); - -} - -FstReader::~FstReader() { - delete _dataMultiPart; -} - -bool FstReader::zip() { - // File Dialog - QString filename = QFileDialog::getOpenFileName(NULL, - "Select your .fst file ...", - QStandardPaths::writableLocation(QStandardPaths::DownloadLocation), - "*.fst"); - if (filename == "") { - // If the user canceled we return. - return false; - } - - // First we check the FST file - QFile fst(filename); - if (!fst.open(QFile::ReadOnly | QFile::Text)) { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Could not open FST file."), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Could not open FST file."); - return false; - } - qDebug() << "Reading FST file : " << QFileInfo(fst).filePath(); - - // Compress and copy the fst - if (!compressFile(QFileInfo(fst).filePath(), _zipDir->path() + "/" + QFileInfo(fst).fileName())) { - return false; - } - if (!addPart(_zipDir->path() + "/" + QFileInfo(fst).fileName(), - QString("fst"))) { - return false; - } - - // Let's read through the FST file - QTextStream stream(&fst); - QList line; - while (!stream.atEnd()) { - line = stream.readLine().split(QRegExp("[ =]"), QString::SkipEmptyParts); - if (line.isEmpty()) { - continue; - } - - // according to what is read, we modify the command - if (line[0] == NAME_FIELD) { - QHttpPart textPart; - textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" - " name=\"model_name\""); - textPart.setBody(line[1].toUtf8()); - _dataMultiPart->append(textPart); - } else if (line[0] == FILENAME_FIELD) { - QFileInfo fbx(QFileInfo(fst).path() + "/" + line[1]); - if (!fbx.exists() || !fbx.isFile()) { // Check existence - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("FBX file %1 could not be found.").arg(fbx.fileName()), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(fbx.fileName()); - return false; - } - // Compress and copy - if (!compressFile(fbx.filePath(), _zipDir->path() + "/" + line[1])) { - return false; - } - if (!addPart(_zipDir->path() + "/" + line[1], "fbx")) { - return false; - } - } else if (line[0] == TEXDIR_FIELD) { // Check existence - QFileInfo texdir(QFileInfo(fst).path() + "/" + line[1]); - if (!texdir.exists() || !texdir.isDir()) { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Texture directory could not be found."), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Texture directory could not be found."); - return false; - } - if (!addTextures(texdir)) { // Recursive compress and copy - return false; - } - } else if (line[0] == LOD_FIELD) { - QFileInfo lod(QFileInfo(fst).path() + "/" + line[1]); - if (!lod.exists() || !lod.isFile()) { // Check existence - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("FBX file %1 could not be found.").arg(lod.fileName()), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("FBX file %1 could not be found.").arg(lod.fileName()); - return false; - } - // Compress and copy - if (!compressFile(lod.filePath(), _zipDir->path() + "/" + line[1])) { - return false; - } - if (!addPart(_zipDir->path() + "/" + line[1], QString("lod%1").arg(++_lodCount))) { - return false; - } - } - } - - QHttpPart textPart; - textPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" - " name=\"model_category\""); - if (_isHead) { - textPart.setBody("head"); - } else { - textPart.setBody("skeleton"); - } - _dataMultiPart->append(textPart); - - _readyToSend = true; - return true; -} - -bool FstReader::send() { - if (!_readyToSend) { - return false; - } - - AccountManager::getInstance().authenticatedRequest(MODEL_URL, QNetworkAccessManager::PostOperation, JSONCallbackParameters(), QByteArray(), _dataMultiPart); - _zipDir = NULL; - _dataMultiPart = NULL; - qDebug() << "Model sent."; - - return true; -} - -bool FstReader::addTextures(const QFileInfo& texdir) { - QStringList filter; - filter << "*.png" << "*.tif" << "*.jpg" << "*.jpeg"; - - QFileInfoList list = QDir(texdir.filePath()).entryInfoList(filter, - QDir::Files | - QDir::AllDirs | - QDir::NoDotAndDotDot | - QDir::NoSymLinks); - foreach (QFileInfo info, list) { - if (info.isFile()) { - // Compress and copy - if (!compressFile(info.filePath(), _zipDir->path() + "/" + info.fileName())) { - return false; - } - if (!addPart(_zipDir->path() + "/" + info.fileName(), - QString("texture%1").arg(++_texturesCount))) { - return false; - } - } else if (info.isDir()) { - if (!addTextures(info)) { - return false; - } - } - } - - return true; -} - -bool FstReader::compressFile(const QString &inFileName, const QString &outFileName) { - QFile inFile(inFileName); - inFile.open(QIODevice::ReadOnly); - QByteArray buffer = inFile.readAll(); - - QFile outFile(outFileName); - if (!outFile.open(QIODevice::WriteOnly)) { - QDir(_zipDir->path()).mkpath(QFileInfo(outFileName).path()); - if (!outFile.open(QIODevice::WriteOnly)) { - QMessageBox::warning(NULL, - QString("ModelUploader::compressFile()"), - QString("Could not compress %1").arg(inFileName), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Could not compress %1").arg(inFileName); - return false; - } - } - QDataStream out(&outFile); - out << qCompress(buffer); - - return true; -} - - -bool FstReader::addPart(const QString &path, const QString& name) { - QFile* file = new QFile(path); - if (!file->open(QIODevice::ReadOnly)) { - QMessageBox::warning(NULL, - QString("ModelUploader::addPart()"), - QString("Could not open %1").arg(path), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Could not open %1").arg(path); - delete file; - return false; - } - - QHttpPart part; - part.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data;" - " name=\"" + name.toUtf8() + "\";" - " filename=\"" + QFileInfo(*file).fileName().toUtf8() + "\""); - part.setHeader(QNetworkRequest::ContentTypeHeader, "application/octet-stream"); - part.setBodyDevice(file); - _dataMultiPart->append(part); - file->setParent(_dataMultiPart); - - - qDebug() << "File " << QFileInfo(*file).fileName() << " added to model."; - _totalSize += file->size(); - if (_totalSize > MAX_SIZE) { - QMessageBox::warning(NULL, - QString("ModelUploader::zip()"), - QString("Model too big, over %1 Bytes.").arg(MAX_SIZE), - QMessageBox::Ok); - qDebug() << "[Warning] " << QString("Model too big, over %1 Bytes.").arg(MAX_SIZE); - return false; - } - qDebug() << "Current model size: " << _totalSize; - - return true; -} - - - - - - diff --git a/libraries/shared/src/FstReader.h b/libraries/shared/src/FstReader.h deleted file mode 100644 index 6d1cac01c4..0000000000 --- a/libraries/shared/src/FstReader.h +++ /dev/null @@ -1,40 +0,0 @@ -// -// FstReader.h -// hifi -// -// Created by Clément Brisset on 3/4/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. -// -// - -#ifndef __hifi__FstReader__ -#define __hifi__FstReader__ - -class TemporaryDir; -class QHttpMultiPart; - -class FstReader : public QObject { -public: - FstReader(bool isHead); - ~FstReader(); - - bool zip(); - bool send(); - -private: - TemporaryDir* _zipDir; - int _lodCount; - int _texturesCount; - int _totalSize; - bool _isHead; - bool _readyToSend; - - QHttpMultiPart* _dataMultiPart; - - - bool addTextures(const QFileInfo& texdir); - bool compressFile(const QString& inFileName, const QString& outFileName); - bool addPart(const QString& path, const QString& name); -}; - -#endif /* defined(__hifi__FstReader__) */ diff --git a/libraries/shared/src/GenericThread.cpp b/libraries/shared/src/GenericThread.cpp index 59f2426bdb..4be253f045 100644 --- a/libraries/shared/src/GenericThread.cpp +++ b/libraries/shared/src/GenericThread.cpp @@ -1,11 +1,12 @@ // // GenericThread.cpp -// shared +// libraries/shared/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Generic Threaded or non-threaded processing class +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/libraries/shared/src/GenericThread.h b/libraries/shared/src/GenericThread.h index 1b5b05db5d..bbb01894ed 100644 --- a/libraries/shared/src/GenericThread.h +++ b/libraries/shared/src/GenericThread.h @@ -1,15 +1,18 @@ // // GenericThread.h -// shared +// libraries/shared/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // Generic Threaded or non-threaded processing class. // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef __shared__GenericThread__ -#define __shared__GenericThread__ +#ifndef hifi_GenericThread_h +#define hifi_GenericThread_h #include #include @@ -62,4 +65,4 @@ private: QThread* _thread; }; -#endif // __shared__GenericThread__ +#endif // hifi_GenericThread_h diff --git a/libraries/shared/src/GeometryUtil.cpp b/libraries/shared/src/GeometryUtil.cpp index ac8e4e97c4..cb2e829811 100644 --- a/libraries/shared/src/GeometryUtil.cpp +++ b/libraries/shared/src/GeometryUtil.cpp @@ -1,9 +1,13 @@ // // GeometryUtil.cpp -// interface +// libraries/shared/src // // Created by Andrzej Kapolka on 5/21/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 #include @@ -462,4 +466,4 @@ void PolygonClip::copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& len vertexArrayA[i] = vertexArrayB[i]; } } -} +} \ No newline at end of file diff --git a/libraries/shared/src/GeometryUtil.h b/libraries/shared/src/GeometryUtil.h index 12afc7c59c..b521a79771 100644 --- a/libraries/shared/src/GeometryUtil.h +++ b/libraries/shared/src/GeometryUtil.h @@ -1,13 +1,16 @@ // // GeometryUtil.h -// interface +// libraries/shared/src // // Created by Andrzej Kapolka on 5/21/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__GeometryUtil__ -#define __interface__GeometryUtil__ +#ifndef hifi_GeometryUtil_h +#define hifi_GeometryUtil_h #include @@ -112,4 +115,4 @@ private: }; -#endif /* defined(__interface__GeometryUtil__) */ +#endif // hifi_GeometryUtil_h diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp new file mode 100644 index 0000000000..d20f4276f1 --- /dev/null +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -0,0 +1,98 @@ +// +// HifiConfigVariantMap.cpp +// libraries/shared/src +// +// Created by Stephen Birarda on 2014-04-08. +// 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 +#include +#include +#include +#include +#include + +#include "HifiConfigVariantMap.h" + +QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringList& argumentList) { + + QVariantMap mergedMap; + + // Add anything in the CL parameter list to the variant map. + // Take anything with a dash in it as a key, and the values after it as the value. + + const QString DASHED_KEY_REGEX_STRING = "(^-{1,2})([\\w-]+)"; + QRegExp dashedKeyRegex(DASHED_KEY_REGEX_STRING); + + int keyIndex = argumentList.indexOf(dashedKeyRegex); + int nextKeyIndex = 0; + + // check if there is a config file to read where we can pull config info not passed on command line + const QString CONFIG_FILE_OPTION = "--config"; + + while (keyIndex != -1) { + if (argumentList[keyIndex] != CONFIG_FILE_OPTION) { + // we have a key - look forward to see how many values associate to it + QString key = dashedKeyRegex.cap(2); + + nextKeyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); + + if (nextKeyIndex == keyIndex + 1) { + // there's no value associated with this option, it's a boolean + // so add it to the variant map with NULL as value + mergedMap.insertMulti(key, QVariant()); + } else { + int maxIndex = (nextKeyIndex == -1) ? argumentList.size() : nextKeyIndex; + + // there's at least one value associated with the option + // pull the first value to start + QString value = argumentList[keyIndex + 1]; + + // for any extra values, append them, with a space, to the value string + for (int i = keyIndex + 2; i < maxIndex; i++) { + value += " " + argumentList[i]; + } + + // add the finalized value to the merged map + mergedMap.insert(key, value); + } + + keyIndex = nextKeyIndex; + } else { + keyIndex = argumentList.indexOf(dashedKeyRegex, keyIndex + 1); + } + } + + int configIndex = argumentList.indexOf(CONFIG_FILE_OPTION); + + if (configIndex != -1) { + // we have a config file - try and read it + QString configFilePath = argumentList[configIndex + 1]; + QFile configFile(configFilePath); + + if (configFile.exists()) { + qDebug() << "Reading JSON config file at" << configFilePath; + configFile.open(QIODevice::ReadOnly); + + QJsonDocument configDocument = QJsonDocument::fromJson(configFile.readAll()); + QJsonObject rootObject = configDocument.object(); + + // enumerate the keys of the configDocument object + foreach(const QString& key, rootObject.keys()) { + + if (!mergedMap.contains(key)) { + // no match in existing list, add it + mergedMap.insert(key, QVariant(rootObject[key])); + } + } + } else { + qDebug() << "Could not find JSON config file at" << configFilePath; + } + } + + return mergedMap; +} diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h new file mode 100644 index 0000000000..378aa749c5 --- /dev/null +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -0,0 +1,22 @@ +// +// HifiConfigVariantMap.h +// libraries/shared/src +// +// Created by Stephen Birarda on 2014-04-08. +// 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_HifiConfigVariantMap_h +#define hifi_HifiConfigVariantMap_h + +#include + +class HifiConfigVariantMap { +public: + static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); +}; + +#endif // hifi_HifiConfigVariantMap_h diff --git a/libraries/shared/src/ListShape.cpp b/libraries/shared/src/ListShape.cpp index 593304c75a..dcea97826e 100644 --- a/libraries/shared/src/ListShape.cpp +++ b/libraries/shared/src/ListShape.cpp @@ -1,10 +1,12 @@ // // ListShape.cpp +// libraries/shared/src // -// ListShape: A collection of shapes, each with a local transform. +// Created by Andrew Meadows on 02/20/2014. +// Copyright 2014 High Fidelity, Inc. // -// Created by Andrew Meadows on 2014.02.20 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "ListShape.h" diff --git a/libraries/shared/src/ListShape.h b/libraries/shared/src/ListShape.h index d6005ddfb9..7ba2410a23 100644 --- a/libraries/shared/src/ListShape.h +++ b/libraries/shared/src/ListShape.h @@ -1,14 +1,18 @@ // // ListShape.h +// libraries/shared/src +// +// Created by Andrew Meadows on 02/20/2014. +// Copyright 2014 High Fidelity, Inc. // // ListShape: A collection of shapes, each with a local transform. // -// Created by Andrew Meadows on 2014.02.20 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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__ListShape__ -#define __hifi__ListShape__ +#ifndef hifi_ListShape_h +#define hifi_ListShape_h #include @@ -62,4 +66,4 @@ private: ListShape(const ListShape& otherList); // don't implement this }; -#endif // __hifi__ListShape__ +#endif // hifi_ListShape_h diff --git a/libraries/shared/src/NodeData.cpp b/libraries/shared/src/NodeData.cpp deleted file mode 100644 index e3800f8b93..0000000000 --- a/libraries/shared/src/NodeData.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// -// NodeData.cpp -// hifi -// -// Created by Stephen Birarda on 2/19/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// - -#include "NodeData.h" - -NodeData::NodeData() : - _mutex() -{ - -} - -NodeData::~NodeData() { - -} \ No newline at end of file diff --git a/libraries/shared/src/NodeList.cpp b/libraries/shared/src/NodeList.cpp deleted file mode 100644 index b725914bfd..0000000000 --- a/libraries/shared/src/NodeList.cpp +++ /dev/null @@ -1,978 +0,0 @@ -// -// NodeList.cpp -// hifi -// -// Created by Stephen Birarda on 2/15/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. -// - -#include -#include -#include - -#include -#include -#include -#include -#include - -#include "AccountManager.h" -#include "Assignment.h" -#include "HifiSockAddr.h" -#include "Logging.h" -#include "NodeList.h" -#include "PacketHeaders.h" -#include "SharedUtil.h" -#include "UUID.h" - -const char SOLO_NODE_TYPES[2] = { - NodeType::AvatarMixer, - NodeType::AudioMixer -}; - -const QUrl DEFAULT_NODE_AUTH_URL = QUrl("https://data-web.highfidelity.io"); - -NodeList* NodeList::_sharedInstance = NULL; - -NodeList* NodeList::createInstance(char ownerType, unsigned short int socketListenPort) { - if (!_sharedInstance) { - NodeType::init(); - - _sharedInstance = new NodeList(ownerType, socketListenPort); - - // register the SharedNodePointer meta-type for signals/slots - qRegisterMetaType(); - } else { - qDebug("NodeList createInstance called with existing instance."); - } - - return _sharedInstance; -} - -NodeList* NodeList::getInstance() { - if (!_sharedInstance) { - qDebug("NodeList getInstance called before call to createInstance. Returning NULL pointer."); - } - - return _sharedInstance; -} - - -NodeList::NodeList(char newOwnerType, unsigned short int newSocketListenPort) : - _nodeHash(), - _nodeHashMutex(QMutex::Recursive), - _nodeSocket(this), - _ownerType(newOwnerType), - _nodeTypesOfInterest(), - _sessionUUID(), - _numNoReplyDomainCheckIns(0), - _assignmentServerSocket(), - _publicSockAddr(), - _hasCompletedInitialSTUNFailure(false), - _stunRequestsSinceSuccess(0), - _numCollectedPackets(0), - _numCollectedBytes(0), - _packetStatTimer() -{ - _nodeSocket.bind(QHostAddress::AnyIPv4, newSocketListenPort); - qDebug() << "NodeList socket is listening on" << _nodeSocket.localPort(); - - // clear our NodeList when the domain changes - connect(&_domainInfo, &DomainInfo::hostnameChanged, this, &NodeList::reset); - - // clear our NodeList when logout is requested - connect(&AccountManager::getInstance(), &AccountManager::logoutComplete , this, &NodeList::reset); - - const int LARGER_SNDBUF_SIZE = 1048576; - changeSendSocketBufferSize(LARGER_SNDBUF_SIZE); - - _packetStatTimer.start(); -} - -void NodeList::changeSendSocketBufferSize(int numSendBytes) { - // change the socket send buffer size to be 1MB - int oldBufferSize = 0; - -#ifdef Q_OS_WIN - int sizeOfInt = sizeof(oldBufferSize); -#else - unsigned int sizeOfInt = sizeof(oldBufferSize); -#endif - - getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&oldBufferSize), &sizeOfInt); - - setsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&numSendBytes), - sizeof(numSendBytes)); - - int newBufferSize = 0; - getsockopt(_nodeSocket.socketDescriptor(), SOL_SOCKET, SO_SNDBUF, reinterpret_cast(&newBufferSize), &sizeOfInt); - - qDebug() << "Changed socket send buffer size from" << oldBufferSize << "to" << newBufferSize << "bytes"; -} - -bool NodeList::packetVersionAndHashMatch(const QByteArray& packet) { - PacketType checkType = packetTypeForPacket(packet); - if (packet[1] != versionForPacketType(checkType) - && checkType != PacketTypeStunResponse) { - PacketType mismatchType = packetTypeForPacket(packet); - int numPacketTypeBytes = numBytesArithmeticCodingFromBuffer(packet.data()); - - static QMultiMap versionDebugSuppressMap; - - QUuid senderUUID = uuidFromPacketHeader(packet); - if (!versionDebugSuppressMap.contains(senderUUID, checkType)) { - qDebug() << "Packet version mismatch on" << packetTypeForPacket(packet) << "- Sender" - << uuidFromPacketHeader(packet) << "sent" << qPrintable(QString::number(packet[numPacketTypeBytes])) << "but" - << qPrintable(QString::number(versionForPacketType(mismatchType))) << "expected."; - - versionDebugSuppressMap.insert(senderUUID, checkType); - } - - return false; - } - - const QSet NON_VERIFIED_PACKETS = QSet() - << PacketTypeDomainServerAuthRequest << PacketTypeDomainConnectRequest - << PacketTypeStunResponse << PacketTypeDataServerConfirm - << PacketTypeDataServerGet << PacketTypeDataServerPut << PacketTypeDataServerSend - << PacketTypeCreateAssignment << PacketTypeRequestAssignment; - - if (!NON_VERIFIED_PACKETS.contains(checkType)) { - // figure out which node this is from - SharedNodePointer sendingNode = sendingNodeForPacket(packet); - if (sendingNode) { - // check if the md5 hash in the header matches the hash we would expect - if (hashFromPacketHeader(packet) == hashForPacketAndConnectionUUID(packet, sendingNode->getConnectionSecret())) { - return true; - } else { - qDebug() << "Packet hash mismatch on" << checkType << "- Sender" - << uuidFromPacketHeader(packet); - } - } else { - if (checkType == PacketTypeDomainList) { - - if (_domainInfo.getRootAuthenticationURL().isEmpty() && _domainInfo.getUUID().isNull()) { - // if this is a domain-server that doesn't require auth, - // pull the UUID from this packet and set it as our domain-server UUID - _domainInfo.setUUID(uuidFromPacketHeader(packet)); - - // we also know this domain-server requires no authentication - // so set the account manager root URL to the default one - AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL); - } - - if (_domainInfo.getUUID() == uuidFromPacketHeader(packet)) { - if (hashForPacketAndConnectionUUID(packet, _domainInfo.getConnectionSecret()) == hashFromPacketHeader(packet)) { - // this is a packet from the domain-server (PacketTypeDomainServerListRequest) - // and the sender UUID matches the UUID we expect for the domain - return true; - } else { - // this is a packet from the domain-server but there is a hash mismatch - qDebug() << "Packet hash mismatch on" << checkType << "from domain-server at" << _domainInfo.getHostname(); - return false; - } - } - } - - qDebug() << "Packet of type" << checkType << "received from unknown node with UUID" - << uuidFromPacketHeader(packet); - } - } else { - return true; - } - - return false; -} - -qint64 NodeList::writeDatagram(const QByteArray& datagram, const HifiSockAddr& destinationSockAddr, - const QUuid& connectionSecret) { - QByteArray datagramCopy = datagram; - - // setup the MD5 hash for source verification in the header - replaceHashInPacketGivenConnectionUUID(datagramCopy, connectionSecret); - - // stat collection for packets - ++_numCollectedPackets; - _numCollectedBytes += datagram.size(); - - qint64 bytesWritten = _nodeSocket.writeDatagram(datagramCopy, destinationSockAddr.getAddress(), destinationSockAddr.getPort()); - - if (bytesWritten < 0) { - qDebug() << "ERROR in writeDatagram:" << _nodeSocket.error() << "-" << _nodeSocket.errorString(); - } - - return bytesWritten; -} - -qint64 NodeList::writeDatagram(const QByteArray& datagram, const SharedNodePointer& destinationNode, - const HifiSockAddr& overridenSockAddr) { - if (destinationNode) { - // if we don't have an ovveriden address, assume they want to send to the node's active socket - const HifiSockAddr* destinationSockAddr = &overridenSockAddr; - if (overridenSockAddr.isNull()) { - if (destinationNode->getActiveSocket()) { - // use the node's active socket as the destination socket - destinationSockAddr = destinationNode->getActiveSocket(); - } else { - // we don't have a socket to send to, return 0 - return 0; - } - } - - writeDatagram(datagram, *destinationSockAddr, destinationNode->getConnectionSecret()); - } - - // didn't have a destinationNode to send to, return 0 - return 0; -} - -qint64 NodeList::writeDatagram(const char* data, qint64 size, const SharedNodePointer& destinationNode, - const HifiSockAddr& overridenSockAddr) { - return writeDatagram(QByteArray(data, size), destinationNode, overridenSockAddr); -} - -qint64 NodeList::sendStatsToDomainServer(const QJsonObject& statsObject) { - QByteArray statsPacket = byteArrayWithPopulatedHeader(PacketTypeNodeJsonStats); - QDataStream statsPacketStream(&statsPacket, QIODevice::Append); - - statsPacketStream << statsObject.toVariantMap(); - - return writeDatagram(statsPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); -} - -void NodeList::timePingReply(const QByteArray& packet, const SharedNodePointer& sendingNode) { - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - quint8 pingType; - quint64 ourOriginalTime, othersReplyTime; - - packetStream >> pingType >> ourOriginalTime >> othersReplyTime; - - quint64 now = usecTimestampNow(); - int pingTime = now - ourOriginalTime; - int oneWayFlightTime = pingTime / 2; // half of the ping is our one way flight - - // The other node's expected time should be our original time plus the one way flight time - // anything other than that is clock skew - quint64 othersExprectedReply = ourOriginalTime + oneWayFlightTime; - int clockSkew = othersReplyTime - othersExprectedReply; - - sendingNode->setPingMs(pingTime / 1000); - sendingNode->setClockSkewUsec(clockSkew); - - const bool wantDebug = false; - - if (wantDebug) { - qDebug() << "PING_REPLY from node " << *sendingNode << "\n" << - " now: " << now << "\n" << - " ourTime: " << ourOriginalTime << "\n" << - " pingTime: " << pingTime << "\n" << - " oneWayFlightTime: " << oneWayFlightTime << "\n" << - " othersReplyTime: " << othersReplyTime << "\n" << - " othersExprectedReply: " << othersExprectedReply << "\n" << - " clockSkew: " << clockSkew; - } -} - -void NodeList::processNodeData(const HifiSockAddr& senderSockAddr, const QByteArray& packet) { - switch (packetTypeForPacket(packet)) { - case PacketTypeDomainList: { - processDomainServerList(packet); - break; - } - case PacketTypeDomainServerAuthRequest: { - // the domain-server has asked us to auth via a data-server - processDomainServerAuthRequest(packet); - - break; - } - case PacketTypePing: { - // send back a reply - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - if (matchingNode) { - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - QByteArray replyPacket = constructPingReplyPacket(packet); - writeDatagram(replyPacket, matchingNode, senderSockAddr); - - // If we don't have a symmetric socket for this node and this socket doesn't match - // what we have for public and local then set it as the symmetric. - // This allows a server on a reachable port to communicate with nodes on symmetric NATs - if (matchingNode->getSymmetricSocket().isNull()) { - if (senderSockAddr != matchingNode->getLocalSocket() && senderSockAddr != matchingNode->getPublicSocket()) { - matchingNode->setSymmetricSocket(senderSockAddr); - } - } - } - - break; - } - case PacketTypePingReply: { - SharedNodePointer sendingNode = sendingNodeForPacket(packet); - - if (sendingNode) { - sendingNode->setLastHeardMicrostamp(usecTimestampNow()); - - // activate the appropriate socket for this node, if not yet updated - activateSocketFromNodeCommunication(packet, sendingNode); - - // set the ping time for this node for stat collection - timePingReply(packet, sendingNode); - } - - break; - } - case PacketTypeStunResponse: { - // a STUN packet begins with 00, we've checked the second zero with packetVersionMatch - // pass it along so it can be processed into our public address and port - processSTUNResponse(packet); - break; - } - default: - // the node decided not to do anything with this packet - // if it comes from a known source we should keep that node alive - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - if (matchingNode) { - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - } - - break; - } -} - -int NodeList::updateNodeWithDataFromPacket(const SharedNodePointer& matchingNode, const QByteArray &packet) { - QMutexLocker locker(&matchingNode->getMutex()); - - matchingNode->setLastHeardMicrostamp(usecTimestampNow()); - matchingNode->recordBytesReceived(packet.size()); - - if (!matchingNode->getLinkedData() && linkedDataCreateCallback) { - linkedDataCreateCallback(matchingNode.data()); - } - - QMutexLocker linkedDataLocker(&matchingNode->getLinkedData()->getMutex()); - - return matchingNode->getLinkedData()->parseData(packet); -} - -int NodeList::findNodeAndUpdateWithDataFromPacket(const QByteArray& packet) { - SharedNodePointer matchingNode = sendingNodeForPacket(packet); - - if (matchingNode) { - updateNodeWithDataFromPacket(matchingNode, packet); - } - - // we weren't able to match the sender address to the address we have for this node, unlock and don't parse - return 0; -} - -SharedNodePointer NodeList::nodeWithUUID(const QUuid& nodeUUID, bool blockingLock) { - const int WAIT_TIME = 10; // wait up to 10ms in the try lock case - SharedNodePointer node; - // if caller wants us to block and guarantee the correct answer, then honor that request - if (blockingLock) { - // this will block till we can get access - QMutexLocker locker(&_nodeHashMutex); - node = _nodeHash.value(nodeUUID); - } else if (_nodeHashMutex.tryLock(WAIT_TIME)) { // some callers are willing to get wrong answers but not block - node = _nodeHash.value(nodeUUID); - _nodeHashMutex.unlock(); - } - return node; - } - -SharedNodePointer NodeList::sendingNodeForPacket(const QByteArray& packet) { - QUuid nodeUUID = uuidFromPacketHeader(packet); - - // return the matching node, or NULL if there is no match - return nodeWithUUID(nodeUUID); -} - -NodeHash NodeList::getNodeHash() { - QMutexLocker locker(&_nodeHashMutex); - return NodeHash(_nodeHash); -} - -void NodeList::eraseAllNodes() { - qDebug() << "Clearing the NodeList. Deleting all nodes in list."; - - QMutexLocker locker(&_nodeHashMutex); - - NodeHash::iterator nodeItem = _nodeHash.begin(); - - // iterate the nodes in the list - while (nodeItem != _nodeHash.end()) { - nodeItem = killNodeAtHashIterator(nodeItem); - } -} - -void NodeList::reset() { - eraseAllNodes(); - _numNoReplyDomainCheckIns = 0; - - // refresh the owner UUID to the NULL UUID - setSessionUUID(QUuid()); - - // clear the domain connection information - _domainInfo.clearConnectionInfo(); -} - -void NodeList::addNodeTypeToInterestSet(NodeType_t nodeTypeToAdd) { - _nodeTypesOfInterest << nodeTypeToAdd; -} - -void NodeList::addSetOfNodeTypesToNodeInterestSet(const NodeSet& setOfNodeTypes) { - _nodeTypesOfInterest.unite(setOfNodeTypes); -} - -const uint32_t RFC_5389_MAGIC_COOKIE = 0x2112A442; -const int NUM_BYTES_STUN_HEADER = 20; -const unsigned int NUM_STUN_REQUESTS_BEFORE_FALLBACK = 5; - -void NodeList::sendSTUNRequest() { - const char STUN_SERVER_HOSTNAME[] = "stun.highfidelity.io"; - const unsigned short STUN_SERVER_PORT = 3478; - - unsigned char stunRequestPacket[NUM_BYTES_STUN_HEADER]; - - int packetIndex = 0; - - const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); - - // leading zeros + message type - const uint16_t REQUEST_MESSAGE_TYPE = htons(0x0001); - memcpy(stunRequestPacket + packetIndex, &REQUEST_MESSAGE_TYPE, sizeof(REQUEST_MESSAGE_TYPE)); - packetIndex += sizeof(REQUEST_MESSAGE_TYPE); - - // message length (no additional attributes are included) - uint16_t messageLength = 0; - memcpy(stunRequestPacket + packetIndex, &messageLength, sizeof(messageLength)); - packetIndex += sizeof(messageLength); - - memcpy(stunRequestPacket + packetIndex, &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)); - packetIndex += sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - - // transaction ID (random 12-byte unsigned integer) - const uint NUM_TRANSACTION_ID_BYTES = 12; - QUuid randomUUID = QUuid::createUuid(); - memcpy(stunRequestPacket + packetIndex, randomUUID.toRfc4122().data(), NUM_TRANSACTION_ID_BYTES); - - // lookup the IP for the STUN server - static HifiSockAddr stunSockAddr(STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - - if (!_hasCompletedInitialSTUNFailure) { - qDebug("Sending intial stun request to %s", stunSockAddr.getAddress().toString().toLocal8Bit().constData()); - } - - _nodeSocket.writeDatagram((char*) stunRequestPacket, sizeof(stunRequestPacket), - stunSockAddr.getAddress(), stunSockAddr.getPort()); - - _stunRequestsSinceSuccess++; - - if (_stunRequestsSinceSuccess >= NUM_STUN_REQUESTS_BEFORE_FALLBACK) { - if (!_hasCompletedInitialSTUNFailure) { - // if we're here this was the last failed STUN request - // use our DS as our stun server - qDebug("Failed to lookup public address via STUN server at %s:%hu. Using DS for STUN.", - STUN_SERVER_HOSTNAME, STUN_SERVER_PORT); - - _hasCompletedInitialSTUNFailure = true; - } - - // reset the public address and port - // use 0 so the DS knows to act as out STUN server - _publicSockAddr = HifiSockAddr(QHostAddress(), _nodeSocket.localPort()); - } -} - -void NodeList::processSTUNResponse(const QByteArray& packet) { - // check the cookie to make sure this is actually a STUN response - // and read the first attribute and make sure it is a XOR_MAPPED_ADDRESS - const int NUM_BYTES_MESSAGE_TYPE_AND_LENGTH = 4; - const uint16_t XOR_MAPPED_ADDRESS_TYPE = htons(0x0020); - - const uint32_t RFC_5389_MAGIC_COOKIE_NETWORK_ORDER = htonl(RFC_5389_MAGIC_COOKIE); - - int attributeStartIndex = NUM_BYTES_STUN_HEADER; - - if (memcmp(packet.data() + NUM_BYTES_MESSAGE_TYPE_AND_LENGTH, - &RFC_5389_MAGIC_COOKIE_NETWORK_ORDER, - sizeof(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER)) == 0) { - - // enumerate the attributes to find XOR_MAPPED_ADDRESS_TYPE - while (attributeStartIndex < packet.size()) { - if (memcmp(packet.data() + attributeStartIndex, &XOR_MAPPED_ADDRESS_TYPE, sizeof(XOR_MAPPED_ADDRESS_TYPE)) == 0) { - const int NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH = 4; - const int NUM_BYTES_FAMILY_ALIGN = 1; - const uint8_t IPV4_FAMILY_NETWORK_ORDER = htons(0x01) >> 8; - - // reset the number of failed STUN requests since last success - _stunRequestsSinceSuccess = 0; - - int byteIndex = attributeStartIndex + NUM_BYTES_STUN_ATTR_TYPE_AND_LENGTH + NUM_BYTES_FAMILY_ALIGN; - - uint8_t addressFamily = 0; - memcpy(&addressFamily, packet.data(), sizeof(addressFamily)); - - byteIndex += sizeof(addressFamily); - - if (addressFamily == IPV4_FAMILY_NETWORK_ORDER) { - // grab the X-Port - uint16_t xorMappedPort = 0; - memcpy(&xorMappedPort, packet.data() + byteIndex, sizeof(xorMappedPort)); - - uint16_t newPublicPort = ntohs(xorMappedPort) ^ (ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER) >> 16); - - byteIndex += sizeof(xorMappedPort); - - // grab the X-Address - uint32_t xorMappedAddress = 0; - memcpy(&xorMappedAddress, packet.data() + byteIndex, sizeof(xorMappedAddress)); - - uint32_t stunAddress = ntohl(xorMappedAddress) ^ ntohl(RFC_5389_MAGIC_COOKIE_NETWORK_ORDER); - - QHostAddress newPublicAddress = QHostAddress(stunAddress); - - if (newPublicAddress != _publicSockAddr.getAddress() || newPublicPort != _publicSockAddr.getPort()) { - _publicSockAddr = HifiSockAddr(newPublicAddress, newPublicPort); - - qDebug("New public socket received from STUN server is %s:%hu", - _publicSockAddr.getAddress().toString().toLocal8Bit().constData(), - _publicSockAddr.getPort()); - - } - - _hasCompletedInitialSTUNFailure = true; - - break; - } - } else { - // push forward attributeStartIndex by the length of this attribute - const int NUM_BYTES_ATTRIBUTE_TYPE = 2; - - uint16_t attributeLength = 0; - memcpy(&attributeLength, packet.data() + attributeStartIndex + NUM_BYTES_ATTRIBUTE_TYPE, - sizeof(attributeLength)); - attributeLength = ntohs(attributeLength); - - attributeStartIndex += NUM_BYTES_MESSAGE_TYPE_AND_LENGTH + attributeLength; - } - } - } -} - -void NodeList::killNodeWithUUID(const QUuid& nodeUUID) { - QMutexLocker locker(&_nodeHashMutex); - - NodeHash::iterator nodeItemToKill = _nodeHash.find(nodeUUID); - if (nodeItemToKill != _nodeHash.end()) { - killNodeAtHashIterator(nodeItemToKill); - } -} - -NodeHash::iterator NodeList::killNodeAtHashIterator(NodeHash::iterator& nodeItemToKill) { - qDebug() << "Killed" << *nodeItemToKill.value(); - emit nodeKilled(nodeItemToKill.value()); - return _nodeHash.erase(nodeItemToKill); -} - -void NodeList::processKillNode(const QByteArray& dataByteArray) { - // read the node id - QUuid nodeUUID = QUuid::fromRfc4122(dataByteArray.mid(numBytesForPacketHeader(dataByteArray), NUM_BYTES_RFC4122_UUID)); - - // kill the node with this UUID, if it exists - killNodeWithUUID(nodeUUID); -} - -void NodeList::sendDomainServerCheckIn() { - if (_publicSockAddr.isNull() && !_hasCompletedInitialSTUNFailure) { - // we don't know our public socket and we need to send it to the domain server - // send a STUN request to figure it out - sendSTUNRequest(); - } else if (!_domainInfo.getIP().isNull()) { - if (_domainInfo.getRootAuthenticationURL().isEmpty() - || !_sessionUUID.isNull() - || !_domainInfo.getRegistrationToken().isEmpty() ) { - // construct the DS check in packet - - PacketType domainPacketType = _sessionUUID.isNull() ? PacketTypeDomainConnectRequest : PacketTypeDomainListRequest; - - QUuid packetUUID = (domainPacketType == PacketTypeDomainListRequest) - ? _sessionUUID : _domainInfo.getAssignmentUUID(); - - QByteArray domainServerPacket = byteArrayWithPopulatedHeader(domainPacketType, packetUUID); - QDataStream packetStream(&domainServerPacket, QIODevice::Append); - - if (domainPacketType == PacketTypeDomainConnectRequest) { - // we may need a registration token to present to the domain-server - packetStream << (quint8) !_domainInfo.getRegistrationToken().isEmpty(); - - if (!_domainInfo.getRegistrationToken().isEmpty()) { - // if we have a registration token send that along in the request - packetStream << _domainInfo.getRegistrationToken(); - } - } - - // pack our data to send to the domain-server - packetStream << _ownerType << _publicSockAddr - << HifiSockAddr(QHostAddress(getHostOrderLocalAddress()), _nodeSocket.localPort()) - << (quint8) _nodeTypesOfInterest.size(); - - // copy over the bytes for node types of interest, if required - foreach (NodeType_t nodeTypeOfInterest, _nodeTypesOfInterest) { - packetStream << nodeTypeOfInterest; - } - - writeDatagram(domainServerPacket, _domainInfo.getSockAddr(), _domainInfo.getConnectionSecret()); - const int NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST = 5; - static unsigned int numDomainCheckins = 0; - - // send a STUN request every Nth domain server check in so we update our public socket, if required - if (numDomainCheckins++ % NUM_DOMAIN_SERVER_CHECKINS_PER_STUN_REQUEST == 0) { - sendSTUNRequest(); - } - - if (_numNoReplyDomainCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { - // we haven't heard back from DS in MAX_SILENT_DOMAIN_SERVER_CHECK_INS - // so emit our signal that indicates that - emit limitOfSilentDomainCheckInsReached(); - } - - // increment the count of un-replied check-ins - _numNoReplyDomainCheckIns++; - } else if (AccountManager::getInstance().hasValidAccessToken()) { - // we have an access token we can use for the authentication server the domain-server requested - // so ask that server to provide us with information to connect to the domain-server - requestAuthForDomainServer(); - } - } -} - -void NodeList::setSessionUUID(const QUuid& sessionUUID) { - QUuid oldUUID = _sessionUUID; - _sessionUUID = sessionUUID; - - if (sessionUUID != oldUUID) { - qDebug() << "NodeList UUID changed from" << uuidStringWithoutCurlyBraces(oldUUID) - << "to" << uuidStringWithoutCurlyBraces(_sessionUUID); - emit uuidChanged(sessionUUID); - } -} - -int NodeList::processDomainServerList(const QByteArray& packet) { - // this is a packet from the domain server, reset the count of un-replied check-ins - _numNoReplyDomainCheckIns = 0; - - // if this was the first domain-server list from this domain, we've now connected - _domainInfo.setIsConnected(true); - - int readNodes = 0; - - // setup variables to read into from QDataStream - qint8 nodeType; - - QUuid nodeUUID, connectionUUID; - - HifiSockAddr nodePublicSocket; - HifiSockAddr nodeLocalSocket; - - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - // pull our owner UUID from the packet, it's always the first thing - QUuid newUUID; - packetStream >> newUUID; - setSessionUUID(newUUID); - - // pull each node in the packet - while(packetStream.device()->pos() < packet.size()) { - packetStream >> nodeType >> nodeUUID >> nodePublicSocket >> nodeLocalSocket; - - // if the public socket address is 0 then it's reachable at the same IP - // as the domain server - if (nodePublicSocket.getAddress().isNull()) { - nodePublicSocket.setAddress(_domainInfo.getIP()); - } - - SharedNodePointer node = addOrUpdateNode(nodeUUID, nodeType, nodePublicSocket, nodeLocalSocket); - - packetStream >> connectionUUID; - node->setConnectionSecret(connectionUUID); - } - - // ping inactive nodes in conjunction with receipt of list from domain-server - // this makes it happen every second and also pings any newly added nodes - pingInactiveNodes(); - - return readNodes; -} - -void NodeList::domainServerAuthReply(const QJsonObject& jsonObject) { - _domainInfo.parseAuthInformationFromJsonObject(jsonObject); -} - -void NodeList::requestAuthForDomainServer() { - JSONCallbackParameters callbackParams; - callbackParams.jsonCallbackReceiver = this; - callbackParams.jsonCallbackMethod = "domainServerAuthReply"; - - AccountManager::getInstance().authenticatedRequest("/api/v1/domains/" - + uuidStringWithoutCurlyBraces(_domainInfo.getUUID()) + "/auth.json", - QNetworkAccessManager::GetOperation, - callbackParams); -} - -void NodeList::processDomainServerAuthRequest(const QByteArray& packet) { - QDataStream authPacketStream(packet); - authPacketStream.skipRawData(numBytesForPacketHeader(packet)); - - _domainInfo.setUUID(uuidFromPacketHeader(packet)); - AccountManager& accountManager = AccountManager::getInstance(); - - // grab the hostname this domain-server wants us to authenticate with - QUrl authenticationRootURL; - authPacketStream >> authenticationRootURL; - - accountManager.setAuthURL(authenticationRootURL); - _domainInfo.setRootAuthenticationURL(authenticationRootURL); - - if (AccountManager::getInstance().checkAndSignalForAccessToken()) { - // request a domain-server auth - requestAuthForDomainServer(); - } -} - -void NodeList::sendAssignment(Assignment& assignment) { - - PacketType assignmentPacketType = assignment.getCommand() == Assignment::CreateCommand - ? PacketTypeCreateAssignment - : PacketTypeRequestAssignment; - - QByteArray packet = byteArrayWithPopulatedHeader(assignmentPacketType); - QDataStream packetStream(&packet, QIODevice::Append); - - packetStream << assignment; - - static HifiSockAddr DEFAULT_ASSIGNMENT_SOCKET(DEFAULT_ASSIGNMENT_SERVER_HOSTNAME, DEFAULT_DOMAIN_SERVER_PORT); - - const HifiSockAddr* assignmentServerSocket = _assignmentServerSocket.isNull() - ? &DEFAULT_ASSIGNMENT_SOCKET - : &_assignmentServerSocket; - - _nodeSocket.writeDatagram(packet, assignmentServerSocket->getAddress(), assignmentServerSocket->getPort()); -} - -QByteArray NodeList::constructPingPacket(PingType_t pingType) { - QByteArray pingPacket = byteArrayWithPopulatedHeader(PacketTypePing); - - QDataStream packetStream(&pingPacket, QIODevice::Append); - - packetStream << pingType; - packetStream << usecTimestampNow(); - - return pingPacket; -} - -QByteArray NodeList::constructPingReplyPacket(const QByteArray& pingPacket) { - QDataStream pingPacketStream(pingPacket); - pingPacketStream.skipRawData(numBytesForPacketHeader(pingPacket)); - - PingType_t typeFromOriginalPing; - pingPacketStream >> typeFromOriginalPing; - - quint64 timeFromOriginalPing; - pingPacketStream >> timeFromOriginalPing; - - QByteArray replyPacket = byteArrayWithPopulatedHeader(PacketTypePingReply); - QDataStream packetStream(&replyPacket, QIODevice::Append); - - packetStream << typeFromOriginalPing << timeFromOriginalPing << usecTimestampNow(); - - return replyPacket; -} - -void NodeList::pingPunchForInactiveNode(const SharedNodePointer& node) { - - // send the ping packet to the local and public sockets for this node - QByteArray localPingPacket = constructPingPacket(PingType::Local); - writeDatagram(localPingPacket, node, node->getLocalSocket()); - - QByteArray publicPingPacket = constructPingPacket(PingType::Public); - writeDatagram(publicPingPacket, node, node->getPublicSocket()); - - if (!node->getSymmetricSocket().isNull()) { - QByteArray symmetricPingPacket = constructPingPacket(PingType::Symmetric); - writeDatagram(symmetricPingPacket, node, node->getSymmetricSocket()); - } -} - -SharedNodePointer NodeList::addOrUpdateNode(const QUuid& uuid, char nodeType, - const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { - _nodeHashMutex.lock(); - - if (!_nodeHash.contains(uuid)) { - - // we didn't have this node, so add them - Node* newNode = new Node(uuid, nodeType, publicSocket, localSocket); - SharedNodePointer newNodeSharedPointer(newNode, &QObject::deleteLater); - - _nodeHash.insert(newNode->getUUID(), newNodeSharedPointer); - - _nodeHashMutex.unlock(); - - qDebug() << "Added" << *newNode; - - emit nodeAdded(newNodeSharedPointer); - - return newNodeSharedPointer; - } else { - _nodeHashMutex.unlock(); - - return updateSocketsForNode(uuid, publicSocket, localSocket); - } -} - -SharedNodePointer NodeList::updateSocketsForNode(const QUuid& uuid, - const HifiSockAddr& publicSocket, const HifiSockAddr& localSocket) { - - SharedNodePointer matchingNode = nodeWithUUID(uuid); - - if (matchingNode) { - // perform appropriate updates to this node - QMutexLocker locker(&matchingNode->getMutex()); - - // check if we need to change this node's public or local sockets - if (publicSocket != matchingNode->getPublicSocket()) { - matchingNode->setPublicSocket(publicSocket); - qDebug() << "Public socket change for node" << *matchingNode; - } - - if (localSocket != matchingNode->getLocalSocket()) { - matchingNode->setLocalSocket(localSocket); - qDebug() << "Local socket change for node" << *matchingNode; - } - } - - return matchingNode; -} - -unsigned NodeList::broadcastToNodes(const QByteArray& packet, const NodeSet& destinationNodeTypes) { - unsigned n = 0; - - foreach (const SharedNodePointer& node, getNodeHash()) { - // only send to the NodeTypes we are asked to send to. - if (destinationNodeTypes.contains(node->getType())) { - writeDatagram(packet, node); - ++n; - } - } - - return n; -} - -void NodeList::pingInactiveNodes() { - foreach (const SharedNodePointer& node, getNodeHash()) { - if (!node->getActiveSocket()) { - // we don't have an active link to this node, ping it to set that up - pingPunchForInactiveNode(node); - } - } -} - -void NodeList::activateSocketFromNodeCommunication(const QByteArray& packet, const SharedNodePointer& sendingNode) { - // deconstruct this ping packet to see if it is a public or local reply - QDataStream packetStream(packet); - packetStream.skipRawData(numBytesForPacketHeader(packet)); - - quint8 pingType; - packetStream >> pingType; - - // if this is a local or public ping then we can activate a socket - // we do nothing with agnostic pings, those are simply for timing - if (pingType == PingType::Local && sendingNode->getActiveSocket() != &sendingNode->getLocalSocket()) { - sendingNode->activateLocalSocket(); - } else if (pingType == PingType::Public && !sendingNode->getActiveSocket()) { - sendingNode->activatePublicSocket(); - } else if (pingType == PingType::Symmetric && !sendingNode->getActiveSocket()) { - sendingNode->activateSymmetricSocket(); - } -} - -SharedNodePointer NodeList::soloNodeOfType(char nodeType) { - - if (memchr(SOLO_NODE_TYPES, nodeType, sizeof(SOLO_NODE_TYPES))) { - foreach (const SharedNodePointer& node, getNodeHash()) { - if (node->getType() == nodeType) { - return node; - } - } - } - return SharedNodePointer(); -} - -void NodeList::getPacketStats(float& packetsPerSecond, float& bytesPerSecond) { - packetsPerSecond = (float) _numCollectedPackets / ((float) _packetStatTimer.elapsed() / 1000.0f); - bytesPerSecond = (float) _numCollectedBytes / ((float) _packetStatTimer.elapsed() / 1000.0f); -} - -void NodeList::resetPacketStats() { - _numCollectedPackets = 0; - _numCollectedBytes = 0; - _packetStatTimer.restart(); -} - -void NodeList::removeSilentNodes() { - - _nodeHashMutex.lock(); - - NodeHash::iterator nodeItem = _nodeHash.begin(); - - while (nodeItem != _nodeHash.end()) { - SharedNodePointer node = nodeItem.value(); - - node->getMutex().lock(); - - if ((usecTimestampNow() - node->getLastHeardMicrostamp()) > NODE_SILENCE_THRESHOLD_USECS) { - // call our private method to kill this node (removes it and emits the right signal) - nodeItem = killNodeAtHashIterator(nodeItem); - } else { - // we didn't kill this node, push the iterator forwards - ++nodeItem; - } - - node->getMutex().unlock(); - } - - _nodeHashMutex.unlock(); -} - -const QString QSETTINGS_GROUP_NAME = "NodeList"; -const QString DOMAIN_SERVER_SETTING_KEY = "domainServerHostname"; - -void NodeList::loadData(QSettings *settings) { - settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); - - QString domainServerHostname = settings->value(DOMAIN_SERVER_SETTING_KEY).toString(); - - if (domainServerHostname.size() > 0) { - _domainInfo.setHostname(domainServerHostname); - } else { - _domainInfo.setHostname(DEFAULT_DOMAIN_HOSTNAME); - } - - settings->endGroup(); -} - -void NodeList::saveData(QSettings* settings) { - settings->beginGroup(DOMAIN_SERVER_SETTING_KEY); - - if (_domainInfo.getHostname() != DEFAULT_DOMAIN_HOSTNAME) { - // the user is using a different hostname, store it - settings->setValue(DOMAIN_SERVER_SETTING_KEY, QVariant(_domainInfo.getHostname())); - } else { - // the user has switched back to default, remove the current setting - settings->remove(DOMAIN_SERVER_SETTING_KEY); - } - - settings->endGroup(); -} diff --git a/libraries/shared/src/OctalCode.cpp b/libraries/shared/src/OctalCode.cpp index ba0a001fb9..82bd471975 100644 --- a/libraries/shared/src/OctalCode.cpp +++ b/libraries/shared/src/OctalCode.cpp @@ -1,9 +1,12 @@ // // OctalCode.cpp -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 3/15/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 // std:min diff --git a/libraries/shared/src/OctalCode.h b/libraries/shared/src/OctalCode.h index c80aa82a2d..07895ff67f 100644 --- a/libraries/shared/src/OctalCode.h +++ b/libraries/shared/src/OctalCode.h @@ -1,13 +1,16 @@ // // OctalCode.h -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 3/15/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__OctalCode__ -#define __hifi__OctalCode__ +#ifndef hifi_OctalCode_h +#define hifi_OctalCode_h #include #include @@ -62,4 +65,4 @@ OctalCodeComparison compareOctalCodes(const unsigned char* code1, const unsigned QString octalCodeToHexString(const unsigned char* octalCode); unsigned char* hexStringToOctalCode(const QString& input); -#endif /* defined(__hifi__OctalCode__) */ +#endif // hifi_OctalCode_h diff --git a/libraries/shared/src/PerfStat.cpp b/libraries/shared/src/PerfStat.cpp index 7bea51dd6e..9235cb3f25 100644 --- a/libraries/shared/src/PerfStat.cpp +++ b/libraries/shared/src/PerfStat.cpp @@ -1,13 +1,12 @@ // -// HiFiPerfStat.cpp -// hifi +// PerfStat.cpp +// libraries/shared/src // // Created by Brad Hefta-Gaub on 3/29/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. -// -// Poor-man's performance stats collector class. Useful for collecting timing -// details from various portions of the code. +// Copyright 2013 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 diff --git a/libraries/shared/src/PerfStat.h b/libraries/shared/src/PerfStat.h index a7a10b97b8..22cf14f207 100644 --- a/libraries/shared/src/PerfStat.h +++ b/libraries/shared/src/PerfStat.h @@ -1,27 +1,23 @@ // -// HiFiPerfStat.h -// hifi +// PerfStat.h +// libraries/shared/src // // Created by Brad Hefta-Gaub on 3/29/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // Poor-man's performance stats collector class. Useful for collecting timing // details from various portions of the code. // +// 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__PerfStat__ -#define __hifi__PerfStat__ +#ifndef hifi_PerfStat_h +#define hifi_PerfStat_h #include #include "SharedUtil.h" -#ifdef _WIN32 -#include "Systime.h" -#else -#include -#endif - #include #include #include @@ -54,4 +50,4 @@ public: }; -#endif /* defined(__hifi__PerfStat__) */ +#endif // hifi_PerfStat_h diff --git a/libraries/shared/src/PlaneShape.cpp b/libraries/shared/src/PlaneShape.cpp new file mode 100644 index 0000000000..a8b4468c93 --- /dev/null +++ b/libraries/shared/src/PlaneShape.cpp @@ -0,0 +1,36 @@ +// +// PlaneShape.cpp +// libraries/shared/src +// +// Created by Andrzej Kapolka on 4/10/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 +// + +#include "PlaneShape.h" +#include "SharedUtil.h" + +const glm::vec3 UNROTATED_NORMAL(0.0f, 1.0f, 0.0f); + +PlaneShape::PlaneShape(const glm::vec4& coefficients) : + Shape(Shape::PLANE_SHAPE) { + + glm::vec3 normal = glm::vec3(coefficients); + _position = -normal * coefficients.w; + + float angle = acosf(glm::dot(normal, UNROTATED_NORMAL)); + if (angle > EPSILON) { + if (angle > PI - EPSILON) { + _rotation = glm::angleAxis(PI, glm::vec3(1.0f, 0.0f, 0.0f)); + } else { + _rotation = glm::angleAxis(angle, glm::normalize(glm::cross(UNROTATED_NORMAL, normal))); + } + } +} + +glm::vec4 PlaneShape::getCoefficients() const { + glm::vec3 normal = _rotation * UNROTATED_NORMAL; + return glm::vec4(normal.x, normal.y, normal.z, -glm::dot(normal, _position)); +} diff --git a/libraries/shared/src/PlaneShape.h b/libraries/shared/src/PlaneShape.h new file mode 100644 index 0000000000..524d53ec73 --- /dev/null +++ b/libraries/shared/src/PlaneShape.h @@ -0,0 +1,24 @@ +// +// PlaneShape.h +// libraries/shared/src +// +// Created by Andrzej Kapolka on 4/9/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 +// + +#ifndef hifi_PlaneShape_h +#define hifi_PlaneShape_h + +#include "Shape.h" + +class PlaneShape : public Shape { +public: + PlaneShape(const glm::vec4& coefficients = glm::vec4(0.0f, 1.0f, 0.0f, 0.0f)); + + glm::vec4 getCoefficients() const; +}; + +#endif // hifi_PlaneShape_h diff --git a/libraries/shared/src/Radix2InplaceSort.h b/libraries/shared/src/Radix2InplaceSort.h index abd650b2a6..dfa455a32d 100644 --- a/libraries/shared/src/Radix2InplaceSort.h +++ b/libraries/shared/src/Radix2InplaceSort.h @@ -1,13 +1,16 @@ // -// Radix2InplaceSort.h -// hifi +// Radix2InplaceSort.h +// libraries/shared/src // -// Created by Tobias Schwinger on 3/22/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/22/13. +// Copyright 2013 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__Radix2InplaceSort__ -#define __hifi__Radix2InplaceSort__ +#ifndef hifi_Radix2InplaceSort_h +#define hifi_Radix2InplaceSort_h #include @@ -91,4 +94,4 @@ void radix2InplaceSort( BidiIterator from, BidiIterator to, .go(from, to, scanner.initial_state()); } -#endif /* defined(__hifi__Radix2InplaceSort__) */ +#endif // hifi_Radix2InplaceSort_h diff --git a/libraries/shared/src/Radix2IntegerScanner.h b/libraries/shared/src/Radix2IntegerScanner.h index 84e7c7077e..16a6224778 100644 --- a/libraries/shared/src/Radix2IntegerScanner.h +++ b/libraries/shared/src/Radix2IntegerScanner.h @@ -1,13 +1,16 @@ // -// Radix2IntegerScanner.h -// hifi +// Radix2IntegerScanner.h +// libraries/shared/src // -// Created by Tobias Schwinger on 3/23/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Tobias Schwinger on 3/23/13. +// Copyright 2013 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__Radix2IntegerScanner__ -#define __hifi__Radix2IntegerScanner__ +#ifndef hifi_Radix2IntegerScanner_h +#define hifi_Radix2IntegerScanner_h #include #include @@ -82,5 +85,4 @@ class Radix2IntegerScanner< Int, true > bool bit(Int const& v, state_type const& s) const { return !!((v-valMsb) & s); } }; -#endif /* defined(__hifi__Radix2IntegerScanner__) */ - +#endif // hifi_Radix2IntegerScanner_h diff --git a/libraries/shared/src/RegisteredMetaTypes.cpp b/libraries/shared/src/RegisteredMetaTypes.cpp index d106977ae0..3e21a72fd5 100644 --- a/libraries/shared/src/RegisteredMetaTypes.cpp +++ b/libraries/shared/src/RegisteredMetaTypes.cpp @@ -1,12 +1,13 @@ // // RegisteredMetaTypes.cpp -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 10/3/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 // -// Used to register meta-types with Qt so that they can be used as properties for objects exposed to our -// Agent scripting. #include "RegisteredMetaTypes.h" @@ -16,6 +17,7 @@ static int vec2MetaTypeId = qRegisterMetaType(); static int quatMetaTypeId = qRegisterMetaType(); static int xColorMetaTypeId = qRegisterMetaType(); static int pickRayMetaTypeId = qRegisterMetaType(); +static int collisionMetaTypeId = qRegisterMetaType(); void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, vec4toScriptValue, vec4FromScriptValue); @@ -24,6 +26,7 @@ void registerMetaTypes(QScriptEngine* engine) { qScriptRegisterMetaType(engine, quatToScriptValue, quatFromScriptValue); qScriptRegisterMetaType(engine, xColorToScriptValue, xColorFromScriptValue); qScriptRegisterMetaType(engine, pickRayToScriptValue, pickRayFromScriptValue); + qScriptRegisterMetaType(engine, collisionToScriptValue, collisionFromScriptValue); } QScriptValue vec4toScriptValue(QScriptEngine* engine, const glm::vec4& vec4) { @@ -122,3 +125,14 @@ void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay) { } } +QScriptValue collisionToScriptValue(QScriptEngine* engine, const CollisionInfo& collision) { + QScriptValue obj = engine->newObject(); + obj.setProperty("penetration", vec3toScriptValue(engine, collision._penetration)); + obj.setProperty("contactPoint", vec3toScriptValue(engine, collision._contactPoint)); + return obj; +} + +void collisionFromScriptValue(const QScriptValue &object, CollisionInfo& collision) { + // TODO: implement this when we know what it means to accept collision events from JS +} + diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index ea65e45c95..d876965723 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -1,12 +1,13 @@ // // RegisteredMetaTypes.h -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 10/3/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 // -// Used to register meta-types with Qt so that they can be used as properties for objects exposed to our -// Agent scripting. #ifndef hifi_RegisteredMetaTypes_h #define hifi_RegisteredMetaTypes_h @@ -15,6 +16,7 @@ #include +#include "CollisionInfo.h" #include "SharedUtil.h" Q_DECLARE_METATYPE(glm::vec4) @@ -50,4 +52,8 @@ Q_DECLARE_METATYPE(PickRay) QScriptValue pickRayToScriptValue(QScriptEngine* engine, const PickRay& pickRay); void pickRayFromScriptValue(const QScriptValue& object, PickRay& pickRay); -#endif +Q_DECLARE_METATYPE(CollisionInfo) +QScriptValue collisionToScriptValue(QScriptEngine* engine, const CollisionInfo& collision); +void collisionFromScriptValue(const QScriptValue &object, CollisionInfo& collision); + +#endif // hifi_RegisteredMetaTypes_h diff --git a/libraries/shared/src/ResourceCache.cpp b/libraries/shared/src/ResourceCache.cpp index c7858e6e20..04b6265513 100644 --- a/libraries/shared/src/ResourceCache.cpp +++ b/libraries/shared/src/ResourceCache.cpp @@ -1,14 +1,18 @@ // // ResourceCache.cpp -// shared +// libraries/shared/src // // Created by Andrzej Kapolka on 2/27/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 #include +#include #include #include @@ -171,6 +175,10 @@ float Resource::getLoadPriority() { } void Resource::allReferencesCleared() { + if (QThread::currentThread() != thread()) { + QMetaObject::invokeMethod(this, "allReferencesCleared"); + return; + } if (_cache) { // create and reinsert new shared pointer QSharedPointer self(this, &Resource::allReferencesCleared); diff --git a/libraries/shared/src/ResourceCache.h b/libraries/shared/src/ResourceCache.h index 2fcda0bb98..0cfabd26fc 100644 --- a/libraries/shared/src/ResourceCache.h +++ b/libraries/shared/src/ResourceCache.h @@ -1,13 +1,16 @@ // // ResourceCache.h -// shared +// libraries/shared/src // // Created by Andrzej Kapolka on 2/27/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __shared__ResourceCache__ -#define __shared__ResourceCache__ +#ifndef hifi_ResourceCache_h +#define hifi_ResourceCache_h #include #include @@ -120,7 +123,7 @@ public: void setCache(ResourceCache* cache) { _cache = cache; } - void allReferencesCleared(); + Q_INVOKABLE void allReferencesCleared(); protected slots: @@ -174,4 +177,4 @@ private: uint qHash(const QPointer& value, uint seed = 0); -#endif /* defined(__shared__ResourceCache__) */ +#endif // hifi_ResourceCache_h diff --git a/libraries/shared/src/Shape.h b/libraries/shared/src/Shape.h index 924d13000e..87b84ea73b 100644 --- a/libraries/shared/src/Shape.h +++ b/libraries/shared/src/Shape.h @@ -1,12 +1,16 @@ // // Shape.h +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.20 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 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 // -#ifndef __hifi__Shape__ -#define __hifi__Shape__ +#ifndef hifi_Shape_h +#define hifi_Shape_h #include #include @@ -18,6 +22,7 @@ public: UNKNOWN_SHAPE = 0, SPHERE_SHAPE, CAPSULE_SHAPE, + PLANE_SHAPE, BOX_SHAPE, LIST_SHAPE }; @@ -51,4 +56,4 @@ protected: glm::quat _rotation; }; -#endif /* defined(__hifi__Shape__) */ +#endif // hifi_Shape_h diff --git a/libraries/shared/src/ShapeCollider.cpp b/libraries/shared/src/ShapeCollider.cpp index 70f9b9c4bf..31d57f14ad 100644 --- a/libraries/shared/src/ShapeCollider.cpp +++ b/libraries/shared/src/ShapeCollider.cpp @@ -1,15 +1,19 @@ // // ShapeCollider.cpp -// hifi +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.20 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/20/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 // #include #include +#include "GeometryUtil.h" #include "ShapeCollider.h" // NOTE: @@ -19,7 +23,7 @@ namespace ShapeCollider { -bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { +bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions) { // ATM we only have two shape types so we just check every case. // TODO: make a fast lookup for correct method int typeA = shapeA->getType(); @@ -30,6 +34,8 @@ bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisi return sphereSphere(sphereA, static_cast(shapeB), collisions); } else if (typeB == Shape::CAPSULE_SHAPE) { return sphereCapsule(sphereA, static_cast(shapeB), collisions); + } else if (typeB == Shape::PLANE_SHAPE) { + return spherePlane(sphereA, static_cast(shapeB), collisions); } } else if (typeA == Shape::CAPSULE_SHAPE) { const CapsuleShape* capsuleA = static_cast(shapeA); @@ -37,6 +43,17 @@ bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisi return capsuleSphere(capsuleA, static_cast(shapeB), collisions); } else if (typeB == Shape::CAPSULE_SHAPE) { return capsuleCapsule(capsuleA, static_cast(shapeB), collisions); + } else if (typeB == Shape::PLANE_SHAPE) { + return capsulePlane(capsuleA, static_cast(shapeB), collisions); + } + } else if (typeA == Shape::PLANE_SHAPE) { + const PlaneShape* planeA = static_cast(shapeA); + if (typeB == Shape::SPHERE_SHAPE) { + return planeSphere(planeA, static_cast(shapeB), collisions); + } else if (typeB == Shape::CAPSULE_SHAPE) { + return planeCapsule(planeA, static_cast(shapeB), collisions); + } else if (typeB == Shape::PLANE_SHAPE) { + return planePlane(planeA, static_cast(shapeB), collisions); } } else if (typeA == Shape::LIST_SHAPE) { const ListShape* listA = static_cast(shapeA); @@ -44,11 +61,60 @@ bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisi return listSphere(listA, static_cast(shapeB), collisions); } else if (typeB == Shape::CAPSULE_SHAPE) { return listCapsule(listA, static_cast(shapeB), collisions); + } else if (typeB == Shape::PLANE_SHAPE) { + return listPlane(listA, static_cast(shapeB), collisions); } } return false; } +static CollisionList tempCollisions(32); + +bool collideShapesCoarse(const QVector& shapesA, const QVector& shapesB, CollisionInfo& collision) { + tempCollisions.clear(); + foreach (const Shape* shapeA, shapesA) { + foreach (const Shape* shapeB, shapesB) { + ShapeCollider::collideShapes(shapeA, shapeB, tempCollisions); + } + } + if (tempCollisions.size() > 0) { + glm::vec3 totalPenetration(0.0f); + glm::vec3 averageContactPoint(0.0f); + for (int j = 0; j < tempCollisions.size(); ++j) { + CollisionInfo* c = tempCollisions.getCollision(j); + totalPenetration = addPenetrations(totalPenetration, c->_penetration); + averageContactPoint += c->_contactPoint; + } + collision._penetration = totalPenetration; + collision._contactPoint = averageContactPoint / (float)(tempCollisions.size()); + return true; + } + return false; +} + +bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { + int typeA = shapeA->getType(); + if (typeA == Shape::SPHERE_SHAPE) { + return sphereAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); + } else if (typeA == Shape::CAPSULE_SHAPE) { + return capsuleAACube(static_cast(shapeA), cubeCenter, cubeSide, collisions); + } else if (typeA == Shape::LIST_SHAPE) { + const ListShape* listA = static_cast(shapeA); + bool touching = false; + for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { + const Shape* subShape = listA->getSubShape(i); + int subType = subShape->getType(); + if (subType == Shape::SPHERE_SHAPE) { + touching = sphereAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; + } else if (subType == Shape::CAPSULE_SHAPE) { + touching = capsuleAACube(static_cast(subShape), cubeCenter, cubeSide, collisions) || touching; + } + } + return touching; + } + return false; +} + bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, CollisionList& collisions) { glm::vec3 BA = sphereB->getPosition() - sphereA->getPosition(); float distanceSquared = glm::dot(BA, BA); @@ -58,7 +124,7 @@ bool sphereSphere(const SphereShape* sphereA, const SphereShape* sphereB, Collis float distance = sqrtf(distanceSquared); if (distance < EPSILON) { // the spheres are on top of each other, so we pick an arbitrary penetration direction - BA = glm::vec3(0.f, 1.f, 0.f); + BA = glm::vec3(0.0f, 1.0f, 0.0f); distance = totalRadius; } else { BA /= distance; @@ -93,7 +159,7 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col } if (absAxialDistance > capsuleB->getHalfHeight()) { // sphere hits capsule on a cap --> recompute radialAxis to point from spherA to cap center - float sign = (axialDistance > 0.f) ? 1.f : -1.f; + float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f; radialAxis = BA + (sign * capsuleB->getHalfHeight()) * capsuleAxis; radialDistance2 = glm::length2(radialAxis); if (radialDistance2 > totalRadius2) { @@ -125,12 +191,12 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col return false; } // ... but still defined for the cap case - if (axialDistance < 0.f) { + if (axialDistance < 0.0f) { // we're hitting the start cap, so we negate the capsuleAxis capsuleAxis *= -1; } // penetration points from A into B - float sign = (axialDistance > 0.f) ? -1.f : 1.f; + float sign = (axialDistance > 0.0f) ? -1.0f : 1.0f; collision->_penetration = (sign * (totalRadius + capsuleB->getHalfHeight() - absAxialDistance)) * capsuleAxis; // contactPoint is on surface of sphereA collision->_contactPoint = sphereA->getPosition() + (sign * sphereA->getRadius()) * capsuleAxis; @@ -140,6 +206,20 @@ bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, Col return false; } +bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions) { + glm::vec3 penetration; + if (findSpherePlanePenetration(sphereA->getPosition(), sphereA->getRadius(), planeB->getCoefficients(), penetration)) { + CollisionInfo* collision = collisions.getNewCollision(); + if (!collision) { + return false; // collision list is full + } + collision->_penetration = penetration; + collision->_contactPoint = sphereA->getPosition() + sphereA->getRadius() * glm::normalize(penetration); + return true; + } + return false; +} + bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, CollisionList& collisions) { // find sphereB's closest approach to axis of capsuleA glm::vec3 AB = capsuleA->getPosition() - sphereB->getPosition(); @@ -163,7 +243,7 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col if (absAxialDistance > capsuleA->getHalfHeight()) { // sphere hits capsule on a cap // --> recompute radialAxis and closestApproach - float sign = (axialDistance > 0.f) ? 1.f : -1.f; + float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f; closestApproach = capsuleA->getPosition() + (sign * capsuleA->getHalfHeight()) * capsuleAxis; radialAxis = closestApproach - sphereB->getPosition(); radialDistance2 = glm::length2(radialAxis); @@ -196,11 +276,11 @@ bool capsuleSphere(const CapsuleShape* capsuleA, const SphereShape* sphereB, Col return false; } // ... but still defined for the cap case - if (axialDistance < 0.f) { + if (axialDistance < 0.0f) { // we're hitting the start cap, so we negate the capsuleAxis capsuleAxis *= -1; } - float sign = (axialDistance > 0.f) ? 1.f : -1.f; + float sign = (axialDistance > 0.0f) ? 1.0f : -1.0f; collision->_penetration = (sign * (totalRadius + capsuleA->getHalfHeight() - absAxialDistance)) * capsuleAxis; // contactPoint is on surface of sphereA collision->_contactPoint = closestApproach + (sign * capsuleA->getRadius()) * capsuleAxis; @@ -223,7 +303,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, // d = [(B - A) . (a - (a.b)b)] / (1 - (a.b)^2) float aDotB = glm::dot(axisA, axisB); - float denominator = 1.f - aDotB * aDotB; + float denominator = 1.0f - aDotB * aDotB; float totalRadius = capsuleA->getRadius() + capsuleB->getRadius(); if (denominator > EPSILON) { // distances to points of closest approach @@ -233,12 +313,12 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, // clamp the distances to the ends of the capsule line segments float absDistanceA = fabs(distanceA); if (absDistanceA > capsuleA->getHalfHeight() + capsuleA->getRadius()) { - float signA = distanceA < 0.f ? -1.f : 1.f; + float signA = distanceA < 0.0f ? -1.0f : 1.0f; distanceA = signA * capsuleA->getHalfHeight(); } float absDistanceB = fabs(distanceB); if (absDistanceB > capsuleB->getHalfHeight() + capsuleB->getRadius()) { - float signB = distanceB < 0.f ? -1.f : 1.f; + float signB = distanceB < 0.0f ? -1.0f : 1.0f; distanceB = signB * capsuleB->getHalfHeight(); } @@ -265,7 +345,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, { // the capsule centers are on top of each other! // give up on a valid penetration direction and just use the yAxis - BA = glm::vec3(0.f, 1.f, 0.f); + BA = glm::vec3(0.0f, 1.0f, 0.0f); distance = glm::max(capsuleB->getRadius(), capsuleA->getRadius()); } } else { @@ -297,7 +377,7 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, float distance = sqrtf(distanceSquared); if (distance < EPSILON) { // the spheres are on top of each other, so we pick an arbitrary penetration direction - BA = glm::vec3(0.f, 1.f, 0.f); + BA = glm::vec3(0.0f, 1.0f, 0.0f); } else { BA /= distance; } @@ -346,6 +426,63 @@ bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, return false; } +bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions) { + glm::vec3 start, end, penetration; + capsuleA->getStartPoint(start); + capsuleA->getEndPoint(end); + glm::vec4 plane = planeB->getCoefficients(); + if (findCapsulePlanePenetration(start, end, capsuleA->getRadius(), plane, penetration)) { + CollisionInfo* collision = collisions.getNewCollision(); + if (!collision) { + return false; // collision list is full + } + collision->_penetration = penetration; + glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end; + collision->_contactPoint = deepestEnd + capsuleA->getRadius() * glm::normalize(penetration); + return true; + } + return false; +} + +bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions) { + glm::vec3 penetration; + if (findSpherePlanePenetration(sphereB->getPosition(), sphereB->getRadius(), planeA->getCoefficients(), penetration)) { + CollisionInfo* collision = collisions.getNewCollision(); + if (!collision) { + return false; // collision list is full + } + collision->_penetration = -penetration; + collision->_contactPoint = sphereB->getPosition() + + (sphereB->getRadius() / glm::length(penetration) - 1.0f) * penetration; + return true; + } + return false; +} + +bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions) { + glm::vec3 start, end, penetration; + capsuleB->getStartPoint(start); + capsuleB->getEndPoint(end); + glm::vec4 plane = planeA->getCoefficients(); + if (findCapsulePlanePenetration(start, end, capsuleB->getRadius(), plane, penetration)) { + CollisionInfo* collision = collisions.getNewCollision(); + if (!collision) { + return false; // collision list is full + } + collision->_penetration = -penetration; + glm::vec3 deepestEnd = (glm::dot(start, glm::vec3(plane)) < glm::dot(end, glm::vec3(plane))) ? start : end; + collision->_contactPoint = deepestEnd + (capsuleB->getRadius() / glm::length(penetration) - 1.0f) * penetration; + return true; + } + return false; +} + +bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions) { + // technically, planes always collide unless they're parallel and not coincident; however, that's + // not going to give us any useful information + return false; +} + bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionList& collisions) { bool touching = false; for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { @@ -355,6 +492,8 @@ bool sphereList(const SphereShape* sphereA, const ListShape* listB, CollisionLis touching = sphereSphere(sphereA, static_cast(subShape), collisions) || touching; } else if (subType == Shape::CAPSULE_SHAPE) { touching = sphereCapsule(sphereA, static_cast(subShape), collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = spherePlane(sphereA, static_cast(subShape), collisions) || touching; } } return touching; @@ -369,6 +508,24 @@ bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, Collision touching = capsuleSphere(capsuleA, static_cast(subShape), collisions) || touching; } else if (subType == Shape::CAPSULE_SHAPE) { touching = capsuleCapsule(capsuleA, static_cast(subShape), collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = capsulePlane(capsuleA, static_cast(subShape), collisions) || touching; + } + } + return touching; +} + +bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions) { + bool touching = false; + for (int i = 0; i < listB->size() && !collisions.isFull(); ++i) { + const Shape* subShape = listB->getSubShape(i); + int subType = subShape->getType(); + if (subType == Shape::SPHERE_SHAPE) { + touching = planeSphere(planeA, static_cast(subShape), collisions) || touching; + } else if (subType == Shape::CAPSULE_SHAPE) { + touching = planeCapsule(planeA, static_cast(subShape), collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = planePlane(planeA, static_cast(subShape), collisions) || touching; } } return touching; @@ -383,6 +540,8 @@ bool listSphere(const ListShape* listA, const SphereShape* sphereB, CollisionLis touching = sphereSphere(static_cast(subShape), sphereB, collisions) || touching; } else if (subType == Shape::CAPSULE_SHAPE) { touching = capsuleSphere(static_cast(subShape), sphereB, collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = planeSphere(static_cast(subShape), sphereB, collisions) || touching; } } return touching; @@ -397,6 +556,24 @@ bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, Collision touching = sphereCapsule(static_cast(subShape), capsuleB, collisions) || touching; } else if (subType == Shape::CAPSULE_SHAPE) { touching = capsuleCapsule(static_cast(subShape), capsuleB, collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = planeCapsule(static_cast(subShape), capsuleB, collisions) || touching; + } + } + return touching; +} + +bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions) { + bool touching = false; + for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { + const Shape* subShape = listA->getSubShape(i); + int subType = subShape->getType(); + if (subType == Shape::SPHERE_SHAPE) { + touching = spherePlane(static_cast(subShape), planeB, collisions) || touching; + } else if (subType == Shape::CAPSULE_SHAPE) { + touching = capsulePlane(static_cast(subShape), planeB, collisions) || touching; + } else if (subType == Shape::PLANE_SHAPE) { + touching = planePlane(static_cast(subShape), planeB, collisions) || touching; } } return touching; @@ -407,10 +584,109 @@ bool listList(const ListShape* listA, const ListShape* listB, CollisionList& col for (int i = 0; i < listA->size() && !collisions.isFull(); ++i) { const Shape* subShape = listA->getSubShape(i); for (int j = 0; j < listB->size() && !collisions.isFull(); ++j) { - touching = shapeShape(subShape, listB->getSubShape(j), collisions) || touching; + touching = collideShapes(subShape, listB->getSubShape(j), collisions) || touching; } } return touching; } +// helper function +bool sphereAACube(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { + glm::vec3 BA = cubeCenter - sphereCenter; + float distance = glm::length(BA); + if (distance > EPSILON) { + BA /= distance; // BA is now normalized + // compute the nearest point on sphere + glm::vec3 surfaceA = sphereCenter + sphereRadius * BA; + // compute the nearest point on cube + float maxBA = glm::max(glm::max(fabs(BA.x), fabs(BA.y)), fabs(BA.z)); + glm::vec3 surfaceB = cubeCenter - (0.5f * cubeSide / maxBA) * BA; + // collision happens when "vector to surfaceA from surfaceB" dots with BA to produce a positive value + glm::vec3 surfaceAB = surfaceA - surfaceB; + if (glm::dot(surfaceAB, BA) > 0.f) { + CollisionInfo* collision = collisions.getNewCollision(); + if (collision) { + /* KEEP THIS CODE -- this is how to collide the cube with stark face normals (no rounding). + * We might want to use this code later for sealing boundaries between adjacent voxels. + // penetration is parallel to box side direction + BA /= maxBA; + glm::vec3 direction; + glm::modf(BA, direction); + direction = glm::normalize(direction); + */ + + // For rounded normals at edges and corners: + // At this point imagine that sphereCenter touches a "normalized" cube with rounded edges. + // This cube has a sidelength of 2 and its smoothing radius is sphereRadius/maxBA. + // We're going to try to compute the "negative normal" (and hence direction of penetration) + // of this surface. + + float radius = sphereRadius / (distance * maxBA); // normalized radius + float shortLength = maxBA - radius; + glm::vec3 direction = BA; + if (shortLength > 0.0f) { + direction = glm::abs(BA) - glm::vec3(shortLength); + // Set any negative components to zero, and adopt the sign of the original BA component. + // Unfortunately there isn't an easy way to make this fast. + if (direction.x < 0.0f) { + direction.x = 0.f; + } else if (BA.x < 0.f) { + direction.x = -direction.x; + } + if (direction.y < 0.0f) { + direction.y = 0.f; + } else if (BA.y < 0.f) { + direction.y = -direction.y; + } + if (direction.z < 0.0f) { + direction.z = 0.f; + } else if (BA.z < 0.f) { + direction.z = -direction.z; + } + } + direction = glm::normalize(direction); + + // penetration is the projection of surfaceAB on direction + collision->_penetration = glm::dot(surfaceAB, direction) * direction; + // contactPoint is on surface of A + collision->_contactPoint = sphereCenter - sphereRadius * direction; + return true; + } + } + } else if (sphereRadius + 0.5f * cubeSide > distance) { + // NOTE: for cocentric approximation we collide sphere and cube as two spheres which means + // this algorithm will probably be wrong when both sphere and cube are very small (both ~EPSILON) + CollisionInfo* collision = collisions.getNewCollision(); + if (collision) { + // the penetration and contactPoint are undefined, so we pick a penetration direction (-yAxis) + collision->_penetration = (sphereRadius + 0.5f * cubeSide) * glm::vec3(0.0f, -1.0f, 0.0f); + // contactPoint is on surface of A + collision->_contactPoint = sphereCenter + collision->_penetration; + return true; + } + } + return false; +} + +bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { + return sphereAACube(sphereA->getPosition(), sphereA->getRadius(), cubeCenter, cubeSide, collisions); +} + +bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions) { + // find nerest approach of capsule line segment to cube + glm::vec3 capsuleAxis; + capsuleA->computeNormalizedAxis(capsuleAxis); + float offset = glm::dot(cubeCenter - capsuleA->getPosition(), capsuleAxis); + float halfHeight = capsuleA->getHalfHeight(); + if (offset > halfHeight) { + offset = halfHeight; + } else if (offset < -halfHeight) { + offset = -halfHeight; + } + glm::vec3 nearestApproach = capsuleA->getPosition() + offset * capsuleAxis; + // collide nearest approach like a sphere at that point + return sphereAACube(nearestApproach, capsuleA->getRadius(), cubeCenter, cubeSide, collisions); +} + + } // namespace ShapeCollider diff --git a/libraries/shared/src/ShapeCollider.h b/libraries/shared/src/ShapeCollider.h index e3e044c8fe..9e83e31571 100644 --- a/libraries/shared/src/ShapeCollider.h +++ b/libraries/shared/src/ShapeCollider.h @@ -1,17 +1,21 @@ // // ShapeCollider.h -// hifi +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.20 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/20/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 // -#ifndef __hifi__ShapeCollider__ -#define __hifi__ShapeCollider__ +#ifndef hifi_ShapeCollider_h +#define hifi_ShapeCollider_h #include "CapsuleShape.h" #include "CollisionInfo.h" #include "ListShape.h" +#include "PlaneShape.h" #include "SharedUtil.h" #include "SphereShape.h" @@ -19,9 +23,22 @@ namespace ShapeCollider { /// \param shapeA pointer to first shape /// \param shapeB pointer to second shape - /// \param[out] collisions where to append collision details + /// \param collisions[out] collision details /// \return true if shapes collide - bool shapeShape(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions); + bool collideShapes(const Shape* shapeA, const Shape* shapeB, CollisionList& collisions); + + /// \param shapesA list of shapes + /// \param shapeB list of shapes + /// \param collisions[out] average collision details + /// \return true if any shapes collide + bool collideShapesCoarse(const QVector& shapesA, const QVector& shapesB, CollisionInfo& collision); + + /// \param shapeA a pointer to a shape + /// \param cubeCenter center of cube + /// \param cubeSide lenght of side of cube + /// \param collisions[out] average collision details + /// \return true if shapeA collides with axis aligned cube + bool collideShapeWithAACube(const Shape* shapeA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); /// \param sphereA pointer to first shape /// \param sphereB pointer to second shape @@ -35,6 +52,12 @@ namespace ShapeCollider { /// \return true if shapes collide bool sphereCapsule(const SphereShape* sphereA, const CapsuleShape* capsuleB, CollisionList& collisions); + /// \param sphereA pointer to first shape + /// \param planeB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool spherePlane(const SphereShape* sphereA, const PlaneShape* planeB, CollisionList& collisions); + /// \param capsuleA pointer to first shape /// \param sphereB pointer to second shape /// \param[out] collisions where to append collision details @@ -47,6 +70,30 @@ namespace ShapeCollider { /// \return true if shapes collide bool capsuleCapsule(const CapsuleShape* capsuleA, const CapsuleShape* capsuleB, CollisionList& collisions); + /// \param capsuleA pointer to first shape + /// \param planeB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool capsulePlane(const CapsuleShape* capsuleA, const PlaneShape* planeB, CollisionList& collisions); + + /// \param planeA pointer to first shape + /// \param sphereB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool planeSphere(const PlaneShape* planeA, const SphereShape* sphereB, CollisionList& collisions); + + /// \param planeA pointer to first shape + /// \param capsuleB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool planeCapsule(const PlaneShape* planeA, const CapsuleShape* capsuleB, CollisionList& collisions); + + /// \param planeA pointer to first shape + /// \param planeB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool planePlane(const PlaneShape* planeA, const PlaneShape* planeB, CollisionList& collisions); + /// \param sphereA pointer to first shape /// \param listB pointer to second shape /// \param[out] collisions where to append collision details @@ -59,6 +106,12 @@ namespace ShapeCollider { /// \return true if shapes collide bool capsuleList(const CapsuleShape* capsuleA, const ListShape* listB, CollisionList& collisions); + /// \param planeA pointer to first shape + /// \param listB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool planeList(const PlaneShape* planeA, const ListShape* listB, CollisionList& collisions); + /// \param listA pointer to first shape /// \param sphereB pointer to second shape /// \param[out] collisions where to append collision details @@ -71,12 +124,32 @@ namespace ShapeCollider { /// \return true if shapes collide bool listCapsule(const ListShape* listA, const CapsuleShape* capsuleB, CollisionList& collisions); + /// \param listA pointer to first shape + /// \param planeB pointer to second shape + /// \param[out] collisions where to append collision details + /// \return true if shapes collide + bool listPlane(const ListShape* listA, const PlaneShape* planeB, CollisionList& collisions); + /// \param listA pointer to first shape /// \param capsuleB pointer to second shape /// \param[out] collisions where to append collision details /// \return true if shapes collide bool listList(const ListShape* listA, const ListShape* listB, CollisionList& collisions); + /// \param sphereA pointer to sphere + /// \param cubeCenter center of cube + /// \param cubeSide lenght of side of cube + /// \param[out] collisions where to append collision details + /// \return true if sphereA collides with axis aligned cube + bool sphereAACube(const SphereShape* sphereA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); + + /// \param capsuleA pointer to capsule + /// \param cubeCenter center of cube + /// \param cubeSide lenght of side of cube + /// \param[out] collisions where to append collision details + /// \return true if capsuleA collides with axis aligned cube + bool capsuleAACube(const CapsuleShape* capsuleA, const glm::vec3& cubeCenter, float cubeSide, CollisionList& collisions); + } // namespace ShapeCollider -#endif // __hifi__ShapeCollider__ +#endif // hifi_ShapeCollider_h diff --git a/libraries/shared/src/SharedUtil.cpp b/libraries/shared/src/SharedUtil.cpp index efd5180d03..b865d9c717 100644 --- a/libraries/shared/src/SharedUtil.cpp +++ b/libraries/shared/src/SharedUtil.cpp @@ -1,9 +1,12 @@ // // SharedUtil.cpp -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 2/22/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -13,7 +16,7 @@ #include #ifdef _WIN32 -#include "Syssocket.h" +#include #endif #ifdef __APPLE__ @@ -21,27 +24,38 @@ #endif #include +#include +#include #include "OctalCode.h" -#include "PacketHeaders.h" #include "SharedUtil.h" -quint64 usecTimestamp(const timeval *time) { - return (time->tv_sec * 1000000 + time->tv_usec); + +static qint64 TIME_REFERENCE = 0; // in usec +static QElapsedTimer timestampTimer; +static int usecTimestampNowAdjust = 0; // in usec + +void initialiseUsecTimestampNow() { + static bool initialised = false; + if (initialised) { + qDebug() << "[WARNING] Double initialisation of usecTimestampNow()."; + return; + } + + TIME_REFERENCE = QDateTime::currentMSecsSinceEpoch() * 1000; // ms to usec + initialised = true; } -int usecTimestampNowAdjust = 0; void usecTimestampNowForceClockSkew(int clockSkew) { ::usecTimestampNowAdjust = clockSkew; } quint64 usecTimestampNow() { - timeval now; - gettimeofday(&now, NULL); - return (now.tv_sec * 1000000 + now.tv_usec) + ::usecTimestampNowAdjust; + // usec nsec to usec usec + return TIME_REFERENCE + timestampTimer.nsecsElapsed() / 1000 + ::usecTimestampNowAdjust; } -float randFloat () { +float randFloat() { return (rand() % 10000)/10000.f; } @@ -53,6 +67,10 @@ float randFloatInRange (float min,float max) { return min + ((rand() % 10000)/10000.f * (max-min)); } +float randomSign() { + return randomBoolean() ? -1.0 : 1.0; +} + unsigned char randomColorValue(int miniumum) { return miniumum + (rand() % (256 - miniumum)); } @@ -661,3 +679,125 @@ glm::vec3 safeEulerAngles(const glm::quat& q) { } } +// Helper function returns the positive angle (in radians) between two 3D vectors +float angleBetween(const glm::vec3& v1, const glm::vec3& v2) { + return acosf((glm::dot(v1, v2)) / (glm::length(v1) * glm::length(v2))); +} + +// Helper function return the rotation from the first vector onto the second +glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2) { + float angle = angleBetween(v1, v2); + if (glm::isnan(angle) || angle < EPSILON) { + return glm::quat(); + } + glm::vec3 axis; + if (angle > 179.99f * RADIANS_PER_DEGREE) { // 180 degree rotation; must use another axis + axis = glm::cross(v1, glm::vec3(1.0f, 0.0f, 0.0f)); + float axisLength = glm::length(axis); + if (axisLength < EPSILON) { // parallel to x; y will work + axis = glm::normalize(glm::cross(v1, glm::vec3(0.0f, 1.0f, 0.0f))); + } else { + axis /= axisLength; + } + } else { + axis = glm::normalize(glm::cross(v1, v2)); + } + return glm::angleAxis(angle, axis); +} + +glm::vec3 extractTranslation(const glm::mat4& matrix) { + return glm::vec3(matrix[3][0], matrix[3][1], matrix[3][2]); +} + +void setTranslation(glm::mat4& matrix, const glm::vec3& translation) { + matrix[3][0] = translation.x; + matrix[3][1] = translation.y; + matrix[3][2] = translation.z; +} + +glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal) { + // uses the iterative polar decomposition algorithm described by Ken Shoemake at + // http://www.cs.wisc.edu/graphics/Courses/838-s2002/Papers/polar-decomp.pdf + // code adapted from Clyde, https://github.com/threerings/clyde/blob/master/src/main/java/com/threerings/math/Matrix4f.java + + // start with the contents of the upper 3x3 portion of the matrix + glm::mat3 upper = glm::mat3(matrix); + if (!assumeOrthogonal) { + for (int i = 0; i < 10; i++) { + // store the results of the previous iteration + glm::mat3 previous = upper; + + // compute average of the matrix with its inverse transpose + float sd00 = previous[1][1] * previous[2][2] - previous[2][1] * previous[1][2]; + float sd10 = previous[0][1] * previous[2][2] - previous[2][1] * previous[0][2]; + float sd20 = previous[0][1] * previous[1][2] - previous[1][1] * previous[0][2]; + float det = previous[0][0] * sd00 + previous[2][0] * sd20 - previous[1][0] * sd10; + if (fabs(det) == 0.0f) { + // determinant is zero; matrix is not invertible + break; + } + float hrdet = 0.5f / det; + upper[0][0] = +sd00 * hrdet + previous[0][0] * 0.5f; + upper[1][0] = -sd10 * hrdet + previous[1][0] * 0.5f; + upper[2][0] = +sd20 * hrdet + previous[2][0] * 0.5f; + + upper[0][1] = -(previous[1][0] * previous[2][2] - previous[2][0] * previous[1][2]) * hrdet + previous[0][1] * 0.5f; + upper[1][1] = +(previous[0][0] * previous[2][2] - previous[2][0] * previous[0][2]) * hrdet + previous[1][1] * 0.5f; + upper[2][1] = -(previous[0][0] * previous[1][2] - previous[1][0] * previous[0][2]) * hrdet + previous[2][1] * 0.5f; + + upper[0][2] = +(previous[1][0] * previous[2][1] - previous[2][0] * previous[1][1]) * hrdet + previous[0][2] * 0.5f; + upper[1][2] = -(previous[0][0] * previous[2][1] - previous[2][0] * previous[0][1]) * hrdet + previous[1][2] * 0.5f; + upper[2][2] = +(previous[0][0] * previous[1][1] - previous[1][0] * previous[0][1]) * hrdet + previous[2][2] * 0.5f; + + // compute the difference; if it's small enough, we're done + glm::mat3 diff = upper - previous; + if (diff[0][0] * diff[0][0] + diff[1][0] * diff[1][0] + diff[2][0] * diff[2][0] + diff[0][1] * diff[0][1] + + diff[1][1] * diff[1][1] + diff[2][1] * diff[2][1] + diff[0][2] * diff[0][2] + diff[1][2] * diff[1][2] + + diff[2][2] * diff[2][2] < EPSILON) { + break; + } + } + } + + // now that we have a nice orthogonal matrix, we can extract the rotation quaternion + // using the method described in http://en.wikipedia.org/wiki/Rotation_matrix#Conversions + float x2 = fabs(1.0f + upper[0][0] - upper[1][1] - upper[2][2]); + float y2 = fabs(1.0f - upper[0][0] + upper[1][1] - upper[2][2]); + float z2 = fabs(1.0f - upper[0][0] - upper[1][1] + upper[2][2]); + float w2 = fabs(1.0f + upper[0][0] + upper[1][1] + upper[2][2]); + return glm::normalize(glm::quat(0.5f * sqrtf(w2), + 0.5f * sqrtf(x2) * (upper[1][2] >= upper[2][1] ? 1.0f : -1.0f), + 0.5f * sqrtf(y2) * (upper[2][0] >= upper[0][2] ? 1.0f : -1.0f), + 0.5f * sqrtf(z2) * (upper[0][1] >= upper[1][0] ? 1.0f : -1.0f))); +} + +glm::vec3 extractScale(const glm::mat4& matrix) { + return glm::vec3(glm::length(matrix[0]), glm::length(matrix[1]), glm::length(matrix[2])); +} + +float extractUniformScale(const glm::mat4& matrix) { + return extractUniformScale(extractScale(matrix)); +} + +float extractUniformScale(const glm::vec3& scale) { + return (scale.x + scale.y + scale.z) / 3.0f; +} + +bool isNaN(float value) { + return value != value; +} + +bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientionB, float similarEnough) { + // Compute the angular distance between the two orientations + float angleOrientation = orientionA == orientionB ? 0.0f : glm::degrees(glm::angle(orientionA * glm::inverse(orientionB))); + if (isNaN(angleOrientation)) { + angleOrientation = 0.0f; + } + return (angleOrientation <= similarEnough); +} + +bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, float similarEnough) { + // Compute the distance between the two points + float positionDistance = glm::distance(positionA, positionB); + return (positionDistance <= similarEnough); +} diff --git a/libraries/shared/src/SharedUtil.h b/libraries/shared/src/SharedUtil.h index d8d686c63b..54d599070d 100644 --- a/libraries/shared/src/SharedUtil.h +++ b/libraries/shared/src/SharedUtil.h @@ -1,13 +1,16 @@ // // SharedUtil.h -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 2/22/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__SharedUtil__ -#define __hifi__SharedUtil__ +#ifndef hifi_SharedUtil_h +#define hifi_SharedUtil_h #include #include @@ -21,14 +24,6 @@ #include -#ifdef _WIN32 -#include "Systime.h" -#else -#include -#endif - -#include "PacketHeaders.h" - const int BYTES_PER_COLOR = 3; const int BYTES_PER_FLAGS = 1; typedef unsigned char rgbColor[BYTES_PER_COLOR]; @@ -42,7 +37,6 @@ struct xColor { unsigned char blue; }; - static const float ZERO = 0.0f; static const float ONE = 1.0f; static const float ONE_HALF = 0.5f; @@ -66,15 +60,14 @@ static const quint64 USECS_PER_SECOND = USECS_PER_MSEC * MSECS_PER_SECOND; const int BITS_IN_BYTE = 8; -const int MAX_PACKET_SIZE = 1500; - -quint64 usecTimestamp(const timeval *time); +void initialiseUsecTimestampNow(); quint64 usecTimestampNow(); void usecTimestampNowForceClockSkew(int clockSkew); float randFloat(); int randIntInRange (int min, int max); float randFloatInRange (float min,float max); +float randomSign(); /// \return -1.0 or 1.0 unsigned char randomColorValue(int minimum); bool randomBoolean(); @@ -168,4 +161,30 @@ int unpackFloatVec3FromSignedTwoByteFixed(const unsigned char* sourceBuffer, glm /// \return vec3 with euler angles in radians glm::vec3 safeEulerAngles(const glm::quat& q); -#endif /* defined(__hifi__SharedUtil__) */ +float angleBetween(const glm::vec3& v1, const glm::vec3& v2); + +glm::quat rotationBetween(const glm::vec3& v1, const glm::vec3& v2); + +glm::vec3 extractTranslation(const glm::mat4& matrix); + +void setTranslation(glm::mat4& matrix, const glm::vec3& translation); + +glm::quat extractRotation(const glm::mat4& matrix, bool assumeOrthogonal = false); + +glm::vec3 extractScale(const glm::mat4& matrix); + +float extractUniformScale(const glm::mat4& matrix); + +float extractUniformScale(const glm::vec3& scale); + +/// \return bool are two orientations similar to each other +const float ORIENTATION_SIMILAR_ENOUGH = 5.0f; // 10 degrees in any direction +bool isSimilarOrientation(const glm::quat& orientionA, const glm::quat& orientionB, + float similarEnough = ORIENTATION_SIMILAR_ENOUGH); +const float POSITION_SIMILAR_ENOUGH = 0.1f; // 0.1 meter +bool isSimilarPosition(const glm::vec3& positionA, const glm::vec3& positionB, float similarEnough = POSITION_SIMILAR_ENOUGH); + +/// \return bool is the float NaN +bool isNaN(float value); + +#endif // hifi_SharedUtil_h diff --git a/libraries/shared/src/SimpleMovingAverage.cpp b/libraries/shared/src/SimpleMovingAverage.cpp index 676c85598e..9f7e541c9a 100644 --- a/libraries/shared/src/SimpleMovingAverage.cpp +++ b/libraries/shared/src/SimpleMovingAverage.cpp @@ -1,9 +1,12 @@ // // SimpleMovingAverage.cpp -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 4/18/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 "SharedUtil.h" @@ -53,4 +56,4 @@ float SimpleMovingAverage::getEventDeltaAverage() const { float SimpleMovingAverage::getAverageSampleValuePerSecond() const { return _average * (1 / getEventDeltaAverage()); -} \ No newline at end of file +} diff --git a/libraries/shared/src/SimpleMovingAverage.h b/libraries/shared/src/SimpleMovingAverage.h index f11cd16e58..3eec9d5be8 100644 --- a/libraries/shared/src/SimpleMovingAverage.h +++ b/libraries/shared/src/SimpleMovingAverage.h @@ -1,15 +1,18 @@ // // SimpleMovingAverage.h -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 4/18/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // Replaces Brad Hefta-Gaub's CounterStats class (RIP) // +// 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__Stats__ -#define __hifi__Stats__ +#ifndef hifi_SimpleMovingAverage_h +#define hifi_SimpleMovingAverage_h #include @@ -34,4 +37,4 @@ private: float ONE_MINUS_WEIGHTING; }; -#endif /* defined(__hifi__Stats__) */ +#endif // hifi_SimpleMovingAverage_h diff --git a/libraries/shared/src/SphereShape.h b/libraries/shared/src/SphereShape.h index d720dd2289..62783ab340 100644 --- a/libraries/shared/src/SphereShape.h +++ b/libraries/shared/src/SphereShape.h @@ -1,13 +1,16 @@ // // SphereShape.h -// hifi +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.20 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 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 // -#ifndef __hifi__SphereShape__ -#define __hifi__SphereShape__ +#ifndef hifi_SphereShape_h +#define hifi_SphereShape_h #include "Shape.h" @@ -28,4 +31,4 @@ public: void setRadius(float radius) { _boundingRadius = radius; } }; -#endif /* defined(__hifi__SphereShape__) */ +#endif // hifi_SphereShape_h diff --git a/libraries/shared/src/StdDev.cpp b/libraries/shared/src/StdDev.cpp index 17bf60e8be..4528d0fa4f 100644 --- a/libraries/shared/src/StdDev.cpp +++ b/libraries/shared/src/StdDev.cpp @@ -1,9 +1,12 @@ // // StdDev.cpp -// hifi +// libraries/shared/src // // Created by Philip Rosedale on 3/12/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -56,4 +59,4 @@ float StDev::getStDev() { return sqrt(stdev/(float)(sampleCount - 1.0)); else return 0; -} \ No newline at end of file +} diff --git a/libraries/shared/src/StdDev.h b/libraries/shared/src/StdDev.h index eea0cff3bb..77873a3549 100644 --- a/libraries/shared/src/StdDev.h +++ b/libraries/shared/src/StdDev.h @@ -1,13 +1,16 @@ // // StdDev.h -// hifi +// libraries/shared/src // // Created by Philip Rosedale on 3/12/13. +// Copyright 2013 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__StdDev__ -#define __hifi__StdDev__ +#ifndef hifi_StdDev_h +#define hifi_StdDev_h class StDev { public: @@ -22,4 +25,4 @@ class StDev { int sampleCount; }; -#endif /* defined(__hifi__StdDev__) */ +#endif // hifi_StdDev_h diff --git a/libraries/shared/src/StreamUtils.cpp b/libraries/shared/src/StreamUtils.cpp index 44c81f9464..d7b0c83c1f 100644 --- a/libraries/shared/src/StreamUtils.cpp +++ b/libraries/shared/src/StreamUtils.cpp @@ -1,8 +1,12 @@ // // StreamUtils.cpp +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.21 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 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 // #include diff --git a/libraries/shared/src/StreamUtils.h b/libraries/shared/src/StreamUtils.h index 1a17a94658..2546d49ffc 100644 --- a/libraries/shared/src/StreamUtils.h +++ b/libraries/shared/src/StreamUtils.h @@ -1,12 +1,16 @@ // // StreamUtils.h +// libraries/shared/src // -// Created by Andrew Meadows on 2014.02.21 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 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 // -#ifndef __tests__StreamUtils__ -#define __tests__StreamUtils__ +#ifndef hifi_StreamUtils_h +#define hifi_StreamUtils_h #include @@ -36,4 +40,4 @@ std::ostream& operator<<(std::ostream& s, const CapsuleShape& capsule); #endif // DEBUG -#endif // __tests__StreamUtils__ +#endif // hifi_StreamUtils_h diff --git a/libraries/shared/src/Syssocket.h b/libraries/shared/src/Syssocket.h deleted file mode 100644 index 36713a2b7b..0000000000 --- a/libraries/shared/src/Syssocket.h +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef __Syssocket__ -#define __Syssocket__ - -#ifdef _WIN32 -#define WINSOCK_API_LINKAGE -#include -#ifndef _timeval_ -#define _timeval_ -#endif -typedef SSIZE_T ssize_t; -typedef ULONG32 in_addr_t; -typedef USHORT in_port_t; -typedef USHORT uint16_t; -typedef ULONG32 socklen_t; - -#endif _Win32 - -#endif __Syssocket__ \ No newline at end of file diff --git a/libraries/shared/src/Systime.cpp b/libraries/shared/src/Systime.cpp deleted file mode 100644 index 7d8764e3f9..0000000000 --- a/libraries/shared/src/Systime.cpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifdef _WIN32 -#include -#define _timeval_ -#include "Systime.h" - - int gettimeofday( timeval* p_tv, timezone* p_tz ) - { - int tt = timeGetTime(); - - p_tv->tv_sec = tt / 1000; - p_tv->tv_usec = tt % 1000 * 1000; - return 0; - } -#endif diff --git a/libraries/shared/src/Systime.h b/libraries/shared/src/Systime.h deleted file mode 100644 index 1d25de0b80..0000000000 --- a/libraries/shared/src/Systime.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef __Systime__ -#define __Systime__ - - -#ifdef _WIN32 - -#ifdef _WINSOCK2API_ -#define _timeval_ -#endif - -#ifndef _timeval_ -#define _timeval_ -/* - * Structure returned by gettimeofday(2) system call, - * and used in other calls. - */ - -// this is a bit of a hack for now, but sometimes on windows -// we need timeval defined here, sometimes we get it -// from winsock.h -#ifdef WANT_TIMEVAL -struct timeval { - long tv_sec; /* seconds */ - long tv_usec; /* and microseconds */ -}; -#endif - -#endif _timeval_ - -struct timezone { - int tz_minuteswest; /* minutes west of Greenwich */ - int tz_dsttime; /* type of dst correction */ -}; - -int gettimeofday( struct timeval* p_tv, struct timezone* p_tz ); - -#endif _Win32 - -#endif __Systime__ diff --git a/libraries/shared/src/UUID.cpp b/libraries/shared/src/UUID.cpp index e24c81ebe3..eafd7dd181 100644 --- a/libraries/shared/src/UUID.cpp +++ b/libraries/shared/src/UUID.cpp @@ -1,9 +1,12 @@ // // UUID.cpp -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 10/7/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 "UUID.h" @@ -11,4 +14,4 @@ QString uuidStringWithoutCurlyBraces(const QUuid& uuid) { QString uuidStringNoBraces = uuid.toString().mid(1, uuid.toString().length() - 2); return uuidStringNoBraces; -} \ No newline at end of file +} diff --git a/libraries/shared/src/UUID.h b/libraries/shared/src/UUID.h index cacadf5a04..7e7048486f 100644 --- a/libraries/shared/src/UUID.h +++ b/libraries/shared/src/UUID.h @@ -1,13 +1,16 @@ // // UUID.h -// hifi +// libraries/shared/src // // Created by Stephen Birarda on 10/7/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__UUID__ -#define __hifi__UUID__ +#ifndef hifi_UUID_h +#define hifi_UUID_h #include @@ -15,4 +18,4 @@ const int NUM_BYTES_RFC4122_UUID = 16; QString uuidStringWithoutCurlyBraces(const QUuid& uuid); -#endif /* defined(__hifi__UUID__) */ +#endif // hifi_UUID_h diff --git a/libraries/voxels/CMakeLists.txt b/libraries/voxels/CMakeLists.txt index afc0b34b64..bdba388594 100644 --- a/libraries/voxels/CMakeLists.txt +++ b/libraries/voxels/CMakeLists.txt @@ -24,9 +24,17 @@ include_glm(${TARGET_NAME} "${ROOT_DIR}") include(${MACRO_DIR}/LinkHifiLibrary.cmake) link_hifi_library(shared ${TARGET_NAME} "${ROOT_DIR}") link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") -# link ZLIB +# link ZLIB and GnuTLS find_package(ZLIB) -include_directories("${ZLIB_INCLUDE_DIRS}") +find_package(GnuTLS REQUIRED) -target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" Qt5::Widgets Qt5::Script) +# 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 "${ZLIB_INCLUDE_DIRS}" "${GNUTLS_INCLUDE_DIR}") +target_link_libraries(${TARGET_NAME} "${ZLIB_LIBRARIES}" "${GNUTLS_LIBRARY}" Qt5::Widgets Qt5::Script) \ No newline at end of file diff --git a/libraries/voxels/src/EnvironmentData.cpp b/libraries/voxels/src/EnvironmentData.cpp index 1c9af55abd..b9b64f72d1 100644 --- a/libraries/voxels/src/EnvironmentData.cpp +++ b/libraries/voxels/src/EnvironmentData.cpp @@ -1,9 +1,13 @@ // // EnvironmentData.cpp -// interface +// libraries/voxels/src // // Created by Andrzej Kapolka on 5/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/voxels/src/EnvironmentData.h b/libraries/voxels/src/EnvironmentData.h index 627a661e1c..3eac4048c2 100644 --- a/libraries/voxels/src/EnvironmentData.h +++ b/libraries/voxels/src/EnvironmentData.h @@ -1,13 +1,16 @@ // // EnvironmentData.h -// interface +// libraries/voxels/src // // Created by Andrzej Kapolka on 5/6/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 __interface__EnvironmentData__ -#define __interface__EnvironmentData__ +#ifndef hifi_EnvironmentData_h +#define hifi_EnvironmentData_h #include @@ -72,4 +75,4 @@ private: float _sunBrightness; }; -#endif /* defined(__interface__EnvironmentData__) */ +#endif // hifi_EnvironmentData_h diff --git a/libraries/voxels/src/LocalVoxelsList.cpp b/libraries/voxels/src/LocalVoxelsList.cpp index e8f5a09d6d..4324306161 100644 --- a/libraries/voxels/src/LocalVoxelsList.cpp +++ b/libraries/voxels/src/LocalVoxelsList.cpp @@ -1,9 +1,12 @@ // // LocalVoxelsList.cpp -// hifi +// libraries/voxels/src // // Created by Clément Brisset on 2/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 "LocalVoxelsList.h" @@ -61,4 +64,4 @@ void LocalVoxelsList::remove(QString treeName) { } else { qDebug() << "[DEBUG] LocalVoxelsList : tree still in use (" << treeName << ")"; } -} \ No newline at end of file +} diff --git a/libraries/voxels/src/LocalVoxelsList.h b/libraries/voxels/src/LocalVoxelsList.h index e4b4decf8e..d22d72446f 100644 --- a/libraries/voxels/src/LocalVoxelsList.h +++ b/libraries/voxels/src/LocalVoxelsList.h @@ -1,13 +1,16 @@ // // LocalVoxelsList.h -// hifi +// libraries/voxels/src // // Created by Clément Brisset on 2/24/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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__LocalVoxelsList__ -#define __hifi__LocalVoxelsList__ +#ifndef hifi_LocalVoxelsList_h +#define hifi_LocalVoxelsList_h #include #include @@ -56,4 +59,4 @@ private: QList _persistantTrees; }; -#endif /* defined(__hifi__LocalVoxelsList__) */ +#endif // hifi_LocalVoxelsList_h diff --git a/libraries/voxels/src/Tags.cpp b/libraries/voxels/src/Tags.cpp index 4d1fb322cd..8f8228faae 100644 --- a/libraries/voxels/src/Tags.cpp +++ b/libraries/voxels/src/Tags.cpp @@ -1,9 +1,12 @@ // // Tags.cpp -// hifi +// libraries/voxels/src // // Created by Clement Brisset on 7/3/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/voxels/src/Tags.h b/libraries/voxels/src/Tags.h index caf4303c51..1ac231f27c 100644 --- a/libraries/voxels/src/Tags.h +++ b/libraries/voxels/src/Tags.h @@ -1,13 +1,16 @@ // // Tags.h -// hifi +// libraries/voxels/src // // Created by Clement Brisset on 7/3/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__Tags__ -#define __hifi__Tags__ +#ifndef hifi_Tags_h +#define hifi_Tags_h #include @@ -177,4 +180,4 @@ private: int* _data; }; -#endif /* defined(__hifi__Tags__) */ +#endif // hifi_Tags_h diff --git a/libraries/voxels/src/VoxelConstants.h b/libraries/voxels/src/VoxelConstants.h index d4cbc54f18..34a7667273 100644 --- a/libraries/voxels/src/VoxelConstants.h +++ b/libraries/voxels/src/VoxelConstants.h @@ -1,16 +1,18 @@ // // VoxelConstants.h -// hifi +// libraries/voxels/src // // Created by Brad Hefta-Gaub on 4/29/13. -// +// Copyright 2013 High Fidelity, Inc. // // Various important constants used throughout the system related to voxels // -// +// 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_VoxelConstants_h__ -#define __hifi_VoxelConstants_h__ +#ifndef hifi_VoxelConstants_h +#define hifi_VoxelConstants_h #include #include @@ -45,4 +47,4 @@ const quint64 CLIENT_TO_SERVER_VOXEL_SEND_INTERVAL_USECS = 1000 * 5; // 1 packet const int DEFAULT_MAX_VOXEL_PPS = 600; // the default maximum PPS we think a voxel server should send to a client -#endif \ No newline at end of file +#endif // hifi_VoxelConstants_h diff --git a/libraries/voxels/src/VoxelDetail.cpp b/libraries/voxels/src/VoxelDetail.cpp index 4527f7ca2c..f1855f5f81 100644 --- a/libraries/voxels/src/VoxelDetail.cpp +++ b/libraries/voxels/src/VoxelDetail.cpp @@ -1,9 +1,12 @@ // // VoxelDetail.cpp -// hifi +// libraries/voxels/src // -// Created by Brad Hefta-Gaub on 1/29/2014 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 1/29/2014. +// Copyright 2013 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 @@ -72,6 +75,9 @@ QScriptValue rayToVoxelIntersectionResultToScriptValue(QScriptEngine* engine, co case MAX_Z_FACE: faceName = "MAX_Z_FACE"; break; + case UNKNOWN_FACE: + faceName = "UNKNOWN_FACE"; + break; } obj.setProperty("face", faceName); diff --git a/libraries/voxels/src/VoxelDetail.h b/libraries/voxels/src/VoxelDetail.h index 1604a022a0..d30255bbb1 100644 --- a/libraries/voxels/src/VoxelDetail.h +++ b/libraries/voxels/src/VoxelDetail.h @@ -1,14 +1,16 @@ // // VoxelDetail.h -// hifi +// libraries/voxels/src // -// Created by Brad Hefta-Gaub on 1/29/2014 -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 1/29/2014. +// Copyright 2013 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__VoxelDetail__ -#define __hifi__VoxelDetail__ +#ifndef hifi_VoxelDetail_h +#define hifi_VoxelDetail_h #include @@ -48,4 +50,4 @@ Q_DECLARE_METATYPE(RayToVoxelIntersectionResult) QScriptValue rayToVoxelIntersectionResultToScriptValue(QScriptEngine* engine, const RayToVoxelIntersectionResult& results); void rayToVoxelIntersectionResultFromScriptValue(const QScriptValue& object, RayToVoxelIntersectionResult& results); -#endif /* defined(__hifi__VoxelDetail__) */ \ No newline at end of file +#endif // hifi_VoxelDetail_h diff --git a/libraries/voxels/src/VoxelEditPacketSender.cpp b/libraries/voxels/src/VoxelEditPacketSender.cpp index 90884f19f4..1832d5436e 100644 --- a/libraries/voxels/src/VoxelEditPacketSender.cpp +++ b/libraries/voxels/src/VoxelEditPacketSender.cpp @@ -1,11 +1,12 @@ // // VoxelEditPacketSender.cpp -// interface +// libraries/voxels/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // -// Threaded or non-threaded voxel packet Sender for the Application +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include diff --git a/libraries/voxels/src/VoxelEditPacketSender.h b/libraries/voxels/src/VoxelEditPacketSender.h index 4a1aa87a1c..ff72e5704a 100644 --- a/libraries/voxels/src/VoxelEditPacketSender.h +++ b/libraries/voxels/src/VoxelEditPacketSender.h @@ -1,15 +1,18 @@ // // VoxelEditPacketSender.h -// shared +// libraries/voxels/src // // Created by Brad Hefta-Gaub on 8/12/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 High Fidelity, Inc. // // Voxel Packet Sender // +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// -#ifndef __shared__VoxelEditPacketSender__ -#define __shared__VoxelEditPacketSender__ +#ifndef hifi_VoxelEditPacketSender_h +#define hifi_VoxelEditPacketSender_h #include #include "VoxelDetail.h" @@ -47,4 +50,4 @@ public: // My server type is the voxel server virtual unsigned char getMyNodeType() const { return NodeType::VoxelServer; } }; -#endif // __shared__VoxelEditPacketSender__ +#endif // hifi_VoxelEditPacketSender_h diff --git a/libraries/voxels/src/VoxelTree.cpp b/libraries/voxels/src/VoxelTree.cpp index 5c48244a39..bb9de72e9d 100644 --- a/libraries/voxels/src/VoxelTree.cpp +++ b/libraries/voxels/src/VoxelTree.cpp @@ -1,9 +1,12 @@ // // VoxelTree.cpp -// hifi +// libraries/voxels/src // // Created by Stephen Birarda on 3/13/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/voxels/src/VoxelTree.h b/libraries/voxels/src/VoxelTree.h index 2079ab91b2..e0bc1d9a47 100644 --- a/libraries/voxels/src/VoxelTree.h +++ b/libraries/voxels/src/VoxelTree.h @@ -1,13 +1,16 @@ // // VoxelTree.h -// hifi +// libraries/voxels/src // // Created by Stephen Birarda on 3/13/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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__VoxelTree__ -#define __hifi__VoxelTree__ +#ifndef hifi_VoxelTree_h +#define hifi_VoxelTree_h #include @@ -54,4 +57,4 @@ private: void readCodeColorBufferToTreeRecursion(VoxelTreeElement* node, ReadCodeColorBufferToTreeArgs& args); }; -#endif /* defined(__hifi__VoxelTree__) */ +#endif // hifi_VoxelTree_h diff --git a/libraries/voxels/src/VoxelTreeCommands.cpp b/libraries/voxels/src/VoxelTreeCommands.cpp new file mode 100644 index 0000000000..f0f092fd04 --- /dev/null +++ b/libraries/voxels/src/VoxelTreeCommands.cpp @@ -0,0 +1,66 @@ +// +// VoxelTreeCommands.cpp +// libraries/voxels/src +// +// Created by Clement on 4/4/14. +// Copyright 2013 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 "VoxelTree.h" + +#include "VoxelTreeCommands.h" + +AddVoxelCommand::AddVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, VoxelEditPacketSender* packetSender, QUndoCommand* parent) : + QUndoCommand("Add Voxel", parent), + _tree(tree), + _packetSender(packetSender), + _voxel(voxel) +{ +} + +void AddVoxelCommand::redo() { + if (_tree) { + _tree->createVoxel(_voxel.x, _voxel.y, _voxel.z, _voxel.s, _voxel.red, _voxel.green, _voxel.blue); + } + if (_packetSender) { + _packetSender->queueVoxelEditMessages(PacketTypeVoxelSet, 1, &_voxel); + } +} + +void AddVoxelCommand::undo() { + if (_tree) { + _tree->deleteVoxelAt(_voxel.x, _voxel.y, _voxel.z, _voxel.s); + } + if (_packetSender) { + _packetSender->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &_voxel); + } +} + +DeleteVoxelCommand::DeleteVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, VoxelEditPacketSender* packetSender, QUndoCommand* parent) : + QUndoCommand("Delete Voxel", parent), + _tree(tree), + _packetSender(packetSender), + _voxel(voxel) +{ +} + +void DeleteVoxelCommand::redo() { + if (_tree) { + _tree->deleteVoxelAt(_voxel.x, _voxel.y, _voxel.z, _voxel.s); + } + if (_packetSender) { + _packetSender->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &_voxel); + } +} + +void DeleteVoxelCommand::undo() { + if (_tree) { + _tree->createVoxel(_voxel.x, _voxel.y, _voxel.z, _voxel.s, _voxel.red, _voxel.green, _voxel.blue); + } + if (_packetSender) { + _packetSender->queueVoxelEditMessages(PacketTypeVoxelSet, 1, &_voxel); + } +} diff --git a/libraries/voxels/src/VoxelTreeCommands.h b/libraries/voxels/src/VoxelTreeCommands.h new file mode 100644 index 0000000000..4f2610577e --- /dev/null +++ b/libraries/voxels/src/VoxelTreeCommands.h @@ -0,0 +1,49 @@ +// +// VoxelTreeCommands.h +// libraries/voxels/src +// +// Created by Clement on 4/4/14. +// Copyright 2013 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_VoxelTreeCommands_h +#define hifi_VoxelTreeCommands_h + +#include +#include + +#include "VoxelDetail.h" +#include "VoxelEditPacketSender.h" + +class VoxelTree; + +class AddVoxelCommand : public QUndoCommand { +public: + AddVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, VoxelEditPacketSender* packetSender = NULL, QUndoCommand* parent = NULL); + + virtual void redo(); + virtual void undo(); + +private: + VoxelTree* _tree; + VoxelEditPacketSender* _packetSender; + VoxelDetail _voxel; +}; + +class DeleteVoxelCommand : public QUndoCommand { +public: + DeleteVoxelCommand(VoxelTree* tree, VoxelDetail& voxel, VoxelEditPacketSender* packetSender = NULL, QUndoCommand* parent = NULL); + + virtual void redo(); + virtual void undo(); + +private: + VoxelTree* _tree; + VoxelEditPacketSender* _packetSender; + VoxelDetail _voxel; +}; + +#endif // hifi_VoxelTreeCommands_h diff --git a/libraries/voxels/src/VoxelTreeElement.cpp b/libraries/voxels/src/VoxelTreeElement.cpp index debcfee987..2582980816 100644 --- a/libraries/voxels/src/VoxelTreeElement.cpp +++ b/libraries/voxels/src/VoxelTreeElement.cpp @@ -1,9 +1,12 @@ // // VoxelTreeElement.cpp -// hifi +// libraries/voxels/src // // Created by Stephen Birarda on 3/13/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 diff --git a/libraries/voxels/src/VoxelTreeElement.h b/libraries/voxels/src/VoxelTreeElement.h index c88fd6d207..8733987df4 100644 --- a/libraries/voxels/src/VoxelTreeElement.h +++ b/libraries/voxels/src/VoxelTreeElement.h @@ -1,13 +1,16 @@ // // VoxelTreeElement.h -// hifi +// libraries/voxels/src // // Created by Stephen Birarda on 3/13/13. +// Copyright 2013 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__VoxelTreeElement__ -#define __hifi__VoxelTreeElement__ +#ifndef hifi_VoxelTreeElement_h +#define hifi_VoxelTreeElement_h //#define HAS_AUDIT_CHILDREN //#define SIMPLE_CHILD_ARRAY @@ -15,10 +18,10 @@ #include +#include #include #include -#include "AABox.h" #include "ViewFrustum.h" #include "VoxelConstants.h" @@ -117,5 +120,4 @@ inline unsigned char VoxelTreeElement::getExteriorOcclusions() const { return _exteriorOcclusions; } -#endif /* defined(__hifi__VoxelTreeElement__) */ - +#endif // hifi_VoxelTreeElement_h diff --git a/libraries/voxels/src/VoxelTreeHeadlessViewer.cpp b/libraries/voxels/src/VoxelTreeHeadlessViewer.cpp index 999ef4fa36..5aaa8c8d97 100644 --- a/libraries/voxels/src/VoxelTreeHeadlessViewer.cpp +++ b/libraries/voxels/src/VoxelTreeHeadlessViewer.cpp @@ -1,10 +1,12 @@ // // VoxelTreeHeadlessViewer.cpp -// hifi +// libraries/voxels/src // -// Created by Brad Hefta-Gaub on 2/26/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/26/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 "VoxelTreeHeadlessViewer.h" diff --git a/libraries/voxels/src/VoxelTreeHeadlessViewer.h b/libraries/voxels/src/VoxelTreeHeadlessViewer.h index 9c7481c929..3a9f00d754 100644 --- a/libraries/voxels/src/VoxelTreeHeadlessViewer.h +++ b/libraries/voxels/src/VoxelTreeHeadlessViewer.h @@ -1,14 +1,16 @@ // // VoxelTreeHeadlessViewer.h -// hifi +// libraries/voxels/src // -// Created by Brad Hefta-Gaub on 2/26/14 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Brad Hefta-Gaub on 2/26/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__VoxelTreeHeadlessViewer__ -#define __hifi__VoxelTreeHeadlessViewer__ +#ifndef hifi_VoxelTreeHeadlessViewer_h +#define hifi_VoxelTreeHeadlessViewer_h #include #include @@ -35,4 +37,4 @@ public: virtual void init(); }; -#endif /* defined(__hifi__VoxelTreeHeadlessViewer__) */ \ No newline at end of file +#endif // hifi_VoxelTreeHeadlessViewer_h diff --git a/libraries/voxels/src/VoxelsScriptingInterface.cpp b/libraries/voxels/src/VoxelsScriptingInterface.cpp index 2aecb2d457..08dcfd3f47 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.cpp +++ b/libraries/voxels/src/VoxelsScriptingInterface.cpp @@ -1,10 +1,15 @@ // // VoxelsScriptingInterface.cpp -// hifi +// libraries/voxels/src // // Created by Stephen Birarda on 9/17/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 "VoxelTreeCommands.h" #include "VoxelsScriptingInterface.h" @@ -37,17 +42,26 @@ VoxelDetail VoxelsScriptingInterface::getVoxelAt(float x, float y, float z, floa } void VoxelsScriptingInterface::setVoxelNonDestructive(float x, float y, float z, float scale, - uchar red, uchar green, uchar blue) { + uchar red, uchar green, uchar blue) { // setup a VoxelDetail struct with the data - VoxelDetail addVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE, + VoxelDetail addVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE, scale / (float)TREE_SCALE, red, green, blue}; - // queue the add packet - queueVoxelAdd(PacketTypeVoxelSet, addVoxelDetail); // handle the local tree also... if (_tree) { - _tree->createVoxel(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s, red, green, blue, false); + if (_undoStack) { + AddVoxelCommand* command = new AddVoxelCommand(_tree, + addVoxelDetail, + getVoxelPacketSender()); + + // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. + _undoStack->push(command); + } else { + // queue the add packet + queueVoxelAdd(PacketTypeVoxelSet, addVoxelDetail); + _tree->createVoxel(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s, red, green, blue, false); + } } } @@ -57,26 +71,70 @@ void VoxelsScriptingInterface::setVoxel(float x, float y, float z, float scale, VoxelDetail addVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE, scale / (float)TREE_SCALE, red, green, blue}; - // queue the destructive add - queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail); // handle the local tree also... if (_tree) { - _tree->createVoxel(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s, red, green, blue, true); + if (_undoStack) { + AddVoxelCommand* addCommand = new AddVoxelCommand(_tree, + addVoxelDetail, + getVoxelPacketSender()); + + VoxelTreeElement* deleteVoxelElement = _tree->getVoxelAt(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s); + if (deleteVoxelElement) { + nodeColor color; + memcpy(&color, &deleteVoxelElement->getColor(), sizeof(nodeColor)); + VoxelDetail deleteVoxelDetail = {addVoxelDetail.x, + addVoxelDetail.y, + addVoxelDetail.z, + addVoxelDetail.s, + color[0], + color[1], + color[2]}; + DeleteVoxelCommand* delCommand = new DeleteVoxelCommand(_tree, + deleteVoxelDetail, + getVoxelPacketSender()); + _undoStack->beginMacro(addCommand->text()); + // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. + _undoStack->push(delCommand); + _undoStack->push(addCommand); + _undoStack->endMacro(); + } else { + // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. + _undoStack->push(addCommand); + } + } else { + // queue the destructive add + queueVoxelAdd(PacketTypeVoxelSetDestructive, addVoxelDetail); + _tree->createVoxel(addVoxelDetail.x, addVoxelDetail.y, addVoxelDetail.z, addVoxelDetail.s, red, green, blue, true); + } } } void VoxelsScriptingInterface::eraseVoxel(float x, float y, float z, float scale) { - // setup a VoxelDetail struct with data - VoxelDetail deleteVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE, - scale / (float)TREE_SCALE, 0, 0, 0}; + VoxelDetail deleteVoxelDetail = {x / (float)TREE_SCALE, y / (float)TREE_SCALE, z / (float)TREE_SCALE, + scale / (float)TREE_SCALE}; - getVoxelPacketSender()->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &deleteVoxelDetail); // handle the local tree also... if (_tree) { - _tree->deleteVoxelAt(deleteVoxelDetail.x, deleteVoxelDetail.y, deleteVoxelDetail.z, deleteVoxelDetail.s); + VoxelTreeElement* deleteVoxelElement = _tree->getVoxelAt(deleteVoxelDetail.x, deleteVoxelDetail.y, deleteVoxelDetail.z, deleteVoxelDetail.s); + if (deleteVoxelElement) { + deleteVoxelDetail.red = deleteVoxelElement->getColor()[0]; + deleteVoxelDetail.green = deleteVoxelElement->getColor()[1]; + deleteVoxelDetail.blue = deleteVoxelElement->getColor()[2]; + } + + if (_undoStack) { + DeleteVoxelCommand* command = new DeleteVoxelCommand(_tree, + deleteVoxelDetail, + getVoxelPacketSender()); + // As QUndoStack automatically executes redo() on push, we don't need to execute the command ourselves. + _undoStack->push(command); + } else { + getVoxelPacketSender()->queueVoxelEditMessages(PacketTypeVoxelErase, 1, &deleteVoxelDetail); + _tree->deleteVoxelAt(deleteVoxelDetail.x, deleteVoxelDetail.y, deleteVoxelDetail.z, deleteVoxelDetail.s); + } } } diff --git a/libraries/voxels/src/VoxelsScriptingInterface.h b/libraries/voxels/src/VoxelsScriptingInterface.h index d07d2a785c..d5322fdc4d 100644 --- a/libraries/voxels/src/VoxelsScriptingInterface.h +++ b/libraries/voxels/src/VoxelsScriptingInterface.h @@ -1,13 +1,16 @@ // // VoxelsScriptingInterface.h -// hifi +// libraries/voxels/src // // Created by Stephen Birarda on 9/17/13. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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__VoxelsScriptingInterface__ -#define __hifi__VoxelsScriptingInterface__ +#ifndef hifi_VoxelsScriptingInterface_h +#define hifi_VoxelsScriptingInterface_h #include @@ -18,18 +21,24 @@ #include "VoxelEditPacketSender.h" #include "VoxelTree.h" +class QUndoStack; + /// handles scripting of voxel commands from JS passed to assigned clients class VoxelsScriptingInterface : public OctreeScriptingInterface { Q_OBJECT public: - VoxelsScriptingInterface() : _tree(NULL) {}; + VoxelsScriptingInterface() : _tree(NULL), _undoStack(NULL) {}; VoxelEditPacketSender* getVoxelPacketSender() { return (VoxelEditPacketSender*)getPacketSender(); } virtual NodeType_t getServerNodeType() const { return NodeType::VoxelServer; } virtual OctreeEditPacketSender* createPacketSender() { return new VoxelEditPacketSender(); } void setVoxelTree(VoxelTree* tree) { _tree = tree; } + void setUndoStack(QUndoStack* undoStack) { _undoStack = undoStack; } public slots: + + /// provide the world scale + const int getTreeScale() const { return TREE_SCALE; } /// checks the local voxel tree for a voxel at the specified location and scale /// \param x the x-coordinate of the voxel (in meter units) @@ -79,6 +88,7 @@ public slots: private: void queueVoxelAdd(PacketType addPacketType, VoxelDetail& addVoxelDetails); VoxelTree* _tree; + QUndoStack* _undoStack; }; -#endif /* defined(__hifi__VoxelsScriptingInterface__) */ +#endif // hifi_VoxelsScriptingInterface_h diff --git a/tests/metavoxels/src/MetavoxelTests.cpp b/tests/metavoxels/src/MetavoxelTests.cpp index a1b79319b5..2400e086eb 100644 --- a/tests/metavoxels/src/MetavoxelTests.cpp +++ b/tests/metavoxels/src/MetavoxelTests.cpp @@ -1,9 +1,12 @@ // // MetavoxelTests.cpp -// metavoxel-tests +// tests/metavoxels/src // // Created by Andrzej Kapolka on 2/7/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 diff --git a/tests/metavoxels/src/MetavoxelTests.h b/tests/metavoxels/src/MetavoxelTests.h index 22a680acbb..206c818c6e 100644 --- a/tests/metavoxels/src/MetavoxelTests.h +++ b/tests/metavoxels/src/MetavoxelTests.h @@ -1,13 +1,16 @@ // // MetavoxelTests.h -// metavoxel-tests +// tests/metavoxels/src // // Created by Andrzej Kapolka on 2/7/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 __interface__MetavoxelTests__ -#define __interface__MetavoxelTests__ +#ifndef hifi_MetavoxelTests_h +#define hifi_MetavoxelTests_h #include #include @@ -157,4 +160,4 @@ public: DECLARE_STREAMABLE_METATYPE(SequencedTestMessage) -#endif /* defined(__interface__MetavoxelTests__) */ +#endif // hifi_MetavoxelTests_h diff --git a/tests/metavoxels/src/main.cpp b/tests/metavoxels/src/main.cpp index 10bf786957..51d4e565b2 100644 --- a/tests/metavoxels/src/main.cpp +++ b/tests/metavoxels/src/main.cpp @@ -1,9 +1,13 @@ // // main.cpp -// metavoxel-tests +// tests/metavoxels/src // // Created by Andrzej Kapolka on 2/7/14. -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// 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 diff --git a/tests/physics/src/CollisionInfoTests.cpp b/tests/physics/src/CollisionInfoTests.cpp index 241204d036..ffc7e94cc4 100644 --- a/tests/physics/src/CollisionInfoTests.cpp +++ b/tests/physics/src/CollisionInfoTests.cpp @@ -1,9 +1,12 @@ // // CollisionInfoTests.cpp -// physics-tests +// tests/physics/src // -// Created by Andrew Meadows on 2014.02.21 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 2/21/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 // #include diff --git a/tests/physics/src/CollisionInfoTests.h b/tests/physics/src/CollisionInfoTests.h index 51579e8f11..54c4e89e95 100644 --- a/tests/physics/src/CollisionInfoTests.h +++ b/tests/physics/src/CollisionInfoTests.h @@ -1,13 +1,16 @@ // // CollisionInfoTests.h -// physics-tests +// tests/physics/src // -// Created by Andrew Meadows on 2014.02.21 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 2/21/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 // -#ifndef __tests__CollisionInfoTests__ -#define __tests__CollisionInfoTests__ +#ifndef hifi_CollisionInfoTests_h +#define hifi_CollisionInfoTests_h namespace CollisionInfoTests { @@ -17,5 +20,4 @@ namespace CollisionInfoTests { void runAllTests(); } -#endif // __tests__CollisionInfoTests__ - +#endif // hifi_CollisionInfoTests_h diff --git a/tests/physics/src/PhysicsTestUtil.cpp b/tests/physics/src/PhysicsTestUtil.cpp index fb940d2043..f176d5e637 100644 --- a/tests/physics/src/PhysicsTestUtil.cpp +++ b/tests/physics/src/PhysicsTestUtil.cpp @@ -1,9 +1,12 @@ // // PhysicsTestUtil.cpp -// physics-tests +// tests/physics/src // -// Created by Andrew Meadows on 2014.02.21 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/21/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 // #include diff --git a/tests/physics/src/PhysicsTestUtil.h b/tests/physics/src/PhysicsTestUtil.h index c4c7962466..998a18ff5d 100644 --- a/tests/physics/src/PhysicsTestUtil.h +++ b/tests/physics/src/PhysicsTestUtil.h @@ -1,13 +1,16 @@ // // PhysicsTestUtil.h -// physics-tests +// tests/physics/src // -// Created by Andrew Meadows on 2014.02.21 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/21/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 // -#ifndef __tests__PhysicsTestUtil__ -#define __tests__PhysicsTestUtil__ +#ifndef hifi_PhysicsTestUtil_h +#define hifi_PhysicsTestUtil_h #include #include @@ -23,4 +26,4 @@ std::ostream& operator<<(std::ostream& s, const glm::quat& q); std::ostream& operator<<(std::ostream& s, const glm::mat4& m); std::ostream& operator<<(std::ostream& s, const CollisionInfo& c); -#endif // __tests__PhysicsTestUtil__ +#endif // hifi_PhysicsTestUtil_h diff --git a/tests/physics/src/ShapeColliderTests.cpp b/tests/physics/src/ShapeColliderTests.cpp index 7aeec84651..3f952236e2 100644 --- a/tests/physics/src/ShapeColliderTests.cpp +++ b/tests/physics/src/ShapeColliderTests.cpp @@ -1,9 +1,12 @@ // // ShapeColliderTests.cpp -// physics-tests +// tests/physics/src // -// Created by Andrew Meadows on 2014.02.21 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/21/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 // //#include @@ -20,18 +23,18 @@ #include "ShapeColliderTests.h" -const glm::vec3 origin(0.f); -static const glm::vec3 xAxis(1.f, 0.f, 0.f); -static const glm::vec3 yAxis(0.f, 1.f, 0.f); -static const glm::vec3 zAxis(0.f, 0.f, 1.f); +const glm::vec3 origin(0.0f); +static const glm::vec3 xAxis(1.0f, 0.0f, 0.0f); +static const glm::vec3 yAxis(0.0f, 1.0f, 0.0f); +static const glm::vec3 zAxis(0.0f, 0.0f, 1.0f); void ShapeColliderTests::sphereMissesSphere() { // non-overlapping spheres of unequal size - float radiusA = 7.f; - float radiusB = 3.f; + float radiusA = 7.0f; + float radiusB = 3.0f; float alpha = 1.2f; float beta = 1.3f; - glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.f, 2.f, 3.f)); + glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); float offsetDistance = alpha * radiusA + beta * radiusB; SphereShape sphereA(radiusA, origin); @@ -40,7 +43,7 @@ void ShapeColliderTests::sphereMissesSphere() { // collide A to B... { - bool touching = ShapeCollider::shapeShape(&sphereA, &sphereB, collisions); + bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions); if (touching) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphereA and sphereB should NOT touch" << std::endl; @@ -49,7 +52,7 @@ void ShapeColliderTests::sphereMissesSphere() { // collide B to A... { - bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions); + bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions); if (touching) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphereA and sphereB should NOT touch" << std::endl; @@ -58,7 +61,7 @@ void ShapeColliderTests::sphereMissesSphere() { // also test shapeShape { - bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions); + bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions); if (touching) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphereA and sphereB should NOT touch" << std::endl; @@ -74,13 +77,13 @@ void ShapeColliderTests::sphereMissesSphere() { void ShapeColliderTests::sphereTouchesSphere() { // overlapping spheres of unequal size - float radiusA = 7.f; - float radiusB = 3.f; + float radiusA = 7.0f; + float radiusB = 3.0f; float alpha = 0.2f; float beta = 0.3f; - glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.f, 2.f, 3.f)); + glm::vec3 offsetDirection = glm::normalize(glm::vec3(1.0f, 2.0f, 3.0f)); float offsetDistance = alpha * radiusA + beta * radiusB; - float expectedPenetrationDistance = (1.f - alpha) * radiusA + (1.f - beta) * radiusB; + float expectedPenetrationDistance = (1.0f - alpha) * radiusA + (1.0f - beta) * radiusB; glm::vec3 expectedPenetration = expectedPenetrationDistance * offsetDirection; SphereShape sphereA(radiusA, origin); @@ -90,7 +93,7 @@ void ShapeColliderTests::sphereTouchesSphere() { // collide A to B... { - bool touching = ShapeCollider::shapeShape(&sphereA, &sphereB, collisions); + bool touching = ShapeCollider::collideShapes(&sphereA, &sphereB, collisions); if (!touching) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphereA and sphereB should touch" << std::endl; @@ -115,8 +118,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -126,14 +128,13 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } // collide B to A... { - bool touching = ShapeCollider::shapeShape(&sphereB, &sphereA, collisions); + bool touching = ShapeCollider::collideShapes(&sphereB, &sphereA, collisions); if (!touching) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphereA and sphereB should touch" << std::endl; @@ -147,8 +148,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -158,8 +158,7 @@ void ShapeColliderTests::sphereTouchesSphere() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } } @@ -178,7 +177,7 @@ void ShapeColliderTests::sphereMissesCapsule() { // give the capsule some arbirary transform float angle = 37.8f; - glm::vec3 axis = glm::normalize( glm::vec3(-7.f, 2.8f, 9.3f) ); + glm::vec3 axis = glm::normalize( glm::vec3(-7.0f, 2.8f, 9.3f) ); glm::quat rotation = glm::angleAxis(angle, axis); glm::vec3 translation(15.1f, -27.1f, -38.6f); capsuleB.setRotation(rotation); @@ -187,7 +186,7 @@ void ShapeColliderTests::sphereMissesCapsule() { CollisionList collisions(16); // walk sphereA along the local yAxis next to, but not touching, capsuleB - glm::vec3 localStartPosition(radialOffset, axialOffset, 0.f); + glm::vec3 localStartPosition(radialOffset, axialOffset, 0.0f); int numberOfSteps = 10; float delta = 1.3f * (totalRadius + halfHeightB) / (numberOfSteps - 1); for (int i = 0; i < numberOfSteps; ++i) { @@ -196,7 +195,7 @@ void ShapeColliderTests::sphereMissesCapsule() { sphereA.setPosition(rotation * localPosition + translation); // sphereA agains capsuleB - if (ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions)) + if (ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere and capsule should NOT touch" @@ -204,7 +203,7 @@ void ShapeColliderTests::sphereMissesCapsule() { } // capsuleB against sphereA - if (ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions)) + if (ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere and capsule should NOT touch" @@ -221,10 +220,10 @@ void ShapeColliderTests::sphereMissesCapsule() { void ShapeColliderTests::sphereTouchesCapsule() { // overlapping sphere and capsule - float radiusA = 2.f; - float radiusB = 1.f; + float radiusA = 2.0f; + float radiusB = 1.0f; float totalRadius = radiusA + radiusB; - float halfHeightB = 2.f; + float halfHeightB = 2.0f; float alpha = 0.5f; float beta = 0.5f; float radialOffset = alpha * radiusA + beta * radiusB; @@ -238,7 +237,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { { // sphereA collides with capsuleB's cylindrical wall sphereA.setPosition(radialOffset * xAxis); - if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions)) + if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere and capsule should touch" @@ -254,8 +253,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -264,12 +262,11 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } // capsuleB collides with sphereA - if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions)) + if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and sphere should touch" @@ -285,8 +282,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of capsuleB @@ -297,15 +293,14 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } { // sphereA hits end cap at axis glm::vec3 axialOffset = (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis; sphereA.setPosition(axialOffset * yAxis); - if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions)) + if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere and capsule should touch" @@ -316,13 +311,12 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB CollisionInfo* collision = collisions.getCollision(numCollisions - 1); - glm::vec3 expectedPenetration = - ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis; + glm::vec3 expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; float inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -331,12 +325,11 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } // capsuleB collides with sphereA - if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions)) + if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and sphere should touch" @@ -347,13 +340,12 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); - expectedPenetration = ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis; + expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of capsuleB @@ -364,15 +356,14 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } { // sphereA hits start cap at axis glm::vec3 axialOffset = - (halfHeightB + alpha * radiusA + beta * radiusB) * yAxis; sphereA.setPosition(axialOffset * yAxis); - if (!ShapeCollider::shapeShape(&sphereA, &capsuleB, collisions)) + if (!ShapeCollider::collideShapes(&sphereA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere and capsule should touch" @@ -383,13 +374,12 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB CollisionInfo* collision = collisions.getCollision(numCollisions - 1); - glm::vec3 expectedPenetration = ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis; + glm::vec3 expectedPenetration = ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; float inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of sphereA @@ -398,12 +388,11 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } // capsuleB collides with sphereA - if (!ShapeCollider::shapeShape(&capsuleB, &sphereA, collisions)) + if (!ShapeCollider::collideShapes(&capsuleB, &sphereA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and sphere should touch" @@ -414,13 +403,12 @@ void ShapeColliderTests::sphereTouchesCapsule() { // penetration points from sphereA into capsuleB collision = collisions.getCollision(numCollisions - 1); - expectedPenetration = - ((1.f - alpha) * radiusA + (1.f - beta) * radiusB) * yAxis; + expectedPenetration = - ((1.0f - alpha) * radiusA + (1.0f - beta) * radiusB) * yAxis; inaccuracy = glm::length(collision->_penetration - expectedPenetration); if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } // contactPoint is on surface of capsuleB @@ -431,8 +419,7 @@ void ShapeColliderTests::sphereTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } } if (collisions.size() != numCollisions) { @@ -444,10 +431,10 @@ void ShapeColliderTests::sphereTouchesCapsule() { void ShapeColliderTests::capsuleMissesCapsule() { // non-overlapping capsules - float radiusA = 2.f; - float halfHeightA = 3.f; - float radiusB = 3.f; - float halfHeightB = 4.f; + float radiusA = 2.0f; + float halfHeightA = 3.0f; + float radiusB = 3.0f; + float halfHeightB = 4.0f; float totalRadius = radiusA + radiusB; float totalHalfLength = totalRadius + halfHeightA + halfHeightB; @@ -459,13 +446,13 @@ void ShapeColliderTests::capsuleMissesCapsule() { // side by side capsuleB.setPosition((1.01f * totalRadius) * xAxis); - if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions)) + if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should NOT touch" << std::endl; } - if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions)) + if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should NOT touch" @@ -474,13 +461,13 @@ void ShapeColliderTests::capsuleMissesCapsule() { // end to end capsuleB.setPosition((1.01f * totalHalfLength) * xAxis); - if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions)) + if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should NOT touch" << std::endl; } - if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions)) + if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should NOT touch" @@ -491,13 +478,13 @@ void ShapeColliderTests::capsuleMissesCapsule() { glm::quat rotation = glm::angleAxis(PI_OVER_TWO, zAxis); capsuleB.setRotation(rotation); capsuleB.setPosition((1.01f * (totalRadius + capsuleB.getHalfHeight())) * xAxis); - if (ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions)) + if (ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should NOT touch" << std::endl; } - if (ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions)) + if (ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should NOT touch" @@ -513,10 +500,10 @@ void ShapeColliderTests::capsuleMissesCapsule() { void ShapeColliderTests::capsuleTouchesCapsule() { // overlapping capsules - float radiusA = 2.f; - float halfHeightA = 3.f; - float radiusB = 3.f; - float halfHeightB = 4.f; + float radiusA = 2.0f; + float halfHeightA = 3.0f; + float radiusB = 3.0f; + float halfHeightB = 4.0f; float totalRadius = radiusA + radiusB; float totalHalfLength = totalRadius + halfHeightA + halfHeightB; @@ -529,7 +516,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { { // side by side capsuleB.setPosition((0.99f * totalRadius) * xAxis); - if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions)) + if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -537,7 +524,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { } else { ++numCollisions; } - if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions)) + if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -550,7 +537,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { { // end to end capsuleB.setPosition((0.99f * totalHalfLength) * yAxis); - if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions)) + if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -558,7 +545,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { } else { ++numCollisions; } - if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions)) + if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -573,7 +560,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { capsuleB.setRotation(rotation); capsuleB.setPosition((0.99f * (totalRadius + capsuleB.getHalfHeight())) * xAxis); - if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions)) + if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -581,7 +568,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { } else { ++numCollisions; } - if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions)) + if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -599,7 +586,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { capsuleB.setPosition(positionB); // capsuleA vs capsuleB - if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions)) + if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -614,8 +601,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad penetration: expected = " << expectedPenetration - << " actual = " << collision->_penetration - << std::endl; + << " actual = " << collision->_penetration; } glm::vec3 expectedContactPoint = capsuleA.getPosition() + radiusA * xAxis; @@ -623,12 +609,11 @@ void ShapeColliderTests::capsuleTouchesCapsule() { if (fabs(inaccuracy) > EPSILON) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: bad contactPoint: expected = " << expectedContactPoint - << " actual = " << collision->_contactPoint - << std::endl; + << " actual = " << collision->_contactPoint; } // capsuleB vs capsuleA - if (!ShapeCollider::shapeShape(&capsuleB, &capsuleA, collisions)) + if (!ShapeCollider::collideShapes(&capsuleB, &capsuleA, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -666,7 +651,7 @@ void ShapeColliderTests::capsuleTouchesCapsule() { capsuleB.setPosition(positionB); // capsuleA vs capsuleB - if (!ShapeCollider::shapeShape(&capsuleA, &capsuleB, collisions)) + if (!ShapeCollider::collideShapes(&capsuleA, &capsuleB, collisions)) { std::cout << __FILE__ << ":" << __LINE__ << " ERROR: capsule and capsule should touch" @@ -696,6 +681,116 @@ void ShapeColliderTests::capsuleTouchesCapsule() { } } +void ShapeColliderTests::sphereTouchesAACube() { + CollisionList collisions(16); + + glm::vec3 cubeCenter(1.23f, 4.56f, 7.89f); + float cubeSide = 2.0f; + + float sphereRadius = 1.0f; + glm::vec3 sphereCenter(0.0f); + SphereShape sphere(sphereRadius, sphereCenter); + + float sphereOffset = (0.5f * cubeSide + sphereRadius - 0.25f); + + // top + sphereCenter = cubeCenter + sphereOffset * yAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // bottom + sphereCenter = cubeCenter - sphereOffset * yAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // left + sphereCenter = cubeCenter + sphereOffset * xAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // right + sphereCenter = cubeCenter - sphereOffset * xAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // forward + sphereCenter = cubeCenter + sphereOffset * zAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } + + // back + sphereCenter = cubeCenter - sphereOffset * zAxis; + sphere.setPosition(sphereCenter); + if (!ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should collide with cube" << std::endl; + } +} + +void ShapeColliderTests::sphereMissesAACube() { + CollisionList collisions(16); + + glm::vec3 cubeCenter(1.23f, 4.56f, 7.89f); + float cubeSide = 2.0f; + + float sphereRadius = 1.0f; + glm::vec3 sphereCenter(0.0f); + SphereShape sphere(sphereRadius, sphereCenter); + + float sphereOffset = (0.5f * cubeSide + sphereRadius + 0.25f); + + // top + sphereCenter = cubeCenter + sphereOffset * yAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // bottom + sphereCenter = cubeCenter - sphereOffset * yAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // left + sphereCenter = cubeCenter + sphereOffset * xAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // right + sphereCenter = cubeCenter - sphereOffset * xAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // forward + sphereCenter = cubeCenter + sphereOffset * zAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } + + // back + sphereCenter = cubeCenter - sphereOffset * zAxis; + sphere.setPosition(sphereCenter); + if (ShapeCollider::sphereAACube(&sphere, cubeCenter, cubeSide, collisions)){ + std::cout << __FILE__ << ":" << __LINE__ << " ERROR: sphere should NOT collide with cube" << std::endl; + } +} + void ShapeColliderTests::runAllTests() { sphereMissesSphere(); @@ -706,4 +801,7 @@ void ShapeColliderTests::runAllTests() { capsuleMissesCapsule(); capsuleTouchesCapsule(); + + sphereTouchesAACube(); + sphereMissesAACube(); } diff --git a/tests/physics/src/ShapeColliderTests.h b/tests/physics/src/ShapeColliderTests.h index ecd4a7f045..a94f5050ff 100644 --- a/tests/physics/src/ShapeColliderTests.h +++ b/tests/physics/src/ShapeColliderTests.h @@ -1,13 +1,16 @@ // // ShapeColliderTests.h -// physics-tests +// tests/physics/src // -// Created by Andrew Meadows on 2014.02.21 -// Copyright (c) 2014 High Fidelity, Inc. All rights reserved. +// Created by Andrew Meadows on 02/21/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 // -#ifndef __tests__ShapeColliderTests__ -#define __tests__ShapeColliderTests__ +#ifndef hifi_ShapeColliderTests_h +#define hifi_ShapeColliderTests_h namespace ShapeColliderTests { @@ -20,7 +23,10 @@ namespace ShapeColliderTests { void capsuleMissesCapsule(); void capsuleTouchesCapsule(); + void sphereTouchesAACube(); + void sphereMissesAACube(); + void runAllTests(); } -#endif // __tests__ShapeColliderTests__ +#endif // hifi_ShapeColliderTests_h diff --git a/tests/physics/src/main.cpp b/tests/physics/src/main.cpp index b0a7adde4e..ca98f4d546 100644 --- a/tests/physics/src/main.cpp +++ b/tests/physics/src/main.cpp @@ -1,6 +1,11 @@ // // main.cpp -// physics-tests +// tests/physics/src +// +// 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 "ShapeColliderTests.h" diff --git a/tools/mtc/src/main.cpp b/tools/mtc/src/main.cpp index 9c77851961..096ade4625 100644 --- a/tools/mtc/src/main.cpp +++ b/tools/mtc/src/main.cpp @@ -1,10 +1,12 @@ // // main.cpp -// mtc +// tools/mtc/src // // Created by Andrzej Kapolka on 12/31/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -143,13 +145,15 @@ void generateOutput (QTextStream& out, const QList& streamables) { out << " }\n"; out << " index = nextIndex;\n"; } - out << " switch (index) {\n"; - for (int i = 0; i < str.fields.size(); i++) { - out << " case " << i << ":\n"; - out << " this->" << str.fields.at(i).name << " = value.value<" << str.fields.at(i).type << ">();\n"; - out << " break;\n"; + if (!str.fields.isEmpty()) { + out << " switch (index) {\n"; + for (int i = 0; i < str.fields.size(); i++) { + out << " case " << i << ":\n"; + out << " this->" << str.fields.at(i).name << " = value.value<" << str.fields.at(i).type << ">();\n"; + out << " break;\n"; + } + out << " }\n"; } - out << " }\n"; out << "}\n"; out << "QVariant " << name << "::getField(int index) const {\n"; @@ -162,12 +166,14 @@ void generateOutput (QTextStream& out, const QList& streamables) { out << " }\n"; out << " index = nextIndex;\n"; } - out << " switch (index) {\n"; - for (int i = 0; i < str.fields.size(); i++) { - out << " case " << i << ":\n"; - out << " return QVariant::fromValue(this->" << str.fields.at(i).name << ");\n"; + if (!str.fields.isEmpty()) { + out << " switch (index) {\n"; + for (int i = 0; i < str.fields.size(); i++) { + out << " case " << i << ":\n"; + out << " return QVariant::fromValue(this->" << str.fields.at(i).name << ");\n"; + } + out << " }\n"; } - out << " }\n"; out << " return QVariant();\n"; out << "}\n"; diff --git a/voxel-edit/CMakeLists.txt b/voxel-edit/CMakeLists.txt index 0de8fd059e..cc0a122bf0 100644 --- a/voxel-edit/CMakeLists.txt +++ b/voxel-edit/CMakeLists.txt @@ -31,8 +31,19 @@ link_hifi_library(octree ${TARGET_NAME} "${ROOT_DIR}") # link in the hifi voxels library link_hifi_library(voxels ${TARGET_NAME} "${ROOT_DIR}") +# link in the hifi networking library +link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}") + +# link GnuTLS +find_package(GnuTLS REQUIRED) + IF (WIN32) target_link_libraries(${TARGET_NAME} Winmm Ws2_32) + + # add a definition for ssize_t so that windows doesn't bail on gnutls.h + add_definitions(-Dssize_t=long) ENDIF(WIN32) -target_link_libraries(${TARGET_NAME} Qt5::Script) \ No newline at end of file +include_directories(SYSTEM "${GNUTLS_INCLUDE_DIR}") + +target_link_libraries(${TARGET_NAME} Qt5::Script "${GNUTLS_LIBRARY}") \ No newline at end of file diff --git a/voxel-edit/src/SceneUtils.cpp b/voxel-edit/src/SceneUtils.cpp index 2cbe73c74e..937b03cc39 100644 --- a/voxel-edit/src/SceneUtils.cpp +++ b/voxel-edit/src/SceneUtils.cpp @@ -1,9 +1,12 @@ // // SceneUtils.cpp -// hifi +// voxel-edit/src // // Created by Brad Hefta-Gaub on 5/7/2013. -// Copyright (c) 2013 HighFidelity, Inc. All rights reserved. +// Copyright 2013 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 @@ -17,7 +20,7 @@ void addCornersAndAxisLines(VoxelTree* tree) { float voxelSize = 0.5f / TREE_SCALE; // Now some more examples... a little more complex - printf("creating corner points...\n"); + qDebug("creating corner points..."); tree->createVoxel(0 , 0 , 0 , voxelSize, 255, 255 ,255); tree->createVoxel(1.0 - voxelSize, 0 , 0 , voxelSize, 255, 0 ,0 ); tree->createVoxel(0 , 1.0 - voxelSize, 0 , voxelSize, 0 , 255 ,0 ); @@ -26,11 +29,11 @@ void addCornersAndAxisLines(VoxelTree* tree) { tree->createVoxel(0 , 1.0 - voxelSize, 1.0 - voxelSize, voxelSize, 0 , 255 ,255); tree->createVoxel(1.0 - voxelSize, 1.0 - voxelSize, 0 , voxelSize, 255, 255 ,0 ); tree->createVoxel(1.0 - voxelSize, 1.0 - voxelSize, 1.0 - voxelSize, voxelSize, 255, 255 ,255); - printf("DONE creating corner points...\n"); + qDebug("DONE creating corner points..."); } void addSurfaceScene(VoxelTree * tree) { - printf("adding surface scene...\n"); + qDebug("adding surface scene..."); float voxelSize = 1.f / (8 * TREE_SCALE); // color 1= blue, color 2=green @@ -54,5 +57,5 @@ void addSurfaceScene(VoxelTree * tree) { } } } - printf("DONE adding surface scene...\n"); + qDebug("DONE adding surface scene..."); } diff --git a/voxel-edit/src/SceneUtils.h b/voxel-edit/src/SceneUtils.h index 6f52a14f05..26c8130c4b 100644 --- a/voxel-edit/src/SceneUtils.h +++ b/voxel-edit/src/SceneUtils.h @@ -1,13 +1,16 @@ // // SceneUtils.h -// hifi +// voxel-edit/src // // Created by Brad Hefta-Gaub on 5/7/2013. +// Copyright 2013 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__SceneUtil__ -#define __hifi__SceneUtil__ +#ifndef hifi_SceneUtils_h +#define hifi_SceneUtils_h #include "VoxelTree.h" #include @@ -16,4 +19,4 @@ void addCornersAndAxisLines(VoxelTree* tree); void addSurfaceScene(VoxelTree * tree); -#endif /* defined(__hifi__SceneUtil__) */ +#endif // hifi_SceneUtils_h diff --git a/voxel-edit/src/main.cpp b/voxel-edit/src/main.cpp index 35401aa1d0..01e4cc0189 100644 --- a/voxel-edit/src/main.cpp +++ b/voxel-edit/src/main.cpp @@ -1,9 +1,12 @@ // // main.cpp -// Voxel Edit +// voxel-edit/src // // Created by Brad Hefta-Gaub on 05/03/13. -// Copyright (c) 2013 High Fidelity, Inc. All rights reserved. +// Copyright 2013 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