This commit is contained in:
Philip Rosedale 2014-07-03 14:08:19 -07:00
commit a25c08a1b7
94 changed files with 2626 additions and 347 deletions

4
.gitignore vendored
View file

@ -50,5 +50,9 @@ interface/external/faceplus/*
interface/external/priovr/*
!interface/external/priovr/readme.txt
# Ignore RtMidi
interface/external/rtmidi/*
!interface/external/rtmidi/readme.txt
# Ignore interfaceCache for Linux users
interface/interfaceCache/

View file

@ -10,6 +10,7 @@ add_definitions(-DGLM_FORCE_RADIANS)
if (WIN32)
add_definitions(-DNOMINMAX -D_CRT_SECURE_NO_WARNINGS)
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "C:\\Program Files\\Microsoft SDKs\\Windows\\v7.1 ")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP")
elseif (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic")
#SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-unknown-pragmas")

View file

@ -12,6 +12,9 @@ set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
# setup for find modules
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../cmake/modules/")
find_package(Qt5 COMPONENTS Script)
include_directories(SYSTEM "${Qt5Script_INCLUDE_DIRS}")
# set up the external glm library
include("${MACRO_DIR}/IncludeGLM.cmake")
include_glm(${TARGET_NAME} "${ROOT_DIR}")
@ -35,4 +38,4 @@ link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
# add a definition for ssize_t so that windows doesn't bail
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()
endif ()

View file

@ -13,13 +13,13 @@
#include <QtCore/QEventLoop>
#include <QtCore/QStandardPaths>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkDiskCache>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <AudioRingBuffer.h>
#include <AvatarData.h>
#include <NetworkAccessManager.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <ResourceCache.h>
@ -208,12 +208,14 @@ void Agent::run() {
scriptURL = QUrl(_payload);
}
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
QNetworkReply *reply = networkManager->get(QNetworkRequest(scriptURL));
QNetworkDiskCache* cache = new QNetworkDiskCache(networkManager);
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply *reply = networkAccessManager.get(QNetworkRequest(scriptURL));
QNetworkDiskCache* cache = new QNetworkDiskCache(&networkAccessManager);
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "agentCache");
networkManager->setCache(cache);
QMetaObject::invokeMethod(&networkAccessManager, "setCache",
Qt::BlockingQueuedConnection,
Q_ARG(QAbstractNetworkCache*, cache));
qDebug() << "Downloading script at" << scriptURL.toString();
@ -222,10 +224,6 @@ void Agent::run() {
loop.exec();
// let the AvatarData and ResourceCache classes use our QNetworkAccessManager
AvatarData::setNetworkAccessManager(networkManager);
ResourceCache::setNetworkAccessManager(networkManager);
QString scriptContents(reply->readAll());
qDebug() << "Downloaded script:" << scriptContents;

View file

@ -104,6 +104,8 @@ AssignmentClient::AssignmentClient(int &argc, char **argv) :
// connections to AccountManager for authentication
connect(&AccountManager::getInstance(), &AccountManager::authRequired,
this, &AssignmentClient::handleAuthenticationRequest);
NetworkAccessManager::getInstance();
}
void AssignmentClient::sendAssignmentRequest() {

View file

@ -38,11 +38,11 @@
#include <QtCore/QJsonObject>
#include <QtCore/QJsonValue>
#include <QtCore/QTimer>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <Logging.h>
#include <NetworkAccessManager.h>
#include <NodeList.h>
#include <Node.h>
#include <PacketHeaders.h>
@ -89,6 +89,9 @@ AudioMixer::~AudioMixer() {
delete _listenerUnattenuatedZone;
}
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
const float ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE = 0.18f;
void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuffer* bufferToAdd,
AvatarAudioRingBuffer* listeningNodeBuffer) {
float bearingRelativeAngleToSource = 0.0f;
@ -99,6 +102,7 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
bool shouldAttenuate = (bufferToAdd != listeningNodeBuffer);
if (shouldAttenuate) {
// if the two buffer pointers do not match then these are different buffers
glm::vec3 relativePosition = bufferToAdd->getPosition() - listeningNodeBuffer->getPosition();
@ -162,19 +166,18 @@ void AudioMixer::addBufferToMixForListeningNodeWithBuffer(PositionalAudioRingBuf
glm::vec3 rotatedSourcePosition = inverseOrientation * relativePosition;
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);
// calculate the distance coefficient using the distance to this node
float distanceCoefficient = powf(GEOMETRIC_AMPLITUDE_SCALAR,
DISTANCE_SCALE_LOG +
(0.5f * logf(distanceSquareToSource) / logf(DISTANCE_LOG_BASE)) - 1);
distanceCoefficient = std::min(1.0f, distanceCoefficient);
// multiply the current attenuation coefficient by the distance coefficient
attenuationCoefficient *= distanceCoefficient;
if (distanceBetween >= ATTENUATION_BEGINS_AT_DISTANCE) {
// calculate the distance coefficient using the distance to this node
float distanceCoefficient = 1 - (logf(distanceBetween / ATTENUATION_BEGINS_AT_DISTANCE) / logf(2.0f)
* ATTENUATION_AMOUNT_PER_DOUBLING_IN_DISTANCE);
if (distanceCoefficient < 0) {
distanceCoefficient = 0;
}
// multiply the current attenuation coefficient by the distance coefficient
attenuationCoefficient *= distanceCoefficient;
}
// project the rotated source position vector onto the XZ plane
rotatedSourcePosition.y = 0.0f;
@ -482,8 +485,8 @@ void AudioMixer::run() {
nodeList->linkedDataCreateCallback = attachNewBufferToNode;
// setup a QNetworkAccessManager to ask the domain-server for our settings
QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
// setup a NetworkAccessManager to ask the domain-server for our settings
NetworkAccessManager& networkManager = NetworkAccessManager::getInstance();
QUrl settingsJSONURL;
settingsJSONURL.setScheme("http");
@ -500,7 +503,7 @@ void AudioMixer::run() {
qDebug() << "Requesting settings for assignment from domain-server at" << settingsJSONURL.toString();
while (!reply || reply->error() != QNetworkReply::NoError) {
reply = networkManager->get(QNetworkRequest(settingsJSONURL));
reply = networkManager.get(QNetworkRequest(settingsJSONURL));
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));

View file

@ -101,8 +101,7 @@ int AudioMixerClientData::parseData(const QByteArray& packet) {
if (!matchingInjectedRingBuffer) {
// we don't have a matching injected audio ring buffer, so add it
matchingInjectedRingBuffer = new InjectedAudioRingBuffer(streamIdentifier,
AudioMixer::getUseDynamicJitterBuffers());
matchingInjectedRingBuffer = new InjectedAudioRingBuffer(streamIdentifier, AudioMixer::getUseDynamicJitterBuffers());
_ringBuffers.push_back(matchingInjectedRingBuffer);
}

View file

@ -216,9 +216,13 @@ int OctreeInboundPacketProcessor::sendNackPackets() {
}
const SharedNodePointer& destinationNode = NodeList::getInstance()->getNodeHash().value(nodeUUID);
const QSet<unsigned short int>& missingSequenceNumbers = nodeStats.getIncomingEditSequenceNumberStats().getMissingSet();
// retrieve sequence number stats of node, prune its missing set
SequenceNumberStats& sequenceNumberStats = nodeStats.getIncomingEditSequenceNumberStats();
sequenceNumberStats.pruneMissingSet();
// construct nack packet(s) for this node
const QSet<unsigned short int>& missingSequenceNumbers = sequenceNumberStats.getMissingSet();
int numSequenceNumbersAvailable = missingSequenceNumbers.size();
QSet<unsigned short int>::const_iterator missingSequenceNumberIterator = missingSequenceNumbers.constBegin();
while (numSequenceNumbersAvailable > 0) {

View file

@ -35,6 +35,7 @@ public:
{ return _totalElementsInPacket == 0 ? 0 : _totalLockWaitTime / _totalElementsInPacket; }
const SequenceNumberStats& getIncomingEditSequenceNumberStats() const { return _incomingEditSequenceNumberStats; }
SequenceNumberStats& getIncomingEditSequenceNumberStats() { return _incomingEditSequenceNumberStats; }
void trackInboundPacket(unsigned short int incomingSequence, quint64 transitTime,
int editsInPacket, quint64 processTime, quint64 lockWaitTime);

View file

@ -42,7 +42,7 @@ OctreeQueryNode::OctreeQueryNode() :
_lastRootTimestamp(0),
_myPacketType(PacketTypeUnknown),
_isShuttingDown(false),
_sentPacketHistory(1000)
_sentPacketHistory()
{
}

View file

@ -10,7 +10,6 @@
//
#include <QJsonObject>
#include <QNetworkAccessManager>
#include <QTimer>
#include <QUuid>

View file

@ -0,0 +1,33 @@
#
# FindRtMidd.cmake
#
# Try to find the RtMidi library
#
# You can provide a RTMIDI_ROOT_DIR which contains lib and include directories
#
# Once done this will define
#
# RTMIDI_FOUND - system found RtMidi
# RTMIDI_INCLUDE_DIRS - the RtMidi include directory
# RTMIDI_CPP - Include this with src to use RtMidi
#
# Created on 6/30/2014 by Stephen Birarda
# 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 (RTMIDI_LIBRARIES AND RTMIDI_INCLUDE_DIRS)
# in cache already
set(RTMIDI_FOUND TRUE)
else ()
set(RTMIDI_SEARCH_DIRS "${RTMIDI_ROOT_DIR}" "$ENV{HIFI_LIB_DIR}/rtmidi")
find_path(RTMIDI_INCLUDE_DIR RtMidi.h PATH_SUFFIXES include HINTS ${RTMIDI_SEARCH_DIRS})
find_file(RTMIDI_CPP NAMES RtMidi.cpp PATH_SUFFIXES src HINTS ${RTMIDI_SEARCH_DIRS})
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(RTMIDI DEFAULT_MSG RTMIDI_INCLUDE_DIR RTMIDI_CPP)
endif ()

View file

@ -51,8 +51,6 @@ DomainServer::DomainServer(int argc, char* argv[]) :
_argumentVariantMap = HifiConfigVariantMap::mergeCLParametersWithJSONConfig(arguments());
_networkAccessManager = new QNetworkAccessManager(this);
if (optionallyReadX509KeyAndCertificate() && optionallySetupOAuth() && optionallySetupAssignmentPayment()) {
// we either read a certificate and private key or were not passed one
// and completed login or did not need to
@ -1196,7 +1194,7 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
QNetworkRequest tokenRequest(tokenRequestUrl);
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* tokenReply = _networkAccessManager->post(tokenRequest, tokenPostBody.toLocal8Bit());
QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit());
qDebug() << "Requesting a token for user with session UUID" << uuidStringWithoutCurlyBraces(stateUUID);
@ -1233,7 +1231,7 @@ void DomainServer::handleTokenRequestFinished() {
profileURL.setPath("/api/v1/users/profile");
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL));
QNetworkReply* profileReply = NetworkAccessManager::getInstance().get(QNetworkRequest(profileURL));
qDebug() << "Requesting access token for user with session UUID" << uuidStringWithoutCurlyBraces(matchingSessionUUID);

View file

@ -103,8 +103,6 @@ private:
bool _isUsingDTLS;
QNetworkAccessManager* _networkAccessManager;
QUrl _oauthProviderURL;
QString _oauthClientID;
QString _oauthClientSecret;

View file

@ -36,9 +36,12 @@ DomainServerSettingsManager::DomainServerSettingsManager() :
// load the existing config file to get the current values
QFile configFile(QCoreApplication::applicationDirPath() + SETTINGS_CONFIG_FILE_RELATIVE_PATH);
configFile.open(QIODevice::ReadOnly);
_settingsMap = QJsonDocument::fromJson(configFile.readAll()).toVariant().toMap();
if (configFile.exists()) {
configFile.open(QIODevice::ReadOnly);
_settingsMap = QJsonDocument::fromJson(configFile.readAll()).toVariant().toMap();
}
}
const QString DESCRIPTION_SETTINGS_KEY = "settings";

View file

@ -0,0 +1,32 @@
//
// animationStateExample.js
// examples
//
// Created by Brad Hefta-Gaub on 7/3/14.
// Copyright 2014 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;
function displayAnimationDetails(deltaTime) {
print("count =" + count + " deltaTime=" + deltaTime);
count++;
var animationDetails = MyAvatar.getAnimationDetailsByRole("idle");
print("animationDetails.frameIndex=" + animationDetails.frameIndex);
}
function scriptEnding() {
print("SCRIPT ENDNG!!!\n");
}
// register the call back so it fires before each data send
Script.update.connect(displayAnimationDetails);
// register our scriptEnding callback
Script.scriptEnding.connect(scriptEnding);

View file

@ -18,6 +18,7 @@ set(LIBOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/oculus")
set(PRIOVR_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/priovr")
set(SIXENSE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/Sixense")
set(VISAGE_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/visage")
set(RTMIDI_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/external/rtmidi")
find_package(Qt5LinguistTools REQUIRED)
find_package(Qt5LinguistToolsMacros)
@ -110,6 +111,16 @@ if (APPLE)
SET(INTERFACE_SRCS ${INTERFACE_SRCS} "${CMAKE_CURRENT_SOURCE_DIR}/interface.icns")
endif()
# RtMidi for scripted MIDI control
find_package(RtMidi)
if (RTMIDI_FOUND AND NOT DISABLE_RTMIDI)
add_definitions(-DHAVE_RTMIDI)
include_directories(SYSTEM ${RTMIDI_INCLUDE_DIR})
set(INTERFACE_SRCS ${INTERFACE_SRCS} "${RTMIDI_CPP}")
endif ()
# create the executable, make it a bundle on OS X
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
@ -143,75 +154,82 @@ find_package(Qxmpp)
# include the Sixense library for Razer Hydra if available
if (SIXENSE_FOUND AND NOT DISABLE_SIXENSE)
add_definitions(-DHAVE_SIXENSE)
include_directories(SYSTEM "${SIXENSE_INCLUDE_DIRS}")
if (APPLE OR UNIX)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${SIXENSE_INCLUDE_DIRS}")
endif (APPLE OR UNIX)
target_link_libraries(${TARGET_NAME} "${SIXENSE_LIBRARIES}")
add_definitions(-DHAVE_SIXENSE)
include_directories(SYSTEM "${SIXENSE_INCLUDE_DIRS}")
if (APPLE OR UNIX)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${SIXENSE_INCLUDE_DIRS}")
endif (APPLE OR UNIX)
target_link_libraries(${TARGET_NAME} "${SIXENSE_LIBRARIES}")
endif (SIXENSE_FOUND AND NOT DISABLE_SIXENSE)
# likewise with Visage library for webcam feature tracking
if (VISAGE_FOUND AND NOT DISABLE_VISAGE)
add_definitions(-DHAVE_VISAGE -DVISAGE_STATIC)
include_directories(SYSTEM "${VISAGE_INCLUDE_DIRS}")
if (APPLE)
add_definitions(-DMAC_OS_X)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-comment")
include_directories(SYSTEM "${VISAGE_INCLUDE_DIRS}")
find_library(AVFoundation AVFoundation)
find_library(CoreMedia CoreMedia)
find_library(NEW_STD_LIBRARY libc++.dylib /usr/lib/)
target_link_libraries(${TARGET_NAME} ${AVFoundation} ${CoreMedia} ${NEW_STD_LIBRARY})
endif (APPLE)
target_link_libraries(${TARGET_NAME} "${VISAGE_LIBRARIES}")
add_definitions(-DHAVE_VISAGE -DVISAGE_STATIC)
include_directories(SYSTEM "${VISAGE_INCLUDE_DIRS}")
if (APPLE)
add_definitions(-DMAC_OS_X)
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-comment")
include_directories(SYSTEM "${VISAGE_INCLUDE_DIRS}")
find_library(AVFoundation AVFoundation)
find_library(CoreMedia CoreMedia)
find_library(NEW_STD_LIBRARY libc++.dylib /usr/lib/)
target_link_libraries(${TARGET_NAME} ${AVFoundation} ${CoreMedia} ${NEW_STD_LIBRARY})
endif (APPLE)
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}")
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)
include_directories(SYSTEM "${LIBOVR_INCLUDE_DIRS}")
add_definitions(-DHAVE_LIBOVR)
include_directories(SYSTEM "${LIBOVR_INCLUDE_DIRS}")
if (APPLE OR UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${LIBOVR_INCLUDE_DIRS}")
endif ()
if (APPLE OR UNIX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -isystem ${LIBOVR_INCLUDE_DIRS}")
endif ()
if (WIN32)
target_link_libraries(${TARGET_NAME} optimized "${LIBOVR_RELEASE_LIBRARIES}" debug "${LIBOVR_DEBUG_LIBRARIES}")
else()
target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}")
endif()
if (WIN32)
target_link_libraries(${TARGET_NAME} optimized "${LIBOVR_RELEASE_LIBRARIES}" debug "${LIBOVR_DEBUG_LIBRARIES}")
else ()
target_link_libraries(${TARGET_NAME} "${LIBOVR_LIBRARIES}")
endif ()
endif (LIBOVR_FOUND AND NOT DISABLE_LIBOVR)
# and with PrioVR library
if (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
add_definitions(-DHAVE_PRIOVR)
include_directories(SYSTEM "${PRIOVR_INCLUDE_DIRS}")
target_link_libraries(${TARGET_NAME} "${PRIOVR_LIBRARIES}")
add_definitions(-DHAVE_PRIOVR)
include_directories(SYSTEM "${PRIOVR_INCLUDE_DIRS}")
target_link_libraries(${TARGET_NAME} "${PRIOVR_LIBRARIES}")
endif (PRIOVR_FOUND AND NOT DISABLE_PRIOVR)
# and with SDL for joysticks
if (SDL_FOUND AND NOT DISABLE_SDL)
add_definitions(-DHAVE_SDL)
include_directories(SYSTEM "${SDL_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} "${SDL_LIBRARY}")
add_definitions(-DHAVE_SDL)
include_directories(SYSTEM "${SDL_INCLUDE_DIR}")
target_link_libraries(${TARGET_NAME} "${SDL_LIBRARY}")
endif (SDL_FOUND AND NOT DISABLE_SDL)
# and with qxmpp for chat
if (QXMPP_FOUND AND NOT DISABLE_QXMPP)
add_definitions(-DHAVE_QXMPP -DQXMPP_STATIC)
include_directories(SYSTEM ${QXMPP_INCLUDE_DIR})
add_definitions(-DHAVE_QXMPP -DQXMPP_STATIC)
include_directories(SYSTEM ${QXMPP_INCLUDE_DIR})
target_link_libraries(${TARGET_NAME} "${QXMPP_LIBRARY}")
target_link_libraries(${TARGET_NAME} "${QXMPP_LIBRARY}")
endif (QXMPP_FOUND AND NOT DISABLE_QXMPP)
# link CoreMIDI if we're using RtMidi
if (RTMIDI_FOUND AND APPLE)
find_library(CoreMIDI CoreMIDI)
add_definitions(-D__MACOSX_CORE__)
target_link_libraries(${TARGET_NAME} ${CoreMIDI})
endif()
# include headers for interface and InterfaceConfig.
include_directories("${PROJECT_SOURCE_DIR}/src" "${PROJECT_BINARY_DIR}/includes")

View file

@ -10,4 +10,7 @@ You can download the Oculus SDK from https://developer.oculusvr.com/ (account cr
You may optionally choose to copy the SDK folders to a location outside the repository (so you can re-use with different checkouts and different projects).
If so our CMake find module expects you to set the ENV variable 'HIFI_LIB_DIR' to a directory containing a subfolder 'oculus' that contains the three folders mentioned above.
2. Clear your build directory, run cmake and build, and you should be all set.
NOTE: For Windows users, you should copy libovr.lib and libovrd.lib from the \oculus\Lib\Win32\VS2010 directory to the \oculus\Lib\Win32\ directory.
2. Clear your build directory, run cmake and build, and you should be all set.

41
interface/external/rtmidi/readme.txt vendored Normal file
View file

@ -0,0 +1,41 @@
Instructions for adding the RtMidi library to Interface
Stephen Birarda, June 30, 2014
1. Download the RtMidi tarball from High Fidelity S3.
http://highfidelity-public.s3.amazonaws.com/dependencies/rtmidi-2.1.0.tar.gz
2. Copy RtMidi.h to externals/rtmidi/include.
3. Copy RtMidi.cpp to externals/rtmidi/src
4. Delete your build directory, run cmake and build, and you should be all set.
=========================
RtMidi: realtime MIDI i/o C++ classes<BR>
Copyright (c) 2003-2014 Gary P. Scavone
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation files
(the "Software"), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge,
publish, distribute, sublicense, and/or sell copies of the Software,
and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
Any person wishing to distribute modifications to the Software is
asked to send the modifications to the original developer so that
they can be incorporated into the canonical version. This is,
however, not a binding provision of this license.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -11,18 +11,114 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
// the texture containing our permutations and normals
uniform sampler2D permutationNormalTexture;
// implementation based on Ken Perlin's Improved Noise reference implementation (orig. in Java) at
// http://mrl.nyu.edu/~perlin/noise/
uniform sampler2D permutationTexture;
// the noise frequency
const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128
const float frequency = 256.0;
//const float frequency = 65536.0; // looks better with current TREE_SCALE, was 1024 when TREE_SCALE was either 512 or 128
// the noise amplitude
const float amplitude = 0.1;
const float amplitude = 0.5;
// the position in model space
varying vec3 position;
// gradient based on gradients from cube edge centers rather than random from texture lookup
float randomEdgeGrad(int hash, vec3 position){
int h = int(mod(hash, 16));
float u = h < 8 ? position.x : position.y;
float v = h < 4 ? position.y : h == 12 || h == 14 ? position.x : position.z;
bool even = mod(hash, 2) == 0;
bool four = mod(hash, 4) == 0;
return (even ? u : -u) + (four ? v : -v);
}
// still have the option to lookup based on texture
float randomTextureGrad(int hash, vec3 position){
float u = float(hash) / 256.0;
vec3 g = -1 + 2 * texture2D(permutationTexture, vec2(u, 0.75)).rgb;
return dot(position, g);
}
float improvedGrad(int hash, vec3 position){
// Untested whether texture lookup is faster than math, uncomment one line or the other to try out
// cube edge gradients versus random spherical gradients sent in texture.
// return randomTextureGrad(hash, position);
return randomEdgeGrad(hash, position);
}
// 5th order fade function to remove 2nd order discontinuties
vec3 fade3(vec3 t){
return t * t * t * (t * (t * 6 - 15) + 10);
}
int permutation(int index){
float u = float(index) / 256.0;
float t = texture2D(permutationTexture, vec2(u, 0.25)).r;
return int(t * 256);
}
float improvedNoise(vec3 position){
int X = int(mod(floor(position.x), 256));
int Y = int(mod(floor(position.y), 256));
int Z = int(mod(floor(position.z), 256));
vec3 fracs = fract(position);
vec3 fades = fade3(fracs);
int A = permutation(X + 0) + Y;
int AA = permutation(A + 0) + Z;
int AB = permutation(A + 1) + Z;
int B = permutation(X + 1) + Y;
int BA = permutation(B + 0) + Z;
int BB = permutation(B + 1) + Z;
float gradAA0 = improvedGrad(permutation(AA + 0), vec3(fracs.x , fracs.y , fracs.z ));
float gradBA0 = improvedGrad(permutation(BA + 0), vec3(fracs.x - 1, fracs.y , fracs.z ));
float gradAB0 = improvedGrad(permutation(AB + 0), vec3(fracs.x , fracs.y - 1, fracs.z ));
float gradBB0 = improvedGrad(permutation(BB + 0), vec3(fracs.x - 1, fracs.y - 1, fracs.z ));
float gradAA1 = improvedGrad(permutation(AA + 1), vec3(fracs.x , fracs.y , fracs.z - 1));
float gradBA1 = improvedGrad(permutation(BA + 1), vec3(fracs.x - 1, fracs.y , fracs.z - 1));
float gradAB1 = improvedGrad(permutation(AB + 1), vec3(fracs.x , fracs.y - 1, fracs.z - 1));
float gradBB1 = improvedGrad(permutation(BB + 1), vec3(fracs.x - 1, fracs.y - 1, fracs.z - 1));
return mix(mix(mix(gradAA0, gradBA0, fades.x), mix(gradAB0, gradBB0, fades.x), fades.y), mix(mix(gradAA1, gradBA1, fades.x), mix(gradAB1, gradBB1, fades.x), fades.y), fades.z);
}
float turbulence(vec3 position, float power){
return (1.0f / power) * improvedNoise(power * position);
}
float turb(vec3 position){
return turbulence(position, 1)
+ turbulence(position, 2),
+ turbulence(position, 4)
+ turbulence(position, 8)
+ turbulence(position, 16)
+ turbulence(position, 32)
+ turbulence(position, 64)
+ turbulence(position, 128)
;
}
void main(void) {
// get noise in range 0 .. 1
float noise = clamp(0.5f + amplitude * turb(position * frequency), 0, 1);
// apply vertex lighting
vec3 color = gl_Color.rgb * vec3(noise, noise, noise);
gl_FragColor = vec4(color, 1);
}
/* old implementation
// returns the gradient at a single corner of our sampling cube
vec3 grad(vec3 location) {
float p1 = texture2D(permutationNormalTexture, vec2(location.x / 256.0, 0.25)).r;
@ -60,7 +156,4 @@ float perlin(vec3 location) {
mix(mix(ffcv, cfcv, params.x), mix(fccv, cccv, params.x), params.y),
params.z);
}
void main(void) {
gl_FragColor = vec4(gl_Color.rgb * (1.0 + amplitude*(perlin(position * frequency) - 1.0)), 1.0);
}
*/

View file

@ -32,7 +32,6 @@
#include <QKeyEvent>
#include <QMenuBar>
#include <QMouseEvent>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkDiskCache>
#include <QOpenGLFramebufferObject>
@ -53,23 +52,25 @@
#include <AccountManager.h>
#include <AudioInjector.h>
#include <LocalVoxelsList.h>
#include <Logging.h>
#include <ModelsScriptingInterface.h>
#include <NetworkAccessManager.h>
#include <OctalCode.h>
#include <OctreeSceneStats.h>
#include <PacketHeaders.h>
#include <ParticlesScriptingInterface.h>
#include <PerfStat.h>
#include <ResourceCache.h>
#include <UserActivityLogger.h>
#include <UUID.h>
#include <OctreeSceneStats.h>
#include <LocalVoxelsList.h>
#include "Application.h"
#include "InterfaceVersion.h"
#include "Menu.h"
#include "ModelUploader.h"
#include "Util.h"
#include "devices/MIDIManager.h"
#include "devices/OculusManager.h"
#include "devices/TV3DManager.h"
#include "renderer/ProgramObject.h"
@ -314,12 +315,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
QString cachePath = QStandardPaths::writableLocation(QStandardPaths::DataLocation);
_networkAccessManager = new QNetworkAccessManager(this);
QNetworkDiskCache* cache = new QNetworkDiskCache(_networkAccessManager);
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkDiskCache* cache = new QNetworkDiskCache(&networkAccessManager);
cache->setCacheDirectory(!cachePath.isEmpty() ? cachePath : "interfaceCache");
_networkAccessManager->setCache(cache);
QMetaObject::invokeMethod(&networkAccessManager, "setCache",
Qt::BlockingQueuedConnection,
Q_ARG(QAbstractNetworkCache*, cache));
ResourceCache::setNetworkAccessManager(_networkAccessManager);
ResourceCache::setRequestLimit(3);
_window->setCentralWidget(_glWidget);
@ -395,21 +397,28 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
OAuthWebViewHandler::addHighFidelityRootCAToSSLConfig();
_trayIcon->show();
#ifdef HAVE_RTMIDI
// setup the MIDIManager
MIDIManager& midiManagerInstance = MIDIManager::getInstance();
midiManagerInstance.openDefaultPort();
#endif
}
Application::~Application() {
qInstallMessageHandler(NULL);
saveSettings();
storeSizeAndPosition();
saveScripts();
int DELAY_TIME = 1000;
UserActivityLogger::getInstance().close(DELAY_TIME);
qInstallMessageHandler(NULL);
// make sure we don't call the idle timer any more
delete idleTimer;
_sharedVoxelSystem.changeTree(new VoxelTree);
saveSettings();
delete _voxelImporter;
// let the avatar mixer know we're out
@ -432,8 +441,6 @@ Application::~Application() {
_particleEditSender.terminate();
_modelEditSender.terminate();
storeSizeAndPosition();
saveScripts();
VoxelTreeElement::removeDeleteHook(&_voxels); // we don't need to do this processing on shutdown
Menu::getInstance()->deleteLater();
@ -441,8 +448,6 @@ Application::~Application() {
_myAvatar = NULL;
delete _glWidget;
AccountManager::getInstance().destroy();
}
void Application::saveSettings() {
@ -590,13 +595,17 @@ void Application::paintGL() {
//Note, the camera distance is set in Camera::setMode() so we dont have to do it here.
_myCamera.setTightness(0.0f); // Camera is directly connected to head without smoothing
_myCamera.setTargetPosition(_myAvatar->getUprightHeadPosition());
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation());
if (OculusManager::isConnected()) {
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation());
} else {
_myCamera.setTargetRotation(_myAvatar->getHead()->getOrientation());
}
} else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
_myCamera.setTightness(0.0f);
_myCamera.setDistance(MIRROR_FULLSCREEN_DISTANCE * _scaleMirror);
_myCamera.setTargetRotation(_myAvatar->getWorldAlignedOrientation() * glm::quat(glm::vec3(0.0f, PI + _rotateMirror, 0.0f)));
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition());
_myCamera.setTargetPosition(_myAvatar->getHead()->calculateAverageEyePosition() + glm::vec3(0, _raiseMirror * _myAvatar->getScale(), 0));
}
// Update camera position
@ -634,6 +643,10 @@ void Application::paintGL() {
//If we aren't using the glow shader, we have to clear the color and depth buffer
if (!glowEnabled) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
} else if (OculusManager::isConnected()) {
//Clear the color buffer to ensure that there isnt any residual color
//Left over from when OR was not connected.
glClear(GL_COLOR_BUFFER_BIT);
}
if (OculusManager::isConnected()) {
@ -645,13 +658,8 @@ void Application::paintGL() {
}
} else if (TV3DManager::isConnected()) {
if (glowEnabled) {
_glowEffect.prepare();
}
TV3DManager::display(whichCamera);
if (glowEnabled) {
_glowEffect.render();
}
} else {
if (glowEnabled) {
@ -1139,7 +1147,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) {
_lastMouseMove = usecTimestampNow();
if (_mouseHidden && showMouse && !OculusManager::isConnected()) {
if (_mouseHidden && showMouse && !OculusManager::isConnected() && !TV3DManager::isConnected()) {
getGLWidget()->setCursor(Qt::ArrowCursor);
_mouseHidden = false;
_seenMouseMove = true;
@ -2183,11 +2191,11 @@ int Application::sendNackPackets() {
_octreeSceneStatsLock.unlock();
continue;
}
OctreeSceneStats& stats = _octreeServerSceneStats[nodeUUID];
// make copy of missing sequence numbers from stats
const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers =
stats.getIncomingOctreeSequenceNumberStats().getMissingSet();
// get sequence number stats of node, prune its missing set, and make a copy of the missing set
SequenceNumberStats& sequenceNumberStats = _octreeServerSceneStats[nodeUUID].getIncomingOctreeSequenceNumberStats();
sequenceNumberStats.pruneMissingSet();
const QSet<OCTREE_PACKET_SEQUENCE> missingSequenceNumbers = sequenceNumberStats.getMissingSet();
_octreeSceneStatsLock.unlock();
@ -3624,6 +3632,10 @@ ScriptEngine* Application::loadScript(const QString& scriptName, bool loadScript
scriptEngine->registerGlobalObject("AnimationCache", &_animationCache);
scriptEngine->registerGlobalObject("AudioReflector", &_audioReflector);
scriptEngine->registerGlobalObject("Account", AccountScriptingInterface::getInstance());
#ifdef HAVE_RTMIDI
scriptEngine->registerGlobalObject("MIDI", &MIDIManager::getInstance());
#endif
QThread* workerThread = new QThread(this);
@ -3802,7 +3814,8 @@ void Application::initAvatarAndViewFrustum() {
void Application::checkVersion() {
QNetworkRequest latestVersionRequest((QUrl(CHECK_VERSION_URL)));
latestVersionRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);
connect(Application::getInstance()->getNetworkAccessManager()->get(latestVersionRequest), SIGNAL(finished()), SLOT(parseVersionXml()));
QNetworkReply* reply = NetworkAccessManager::getInstance().get(latestVersionRequest);
connect(reply, SIGNAL(finished()), SLOT(parseVersionXml()));
}
void Application::parseVersionXml() {

View file

@ -97,7 +97,6 @@ class QActionGroup;
class QGLWidget;
class QKeyEvent;
class QMouseEvent;
class QNetworkAccessManager;
class QSettings;
class QWheelEvent;
@ -236,7 +235,6 @@ public:
void lockOctreeSceneStats() { _octreeSceneStatsLock.lockForRead(); }
void unlockOctreeSceneStats() { _octreeSceneStatsLock.unlock(); }
QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
GeometryCache* getGeometryCache() { return &_geometryCache; }
AnimationCache* getAnimationCache() { return &_animationCache; }
TextureCache* getTextureCache() { return &_textureCache; }
@ -423,7 +421,6 @@ private:
QThread* _nodeThread;
DatagramProcessor _datagramProcessor;
QNetworkAccessManager* _networkAccessManager;
QMutex _settingsMutex;
QSettings* _settings;
int _numChangedSettings;

View file

@ -67,7 +67,7 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
_proceduralAudioOutput(NULL),
_proceduralOutputDevice(NULL),
_inputRingBuffer(0),
_ringBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL),
_ringBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO),
_isStereoInput(false),
_averagedLatency(0.0),
_measuredJitter(0),
@ -93,7 +93,7 @@ Audio::Audio(int16_t initialJitterBufferSamples, QObject* parent) :
_processSpatialAudio(false),
_spatialAudioStart(0),
_spatialAudioFinish(0),
_spatialAudioRingBuffer(NETWORK_BUFFER_LENGTH_BYTES_PER_CHANNEL, true), // random access mode
_spatialAudioRingBuffer(NETWORK_BUFFER_LENGTH_SAMPLES_STEREO, true), // random access mode
_scopeEnabled(false),
_scopeEnabledPause(false),
_scopeInputOffset(0),
@ -1379,8 +1379,10 @@ bool Audio::switchInputToAudioDevice(const QAudioDeviceInfo& inputDeviceInfo) {
// cleanup any previously initialized device
if (_audioInput) {
// The call to stop() causes _inputDevice to be destructed.
// That in turn causes it to be disconnected (see for example
// http://stackoverflow.com/questions/9264750/qt-signals-and-slots-object-disconnect).
_audioInput->stop();
disconnect(_inputDevice);
_inputDevice = NULL;
delete _audioInput;

View file

@ -149,13 +149,19 @@ void DatagramProcessor::processDatagrams() {
break;
}
case PacketTypeVoxelEditNack:
application->_voxelEditSender.processNackPacket(incomingPacket);
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) {
application->_voxelEditSender.processNackPacket(incomingPacket);
}
break;
case PacketTypeParticleEditNack:
application->_particleEditSender.processNackPacket(incomingPacket);
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) {
application->_particleEditSender.processNackPacket(incomingPacket);
}
break;
case PacketTypeModelEditNack:
application->_modelEditSender.processNackPacket(incomingPacket);
if (!Menu::getInstance()->isOptionChecked(MenuOption::DisableNackPackets)) {
application->_modelEditSender.processNackPacket(incomingPacket);
}
break;
default:
nodeList->processNodeData(senderSockAddr, incomingPacket);

View file

@ -91,6 +91,7 @@ Menu::Menu() :
_jsConsole(NULL),
_octreeStatsDialog(NULL),
_lodToolsDialog(NULL),
_userLocationsDialog(NULL),
_maxVoxels(DEFAULT_MAX_VOXELS_PER_SYSTEM),
_voxelSizeScale(DEFAULT_OCTREE_SIZE_SCALE),
_oculusUIAngularSize(DEFAULT_OCULUS_UI_ANGULAR_SIZE),
@ -166,6 +167,11 @@ Menu::Menu() :
Qt::CTRL | Qt::Key_N,
this,
SLOT(nameLocation()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::MyLocations,
Qt::CTRL | Qt::Key_K,
this,
SLOT(toggleLocationList()));
addActionToQMenuAndActionHash(fileMenu,
MenuOption::GoTo,
Qt::Key_At,
@ -1184,6 +1190,17 @@ void Menu::namedLocationCreated(LocationManager::NamedLocationCreateResponse res
msgBox.exec();
}
void Menu::toggleLocationList() {
if (!_userLocationsDialog) {
_userLocationsDialog = new UserLocationsDialog(Application::getInstance()->getWindow());
}
if (_userLocationsDialog->isVisible()) {
_userLocationsDialog->hide();
} else {
_userLocationsDialog->show();
}
}
void Menu::nameLocation() {
// check if user is logged in or show login dialog if not

View file

@ -29,6 +29,7 @@
#include "ui/JSConsole.h"
#include "ui/LoginDialog.h"
#include "ui/ScriptEditorWindow.h"
#include "ui/UserLocationsDialog.h"
const float ADJUST_LOD_DOWN_FPS = 40.0;
const float ADJUST_LOD_UP_FPS = 55.0;
@ -199,6 +200,7 @@ private slots:
void goToDomainDialog();
void goToLocation();
void nameLocation();
void toggleLocationList();
void bandwidthDetailsClosed();
void octreeStatsDetailsClosed();
void lodToolsClosed();
@ -265,6 +267,7 @@ private:
QDialog* _jsConsole;
OctreeStatsDialog* _octreeStatsDialog;
LodToolsDialog* _lodToolsDialog;
UserLocationsDialog* _userLocationsDialog;
int _maxVoxels;
float _voxelSizeScale;
float _oculusUIAngularSize;
@ -395,6 +398,7 @@ namespace MenuOption {
const QString MoveWithLean = "Move with Lean";
const QString MuteAudio = "Mute Microphone";
const QString MuteEnvironment = "Mute Environment";
const QString MyLocations = "My Locations...";
const QString NameLocation = "Name this location";
const QString NewVoxelCullingMode = "New Voxel Culling Mode";
const QString OctreeStats = "Voxel and Particle Statistics";

View file

@ -396,7 +396,7 @@ void ModelUploader::uploadFailed(QNetworkReply::NetworkError errorCode, const QS
void ModelUploader::checkS3() {
qDebug() << "Checking S3 for " << _url;
QNetworkRequest request(_url);
QNetworkReply* reply = _networkAccessManager.head(request);
QNetworkReply* reply = NetworkAccessManager::getInstance().head(request);
connect(reply, SIGNAL(finished()), SLOT(processCheck()));
}

View file

@ -58,7 +58,6 @@ private:
bool _readyToSend;
QHttpMultiPart* _dataMultiPart;
QNetworkAccessManager _networkAccessManager;
int _numberOfChecks;
QTimer _timer;

View file

@ -11,14 +11,15 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QNetworkAccessManager>
#include <QUrl>
#include <QUrlQuery>
#include <QXmlStreamReader>
#include "ScriptsModel.h"
#include <NetworkAccessManager.h>
#include "Menu.h"
#include "ScriptsModel.h"
static const QString S3_URL = "http://highfidelity-public.s3-us-west-1.amazonaws.com";
static const QString PUBLIC_URL = "http://public.highfidelity.io";
@ -30,8 +31,6 @@ static const QString IS_TRUNCATED_NAME = "IsTruncated";
static const QString CONTAINER_NAME = "Contents";
static const QString KEY_NAME = "Key";
static const int SCRIPT_PATH = Qt::UserRole;
ScriptItem::ScriptItem(const QString& filename, const QString& fullPath) :
_filename(filename),
_fullPath(fullPath) {
@ -113,14 +112,15 @@ void ScriptsModel::requestRemoteFiles(QString marker) {
}
url.setQuery(query);
QNetworkAccessManager* accessManager = new QNetworkAccessManager(this);
connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*)));
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request(url);
accessManager->get(request);
QNetworkReply* reply = networkAccessManager.get(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
}
void ScriptsModel::downloadFinished(QNetworkReply* reply) {
void ScriptsModel::downloadFinished() {
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
bool finished = true;
if (reply->error() == QNetworkReply::NoError) {

View file

@ -43,7 +43,7 @@ public:
protected slots:
void updateScriptsLocation(const QString& newPath);
void downloadFinished(QNetworkReply* reply);
void downloadFinished();
void reloadLocalFiles();
void reloadRemoteFiles();

View file

@ -0,0 +1,246 @@
//
// UserLocationsModel.cpp
// interface/src
//
// Created by Ryan Huffman on 06/24/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 <QDebug>
#include <QJsonArray>
#include <QJsonDocument>
#include <QMessageBox>
#include "AccountManager.h"
#include "Application.h"
#include "UserLocationsModel.h"
static const QString PLACES_GET = "/api/v1/places";
static const QString PLACES_UPDATE = "/api/v1/places/%1";
static const QString PLACES_DELETE= "/api/v1/places/%1";
UserLocation::UserLocation(QString id, QString name, QString location) :
_id(id),
_name(name),
_location(location),
_previousName(name),
_updating(false) {
}
void UserLocation::requestRename(const QString& newName) {
if (!_updating && newName.toLower() != _name) {
_updating = true;
JSONCallbackParameters callbackParams(this, "handleRenameResponse", this, "handleRenameError");
QJsonObject jsonNameObject;
jsonNameObject.insert("name", QJsonValue(newName));
QJsonDocument jsonDocument(jsonNameObject);
AccountManager::getInstance().authenticatedRequest(PLACES_UPDATE.arg(_id),
QNetworkAccessManager::PutOperation,
callbackParams,
jsonDocument.toJson());
_previousName = _name;
_name = newName;
emit updated(_name);
}
}
void UserLocation::handleRenameResponse(const QJsonObject& responseData) {
_updating = false;
QJsonValue status = responseData["status"];
if (!status.isUndefined() && status.toString() == "success") {
QString updatedName = responseData["data"].toObject()["name"].toString();
_name = updatedName;
} else {
_name = _previousName;
QString msg = "There was an error renaming location '" + _name + "'";
QJsonValue data = responseData["data"];
if (!data.isUndefined()) {
QJsonValue nameError = data.toObject()["name"];
if (!nameError.isUndefined()) {
msg += ": " + nameError.toString();
}
}
qDebug() << msg;
QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg);
}
emit updated(_name);
}
void UserLocation::handleRenameError(QNetworkReply::NetworkError error, const QString& errorString) {
_updating = false;
QString msg = "There was an error renaming location '" + _name + "': " + errorString;
qDebug() << msg;
QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg);
emit updated(_name);
}
void UserLocation::requestDelete() {
if (!_updating) {
_updating = true;
JSONCallbackParameters callbackParams(this, "handleDeleteResponse", this, "handleDeleteError");
AccountManager::getInstance().authenticatedRequest(PLACES_DELETE.arg(_id),
QNetworkAccessManager::DeleteOperation,
callbackParams);
}
}
void UserLocation::handleDeleteResponse(const QJsonObject& responseData) {
_updating = false;
QJsonValue status = responseData["status"];
if (!status.isUndefined() && status.toString() == "success") {
emit deleted(_name);
} else {
QString msg = "There was an error deleting location '" + _name + "'";
qDebug() << msg;
QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg);
}
}
void UserLocation::handleDeleteError(QNetworkReply::NetworkError error, const QString& errorString) {
_updating = false;
QString msg = "There was an error deleting location '" + _name + "': " + errorString;
qDebug() << msg;
QMessageBox::warning(Application::getInstance()->getWindow(), "Error", msg);
}
UserLocationsModel::UserLocationsModel(QObject* parent) :
QAbstractListModel(parent),
_updating(false) {
refresh();
}
UserLocationsModel::~UserLocationsModel() {
qDeleteAll(_locations);
_locations.clear();
}
void UserLocationsModel::update() {
beginResetModel();
endResetModel();
}
void UserLocationsModel::deleteLocation(const QModelIndex& index) {
UserLocation* location = _locations[index.row()];
location->requestDelete();
}
void UserLocationsModel::renameLocation(const QModelIndex& index, const QString& newName) {
UserLocation* location = _locations[index.row()];
location->requestRename(newName);
}
void UserLocationsModel::refresh() {
if (!_updating) {
beginResetModel();
qDeleteAll(_locations);
_locations.clear();
_updating = true;
endResetModel();
JSONCallbackParameters callbackParams(this, "handleLocationsResponse");
AccountManager::getInstance().authenticatedRequest(PLACES_GET,
QNetworkAccessManager::GetOperation,
callbackParams);
}
}
void UserLocationsModel::handleLocationsResponse(const QJsonObject& responseData) {
_updating = false;
QJsonValue status = responseData["status"];
if (!status.isUndefined() && status.toString() == "success") {
beginResetModel();
QJsonArray locations = responseData["data"].toObject()["places"].toArray();
for (QJsonArray::const_iterator it = locations.constBegin(); it != locations.constEnd(); it++) {
QJsonObject location = (*it).toObject();
QJsonObject address = location["address"].toObject();
UserLocation* userLocation = new UserLocation(location["id"].toString(), location["name"].toString(),
"hifi://" + address["domain"].toString()
+ "/" + address["position"].toString()
+ "/" + address["orientation"].toString());
_locations.append(userLocation);
connect(userLocation, &UserLocation::deleted, this, &UserLocationsModel::removeLocation);
connect(userLocation, &UserLocation::updated, this, &UserLocationsModel::update);
}
endResetModel();
} else {
qDebug() << "Error loading location data";
}
}
void UserLocationsModel::removeLocation(const QString& name) {
beginResetModel();
for (QList<UserLocation*>::iterator it = _locations.begin(); it != _locations.end(); it++) {
if ((*it)->name() == name) {
_locations.erase(it);
break;
}
}
endResetModel();
}
int UserLocationsModel::rowCount(const QModelIndex& parent) const {
if (parent.isValid()) {
return 0;
}
if (_updating) {
return 1;
}
return _locations.length();
}
QVariant UserLocationsModel::data(const QModelIndex& index, int role) const {
if (role == Qt::DisplayRole) {
if (_updating) {
return QVariant("Updating...");
} else if (index.row() > _locations.length()) {
return QVariant();
} else if (index.column() == NameColumn) {
return _locations[index.row()]->name();
} else if (index.column() == LocationColumn) {
return QVariant(_locations[index.row()]->location());
}
}
return QVariant();
}
QVariant UserLocationsModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
switch (section) {
case NameColumn: return "Name";
case LocationColumn: return "Location";
default: return QVariant();
}
}
return QVariant();
}
Qt::ItemFlags UserLocationsModel::flags(const QModelIndex& index) const {
if (index.row() < _locations.length()) {
UserLocation* ul = _locations[index.row()];
if (ul->isUpdating()) {
return Qt::NoItemFlags;
}
}
return QAbstractListModel::flags(index);
}

View file

@ -0,0 +1,82 @@
//
// UserLocationsModel.h
// interface/src
//
// Created by Ryan Huffman on 06/24/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_UserLocationsModel_h
#define hifi_UserLocationsModel_h
#include <QAbstractListModel>
#include <QModelIndex>
#include <QVariant>
class UserLocation : public QObject {
Q_OBJECT
public:
UserLocation(QString id, QString name, QString location);
bool isUpdating() { return _updating; }
void requestRename(const QString& newName);
void requestDelete();
QString id() { return _id; }
QString name() { return _name; }
QString location() { return _location; }
public slots:
void handleRenameResponse(const QJsonObject& responseData);
void handleRenameError(QNetworkReply::NetworkError error, const QString& errorString);
void handleDeleteResponse(const QJsonObject& responseData);
void handleDeleteError(QNetworkReply::NetworkError error, const QString& errorString);
signals:
void updated(const QString& name);
void deleted(const QString& name);
private:
QString _id;
QString _name;
QString _location;
QString _previousName;
bool _updating;
};
class UserLocationsModel : public QAbstractListModel {
Q_OBJECT
public:
UserLocationsModel(QObject* parent = NULL);
~UserLocationsModel();
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const;
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const;
virtual int columnCount(const QModelIndex& parent = QModelIndex()) const { return 2; };
virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
virtual Qt::ItemFlags flags(const QModelIndex& index) const;
void deleteLocation(const QModelIndex& index);
void renameLocation(const QModelIndex& index, const QString& newName);
enum Columns {
NameColumn = 0,
LocationColumn
};
public slots:
void refresh();
void update();
void handleLocationsResponse(const QJsonObject& responseData);
void removeLocation(const QString& name);
private:
bool _updating;
QList<UserLocation*> _locations;
};
#endif // hifi_UserLocationsModel_h

View file

@ -554,6 +554,40 @@ void MyAvatar::stopAnimation(const QString& url) {
}
}
AnimationDetails MyAvatar::getAnimationDetailsByRole(const QString& role) {
AnimationDetails result;
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "getAnimationDetailsByRole", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(AnimationDetails, result),
Q_ARG(const QString&, role));
return result;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
if (handle->getRole() == role) {
result = handle->getAnimationDetails();
break;
}
}
return result;
}
AnimationDetails MyAvatar::getAnimationDetails(const QString& url) {
AnimationDetails result;
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "getAnimationDetails", Qt::BlockingQueuedConnection,
Q_RETURN_ARG(AnimationDetails, result),
Q_ARG(const QString&, url));
return result;
}
foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) {
if (handle->getURL() == url) {
result = handle->getAnimationDetails();
break;
}
}
return result;
}
void MyAvatar::saveData(QSettings* settings) {
settings->beginGroup("Avatar");

View file

@ -83,6 +83,9 @@ public:
/// Stops an animation identified by its role.
Q_INVOKABLE void stopAnimationByRole(const QString& role);
Q_INVOKABLE AnimationDetails getAnimationDetailsByRole(const QString& role);
Q_INVOKABLE AnimationDetails getAnimationDetails(const QString& url);
// get/set avatar data
void saveData(QSettings* settings);

View file

@ -0,0 +1,68 @@
//
// MIDIManager.cpp
//
//
// Created by Stephen Birarda on 2014-06-30.
// Copyright 2014 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtCore/QDebug>
#include "MIDIManager.h"
#ifdef HAVE_RTMIDI
MIDIManager& MIDIManager::getInstance() {
static MIDIManager sharedInstance;
return sharedInstance;
}
void MIDIManager::midiCallback(double deltaTime, std::vector<unsigned char>* message, void* userData) {
MIDIEvent callbackEvent;
callbackEvent.deltaTime = deltaTime;
callbackEvent.type = message->at(0);
if (message->size() > 1) {
callbackEvent.data1 = message->at(1);
}
if (message->size() > 2) {
callbackEvent.data2 = message->at(2);
}
emit getInstance().midiEvent(callbackEvent);
}
MIDIManager::~MIDIManager() {
delete _midiInput;
}
const int DEFAULT_MIDI_PORT = 0;
void MIDIManager::openDefaultPort() {
if (!_midiInput) {
_midiInput = new RtMidiIn();
if (_midiInput->getPortCount() > 0) {
qDebug() << "MIDIManager opening port" << DEFAULT_MIDI_PORT;
_midiInput->openPort(DEFAULT_MIDI_PORT);
// don't ignore sysex, timing, or active sensing messages
_midiInput->ignoreTypes(false, false, false);
_midiInput->setCallback(&MIDIManager::midiCallback);
} else {
qDebug() << "MIDIManager openDefaultPort called but there are no ports available.";
delete _midiInput;
_midiInput = NULL;
}
}
}
#endif

View file

@ -0,0 +1,56 @@
//
// MIDIManager.h
// interface/src/devices
//
// Created by Stephen Birarda on 2014-06-30.
// 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_MIDIManager_h
#define hifi_MIDIManager_h
#ifdef HAVE_RTMIDI
#include <QtCore/QObject>
#include <QtScript/QScriptEngine>
#include <MIDIEvent.h>
#include <RtMidi.h>
class MIDIManager : public QObject {
Q_OBJECT
Q_PROPERTY(unsigned int NoteOn READ NoteOn)
Q_PROPERTY(unsigned int NoteOff READ NoteOff)
Q_PROPERTY(unsigned int ModWheel READ ModWheel)
Q_PROPERTY(unsigned int PitchWheel READ PitchWheel)
public:
static MIDIManager& getInstance();
static void midiCallback(double deltaTime, std::vector<unsigned char>* message, void* userData);
~MIDIManager();
void openDefaultPort();
bool hasDevice() const { return !!_midiInput; }
public slots:
unsigned int NoteOn() const { return 144; }
unsigned int NoteOff() const { return 128; }
unsigned int ModWheel() const { return 176; }
unsigned int PitchWheel() const { return 224; }
signals:
void midiEvent(const MIDIEvent& event);
private:
RtMidiIn* _midiInput;
};
#endif
#endif // hifi_MIDIManager_h

View file

@ -266,7 +266,7 @@ void OculusManager::display(const glm::quat &bodyOrientation, const glm::vec3 &p
ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay();
// We only need to render the overlays to a texture once, then we just render the texture as a quad
// We only need to render the overlays to a texture once, then we just render the texture on the hemisphere
// PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay()
applicationOverlay.renderOverlay(true);
const bool displayOverlays = Menu::getInstance()->isOptionChecked(MenuOption::DisplayOculusOverlays);

View file

@ -93,6 +93,18 @@ void TV3DManager::display(Camera& whichCamera) {
int portalW = Application::getInstance()->getGLWidget()->width() / 2;
int portalH = Application::getInstance()->getGLWidget()->height();
const bool glowEnabled = Menu::getInstance()->isOptionChecked(MenuOption::EnableGlowEffect);
ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay();
// We only need to render the overlays to a texture once, then we just render the texture as a quad
// PrioVR will only work if renderOverlay is called, calibration is connected to Application::renderingOverlay()
applicationOverlay.renderOverlay(true);
if (glowEnabled) {
Application::getInstance()->getGlowEffect()->prepare();
}
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_SCISSOR_TEST);
@ -102,13 +114,21 @@ void TV3DManager::display(Camera& whichCamera) {
glPushMatrix();
{
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); // reset projection matrix
glFrustum(_leftEye.left, _leftEye.right, _leftEye.bottom, _leftEye.top, nearZ, farZ); // set left view frustum
GLfloat p[4][4];
glGetFloatv(GL_PROJECTION_MATRIX, &(p[0][0]));
GLfloat cotangent = p[1][1];
GLfloat fov = atan(1.0f / cotangent);
glTranslatef(_leftEye.modelTranslation, 0.0, 0.0); // translate to cancel parallax
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
Application::getInstance()->displaySide(whichCamera);
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
}
glPopMatrix();
glDisable(GL_SCISSOR_TEST);
@ -124,14 +144,25 @@ void TV3DManager::display(Camera& whichCamera) {
glMatrixMode(GL_PROJECTION);
glLoadIdentity(); // reset projection matrix
glFrustum(_rightEye.left, _rightEye.right, _rightEye.bottom, _rightEye.top, nearZ, farZ); // set left view frustum
GLfloat p[4][4];
glGetFloatv(GL_PROJECTION_MATRIX, &(p[0][0]));
GLfloat cotangent = p[1][1];
GLfloat fov = atan(1.0f / cotangent);
glTranslatef(_rightEye.modelTranslation, 0.0, 0.0); // translate to cancel parallax
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
Application::getInstance()->displaySide(whichCamera);
applicationOverlay.displayOverlayTexture3DTV(whichCamera, _aspect, fov);
}
glPopMatrix();
glDisable(GL_SCISSOR_TEST);
// reset the viewport to how we started
glViewport(0, 0, Application::getInstance()->getGLWidget()->width(), Application::getInstance()->getGLWidget()->height());
if (glowEnabled) {
Application::getInstance()->getGlowEffect()->render();
}
}

View file

@ -11,6 +11,8 @@
#include <glm/gtx/norm.hpp>
#include <AngularConstraint.h>
//#include <GeometryUtil.h>
#include <SharedUtil.h>
#include "JointState.h"
@ -18,7 +20,48 @@
JointState::JointState() :
_animationPriority(0.0f),
_fbxJoint(NULL),
_isConstrained(false) {
_constraint(NULL) {
}
JointState::JointState(const JointState& other) : _constraint(NULL) {
_transform = other._transform;
_rotation = other._rotation;
_rotationInParentFrame = other._rotationInParentFrame;
_animationPriority = other._animationPriority;
_fbxJoint = other._fbxJoint;
// DO NOT copy _constraint
}
JointState::~JointState() {
delete _constraint;
_constraint = NULL;
if (_constraint) {
delete _constraint;
_constraint = NULL;
}
}
void JointState::setFBXJoint(const FBXJoint* joint) {
assert(joint != NULL);
_rotationInParentFrame = joint->rotation;
// NOTE: JointState does not own the FBXJoint to which it points.
_fbxJoint = joint;
if (_constraint) {
delete _constraint;
_constraint = NULL;
}
}
void JointState::updateConstraint() {
if (_constraint) {
delete _constraint;
_constraint = NULL;
}
if (glm::distance2(glm::vec3(-PI), _fbxJoint->rotationMin) > EPSILON ||
glm::distance2(glm::vec3(PI), _fbxJoint->rotationMax) > EPSILON ) {
// this joint has rotation constraints
_constraint = AngularConstraint::newAngularConstraint(_fbxJoint->rotationMin, _fbxJoint->rotationMax);
}
}
void JointState::copyState(const JointState& state) {
@ -30,18 +73,7 @@ void JointState::copyState(const JointState& state) {
_visibleTransform = state._visibleTransform;
_visibleRotation = extractRotation(_visibleTransform);
_visibleRotationInParentFrame = state._visibleRotationInParentFrame;
// DO NOT copy _fbxJoint
}
void JointState::setFBXJoint(const FBXJoint* joint) {
assert(joint != NULL);
_rotationInParentFrame = joint->rotation;
// NOTE: JointState does not own the FBXJoint to which it points.
_fbxJoint = joint;
// precompute whether there are any constraints or not
float distanceMin = glm::distance(_fbxJoint->rotationMin, glm::vec3(-PI));
float distanceMax = glm::distance(_fbxJoint->rotationMax, glm::vec3(PI));
_isConstrained = distanceMin > EPSILON || distanceMax > EPSILON;
// DO NOT copy _fbxJoint or _constraint
}
void JointState::computeTransform(const glm::mat4& parentTransform) {
@ -70,11 +102,15 @@ void JointState::restoreRotation(float fraction, float priority) {
}
}
void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority) {
void JointState::setRotationFromBindFrame(const glm::quat& rotation, float priority, bool constrain) {
// rotation is from bind- to model-frame
assert(_fbxJoint != NULL);
if (priority >= _animationPriority) {
setRotationInParentFrame(_rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation));
glm::quat targetRotation = _rotationInParentFrame * glm::inverse(_rotation) * rotation * glm::inverse(_fbxJoint->inverseBindRotation);
if (constrain && _constraint) {
_constraint->softClamp(targetRotation, _rotationInParentFrame, 0.5f);
}
setRotationInParentFrame(targetRotation);
_animationPriority = priority;
}
}
@ -99,7 +135,7 @@ void JointState::applyRotationDelta(const glm::quat& delta, bool constrain, floa
return;
}
_animationPriority = priority;
if (!constrain || !_isConstrained) {
if (!constrain || _constraint == NULL) {
// no constraints
_rotationInParentFrame = _rotationInParentFrame * glm::inverse(_rotation) * delta * _rotation;
_rotation = delta * _rotation;
@ -122,10 +158,12 @@ void JointState::mixRotationDelta(const glm::quat& delta, float mixFactor, float
if (mixFactor > 0.0f && mixFactor <= 1.0f) {
targetRotation = safeMix(targetRotation, _fbxJoint->rotation, mixFactor);
}
if (_constraint) {
_constraint->softClamp(targetRotation, _rotationInParentFrame, 0.5f);
}
setRotationInParentFrame(targetRotation);
}
glm::quat JointState::computeParentRotation() const {
// R = Rp * Rpre * r * Rpost
// Rp = R * (Rpre * r * Rpost)^

View file

@ -18,15 +18,19 @@
#include <FBXReader.h>
class AngularConstraint;
class JointState {
public:
JointState();
void copyState(const JointState& state);
JointState(const JointState& other);
~JointState();
void setFBXJoint(const FBXJoint* joint);
const FBXJoint& getFBXJoint() const { return *_fbxJoint; }
void updateConstraint();
void copyState(const JointState& state);
void computeTransform(const glm::mat4& parentTransform);
@ -64,7 +68,7 @@ public:
/// \param rotation is from bind- to model-frame
/// computes and sets new _rotationInParentFrame
/// NOTE: the JointState's model-frame transform/rotation are NOT updated!
void setRotationFromBindFrame(const glm::quat& rotation, float priority);
void setRotationFromBindFrame(const glm::quat& rotation, float priority, bool constrain = false);
void setRotationInParentFrame(const glm::quat& targetRotation);
const glm::quat& getRotationInParentFrame() const { return _rotationInParentFrame; }
@ -95,7 +99,7 @@ private:
glm::quat _visibleRotationInParentFrame;
const FBXJoint* _fbxJoint; // JointState does NOT own its FBXJoint
bool _isConstrained;
AngularConstraint* _constraint; // JointState owns its AngularConstraint
};
#endif // hifi_JointState_h

View file

@ -561,8 +561,6 @@ bool Model::updateGeometry() {
void Model::setJointStates(QVector<JointState> states) {
_jointStates = states;
// compute an approximate bounding radius for broadphase collision queries
// against PhysicsSimulation boundaries
int numJoints = _jointStates.size();
float radius = 0.0f;
for (int i = 0; i < numJoints; ++i) {
@ -570,6 +568,7 @@ void Model::setJointStates(QVector<JointState> states) {
if (distance > radius) {
radius = distance;
}
_jointStates[i].updateConstraint();
}
for (int i = 0; i < _jointStates.size(); i++) {
_jointStates[i].slaveVisibleTransform();
@ -1159,14 +1158,9 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm:
}
glm::quat deltaRotation = rotationBetween(leverArm, targetPosition - pivot);
/* DON'T REMOVE! This code provides the gravitational effect on the IK solution.
* It is commented out for the moment because we're blending the IK solution with
* the default pose which provides similar stability, but we might want to use
* gravity again later.
// We want to mix the shortest rotation with one that will pull the system down with gravity.
// So we compute a simplified center of mass, where each joint has a mass of 1.0 and we don't
// bother averaging it because we only need direction.
// We want to mix the shortest rotation with one that will pull the system down with gravity
// so that limbs don't float unrealistically. To do this we compute a simplified center of mass
// where each joint has unit mass and we don't bother averaging it because we only need direction.
if (j > 1) {
glm::vec3 centerOfMass(0.0f);
@ -1188,11 +1182,9 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm:
}
deltaRotation = safeMix(deltaRotation, gravityDelta, mixFactor);
}
*/
// Apply the rotation, but use mixRotationDelta() which blends a bit of the default pose
// at in the process. This provides stability to the IK solution and removes the necessity
// for the gravity effect.
// at in the process. This provides stability to the IK solution for most models.
glm::quat oldNextRotation = nextState.getRotation();
float mixFactor = 0.03f;
nextState.mixRotationDelta(deltaRotation, mixFactor, priority);
@ -1217,7 +1209,7 @@ void Model::inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm:
} while (numIterations < MAX_ITERATION_COUNT && distanceToGo < ACCEPTABLE_IK_ERROR);
// set final rotation of the end joint
endState.setRotationFromBindFrame(targetRotation, priority);
endState.setRotationFromBindFrame(targetRotation, priority, true);
_shapesAreDirty = !_shapes.isEmpty();
}
@ -1709,6 +1701,13 @@ AnimationHandle::AnimationHandle(Model* model) :
_running(false) {
}
AnimationDetails AnimationHandle::getAnimationDetails() const {
AnimationDetails details(_role, _url, _fps, _priority, _loop, _hold,
_startAutomatically, _firstFrame, _lastFrame, _running, _frameIndex);
return details;
}
void AnimationHandle::simulate(float deltaTime) {
_frameIndex += deltaTime * _fps;

View file

@ -354,6 +354,11 @@ public:
void setRunning(bool running);
bool isRunning() const { return _running; }
void setFrameIndex(float frameIndex) { _frameIndex = glm::clamp(_frameIndex, _firstFrame, _lastFrame); }
float getFrameIndex() const { return _frameIndex; }
AnimationDetails getAnimationDetails() const;
signals:
void runningChanged(bool running);

View file

@ -85,6 +85,33 @@ void TextureCache::setFrameBufferSize(QSize frameBufferSize) {
}
}
// use fixed table of permutations. Could also make ordered list programmatically
// and then shuffle algorithm. For testing, this ensures consistent behavior in each run.
// this list taken from Ken Perlin's Improved Noise reference implementation (orig. in Java) at
// http://mrl.nyu.edu/~perlin/noise/
const int permutation[256] =
{
151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225,
140, 36, 103, 30, 69, 142, 8, 99, 37, 240, 21, 10, 23, 190, 6, 148,
247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 203, 117, 35, 11, 32,
57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168, 68, 175,
74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122,
60, 211, 133, 230, 220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,
65, 25, 63, 161, 1, 216, 80, 73, 209, 76, 132, 187, 208, 89, 18, 169,
200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 186, 3, 64,
52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212,
207, 206, 59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213,
119, 248, 152, 2, 44, 154, 163, 70, 221, 153, 101, 155, 167, 43, 172, 9,
129, 22, 39, 253, 19, 98, 108, 110, 79, 113, 224, 232, 178, 185, 112, 104,
218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,
81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214, 31, 181, 199, 106, 157,
184, 84, 204, 176, 115, 121, 50, 45, 127, 4, 150, 254, 138, 236, 205, 93,
222, 114, 67, 29, 24, 72, 243, 141, 128, 195, 78, 66, 215, 61, 156, 180
};
#define USE_CHRIS_NOISE 1
GLuint TextureCache::getPermutationNormalTextureID() {
if (_permutationNormalTextureID == 0) {
glGenTextures(1, &_permutationNormalTextureID);
@ -92,10 +119,17 @@ GLuint TextureCache::getPermutationNormalTextureID() {
// the first line consists of random permutation offsets
unsigned char data[256 * 2 * 3];
#if (USE_CHRIS_NOISE==1)
for (int i = 0; i < 256; i++) {
data[3*i+0] = permutation[i];
data[3*i+1] = permutation[i];
data[3*i+2] = permutation[i];
#else
for (int i = 0; i < 256 * 3; i++) {
data[i] = rand() % 256;
#endif
}
// the next, random unit normals
for (int i = 256 * 3; i < 256 * 3 * 2; i += 3) {
glm::vec3 randvec = glm::sphericalRand(1.0f);
data[i] = ((randvec.x + 1.0f) / 2.0f) * 255.0f;
@ -105,7 +139,6 @@ GLuint TextureCache::getPermutationNormalTextureID() {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 256, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glBindTexture(GL_TEXTURE_2D, 0);
}
return _permutationNormalTextureID;

View file

@ -205,7 +205,7 @@ void ApplicationOverlay::getClickLocation(int &x, int &y) const {
}
}
// Draws the FBO texture for Oculus rift. TODO: Draw a curved texture instead of plane.
// Draws the FBO texture for Oculus rift.
void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
if (_alpha == 0.0f) {
@ -292,6 +292,107 @@ void ApplicationOverlay::displayOverlayTextureOculus(Camera& whichCamera) {
}
// Draws the FBO texture for 3DTV.
void ApplicationOverlay::displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov) {
if (_alpha == 0.0f) {
return;
}
Application* application = Application::getInstance();
MyAvatar* myAvatar = application->getAvatar();
const glm::vec3& viewMatrixTranslation = application->getViewMatrixTranslation();
glActiveTexture(GL_TEXTURE0);
glEnable(GL_BLEND);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE);
glBindTexture(GL_TEXTURE_2D, getFramebufferObject()->texture());
glEnable(GL_DEPTH_TEST);
glDisable(GL_LIGHTING);
glEnable(GL_TEXTURE_2D);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
// Transform to world space
glm::quat rotation = whichCamera.getRotation();
glm::vec3 axis2 = glm::axis(rotation);
glRotatef(-glm::degrees(glm::angle(rotation)), axis2.x, axis2.y, axis2.z);
glTranslatef(viewMatrixTranslation.x, viewMatrixTranslation.y, viewMatrixTranslation.z);
// Translate to the front of the camera
glm::vec3 pos = whichCamera.getPosition();
glm::quat rot = myAvatar->getOrientation();
glm::vec3 axis = glm::axis(rot);
glTranslatef(pos.x, pos.y, pos.z);
glRotatef(glm::degrees(glm::angle(rot)), axis.x, axis.y, axis.z);
glColor4f(1.0f, 1.0f, 1.0f, _alpha);
//Render
// fov -= RADIANS_PER_DEGREE * 2.5f; //reduce by 5 degrees so it fits in the view
const GLfloat distance = 1.0f;
const GLfloat halfQuadHeight = distance * tan(fov);
const GLfloat halfQuadWidth = halfQuadHeight * aspectRatio;
const GLfloat quadWidth = halfQuadWidth * 2.0f;
const GLfloat quadHeight = halfQuadHeight * 2.0f;
GLfloat x = -halfQuadWidth;
GLfloat y = -halfQuadHeight;
glDisable(GL_DEPTH_TEST);
glBegin(GL_QUADS);
glTexCoord2f(0.0f, 1.0f); glVertex3f(x, y + quadHeight, -distance);
glTexCoord2f(1.0f, 1.0f); glVertex3f(x + quadWidth, y + quadHeight, -distance);
glTexCoord2f(1.0f, 0.0f); glVertex3f(x + quadWidth, y, -distance);
glTexCoord2f(0.0f, 0.0f); glVertex3f(x, y, -distance);
glEnd();
if (_crosshairTexture == 0) {
_crosshairTexture = Application::getInstance()->getGLWidget()->bindTexture(QImage(Application::resourcesPath() + "images/sixense-reticle.png"));
}
//draw the mouse pointer
glBindTexture(GL_TEXTURE_2D, _crosshairTexture);
const float reticleSize = 40.0f / application->getGLWidget()->width() * quadWidth;
x -= reticleSize / 2.0f;
y += reticleSize / 2.0f;
const float mouseX = (application->getMouseX() / (float)application->getGLWidget()->width()) * quadWidth;
const float mouseY = (1.0 - (application->getMouseY() / (float)application->getGLWidget()->height())) * quadHeight;
glBegin(GL_QUADS);
glColor3f(RETICLE_COLOR[0], RETICLE_COLOR[1], RETICLE_COLOR[2]);
glTexCoord2d(0.0f, 0.0f); glVertex3f(x + mouseX, y + mouseY, -distance);
glTexCoord2d(1.0f, 0.0f); glVertex3f(x + mouseX + reticleSize, y + mouseY, -distance);
glTexCoord2d(1.0f, 1.0f); glVertex3f(x + mouseX + reticleSize, y + mouseY - reticleSize, -distance);
glTexCoord2d(0.0f, 1.0f); glVertex3f(x + mouseX, y + mouseY - reticleSize, -distance);
glEnd();
glEnable(GL_DEPTH_TEST);
glPopMatrix();
glDepthMask(GL_TRUE);
glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);
glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_CONSTANT_ALPHA, GL_ONE);
glEnable(GL_LIGHTING);
glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
}
//Renders optional pointers
void ApplicationOverlay::renderPointers() {
Application* application = Application::getInstance();

View file

@ -29,6 +29,7 @@ public:
void renderOverlay(bool renderToTexture = false);
void displayOverlayTexture();
void displayOverlayTextureOculus(Camera& whichCamera);
void displayOverlayTexture3DTV(Camera& whichCamera, float aspectRatio, float fov);
void computeOculusPickRay(float x, float y, glm::vec3& direction) const;
void getClickLocation(int &x, int &y) const;

View file

@ -14,10 +14,11 @@
#include <QGridLayout>
#include <QHeaderView>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QUrl>
#include <QXmlStreamReader>
#include <NetworkAccessManager.h>
#include "Application.h"
#include "ModelsBrowser.h"
@ -210,10 +211,10 @@ void ModelHandler::update() {
}
for (int i = 0; i < _model.rowCount(); ++i) {
QUrl url(_model.item(i,0)->data(Qt::UserRole).toString());
QNetworkAccessManager* accessManager = new QNetworkAccessManager(this);
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request(url);
accessManager->head(request);
connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*)));
QNetworkReply* reply = networkAccessManager.head(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
}
_lock.unlock();
}
@ -233,7 +234,8 @@ void ModelHandler::exit() {
_lock.unlock();
}
void ModelHandler::downloadFinished(QNetworkReply* reply) {
void ModelHandler::downloadFinished() {
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
QByteArray data = reply->readAll();
if (!data.isEmpty()) {
@ -261,10 +263,10 @@ void ModelHandler::queryNewFiles(QString marker) {
// Download
url.setQuery(query);
QNetworkAccessManager* accessManager = new QNetworkAccessManager(this);
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request(url);
accessManager->get(request);
connect(accessManager, SIGNAL(finished(QNetworkReply*)), SLOT(downloadFinished(QNetworkReply*)));
QNetworkReply* reply = networkAccessManager.get(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
}

View file

@ -43,7 +43,7 @@ public slots:
void exit();
private slots:
void downloadFinished(QNetworkReply* reply);
void downloadFinished();
private:
bool _initiateExit;

View file

@ -148,8 +148,8 @@ void ScriptEditorWidget::loadFile(const QString& scriptPath) {
disconnect(_scriptEngine, &ScriptEngine::finished, this, &ScriptEditorWidget::onScriptFinished);
}
} else {
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
QNetworkReply* reply = networkManager->get(QNetworkRequest(url));
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
qDebug() << "Downloading included script at" << scriptPath;
QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);

View file

@ -40,8 +40,7 @@ Q_DECLARE_METATYPE(QNetworkAccessManager::Operation)
SnapshotShareDialog::SnapshotShareDialog(QString fileName, QWidget* parent) :
QDialog(parent),
_fileName(fileName),
_networkAccessManager(NULL)
_fileName(fileName)
{
setAttribute(Qt::WA_DeleteOnClose);
@ -92,10 +91,6 @@ void SnapshotShareDialog::uploadSnapshot() {
return;
}
if (!_networkAccessManager) {
_networkAccessManager = new QNetworkAccessManager(this);
}
QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
QHttpPart apiKeyPart;
@ -118,9 +113,7 @@ void SnapshotShareDialog::uploadSnapshot() {
QUrl url(FORUM_UPLOADS_URL);
QNetworkRequest request(url);
QNetworkReply* reply = _networkAccessManager->post(request, multiPart);
QNetworkReply* reply = NetworkAccessManager::getInstance().post(request, multiPart);
connect(reply, &QNetworkReply::finished, this, &SnapshotShareDialog::uploadRequestFinished);
QEventLoop loop;
@ -129,11 +122,6 @@ void SnapshotShareDialog::uploadSnapshot() {
}
void SnapshotShareDialog::sendForumPost(QString snapshotPath) {
if (!_networkAccessManager) {
_networkAccessManager = new QNetworkAccessManager(this);
}
// post to Discourse forum
QNetworkRequest request;
QUrl forumUrl(FORUM_POST_URL);
@ -148,7 +136,7 @@ void SnapshotShareDialog::sendForumPost(QString snapshotPath) {
request.setUrl(forumUrl);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* requestReply = _networkAccessManager->post(request, postData);
QNetworkReply* requestReply = NetworkAccessManager::getInstance().post(request, postData);
connect(requestReply, &QNetworkReply::finished, this, &SnapshotShareDialog::postRequestFinished);
QEventLoop loop;

View file

@ -14,7 +14,6 @@
#include "ui_shareSnapshot.h"
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QUrl>
@ -26,7 +25,6 @@ public:
private:
QString _fileName;
QNetworkAccessManager* _networkAccessManager;
Ui_SnapshotShareDialog _ui;
void uploadSnapshot();

View file

@ -0,0 +1,77 @@
//
// UserLocationsDialog.cpp
// interface/src/ui
//
// Created by Ryan Huffman on 06/24/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 <QDebug>
#include <QInputDialog>
#include <QPushButton>
#include "Menu.h"
#include "UserLocationsDialog.h"
UserLocationsDialog::UserLocationsDialog(QWidget* parent) :
QDialog(parent),
_ui(),
_proxyModel(this),
_userLocationsModel(this) {
_ui.setupUi(this);
_proxyModel.setSourceModel(&_userLocationsModel);
_proxyModel.setDynamicSortFilter(true);
_ui.locationsTreeView->setModel(&_proxyModel);
_ui.locationsTreeView->setSortingEnabled(true);
_ui.locationsTreeView->sortByColumn(UserLocationsModel::NameColumn, Qt::AscendingOrder);
connect(_ui.locationsTreeView->selectionModel(), &QItemSelectionModel::selectionChanged,
this, &UserLocationsDialog::updateEnabled);
connect(&_userLocationsModel, &UserLocationsModel::modelReset, this, &UserLocationsDialog::updateEnabled);
connect(&_userLocationsModel, &UserLocationsModel::modelReset, &_proxyModel, &QSortFilterProxyModel::invalidate);
connect(_ui.locationsTreeView, &QTreeView::doubleClicked, this, &UserLocationsDialog::goToModelIndex);
connect(_ui.deleteButton, &QPushButton::clicked, this, &UserLocationsDialog::deleteSelection);
connect(_ui.renameButton, &QPushButton::clicked, this, &UserLocationsDialog::renameSelection);
connect(_ui.refreshButton, &QPushButton::clicked, &_userLocationsModel, &UserLocationsModel::refresh);
this->setWindowTitle("My Locations");
}
void UserLocationsDialog::updateEnabled() {
bool enabled = _ui.locationsTreeView->selectionModel()->hasSelection();
_ui.renameButton->setEnabled(enabled);
_ui.deleteButton->setEnabled(enabled);
}
void UserLocationsDialog::goToModelIndex(const QModelIndex& index) {
QVariant location = _proxyModel.data(index.sibling(index.row(), UserLocationsModel::LocationColumn));
Menu::getInstance()->goToURL(location.toString());
}
void UserLocationsDialog::deleteSelection() {
QModelIndex selection = _ui.locationsTreeView->selectionModel()->currentIndex();
selection = _proxyModel.mapToSource(selection);
if (selection.isValid()) {
_userLocationsModel.deleteLocation(selection);
}
}
void UserLocationsDialog::renameSelection() {
QModelIndex selection = _ui.locationsTreeView->selectionModel()->currentIndex();
selection = _proxyModel.mapToSource(selection);
if (selection.isValid()) {
bool ok;
QString name = _userLocationsModel.data(selection.sibling(selection.row(), UserLocationsModel::NameColumn)).toString();
QString newName = QInputDialog::getText(this, "Rename '" + name + "'", "Set name to:", QLineEdit::Normal, name, &ok);
if (ok && !newName.isEmpty()) {
_userLocationsModel.renameLocation(selection, newName);
}
}
}

View file

@ -0,0 +1,35 @@
//
// UserLocationsDialog.h
// interface/src/ui
//
// Created by Ryan Huffman on 06/24/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_UserLocationsDialog_h
#define hifi_UserLocationsDialog_h
#include "ui_userLocationsDialog.h"
#include "UserLocationsModel.h"
class UserLocationsDialog : public QDialog {
Q_OBJECT
public:
UserLocationsDialog(QWidget* parent = NULL);
protected slots:
void updateEnabled();
void goToModelIndex(const QModelIndex& index);
void deleteSelection();
void renameSelection();
private:
Ui::UserLocationsDialog _ui;
QSortFilterProxyModel _proxyModel;
UserLocationsModel _userLocationsModel;
};
#endif // hifi_UserLocationsDialog_h

View file

@ -14,8 +14,7 @@
#include "BillboardOverlay.h"
BillboardOverlay::BillboardOverlay()
: _manager(NULL),
_scale(1.0f),
: _scale(1.0f),
_isFacingAvatar(true) {
}
@ -119,18 +118,13 @@ void BillboardOverlay::setProperties(const QScriptValue &properties) {
}
}
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void BillboardOverlay::setBillboardURL(const QUrl url) {
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
_manager->deleteLater();
_manager = new QNetworkAccessManager();
connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
_manager->get(QNetworkRequest(url));
QNetworkReply* reply = NetworkAccessManager::getInstance().get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &BillboardOverlay::replyFinished);
}
void BillboardOverlay::replyFinished(QNetworkReply* reply) {
void BillboardOverlay::replyFinished() {
// replace our byte array with the downloaded data
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
_billboard = reply->readAll();
_manager->deleteLater();
_manager = NULL;
}

View file

@ -27,12 +27,11 @@ public:
virtual void setProperties(const QScriptValue& properties);
private slots:
void replyFinished(QNetworkReply* reply);
void replyFinished();
private:
void setBillboardURL(const QUrl url);
QNetworkAccessManager* _manager;
QUrl _url;
QByteArray _billboard;
QSize _size;

View file

@ -19,7 +19,6 @@
#include "ImageOverlay.h"
ImageOverlay::ImageOverlay() :
_manager(NULL),
_textureID(0),
_renderImage(false),
_textureBound(false),
@ -36,21 +35,18 @@ ImageOverlay::~ImageOverlay() {
// TODO: handle setting image multiple times, how do we manage releasing the bound texture?
void ImageOverlay::setImageURL(const QUrl& url) {
// TODO: are we creating too many QNetworkAccessManager() when multiple calls to setImageURL are made?
_manager->deleteLater();
_manager = new QNetworkAccessManager();
connect(_manager, SIGNAL(finished(QNetworkReply*)), this, SLOT(replyFinished(QNetworkReply*)));
_manager->get(QNetworkRequest(url));
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
connect(reply, &QNetworkReply::finished, this, &ImageOverlay::replyFinished);
}
void ImageOverlay::replyFinished(QNetworkReply* reply) {
void ImageOverlay::replyFinished() {
QNetworkReply* reply = static_cast<QNetworkReply*>(sender());
// replace our byte array with the downloaded data
QByteArray rawData = reply->readAll();
_textureImage.loadFromData(rawData);
_renderImage = true;
_manager->deleteLater();
_manager = NULL;
}
void ImageOverlay::render() {

View file

@ -16,13 +16,13 @@
#include <QGLWidget>
#include <QImage>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRect>
#include <QScriptValue>
#include <QString>
#include <QUrl>
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
#include "Overlay.h"
@ -46,13 +46,12 @@ public:
virtual void setProperties(const QScriptValue& properties);
private slots:
void replyFinished(QNetworkReply* reply); // we actually want to hide this...
void replyFinished(); // we actually want to hide this...
private:
QUrl _imageURL;
QImage _textureImage;
QNetworkAccessManager* _manager;
GLuint _textureID;
QRect _fromImage; // where from in the image to sample

View file

@ -16,7 +16,6 @@
#include <QGLWidget>
#include <QImage>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QRect>
#include <QScriptValue>

View file

@ -0,0 +1,130 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>UserLocationsDialog</class>
<widget class="QWidget" name="UserLocationsDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>929</width>
<height>633</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="spacing">
<number>-1</number>
</property>
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>12</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>12</number>
</property>
<item>
<widget class="QWidget" name="widget_2" native="true">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label">
<property name="styleSheet">
<string notr="true">font-size: 16px</string>
</property>
<property name="text">
<string>My Locations</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="refreshButton">
<property name="text">
<string>Refresh</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QTreeView" name="locationsTreeView">
<property name="indentation">
<number>0</number>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="spacing">
<number>-1</number>
</property>
<property name="leftMargin">
<number>12</number>
</property>
<property name="topMargin">
<number>12</number>
</property>
<property name="rightMargin">
<number>12</number>
</property>
<property name="bottomMargin">
<number>12</number>
</property>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="renameButton">
<property name="text">
<string>Rename</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="deleteButton">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -102,3 +102,39 @@ void Animation::downloadFinished(QNetworkReply* reply) {
QThreadPool::globalInstance()->start(new AnimationReader(_self, reply));
}
AnimationDetails::AnimationDetails() :
role(), url(), fps(0.0f), priority(0.0f), loop(false), hold(false),
startAutomatically(false), firstFrame(0.0f), lastFrame(0.0f), running(false), frameIndex(0.0f)
{
}
AnimationDetails::AnimationDetails(QString role, QUrl url, float fps, float priority, bool loop,
bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float frameIndex) :
role(role), url(url), fps(fps), priority(priority), loop(loop), hold(hold),
startAutomatically(startAutomatically), firstFrame(firstFrame), lastFrame(lastFrame),
running(running), frameIndex(frameIndex)
{
}
QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& details) {
QScriptValue obj = engine->newObject();
obj.setProperty("role", details.role);
obj.setProperty("url", details.url.toString());
obj.setProperty("fps", details.fps);
obj.setProperty("priority", details.priority);
obj.setProperty("loop", details.loop);
obj.setProperty("hold", details.hold);
obj.setProperty("startAutomatically", details.startAutomatically);
obj.setProperty("firstFrame", details.firstFrame);
obj.setProperty("lastFrame", details.lastFrame);
obj.setProperty("running", details.running);
obj.setProperty("frameIndex", details.frameIndex);
return obj;
}
void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& details) {
// nothing for now...
}

View file

@ -12,6 +12,9 @@
#ifndef hifi_AnimationCache_h
#define hifi_AnimationCache_h
#include <QScriptEngine>
#include <QScriptValue>
#include <ResourceCache.h>
#include <FBXReader.h>
@ -68,4 +71,28 @@ private:
bool _isValid;
};
class AnimationDetails {
public:
AnimationDetails();
AnimationDetails(QString role, QUrl url, float fps, float priority, bool loop,
bool hold, bool startAutomatically, float firstFrame, float lastFrame, bool running, float frameIndex);
QString role;
QUrl url;
float fps;
float priority;
bool loop;
bool hold;
bool startAutomatically;
float firstFrame;
float lastFrame;
bool running;
float frameIndex;
};
Q_DECLARE_METATYPE(AnimationDetails);
QScriptValue animationDetailsToScriptValue(QScriptEngine* engine, const AnimationDetails& event);
void animationDetailsFromScriptValue(const QScriptValue& object, AnimationDetails& event);
#endif // hifi_AnimationCache_h

View file

@ -12,6 +12,9 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/../../cm
set(TARGET_NAME audio)
find_package(Qt5 COMPONENTS Script)
include_directories(SYSTEM "${Qt5Script_INCLUDE_DIRS}")
# set up the external glm library
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME})
@ -26,4 +29,4 @@ link_hifi_library(networking ${TARGET_NAME} "${ROOT_DIR}")
# add a definition for ssize_t so that windows doesn't bail
if (WIN32)
add_definitions(-Dssize_t=long)
endif ()
endif ()

View file

@ -19,10 +19,11 @@
#include "AudioRingBuffer.h"
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode) :
AudioRingBuffer::AudioRingBuffer(int numFrameSamples, bool randomAccessMode, int numFramesCapacity) :
NodeData(),
_overflowCount(0),
_sampleCapacity(numFrameSamples * RING_BUFFER_LENGTH_FRAMES),
_frameCapacity(numFramesCapacity),
_sampleCapacity(numFrameSamples * numFramesCapacity),
_isFull(false),
_numFrameSamples(numFrameSamples),
_isStarved(true),
@ -48,6 +49,8 @@ AudioRingBuffer::~AudioRingBuffer() {
}
void AudioRingBuffer::reset() {
_overflowCount = 0;
_isFull = false;
_endOfLastWrite = _buffer;
_nextOutput = _buffer;
_isStarved = true;
@ -55,13 +58,13 @@ void AudioRingBuffer::reset() {
void AudioRingBuffer::resizeForFrameSize(int numFrameSamples) {
delete[] _buffer;
_sampleCapacity = numFrameSamples * RING_BUFFER_LENGTH_FRAMES;
_sampleCapacity = numFrameSamples * _frameCapacity;
_numFrameSamples = numFrameSamples;
_buffer = new int16_t[_sampleCapacity];
if (_randomAccessMode) {
memset(_buffer, 0, _sampleCapacity * sizeof(int16_t));
}
_nextOutput = _buffer;
_endOfLastWrite = _buffer;
reset();
}
int AudioRingBuffer::parseData(const QByteArray& packet) {

View file

@ -31,15 +31,15 @@ const int NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL = NETWORK_BUFFER_LENGTH_BYTE
const unsigned int BUFFER_SEND_INTERVAL_USECS = floorf((NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL
/ (float) SAMPLE_RATE) * 1000 * 1000);
const short RING_BUFFER_LENGTH_FRAMES = 10;
const int MAX_SAMPLE_VALUE = std::numeric_limits<int16_t>::max();
const int MIN_SAMPLE_VALUE = std::numeric_limits<int16_t>::min();
const int DEFAULT_RING_BUFFER_FRAME_CAPACITY = 10;
class AudioRingBuffer : public NodeData {
Q_OBJECT
public:
AudioRingBuffer(int numFrameSamples, bool randomAccessMode = false);
AudioRingBuffer(int numFrameSamples, bool randomAccessMode = false, int numFramesCapacity = DEFAULT_RING_BUFFER_FRAME_CAPACITY);
~AudioRingBuffer();
void reset();
@ -84,6 +84,7 @@ protected:
int _overflowCount; /// how many times has the ring buffer has overwritten old data
int _frameCapacity;
int _sampleCapacity;
bool _isFull;
int _numFrameSamples;

View file

@ -20,7 +20,7 @@
#include "InjectedAudioRingBuffer.h"
InjectedAudioRingBuffer::InjectedAudioRingBuffer(const QUuid& streamIdentifier, bool dynamicJitterBuffer) :
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Injector, /* isStereo=*/ false , dynamicJitterBuffer),
PositionalAudioRingBuffer(PositionalAudioRingBuffer::Injector, false, dynamicJitterBuffer),
_streamIdentifier(streamIdentifier),
_radius(0.0f),
_attenuationRatio(0)

View file

@ -85,10 +85,10 @@ quint64 InterframeTimeGapStats::getWindowMaxGap() {
}
PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type,
bool isStereo, bool dynamicJitterBuffers) :
PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::Type type, bool isStereo, bool dynamicJitterBuffers) :
AudioRingBuffer(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL),
AudioRingBuffer(isStereo ? NETWORK_BUFFER_LENGTH_SAMPLES_STEREO : NETWORK_BUFFER_LENGTH_SAMPLES_PER_CHANNEL,
false, AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY),
_type(type),
_position(0.0f, 0.0f, 0.0f),
_orientation(0.0f, 0.0f, 0.0f, 0.0f),
@ -98,7 +98,7 @@ PositionalAudioRingBuffer::PositionalAudioRingBuffer(PositionalAudioRingBuffer::
_isStereo(isStereo),
_listenerUnattenuatedZone(NULL),
_desiredJitterBufferFrames(1),
_currentJitterBufferFrames(0),
_currentJitterBufferFrames(-1),
_dynamicJitterBuffers(dynamicJitterBuffers)
{
}
@ -216,8 +216,8 @@ bool PositionalAudioRingBuffer::shouldBeAddedToMix() {
// if the buffer doesn't have a full frame of samples to take for mixing, it is starved
_isStarved = true;
// set to 0 to indicate the jitter buffer is starved
_currentJitterBufferFrames = 0;
// set to -1 to indicate the jitter buffer is starved
_currentJitterBufferFrames = -1;
// reset our _shouldOutputStarveDebug to true so the next is printed
_shouldOutputStarveDebug = true;
@ -261,7 +261,7 @@ void PositionalAudioRingBuffer::updateDesiredJitterBufferFrames() {
if (_desiredJitterBufferFrames < 1) {
_desiredJitterBufferFrames = 1;
}
const int maxDesired = RING_BUFFER_LENGTH_FRAMES - 1;
const int maxDesired = _frameCapacity - 1;
if (_desiredJitterBufferFrames > maxDesired) {
_desiredJitterBufferFrames = maxDesired;
}

View file

@ -43,6 +43,8 @@ private:
bool _newWindowMaxGapAvailable;
};
const int AUDIOMIXER_INBOUND_RING_BUFFER_FRAME_CAPACITY = 100;
class PositionalAudioRingBuffer : public AudioRingBuffer {
public:
enum Type {
@ -103,6 +105,11 @@ protected:
int _desiredJitterBufferFrames;
int _currentJitterBufferFrames;
bool _dynamicJitterBuffers;
// extra stats
int _starveCount;
int _silentFramesDropped;
};
#endif // hifi_PositionalAudioRingBuffer_h

View file

@ -15,12 +15,12 @@
#include <QDataStream>
#include <QtCore/QDebug>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <qendian.h>
#include <LimitedNodeList.h>
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
#include "AudioRingBuffer.h"
@ -73,11 +73,11 @@ Sound::Sound(const QUrl& sampleURL, QObject* parent) :
// 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);
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
qDebug() << "Requesting audio file" << sampleURL.toDisplayString();
QNetworkReply* soundDownload = manager->get(QNetworkRequest(sampleURL));
QNetworkReply* soundDownload = networkAccessManager.get(QNetworkRequest(sampleURL));
connect(soundDownload, &QNetworkReply::finished, this, &Sound::replyFinished);
connect(soundDownload, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(replyError(QNetworkReply::NetworkError)));
}

View file

@ -16,10 +16,10 @@
#include <QtCore/QDataStream>
#include <QtCore/QThread>
#include <QtCore/QUuid>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include <QtNetwork/QNetworkRequest>
#include <NetworkAccessManager.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <SharedUtil.h>
@ -33,8 +33,6 @@ quint64 DEFAULT_FILTERED_LOG_EXPIRY = 2 * USECS_PER_SECOND;
using namespace std;
QNetworkAccessManager* AvatarData::networkAccessManager = NULL;
AvatarData::AvatarData() :
_sessionUUID(),
_handPosition(0,0,0),
@ -751,18 +749,15 @@ void AvatarData::setBillboard(const QByteArray& billboard) {
void AvatarData::setBillboardFromURL(const QString &billboardURL) {
_billboardURL = billboardURL;
if (AvatarData::networkAccessManager) {
qDebug() << "Changing billboard for avatar to PNG at" << qPrintable(billboardURL);
QNetworkRequest billboardRequest;
billboardRequest.setUrl(QUrl(billboardURL));
QNetworkReply* networkReply = AvatarData::networkAccessManager->get(billboardRequest);
connect(networkReply, SIGNAL(finished()), this, SLOT(setBillboardFromNetworkReply()));
} else {
qDebug() << "Billboard PNG download requested but no network access manager is available.";
}
qDebug() << "Changing billboard for avatar to PNG at" << qPrintable(billboardURL);
QNetworkRequest billboardRequest;
billboardRequest.setUrl(QUrl(billboardURL));
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* networkReply = networkAccessManager.get(billboardRequest);
connect(networkReply, SIGNAL(finished()), this, SLOT(setBillboardFromNetworkReply()));
}
void AvatarData::setBillboardFromNetworkReply() {
@ -839,8 +834,9 @@ void AvatarData::updateJointMappings() {
_jointIndices.clear();
_jointNames.clear();
if (networkAccessManager && _skeletonModelURL.fileName().toLower().endsWith(".fst")) {
QNetworkReply* networkReply = networkAccessManager->get(QNetworkRequest(_skeletonModelURL));
if (_skeletonModelURL.fileName().toLower().endsWith(".fst")) {
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* networkReply = networkAccessManager.get(QNetworkRequest(_skeletonModelURL));
connect(networkReply, SIGNAL(finished()), this, SLOT(setJointMappingsFromNetworkReply()));
}
}

View file

@ -102,7 +102,6 @@ enum KeyState {
const glm::vec3 vec3Zero(0.0f);
class QDataStream;
class QNetworkAccessManager;
class AttachmentData;
class JointData;
@ -269,8 +268,6 @@ public:
QElapsedTimer& getLastUpdateTimer() { return _lastUpdateTimer; }
virtual float getBoundingRadius() const { return 1.f; }
static void setNetworkAccessManager(QNetworkAccessManager* sharedAccessManager) { networkAccessManager = sharedAccessManager; }
public slots:
void sendIdentityPacket();
@ -323,8 +320,6 @@ protected:
QHash<QString, int> _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

View file

@ -37,19 +37,20 @@ Q_DECLARE_METATYPE(JSONCallbackParameters)
const QString ACCOUNTS_GROUP = "accounts";
JSONCallbackParameters::JSONCallbackParameters() :
jsonCallbackReceiver(NULL),
jsonCallbackMethod(),
errorCallbackReceiver(NULL),
errorCallbackMethod(),
updateReciever(NULL),
updateSlot()
JSONCallbackParameters::JSONCallbackParameters(QObject* jsonCallbackReceiver, const QString& jsonCallbackMethod,
QObject* errorCallbackReceiver, const QString& errorCallbackMethod,
QObject* updateReceiver, const QString& updateSlot) :
jsonCallbackReceiver(jsonCallbackReceiver),
jsonCallbackMethod(jsonCallbackMethod),
errorCallbackReceiver(errorCallbackReceiver),
errorCallbackMethod(errorCallbackMethod),
updateReciever(updateReceiver),
updateSlot(updateSlot)
{
}
AccountManager::AccountManager() :
_authURL(),
_networkAccessManager(NULL),
_pendingCallbackMap(),
_accountInfo()
{
@ -153,9 +154,7 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
const JSONCallbackParameters& callbackParams,
const QByteArray& dataByteArray, QHttpMultiPart* dataMultiPart) {
if (!_networkAccessManager) {
_networkAccessManager = new QNetworkAccessManager(this);
}
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
if (hasValidAccessToken()) {
QNetworkRequest authenticatedRequest;
@ -184,26 +183,29 @@ void AccountManager::invokedRequest(const QString& path, QNetworkAccessManager::
switch (operation) {
case QNetworkAccessManager::GetOperation:
networkReply = _networkAccessManager->get(authenticatedRequest);
networkReply = networkAccessManager.get(authenticatedRequest);
break;
case QNetworkAccessManager::PostOperation:
case QNetworkAccessManager::PutOperation:
if (dataMultiPart) {
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = _networkAccessManager->post(authenticatedRequest, dataMultiPart);
networkReply = networkAccessManager.post(authenticatedRequest, dataMultiPart);
} else {
networkReply = _networkAccessManager->put(authenticatedRequest, dataMultiPart);
networkReply = networkAccessManager.put(authenticatedRequest, dataMultiPart);
}
dataMultiPart->setParent(networkReply);
} else {
authenticatedRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (operation == QNetworkAccessManager::PostOperation) {
networkReply = _networkAccessManager->post(authenticatedRequest, dataByteArray);
networkReply = networkAccessManager.post(authenticatedRequest, dataByteArray);
} else {
networkReply = _networkAccessManager->put(authenticatedRequest, dataByteArray);
networkReply = networkAccessManager.put(authenticatedRequest, dataByteArray);
}
}
break;
case QNetworkAccessManager::DeleteOperation:
networkReply = networkAccessManager.sendCustomRequest(authenticatedRequest, "DELETE");
break;
default:
// other methods not yet handled
@ -304,9 +306,7 @@ bool AccountManager::checkAndSignalForAccessToken() {
void AccountManager::requestAccessToken(const QString& login, const QString& password) {
if (!_networkAccessManager) {
_networkAccessManager = new QNetworkAccessManager(this);
}
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request;
@ -324,7 +324,7 @@ void AccountManager::requestAccessToken(const QString& login, const QString& pas
request.setUrl(grantURL);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* requestReply = _networkAccessManager->post(request, postData);
QNetworkReply* requestReply = networkAccessManager.post(request, postData);
connect(requestReply, &QNetworkReply::finished, this, &AccountManager::requestAccessTokenFinished);
connect(requestReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestAccessTokenError(QNetworkReply::NetworkError)));
}
@ -376,15 +376,13 @@ void AccountManager::requestAccessTokenError(QNetworkReply::NetworkError error)
}
void AccountManager::requestProfile() {
if (!_networkAccessManager) {
_networkAccessManager = new QNetworkAccessManager(this);
}
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QUrl profileURL = _authURL;
profileURL.setPath("/api/v1/users/profile");
profileURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
QNetworkReply* profileReply = _networkAccessManager->get(QNetworkRequest(profileURL));
QNetworkReply* profileReply = networkAccessManager.get(QNetworkRequest(profileURL));
connect(profileReply, &QNetworkReply::finished, this, &AccountManager::requestProfileFinished);
connect(profileReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError)));
}

View file

@ -15,14 +15,17 @@
#include <QtCore/QByteArray>
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkReply>
#include "NetworkAccessManager.h"
#include "DataServerAccountInfo.h"
class JSONCallbackParameters {
public:
JSONCallbackParameters();
JSONCallbackParameters(QObject* jsonCallbackReceiver = NULL, const QString& jsonCallbackMethod = QString(),
QObject* errorCallbackReceiver = NULL, const QString& errorCallbackMethod = QString(),
QObject* updateReceiver = NULL, const QString& updateSlot = QString());
bool isEmpty() const { return !jsonCallbackReceiver && !errorCallbackReceiver; }
@ -58,8 +61,6 @@ public:
const DataServerAccountInfo& getAccountInfo() const { return _accountInfo; }
void destroy() { delete _networkAccessManager; }
public slots:
void requestAccessTokenFinished();
void requestProfileFinished();
@ -93,7 +94,6 @@ private:
QHttpMultiPart* dataMultiPart);
QUrl _authURL;
QNetworkAccessManager* _networkAccessManager;
QMap<QNetworkReply*, JSONCallbackParameters> _pendingCallbackMap;
DataServerAccountInfo _accountInfo;

View file

@ -0,0 +1,149 @@
//
// NetworkAccessManager.cpp
//
//
// Created by Clement on 7/1/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 <QMetaObject>
#include <QThread>
#include "NetworkAccessManager.h"
NetworkAccessManager& NetworkAccessManager::getInstance() {
static NetworkAccessManager sharedInstance;
return sharedInstance;
}
NetworkAccessManager::NetworkAccessManager() {
}
QNetworkReply* NetworkAccessManager::get(const QNetworkRequest& request) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"get",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request));
return result;
}
return QNetworkAccessManager::get(request);
}
QNetworkReply* NetworkAccessManager::head(const QNetworkRequest& request) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"head",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request));
return result;
}
return QNetworkAccessManager::head(request);
}
QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, QIODevice* data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"post",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(QIODevice*, data));
return result;
}
return QNetworkAccessManager::post(request, data);
}
QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, const QByteArray& data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"post",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(const QByteArray, data));
return result;
}
return QNetworkAccessManager::post(request, data);
}
QNetworkReply* NetworkAccessManager::post(const QNetworkRequest& request, QHttpMultiPart* multiPart) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"post",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(QHttpMultiPart*, multiPart));
return result;
}
return QNetworkAccessManager::post(request, multiPart);
}
QNetworkReply* NetworkAccessManager::put(const QNetworkRequest& request, QIODevice* data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"put",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(QIODevice*, data));
return result;
}
return QNetworkAccessManager::put(request, data);
}
QNetworkReply* NetworkAccessManager::put(const QNetworkRequest& request, QHttpMultiPart* multiPart) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"put",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(QHttpMultiPart*, multiPart));
return result;
}
return QNetworkAccessManager::put(request, multiPart);
}
QNetworkReply* NetworkAccessManager::put(const QNetworkRequest & request, const QByteArray & data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"put",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(const QByteArray, data));
return result;
}
return QNetworkAccessManager::put(request, data);
}
QNetworkReply* NetworkAccessManager::sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, QIODevice* data) {
if (QThread::currentThread() != thread()) {
QNetworkReply* result;
QMetaObject::invokeMethod(this,
"sendCustomRequest",
Qt::BlockingQueuedConnection,
Q_RETURN_ARG(QNetworkReply*, result),
Q_ARG(const QNetworkRequest, request),
Q_ARG(const QByteArray, verb),
Q_ARG(QIODevice*, data));
return result;
}
return QNetworkAccessManager::sendCustomRequest(request, verb, data);
}

