Merge branch 'master' of github.com:highfidelity/hifi into fix-head-puck-broken-eyes

This commit is contained in:
Seth Alves 2017-06-09 09:28:07 -07:00
commit 197fb620d4
52 changed files with 1069 additions and 297 deletions

View file

@ -166,11 +166,7 @@ void Agent::run() {
// Setup MessagesClient
auto messagesClient = DependencyManager::set<MessagesClient>();
QThread* messagesThread = new QThread;
messagesThread->setObjectName("Messages Client Thread");
messagesClient->moveToThread(messagesThread);
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
messagesThread->start();
messagesClient->startThread();
// make sure we hear about connected nodes so we can grab an ATP script if a request is pending
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &Agent::nodeActivated);

View file

@ -69,17 +69,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
DependencyManager::set<ResourceScriptingInterface>();
DependencyManager::set<UserActivityLoggerScriptingInterface>();
// setup a thread for the NodeList and its PacketReceiver
QThread* nodeThread = new QThread(this);
nodeThread->setObjectName("NodeList Thread");
nodeThread->start();
// make sure the node thread is given highest priority
nodeThread->setPriority(QThread::TimeCriticalPriority);
// put the NodeList on the node thread
nodeList->moveToThread(nodeThread);
nodeList->startThread();
// set the logging target to the the CHILD_TARGET_NAME
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
@ -166,14 +156,8 @@ void AssignmentClient::stopAssignmentClient() {
}
AssignmentClient::~AssignmentClient() {
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
// ask the node thread to quit and wait until it is done
nodeThread->quit();
nodeThread->wait();
}
void AssignmentClient::aboutToQuit() {

View file

@ -16,10 +16,10 @@
#include <mutex>
#include <vector>
#include <tbb/concurrent_queue.h>
#include <QThread>
#include <TBBHelpers.h>
#include "AudioMixerSlave.h"
class AudioMixerSlavePool;

View file

@ -16,10 +16,9 @@
#include <mutex>
#include <vector>
#include <tbb/concurrent_queue.h>
#include <QThread>
#include <TBBHelpers.h>
#include <NodeList.h>
#include "AvatarMixerSlave.h"

View file

@ -236,11 +236,7 @@ void EntityScriptServer::run() {
// Setup MessagesClient
auto messagesClient = DependencyManager::set<MessagesClient>();
QThread* messagesThread = new QThread;
messagesThread->setObjectName("Messages Client Thread");
messagesClient->moveToThread(messagesThread);
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
messagesThread->start();
messagesClient->startThread();
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
connect(&domainHandler, &DomainHandler::settingsReceived, this, &EntityScriptServer::handleSettings);

View file

@ -21,7 +21,7 @@
</p>
<p>
If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:<br>
<pre>C:\Users\[username]\AppData\Roaming\High Fidelity\assignment-client/entities/models.json.gz</pre>
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
</p>

View file

@ -37,20 +37,12 @@ RenderingClient::RenderingClient(QObject *parent, const QString& launchURLString
DependencyManager::set<AvatarHashMap>();
// get our audio client setup on its own thread
QThread* audioThread = new QThread();
auto audioClient = DependencyManager::set<AudioClient>();
audioClient->setPositionGetter(getPositionForAudio);
audioClient->setOrientationGetter(getOrientationForAudio);
audioClient->startThread();
audioClient->moveToThread(audioThread);
connect(audioThread, &QThread::started, audioClient.data(), &AudioClient::start);
connect(audioClient.data(), &AudioClient::destroyed, audioThread, &QThread::quit);
connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
audioThread->start();
connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket);
_avatarTimer.setInterval(16); // 60 FPS
_avatarTimer.start();

View file

@ -103,8 +103,8 @@
"rotationVar": "spine2Rotation",
"typeVar": "spine2Type",
"weightVar": "spine2Weight",
"weight": 1.0,
"flexCoefficients": [1.0, 0.5, 0.5]
"weight": 2.0,
"flexCoefficients": [1.0, 0.5, 0.25]
},
{
"jointName": "Head",
@ -113,7 +113,7 @@
"typeVar": "headType",
"weightVar": "headWeight",
"weight": 4.0,
"flexCoefficients": [1, 0.05, 0.25, 0.25, 0.25]
"flexCoefficients": [1, 0.5, 0.25, 0.2, 0.1]
},
{
"jointName": "LeftArm",

View file

@ -653,6 +653,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
auto nodeList = DependencyManager::get<NodeList>();
nodeList->startThread();
// Set up a watchdog thread to intentionally crash the application on deadlocks
_deadlockWatchdogThread = new DeadlockWatchdogThread();
@ -678,25 +679,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
updateHeartbeat();
// start the nodeThread so its event loop is running
QThread* nodeThread = new QThread(this);
nodeThread->setObjectName("NodeList Thread");
nodeThread->start();
// make sure the node thread is given highest priority
nodeThread->setPriority(QThread::TimeCriticalPriority);
// setup a timer for domain-server check ins
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
// put the NodeList and datagram processing on the node thread
nodeList->moveToThread(nodeThread);
// put the audio processing on a separate thread
QThread* audioThread = new QThread();
audioThread->setObjectName("Audio Thread");
auto audioIO = DependencyManager::get<AudioClient>();
audioIO->setPositionGetter([]{
@ -712,7 +699,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
return myAvatar ? myAvatar->getOrientationForAudio() : Quaternions::IDENTITY;
});
audioIO->moveToThread(audioThread);
recording::Frame::registerFrameHandler(AudioConstants::getAudioFrameName(), [=](recording::Frame::ConstPointer frame) {
audioIO->handleRecordedAudioInput(frame->data);
});
@ -726,9 +712,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
});
auto audioScriptingInterface = DependencyManager::set<AudioScriptingInterface>();
connect(audioThread, &QThread::started, audioIO.data(), &AudioClient::start);
connect(audioIO.data(), &AudioClient::destroyed, audioThread, &QThread::quit);
connect(audioThread, &QThread::finished, audioThread, &QThread::deleteLater);
connect(audioIO.data(), &AudioClient::muteToggled, this, &Application::audioMuteToggled);
connect(audioIO.data(), &AudioClient::mutedByMixer, audioScriptingInterface.data(), &AudioScriptingInterface::mutedByMixer);
connect(audioIO.data(), &AudioClient::receivedFirstPacket, audioScriptingInterface.data(), &AudioScriptingInterface::receivedFirstPacket);
@ -746,19 +729,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
}
});
audioThread->start();
audioIO->startThread();
ResourceManager::init();
// Make sure we don't time out during slow operations at startup
updateHeartbeat();
// Setup MessagesClient
auto messagesClient = DependencyManager::get<MessagesClient>();
QThread* messagesThread = new QThread;
messagesThread->setObjectName("Messages Client Thread");
messagesClient->moveToThread(messagesThread);
connect(messagesThread, &QThread::started, messagesClient.data(), &MessagesClient::init);
messagesThread->start();
DependencyManager::get<MessagesClient>()->startThread();
const DomainHandler& domainHandler = nodeList->getDomainHandler();
@ -1678,12 +1656,7 @@ void Application::aboutToQuit() {
_autoSwitchDisplayModeSupportedHMDPlugin->endSession();
}
// use the CloseEventSender via a QThread to send an event that says the user asked for the app to close
auto closeEventSender = DependencyManager::get<CloseEventSender>();
QThread* closureEventThread = new QThread(this);
closeEventSender->moveToThread(closureEventThread);
// sendQuitEventAsync will bail immediately if the UserActivityLogger is not enabled
connect(closureEventThread, &QThread::started, closeEventSender.data(), &CloseEventSender::sendQuitEventAsync);
closureEventThread->start();
DependencyManager::get<CloseEventSender>()->startThread();
// Hide Running Scripts dialog so that it gets destroyed in an orderly manner; prevents warnings at shutdown.
DependencyManager::get<OffscreenUi>()->hide("RunningScripts");
@ -1771,6 +1744,8 @@ void Application::cleanupBeforeQuit() {
// stop QML
DependencyManager::destroy<OffscreenUi>();
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
if (_snapshotSoundInjector != nullptr) {
_snapshotSoundInjector->stop();
}
@ -1830,15 +1805,9 @@ Application::~Application() {
ResourceManager::cleanup();
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
// ask the node thread to quit and wait until it is done
nodeThread->quit();
nodeThread->wait();
Leapmotion::destroy();
if (auto steamClient = PluginManager::getInstance()->getSteamClientPlugin()) {

View file

@ -14,6 +14,7 @@
#include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkReply>
#include <ThreadHelpers.h>
#include <AccountManager.h>
#include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
@ -87,4 +88,8 @@ bool CloseEventSender::hasTimedOutQuitEvent() {
&& QDateTime::currentMSecsSinceEpoch() - _quitEventStartTimestamp > CLOSURE_EVENT_TIMEOUT_MS;
}
void CloseEventSender::startThread() {
moveToNewNamedThread(this, "CloseEvent Logger Thread", [this] {
sendQuitEventAsync();
});
}

View file

@ -24,6 +24,7 @@ class CloseEventSender : public QObject, public Dependency {
SINGLETON_DEPENDENCY
public:
void startThread();
bool hasTimedOutQuitEvent();
bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; }

View file

@ -9,9 +9,12 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QtConcurrent/QtConcurrentRun>
#include <ThreadHelpers.h>
#include <src/InterfaceLogging.h>
#include <src/ui/AvatarInputs.h>
#include <QtConcurrent/QtConcurrentRun>
#include "LimitlessVoiceRecognitionScriptingInterface.h"
const float LimitlessVoiceRecognitionScriptingInterface::_audioLevelThreshold = 0.33f;
@ -24,9 +27,7 @@ LimitlessVoiceRecognitionScriptingInterface::LimitlessVoiceRecognitionScriptingI
connect(&_voiceTimer, &QTimer::timeout, this, &LimitlessVoiceRecognitionScriptingInterface::voiceTimeout);
connect(&_connection, &LimitlessConnection::onReceivedTranscription, this, [this](QString transcription){emit onReceivedTranscription(transcription);});
connect(&_connection, &LimitlessConnection::onFinishedSpeaking, this, [this](QString transcription){emit onFinishedSpeaking(transcription);});
_connection.moveToThread(&_connectionThread);
_connectionThread.setObjectName("Limitless Connection");
_connectionThread.start();
moveToNewNamedThread(&_connection, "Limitless Connection");
}
void LimitlessVoiceRecognitionScriptingInterface::update() {

View file

@ -41,7 +41,6 @@ private:
static const int _voiceTimeoutDuration;
QTimer _voiceTimer;
QThread _connectionThread;
LimitlessConnection _connection;
void voiceTimeout();

View file

@ -23,6 +23,7 @@
#include <QUrlQuery>
#include <QXmlStreamReader>
#include <ThreadHelpers.h>
#include <NetworkAccessManager.h>
#include <SharedUtil.h>
@ -84,12 +85,7 @@ ModelsBrowser::ModelsBrowser(FSTReader::ModelType modelsType, QWidget* parent) :
_handler->connect(this, SIGNAL(destroyed()), SLOT(exit()));
// Setup and launch update thread
QThread* thread = new QThread();
thread->setObjectName("Models Browser");
thread->connect(_handler, SIGNAL(destroyed()), SLOT(quit()));
thread->connect(thread, SIGNAL(finished()), SLOT(deleteLater()));
_handler->moveToThread(thread);
thread->start();
moveToNewNamedThread(_handler, "Models Browser");
emit startDownloading();
// Initialize the view

View file

@ -20,6 +20,8 @@
#include "ElbowConstraint.h"
#include "SwingTwistConstraint.h"
#include "AnimationLogging.h"
#include "CubicHermiteSpline.h"
#include "AnimUtil.h"
AnimInverseKinematics::IKTargetVar::IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn) :
@ -59,7 +61,8 @@ AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimN
AnimInverseKinematics::~AnimInverseKinematics() {
clearConstraints();
_accumulators.clear();
_rotationAccumulators.clear();
_translationAccumulators.clear();
_targetVarVec.clear();
}
@ -72,10 +75,12 @@ void AnimInverseKinematics::loadPoses(const AnimPoseVec& poses) {
assert(_skeleton && ((poses.size() == 0) || (_skeleton->getNumJoints() == (int)poses.size())));
if (_skeleton->getNumJoints() == (int)poses.size()) {
_relativePoses = poses;
_accumulators.resize(_relativePoses.size());
_rotationAccumulators.resize(_relativePoses.size());
_translationAccumulators.resize(_relativePoses.size());
} else {
_relativePoses.clear();
_accumulators.clear();
_rotationAccumulators.clear();
_translationAccumulators.clear();
}
}
@ -175,14 +180,17 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
}
}
void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets) {
void AnimInverseKinematics::solve(const AnimContext& context, const std::vector<IKTarget>& targets) {
// compute absolute poses that correspond to relative target poses
AnimPoseVec absolutePoses;
absolutePoses.resize(_relativePoses.size());
computeAbsolutePoses(absolutePoses);
// clear the accumulators before we start the IK solver
for (auto& accumulator: _accumulators) {
for (auto& accumulator : _rotationAccumulators) {
accumulator.clearAndClean();
}
for (auto& accumulator : _translationAccumulators) {
accumulator.clearAndClean();
}
@ -197,14 +205,22 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext&
// solve all targets
for (auto& target: targets) {
solveTargetWithCCD(context, target, absolutePoses, debug);
if (target.getType() == IKTarget::Type::Spline) {
solveTargetWithSpline(context, target, absolutePoses, debug);
} else {
solveTargetWithCCD(context, target, absolutePoses, debug);
}
}
// harvest accumulated rotations and apply the average
for (int i = 0; i < (int)_relativePoses.size(); ++i) {
if (_accumulators[i].size() > 0) {
_relativePoses[i].rot() = _accumulators[i].getAverage();
_accumulators[i].clear();
if (_rotationAccumulators[i].size() > 0) {
_relativePoses[i].rot() = _rotationAccumulators[i].getAverage();
_rotationAccumulators[i].clear();
}
if (_translationAccumulators[i].size() > 0) {
_relativePoses[i].trans() = _translationAccumulators[i].getAverage();
_translationAccumulators[i].clear();
}
}
@ -236,7 +252,7 @@ void AnimInverseKinematics::solveWithCyclicCoordinateDescent(const AnimContext&
int parentIndex = _skeleton->getParentIndex(tipIndex);
// update rotationOnly targets that don't lie on the ik chain of other ik targets.
if (parentIndex != -1 && !_accumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
if (parentIndex != -1 && !_rotationAccumulators[tipIndex].isDirty() && target.getType() == IKTarget::Type::RotationOnly) {
const glm::quat& targetRotation = target.getRotation();
// compute tip's new parent-relative rotation
// Q = Qp * q --> q' = Qp^ * Q
@ -311,10 +327,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
}
// store the relative rotation change in the accumulator
_accumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
_rotationAccumulators[tipIndex].add(tipRelativeRotation, target.getWeight());
glm::vec3 tipRelativeTranslation = _relativePoses[target.getIndex()].trans();
_translationAccumulators[tipIndex].add(tipRelativeTranslation);
if (debug) {
debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, constrained);
debugJointMap[tipIndex] = DebugJoint(tipRelativeRotation, tipRelativeTranslation, constrained);
}
}
@ -422,10 +441,13 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
}
// store the relative rotation change in the accumulator
_accumulators[pivotIndex].add(newRot, target.getWeight());
_rotationAccumulators[pivotIndex].add(newRot, target.getWeight());
glm::vec3 newTrans = _relativePoses[pivotIndex].trans();
_translationAccumulators[pivotIndex].add(newTrans);
if (debug) {
debugJointMap[pivotIndex] = DebugJoint(newRot, constrained);
debugJointMap[pivotIndex] = DebugJoint(newRot, newTrans, constrained);
}
// keep track of tip's new transform as we descend towards root
@ -444,6 +466,187 @@ void AnimInverseKinematics::solveTargetWithCCD(const AnimContext& context, const
}
}
static CubicHermiteSplineFunctorWithArcLength computeSplineFromTipAndBase(const AnimPose& tipPose, const AnimPose& basePose, float baseGain = 1.0f, float tipGain = 1.0f) {
float linearDistance = glm::length(basePose.trans() - tipPose.trans());
glm::vec3 p0 = basePose.trans();
glm::vec3 m0 = baseGain * linearDistance * (basePose.rot() * Vectors::UNIT_Y);
glm::vec3 p1 = tipPose.trans();
glm::vec3 m1 = tipGain * linearDistance * (tipPose.rot() * Vectors::UNIT_Y);
return CubicHermiteSplineFunctorWithArcLength(p0, m0, p1, m1);
}
// pre-compute information about each joint influeced by this spline IK target.
void AnimInverseKinematics::computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target) {
std::vector<SplineJointInfo> splineJointInfoVec;
// build spline between the default poses.
AnimPose tipPose = _skeleton->getAbsoluteDefaultPose(target.getIndex());
AnimPose basePose = _skeleton->getAbsoluteDefaultPose(_hipsIndex);
CubicHermiteSplineFunctorWithArcLength spline;
if (target.getIndex() == _headIndex) {
// set gain factors so that more curvature occurs near the tip of the spline.
const float HIPS_GAIN = 0.5f;
const float HEAD_GAIN = 1.0f;
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
} else {
spline = computeSplineFromTipAndBase(tipPose, basePose);
}
// measure the total arc length along the spline
float totalArcLength = spline.arcLength(1.0f);
glm::vec3 baseToTip = tipPose.trans() - basePose.trans();
float baseToTipLength = glm::length(baseToTip);
glm::vec3 baseToTipNormal = baseToTip / baseToTipLength;
int index = target.getIndex();
int endIndex = _skeleton->getParentIndex(_hipsIndex);
while (index != endIndex) {
AnimPose defaultPose = _skeleton->getAbsoluteDefaultPose(index);
float ratio = glm::dot(defaultPose.trans() - basePose.trans(), baseToTipNormal) / baseToTipLength;
// compute offset from spline to the default pose.
float t = spline.arcLengthInverse(ratio * totalArcLength);
// compute the rotation by using the derivative of the spline as the y-axis, and the defaultPose x-axis
glm::vec3 y = glm::normalize(spline.d(t));
glm::vec3 x = defaultPose.rot() * Vectors::UNIT_X;
glm::vec3 u, v, w;
generateBasisVectors(y, x, v, u, w);
glm::mat3 m(u, v, glm::cross(u, v));
glm::quat rot = glm::normalize(glm::quat_cast(m));
AnimPose pose(glm::vec3(1.0f), rot, spline(t));
AnimPose offsetPose = pose.inverse() * defaultPose;
SplineJointInfo splineJointInfo = { index, ratio, offsetPose };
splineJointInfoVec.push_back(splineJointInfo);
index = _skeleton->getParentIndex(index);
}
_splineJointInfoMap[target.getIndex()] = splineJointInfoVec;
}
const std::vector<AnimInverseKinematics::SplineJointInfo>* AnimInverseKinematics::findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target) {
// find or create splineJointInfo for this target
auto iter = _splineJointInfoMap.find(target.getIndex());
if (iter != _splineJointInfoMap.end()) {
return &(iter->second);
} else {
computeSplineJointInfosForIKTarget(context, target);
auto iter = _splineJointInfoMap.find(target.getIndex());
if (iter != _splineJointInfoMap.end()) {
return &(iter->second);
}
}
return nullptr;
}
void AnimInverseKinematics::solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug) {
std::map<int, DebugJoint> debugJointMap;
const int baseIndex = _hipsIndex;
// build spline from tip to base
AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
AnimPose basePose = absolutePoses[baseIndex];
CubicHermiteSplineFunctorWithArcLength spline;
if (target.getIndex() == _headIndex) {
// set gain factors so that more curvature occurs near the tip of the spline.
const float HIPS_GAIN = 0.5f;
const float HEAD_GAIN = 1.0f;
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
} else {
spline = computeSplineFromTipAndBase(tipPose, basePose);
}
float totalArcLength = spline.arcLength(1.0f);
// This prevents the rotation interpolation from rotating the wrong physical way (but correct mathematical way)
// when the head is arched backwards very far.
glm::quat halfRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), 0.5f));
if (glm::dot(halfRot * Vectors::UNIT_Z, basePose.rot() * Vectors::UNIT_Z) < 0.0f) {
tipPose.rot() = -tipPose.rot();
}
// find or create splineJointInfo for this target
const std::vector<SplineJointInfo>* splineJointInfoVec = findOrCreateSplineJointInfo(context, target);
if (splineJointInfoVec && splineJointInfoVec->size() > 0) {
const int baseParentIndex = _skeleton->getParentIndex(baseIndex);
AnimPose parentAbsPose = (baseParentIndex >= 0) ? absolutePoses[baseParentIndex] : AnimPose();
// go thru splineJointInfoVec backwards (base to tip)
for (int i = (int)splineJointInfoVec->size() - 1; i >= 0; i--) {
const SplineJointInfo& splineJointInfo = (*splineJointInfoVec)[i];
float t = spline.arcLengthInverse(splineJointInfo.ratio * totalArcLength);
glm::vec3 trans = spline(t);
// for head splines, preform most twist toward the tip by using ease in function. t^2
float rotT = t;
if (target.getIndex() == _headIndex) {
rotT = t * t;
}
glm::quat twistRot = glm::normalize(glm::lerp(basePose.rot(), tipPose.rot(), rotT));
// compute the rotation by using the derivative of the spline as the y-axis, and the twistRot x-axis
glm::vec3 y = glm::normalize(spline.d(t));
glm::vec3 x = twistRot * Vectors::UNIT_X;
glm::vec3 u, v, w;
generateBasisVectors(y, x, v, u, w);
glm::mat3 m(u, v, glm::cross(u, v));
glm::quat rot = glm::normalize(glm::quat_cast(m));
AnimPose desiredAbsPose = AnimPose(glm::vec3(1.0f), rot, trans) * splineJointInfo.offsetPose;
// apply flex coefficent
AnimPose flexedAbsPose;
::blend(1, &absolutePoses[splineJointInfo.jointIndex], &desiredAbsPose, target.getFlexCoefficient(i), &flexedAbsPose);
AnimPose relPose = parentAbsPose.inverse() * flexedAbsPose;
_rotationAccumulators[splineJointInfo.jointIndex].add(relPose.rot(), target.getWeight());
bool constrained = false;
if (splineJointInfo.jointIndex != _hipsIndex) {
// constrain the amount the spine can stretch or compress
float length = glm::length(relPose.trans());
const float EPSILON = 0.0001f;
if (length > EPSILON) {
float defaultLength = glm::length(_skeleton->getRelativeDefaultPose(splineJointInfo.jointIndex).trans());
const float STRETCH_COMPRESS_PERCENTAGE = 0.15f;
const float MAX_LENGTH = defaultLength * (1.0f + STRETCH_COMPRESS_PERCENTAGE);
const float MIN_LENGTH = defaultLength * (1.0f - STRETCH_COMPRESS_PERCENTAGE);
if (length > MAX_LENGTH) {
relPose.trans() = (relPose.trans() / length) * MAX_LENGTH;
constrained = true;
} else if (length < MIN_LENGTH) {
relPose.trans() = (relPose.trans() / length) * MIN_LENGTH;
constrained = true;
}
} else {
relPose.trans() = glm::vec3(0.0f);
}
}
_translationAccumulators[splineJointInfo.jointIndex].add(relPose.trans(), target.getWeight());
if (debug) {
debugJointMap[splineJointInfo.jointIndex] = DebugJoint(relPose.rot(), relPose.trans(), constrained);
}
parentAbsPose = flexedAbsPose;
}
}
if (debug) {
debugDrawIKChain(debugJointMap, context);
}
}
//virtual
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) {
// don't call this function, call overlay() instead
@ -453,14 +656,9 @@ const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVar
//virtual
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
// allows solutionSource to be overridden by an animVar
auto solutionSource = animVars.lookup(_solutionSourceVar, (int)_solutionSource);
if (context.getEnableDebugDrawIKConstraints()) {
debugDrawConstraints(context);
}
const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
if (dt > MAX_OVERLAY_DT) {
dt = MAX_OVERLAY_DT;
@ -569,7 +767,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
{
PROFILE_RANGE_EX(simulation_animation, "ik/ccd", 0xffff00ff, 0);
solveWithCyclicCoordinateDescent(context, targets);
solve(context, targets);
}
if (_hipsTargetIndex < 0) {
@ -579,6 +777,20 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
_hipsOffset = Vectors::ZERO;
}
}
if (context.getEnableDebugDrawIKConstraints()) {
debugDrawConstraints(context);
}
}
if (_leftHandIndex > -1) {
_uncontrolledLeftHandPose = _skeleton->getAbsolutePose(_leftHandIndex, underPoses);
}
if (_rightHandIndex > -1) {
_uncontrolledRightHandPose = _skeleton->getAbsolutePose(_rightHandIndex, underPoses);
}
if (_hipsIndex > -1) {
_uncontrolledHipsPose = _skeleton->getAbsolutePose(_hipsIndex, underPoses);
}
return _relativePoses;
@ -722,8 +934,10 @@ void AnimInverseKinematics::initConstraints() {
loadDefaultPoses(_skeleton->getRelativeBindPoses());
// compute corresponding absolute poses
int numJoints = (int)_defaultRelativePoses.size();
/* KEEP THIS CODE for future experimentation
// compute corresponding absolute poses
AnimPoseVec absolutePoses;
absolutePoses.resize(numJoints);
for (int i = 0; i < numJoints; ++i) {
@ -734,6 +948,7 @@ void AnimInverseKinematics::initConstraints() {
absolutePoses[i] = absolutePoses[parentIndex] * _defaultRelativePoses[i];
}
}
*/
clearConstraints();
for (int i = 0; i < numJoints; ++i) {
@ -1045,7 +1260,10 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
_maxTargetIndex = -1;
for (auto& accumulator: _accumulators) {
for (auto& accumulator: _rotationAccumulators) {
accumulator.clearAndClean();
}
for (auto& accumulator: _translationAccumulators) {
accumulator.clearAndClean();
}
@ -1061,12 +1279,21 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
} else {
_hipsParentIndex = -1;
}
_leftHandIndex = _skeleton->nameToJointIndex("LeftHand");
_rightHandIndex = _skeleton->nameToJointIndex("RightHand");
} else {
clearConstraints();
_headIndex = -1;
_hipsIndex = -1;
_hipsParentIndex = -1;
_leftHandIndex = -1;
_rightHandIndex = -1;
}
_uncontrolledLeftHandPose = AnimPose();
_uncontrolledRightHandPose = AnimPose();
_uncontrolledHipsPose = AnimPose();
}
static glm::vec3 sphericalToCartesian(float phi, float theta) {
@ -1117,6 +1344,7 @@ void AnimInverseKinematics::debugDrawIKChain(std::map<int, DebugJoint>& debugJoi
// copy debug joint rotations into the relative poses
for (auto& debugJoint : debugJointMap) {
poses[debugJoint.first].rot() = debugJoint.second.relRot;
poses[debugJoint.first].trans() = debugJoint.second.relTrans;
}
// convert relative poses to absolute
@ -1282,7 +1510,7 @@ void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const A
int numJoints = (int)_relativePoses.size();
for (int i = 0; i < numJoints; ++i) {
float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), targetPoses[i].rot()));
if (_accumulators[i].isDirty()) {
if (_rotationAccumulators[i].isDirty()) {
// this joint is affected by IK --> blend toward the targetPoses rotation
_relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * targetPoses[i].rot(), blendFactor));
} else {
@ -1316,3 +1544,46 @@ void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource s
break;
}
}
void AnimInverseKinematics::debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const {
for (auto& target : targets) {
if (target.getType() != IKTarget::Type::Spline) {
continue;
}
const int baseIndex = _hipsIndex;
// build spline
AnimPose tipPose = AnimPose(glm::vec3(1.0f), target.getRotation(), target.getTranslation());
AnimPose basePose = _skeleton->getAbsolutePose(baseIndex, _relativePoses);
CubicHermiteSplineFunctorWithArcLength spline;
if (target.getIndex() == _headIndex) {
// set gain factors so that more curvature occurs near the tip of the spline.
const float HIPS_GAIN = 0.5f;
const float HEAD_GAIN = 1.0f;
spline = computeSplineFromTipAndBase(tipPose, basePose, HIPS_GAIN, HEAD_GAIN);
} else {
spline = computeSplineFromTipAndBase(tipPose, basePose);
}
float totalArcLength = spline.arcLength(1.0f);
const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
// draw red and white stripped spline, parameterized by arc length.
// i.e. each stripe should be the same length.
AnimPose geomToWorldPose = AnimPose(context.getRigToWorldMatrix() * context.getGeometryToRigMatrix());
const int NUM_SEGMENTS = 20;
const float dArcLength = totalArcLength / NUM_SEGMENTS;
float arcLength = 0.0f;
for (int i = 0; i < NUM_SEGMENTS; i++) {
float prevT = spline.arcLengthInverse(arcLength);
float nextT = spline.arcLengthInverse(arcLength + dArcLength);
DebugDraw::getInstance().drawRay(geomToWorldPose.xformPoint(spline(prevT)), geomToWorldPose.xformPoint(spline(nextT)), (i % 2) == 0 ? RED : WHITE);
arcLength += dArcLength;
}
}
}

View file

@ -19,6 +19,7 @@
#include "IKTarget.h"
#include "RotationAccumulator.h"
#include "TranslationAccumulator.h"
class RotationConstraint;
@ -56,23 +57,39 @@ public:
void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; }
void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; }
const AnimPose& getUncontrolledLeftHandPose() { return _uncontrolledLeftHandPose; }
const AnimPose& getUncontrolledRightHandPose() { return _uncontrolledRightHandPose; }
const AnimPose& getUncontrolledHipPose() { return _uncontrolledHipsPose; }
protected:
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
void solveWithCyclicCoordinateDescent(const AnimContext& context, const std::vector<IKTarget>& targets);
void solve(const AnimContext& context, const std::vector<IKTarget>& targets);
void solveTargetWithCCD(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
void solveTargetWithSpline(const AnimContext& context, const IKTarget& target, const AnimPoseVec& absolutePoses, bool debug);
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
struct DebugJoint {
DebugJoint() : relRot(), constrained(false) {}
DebugJoint(const glm::quat& relRotIn, bool constrainedIn) : relRot(relRotIn), constrained(constrainedIn) {}
DebugJoint(const glm::quat& relRotIn, const glm::vec3& relTransIn, bool constrainedIn) : relRot(relRotIn), relTrans(relTransIn), constrained(constrainedIn) {}
glm::quat relRot;
glm::vec3 relTrans;
bool constrained;
};
void debugDrawIKChain(std::map<int, DebugJoint>& debugJointMap, const AnimContext& context) const;
void debugDrawRelativePoses(const AnimContext& context) const;
void debugDrawConstraints(const AnimContext& context) const;
void debugDrawSpineSplines(const AnimContext& context, const std::vector<IKTarget>& targets) const;
void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
// used to pre-compute information about each joint influeced by a spline IK target.
struct SplineJointInfo {
int jointIndex; // joint in the skeleton that this information pertains to.
float ratio; // percentage (0..1) along the spline for this joint.
AnimPose offsetPose; // local offset from the spline to the joint.
};
void computeSplineJointInfosForIKTarget(const AnimContext& context, const IKTarget& target);
const std::vector<SplineJointInfo>* findOrCreateSplineJointInfo(const AnimContext& context, const IKTarget& target);
// for AnimDebugDraw rendering
virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
@ -105,12 +122,15 @@ protected:
};
std::map<int, RotationConstraint*> _constraints;
std::vector<RotationAccumulator> _accumulators;
std::vector<RotationAccumulator> _rotationAccumulators;
std::vector<TranslationAccumulator> _translationAccumulators;
std::vector<IKTargetVar> _targetVarVec;
AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
AnimPoseVec _relativePoses; // current relative poses
AnimPoseVec _limitCenterPoses; // relative
std::map<int, std::vector<SplineJointInfo>> _splineJointInfoMap;
// experimental data for moving hips during IK
glm::vec3 _hipsOffset { Vectors::ZERO };
float _maxHipsOffsetLength{ FLT_MAX };
@ -118,6 +138,8 @@ protected:
int _hipsIndex { -1 };
int _hipsParentIndex { -1 };
int _hipsTargetIndex { -1 };
int _leftHandIndex { -1 };
int _rightHandIndex { -1 };
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
// during the the cyclic coordinate descent algorithm
@ -127,6 +149,10 @@ protected:
bool _previousEnableDebugIKTargets { false };
SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses };
QString _solutionSourceVar;
AnimPose _uncontrolledLeftHandPose { AnimPose() };
AnimPose _uncontrolledRightHandPose { AnimPose() };
AnimPose _uncontrolledHipsPose { AnimPose() };
};
#endif // hifi_AnimInverseKinematics_h

