mirror of
https://github.com/lubosz/overte.git
synced 2025-04-27 14:35:30 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into spectator-camera
This commit is contained in:
commit
a4f5d327a2
96 changed files with 1693 additions and 458 deletions
assignment-client/src
domain-server/resources/web/content
gvr-interface/src
interface
resources
avatar
controllers
qml
src
libraries
animation/src
AnimInverseKinematics.cppAnimInverseKinematics.hIKTarget.cppIKTarget.hRig.cppRig.hRotationAccumulator.cppTranslationAccumulator.cppTranslationAccumulator.h
audio-client/src
avatars-renderer/src/avatars-renderer
avatars/src
controllers/src/controllers
entities-renderer/src
input-plugins/src/input-plugins
networking/src
physics/src
plugins/src/plugins
render-utils/src
shared/src
plugins
hifiKinect/src
hifiNeuron/src
hifiSdl2/src
hifiSixense/src
oculus/src
openvr/src
scripts
defaultScripts.js
system
tests
tools
|
@ -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",
|
||||
|
|
|
@ -57,6 +57,8 @@
|
|||
{ "from": "OculusTouch.LeftThumbUp", "to": "Standard.LeftThumbUp" },
|
||||
{ "from": "OculusTouch.RightThumbUp", "to": "Standard.RightThumbUp" },
|
||||
{ "from": "OculusTouch.LeftIndexPoint", "to": "Standard.LeftIndexPoint" },
|
||||
{ "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" }
|
||||
{ "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" },
|
||||
|
||||
{ "from": "OculusTouch.Head", "to" : "Standard.Head", "when" : [ "Application.InHMD"] }
|
||||
]
|
||||
}
|
||||
|
|
|
@ -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()) {
|
||||
|
@ -2200,7 +2169,7 @@ void Application::paintGL() {
|
|||
_myCamera.setOrientation(glm::quat_cast(camMat));
|
||||
} else {
|
||||
_myCamera.setPosition(myAvatar->getDefaultEyePosition());
|
||||
_myCamera.setOrientation(myAvatar->getMyHead()->getCameraOrientation());
|
||||
_myCamera.setOrientation(myAvatar->getMyHead()->getHeadOrientation());
|
||||
}
|
||||
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
|
||||
if (isHMDMode()) {
|
||||
|
@ -4127,6 +4096,7 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
lookAtPosition.x = -lookAtPosition.x;
|
||||
}
|
||||
if (isHMD) {
|
||||
// TODO -- this code is probably wrong, getHeadPose() returns something in sensor frame, not avatar
|
||||
glm::mat4 headPose = getActiveDisplayPlugin()->getHeadPose();
|
||||
glm::quat hmdRotation = glm::quat_cast(headPose);
|
||||
lookAtSpot = _myCamera.getPosition() + myAvatar->getOrientation() * (hmdRotation * lookAtPosition);
|
||||
|
@ -4169,8 +4139,9 @@ void Application::updateMyAvatarLookAtPosition() {
|
|||
} else {
|
||||
// I am not looking at anyone else, so just look forward
|
||||
if (isHMD) {
|
||||
glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
|
||||
lookAtSpot = transformPoint(worldHMDMat, glm::vec3(0.0f, 0.0f, -TREE_SCALE));
|
||||
glm::mat4 worldHeadMat = myAvatar->getSensorToWorldMatrix() *
|
||||
myAvatar->getHeadControllerPoseInSensorFrame().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));
|
||||
|
|
|
@ -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();
|
||||
|
@ -409,7 +410,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
// update moving average of HMD facing in xz plane.
|
||||
const float HMD_FACING_TIMESCALE = 4.0f; // very slow average
|
||||
float tau = deltaTime / HMD_FACING_TIMESCALE;
|
||||
_hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau);
|
||||
_headControllerFacingMovingAverage = lerp(_headControllerFacingMovingAverage, _headControllerFacing, tau);
|
||||
|
||||
if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) {
|
||||
_rotationChanged = usecTimestampNow();
|
||||
|
@ -417,16 +418,18 @@ void MyAvatar::update(float deltaTime) {
|
|||
}
|
||||
|
||||
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
|
||||
glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y));
|
||||
glm::vec3 p = transformPoint(getSensorToWorldMatrix(), getHeadControllerPoseInAvatarFrame() *
|
||||
glm::vec3(_headControllerFacingMovingAverage.x, 0.0f, _headControllerFacingMovingAverage.y));
|
||||
DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f));
|
||||
p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacing.x, 0.0f, _hmdSensorFacing.y));
|
||||
p = transformPoint(getSensorToWorldMatrix(), getHMDSensorPosition() +
|
||||
glm::vec3(_headControllerFacing.x, 0.0f, _headControllerFacing.y));
|
||||
DebugDraw::getInstance().addMarker("facing", getOrientation(), p, glm::vec4(1.0f));
|
||||
#endif
|
||||
|
||||
if (_goToPending) {
|
||||
setPosition(_goToPosition);
|
||||
setOrientation(_goToOrientation);
|
||||
_hmdSensorFacingMovingAverage = _hmdSensorFacing; // reset moving average
|
||||
_headControllerFacingMovingAverage = _headControllerFacing; // reset moving average
|
||||
_goToPending = false;
|
||||
// updateFromHMDSensorMatrix (called from paintGL) expects that the sensorToWorldMatrix is updated for any position changes
|
||||
// that happen between render and Application::update (which calls updateSensorToWorldMatrix to do so).
|
||||
|
@ -434,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();
|
||||
|
@ -448,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)),
|
||||
|
@ -633,15 +644,21 @@ void MyAvatar::updateFromHMDSensorMatrix(const glm::mat4& hmdSensorMatrix) {
|
|||
_hmdSensorMatrix = hmdSensorMatrix;
|
||||
auto newHmdSensorPosition = extractTranslation(hmdSensorMatrix);
|
||||
|
||||
if (newHmdSensorPosition != _hmdSensorPosition &&
|
||||
if (newHmdSensorPosition != getHMDSensorPosition() &&
|
||||
glm::length(newHmdSensorPosition) > MAX_HMD_ORIGIN_DISTANCE) {
|
||||
qWarning() << "Invalid HMD sensor position " << newHmdSensorPosition;
|
||||
// Ignore unreasonable HMD sensor data
|
||||
return;
|
||||
}
|
||||
|
||||
_hmdSensorPosition = newHmdSensorPosition;
|
||||
_hmdSensorOrientation = glm::quat_cast(hmdSensorMatrix);
|
||||
_hmdSensorFacing = getFacingDir2D(_hmdSensorOrientation);
|
||||
auto headPose = _headControllerPoseInSensorFrameCache.get();
|
||||
if (headPose.isValid()) {
|
||||
_headControllerFacing = getFacingDir2D(headPose.rotation);
|
||||
} else {
|
||||
_headControllerFacing = glm::vec2(1.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeValueCache<glm::mat4>& matrixCache) {
|
||||
|
@ -679,7 +696,7 @@ void MyAvatar::updateSensorToWorldMatrix() {
|
|||
|
||||
// Update avatar head rotation with sensor data
|
||||
void MyAvatar::updateFromTrackers(float deltaTime) {
|
||||
glm::vec3 estimatedPosition, estimatedRotation;
|
||||
glm::vec3 estimatedRotation;
|
||||
|
||||
bool inHmd = qApp->isHMDMode();
|
||||
bool playing = DependencyManager::get<recording::Deck>()->isPlaying();
|
||||
|
@ -690,11 +707,7 @@ void MyAvatar::updateFromTrackers(float deltaTime) {
|
|||
FaceTracker* tracker = qApp->getActiveFaceTracker();
|
||||
bool inFacetracker = tracker && !FaceTracker::isMuted();
|
||||
|
||||
if (inHmd) {
|
||||
estimatedPosition = extractTranslation(getHMDSensorMatrix());
|
||||
estimatedPosition.x *= -1.0f;
|
||||
} else if (inFacetracker) {
|
||||
estimatedPosition = tracker->getHeadTranslation();
|
||||
if (inFacetracker) {
|
||||
estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation()));
|
||||
}
|
||||
|
||||
|
@ -1479,12 +1492,12 @@ void MyAvatar::updateMotors() {
|
|||
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
|
||||
if (_characterController.getState() == CharacterController::State::Hover ||
|
||||
_characterController.computeCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
motorRotation = getMyHead()->getCameraOrientation();
|
||||
motorRotation = getMyHead()->getHeadOrientation();
|
||||
} else {
|
||||
// non-hovering = walking: follow camera twist about vertical but not lift
|
||||
// so we decompose camera's rotation and store the twist part in motorRotation
|
||||
glm::quat liftRotation;
|
||||
swingTwistDecomposition(getMyHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation);
|
||||
swingTwistDecomposition(getMyHead()->getHeadOrientation(), _worldUpDirection, liftRotation, motorRotation);
|
||||
}
|
||||
const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
|
@ -1498,7 +1511,7 @@ void MyAvatar::updateMotors() {
|
|||
}
|
||||
if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) {
|
||||
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
|
||||
motorRotation = getMyHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
|
||||
motorRotation = getMyHead()->getHeadOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
|
||||
} else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) {
|
||||
motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
|
||||
} else {
|
||||
|
@ -1548,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());
|
||||
}
|
||||
|
@ -1847,7 +1864,7 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
if (getCharacterController()->getState() == CharacterController::State::Hover) {
|
||||
|
||||
// This is the direction the user desires to fly in.
|
||||
glm::vec3 desiredFacing = getMyHead()->getCameraOrientation() * Vectors::UNIT_Z;
|
||||
glm::vec3 desiredFacing = getMyHead()->getHeadOrientation() * Vectors::UNIT_Z;
|
||||
desiredFacing.y = 0.0f;
|
||||
|
||||
// This is our reference frame, it is captured when the user begins to move.
|
||||
|
@ -1886,11 +1903,9 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation();
|
||||
glm::quat bodyOrientation = getWorldBodyOrientation();
|
||||
glm::quat localOrientation = glm::inverse(bodyOrientation) * orientation;
|
||||
|
||||
auto headPose = getHeadControllerPoseInAvatarFrame();
|
||||
if (headPose.isValid()) {
|
||||
glm::quat localOrientation = headPose.rotation * Quaternions::Y_180;
|
||||
// these angles will be in radians
|
||||
// ... so they need to be converted to degrees before we do math...
|
||||
glm::vec3 euler = glm::eulerAngles(localOrientation) * DEGREES_PER_RADIAN;
|
||||
|
@ -2004,11 +2019,14 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
}
|
||||
|
||||
// capture the head rotation, in sensor space, when the user first indicates they would like to move/fly.
|
||||
if (!_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) {
|
||||
if (!_hoverReferenceCameraFacingIsCaptured &&
|
||||
(fabs(getDriveKey(TRANSLATE_Z)) > 0.1f || fabs(getDriveKey(TRANSLATE_X)) > 0.1f)) {
|
||||
_hoverReferenceCameraFacingIsCaptured = true;
|
||||
// transform the camera facing vector into sensor space.
|
||||
_hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix), getMyHead()->getCameraOrientation() * Vectors::UNIT_Z);
|
||||
} else if (_hoverReferenceCameraFacingIsCaptured && (fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) {
|
||||
_hoverReferenceCameraFacing = transformVectorFast(glm::inverse(_sensorToWorldMatrix),
|
||||
getMyHead()->getHeadOrientation() * Vectors::UNIT_Z);
|
||||
} else if (_hoverReferenceCameraFacingIsCaptured &&
|
||||
(fabs(getDriveKey(TRANSLATE_Z)) <= 0.1f && fabs(getDriveKey(TRANSLATE_X)) <= 0.1f)) {
|
||||
_hoverReferenceCameraFacingIsCaptured = false;
|
||||
}
|
||||
}
|
||||
|
@ -2219,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()) {
|
||||
|
@ -2322,36 +2478,27 @@ bool MyAvatar::isDriveKeyDisabled(DriveKeys key) const {
|
|||
}
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getWorldBodyPosition() const {
|
||||
return transformPoint(_sensorToWorldMatrix, extractTranslation(_bodySensorMatrix));
|
||||
}
|
||||
|
||||
glm::quat MyAvatar::getWorldBodyOrientation() const {
|
||||
return glm::quat_cast(_sensorToWorldMatrix * _bodySensorMatrix);
|
||||
}
|
||||
|
||||
// old school meat hook style
|
||||
glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
|
||||
|
||||
// HMD is in sensor space.
|
||||
const glm::vec3 hmdPosition = getHMDSensorPosition();
|
||||
const glm::quat hmdOrientation = getHMDSensorOrientation();
|
||||
const glm::quat hmdOrientationYawOnly = cancelOutRollAndPitch(hmdOrientation);
|
||||
glm::vec3 headPosition;
|
||||
glm::quat headOrientation;
|
||||
auto headPose = getHeadControllerPoseInSensorFrame();
|
||||
if (headPose.isValid()) {
|
||||
headPosition = getHeadControllerPoseInSensorFrame().translation;
|
||||
headOrientation = getHeadControllerPoseInSensorFrame().rotation * Quaternions::Y_180;
|
||||
}
|
||||
const glm::quat headOrientationYawOnly = cancelOutRollAndPitch(headOrientation);
|
||||
|
||||
const Rig& rig = _skeletonModel->getRig();
|
||||
int rightEyeIndex = rig.indexOfJoint("RightEye");
|
||||
int leftEyeIndex = rig.indexOfJoint("LeftEye");
|
||||
int headIndex = rig.indexOfJoint("Head");
|
||||
int neckIndex = rig.indexOfJoint("Neck");
|
||||
int hipsIndex = rig.indexOfJoint("Hips");
|
||||
|
||||
glm::vec3 rigMiddleEyePos = DEFAULT_AVATAR_MIDDLE_EYE_POS;
|
||||
if (leftEyeIndex >= 0 && rightEyeIndex >= 0) {
|
||||
rigMiddleEyePos = (rig.getAbsoluteDefaultPose(leftEyeIndex).trans() + rig.getAbsoluteDefaultPose(rightEyeIndex).trans()) / 2.0f;
|
||||
}
|
||||
glm::vec3 rigHeadPos = headIndex != -1 ? rig.getAbsoluteDefaultPose(headIndex).trans() : DEFAULT_AVATAR_HEAD_POS;
|
||||
glm::vec3 rigNeckPos = neckIndex != -1 ? rig.getAbsoluteDefaultPose(neckIndex).trans() : DEFAULT_AVATAR_NECK_POS;
|
||||
glm::vec3 rigHipsPos = hipsIndex != -1 ? rig.getAbsoluteDefaultPose(hipsIndex).trans() : DEFAULT_AVATAR_HIPS_POS;
|
||||
|
||||
glm::vec3 localEyes = (rigMiddleEyePos - rigHipsPos);
|
||||
glm::vec3 localHead = (rigHeadPos - rigHipsPos);
|
||||
glm::vec3 localNeck = (rigNeckPos - rigHipsPos);
|
||||
|
||||
// apply simplistic head/neck model
|
||||
|
@ -2360,11 +2507,11 @@ glm::mat4 MyAvatar::deriveBodyFromHMDSensor() const {
|
|||
// eyeToNeck offset is relative full HMD orientation.
|
||||
// while neckToRoot offset is only relative to HMDs yaw.
|
||||
// Y_180 is necessary because rig is z forward and hmdOrientation is -z forward
|
||||
glm::vec3 eyeToNeck = hmdOrientation * Quaternions::Y_180 * (localNeck - localEyes);
|
||||
glm::vec3 neckToRoot = hmdOrientationYawOnly * Quaternions::Y_180 * -localNeck;
|
||||
glm::vec3 bodyPos = hmdPosition + eyeToNeck + neckToRoot;
|
||||
glm::vec3 headToNeck = headOrientation * Quaternions::Y_180 * (localNeck - localHead);
|
||||
glm::vec3 neckToRoot = headOrientationYawOnly * Quaternions::Y_180 * -localNeck;
|
||||
glm::vec3 bodyPos = headPosition + headToNeck + neckToRoot;
|
||||
|
||||
return createMatFromQuatAndPos(hmdOrientationYawOnly, bodyPos);
|
||||
return createMatFromQuatAndPos(headOrientationYawOnly, bodyPos);
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getPositionForAudio() {
|
||||
|
@ -2480,7 +2627,7 @@ bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, co
|
|||
} else {
|
||||
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
|
||||
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
|
||||
return glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
|
||||
return glm::dot(-myAvatar.getHeadControllerFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2627,9 +2774,10 @@ glm::mat4 MyAvatar::computeCameraRelativeHandControllerMatrix(const glm::mat4& c
|
|||
cameraWorldMatrix *= createMatFromScaleQuatAndPos(vec3(-1.0f, 1.0f, 1.0f), glm::quat(), glm::vec3());
|
||||
}
|
||||
|
||||
// compute a NEW sensorToWorldMatrix for the camera. The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix.
|
||||
// compute a NEW sensorToWorldMatrix for the camera.
|
||||
// The equation is cameraWorldMatrix = cameraSensorToWorldMatrix * _hmdSensorMatrix.
|
||||
// here we solve for the unknown cameraSensorToWorldMatrix.
|
||||
glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(_hmdSensorMatrix);
|
||||
glm::mat4 cameraSensorToWorldMatrix = cameraWorldMatrix * glm::inverse(getHMDSensorMatrix());
|
||||
|
||||
// Using the new cameraSensorToWorldMatrix, compute where the controller is in world space.
|
||||
glm::mat4 controllerWorldMatrix = cameraSensorToWorldMatrix * controllerSensorMatrix;
|
||||
|
|
|
@ -185,7 +185,6 @@ public:
|
|||
const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; }
|
||||
const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; }
|
||||
const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
|
||||
const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
|
||||
|
||||
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
|
||||
Q_INVOKABLE QVariant getOrientationVar() const;
|
||||
|
@ -470,6 +469,8 @@ public:
|
|||
controller::Pose getHeadControllerPoseInSensorFrame() const;
|
||||
controller::Pose getHeadControllerPoseInWorldFrame() const;
|
||||
controller::Pose getHeadControllerPoseInAvatarFrame() const;
|
||||
const glm::vec2& getHeadControllerFacingMovingAverage() const { return _headControllerFacingMovingAverage; }
|
||||
|
||||
|
||||
void setArmControllerPosesInSensorFrame(const controller::Pose& left, const controller::Pose& right);
|
||||
controller::Pose getLeftArmControllerPoseInSensorFrame() const;
|
||||
|
@ -509,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();
|
||||
|
@ -518,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();
|
||||
|
@ -563,9 +570,7 @@ signals:
|
|||
|
||||
private:
|
||||
|
||||
glm::vec3 getWorldBodyPosition() const;
|
||||
glm::quat getWorldBodyOrientation() const;
|
||||
|
||||
bool requiresSafeLanding(const glm::vec3& positionIn, glm::vec3& positionOut);
|
||||
|
||||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail) override;
|
||||
|
||||
|
@ -676,13 +681,13 @@ private:
|
|||
// working copies -- see AvatarData for thread-safe _sensorToWorldMatrixCache, used for outward facing access
|
||||
glm::mat4 _sensorToWorldMatrix { glm::mat4() };
|
||||
|
||||
// cache of the current HMD sensor position and orientation
|
||||
// in sensor space.
|
||||
// cache of the current HMD sensor position and orientation in sensor space.
|
||||
glm::mat4 _hmdSensorMatrix;
|
||||
glm::quat _hmdSensorOrientation;
|
||||
glm::vec3 _hmdSensorPosition;
|
||||
glm::vec2 _hmdSensorFacing; // facing vector in xz plane
|
||||
glm::vec2 _hmdSensorFacingMovingAverage { 0, 0 }; // facing vector in xz plane
|
||||
// cache head controller pose in sensor space
|
||||
glm::vec2 _headControllerFacing; // facing vector in xz plane
|
||||
glm::vec2 _headControllerFacingMovingAverage { 0, 0 }; // facing vector in xz plane
|
||||
|
||||
// cache of the current body position and orientation of the avatar's body,
|
||||
// in sensor space.
|
||||
|
@ -716,7 +721,8 @@ private:
|
|||
};
|
||||
FollowHelper _follow;
|
||||
|
||||
bool _goToPending;
|
||||
bool _goToPending { false };
|
||||
bool _physicsSafetyPending { false };
|
||||
glm::vec3 _goToPosition;
|
||||
glm::quat _goToOrientation;
|
||||
|
||||
|
|
|
@ -26,19 +26,20 @@ using namespace std;
|
|||
MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) {
|
||||
}
|
||||
|
||||
glm::quat MyHead::getCameraOrientation() const {
|
||||
// NOTE: Head::getCameraOrientation() is not used for orienting the camera "view" while in Oculus mode, so
|
||||
glm::quat MyHead::getHeadOrientation() const {
|
||||
// NOTE: Head::getHeadOrientation() is not used for orienting the camera "view" while in Oculus mode, so
|
||||
// you may wonder why this code is here. This method will be called while in Oculus mode to determine how
|
||||
// to change the driving direction while in Oculus mode. It is used to support driving toward where you're
|
||||
// head is looking. Note that in oculus mode, your actual camera view and where your head is looking is not
|
||||
// always the same.
|
||||
if (qApp->isHMDMode()) {
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
return glm::quat_cast(myAvatar->getSensorToWorldMatrix()) * myAvatar->getHMDSensorOrientation();
|
||||
} else {
|
||||
Avatar* owningAvatar = static_cast<Avatar*>(_owningAvatar);
|
||||
return owningAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
|
||||
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
auto headPose = myAvatar->getHeadControllerPoseInWorldFrame();
|
||||
if (headPose.isValid()) {
|
||||
return headPose.rotation * Quaternions::Y_180;
|
||||
}
|
||||
|
||||
return myAvatar->getWorldAlignedOrientation() * glm::quat(glm::radians(glm::vec3(_basePitch, 0.0f, 0.0f)));
|
||||
}
|
||||
|
||||
void MyHead::simulate(float deltaTime) {
|
||||
|
|
|
@ -18,7 +18,7 @@ public:
|
|||
explicit MyHead(MyAvatar* owningAvatar);
|
||||
|
||||
/// \return orientationBody * orientationBasePitch
|
||||
glm::quat getCameraOrientation() const;
|
||||
glm::quat getHeadOrientation() const;
|
||||
void simulate(float deltaTime) override;
|
||||
|
||||
private:
|
||||
|
|
|
@ -52,26 +52,18 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
// input action is the highest priority source for head orientation.
|
||||
auto avatarHeadPose = myAvatar->getHeadControllerPoseInAvatarFrame();
|
||||
if (avatarHeadPose.isValid()) {
|
||||
glm::mat4 rigHeadMat = Matrices::Y_180 * createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
|
||||
glm::mat4 rigHeadMat = Matrices::Y_180 *
|
||||
createMatFromQuatAndPos(avatarHeadPose.getRotation(), avatarHeadPose.getTranslation());
|
||||
headParams.rigHeadPosition = extractTranslation(rigHeadMat);
|
||||
headParams.rigHeadOrientation = glmExtractRotation(rigHeadMat);
|
||||
headParams.headEnabled = true;
|
||||
} else {
|
||||
if (qApp->isHMDMode()) {
|
||||
// get HMD position from sensor space into world space, and back into rig space
|
||||
glm::mat4 worldHMDMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
|
||||
glm::mat4 rigToWorld = createMatFromQuatAndPos(getRotation(), getTranslation());
|
||||
glm::mat4 worldToRig = glm::inverse(rigToWorld);
|
||||
glm::mat4 rigHMDMat = worldToRig * worldHMDMat;
|
||||
_rig.computeHeadFromHMD(AnimPose(rigHMDMat), headParams.rigHeadPosition, headParams.rigHeadOrientation);
|
||||
headParams.headEnabled = true;
|
||||
} else {
|
||||
// even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and down in desktop mode.
|
||||
// preMult 180 is necessary to convert from avatar to rig coordinates.
|
||||
// postMult 180 is necessary to convert head from -z forward to z forward.
|
||||
headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180;
|
||||
headParams.headEnabled = false;
|
||||
}
|
||||
// even though full head IK is disabled, the rig still needs the head orientation to rotate the head up and
|
||||
// down in desktop mode.
|
||||
// preMult 180 is necessary to convert from avatar to rig coordinates.
|
||||
// postMult 180 is necessary to convert head from -z forward to z forward.
|
||||
headParams.rigHeadOrientation = Quaternions::Y_180 * head->getFinalOrientationInLocalFrame() * Quaternions::Y_180;
|
||||
headParams.headEnabled = false;
|
||||
}
|
||||
|
||||
auto avatarHipsPose = myAvatar->getHipsControllerPoseInAvatarFrame();
|
||||
|
|
|
@ -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; }
|
||||
|
||||
|
|
|
@ -54,6 +54,10 @@ bool HMDScriptingInterface::isHMDAvailable(const QString& name) {
|
|||
return PluginUtils::isHMDAvailable(name);
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::isHeadControllerAvailable(const QString& name) {
|
||||
return PluginUtils::isHeadControllerAvailable(name);
|
||||
}
|
||||
|
||||
bool HMDScriptingInterface::isHandControllerAvailable(const QString& name) {
|
||||
return PluginUtils::isHandControllerAvailable(name);
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ public:
|
|||
Q_INVOKABLE QString preferredAudioOutput() const;
|
||||
|
||||
Q_INVOKABLE bool isHMDAvailable(const QString& name = "");
|
||||
Q_INVOKABLE bool isHeadControllerAvailable(const QString& name = "");
|
||||
Q_INVOKABLE bool isHandControllerAvailable(const QString& name = "");
|
||||
Q_INVOKABLE bool isSubdeviceContainingNameAvailable(const QString& name);
|
||||
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -304,18 +304,6 @@ glm::quat Head::getEyeRotation(const glm::vec3& eyePosition) const {
|
|||
return rotationBetween(orientation * IDENTITY_FORWARD, lookAtDelta + glm::length(lookAtDelta) * _saccade) * orientation;
|
||||
}
|
||||
|
||||
void Head::setFinalPitch(float finalPitch) {
|
||||
_deltaPitch = glm::clamp(finalPitch, MIN_HEAD_PITCH, MAX_HEAD_PITCH) - _basePitch;
|
||||
}
|
||||
|
||||
void Head::setFinalYaw(float finalYaw) {
|
||||
_deltaYaw = glm::clamp(finalYaw, MIN_HEAD_YAW, MAX_HEAD_YAW) - _baseYaw;
|
||||
}
|
||||
|
||||
void Head::setFinalRoll(float finalRoll) {
|
||||
_deltaRoll = glm::clamp(finalRoll, MIN_HEAD_ROLL, MAX_HEAD_ROLL) - _baseRoll;
|
||||
}
|
||||
|
||||
float Head::getFinalYaw() const {
|
||||
return glm::clamp(_baseYaw + _deltaYaw, MIN_HEAD_YAW, MAX_HEAD_YAW);
|
||||
}
|
||||
|
|
|
@ -71,9 +71,6 @@ public:
|
|||
void setDeltaRoll(float roll) { _deltaRoll = roll; }
|
||||
float getDeltaRoll() const { return _deltaRoll; }
|
||||
|
||||
virtual void setFinalYaw(float finalYaw) override;
|
||||
virtual void setFinalPitch(float finalPitch) override;
|
||||
virtual void setFinalRoll(float finalRoll) override;
|
||||
virtual float getFinalPitch() const override;
|
||||
virtual float getFinalYaw() const override;
|
||||
virtual float getFinalRoll() const override;
|
||||
|
|
|
@ -45,9 +45,6 @@ public:
|
|||
float getBaseRoll() const { return _baseRoll; }
|
||||
void setBaseRoll(float roll) { _baseRoll = glm::clamp(roll, MIN_HEAD_ROLL, MAX_HEAD_ROLL); }
|
||||
|
||||
virtual void setFinalYaw(float finalYaw) { _baseYaw = finalYaw; }
|
||||
virtual void setFinalPitch(float finalPitch) { _basePitch = finalPitch; }
|
||||
virtual void setFinalRoll(float finalRoll) { _baseRoll = finalRoll; }
|
||||
virtual float getFinalYaw() const { return _baseYaw; }
|
||||
virtual float getFinalPitch() const { return _basePitch; }
|
||||
virtual float getFinalRoll() const { return _baseRoll; }
|
||||
|
|
|
@ -39,6 +39,7 @@ namespace controller {
|
|||
quat getRotation() const { return rotation; }
|
||||
vec3 getVelocity() const { return velocity; }
|
||||
vec3 getAngularVelocity() const { return angularVelocity; }
|
||||
mat4 getMatrix() const { return createMatFromQuatAndPos(rotation, translation); }
|
||||
|
||||
Pose transform(const glm::mat4& mat) const;
|
||||
Pose postTransform(const glm::mat4& mat) const;
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -70,8 +70,6 @@ public:
|
|||
bool isSupported() const override { return true; }
|
||||
const QString getName() const override { return NAME; }
|
||||
|
||||
bool isHandController() const override { return false; }
|
||||
|
||||
void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
|
||||
void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
|
||||
|
||||
|
|
|
@ -39,8 +39,6 @@ public:
|
|||
virtual bool isSupported() const override;
|
||||
virtual const QString getName() const override { return NAME; }
|
||||
|
||||
bool isHandController() const override { return false; }
|
||||
|
||||
virtual void pluginFocusOutEvent() override { _inputDevice->focusOutEvent(); }
|
||||
virtual void pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
|
||||
|
||||
|
|
|
@ -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); }
|
||||
|
||||
|
|
|
@ -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 };
|
||||
|
|
|
@ -24,6 +24,7 @@ public:
|
|||
// Some input plugins are comprised of multiple subdevices (SDL2, for instance).
|
||||
// If an input plugin is only a single device, it will only return it's primary name.
|
||||
virtual QStringList getSubdeviceNames() { return { getName() }; };
|
||||
virtual bool isHandController() const = 0;
|
||||
virtual bool isHandController() const { return false; }
|
||||
virtual bool isHeadController() const { return false; }
|
||||
};
|
||||
|
||||
|
|
|
@ -24,6 +24,15 @@ bool PluginUtils::isHMDAvailable(const QString& pluginName) {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool PluginUtils::isHeadControllerAvailable(const QString& pluginName) {
|
||||
for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
|
||||
if (inputPlugin->isHeadController() && (pluginName.isEmpty() || inputPlugin->getName() == pluginName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
bool PluginUtils::isHandControllerAvailable(const QString& pluginName) {
|
||||
for (auto& inputPlugin : PluginManager::getInstance()->getInputPlugins()) {
|
||||
if (inputPlugin->isHandController() && (pluginName.isEmpty() || inputPlugin->getName() == pluginName)) {
|
||||
|
|
|
@ -16,6 +16,7 @@ class PluginUtils {
|
|||
public:
|
||||
static bool isHMDAvailable(const QString& pluginName = "");
|
||||
static bool isHandControllerAvailable(const QString& pluginName = "");
|
||||
static bool isHeadControllerAvailable(const QString& pluginName = "");
|
||||
static bool isSubdeviceContainingNameAvailable(QString name);
|
||||
static bool isViveControllerAvailable();
|
||||
static bool isOculusTouchControllerAvailable();
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -273,7 +273,7 @@ bool KinectPlugin::activate() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool KinectPlugin::isHandController() const {
|
||||
bool KinectPlugin::isHandController() const {
|
||||
bool sensorAvailable = false;
|
||||
#ifdef HAVE_KINECT
|
||||
if (_kinectSensor) {
|
||||
|
@ -285,6 +285,10 @@ bool KinectPlugin::isHandController() const {
|
|||
return _enabled && _initialized && sensorAvailable;
|
||||
}
|
||||
|
||||
bool KinectPlugin::isHeadController() const {
|
||||
return isHandController();
|
||||
}
|
||||
|
||||
|
||||
bool KinectPlugin::initializeDefaultSensor() const {
|
||||
#ifdef HAVE_KINECT
|
||||
|
@ -654,4 +658,4 @@ void KinectPlugin::InputDevice::clearState() {
|
|||
int poseIndex = KinectJointIndexToPoseIndex((KinectJointIndex)i);
|
||||
_poseStateMap[poseIndex] = controller::Pose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -43,6 +43,7 @@ class KinectPlugin : public InputPlugin {
|
|||
Q_OBJECT
|
||||
public:
|
||||
bool isHandController() const override;
|
||||
bool isHeadController() const override;
|
||||
|
||||
// Plugin functions
|
||||
virtual void init() override;
|
||||
|
|
|
@ -25,8 +25,6 @@ class NeuronPlugin : public InputPlugin {
|
|||
public:
|
||||
friend void FrameDataReceivedCallback(void* context, void* sender, _BvhDataHeaderEx* header, float* data);
|
||||
|
||||
bool isHandController() const override { return false; }
|
||||
|
||||
// Plugin functions
|
||||
virtual void init() override;
|
||||
virtual bool isSupported() const override;
|
||||
|
|
|
@ -27,7 +27,6 @@ public:
|
|||
const QString getName() const override { return NAME; }
|
||||
|
||||
QStringList getSubdeviceNames() override;
|
||||
bool isHandController() const override { return false; }
|
||||
|
||||
void init() override;
|
||||
void deinit() override;
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
|
||||
// Sixense always seems to initialize even if the hydras are not present. Is there
|
||||
// a way we can properly detect whether the hydras are present?
|
||||
bool isHandController() const override { return false; }
|
||||
// bool isHandController() const override { return true; }
|
||||
|
||||
virtual bool activate() override;
|
||||
virtual void deactivate() override;
|
||||
|
|
|
@ -19,6 +19,8 @@
|
|||
|
||||
#include <PerfStat.h>
|
||||
#include <PathUtils.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <StreamUtils.h>
|
||||
|
||||
#include <OVR_CAPI.h>
|
||||
|
||||
|
@ -208,13 +210,18 @@ void OculusControllerManager::RemoteDevice::focusOutEvent() {
|
|||
_buttonPressedMap.clear();
|
||||
}
|
||||
|
||||
void OculusControllerManager::TouchDevice::update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
||||
_buttonPressedMap.clear();
|
||||
bool OculusControllerManager::isHeadControllerMounted() const {
|
||||
ovrSessionStatus status;
|
||||
if (!OVR_SUCCESS(ovr_GetSessionStatus(_parent._session, &status)) || (ovrFalse == status.HmdMounted)) {
|
||||
// if the HMD isn't on someone's head, don't take input from the controllers
|
||||
return;
|
||||
bool success = OVR_SUCCESS(ovr_GetSessionStatus(_session, &status));
|
||||
if (!success) {
|
||||
return false;
|
||||
}
|
||||
return status.HmdMounted == ovrTrue;
|
||||
}
|
||||
|
||||
void OculusControllerManager::TouchDevice::update(float deltaTime,
|
||||
const controller::InputCalibrationData& inputCalibrationData) {
|
||||
_buttonPressedMap.clear();
|
||||
|
||||
int numTrackedControllers = 0;
|
||||
quint64 currentTime = usecTimestampNow();
|
||||
|
@ -230,14 +237,14 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control
|
|||
_lastControllerPose[controller] = tracking.HandPoses[hand];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (_lostTracking[controller]) {
|
||||
if (currentTime > _regainTrackingDeadline[controller]) {
|
||||
_poseStateMap.erase(controller);
|
||||
_poseStateMap[controller].valid = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
quint64 deadlineToRegainTracking = currentTime + LOST_TRACKING_DELAY;
|
||||
_regainTrackingDeadline[controller] = deadlineToRegainTracking;
|
||||
|
@ -245,6 +252,13 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control
|
|||
}
|
||||
handleRotationForUntrackedHand(inputCalibrationData, hand, tracking.HandPoses[hand]);
|
||||
});
|
||||
|
||||
if (_parent.isHeadControllerMounted()) {
|
||||
handleHeadPose(deltaTime, inputCalibrationData, tracking.HeadPose);
|
||||
} else {
|
||||
_poseStateMap[controller::HEAD].valid = false;
|
||||
}
|
||||
|
||||
using namespace controller;
|
||||
// Axes
|
||||
const auto& inputState = _parent._inputState;
|
||||
|
@ -269,7 +283,7 @@ void OculusControllerManager::TouchDevice::update(float deltaTime, const control
|
|||
if (inputState.Touches & pair.first) {
|
||||
_buttonPressedMap.insert(pair.second);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Haptics
|
||||
{
|
||||
|
@ -292,16 +306,38 @@ void OculusControllerManager::TouchDevice::focusOutEvent() {
|
|||
_buttonPressedMap.clear();
|
||||
};
|
||||
|
||||
void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
|
||||
const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand,
|
||||
const ovrPoseStatef& handPose) {
|
||||
void OculusControllerManager::TouchDevice::handlePose(float deltaTime,
|
||||
const controller::InputCalibrationData& inputCalibrationData,
|
||||
ovrHandType hand, const ovrPoseStatef& handPose) {
|
||||
auto poseId = hand == ovrHand_Left ? controller::LEFT_HAND : controller::RIGHT_HAND;
|
||||
auto& pose = _poseStateMap[poseId];
|
||||
pose = ovrControllerPoseToHandPose(hand, handPose);
|
||||
// transform into avatar frame
|
||||
glm::mat4 controllerToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
|
||||
pose = pose.transform(controllerToAvatar);
|
||||
}
|
||||
|
||||
void OculusControllerManager::TouchDevice::handleHeadPose(float deltaTime,
|
||||
const controller::InputCalibrationData& inputCalibrationData,
|
||||
const ovrPoseStatef& headPose) {
|
||||
glm::mat4 mat = createMatFromQuatAndPos(toGlm(headPose.ThePose.Orientation),
|
||||
toGlm(headPose.ThePose.Position));
|
||||
|
||||
//perform a 180 flip to make the HMD face the +z instead of -z, beacuse the head faces +z
|
||||
glm::mat4 matYFlip = mat * Matrices::Y_180;
|
||||
controller::Pose pose(extractTranslation(matYFlip),
|
||||
glmExtractRotation(matYFlip),
|
||||
toGlm(headPose.LinearVelocity), // XXX * matYFlip ?
|
||||
toGlm(headPose.AngularVelocity));
|
||||
|
||||
glm::mat4 sensorToAvatar = glm::inverse(inputCalibrationData.avatarMat) * inputCalibrationData.sensorToWorldMat;
|
||||
glm::mat4 defaultHeadOffset = glm::inverse(inputCalibrationData.defaultCenterEyeMat) *
|
||||
inputCalibrationData.defaultHeadMat;
|
||||
|
||||
controller::Pose hmdHeadPose = pose.transform(sensorToAvatar);
|
||||
|
||||
pose.valid = true;
|
||||
_poseStateMap[controller::HEAD] = hmdHeadPose.postTransform(defaultHeadOffset);
|
||||
}
|
||||
|
||||
void OculusControllerManager::TouchDevice::handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
|
||||
|
@ -382,6 +418,7 @@ controller::Input::NamedVector OculusControllerManager::TouchDevice::getAvailabl
|
|||
|
||||
makePair(LEFT_HAND, "LeftHand"),
|
||||
makePair(RIGHT_HAND, "RightHand"),
|
||||
makePair(HEAD, "Head"),
|
||||
|
||||
makePair(LEFT_PRIMARY_THUMB_TOUCH, "LeftPrimaryThumbTouch"),
|
||||
makePair(LEFT_SECONDARY_THUMB_TOUCH, "LeftSecondaryThumbTouch"),
|
||||
|
|
|
@ -28,6 +28,8 @@ public:
|
|||
const QString getName() const override { return NAME; }
|
||||
|
||||
bool isHandController() const override { return _touch != nullptr; }
|
||||
bool isHeadController() const override { return true; }
|
||||
bool isHeadControllerMounted() const;
|
||||
QStringList getSubdeviceNames() override;
|
||||
|
||||
bool activate() override;
|
||||
|
@ -75,8 +77,13 @@ private:
|
|||
|
||||
private:
|
||||
void stopHapticPulse(bool leftHand);
|
||||
void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose);
|
||||
void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData, ovrHandType hand, const ovrPoseStatef& handPose);
|
||||
void handlePose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
|
||||
ovrHandType hand, const ovrPoseStatef& handPose);
|
||||
void handleRotationForUntrackedHand(const controller::InputCalibrationData& inputCalibrationData,
|
||||
ovrHandType hand, const ovrPoseStatef& handPose);
|
||||
void handleHeadPose(float deltaTime, const controller::InputCalibrationData& inputCalibrationData,
|
||||
const ovrPoseStatef& headPose);
|
||||
|
||||
int _trackedControllers { 0 };
|
||||
|
||||
// perform an action when the TouchDevice mutex is acquired.
|
||||
|
|
|
@ -56,7 +56,7 @@ public:
|
|||
bool isKeyboardVisible() override;
|
||||
|
||||
// Possibly needs an additional thread for VR submission
|
||||
int getRequiredThreadCount() const override;
|
||||
int getRequiredThreadCount() const override;
|
||||
|
||||
protected:
|
||||
bool internalActivate() override;
|
||||
|
|
|
@ -164,6 +164,14 @@ void ViveControllerManager::deactivate() {
|
|||
_registeredWithInputMapper = false;
|
||||
}
|
||||
|
||||
bool ViveControllerManager::isHeadControllerMounted() const {
|
||||
if (_inputDevice && _inputDevice->isHeadControllerMounted()) {
|
||||
return true;
|
||||
}
|
||||
vr::EDeviceActivityLevel activityLevel = _system->GetTrackedDeviceActivityLevel(vr::k_unTrackedDeviceIndex_Hmd);
|
||||
return activityLevel == vr::k_EDeviceActivityLevel_UserInteraction;
|
||||
}
|
||||
|
||||
void ViveControllerManager::pluginUpdate(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) {
|
||||
|
||||
if (!_system) {
|
||||
|
|
|
@ -41,6 +41,8 @@ public:
|
|||
const QString getName() const override { return NAME; }
|
||||
|
||||
bool isHandController() const override { return true; }
|
||||
bool isHeadController() const override { return true; }
|
||||
bool isHeadControllerMounted() const;
|
||||
|
||||
bool activate() override;
|
||||
void deactivate() override;
|
||||
|
@ -54,6 +56,7 @@ private:
|
|||
class InputDevice : public controller::InputDevice {
|
||||
public:
|
||||
InputDevice(vr::IVRSystem*& system);
|
||||
bool isHeadControllerMounted() const { return _overrideHead; }
|
||||
private:
|
||||
// Device functions
|
||||
controller::Input::NamedVector getAvailableInputs() const override;
|
||||
|
@ -143,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 };
|
||||
|
|
|
@ -33,7 +33,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
|
||||
|
|
BIN
scripts/system/assets/sounds/sound-print-photo.wav
Normal file
BIN
scripts/system/assets/sounds/sound-print-photo.wav
Normal file
Binary file not shown.
|
@ -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;
|
||||
|
|
|
@ -28,7 +28,11 @@
|
|||
</form>
|
||||
</div>
|
||||
<input type="button" id="snap-button" onclick="takeSnapshot()" />
|
||||
<div id="snap-settings-right"></div>
|
||||
<div id="snap-settings-right">
|
||||
<button type="image" class="blueButton" id="print-button" onclick="printToPolaroid()">
|
||||
<div id="print-icon" class="print-icon print-icon-default">
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -286,6 +286,43 @@ input[type=button].naked:active {
|
|||
// END styling of snapshot controls (bottom panel) and its contents
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
// START polaroid styling
|
||||
*/
|
||||
|
||||
#print-button {
|
||||
width: 72px;
|
||||
height: 72px;
|
||||
margin-left: 30px;
|
||||
margin-top: -10px;
|
||||
box-sizing: content-box;
|
||||
display: inline;
|
||||
outline:none;
|
||||
}
|
||||
|
||||
.print-icon {
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.print-icon-default {
|
||||
background: url(../img/button-snap-print.svg) no-repeat;
|
||||
margin-right: -1px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.print-icon-loading {
|
||||
background: url(../img/loader.gif) no-repeat;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
/*
|
||||
// END polaroid styling
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
// START misc styling
|
||||
*/
|
||||
|
|
25
scripts/system/html/img/button-snap-print.svg
Normal file
25
scripts/system/html/img/button-snap-print.svg
Normal file
|
@ -0,0 +1,25 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 199.8 231" style="enable-background:new 0 0 199.8 231;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M146.7,145.3H48V50.1h98.8V145.3z M56.2,122.8h83.1V57.6H56.2V122.8z"/>
|
||||
<circle class="st0" cx="96.8" cy="83.5" r="10.9"/>
|
||||
<path class="st0" d="M116.9,112.8h-40v-1.2c0-6.5,5.9-11.8,12.5-11.8H105c6.5,0,11.8,5.3,11.8,11.8V112.8z"/>
|
||||
<polygon class="st0" points="155.5,80.6 155.5,125.7 170.9,39.2 74.6,19.6 70.3,43.7 78.3,43.7 81,28.8 161.7,45.6 "/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M12.4,213.7v-29.8h14c6.5,0,10,4.4,10,9.6c0,5.2-3.6,9.6-10,9.6h-7.6v10.6H12.4z M29.9,193.4
|
||||
c0-2.5-1.9-4-4.4-4h-6.7v8h6.7C28,197.4,29.9,195.9,29.9,193.4z"/>
|
||||
<path class="st0" d="M69.9,213.7L64,203.1h-4.7v10.6H53v-29.8h14c6.2,0,10.1,4.1,10.1,9.6c0,5.2-3.4,8.1-6.6,8.9l6.8,11.4H69.9z
|
||||
M70.5,193.4c0-2.5-1.9-4-4.4-4h-6.7v8.1h6.7C68.6,197.5,70.5,195.9,70.5,193.4z"/>
|
||||
<path class="st0" d="M94.4,213.7v-29.8h6.4v29.8H94.4z"/>
|
||||
<path class="st0" d="M139.8,213.7l-14.2-19.5v19.5h-6.4v-29.8h6.5l13.8,18.7v-18.7h6.4v29.8H139.8z"/>
|
||||
<path class="st0" d="M171.4,213.7v-24.2h-8.7v-5.6h23.8v5.6h-8.7v24.2H171.4z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After (image error) Size: 1.4 KiB |
|
@ -311,10 +311,17 @@ function addImage(image_data, isLoggedIn, canShare, isGifLoading, isShowingPrevi
|
|||
if (isShowingPreviousImages && isLoggedIn && image_data.story_id) {
|
||||
updateShareInfo(id, image_data.story_id);
|
||||
}
|
||||
if (isShowingPreviousImages) {
|
||||
requestPrintButtonUpdate();
|
||||
}
|
||||
};
|
||||
img.onerror = function () {
|
||||
img.onload = null;
|
||||
img.src = image_data.errorPath;
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "alertSnapshotLoadFailed"
|
||||
}));
|
||||
};
|
||||
}
|
||||
function showConfirmationMessage(selectedID, destination) {
|
||||
|
@ -670,10 +677,22 @@ window.onload = function () {
|
|||
break;
|
||||
case 'captureSettings':
|
||||
handleCaptureSetting(message.setting);
|
||||
break;
|
||||
case 'setPrintButtonEnabled':
|
||||
setPrintButtonEnabled();
|
||||
break;
|
||||
case 'setPrintButtonLoading':
|
||||
setPrintButtonLoading();
|
||||
break;
|
||||
case 'setPrintButtonDisabled':
|
||||
setPrintButtonDisabled();
|
||||
break;
|
||||
case 'snapshotUploadComplete':
|
||||
var isGif = fileExtensionMatches(message.image_url, "gif");
|
||||
updateShareInfo(isGif ? "p1" : "p0", message.story_id);
|
||||
if (isPrintProcessing()) {
|
||||
setPrintButtonEnabled();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
console.log("Unknown message action received in SnapshotReview.js.");
|
||||
|
@ -703,6 +722,59 @@ function takeSnapshot() {
|
|||
}
|
||||
}
|
||||
|
||||
function isPrintDisabled() {
|
||||
var printElement = document.getElementById('print-icon');
|
||||
|
||||
return printElement.classList.contains("print-icon") &&
|
||||
printElement.classList.contains("print-icon-default") &&
|
||||
document.getElementById('print-button').disabled;
|
||||
}
|
||||
function isPrintProcessing() {
|
||||
var printElement = document.getElementById('print-icon');
|
||||
|
||||
return printElement.classList.contains("print-icon") &&
|
||||
printElement.classList.contains("print-icon-loading") &&
|
||||
document.getElementById('print-button').disabled;
|
||||
}
|
||||
function isPrintEnabled() {
|
||||
var printElement = document.getElementById('print-icon');
|
||||
|
||||
return printElement.classList.contains("print-icon") &&
|
||||
printElement.classList.contains("print-icon-default") &&
|
||||
!document.getElementById('print-button').disabled;
|
||||
}
|
||||
|
||||
function setPrintButtonLoading() {
|
||||
document.getElementById('print-icon').className = "print-icon print-icon-loading";
|
||||
document.getElementById('print-button').disabled = true;
|
||||
}
|
||||
function setPrintButtonDisabled() {
|
||||
document.getElementById('print-icon').className = "print-icon print-icon-default";
|
||||
document.getElementById('print-button').disabled = true;
|
||||
}
|
||||
function setPrintButtonEnabled() {
|
||||
document.getElementById('print-button').disabled = false;
|
||||
document.getElementById('print-icon').className = "print-icon print-icon-default";
|
||||
}
|
||||
|
||||
function requestPrintButtonUpdate() {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "requestPrintButtonUpdate"
|
||||
}));
|
||||
}
|
||||
|
||||
function printToPolaroid() {
|
||||
if (isPrintEnabled()) {
|
||||
EventBridge.emitWebEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action: "printToPolaroid"
|
||||
}));
|
||||
} else {
|
||||
setPrintButtonLoading();
|
||||
}
|
||||
}
|
||||
|
||||
function testInBrowser(test) {
|
||||
if (test === 0) {
|
||||
showSetupInstructions();
|
||||
|
|
|
@ -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);
|
||||
|
||||
}());
|
||||
|
|
|
@ -99,11 +99,13 @@ function onMessage(message) {
|
|||
Settings.setValue("previousStillSnapStoryID", "");
|
||||
Settings.setValue("previousStillSnapBlastingDisabled", false);
|
||||
Settings.setValue("previousStillSnapHifiSharingDisabled", false);
|
||||
Settings.setValue("previousStillSnapUrl", "");
|
||||
Settings.setValue("previousAnimatedSnapPath", "");
|
||||
Settings.setValue("previousAnimatedSnapStoryID", "");
|
||||
Settings.setValue("previousAnimatedSnapBlastingDisabled", false);
|
||||
Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false);
|
||||
}
|
||||
updatePrintPermissions();
|
||||
break;
|
||||
case 'login':
|
||||
openLoginWindow();
|
||||
|
@ -140,12 +142,13 @@ function onMessage(message) {
|
|||
break;
|
||||
case 'shareSnapshotForUrl':
|
||||
isDomainOpen(Settings.getValue("previousSnapshotDomainID"), function (canShare) {
|
||||
if (canShare) {
|
||||
isLoggedIn = Account.isLoggedIn();
|
||||
var isGif = fileExtensionMatches(message.data, "gif");
|
||||
isLoggedIn = Account.isLoggedIn();
|
||||
isUploadingPrintableStill = canShare && Account.isLoggedIn() && !isGif;
|
||||
if (canShare) {
|
||||
if (isLoggedIn) {
|
||||
print('Sharing snapshot with audience "for_url":', message.data);
|
||||
Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref"));
|
||||
var isGif = fileExtensionMatches(message.data, "gif");
|
||||
Window.shareSnapshot(message.data, Settings.getValue("previousSnapshotHref"));
|
||||
if (isGif) {
|
||||
numGifSnapshotUploadsPending++;
|
||||
} else {
|
||||
|
@ -156,6 +159,7 @@ function onMessage(message) {
|
|||
snapshotToShareAfterLogin.push({ path: message.data, href: Settings.getValue("previousSnapshotHref") });
|
||||
}
|
||||
}
|
||||
updatePrintPermissions();
|
||||
});
|
||||
break;
|
||||
case 'blastToConnections':
|
||||
|
@ -215,6 +219,18 @@ function onMessage(message) {
|
|||
});
|
||||
}
|
||||
break;
|
||||
case 'requestPrintButtonUpdate':
|
||||
updatePrintPermissions();
|
||||
break;
|
||||
case 'printToPolaroid':
|
||||
if (Entities.canRez() || Entities.canRezTmp()) {
|
||||
printToPolaroid(Settings.getValue("previousStillSnapUrl"));
|
||||
removeFromStoryIDsToMaybeDelete(Settings.getValue("previousStillSnapStoryID"));
|
||||
}
|
||||
break;
|
||||
case 'alertSnapshotLoadFailed':
|
||||
snapshotFailedToLoad = true;
|
||||
break;
|
||||
case 'shareSnapshotWithEveryone':
|
||||
isLoggedIn = Account.isLoggedIn();
|
||||
if (isLoggedIn) {
|
||||
|
@ -262,6 +278,54 @@ function onMessage(message) {
|
|||
}
|
||||
}
|
||||
|
||||
var POLAROID_PRINT_SOUND = SoundCache.getSound(Script.resolvePath("assets/sounds/sound-print-photo.wav"));
|
||||
var POLAROID_MODEL_URL = 'http://hifi-content.s3.amazonaws.com/alan/dev/Test/snapshot.fbx';
|
||||
|
||||
function printToPolaroid(image_url) {
|
||||
var polaroid_url = image_url;
|
||||
|
||||
var model_pos = Vec3.sum(MyAvatar.position, Vec3.multiply(1.25, Quat.getForward(MyAvatar.orientation)));
|
||||
|
||||
var model_q1 = MyAvatar.orientation;
|
||||
var model_q2 = Quat.angleAxis(90, Quat.getRight(model_q1));
|
||||
var model_rot = Quat.multiply(model_q2, model_q1);
|
||||
|
||||
var properties = {
|
||||
"type": 'Model',
|
||||
"shapeType": 'box',
|
||||
|
||||
"name": "New Snapshot",
|
||||
"description": "Printed from Snaps",
|
||||
"modelURL": POLAROID_MODEL_URL,
|
||||
|
||||
"position": model_pos,
|
||||
"rotation": model_rot,
|
||||
|
||||
"textures": JSON.stringify( { "tex.picture": polaroid_url } ),
|
||||
|
||||
"density": 200,
|
||||
"restitution": 0.15,
|
||||
"gravity": { "x": 0, "y": -4.5, "z": 0 },
|
||||
|
||||
"velocity": { "x": 0, "y": 3.5, "z": 0 },
|
||||
"angularVelocity": { "x": -1.0, "y": 0, "z": -1.3 },
|
||||
|
||||
"dynamic": true,
|
||||
"collisionsWillMove": true,
|
||||
|
||||
"userData": {
|
||||
"grabbableKey": { "grabbable" : true }
|
||||
}
|
||||
};
|
||||
|
||||
var polaroid = Entities.addEntity(properties);
|
||||
Audio.playSound(POLAROID_PRINT_SOUND, {
|
||||
position: model_pos,
|
||||
localOnly: false,
|
||||
volume: 0.2
|
||||
});
|
||||
}
|
||||
|
||||
function fillImageDataFromPrevious() {
|
||||
isLoggedIn = Account.isLoggedIn();
|
||||
var previousStillSnapPath = Settings.getValue("previousStillSnapPath");
|
||||
|
@ -349,6 +413,7 @@ function snapshotUploaded(isError, reply) {
|
|||
Settings.setValue("previousAnimatedSnapStoryID", storyID);
|
||||
} else {
|
||||
Settings.setValue("previousStillSnapStoryID", storyID);
|
||||
Settings.setValue("previousStillSnapUrl", imageURL);
|
||||
}
|
||||
} else {
|
||||
print('Ignoring snapshotUploaded() callback for stale ' + (isGif ? 'GIF' : 'Still' ) + ' snapshot. Stale story ID:', storyID);
|
||||
|
@ -356,6 +421,7 @@ function snapshotUploaded(isError, reply) {
|
|||
} else {
|
||||
print(reply);
|
||||
}
|
||||
isUploadingPrintableStill = false;
|
||||
}
|
||||
var href, domainId;
|
||||
function takeSnapshot() {
|
||||
|
@ -367,10 +433,16 @@ function takeSnapshot() {
|
|||
Settings.setValue("previousStillSnapStoryID", "");
|
||||
Settings.setValue("previousStillSnapBlastingDisabled", false);
|
||||
Settings.setValue("previousStillSnapHifiSharingDisabled", false);
|
||||
Settings.setValue("previousStillSnapUrl", "");
|
||||
Settings.setValue("previousAnimatedSnapPath", "");
|
||||
Settings.setValue("previousAnimatedSnapStoryID", "");
|
||||
Settings.setValue("previousAnimatedSnapBlastingDisabled", false);
|
||||
Settings.setValue("previousAnimatedSnapHifiSharingDisabled", false);
|
||||
|
||||
// Since we are taking a snapshot, we should make the print button appear to be loading/processing
|
||||
snapshotFailedToLoad = false;
|
||||
isUploadingPrintableStill = true;
|
||||
updatePrintPermissions();
|
||||
|
||||
// Raising the desktop for the share dialog at end will interact badly with clearOverlayWhenMoving.
|
||||
// Turn it off now, before we start futzing with things (and possibly moving).
|
||||
|
@ -612,6 +684,8 @@ function onUsernameChanged() {
|
|||
}
|
||||
});
|
||||
}
|
||||
isUploadingPrintableStill = canShare;
|
||||
updatePrintPermissions();
|
||||
});
|
||||
|
||||
shareAfterLogin = false;
|
||||
|
@ -619,6 +693,7 @@ function onUsernameChanged() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
function snapshotLocationSet(location) {
|
||||
if (location !== "") {
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
|
@ -628,12 +703,44 @@ function snapshotLocationSet(location) {
|
|||
}
|
||||
}
|
||||
|
||||
function updatePrintPermissions() {
|
||||
processRezPermissionChange(Entities.canRez() || Entities.canRezTmp());
|
||||
}
|
||||
|
||||
var snapshotFailedToLoad = false;
|
||||
var isUploadingPrintableStill = false;
|
||||
function processRezPermissionChange(canRez) {
|
||||
var action = "";
|
||||
|
||||
if (canRez && !snapshotFailedToLoad) {
|
||||
if (Settings.getValue("previousStillSnapUrl")) {
|
||||
action = 'setPrintButtonEnabled';
|
||||
} else if (isUploadingPrintableStill) {
|
||||
action = 'setPrintButtonLoading';
|
||||
} else {
|
||||
action = 'setPrintButtonDisabled';
|
||||
}
|
||||
} else {
|
||||
action = 'setPrintButtonDisabled';
|
||||
}
|
||||
|
||||
tablet.emitScriptEvent(JSON.stringify({
|
||||
type: "snapshot",
|
||||
action : action
|
||||
}));
|
||||
}
|
||||
|
||||
button.clicked.connect(onButtonClicked);
|
||||
buttonConnected = true;
|
||||
|
||||
Window.snapshotShared.connect(snapshotUploaded);
|
||||
tablet.screenChanged.connect(onTabletScreenChanged);
|
||||
GlobalServices.myUsernameChanged.connect(onUsernameChanged);
|
||||
Snapshot.snapshotLocationSet.connect(snapshotLocationSet);
|
||||
|
||||
Entities.canRezChanged.connect(updatePrintPermissions);
|
||||
Entities.canRezTmpChanged.connect(updatePrintPermissions);
|
||||
|
||||
Script.scriptEnding.connect(function () {
|
||||
if (buttonConnected) {
|
||||
button.clicked.disconnect(onButtonClicked);
|
||||
|
@ -645,6 +752,9 @@ Script.scriptEnding.connect(function () {
|
|||
Window.snapshotShared.disconnect(snapshotUploaded);
|
||||
tablet.screenChanged.disconnect(onTabletScreenChanged);
|
||||
Snapshot.snapshotLocationSet.disconnect(snapshotLocationSet);
|
||||
|
||||
Entities.canRezChanged.disconnect(processRezPermissionChange);
|
||||
Entities.canRezTmpChanged.disconnect(processRezPermissionChange);
|
||||
});
|
||||
|
||||
}()); // END LOCAL_SCOPE
|
||||
|
|
15
tests/qt59/CMakeLists.txt
Normal file
15
tests/qt59/CMakeLists.txt
Normal file
|
@ -0,0 +1,15 @@
|
|||
|
||||
set(TARGET_NAME qt59)
|
||||
|
||||
if (WIN32)
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4049 /ignore:4217")
|
||||
endif()
|
||||
|
||||
# This is not a testcase -- just set it up as a regular hifi project
|
||||
setup_hifi_project(Gui)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Tests/manual-tests/")
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(shared networking)
|
||||
|
||||
package_libraries_for_deployment()
|
78
tests/qt59/src/main.cpp
Normal file
78
tests/qt59/src/main.cpp
Normal file
|
@ -0,0 +1,78 @@
|
|||
//
|
||||
// Created by Bradley Austin Davis on 2017/06/06
|
||||
// Copyright 2013-2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QCoreApplication>
|
||||
|
||||
#include <NodeList.h>
|
||||
#include <AccountManager.h>
|
||||
#include <AddressManager.h>
|
||||
#include <MessagesClient.h>
|
||||
|
||||
#include <BuildInfo.h>
|
||||
|
||||
|
||||
class Qt59TestApp : public QCoreApplication {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Qt59TestApp(int argc, char* argv[]);
|
||||
~Qt59TestApp();
|
||||
|
||||
private:
|
||||
void finish(int exitCode);
|
||||
};
|
||||
|
||||
|
||||
|
||||
Qt59TestApp::Qt59TestApp(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv)
|
||||
{
|
||||
|
||||
Setting::init();
|
||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
||||
DependencyManager::set<AccountManager>([&] { return QString("Mozilla/5.0 (HighFidelityACClient)"); });
|
||||
DependencyManager::set<AddressManager>();
|
||||
DependencyManager::set<NodeList>(NodeType::Agent, 0);
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->startThread();
|
||||
auto messagesClient = DependencyManager::set<MessagesClient>();
|
||||
messagesClient->startThread();
|
||||
QTimer::singleShot(1000, [this] { finish(0); });
|
||||
}
|
||||
|
||||
Qt59TestApp::~Qt59TestApp() {
|
||||
}
|
||||
|
||||
|
||||
void Qt59TestApp::finish(int exitCode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// send the domain a disconnect packet, force stoppage of domain-server check-ins
|
||||
nodeList->getDomainHandler().disconnect();
|
||||
nodeList->setIsShuttingDown(true);
|
||||
nodeList->getPacketReceiver().setShouldDropPackets(true);
|
||||
|
||||
// remove the NodeList from the DependencyManager
|
||||
DependencyManager::destroy<NodeList>();
|
||||
QCoreApplication::exit(exitCode);
|
||||
}
|
||||
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
QCoreApplication::setApplicationName("Qt59Test");
|
||||
QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
|
||||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
Qt59TestApp app(argc, argv);
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
#include "main.moc"
|
77
tests/shared/src/CubicHermiteSplineTests.cpp
Normal file
77
tests/shared/src/CubicHermiteSplineTests.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
//
|
||||
// CubicHermiteSplineTests.cpp
|
||||
// tests/shared/src
|
||||
//
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "CubicHermiteSplineTests.h"
|
||||
#include "../QTestExtensions.h"
|
||||
#include <QtCore/QDebug>
|
||||
#include "CubicHermiteSpline.h"
|
||||
|
||||
QTEST_MAIN(CubicHermiteSplineTests)
|
||||
|
||||
void CubicHermiteSplineTests::testCubicHermiteSplineFunctor() {
|
||||
glm::vec3 p0(0.0f, 0.0f, 0.0f);
|
||||
glm::vec3 m0(1.0f, 0.0f, 0.0f);
|
||||
glm::vec3 p1(1.0f, 1.0f, 0.0f);
|
||||
glm::vec3 m1(2.0f, 0.0f, 0.0f);
|
||||
CubicHermiteSplineFunctor hermiteSpline(p0, m0, p1, m1);
|
||||
|
||||
const float EPSILON = 0.0001f;
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(p0, hermiteSpline(0.0f), EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(p1, hermiteSpline(1.0f), EPSILON);
|
||||
|
||||
// these values were computed offline.
|
||||
const glm::vec3 oneFourth(0.203125f, 0.15625f, 0.0f);
|
||||
const glm::vec3 oneHalf(0.375f, 0.5f, 0.0f);
|
||||
const glm::vec3 threeFourths(0.609375f, 0.84375f, 0.0f);
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(oneFourth, hermiteSpline(0.25f), EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(oneHalf, hermiteSpline(0.5f), EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(threeFourths, hermiteSpline(0.75f), EPSILON);
|
||||
}
|
||||
|
||||
void CubicHermiteSplineTests::testCubicHermiteSplineFunctorWithArcLength() {
|
||||
glm::vec3 p0(0.0f, 0.0f, 0.0f);
|
||||
glm::vec3 m0(1.0f, 0.0f, 0.0f);
|
||||
glm::vec3 p1(1.0f, 1.0f, 0.0f);
|
||||
glm::vec3 m1(2.0f, 0.0f, 0.0f);
|
||||
CubicHermiteSplineFunctorWithArcLength hermiteSpline(p0, m0, p1, m1);
|
||||
|
||||
const float EPSILON = 0.001f;
|
||||
|
||||
float arcLengths[5] = {
|
||||
hermiteSpline.arcLength(0.0f),
|
||||
hermiteSpline.arcLength(0.25f),
|
||||
hermiteSpline.arcLength(0.5f),
|
||||
hermiteSpline.arcLength(0.75f),
|
||||
hermiteSpline.arcLength(1.0f)
|
||||
};
|
||||
|
||||
// these values were computed offline
|
||||
float referenceArcLengths[5] = {
|
||||
0.0f,
|
||||
0.268317f,
|
||||
0.652788f,
|
||||
1.07096f,
|
||||
1.50267f
|
||||
};
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(arcLengths[0], referenceArcLengths[0], EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(arcLengths[1], referenceArcLengths[1], EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(arcLengths[2], referenceArcLengths[2], EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(arcLengths[3], referenceArcLengths[3], EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(arcLengths[4], referenceArcLengths[4], EPSILON);
|
||||
|
||||
QCOMPARE_WITH_ABS_ERROR(0.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[0]), EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(0.25f, hermiteSpline.arcLengthInverse(referenceArcLengths[1]), EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(0.5f, hermiteSpline.arcLengthInverse(referenceArcLengths[2]), EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(0.75f, hermiteSpline.arcLengthInverse(referenceArcLengths[3]), EPSILON);
|
||||
QCOMPARE_WITH_ABS_ERROR(1.0f, hermiteSpline.arcLengthInverse(referenceArcLengths[4]), EPSILON);
|
||||
}
|
23
tests/shared/src/CubicHermiteSplineTests.h
Normal file
23
tests/shared/src/CubicHermiteSplineTests.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// CubicHermiteSplineTests.h
|
||||
// tests/shared/src
|
||||
//
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CubicHermiteSplineTests_h
|
||||
#define hifi_CubicHermiteSplineTests_h
|
||||
|
||||
#include <QtTest/QtTest>
|
||||
|
||||
class CubicHermiteSplineTests : public QObject {
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void testCubicHermiteSplineFunctor();
|
||||
void testCubicHermiteSplineFunctorWithArcLength();
|
||||
};
|
||||
|
||||
#endif // hifi_TransformTests_h
|
|
@ -90,23 +90,14 @@ ACClientApp::ACClientApp(int argc, char* argv[]) :
|
|||
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// start the nodeThread so its event loop is running
|
||||
QThread* nodeThread = new QThread(this);
|
||||
nodeThread->setObjectName("NodeList Thread");
|
||||
nodeThread->start();
|
||||
|
||||
// make sure the node thread is given highest priority
|
||||
nodeThread->setPriority(QThread::TimeCriticalPriority);
|
||||
nodeList->startThread();
|
||||
|
||||
// setup a timer for domain-server check ins
|
||||
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
|
||||
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
|
||||
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
||||
|
||||
// put the NodeList and datagram processing on the node thread
|
||||
nodeList->moveToThread(nodeThread);
|
||||
|
||||
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
|
||||
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
|
||||
|
@ -239,12 +230,8 @@ void ACClientApp::finish(int exitCode) {
|
|||
// tell the packet receiver we're shutting down, so it can drop packets
|
||||
nodeList->getPacketReceiver().setShouldDropPackets(true);
|
||||
|
||||
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
|
||||
// remove the NodeList from the DependencyManager
|
||||
DependencyManager::destroy<NodeList>();
|
||||
// ask the node thread to quit and wait until it is done
|
||||
nodeThread->quit();
|
||||
nodeThread->wait();
|
||||
|
||||
printFailedServers();
|
||||
QCoreApplication::exit(exitCode);
|
||||
|
|
|
@ -111,23 +111,13 @@ ATPGetApp::ATPGetApp(int argc, char* argv[]) :
|
|||
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// start the nodeThread so its event loop is running
|
||||
QThread* nodeThread = new QThread(this);
|
||||
nodeThread->setObjectName("NodeList Thread");
|
||||
nodeThread->start();
|
||||
|
||||
// make sure the node thread is given highest priority
|
||||
nodeThread->setPriority(QThread::TimeCriticalPriority);
|
||||
nodeList->startThread();
|
||||
|
||||
// setup a timer for domain-server check ins
|
||||
QTimer* domainCheckInTimer = new QTimer(nodeList.data());
|
||||
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
|
||||
domainCheckInTimer->start(DOMAIN_SERVER_CHECK_IN_MSECS);
|
||||
|
||||
// put the NodeList and datagram processing on the node thread
|
||||
nodeList->moveToThread(nodeThread);
|
||||
|
||||
const DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
|
||||
connect(&domainHandler, SIGNAL(hostnameChanged(const QString&)), SLOT(domainChanged(const QString&)));
|
||||
|
@ -258,12 +248,8 @@ void ATPGetApp::finish(int exitCode) {
|
|||
// tell the packet receiver we're shutting down, so it can drop packets
|
||||
nodeList->getPacketReceiver().setShouldDropPackets(true);
|
||||
|
||||
QThread* nodeThread = DependencyManager::get<NodeList>()->thread();
|
||||
// remove the NodeList from the DependencyManager
|
||||
DependencyManager::destroy<NodeList>();
|
||||
// ask the node thread to quit and wait until it is done
|
||||
nodeThread->quit();
|
||||
nodeThread->wait();
|
||||
|
||||
QCoreApplication::exit(exitCode);
|
||||
}
|
||||
|
|
64
tools/oven/src/BakerCLI.cpp
Normal file
64
tools/oven/src/BakerCLI.cpp
Normal file
|
@ -0,0 +1,64 @@
|
|||
//
|
||||
// BakerCLI.cpp
|
||||
// tools/oven/src
|
||||
//
|
||||
// Created by Robbie Uvanni on 6/6/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QObject>
|
||||
#include <QImageReader>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "ModelBakingLoggingCategory.h"
|
||||
#include "Oven.h"
|
||||
#include "BakerCLI.h"
|
||||
#include "FBXBaker.h"
|
||||
#include "TextureBaker.h"
|
||||
|
||||
BakerCLI::BakerCLI(Oven* parent) : QObject(parent) {
|
||||
}
|
||||
|
||||
void BakerCLI::bakeFile(QUrl inputUrl, const QString outputPath) {
|
||||
|
||||
// if the URL doesn't have a scheme, assume it is a local file
|
||||
if (inputUrl.scheme() != "http" && inputUrl.scheme() != "https" && inputUrl.scheme() != "ftp") {
|
||||
inputUrl.setScheme("file");
|
||||
}
|
||||
|
||||
static const QString MODEL_EXTENSION { ".fbx" };
|
||||
|
||||
// check what kind of baker we should be creating
|
||||
bool isFBX = inputUrl.toDisplayString().endsWith(MODEL_EXTENSION, Qt::CaseInsensitive);
|
||||
bool isSupportedImage = false;
|
||||
|
||||
for (QByteArray format : QImageReader::supportedImageFormats()) {
|
||||
isSupportedImage |= inputUrl.toDisplayString().endsWith(format, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
// create our appropiate baker
|
||||
if (isFBX) {
|
||||
_baker = std::unique_ptr<Baker> { new FBXBaker(inputUrl, outputPath, []() -> QThread* { return qApp->getNextWorkerThread(); }) };
|
||||
_baker->moveToThread(qApp->getFBXBakerThread());
|
||||
} else if (isSupportedImage) {
|
||||
_baker = std::unique_ptr<Baker> { new TextureBaker(inputUrl, image::TextureUsage::CUBE_TEXTURE, outputPath) };
|
||||
_baker->moveToThread(qApp->getNextWorkerThread());
|
||||
} else {
|
||||
qCDebug(model_baking) << "Failed to determine baker type for file" << inputUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
// invoke the bake method on the baker thread
|
||||
QMetaObject::invokeMethod(_baker.get(), "bake");
|
||||
|
||||
// make sure we hear about the results of this baker when it is done
|
||||
connect(_baker.get(), &Baker::finished, this, &BakerCLI::handleFinishedBaker);
|
||||
}
|
||||
|
||||
void BakerCLI::handleFinishedBaker() {
|
||||
qCDebug(model_baking) << "Finished baking file.";
|
||||
QApplication::quit();
|
||||
}
|
34
tools/oven/src/BakerCLI.h
Normal file
34
tools/oven/src/BakerCLI.h
Normal file
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// BakerCLI.h
|
||||
// tools/oven/src
|
||||
//
|
||||
// Created by Robbie Uvanni on 6/6/17.
|
||||
// Copyright 2017 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_BakerCLI_h
|
||||
#define hifi_BakerCLI_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
#include "Baker.h"
|
||||
#include "Oven.h"
|
||||
|
||||
class BakerCLI : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
BakerCLI(Oven* parent);
|
||||
void bakeFile(QUrl inputUrl, const QString outputPath);
|
||||
|
||||
private slots:
|
||||
void handleFinishedBaker();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Baker> _baker;
|
||||
};
|
||||
|
||||
#endif // hifi_BakerCLI_h
|
|
@ -11,16 +11,20 @@
|
|||
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QCommandLineParser>
|
||||
|
||||
#include <image/Image.h>
|
||||
#include <SettingInterface.h>
|
||||
|
||||
#include "ui/OvenMainWindow.h"
|
||||
|
||||
#include "Oven.h"
|
||||
#include "BakerCli.h"
|
||||
|
||||
static const QString OUTPUT_FOLDER = "/Users/birarda/code/hifi/lod/test-oven/export";
|
||||
|
||||
static const QString CLI_INPUT_PARAMETER = "i";
|
||||
static const QString CLI_OUTPUT_PARAMETER = "o";
|
||||
|
||||
Oven::Oven(int argc, char* argv[]) :
|
||||
QApplication(argc, argv)
|
||||
{
|
||||
|
@ -30,24 +34,43 @@ Oven::Oven(int argc, char* argv[]) :
|
|||
// init the settings interface so we can save and load settings
|
||||
Setting::init();
|
||||
|
||||
// parse the command line parameters
|
||||
QCommandLineParser parser;
|
||||
|
||||
parser.addOptions({
|
||||
{ CLI_INPUT_PARAMETER, "Path to file that you would like to bake.", "input" },
|
||||
{ CLI_OUTPUT_PARAMETER, "Path to folder that will be used as output.", "output" }
|
||||
});
|
||||
parser.addHelpOption();
|
||||
parser.process(*this);
|
||||
|
||||
// enable compression in image library, except for cube maps
|
||||
image::setColorTexturesCompressionEnabled(true);
|
||||
image::setGrayscaleTexturesCompressionEnabled(true);
|
||||
image::setNormalTexturesCompressionEnabled(true);
|
||||
image::setCubeTexturesCompressionEnabled(true);
|
||||
|
||||
// check if we were passed any command line arguments that would tell us just to run without the GUI
|
||||
|
||||
// setup the GUI
|
||||
_mainWindow = new OvenMainWindow;
|
||||
_mainWindow->show();
|
||||
|
||||
// setup our worker threads
|
||||
setupWorkerThreads(QThread::idealThreadCount() - 1);
|
||||
|
||||
// Autodesk's SDK means that we need a single thread for all FBX importing/exporting in the same process
|
||||
// setup the FBX Baker thread
|
||||
setupFBXBakerThread();
|
||||
|
||||
// check if we were passed any command line arguments that would tell us just to run without the GUI
|
||||
if (parser.isSet(CLI_INPUT_PARAMETER) || parser.isSet(CLI_OUTPUT_PARAMETER)) {
|
||||
if (parser.isSet(CLI_INPUT_PARAMETER) && parser.isSet(CLI_OUTPUT_PARAMETER)) {
|
||||
BakerCLI* cli = new BakerCLI(this);
|
||||
cli->bakeFile(parser.value(CLI_INPUT_PARAMETER), parser.value(CLI_OUTPUT_PARAMETER));
|
||||
} else {
|
||||
parser.showHelp();
|
||||
QApplication::quit();
|
||||
}
|
||||
} else {
|
||||
// setup the GUI
|
||||
_mainWindow = new OvenMainWindow;
|
||||
_mainWindow->show();
|
||||
}
|
||||
}
|
||||
|
||||
Oven::~Oven() {
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
#include <QtWidgets/QApplication>
|
||||
|
||||
#include <tbb/concurrent_vector.h>
|
||||
#include <TBBHelpers.h>
|
||||
|
||||
#include <atomic>
|
||||
|
||||
|
|
Loading…
Reference in a new issue