View file

@ -0,0 +1,41 @@
//
// NetworkAccessManager.h
//
//
// Created by Clement on 7/1/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_NetworkAccessManager_h
#define hifi_NetworkAccessManager_h
#include <QNetworkAccessManager>
#include <QNetworkConfiguration>
#include <QNetworkProxy>
/// Wrapper around QNetworkAccessManager wo that we only use one instance
/// For any other method you should need, make sure to be on the right thread
/// or call the method using QMetaObject::invokeMethod()
class NetworkAccessManager : public QNetworkAccessManager {
Q_OBJECT
public:
static NetworkAccessManager& getInstance();
Q_INVOKABLE QNetworkReply* get(const QNetworkRequest& request);
Q_INVOKABLE QNetworkReply* head(const QNetworkRequest& request);
Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, QIODevice* data);
Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, const QByteArray& data);
Q_INVOKABLE QNetworkReply* post(const QNetworkRequest& request, QHttpMultiPart* multiPart);
Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, QIODevice* data);
Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, QHttpMultiPart* multiPart);
Q_INVOKABLE QNetworkReply* put(const QNetworkRequest& request, const QByteArray& data);
Q_INVOKABLE QNetworkReply* sendCustomRequest(const QNetworkRequest& request, const QByteArray& verb, QIODevice* data = 0);
private:
NetworkAccessManager();
};
#endif // hifi_NetworkAccessManager_h

