Merge branch 'master' of https://github.com/highfidelity/hifi into update-from-master-1

This commit is contained in:
howard-stearns 2017-05-18 15:28:40 -07:00
commit 851911f55a
120 changed files with 2499 additions and 1494 deletions

View file

@ -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();

View file

@ -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.";
}

View file

@ -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;
};

View file

@ -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

View file

@ -79,6 +79,7 @@ private:
QString percentageForMixStats(int counter);
void parseSettingsObject(const QJsonObject& settingsObject);
void clearDomainSettings();
float _trailingMixRatio { 0.0f };
float _throttlingRatio { 0.0f };

View file

@ -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";

View file

@ -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;

View file

@ -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

View file

@ -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]
}
]
},

View file

@ -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 {

View file

@ -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();
}
}

View file

@ -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();

View file

@ -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:

View file

@ -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()),

View file

@ -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...";

View file

@ -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);

View file

@ -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;

View file

@ -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() {

View file

@ -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:

View file

@ -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()) {

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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();
}

View file

@ -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) {

View file

@ -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)
{
}

View file

@ -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

View file

@ -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;
}
}

View file

@ -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

View file

@ -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;
}

View file

@ -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);

View file

@ -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;

View file

@ -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) {

View file

@ -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

View file

@ -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);
}

View file

@ -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;

View file

@ -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:

View file

@ -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

View file

@ -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());

View file

@ -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;

View file

@ -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();
};

View file

@ -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;
}

View file

@ -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;

View file

@ -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()

View file

@ -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; }

View file

@ -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()

View file

@ -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() {

View file

@ -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:

View file

@ -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) {

View file

@ -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&);

View file

@ -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;
}
}

View file

@ -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);

View file

@ -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);
}
}

View file

@ -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)),

View file

@ -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;

View file

@ -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:

View file

@ -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 };

View file

@ -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;
}
}
}

View file

@ -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__);

View file

@ -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.

View file

@ -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;

View file

@ -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) {

View file

@ -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;

View file

@ -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();
}

View file

@ -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];
//

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}

View 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 {

View file

@ -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));

View file

@ -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

View 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??
}
}

View file

@ -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

View file

@ -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:

View file

@ -234,7 +234,8 @@ enum class AvatarMixerPacketVersion : PacketVersion {
VariableAvatarData,
AvatarAsChildFixes,
StickAndBallDefaultAvatar,
IdentityPacketsIncludeUpdateTime
IdentityPacketsIncludeUpdateTime,
AvatarIdentitySequenceId
};
enum class DomainConnectRequestVersion : PacketVersion {

View file

@ -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) {

View file

@ -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);

View file

@ -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);
}

View file

@ -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

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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) {

View file

@ -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();

View file

@ -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 {

View file

@ -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 };

View file

@ -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);
}

View file

@ -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:

View file

@ -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;
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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

View file

@ -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

View file

@ -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 };

View file

@ -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;

View file

@ -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() {

View file

@ -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;
}

View file

@ -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