mirror of
https://github.com/HifiExperiments/overte.git
synced 2025-08-05 03:09:45 +02:00
Merge branch 'master' of https://github.com/highfidelity/hifi into update-from-master-1
This commit is contained in:
commit
851911f55a
120 changed files with 2499 additions and 1494 deletions
|
@ -57,6 +57,7 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
ThreadedAssignment(message),
|
||||
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES)
|
||||
{
|
||||
_entityEditSender.setPacketsPerSecond(DEFAULT_ENTITY_PPS_PER_SCRIPT);
|
||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
ResourceManager::init();
|
||||
|
|
|
@ -53,31 +53,3 @@ QVariantMap AssignmentDynamic::getArguments() {
|
|||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getArguments called in assignment-client.";
|
||||
return QVariantMap();
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentDynamic::getPosition() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getPosition called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
glm::quat AssignmentDynamic::getRotation() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getRotation called in assignment-client.";
|
||||
return glm::quat();
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentDynamic::getLinearVelocity() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getLinearVelocity called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentDynamic::setLinearVelocity(glm::vec3 linearVelocity) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::setLinearVelocity called in assignment-client.";
|
||||
}
|
||||
|
||||
glm::vec3 AssignmentDynamic::getAngularVelocity() {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::getAngularVelocity called in assignment-client.";
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
|
||||
void AssignmentDynamic::setAngularVelocity(glm::vec3 angularVelocity) {
|
||||
qDebug() << "UNEXPECTED -- AssignmentDynamic::setAngularVelocity called in assignment-client.";
|
||||
}
|
||||
|
|
|
@ -39,13 +39,6 @@ private:
|
|||
QByteArray _data;
|
||||
|
||||
protected:
|
||||
virtual glm::vec3 getPosition() override;
|
||||
virtual glm::quat getRotation() override;
|
||||
virtual glm::vec3 getLinearVelocity() override;
|
||||
virtual void setLinearVelocity(glm::vec3 linearVelocity) override;
|
||||
virtual glm::vec3 getAngularVelocity() override;
|
||||
virtual void setAngularVelocity(glm::vec3 angularVelocity) override;
|
||||
|
||||
bool _active;
|
||||
EntityItemWeakPointer _ownerEntity;
|
||||
};
|
||||
|
|
|
@ -38,13 +38,14 @@
|
|||
#include "AudioMixer.h"
|
||||
|
||||
static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance)
|
||||
static const int DISABLE_STATIC_JITTER_FRAMES = -1;
|
||||
static const float DEFAULT_NOISE_MUTING_THRESHOLD = 1.0f;
|
||||
static const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
|
||||
static const QString AUDIO_ENV_GROUP_KEY = "audio_env";
|
||||
static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer";
|
||||
static const QString AUDIO_THREADING_GROUP_KEY = "audio_threading";
|
||||
|
||||
int AudioMixer::_numStaticJitterFrames{ -1 };
|
||||
int AudioMixer::_numStaticJitterFrames{ DISABLE_STATIC_JITTER_FRAMES };
|
||||
float AudioMixer::_noiseMutingThreshold{ DEFAULT_NOISE_MUTING_THRESHOLD };
|
||||
float AudioMixer::_attenuationPerDoublingInDistance{ DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE };
|
||||
std::map<QString, std::shared_ptr<CodecPlugin>> AudioMixer::_availableCodecs{ };
|
||||
|
@ -56,7 +57,12 @@ QVector<AudioMixer::ReverbSettings> AudioMixer::_zoneReverbSettings;
|
|||
AudioMixer::AudioMixer(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message) {
|
||||
|
||||
// Always clear settings first
|
||||
// This prevents previous assignment settings from sticking around
|
||||
clearDomainSettings();
|
||||
|
||||
// hash the available codecs (on the mixer)
|
||||
_availableCodecs.clear(); // Make sure struct is clean
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
std::for_each(codecPlugins.cbegin(), codecPlugins.cend(),
|
||||
[&](const CodecPluginPointer& codec) {
|
||||
|
@ -232,7 +238,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
}
|
||||
|
||||
// general stats
|
||||
statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == -1;
|
||||
statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == DISABLE_STATIC_JITTER_FRAMES;
|
||||
|
||||
statsObject["threads"] = _slavePool.numThreads();
|
||||
|
||||
|
@ -490,6 +496,16 @@ int AudioMixer::prepareFrame(const SharedNodePointer& node, unsigned int frame)
|
|||
return data->checkBuffersBeforeFrameSend();
|
||||
}
|
||||
|
||||
void AudioMixer::clearDomainSettings() {
|
||||
_numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
|
||||
_attenuationPerDoublingInDistance = DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE;
|
||||
_noiseMutingThreshold = DEFAULT_NOISE_MUTING_THRESHOLD;
|
||||
_codecPreferenceOrder.clear();
|
||||
_audioZones.clear();
|
||||
_zoneSettings.clear();
|
||||
_zoneReverbSettings.clear();
|
||||
}
|
||||
|
||||
void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
|
||||
qDebug() << "AVX2 Support:" << (cpuSupportsAVX2() ? "enabled" : "disabled");
|
||||
|
||||
|
@ -525,7 +541,7 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
|
|||
qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
|
||||
} else {
|
||||
qDebug() << "Disabling dynamic jitter buffers.";
|
||||
_numStaticJitterFrames = -1;
|
||||
_numStaticJitterFrames = DISABLE_STATIC_JITTER_FRAMES;
|
||||
}
|
||||
|
||||
// check for deprecated audio settings
|
||||
|
|
|
@ -79,6 +79,7 @@ private:
|
|||
QString percentageForMixStats(int counter);
|
||||
|
||||
void parseSettingsObject(const QJsonObject& settingsObject);
|
||||
void clearDomainSettings();
|
||||
|
||||
float _trailingMixRatio { 0.0f };
|
||||
float _throttlingRatio { 0.0f };
|
||||
|
|
|
@ -402,7 +402,7 @@ void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> mes
|
|||
AvatarData::parseAvatarIdentityPacket(message->getMessage(), identity);
|
||||
bool identityChanged = false;
|
||||
bool displayNameChanged = false;
|
||||
avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged, senderNode->getClockSkewUsec());
|
||||
avatar.processAvatarIdentity(identity, identityChanged, displayNameChanged);
|
||||
if (identityChanged) {
|
||||
QMutexLocker nodeDataLocker(&nodeData->getMutex());
|
||||
nodeData->flagIdentityChange();
|
||||
|
@ -665,12 +665,12 @@ void AvatarMixer::sendStatsPacket() {
|
|||
|
||||
void AvatarMixer::run() {
|
||||
qCDebug(avatars) << "Waiting for connection to domain to request settings from domain-server.";
|
||||
|
||||
|
||||
// wait until we have the domain-server settings, otherwise we bail
|
||||
DomainHandler& domainHandler = DependencyManager::get<NodeList>()->getDomainHandler();
|
||||
connect(&domainHandler, &DomainHandler::settingsReceived, this, &AvatarMixer::domainSettingsRequestComplete);
|
||||
connect(&domainHandler, &DomainHandler::settingsReceiveFail, this, &AvatarMixer::domainSettingsRequestFailed);
|
||||
|
||||
|
||||
ThreadedAssignment::commonInit(AVATAR_MIXER_LOGGING_NAME, NodeType::AvatarMixer);
|
||||
|
||||
}
|
||||
|
@ -695,7 +695,7 @@ void AvatarMixer::domainSettingsRequestComplete() {
|
|||
|
||||
// parse the settings to pull out the values we need
|
||||
parseDomainServerSettings(nodeList->getDomainHandler().getSettingsObject());
|
||||
|
||||
|
||||
// start our tight loop...
|
||||
start();
|
||||
}
|
||||
|
@ -745,7 +745,7 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
} else {
|
||||
qCDebug(avatars) << "Avatar mixer will automatically determine number of threads to use. Using:" << _slavePool.numThreads() << "threads.";
|
||||
}
|
||||
|
||||
|
||||
const QString AVATARS_SETTINGS_KEY = "avatars";
|
||||
|
||||
static const QString MIN_SCALE_OPTION = "min_avatar_scale";
|
||||
|
|
|
@ -145,7 +145,7 @@ private:
|
|||
std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints;
|
||||
|
||||
uint64_t _identityChangeTimestamp;
|
||||
bool _avatarSessionDisplayNameMustChange{ false };
|
||||
bool _avatarSessionDisplayNameMustChange{ true };
|
||||
|
||||
int _numAvatarsSentLastFrame = 0;
|
||||
int _numFramesSinceAdjustment = 0;
|
||||
|
|
|
@ -24,9 +24,6 @@
|
|||
#include <ScriptEngine.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
static const int DEFAULT_MAX_ENTITY_PPS = 9000;
|
||||
static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900;
|
||||
|
||||
class EntityScriptServer : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
|
||||
|
|
|
@ -49,48 +49,71 @@
|
|||
"id": "ik",
|
||||
"type": "inverseKinematics",
|
||||
"data": {
|
||||
"solutionSource": "relaxToUnderPoses",
|
||||
"solutionSourceVar": "solutionSource",
|
||||
"targets": [
|
||||
{
|
||||
"jointName": "Hips",
|
||||
"positionVar": "hipsPosition",
|
||||
"rotationVar": "hipsRotation",
|
||||
"typeVar": "hipsType"
|
||||
"typeVar": "hipsType",
|
||||
"weightVar": "hipsWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1]
|
||||
},
|
||||
{
|
||||
"jointName": "RightHand",
|
||||
"positionVar": "rightHandPosition",
|
||||
"rotationVar": "rightHandRotation",
|
||||
"typeVar": "rightHandType"
|
||||
"typeVar": "rightHandType",
|
||||
"weightVar": "rightHandWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1, 0.5, 0.5, 0.25, 0.1, 0.05, 0.01, 0.0, 0.0]
|
||||
},
|
||||
{
|
||||
"jointName": "LeftHand",
|
||||
"positionVar": "leftHandPosition",
|
||||
"rotationVar": "leftHandRotation",
|
||||
"typeVar": "leftHandType"
|
||||
"typeVar": "leftHandType",
|
||||
"weightVar": "leftHandWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1, 0.5, 0.5, 0.25, 0.1, 0.05, 0.01, 0.0, 0.0]
|
||||
},
|
||||
{
|
||||
"jointName": "RightFoot",
|
||||
"positionVar": "rightFootPosition",
|
||||
"rotationVar": "rightFootRotation",
|
||||
"typeVar": "rightFootType"
|
||||
"typeVar": "rightFootType",
|
||||
"weightVar": "rightFootWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1, 0.45, 0.45]
|
||||
},
|
||||
{
|
||||
"jointName": "LeftFoot",
|
||||
"positionVar": "leftFootPosition",
|
||||
"rotationVar": "leftFootRotation",
|
||||
"typeVar": "leftFootType"
|
||||
"typeVar": "leftFootType",
|
||||
"weightVar": "leftFootWeight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [1, 0.45, 0.45]
|
||||
},
|
||||
{
|
||||
"jointName": "Spine2",
|
||||
"positionVar": "spine2Position",
|
||||
"rotationVar": "spine2Rotation",
|
||||
"typeVar": "spine2Type"
|
||||
"typeVar": "spine2Type",
|
||||
"weightVar": "spine2Weight",
|
||||
"weight": 1.0,
|
||||
"flexCoefficients": [0.45, 0.45]
|
||||
},
|
||||
{
|
||||
"jointName": "Head",
|
||||
"positionVar": "headPosition",
|
||||
"rotationVar": "headRotation",
|
||||
"typeVar": "headType"
|
||||
"typeVar": "headType",
|
||||
"weightVar": "headWeight",
|
||||
"weight": 4.0,
|
||||
"flexCoefficients": [1, 0.5, 0.5, 0.5, 0.5]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
Binary file not shown.
|
@ -132,62 +132,16 @@ Item {
|
|||
color: hifi.colors.textFieldLightBackground
|
||||
border.color: hifi.colors.blueHighlight
|
||||
border.width: 0
|
||||
TextInput {
|
||||
id: myDisplayNameText
|
||||
// Properties
|
||||
text: thisNameCard.displayName
|
||||
maximumLength: 256
|
||||
clip: true
|
||||
// Size
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
|
||||
// Style
|
||||
color: hifi.colors.darkGray
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
font.family: firaSansSemiBold.name
|
||||
font.pixelSize: displayNameTextPixelSize
|
||||
selectionColor: hifi.colors.blueAccent
|
||||
selectedTextColor: "black"
|
||||
// Text Positioning
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: TextInput.AlignLeft
|
||||
autoScroll: false;
|
||||
// Signals
|
||||
onEditingFinished: {
|
||||
if (MyAvatar.displayName !== text) {
|
||||
MyAvatar.displayName = text;
|
||||
UserActivityLogger.palAction("display_name_change", text);
|
||||
}
|
||||
cursorPosition = 0
|
||||
focus = false
|
||||
myDisplayName.border.width = 0
|
||||
color = hifi.colors.darkGray
|
||||
pal.currentlyEditingDisplayName = false
|
||||
autoScroll = false;
|
||||
}
|
||||
}
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
onClicked: {
|
||||
myDisplayName.border.width = 1
|
||||
myDisplayNameText.focus ? myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX, mouseY, TextInput.CursorOnCharacter) : myDisplayNameText.selectAll();
|
||||
myDisplayNameText.focus = true
|
||||
myDisplayNameText.color = "black"
|
||||
pal.currentlyEditingDisplayName = true
|
||||
myDisplayNameText.autoScroll = true;
|
||||
myDisplayNameText.focus = true;
|
||||
myDisplayNameText.cursorPosition = myDisplayNameText.positionAt(mouseX - myDisplayNameText.anchors.leftMargin, mouseY, TextInput.CursorOnCharacter);
|
||||
}
|
||||
onDoubleClicked: {
|
||||
myDisplayNameText.selectAll();
|
||||
myDisplayNameText.focus = true;
|
||||
pal.currentlyEditingDisplayName = true
|
||||
myDisplayNameText.autoScroll = true;
|
||||
}
|
||||
onEntered: myDisplayName.color = hifi.colors.lightGrayText;
|
||||
onExited: myDisplayName.color = hifi.colors.textFieldLightBackground;
|
||||
|
@ -207,6 +161,54 @@ Item {
|
|||
verticalAlignment: Text.AlignVCenter
|
||||
color: hifi.colors.baseGray
|
||||
}
|
||||
TextInput {
|
||||
id: myDisplayNameText
|
||||
// Properties
|
||||
text: thisNameCard.displayName
|
||||
maximumLength: 256
|
||||
clip: true
|
||||
// Size
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
// Anchors
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: editGlyph.width + editGlyph.anchors.rightMargin
|
||||
// Style
|
||||
FontLoader { id: firaSansSemiBold; source: "../../fonts/FiraSans-SemiBold.ttf"; }
|
||||
font.family: firaSansSemiBold.name
|
||||
font.pixelSize: displayNameTextPixelSize
|
||||
selectionColor: hifi.colors.blueAccent
|
||||
selectedTextColor: "black"
|
||||
// Text Positioning
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: TextInput.AlignLeft
|
||||
autoScroll: false;
|
||||
// Signals
|
||||
onEditingFinished: {
|
||||
if (MyAvatar.displayName !== text) {
|
||||
MyAvatar.displayName = text;
|
||||
UserActivityLogger.palAction("display_name_change", text);
|
||||
}
|
||||
focus = false;
|
||||
}
|
||||
onFocusChanged: {
|
||||
if (focus === true) {
|
||||
myDisplayName.border.width = 1
|
||||
color = "black"
|
||||
autoScroll = true;
|
||||
pal.currentlyEditingDisplayName = true
|
||||
} else {
|
||||
myDisplayName.border.width = 0
|
||||
color: hifi.colors.darkGray
|
||||
cursorPosition = 0;
|
||||
autoScroll = false;
|
||||
pal.currentlyEditingDisplayName = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// DisplayName container for others' cards
|
||||
Item {
|
||||
|
|
|
@ -564,11 +564,8 @@ const bool DEFAULT_DESKTOP_TABLET_BECOMES_TOOLBAR = true;
|
|||
const bool DEFAULT_HMD_TABLET_BECOMES_TOOLBAR = false;
|
||||
const bool DEFAULT_PREFER_AVATAR_FINGER_OVER_STYLUS = false;
|
||||
|
||||
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) :
|
||||
Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) :
|
||||
QApplication(argc, argv),
|
||||
_shouldRunServer(runServer),
|
||||
_runServerPath(runServerPathOption),
|
||||
_runningMarker(this, RUNNING_MARKER_FILENAME),
|
||||
_window(new MainWindow(desktop())),
|
||||
_sessionRunTimer(startupTimer),
|
||||
_previousSessionCrashed(setupEssentials(argc, argv)),
|
||||
|
@ -623,8 +620,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
// make sure the debug draw singleton is initialized on the main thread.
|
||||
DebugDraw::getInstance().removeMarker("");
|
||||
|
||||
_runningMarker.startRunningMarker();
|
||||
|
||||
PluginContainer* pluginContainer = dynamic_cast<PluginContainer*>(this); // set the container for any plugins that care
|
||||
PluginManager::getInstance()->setContainer(pluginContainer);
|
||||
|
||||
|
@ -676,38 +671,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
static const QString OCULUS_STORE_ARG = "--oculus-store";
|
||||
setProperty(hifi::properties::OCULUS_STORE, arguments().indexOf(OCULUS_STORE_ARG) != -1);
|
||||
|
||||
static const QString NO_UPDATER_ARG = "--no-updater";
|
||||
static const bool noUpdater = arguments().indexOf(NO_UPDATER_ARG) != -1;
|
||||
static const bool wantsSandboxRunning = shouldRunServer();
|
||||
static bool determinedSandboxState = false;
|
||||
static bool sandboxIsRunning = false;
|
||||
SandboxUtils sandboxUtils;
|
||||
// updateHeartbeat() because we are going to poll shortly...
|
||||
updateHeartbeat();
|
||||
sandboxUtils.ifLocalSandboxRunningElse([&]() {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running.....";
|
||||
determinedSandboxState = true;
|
||||
sandboxIsRunning = true;
|
||||
}, [&]() {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running....";
|
||||
if (wantsSandboxRunning) {
|
||||
QString contentPath = getRunServerPath();
|
||||
SandboxUtils::runLocalSandbox(contentPath, true, RUNNING_MARKER_FILENAME, noUpdater);
|
||||
sandboxIsRunning = true;
|
||||
}
|
||||
determinedSandboxState = true;
|
||||
});
|
||||
|
||||
// SandboxUtils::runLocalSandbox currently has 2 sec delay after spawning sandbox, so 4
|
||||
// sec here is ok I guess. TODO: ping sandbox so we know it is up, perhaps?
|
||||
quint64 MAX_WAIT_TIME = USECS_PER_SECOND * 4;
|
||||
auto startWaiting = usecTimestampNow();
|
||||
while (!determinedSandboxState && (usecTimestampNow() - startWaiting <= MAX_WAIT_TIME)) {
|
||||
QCoreApplication::processEvents();
|
||||
// updateHeartbeat() while polling so we don't scare the deadlock watchdog
|
||||
updateHeartbeat();
|
||||
usleep(USECS_PER_MSEC * 50); // 20hz
|
||||
}
|
||||
|
||||
// start the nodeThread so its event loop is running
|
||||
QThread* nodeThread = new QThread(this);
|
||||
|
@ -1224,6 +1188,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
#endif
|
||||
|
||||
// If launched from Steam, let it handle updates
|
||||
const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater";
|
||||
bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1;
|
||||
if (!noUpdater) {
|
||||
auto applicationUpdater = DependencyManager::get<AutoUpdater>();
|
||||
connect(applicationUpdater.data(), &AutoUpdater::newVersionIsAvailable, dialogsManager.data(), &DialogsManager::showUpdateDialog);
|
||||
|
@ -1466,110 +1432,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
const auto testScript = property(hifi::properties::TEST).toUrl();
|
||||
scriptEngines->loadScript(testScript, false);
|
||||
} else {
|
||||
enum HandControllerType {
|
||||
Vive,
|
||||
Oculus
|
||||
};
|
||||
static const std::map<HandControllerType, int> MIN_CONTENT_VERSION = {
|
||||
{ Vive, 1 },
|
||||
{ Oculus, 27 }
|
||||
};
|
||||
|
||||
// Get sandbox content set version
|
||||
auto acDirPath = PathUtils::getAppDataPath() + "../../" + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
|
||||
auto contentVersionPath = acDirPath + "content-version.txt";
|
||||
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
|
||||
int contentVersion = 0;
|
||||
QFile contentVersionFile(contentVersionPath);
|
||||
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString line = contentVersionFile.readAll();
|
||||
contentVersion = line.toInt(); // returns 0 if conversion fails
|
||||
}
|
||||
|
||||
// Get controller availability
|
||||
bool hasHandControllers = false;
|
||||
HandControllerType handControllerType = Vive;
|
||||
if (PluginUtils::isViveControllerAvailable()) {
|
||||
hasHandControllers = true;
|
||||
handControllerType = Vive;
|
||||
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
|
||||
hasHandControllers = true;
|
||||
handControllerType = Oculus;
|
||||
}
|
||||
|
||||
// Check tutorial content versioning
|
||||
bool hasTutorialContent = contentVersion >= MIN_CONTENT_VERSION.at(handControllerType);
|
||||
|
||||
// Check HMD use (may be technically available without being in use)
|
||||
bool hasHMD = PluginUtils::isHMDAvailable();
|
||||
bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd();
|
||||
|
||||
Setting::Handle<bool> tutorialComplete { "tutorialComplete", false };
|
||||
Setting::Handle<bool> firstRun { Settings::firstRun, true };
|
||||
|
||||
bool isTutorialComplete = tutorialComplete.get();
|
||||
bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete;
|
||||
|
||||
qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD;
|
||||
qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent <<
|
||||
", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial;
|
||||
|
||||
// when --url in command line, teleport to location
|
||||
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
|
||||
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
|
||||
QString addressLookupString;
|
||||
if (urlIndex != -1) {
|
||||
addressLookupString = arguments().value(urlIndex + 1);
|
||||
}
|
||||
|
||||
const QString TUTORIAL_PATH = "/tutorial_begin";
|
||||
|
||||
if (shouldGoToTutorial) {
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
if (firstRun.get()) {
|
||||
showHelp();
|
||||
}
|
||||
if (addressLookupString.isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
bool isFirstRun = firstRun.get();
|
||||
|
||||
if (isFirstRun) {
|
||||
showHelp();
|
||||
}
|
||||
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (isFirstRun) {
|
||||
if (isUsingHMD) {
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
}
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
}
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
}
|
||||
|
||||
_connectionMonitor.init();
|
||||
|
||||
// After all of the constructor is completed, then set firstRun to false.
|
||||
firstRun.set(false);
|
||||
PROFILE_RANGE(render, "GetSandboxStatus");
|
||||
auto reply = SandboxUtils::getStatus();
|
||||
connect(reply, &QNetworkReply::finished, this, [=] {
|
||||
handleSandboxStatus(reply);
|
||||
});
|
||||
}
|
||||
|
||||
// Monitor model assets (e.g., from Clara.io) added to the world that may need resizing.
|
||||
|
@ -2486,6 +2353,118 @@ void Application::resizeGL() {
|
|||
}
|
||||
}
|
||||
|
||||
void Application::handleSandboxStatus(QNetworkReply* reply) {
|
||||
PROFILE_RANGE(render, "HandleSandboxStatus");
|
||||
|
||||
bool sandboxIsRunning = SandboxUtils::readStatus(reply->readAll());
|
||||
qDebug() << "HandleSandboxStatus" << sandboxIsRunning;
|
||||
|
||||
enum HandControllerType {
|
||||
Vive,
|
||||
Oculus
|
||||
};
|
||||
static const std::map<HandControllerType, int> MIN_CONTENT_VERSION = {
|
||||
{ Vive, 1 },
|
||||
{ Oculus, 27 }
|
||||
};
|
||||
|
||||
// Get sandbox content set version
|
||||
auto acDirPath = PathUtils::getAppDataPath() + "../../" + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/";
|
||||
auto contentVersionPath = acDirPath + "content-version.txt";
|
||||
qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version";
|
||||
int contentVersion = 0;
|
||||
QFile contentVersionFile(contentVersionPath);
|
||||
if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QString line = contentVersionFile.readAll();
|
||||
contentVersion = line.toInt(); // returns 0 if conversion fails
|
||||
}
|
||||
|
||||
// Get controller availability
|
||||
bool hasHandControllers = false;
|
||||
HandControllerType handControllerType = Vive;
|
||||
if (PluginUtils::isViveControllerAvailable()) {
|
||||
hasHandControllers = true;
|
||||
handControllerType = Vive;
|
||||
} else if (PluginUtils::isOculusTouchControllerAvailable()) {
|
||||
hasHandControllers = true;
|
||||
handControllerType = Oculus;
|
||||
}
|
||||
|
||||
// Check tutorial content versioning
|
||||
bool hasTutorialContent = contentVersion >= MIN_CONTENT_VERSION.at(handControllerType);
|
||||
|
||||
// Check HMD use (may be technically available without being in use)
|
||||
bool hasHMD = PluginUtils::isHMDAvailable();
|
||||
bool isUsingHMD = hasHMD && hasHandControllers && _displayPlugin->isHmd();
|
||||
|
||||
Setting::Handle<bool> tutorialComplete{ "tutorialComplete", false };
|
||||
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
|
||||
|
||||
bool isTutorialComplete = tutorialComplete.get();
|
||||
bool shouldGoToTutorial = isUsingHMD && hasTutorialContent && !isTutorialComplete;
|
||||
|
||||
qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMD;
|
||||
qCDebug(interfaceapp) << "Tutorial version:" << contentVersion << ", sufficient:" << hasTutorialContent <<
|
||||
", complete:" << isTutorialComplete << ", should go:" << shouldGoToTutorial;
|
||||
|
||||
// when --url in command line, teleport to location
|
||||
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
|
||||
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
|
||||
QString addressLookupString;
|
||||
if (urlIndex != -1) {
|
||||
addressLookupString = arguments().value(urlIndex + 1);
|
||||
}
|
||||
|
||||
const QString TUTORIAL_PATH = "/tutorial_begin";
|
||||
|
||||
if (shouldGoToTutorial) {
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox(TUTORIAL_PATH);
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
if (firstRun.get()) {
|
||||
showHelp();
|
||||
}
|
||||
if (addressLookupString.isEmpty()) {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
bool isFirstRun = firstRun.get();
|
||||
|
||||
if (isFirstRun) {
|
||||
showHelp();
|
||||
}
|
||||
|
||||
// If this is a first run we short-circuit the address passed in
|
||||
if (isFirstRun) {
|
||||
if (isUsingHMD) {
|
||||
if (sandboxIsRunning) {
|
||||
qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home.";
|
||||
DependencyManager::get<AddressManager>()->goToLocalSandbox();
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry.";
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
}
|
||||
} else {
|
||||
DependencyManager::get<AddressManager>()->goToEntry();
|
||||
}
|
||||
} else {
|
||||
qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString);
|
||||
DependencyManager::get<AddressManager>()->loadSettings(addressLookupString);
|
||||
}
|
||||
}
|
||||
|
||||
_connectionMonitor.init();
|
||||
|
||||
// After all of the constructor is completed, then set firstRun to false.
|
||||
firstRun.set(false);
|
||||
}
|
||||
|
||||
bool Application::importJSONFromURL(const QString& urlString) {
|
||||
// we only load files that terminate in just .json (not .svo.json and not .ava.json)
|
||||
// if they come from the High Fidelity Marketplace Assets CDN
|
||||
|
@ -5291,9 +5270,8 @@ void Application::nodeActivated(SharedNodePointer node) {
|
|||
}
|
||||
|
||||
if (node->getType() == NodeType::AvatarMixer) {
|
||||
// new avatar mixer, send off our identity packet right away
|
||||
// new avatar mixer, send off our identity packet on next update loop
|
||||
getMyAvatar()->markIdentityDataChanged();
|
||||
getMyAvatar()->sendIdentityPacket();
|
||||
getMyAvatar()->resetLastSent();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -112,17 +112,7 @@ class Application : public QApplication,
|
|||
// TODO? Get rid of those
|
||||
friend class OctreePacketProcessor;
|
||||
|
||||
private:
|
||||
bool _shouldRunServer { false };
|
||||
QString _runServerPath;
|
||||
RunningMarker _runningMarker;
|
||||
|
||||
public:
|
||||
// startup related getter/setters
|
||||
bool shouldRunServer() const { return _shouldRunServer; }
|
||||
bool hasRunServerPath() const { return !_runServerPath.isEmpty(); }
|
||||
QString getRunServerPath() const { return _runServerPath; }
|
||||
|
||||
// virtual functions required for PluginContainer
|
||||
virtual ui::Menu* getPrimaryMenu() override;
|
||||
virtual void requestReset() override { resetSensors(true); }
|
||||
|
@ -146,7 +136,7 @@ public:
|
|||
static void initPlugins(const QStringList& arguments);
|
||||
static void shutdownPlugins();
|
||||
|
||||
Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runServer, QString runServerPathOption);
|
||||
Application(int& argc, char** argv, QElapsedTimer& startup_time);
|
||||
~Application();
|
||||
|
||||
void postLambdaEvent(std::function<void()> f) override;
|
||||
|
@ -452,6 +442,8 @@ private slots:
|
|||
void addAssetToWorldInfoTimeout();
|
||||
void addAssetToWorldErrorTimeout();
|
||||
|
||||
void handleSandboxStatus(QNetworkReply* reply);
|
||||
|
||||
private:
|
||||
static void initDisplay();
|
||||
void init();
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
#include <avatar/AvatarActionHold.h>
|
||||
#include <avatar/AvatarActionFarGrab.h>
|
||||
#include <ObjectActionOffset.h>
|
||||
#include <ObjectActionSpring.h>
|
||||
#include <ObjectActionTractor.h>
|
||||
#include <ObjectActionTravelOriented.h>
|
||||
#include <ObjectConstraintHinge.h>
|
||||
|
@ -33,7 +32,7 @@ EntityDynamicPointer interfaceDynamicFactory(EntityDynamicType type, const QUuid
|
|||
case DYNAMIC_TYPE_OFFSET:
|
||||
return std::make_shared<ObjectActionOffset>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_SPRING:
|
||||
return std::make_shared<ObjectActionSpring>(id, ownerEntity);
|
||||
qDebug() << "The 'spring' Action is deprecated. Replacing with 'tractor' Action.";
|
||||
case DYNAMIC_TYPE_TRACTOR:
|
||||
return std::make_shared<ObjectActionTractor>(id, ownerEntity);
|
||||
case DYNAMIC_TYPE_HOLD:
|
||||
|
|
|
@ -523,6 +523,8 @@ Menu::Menu() {
|
|||
avatar.get(), SLOT(setEnableDebugDrawSensorToWorldMatrix(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKTargets, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawIKTargets(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKConstraints, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawIKConstraints(bool)));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu()),
|
||||
|
|
|
@ -161,6 +161,7 @@ namespace MenuOption {
|
|||
const QString RenderResolutionQuarter = "1/4";
|
||||
const QString RenderSensorToWorldMatrix = "Show SensorToWorld Matrix";
|
||||
const QString RenderIKTargets = "Show IK Targets";
|
||||
const QString RenderIKConstraints = "Show IK Constraints";
|
||||
const QString ResetAvatarSize = "Reset Avatar Size";
|
||||
const QString ResetSensors = "Reset Sensors";
|
||||
const QString RunningScripts = "Running Scripts...";
|
||||
|
|
|
@ -189,6 +189,7 @@ void AvatarManager::updateOtherAvatars(float deltaTime) {
|
|||
btCollisionShape* shape = const_cast<btCollisionShape*>(ObjectMotionState::getShapeManager()->getShape(shapeInfo));
|
||||
if (shape) {
|
||||
AvatarMotionState* motionState = new AvatarMotionState(avatar, shape);
|
||||
motionState->setMass(avatar->computeMass());
|
||||
avatar->setPhysicsCallback([=] (uint32_t flags) { motionState->addDirtyFlags(flags); });
|
||||
_motionStates.insert(avatar.get(), motionState);
|
||||
_motionStatesToAddToPhysics.insert(motionState);
|
||||
|
|
|
@ -21,9 +21,9 @@
|
|||
#include <PIDController.h>
|
||||
#include <SimpleMovingAverage.h>
|
||||
#include <shared/RateCounter.h>
|
||||
#include <avatars-renderer/AvatarMotionState.h>
|
||||
#include <avatars-renderer/ScriptAvatar.h>
|
||||
|
||||
#include "AvatarMotionState.h"
|
||||
#include "MyAvatar.h"
|
||||
|
||||
class AudioInjector;
|
||||
|
|
|
@ -19,9 +19,6 @@
|
|||
AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) {
|
||||
assert(_avatar);
|
||||
_type = MOTIONSTATE_TYPE_AVATAR;
|
||||
if (_shape) {
|
||||
_mass = 100.0f; // HACK
|
||||
}
|
||||
}
|
||||
|
||||
AvatarMotionState::~AvatarMotionState() {
|
|
@ -14,10 +14,10 @@
|
|||
|
||||
#include <QSet>
|
||||
|
||||
#include <avatars-renderer/Avatar.h>
|
||||
#include <ObjectMotionState.h>
|
||||
#include <BulletUtil.h>
|
||||
|
||||
#include "Avatar.h"
|
||||
|
||||
class AvatarMotionState : public ObjectMotionState {
|
||||
public:
|
|
@ -38,6 +38,7 @@
|
|||
#include <UserActivityLogger.h>
|
||||
#include <AnimDebugDraw.h>
|
||||
#include <AnimClip.h>
|
||||
#include <AnimInverseKinematics.h>
|
||||
#include <recording/Deck.h>
|
||||
#include <recording/Recorder.h>
|
||||
#include <recording/Clip.h>
|
||||
|
@ -109,6 +110,9 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
|
|||
_realWorldFieldOfView("realWorldFieldOfView",
|
||||
DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES),
|
||||
_useAdvancedMovementControls("advancedMovementForHandControllersIsChecked", false),
|
||||
_smoothOrientationTimer(std::numeric_limits<float>::max()),
|
||||
_smoothOrientationInitial(),
|
||||
_smoothOrientationTarget(),
|
||||
_hmdSensorMatrix(),
|
||||
_hmdSensorOrientation(),
|
||||
_hmdSensorPosition(),
|
||||
|
@ -235,6 +239,7 @@ MyAvatar::MyAvatar(QThread* thread, RigPointer rig) :
|
|||
});
|
||||
|
||||
connect(rig.get(), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete()));
|
||||
_characterController.setDensity(_density);
|
||||
}
|
||||
|
||||
MyAvatar::~MyAvatar() {
|
||||
|
@ -264,6 +269,17 @@ QVariant MyAvatar::getOrientationVar() const {
|
|||
return quatToVariant(Avatar::getOrientation());
|
||||
}
|
||||
|
||||
glm::quat MyAvatar::getOrientationOutbound() const {
|
||||
// Allows MyAvatar to send out smoothed data to remote agents if required.
|
||||
if (_smoothOrientationTimer > SMOOTH_TIME_ORIENTATION) {
|
||||
return (getLocalOrientation());
|
||||
}
|
||||
|
||||
// Smooth the remote avatar movement.
|
||||
float t = _smoothOrientationTimer / SMOOTH_TIME_ORIENTATION;
|
||||
float interp = Interpolate::easeInOutQuad(glm::clamp(t, 0.0f, 1.0f));
|
||||
return (slerp(_smoothOrientationInitial, _smoothOrientationTarget, interp));
|
||||
}
|
||||
|
||||
// virtual
|
||||
void MyAvatar::simulateAttachments(float deltaTime) {
|
||||
|
@ -290,6 +306,11 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail) {
|
|||
}
|
||||
|
||||
void MyAvatar::resetSensorsAndBody() {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "resetSensorsAndBody");
|
||||
return;
|
||||
}
|
||||
|
||||
qApp->getActiveDisplayPlugin()->resetSensors();
|
||||
reset(true, false, true);
|
||||
}
|
||||
|
@ -387,6 +408,11 @@ void MyAvatar::update(float deltaTime) {
|
|||
float tau = deltaTime / HMD_FACING_TIMESCALE;
|
||||
_hmdSensorFacingMovingAverage = lerp(_hmdSensorFacingMovingAverage, _hmdSensorFacing, tau);
|
||||
|
||||
if (_smoothOrientationTimer < SMOOTH_TIME_ORIENTATION) {
|
||||
_rotationChanged = usecTimestampNow();
|
||||
_smoothOrientationTimer += deltaTime;
|
||||
}
|
||||
|
||||
#ifdef DEBUG_DRAW_HMD_MOVING_AVERAGE
|
||||
glm::vec3 p = transformPoint(getSensorToWorldMatrix(), _hmdSensorPosition + glm::vec3(_hmdSensorFacingMovingAverage.x, 0.0f, _hmdSensorFacingMovingAverage.y));
|
||||
DebugDraw::getInstance().addMarker("facing-avg", getOrientation(), p, glm::vec4(1.0f));
|
||||
|
@ -504,6 +530,7 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
|
||||
if (_rig) {
|
||||
_rig->setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets);
|
||||
_rig->setEnableDebugDrawIKConstraints(_enableDebugDrawIKConstraints);
|
||||
}
|
||||
|
||||
_skeletonModel->simulate(deltaTime);
|
||||
|
@ -927,6 +954,10 @@ void MyAvatar::setEnableDebugDrawIKTargets(bool isEnabled) {
|
|||
_enableDebugDrawIKTargets = isEnabled;
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawIKConstraints(bool isEnabled) {
|
||||
_enableDebugDrawIKConstraints = isEnabled;
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
|
||||
_skeletonModel->setVisibleInScene(isEnabled, qApp->getMain3DScene());
|
||||
}
|
||||
|
@ -1806,8 +1837,10 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
|
||||
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
|
||||
// snap turn every half second.
|
||||
bool snapTurn = false;
|
||||
if (getDriveKey(STEP_YAW) != 0.0f) {
|
||||
totalBodyYaw += getDriveKey(STEP_YAW);
|
||||
snapTurn = true;
|
||||
}
|
||||
|
||||
// use head/HMD orientation to turn while flying
|
||||
|
@ -1840,10 +1873,17 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
totalBodyYaw += (speedFactor * deltaAngle * (180.0f / PI));
|
||||
}
|
||||
|
||||
|
||||
// update body orientation by movement inputs
|
||||
glm::quat initialOrientation = getOrientationOutbound();
|
||||
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
|
||||
|
||||
if (snapTurn) {
|
||||
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
|
||||
_smoothOrientationInitial = initialOrientation;
|
||||
_smoothOrientationTarget = getOrientation();
|
||||
_smoothOrientationTimer = 0.0f;
|
||||
}
|
||||
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime);
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
|
|
|
@ -125,7 +125,7 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(controller::Pose rightHandTipPose READ getRightHandTipPose)
|
||||
|
||||
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
|
||||
Q_PROPERTY(float isAway READ getIsAway WRITE setAway)
|
||||
Q_PROPERTY(bool isAway READ getIsAway WRITE setAway)
|
||||
|
||||
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||
Q_PROPERTY(bool collisionsEnabled READ getCollisionsEnabled WRITE setCollisionsEnabled)
|
||||
|
@ -190,6 +190,8 @@ public:
|
|||
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
|
||||
Q_INVOKABLE QVariant getOrientationVar() const;
|
||||
|
||||
// A method intended to be overriden by MyAvatar for polling orientation for network transmission.
|
||||
glm::quat getOrientationOutbound() const override;
|
||||
|
||||
// Pass a recent sample of the HMD to the avatar.
|
||||
// This can also update the avatar's position to follow the HMD
|
||||
|
@ -521,6 +523,7 @@ public slots:
|
|||
void setEnableDebugDrawHandControllers(bool isEnabled);
|
||||
void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled);
|
||||
void setEnableDebugDrawIKTargets(bool isEnabled);
|
||||
void setEnableDebugDrawIKConstraints(bool isEnabled);
|
||||
bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); }
|
||||
void setEnableMeshVisible(bool isEnabled);
|
||||
void setUseAnimPreAndPostRotations(bool isEnabled);
|
||||
|
@ -632,6 +635,14 @@ private:
|
|||
Setting::Handle<float> _realWorldFieldOfView;
|
||||
Setting::Handle<bool> _useAdvancedMovementControls;
|
||||
|
||||
// Smoothing.
|
||||
const float SMOOTH_TIME_ORIENTATION = 0.5f;
|
||||
|
||||
// Smoothing data for blending from one position/orientation to another on remote agents.
|
||||
float _smoothOrientationTimer;
|
||||
glm::quat _smoothOrientationInitial;
|
||||
glm::quat _smoothOrientationTarget;
|
||||
|
||||
// private methods
|
||||
void updateOrientation(float deltaTime);
|
||||
void updateActionMotor(float deltaTime);
|
||||
|
@ -706,6 +717,7 @@ private:
|
|||
bool _enableDebugDrawHandControllers { false };
|
||||
bool _enableDebugDrawSensorToWorldMatrix { false };
|
||||
bool _enableDebugDrawIKTargets { false };
|
||||
bool _enableDebugDrawIKConstraints { false };
|
||||
|
||||
AudioListenerMode _audioListenerMode;
|
||||
glm::vec3 _customListenPosition;
|
||||
|
|
|
@ -49,12 +49,9 @@ void MyCharacterController::updateShapeIfNecessary() {
|
|||
// create RigidBody if it doesn't exist
|
||||
if (!_rigidBody) {
|
||||
btCollisionShape* shape = computeShape();
|
||||
|
||||
// HACK: use some simple mass property defaults for now
|
||||
const btScalar DEFAULT_AVATAR_MASS = 100.0f;
|
||||
const btVector3 DEFAULT_AVATAR_INERTIA_TENSOR(30.0f, 8.0f, 30.0f);
|
||||
|
||||
_rigidBody = new btRigidBody(DEFAULT_AVATAR_MASS, nullptr, shape, DEFAULT_AVATAR_INERTIA_TENSOR);
|
||||
btScalar mass = 1.0f;
|
||||
btVector3 inertia(1.0f, 1.0f, 1.0f);
|
||||
_rigidBody = new btRigidBody(mass, nullptr, shape, inertia);
|
||||
} else {
|
||||
btCollisionShape* shape = _rigidBody->getCollisionShape();
|
||||
if (shape) {
|
||||
|
@ -63,6 +60,7 @@ void MyCharacterController::updateShapeIfNecessary() {
|
|||
shape = computeShape();
|
||||
_rigidBody->setCollisionShape(shape);
|
||||
}
|
||||
updateMassProperties();
|
||||
|
||||
_rigidBody->setSleepingThresholds(0.0f, 0.0f);
|
||||
_rigidBody->setAngularFactor(0.0f);
|
||||
|
@ -331,3 +329,23 @@ void MyCharacterController::initRayShotgun(const btCollisionWorld* world) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyCharacterController::updateMassProperties() {
|
||||
assert(_rigidBody);
|
||||
// the inertia tensor of a capsule with Y-axis of symmetry, radius R and cylinder height H is:
|
||||
// Ix = density * (volumeCylinder * (H^2 / 12 + R^2 / 4) + volumeSphere * (2R^2 / 5 + H^2 / 2 + 3HR / 8))
|
||||
// Iy = density * (volumeCylinder * (R^2 / 2) + volumeSphere * (2R^2 / 5)
|
||||
btScalar r2 = _radius * _radius;
|
||||
btScalar h2 = 4.0f * _halfHeight * _halfHeight;
|
||||
btScalar volumeSphere = 4.0f * PI * r2 * _radius / 3.0f;
|
||||
btScalar volumeCylinder = TWO_PI * r2 * 2.0f * _halfHeight;
|
||||
btScalar cylinderXZ = volumeCylinder * (h2 / 12.0f + r2 / 4.0f);
|
||||
btScalar capsXZ = volumeSphere * (2.0f * r2 / 5.0f + h2 / 2.0f + 6.0f * _halfHeight * _radius / 8.0f);
|
||||
btScalar inertiaXZ = _density * (cylinderXZ + capsXZ);
|
||||
btScalar inertiaY = _density * ((volumeCylinder * r2 / 2.0f) + volumeSphere * (2.0f * r2 / 5.0f));
|
||||
btVector3 inertia(inertiaXZ, inertiaY, inertiaXZ);
|
||||
|
||||
btScalar mass = _density * (volumeCylinder + volumeSphere);
|
||||
|
||||
_rigidBody->setMassProps(mass, inertia);
|
||||
}
|
||||
|
|
|
@ -40,8 +40,11 @@ public:
|
|||
/// return true if RayShotgun hits anything
|
||||
bool testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result);
|
||||
|
||||
void setDensity(btScalar density) { _density = density; }
|
||||
|
||||
protected:
|
||||
void initRayShotgun(const btCollisionWorld* world);
|
||||
void updateMassProperties() override;
|
||||
|
||||
private:
|
||||
btConvexHullShape* computeShape() const;
|
||||
|
@ -52,6 +55,7 @@ protected:
|
|||
// shotgun scan data
|
||||
btAlignedObjectArray<btVector3> _topPoints;
|
||||
btAlignedObjectArray<btVector3> _bottomPoints;
|
||||
btScalar _density { 1.0f };
|
||||
};
|
||||
|
||||
#endif // hifi_MyCharacterController_h
|
||||
|
|
|
@ -44,14 +44,17 @@ glm::quat MyHead::getCameraOrientation() const {
|
|||
void MyHead::simulate(float deltaTime) {
|
||||
auto player = DependencyManager::get<recording::Deck>();
|
||||
// Only use face trackers when not playing back a recording.
|
||||
if (!player->isPlaying()) {
|
||||
if (player->isPlaying()) {
|
||||
Parent::simulate(deltaTime);
|
||||
} else {
|
||||
computeAudioLoudness(deltaTime);
|
||||
|
||||
FaceTracker* faceTracker = qApp->getActiveFaceTracker();
|
||||
_isFaceTrackerConnected = faceTracker != NULL && !faceTracker->isMuted();
|
||||
_isFaceTrackerConnected = faceTracker && !faceTracker->isMuted();
|
||||
if (_isFaceTrackerConnected) {
|
||||
_transientBlendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
||||
|
||||
if (typeid(*faceTracker) == typeid(DdeFaceTracker)) {
|
||||
|
||||
if (Menu::getInstance()->isOptionChecked(MenuOption::UseAudioForMouth)) {
|
||||
calculateMouthShapes(deltaTime);
|
||||
|
||||
|
@ -68,9 +71,19 @@ void MyHead::simulate(float deltaTime) {
|
|||
}
|
||||
applyEyelidOffset(getFinalOrientationInWorldFrame());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
computeFaceMovement(deltaTime);
|
||||
}
|
||||
|
||||
auto eyeTracker = DependencyManager::get<EyeTracker>();
|
||||
_isEyeTrackerConnected = eyeTracker->isTracking();
|
||||
_isEyeTrackerConnected = eyeTracker && eyeTracker->isTracking();
|
||||
if (_isEyeTrackerConnected) {
|
||||
// TODO? figure out where EyeTracker data harvested. Move it here?
|
||||
_saccade = glm::vec3();
|
||||
} else {
|
||||
computeEyeMovement(deltaTime);
|
||||
}
|
||||
|
||||
}
|
||||
Parent::simulate(deltaTime);
|
||||
}
|
||||
computeEyePosition();
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include <thread>
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QtCore/QProcess>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QLocalSocket>
|
||||
|
@ -20,6 +21,7 @@
|
|||
|
||||
#include <BuildInfo.h>
|
||||
#include <gl/OpenGLVersionChecker.h>
|
||||
#include <SandboxUtils.h>
|
||||
#include <SharedUtil.h>
|
||||
|
||||
|
||||
|
@ -28,7 +30,6 @@
|
|||
#include "InterfaceLogging.h"
|
||||
#include "UserActivityLogger.h"
|
||||
#include "MainWindow.h"
|
||||
#include <QtCore/QProcess>
|
||||
|
||||
#ifdef HAS_BUGSPLAT
|
||||
#include <BugSplat.h>
|
||||
|
@ -50,50 +51,49 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
disableQtBearerPoll(); // Fixes wifi ping spikes
|
||||
|
||||
QElapsedTimer startupTime;
|
||||
startupTime.start();
|
||||
|
||||
// Set application infos
|
||||
QCoreApplication::setApplicationName(BuildInfo::INTERFACE_NAME);
|
||||
QCoreApplication::setOrganizationName(BuildInfo::MODIFIED_ORGANIZATION);
|
||||
QCoreApplication::setOrganizationDomain(BuildInfo::ORGANIZATION_DOMAIN);
|
||||
QCoreApplication::setApplicationVersion(BuildInfo::VERSION);
|
||||
|
||||
const QString& applicationName = getInterfaceSharedMemoryName();
|
||||
|
||||
bool instanceMightBeRunning = true;
|
||||
|
||||
QStringList arguments;
|
||||
for (int i = 0; i < argc; ++i) {
|
||||
arguments << argv[i];
|
||||
}
|
||||
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
// Try to create a shared memory block - if it can't be created, there is an instance of
|
||||
// interface already running. We only do this on Windows for now because of the potential
|
||||
// for crashed instances to leave behind shared memory instances on unix.
|
||||
QSharedMemory sharedMemory { applicationName };
|
||||
instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly);
|
||||
#endif
|
||||
|
||||
// allow multiple interfaces to run if this environment variable is set.
|
||||
if (QProcessEnvironment::systemEnvironment().contains("HIFI_ALLOW_MULTIPLE_INSTANCES")) {
|
||||
instanceMightBeRunning = false;
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
QCommandLineOption urlOption("url", "", "value");
|
||||
QCommandLineOption noUpdaterOption("no-updater", "Do not show auto-updater");
|
||||
QCommandLineOption checkMinSpecOption("checkMinSpec", "Check if machine meets minimum specifications");
|
||||
QCommandLineOption runServerOption("runServer", "Whether to run the server");
|
||||
QCommandLineOption serverContentPathOption("serverContentPath", "Where to find server content", "serverContentPath");
|
||||
QCommandLineOption allowMultipleInstancesOption("allowMultipleInstances", "Allow multiple instances to run");
|
||||
parser.addOption(urlOption);
|
||||
parser.addOption(noUpdaterOption);
|
||||
parser.addOption(checkMinSpecOption);
|
||||
parser.addOption(runServerOption);
|
||||
parser.addOption(serverContentPathOption);
|
||||
parser.addOption(allowMultipleInstancesOption);
|
||||
parser.parse(arguments);
|
||||
bool runServer = parser.isSet(runServerOption);
|
||||
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
||||
QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
||||
bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption);
|
||||
|
||||
|
||||
const QString& applicationName = getInterfaceSharedMemoryName();
|
||||
bool instanceMightBeRunning = true;
|
||||
#ifdef Q_OS_WIN
|
||||
// Try to create a shared memory block - if it can't be created, there is an instance of
|
||||
// interface already running. We only do this on Windows for now because of the potential
|
||||
// for crashed instances to leave behind shared memory instances on unix.
|
||||
QSharedMemory sharedMemory{ applicationName };
|
||||
instanceMightBeRunning = !sharedMemory.create(1, QSharedMemory::ReadOnly);
|
||||
#endif
|
||||
|
||||
// allow multiple interfaces to run if this environment variable is set.
|
||||
bool allowMultipleInstances = parser.isSet(allowMultipleInstancesOption) ||
|
||||
QProcessEnvironment::systemEnvironment().contains("HIFI_ALLOW_MULTIPLE_INSTANCES");
|
||||
if (allowMultipleInstances) {
|
||||
instanceMightBeRunning = false;
|
||||
}
|
||||
|
@ -108,11 +108,6 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
// Try to connect - if we can't connect, interface has probably just gone down
|
||||
if (socket.waitForConnected(LOCAL_SERVER_TIMEOUT_MS)) {
|
||||
QCommandLineParser parser;
|
||||
QCommandLineOption urlOption("url", "", "value");
|
||||
parser.addOption(urlOption);
|
||||
parser.process(arguments);
|
||||
|
||||
if (parser.isSet(urlOption)) {
|
||||
QUrl url = QUrl(parser.value(urlOption));
|
||||
if (url.isValid() && url.scheme() == HIFI_URL_SCHEME) {
|
||||
|
@ -156,9 +151,6 @@ int main(int argc, const char* argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
QElapsedTimer startupTime;
|
||||
startupTime.start();
|
||||
|
||||
// Debug option to demonstrate that the client's local time does not
|
||||
// need to be in sync with any other network node. This forces clock
|
||||
// skew for the individual client
|
||||
|
@ -199,7 +191,21 @@ int main(int argc, const char* argv[]) {
|
|||
|
||||
int exitCode;
|
||||
{
|
||||
Application app(argc, const_cast<char**>(argv), startupTime, runServer, serverContentPathOptionValue);
|
||||
RunningMarker runningMarker(nullptr, RUNNING_MARKER_FILENAME);
|
||||
runningMarker.writeRunningMarkerFile();
|
||||
|
||||
bool noUpdater = parser.isSet(noUpdaterOption);
|
||||
bool runServer = parser.isSet(runServerOption);
|
||||
bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption);
|
||||
QString serverContentPath = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString();
|
||||
if (runServer) {
|
||||
SandboxUtils::runLocalSandbox(serverContentPath, true, RUNNING_MARKER_FILENAME, noUpdater);
|
||||
}
|
||||
|
||||
Application app(argc, const_cast<char**>(argv), startupTime);
|
||||
|
||||
// Now that the main event loop is setup, launch running marker thread
|
||||
runningMarker.startRunningMarker();
|
||||
|
||||
// If we failed the OpenGLVersion check, log it.
|
||||
if (override) {
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
|
||||
#include "AnimContext.h"
|
||||
|
||||
AnimContext::AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix) :
|
||||
AnimContext::AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints,
|
||||
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix) :
|
||||
_enableDebugDrawIKTargets(enableDebugDrawIKTargets),
|
||||
_geometryToRigMatrix(geometryToRigMatrix) {
|
||||
_enableDebugDrawIKConstraints(enableDebugDrawIKConstraints),
|
||||
_geometryToRigMatrix(geometryToRigMatrix),
|
||||
_rigToWorldMatrix(rigToWorldMatrix)
|
||||
{
|
||||
}
|
||||
|
|
|
@ -16,15 +16,20 @@
|
|||
|
||||
class AnimContext {
|
||||
public:
|
||||
AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix);
|
||||
AnimContext(bool enableDebugDrawIKTargets, bool enableDebugDrawIKConstraints,
|
||||
const glm::mat4& geometryToRigMatrix, const glm::mat4& rigToWorldMatrix);
|
||||
|
||||
bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; }
|
||||
bool getEnableDebugDrawIKConstraints() const { return _enableDebugDrawIKConstraints; }
|
||||
const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; }
|
||||
const glm::mat4& getRigToWorldMatrix() const { return _rigToWorldMatrix; }
|
||||
|
||||
protected:
|
||||
|
||||
bool _enableDebugDrawIKTargets { false };
|
||||
bool _enableDebugDrawIKConstraints{ false };
|
||||
glm::mat4 _geometryToRigMatrix;
|
||||
glm::mat4 _rigToWorldMatrix;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimContext_h
|
||||
|
|
|
@ -21,6 +21,39 @@
|
|||
#include "SwingTwistConstraint.h"
|
||||
#include "AnimationLogging.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) :
|
||||
jointName(jointNameIn),
|
||||
positionVar(positionVarIn),
|
||||
rotationVar(rotationVarIn),
|
||||
typeVar(typeVarIn),
|
||||
weightVar(weightVarIn),
|
||||
weight(weightIn),
|
||||
numFlexCoefficients(flexCoefficientsIn.size()),
|
||||
jointIndex(-1)
|
||||
{
|
||||
numFlexCoefficients = std::min(numFlexCoefficients, (size_t)MAX_FLEX_COEFFICIENTS);
|
||||
for (size_t i = 0; i < numFlexCoefficients; i++) {
|
||||
flexCoefficients[i] = flexCoefficientsIn[i];
|
||||
}
|
||||
}
|
||||
|
||||
AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) :
|
||||
jointName(orig.jointName),
|
||||
positionVar(orig.positionVar),
|
||||
rotationVar(orig.rotationVar),
|
||||
typeVar(orig.typeVar),
|
||||
weightVar(orig.weightVar),
|
||||
weight(orig.weight),
|
||||
numFlexCoefficients(orig.numFlexCoefficients),
|
||||
jointIndex(orig.jointIndex)
|
||||
{
|
||||
numFlexCoefficients = std::min(numFlexCoefficients, (size_t)MAX_FLEX_COEFFICIENTS);
|
||||
for (size_t i = 0; i < numFlexCoefficients; i++) {
|
||||
flexCoefficients[i] = orig.flexCoefficients[i];
|
||||
}
|
||||
}
|
||||
|
||||
AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimNode::Type::InverseKinematics, id) {
|
||||
}
|
||||
|
||||
|
@ -60,26 +93,22 @@ void AnimInverseKinematics::computeAbsolutePoses(AnimPoseVec& absolutePoses) con
|
|||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::setTargetVars(
|
||||
const QString& jointName,
|
||||
const QString& positionVar,
|
||||
const QString& rotationVar,
|
||||
const QString& typeVar) {
|
||||
void AnimInverseKinematics::setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar,
|
||||
const QString& typeVar, const QString& weightVar, float weight, const std::vector<float>& flexCoefficients) {
|
||||
IKTargetVar targetVar(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients);
|
||||
|
||||
// if there are dups, last one wins.
|
||||
bool found = false;
|
||||
for (auto& targetVar: _targetVarVec) {
|
||||
if (targetVar.jointName == jointName) {
|
||||
// update existing targetVar
|
||||
targetVar.positionVar = positionVar;
|
||||
targetVar.rotationVar = rotationVar;
|
||||
targetVar.typeVar = typeVar;
|
||||
for (auto& targetVarIter: _targetVarVec) {
|
||||
if (targetVarIter.jointName == jointName) {
|
||||
targetVarIter = targetVar;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
// create a new entry
|
||||
_targetVarVec.push_back(IKTargetVar(jointName, positionVar, rotationVar, typeVar));
|
||||
_targetVarVec.push_back(targetVar);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -107,10 +136,15 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
|
|||
AnimPose defaultPose = _skeleton->getAbsolutePose(targetVar.jointIndex, underPoses);
|
||||
glm::quat rotation = animVars.lookupRigToGeometry(targetVar.rotationVar, defaultPose.rot());
|
||||
glm::vec3 translation = animVars.lookupRigToGeometry(targetVar.positionVar, defaultPose.trans());
|
||||
float weight = animVars.lookup(targetVar.weightVar, targetVar.weight);
|
||||
|
||||
target.setPose(rotation, translation);
|
||||
target.setIndex(targetVar.jointIndex);
|
||||
target.setWeight(weight);
|
||||
target.setFlexCoefficients(targetVar.numFlexCoefficients, targetVar.flexCoefficients);
|
||||
|
||||
targets.push_back(target);
|
||||
|
||||
if (targetVar.jointIndex > _maxTargetIndex) {
|
||||
_maxTargetIndex = targetVar.jointIndex;
|
||||
}
|
||||
|
@ -271,6 +305,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
|||
// cache tip absolute position
|
||||
glm::vec3 tipPosition = absolutePoses[tipIndex].trans();
|
||||
|
||||
size_t chainDepth = 1;
|
||||
|
||||
// descend toward root, pivoting each joint to get tip closer to target position
|
||||
while (pivotIndex != _hipsIndex && pivotsParentIndex != -1) {
|
||||
// compute the two lines that should be aligned
|
||||
|
@ -312,9 +348,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
|||
float angle = acosf(cosAngle);
|
||||
const float MIN_ADJUSTMENT_ANGLE = 1.0e-4f;
|
||||
if (angle > MIN_ADJUSTMENT_ANGLE) {
|
||||
// reduce angle by a fraction (for stability)
|
||||
const float STABILITY_FRACTION = 0.5f;
|
||||
angle *= STABILITY_FRACTION;
|
||||
// reduce angle by a flexCoefficient
|
||||
angle *= target.getFlexCoefficient(chainDepth);
|
||||
deltaRotation = glm::angleAxis(angle, axis);
|
||||
|
||||
// The swing will re-orient the tip but there will tend to be be a non-zero delta between the tip's
|
||||
|
@ -385,6 +420,8 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
|||
|
||||
pivotIndex = pivotsParentIndex;
|
||||
pivotsParentIndex = _skeleton->getParentIndex(pivotIndex);
|
||||
|
||||
chainDepth++;
|
||||
}
|
||||
return lowestMovedIndex;
|
||||
}
|
||||
|
@ -399,6 +436,13 @@ 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;
|
||||
|
@ -410,25 +454,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
|
||||
PROFILE_RANGE_EX(simulation_animation, "ik/relax", 0xffff00ff, 0);
|
||||
|
||||
// relax toward underPoses
|
||||
// HACK: this relaxation needs to be constant per-frame rather than per-realtime
|
||||
// in order to prevent IK "flutter" for bad FPS. The bad news is that the good parts
|
||||
// of this relaxation will be FPS dependent (low FPS will make the limbs align slower
|
||||
// in real-time), however most people will not notice this and this problem is less
|
||||
// annoying than the flutter.
|
||||
const float blend = (1.0f / 60.0f) / (0.25f); // effectively: dt / RELAXATION_TIMESCALE
|
||||
int numJoints = (int)_relativePoses.size();
|
||||
for (int i = 0; i < numJoints; ++i) {
|
||||
float dotSign = copysignf(1.0f, glm::dot(_relativePoses[i].rot(), underPoses[i].rot()));
|
||||
if (_accumulators[i].isDirty()) {
|
||||
// this joint is affected by IK --> blend toward underPose rotation
|
||||
_relativePoses[i].rot() = glm::normalize(glm::lerp(_relativePoses[i].rot(), dotSign * underPoses[i].rot(), blend));
|
||||
} else {
|
||||
// this joint is NOT affected by IK --> slam to underPose rotation
|
||||
_relativePoses[i].rot() = underPoses[i].rot();
|
||||
}
|
||||
_relativePoses[i].trans() = underPoses[i].trans();
|
||||
}
|
||||
initRelativePosesFromSolutionSource((SolutionSource)solutionSource, underPoses);
|
||||
|
||||
if (!underPoses.empty()) {
|
||||
// Sometimes the underpose itself can violate the constraints. Rather than
|
||||
|
@ -604,9 +630,9 @@ void AnimInverseKinematics::clearIKJointLimitHistory() {
|
|||
}
|
||||
}
|
||||
|
||||
RotationConstraint* AnimInverseKinematics::getConstraint(int index) {
|
||||
RotationConstraint* AnimInverseKinematics::getConstraint(int index) const {
|
||||
RotationConstraint* constraint = nullptr;
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.find(index);
|
||||
std::map<int, RotationConstraint*>::const_iterator constraintItr = _constraints.find(index);
|
||||
if (constraintItr != _constraints.end()) {
|
||||
constraint = constraintItr->second;
|
||||
}
|
||||
|
@ -622,17 +648,19 @@ void AnimInverseKinematics::clearConstraints() {
|
|||
_constraints.clear();
|
||||
}
|
||||
|
||||
// set up swing limits around a swingTwistConstraint in an ellipse, where lateralSwingTheta is the swing limit for lateral swings (side to side)
|
||||
// anteriorSwingTheta is swing limit for forward and backward swings. (where x-axis of reference rotation is sideways and -z-axis is forward)
|
||||
static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float lateralSwingTheta, float anteriorSwingTheta) {
|
||||
// set up swing limits around a swingTwistConstraint in an ellipse, where lateralSwingPhi is the swing limit for lateral swings (side to side)
|
||||
// anteriorSwingPhi is swing limit for forward and backward swings. (where x-axis of reference rotation is sideways and -z-axis is forward)
|
||||
static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float lateralSwingPhi, float anteriorSwingPhi) {
|
||||
assert(stConstraint);
|
||||
const int NUM_SUBDIVISIONS = 8;
|
||||
const int NUM_SUBDIVISIONS = 16;
|
||||
std::vector<float> minDots;
|
||||
minDots.reserve(NUM_SUBDIVISIONS);
|
||||
float dTheta = TWO_PI / NUM_SUBDIVISIONS;
|
||||
float theta = 0.0f;
|
||||
for (int i = 0; i < NUM_SUBDIVISIONS; i++) {
|
||||
minDots.push_back(cosf(glm::length(glm::vec2(anteriorSwingTheta * cosf(theta), lateralSwingTheta * sinf(theta)))));
|
||||
float theta_prime = atanf((lateralSwingPhi / anteriorSwingPhi) * tanf(theta));
|
||||
float phi = (cosf(2.0f * theta_prime) * ((lateralSwingPhi - anteriorSwingPhi) / 2.0f)) + ((lateralSwingPhi + anteriorSwingPhi) / 2.0f);
|
||||
minDots.push_back(cosf(phi));
|
||||
theta += dTheta;
|
||||
}
|
||||
stConstraint->setSwingLimits(minDots);
|
||||
|
@ -640,7 +668,6 @@ static void setEllipticalSwingLimits(SwingTwistConstraint* stConstraint, float l
|
|||
|
||||
void AnimInverseKinematics::initConstraints() {
|
||||
if (!_skeleton) {
|
||||
return;
|
||||
}
|
||||
// We create constraints for the joints shown here
|
||||
// (and their Left counterparts if applicable).
|
||||
|
@ -744,30 +771,27 @@ void AnimInverseKinematics::initConstraints() {
|
|||
std::vector<glm::vec3> swungDirections;
|
||||
float deltaTheta = PI / 4.0f;
|
||||
float theta = 0.0f;
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta)));
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta)));
|
||||
theta += deltaTheta;
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta)));
|
||||
theta += deltaTheta;
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta))); // posterior
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta))); // posterior
|
||||
theta += deltaTheta;
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.0f, sinf(theta)));
|
||||
theta += deltaTheta;
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.25f, sinf(theta)));
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.25f, sinf(theta)));
|
||||
theta += deltaTheta;
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta)));
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta)));
|
||||
theta += deltaTheta;
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta))); // anterior
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta))); // anterior
|
||||
theta += deltaTheta;
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), 0.5f, sinf(theta)));
|
||||
swungDirections.push_back(glm::vec3(mirror * cosf(theta), -0.5f, sinf(theta)));
|
||||
|
||||
// rotate directions into joint-frame
|
||||
glm::quat invAbsoluteRotation = glm::inverse(absolutePoses[i].rot());
|
||||
int numDirections = (int)swungDirections.size();
|
||||
for (int j = 0; j < numDirections; ++j) {
|
||||
swungDirections[j] = invAbsoluteRotation * swungDirections[j];
|
||||
std::vector<float> minDots;
|
||||
for (size_t i = 0; i < swungDirections.size(); i++) {
|
||||
minDots.push_back(glm::dot(glm::normalize(swungDirections[i]), Vectors::UNIT_Y));
|
||||
}
|
||||
stConstraint->setSwingLimits(swungDirections);
|
||||
|
||||
stConstraint->setSwingLimits(minDots);
|
||||
constraint = static_cast<RotationConstraint*>(stConstraint);
|
||||
} else if (0 == baseName.compare("Hand", Qt::CaseSensitive)) {
|
||||
SwingTwistConstraint* stConstraint = new SwingTwistConstraint();
|
||||
|
@ -819,7 +843,7 @@ void AnimInverseKinematics::initConstraints() {
|
|||
stConstraint->setTwistLimits(-MAX_SHOULDER_TWIST, MAX_SHOULDER_TWIST);
|
||||
|
||||
std::vector<float> minDots;
|
||||
const float MAX_SHOULDER_SWING = PI / 20.0f;
|
||||
const float MAX_SHOULDER_SWING = PI / 16.0f;
|
||||
minDots.push_back(cosf(MAX_SHOULDER_SWING));
|
||||
stConstraint->setSwingLimits(minDots);
|
||||
|
||||
|
@ -957,6 +981,32 @@ void AnimInverseKinematics::initConstraints() {
|
|||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::initLimitCenterPoses() {
|
||||
assert(_skeleton);
|
||||
_limitCenterPoses.reserve(_skeleton->getNumJoints());
|
||||
for (int i = 0; i < _skeleton->getNumJoints(); i++) {
|
||||
AnimPose pose = _skeleton->getRelativeDefaultPose(i);
|
||||
RotationConstraint* constraint = getConstraint(i);
|
||||
if (constraint) {
|
||||
pose.rot() = constraint->computeCenterRotation();
|
||||
}
|
||||
_limitCenterPoses.push_back(pose);
|
||||
}
|
||||
|
||||
// The limit center rotations for the LeftArm and RightArm form a t-pose.
|
||||
// In order for the elbows to look more natural, we rotate them down by the avatar's sides
|
||||
const float UPPER_ARM_THETA = PI / 3.0f; // 60 deg
|
||||
int leftArmIndex = _skeleton->nameToJointIndex("LeftArm");
|
||||
const glm::quat armRot = glm::angleAxis(UPPER_ARM_THETA, Vectors::UNIT_X);
|
||||
if (leftArmIndex >= 0 && leftArmIndex < (int)_limitCenterPoses.size()) {
|
||||
_limitCenterPoses[leftArmIndex].rot() = _limitCenterPoses[leftArmIndex].rot() * armRot;
|
||||
}
|
||||
int rightArmIndex = _skeleton->nameToJointIndex("RightArm");
|
||||
if (rightArmIndex >= 0 && rightArmIndex < (int)_limitCenterPoses.size()) {
|
||||
_limitCenterPoses[rightArmIndex].rot() = _limitCenterPoses[rightArmIndex].rot() * armRot;
|
||||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) {
|
||||
AnimNode::setSkeletonInternal(skeleton);
|
||||
|
||||
|
@ -973,6 +1023,7 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
|
|||
|
||||
if (skeleton) {
|
||||
initConstraints();
|
||||
initLimitCenterPoses();
|
||||
_headIndex = _skeleton->nameToJointIndex("Head");
|
||||
_hipsIndex = _skeleton->nameToJointIndex("Hips");
|
||||
|
||||
|
@ -989,3 +1040,170 @@ void AnimInverseKinematics::setSkeletonInternal(AnimSkeleton::ConstPointer skele
|
|||
_hipsParentIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
static glm::vec3 sphericalToCartesian(float phi, float theta) {
|
||||
float cos_phi = cosf(phi);
|
||||
float sin_phi = sinf(phi);
|
||||
return glm::vec3(sin_phi * cosf(theta), cos_phi, -sin_phi * sinf(theta));
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::debugDrawConstraints(const AnimContext& context) const {
|
||||
if (_skeleton) {
|
||||
const vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
const vec4 GREEN(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
const vec4 BLUE(0.0f, 0.0f, 1.0f, 1.0f);
|
||||
const vec4 PURPLE(0.5f, 0.0f, 1.0f, 1.0f);
|
||||
const vec4 CYAN(0.0f, 1.0f, 1.0f, 1.0f);
|
||||
const vec4 GRAY(0.2f, 0.2f, 0.2f, 1.0f);
|
||||
const vec4 MAGENTA(1.0f, 0.0f, 1.0f, 1.0f);
|
||||
const float AXIS_LENGTH = 2.0f; // cm
|
||||
const float TWIST_LENGTH = 4.0f; // cm
|
||||
const float HINGE_LENGTH = 6.0f; // cm
|
||||
const float SWING_LENGTH = 5.0f; // cm
|
||||
|
||||
AnimPoseVec poses = _skeleton->getRelativeDefaultPoses();
|
||||
|
||||
// copy reference rotations into the relative poses
|
||||
for (int i = 0; i < (int)poses.size(); i++) {
|
||||
const RotationConstraint* constraint = getConstraint(i);
|
||||
if (constraint) {
|
||||
poses[i].rot() = constraint->getReferenceRotation();
|
||||
}
|
||||
}
|
||||
|
||||
// convert relative poses to absolute
|
||||
_skeleton->convertRelativePosesToAbsolute(poses);
|
||||
|
||||
mat4 geomToWorldMatrix = context.getRigToWorldMatrix() * context.getGeometryToRigMatrix();
|
||||
|
||||
// draw each pose and constraint
|
||||
for (int i = 0; i < (int)poses.size(); i++) {
|
||||
// transform local axes into world space.
|
||||
auto pose = poses[i];
|
||||
glm::vec3 xAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_X);
|
||||
glm::vec3 yAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Y);
|
||||
glm::vec3 zAxis = transformVectorFast(geomToWorldMatrix, pose.rot() * Vectors::UNIT_Z);
|
||||
glm::vec3 pos = transformPoint(geomToWorldMatrix, pose.trans());
|
||||
DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * xAxis, RED);
|
||||
DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * yAxis, GREEN);
|
||||
DebugDraw::getInstance().drawRay(pos, pos + AXIS_LENGTH * zAxis, BLUE);
|
||||
|
||||
// draw line to parent
|
||||
int parentIndex = _skeleton->getParentIndex(i);
|
||||
if (parentIndex != -1) {
|
||||
glm::vec3 parentPos = transformPoint(geomToWorldMatrix, poses[parentIndex].trans());
|
||||
DebugDraw::getInstance().drawRay(pos, parentPos, GRAY);
|
||||
}
|
||||
|
||||
glm::quat parentAbsRot;
|
||||
if (parentIndex != -1) {
|
||||
parentAbsRot = poses[parentIndex].rot();
|
||||
}
|
||||
|
||||
const RotationConstraint* constraint = getConstraint(i);
|
||||
if (constraint) {
|
||||
glm::quat refRot = constraint->getReferenceRotation();
|
||||
const ElbowConstraint* elbowConstraint = dynamic_cast<const ElbowConstraint*>(constraint);
|
||||
if (elbowConstraint) {
|
||||
glm::vec3 hingeAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * elbowConstraint->getHingeAxis());
|
||||
DebugDraw::getInstance().drawRay(pos, pos + HINGE_LENGTH * hingeAxis, MAGENTA);
|
||||
|
||||
// draw elbow constraints
|
||||
glm::quat minRot = glm::angleAxis(elbowConstraint->getMinAngle(), elbowConstraint->getHingeAxis());
|
||||
glm::quat maxRot = glm::angleAxis(elbowConstraint->getMaxAngle(), elbowConstraint->getHingeAxis());
|
||||
|
||||
const int NUM_SWING_STEPS = 10;
|
||||
for (int i = 0; i < NUM_SWING_STEPS + 1; i++) {
|
||||
glm::quat rot = glm::normalize(glm::lerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS)));
|
||||
glm::vec3 axis = transformVectorFast(geomToWorldMatrix, parentAbsRot * rot * refRot * Vectors::UNIT_Y);
|
||||
DebugDraw::getInstance().drawRay(pos, pos + TWIST_LENGTH * axis, CYAN);
|
||||
}
|
||||
|
||||
} else {
|
||||
const SwingTwistConstraint* swingTwistConstraint = dynamic_cast<const SwingTwistConstraint*>(constraint);
|
||||
if (swingTwistConstraint) {
|
||||
// twist constraints
|
||||
|
||||
glm::vec3 hingeAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * Vectors::UNIT_Y);
|
||||
DebugDraw::getInstance().drawRay(pos, pos + HINGE_LENGTH * hingeAxis, MAGENTA);
|
||||
|
||||
glm::quat minRot = glm::angleAxis(swingTwistConstraint->getMinTwist(), Vectors::UNIT_Y);
|
||||
glm::quat maxRot = glm::angleAxis(swingTwistConstraint->getMaxTwist(), Vectors::UNIT_Y);
|
||||
|
||||
const int NUM_SWING_STEPS = 10;
|
||||
for (int i = 0; i < NUM_SWING_STEPS + 1; i++) {
|
||||
glm::quat rot = glm::normalize(glm::lerp(minRot, maxRot, i * (1.0f / NUM_SWING_STEPS)));
|
||||
glm::vec3 axis = transformVectorFast(geomToWorldMatrix, parentAbsRot * rot * refRot * Vectors::UNIT_X);
|
||||
DebugDraw::getInstance().drawRay(pos, pos + TWIST_LENGTH * axis, CYAN);
|
||||
}
|
||||
|
||||
// draw swing constraints.
|
||||
const size_t NUM_MIN_DOTS = swingTwistConstraint->getMinDots().size();
|
||||
const float D_THETA = TWO_PI / (NUM_MIN_DOTS - 1);
|
||||
float theta = 0.0f;
|
||||
for (size_t i = 0, j = NUM_MIN_DOTS - 2; i < NUM_MIN_DOTS - 1; j = i, i++, theta += D_THETA) {
|
||||
// compute swing rotation from theta and phi angles.
|
||||
float phi = acosf(swingTwistConstraint->getMinDots()[i]);
|
||||
glm::vec3 swungAxis = sphericalToCartesian(phi, theta);
|
||||
glm::vec3 worldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * swungAxis);
|
||||
glm::vec3 swingTip = pos + SWING_LENGTH * worldSwungAxis;
|
||||
|
||||
float prevPhi = acos(swingTwistConstraint->getMinDots()[j]);
|
||||
float prevTheta = theta - D_THETA;
|
||||
glm::vec3 prevSwungAxis = sphericalToCartesian(prevPhi, prevTheta);
|
||||
glm::vec3 prevWorldSwungAxis = transformVectorFast(geomToWorldMatrix, parentAbsRot * refRot * prevSwungAxis);
|
||||
glm::vec3 prevSwingTip = pos + SWING_LENGTH * prevWorldSwungAxis;
|
||||
|
||||
DebugDraw::getInstance().drawRay(pos, swingTip, PURPLE);
|
||||
DebugDraw::getInstance().drawRay(prevSwingTip, swingTip, PURPLE);
|
||||
}
|
||||
}
|
||||
}
|
||||
pose.rot() = constraint->computeCenterRotation();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for bones under IK, blend between previous solution (_relativePoses) to targetPoses
|
||||
// for bones NOT under IK, copy directly from underPoses.
|
||||
// mutates _relativePoses.
|
||||
void AnimInverseKinematics::blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPoses, float blendFactor) {
|
||||
// relax toward poses
|
||||
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()) {
|
||||
// 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 {
|
||||
// this joint is NOT affected by IK --> slam to underPoses rotation
|
||||
_relativePoses[i].rot() = underPoses[i].rot();
|
||||
}
|
||||
_relativePoses[i].trans() = underPoses[i].trans();
|
||||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPoses) {
|
||||
const float RELAX_BLEND_FACTOR = (1.0f / 16.0f);
|
||||
const float COPY_BLEND_FACTOR = 1.0f;
|
||||
switch (solutionSource) {
|
||||
default:
|
||||
case SolutionSource::RelaxToUnderPoses:
|
||||
blendToPoses(underPoses, underPoses, RELAX_BLEND_FACTOR);
|
||||
break;
|
||||
case SolutionSource::RelaxToLimitCenterPoses:
|
||||
blendToPoses(_limitCenterPoses, underPoses, RELAX_BLEND_FACTOR);
|
||||
break;
|
||||
case SolutionSource::PreviousSolution:
|
||||
// do nothing... _relativePoses is already the previous solution
|
||||
break;
|
||||
case SolutionSource::UnderPoses:
|
||||
_relativePoses = underPoses;
|
||||
break;
|
||||
case SolutionSource::LimitCenterPoses:
|
||||
// essentially copy limitCenterPoses over to _relativePoses.
|
||||
blendToPoses(_limitCenterPoses, underPoses, COPY_BLEND_FACTOR);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,7 +32,8 @@ public:
|
|||
void loadPoses(const AnimPoseVec& poses);
|
||||
void computeAbsolutePoses(AnimPoseVec& absolutePoses) const;
|
||||
|
||||
void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, const QString& typeVar);
|
||||
void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar,
|
||||
const QString& typeVar, const QString& weightVar, float weight, const std::vector<float>& flexCoefficients);
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
|
||||
|
@ -43,40 +44,54 @@ public:
|
|||
|
||||
float getMaxErrorOnLastSolve() { return _maxErrorOnLastSolve; }
|
||||
|
||||
enum class SolutionSource {
|
||||
RelaxToUnderPoses = 0,
|
||||
RelaxToLimitCenterPoses,
|
||||
PreviousSolution,
|
||||
UnderPoses,
|
||||
LimitCenterPoses,
|
||||
NumSolutionSources,
|
||||
};
|
||||
|
||||
void setSolutionSource(SolutionSource solutionSource) { _solutionSource = solutionSource; }
|
||||
void setSolutionSourceVar(const QString& solutionSourceVar) { _solutionSourceVar = solutionSourceVar; }
|
||||
|
||||
protected:
|
||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
||||
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
||||
int solveTargetWithCCD(const IKTarget& target, AnimPoseVec& absolutePoses);
|
||||
virtual void setSkeletonInternal(AnimSkeleton::ConstPointer skeleton) override;
|
||||
void debugDrawConstraints(const AnimContext& context) const;
|
||||
void initRelativePosesFromSolutionSource(SolutionSource solutionSource, const AnimPoseVec& underPose);
|
||||
void blendToPoses(const AnimPoseVec& targetPoses, const AnimPoseVec& underPose, float blendFactor);
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override { return _relativePoses; }
|
||||
|
||||
RotationConstraint* getConstraint(int index);
|
||||
RotationConstraint* getConstraint(int index) const;
|
||||
void clearConstraints();
|
||||
void initConstraints();
|
||||
void initLimitCenterPoses();
|
||||
void computeHipsOffset(const std::vector<IKTarget>& targets, const AnimPoseVec& underPoses, float dt);
|
||||
|
||||
// no copies
|
||||
AnimInverseKinematics(const AnimInverseKinematics&) = delete;
|
||||
AnimInverseKinematics& operator=(const AnimInverseKinematics&) = delete;
|
||||
|
||||
enum FlexCoefficients { MAX_FLEX_COEFFICIENTS = 10 };
|
||||
struct IKTargetVar {
|
||||
IKTargetVar(const QString& jointNameIn,
|
||||
const QString& positionVarIn,
|
||||
const QString& rotationVarIn,
|
||||
const QString& typeVarIn) :
|
||||
positionVar(positionVarIn),
|
||||
rotationVar(rotationVarIn),
|
||||
typeVar(typeVarIn),
|
||||
jointName(jointNameIn),
|
||||
jointIndex(-1)
|
||||
{}
|
||||
IKTargetVar(const QString& jointNameIn, const QString& positionVarIn, const QString& rotationVarIn,
|
||||
const QString& typeVarIn, const QString& weightVarIn, float weightIn, const std::vector<float>& flexCoefficientsIn);
|
||||
IKTargetVar(const IKTargetVar& orig);
|
||||
|
||||
QString jointName;
|
||||
QString positionVar;
|
||||
QString rotationVar;
|
||||
QString typeVar;
|
||||
QString jointName;
|
||||
QString weightVar;
|
||||
float weight;
|
||||
float flexCoefficients[MAX_FLEX_COEFFICIENTS];
|
||||
size_t numFlexCoefficients;
|
||||
int jointIndex; // cached joint index
|
||||
};
|
||||
|
||||
|
@ -85,6 +100,7 @@ protected:
|
|||
std::vector<IKTargetVar> _targetVarVec;
|
||||
AnimPoseVec _defaultRelativePoses; // poses of the relaxed state
|
||||
AnimPoseVec _relativePoses; // current relative poses
|
||||
AnimPoseVec _limitCenterPoses; // relative
|
||||
|
||||
// experimental data for moving hips during IK
|
||||
glm::vec3 _hipsOffset { Vectors::ZERO };
|
||||
|
@ -100,6 +116,8 @@ protected:
|
|||
|
||||
float _maxErrorOnLastSolve { FLT_MAX };
|
||||
bool _previousEnableDebugIKTargets { false };
|
||||
SolutionSource _solutionSource { SolutionSource::RelaxToUnderPoses };
|
||||
QString _solutionSourceVar;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimInverseKinematics_h
|
||||
|
|
|
@ -173,6 +173,13 @@ static NodeProcessFunc animNodeTypeToProcessFunc(AnimNode::Type type) {
|
|||
} \
|
||||
float NAME = (float)NAME##_VAL.toDouble()
|
||||
|
||||
#define READ_OPTIONAL_FLOAT(NAME, JSON_OBJ, DEFAULT) \
|
||||
auto NAME##_VAL = JSON_OBJ.value(#NAME); \
|
||||
float NAME = (float)DEFAULT; \
|
||||
if (NAME##_VAL.isDouble()) { \
|
||||
NAME = (float)NAME##_VAL.toDouble(); \
|
||||
} \
|
||||
do {} while (0)
|
||||
|
||||
static AnimNode::Pointer loadNode(const QJsonObject& jsonObj, const QUrl& jsonUrl) {
|
||||
auto idVal = jsonObj.value("id");
|
||||
|
@ -352,6 +359,23 @@ static AnimOverlay::BoneSet stringToBoneSetEnum(const QString& str) {
|
|||
return AnimOverlay::NumBoneSets;
|
||||
}
|
||||
|
||||
static const char* solutionSourceStrings[(int)AnimInverseKinematics::SolutionSource::NumSolutionSources] = {
|
||||
"relaxToUnderPoses",
|
||||
"relaxToLimitCenterPoses",
|
||||
"previousSolution",
|
||||
"underPoses",
|
||||
"limitCenterPoses"
|
||||
};
|
||||
|
||||
static AnimInverseKinematics::SolutionSource stringToSolutionSourceEnum(const QString& str) {
|
||||
for (int i = 0; i < (int)AnimInverseKinematics::SolutionSource::NumSolutionSources; i++) {
|
||||
if (str == solutionSourceStrings[i]) {
|
||||
return (AnimInverseKinematics::SolutionSource)i;
|
||||
}
|
||||
}
|
||||
return AnimInverseKinematics::SolutionSource::NumSolutionSources;
|
||||
}
|
||||
|
||||
static AnimNode::Pointer loadOverlayNode(const QJsonObject& jsonObj, const QString& id, const QUrl& jsonUrl) {
|
||||
|
||||
READ_STRING(boneSet, jsonObj, id, jsonUrl, nullptr);
|
||||
|
@ -453,10 +477,40 @@ AnimNode::Pointer loadInverseKinematicsNode(const QJsonObject& jsonObj, const QS
|
|||
READ_STRING(positionVar, targetObj, id, jsonUrl, nullptr);
|
||||
READ_STRING(rotationVar, targetObj, id, jsonUrl, nullptr);
|
||||
READ_OPTIONAL_STRING(typeVar, targetObj);
|
||||
READ_OPTIONAL_STRING(weightVar, targetObj);
|
||||
READ_OPTIONAL_FLOAT(weight, targetObj, 1.0f);
|
||||
|
||||
node->setTargetVars(jointName, positionVar, rotationVar, typeVar);
|
||||
auto flexCoefficientsValue = targetObj.value("flexCoefficients");
|
||||
if (!flexCoefficientsValue.isArray()) {
|
||||
qCCritical(animation) << "AnimNodeLoader, bad or missing flexCoefficients array in \"targets\", id =" << id << ", url =" << jsonUrl.toDisplayString();
|
||||
return nullptr;
|
||||
}
|
||||
auto flexCoefficientsArray = flexCoefficientsValue.toArray();
|
||||
std::vector<float> flexCoefficients;
|
||||
for (const auto& value : flexCoefficientsArray) {
|
||||
flexCoefficients.push_back((float)value.toDouble());
|
||||
}
|
||||
|
||||
node->setTargetVars(jointName, positionVar, rotationVar, typeVar, weightVar, weight, flexCoefficients);
|
||||
};
|
||||
|
||||
READ_OPTIONAL_STRING(solutionSource, jsonObj);
|
||||
|
||||
if (!solutionSource.isEmpty()) {
|
||||
AnimInverseKinematics::SolutionSource solutionSourceType = stringToSolutionSourceEnum(solutionSource);
|
||||
if (solutionSourceType != AnimInverseKinematics::SolutionSource::NumSolutionSources) {
|
||||
node->setSolutionSource(solutionSourceType);
|
||||
} else {
|
||||
qCWarning(animation) << "AnimNodeLoader, bad solutionSourceType in \"solutionSource\", id = " << id << ", url = " << jsonUrl.toDisplayString();
|
||||
}
|
||||
}
|
||||
|
||||
READ_OPTIONAL_STRING(solutionSourceVar, jsonObj);
|
||||
|
||||
if (!solutionSourceVar.isEmpty()) {
|
||||
node->setSolutionSourceVar(solutionSourceVar);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,7 +39,7 @@ glm::vec3 AnimPose::xformPoint(const glm::vec3& rhs) const {
|
|||
return *this * rhs;
|
||||
}
|
||||
|
||||
// really slow
|
||||
// really slow, but accurate for transforms with non-uniform scale
|
||||
glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const {
|
||||
glm::vec3 xAxis = _rot * glm::vec3(_scale.x, 0.0f, 0.0f);
|
||||
glm::vec3 yAxis = _rot * glm::vec3(0.0f, _scale.y, 0.0f);
|
||||
|
@ -49,6 +49,11 @@ glm::vec3 AnimPose::xformVector(const glm::vec3& rhs) const {
|
|||
return transInvMat * rhs;
|
||||
}
|
||||
|
||||
// faster, but does not handle non-uniform scale correctly.
|
||||
glm::vec3 AnimPose::xformVectorFast(const glm::vec3& rhs) const {
|
||||
return _rot * (_scale * rhs);
|
||||
}
|
||||
|
||||
AnimPose AnimPose::operator*(const AnimPose& rhs) const {
|
||||
glm::mat4 result;
|
||||
glm_mat4u_mul(*this, rhs, result);
|
||||
|
|
|
@ -25,7 +25,8 @@ public:
|
|||
static const AnimPose identity;
|
||||
|
||||
glm::vec3 xformPoint(const glm::vec3& rhs) const;
|
||||
glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow
|
||||
glm::vec3 xformVector(const glm::vec3& rhs) const; // really slow, but accurate for transforms with non-uniform scale
|
||||
glm::vec3 xformVectorFast(const glm::vec3& rhs) const; // faster, but does not handle non-uniform scale correctly.
|
||||
|
||||
glm::vec3 operator*(const glm::vec3& rhs) const; // same as xformPoint
|
||||
AnimPose operator*(const AnimPose& rhs) const;
|
||||
|
|
|
@ -33,6 +33,23 @@ void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, A
|
|||
}
|
||||
}
|
||||
|
||||
glm::quat averageQuats(size_t numQuats, const glm::quat* quats) {
|
||||
if (numQuats == 0) {
|
||||
return glm::quat();
|
||||
}
|
||||
glm::quat accum = quats[0];
|
||||
glm::quat firstRot = quats[0];
|
||||
for (size_t i = 1; i < numQuats; i++) {
|
||||
glm::quat rot = quats[i];
|
||||
float dot = glm::dot(firstRot, rot);
|
||||
if (dot < 0.0f) {
|
||||
rot = -rot;
|
||||
}
|
||||
accum += rot;
|
||||
}
|
||||
return glm::normalize(accum);
|
||||
}
|
||||
|
||||
float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag,
|
||||
const QString& id, AnimNode::Triggers& triggersOut) {
|
||||
|
||||
|
|
|
@ -16,9 +16,9 @@
|
|||
// this is where the magic happens
|
||||
void blend(size_t numPoses, const AnimPose* a, const AnimPose* b, float alpha, AnimPose* result);
|
||||
|
||||
glm::quat averageQuats(size_t numQuats, const glm::quat* quats);
|
||||
|
||||
float accumulateTime(float startFrame, float endFrame, float timeScale, float currentFrame, float dt, bool loopFlag,
|
||||
const QString& id, AnimNode::Triggers& triggersOut);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
#include <GeometryUtil.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include "AnimUtil.h"
|
||||
|
||||
ElbowConstraint::ElbowConstraint() :
|
||||
_minAngle(-PI),
|
||||
|
@ -77,3 +78,10 @@ bool ElbowConstraint::apply(glm::quat& rotation) const {
|
|||
return false;
|
||||
}
|
||||
|
||||
glm::quat ElbowConstraint::computeCenterRotation() const {
|
||||
const size_t NUM_LIMITS = 2;
|
||||
glm::quat limits[NUM_LIMITS];
|
||||
limits[0] = glm::angleAxis(_minAngle, _axis) * _referenceRotation;
|
||||
limits[1] = glm::angleAxis(_maxAngle, _axis) * _referenceRotation;
|
||||
return averageQuats(NUM_LIMITS, limits);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,12 @@ public:
|
|||
void setHingeAxis(const glm::vec3& axis);
|
||||
void setAngleLimits(float minAngle, float maxAngle);
|
||||
virtual bool apply(glm::quat& rotation) const override;
|
||||
virtual glm::quat computeCenterRotation() const override;
|
||||
|
||||
glm::vec3 getHingeAxis() const { return _axis; }
|
||||
float getMinAngle() const { return _minAngle; }
|
||||
float getMaxAngle() const { return _maxAngle; }
|
||||
|
||||
protected:
|
||||
glm::vec3 _axis;
|
||||
glm::vec3 _perpAxis;
|
||||
|
|
|
@ -14,6 +14,23 @@ void IKTarget::setPose(const glm::quat& rotation, const glm::vec3& translation)
|
|||
_pose.trans() = translation;
|
||||
}
|
||||
|
||||
void IKTarget::setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn) {
|
||||
_numFlexCoefficients = std::min(numFlexCoefficientsIn, (size_t)MAX_FLEX_COEFFICIENTS);
|
||||
for (size_t i = 0; i < _numFlexCoefficients; i++) {
|
||||
_flexCoefficients[i] = flexCoefficientsIn[i];
|
||||
}
|
||||
}
|
||||
|
||||
float IKTarget::getFlexCoefficient(size_t chainDepth) const {
|
||||
const float DEFAULT_FLEX_COEFFICIENT = 0.5f;
|
||||
|
||||
if (chainDepth < _numFlexCoefficients) {
|
||||
return _flexCoefficients[chainDepth];
|
||||
} else {
|
||||
return DEFAULT_FLEX_COEFFICIENT;
|
||||
}
|
||||
}
|
||||
|
||||
void IKTarget::setType(int type) {
|
||||
switch (type) {
|
||||
case (int)Type::RotationAndPosition:
|
||||
|
|
|
@ -35,15 +35,21 @@ public:
|
|||
void setPose(const glm::quat& rotation, const glm::vec3& translation);
|
||||
void setIndex(int index) { _index = index; }
|
||||
void setType(int);
|
||||
void setFlexCoefficients(size_t numFlexCoefficientsIn, const float* flexCoefficientsIn);
|
||||
float getFlexCoefficient(size_t chainDepth) const;
|
||||
|
||||
// HACK: give HmdHead targets more "weight" during IK algorithm
|
||||
float getWeight() const { return _type == Type::HmdHead ? HACK_HMD_TARGET_WEIGHT : 1.0f; }
|
||||
void setWeight(float weight) { _weight = weight; }
|
||||
float getWeight() const { return _weight; }
|
||||
|
||||
enum FlexCoefficients { MAX_FLEX_COEFFICIENTS = 10 };
|
||||
|
||||
private:
|
||||
AnimPose _pose;
|
||||
int _index{-1};
|
||||
Type _type{Type::RotationAndPosition};
|
||||
|
||||
float _weight;
|
||||
float _flexCoefficients[MAX_FLEX_COEFFICIENTS];
|
||||
size_t _numFlexCoefficients;
|
||||
};
|
||||
|
||||
#endif // hifi_IKTarget_h
|
||||
|
|
|
@ -305,30 +305,35 @@ void Rig::clearJointAnimationPriority(int index) {
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::clearIKJointLimitHistory() {
|
||||
std::shared_ptr<AnimInverseKinematics> Rig::getAnimInverseKinematicsNode() const {
|
||||
std::shared_ptr<AnimInverseKinematics> result;
|
||||
if (_animNode) {
|
||||
_animNode->traverse([&](AnimNode::Pointer node) {
|
||||
// only report clip nodes as valid roles.
|
||||
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
|
||||
if (ikNode) {
|
||||
ikNode->clearIKJointLimitHistory();
|
||||
result = ikNode;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void Rig::clearIKJointLimitHistory() {
|
||||
auto ikNode = getAnimInverseKinematicsNode();
|
||||
if (ikNode) {
|
||||
ikNode->clearIKJointLimitHistory();
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::setMaxHipsOffsetLength(float maxLength) {
|
||||
_maxHipsOffsetLength = maxLength;
|
||||
|
||||
if (_animNode) {
|
||||
_animNode->traverse([&](AnimNode::Pointer node) {
|
||||
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
|
||||
if (ikNode) {
|
||||
ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
auto ikNode = getAnimInverseKinematicsNode();
|
||||
if (ikNode) {
|
||||
ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -936,7 +941,7 @@ void Rig::updateAnimationStateHandlers() { // called on avatar update thread (wh
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
|
||||
void Rig::updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform) {
|
||||
|
||||
PROFILE_RANGE_EX(simulation_animation_detail, __FUNCTION__, 0xffff00ff, 0);
|
||||
PerformanceTimer perfTimer("updateAnimations");
|
||||
|
@ -949,7 +954,8 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
|
|||
updateAnimationStateHandlers();
|
||||
_animVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
|
||||
AnimContext context(_enableDebugDrawIKTargets, getGeometryToRigTransform());
|
||||
AnimContext context(_enableDebugDrawIKTargets, _enableDebugDrawIKConstraints,
|
||||
getGeometryToRigTransform(), rigToWorldTransform);
|
||||
|
||||
// evaluate the animation
|
||||
AnimNode::Triggers triggersOut;
|
||||
|
@ -1025,10 +1031,12 @@ void Rig::updateFromHeadParameters(const HeadParameters& params, float dt) {
|
|||
_animVars.set("notIsTalking", !params.isTalking);
|
||||
|
||||
if (params.hipsEnabled) {
|
||||
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToLimitCenterPoses);
|
||||
_animVars.set("hipsType", (int)IKTarget::Type::RotationAndPosition);
|
||||
_animVars.set("hipsPosition", extractTranslation(params.hipsMatrix));
|
||||
_animVars.set("hipsRotation", glmExtractRotation(params.hipsMatrix));
|
||||
} else {
|
||||
_animVars.set("solutionSource", (int)AnimInverseKinematics::SolutionSource::RelaxToUnderPoses);
|
||||
_animVars.set("hipsType", (int)IKTarget::Type::Unknown);
|
||||
}
|
||||
|
||||
|
@ -1080,10 +1088,12 @@ void Rig::updateHeadAnimVars(const HeadParameters& params) {
|
|||
// 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);
|
||||
_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,
|
||||
// but because the IK _hipsOffset is enabled, the hips will naturally follow underneath the head.
|
||||
_animVars.set("headType", (int)IKTarget::Type::HmdHead);
|
||||
_animVars.set("headWeight", 8.0f);
|
||||
}
|
||||
} else {
|
||||
_animVars.unset("headPosition");
|
||||
|
@ -1392,22 +1402,24 @@ void Rig::computeAvatarBoundingCapsule(
|
|||
|
||||
AnimInverseKinematics ikNode("boundingShape");
|
||||
ikNode.setSkeleton(_animSkeleton);
|
||||
|
||||
// AJT: FIX ME!!!!! ensure that empty weights vector does something reasonable....
|
||||
ikNode.setTargetVars("LeftHand",
|
||||
"leftHandPosition",
|
||||
"leftHandRotation",
|
||||
"leftHandType");
|
||||
"leftHandType", "leftHandWeight", 1.0f, {});
|
||||
ikNode.setTargetVars("RightHand",
|
||||
"rightHandPosition",
|
||||
"rightHandRotation",
|
||||
"rightHandType");
|
||||
"rightHandType", "rightHandWeight", 1.0f, {});
|
||||
ikNode.setTargetVars("LeftFoot",
|
||||
"leftFootPosition",
|
||||
"leftFootRotation",
|
||||
"leftFootType");
|
||||
"leftFootType", "leftFootWeight", 1.0f, {});
|
||||
ikNode.setTargetVars("RightFoot",
|
||||
"rightFootPosition",
|
||||
"rightFootRotation",
|
||||
"rightFootType");
|
||||
"rightFootType", "rightFootWeight", 1.0f, {});
|
||||
|
||||
AnimPose geometryToRig = _modelOffset * _geometryOffset;
|
||||
|
||||
|
@ -1440,7 +1452,7 @@ void Rig::computeAvatarBoundingCapsule(
|
|||
|
||||
// call overlay twice: once to verify AnimPoseVec joints and again to do the IK
|
||||
AnimNode::Triggers triggersOut;
|
||||
AnimContext context(false, glm::mat4());
|
||||
AnimContext context(false, false, glm::mat4(), glm::mat4());
|
||||
float dt = 1.0f; // the value of this does not matter
|
||||
ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
|
||||
AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
#include "SimpleMovingAverage.h"
|
||||
|
||||
class Rig;
|
||||
class AnimInverseKinematics;
|
||||
typedef std::shared_ptr<Rig> RigPointer;
|
||||
|
||||
// Rig instances are reentrant.
|
||||
|
@ -111,6 +112,8 @@ public:
|
|||
void clearJointStates();
|
||||
void clearJointAnimationPriority(int index);
|
||||
|
||||
std::shared_ptr<AnimInverseKinematics> getAnimInverseKinematicsNode() const;
|
||||
|
||||
void clearIKJointLimitHistory();
|
||||
void setMaxHipsOffsetLength(float maxLength);
|
||||
float getMaxHipsOffsetLength() const;
|
||||
|
@ -159,7 +162,7 @@ public:
|
|||
void computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState);
|
||||
|
||||
// Regardless of who started the animations or how many, update the joints.
|
||||
void updateAnimations(float deltaTime, glm::mat4 rootTransform);
|
||||
void updateAnimations(float deltaTime, const glm::mat4& rootTransform, const glm::mat4& rigToWorldTransform);
|
||||
|
||||
// legacy
|
||||
void inverseKinematics(int endIndex, glm::vec3 targetPosition, const glm::quat& targetRotation, float priority,
|
||||
|
@ -228,6 +231,7 @@ public:
|
|||
const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; }
|
||||
|
||||
void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; }
|
||||
void setEnableDebugDrawIKConstraints(bool enableDebugDrawIKConstraints) { _enableDebugDrawIKConstraints = enableDebugDrawIKConstraints; }
|
||||
|
||||
// input assumed to be in rig space
|
||||
void computeHeadFromHMD(const AnimPose& hmdPose, glm::vec3& headPositionOut, glm::quat& headOrientationOut) const;
|
||||
|
@ -338,6 +342,7 @@ protected:
|
|||
float _maxHipsOffsetLength { 1.0f };
|
||||
|
||||
bool _enableDebugDrawIKTargets { false };
|
||||
bool _enableDebugDrawIKConstraints { false };
|
||||
|
||||
private:
|
||||
QMap<int, StateHandler> _stateHandlers;
|
||||
|
|
|
@ -38,6 +38,9 @@ public:
|
|||
/// \brief reset any remembered joint limit history
|
||||
virtual void clearHistory() {};
|
||||
|
||||
/// \brief return the rotation that lies at the "center" of all the joint limits.
|
||||
virtual glm::quat computeCenterRotation() const = 0;
|
||||
|
||||
protected:
|
||||
glm::quat _referenceRotation = glm::quat();
|
||||
};
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <GeometryUtil.h>
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include "AnimUtil.h"
|
||||
|
||||
|
||||
const float MIN_MINDOT = -0.999f;
|
||||
|
@ -430,3 +431,33 @@ void SwingTwistConstraint::dynamicallyAdjustLimits(const glm::quat& rotation) {
|
|||
void SwingTwistConstraint::clearHistory() {
|
||||
_lastTwistBoundary = LAST_CLAMP_NO_BOUNDARY;
|
||||
}
|
||||
|
||||
glm::quat SwingTwistConstraint::computeCenterRotation() const {
|
||||
const size_t NUM_TWIST_LIMITS = 2;
|
||||
const size_t NUM_MIN_DOTS = getMinDots().size();
|
||||
std::vector<glm::quat> swingLimits;
|
||||
swingLimits.reserve(NUM_MIN_DOTS);
|
||||
|
||||
glm::quat twistLimits[NUM_TWIST_LIMITS];
|
||||
if (_minTwist != _maxTwist) {
|
||||
// to ensure that twists do not flip the center rotation, we devide twist angle by 2.
|
||||
twistLimits[0] = glm::angleAxis(_minTwist / 2.0f, _referenceRotation * Vectors::UNIT_Y);
|
||||
twistLimits[1] = glm::angleAxis(_maxTwist / 2.0f, _referenceRotation * Vectors::UNIT_Y);
|
||||
}
|
||||
const float D_THETA = TWO_PI / (NUM_MIN_DOTS - 1);
|
||||
float theta = 0.0f;
|
||||
for (size_t i = 0; i < NUM_MIN_DOTS - 1; i++, theta += D_THETA) {
|
||||
// compute swing rotation from theta and phi angles.
|
||||
float phi = acos(getMinDots()[i]);
|
||||
float cos_phi = getMinDots()[i];
|
||||
float sin_phi = sinf(phi);
|
||||
glm::vec3 swungAxis(sin_phi * cosf(theta), cos_phi, -sin_phi * sinf(theta));
|
||||
|
||||
// to ensure that swings > 90 degrees do not flip the center rotation, we devide phi / 2
|
||||
glm::quat swing = glm::angleAxis(phi / 2, glm::normalize(glm::cross(Vectors::UNIT_Y, swungAxis)));
|
||||
swingLimits.push_back(swing);
|
||||
}
|
||||
glm::quat averageSwing = averageQuats(swingLimits.size(), &swingLimits[0]);
|
||||
glm::quat averageTwist = averageQuats(2, twistLimits);
|
||||
return averageSwing * averageTwist * _referenceRotation;
|
||||
}
|
||||
|
|
|
@ -58,7 +58,7 @@ public:
|
|||
virtual void dynamicallyAdjustLimits(const glm::quat& rotation) override;
|
||||
|
||||
// for testing purposes
|
||||
const std::vector<float>& getMinDots() { return _swingLimitFunction.getMinDots(); }
|
||||
const std::vector<float>& getMinDots() const { return _swingLimitFunction.getMinDots(); }
|
||||
|
||||
// SwingLimitFunction is an implementation of the constraint check described in the paper:
|
||||
// "The Parameterization of Joint Rotation with the Unit Quaternion" by Quang Liu and Edmond C. Prakash
|
||||
|
@ -81,7 +81,7 @@ public:
|
|||
float getMinDot(float theta) const;
|
||||
|
||||
// for testing purposes
|
||||
const std::vector<float>& getMinDots() { return _minDots; }
|
||||
const std::vector<float>& getMinDots() const { return _minDots; }
|
||||
|
||||
private:
|
||||
// the limits are stored in a lookup table with cyclic boundary conditions
|
||||
|
@ -99,6 +99,11 @@ public:
|
|||
|
||||
void clearHistory() override;
|
||||
|
||||
virtual glm::quat computeCenterRotation() const override;
|
||||
|
||||
float getMinTwist() const { return _minTwist; }
|
||||
float getMaxTwist() const { return _maxTwist; }
|
||||
|
||||
private:
|
||||
float handleTwistBoundaryConditions(float twistAngle) const;
|
||||
|
||||
|
|
|
@ -1506,6 +1506,7 @@ bool AudioClient::switchOutputToAudioDevice(const QAudioDeviceInfo& outputDevice
|
|||
|
||||
// cleanup any previously initialized device
|
||||
if (_audioOutput) {
|
||||
_audioOutputIODevice.close();
|
||||
_audioOutput->stop();
|
||||
|
||||
//must be deleted in next eventloop cycle when its called from notify()
|
||||
|
|
|
@ -94,7 +94,6 @@ public:
|
|||
_audio(audio), _unfulfilledReads(0) {}
|
||||
|
||||
void start() { open(QIODevice::ReadOnly | QIODevice::Unbuffered); }
|
||||
void stop() { close(); }
|
||||
qint64 readData(char * data, qint64 maxSize) override;
|
||||
qint64 writeData(const char * data, qint64 maxSize) override { return 0; }
|
||||
int getRecentUnfulfilledReads() { int unfulfilledReads = _unfulfilledReads; _unfulfilledReads = 0; return unfulfilledReads; }
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
set(TARGET_NAME avatars-renderer)
|
||||
AUTOSCRIBE_SHADER_LIB(gpu model render render-utils)
|
||||
setup_hifi_library(Widgets Network Script)
|
||||
link_hifi_libraries(shared gpu model animation physics model-networking script-engine render image render-utils)
|
||||
link_hifi_libraries(shared gpu model animation model-networking script-engine render image render-utils)
|
||||
|
||||
target_bullet()
|
||||
|
|
|
@ -338,34 +338,6 @@ void Avatar::simulate(float deltaTime, bool inView) {
|
|||
_simulationInViewRate.increment();
|
||||
}
|
||||
|
||||
if (!isMyAvatar()) {
|
||||
if (_smoothPositionTimer < _smoothPositionTime) {
|
||||
// Smooth the remote avatar movement.
|
||||
_smoothPositionTimer += deltaTime;
|
||||
if (_smoothPositionTimer < _smoothPositionTime) {
|
||||
AvatarData::setPosition(
|
||||
lerp(_smoothPositionInitial,
|
||||
_smoothPositionTarget,
|
||||
easeInOutQuad(glm::clamp(_smoothPositionTimer / _smoothPositionTime, 0.0f, 1.0f)))
|
||||
);
|
||||
updateAttitude();
|
||||
}
|
||||
}
|
||||
|
||||
if (_smoothOrientationTimer < _smoothOrientationTime) {
|
||||
// Smooth the remote avatar movement.
|
||||
_smoothOrientationTimer += deltaTime;
|
||||
if (_smoothOrientationTimer < _smoothOrientationTime) {
|
||||
AvatarData::setOrientation(
|
||||
slerp(_smoothOrientationInitial,
|
||||
_smoothOrientationTarget,
|
||||
easeInOutQuad(glm::clamp(_smoothOrientationTimer / _smoothOrientationTime, 0.0f, 1.0f)))
|
||||
);
|
||||
updateAttitude();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PerformanceTimer perfTimer("simulate");
|
||||
{
|
||||
PROFILE_RANGE(simulation, "updateJoints");
|
||||
|
@ -1293,6 +1265,17 @@ void Avatar::getCapsule(glm::vec3& start, glm::vec3& end, float& radius) {
|
|||
radius = halfExtents.x;
|
||||
}
|
||||
|
||||
float Avatar::computeMass() {
|
||||
float radius;
|
||||
glm::vec3 start, end;
|
||||
getCapsule(start, end, radius);
|
||||
// NOTE:
|
||||
// volumeOfCapsule = volumeOfCylinder + volumeOfSphere
|
||||
// volumeOfCapsule = (2PI * R^2 * H) + (4PI * R^3 / 3)
|
||||
// volumeOfCapsule = 2PI * R^2 * (H + 2R/3)
|
||||
return _density * TWO_PI * radius * radius * (glm::length(end - start) + 2.0f * radius / 3.0f);
|
||||
}
|
||||
|
||||
// virtual
|
||||
void Avatar::rebuildCollisionShape() {
|
||||
addPhysicsFlags(Simulation::DIRTY_SHAPE);
|
||||
|
@ -1371,31 +1354,13 @@ glm::quat Avatar::getUncachedRightPalmRotation() const {
|
|||
}
|
||||
|
||||
void Avatar::setPosition(const glm::vec3& position) {
|
||||
if (isMyAvatar()) {
|
||||
// This is the local avatar, no need to handle any position smoothing.
|
||||
AvatarData::setPosition(position);
|
||||
updateAttitude();
|
||||
return;
|
||||
}
|
||||
|
||||
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
|
||||
_smoothPositionInitial = getPosition();
|
||||
_smoothPositionTarget = position;
|
||||
_smoothPositionTimer = 0.0f;
|
||||
AvatarData::setPosition(position);
|
||||
updateAttitude();
|
||||
}
|
||||
|
||||
void Avatar::setOrientation(const glm::quat& orientation) {
|
||||
if (isMyAvatar()) {
|
||||
// This is the local avatar, no need to handle any position smoothing.
|
||||
AvatarData::setOrientation(orientation);
|
||||
updateAttitude();
|
||||
return;
|
||||
}
|
||||
|
||||
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
|
||||
_smoothOrientationInitial = getOrientation();
|
||||
_smoothOrientationTarget = orientation;
|
||||
_smoothOrientationTimer = 0.0f;
|
||||
AvatarData::setOrientation(orientation);
|
||||
updateAttitude();
|
||||
}
|
||||
|
||||
void Avatar::updatePalms() {
|
||||
|
|
|
@ -36,7 +36,6 @@ namespace render {
|
|||
}
|
||||
|
||||
static const float SCALING_RATIO = .05f;
|
||||
static const float SMOOTHING_RATIO = .05f; // 0 < ratio < 1
|
||||
|
||||
extern const float CHAT_MESSAGE_SCALE;
|
||||
extern const float CHAT_MESSAGE_HEIGHT;
|
||||
|
@ -196,6 +195,7 @@ public:
|
|||
|
||||
virtual void computeShapeInfo(ShapeInfo& shapeInfo);
|
||||
void getCapsule(glm::vec3& start, glm::vec3& end, float& radius);
|
||||
float computeMass();
|
||||
|
||||
using SpatiallyNestable::setPosition;
|
||||
virtual void setPosition(const glm::vec3& position) override;
|
||||
|
@ -239,17 +239,8 @@ public:
|
|||
|
||||
bool hasNewJointData() const { return _hasNewJointData; }
|
||||
|
||||
inline float easeInOutQuad(float lerpValue) {
|
||||
assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f)));
|
||||
|
||||
if (lerpValue < 0.5f) {
|
||||
return (2.0f * lerpValue * lerpValue);
|
||||
}
|
||||
|
||||
return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f);
|
||||
}
|
||||
float getBoundingRadius() const;
|
||||
|
||||
|
||||
void addToScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
||||
void ensureInScene(AvatarSharedPointer self, const render::ScenePointer& scene);
|
||||
bool isInScene() const { return render::Item::isValidID(_renderItemID); }
|
||||
|
@ -271,9 +262,6 @@ public slots:
|
|||
void setModelURLFinished(bool success);
|
||||
|
||||
protected:
|
||||
const float SMOOTH_TIME_POSITION = 0.125f;
|
||||
const float SMOOTH_TIME_ORIENTATION = 0.075f;
|
||||
|
||||
virtual const QString& getSessionDisplayNameForTransport() const override { return _empty; } // Save a tiny bit of bandwidth. Mixer won't look at what we send.
|
||||
QString _empty{};
|
||||
virtual void maybeUpdateSessionDisplayNameFromTransport(const QString& sessionDisplayName) override { _sessionDisplayName = sessionDisplayName; } // don't use no-op setter!
|
||||
|
@ -336,16 +324,6 @@ protected:
|
|||
RateCounter<> _skeletonModelSimulationRate;
|
||||
RateCounter<> _jointDataSimulationRate;
|
||||
|
||||
// Smoothing data for blending from one position/orientation to another on remote agents.
|
||||
float _smoothPositionTime { SMOOTH_TIME_POSITION };
|
||||
float _smoothPositionTimer { std::numeric_limits<float>::max() };
|
||||
float _smoothOrientationTime { SMOOTH_TIME_ORIENTATION };
|
||||
float _smoothOrientationTimer { std::numeric_limits<float>::max() };
|
||||
glm::vec3 _smoothPositionInitial;
|
||||
glm::vec3 _smoothPositionTarget;
|
||||
glm::quat _smoothOrientationInitial;
|
||||
glm::quat _smoothOrientationTarget;
|
||||
|
||||
private:
|
||||
class AvatarEntityDataHash {
|
||||
public:
|
||||
|
|
|
@ -23,9 +23,10 @@
|
|||
|
||||
#include "Avatar.h"
|
||||
|
||||
const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for
|
||||
|
||||
using namespace std;
|
||||
|
||||
static bool fixGaze { false };
|
||||
static bool disableEyelidAdjustment { false };
|
||||
|
||||
Head::Head(Avatar* owningAvatar) :
|
||||
|
@ -42,17 +43,11 @@ void Head::reset() {
|
|||
_baseYaw = _basePitch = _baseRoll = 0.0f;
|
||||
}
|
||||
|
||||
void Head::simulate(float deltaTime) {
|
||||
const float NORMAL_HZ = 60.0f; // the update rate the constant values were tuned for
|
||||
|
||||
void Head::computeAudioLoudness(float deltaTime) {
|
||||
// grab the audio loudness from the owning avatar, if we have one
|
||||
float audioLoudness = 0.0f;
|
||||
float audioLoudness = _owningAvatar ? _owningAvatar->getAudioLoudness() : 0.0f;
|
||||
|
||||
if (_owningAvatar) {
|
||||
audioLoudness = _owningAvatar->getAudioLoudness();
|
||||
}
|
||||
|
||||
// Update audio trailing average for rendering facial animations
|
||||
// Update audio trailing average for rendering facial animations
|
||||
const float AUDIO_AVERAGING_SECS = 0.05f;
|
||||
const float AUDIO_LONG_TERM_AVERAGING_SECS = 30.0f;
|
||||
_averageLoudness = glm::mix(_averageLoudness, audioLoudness, glm::min(deltaTime / AUDIO_AVERAGING_SECS, 1.0f));
|
||||
|
@ -63,116 +58,114 @@ void Head::simulate(float deltaTime) {
|
|||
_longTermAverageLoudness = glm::mix(_longTermAverageLoudness, _averageLoudness, glm::min(deltaTime / AUDIO_LONG_TERM_AVERAGING_SECS, 1.0f));
|
||||
}
|
||||
|
||||
if (!_isFaceTrackerConnected) {
|
||||
if (!_isEyeTrackerConnected) {
|
||||
// Update eye saccades
|
||||
const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f;
|
||||
const float AVERAGE_SACCADE_INTERVAL = 6.0f;
|
||||
const float MICROSACCADE_MAGNITUDE = 0.002f;
|
||||
const float SACCADE_MAGNITUDE = 0.04f;
|
||||
const float NOMINAL_FRAME_RATE = 60.0f;
|
||||
float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz
|
||||
_audioAttack = audioAttackAveragingRate * _audioAttack +
|
||||
(1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness);
|
||||
_lastLoudness = (audioLoudness - _longTermAverageLoudness);
|
||||
}
|
||||
|
||||
if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) {
|
||||
_saccadeTarget = MICROSACCADE_MAGNITUDE * randVector();
|
||||
} else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) {
|
||||
_saccadeTarget = SACCADE_MAGNITUDE * randVector();
|
||||
}
|
||||
_saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime);
|
||||
} else {
|
||||
_saccade = glm::vec3();
|
||||
}
|
||||
void Head::computeEyeMovement(float deltaTime) {
|
||||
// Update eye saccades
|
||||
const float AVERAGE_MICROSACCADE_INTERVAL = 1.0f;
|
||||
const float AVERAGE_SACCADE_INTERVAL = 6.0f;
|
||||
const float MICROSACCADE_MAGNITUDE = 0.002f;
|
||||
const float SACCADE_MAGNITUDE = 0.04f;
|
||||
const float NOMINAL_FRAME_RATE = 60.0f;
|
||||
|
||||
// Detect transition from talking to not; force blink after that and a delay
|
||||
bool forceBlink = false;
|
||||
const float TALKING_LOUDNESS = 100.0f;
|
||||
const float BLINK_AFTER_TALKING = 0.25f;
|
||||
_timeWithoutTalking += deltaTime;
|
||||
if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) {
|
||||
_timeWithoutTalking = 0.0f;
|
||||
} else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
|
||||
forceBlink = true;
|
||||
}
|
||||
if (randFloat() < deltaTime / AVERAGE_MICROSACCADE_INTERVAL) {
|
||||
_saccadeTarget = MICROSACCADE_MAGNITUDE * randVector();
|
||||
} else if (randFloat() < deltaTime / AVERAGE_SACCADE_INTERVAL) {
|
||||
_saccadeTarget = SACCADE_MAGNITUDE * randVector();
|
||||
}
|
||||
_saccade += (_saccadeTarget - _saccade) * pow(0.5f, NOMINAL_FRAME_RATE * deltaTime);
|
||||
|
||||
// Update audio attack data for facial animation (eyebrows and mouth)
|
||||
float audioAttackAveragingRate = (10.0f - deltaTime * NORMAL_HZ) / 10.0f; // --> 0.9 at 60 Hz
|
||||
_audioAttack = audioAttackAveragingRate * _audioAttack +
|
||||
(1.0f - audioAttackAveragingRate) * fabs((audioLoudness - _longTermAverageLoudness) - _lastLoudness);
|
||||
_lastLoudness = (audioLoudness - _longTermAverageLoudness);
|
||||
// Detect transition from talking to not; force blink after that and a delay
|
||||
bool forceBlink = false;
|
||||
const float TALKING_LOUDNESS = 100.0f;
|
||||
const float BLINK_AFTER_TALKING = 0.25f;
|
||||
_timeWithoutTalking += deltaTime;
|
||||
if ((_averageLoudness - _longTermAverageLoudness) > TALKING_LOUDNESS) {
|
||||
_timeWithoutTalking = 0.0f;
|
||||
} else if (_timeWithoutTalking - deltaTime < BLINK_AFTER_TALKING && _timeWithoutTalking >= BLINK_AFTER_TALKING) {
|
||||
forceBlink = true;
|
||||
}
|
||||
|
||||
const float BROW_LIFT_THRESHOLD = 100.0f;
|
||||
if (_audioAttack > BROW_LIFT_THRESHOLD) {
|
||||
_browAudioLift += sqrtf(_audioAttack) * 0.01f;
|
||||
}
|
||||
_browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f);
|
||||
|
||||
const float BLINK_SPEED = 10.0f;
|
||||
const float BLINK_SPEED_VARIABILITY = 1.0f;
|
||||
const float BLINK_START_VARIABILITY = 0.25f;
|
||||
const float FULLY_OPEN = 0.0f;
|
||||
const float FULLY_CLOSED = 1.0f;
|
||||
if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) {
|
||||
// no blinking when brows are raised; blink less with increasing loudness
|
||||
const float BASE_BLINK_RATE = 15.0f / 60.0f;
|
||||
const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f;
|
||||
if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) *
|
||||
ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) {
|
||||
_leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
|
||||
_rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
|
||||
if (randFloat() < 0.5f) {
|
||||
_leftEyeBlink = BLINK_START_VARIABILITY;
|
||||
} else {
|
||||
_rightEyeBlink = BLINK_START_VARIABILITY;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
|
||||
_rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
|
||||
|
||||
if (_leftEyeBlink == FULLY_CLOSED) {
|
||||
_leftEyeBlinkVelocity = -BLINK_SPEED;
|
||||
|
||||
} else if (_leftEyeBlink == FULLY_OPEN) {
|
||||
_leftEyeBlinkVelocity = 0.0f;
|
||||
}
|
||||
if (_rightEyeBlink == FULLY_CLOSED) {
|
||||
_rightEyeBlinkVelocity = -BLINK_SPEED;
|
||||
|
||||
} else if (_rightEyeBlink == FULLY_OPEN) {
|
||||
_rightEyeBlinkVelocity = 0.0f;
|
||||
const float BLINK_SPEED = 10.0f;
|
||||
const float BLINK_SPEED_VARIABILITY = 1.0f;
|
||||
const float BLINK_START_VARIABILITY = 0.25f;
|
||||
const float FULLY_OPEN = 0.0f;
|
||||
const float FULLY_CLOSED = 1.0f;
|
||||
if (_leftEyeBlinkVelocity == 0.0f && _rightEyeBlinkVelocity == 0.0f) {
|
||||
// no blinking when brows are raised; blink less with increasing loudness
|
||||
const float BASE_BLINK_RATE = 15.0f / 60.0f;
|
||||
const float ROOT_LOUDNESS_TO_BLINK_INTERVAL = 0.25f;
|
||||
if (forceBlink || (_browAudioLift < EPSILON && shouldDo(glm::max(1.0f, sqrt(fabs(_averageLoudness - _longTermAverageLoudness)) *
|
||||
ROOT_LOUDNESS_TO_BLINK_INTERVAL) / BASE_BLINK_RATE, deltaTime))) {
|
||||
_leftEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
|
||||
_rightEyeBlinkVelocity = BLINK_SPEED + randFloat() * BLINK_SPEED_VARIABILITY;
|
||||
if (randFloat() < 0.5f) {
|
||||
_leftEyeBlink = BLINK_START_VARIABILITY;
|
||||
} else {
|
||||
_rightEyeBlink = BLINK_START_VARIABILITY;
|
||||
}
|
||||
}
|
||||
|
||||
// use data to update fake Faceshift blendshape coefficients
|
||||
calculateMouthShapes(deltaTime);
|
||||
FaceTracker::updateFakeCoefficients(_leftEyeBlink,
|
||||
_rightEyeBlink,
|
||||
_browAudioLift,
|
||||
_audioJawOpen,
|
||||
_mouth2,
|
||||
_mouth3,
|
||||
_mouth4,
|
||||
_transientBlendshapeCoefficients);
|
||||
|
||||
applyEyelidOffset(getOrientation());
|
||||
|
||||
} else {
|
||||
_saccade = glm::vec3();
|
||||
}
|
||||
if (fixGaze) { // if debug menu turns off, use no saccade
|
||||
_saccade = glm::vec3();
|
||||
_leftEyeBlink = glm::clamp(_leftEyeBlink + _leftEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
|
||||
_rightEyeBlink = glm::clamp(_rightEyeBlink + _rightEyeBlinkVelocity * deltaTime, FULLY_OPEN, FULLY_CLOSED);
|
||||
|
||||
if (_leftEyeBlink == FULLY_CLOSED) {
|
||||
_leftEyeBlinkVelocity = -BLINK_SPEED;
|
||||
|
||||
} else if (_leftEyeBlink == FULLY_OPEN) {
|
||||
_leftEyeBlinkVelocity = 0.0f;
|
||||
}
|
||||
if (_rightEyeBlink == FULLY_CLOSED) {
|
||||
_rightEyeBlinkVelocity = -BLINK_SPEED;
|
||||
|
||||
} else if (_rightEyeBlink == FULLY_OPEN) {
|
||||
_rightEyeBlinkVelocity = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
applyEyelidOffset(getOrientation());
|
||||
}
|
||||
|
||||
void Head::computeFaceMovement(float deltaTime) {
|
||||
// Update audio attack data for facial animation (eyebrows and mouth)
|
||||
const float BROW_LIFT_THRESHOLD = 100.0f;
|
||||
if (_audioAttack > BROW_LIFT_THRESHOLD) {
|
||||
_browAudioLift += sqrtf(_audioAttack) * 0.01f;
|
||||
}
|
||||
_browAudioLift = glm::clamp(_browAudioLift *= 0.7f, 0.0f, 1.0f);
|
||||
|
||||
// use data to update fake Faceshift blendshape coefficients
|
||||
calculateMouthShapes(deltaTime);
|
||||
FaceTracker::updateFakeCoefficients(_leftEyeBlink,
|
||||
_rightEyeBlink,
|
||||
_browAudioLift,
|
||||
_audioJawOpen,
|
||||
_mouth2,
|
||||
_mouth3,
|
||||
_mouth4,
|
||||
_transientBlendshapeCoefficients);
|
||||
}
|
||||
|
||||
void Head::computeEyePosition() {
|
||||
_leftEyePosition = _rightEyePosition = getPosition();
|
||||
_eyePosition = getPosition();
|
||||
|
||||
if (_owningAvatar) {
|
||||
auto skeletonModel = static_cast<Avatar*>(_owningAvatar)->getSkeletonModel();
|
||||
if (skeletonModel) {
|
||||
skeletonModel->getEyePositions(_leftEyePosition, _rightEyePosition);
|
||||
}
|
||||
}
|
||||
_eyePosition = 0.5f * (_leftEyePosition + _rightEyePosition);
|
||||
}
|
||||
|
||||
_eyePosition = calculateAverageEyePosition();
|
||||
void Head::simulate(float deltaTime) {
|
||||
computeAudioLoudness(deltaTime);
|
||||
computeFaceMovement(deltaTime);
|
||||
computeEyeMovement(deltaTime);
|
||||
computeEyePosition();
|
||||
}
|
||||
|
||||
void Head::calculateMouthShapes(float deltaTime) {
|
||||
|
|
|
@ -83,7 +83,10 @@ public:
|
|||
float getTimeWithoutTalking() const { return _timeWithoutTalking; }
|
||||
|
||||
protected:
|
||||
glm::vec3 calculateAverageEyePosition() const { return _leftEyePosition + (_rightEyePosition - _leftEyePosition ) * 0.5f; }
|
||||
void computeAudioLoudness(float deltaTime);
|
||||
void computeEyeMovement(float deltaTime);
|
||||
void computeFaceMovement(float deltaTime);
|
||||
void computeEyePosition();
|
||||
|
||||
// disallow copies of the Head, copy of owning Avatar is disallowed too
|
||||
Head(const Head&);
|
||||
|
|
|
@ -52,6 +52,7 @@ const QString AvatarData::FRAME_NAME = "com.highfidelity.recording.AvatarData";
|
|||
static const int TRANSLATION_COMPRESSION_RADIX = 12;
|
||||
static const int SENSOR_TO_WORLD_SCALE_RADIX = 10;
|
||||
static const float AUDIO_LOUDNESS_SCALE = 1024.0f;
|
||||
static const float DEFAULT_AVATAR_DENSITY = 1000.0f; // density of water
|
||||
|
||||
#define ASSERT(COND) do { if (!(COND)) { abort(); } } while(0)
|
||||
|
||||
|
@ -65,7 +66,8 @@ AvatarData::AvatarData() :
|
|||
_headData(NULL),
|
||||
_errorLogExpiry(0),
|
||||
_owningAvatarMixer(),
|
||||
_targetVelocity(0.0f)
|
||||
_targetVelocity(0.0f),
|
||||
_density(DEFAULT_AVATAR_DENSITY)
|
||||
{
|
||||
setBodyPitch(0.0f);
|
||||
setBodyYaw(-90.0f);
|
||||
|
@ -170,13 +172,13 @@ QByteArray AvatarData::toByteArrayStateful(AvatarDataDetail dataDetail) {
|
|||
AvatarDataPacket::HasFlags hasFlagsOut;
|
||||
auto lastSentTime = _lastToByteArray;
|
||||
_lastToByteArray = usecTimestampNow();
|
||||
return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(),
|
||||
return AvatarData::toByteArray(dataDetail, lastSentTime, getLastSentJointData(),
|
||||
hasFlagsOut, false, false, glm::vec3(0), nullptr,
|
||||
&_outboundDataRate);
|
||||
}
|
||||
|
||||
QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||
AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust,
|
||||
AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust,
|
||||
glm::vec3 viewerPosition, QVector<JointData>* sentJointDataOut, AvatarDataRate* outboundDataRateOut) const {
|
||||
|
||||
bool cullSmallChanges = (dataDetail == CullSmallData);
|
||||
|
@ -199,7 +201,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
|
||||
// FIXME -
|
||||
//
|
||||
// BUG -- if you enter a space bubble, and then back away, the avatar has wrong orientation until "send all" happens...
|
||||
// BUG -- if you enter a space bubble, and then back away, the avatar has wrong orientation until "send all" happens...
|
||||
// this is an iFrame issue... what to do about that?
|
||||
//
|
||||
// BUG -- Resizing avatar seems to "take too long"... the avatar doesn't redraw at smaller size right away
|
||||
|
@ -310,7 +312,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
|
||||
if (hasAvatarOrientation) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto localOrientation = getLocalOrientation();
|
||||
auto localOrientation = getOrientationOutbound();
|
||||
destinationBuffer += packOrientationQuatToSixBytes(destinationBuffer, localOrientation);
|
||||
|
||||
int numBytes = destinationBuffer - startSection;
|
||||
|
@ -445,7 +447,7 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent
|
|||
if (hasFaceTrackerInfo) {
|
||||
auto startSection = destinationBuffer;
|
||||
auto faceTrackerInfo = reinterpret_cast<AvatarDataPacket::FaceTrackerInfo*>(destinationBuffer);
|
||||
auto blendshapeCoefficients = _headData->getSummedBlendshapeCoefficients();
|
||||
const auto& blendshapeCoefficients = _headData->getSummedBlendshapeCoefficients();
|
||||
|
||||
faceTrackerInfo->leftEyeBlink = _headData->_leftEyeBlink;
|
||||
faceTrackerInfo->rightEyeBlink = _headData->_rightEyeBlink;
|
||||
|
@ -1471,13 +1473,13 @@ QStringList AvatarData::getJointNames() const {
|
|||
void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut) {
|
||||
QDataStream packetStream(data);
|
||||
|
||||
packetStream >> identityOut.uuid
|
||||
>> identityOut.skeletonModelURL
|
||||
packetStream >> identityOut.uuid
|
||||
>> identityOut.skeletonModelURL
|
||||
>> identityOut.attachmentData
|
||||
>> identityOut.displayName
|
||||
>> identityOut.sessionDisplayName
|
||||
>> identityOut.avatarEntityData
|
||||
>> identityOut.updatedAt;
|
||||
>> identityOut.sequenceId;
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qCDebug(avatars) << __FUNCTION__
|
||||
|
@ -1489,22 +1491,25 @@ void AvatarData::parseAvatarIdentityPacket(const QByteArray& data, Identity& ide
|
|||
|
||||
}
|
||||
|
||||
glm::quat AvatarData::getOrientationOutbound() const {
|
||||
return (getLocalOrientation());
|
||||
}
|
||||
|
||||
static const QUrl emptyURL("");
|
||||
QUrl AvatarData::cannonicalSkeletonModelURL(const QUrl& emptyURL) const {
|
||||
// We don't put file urls on the wire, but instead convert to empty.
|
||||
return _skeletonModelURL.scheme() == "file" ? emptyURL : _skeletonModelURL;
|
||||
}
|
||||
|
||||
void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, const qint64 clockSkew) {
|
||||
void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged) {
|
||||
|
||||
// Consider the case where this packet is being processed on Client A, and Client A is connected to Sandbox B.
|
||||
// If Client A's system clock is *ahead of* Sandbox B's system clock, "clockSkew" will be *negative*.
|
||||
// If Client A's system clock is *behind* Sandbox B's system clock, "clockSkew" will be *positive*.
|
||||
if ((_identityUpdatedAt > identity.updatedAt - clockSkew) && (_identityUpdatedAt != 0)) {
|
||||
qCDebug(avatars) << "Ignoring late identity packet for avatar " << getSessionUUID()
|
||||
<< "_identityUpdatedAt (" << _identityUpdatedAt << ") is greater than identity.updatedAt - clockSkew (" << identity.updatedAt << "-" << clockSkew << ")";
|
||||
if (identity.sequenceId < _identitySequenceId) {
|
||||
qCDebug(avatars) << "Ignoring older identity packet for avatar" << getSessionUUID()
|
||||
<< "_identitySequenceId (" << _identitySequenceId << ") is greater than" << identity.sequenceId;
|
||||
return;
|
||||
}
|
||||
// otherwise, set the identitySequenceId to match the incoming identity
|
||||
_identitySequenceId = identity.sequenceId;
|
||||
|
||||
if (_firstSkeletonCheck || (identity.skeletonModelURL != cannonicalSkeletonModelURL(emptyURL))) {
|
||||
setSkeletonModelURL(identity.skeletonModelURL);
|
||||
|
@ -1536,9 +1541,6 @@ void AvatarData::processAvatarIdentity(const Identity& identity, bool& identityC
|
|||
identityChanged = true;
|
||||
}
|
||||
|
||||
// use the timestamp from this identity, since we want to honor the updated times in "server clock"
|
||||
// this will overwrite any changes we made locally to this AvatarData's _identityUpdatedAt
|
||||
_identityUpdatedAt = identity.updatedAt - clockSkew;
|
||||
}
|
||||
|
||||
QByteArray AvatarData::identityByteArray() const {
|
||||
|
@ -1547,13 +1549,13 @@ QByteArray AvatarData::identityByteArray() const {
|
|||
const QUrl& urlToSend = cannonicalSkeletonModelURL(emptyURL); // depends on _skeletonModelURL
|
||||
|
||||
_avatarEntitiesLock.withReadLock([&] {
|
||||
identityStream << getSessionUUID()
|
||||
<< urlToSend
|
||||
identityStream << getSessionUUID()
|
||||
<< urlToSend
|
||||
<< _attachmentData
|
||||
<< _displayName
|
||||
<< getSessionDisplayNameForTransport() // depends on _sessionDisplayName
|
||||
<< _avatarEntityData
|
||||
<< _identityUpdatedAt;
|
||||
<< _identitySequenceId;
|
||||
});
|
||||
|
||||
return identityData;
|
||||
|
@ -1579,8 +1581,6 @@ void AvatarData::setDisplayName(const QString& displayName) {
|
|||
_displayName = displayName;
|
||||
_sessionDisplayName = "";
|
||||
|
||||
sendIdentityPacket();
|
||||
|
||||
qCDebug(avatars) << "Changing display name for avatar to" << displayName;
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
|
@ -2047,11 +2047,13 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
setSkeletonModelURL(bodyModelURL);
|
||||
}
|
||||
}
|
||||
|
||||
QString newDisplayName = "";
|
||||
if (json.contains(JSON_AVATAR_DISPLAY_NAME)) {
|
||||
auto newDisplayName = json[JSON_AVATAR_DISPLAY_NAME].toString();
|
||||
if (newDisplayName != getDisplayName()) {
|
||||
setDisplayName(newDisplayName);
|
||||
}
|
||||
newDisplayName = json[JSON_AVATAR_DISPLAY_NAME].toString();
|
||||
}
|
||||
if (newDisplayName != getDisplayName()) {
|
||||
setDisplayName(newDisplayName);
|
||||
}
|
||||
|
||||
auto currentBasis = getRecordingBasis();
|
||||
|
@ -2081,14 +2083,16 @@ void AvatarData::fromJson(const QJsonObject& json, bool useFrameSkeleton) {
|
|||
setTargetScale((float)json[JSON_AVATAR_SCALE].toDouble());
|
||||
}
|
||||
|
||||
QVector<AttachmentData> attachments;
|
||||
if (json.contains(JSON_AVATAR_ATTACHMENTS) && json[JSON_AVATAR_ATTACHMENTS].isArray()) {
|
||||
QJsonArray attachmentsJson = json[JSON_AVATAR_ATTACHMENTS].toArray();
|
||||
QVector<AttachmentData> attachments;
|
||||
for (auto attachmentJson : attachmentsJson) {
|
||||
AttachmentData attachment;
|
||||
attachment.fromJson(attachmentJson.toObject());
|
||||
attachments.push_back(attachment);
|
||||
}
|
||||
}
|
||||
if (attachments != getAttachmentData()) {
|
||||
setAttachmentData(attachments);
|
||||
}
|
||||
|
||||
|
@ -2464,11 +2468,11 @@ QScriptValue AvatarEntityMapToScriptValue(QScriptEngine* engine, const AvatarEnt
|
|||
if (!jsonEntityProperties.isObject()) {
|
||||
qCDebug(avatars) << "bad AvatarEntityData in AvatarEntityMap" << QString(entityProperties.toHex());
|
||||
}
|
||||
|
||||
|
||||
QVariant variantEntityProperties = jsonEntityProperties.toVariant();
|
||||
QVariantMap entityPropertiesMap = variantEntityProperties.toMap();
|
||||
QScriptValue scriptEntityProperties = variantMapToScriptValue(entityPropertiesMap, *engine);
|
||||
|
||||
|
||||
QString key = entityID.toString();
|
||||
obj.setProperty(key, scriptEntityProperties);
|
||||
}
|
||||
|
@ -2480,12 +2484,12 @@ void AvatarEntityMapFromScriptValue(const QScriptValue& object, AvatarEntityMap&
|
|||
while (itr.hasNext()) {
|
||||
itr.next();
|
||||
QUuid EntityID = QUuid(itr.name());
|
||||
|
||||
|
||||
QScriptValue scriptEntityProperties = itr.value();
|
||||
QVariant variantEntityProperties = scriptEntityProperties.toVariant();
|
||||
QJsonDocument jsonEntityProperties = QJsonDocument::fromVariant(variantEntityProperties);
|
||||
QByteArray binaryEntityProperties = jsonEntityProperties.toBinaryData();
|
||||
|
||||
|
||||
value[EntityID] = binaryEntityProperties;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -165,15 +165,15 @@ namespace AvatarDataPacket {
|
|||
const size_t AVATAR_ORIENTATION_SIZE = 6;
|
||||
|
||||
PACKED_BEGIN struct AvatarScale {
|
||||
SmallFloat scale; // avatar's scale, compressed by packFloatRatioToTwoByte()
|
||||
SmallFloat scale; // avatar's scale, compressed by packFloatRatioToTwoByte()
|
||||
} PACKED_END;
|
||||
const size_t AVATAR_SCALE_SIZE = 2;
|
||||
|
||||
PACKED_BEGIN struct LookAtPosition {
|
||||
float lookAtPosition[3]; // world space position that eyes are focusing on.
|
||||
// FIXME - unless the person has an eye tracker, this is simulated...
|
||||
// FIXME - unless the person has an eye tracker, this is simulated...
|
||||
// a) maybe we can just have the client calculate this
|
||||
// b) at distance this will be hard to discern and can likely be
|
||||
// b) at distance this will be hard to discern and can likely be
|
||||
// descimated or dropped completely
|
||||
//
|
||||
// POTENTIAL SAVINGS - 12 bytes
|
||||
|
@ -376,10 +376,10 @@ public:
|
|||
glm::vec3 getHandPosition() const;
|
||||
void setHandPosition(const glm::vec3& handPosition);
|
||||
|
||||
typedef enum {
|
||||
typedef enum {
|
||||
NoData,
|
||||
PALMinimum,
|
||||
MinimumData,
|
||||
MinimumData,
|
||||
CullSmallData,
|
||||
IncludeSmallData,
|
||||
SendAllData
|
||||
|
@ -388,7 +388,7 @@ public:
|
|||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail);
|
||||
|
||||
virtual QByteArray toByteArray(AvatarDataDetail dataDetail, quint64 lastSentTime, const QVector<JointData>& lastSentJointData,
|
||||
AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition,
|
||||
AvatarDataPacket::HasFlags& hasFlagsOut, bool dropFaceTracking, bool distanceAdjust, glm::vec3 viewerPosition,
|
||||
QVector<JointData>* sentJointDataOut, AvatarDataRate* outboundDataRateOut = nullptr) const;
|
||||
|
||||
virtual void doneEncoding(bool cullSmallChanges);
|
||||
|
@ -417,23 +417,23 @@ public:
|
|||
void nextAttitude(glm::vec3 position, glm::quat orientation); // Can be safely called at any time.
|
||||
virtual void updateAttitude() {} // Tell skeleton mesh about changes
|
||||
|
||||
glm::quat getHeadOrientation() const {
|
||||
glm::quat getHeadOrientation() const {
|
||||
lazyInitHeadData();
|
||||
return _headData->getOrientation();
|
||||
return _headData->getOrientation();
|
||||
}
|
||||
void setHeadOrientation(const glm::quat& orientation) {
|
||||
void setHeadOrientation(const glm::quat& orientation) {
|
||||
if (_headData) {
|
||||
_headData->setOrientation(orientation);
|
||||
}
|
||||
}
|
||||
|
||||
void setLookAtPosition(const glm::vec3& lookAtPosition) {
|
||||
void setLookAtPosition(const glm::vec3& lookAtPosition) {
|
||||
if (_headData) {
|
||||
_headData->setLookAtPosition(lookAtPosition);
|
||||
_headData->setLookAtPosition(lookAtPosition);
|
||||
}
|
||||
}
|
||||
|
||||
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) {
|
||||
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) {
|
||||
if (_headData) {
|
||||
_headData->setBlendshapeCoefficients(blendshapeCoefficients);
|
||||
}
|
||||
|
@ -470,7 +470,7 @@ public:
|
|||
|
||||
void setDomainMinimumScale(float domainMinimumScale)
|
||||
{ _domainMinimumScale = glm::clamp(domainMinimumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); }
|
||||
void setDomainMaximumScale(float domainMaximumScale)
|
||||
void setDomainMaximumScale(float domainMaximumScale)
|
||||
{ _domainMaximumScale = glm::clamp(domainMaximumScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE); _scaleChanged = usecTimestampNow(); }
|
||||
|
||||
// Hand State
|
||||
|
@ -531,14 +531,14 @@ public:
|
|||
QString displayName;
|
||||
QString sessionDisplayName;
|
||||
AvatarEntityMap avatarEntityData;
|
||||
quint64 updatedAt;
|
||||
quint64 sequenceId;
|
||||
};
|
||||
|
||||
static void parseAvatarIdentityPacket(const QByteArray& data, Identity& identityOut);
|
||||
|
||||
// identityChanged returns true if identity has changed, false otherwise.
|
||||
// displayNameChanged returns true if displayName has changed, false otherwise.
|
||||
void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged, const qint64 clockSkew);
|
||||
void processAvatarIdentity(const Identity& identity, bool& identityChanged, bool& displayNameChanged);
|
||||
|
||||
QByteArray identityByteArray() const;
|
||||
|
||||
|
@ -548,8 +548,8 @@ public:
|
|||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL);
|
||||
|
||||
virtual void setDisplayName(const QString& displayName);
|
||||
virtual void setSessionDisplayName(const QString& sessionDisplayName) {
|
||||
_sessionDisplayName = sessionDisplayName;
|
||||
virtual void setSessionDisplayName(const QString& sessionDisplayName) {
|
||||
_sessionDisplayName = sessionDisplayName;
|
||||
markIdentityDataChanged();
|
||||
}
|
||||
|
||||
|
@ -604,6 +604,9 @@ public:
|
|||
return _lastSentJointData;
|
||||
}
|
||||
|
||||
// A method intended to be overriden by MyAvatar for polling orientation for network transmission.
|
||||
virtual glm::quat getOrientationOutbound() const;
|
||||
|
||||
static const float OUT_OF_VIEW_PENALTY;
|
||||
|
||||
static void sortAvatars(
|
||||
|
@ -623,9 +626,11 @@ public:
|
|||
bool getIdentityDataChanged() const { return _identityDataChanged; } // has the identity data changed since the last time sendIdentityPacket() was called
|
||||
void markIdentityDataChanged() {
|
||||
_identityDataChanged = true;
|
||||
_identityUpdatedAt = usecTimestampNow();
|
||||
_identitySequenceId++;
|
||||
}
|
||||
|
||||
float getDensity() const { return _density; }
|
||||
|
||||
signals:
|
||||
void displayNameChanged();
|
||||
|
||||
|
@ -781,7 +786,8 @@ protected:
|
|||
float _audioAverageLoudness { 0.0f };
|
||||
|
||||
bool _identityDataChanged { false };
|
||||
quint64 _identityUpdatedAt { 0 };
|
||||
quint64 _identitySequenceId { 0 };
|
||||
float _density;
|
||||
|
||||
private:
|
||||
friend void avatarStateFromFrame(const QByteArray& frameData, AvatarData* _avatar);
|
||||
|
|
|
@ -149,7 +149,7 @@ void AvatarHashMap::processAvatarIdentityPacket(QSharedPointer<ReceivedMessage>
|
|||
bool identityChanged = false;
|
||||
bool displayNameChanged = false;
|
||||
// In this case, the "sendingNode" is the Avatar Mixer.
|
||||
avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged, sendingNode->getClockSkewUsec());
|
||||
avatar->processAvatarIdentity(identity, identityChanged, displayNameChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,12 +28,6 @@ HeadData::HeadData(AvatarData* owningAvatar) :
|
|||
_basePitch(0.0f),
|
||||
_baseRoll(0.0f),
|
||||
_lookAtPosition(0.0f, 0.0f, 0.0f),
|
||||
_isFaceTrackerConnected(false),
|
||||
_isEyeTrackerConnected(false),
|
||||
_leftEyeBlink(0.0f),
|
||||
_rightEyeBlink(0.0f),
|
||||
_averageLoudness(0.0f),
|
||||
_browAudioLift(0.0f),
|
||||
_blendshapeCoefficients(QVector<float>(0, 0.0f)),
|
||||
_transientBlendshapeCoefficients(QVector<float>(0, 0.0f)),
|
||||
_summedBlendshapeCoefficients(QVector<float>(0, 0.0f)),
|
||||
|
|
|
@ -63,7 +63,7 @@ public:
|
|||
void setBlendshapeCoefficients(const QVector<float>& blendshapeCoefficients) { _blendshapeCoefficients = blendshapeCoefficients; }
|
||||
|
||||
const glm::vec3& getLookAtPosition() const { return _lookAtPosition; }
|
||||
void setLookAtPosition(const glm::vec3& lookAtPosition) {
|
||||
void setLookAtPosition(const glm::vec3& lookAtPosition) {
|
||||
if (_lookAtPosition != lookAtPosition) {
|
||||
_lookAtPositionChanged = usecTimestampNow();
|
||||
}
|
||||
|
@ -85,12 +85,12 @@ protected:
|
|||
glm::vec3 _lookAtPosition;
|
||||
quint64 _lookAtPositionChanged { 0 };
|
||||
|
||||
bool _isFaceTrackerConnected;
|
||||
bool _isEyeTrackerConnected;
|
||||
float _leftEyeBlink;
|
||||
float _rightEyeBlink;
|
||||
float _averageLoudness;
|
||||
float _browAudioLift;
|
||||
bool _isFaceTrackerConnected { false };
|
||||
bool _isEyeTrackerConnected { false };
|
||||
float _leftEyeBlink { 0.0f };
|
||||
float _rightEyeBlink { 0.0f };
|
||||
float _averageLoudness { 0.0f };
|
||||
float _browAudioLift { 0.0f };
|
||||
|
||||
QVector<float> _blendshapeCoefficients;
|
||||
QVector<float> _transientBlendshapeCoefficients;
|
||||
|
|
|
@ -43,14 +43,14 @@
|
|||
| |
|
||||
+--------------------+ +-----------------------+
|
||||
| | | |
|
||||
| ObjectActionSpring | | ObjectConstraintHinge |
|
||||
| ObjectActionTractor| | ObjectConstraintHinge |
|
||||
| (physics) | | (physics) |
|
||||
+--------------------+ +-----------------------+
|
||||
|
||||
|
||||
|
||||
A dynamic is a callback which is registered with bullet. A dynamic is called-back every physics
|
||||
simulation step and can do whatever it wants with the various datastructures it has available. An
|
||||
simulation step and can do whatever it wants with the various datastructures it has available. A
|
||||
dynamic, for example, can pull an EntityItem toward a point as if that EntityItem were connected to that
|
||||
point by a spring.
|
||||
|
||||
|
@ -60,7 +60,7 @@ script or when receiving information via an EntityTree data-stream (either over
|
|||
svo file).
|
||||
|
||||
In the interface, if an EntityItem has dynamics, this EntityItem will have pointers to ObjectDynamic
|
||||
subclass (like ObjectDynamicSpring) instantiations. Code in the entities library affects a dynamic-object
|
||||
subclass (like ObjectDynamicTractor) instantiations. Code in the entities library affects a dynamic-object
|
||||
via the EntityDynamicInterface (which knows nothing about bullet). When the ObjectDynamic subclass
|
||||
instance is created, it is registered as a dynamic with bullet. Bullet will call into code in this
|
||||
instance with the btDynamicInterface every physics-simulation step.
|
||||
|
@ -75,11 +75,11 @@ right now the AssignmentDynamic class is a place-holder.
|
|||
|
||||
The dynamic-objects are instantiated by singleton (dependecy) subclasses of EntityDynamicFactoryInterface.
|
||||
In the interface, the subclass is an InterfaceDynamicFactory and it will produce things like
|
||||
ObjectDynamicSpring. In an entity-server the subclass is an AssignmentDynamicFactory and it always
|
||||
ObjectDynamicTractor. In an entity-server the subclass is an AssignmentDynamicFactory and it always
|
||||
produces AssignmentDynamics.
|
||||
|
||||
Depending on the dynamic's type, it will have various arguments. When a script changes an argument of an
|
||||
dynamic, the argument-holding member-variables of ObjectDynamicSpring (in this example) are updated and
|
||||
dynamic, the argument-holding member-variables of ObjectDynamicTractor (in this example) are updated and
|
||||
also serialized into _dynamicData in the EntityItem. Each subclass of ObjectDynamic knows how to serialize
|
||||
and deserialize its own arguments. _dynamicData is what gets sent over the wire or saved in an svo file.
|
||||
When a packet-reader receives data for _dynamicData, it will save it in the EntityItem; this causes the
|
||||
|
@ -103,7 +103,7 @@ EntityDynamicType EntityDynamicInterface::dynamicTypeFromString(QString dynamicT
|
|||
return DYNAMIC_TYPE_OFFSET;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "spring") {
|
||||
return DYNAMIC_TYPE_SPRING;
|
||||
return DYNAMIC_TYPE_TRACTOR;
|
||||
}
|
||||
if (normalizedDynamicTypeString == "tractor") {
|
||||
return DYNAMIC_TYPE_TRACTOR;
|
||||
|
@ -142,7 +142,6 @@ QString EntityDynamicInterface::dynamicTypeToString(EntityDynamicType dynamicTyp
|
|||
case DYNAMIC_TYPE_OFFSET:
|
||||
return "offset";
|
||||
case DYNAMIC_TYPE_SPRING:
|
||||
return "spring";
|
||||
case DYNAMIC_TYPE_TRACTOR:
|
||||
return "tractor";
|
||||
case DYNAMIC_TYPE_HOLD:
|
||||
|
|
|
@ -94,15 +94,6 @@ public:
|
|||
QString argumentName, bool& ok, bool required = true);
|
||||
|
||||
protected:
|
||||
virtual glm::vec3 getPosition() = 0;
|
||||
// virtual void setPosition(glm::vec3 position) = 0;
|
||||
virtual glm::quat getRotation() = 0;
|
||||
// virtual void setRotation(glm::quat rotation) = 0;
|
||||
virtual glm::vec3 getLinearVelocity() = 0;
|
||||
virtual void setLinearVelocity(glm::vec3 linearVelocity) = 0;
|
||||
virtual glm::vec3 getAngularVelocity() = 0;
|
||||
virtual void setAngularVelocity(glm::vec3 angularVelocity) = 0;
|
||||
|
||||
QUuid _id;
|
||||
EntityDynamicType _type;
|
||||
bool _active { false };
|
||||
|
|
|
@ -1691,14 +1691,20 @@ void EntityItem::updateVelocity(const glm::vec3& value) {
|
|||
setLocalVelocity(Vectors::ZERO);
|
||||
}
|
||||
} else {
|
||||
const float MIN_LINEAR_SPEED = 0.001f;
|
||||
if (glm::length(value) < MIN_LINEAR_SPEED) {
|
||||
velocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else {
|
||||
velocity = value;
|
||||
float speed = glm::length(value);
|
||||
if (!glm::isnan(speed)) {
|
||||
const float MIN_LINEAR_SPEED = 0.001f;
|
||||
const float MAX_LINEAR_SPEED = 270.0f; // 3m per step at 90Hz
|
||||
if (speed < MIN_LINEAR_SPEED) {
|
||||
velocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else if (speed > MAX_LINEAR_SPEED) {
|
||||
velocity = (MAX_LINEAR_SPEED / speed) * value;
|
||||
} else {
|
||||
velocity = value;
|
||||
}
|
||||
setLocalVelocity(velocity);
|
||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||
}
|
||||
setLocalVelocity(velocity);
|
||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1723,8 +1729,16 @@ void EntityItem::updateGravity(const glm::vec3& value) {
|
|||
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||
_gravity = Vectors::ZERO;
|
||||
} else {
|
||||
_gravity = value;
|
||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||
float magnitude = glm::length(value);
|
||||
if (!glm::isnan(magnitude)) {
|
||||
const float MAX_ACCELERATION_OF_GRAVITY = 10.0f * 9.8f; // 10g
|
||||
if (magnitude > MAX_ACCELERATION_OF_GRAVITY) {
|
||||
_gravity = (MAX_ACCELERATION_OF_GRAVITY / magnitude) * value;
|
||||
} else {
|
||||
_gravity = value;
|
||||
}
|
||||
_dirtyFlags |= Simulation::DIRTY_LINEAR_VELOCITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1735,14 +1749,20 @@ void EntityItem::updateAngularVelocity(const glm::vec3& value) {
|
|||
if (getShapeType() == SHAPE_TYPE_STATIC_MESH) {
|
||||
setLocalAngularVelocity(Vectors::ZERO);
|
||||
} else {
|
||||
const float MIN_ANGULAR_SPEED = 0.0002f;
|
||||
if (glm::length(value) < MIN_ANGULAR_SPEED) {
|
||||
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else {
|
||||
angularVelocity = value;
|
||||
float speed = glm::length(value);
|
||||
if (!glm::isnan(speed)) {
|
||||
const float MIN_ANGULAR_SPEED = 0.0002f;
|
||||
const float MAX_ANGULAR_SPEED = 9.0f * TWO_PI; // 1/10 rotation per step at 90Hz
|
||||
if (speed < MIN_ANGULAR_SPEED) {
|
||||
angularVelocity = ENTITY_ITEM_ZERO_VEC3;
|
||||
} else if (speed > MAX_ANGULAR_SPEED) {
|
||||
angularVelocity = (MAX_ANGULAR_SPEED / speed) * value;
|
||||
} else {
|
||||
angularVelocity = value;
|
||||
}
|
||||
setLocalAngularVelocity(angularVelocity);
|
||||
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
|
||||
}
|
||||
setLocalAngularVelocity(angularVelocity);
|
||||
_dirtyFlags |= Simulation::DIRTY_ANGULAR_VELOCITY;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -662,6 +662,25 @@ QVector<QUuid> EntityScriptingInterface::findEntitiesInFrustum(QVariantMap frust
|
|||
return result;
|
||||
}
|
||||
|
||||
QVector<QUuid> EntityScriptingInterface::findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const {
|
||||
EntityTypes::EntityType type = EntityTypes::getEntityTypeFromName(entityType);
|
||||
|
||||
QVector<QUuid> result;
|
||||
if (_entityTree) {
|
||||
QVector<EntityItemPointer> entities;
|
||||
_entityTree->withReadLock([&] {
|
||||
_entityTree->findEntities(center, radius, entities);
|
||||
});
|
||||
|
||||
foreach(EntityItemPointer entity, entities) {
|
||||
if (entity->getType() == type) {
|
||||
result << entity->getEntityItemID();
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
RayToEntityIntersectionResult EntityScriptingInterface::findRayIntersection(const PickRay& ray, bool precisionPicking,
|
||||
const QScriptValue& entityIdsToInclude, const QScriptValue& entityIdsToDiscard, bool visibleOnly, bool collidableOnly) {
|
||||
PROFILE_RANGE(script_entities, __FUNCTION__);
|
||||
|
|
|
@ -212,9 +212,16 @@ public slots:
|
|||
/// - orientation
|
||||
/// - projection
|
||||
/// - centerRadius
|
||||
/// this function will not find any models in script engine contexts which don't have access to models
|
||||
/// this function will not find any models in script engine contexts which don't have access to entities
|
||||
Q_INVOKABLE QVector<QUuid> findEntitiesInFrustum(QVariantMap frustum) const;
|
||||
|
||||
/// finds entities of the indicated type within a sphere given by the center point and radius
|
||||
/// @param {QString} string representation of entity type
|
||||
/// @param {vec3} center point
|
||||
/// @param {float} radius to search
|
||||
/// this function will not find any entities in script engine contexts which don't have access to entities
|
||||
Q_INVOKABLE QVector<QUuid> findEntitiesByType(const QString entityType, const glm::vec3& center, float radius) const;
|
||||
|
||||
/// If the scripting context has visible entities, this will determine a ray intersection, the results
|
||||
/// may be inaccurate if the engine is unable to access the visible entities, in which case result.accurate
|
||||
/// will be false.
|
||||
|
|
|
@ -452,6 +452,13 @@ void EntityTree::deleteEntity(const EntityItemID& entityID, bool force, bool ign
|
|||
|
||||
// NOTE: callers must lock the tree before using this method
|
||||
DeleteEntityOperator theOperator(getThisPointer(), entityID);
|
||||
|
||||
existingEntity->forEachDescendant([&](SpatiallyNestablePointer descendant) {
|
||||
auto descendantID = descendant->getID();
|
||||
theOperator.addEntityIDToDeleteList(descendantID);
|
||||
emit deletingEntity(descendantID);
|
||||
});
|
||||
|
||||
recurseTreeWithOperator(&theOperator);
|
||||
processRemovedEntities(theOperator);
|
||||
_isDirty = true;
|
||||
|
|
|
@ -441,7 +441,11 @@ void Texture::assignStoredMip(uint16 level, storage::StoragePointer& storage) {
|
|||
// THen check that the mem texture passed make sense with its format
|
||||
Size expectedSize = evalStoredMipSize(level, getStoredMipFormat());
|
||||
auto size = storage->size();
|
||||
if (storage->size() <= expectedSize) {
|
||||
// NOTE: doing the same thing in all the next block but beeing able to breakpoint with more accuracy
|
||||
if (storage->size() < expectedSize) {
|
||||
_storage->assignMipData(level, storage);
|
||||
_stamp++;
|
||||
} else if (size == expectedSize) {
|
||||
_storage->assignMipData(level, storage);
|
||||
_stamp++;
|
||||
} else if (size > expectedSize) {
|
||||
|
@ -468,7 +472,11 @@ void Texture::assignStoredMipFace(uint16 level, uint8 face, storage::StoragePoin
|
|||
// THen check that the mem texture passed make sense with its format
|
||||
Size expectedSize = evalStoredMipFaceSize(level, getStoredMipFormat());
|
||||
auto size = storage->size();
|
||||
if (size <= expectedSize) {
|
||||
// NOTE: doing the same thing in all the next block but beeing able to breakpoint with more accuracy
|
||||
if (size < expectedSize) {
|
||||
_storage->assignMipFaceData(level, face, storage);
|
||||
_stamp++;
|
||||
} else if (size == expectedSize) {
|
||||
_storage->assignMipFaceData(level, face, storage);
|
||||
_stamp++;
|
||||
} else if (size > expectedSize) {
|
||||
|
|
|
@ -210,7 +210,16 @@ PixelsPointer KtxStorage::getMipFace(uint16 level, uint8 face) const {
|
|||
auto faceSize = _ktxDescriptor->getMipFaceTexelsSize(level, face);
|
||||
if (faceSize != 0 && faceOffset != 0) {
|
||||
auto file = maybeOpenFile();
|
||||
result = file->createView(faceSize, faceOffset)->toMemoryStorage();
|
||||
if (file) {
|
||||
auto storageView = file->createView(faceSize, faceOffset);
|
||||
if (storageView) {
|
||||
return storageView->toMemoryStorage();
|
||||
} else {
|
||||
qWarning() << "Failed to get a valid storageView for faceSize=" << faceSize << " faceOffset=" << faceOffset << "out of valid file " << QString::fromStdString(_filename);
|
||||
}
|
||||
} else {
|
||||
qWarning() << "Failed to get a valid file out of maybeOpenFile " << QString::fromStdString(_filename);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
@ -238,8 +247,9 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
|
|||
throw std::runtime_error("Invalid level");
|
||||
}
|
||||
|
||||
if (storage->size() != _ktxDescriptor->images[level]._imageSize) {
|
||||
qWarning() << "Invalid image size: " << storage->size() << ", expected: " << _ktxDescriptor->images[level]._imageSize
|
||||
auto& imageDesc = _ktxDescriptor->images[level];
|
||||
if (storage->size() != imageDesc._imageSize) {
|
||||
qWarning() << "Invalid image size: " << storage->size() << ", expected: " << imageDesc._imageSize
|
||||
<< ", level: " << level << ", filename: " << QString::fromStdString(_filename);
|
||||
return;
|
||||
}
|
||||
|
@ -258,7 +268,7 @@ void KtxStorage::assignMipData(uint16 level, const storage::StoragePointer& stor
|
|||
return;
|
||||
}
|
||||
|
||||
memcpy(imageData, storage->data(), _ktxDescriptor->images[level]._imageSize);
|
||||
memcpy(imageData, storage->data(), storage->size());
|
||||
_minMipLevelAvailable = level;
|
||||
if (_offsetToMinMipKV > 0) {
|
||||
auto minMipKeyData = file->mutableData() + ktx::KTX_HEADER_SIZE + _offsetToMinMipKV;
|
||||
|
@ -542,6 +552,13 @@ bool Texture::evalTextureFormat(const ktx::Header& header, Element& mipFormat, E
|
|||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (header.getGLFormat() == ktx::GLFormat::RG && header.getGLType() == ktx::GLType::UNSIGNED_BYTE && header.getTypeSize() == 1) {
|
||||
mipFormat = Format::VEC2NU8_XY;
|
||||
if (header.getGLInternaFormat_Uncompressed() == ktx::GLInternalFormat_Uncompressed::RG8) {
|
||||
texelFormat = Format::VEC2NU8_XY;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
} else if (header.getGLFormat() == ktx::GLFormat::COMPRESSED_FORMAT && header.getGLType() == ktx::GLType::COMPRESSED_TYPE) {
|
||||
if (header.getGLInternaFormat_Compressed() == ktx::GLInternalFormat_Compressed::COMPRESSED_SRGB_S3TC_DXT1_EXT) {
|
||||
mipFormat = Format::COLOR_COMPRESSED_SRGB;
|
||||
|
|
|
@ -22,6 +22,9 @@ uint32_t Header::evalPadding(size_t byteSize) {
|
|||
return (uint32_t) (3 - (byteSize + 3) % PACKING_SIZE);// padding ? PACKING_SIZE - padding : 0);
|
||||
}
|
||||
|
||||
bool Header::checkAlignment(size_t byteSize) {
|
||||
return ((byteSize & 0x3) == 0);
|
||||
}
|
||||
|
||||
const Header::Identifier ktx::Header::IDENTIFIER {{
|
||||
0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
|
||||
|
@ -114,6 +117,9 @@ size_t Header::evalFaceSize(uint32_t level) const {
|
|||
}
|
||||
size_t Header::evalImageSize(uint32_t level) const {
|
||||
auto faceSize = evalFaceSize(level);
|
||||
if (!checkAlignment(faceSize)) {
|
||||
return 0;
|
||||
}
|
||||
if (numberOfFaces == NUM_CUBEMAPFACES && numberOfArrayElements == 0) {
|
||||
return faceSize;
|
||||
} else {
|
||||
|
@ -139,6 +145,9 @@ ImageDescriptors Header::generateImageDescriptors() const {
|
|||
size_t imageOffset = 0;
|
||||
for (uint32_t level = 0; level < numberOfMipmapLevels; ++level) {
|
||||
auto imageSize = static_cast<uint32_t>(evalImageSize(level));
|
||||
if (!checkAlignment(imageSize)) {
|
||||
return ImageDescriptors();
|
||||
}
|
||||
if (imageSize == 0) {
|
||||
return ImageDescriptors();
|
||||
}
|
||||
|
|
|
@ -38,15 +38,15 @@ UInt32 numberOfArrayElements
|
|||
UInt32 numberOfFaces
|
||||
UInt32 numberOfMipmapLevels
|
||||
UInt32 bytesOfKeyValueData
|
||||
|
||||
|
||||
for each keyValuePair that fits in bytesOfKeyValueData
|
||||
UInt32 keyAndValueByteSize
|
||||
Byte keyAndValue[keyAndValueByteSize]
|
||||
Byte valuePadding[3 - ((keyAndValueByteSize + 3) % 4)]
|
||||
end
|
||||
|
||||
|
||||
for each mipmap_level in numberOfMipmapLevels*
|
||||
UInt32 imageSize;
|
||||
UInt32 imageSize;
|
||||
for each array_element in numberOfArrayElements*
|
||||
for each face in numberOfFaces
|
||||
for each z_slice in pixelDepth*
|
||||
|
@ -269,7 +269,7 @@ namespace ktx {
|
|||
COMPRESSED_RG11_EAC = 0x9272,
|
||||
COMPRESSED_SIGNED_RG11_EAC = 0x9273,
|
||||
};
|
||||
|
||||
|
||||
enum class GLBaseInternalFormat : uint32_t {
|
||||
// GL 4.4 Table 8.11
|
||||
DEPTH_COMPONENT = 0x1902,
|
||||
|
@ -309,6 +309,7 @@ namespace ktx {
|
|||
static const uint32_t REVERSE_ENDIAN_TEST = 0x01020304;
|
||||
|
||||
static uint32_t evalPadding(size_t byteSize);
|
||||
static bool checkAlignment(size_t byteSize);
|
||||
|
||||
Header();
|
||||
|
||||
|
@ -418,9 +419,9 @@ namespace ktx {
|
|||
using FaceOffsets = std::vector<size_t>;
|
||||
using FaceBytes = std::vector<const Byte*>;
|
||||
|
||||
const uint32_t _numFaces;
|
||||
// This is the byte offset from the _start_ of the image region. For example, level 0
|
||||
// will have a byte offset of 0.
|
||||
const uint32_t _numFaces;
|
||||
const size_t _imageOffset;
|
||||
const uint32_t _imageSize;
|
||||
const uint32_t _faceSize;
|
||||
|
@ -465,7 +466,7 @@ namespace ktx {
|
|||
|
||||
class KTX;
|
||||
|
||||
// A KTX descriptor is a lightweight container for all the information about a serialized KTX file, but without the
|
||||
// A KTX descriptor is a lightweight container for all the information about a serialized KTX file, but without the
|
||||
// actual image / face data available.
|
||||
struct KTXDescriptor {
|
||||
KTXDescriptor(const Header& header, const KeyValues& keyValues, const ImageDescriptors& imageDescriptors) : header(header), keyValues(keyValues), images(imageDescriptors) {}
|
||||
|
@ -494,7 +495,7 @@ namespace ktx {
|
|||
// Instead of creating a full Copy of the src data in a KTX object, the write serialization can be performed with the
|
||||
// following two functions
|
||||
// size_t sizeNeeded = KTX::evalStorageSize(header, images);
|
||||
//
|
||||
//
|
||||
// //allocate a buffer of size "sizeNeeded" or map a file with enough capacity
|
||||
// Byte* destBytes = new Byte[sizeNeeded];
|
||||
//
|
||||
|
|
|
@ -148,12 +148,24 @@ namespace ktx {
|
|||
size_t imageSize = *reinterpret_cast<const uint32_t*>(currentPtr);
|
||||
currentPtr += sizeof(uint32_t);
|
||||
|
||||
auto expectedImageSize = header.evalImageSize((uint32_t) images.size());
|
||||
if (imageSize != expectedImageSize) {
|
||||
break;
|
||||
} else if (!Header::checkAlignment(imageSize)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// The image size is the face size, beware!
|
||||
size_t faceSize = imageSize;
|
||||
if (numFaces == NUM_CUBEMAPFACES) {
|
||||
imageSize = NUM_CUBEMAPFACES * faceSize;
|
||||
}
|
||||
|
||||
// If enough data ahead then capture the pointer
|
||||
if ((currentPtr - srcBytes) + imageSize <= (srcSize)) {
|
||||
auto padding = Header::evalPadding(imageSize);
|
||||
|
||||
if (numFaces == NUM_CUBEMAPFACES) {
|
||||
size_t faceSize = imageSize / NUM_CUBEMAPFACES;
|
||||
Image::FaceBytes faces(NUM_CUBEMAPFACES);
|
||||
for (uint32_t face = 0; face < NUM_CUBEMAPFACES; face++) {
|
||||
faces[face] = currentPtr;
|
||||
|
@ -166,6 +178,7 @@ namespace ktx {
|
|||
currentPtr += imageSize + padding;
|
||||
}
|
||||
} else {
|
||||
// Stop here
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -190,6 +203,10 @@ namespace ktx {
|
|||
|
||||
// populate image table
|
||||
result->_images = parseImages(result->getHeader(), result->getTexelsDataSize(), result->getTexelsData());
|
||||
if (result->_images.size() != result->getHeader().getNumberOfLevels()) {
|
||||
// Fail if the number of images produced doesn't match the header number of levels
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -127,6 +127,7 @@ namespace ktx {
|
|||
size_t KTX::writeWithoutImages(Byte* destBytes, size_t destByteSize, const Header& header, const ImageDescriptors& descriptors, const KeyValues& keyValues) {
|
||||
// Check again that we have enough destination capacity
|
||||
if (!destBytes || (destByteSize < evalStorageSize(header, descriptors, keyValues))) {
|
||||
qWarning() << "Destination capacity is insufficient to write KTX without images";
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
@ -148,14 +149,17 @@ namespace ktx {
|
|||
|
||||
for (size_t i = 0; i < descriptors.size(); ++i) {
|
||||
auto ptr = reinterpret_cast<uint32_t*>(currentDestPtr);
|
||||
*ptr = descriptors[i]._imageSize;
|
||||
ptr++;
|
||||
uint32_t imageFaceSize = descriptors[i]._faceSize;
|
||||
*ptr = imageFaceSize; // the imageSize written in the ktx is the FACE size
|
||||
|
||||
#ifdef DEBUG
|
||||
ptr++;
|
||||
for (size_t k = 0; k < descriptors[i]._imageSize/4; k++) {
|
||||
*(ptr + k) = 0xFFFFFFFF;
|
||||
}
|
||||
#endif
|
||||
currentDestPtr += descriptors[i]._imageSize + sizeof(uint32_t);
|
||||
currentDestPtr += sizeof(uint32_t);
|
||||
currentDestPtr += descriptors[i]._imageSize;
|
||||
}
|
||||
|
||||
return destByteSize;
|
||||
|
@ -210,7 +214,8 @@ namespace ktx {
|
|||
if (currentDataSize + sizeof(uint32_t) < allocatedImagesDataSize) {
|
||||
uint32_t imageOffset = currentPtr - destBytes;
|
||||
size_t imageSize = srcImages[l]._imageSize;
|
||||
*(reinterpret_cast<uint32_t*> (currentPtr)) = (uint32_t) imageSize;
|
||||
size_t imageFaceSize = srcImages[l]._faceSize;
|
||||
*(reinterpret_cast<uint32_t*> (currentPtr)) = (uint32_t)imageFaceSize; // the imageSize written in the ktx is the FACE size
|
||||
currentPtr += sizeof(uint32_t);
|
||||
currentDataSize += sizeof(uint32_t);
|
||||
|
||||
|
|
|
@ -22,7 +22,7 @@ KTXCache::KTXCache(const std::string& dir, const std::string& ext) :
|
|||
}
|
||||
|
||||
KTXFilePointer KTXCache::writeFile(const char* data, Metadata&& metadata) {
|
||||
FilePointer file = FileCache::writeFile(data, std::move(metadata));
|
||||
FilePointer file = FileCache::writeFile(data, std::move(metadata), true);
|
||||
return std::static_pointer_cast<KTXFile>(file);
|
||||
}
|
||||
|
||||
|
|
|
@ -519,12 +519,19 @@ void NetworkTexture::ktxMipRequestFinished() {
|
|||
if (texture) {
|
||||
texture->assignStoredMip(_ktxMipLevelRangeInFlight.first,
|
||||
_ktxMipRequest->getData().size(), reinterpret_cast<uint8_t*>(_ktxMipRequest->getData().data()));
|
||||
_lowestKnownPopulatedMip = _textureSource->getGPUTexture()->minAvailableMipLevel();
|
||||
|
||||
if (texture->minAvailableMipLevel() <= _ktxMipLevelRangeInFlight.first) {
|
||||
_lowestKnownPopulatedMip = texture->minAvailableMipLevel();
|
||||
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
|
||||
} else {
|
||||
qWarning(networking) << "Failed to load mip: " << _url << ":" << _ktxMipLevelRangeInFlight.first;
|
||||
_ktxResourceState = FAILED_TO_LOAD;
|
||||
}
|
||||
} else {
|
||||
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
|
||||
qWarning(networking) << "Trying to update mips but texture is null";
|
||||
}
|
||||
finishedLoading(true);
|
||||
_ktxResourceState = WAITING_FOR_MIP_REQUEST;
|
||||
} else {
|
||||
finishedLoading(false);
|
||||
if (handleFailedRequest(_ktxMipRequest->getResult())) {
|
||||
|
@ -810,6 +817,8 @@ void ImageReader::read() {
|
|||
texture = gpu::Texture::unserialize(ktxFile->getFilepath());
|
||||
if (texture) {
|
||||
texture = textureCache->cacheTextureByHash(hash, texture);
|
||||
} else {
|
||||
qCWarning(modelnetworking) << "Invalid cached KTX " << _url << " under hash " << hash.c_str() << ", recreating...";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -853,7 +862,7 @@ void ImageReader::read() {
|
|||
const char* data = reinterpret_cast<const char*>(memKtx->_storage->data());
|
||||
size_t length = memKtx->_storage->size();
|
||||
auto& ktxCache = textureCache->_ktxCache;
|
||||
networkTexture->_file = ktxCache.writeFile(data, KTXCache::Metadata(hash, length));
|
||||
networkTexture->_file = ktxCache.writeFile(data, KTXCache::Metadata(hash, length)); //
|
||||
if (!networkTexture->_file) {
|
||||
qCWarning(modelnetworking) << _url << "file cache failed";
|
||||
} else {
|
||||
|
|
|
@ -97,7 +97,7 @@ FilePointer FileCache::addFile(Metadata&& metadata, const std::string& filepath)
|
|||
return file;
|
||||
}
|
||||
|
||||
FilePointer FileCache::writeFile(const char* data, File::Metadata&& metadata) {
|
||||
FilePointer FileCache::writeFile(const char* data, File::Metadata&& metadata, bool overwrite) {
|
||||
assert(_initialized);
|
||||
|
||||
std::string filepath = getFilepath(metadata.key);
|
||||
|
@ -107,8 +107,13 @@ FilePointer FileCache::writeFile(const char* data, File::Metadata&& metadata) {
|
|||
// if file already exists, return it
|
||||
FilePointer file = getFile(metadata.key);
|
||||
if (file) {
|
||||
qCWarning(file_cache, "[%s] Attempted to overwrite %s", _dirname.c_str(), metadata.key.c_str());
|
||||
return file;
|
||||
if (!overwrite) {
|
||||
qCWarning(file_cache, "[%s] Attempted to overwrite %s", _dirname.c_str(), metadata.key.c_str());
|
||||
return file;
|
||||
} else {
|
||||
qCWarning(file_cache, "[%s] Overwriting %s", _dirname.c_str(), metadata.key.c_str());
|
||||
file.reset();
|
||||
}
|
||||
}
|
||||
|
||||
QSaveFile saveFile(QString::fromStdString(filepath));
|
||||
|
|
|
@ -80,7 +80,7 @@ protected:
|
|||
/// must be called after construction to create the cache on the fs and restore persisted files
|
||||
void initialize();
|
||||
|
||||
FilePointer writeFile(const char* data, Metadata&& metadata);
|
||||
FilePointer writeFile(const char* data, Metadata&& metadata, bool overwrite = false);
|
||||
FilePointer getFile(const Key& key);
|
||||
|
||||
/// create a file
|
||||
|
|
|
@ -9,63 +9,52 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <QDataStream>
|
||||
#include <QDebug>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include "SandboxUtils.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QNetworkReply>
|
||||
#include <QProcess>
|
||||
#include <QStandardPaths>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
|
||||
#include <NumericalConstants.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <RunningMarker.h>
|
||||
|
||||
#include "SandboxUtils.h"
|
||||
#include "NetworkAccessManager.h"
|
||||
#include "NetworkLogging.h"
|
||||
|
||||
namespace SandboxUtils {
|
||||
|
||||
void SandboxUtils::ifLocalSandboxRunningElse(std::function<void()> localSandboxRunningDoThis,
|
||||
std::function<void()> localSandboxNotRunningDoThat) {
|
||||
|
||||
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkReply* getStatus() {
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL);
|
||||
sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
QNetworkReply* reply = networkAccessManager.get(sandboxStatus);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() {
|
||||
if (reply->error() == QNetworkReply::NoError) {
|
||||
auto statusData = reply->readAll();
|
||||
auto statusJson = QJsonDocument::fromJson(statusData);
|
||||
if (!statusJson.isEmpty()) {
|
||||
auto statusObject = statusJson.object();
|
||||
auto serversValue = statusObject.value("servers");
|
||||
if (!serversValue.isUndefined() && serversValue.isObject()) {
|
||||
auto serversObject = serversValue.toObject();
|
||||
auto serversCount = serversObject.size();
|
||||
const int MINIMUM_EXPECTED_SERVER_COUNT = 5;
|
||||
if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) {
|
||||
localSandboxRunningDoThis();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
localSandboxNotRunningDoThat();
|
||||
});
|
||||
return networkAccessManager.get(sandboxStatus);
|
||||
}
|
||||
|
||||
bool readStatus(QByteArray statusData) {
|
||||
auto statusJson = QJsonDocument::fromJson(statusData);
|
||||
|
||||
void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) {
|
||||
QString applicationDirPath = QFileInfo(QCoreApplication::applicationFilePath()).path();
|
||||
QString serverPath = applicationDirPath + "/server-console/server-console.exe";
|
||||
qCDebug(networking) << "Application dir path is: " << applicationDirPath;
|
||||
if (!statusJson.isEmpty()) {
|
||||
auto statusObject = statusJson.object();
|
||||
auto serversValue = statusObject.value("servers");
|
||||
if (!serversValue.isUndefined() && serversValue.isObject()) {
|
||||
auto serversObject = serversValue.toObject();
|
||||
auto serversCount = serversObject.size();
|
||||
const int MINIMUM_EXPECTED_SERVER_COUNT = 5;
|
||||
if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater) {
|
||||
QString serverPath = "./server-console/server-console.exe";
|
||||
qCDebug(networking) << "Running marker path is: " << runningMarkerName;
|
||||
qCDebug(networking) << "Server path is: " << serverPath;
|
||||
qCDebug(networking) << "autoShutdown: " << autoShutdown;
|
||||
qCDebug(networking) << "noUpdater: " << noUpdater;
|
||||
|
@ -80,7 +69,7 @@ void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QStri
|
|||
}
|
||||
|
||||
if (hasContentPath) {
|
||||
QString serverContentPath = applicationDirPath + "/" + contentPath;
|
||||
QString serverContentPath = "./" + contentPath;
|
||||
args << "--contentPath" << serverContentPath;
|
||||
}
|
||||
|
||||
|
@ -93,10 +82,8 @@ void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QStri
|
|||
args << "--noUpdater";
|
||||
}
|
||||
|
||||
qCDebug(networking) << applicationDirPath;
|
||||
qCDebug(networking) << "Launching sandbox with:" << args;
|
||||
qCDebug(networking) << QProcess::startDetached(serverPath, args);
|
||||
|
||||
// Sleep a short amount of time to give the server a chance to start
|
||||
usleep(2000000); /// do we really need this??
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,21 +12,16 @@
|
|||
#ifndef hifi_SandboxUtils_h
|
||||
#define hifi_SandboxUtils_h
|
||||
|
||||
#include <functional>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QString>
|
||||
|
||||
class QNetworkReply;
|
||||
|
||||
const QString SANDBOX_STATUS_URL = "http://localhost:60332/status";
|
||||
namespace SandboxUtils {
|
||||
const QString SANDBOX_STATUS_URL = "http://localhost:60332/status";
|
||||
|
||||
class SandboxUtils : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
/// determines if the local sandbox is likely running. It does not account for custom setups, and is only
|
||||
/// intended to detect the standard local sandbox install.
|
||||
void ifLocalSandboxRunningElse(std::function<void()> localSandboxRunningDoThis,
|
||||
std::function<void()> localSandboxNotRunningDoThat);
|
||||
|
||||
static void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater);
|
||||
QNetworkReply* getStatus();
|
||||
bool readStatus(QByteArray statusData);
|
||||
void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName, bool noUpdater);
|
||||
};
|
||||
|
||||
#endif // hifi_SandboxUtils_h
|
||||
|
|
|
@ -56,7 +56,7 @@ PacketVersion versionForPacketType(PacketType packetType) {
|
|||
case PacketType::AvatarData:
|
||||
case PacketType::BulkAvatarData:
|
||||
case PacketType::KillAvatar:
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::IdentityPacketsIncludeUpdateTime);
|
||||
return static_cast<PacketVersion>(AvatarMixerPacketVersion::AvatarIdentitySequenceId);
|
||||
case PacketType::MessagesData:
|
||||
return static_cast<PacketVersion>(MessageDataVersion::TextOrBinaryData);
|
||||
case PacketType::ICEServerHeartbeat:
|
||||
|
|
|
@ -234,7 +234,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
|
|||
VariableAvatarData,
|
||||
AvatarAsChildFixes,
|
||||
StickAndBallDefaultAvatar,
|
||||
IdentityPacketsIncludeUpdateTime
|
||||
IdentityPacketsIncludeUpdateTime,
|
||||
AvatarIdentitySequenceId
|
||||
};
|
||||
|
||||
enum class DomainConnectRequestVersion : PacketVersion {
|
||||
|
|
|
@ -112,6 +112,9 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
|||
_dynamicsWorld = nullptr;
|
||||
}
|
||||
int16_t collisionGroup = computeCollisionGroup();
|
||||
if (_rigidBody) {
|
||||
updateMassProperties();
|
||||
}
|
||||
if (world && _rigidBody) {
|
||||
// add to new world
|
||||
_dynamicsWorld = world;
|
||||
|
@ -127,7 +130,9 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
|||
_ghost.setCollisionGroupAndMask(collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ collisionGroup));
|
||||
_ghost.setCollisionWorld(_dynamicsWorld);
|
||||
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
|
||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||
if (_rigidBody) {
|
||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||
}
|
||||
}
|
||||
if (_dynamicsWorld) {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
|
|
|
@ -128,6 +128,7 @@ protected:
|
|||
void setState(State state);
|
||||
#endif
|
||||
|
||||
virtual void updateMassProperties() = 0;
|
||||
void updateGravity();
|
||||
void updateUpAxis(const glm::quat& rotation);
|
||||
bool checkForSupport(btCollisionWorld* collisionWorld);
|
||||
|
|
|
@ -1,378 +0,0 @@
|
|||
//
|
||||
// ObjectActionSpring.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2015-6-5
|
||||
// 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 "QVariantGLM.h"
|
||||
|
||||
#include "ObjectActionSpring.h"
|
||||
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
const float SPRING_MAX_SPEED = 10.0f;
|
||||
const float MAX_SPRING_TIMESCALE = 600.0f; // 10 min is a long time
|
||||
|
||||
const uint16_t ObjectActionSpring::springVersion = 1;
|
||||
|
||||
|
||||
ObjectActionSpring::ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity) :
|
||||
ObjectAction(DYNAMIC_TYPE_SPRING, id, ownerEntity),
|
||||
_positionalTarget(glm::vec3(0.0f)),
|
||||
_desiredPositionalTarget(glm::vec3(0.0f)),
|
||||
_linearTimeScale(FLT_MAX),
|
||||
_positionalTargetSet(true),
|
||||
_rotationalTarget(glm::quat()),
|
||||
_desiredRotationalTarget(glm::quat()),
|
||||
_angularTimeScale(FLT_MAX),
|
||||
_rotationalTargetSet(true) {
|
||||
#if WANT_DEBUG
|
||||
qCDebug(physics) << "ObjectActionSpring::ObjectActionSpring";
|
||||
#endif
|
||||
}
|
||||
|
||||
ObjectActionSpring::~ObjectActionSpring() {
|
||||
#if WANT_DEBUG
|
||||
qCDebug(physics) << "ObjectActionSpring::~ObjectActionSpring";
|
||||
#endif
|
||||
}
|
||||
|
||||
bool ObjectActionSpring::getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity,
|
||||
float& linearTimeScale, float& angularTimeScale) {
|
||||
SpatiallyNestablePointer other = getOther();
|
||||
withReadLock([&]{
|
||||
linearTimeScale = _linearTimeScale;
|
||||
angularTimeScale = _angularTimeScale;
|
||||
|
||||
if (!_otherID.isNull()) {
|
||||
if (other) {
|
||||
rotation = _desiredRotationalTarget * other->getRotation();
|
||||
position = other->getRotation() * _desiredPositionalTarget + other->getPosition();
|
||||
} else {
|
||||
// we should have an "other" but can't find it, so disable the spring.
|
||||
linearTimeScale = FLT_MAX;
|
||||
angularTimeScale = FLT_MAX;
|
||||
}
|
||||
} else {
|
||||
rotation = _desiredRotationalTarget;
|
||||
position = _desiredPositionalTarget;
|
||||
}
|
||||
linearVelocity = glm::vec3();
|
||||
angularVelocity = glm::vec3();
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ObjectActionSpring::prepareForSpringUpdate(btScalar deltaTimeStep) {
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (!ownerEntity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
glm::quat rotation;
|
||||
glm::vec3 position;
|
||||
glm::vec3 linearVelocity;
|
||||
glm::vec3 angularVelocity;
|
||||
|
||||
bool linearValid = false;
|
||||
int linearSpringCount = 0;
|
||||
bool angularValid = false;
|
||||
int angularSpringCount = 0;
|
||||
|
||||
QList<EntityDynamicPointer> springDerivedActions;
|
||||
springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_SPRING));
|
||||
springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_FAR_GRAB));
|
||||
springDerivedActions.append(ownerEntity->getActionsOfType(DYNAMIC_TYPE_HOLD));
|
||||
|
||||
foreach (EntityDynamicPointer action, springDerivedActions) {
|
||||
std::shared_ptr<ObjectActionSpring> springAction = std::static_pointer_cast<ObjectActionSpring>(action);
|
||||
glm::quat rotationForAction;
|
||||
glm::vec3 positionForAction;
|
||||
glm::vec3 linearVelocityForAction;
|
||||
glm::vec3 angularVelocityForAction;
|
||||
float linearTimeScale;
|
||||
float angularTimeScale;
|
||||
bool success = springAction->getTarget(deltaTimeStep,
|
||||
rotationForAction, positionForAction,
|
||||
linearVelocityForAction, angularVelocityForAction,
|
||||
linearTimeScale, angularTimeScale);
|
||||
if (success) {
|
||||
if (angularTimeScale < MAX_SPRING_TIMESCALE) {
|
||||
angularValid = true;
|
||||
angularSpringCount++;
|
||||
angularVelocity += angularVelocityForAction;
|
||||
if (springAction.get() == this) {
|
||||
// only use the rotation for this action
|
||||
rotation = rotationForAction;
|
||||
}
|
||||
}
|
||||
|
||||
if (linearTimeScale < MAX_SPRING_TIMESCALE) {
|
||||
linearValid = true;
|
||||
linearSpringCount++;
|
||||
position += positionForAction;
|
||||
linearVelocity += linearVelocityForAction;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((angularValid && angularSpringCount > 0) || (linearValid && linearSpringCount > 0)) {
|
||||
withWriteLock([&]{
|
||||
if (linearValid && linearSpringCount > 0) {
|
||||
position /= linearSpringCount;
|
||||
linearVelocity /= linearSpringCount;
|
||||
_positionalTarget = position;
|
||||
_linearVelocityTarget = linearVelocity;
|
||||
_positionalTargetSet = true;
|
||||
_active = true;
|
||||
}
|
||||
if (angularValid && angularSpringCount > 0) {
|
||||
angularVelocity /= angularSpringCount;
|
||||
_rotationalTarget = rotation;
|
||||
_angularVelocityTarget = angularVelocity;
|
||||
_rotationalTargetSet = true;
|
||||
_active = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return linearValid || angularValid;
|
||||
}
|
||||
|
||||
|
||||
void ObjectActionSpring::updateActionWorker(btScalar deltaTimeStep) {
|
||||
if (!prepareForSpringUpdate(deltaTimeStep)) {
|
||||
return;
|
||||
}
|
||||
|
||||
withReadLock([&]{
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (!ownerEntity) {
|
||||
return;
|
||||
}
|
||||
|
||||
void* physicsInfo = ownerEntity->getPhysicsInfo();
|
||||
if (!physicsInfo) {
|
||||
return;
|
||||
}
|
||||
ObjectMotionState* motionState = static_cast<ObjectMotionState*>(physicsInfo);
|
||||
btRigidBody* rigidBody = motionState->getRigidBody();
|
||||
if (!rigidBody) {
|
||||
qCDebug(physics) << "ObjectActionSpring::updateActionWorker no rigidBody";
|
||||
return;
|
||||
}
|
||||
|
||||
if (_linearTimeScale < MAX_SPRING_TIMESCALE) {
|
||||
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
|
||||
btVector3 offset = rigidBody->getCenterOfMassPosition() - glmToBullet(_positionalTarget);
|
||||
float offsetLength = offset.length();
|
||||
if (offsetLength > FLT_EPSILON) {
|
||||
float speed = glm::min(offsetLength / _linearTimeScale, SPRING_MAX_SPEED);
|
||||
targetVelocity = (-speed / offsetLength) * offset;
|
||||
if (speed > rigidBody->getLinearSleepingThreshold()) {
|
||||
forceBodyNonStatic();
|
||||
rigidBody->activate();
|
||||
}
|
||||
}
|
||||
// this action is aggresively critically damped and defeats the current velocity
|
||||
rigidBody->setLinearVelocity(targetVelocity);
|
||||
}
|
||||
|
||||
if (_angularTimeScale < MAX_SPRING_TIMESCALE) {
|
||||
btVector3 targetVelocity(0.0f, 0.0f, 0.0f);
|
||||
|
||||
btQuaternion bodyRotation = rigidBody->getOrientation();
|
||||
auto alignmentDot = bodyRotation.dot(glmToBullet(_rotationalTarget));
|
||||
const float ALMOST_ONE = 0.99999f;
|
||||
if (glm::abs(alignmentDot) < ALMOST_ONE) {
|
||||
btQuaternion target = glmToBullet(_rotationalTarget);
|
||||
if (alignmentDot < 0.0f) {
|
||||
target = -target;
|
||||
}
|
||||
// if dQ is the incremental rotation that gets an object from Q0 to Q1 then:
|
||||
//
|
||||
// Q1 = dQ * Q0
|
||||
//
|
||||
// solving for dQ gives:
|
||||
//
|
||||
// dQ = Q1 * Q0^
|
||||
btQuaternion deltaQ = target * bodyRotation.inverse();
|
||||
float speed = deltaQ.getAngle() / _angularTimeScale;
|
||||
targetVelocity = speed * deltaQ.getAxis();
|
||||
if (speed > rigidBody->getAngularSleepingThreshold()) {
|
||||
rigidBody->activate();
|
||||
}
|
||||
}
|
||||
// this action is aggresively critically damped and defeats the current velocity
|
||||
rigidBody->setAngularVelocity(targetVelocity);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const float MIN_TIMESCALE = 0.1f;
|
||||
|
||||
|
||||
bool ObjectActionSpring::updateArguments(QVariantMap arguments) {
|
||||
glm::vec3 positionalTarget;
|
||||
float linearTimeScale;
|
||||
glm::quat rotationalTarget;
|
||||
float angularTimeScale;
|
||||
QUuid otherID;
|
||||
|
||||
|
||||
bool needUpdate = false;
|
||||
bool somethingChanged = ObjectDynamic::updateArguments(arguments);
|
||||
withReadLock([&]{
|
||||
// targets are required, spring-constants are optional
|
||||
bool ok = true;
|
||||
positionalTarget = EntityDynamicInterface::extractVec3Argument("spring action", arguments, "targetPosition", ok, false);
|
||||
if (!ok) {
|
||||
positionalTarget = _desiredPositionalTarget;
|
||||
}
|
||||
ok = true;
|
||||
linearTimeScale = EntityDynamicInterface::extractFloatArgument("spring action", arguments, "linearTimeScale", ok, false);
|
||||
if (!ok || linearTimeScale <= 0.0f) {
|
||||
linearTimeScale = _linearTimeScale;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
rotationalTarget = EntityDynamicInterface::extractQuatArgument("spring action", arguments, "targetRotation", ok, false);
|
||||
if (!ok) {
|
||||
rotationalTarget = _desiredRotationalTarget;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
angularTimeScale =
|
||||
EntityDynamicInterface::extractFloatArgument("spring action", arguments, "angularTimeScale", ok, false);
|
||||
if (!ok) {
|
||||
angularTimeScale = _angularTimeScale;
|
||||
}
|
||||
|
||||
ok = true;
|
||||
otherID = QUuid(EntityDynamicInterface::extractStringArgument("spring action",
|
||||
arguments, "otherID", ok, false));
|
||||
if (!ok) {
|
||||
otherID = _otherID;
|
||||
}
|
||||
|
||||
if (somethingChanged ||
|
||||
positionalTarget != _desiredPositionalTarget ||
|
||||
linearTimeScale != _linearTimeScale ||
|
||||
rotationalTarget != _desiredRotationalTarget ||
|
||||
angularTimeScale != _angularTimeScale ||
|
||||
otherID != _otherID) {
|
||||
// something changed
|
||||
needUpdate = true;
|
||||
}
|
||||
});
|
||||
|
||||
if (needUpdate) {
|
||||
withWriteLock([&] {
|
||||
_desiredPositionalTarget = positionalTarget;
|
||||
_linearTimeScale = glm::max(MIN_TIMESCALE, glm::abs(linearTimeScale));
|
||||
_desiredRotationalTarget = rotationalTarget;
|
||||
_angularTimeScale = glm::max(MIN_TIMESCALE, glm::abs(angularTimeScale));
|
||||
_otherID = otherID;
|
||||
_active = true;
|
||||
|
||||
auto ownerEntity = _ownerEntity.lock();
|
||||
if (ownerEntity) {
|
||||
ownerEntity->setDynamicDataDirty(true);
|
||||
ownerEntity->setDynamicDataNeedsTransmit(true);
|
||||
}
|
||||
});
|
||||
activateBody();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariantMap ObjectActionSpring::getArguments() {
|
||||
QVariantMap arguments = ObjectDynamic::getArguments();
|
||||
withReadLock([&] {
|
||||
arguments["linearTimeScale"] = _linearTimeScale;
|
||||
arguments["targetPosition"] = glmToQMap(_desiredPositionalTarget);
|
||||
|
||||
arguments["targetRotation"] = glmToQMap(_desiredRotationalTarget);
|
||||
arguments["angularTimeScale"] = _angularTimeScale;
|
||||
|
||||
arguments["otherID"] = _otherID;
|
||||
});
|
||||
return arguments;
|
||||
}
|
||||
|
||||
void ObjectActionSpring::serializeParameters(QDataStream& dataStream) const {
|
||||
withReadLock([&] {
|
||||
dataStream << _desiredPositionalTarget;
|
||||
dataStream << _linearTimeScale;
|
||||
dataStream << _positionalTargetSet;
|
||||
dataStream << _desiredRotationalTarget;
|
||||
dataStream << _angularTimeScale;
|
||||
dataStream << _rotationalTargetSet;
|
||||
dataStream << localTimeToServerTime(_expires);
|
||||
dataStream << _tag;
|
||||
dataStream << _otherID;
|
||||
});
|
||||
}
|
||||
|
||||
QByteArray ObjectActionSpring::serialize() const {
|
||||
QByteArray serializedActionArguments;
|
||||
QDataStream dataStream(&serializedActionArguments, QIODevice::WriteOnly);
|
||||
|
||||
dataStream << DYNAMIC_TYPE_SPRING;
|
||||
dataStream << getID();
|
||||
dataStream << ObjectActionSpring::springVersion;
|
||||
|
||||
serializeParameters(dataStream);
|
||||
|
||||
return serializedActionArguments;
|
||||
}
|
||||
|
||||
void ObjectActionSpring::deserializeParameters(QByteArray serializedArguments, QDataStream& dataStream) {
|
||||
withWriteLock([&] {
|
||||
dataStream >> _desiredPositionalTarget;
|
||||
dataStream >> _linearTimeScale;
|
||||
dataStream >> _positionalTargetSet;
|
||||
|
||||
dataStream >> _desiredRotationalTarget;
|
||||
dataStream >> _angularTimeScale;
|
||||
dataStream >> _rotationalTargetSet;
|
||||
|
||||
quint64 serverExpires;
|
||||
dataStream >> serverExpires;
|
||||
_expires = serverTimeToLocalTime(serverExpires);
|
||||
|
||||
dataStream >> _tag;
|
||||
|
||||
dataStream >> _otherID;
|
||||
|
||||
_active = true;
|
||||
});
|
||||
}
|
||||
|
||||
void ObjectActionSpring::deserialize(QByteArray serializedArguments) {
|
||||
QDataStream dataStream(serializedArguments);
|
||||
|
||||
EntityDynamicType type;
|
||||
dataStream >> type;
|
||||
assert(type == getType());
|
||||
|
||||
QUuid id;
|
||||
dataStream >> id;
|
||||
assert(id == getID());
|
||||
|
||||
uint16_t serializationVersion;
|
||||
dataStream >> serializationVersion;
|
||||
if (serializationVersion != ObjectActionSpring::springVersion) {
|
||||
assert(false);
|
||||
return;
|
||||
}
|
||||
|
||||
deserializeParameters(serializedArguments, dataStream);
|
||||
}
|
|
@ -1,56 +0,0 @@
|
|||
//
|
||||
// ObjectActionSpring.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Seth Alves 2015-6-5
|
||||
// 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
|
||||
//
|
||||
|
||||
#ifndef hifi_ObjectActionSpring_h
|
||||
#define hifi_ObjectActionSpring_h
|
||||
|
||||
#include "ObjectAction.h"
|
||||
|
||||
class ObjectActionSpring : public ObjectAction {
|
||||
public:
|
||||
ObjectActionSpring(const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~ObjectActionSpring();
|
||||
|
||||
virtual bool updateArguments(QVariantMap arguments) override;
|
||||
virtual QVariantMap getArguments() override;
|
||||
|
||||
virtual void updateActionWorker(float deltaTimeStep) override;
|
||||
|
||||
virtual QByteArray serialize() const override;
|
||||
virtual void deserialize(QByteArray serializedArguments) override;
|
||||
|
||||
virtual bool getTarget(float deltaTimeStep, glm::quat& rotation, glm::vec3& position,
|
||||
glm::vec3& linearVelocity, glm::vec3& angularVelocity,
|
||||
float& linearTimeScale, float& angularTimeScale);
|
||||
|
||||
protected:
|
||||
static const uint16_t springVersion;
|
||||
|
||||
glm::vec3 _positionalTarget;
|
||||
glm::vec3 _desiredPositionalTarget;
|
||||
float _linearTimeScale;
|
||||
bool _positionalTargetSet;
|
||||
|
||||
glm::quat _rotationalTarget;
|
||||
glm::quat _desiredRotationalTarget;
|
||||
float _angularTimeScale;
|
||||
bool _rotationalTargetSet;
|
||||
|
||||
glm::vec3 _linearVelocityTarget;
|
||||
glm::vec3 _angularVelocityTarget;
|
||||
|
||||
virtual bool prepareForSpringUpdate(btScalar deltaTimeStep);
|
||||
|
||||
void serializeParameters(QDataStream& dataStream) const;
|
||||
void deserializeParameters(QByteArray serializedArguments, QDataStream& dataStream);
|
||||
};
|
||||
|
||||
#endif // hifi_ObjectActionSpring_h
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
|
@ -83,6 +85,9 @@ btTypedConstraint* ObjectConstraintBallSocket::getConstraint() {
|
|||
return constraint;
|
||||
}
|
||||
|
||||
static QString repeatedBallSocketNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex(
|
||||
"ObjectConstraintBallSocket::getConstraint -- no rigidBody.*");
|
||||
|
||||
btRigidBody* rigidBodyA = getRigidBody();
|
||||
if (!rigidBodyA) {
|
||||
qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyA";
|
||||
|
@ -94,6 +99,7 @@ btTypedConstraint* ObjectConstraintBallSocket::getConstraint() {
|
|||
|
||||
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
|
||||
if (!rigidBodyB) {
|
||||
qCDebug(physics) << "ObjectConstraintBallSocket::getConstraint -- no rigidBodyB";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
|
@ -94,6 +96,9 @@ btTypedConstraint* ObjectConstraintConeTwist::getConstraint() {
|
|||
return constraint;
|
||||
}
|
||||
|
||||
static QString repeatedConeTwistNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex(
|
||||
"ObjectConstraintConeTwist::getConstraint -- no rigidBody.*");
|
||||
|
||||
btRigidBody* rigidBodyA = getRigidBody();
|
||||
if (!rigidBodyA) {
|
||||
qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyA";
|
||||
|
@ -125,6 +130,7 @@ btTypedConstraint* ObjectConstraintConeTwist::getConstraint() {
|
|||
|
||||
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
|
||||
if (!rigidBodyB) {
|
||||
qCDebug(physics) << "ObjectConstraintConeTwist::getConstraint -- no rigidBodyB";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
|
@ -93,6 +95,9 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() {
|
|||
return constraint;
|
||||
}
|
||||
|
||||
static QString repeatedHingeNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex(
|
||||
"ObjectConstraintHinge::getConstraint -- no rigidBody.*");
|
||||
|
||||
btRigidBody* rigidBodyA = getRigidBody();
|
||||
if (!rigidBodyA) {
|
||||
qCDebug(physics) << "ObjectConstraintHinge::getConstraint -- no rigidBodyA";
|
||||
|
@ -110,6 +115,7 @@ btTypedConstraint* ObjectConstraintHinge::getConstraint() {
|
|||
// This hinge is between two entities... find the other rigid body.
|
||||
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
|
||||
if (!rigidBodyB) {
|
||||
qCDebug(physics) << "ObjectConstraintHinge::getConstraint -- no rigidBodyB";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <LogHandler.h>
|
||||
|
||||
#include "QVariantGLM.h"
|
||||
|
||||
#include "EntityTree.h"
|
||||
|
@ -85,6 +87,9 @@ btTypedConstraint* ObjectConstraintSlider::getConstraint() {
|
|||
return constraint;
|
||||
}
|
||||
|
||||
static QString repeatedSliderNoRigidBody = LogHandler::getInstance().addRepeatedMessageRegex(
|
||||
"ObjectConstraintSlider::getConstraint -- no rigidBody.*");
|
||||
|
||||
btRigidBody* rigidBodyA = getRigidBody();
|
||||
if (!rigidBodyA) {
|
||||
qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyA";
|
||||
|
@ -116,6 +121,7 @@ btTypedConstraint* ObjectConstraintSlider::getConstraint() {
|
|||
|
||||
btRigidBody* rigidBodyB = getOtherRigidBody(otherEntityID);
|
||||
if (!rigidBodyB) {
|
||||
qCDebug(physics) << "ObjectConstraintSlider::getConstraint -- no rigidBodyB";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
|
|
@ -160,56 +160,6 @@ btRigidBody* ObjectDynamic::getRigidBody() {
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
glm::vec3 ObjectDynamic::getPosition() {
|
||||
auto rigidBody = getRigidBody();
|
||||
if (!rigidBody) {
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
return bulletToGLM(rigidBody->getCenterOfMassPosition());
|
||||
}
|
||||
|
||||
glm::quat ObjectDynamic::getRotation() {
|
||||
auto rigidBody = getRigidBody();
|
||||
if (!rigidBody) {
|
||||
return glm::quat(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
return bulletToGLM(rigidBody->getOrientation());
|
||||
}
|
||||
|
||||
glm::vec3 ObjectDynamic::getLinearVelocity() {
|
||||
auto rigidBody = getRigidBody();
|
||||
if (!rigidBody) {
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
return bulletToGLM(rigidBody->getLinearVelocity());
|
||||
}
|
||||
|
||||
void ObjectDynamic::setLinearVelocity(glm::vec3 linearVelocity) {
|
||||
auto rigidBody = getRigidBody();
|
||||
if (!rigidBody) {
|
||||
return;
|
||||
}
|
||||
rigidBody->setLinearVelocity(glmToBullet(glm::vec3(0.0f)));
|
||||
rigidBody->activate();
|
||||
}
|
||||
|
||||
glm::vec3 ObjectDynamic::getAngularVelocity() {
|
||||
auto rigidBody = getRigidBody();
|
||||
if (!rigidBody) {
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
return bulletToGLM(rigidBody->getAngularVelocity());
|
||||
}
|
||||
|
||||
void ObjectDynamic::setAngularVelocity(glm::vec3 angularVelocity) {
|
||||
auto rigidBody = getRigidBody();
|
||||
if (!rigidBody) {
|
||||
return;
|
||||
}
|
||||
rigidBody->setAngularVelocity(glmToBullet(angularVelocity));
|
||||
rigidBody->activate();
|
||||
}
|
||||
|
||||
void ObjectDynamic::activateBody(bool forceActivation) {
|
||||
auto rigidBody = getRigidBody();
|
||||
if (rigidBody) {
|
||||
|
|
|
@ -56,12 +56,6 @@ protected:
|
|||
btRigidBody* getOtherRigidBody(EntityItemID otherEntityID);
|
||||
EntityItemPointer getEntityByID(EntityItemID entityID) const;
|
||||
virtual btRigidBody* getRigidBody();
|
||||
virtual glm::vec3 getPosition() override;
|
||||
virtual glm::quat getRotation() override;
|
||||
virtual glm::vec3 getLinearVelocity() override;
|
||||
virtual void setLinearVelocity(glm::vec3 linearVelocity) override;
|
||||
virtual glm::vec3 getAngularVelocity() override;
|
||||
virtual void setAngularVelocity(glm::vec3 angularVelocity) override;
|
||||
virtual void activateBody(bool forceActivation = false);
|
||||
virtual void forceBodyNonStatic();
|
||||
|
||||
|
|
|
@ -63,10 +63,7 @@ ShapeManager* ObjectMotionState::getShapeManager() {
|
|||
}
|
||||
|
||||
ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) :
|
||||
_motionType(MOTION_TYPE_STATIC),
|
||||
_shape(shape),
|
||||
_body(nullptr),
|
||||
_mass(0.0f),
|
||||
_lastKinematicStep(worldSimulationStep)
|
||||
{
|
||||
}
|
||||
|
@ -74,7 +71,43 @@ ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) :
|
|||
ObjectMotionState::~ObjectMotionState() {
|
||||
assert(!_body);
|
||||
setShape(nullptr);
|
||||
_type = MOTIONSTATE_TYPE_INVALID;
|
||||
}
|
||||
|
||||
void ObjectMotionState::setMass(float mass) {
|
||||
_density = 1.0f;
|
||||
if (_shape) {
|
||||
// we compute the density for the current shape's Aabb volume
|
||||
// and save that instead of the total mass
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
btVector3 minCorner, maxCorner;
|
||||
_shape->getAabb(transform, minCorner, maxCorner);
|
||||
btVector3 diagonal = maxCorner - minCorner;
|
||||
float volume = diagonal.getX() * diagonal.getY() * diagonal.getZ();
|
||||
if (volume > EPSILON) {
|
||||
_density = fabsf(mass) / volume;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float ObjectMotionState::getMass() const {
|
||||
if (_shape) {
|
||||
// scale the density by the current Aabb volume to get mass
|
||||
btTransform transform;
|
||||
transform.setIdentity();
|
||||
btVector3 minCorner, maxCorner;
|
||||
_shape->getAabb(transform, minCorner, maxCorner);
|
||||
btVector3 diagonal = maxCorner - minCorner;
|
||||
float volume = diagonal.getX() * diagonal.getY() * diagonal.getZ();
|
||||
|
||||
// cap the max mass for numerical stability
|
||||
const float MIN_OBJECT_MASS = 0.0f;
|
||||
const float MAX_OBJECT_DENSITY = 20000.0f; // kg/m^3 density of Tungsten
|
||||
const float MAX_OBJECT_VOLUME = 1.0e6f;
|
||||
const float MAX_OBJECT_MASS = MAX_OBJECT_DENSITY * MAX_OBJECT_VOLUME;
|
||||
return glm::clamp(_density * volume, MIN_OBJECT_MASS, MAX_OBJECT_MASS);
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
void ObjectMotionState::setBodyLinearVelocity(const glm::vec3& velocity) const {
|
||||
|
|
|
@ -93,8 +93,8 @@ public:
|
|||
MotionStateType getType() const { return _type; }
|
||||
virtual PhysicsMotionType getMotionType() const { return _motionType; }
|
||||
|
||||
void setMass(float mass) { _mass = fabsf(mass); }
|
||||
float getMass() { return _mass; }
|
||||
void setMass(float mass);
|
||||
float getMass() const;
|
||||
|
||||
void setBodyLinearVelocity(const glm::vec3& velocity) const;
|
||||
void setBodyAngularVelocity(const glm::vec3& velocity) const;
|
||||
|
@ -159,12 +159,12 @@ protected:
|
|||
void setRigidBody(btRigidBody* body);
|
||||
virtual void setShape(const btCollisionShape* shape);
|
||||
|
||||
MotionStateType _type = MOTIONSTATE_TYPE_INVALID; // type of MotionState
|
||||
PhysicsMotionType _motionType; // type of motion: KINEMATIC, DYNAMIC, or STATIC
|
||||
MotionStateType _type { MOTIONSTATE_TYPE_INVALID }; // type of MotionState
|
||||
PhysicsMotionType _motionType { MOTION_TYPE_STATIC }; // type of motion: KINEMATIC, DYNAMIC, or STATIC
|
||||
|
||||
const btCollisionShape* _shape;
|
||||
btRigidBody* _body;
|
||||
float _mass;
|
||||
btRigidBody* _body { nullptr };
|
||||
float _density { 1.0f };
|
||||
|
||||
uint32_t _lastKinematicStep;
|
||||
bool _hasInternalKinematicChanges { false };
|
||||
|
|
|
@ -376,7 +376,7 @@ bool Model::findRayIntersectionAgainstSubMeshes(const glm::vec3& origin, const g
|
|||
glm::vec3 meshFrameOrigin = glm::vec3(worldToMeshMatrix * glm::vec4(origin, 1.0f));
|
||||
glm::vec3 meshFrameDirection = glm::vec3(worldToMeshMatrix * glm::vec4(direction, 0.0f));
|
||||
|
||||
for (const auto& triangleSet : _modelSpaceMeshTriangleSets) {
|
||||
for (auto& triangleSet : _modelSpaceMeshTriangleSets) {
|
||||
float triangleSetDistance = 0.0f;
|
||||
BoxFace triangleSetFace;
|
||||
glm::vec3 triangleSetNormal;
|
||||
|
@ -1046,12 +1046,12 @@ void Model::simulate(float deltaTime, bool fullUpdate) {
|
|||
//virtual
|
||||
void Model::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
_needsUpdateClusterMatrices = true;
|
||||
_rig->updateAnimations(deltaTime, parentTransform);
|
||||
glm::mat4 rigToWorldTransform = createMatFromQuatAndPos(getRotation(), getTranslation());
|
||||
_rig->updateAnimations(deltaTime, parentTransform, rigToWorldTransform);
|
||||
}
|
||||
|
||||
void Model::computeMeshPartLocalBounds() {
|
||||
for (auto& part : _modelMeshRenderItems) {
|
||||
assert(part->_meshIndex < _modelMeshRenderItems.size());
|
||||
const Model::MeshState& state = _meshStates.at(part->_meshIndex);
|
||||
part->computeAdjustedLocalBound(state.clusterMatrices);
|
||||
}
|
||||
|
|
|
@ -48,6 +48,8 @@ class QScriptEngineDebugger;
|
|||
static const QString NO_SCRIPT("");
|
||||
|
||||
static const int SCRIPT_FPS = 60;
|
||||
static const int DEFAULT_MAX_ENTITY_PPS = 9000;
|
||||
static const int DEFAULT_ENTITY_PPS_PER_SCRIPT = 900;
|
||||
|
||||
class CallbackData {
|
||||
public:
|
||||
|
|
|
@ -114,6 +114,10 @@ static bool isWithin(float value, float corner, float size) {
|
|||
return value >= corner && value <= corner + size;
|
||||
}
|
||||
|
||||
bool AABox::contains(const Triangle& triangle) const {
|
||||
return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2);
|
||||
}
|
||||
|
||||
bool AABox::contains(const glm::vec3& point) const {
|
||||
return isWithin(point.x, _corner.x, _scale.x) &&
|
||||
isWithin(point.y, _corner.y, _scale.y) &&
|
||||
|
@ -622,3 +626,40 @@ void AABox::transform(const glm::mat4& matrix) {
|
|||
_corner = newCenter - newDir;
|
||||
_scale = newDir * 2.0f;
|
||||
}
|
||||
|
||||
AABox AABox::getOctreeChild(OctreeChild child) const {
|
||||
AABox result(*this); // self
|
||||
switch (child) {
|
||||
case topLeftNear:
|
||||
result._corner.y += _scale.y / 2.0f;
|
||||
break;
|
||||
case topLeftFar:
|
||||
result._corner.y += _scale.y / 2.0f;
|
||||
result._corner.z += _scale.z / 2.0f;
|
||||
break;
|
||||
case topRightNear:
|
||||
result._corner.y += _scale.y / 2.0f;
|
||||
result._corner.x += _scale.x / 2.0f;
|
||||
break;
|
||||
case topRightFar:
|
||||
result._corner.y += _scale.y / 2.0f;
|
||||
result._corner.x += _scale.x / 2.0f;
|
||||
result._corner.z += _scale.z / 2.0f;
|
||||
break;
|
||||
case bottomLeftNear:
|
||||
// _corner = same as parent
|
||||
break;
|
||||
case bottomLeftFar:
|
||||
result._corner.z += _scale.z / 2.0f;
|
||||
break;
|
||||
case bottomRightNear:
|
||||
result._corner.x += _scale.x / 2.0f;
|
||||
break;
|
||||
case bottomRightFar:
|
||||
result._corner.x += _scale.x / 2.0f;
|
||||
result._corner.z += _scale.z / 2.0f;
|
||||
break;
|
||||
}
|
||||
result._scale /= 2.0f; // everything is half the scale
|
||||
return result;
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <QDebug>
|
||||
|
||||
#include "BoxBase.h"
|
||||
#include "GeometryUtil.h"
|
||||
#include "StreamUtils.h"
|
||||
|
||||
class AACube;
|
||||
|
@ -58,6 +59,7 @@ public:
|
|||
const glm::vec3& getMinimumPoint() const { return _corner; }
|
||||
glm::vec3 getMaximumPoint() const { return calcTopFarLeft(); }
|
||||
|
||||
bool contains(const Triangle& triangle) const;
|
||||
bool contains(const glm::vec3& point) const;
|
||||
bool contains(const AABox& otherBox) const;
|
||||
bool touches(const AABox& otherBox) const;
|
||||
|
@ -112,6 +114,19 @@ public:
|
|||
|
||||
void clear() { _corner = INFINITY_VECTOR; _scale = glm::vec3(0.0f); }
|
||||
|
||||
typedef enum {
|
||||
topLeftNear,
|
||||
topLeftFar,
|
||||
topRightNear,
|
||||
topRightFar,
|
||||
bottomLeftNear,
|
||||
bottomLeftFar,
|
||||
bottomRightNear,
|
||||
bottomRightFar
|
||||
} OctreeChild;
|
||||
|
||||
AABox getOctreeChild(OctreeChild child) const; // returns the AABox of the would be octree child of this AABox
|
||||
|
||||
private:
|
||||
glm::vec3 getClosestPointOnFace(const glm::vec3& point, BoxFace face) const;
|
||||
glm::vec3 getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const;
|
||||
|
|
|
@ -77,3 +77,13 @@ float Interpolate::calculateFadeRatio(quint64 start) {
|
|||
const float EASING_SCALE = 1.001f;
|
||||
return std::min(EASING_SCALE * fadeRatio, 1.0f);
|
||||
}
|
||||
|
||||
float Interpolate::easeInOutQuad(float lerpValue) {
|
||||
assert(!((lerpValue < 0.0f) || (lerpValue > 1.0f)));
|
||||
|
||||
if (lerpValue < 0.5f) {
|
||||
return (2.0f * lerpValue * lerpValue);
|
||||
}
|
||||
|
||||
return (lerpValue*(4.0f - 2.0f * lerpValue) - 1.0f);
|
||||
}
|
|
@ -30,6 +30,9 @@ public:
|
|||
static float simpleNonLinearBlend(float fraction);
|
||||
|
||||
static float calculateFadeRatio(quint64 start);
|
||||
|
||||
// Basic ease-in-ease-out function for smoothing values.
|
||||
static float easeInOutQuad(float lerpValue);
|
||||
};
|
||||
|
||||
#endif // hifi_Interpolate_h
|
||||
|
|
|
@ -33,11 +33,11 @@ void RunningMarker::startRunningMarker() {
|
|||
_runningMarkerThread->setObjectName("Running Marker Thread");
|
||||
_runningMarkerThread->start();
|
||||
|
||||
writeRunningMarkerFiler(); // write the first file, even before timer
|
||||
writeRunningMarkerFile(); // write the first file, even before timer
|
||||
|
||||
_runningMarkerTimer = new QTimer();
|
||||
QObject::connect(_runningMarkerTimer, &QTimer::timeout, [=](){
|
||||
writeRunningMarkerFiler();
|
||||
writeRunningMarkerFile();
|
||||
});
|
||||
_runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS);
|
||||
|
||||
|
@ -53,7 +53,7 @@ RunningMarker::~RunningMarker() {
|
|||
_runningMarkerThread->deleteLater();
|
||||
}
|
||||
|
||||
void RunningMarker::writeRunningMarkerFiler() {
|
||||
void RunningMarker::writeRunningMarkerFile() {
|
||||
QFile runningMarkerFile(getFilePath());
|
||||
|
||||
// always write, even it it exists, so that it touches the files
|
||||
|
|
|
@ -27,10 +27,11 @@ public:
|
|||
|
||||
QString getFilePath();
|
||||
static QString getMarkerFilePath(QString name);
|
||||
protected:
|
||||
void writeRunningMarkerFiler();
|
||||
|
||||
void writeRunningMarkerFile();
|
||||
void deleteRunningMarkerFile();
|
||||
|
||||
private:
|
||||
QObject* _parent { nullptr };
|
||||
QString _name;
|
||||
QThread* _runningMarkerThread { nullptr };
|
||||
|
|
|
@ -126,10 +126,8 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) {
|
|||
}
|
||||
|
||||
switch (variantType) {
|
||||
case QVariant::Map:
|
||||
case QVariant::List:
|
||||
case QVariant::Hash: {
|
||||
qCritical() << "Unsupported variant type" << variant.typeName();
|
||||
qCritical() << "Unsupported variant type" << variant.typeName() << ";" << key << variant;
|
||||
Q_ASSERT(false);
|
||||
break;
|
||||
}
|
||||
|
@ -143,6 +141,8 @@ QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map) {
|
|||
case QVariant::UInt:
|
||||
case QVariant::Bool:
|
||||
case QVariant::Double:
|
||||
case QVariant::Map:
|
||||
case QVariant::List:
|
||||
object.insert(key, QJsonValue::fromVariant(variant));
|
||||
break;
|
||||
|
||||
|
|
|
@ -768,9 +768,10 @@ bool similarStrings(const QString& stringA, const QString& stringB) {
|
|||
}
|
||||
|
||||
void disableQtBearerPoll() {
|
||||
// to work around the Qt constant wireless scanning, set the env for polling interval very high
|
||||
const QByteArray EXTREME_BEARER_POLL_TIMEOUT = QString::number(INT16_MAX).toLocal8Bit();
|
||||
qputenv("QT_BEARER_POLL_TIMEOUT", EXTREME_BEARER_POLL_TIMEOUT);
|
||||
// to disable the Qt constant wireless scanning, set the env for polling interval
|
||||
qDebug() << "Disabling Qt wireless polling by using a negative value for QTimer::setInterval";
|
||||
const QByteArray DISABLE_BEARER_POLL_TIMEOUT = QString::number(-1).toLocal8Bit();
|
||||
qputenv("QT_BEARER_POLL_TIMEOUT", DISABLE_BEARER_POLL_TIMEOUT);
|
||||
}
|
||||
|
||||
void printSystemInformation() {
|
||||
|
|
|
@ -12,9 +12,11 @@
|
|||
#include "GLMHelpers.h"
|
||||
#include "TriangleSet.h"
|
||||
|
||||
void TriangleSet::insert(const Triangle& t) {
|
||||
_triangles.push_back(t);
|
||||
|
||||
void TriangleSet::insert(const Triangle& t) {
|
||||
_isBalanced = false;
|
||||
|
||||
_triangles.push_back(t);
|
||||
_bounds += t.v0;
|
||||
_bounds += t.v1;
|
||||
_bounds += t.v2;
|
||||
|
@ -23,39 +25,31 @@ void TriangleSet::insert(const Triangle& t) {
|
|||
void TriangleSet::clear() {
|
||||
_triangles.clear();
|
||||
_bounds.clear();
|
||||
_isBalanced = false;
|
||||
|
||||
_triangleOctree.clear();
|
||||
}
|
||||
|
||||
// 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::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) const {
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) {
|
||||
|
||||
bool intersectedSomething = false;
|
||||
float boxDistance = std::numeric_limits<float>::max();
|
||||
float bestDistance = std::numeric_limits<float>::max();
|
||||
// reset our distance to be the max possible, lower level tests will store best distance here
|
||||
distance = std::numeric_limits<float>::max();
|
||||
|
||||
if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) {
|
||||
if (precision) {
|
||||
for (const auto& triangle : _triangles) {
|
||||
float thisTriangleDistance;
|
||||
if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) {
|
||||
if (thisTriangleDistance < bestDistance) {
|
||||
bestDistance = thisTriangleDistance;
|
||||
intersectedSomething = true;
|
||||
surfaceNormal = triangle.getNormal();
|
||||
distance = bestDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
intersectedSomething = true;
|
||||
distance = boxDistance;
|
||||
}
|
||||
if (!_isBalanced) {
|
||||
balanceOctree();
|
||||
}
|
||||
|
||||
return intersectedSomething;
|
||||
}
|
||||
int trianglesTouched = 0;
|
||||
auto result = _triangleOctree.findRayIntersection(origin, direction, distance, face, surfaceNormal, precision, trianglesTouched);
|
||||
|
||||
#if WANT_DEBUGGING
|
||||
if (precision) {
|
||||
qDebug() << "trianglesTouched :" << trianglesTouched << "out of:" << _triangleOctree._population << "_triangles.size:" << _triangles.size();
|
||||
}
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TriangleSet::convexHullContains(const glm::vec3& point) const {
|
||||
if (!_bounds.contains(point)) {
|
||||
|
@ -74,3 +68,198 @@ bool TriangleSet::convexHullContains(const glm::vec3& point) const {
|
|||
return insideMesh;
|
||||
}
|
||||
|
||||
void TriangleSet::debugDump() {
|
||||
qDebug() << __FUNCTION__;
|
||||
qDebug() << "bounds:" << getBounds();
|
||||
qDebug() << "triangles:" << size() << "at top level....";
|
||||
qDebug() << "----- _triangleOctree -----";
|
||||
_triangleOctree.debugDump();
|
||||
}
|
||||
|
||||
void TriangleSet::balanceOctree() {
|
||||
_triangleOctree.reset(_bounds, 0);
|
||||
|
||||
// insert all the triangles
|
||||
for (size_t i = 0; i < _triangles.size(); i++) {
|
||||
_triangleOctree.insert(i);
|
||||
}
|
||||
|
||||
_isBalanced = true;
|
||||
|
||||
#if WANT_DEBUGGING
|
||||
debugDump();
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// 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) {
|
||||
|
||||
bool intersectedSomething = false;
|
||||
float boxDistance = distance;
|
||||
float bestDistance = distance;
|
||||
|
||||
if (_bounds.findRayIntersection(origin, direction, boxDistance, face, surfaceNormal)) {
|
||||
|
||||
// if our bounding box intersects at a distance greater than the current known
|
||||
// best distance, than we can safely not check any of our triangles
|
||||
if (boxDistance > bestDistance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (precision) {
|
||||
for (const auto& triangleIndex : _triangleIndices) {
|
||||
const auto& triangle = _allTriangles[triangleIndex];
|
||||
float thisTriangleDistance;
|
||||
trianglesTouched++;
|
||||
if (findRayTriangleIntersection(origin, direction, triangle, thisTriangleDistance)) {
|
||||
if (thisTriangleDistance < bestDistance) {
|
||||
bestDistance = thisTriangleDistance;
|
||||
intersectedSomething = true;
|
||||
surfaceNormal = triangle.getNormal();
|
||||
distance = bestDistance;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
intersectedSomething = true;
|
||||
distance = boxDistance;
|
||||
}
|
||||
}
|
||||
|
||||
return intersectedSomething;
|
||||
}
|
||||
|
||||
static const int MAX_DEPTH = 4; // for now
|
||||
static const int MAX_CHILDREN = 8;
|
||||
|
||||
TriangleSet::TriangleOctreeCell::TriangleOctreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth) :
|
||||
_allTriangles(allTriangles)
|
||||
{
|
||||
reset(bounds, depth);
|
||||
}
|
||||
|
||||
void TriangleSet::TriangleOctreeCell::clear() {
|
||||
_population = 0;
|
||||
_triangleIndices.clear();
|
||||
_bounds.clear();
|
||||
_children.clear();
|
||||
}
|
||||
|
||||
void TriangleSet::TriangleOctreeCell::reset(const AABox& bounds, int depth) {
|
||||
clear();
|
||||
_bounds = bounds;
|
||||
_depth = depth;
|
||||
}
|
||||
|
||||
void TriangleSet::TriangleOctreeCell::debugDump() {
|
||||
qDebug() << __FUNCTION__;
|
||||
qDebug() << "bounds:" << getBounds();
|
||||
qDebug() << "depth:" << _depth;
|
||||
qDebug() << "population:" << _population << "this level or below"
|
||||
<< " ---- triangleIndices:" << _triangleIndices.size() << "in this cell";
|
||||
|
||||
qDebug() << "child cells:" << _children.size();
|
||||
if (_depth < MAX_DEPTH) {
|
||||
int childNum = 0;
|
||||
for (auto& child : _children) {
|
||||
qDebug() << "child:" << childNum;
|
||||
child.second.debugDump();
|
||||
childNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void TriangleSet::TriangleOctreeCell::insert(size_t triangleIndex) {
|
||||
const Triangle& triangle = _allTriangles[triangleIndex];
|
||||
_population++;
|
||||
// if we're not yet at the max depth, then check which child the triangle fits in
|
||||
if (_depth < MAX_DEPTH) {
|
||||
|
||||
for (int child = 0; child < MAX_CHILDREN; child++) {
|
||||
AABox childBounds = getBounds().getOctreeChild((AABox::OctreeChild)child);
|
||||
|
||||
|
||||
// if the child AABox would contain the triangle...
|
||||
if (childBounds.contains(triangle)) {
|
||||
// if the child cell doesn't yet exist, create it...
|
||||
if (_children.find((AABox::OctreeChild)child) == _children.end()) {
|
||||
_children.insert(
|
||||
std::pair<AABox::OctreeChild, TriangleOctreeCell>
|
||||
((AABox::OctreeChild)child, TriangleOctreeCell(_allTriangles, childBounds, _depth + 1)));
|
||||
}
|
||||
|
||||
// insert the triangleIndex in the child cell
|
||||
_children.at((AABox::OctreeChild)child).insert(triangleIndex);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
// either we're at max depth, or the triangle doesn't fit in one of our
|
||||
// children and so we want to just record it here
|
||||
_triangleIndices.push_back(triangleIndex);
|
||||
}
|
||||
|
||||
bool TriangleSet::TriangleOctreeCell::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched) {
|
||||
|
||||
if (_population < 1) {
|
||||
return false; // no triangles below here, so we can't intersect
|
||||
}
|
||||
|
||||
float bestLocalDistance = distance;
|
||||
BoxFace bestLocalFace;
|
||||
glm::vec3 bestLocalNormal;
|
||||
bool intersects = false;
|
||||
|
||||
// if the ray intersects our bounding box, then continue
|
||||
if (getBounds().findRayIntersection(origin, direction, bestLocalDistance, bestLocalFace, bestLocalNormal)) {
|
||||
|
||||
// if the intersection with our bounding box, is greater than the current best distance (the distance passed in)
|
||||
// then we know that none of our triangles can represent a better intersection and we can return
|
||||
|
||||
if (bestLocalDistance > distance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bestLocalDistance = distance;
|
||||
|
||||
float childDistance = distance;
|
||||
BoxFace childFace;
|
||||
glm::vec3 childNormal;
|
||||
|
||||
// if we're not yet at the max depth, then check which child the triangle fits in
|
||||
if (_depth < MAX_DEPTH) {
|
||||
for (auto& child : _children) {
|
||||
// check each child, if there's an intersection, it will return some distance that we need
|
||||
// to compare against the other results, because there might be multiple intersections and
|
||||
// we will always choose the best (shortest) intersection
|
||||
if (child.second.findRayIntersection(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) {
|
||||
if (childDistance < bestLocalDistance) {
|
||||
bestLocalDistance = childDistance;
|
||||
bestLocalFace = childFace;
|
||||
bestLocalNormal = childNormal;
|
||||
intersects = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// also check our local triangle set
|
||||
if (findRayIntersectionInternal(origin, direction, childDistance, childFace, childNormal, precision, trianglesTouched)) {
|
||||
if (childDistance < bestLocalDistance) {
|
||||
bestLocalDistance = childDistance;
|
||||
bestLocalFace = childFace;
|
||||
bestLocalNormal = childNormal;
|
||||
intersects = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (intersects) {
|
||||
distance = bestLocalDistance;
|
||||
face = bestLocalFace;
|
||||
surfaceNormal = bestLocalNormal;
|
||||
}
|
||||
return intersects;
|
||||
}
|
||||
|
|
|
@ -15,19 +15,64 @@
|
|||
#include "GeometryUtil.h"
|
||||
|
||||
class TriangleSet {
|
||||
public:
|
||||
void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles
|
||||
size_t size() const { return _triangles.size(); }
|
||||
|
||||
const Triangle& getTriangle(size_t t) const { return _triangles[t]; }
|
||||
class TriangleOctreeCell {
|
||||
public:
|
||||
TriangleOctreeCell(std::vector<Triangle>& allTriangles) :
|
||||
_allTriangles(allTriangles)
|
||||
{ }
|
||||
|
||||
void insert(size_t triangleIndex);
|
||||
void reset(const AABox& bounds, int depth = 0);
|
||||
void clear();
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
|
||||
|
||||
const AABox& getBounds() const { return _bounds; }
|
||||
|
||||
void debugDump();
|
||||
|
||||
protected:
|
||||
TriangleOctreeCell(std::vector<Triangle>& allTriangles, const AABox& bounds, int depth);
|
||||
|
||||
// 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);
|
||||
|
||||
std::vector<Triangle>& _allTriangles;
|
||||
std::map<AABox::OctreeChild, TriangleOctreeCell> _children;
|
||||
int _depth{ 0 };
|
||||
int _population{ 0 };
|
||||
AABox _bounds;
|
||||
std::vector<size_t> _triangleIndices;
|
||||
|
||||
friend class TriangleSet;
|
||||
};
|
||||
|
||||
public:
|
||||
TriangleSet() :
|
||||
_triangleOctree(_triangles)
|
||||
{}
|
||||
|
||||
void debugDump();
|
||||
|
||||
void insert(const Triangle& t);
|
||||
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision);
|
||||
|
||||
void balanceOctree();
|
||||
|
||||
void reserve(size_t size) { _triangles.reserve(size); } // reserve space in the datastructure for size number of triangles
|
||||
size_t size() const { return _triangles.size(); }
|
||||
void clear();
|
||||
|
||||
// Determine if 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 findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision) const;
|
||||
// note: this might side-effect internal structures
|
||||
bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction,
|
||||
float& distance, BoxFace& face, glm::vec3& surfaceNormal, bool precision, int& trianglesTouched);
|
||||
|
||||
// Determine if a point is "inside" all the triangles of a convex hull. It is the responsibility of the caller to
|
||||
// determine that the triangle set is indeed a convex hull. If the triangles added to this set are not in fact a
|
||||
|
@ -35,7 +80,10 @@ public:
|
|||
bool convexHullContains(const glm::vec3& point) const;
|
||||
const AABox& getBounds() const { return _bounds; }
|
||||
|
||||
private:
|
||||
protected:
|
||||
|
||||
bool _isBalanced{ false };
|
||||
TriangleOctreeCell _triangleOctree;
|
||||
std::vector<Triangle> _triangles;
|
||||
AABox _bounds;
|
||||
};
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue