mirror of
https://github.com/JulianGro/overte.git
synced 2025-04-25 17:14:59 +02:00
Merge branch 'master' of github.com:highfidelity/hifi into fix-head-puck-broken-eyes
This commit is contained in:
commit
197fb620d4
52 changed files with 1069 additions and 297 deletions
|
@ -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);
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -16,10 +16,10 @@
|
|||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <tbb/concurrent_queue.h>
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include <TBBHelpers.h>
|
||||
|
||||
#include "AudioMixerSlave.h"
|
||||
|
||||
class AudioMixerSlavePool;
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <tbb/concurrent_queue.h>
|
||||
|
||||
#include <QThread>
|
||||
|
||||
#include <TBBHelpers.h>
|
||||
#include <NodeList.h>
|
||||
|
||||
#include "AvatarMixerSlave.h"
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ class CloseEventSender : public QObject, public Dependency {
|
|||
SINGLETON_DEPENDENCY
|
||||
|
||||
public:
|
||||
void startThread();
|
||||
bool hasTimedOutQuitEvent();
|
||||
bool hasFinishedQuitEvent() { return _hasFinishedQuitEvent; }
|
||||
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -41,7 +41,6 @@ private:
|
|||
static const int _voiceTimeoutDuration;
|
||||
|
||||
QTimer _voiceTimer;
|
||||
QThread _connectionThread;
|
||||
LimitlessConnection _connection;
|
||||
|
||||
void voiceTimeout();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ public:
|
|||
RotationOnly,
|
||||
HmdHead,
|
||||
HipsRelativeRotationAndPosition,
|
||||
Spline,
|
||||
Unknown
|
||||
};
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -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__) */
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
34
libraries/animation/src/TranslationAccumulator.cpp
Normal file
34
libraries/animation/src/TranslationAccumulator.cpp
Normal 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;
|
||||
}
|
42
libraries/animation/src/TranslationAccumulator.h
Normal file
42
libraries/animation/src/TranslationAccumulator.h
Normal 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
|
|
@ -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(); });
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#include <QReadLocker>
|
||||
#include <UUIDHasher.h>
|
||||
|
||||
#include <tbb/concurrent_unordered_set.h>
|
||||
#include <TBBHelpers.h>
|
||||
|
||||
#include "HifiSockAddr.h"
|
||||
#include "NetworkPeer.h"
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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); }
|
||||
|
||||
|
|
113
libraries/shared/src/CubicHermiteSpline.h
Normal file
113
libraries/shared/src/CubicHermiteSpline.h
Normal 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
|
28
libraries/shared/src/TBBHelpers.h
Normal file
28
libraries/shared/src/TBBHelpers.h
Normal 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
|
39
libraries/shared/src/ThreadHelpers.cpp
Normal file
39
libraries/shared/src/ThreadHelpers.cpp
Normal 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);
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
|
@ -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;
|
||||
|
|
|
@ -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
15
tests/qt59/CMakeLists.txt
Normal 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
78
tests/qt59/src/main.cpp
Normal 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"
|
77
tests/shared/src/CubicHermiteSplineTests.cpp
Normal file
77
tests/shared/src/CubicHermiteSplineTests.cpp
Normal 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);
|
||||
}
|
23
tests/shared/src/CubicHermiteSplineTests.h
Normal file
23
tests/shared/src/CubicHermiteSplineTests.h
Normal 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
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
64
tools/oven/src/BakerCLI.cpp
Normal file
64
tools/oven/src/BakerCLI.cpp
Normal 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
34
tools/oven/src/BakerCLI.h
Normal 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
|
|
@ -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() {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <TBBHelpers.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
|
|
Loading…
Reference in a new issue