View file

@ -16,6 +16,8 @@
#include <QTimer>
#include <QtDebug>
#include "NetworkAccessManager.h"
#include "ResourceCache.h"
ResourceCache::ResourceCache(QObject* parent) :
@ -103,8 +105,6 @@ void ResourceCache::requestCompleted(Resource* resource) {
}
}
QNetworkAccessManager* ResourceCache::_networkAccessManager = NULL;
const int DEFAULT_REQUEST_LIMIT = 10;
int ResourceCache::_requestLimit = DEFAULT_REQUEST_LIMIT;
@ -219,7 +219,7 @@ void Resource::init() {
if (_url.isEmpty()) {
_startedLoading = _loaded = true;
} else if (!(_url.isValid() && ResourceCache::getNetworkAccessManager())) {
} else if (!(_url.isValid())) {
_startedLoading = _failedToLoad = true;
}
}
@ -272,7 +272,7 @@ void Resource::handleReplyTimeout() {
}
void Resource::makeRequest() {
_reply = ResourceCache::getNetworkAccessManager()->get(_request);
_reply = NetworkAccessManager::getInstance().get(_request);
connect(_reply, SIGNAL(downloadProgress(qint64,qint64)), SLOT(handleDownloadProgress(qint64,qint64)));
connect(_reply, SIGNAL(error(QNetworkReply::NetworkError)), SLOT(handleReplyError()));

View file

@ -22,7 +22,6 @@
#include <QUrl>
#include <QWeakPointer>
class QNetworkAccessManager;
class QNetworkReply;
class QTimer;
@ -33,10 +32,6 @@ class ResourceCache : public QObject {
Q_OBJECT
public:
static void setNetworkAccessManager(QNetworkAccessManager* manager) { _networkAccessManager = manager; }
static QNetworkAccessManager* getNetworkAccessManager() { return _networkAccessManager; }
static void setRequestLimit(int limit) { _requestLimit = limit; }
static int getRequestLimit() { return _requestLimit; }
@ -76,7 +71,6 @@ private:
QHash<QUrl, QWeakPointer<Resource> > _resources;
int _lastLRUKey;
static QNetworkAccessManager* _networkAccessManager;
static int _requestLimit;
static QList<QPointer<Resource> > _pendingRequests;
static QList<Resource*> _loadingRequests;

View file

@ -15,10 +15,12 @@
#include <qbytearray.h>
#include <qvector.h>
#include "SequenceNumberStats.h"
class SentPacketHistory {
public:
SentPacketHistory(int size = 1000);
SentPacketHistory(int size = MAX_REASONABLE_SEQUENCE_GAP);
void packetSent(uint16_t sequenceNumber, const QByteArray& packet);
const QByteArray* getPacket(uint16_t sequenceNumber) const;

View file

@ -39,7 +39,6 @@ void SequenceNumberStats::reset() {
}
static const int UINT16_RANGE = std::numeric_limits<uint16_t>::max() + 1;
static const int MAX_REASONABLE_SEQUENCE_GAP = 1000; // this must be less than UINT16_RANGE / 2 for rollover handling to work
void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderUUID, const bool wantExtraDebugging) {
@ -95,6 +94,7 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU
_numEarly++;
_numLost += (incomingInt - expectedInt);
_lastReceived = incoming;
// add all sequence numbers that were skipped to the missing sequence numbers list
for (int missingInt = expectedInt; missingInt < incomingInt; missingInt++) {
@ -106,14 +106,14 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU
if (_missingSet.size() > MAX_REASONABLE_SEQUENCE_GAP) {
pruneMissingSet(wantExtraDebugging);
}
_lastReceived = incoming;
} else { // late
if (wantExtraDebugging) {
qDebug() << "this packet is later than expected...";
}
_numLate++;
// do not update _lastReceived; it shouldn't become smaller
// remove this from missing sequence number if it's in there
if (_missingSet.remove(incoming)) {
if (wantExtraDebugging) {
@ -127,8 +127,6 @@ void SequenceNumberStats::sequenceNumberReceived(quint16 incoming, QUuid senderU
}
_numDuplicate++;
}
// do not update _incomingLastSequence; it shouldn't become smaller
}
}
}

View file

@ -15,13 +15,15 @@
#include "SharedUtil.h"
#include <quuid.h>
const int MAX_REASONABLE_SEQUENCE_GAP = 1000;
class SequenceNumberStats {
public:
SequenceNumberStats();
void reset();
void sequenceNumberReceived(quint16 incoming, QUuid senderUUID = QUuid(), const bool wantExtraDebugging = false);
void pruneMissingSet(const bool wantExtraDebugging = false);
quint32 getNumReceived() const { return _numReceived; }
quint32 getNumUnreasonable() const { return _numUnreasonable; }
@ -34,8 +36,6 @@ public:
const QSet<quint16>& getMissingSet() const { return _missingSet; }
private:
void pruneMissingSet(const bool wantExtraDebugging);
quint16 _lastReceived;
QSet<quint16> _missingSet;

View file

@ -83,17 +83,10 @@ void UserActivityLogger::close(int delayTime) {
// In order to get the end of the session, we need to give the account manager enough time to send the packet.
QEventLoop loop;
// Here we connect the callbacks to stop the event loop
JSONCallbackParameters params;
params.jsonCallbackReceiver = &loop;
params.errorCallbackReceiver = &loop;
params.jsonCallbackMethod = "quit";
params.errorCallbackMethod = "quit";
// In case something goes wrong, we also setup a timer so that the delai is not greater than delayTime
QTimer timer;
connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
// Now we can log it
logAction(ACTION_NAME, QJsonObject(), params);
logAction(ACTION_NAME, QJsonObject());
timer.start(delayTime);
loop.exec();
}

View file

@ -168,6 +168,7 @@ public:
float getIncomingFlightTimeAverage() { return _incomingFlightTimeAverage.getAverage(); }
const SequenceNumberStats& getIncomingOctreeSequenceNumberStats() const { return _incomingOctreeSequenceNumberStats; }
SequenceNumberStats& getIncomingOctreeSequenceNumberStats() { return _incomingOctreeSequenceNumberStats; }
private:

View file

@ -183,7 +183,6 @@ void ParticleCollisionSystem::updateCollisionWithParticles(Particle* particleA)
// MIN_VALID_SPEED is obtained by computing speed gained at one gravity after the shortest expected frame
const float MIN_EXPECTED_FRAME_PERIOD = 0.0167f; // 1/60th of a second
const float HALTING_SPEED = 9.8 * MIN_EXPECTED_FRAME_PERIOD / (float)(TREE_SCALE);
void ParticleCollisionSystem::updateCollisionWithAvatars(Particle* particle) {
// particles that are in hand, don't collide with avatars

View file

@ -0,0 +1,37 @@
//
// MIDIEvent.cpp
// libraries/script-engine/src
//
// Created by Stephen Birarda on 2014-06-30.
// 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 "MIDIEvent.h"
void registerMIDIMetaTypes(QScriptEngine* engine) {
qScriptRegisterMetaType(engine, midiEventToScriptValue, midiEventFromScriptValue);
}
const QString MIDI_DELTA_TIME_PROP_NAME = "deltaTime";
const QString MIDI_EVENT_TYPE_PROP_NAME = "type";
const QString MIDI_DATA_1_PROP_NAME = "data1";
const QString MIDI_DATA_2_PROP_NAME = "data2";
QScriptValue midiEventToScriptValue(QScriptEngine* engine, const MIDIEvent& event) {
QScriptValue obj = engine->newObject();
obj.setProperty(MIDI_DELTA_TIME_PROP_NAME, event.deltaTime);
obj.setProperty(MIDI_EVENT_TYPE_PROP_NAME, event.type);
obj.setProperty(MIDI_DATA_1_PROP_NAME, event.data1);
obj.setProperty(MIDI_DATA_2_PROP_NAME, event.data2);
return obj;
}
void midiEventFromScriptValue(const QScriptValue &object, MIDIEvent& event) {
event.deltaTime = object.property(MIDI_DELTA_TIME_PROP_NAME).toVariant().toDouble();
event.type = object.property(MIDI_EVENT_TYPE_PROP_NAME).toVariant().toUInt();
event.data1 = object.property(MIDI_DATA_1_PROP_NAME).toVariant().toUInt();
event.data2 = object.property(MIDI_DATA_2_PROP_NAME).toVariant().toUInt();
}

View file

@ -0,0 +1,32 @@
//
// MIDIEvent.h
// libraries/script-engine/src
//
// Created by Stephen Birarda on 2014-06-30.
// 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 <QtScript/QScriptEngine>
#ifndef hifi_MIDIEvent_h
#define hifi_MIDIEvent_h
class MIDIEvent {
public:
double deltaTime;
unsigned int type;
unsigned int data1;
unsigned int data2;
};
Q_DECLARE_METATYPE(MIDIEvent)
void registerMIDIMetaTypes(QScriptEngine* engine);
QScriptValue midiEventToScriptValue(QScriptEngine* engine, const MIDIEvent& event);
void midiEventFromScriptValue(const QScriptValue &object, MIDIEvent& event);
#endif // hifi_MIDIEvent_h

View file

@ -13,7 +13,6 @@
#include <QtCore/QEventLoop>
#include <QtCore/QTimer>
#include <QtCore/QThread>
#include <QtNetwork/QNetworkAccessManager>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <QScriptEngine>
@ -23,6 +22,7 @@
#include <AvatarData.h>
#include <CollisionInfo.h>
#include <ModelsScriptingInterface.h>
#include <NetworkAccessManager.h>
#include <NodeList.h>
#include <PacketHeaders.h>
#include <ParticlesScriptingInterface.h>
@ -33,6 +33,7 @@
#include "AnimationObject.h"
#include "MenuItemProperties.h"
#include "MIDIEvent.h"
#include "LocalVoxels.h"
#include "ScriptEngine.h"
#include "XMLHttpRequestClass.h"
@ -141,8 +142,8 @@ ScriptEngine::ScriptEngine(const QUrl& scriptURL,
emit errorMessage("ERROR Loading file:" + fileName);
}
} else {
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
QNetworkReply* reply = networkManager->get(QNetworkRequest(url));
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
qDebug() << "Downloading included script at" << url;
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));
@ -217,6 +218,7 @@ void ScriptEngine::init() {
// register various meta-types
registerMetaTypes(&_engine);
registerMIDIMetaTypes(&_engine);
registerVoxelMetaTypes(&_engine);
registerEventTypes(&_engine);
registerMenuItemProperties(&_engine);
@ -254,6 +256,8 @@ void ScriptEngine::init() {
qScriptRegisterMetaType(&_engine, injectorToScriptValue, injectorFromScriptValue);
qScriptRegisterMetaType(&_engine, animationDetailsToScriptValue, animationDetailsFromScriptValue);
registerGlobalObject("Script", this);
registerGlobalObject("Audio", &_audioScriptingInterface);
registerGlobalObject("Controller", _controllerScriptingInterface);
@ -469,7 +473,10 @@ void ScriptEngine::run() {
// pack a placeholder value for sequence number for now, will be packed when destination node is known
int numPreSequenceNumberBytes = audioPacket.size();
packetStream << (quint16)0;
packetStream << (quint16) 0;
// assume scripted avatar audio is mono and set channel flag to zero
packetStream << (quint8) 0;
// use the orientation and position of this avatar for the source of this audio
packetStream.writeRawData(reinterpret_cast<const char*>(&_avatarData->getPosition()), sizeof(glm::vec3));
@ -646,8 +653,8 @@ void ScriptEngine::include(const QString& includeFile) {
QString includeContents;
if (url.scheme() == "http" || url.scheme() == "ftp") {
QNetworkAccessManager* networkManager = new QNetworkAccessManager(this);
QNetworkReply* reply = networkManager->get(QNetworkRequest(url));
NetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* reply = networkAccessManager.get(QNetworkRequest(url));
qDebug() << "Downloading included script at" << includeFile;
QEventLoop loop;
QObject::connect(reply, SIGNAL(finished()), &loop, SLOT(quit()));

View file

@ -14,6 +14,8 @@
#include <QEventLoop>
#include <NetworkAccessManager.h>
#include "XMLHttpRequestClass.h"
XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) :
@ -22,7 +24,6 @@ XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) :
_url(),
_method(""),
_responseType(""),
_manager(this),
_request(),
_reply(NULL),
_sendData(NULL),
@ -161,7 +162,7 @@ void XMLHttpRequestClass::send(const QString& data) {
}
void XMLHttpRequestClass::doSend() {
_reply = _manager.sendCustomRequest(_request, _method.toLatin1(), _sendData);
_reply = NetworkAccessManager::getInstance().sendCustomRequest(_request, _method.toLatin1(), _sendData);
connectToReply(_reply);

View file

@ -13,7 +13,6 @@
#define hifi_XMLHttpRequestClass_h
#include <QBuffer>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QNetworkRequest>
#include <QObject>
@ -104,7 +103,6 @@ private:
QUrl _url;
QString _method;
QString _responseType;
QNetworkAccessManager _manager;
QNetworkRequest _request;
QNetworkReply* _reply;
QBuffer* _sendData;

View file

@ -10,7 +10,7 @@ set(MACRO_DIR "${ROOT_DIR}/cmake/macros")
set(TARGET_NAME shared)
project(${TARGET_NAME})
find_package(Qt5 COMPONENTS Network Widgets Xml)
find_package(Qt5 COMPONENTS Network Widgets Xml Script)
include(${MACRO_DIR}/SetupHifiLibrary.cmake)
setup_hifi_library(${TARGET_NAME})
@ -32,4 +32,8 @@ 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)
# There is something special (bug) about Qt5Scripts, that we have to explicitly add its include
# directory when Qt5 (5.2.1) is compiled from source and is not in a standard place.
include_directories(SYSTEM "${Qt5Script_INCLUDE_DIRS}")
target_link_libraries(${TARGET_NAME} Qt5::Network Qt5::Widgets)

View file

@ -0,0 +1,201 @@
//
// AngularConstraint.cpp
// interface/src/renderer
//
// Created by Andrew Meadows on 2014.05.30
// 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 <glm/gtx/norm.hpp>
#include "AngularConstraint.h"
#include "SharedUtil.h"
// helper function
/// \param angle radian angle to be clamped within angleMin and angleMax
/// \param angleMin minimum value
/// \param angleMax maximum value
/// \return value between minAngle and maxAngle closest to angle
float clampAngle(float angle, float angleMin, float angleMax) {
float minDistance = angle - angleMin;
float maxDistance = angle - angleMax;
if (maxDistance > 0.0f) {
minDistance = glm::min(minDistance, angleMin + TWO_PI - angle);
angle = (minDistance < maxDistance) ? angleMin : angleMax;
} else if (minDistance < 0.0f) {
maxDistance = glm::max(maxDistance, angleMax - TWO_PI - angle);
angle = (minDistance > maxDistance) ? angleMin : angleMax;
}
return angle;
}
// static
AngularConstraint* AngularConstraint::newAngularConstraint(const glm::vec3& minAngles, const glm::vec3& maxAngles) {
float minDistance2 = glm::distance2(minAngles, glm::vec3(-PI, -PI, -PI));
float maxDistance2 = glm::distance2(maxAngles, glm::vec3(PI, PI, PI));
if (minDistance2 < EPSILON && maxDistance2 < EPSILON) {
// no constraint
return NULL;
}
// count the zero length elements
glm::vec3 rangeAngles = maxAngles - minAngles;
int pivotIndex = -1;
int numZeroes = 0;
for (int i = 0; i < 3; ++i) {
if (rangeAngles[i] < EPSILON) {
++numZeroes;
} else {
pivotIndex = i;
}
}
if (numZeroes == 2) {
// this is a hinge
int forwardIndex = (pivotIndex + 1) % 3;
glm::vec3 forwardAxis(0.0f);
forwardAxis[forwardIndex] = 1.0f;
glm::vec3 rotationAxis(0.0f);
rotationAxis[pivotIndex] = 1.0f;
return new HingeConstraint(forwardAxis, rotationAxis, minAngles[pivotIndex], maxAngles[pivotIndex]);
} else if (numZeroes == 0) {
// approximate the angular limits with a cone roller
// we assume the roll is about z
glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles);
glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f));
glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f));
glm::vec3 coneAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f);
// the coneAngle is half the average range of the two non-roll rotations
glm::vec3 range = maxAngles - minAngles;
float coneAngle = 0.25f * (range[0] + range[1]);
return new ConeRollerConstraint(coneAngle, coneAxis, minAngles.z, maxAngles.z);
}
return NULL;
}
bool AngularConstraint::softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction) {
glm::quat clampedTarget = targetRotation;
bool clamped = clamp(clampedTarget);
if (clamped) {
// check if oldRotation is also clamped
glm::quat clampedOld = oldRotation;
bool clamped2 = clamp(clampedOld);
if (clamped2) {
// oldRotation is already beyond the constraint
// we clamp again midway between targetRotation and clamped oldPosition
clampedTarget = glm::shortMix(clampedOld, targetRotation, mixFraction);
// and then clamp that
clamp(clampedTarget);
}
// finally we mix targetRotation with the clampedTarget
targetRotation = glm::shortMix(clampedTarget, targetRotation, mixFraction);
}
return clamped;
}
HingeConstraint::HingeConstraint(const glm::vec3& forwardAxis, const glm::vec3& rotationAxis, float minAngle, float maxAngle)
: _minAngle(minAngle), _maxAngle(maxAngle) {
assert(_minAngle < _maxAngle);
// we accept the rotationAxis direction
assert(glm::length(rotationAxis) > EPSILON);
_rotationAxis = glm::normalize(rotationAxis);
// but we compute the final _forwardAxis
glm::vec3 otherAxis = glm::cross(_rotationAxis, forwardAxis);
assert(glm::length(otherAxis) > EPSILON);
_forwardAxis = glm::normalize(glm::cross(otherAxis, _rotationAxis));
}
// virtual
bool HingeConstraint::clamp(glm::quat& rotation) const {
glm::vec3 forward = rotation * _forwardAxis;
forward -= glm::dot(forward, _rotationAxis) * _rotationAxis;
float length = glm::length(forward);
if (length < EPSILON) {
// infinite number of solutions ==> choose the middle of the contrained range
rotation = glm::angleAxis(0.5f * (_minAngle + _maxAngle), _rotationAxis);
return true;
}
forward /= length;
float sign = (glm::dot(glm::cross(_forwardAxis, forward), _rotationAxis) > 0.0f ? 1.0f : -1.0f);
//float angle = sign * acos(glm::dot(forward, _forwardAxis) / length);
float angle = sign * acos(glm::dot(forward, _forwardAxis));
glm::quat newRotation = glm::angleAxis(clampAngle(angle, _minAngle, _maxAngle), _rotationAxis);
if (fabsf(1.0f - glm::dot(newRotation, rotation)) > EPSILON * EPSILON) {
rotation = newRotation;
return true;
}
return false;
}
bool HingeConstraint::softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction) {
// the hinge works best without a soft clamp
return clamp(targetRotation);
}
ConeRollerConstraint::ConeRollerConstraint(float coneAngle, const glm::vec3& coneAxis, float minRoll, float maxRoll)
: _coneAngle(coneAngle), _minRoll(minRoll), _maxRoll(maxRoll) {
assert(_maxRoll >= _minRoll);
float axisLength = glm::length(coneAxis);
assert(axisLength > EPSILON);
_coneAxis = coneAxis / axisLength;
}
// virtual
bool ConeRollerConstraint::clamp(glm::quat& rotation) const {
bool applied = false;
glm::vec3 rotatedAxis = rotation * _coneAxis;
glm::vec3 perpAxis = glm::cross(rotatedAxis, _coneAxis);
float perpAxisLength = glm::length(perpAxis);
if (perpAxisLength > EPSILON) {
perpAxis /= perpAxisLength;
// enforce the cone
float angle = acosf(glm::dot(rotatedAxis, _coneAxis));
if (angle > _coneAngle) {
rotation = glm::angleAxis(angle - _coneAngle, perpAxis) * rotation;
rotatedAxis = rotation * _coneAxis;
applied = true;
}
} else {
// the rotation is 100% roll
// there is no obvious perp axis so we must pick one
perpAxis = rotatedAxis;
// find the first non-zero element:
float iValue = 0.0f;
int i = 0;
for (i = 0; i < 3; ++i) {
if (fabsf(perpAxis[i]) > EPSILON) {
iValue = perpAxis[i];
break;
}
}
assert(i != 3);
// swap or negate the next element
int j = (i + 1) % 3;
float jValue = perpAxis[j];
if (fabsf(jValue - iValue) > EPSILON) {
perpAxis[i] = jValue;
perpAxis[j] = iValue;
} else {
perpAxis[i] = -iValue;
}
perpAxis = glm::cross(perpAxis, rotatedAxis);
perpAxisLength = glm::length(perpAxis);
assert(perpAxisLength > EPSILON);
perpAxis /= perpAxisLength;
}
// measure the roll
// NOTE: perpAxis is perpendicular to both _coneAxis and rotatedConeAxis, so we can
// rotate it again and we'll end up with an something that has only been rolled.
glm::vec3 rolledPerpAxis = rotation * perpAxis;
float sign = glm::dot(rotatedAxis, glm::cross(perpAxis, rolledPerpAxis)) > 0.0f ? 1.0f : -1.0f;
float roll = sign * angleBetween(rolledPerpAxis, perpAxis);
if (roll < _minRoll || roll > _maxRoll) {
float clampedRoll = clampAngle(roll, _minRoll, _maxRoll);
rotation = glm::normalize(glm::angleAxis(clampedRoll - roll, rotatedAxis) * rotation);
applied = true;
}
return applied;
}