View file

@ -44,6 +44,9 @@ void IKTarget::setType(int type) {
case (int)Type::HipsRelativeRotationAndPosition:
_type = Type::HipsRelativeRotationAndPosition;
break;
case (int)Type::Spline:
_type = Type::Spline;
break;
default:
_type = Type::Unknown;
}

View file

@ -21,6 +21,7 @@ public:
RotationOnly,
HmdHead,
HipsRelativeRotationAndPosition,
Spline,
Unknown
};

View file

@ -28,6 +28,7 @@
#include "AnimClip.h"
#include "AnimInverseKinematics.h"
#include "AnimSkeleton.h"
#include "AnimUtil.h"
#include "IKTarget.h"
static bool isEqual(const glm::vec3& u, const glm::vec3& v) {
@ -401,16 +402,6 @@ void Rig::setJointRotation(int index, bool valid, const glm::quat& rotation, flo
}
}
void Rig::restoreJointRotation(int index, float fraction, float priority) {
// AJT: DEAD CODE?
ASSERT(false);
}
void Rig::restoreJointTranslation(int index, float fraction, float priority) {
// AJT: DEAD CODE?
ASSERT(false);
}
bool Rig::getJointPositionInWorldFrame(int jointIndex, glm::vec3& position, glm::vec3 translation, glm::quat rotation) const {
if (isIndexValid(jointIndex)) {
position = (rotation * _internalPoseSet._absolutePoses[jointIndex].trans()) + translation;
@ -1040,8 +1031,8 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
_animVars.set("hipsType", (int)IKTarget::Type::Unknown);
}
if (params.spine2Enabled) {
_animVars.set("spine2Type", (int)IKTarget::Type::RotationAndPosition);
if (params.hipsEnabled && params.spine2Enabled) {
_animVars.set("spine2Type", (int)IKTarget::Type::Spline);
_animVars.set("spine2Position", extractTranslation(params.spine2Matrix));
_animVars.set("spine2Rotation", glmExtractRotation(params.spine2Matrix));
} else {
@ -1051,7 +1042,7 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
if (params.leftArmEnabled) {
_animVars.set("leftArmType", (int)IKTarget::Type::RotationAndPosition);
_animVars.set("leftArmPosition", params.leftArmPosition);
_animVars.set("leftArmRotation", params.leftArmRotation);
_animVars.set("leftArmRotation", params.leftArmRotation);
} else {
_animVars.set("leftArmType", (int)IKTarget::Type::Unknown);
}
@ -1101,9 +1092,9 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) {
_animVars.set("headPosition", params.rigHeadPosition);
_animVars.set("headRotation", params.rigHeadOrientation);
if (params.hipsEnabled) {
// Since there is an explicit hips ik target, switch the head to use the more generic RotationAndPosition IK chain type.
// this will allow the spine to bend more, ensuring that it can reach the head target position.
_animVars.set("headType", (int)IKTarget::Type::RotationAndPosition);
// Since there is an explicit hips ik target, switch the head to use the more flexible Spline IK chain type.
// this will allow the spine to compress/expand and bend more natrually, ensuring that it can reach the head target position.
_animVars.set("headType", (int)IKTarget::Type::Spline);
_animVars.unset("headWeight"); // use the default weight for this target.
} else {
// When there is no hips IK target, use the HmdHead IK chain type. This will make the spine very stiff,
@ -1172,10 +1163,10 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f
// TODO: add isHipsEnabled
bool bodySensorTrackingEnabled = params.isLeftFootEnabled || params.isRightFootEnabled;
const float RELAX_DURATION = 0.6f;
if (params.isLeftEnabled) {
glm::vec3 handPosition = params.leftPosition;
if (!bodySensorTrackingEnabled) {
// prevent the hand IK targets from intersecting the body capsule
glm::vec3 displacement;
@ -1187,16 +1178,38 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f
_animVars.set("leftHandPosition", handPosition);
_animVars.set("leftHandRotation", params.leftOrientation);
_animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
_isLeftHandControlled = true;
_lastLeftHandControlledPose = AnimPose(glm::vec3(1.0f), params.leftOrientation, handPosition);
} else {
_animVars.unset("leftHandPosition");
_animVars.unset("leftHandRotation");
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
if (_isLeftHandControlled) {
_leftHandRelaxDuration = RELAX_DURATION;
_isLeftHandControlled = false;
}
if (_leftHandRelaxDuration > 0) {
// Move hand from controlled position to non-controlled position.
_leftHandRelaxDuration = std::max(_leftHandRelaxDuration - dt, 0.0f);
auto ikNode = getAnimInverseKinematicsNode();
if (ikNode) {
float alpha = 1.0f - _leftHandRelaxDuration / RELAX_DURATION;
const AnimPose geometryToRigTransform(_geometryToRigTransform);
AnimPose uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledLeftHandPose();
AnimPose handPose;
::blend(1, &_lastLeftHandControlledPose, &uncontrolledHandPose, alpha, &handPose);
_animVars.set("leftHandPosition", handPose.trans());
_animVars.set("leftHandRotation", handPose.rot());
_animVars.set("leftHandType", (int)IKTarget::Type::RotationAndPosition);
}
} else {
_animVars.unset("leftHandPosition");
_animVars.unset("leftHandRotation");
_animVars.set("leftHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
}
}
if (params.isRightEnabled) {
glm::vec3 handPosition = params.rightPosition;
if (!bodySensorTrackingEnabled) {
// prevent the hand IK targets from intersecting the body capsule
glm::vec3 displacement;
@ -1208,10 +1221,34 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f
_animVars.set("rightHandPosition", handPosition);
_animVars.set("rightHandRotation", params.rightOrientation);
_animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
_isRightHandControlled = true;
_lastRightHandControlledPose = AnimPose(glm::vec3(1.0f), params.rightOrientation, handPosition);
} else {
_animVars.unset("rightHandPosition");
_animVars.unset("rightHandRotation");
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
if (_isRightHandControlled) {
_rightHandRelaxDuration = RELAX_DURATION;
_isRightHandControlled = false;
}
if (_rightHandRelaxDuration > 0) {
// Move hand from controlled position to non-controlled position.
_rightHandRelaxDuration = std::max(_rightHandRelaxDuration - dt, 0.0f);
auto ikNode = getAnimInverseKinematicsNode();
if (ikNode) {
float alpha = 1.0f - _rightHandRelaxDuration / RELAX_DURATION;
const AnimPose geometryToRigTransform(_geometryToRigTransform);
AnimPose uncontrolledHandPose = geometryToRigTransform * ikNode->getUncontrolledRightHandPose();
AnimPose handPose;
::blend(1, &_lastRightHandControlledPose, &uncontrolledHandPose, alpha, &handPose);
_animVars.set("rightHandPosition", handPose.trans());
_animVars.set("rightHandRotation", handPose.rot());
_animVars.set("rightHandType", (int)IKTarget::Type::RotationAndPosition);
}
} else {
_animVars.unset("rightHandPosition");
_animVars.unset("rightHandRotation");
_animVars.set("rightHandType", (int)IKTarget::Type::HipsRelativeRotationAndPosition);
}
}
if (params.isLeftFootEnabled) {
@ -1233,7 +1270,6 @@ void Rig::updateFromHandAndFeetParameters(const HandAndFeetParameters& params, f
_animVars.unset("rightFootRotation");
_animVars.set("rightFootType", (int)IKTarget::Type::RotationAndPosition);
}
}
}
@ -1509,5 +1545,3 @@ void Rig::computeAvatarBoundingCapsule(
glm::vec3 rigCenter = (geometryToRig * (0.5f * (totalExtents.maximum + totalExtents.minimum)));
localOffsetOut = rigCenter - (geometryToRig * rootPosition);
}

View file

@ -134,10 +134,6 @@ public:
void setJointTranslation(int index, bool valid, const glm::vec3& translation, float priority);
void setJointRotation(int index, bool valid, const glm::quat& rotation, float priority);
// legacy
void restoreJointRotation(int index, float fraction, float priority);
void restoreJointTranslation(int index, float fraction, float priority);
// if translation and rotation is identity, position will be in rig space
bool getJointPositionInWorldFrame(int jointIndex, glm::vec3& position,
glm::vec3 translation, glm::quat rotation) const;
@ -355,6 +351,13 @@ private:
QMap<int, StateHandler> _stateHandlers;
int _nextStateHandlerId { 0 };
QMutex _stateMutex;
bool _isLeftHandControlled { false };
bool _isRightHandControlled { false };
float _leftHandRelaxDuration { 0.0f };
float _rightHandRelaxDuration { 0.0f };
AnimPose _lastLeftHandControlledPose;
AnimPose _lastRightHandControlledPose;
};
#endif /* defined(__hifi__Rig__) */

View file

@ -1,5 +1,5 @@
//
// RotationAccumulator.h
// RotationAccumulator.cpp
//
// Copyright 2015 High Fidelity, Inc.
//
@ -27,7 +27,7 @@ void RotationAccumulator::clear() {
_numRotations = 0;
}
void RotationAccumulator::clearAndClean() {
void RotationAccumulator::clearAndClean() {
clear();
_isDirty = false;
}

View file

@ -0,0 +1,34 @@
//
// TranslationAccumulator.cpp
//
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "TranslationAccumulator.h"
void TranslationAccumulator::add(const glm::vec3& translation, float weight) {
_accum += weight * translation;
_totalWeight += weight;
_isDirty = true;
}
glm::vec3 TranslationAccumulator::getAverage() {
if (_totalWeight > 0.0f) {
return _accum / _totalWeight;
} else {
return glm::vec3();
}
}
void TranslationAccumulator::clear() {
_accum *= 0.0f;
_totalWeight = 0.0f;
}
void TranslationAccumulator::clearAndClean() {
clear();
_isDirty = false;
}

View file

@ -0,0 +1,42 @@
//
// TranslationAccumulator.h
//
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_TranslationAccumulator_h
#define hifi_TranslationAccumulator_h
#include <glm/glm.hpp>
class TranslationAccumulator {
public:
TranslationAccumulator() : _accum(0.0f, 0.0f, 0.0f), _totalWeight(0), _isDirty(false) { }
int size() const { return _totalWeight > 0.0f; }
/// \param translation translation to add
/// \param weight contribution factor of this translation to total accumulation
void add(const glm::vec3& translation, float weight = 1.0f);
glm::vec3 getAverage();
/// \return true if any translation were accumulated
bool isDirty() const { return _isDirty; }
/// \brief clear accumulated translation but don't change _isDirty
void clear();
/// \brief clear accumulated translation and set _isDirty to false
void clearAndClean();
private:
glm::vec3 _accum;
float _totalWeight;
bool _isDirty;
};
#endif // hifi_TranslationAccumulator_h

View file

@ -31,10 +31,13 @@
#include <VersionHelpers.h>
#endif
#include <QtConcurrent/QtConcurrent>
#include <QtCore/QThreadPool>
#include <QtCore/QBuffer>
#include <QtMultimedia/QAudioInput>
#include <QtMultimedia/QAudioOutput>
#include <ThreadHelpers.h>
#include <NodeList.h>
#include <plugins/CodecPlugin.h>
#include <plugins/PluginManager.h>
@ -76,59 +79,6 @@ using Mutex = std::mutex;
using Lock = std::unique_lock<Mutex>;
static Mutex _deviceMutex;
class BackgroundThread : public QThread {
public:
BackgroundThread(AudioClient* client) : QThread((QObject*)client), _client(client) {}
virtual void join() = 0;
protected:
AudioClient* _client;
};
// background thread continuously polling device changes
class CheckDevicesThread : public BackgroundThread {
public:
CheckDevicesThread(AudioClient* client) : BackgroundThread(client) {}
void join() override {
_shouldQuit = true;
std::unique_lock<std::mutex> lock(_joinMutex);
_joinCondition.wait(lock, [&]{ return !_isRunning; });
}
protected:
void run() override {
while (!_shouldQuit) {
_client->checkDevices();
const unsigned long DEVICE_CHECK_INTERVAL_MSECS = 2 * 1000;
QThread::msleep(DEVICE_CHECK_INTERVAL_MSECS);
}
std::lock_guard<std::mutex> lock(_joinMutex);
_isRunning = false;
_joinCondition.notify_one();
}
private:
std::atomic<bool> _shouldQuit { false };
bool _isRunning { true };
std::mutex _joinMutex;
std::condition_variable _joinCondition;
};
// background thread buffering local injectors
class LocalInjectorsThread : public BackgroundThread {
Q_OBJECT
public:
LocalInjectorsThread(AudioClient* client) : BackgroundThread(client) {}
void join() override { return; }
private slots:
void prepare() { _client->prepareLocalAudioInjectors(); }
};
#include "AudioClient.moc"
static void channelUpmix(int16_t* source, int16_t* dest, int numSamples, int numExtraChannels) {
for (int i = 0; i < numSamples/2; i++) {
@ -223,16 +173,15 @@ AudioClient::AudioClient() :
_inputDevices = getDeviceNames(QAudio::AudioInput);
_outputDevices = getDeviceNames(QAudio::AudioOutput);
// start a thread to detect any device changes
_checkDevicesThread = new CheckDevicesThread(this);
_checkDevicesThread->setObjectName("AudioClient CheckDevices Thread");
_checkDevicesThread->setPriority(QThread::LowPriority);
_checkDevicesThread->start();
// start a thread to process local injectors
_localInjectorsThread = new LocalInjectorsThread(this);
_localInjectorsThread->setObjectName("AudioClient LocalInjectors Thread");
_localInjectorsThread->start();
// start a thread to detect any device changes
_checkDevicesTimer = new QTimer(this);
connect(_checkDevicesTimer, &QTimer::timeout, [this] {
QtConcurrent::run(QThreadPool::globalInstance(), [this] {
checkDevices();
});
});
configureReverb();
@ -263,15 +212,7 @@ void AudioClient::cleanupBeforeQuit() {
stop();
if (_checkDevicesThread) {
static_cast<BackgroundThread*>(_checkDevicesThread)->join();
delete _checkDevicesThread;
}
if (_localInjectorsThread) {
static_cast<BackgroundThread*>(_localInjectorsThread)->join();
delete _localInjectorsThread;
}
_checkDevicesTimer->stop();
}
void AudioClient::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
@ -1369,10 +1310,8 @@ bool AudioClient::outputLocalInjector(AudioInjector* injector) {
if (!_activeLocalAudioInjectors.contains(injector)) {
qCDebug(audioclient) << "adding new injector";
_activeLocalAudioInjectors.append(injector);
// move local buffer to the LocalAudioThread to avoid dataraces with AudioInjector (like stop())
injectorBuffer->setParent(nullptr);
injectorBuffer->moveToThread(_localInjectorsThread);
// update the flag
_localInjectorsAvailable.exchange(true, std::memory_order_release);
@ -1782,7 +1721,9 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
}
// prepare injectors for the next callback
QMetaObject::invokeMethod(_audio->_localInjectorsThread, "prepare", Qt::QueuedConnection);
QtConcurrent::run(QThreadPool::globalInstance(), [this] {
_audio->prepareLocalAudioInjectors();
});
int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped);
int framesPopped = samplesPopped / AudioConstants::STEREO;
@ -1855,3 +1796,8 @@ void AudioClient::setAvatarBoundingBoxParameters(glm::vec3 corner, glm::vec3 sca
avatarBoundingBoxCorner = corner;
avatarBoundingBoxScale = scale;
}
void AudioClient::startThread() {
moveToNewNamedThread(this, "Audio Thread", [this] { start(); });
}

View file

@ -104,6 +104,7 @@ public:
int _unfulfilledReads;
};
void startThread();
void negotiateAudioFormat();
void selectAudioFormat(const QString& selectedCodecName);
@ -386,8 +387,7 @@ private:
RateCounter<> _silentInbound;
RateCounter<> _audioInbound;
QThread* _checkDevicesThread { nullptr };
QThread* _localInjectorsThread { nullptr };
QTimer* _checkDevicesTimer { nullptr };
};

View file

@ -22,8 +22,6 @@
#include <QtCore/QUrl>
#include <QtNetwork/QHostInfo>
#include <tbb/parallel_for.h>
#include <LogHandler.h>
#include <shared/NetworkUtils.h>
#include <NumericalConstants.h>

View file

@ -31,7 +31,7 @@
#include <QtNetwork/QUdpSocket>
#include <QtNetwork/QHostAddress>
#include <tbb/concurrent_unordered_map.h>
#include <TBBHelpers.h>
#include <DependencyManager.h>
#include <SharedUtil.h>
@ -68,9 +68,8 @@ const QString USERNAME_UUID_REPLACEMENT_STATS_KEY = "$username";
const QString LOCAL_SOCKET_CHANGE_STAT = "LocalSocketChanges";
using namespace tbb;
typedef std::pair<QUuid, SharedNodePointer> UUIDNodePair;
typedef concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash;
typedef tbb::concurrent_unordered_map<QUuid, SharedNodePointer, UUIDHasher> NodeHash;
typedef quint8 PingType_t;
namespace PingType {

View file

@ -16,6 +16,8 @@
#include <QtCore/QBuffer>
#include <QtCore/QThread>
#include <ThreadHelpers.h>
#include "NetworkLogging.h"
#include "NodeList.h"
#include "PacketReceiver.h"
@ -30,12 +32,6 @@ MessagesClient::MessagesClient() {
connect(nodeList.data(), &LimitedNodeList::nodeActivated, this, &MessagesClient::handleNodeActivated);
}
void MessagesClient::init() {
if (QThread::currentThread() != thread()) {
QMetaObject::invokeMethod(this, "init", Qt::BlockingQueuedConnection);
}
}
void MessagesClient::decodeMessagesPacket(QSharedPointer<ReceivedMessage> receivedMessage, QString& channel,
bool& isText, QString& message, QByteArray& data, QUuid& senderID) {
quint16 channelLength;
@ -185,3 +181,7 @@ void MessagesClient::handleNodeActivated(SharedNodePointer node) {
}
}
}
void MessagesClient::startThread() {
moveToNewNamedThread(this, "Messages Client Thread");
}

View file

@ -28,7 +28,7 @@ class MessagesClient : public QObject, public Dependency {
public:
MessagesClient();
Q_INVOKABLE void init();
void startThread();
Q_INVOKABLE void sendMessage(QString channel, QString message, bool localOnly = false);
Q_INVOKABLE void sendLocalMessage(QString channel, QString message);

View file

@ -24,7 +24,7 @@
#include <QReadLocker>
#include <UUIDHasher.h>
#include <tbb/concurrent_unordered_set.h>
#include <TBBHelpers.h>
#include "HifiSockAddr.h"
#include "NetworkPeer.h"

View file

@ -20,6 +20,7 @@
#include <QtNetwork/QHostInfo>
#include <QtNetwork/QNetworkInterface>
#include <ThreadHelpers.h>
#include <LogHandler.h>
#include <UUID.h>
@ -1115,3 +1116,8 @@ void NodeList::setRequestsDomainListData(bool isRequesting) {
});
_requestsDomainListData = isRequesting;
}
void NodeList::startThread() {
moveToNewNamedThread(this, "NodeList Thread", QThread::TimeCriticalPriority);
}

View file

@ -20,7 +20,7 @@
#include <unistd.h> // not on windows, not needed for mac or windows
#endif
#include <tbb/concurrent_unordered_set.h>
#include <TBBHelpers.h>
#include <QtCore/QElapsedTimer>
#include <QtCore/QMutex>
@ -52,6 +52,7 @@ class NodeList : public LimitedNodeList {
SINGLETON_DEPENDENCY
public:
void startThread();
NodeType_t getOwnerType() const { return _ownerType.load(); }
void setOwnerType(NodeType_t ownerType) { _ownerType.store(ownerType); }

View file

@ -0,0 +1,113 @@
//
// CubicHermiteSpline.h
//
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CubicHermiteSpline_h
#define hifi_CubicHermiteSpline_h
#include "GLMHelpers.h"
class CubicHermiteSplineFunctor {
public:
CubicHermiteSplineFunctor() : _p0(), _m0(), _p1(), _m1() {}
CubicHermiteSplineFunctor(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : _p0(p0), _m0(m0), _p1(p1), _m1(m1) {}
CubicHermiteSplineFunctor(const CubicHermiteSplineFunctor& orig) : _p0(orig._p0), _m0(orig._m0), _p1(orig._p1), _m1(orig._m1) {}
virtual ~CubicHermiteSplineFunctor() {}
// evalute the hermite curve at parameter t (0..1)
glm::vec3 operator()(float t) const {
float t2 = t * t;
float t3 = t2 * t;
float w0 = 2.0f * t3 - 3.0f * t2 + 1.0f;
float w1 = t3 - 2.0f * t2 + t;
float w2 = -2.0f * t3 + 3.0f * t2;
float w3 = t3 - t2;
return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
}
// evaulate the first derivative of the hermite curve at parameter t (0..1)
glm::vec3 d(float t) const {
float t2 = t * t;
float w0 = -6.0f * t + 6.0f * t2;
float w1 = 1.0f - 4.0f * t + 3.0f * t2;
float w2 = 6.0f * t - 6.0f * t2;
float w3 = -2.0f * t + 3.0f * t2;
return w0 * _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
}
// evaulate the second derivative of the hermite curve at paramter t (0..1)
glm::vec3 d2(float t) const {
float w0 = -6.0f + 12.0f * t;
float w1 = -4.0f + 6.0f * t;
float w2 = 6.0f - 12.0f * t;
float w3 = -2.0f + 6.0f * t;
return w0 + _p0 + w1 * _m0 + w2 * _p1 + w3 * _m1;
}
protected:
glm::vec3 _p0;
glm::vec3 _m0;
glm::vec3 _p1;
glm::vec3 _m1;
};
class CubicHermiteSplineFunctorWithArcLength : public CubicHermiteSplineFunctor {
public:
enum Constants { NUM_SUBDIVISIONS = 30 };
CubicHermiteSplineFunctorWithArcLength() : CubicHermiteSplineFunctor() {
memset(_values, 0, sizeof(float) * (NUM_SUBDIVISIONS + 1));
}
CubicHermiteSplineFunctorWithArcLength(const glm::vec3& p0, const glm::vec3& m0, const glm::vec3& p1, const glm::vec3& m1) : CubicHermiteSplineFunctor(p0, m0, p1, m1) {
// initialize _values with the accumulated arcLength along the spline.
const float DELTA = 1.0f / NUM_SUBDIVISIONS;
float alpha = 0.0f;
float accum = 0.0f;
_values[0] = 0.0f;
for (int i = 1; i < NUM_SUBDIVISIONS + 1; i++) {
accum += glm::distance(this->operator()(alpha),
this->operator()(alpha + DELTA));
alpha += DELTA;
_values[i] = accum;
}
}
CubicHermiteSplineFunctorWithArcLength(const CubicHermiteSplineFunctorWithArcLength& orig) : CubicHermiteSplineFunctor(orig) {
memcpy(_values, orig._values, sizeof(float) * (NUM_SUBDIVISIONS + 1));
}
// given the spline parameter (0..1) output the arcLength of the spline up to that point.
float arcLength(float t) const {
float index = t * NUM_SUBDIVISIONS;
int prevIndex = std::min(std::max(0, (int)glm::floor(index)), (int)NUM_SUBDIVISIONS);
int nextIndex = std::min(std::max(0, (int)glm::ceil(index)), (int)NUM_SUBDIVISIONS);
float alpha = glm::fract(index);
return lerp(_values[prevIndex], _values[nextIndex], alpha);
}
// given an arcLength compute the spline parameter (0..1) that cooresponds to that arcLength.
float arcLengthInverse(float s) const {
// find first item in _values that is > s.
int nextIndex;
for (nextIndex = 0; nextIndex < NUM_SUBDIVISIONS; nextIndex++) {
if (_values[nextIndex] > s) {
break;
}
}
int prevIndex = std::min(std::max(0, nextIndex - 1), (int)NUM_SUBDIVISIONS);
float alpha = glm::clamp((s - _values[prevIndex]) / (_values[nextIndex] - _values[prevIndex]), 0.0f, 1.0f);
const float DELTA = 1.0f / NUM_SUBDIVISIONS;
return lerp(prevIndex * DELTA, nextIndex * DELTA, alpha);
}
protected:
float _values[NUM_SUBDIVISIONS + 1];
};
#endif // hifi_CubicHermiteSpline_h

View file

@ -0,0 +1,28 @@
//
// Created by Bradley Austin Davis on 2017/06/06
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#ifndef hifi_TBBHelpers_h
#define hifi_TBBHelpers_h
#ifdef _WIN32
#pragma warning( push )
#pragma warning( disable : 4334 )
#endif
#include <tbb/concurrent_queue.h>
#include <tbb/concurrent_unordered_map.h>
#include <tbb/concurrent_unordered_set.h>
#include <tbb/concurrent_vector.h>
#include <tbb/parallel_for.h>
#ifdef _WIN32
#pragma warning( pop )
#endif
#endif // hifi_TBBHelpers_h

View file

@ -0,0 +1,39 @@
//
// Created by Bradley Austin Davis on 2017/06/06
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "ThreadHelpers.h"
#include <QtCore/QDebug>
void moveToNewNamedThread(QObject* object, const QString& name, std::function<void()> startCallback, QThread::Priority priority) {
Q_ASSERT(QThread::currentThread() == object->thread());
// setup a thread for the NodeList and its PacketReceiver
QThread* thread = new QThread();
thread->setObjectName(name);
if (priority != QThread::InheritPriority) {
thread->setPriority(priority);
}
QString tempName = name;
QObject::connect(thread, &QThread::started, [startCallback] {
startCallback();
});
// Make sure the thread will be destroyed and cleaned up
QObject::connect(object, &QObject::destroyed, thread, &QThread::quit);
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
// put the object on the thread
object->moveToThread(thread);
thread->start();
}
void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority) {
moveToNewNamedThread(object, name, [] {}, priority);
}

View file

@ -12,8 +12,13 @@
#define hifi_ThreadHelpers_h
#include <exception>
#include <QMutex>
#include <QMutexLocker>
#include <functional>
#include <QtCore/QMutex>
#include <QtCore/QMutexLocker>
#include <QtCore/QObject>
#include <QtCore/QString>
#include <QtCore/QThread>
template <typename L, typename F>
void withLock(L lock, F function) {
@ -26,4 +31,7 @@ void withLock(QMutex& lock, F function) {
function();
}
void moveToNewNamedThread(QObject* object, const QString& name, std::function<void()> startCallback, QThread::Priority priority = QThread::InheritPriority);
void moveToNewNamedThread(QObject* object, const QString& name, QThread::Priority priority = QThread::InheritPriority);
#endif

View file

@ -146,7 +146,7 @@ private:
int _trackedControllers { 0 };
vr::IVRSystem*& _system;
quint64 _timeTilCalibration { 0.0f };
quint64 _timeTilCalibration { 0 };
float _leftHapticStrength { 0.0f };
float _leftHapticDuration { 0.0f };
float _rightHapticStrength { 0.0f };

View file

@ -32,7 +32,8 @@ var DEFAULT_SCRIPTS_COMBINED = [
"system/tablet-ui/tabletUI.js"
];
var DEFAULT_SCRIPTS_SEPARATE = [
"system/controllers/controllerScripts.js"
"system/controllers/controllerScripts.js",
"system/chat.js"
];
// add a menu item for debugging

View file

@ -9,7 +9,7 @@
(function() {
var webPageURL = "ChatPage.html"; // URL of tablet web page.
var webPageURL = Script.resolvePath("html/ChatPage.html"); // URL of tablet web page.
var randomizeWebPageURL = true; // Set to true for debugging.
var lastWebPageURL = ""; // Last random URL of tablet web page.
var onChatPage = false; // True when chat web page is opened.

View file

@ -3334,7 +3334,8 @@ function MyController(hand) {
// If both secondary triggers squeezed, and the non-holding hand is empty, start scaling
if (this.secondarySqueezed() &&
this.getOtherHandController().secondarySqueezed() &&
this.getOtherHandController().state === STATE_OFF) {
this.grabbedThingID && this.getOtherHandController().grabbedThingID &&
this.grabbedThingID == this.getOtherHandController().grabbedThingID) {
this.scalingStartDistance = Vec3.length(Vec3.subtract(this.getHandPosition(),
this.getOtherHandController().getHandPosition()));
this.scalingStartDimensions = props.dimensions;

View file

@ -25,9 +25,7 @@
var canWriteAssets = false;
var xmlHttpRequest = null;
var isPreparing = false; // Explicitly track download request status.
var lastPage = "https://metaverse.highfidelity.com/marketplace?";
function injectCommonCode(isDirectoryPage) {
// Supporting styles from marketplaces.css.
@ -67,7 +65,7 @@
// Footer actions.
$("#back-button").on("click", function () {
window.location = lastPage;
window.history.back();
});
$("#all-markets").on("click", function () {
EventBridge.emitWebEvent(GOTO_DIRECTORY);
@ -344,12 +342,7 @@
}
}
function locationChanged() {
lastPage = location.href;
}
// Load / unload.
window.addEventListener("load", onLoad); // More robust to Web site issues than using $(document).ready().
window.addEventListener("hashchange", locationChanged);
}());

15
tests/qt59/CMakeLists.txt Normal file
View file

@ -0,0 +1,15 @@
set(TARGET_NAME qt59)
if (WIN32)
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217")
endif()
# This is not a testcase -- just set it up as a regular hifi project
setup_hifi_project(Gui)
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
# link in the shared libraries
link_hifi_libraries(shared networking)
package_libraries_for_deployment()

78
tests/qt59/src/main.cpp Normal file
View file

@ -0,0 +1,78 @@
//
// Created by Bradley Austin Davis on 2017/06/06
// Copyright 2013-2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <mutex>
#include <QtCore/QCoreApplication>
#include <NodeList.h>
#include <AccountManager.h>
#include <AddressManager.h>
#include <MessagesClient.h>
#include <BuildInfo.h>
class Qt59TestApp : public QCoreApplication {
Q_OBJECT
public:
Qt59TestApp(int argc, char* argv[]);
~Qt59TestApp();
private:
void finish(int exitCode);
};
Qt59TestApp::Qt59TestApp(int argc, char* argv[]) :
QCoreApplication(argc, argv)
{
Setting::init();
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
DependencyManager::set<AccountManager>([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); });
DependencyManager::set<AddressManager>();
DependencyManager::set<NodeList>(NodeType::Agent, 0);
auto nodeList = DependencyManager::get<NodeList>();
nodeList->startThread();
auto messagesClient = DependencyManager::set<MessagesClient>();
messagesClient->startThread();
QTimer::singleShot(1000, [this] { finish(0); });
}
Qt59TestApp::~Qt59TestApp() {
}
void Qt59TestApp::finish(int exitCode) {
auto nodeList = DependencyManager::get<NodeList>();
// send the domain a disconnect packet, force stoppage of domain-server check-ins
nodeList->getDomainHandler().disconnect();
nodeList->setIsShuttingDown(true);
nodeList->getPacketReceiver().setShouldDropPackets(true);
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
QCoreApplication::exit(exitCode);
}
int main(int argc, char * argv[]) {
QCoreApplication::setApplicationName("Qt59Test");
QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
Qt59TestApp app(argc, argv);
return app.exec();
}
#include "main.moc"

View file

@ -0,0 +1,77 @@
//
// CubicHermiteSplineTests.cpp
// tests/shared/src
//
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "CubicHermiteSplineTests.h"
#include "../QTestExtensions.h"
#include <QtCore/QDebug>
#include "CubicHermiteSpline.h"
QTEST_MAIN(CubicHermiteSplineTests)
void CubicHermiteSplineTests::testCubicHermiteSplineFunctor() {
glm::vec3 p0(0.0f, 0.0f, 0.0f);
glm::vec3 m0(1.0f, 0.0f, 0.0f);
glm::vec3 p1(1.0f, 1.0f, 0.0f);
glm::vec3 m1(2.0f, 0.0f, 0.0f);
CubicHermiteSplineFunctor hermiteSpline(p0, m0, p1, m1);
const float EPSILON = 0.0001f;
QCOMPARE_WITH_ABS_ERROR(p0, hermiteSpline(0.0f), EPSILON);
QCOMPARE_WITH_ABS_ERROR(p1, hermiteSpline(1.0f), EPSILON);
// these values were computed offline.
const glm::vec3 oneFourth(0.203125f, 0.15625f, 0.0f);
const glm::vec3 oneHalf(0.375f, 0.5f, 0.0f);
const glm::vec3 threeFourths(0.609375f, 0.84375f, 0.0f);
QCOMPARE_WITH_ABS_ERROR(oneFourth, hermiteSpline(0.25f), EPSILON);
QCOMPARE_WITH_ABS_ERROR(oneHalf, hermiteSpline(0.5f), EPSILON);
QCOMPARE_WITH_ABS_ERROR(threeFourths, hermiteSpline(0.75f), EPSILON);
}
void CubicHermiteSplineTests::testCubicHermiteSplineFunctorWithArcLength() {
glm::vec3 p0(0.0f, 0.0f, 0.0f);
glm::vec3 m0(1.0f, 0.0f, 0.0f);
glm::vec3 p1(1.0f, 1.0f, 0.0f);
glm::vec3 m1(2.0f, 0.0f, 0.0f);
CubicHermiteSplineFunctorWithArcLength hermiteSpline(p0, m0, p1, m1);
const float EPSILON = 0.001f;
float arcLengths[5] = {
hermiteSpline.arcLength(0.0f),
hermiteSpline.arcLength(0.25f),
hermiteSpline.arcLength(0.5f),
hermiteSpline.arcLength(0.75f),
hermiteSpline.arcLength(1.0f)
};
// these values were computed offline
float referenceArcLengths[5] = {
0.0f,
0.268317f,
0.652788f,
1.07096f,
1.50267f
};
QCOMPARE_WITH_ABS_ERROR(arcLengths[0], referenceArcLengths[0], EPSILON);
QCOMPARE_WITH_ABS_ERROR(arcLengths[1], referenceArcLengths[1], EPSILON);
QCOMPARE_WITH_ABS_ERROR(arcLengths[2], referenceArcLengths[2], EPSILON);
QCOMPARE_WITH_ABS_ERROR(arcLengths[3], referenceArcLengths[3], EPSILON);
QCOMPARE_WITH_ABS_ERROR(arcLengths[4], referenceArcLengths[4], EPSILON);
QCOMPARE_WITH_ABS_ERROR(0.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[0]), EPSILON);
QCOMPARE_WITH_ABS_ERROR(0.25f, hermiteSpline.arcLengthInverse(referenceArcLengths[1]), EPSILON);
QCOMPARE_WITH_ABS_ERROR(0.5f, hermiteSpline.arcLengthInverse(referenceArcLengths[2]), EPSILON);
QCOMPARE_WITH_ABS_ERROR(0.75f, hermiteSpline.arcLengthInverse(referenceArcLengths[3]), EPSILON);
QCOMPARE_WITH_ABS_ERROR(1.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[4]), EPSILON);
}

View file

@ -0,0 +1,23 @@
//
// CubicHermiteSplineTests.h
// tests/shared/src
//
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_CubicHermiteSplineTests_h
#define hifi_CubicHermiteSplineTests_h
#include <QtTest/QtTest>
class CubicHermiteSplineTests : public QObject {
Q_OBJECT
private slots:
void testCubicHermiteSplineFunctor();
void testCubicHermiteSplineFunctorWithArcLength();
};
#endif // hifi_TransformTests_h

View file

@ -90,23 +90,14 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
auto nodeList = DependencyManager::get<NodeList>();
// start the nodeThread so its event loop is running
QThread* nodeThread = new QThread(this);
nodeThread->setObjectName("NodeList Thread");
nodeThread->start();
// make sure the node thread is given highest priority
nodeThread->setPriority(QThread::TimeCriticalPriority);
nodeList->startThread();
// setup a timer for domain-server check ins
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
// put the NodeList and datagram processing on the node thread
nodeList->moveToThread(nodeThread);
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
@ -239,12 +230,8 @@ void ACClientApp::finish(int exitCode) {
// tell the packet receiver we're shutting down, so it can drop packets
nodeList->getPacketReceiver().setShouldDropPackets(true);
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
// ask the node thread to quit and wait until it is done
nodeThread->quit();
nodeThread->wait();
printFailedServers();
QCoreApplication::exit(exitCode);

View file

@ -111,23 +111,13 @@ ATPGetApp::ATPGetApp(int argc, char* argv[]) :
auto nodeList = DependencyManager::get<NodeList>();
// start the nodeThread so its event loop is running
QThread* nodeThread = new QThread(this);
nodeThread->setObjectName("NodeList Thread");
nodeThread->start();
// make sure the node thread is given highest priority
nodeThread->setPriority(QThread::TimeCriticalPriority);
nodeList->startThread();
// setup a timer for domain-server check ins
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
// put the NodeList and datagram processing on the node thread
nodeList->moveToThread(nodeThread);
const DomainHandler& domainHandler = nodeList->getDomainHandler();
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
@ -258,12 +248,8 @@ void ATPGetApp::finish(int exitCode) {
// tell the packet receiver we're shutting down, so it can drop packets
nodeList->getPacketReceiver().setShouldDropPackets(true);
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
// remove the NodeList from the DependencyManager
DependencyManager::destroy<NodeList>();
// ask the node thread to quit and wait until it is done
nodeThread->quit();
nodeThread->wait();
QCoreApplication::exit(exitCode);
}

View file

@ -0,0 +1,64 @@
//
// BakerCLI.cpp
// tools/oven/src
//
// Created by Robbie Uvanni on 6/6/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include <QObject>
#include <QImageReader>
#include <QtCore/QDebug>
#include "ModelBakingLoggingCategory.h"
#include "Oven.h"
#include "BakerCLI.h"
#include "FBXBaker.h"
#include "TextureBaker.h"
BakerCLI::BakerCLI(Oven* parent) : QObject(parent) {
}
void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) {
// if the URL doesn't have a scheme, assume it is a local file
if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") {
inputUrl.setScheme("file");
}
static const QString MODEL_EXTENSION { ".fbx" };
// check what kind of baker we should be creating
bool isFBX = inputUrl.toDisplayString().endsWith(MODEL_EXTENSION, Qt::CaseInsensitive);
bool isSupportedImage = false;
for (QByteArray format : QImageReader::supportedImageFormats()) {
isSupportedImage |= inputUrl.toDisplayString().endsWith(format, Qt::CaseInsensitive);
}
// create our appropiate baker
if (isFBX) {
_baker = std::unique_ptr<Baker> { new FBXBaker(inputUrl, outputPath, []() -> QThread* { return qApp->getNextWorkerThread(); }) };
_baker->moveToThread(qApp->getFBXBakerThread());
} else if (isSupportedImage) {
_baker = std::unique_ptr<Baker> { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) };
_baker->moveToThread(qApp->getNextWorkerThread());
} else {
qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl;
return;
}
// invoke the bake method on the baker thread
QMetaObject::invokeMethod(_baker.get(), "bake");
// make sure we hear about the results of this baker when it is done
connect(_baker.get(), &Baker::finished, this, &BakerCLI::handleFinishedBaker);
}
void BakerCLI::handleFinishedBaker() {
qCDebug(model_baking) << "Finished baking file.";
QApplication::quit();
}

34
tools/oven/src/BakerCLI.h Normal file
View file

@ -0,0 +1,34 @@
//
// BakerCLI.h
// tools/oven/src
//
// Created by Robbie Uvanni on 6/6/17.
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#ifndef hifi_BakerCLI_h
#define hifi_BakerCLI_h
#include <QtCore/QObject>
#include "Baker.h"
#include "Oven.h"
class BakerCLI : public QObject {
Q_OBJECT
public:
BakerCLI(Oven* parent);
void bakeFile(QUrl inputUrl, const QString outputPath);
private slots:
void handleFinishedBaker();
private:
std::unique_ptr<Baker> _baker;
};
#endif // hifi_BakerCLI_h

View file

@ -11,16 +11,20 @@
#include <QtCore/QDebug>
#include <QtCore/QThread>
#include <QtCore/QCommandLineParser>
#include <image/Image.h>
#include <SettingInterface.h>
#include "ui/OvenMainWindow.h"
#include "Oven.h"
#include "BakerCli.h"
static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export";
static const QString CLI_INPUT_PARAMETER = "i";
static const QString CLI_OUTPUT_PARAMETER = "o";
Oven::Oven(int argc, char* argv[]) :
QApplication(argc, argv)
{
@ -30,24 +34,43 @@ Oven::Oven(int argc, char* argv[]) :
// init the settings interface so we can save and load settings
Setting::init();
// parse the command line parameters
QCommandLineParser parser;
parser.addOptions({
{ CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" },
{ CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }
});
parser.addHelpOption();
parser.process(*this);
// enable compression in image library, except for cube maps
image::setColorTexturesCompressionEnabled(true);
image::setGrayscaleTexturesCompressionEnabled(true);
image::setNormalTexturesCompressionEnabled(true);
image::setCubeTexturesCompressionEnabled(true);
// check if we were passed any command line arguments that would tell us just to run without the GUI
// setup the GUI
_mainWindow = new OvenMainWindow;
_mainWindow->show();
// setup our worker threads
setupWorkerThreads(QThread::idealThreadCount() - 1);
// Autodesk's SDK means that we need a single thread for all FBX importing/exporting in the same process
// setup the FBX Baker thread
setupFBXBakerThread();
// check if we were passed any command line arguments that would tell us just to run without the GUI
if (parser.isSet(CLI_INPUT_PARAMETER) || parser.isSet(CLI_OUTPUT_PARAMETER)) {
if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) {
BakerCLI* cli = new BakerCLI(this);
cli->bakeFile(parser.value(CLI_INPUT_PARAMETER), parser.value(CLI_OUTPUT_PARAMETER));
} else {
parser.showHelp();
QApplication::quit();
}
} else {
// setup the GUI
_mainWindow = new OvenMainWindow;
_mainWindow->show();
}
}
Oven::~Oven() {

View file

@ -14,7 +14,7 @@
#include <QtWidgets/QApplication>
#include <tbb/concurrent_vector.h>
#include <TBBHelpers.h>
#include <atomic>