mirror of
https://github.com/overte-org/overte.git
synced 2025-04-20 04:44:11 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into orange
This commit is contained in:
commit
bb38938534
70 changed files with 1310 additions and 333 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",
|
||||
|
|
|
@ -177,7 +177,7 @@ ScrollingWindow {
|
|||
SHAPE_TYPE_STATIC_MESH
|
||||
],
|
||||
checkStateOnDisable: false,
|
||||
warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic"
|
||||
warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors"
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -179,7 +179,7 @@ Rectangle {
|
|||
SHAPE_TYPE_STATIC_MESH
|
||||
],
|
||||
checkStateOnDisable: false,
|
||||
warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic"
|
||||
warningOnDisable: "Models with 'Exact' automatic collisions cannot be dynamic, and should not be used as floors"
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ Rectangle {
|
|||
id: text2
|
||||
width: 160
|
||||
color: "#ffffff"
|
||||
text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic")
|
||||
text: qsTr("Models with automatic collisions set to 'Exact' cannot be dynamic, and should not be used as floors")
|
||||
wrapMode: Text.WordWrap
|
||||
font.pixelSize: 12
|
||||
}
|
||||
|
|
|
@ -654,6 +654,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();
|
||||
|
@ -679,25 +680,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([]{
|
||||
|
@ -713,7 +700,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);
|
||||
});
|
||||
|
@ -727,9 +713,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);
|
||||
|
@ -747,19 +730,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();
|
||||
|
||||
|
@ -1679,12 +1657,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");
|
||||
|
@ -1772,6 +1745,8 @@ void Application::cleanupBeforeQuit() {
|
|||
// stop QML
|
||||
DependencyManager::destroy<OffscreenUi>();
|
||||
|
||||
DependencyManager::destroy<OffscreenQmlSurfaceCache>();
|
||||
|
||||
if (_snapshotSoundInjector != nullptr) {
|
||||
_snapshotSoundInjector->stop();
|
||||
}
|
||||
|
@ -1831,15 +1806,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()) {
|
||||
|
@ -4152,10 +4121,10 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
}
|
||||
} else {
|
||||
// I am not looking at anyone else, so just look forward
|
||||
if (isHMD) {
|
||||
glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() *
|
||||
myAvatar->getHeadControllerPoseInSensorFrame().getMatrix();
|
||||
lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE));
|
||||
auto headPose = myAvatar->getHeadControllerPoseInSensorFrame();
|
||||
if (headPose.isValid()) {
|
||||
glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() * headPose.getMatrix();
|
||||
lookAtSpot = transformPoint(worldHeadMat, glm::vec3(0.0f, 0.0f, TREE_SCALE));
|
||||
} else {
|
||||
lookAtSpot = myAvatar->getHead()->getEyePosition() +
|
||||
(myAvatar->getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE));
|
||||
|
|
|
@ -21,15 +21,35 @@
|
|||
|
||||
#include "MainWindow.h"
|
||||
#include "Menu.h"
|
||||
|
||||
#include "AvatarBookmarks.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
||||
#include <QtQuick/QQuickWindow>
|
||||
|
||||
AvatarBookmarks::AvatarBookmarks() {
|
||||
_bookmarksFilename = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
|
||||
_bookmarksFilename = PathUtils::getAppDataPath() + "/" + AVATARBOOKMARKS_FILENAME;
|
||||
readFromFile();
|
||||
}
|
||||
|
||||
void AvatarBookmarks::readFromFile() {
|
||||
// migrate old avatarbookmarks.json, used to be in 'local' folder on windows
|
||||
QString oldConfigPath = QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + AVATARBOOKMARKS_FILENAME;
|
||||
QFile oldConfig(oldConfigPath);
|
||||
|
||||
// I imagine that in a year from now, this code for migrating (as well as the two lines above)
|
||||
// may be removed since all bookmarks should have been migrated by then
|
||||
// - Robbie Uvanni (6.8.2017)
|
||||
if (oldConfig.exists()) {
|
||||
if (QDir().rename(oldConfigPath, _bookmarksFilename)) {
|
||||
qCDebug(interfaceapp) << "Successfully migrated" << AVATARBOOKMARKS_FILENAME;
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Failed to migrate" << AVATARBOOKMARKS_FILENAME;
|
||||
}
|
||||
}
|
||||
|
||||
Bookmarks::readFromFile();
|
||||
}
|
||||
|
||||
void AvatarBookmarks::setupMenus(Menu* menubar, MenuWrapper* menu) {
|
||||
// Add menus/actions
|
||||
auto bookmarkAction = menubar->addActionToQMenuAndActionHash(menu, MenuOption::BookmarkAvatar);
|
||||
|
|
|
@ -29,6 +29,7 @@ public slots:
|
|||
|
||||
protected:
|
||||
void addBookmarkToMenu(Menu* menubar, const QString& name, const QString& address) override;
|
||||
void readFromFile();
|
||||
|
||||
private:
|
||||
const QString AVATARBOOKMARKS_FILENAME = "avatarbookmarks.json";
|
||||
|
|
|
@ -294,6 +294,7 @@ void MyAvatar::simulateAttachments(float deltaTime) {
|
|||
QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
|
||||
CameraMode mode = qApp->getCamera().getMode();
|
||||
_globalPosition = getPosition();
|
||||
// This might not be right! Isn't the capsule local offset in avatar space, and don't we need to add the radius to the y as well? -HRS 5/26/17
|
||||
_globalBoundingBoxDimensions.x = _characterController.getCapsuleRadius();
|
||||
_globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight();
|
||||
_globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius();
|
||||
|
@ -436,6 +437,13 @@ void MyAvatar::update(float deltaTime) {
|
|||
// so we update now. It's ok if it updates again in the normal way.
|
||||
updateSensorToWorldMatrix();
|
||||
emit positionGoneTo();
|
||||
// Run safety tests as soon as we can after goToLocation, or clear if we're not colliding.
|
||||
_physicsSafetyPending = getCollisionsEnabled();
|
||||
}
|
||||
if (_physicsSafetyPending && qApp->isPhysicsEnabled() && _characterController.isEnabledAndReady()) {
|
||||
// When needed and ready, arrange to check and fix.
|
||||
_physicsSafetyPending = false;
|
||||
safeLanding(_goToPosition); // no-op if already safe
|
||||
}
|
||||
|
||||
Head* head = getHead();
|
||||
|
@ -450,6 +458,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
setAudioAverageLoudness(audio->getAudioAverageInputLoudness());
|
||||
|
||||
glm::vec3 halfBoundingBoxDimensions(_characterController.getCapsuleRadius(), _characterController.getCapsuleHalfHeight(), _characterController.getCapsuleRadius());
|
||||
// This might not be right! Isn't the capsule local offset in avatar space? -HRS 5/26/17
|
||||
halfBoundingBoxDimensions += _characterController.getCapsuleLocalOffset();
|
||||
QMetaObject::invokeMethod(audio.data(), "setAvatarBoundingBoxParameters",
|
||||
Q_ARG(glm::vec3, (getPosition() - halfBoundingBoxDimensions)),
|
||||
|
@ -1552,6 +1561,10 @@ void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
|||
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
|
||||
if (_characterController.isStuck()) {
|
||||
_physicsSafetyPending = true;
|
||||
_goToPosition = getPosition();
|
||||
}
|
||||
} else {
|
||||
setVelocity(getVelocity() + _characterController.getFollowVelocity());
|
||||
}
|
||||
|
@ -2224,6 +2237,144 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
|
|||
emit transformChanged();
|
||||
}
|
||||
|
||||
void MyAvatar::goToLocationAndEnableCollisions(const glm::vec3& position) { // See use case in safeLanding.
|
||||
goToLocation(position);
|
||||
QMetaObject::invokeMethod(this, "setCollisionsEnabled", Qt::QueuedConnection, Q_ARG(bool, true));
|
||||
}
|
||||
bool MyAvatar::safeLanding(const glm::vec3& position) {
|
||||
// Considers all collision hull or non-collisionless primitive intersections on a vertical line through the point.
|
||||
// There needs to be a "landing" if:
|
||||
// a) the closest above and the closest below are less than the avatar capsule height apart, or
|
||||
// b) the above point is the top surface of an entity, indicating that we are inside it.
|
||||
// If no landing is required, we go to that point directly and return false;
|
||||
// When a landing is required by a, we find the highest intersection on that closest-agbove entity
|
||||
// (which may be that same "nearest above intersection"). That highest intersection is the candidate landing point.
|
||||
// For b, use that top surface point.
|
||||
// We then place our feet there, recurse with new capsule center point, and return true.
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
bool result;
|
||||
QMetaObject::invokeMethod(this, "safeLanding", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result), Q_ARG(const glm::vec3&, position));
|
||||
return result;
|
||||
}
|
||||
glm::vec3 better;
|
||||
if (!requiresSafeLanding(position, better)) {
|
||||
return false;
|
||||
}
|
||||
if (!getCollisionsEnabled()) {
|
||||
goToLocation(better); // recurses on next update
|
||||
} else { // If you try to go while stuck, physics will keep you stuck.
|
||||
setCollisionsEnabled(false);
|
||||
// Don't goToLocation just yet. Yield so that physics can act on the above.
|
||||
QMetaObject::invokeMethod(this, "goToLocationAndEnableCollisions", Qt::QueuedConnection, // The equivalent of javascript nextTick
|
||||
Q_ARG(glm::vec3, better));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If position is not reliably safe from being stuck by physics, answer true and place a candidate better position in betterPositionOut.
|
||||
bool MyAvatar::requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& betterPositionOut) {
|
||||
// We begin with utilities and tests. The Algorithm in four parts is below.
|
||||
auto halfHeight = _characterController.getCapsuleHalfHeight() + _characterController.getCapsuleRadius();
|
||||
if (halfHeight == 0) {
|
||||
return false; // zero height avatar
|
||||
}
|
||||
auto entityTree = DependencyManager::get<EntityTreeRenderer>()->getTree();
|
||||
if (!entityTree) {
|
||||
return false; // no entity tree
|
||||
}
|
||||
// More utilities.
|
||||
const auto offset = getOrientation() *_characterController.getCapsuleLocalOffset();
|
||||
const auto capsuleCenter = positionIn + offset;
|
||||
const auto up = _worldUpDirection, down = -up;
|
||||
glm::vec3 upperIntersection, upperNormal, lowerIntersection, lowerNormal;
|
||||
EntityItemID upperId, lowerId;
|
||||
QVector<EntityItemID> include{}, ignore{};
|
||||
auto mustMove = [&] { // Place bottom of capsule at the upperIntersection, and check again based on the capsule center.
|
||||
betterPositionOut = upperIntersection + (up * halfHeight) - offset;
|
||||
return true;
|
||||
};
|
||||
auto findIntersection = [&](const glm::vec3& startPointIn, const glm::vec3& directionIn, glm::vec3& intersectionOut, EntityItemID& entityIdOut, glm::vec3& normalOut) {
|
||||
OctreeElementPointer element;
|
||||
EntityItemPointer intersectedEntity = NULL;
|
||||
float distance;
|
||||
BoxFace face;
|
||||
const bool visibleOnly = false;
|
||||
// This isn't quite what we really want here. findRayIntersection always works on mesh, skipping entirely based on collidable.
|
||||
// What we really want is to use the collision hull!
|
||||
// See https://highfidelity.fogbugz.com/f/cases/5003/findRayIntersection-has-option-to-use-collidableOnly-but-doesn-t-actually-use-colliders
|
||||
const bool collidableOnly = true;
|
||||
const bool precisionPicking = true;
|
||||
const auto lockType = Octree::Lock; // Should we refactor to take a lock just once?
|
||||
bool* accurateResult = NULL;
|
||||
|
||||
bool intersects = entityTree->findRayIntersection(startPointIn, directionIn, include, ignore, visibleOnly, collidableOnly, precisionPicking,
|
||||
element, distance, face, normalOut, (void**)&intersectedEntity, lockType, accurateResult);
|
||||
if (!intersects || !intersectedEntity) {
|
||||
return false;
|
||||
}
|
||||
intersectionOut = startPointIn + (directionIn * distance);
|
||||
entityIdOut = intersectedEntity->getEntityItemID();
|
||||
return true;
|
||||
};
|
||||
|
||||
// The Algorithm, in four parts:
|
||||
|
||||
if (!findIntersection(capsuleCenter, up, upperIntersection, upperId, upperNormal)) {
|
||||
// We currently believe that physics will reliably push us out if our feet are embedded,
|
||||
// as long as our capsule center is out and there's room above us. Here we have those
|
||||
// conditions, so no need to check our feet below.
|
||||
return false; // nothing above
|
||||
}
|
||||
|
||||
if (!findIntersection(capsuleCenter, down, lowerIntersection, lowerId, lowerNormal)) {
|
||||
// Our head may be embedded, but our center is out and there's room below. See corresponding comment above.
|
||||
return false; // nothing below
|
||||
}
|
||||
|
||||
// See if we have room between entities above and below, but that we are not contained.
|
||||
// First check if the surface above us is the bottom of something, and the surface below us it the top of something.
|
||||
// I.e., we are in a clearing between two objects.
|
||||
if (isDown(upperNormal) && isUp(lowerNormal)) {
|
||||
auto spaceBetween = glm::distance(upperIntersection, lowerIntersection);
|
||||
const float halfHeightFactor = 2.5f; // Until case 5003 is fixed (and maybe after?), we need a fudge factor. Also account for content modelers not being precise.
|
||||
if (spaceBetween > (halfHeightFactor * halfHeight)) {
|
||||
// There is room for us to fit in that clearing. If there wasn't, physics would oscilate us between the objects above and below.
|
||||
// We're now going to iterate upwards through successive upperIntersections, testing to see if we're contained within the top surface of some entity.
|
||||
// There will be one of two outcomes:
|
||||
// a) We're not contained, so we have enough room and our position is good.
|
||||
// b) We are contained, so we'll bail out of this but try again at a position above the containing entity.
|
||||
const int iterationLimit = 1000;
|
||||
for (int counter = 0; counter < iterationLimit; counter++) {
|
||||
ignore.push_back(upperId);
|
||||
if (!findIntersection(upperIntersection, up, upperIntersection, upperId, upperNormal)) {
|
||||
// We're not inside an entity, and from the nested tests, we have room between what is above and below. So position is good!
|
||||
return false; // enough room
|
||||
}
|
||||
if (isUp(upperNormal)) {
|
||||
// This new intersection is the top surface of an entity that we have not yet seen, which means we're contained within it.
|
||||
// We could break here and recurse from the top of the original ceiling, but since we've already done the work to find the top
|
||||
// of the enclosing entity, let's put our feet at upperIntersection and start over.
|
||||
return mustMove();
|
||||
}
|
||||
// We found a new bottom surface, which we're not interested in.
|
||||
// But there could still be a top surface above us for an entity we haven't seen, so keep looking upward.
|
||||
}
|
||||
qCDebug(interfaceapp) << "Loop in requiresSafeLanding. Floor/ceiling do not make sense.";
|
||||
}
|
||||
}
|
||||
|
||||
include.push_back(upperId); // We're now looking for the intersection from above onto this entity.
|
||||
const float big = (float)TREE_SCALE;
|
||||
const auto skyHigh = up * big;
|
||||
auto fromAbove = capsuleCenter + skyHigh;
|
||||
if (!findIntersection(fromAbove, down, upperIntersection, upperId, upperNormal)) {
|
||||
return false; // Unable to find a landing
|
||||
}
|
||||
// Our arbitrary rule is to always go up. There's no need to look down or sideways for a "closer" safe candidate.
|
||||
return mustMove();
|
||||
}
|
||||
|
||||
void MyAvatar::updateMotionBehaviorFromMenu() {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
|
@ -2513,8 +2664,8 @@ bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, co
|
|||
return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
||||
_desiredBodyMatrix = desiredBodyMatrix;
|
||||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix,
|
||||
const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
||||
|
||||
if (myAvatar.getHMDLeanRecenterEnabled()) {
|
||||
if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
|
@ -2528,7 +2679,7 @@ void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat
|
|||
}
|
||||
}
|
||||
|
||||
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix;
|
||||
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * desiredBodyMatrix;
|
||||
glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix;
|
||||
|
||||
AnimPose followWorldPose(currentWorldMatrix);
|
||||
|
|
|
@ -510,6 +510,10 @@ public:
|
|||
// results are in HMD frame
|
||||
glm::mat4 deriveBodyFromHMDSensor() const;
|
||||
|
||||
Q_INVOKABLE bool isUp(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) > 0.0f; }; // true iff direction points up wrt avatar's definition of up.
|
||||
Q_INVOKABLE bool isDown(const glm::vec3& direction) { return glm::dot(direction, _worldUpDirection) < 0.0f; };
|
||||
|
||||
|
||||
public slots:
|
||||
void increaseSize();
|
||||
void decreaseSize();
|
||||
|
@ -519,6 +523,8 @@ public slots:
|
|||
bool hasOrientation = false, const glm::quat& newOrientation = glm::quat(),
|
||||
bool shouldFaceLocation = false);
|
||||
void goToLocation(const QVariant& properties);
|
||||
void goToLocationAndEnableCollisions(const glm::vec3& newPosition);
|
||||
bool safeLanding(const glm::vec3& position);
|
||||
|
||||
void restrictScaleFromDomainSettings(const QJsonObject& domainSettingsObject);
|
||||
void clearScaleRestriction();
|
||||
|
@ -564,6 +570,8 @@ signals:
|
|||
|
||||
private:
|
||||
|
||||
bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut);
|
||||
|
||||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override;
|
||||
|
||||
void simulate(float deltaTime);
|
||||
|
@ -694,7 +702,6 @@ private:
|
|||
Vertical,
|
||||
NumFollowTypes
|
||||
};
|
||||
glm::mat4 _desiredBodyMatrix;
|
||||
float _timeRemaining[NumFollowTypes];
|
||||
|
||||
void deactivate();
|
||||
|
@ -713,7 +720,8 @@ private:
|
|||
};
|
||||
FollowHelper _follow;
|
||||
|
||||
bool _goToPending;
|
||||
bool _goToPending { false };
|
||||
bool _physicsSafetyPending { false };
|
||||
glm::vec3 _goToPosition;
|
||||
glm::quat _goToOrientation;
|
||||
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -605,7 +605,7 @@ bool RenderableModelEntityItem::findDetailedRayIntersection(const glm::vec3& ori
|
|||
|
||||
QString extraInfo;
|
||||
return _model->findRayIntersectionAgainstSubMeshes(origin, direction, distance,
|
||||
face, surfaceNormal, extraInfo, precisionPicking);
|
||||
face, surfaceNormal, extraInfo, precisionPicking, precisionPicking);
|
||||
}
|
||||
|
||||
void RenderableModelEntityItem::getCollisionGeometryResource() {
|
||||
|
|
|
@ -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); }
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
#include <DependencyManager.h>
|
||||
#include "AddressManager.h"
|
||||
|
||||
UserActivityLogger::UserActivityLogger() {
|
||||
_timer.start();
|
||||
}
|
||||
|
||||
UserActivityLogger& UserActivityLogger::getInstance() {
|
||||
static UserActivityLogger sharedInstance;
|
||||
return sharedInstance;
|
||||
|
@ -42,6 +46,12 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
|
|||
actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
|
||||
actionPart.setBody(QByteArray().append(action));
|
||||
multipart->append(actionPart);
|
||||
|
||||
// Log the local-time that this event was logged
|
||||
QHttpPart elapsedPart;
|
||||
elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\"");
|
||||
elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit());
|
||||
multipart->append(elapsedPart);
|
||||
|
||||
// If there are action details, add them to the multipart
|
||||
if (!details.isEmpty()) {
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <QString>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QElapsedTimer>
|
||||
|
||||
#include <SettingHandle.h>
|
||||
#include "AddressManager.h"
|
||||
|
@ -51,8 +52,10 @@ private slots:
|
|||
void requestError(QNetworkReply& errorReply);
|
||||
|
||||
private:
|
||||
UserActivityLogger() {};
|
||||
UserActivityLogger();
|
||||
Setting::Handle<bool> _disabled { "UserActivityLoggerDisabled", false };
|
||||
|
||||
QElapsedTimer _timer;
|
||||
};
|
||||
|
||||
#endif // hifi_UserActivityLogger_h
|
||||
|
|
|
@ -152,6 +152,7 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
|
|||
btDispatcher* dispatcher = collisionWorld->getDispatcher();
|
||||
int numManifolds = dispatcher->getNumManifolds();
|
||||
bool hasFloor = false;
|
||||
bool isStuck = false;
|
||||
|
||||
btTransform rotation = _rigidBody->getWorldTransform();
|
||||
rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part
|
||||
|
@ -169,10 +170,18 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
|
|||
btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
|
||||
btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character
|
||||
btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp);
|
||||
// If there's non-trivial penetration with a big impulse for several steps, we're probably stuck.
|
||||
// Note it here in the controller, and let MyAvatar figure out what to do about it.
|
||||
const float STUCK_PENETRATION = -0.05f; // always negative into the object.
|
||||
const float STUCK_IMPULSE = 500.0f;
|
||||
const int STUCK_LIFETIME = 3;
|
||||
if ((contact.getDistance() < STUCK_PENETRATION) && (contact.getAppliedImpulse() > STUCK_IMPULSE) && (contact.getLifeTime() > STUCK_LIFETIME)) {
|
||||
isStuck = true; // latch on
|
||||
}
|
||||
if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) {
|
||||
hasFloor = true;
|
||||
if (!pushing) {
|
||||
// we're not pushing against anything so we can early exit
|
||||
if (!pushing && isStuck) {
|
||||
// we're not pushing against anything and we're stuck so we can early exit
|
||||
// (all we need to know is that there is a floor)
|
||||
break;
|
||||
}
|
||||
|
@ -198,12 +207,13 @@ bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) {
|
|||
_stepHeight = highestStep;
|
||||
_stepPoint = rotation * pointOnCharacter; // rotate into world-frame
|
||||
}
|
||||
if (hasFloor && !(pushing && _stepUpEnabled)) {
|
||||
if (hasFloor && isStuck && !(pushing && _stepUpEnabled)) {
|
||||
// early exit since all we need to know is that we're on a floor
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
_isStuck = isStuck;
|
||||
return hasFloor;
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ public:
|
|||
void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale);
|
||||
|
||||
bool isEnabledAndReady() const { return _dynamicsWorld; }
|
||||
bool isStuck() const { return _isStuck; }
|
||||
|
||||
void setCollisionless(bool collisionless);
|
||||
int16_t computeCollisionGroup() const;
|
||||
|
@ -192,6 +193,7 @@ protected:
|
|||
|
||||
State _state;
|
||||
bool _isPushingUp;
|
||||
bool _isStuck { false };
|
||||
|
||||
btDynamicsWorld* _dynamicsWorld { nullptr };
|
||||
btRigidBody* _rigidBody { nullptr };
|
||||
|
|
|
@ -332,7 +332,7 @@ void Model::initJointStates() {
|
|||
|
||||
bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QString& extraInfo, bool pickAgainstTriangles) {
|
||||
QString& extraInfo, bool pickAgainstTriangles, bool allowBackface) {
|
||||
|
||||
bool intersectedSomething = false;
|
||||
|
||||
|
@ -381,7 +381,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
float triangleSetDistance = 0.0f;
|
||||
BoxFace triangleSetFace;
|
||||
glm::vec3 triangleSetNormal;
|
||||
if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles)) {
|
||||
if (triangleSet.findRayIntersection(meshFrameOrigin, meshFrameDirection, triangleSetDistance, triangleSetFace, triangleSetNormal, pickAgainstTriangles, allowBackface)) {
|
||||
|
||||
glm::vec3 meshIntersectionPoint = meshFrameOrigin + (meshFrameDirection * triangleSetDistance);
|
||||
glm::vec3 worldIntersectionPoint = glm::vec3(meshToWorldMatrix * glm::vec4(meshIntersectionPoint, 1.0f));
|
||||
|
|
|
@ -156,7 +156,7 @@ public:
|
|||
|
||||
bool findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const glm::vec3& direction, float& distance,
|
||||
BoxFace& face, glm::vec3& surfaceNormal,
|
||||
QString& extraInfo, bool pickAgainstTriangles = false);
|
||||
QString& extraInfo, bool pickAgainstTriangles = false, bool allowBackface = false);
|
||||
|
||||
void setOffset(const glm::vec3& offset);
|
||||
const glm::vec3& getOffset() const { return _offset; }
|
||||
|
|
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
|
|
@ -290,12 +290,12 @@ glm::vec3 Triangle::getNormal() const {
|
|||
}
|
||||
|
||||
bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance) {
|
||||
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) {
|
||||
glm::vec3 firstSide = v0 - v1;
|
||||
glm::vec3 secondSide = v2 - v1;
|
||||
glm::vec3 normal = glm::cross(secondSide, firstSide);
|
||||
float dividend = glm::dot(normal, v1) - glm::dot(origin, normal);
|
||||
if (dividend > 0.0f) {
|
||||
if (!allowBackface && dividend > 0.0f) {
|
||||
return false; // origin below plane
|
||||
}
|
||||
float divisor = glm::dot(normal, direction);
|
||||
|
|
|
@ -83,7 +83,7 @@ bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& dire
|
|||
const glm::vec3& position, const glm::vec2& dimensions, float& distance);
|
||||
|
||||
bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance);
|
||||
const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface = false);
|
||||
|
||||
/// \brief decomposes rotation into its components such that: rotation = swing * twist
|
||||
/// \param rotation[in] rotation to decompose
|
||||
|
@ -104,8 +104,8 @@ public:
|
|||
};
|
||||
|
||||
inline bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
const Triangle& triangle, float& distance) {
|
||||
return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance);
|
||||
const Triangle& triangle, float& distance, bool allowBackface = false) {
|
||||
return findRayTriangleIntersection(origin, direction, triangle.v0, triangle.v1, triangle.v2, distance, allowBackface);
|
||||
}
|
||||
|
||||
|
||||
|
|
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
|
||||
|
|
|
@ -31,7 +31,7 @@ void TriangleSet::clear() {
|
|||
}
|
||||
|
||||
bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface) {
|
||||
|
||||
// reset our distance to be the max possible, lower level tests will store best distance here
|
||||
distance = std::numeric_limits<float>::max();
|
||||
|
@ -41,7 +41,7 @@ bool TriangleSet::findRayIntersection(const glm::vec3& origin, const glm::vec3&
|
|||
}
|
||||
|
||||
int trianglesTouched = 0;
|
||||
auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched);
|
||||
auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched, allowBackface);
|
||||
|
||||
#if WANT_DEBUGGING
|
||||
if (precision) {
|
||||
|
@ -95,7 +95,7 @@ void TriangleSet::balanceOctree() {
|
|||
// Determine of the given ray (origin/direction) in model space intersects with any triangles
|
||||
// in the set. If an intersection occurs, the distance and surface normal will be provided.
|
||||
bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) {
|
||||
|
||||
bool intersectedSomething = false;
|
||||
float boxDistance = distance;
|
||||
|
@ -114,7 +114,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersectionInternal(const glm::vec
|
|||
const auto& triangle = _allTriangles[triangleIndex];
|
||||
float thisTriangleDistance;
|
||||
trianglesTouched++;
|
||||
if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) {
|
||||
if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance, allowBackface)) {
|
||||
if (thisTriangleDistance < bestDistance) {
|
||||
bestDistance = thisTriangleDistance;
|
||||
intersectedSomething = true;
|
||||
|
@ -203,7 +203,7 @@ void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) {
|
|||
}
|
||||
|
||||
bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface) {
|
||||
|
||||
if (_population < 1) {
|
||||
return false; // no triangles below here, so we can't intersect
|
||||
|
@ -247,7 +247,7 @@ bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origi
|
|||
}
|
||||
}
|
||||
// also check our local triangle set
|
||||
if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) {
|
||||
if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched, allowBackface)) {
|
||||
if (childDistance < bestLocalDistance) {
|
||||
bestLocalDistance = childDistance;
|
||||
bestLocalFace = childFace;
|
||||
|
|
|
@ -27,7 +27,7 @@ class TriangleSet {
|
|||
void clear();
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false);
|
||||
|
||||
const AABox& getBounds() const { return _bounds; }
|
||||
|
||||
|
@ -38,7 +38,7 @@ class TriangleSet {
|
|||
|
||||
// checks our internal list of triangles
|
||||
bool findRayIntersectionInternal(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched, bool allowBackface = false);
|
||||
|
||||
std::vector<Triangle>& _allTriangles;
|
||||
std::map<AABox::OctreeChild, TriangleOctreeCell> _children;
|
||||
|
@ -60,7 +60,7 @@ public:
|
|||
void insert(const Triangle& t);
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision);
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, bool allowBackface = false);
|
||||
|
||||
void balanceOctree();
|
||||
|
||||
|
|
|
@ -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;
|
||||
QApplication::exit(1);
|
||||
}
|
||||
|
||||
// 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::exit(_baker.get()->hasErrors());
|
||||
}
|
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