View file

@ -0,0 +1,55 @@
//
// AngularConstraint.h
// interface/src/renderer
//
// Created by Andrew Meadows on 2014.05.30
// Copyright 2013 High Fidelity, 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_AngularConstraint_h
#define hifi_AngularConstraint_h
#include <glm/glm.hpp>
class AngularConstraint {
public:
/// \param minAngles minumum euler angles for the constraint
/// \param maxAngles minumum euler angles for the constraint
/// \return pointer to new AngularConstraint of the right type or NULL if none could be made
static AngularConstraint* newAngularConstraint(const glm::vec3& minAngles, const glm::vec3& maxAngles);
AngularConstraint() {}
virtual ~AngularConstraint() {}
virtual bool clamp(glm::quat& rotation) const = 0;
virtual bool softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction);
protected:
};
class HingeConstraint : public AngularConstraint {
public:
HingeConstraint(const glm::vec3& forwardAxis, const glm::vec3& rotationAxis, float minAngle, float maxAngle);
virtual bool clamp(glm::quat& rotation) const;
virtual bool softClamp(glm::quat& targetRotation, const glm::quat& oldRotation, float mixFraction);
protected:
glm::vec3 _forwardAxis;
glm::vec3 _rotationAxis;
float _minAngle;
float _maxAngle;
};
class ConeRollerConstraint : public AngularConstraint {
public:
ConeRollerConstraint(float coneAngle, const glm::vec3& coneAxis, float minRoll, float maxRoll);
virtual bool clamp(glm::quat& rotation) const;
private:
float _coneAngle;
glm::vec3 _coneAxis;
float _minRoll;
float _maxRoll;
};
#endif // hifi_AngularConstraint_h

View file

@ -29,7 +29,7 @@ void AudioRingBufferTests::runAllTests() {
int readIndexAt;
AudioRingBuffer ringBuffer(10); // makes buffer of 100 int16_t samples
AudioRingBuffer ringBuffer(10, false, 10); // makes buffer of 100 int16_t samples
for (int T = 0; T < 300; T++) {
writeIndexAt = 0;

View file

@ -115,6 +115,11 @@ void SequenceNumberStatsTests::earlyLateTest() {
}
}
stats.reset();
numSent = 0;
numEarly = 0;
numLate = 0;
numLost = 0;
numRecovered = 0;
}
}
@ -203,6 +208,11 @@ void SequenceNumberStatsTests::duplicateTest() {
}
}
stats.reset();
numSent = 0;
numDuplicate = 0;
numEarly = 0;
numLate = 0;
numLost = 0;
}
}
@ -263,5 +273,8 @@ void SequenceNumberStatsTests::pruneTest() {
}
}
stats.reset();
numSent = 0;
numEarly = 0;
numLost = 0;
}
}

View file

@ -0,0 +1,476 @@
//
// AngularConstraintTests.cpp
// tests/physics/src
//
// Created by Andrew Meadows on 2014.05.30
// 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 <iostream>
#include <AngularConstraint.h>
#include <SharedUtil.h>
#include <StreamUtils.h>
#include "AngularConstraintTests.h"
void AngularConstraintTests::testHingeConstraint() {
float minAngle = -PI;
float maxAngle = 0.0f;
glm::vec3 yAxis(0.0f, 1.0f, 0.0f);
glm::vec3 minAngles(0.0f, -PI, 0.0f);
glm::vec3 maxAngles(0.0f, 0.0f, 0.0f);
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
if (!c) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: newAngularConstraint() should make a constraint" << std::endl;
}
{ // test in middle of constraint
float angle = 0.5f * (minAngle + maxAngle);
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should not clamp()" << std::endl;
}
if (rotation != newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should not change rotation" << std::endl;
}
}
{ // test just inside min edge of constraint
float angle = minAngle + 10.f * EPSILON;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should not clamp()" << std::endl;
}
if (rotation != newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should not change rotation" << std::endl;
}
}
{ // test just inside max edge of constraint
float angle = maxAngle - 10.f * EPSILON;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should not clamp()" << std::endl;
}
if (rotation != newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should not change rotation" << std::endl;
}
}
{ // test just outside min edge of constraint
float angle = minAngle - 0.001f;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test just outside max edge of constraint
float angle = maxAngle + 0.001f;
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test far outside min edge of constraint (wraps around to max)
float angle = minAngle - 0.75f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test far outside max edge of constraint (wraps around to min)
float angle = maxAngle + 0.75f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
float ACCEPTABLE_ERROR = 1.0e-4f;
{ // test nearby but off-axis rotation
float offAngle = 0.1f;
glm::quat offRotation(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = 0.5f * (maxAngle + minAngle);
glm::quat rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(angle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test way off rotation > maxAngle
float offAngle = 0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = maxAngle + 0.2f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test way off rotation < minAngle
float offAngle = 0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = minAngle - 0.2f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test way off rotation > maxAngle with wrap over to minAngle
float offAngle = -0.5f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = maxAngle + 0.6f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(minAngle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test way off rotation < minAngle with wrap over to maxAngle
float offAngle = -0.6f;
glm::quat offRotation = glm::angleAxis(offAngle, glm::vec3(1.0f, 0.0f, 0.0f));
float angle = minAngle - 0.7f * (TWO_PI - (maxAngle - minAngle));
glm::quat rotation = glm::angleAxis(angle, yAxis);
rotation = offRotation * glm::angleAxis(angle, yAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(maxAngle, yAxis);
float qDot = glm::dot(expectedRotation, newRotation);
if (fabsf(qDot - 1.0f) > ACCEPTABLE_ERROR) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: HingeConstraint rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
delete c;
}
void AngularConstraintTests::testConeRollerConstraint() {
float minAngleX = -PI / 5.0f;
float minAngleY = -PI / 5.0f;
float minAngleZ = -PI / 8.0f;
float maxAngleX = PI / 4.0f;
float maxAngleY = PI / 3.0f;
float maxAngleZ = PI / 4.0f;
glm::vec3 minAngles(minAngleX, minAngleY, minAngleZ);
glm::vec3 maxAngles(maxAngleX, maxAngleY, maxAngleZ);
AngularConstraint* c = AngularConstraint::newAngularConstraint(minAngles, maxAngles);
float expectedConeAngle = 0.25 * (maxAngleX - minAngleX + maxAngleY - minAngleY);
glm::vec3 middleAngles = 0.5f * (maxAngles + minAngles);
glm::quat yaw = glm::angleAxis(middleAngles[1], glm::vec3(0.0f, 1.0f, 0.0f));
glm::quat pitch = glm::angleAxis(middleAngles[0], glm::vec3(1.0f, 0.0f, 0.0f));
glm::vec3 expectedConeAxis = pitch * yaw * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 xAxis(1.0f, 0.0f, 0.0f);
glm::vec3 perpAxis = glm::normalize(xAxis - glm::dot(xAxis, expectedConeAxis) * expectedConeAxis);
if (!c) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: newAngularConstraint() should make a constraint" << std::endl;
}
{ // test in middle of constraint
glm::vec3 angles(PI/20.0f, 0.0f, PI/10.0f);
glm::quat rotation(angles);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should not clamp()" << std::endl;
}
if (rotation != newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should not change rotation" << std::endl;
}
}
float deltaAngle = 0.001f;
{ // test just inside edge of cone
glm::quat rotation = glm::angleAxis(expectedConeAngle - deltaAngle, perpAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should not clamp()" << std::endl;
}
if (rotation != newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should not change rotation" << std::endl;
}
}
{ // test just outside edge of cone
glm::quat rotation = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should change rotation" << std::endl;
}
}
{ // test just inside min edge of roll
glm::quat rotation = glm::angleAxis(minAngleZ + deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should not clamp()" << std::endl;
}
if (rotation != newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should not change rotation" << std::endl;
}
}
{ // test just inside max edge of roll
glm::quat rotation = glm::angleAxis(maxAngleZ - deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should not clamp()" << std::endl;
}
if (rotation != newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should not change rotation" << std::endl;
}
}
{ // test just outside min edge of roll
glm::quat rotation = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(minAngleZ, expectedConeAxis);
if (fabsf(1.0f - glm::dot(newRotation, expectedRotation)) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test just outside max edge of roll
glm::quat rotation = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should change rotation" << std::endl;
}
glm::quat expectedRotation = glm::angleAxis(maxAngleZ, expectedConeAxis);
if (fabsf(1.0f - glm::dot(newRotation, expectedRotation)) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
deltaAngle = 0.25f * expectedConeAngle;
{ // test far outside cone and min roll
glm::quat roll = glm::angleAxis(minAngleZ - deltaAngle, expectedConeAxis);
glm::quat pitchYaw = glm::angleAxis(expectedConeAngle + deltaAngle, perpAxis);
glm::quat rotation = pitchYaw * roll;
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should change rotation" << std::endl;
}
glm::quat expectedRoll = glm::angleAxis(minAngleZ, expectedConeAxis);
glm::quat expectedPitchYaw = glm::angleAxis(expectedConeAngle, perpAxis);
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
if (fabsf(1.0f - glm::dot(newRotation, expectedRotation)) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
{ // test far outside cone and max roll
glm::quat roll = glm::angleAxis(maxAngleZ + deltaAngle, expectedConeAxis);
glm::quat pitchYaw = glm::angleAxis(- expectedConeAngle - deltaAngle, perpAxis);
glm::quat rotation = pitchYaw * roll;
glm::quat newRotation = rotation;
bool constrained = c->clamp(newRotation);
if (!constrained) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should clamp()" << std::endl;
}
if (rotation == newRotation) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: ConeRollerConstraint should change rotation" << std::endl;
}
glm::quat expectedRoll = glm::angleAxis(maxAngleZ, expectedConeAxis);
glm::quat expectedPitchYaw = glm::angleAxis(- expectedConeAngle, perpAxis);
glm::quat expectedRotation = expectedPitchYaw * expectedRoll;
if (fabsf(1.0f - glm::dot(newRotation, expectedRotation)) > EPSILON) {
std::cout << __FILE__ << ":" << __LINE__
<< " ERROR: rotation = " << newRotation << " but expected " << expectedRotation << std::endl;
}
}
delete c;
}
void AngularConstraintTests::runAllTests() {
testHingeConstraint();
testConeRollerConstraint();
}

View file

@ -0,0 +1,21 @@
//
// AngularConstraintTests.h
// tests/physics/src
//
// Created by Andrew Meadows on 2014.05.30
// 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_AngularConstraintTests_h
#define hifi_AngularConstraintTests_h
namespace AngularConstraintTests {
void testHingeConstraint();
void testConeRollerConstraint();
void runAllTests();
}
#endif // hifi_AngularConstraintTests_h

View file

@ -8,9 +8,11 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "AngularConstraintTests.h"
#include "MovingPercentileTests.h"
int main(int argc, char** argv) {
MovingPercentileTests::runAllTests();
AngularConstraintTests::runAllTests();
return 0;
}