mirror of
https://github.com/overte-org/overte.git
synced 2025-08-09 14:08:51 +02:00
merge fix
This commit is contained in:
commit
432622d168
26 changed files with 580 additions and 281 deletions
|
@ -309,8 +309,15 @@ void DomainServer::setupDynamicIPAddressUpdating() {
|
||||||
// send public socket changes to the data server so nodes can find us at our new IP
|
// send public socket changes to the data server so nodes can find us at our new IP
|
||||||
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer);
|
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer);
|
||||||
|
|
||||||
// check our IP address right away
|
if (!AccountManager::getInstance().hasValidAccessToken()) {
|
||||||
requestCurrentIPAddressViaSTUN();
|
// we don't have an access token to talk to data-web yet, so
|
||||||
|
// check our IP address as soon as we get an AccountManager access token
|
||||||
|
connect(&AccountManager::getInstance(), &AccountManager::loginComplete,
|
||||||
|
this, &DomainServer::requestCurrentIPAddressViaSTUN);
|
||||||
|
} else {
|
||||||
|
// access token good to go, attempt to update our IP now
|
||||||
|
requestCurrentIPAddressViaSTUN();
|
||||||
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID."
|
qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID."
|
||||||
|
|
|
@ -16,17 +16,30 @@ var debug = false;
|
||||||
var movingWithHead = false;
|
var movingWithHead = false;
|
||||||
var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw;
|
var headStartPosition, headStartDeltaPitch, headStartFinalPitch, headStartRoll, headStartYaw;
|
||||||
|
|
||||||
var HEAD_MOVE_DEAD_ZONE = 0.0;
|
var HEAD_MOVE_DEAD_ZONE = 0.10;
|
||||||
var HEAD_STRAFE_DEAD_ZONE = 0.0;
|
var HEAD_STRAFE_DEAD_ZONE = 0.0;
|
||||||
var HEAD_ROTATE_DEAD_ZONE = 0.0;
|
var HEAD_ROTATE_DEAD_ZONE = 0.0;
|
||||||
var HEAD_THRUST_FWD_SCALE = 12000.0;
|
//var HEAD_THRUST_FWD_SCALE = 12000.0;
|
||||||
var HEAD_THRUST_STRAFE_SCALE = 2000.0;
|
//var HEAD_THRUST_STRAFE_SCALE = 0.0;
|
||||||
var HEAD_YAW_RATE = 1.0;
|
var HEAD_YAW_RATE = 1.0;
|
||||||
var HEAD_PITCH_RATE = 1.0;
|
var HEAD_PITCH_RATE = 1.0;
|
||||||
var HEAD_ROLL_THRUST_SCALE = 75.0;
|
//var HEAD_ROLL_THRUST_SCALE = 75.0;
|
||||||
var HEAD_PITCH_LIFT_THRUST = 3.0;
|
//var HEAD_PITCH_LIFT_THRUST = 3.0;
|
||||||
var WALL_BOUNCE = 4000.0;
|
var WALL_BOUNCE = 4000.0;
|
||||||
|
|
||||||
|
// Modify these values to tweak the strength of the motion.
|
||||||
|
// A larger *FACTOR increases the speed.
|
||||||
|
// A lower SHORT_TIMESCALE makes the motor achieve full speed faster.
|
||||||
|
var HEAD_VELOCITY_FWD_FACTOR = 20.0;
|
||||||
|
var HEAD_VELOCITY_LEFT_FACTOR = 20.0;
|
||||||
|
var HEAD_VELOCITY_UP_FACTOR = 20.0;
|
||||||
|
var SHORT_TIMESCALE = 0.125;
|
||||||
|
var VERY_LARGE_TIMESCALE = 1000000.0;
|
||||||
|
|
||||||
|
var xAxis = {x:1.0, y:0.0, z:0.0 };
|
||||||
|
var yAxis = {x:0.0, y:1.0, z:0.0 };
|
||||||
|
var zAxis = {x:0.0, y:0.0, z:1.0 };
|
||||||
|
|
||||||
// If these values are set to something
|
// If these values are set to something
|
||||||
var maxVelocity = 1.25;
|
var maxVelocity = 1.25;
|
||||||
var noFly = true;
|
var noFly = true;
|
||||||
|
@ -51,8 +64,8 @@ function isInRoom(position) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function moveWithHead(deltaTime) {
|
function moveWithHead(deltaTime) {
|
||||||
var thrust = { x: 0, y: 0, z: 0 };
|
|
||||||
var position = MyAvatar.position;
|
var position = MyAvatar.position;
|
||||||
|
var motorTimescale = VERY_LARGE_TIMESCALE;
|
||||||
if (movingWithHead) {
|
if (movingWithHead) {
|
||||||
var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw;
|
var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw;
|
||||||
var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch;
|
var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch;
|
||||||
|
@ -64,44 +77,36 @@ function moveWithHead(deltaTime) {
|
||||||
headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta);
|
headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta);
|
||||||
headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion
|
headDelta.y = 0.0; // Don't respond to any of the vertical component of head motion
|
||||||
|
|
||||||
var forward = Quat.getFront(Camera.getOrientation());
|
|
||||||
var right = Quat.getRight(Camera.getOrientation());
|
|
||||||
var up = Quat.getUp(Camera.getOrientation());
|
|
||||||
if (noFly) {
|
|
||||||
forward.y = 0.0;
|
|
||||||
forward = Vec3.normalize(forward);
|
|
||||||
right.y = 0.0;
|
|
||||||
right = Vec3.normalize(right);
|
|
||||||
up = { x: 0, y: 1, z: 0};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Thrust based on leaning forward and side-to-side
|
// Thrust based on leaning forward and side-to-side
|
||||||
|
var targetVelocity = {x:0.0, y:0.0, z:0.0};
|
||||||
if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) {
|
if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) {
|
||||||
if (Math.abs(Vec3.dot(velocity, forward)) < maxVelocity) {
|
targetVelocity = Vec3.multiply(zAxis, -headDelta.z * HEAD_VELOCITY_FWD_FACTOR);
|
||||||
thrust = Vec3.sum(thrust, Vec3.multiply(forward, -headDelta.z * HEAD_THRUST_FWD_SCALE * deltaTime));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) {
|
if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) {
|
||||||
if (Math.abs(Vec3.dot(velocity, right)) < maxVelocity) {
|
var deltaVelocity = Vec3.multiply(xAxis, -headDelta.x * HEAD_VELOCITY_LEFT_FACTOR);
|
||||||
thrust = Vec3.sum(thrust, Vec3.multiply(right, headDelta.x * HEAD_THRUST_STRAFE_SCALE * deltaTime));
|
targetVelocity = Vec3.sum(targetVelocity, deltaVelocity);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) {
|
if (Math.abs(deltaYaw) > HEAD_ROTATE_DEAD_ZONE) {
|
||||||
var orientation = Quat.multiply(Quat.angleAxis((deltaYaw + deltaRoll) * HEAD_YAW_RATE * deltaTime, {x:0, y: 1, z:0}), MyAvatar.orientation);
|
var orientation = Quat.multiply(Quat.angleAxis((deltaYaw + deltaRoll) * HEAD_YAW_RATE * deltaTime, yAxis), MyAvatar.orientation);
|
||||||
MyAvatar.orientation = orientation;
|
MyAvatar.orientation = orientation;
|
||||||
}
|
}
|
||||||
// Thrust Up/Down based on head pitch
|
// Thrust Up/Down based on head pitch
|
||||||
if (!noFly) {
|
if (!noFly) {
|
||||||
if ((Math.abs(Vec3.dot(velocity, up)) < maxVelocity)) {
|
var deltaVelocity = Vec3.multiply(yAxis, headDelta.y * HEAD_VELOCITY_UP_FACTOR);
|
||||||
thrust = Vec3.sum(thrust, Vec3.multiply({ x:0, y:1, z:0 }, (MyAvatar.getHeadFinalPitch() - headStartFinalPitch) * HEAD_PITCH_LIFT_THRUST * deltaTime));
|
targetVelocity = Vec3.sum(targetVelocity, deltaVelocity);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// For head trackers, adjust pitch by head pitch
|
// For head trackers, adjust pitch by head pitch
|
||||||
MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime;
|
MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime;
|
||||||
|
|
||||||
|
// apply the motor
|
||||||
|
MyAvatar.motorVelocity = targetVelocity;
|
||||||
|
motorTimescale = SHORT_TIMESCALE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check against movement box limits
|
||||||
if (isInRoom(position)) {
|
if (isInRoom(position)) {
|
||||||
// Impose constraints to keep you in the space
|
var thrust = { x: 0, y: 0, z: 0 };
|
||||||
|
// use thrust to constrain the avatar to the space
|
||||||
if (position.x < roomLimits.xMin) {
|
if (position.x < roomLimits.xMin) {
|
||||||
thrust.x += (roomLimits.xMin - position.x) * WALL_BOUNCE * deltaTime;
|
thrust.x += (roomLimits.xMin - position.x) * WALL_BOUNCE * deltaTime;
|
||||||
} else if (position.x > roomLimits.xMax) {
|
} else if (position.x > roomLimits.xMax) {
|
||||||
|
@ -112,11 +117,14 @@ function moveWithHead(deltaTime) {
|
||||||
} else if (position.z > roomLimits.zMax) {
|
} else if (position.z > roomLimits.zMax) {
|
||||||
thrust.z += (roomLimits.zMax - position.z) * WALL_BOUNCE * deltaTime;
|
thrust.z += (roomLimits.zMax - position.z) * WALL_BOUNCE * deltaTime;
|
||||||
}
|
}
|
||||||
|
MyAvatar.addThrust(thrust);
|
||||||
|
if (movingWithHead && Vec3.length(thrust) > 0.0) {
|
||||||
|
// reduce the timescale of the motor so that it won't defeat the thrust code
|
||||||
|
Vec3.print("adebug room containment thrust = ", thrust);
|
||||||
|
motorTimescale = 4.0 * SHORT_TIMESCALE;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
MyAvatar.motorTimescale = motorTimescale;
|
||||||
// Check against movement box limits
|
|
||||||
|
|
||||||
MyAvatar.addThrust(thrust);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.keyPressEvent.connect(function(event) {
|
Controller.keyPressEvent.connect(function(event) {
|
||||||
|
@ -126,13 +134,21 @@ Controller.keyPressEvent.connect(function(event) {
|
||||||
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
|
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
|
||||||
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
|
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
|
||||||
headStartRoll = MyAvatar.getHeadFinalRoll();
|
headStartRoll = MyAvatar.getHeadFinalRoll();
|
||||||
headStartYaw = MyAvatar.getHeadFinalYaw();
|
headStartYaw = MyAvatar.getHeadFinalYaw();
|
||||||
}
|
// start with disabled motor -- it will be updated shortly
|
||||||
|
MyAvatar.motorTimescale = VERY_LARGE_TIMESCALE;
|
||||||
|
MyAvatar.motorVelocity = {x:0.0, y:0.0, z:0.0};
|
||||||
|
MyAvatar.motorReferenceFrame = "camera"; // alternatives are: "avatar" and "world"
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Controller.keyReleaseEvent.connect(function(event) {
|
Controller.keyReleaseEvent.connect(function(event) {
|
||||||
if (event.text == "SPACE") {
|
if (event.text == "SPACE") {
|
||||||
movingWithHead = false;
|
movingWithHead = false;
|
||||||
}
|
// disable motor by giving it an obnoxiously large timescale
|
||||||
|
MyAvatar.motorTimescale = VERY_LARGE_TIMESCALE;
|
||||||
|
MyAvatar.motorVelocity = {x:0.0, y:0.0, z:0.0};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Script.update.connect(moveWithHead);
|
Script.update.connect(moveWithHead);
|
||||||
|
|
|
@ -288,6 +288,9 @@ Application::Application(int& argc, char** argv, QElapsedTimer &startup_time) :
|
||||||
// set the account manager's root URL and trigger a login request if we don't have the access token
|
// set the account manager's root URL and trigger a login request if we don't have the access token
|
||||||
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
|
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
|
||||||
UserActivityLogger::getInstance().launch(applicationVersion());
|
UserActivityLogger::getInstance().launch(applicationVersion());
|
||||||
|
|
||||||
|
// grab the location manager instance early so it lives in our thread
|
||||||
|
LocationManager::getInstance();
|
||||||
|
|
||||||
// once the event loop has started, check and signal for an access token
|
// once the event loop has started, check and signal for an access token
|
||||||
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
|
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
|
||||||
|
@ -927,6 +930,13 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Qt::Key_N:
|
||||||
|
if (isMeta) {
|
||||||
|
Menu::getInstance()->triggerOption(MenuOption::NameLocation);
|
||||||
|
}
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case Qt::Key_Up:
|
case Qt::Key_Up:
|
||||||
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
|
||||||
|
@ -3890,7 +3900,7 @@ void Application::stopAllScripts(bool restart) {
|
||||||
// HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities
|
// HACK: ATM scripts cannot set/get their animation priorities, so we clear priorities
|
||||||
// whenever a script stops in case it happened to have been setting joint rotations.
|
// whenever a script stops in case it happened to have been setting joint rotations.
|
||||||
// TODO: expose animation priorities and provide a layered animation control system.
|
// TODO: expose animation priorities and provide a layered animation control system.
|
||||||
_myAvatar->clearJointAnimationPriorities();
|
_myAvatar->clearScriptableSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::stopScript(const QString &scriptName) {
|
void Application::stopScript(const QString &scriptName) {
|
||||||
|
@ -3903,6 +3913,9 @@ void Application::stopScript(const QString &scriptName) {
|
||||||
// TODO: expose animation priorities and provide a layered animation control system.
|
// TODO: expose animation priorities and provide a layered animation control system.
|
||||||
_myAvatar->clearJointAnimationPriorities();
|
_myAvatar->clearJointAnimationPriorities();
|
||||||
}
|
}
|
||||||
|
if (_scriptEnginesHash.empty()) {
|
||||||
|
_myAvatar->clearScriptableSettings();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::reloadAllScripts() {
|
void Application::reloadAllScripts() {
|
||||||
|
|
|
@ -268,13 +268,17 @@ Menu::Menu() :
|
||||||
SLOT(resetSize()));
|
SLOT(resetSize()));
|
||||||
|
|
||||||
QObject* avatar = appInstance->getAvatar();
|
QObject* avatar = appInstance->getAvatar();
|
||||||
|
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::KeyboardMotorControl,
|
||||||
|
Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar, SLOT(updateMotionBehavior()));
|
||||||
|
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ScriptedMotorControl, 0, true,
|
||||||
|
avatar, SLOT(updateMotionBehavior()));
|
||||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ChatCircling, 0, false);
|
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ChatCircling, 0, false);
|
||||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::GlowWhenSpeaking, 0, true);
|
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::GlowWhenSpeaking, 0, true);
|
||||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true);
|
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true);
|
||||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false,
|
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false,
|
||||||
avatar, SLOT(updateMotionBehaviorsFromMenu()));
|
avatar, SLOT(updateMotionBehavior()));
|
||||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true,
|
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true,
|
||||||
avatar, SLOT(updateMotionBehaviorsFromMenu()));
|
avatar, SLOT(updateMotionBehavior()));
|
||||||
|
|
||||||
QMenu* collisionsMenu = avatarMenu->addMenu("Collide With...");
|
QMenu* collisionsMenu = avatarMenu->addMenu("Collide With...");
|
||||||
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll, 0, false,
|
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll, 0, false,
|
||||||
|
@ -328,9 +332,9 @@ Menu::Menu() :
|
||||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes,
|
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes,
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_1, false,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_1, false,
|
||||||
&nodeBounds, SLOT(setShowVoxelNodes(bool)));
|
&nodeBounds, SLOT(setShowVoxelNodes(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersModelNodes,
|
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersEntityNodes,
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_2, false,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_2, false,
|
||||||
&nodeBounds, SLOT(setShowModelNodes(bool)));
|
&nodeBounds, SLOT(setShowEntityNodes(bool)));
|
||||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersParticleNodes,
|
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersParticleNodes,
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_3, false,
|
Qt::CTRL | Qt::SHIFT | Qt::Key_3, false,
|
||||||
&nodeBounds, SLOT(setShowParticleNodes(bool)));
|
&nodeBounds, SLOT(setShowParticleNodes(bool)));
|
||||||
|
@ -744,9 +748,11 @@ void Menu::loadSettings(QSettings* settings) {
|
||||||
|
|
||||||
// MyAvatar caches some menu options, so we have to update them whenever we load settings.
|
// MyAvatar caches some menu options, so we have to update them whenever we load settings.
|
||||||
// TODO: cache more settings in MyAvatar that are checked with very high frequency.
|
// TODO: cache more settings in MyAvatar that are checked with very high frequency.
|
||||||
|
setIsOptionChecked(MenuOption::KeyboardMotorControl , true);
|
||||||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||||
myAvatar->updateCollisionGroups();
|
myAvatar->updateCollisionGroups();
|
||||||
myAvatar->onToggleRagdoll();
|
myAvatar->onToggleRagdoll();
|
||||||
|
myAvatar->updateMotionBehavior();
|
||||||
|
|
||||||
if (lockedSettings) {
|
if (lockedSettings) {
|
||||||
Application::getInstance()->unlockSettings();
|
Application::getInstance()->unlockSettings();
|
||||||
|
|
|
@ -399,6 +399,7 @@ namespace MenuOption {
|
||||||
const QString HeadMouse = "Head Mouse";
|
const QString HeadMouse = "Head Mouse";
|
||||||
const QString IncreaseAvatarSize = "Increase Avatar Size";
|
const QString IncreaseAvatarSize = "Increase Avatar Size";
|
||||||
const QString IncreaseVoxelSize = "Increase Voxel Size";
|
const QString IncreaseVoxelSize = "Increase Voxel Size";
|
||||||
|
const QString KeyboardMotorControl = "Enable Keyboard Motor Control";
|
||||||
const QString LoadScript = "Open and Run Script File...";
|
const QString LoadScript = "Open and Run Script File...";
|
||||||
const QString LoadScriptURL = "Open and Run Script from URL...";
|
const QString LoadScriptURL = "Open and Run Script from URL...";
|
||||||
const QString LodTools = "LOD Tools";
|
const QString LodTools = "LOD Tools";
|
||||||
|
@ -437,9 +438,10 @@ namespace MenuOption {
|
||||||
const QString RunningScripts = "Running Scripts";
|
const QString RunningScripts = "Running Scripts";
|
||||||
const QString RunTimingTests = "Run Timing Tests";
|
const QString RunTimingTests = "Run Timing Tests";
|
||||||
const QString ScriptEditor = "Script Editor...";
|
const QString ScriptEditor = "Script Editor...";
|
||||||
|
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
||||||
const QString SettingsExport = "Export Settings";
|
const QString SettingsExport = "Export Settings";
|
||||||
const QString SettingsImport = "Import Settings";
|
const QString SettingsImport = "Import Settings";
|
||||||
const QString ShowBordersModelNodes = "Show Model Nodes";
|
const QString ShowBordersEntityNodes = "Show Entity Nodes";
|
||||||
const QString ShowBordersParticleNodes = "Show Particle Nodes";
|
const QString ShowBordersParticleNodes = "Show Particle Nodes";
|
||||||
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
|
const QString ShowBordersVoxelNodes = "Show Voxel Nodes";
|
||||||
const QString ShowIKConstraints = "Show IK Constraints";
|
const QString ShowIKConstraints = "Show IK Constraints";
|
||||||
|
|
|
@ -42,17 +42,15 @@ ScriptsModel::ScriptsModel(QObject* parent) :
|
||||||
_localDirectory(),
|
_localDirectory(),
|
||||||
_fsWatcher(),
|
_fsWatcher(),
|
||||||
_localFiles(),
|
_localFiles(),
|
||||||
_remoteFiles() {
|
_remoteFiles()
|
||||||
|
{
|
||||||
QString scriptPath = Menu::getInstance()->getScriptsLocation();
|
|
||||||
|
|
||||||
_localDirectory.setPath(scriptPath);
|
|
||||||
_localDirectory.setFilter(QDir::Files | QDir::Readable);
|
_localDirectory.setFilter(QDir::Files | QDir::Readable);
|
||||||
_localDirectory.setNameFilters(QStringList("*.js"));
|
_localDirectory.setNameFilters(QStringList("*.js"));
|
||||||
|
|
||||||
_fsWatcher.addPath(_localDirectory.absolutePath());
|
updateScriptsLocation(Menu::getInstance()->getScriptsLocation());
|
||||||
|
|
||||||
connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles);
|
connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles);
|
||||||
|
|
||||||
connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation);
|
connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation);
|
||||||
|
|
||||||
reloadLocalFiles();
|
reloadLocalFiles();
|
||||||
|
@ -88,8 +86,13 @@ int ScriptsModel::rowCount(const QModelIndex& parent) const {
|
||||||
|
|
||||||
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
|
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
|
||||||
_fsWatcher.removePath(_localDirectory.absolutePath());
|
_fsWatcher.removePath(_localDirectory.absolutePath());
|
||||||
|
|
||||||
_localDirectory.setPath(newPath);
|
_localDirectory.setPath(newPath);
|
||||||
_fsWatcher.addPath(_localDirectory.absolutePath());
|
|
||||||
|
if (!_localDirectory.absolutePath().isEmpty()) {
|
||||||
|
_fsWatcher.addPath(_localDirectory.absolutePath());
|
||||||
|
}
|
||||||
|
|
||||||
reloadLocalFiles();
|
reloadLocalFiles();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -57,15 +57,15 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX
|
||||||
* joint.rotation, DEFAULT_PRIORITY);
|
* joint.rotation, DEFAULT_PRIORITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FaceModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
void FaceModel::maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||||
// likewise with the eye joints
|
// likewise with the eye joints
|
||||||
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
|
// NOTE: at the moment we do the math in the world-frame, hence the inverse transform is more complex than usual.
|
||||||
glm::mat4 inverse = glm::inverse(glm::mat4_cast(_rotation) * parentState.getTransform() *
|
glm::mat4 inverse = glm::inverse(glm::mat4_cast(model->getRotation()) * parentState.getTransform() *
|
||||||
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
|
glm::translate(state.getDefaultTranslationInConstrainedFrame()) *
|
||||||
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
|
joint.preTransform * glm::mat4_cast(joint.preRotation * joint.rotation));
|
||||||
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
|
glm::vec3 front = glm::vec3(inverse * glm::vec4(_owningHead->getFinalOrientationInWorldFrame() * IDENTITY_FRONT, 0.0f));
|
||||||
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() +
|
glm::vec3 lookAt = glm::vec3(inverse * glm::vec4(_owningHead->getLookAtPosition() +
|
||||||
_owningHead->getSaccade() - _translation, 1.0f));
|
_owningHead->getSaccade() - model->getTranslation(), 1.0f));
|
||||||
glm::quat between = rotationBetween(front, lookAt);
|
glm::quat between = rotationBetween(front, lookAt);
|
||||||
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
|
const float MAX_ANGLE = 30.0f * RADIANS_PER_DEGREE;
|
||||||
state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
|
state.setRotationInConstrainedFrame(glm::angleAxis(glm::clamp(glm::angle(between), -MAX_ANGLE, MAX_ANGLE), glm::axis(between)) *
|
||||||
|
@ -82,7 +82,7 @@ void FaceModel::updateJointState(int index) {
|
||||||
maybeUpdateNeckRotation(parentState, joint, state);
|
maybeUpdateNeckRotation(parentState, joint, state);
|
||||||
|
|
||||||
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
|
} else if (index == geometry.leftEyeJointIndex || index == geometry.rightEyeJointIndex) {
|
||||||
maybeUpdateEyeRotation(parentState, joint, state);
|
maybeUpdateEyeRotation(this, parentState, joint, state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
virtual void simulate(float deltaTime, bool fullUpdate = true);
|
||||||
|
|
||||||
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
virtual void maybeUpdateNeckRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||||
virtual void maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state);
|
virtual void maybeUpdateEyeRotation(Model* model, const JointState& parentState, const FBXJoint& joint, JointState& state);
|
||||||
virtual void updateJointState(int index);
|
virtual void updateJointState(int index);
|
||||||
|
|
||||||
/// Retrieve the positions of up to two eye meshes.
|
/// Retrieve the positions of up to two eye meshes.
|
||||||
|
|
|
@ -194,8 +194,8 @@ void Head::relaxLean(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Head::render(float alpha, Model::RenderMode mode) {
|
void Head::render(float alpha, Model::RenderMode mode) {
|
||||||
if (_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)) &&
|
_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows));
|
||||||
_renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) {
|
if (_renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) {
|
||||||
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
|
renderLookatVectors(_leftEyePosition, _rightEyePosition, _lookAtPosition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,9 +54,14 @@ const float MAX_WALKING_SPEED = 3.0f * MIN_KEYBOARD_CONTROL_SPEED;
|
||||||
|
|
||||||
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
|
// TODO: normalize avatar speed for standard avatar size, then scale all motion logic
|
||||||
// to properly follow avatar size.
|
// to properly follow avatar size.
|
||||||
float DEFAULT_MOTOR_TIMESCALE = 0.25f;
|
|
||||||
float MAX_AVATAR_SPEED = 300.0f;
|
float MAX_AVATAR_SPEED = 300.0f;
|
||||||
float MAX_MOTOR_SPEED = MAX_AVATAR_SPEED;
|
float MAX_KEYBOARD_MOTOR_SPEED = MAX_AVATAR_SPEED;
|
||||||
|
float DEFAULT_KEYBOARD_MOTOR_TIMESCALE = 0.25f;
|
||||||
|
float MIN_SCRIPTED_MOTOR_TIMESCALE = 0.005f;
|
||||||
|
float DEFAULT_SCRIPTED_MOTOR_TIMESCALE = 1.0e6f;
|
||||||
|
const int SCRIPTED_MOTOR_AVATAR_FRAME = 0;
|
||||||
|
const int SCRIPTED_MOTOR_CAMERA_FRAME = 1;
|
||||||
|
const int SCRIPTED_MOTOR_WORLD_FRAME = 2;
|
||||||
|
|
||||||
MyAvatar::MyAvatar() :
|
MyAvatar::MyAvatar() :
|
||||||
Avatar(),
|
Avatar(),
|
||||||
|
@ -71,9 +76,11 @@ MyAvatar::MyAvatar() :
|
||||||
_isBraking(false),
|
_isBraking(false),
|
||||||
_trapDuration(0.0f),
|
_trapDuration(0.0f),
|
||||||
_thrust(0.0f),
|
_thrust(0.0f),
|
||||||
_motorVelocity(0.0f),
|
_keyboardMotorVelocity(0.0f),
|
||||||
_motorTimescale(DEFAULT_MOTOR_TIMESCALE),
|
_keyboardMotorTimescale(DEFAULT_KEYBOARD_MOTOR_TIMESCALE),
|
||||||
_maxMotorSpeed(MAX_MOTOR_SPEED),
|
_scriptedMotorVelocity(0.0f),
|
||||||
|
_scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
|
||||||
|
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
|
||||||
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
|
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
|
||||||
_lookAtTargetAvatar(),
|
_lookAtTargetAvatar(),
|
||||||
_shouldRender(true),
|
_shouldRender(true),
|
||||||
|
@ -1028,6 +1035,48 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData)
|
||||||
_billboardValid = false;
|
_billboardValid = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString MyAvatar::getScriptedMotorFrame() const {
|
||||||
|
QString frame = "avatar";
|
||||||
|
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
|
||||||
|
frame = "camera";
|
||||||
|
} else if (_scriptedMotorFrame == SCRIPTED_MOTOR_WORLD_FRAME) {
|
||||||
|
frame = "world";
|
||||||
|
}
|
||||||
|
return frame;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::setScriptedMotorVelocity(const glm::vec3& velocity) {
|
||||||
|
float MAX_SCRIPTED_MOTOR_SPEED = 500.0f;
|
||||||
|
_scriptedMotorVelocity = velocity;
|
||||||
|
float speed = glm::length(_scriptedMotorVelocity);
|
||||||
|
if (speed > MAX_SCRIPTED_MOTOR_SPEED) {
|
||||||
|
_scriptedMotorVelocity *= MAX_SCRIPTED_MOTOR_SPEED / speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::setScriptedMotorTimescale(float timescale) {
|
||||||
|
// we clamp the timescale on the large side (instead of just the low side) to prevent
|
||||||
|
// obnoxiously large values from introducing NaN into avatar's velocity
|
||||||
|
_scriptedMotorTimescale = glm::clamp(timescale, MIN_SCRIPTED_MOTOR_TIMESCALE,
|
||||||
|
DEFAULT_SCRIPTED_MOTOR_TIMESCALE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::setScriptedMotorFrame(QString frame) {
|
||||||
|
if (frame.toLower() == "avatar") {
|
||||||
|
_scriptedMotorFrame = SCRIPTED_MOTOR_AVATAR_FRAME;
|
||||||
|
} else if (frame.toLower() == "camera") {
|
||||||
|
_scriptedMotorFrame = SCRIPTED_MOTOR_CAMERA_FRAME;
|
||||||
|
} else if (frame.toLower() == "world") {
|
||||||
|
_scriptedMotorFrame = SCRIPTED_MOTOR_WORLD_FRAME;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::clearScriptableSettings() {
|
||||||
|
clearJointAnimationPriorities();
|
||||||
|
_scriptedMotorVelocity = glm::vec3(0.0f);
|
||||||
|
_scriptedMotorTimescale = DEFAULT_SCRIPTED_MOTOR_TIMESCALE;
|
||||||
|
}
|
||||||
|
|
||||||
void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation,
|
void MyAvatar::attach(const QString& modelURL, const QString& jointName, const glm::vec3& translation,
|
||||||
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
|
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
|
||||||
if (QThread::currentThread() != thread()) {
|
if (QThread::currentThread() != thread()) {
|
||||||
|
@ -1139,41 +1188,133 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
||||||
setOrientation(orientation);
|
setOrientation(orientation);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::vec3 MyAvatar::applyKeyboardMotor(float deltaTime, const glm::vec3& localVelocity, bool hasFloor) {
|
||||||
|
if (! (_motionBehaviors & AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED)) {
|
||||||
|
return localVelocity;
|
||||||
|
}
|
||||||
|
// compute motor efficiency
|
||||||
|
// The timescale of the motor is the approximate time it takes for the motor to
|
||||||
|
// accomplish its intended localVelocity. A short timescale makes the motor strong,
|
||||||
|
// and a long timescale makes it weak. The value of timescale to use depends
|
||||||
|
// on what the motor is doing:
|
||||||
|
//
|
||||||
|
// (1) braking --> short timescale (aggressive motor assertion)
|
||||||
|
// (2) pushing --> medium timescale (mild motor assertion)
|
||||||
|
// (3) inactive --> long timescale (gentle friction for low speeds)
|
||||||
|
float MIN_KEYBOARD_MOTOR_TIMESCALE = 0.125f;
|
||||||
|
float MAX_KEYBOARD_MOTOR_TIMESCALE = 0.4f;
|
||||||
|
float MIN_KEYBOARD_BRAKE_SPEED = 0.3f;
|
||||||
|
float timescale = MAX_KEYBOARD_MOTOR_TIMESCALE;
|
||||||
|
bool isThrust = (glm::length2(_thrust) > EPSILON);
|
||||||
|
if (_isPushing || isThrust ||
|
||||||
|
(_scriptedMotorTimescale < MAX_KEYBOARD_MOTOR_TIMESCALE &&
|
||||||
|
_motionBehaviors | AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) {
|
||||||
|
// we don't want to break if anything is pushing the avatar around
|
||||||
|
timescale = _keyboardMotorTimescale;
|
||||||
|
_isBraking = false;
|
||||||
|
} else {
|
||||||
|
float speed = glm::length(localVelocity);
|
||||||
|
_isBraking = _wasPushing || (_isBraking && speed > MIN_KEYBOARD_BRAKE_SPEED);
|
||||||
|
if (_isBraking) {
|
||||||
|
timescale = MIN_KEYBOARD_MOTOR_TIMESCALE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_wasPushing = _isPushing || isThrust;
|
||||||
|
_isPushing = false;
|
||||||
|
float motorEfficiency = glm::clamp(deltaTime / timescale, 0.0f, 1.0f);
|
||||||
|
|
||||||
|
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
|
||||||
|
(fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) +
|
||||||
|
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
|
||||||
|
if (keyboardInput) {
|
||||||
|
// Compute keyboard input
|
||||||
|
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
|
||||||
|
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
|
||||||
|
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
|
||||||
|
|
||||||
|
glm::vec3 direction = front + right + up;
|
||||||
|
float directionLength = glm::length(direction);
|
||||||
|
|
||||||
|
// Compute motor magnitude
|
||||||
|
if (directionLength > EPSILON) {
|
||||||
|
direction /= directionLength;
|
||||||
|
|
||||||
|
// Compute the target keyboard velocity (which ramps up slowly, and damps very quickly)
|
||||||
|
// the max magnitude of which depends on what we're doing:
|
||||||
|
float finalMaxMotorSpeed = hasFloor ? _scale * MAX_WALKING_SPEED : _scale * MAX_KEYBOARD_MOTOR_SPEED;
|
||||||
|
float motorLength = glm::length(_keyboardMotorVelocity);
|
||||||
|
if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) {
|
||||||
|
// an active keyboard motor should never be slower than this
|
||||||
|
_keyboardMotorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction;
|
||||||
|
motorEfficiency = 1.0f;
|
||||||
|
} else {
|
||||||
|
float KEYBOARD_MOTOR_LENGTH_TIMESCALE = 2.0f;
|
||||||
|
float INCREASE_FACTOR = 1.8f;
|
||||||
|
motorLength *= 1.0f + glm::clamp(deltaTime / KEYBOARD_MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f) * INCREASE_FACTOR;
|
||||||
|
if (motorLength > finalMaxMotorSpeed) {
|
||||||
|
motorLength = finalMaxMotorSpeed;
|
||||||
|
}
|
||||||
|
_keyboardMotorVelocity = motorLength * direction;
|
||||||
|
}
|
||||||
|
_isPushing = true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
_keyboardMotorVelocity = glm::vec3(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// apply keyboard motor
|
||||||
|
return localVelocity + motorEfficiency * (_keyboardMotorVelocity - localVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVelocity) {
|
||||||
|
if (! (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)) {
|
||||||
|
return localVelocity;
|
||||||
|
}
|
||||||
|
float motorEfficiency = glm::clamp(deltaTime / _scriptedMotorTimescale, 0.0f, 1.0f);
|
||||||
|
return localVelocity + motorEfficiency * (_scriptedMotorVelocity - localVelocity);
|
||||||
|
}
|
||||||
|
|
||||||
const float NEARBY_FLOOR_THRESHOLD = 5.0f;
|
const float NEARBY_FLOOR_THRESHOLD = 5.0f;
|
||||||
|
|
||||||
void MyAvatar::updatePosition(float deltaTime) {
|
void MyAvatar::updatePosition(float deltaTime) {
|
||||||
|
|
||||||
// check for floor by casting a ray straight down from avatar's position
|
// check for floor by casting a ray straight down from avatar's position
|
||||||
float heightAboveFloor = FLT_MAX;
|
float heightAboveFloor = FLT_MAX;
|
||||||
|
bool hasFloor = false;
|
||||||
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
|
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
|
||||||
|
const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD;
|
||||||
|
|
||||||
RayIntersectionInfo intersection;
|
RayIntersectionInfo intersection;
|
||||||
// NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast
|
// NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast
|
||||||
intersection._rayStart = glm::vec3(0.0f);
|
intersection._rayStart = glm::vec3(0.0f);
|
||||||
intersection._rayDirection = - _worldUpDirection;
|
intersection._rayDirection = - _worldUpDirection;
|
||||||
intersection._rayLength = 5.0f * boundingShape.getBoundingRadius();
|
intersection._rayLength = 4.0f * boundingShape.getBoundingRadius();
|
||||||
if (_physicsSimulation.findFloorRayIntersection(intersection)) {
|
if (_physicsSimulation.findFloorRayIntersection(intersection)) {
|
||||||
// NOTE: heightAboveFloor is the distance between the bottom of the avatar and the floor
|
// NOTE: heightAboveFloor is the distance between the bottom of the avatar and the floor
|
||||||
heightAboveFloor = intersection._hitDistance - boundingShape.getBoundingRadius();
|
heightAboveFloor = intersection._hitDistance - boundingShape.getBoundingRadius()
|
||||||
|
+ _skeletonModel.getBoundingShapeOffset().y;
|
||||||
|
if (heightAboveFloor < maxFloorDistance) {
|
||||||
|
hasFloor = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// velocity is initialized to the measured _velocity but will be modified by friction, external thrust, etc
|
// velocity is initialized to the measured _velocity but will be modified by friction, external thrust, etc
|
||||||
glm::vec3 velocity = _velocity;
|
glm::vec3 velocity = _velocity;
|
||||||
|
|
||||||
bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f);
|
bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f) || _scriptedMotorVelocity.y > 0.0f;
|
||||||
bool walkingOnFloor = false;
|
|
||||||
if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
|
if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
|
||||||
const float MAX_SPEED_UNDER_GRAVITY = 2.0f * _scale * MAX_WALKING_SPEED;
|
const float MAX_SPEED_UNDER_GRAVITY = 2.0f * _scale * MAX_WALKING_SPEED;
|
||||||
if (pushingUp || glm::length2(velocity) > MAX_SPEED_UNDER_GRAVITY * MAX_SPEED_UNDER_GRAVITY) {
|
if (pushingUp || glm::length2(velocity) > MAX_SPEED_UNDER_GRAVITY * MAX_SPEED_UNDER_GRAVITY) {
|
||||||
// we're pushing up or moving quickly, so disable gravity
|
// we're pushing up or moving quickly, so disable gravity
|
||||||
setLocalGravity(glm::vec3(0.0f));
|
setLocalGravity(glm::vec3(0.0f));
|
||||||
|
hasFloor = false;
|
||||||
} else {
|
} else {
|
||||||
const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD;
|
|
||||||
if (heightAboveFloor > maxFloorDistance) {
|
if (heightAboveFloor > maxFloorDistance) {
|
||||||
// disable local gravity when floor is too far away
|
// disable local gravity when floor is too far away
|
||||||
setLocalGravity(glm::vec3(0.0f));
|
setLocalGravity(glm::vec3(0.0f));
|
||||||
|
hasFloor = false;
|
||||||
} else {
|
} else {
|
||||||
// enable gravity
|
// enable gravity
|
||||||
walkingOnFloor = true;
|
|
||||||
setLocalGravity(-_worldUpDirection);
|
setLocalGravity(-_worldUpDirection);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1182,76 +1323,35 @@ void MyAvatar::updatePosition(float deltaTime) {
|
||||||
bool zeroDownwardVelocity = false;
|
bool zeroDownwardVelocity = false;
|
||||||
bool gravityEnabled = (glm::length2(_gravity) > EPSILON);
|
bool gravityEnabled = (glm::length2(_gravity) > EPSILON);
|
||||||
if (gravityEnabled) {
|
if (gravityEnabled) {
|
||||||
if (heightAboveFloor < 0.0f) {
|
const float SLOP = 0.002f;
|
||||||
// Gravity is in effect so we assume that the avatar is colliding against the world and we need
|
if (heightAboveFloor < SLOP) {
|
||||||
// to lift avatar out of floor, but we don't want to do it too fast (keep it smooth).
|
if (heightAboveFloor < 0.0) {
|
||||||
float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime);
|
// Gravity is in effect so we assume that the avatar is colliding against the world and we need
|
||||||
|
// to lift avatar out of floor, but we don't want to do it too fast (keep it smooth).
|
||||||
// We don't use applyPositionDelta() for this lift distance because we don't want the avatar
|
float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime);
|
||||||
// to come flying out of the floor. Instead we update position directly, and set a boolean
|
|
||||||
// that will remind us later to zero any downward component of the velocity.
|
// We don't use applyPositionDelta() for this lift distance because we don't want the avatar
|
||||||
_position += (distanceToLift - EPSILON) * _worldUpDirection;
|
// to come flying out of the floor. Instead we update position directly, and set a boolean
|
||||||
|
// that will remind us later to zero any downward component of the velocity.
|
||||||
|
_position += distanceToLift * _worldUpDirection;
|
||||||
|
}
|
||||||
zeroDownwardVelocity = true;
|
zeroDownwardVelocity = true;
|
||||||
}
|
}
|
||||||
velocity += (deltaTime * GRAVITY_EARTH) * _gravity;
|
if (!zeroDownwardVelocity) {
|
||||||
}
|
velocity += (deltaTime * GRAVITY_EARTH) * _gravity;
|
||||||
|
|
||||||
float motorEfficiency = glm::clamp(deltaTime / computeMotorTimescale(velocity), 0.0f, 1.0f);
|
|
||||||
|
|
||||||
// compute targetVelocity
|
|
||||||
glm::vec3 targetVelocity(0.0f);
|
|
||||||
if (_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED) {
|
|
||||||
float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) +
|
|
||||||
(fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT])) +
|
|
||||||
fabsf(_driveKeys[UP] - _driveKeys[DOWN]);
|
|
||||||
if (keyboardInput) {
|
|
||||||
// Compute keyboard input
|
|
||||||
glm::vec3 front = (_driveKeys[FWD] - _driveKeys[BACK]) * IDENTITY_FRONT;
|
|
||||||
glm::vec3 right = (_driveKeys[RIGHT] - _driveKeys[LEFT]) * IDENTITY_RIGHT;
|
|
||||||
glm::vec3 up = (_driveKeys[UP] - _driveKeys[DOWN]) * IDENTITY_UP;
|
|
||||||
|
|
||||||
glm::vec3 direction = front + right + up;
|
|
||||||
float directionLength = glm::length(direction);
|
|
||||||
|
|
||||||
// Compute motor magnitude
|
|
||||||
if (directionLength > EPSILON) {
|
|
||||||
direction /= directionLength;
|
|
||||||
|
|
||||||
// Compute the target keyboard velocity (which ramps up slowly, and damps very quickly)
|
|
||||||
// the max magnitude of which depends on what we're doing:
|
|
||||||
float finalMaxMotorSpeed = walkingOnFloor ? _scale * MAX_WALKING_SPEED : _scale * _maxMotorSpeed;
|
|
||||||
float motorLength = glm::length(_motorVelocity);
|
|
||||||
if (motorLength < _scale * MIN_KEYBOARD_CONTROL_SPEED) {
|
|
||||||
// an active keyboard motor should never be slower than this
|
|
||||||
_motorVelocity = _scale * MIN_KEYBOARD_CONTROL_SPEED * direction;
|
|
||||||
motorEfficiency = 1.0f;
|
|
||||||
} else {
|
|
||||||
float MOTOR_LENGTH_TIMESCALE = 2.0f;
|
|
||||||
float INCREASE_FACTOR = 1.8f;
|
|
||||||
motorLength *= 1.0f + glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f) * INCREASE_FACTOR;
|
|
||||||
if (motorLength > finalMaxMotorSpeed) {
|
|
||||||
motorLength = finalMaxMotorSpeed;
|
|
||||||
}
|
|
||||||
_motorVelocity = motorLength * direction;
|
|
||||||
}
|
|
||||||
_isPushing = true;
|
|
||||||
}
|
|
||||||
targetVelocity = _motorVelocity;
|
|
||||||
} else {
|
|
||||||
_motorVelocity = glm::vec3(0.0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
targetVelocity = getHead()->getCameraOrientation() * targetVelocity;
|
|
||||||
|
|
||||||
glm::vec3 deltaVelocity = targetVelocity - velocity;
|
// rotate velocity into camera frame
|
||||||
|
glm::quat rotation = getHead()->getCameraOrientation();
|
||||||
|
glm::vec3 localVelocity = glm::inverse(rotation) * velocity;
|
||||||
|
|
||||||
if (walkingOnFloor && !pushingUp) {
|
// apply motors in camera frame
|
||||||
// remove vertical component of deltaVelocity
|
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor);
|
||||||
deltaVelocity -= glm::dot(deltaVelocity, _worldUpDirection) * _worldUpDirection;
|
newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity);
|
||||||
}
|
|
||||||
|
|
||||||
// apply motor
|
// rotate back into world-frame
|
||||||
velocity += motorEfficiency * deltaVelocity;
|
velocity = rotation * newLocalVelocity;
|
||||||
|
|
||||||
// apply thrust
|
// apply thrust
|
||||||
velocity += _thrust * deltaTime;
|
velocity += _thrust * deltaTime;
|
||||||
|
@ -1260,8 +1360,8 @@ void MyAvatar::updatePosition(float deltaTime) {
|
||||||
// remove downward velocity so we don't push into floor
|
// remove downward velocity so we don't push into floor
|
||||||
if (zeroDownwardVelocity) {
|
if (zeroDownwardVelocity) {
|
||||||
float verticalSpeed = glm::dot(velocity, _worldUpDirection);
|
float verticalSpeed = glm::dot(velocity, _worldUpDirection);
|
||||||
if (verticalSpeed < 0.0f) {
|
if (verticalSpeed < 0.0f || !pushingUp) {
|
||||||
velocity += verticalSpeed * _worldUpDirection;
|
velocity -= verticalSpeed * _worldUpDirection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1286,37 +1386,6 @@ void MyAvatar::updatePosition(float deltaTime) {
|
||||||
measureMotionDerivatives(deltaTime);
|
measureMotionDerivatives(deltaTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
float MyAvatar::computeMotorTimescale(const glm::vec3& velocity) {
|
|
||||||
// The timescale of the motor is the approximate time it takes for the motor to
|
|
||||||
// accomplish its intended velocity. A short timescale makes the motor strong,
|
|
||||||
// and a long timescale makes it weak. The value of timescale to use depends
|
|
||||||
// on what the motor is doing:
|
|
||||||
//
|
|
||||||
// (1) braking --> short timescale (aggressive motor assertion)
|
|
||||||
// (2) pushing --> medium timescale (mild motor assertion)
|
|
||||||
// (3) inactive --> long timescale (gentle friction for low speeds)
|
|
||||||
|
|
||||||
float MIN_MOTOR_TIMESCALE = 0.125f;
|
|
||||||
float MAX_MOTOR_TIMESCALE = 0.4f;
|
|
||||||
float MIN_BRAKE_SPEED = 0.3f;
|
|
||||||
|
|
||||||
float timescale = MAX_MOTOR_TIMESCALE;
|
|
||||||
bool isThrust = (glm::length2(_thrust) > EPSILON);
|
|
||||||
if (_isPushing || isThrust) {
|
|
||||||
timescale = _motorTimescale;
|
|
||||||
_isBraking = false;
|
|
||||||
} else {
|
|
||||||
float speed = glm::length(velocity);
|
|
||||||
_isBraking = _wasPushing || (_isBraking && speed > MIN_BRAKE_SPEED);
|
|
||||||
if (_isBraking) {
|
|
||||||
timescale = MIN_MOTOR_TIMESCALE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_wasPushing = _isPushing || isThrust;
|
|
||||||
_isPushing = false;
|
|
||||||
return timescale;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
|
void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) {
|
||||||
glm::vec3 up = getBodyUpDirection();
|
glm::vec3 up = getBodyUpDirection();
|
||||||
const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f;
|
const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f;
|
||||||
|
@ -1436,10 +1505,10 @@ void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) {
|
||||||
if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) {
|
if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) {
|
||||||
// we're colliding against an edge
|
// we're colliding against an edge
|
||||||
|
|
||||||
// rotate _motorVelocity into world frame
|
// rotate _keyboardMotorVelocity into world frame
|
||||||
glm::vec3 targetVelocity = _motorVelocity;
|
glm::vec3 targetVelocity = _keyboardMotorVelocity;
|
||||||
glm::quat rotation = getHead()->getCameraOrientation();
|
glm::quat rotation = getHead()->getCameraOrientation();
|
||||||
targetVelocity = rotation * _motorVelocity;
|
targetVelocity = rotation * _keyboardMotorVelocity;
|
||||||
if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) {
|
if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) {
|
||||||
// we're puhing into the edge, so we want to lift
|
// we're puhing into the edge, so we want to lift
|
||||||
|
|
||||||
|
@ -1799,7 +1868,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
|
||||||
emit transformChanged();
|
emit transformChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::updateMotionBehaviorsFromMenu() {
|
void MyAvatar::updateMotionBehavior() {
|
||||||
Menu* menu = Menu::getInstance();
|
Menu* menu = Menu::getInstance();
|
||||||
if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
|
if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
|
||||||
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
|
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
|
||||||
|
@ -1822,6 +1891,16 @@ void MyAvatar::updateMotionBehaviorsFromMenu() {
|
||||||
if (!(_collisionGroups | COLLISION_GROUP_VOXELS)) {
|
if (!(_collisionGroups | COLLISION_GROUP_VOXELS)) {
|
||||||
_voxelShapeManager.clearShapes();
|
_voxelShapeManager.clearShapes();
|
||||||
}
|
}
|
||||||
|
if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) {
|
||||||
|
_motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;
|
||||||
|
} else {
|
||||||
|
_motionBehaviors &= ~AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED;
|
||||||
|
}
|
||||||
|
if (menu->isOptionChecked(MenuOption::ScriptedMotorControl)) {
|
||||||
|
_motionBehaviors |= AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
|
||||||
|
} else {
|
||||||
|
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::onToggleRagdoll() {
|
void MyAvatar::onToggleRagdoll() {
|
||||||
|
@ -1867,25 +1946,6 @@ void MyAvatar::setCollisionGroups(quint32 collisionGroups) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::setMotionBehaviorsByScript(quint32 flags) {
|
|
||||||
// start with the defaults
|
|
||||||
_motionBehaviors = AVATAR_MOTION_DEFAULTS;
|
|
||||||
|
|
||||||
// add the set scriptable bits
|
|
||||||
_motionBehaviors += flags & AVATAR_MOTION_SCRIPTABLE_BITS;
|
|
||||||
|
|
||||||
// reconcile incompatible settings from menu (if any)
|
|
||||||
Menu* menu = Menu::getInstance();
|
|
||||||
menu->setIsOptionChecked(MenuOption::ObeyEnvironmentalGravity, (bool)(_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY));
|
|
||||||
// Environmental and Local gravities are incompatible. Environmental setting trumps local.
|
|
||||||
if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) {
|
|
||||||
_motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY;
|
|
||||||
setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()));
|
|
||||||
} else if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) {
|
|
||||||
setGravity(glm::vec3(0.0f));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) {
|
void MyAvatar::applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration) {
|
||||||
glm::vec3 leverAxis = contactPoint - getPosition();
|
glm::vec3 leverAxis = contactPoint - getPosition();
|
||||||
float leverLength = glm::length(leverAxis);
|
float leverLength = glm::length(leverAxis);
|
||||||
|
|
|
@ -33,7 +33,9 @@ enum AvatarHandState
|
||||||
class MyAvatar : public Avatar {
|
class MyAvatar : public Avatar {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
|
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
|
||||||
Q_PROPERTY(quint32 motionBehaviors READ getMotionBehaviorsForScript WRITE setMotionBehaviorsByScript)
|
Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity)
|
||||||
|
Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale)
|
||||||
|
Q_PROPERTY(QString motorReferenceFrame READ getScriptedMotorFrame WRITE setScriptedMotorFrame)
|
||||||
Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setLocalGravity)
|
Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setLocalGravity)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -131,15 +133,22 @@ public:
|
||||||
|
|
||||||
void clearJointAnimationPriorities();
|
void clearJointAnimationPriorities();
|
||||||
|
|
||||||
|
glm::vec3 getScriptedMotorVelocity() const { return _scriptedMotorVelocity; }
|
||||||
|
float getScriptedMotorTimescale() const { return _scriptedMotorTimescale; }
|
||||||
|
QString getScriptedMotorFrame() const;
|
||||||
|
|
||||||
|
void setScriptedMotorVelocity(const glm::vec3& velocity);
|
||||||
|
void setScriptedMotorTimescale(float timescale);
|
||||||
|
void setScriptedMotorFrame(QString frame);
|
||||||
|
|
||||||
|
void clearScriptableSettings();
|
||||||
|
|
||||||
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
|
virtual void attach(const QString& modelURL, const QString& jointName = QString(),
|
||||||
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
|
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
|
||||||
bool allowDuplicates = false, bool useSaved = true);
|
bool allowDuplicates = false, bool useSaved = true);
|
||||||
|
|
||||||
virtual void setCollisionGroups(quint32 collisionGroups);
|
virtual void setCollisionGroups(quint32 collisionGroups);
|
||||||
|
|
||||||
void setMotionBehaviorsByScript(quint32 flags);
|
|
||||||
quint32 getMotionBehaviorsForScript() const { return _motionBehaviors & AVATAR_MOTION_SCRIPTABLE_BITS; }
|
|
||||||
|
|
||||||
void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration);
|
void applyCollision(const glm::vec3& contactPoint, const glm::vec3& penetration);
|
||||||
|
|
||||||
/// Renders a laser pointer for UI picking
|
/// Renders a laser pointer for UI picking
|
||||||
|
@ -165,7 +174,7 @@ public slots:
|
||||||
|
|
||||||
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
|
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
|
||||||
|
|
||||||
void updateMotionBehaviorsFromMenu();
|
void updateMotionBehavior();
|
||||||
void onToggleRagdoll();
|
void onToggleRagdoll();
|
||||||
|
|
||||||
glm::vec3 getLeftPalmPosition();
|
glm::vec3 getLeftPalmPosition();
|
||||||
|
@ -204,9 +213,11 @@ private:
|
||||||
float _trapDuration; // seconds that avatar has been trapped by collisions
|
float _trapDuration; // seconds that avatar has been trapped by collisions
|
||||||
glm::vec3 _thrust; // impulse accumulator for outside sources
|
glm::vec3 _thrust; // impulse accumulator for outside sources
|
||||||
|
|
||||||
glm::vec3 _motorVelocity; // intended velocity of avatar motion (relative to what it's standing on)
|
glm::vec3 _keyboardMotorVelocity; // target local-frame velocity of avatar (keyboard)
|
||||||
float _motorTimescale; // timescale for avatar motor to achieve its desired velocity
|
float _keyboardMotorTimescale; // timescale for avatar to achieve its target velocity
|
||||||
float _maxMotorSpeed;
|
glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (script)
|
||||||
|
float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity
|
||||||
|
int _scriptedMotorFrame;
|
||||||
quint32 _motionBehaviors;
|
quint32 _motionBehaviors;
|
||||||
|
|
||||||
QWeakPointer<AvatarData> _lookAtTargetAvatar;
|
QWeakPointer<AvatarData> _lookAtTargetAvatar;
|
||||||
|
@ -223,8 +234,9 @@ private:
|
||||||
|
|
||||||
// private methods
|
// private methods
|
||||||
void updateOrientation(float deltaTime);
|
void updateOrientation(float deltaTime);
|
||||||
|
glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool walkingOnFloor);
|
||||||
|
glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity);
|
||||||
void updatePosition(float deltaTime);
|
void updatePosition(float deltaTime);
|
||||||
float computeMotorTimescale(const glm::vec3& velocity);
|
|
||||||
void updateCollisionWithAvatars(float deltaTime);
|
void updateCollisionWithAvatars(float deltaTime);
|
||||||
void updateCollisionWithEnvironment(float deltaTime, float radius);
|
void updateCollisionWithEnvironment(float deltaTime, float radius);
|
||||||
void updateCollisionWithVoxels(float deltaTime, float radius);
|
void updateCollisionWithVoxels(float deltaTime, float radius);
|
||||||
|
|
|
@ -281,7 +281,7 @@ void SkeletonModel::maybeUpdateNeckRotation(const JointState& parentState, const
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
void SkeletonModel::maybeUpdateEyeRotation(const JointState& parentState, const FBXJoint& joint, JointState& state) {
|
||||||
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(parentState, joint, state);
|
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(this, parentState, joint, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SkeletonModel::renderJointConstraints(int jointIndex) {
|
void SkeletonModel::renderJointConstraints(int jointIndex) {
|
||||||
|
|
|
@ -109,6 +109,7 @@ public:
|
||||||
void renderJointCollisionShapes(float alpha);
|
void renderJointCollisionShapes(float alpha);
|
||||||
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
|
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
|
||||||
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
|
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
|
||||||
|
const glm::vec3 getBoundingShapeOffset() const { return _boundingShapeLocalOffset; }
|
||||||
|
|
||||||
void resetShapePositionsToDefaultPose(); // DEBUG method
|
void resetShapePositionsToDefaultPose(); // DEBUG method
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,14 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
|
#include <qhttpmultipart.h>
|
||||||
#include <qjsonobject.h>
|
#include <qjsonobject.h>
|
||||||
|
|
||||||
#include <AccountManager.h>
|
#include <AccountManager.h>
|
||||||
|
|
||||||
|
#include "Application.h"
|
||||||
|
#include "ui/Snapshot.h"
|
||||||
|
|
||||||
#include "LocationManager.h"
|
#include "LocationManager.h"
|
||||||
|
|
||||||
const QString POST_LOCATION_CREATE = "/api/v1/locations/";
|
const QString POST_LOCATION_CREATE = "/api/v1/locations/";
|
||||||
|
@ -24,13 +28,17 @@ LocationManager& LocationManager::getInstance() {
|
||||||
|
|
||||||
const QString UNKNOWN_ERROR_MESSAGE = "Unknown error creating named location. Please try again!";
|
const QString UNKNOWN_ERROR_MESSAGE = "Unknown error creating named location. Please try again!";
|
||||||
|
|
||||||
void LocationManager::namedLocationDataReceived(const QJsonObject& data) {
|
const QString LOCATION_OBJECT_KEY = "location";
|
||||||
if (data.isEmpty()) {
|
const QString LOCATION_ID_KEY = "id";
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (data.contains("status") && data["status"].toString() == "success") {
|
void LocationManager::namedLocationDataReceived(const QJsonObject& rootObject) {
|
||||||
|
|
||||||
|
if (rootObject.contains("status") && rootObject["status"].toString() == "success") {
|
||||||
emit creationCompleted(QString());
|
emit creationCompleted(QString());
|
||||||
|
|
||||||
|
// successfuly created a location - grab the ID from the response and create a snapshot to upload
|
||||||
|
QString locationIDString = rootObject[LOCATION_OBJECT_KEY].toObject()[LOCATION_ID_KEY].toString();
|
||||||
|
updateSnapshotForExistingLocation(locationIDString);
|
||||||
} else {
|
} else {
|
||||||
emit creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
emit creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
|
@ -87,3 +95,57 @@ void LocationManager::errorDataReceived(QNetworkReply& errorReply) {
|
||||||
creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void LocationManager::locationImageUpdateSuccess(const QJsonObject& rootObject) {
|
||||||
|
qDebug() << "Successfuly updated a location image.";
|
||||||
|
}
|
||||||
|
|
||||||
|
void LocationManager::updateSnapshotForExistingLocation(const QString& locationID) {
|
||||||
|
// first create a snapshot and save it
|
||||||
|
Application* application = Application::getInstance();
|
||||||
|
|
||||||
|
QTemporaryFile* tempImageFile = Snapshot::saveTempSnapshot(application->getGLWidget(), application->getAvatar());
|
||||||
|
|
||||||
|
if (tempImageFile && tempImageFile->open()) {
|
||||||
|
AccountManager& accountManager = AccountManager::getInstance();
|
||||||
|
|
||||||
|
// setup a multipart that is in the AccountManager thread - we need this so it can be cleaned up after the QNetworkReply
|
||||||
|
QHttpMultiPart* imageFileMultiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
|
||||||
|
imageFileMultiPart->moveToThread(accountManager.thread());
|
||||||
|
|
||||||
|
// parent the temp file to the QHttpMultipart after moving it to account manager thread
|
||||||
|
tempImageFile->moveToThread(accountManager.thread());
|
||||||
|
tempImageFile->setParent(imageFileMultiPart);
|
||||||
|
|
||||||
|
qDebug() << "Uploading a snapshot from" << QFileInfo(*tempImageFile).absoluteFilePath()
|
||||||
|
<< "as location image for" << locationID;
|
||||||
|
|
||||||
|
const QString LOCATION_IMAGE_NAME = "location[image]";
|
||||||
|
|
||||||
|
QHttpPart imagePart;
|
||||||
|
imagePart.setHeader(QNetworkRequest::ContentDispositionHeader,
|
||||||
|
QVariant("form-data; name=\"" + LOCATION_IMAGE_NAME + "\";"
|
||||||
|
" filename=\"" + QFileInfo(tempImageFile->fileName()).fileName().toUtf8() + "\""));
|
||||||
|
imagePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
|
||||||
|
imagePart.setBodyDevice(tempImageFile);
|
||||||
|
|
||||||
|
imageFileMultiPart->append(imagePart);
|
||||||
|
|
||||||
|
const QString LOCATION_IMAGE_PUT_PATH = "api/v1/locations/%1/image";
|
||||||
|
|
||||||
|
JSONCallbackParameters imageCallbackParams;
|
||||||
|
imageCallbackParams.jsonCallbackReceiver = this;
|
||||||
|
imageCallbackParams.jsonCallbackMethod = "locationImageUpdateSuccess";
|
||||||
|
|
||||||
|
// make an authenticated request via account manager to upload the image
|
||||||
|
// don't do anything with error or success for now
|
||||||
|
AccountManager::getInstance().authenticatedRequest(LOCATION_IMAGE_PUT_PATH.arg(locationID),
|
||||||
|
QNetworkAccessManager::PutOperation,
|
||||||
|
JSONCallbackParameters(), QByteArray(), imageFileMultiPart);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Couldn't open snapshot file to upload as location image. No location image will be stored.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,8 +35,12 @@ signals:
|
||||||
void creationCompleted(const QString& errorMessage);
|
void creationCompleted(const QString& errorMessage);
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void namedLocationDataReceived(const QJsonObject& data);
|
void namedLocationDataReceived(const QJsonObject& jsonObject);
|
||||||
void errorDataReceived(QNetworkReply& errorReply);
|
void errorDataReceived(QNetworkReply& errorReply);
|
||||||
|
void locationImageUpdateSuccess(const QJsonObject& jsonObject);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateSnapshotForExistingLocation(const QString& locationID);
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@
|
||||||
#include "Model.h"
|
#include "Model.h"
|
||||||
#include "world.h"
|
#include "world.h"
|
||||||
|
|
||||||
|
GeometryCache::GeometryCache() :
|
||||||
|
_pendingBlenders(0) {
|
||||||
|
}
|
||||||
|
|
||||||
GeometryCache::~GeometryCache() {
|
GeometryCache::~GeometryCache() {
|
||||||
foreach (const VerticesIndices& vbo, _hemisphereVBOs) {
|
foreach (const VerticesIndices& vbo, _hemisphereVBOs) {
|
||||||
glDeleteBuffers(1, &vbo.first);
|
glDeleteBuffers(1, &vbo.first);
|
||||||
|
@ -296,10 +300,30 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
|
||||||
return getResource(url, fallback, delayLoad).staticCast<NetworkGeometry>();
|
return getResource(url, fallback, delayLoad).staticCast<NetworkGeometry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GeometryCache::setBlendedVertices(const QPointer<Model>& model, const QWeakPointer<NetworkGeometry>& geometry,
|
void GeometryCache::noteRequiresBlend(Model* model) {
|
||||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
if (_pendingBlenders < QThread::idealThreadCount()) {
|
||||||
|
if (model->maybeStartBlender()) {
|
||||||
|
_pendingBlenders++;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!_modelsRequiringBlends.contains(model)) {
|
||||||
|
_modelsRequiringBlends.append(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GeometryCache::setBlendedVertices(const QPointer<Model>& model, int blendNumber,
|
||||||
|
const QWeakPointer<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||||
if (!model.isNull()) {
|
if (!model.isNull()) {
|
||||||
model->setBlendedVertices(geometry, vertices, normals);
|
model->setBlendedVertices(blendNumber, geometry, vertices, normals);
|
||||||
|
}
|
||||||
|
_pendingBlenders--;
|
||||||
|
while (!_modelsRequiringBlends.isEmpty()) {
|
||||||
|
Model* nextModel = _modelsRequiringBlends.takeFirst();
|
||||||
|
if (nextModel && nextModel->maybeStartBlender()) {
|
||||||
|
_pendingBlenders++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,7 @@ class GeometryCache : public ResourceCache {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
|
GeometryCache();
|
||||||
virtual ~GeometryCache();
|
virtual ~GeometryCache();
|
||||||
|
|
||||||
void renderHemisphere(int slices, int stacks);
|
void renderHemisphere(int slices, int stacks);
|
||||||
|
@ -47,9 +48,12 @@ public:
|
||||||
/// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested
|
/// \param delayLoad if true, don't load the geometry immediately; wait until load is first requested
|
||||||
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
QSharedPointer<NetworkGeometry> getGeometry(const QUrl& url, const QUrl& fallback = QUrl(), bool delayLoad = false);
|
||||||
|
|
||||||
|
/// Adds the specified model to the list requiring vertex blends.
|
||||||
|
void noteRequiresBlend(Model* model);
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
void setBlendedVertices(const QPointer<Model>& model, const QWeakPointer<NetworkGeometry>& geometry,
|
void setBlendedVertices(const QPointer<Model>& model, int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
@ -68,6 +72,9 @@ private:
|
||||||
QHash<IntPair, QOpenGLBuffer> _gridBuffers;
|
QHash<IntPair, QOpenGLBuffer> _gridBuffers;
|
||||||
|
|
||||||
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
|
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
|
||||||
|
|
||||||
|
QList<QPointer<Model> > _modelsRequiringBlends;
|
||||||
|
int _pendingBlenders;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Geometry loaded from the network.
|
/// Geometry loaded from the network.
|
||||||
|
|
|
@ -62,8 +62,8 @@ Model::Model(QObject* parent) :
|
||||||
_lodDistance(0.0f),
|
_lodDistance(0.0f),
|
||||||
_pupilDilation(0.0f),
|
_pupilDilation(0.0f),
|
||||||
_url("http://invalid.com"),
|
_url("http://invalid.com"),
|
||||||
_blenderPending(false),
|
_blendNumber(0),
|
||||||
_blendRequired(false) {
|
_appliedBlendNumber(0) {
|
||||||
|
|
||||||
// we may have been created in the network thread, but we live in the main thread
|
// we may have been created in the network thread, but we live in the main thread
|
||||||
moveToThread(Application::getInstance()->thread());
|
moveToThread(Application::getInstance()->thread());
|
||||||
|
@ -826,7 +826,7 @@ void Model::updateShapePositions() {
|
||||||
class Blender : public QRunnable {
|
class Blender : public QRunnable {
|
||||||
public:
|
public:
|
||||||
|
|
||||||
Blender(Model* model, const QWeakPointer<NetworkGeometry>& geometry,
|
Blender(Model* model, int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients);
|
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients);
|
||||||
|
|
||||||
virtual void run();
|
virtual void run();
|
||||||
|
@ -834,55 +834,55 @@ public:
|
||||||
private:
|
private:
|
||||||
|
|
||||||
QPointer<Model> _model;
|
QPointer<Model> _model;
|
||||||
|
int _blendNumber;
|
||||||
QWeakPointer<NetworkGeometry> _geometry;
|
QWeakPointer<NetworkGeometry> _geometry;
|
||||||
QVector<FBXMesh> _meshes;
|
QVector<FBXMesh> _meshes;
|
||||||
QVector<float> _blendshapeCoefficients;
|
QVector<float> _blendshapeCoefficients;
|
||||||
};
|
};
|
||||||
|
|
||||||
Blender::Blender(Model* model, const QWeakPointer<NetworkGeometry>& geometry,
|
Blender::Blender(Model* model, int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients) :
|
const QVector<FBXMesh>& meshes, const QVector<float>& blendshapeCoefficients) :
|
||||||
_model(model),
|
_model(model),
|
||||||
|
_blendNumber(blendNumber),
|
||||||
_geometry(geometry),
|
_geometry(geometry),
|
||||||
_meshes(meshes),
|
_meshes(meshes),
|
||||||
_blendshapeCoefficients(blendshapeCoefficients) {
|
_blendshapeCoefficients(blendshapeCoefficients) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Blender::run() {
|
void Blender::run() {
|
||||||
// make sure the model still exists
|
|
||||||
if (_model.isNull()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QVector<glm::vec3> vertices, normals;
|
QVector<glm::vec3> vertices, normals;
|
||||||
int offset = 0;
|
if (!_model.isNull()) {
|
||||||
foreach (const FBXMesh& mesh, _meshes) {
|
int offset = 0;
|
||||||
if (mesh.blendshapes.isEmpty()) {
|
foreach (const FBXMesh& mesh, _meshes) {
|
||||||
continue;
|
if (mesh.blendshapes.isEmpty()) {
|
||||||
}
|
|
||||||
vertices += mesh.vertices;
|
|
||||||
normals += mesh.normals;
|
|
||||||
glm::vec3* meshVertices = vertices.data() + offset;
|
|
||||||
glm::vec3* meshNormals = normals.data() + offset;
|
|
||||||
offset += mesh.vertices.size();
|
|
||||||
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
|
||||||
for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) {
|
|
||||||
float vertexCoefficient = _blendshapeCoefficients.at(i);
|
|
||||||
if (vertexCoefficient < EPSILON) {
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE;
|
vertices += mesh.vertices;
|
||||||
const FBXBlendshape& blendshape = mesh.blendshapes.at(i);
|
normals += mesh.normals;
|
||||||
for (int j = 0; j < blendshape.indices.size(); j++) {
|
glm::vec3* meshVertices = vertices.data() + offset;
|
||||||
int index = blendshape.indices.at(j);
|
glm::vec3* meshNormals = normals.data() + offset;
|
||||||
meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient;
|
offset += mesh.vertices.size();
|
||||||
meshNormals[index] += blendshape.normals.at(j) * normalCoefficient;
|
const float NORMAL_COEFFICIENT_SCALE = 0.01f;
|
||||||
|
for (int i = 0, n = qMin(_blendshapeCoefficients.size(), mesh.blendshapes.size()); i < n; i++) {
|
||||||
|
float vertexCoefficient = _blendshapeCoefficients.at(i);
|
||||||
|
if (vertexCoefficient < EPSILON) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
float normalCoefficient = vertexCoefficient * NORMAL_COEFFICIENT_SCALE;
|
||||||
|
const FBXBlendshape& blendshape = mesh.blendshapes.at(i);
|
||||||
|
for (int j = 0; j < blendshape.indices.size(); j++) {
|
||||||
|
int index = blendshape.indices.at(j);
|
||||||
|
meshVertices[index] += blendshape.vertices.at(j) * vertexCoefficient;
|
||||||
|
meshNormals[index] += blendshape.normals.at(j) * normalCoefficient;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// post the result to the geometry cache, which will dispatch to the model if still alive
|
// post the result to the geometry cache, which will dispatch to the model if still alive
|
||||||
QMetaObject::invokeMethod(Application::getInstance()->getGeometryCache(), "setBlendedVertices",
|
QMetaObject::invokeMethod(Application::getInstance()->getGeometryCache(), "setBlendedVertices",
|
||||||
Q_ARG(const QPointer<Model>&, _model), Q_ARG(const QWeakPointer<NetworkGeometry>&, _geometry),
|
Q_ARG(const QPointer<Model>&, _model), Q_ARG(int, _blendNumber),
|
||||||
Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));
|
Q_ARG(const QWeakPointer<NetworkGeometry>&, _geometry), Q_ARG(const QVector<glm::vec3>&, vertices),
|
||||||
|
Q_ARG(const QVector<glm::vec3>&, normals));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) {
|
void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) {
|
||||||
|
@ -894,6 +894,10 @@ void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
|
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
|
||||||
|
if (!isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (_scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) {
|
if (_scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) {
|
||||||
_scaleToFit = scaleToFit;
|
_scaleToFit = scaleToFit;
|
||||||
|
|
||||||
|
@ -1016,14 +1020,9 @@ void Model::simulateInternal(float deltaTime) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// post the blender if we're not currently waiting for one to finish
|
// post the blender if we're not currently waiting for one to finish
|
||||||
if (geometry.hasBlendedMeshes()) {
|
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
||||||
if (_blenderPending) {
|
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
||||||
_blendRequired = true;
|
Application::getInstance()->getGeometryCache()->noteRequiresBlend(this);
|
||||||
} else {
|
|
||||||
_blendRequired = false;
|
|
||||||
_blenderPending = true;
|
|
||||||
QThreadPool::globalInstance()->start(new Blender(this, _geometry, geometry.meshes, _blendshapeCoefficients));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1286,22 +1285,23 @@ void Model::renderJointCollisionShapes(float alpha) {
|
||||||
// implement this when we have shapes for regular models
|
// implement this when we have shapes for regular models
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::setBlendedVertices(const QWeakPointer<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices,
|
bool Model::maybeStartBlender() {
|
||||||
const QVector<glm::vec3>& normals) {
|
|
||||||
_blenderPending = false;
|
|
||||||
|
|
||||||
// start the next blender if required
|
|
||||||
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
|
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
|
||||||
if (_blendRequired) {
|
if (fbxGeometry.hasBlendedMeshes()) {
|
||||||
_blendRequired = false;
|
QThreadPool::globalInstance()->start(new Blender(this, ++_blendNumber, _geometry,
|
||||||
if (fbxGeometry.hasBlendedMeshes()) {
|
fbxGeometry.meshes, _blendshapeCoefficients));
|
||||||
_blenderPending = true;
|
return true;
|
||||||
QThreadPool::globalInstance()->start(new Blender(this, _geometry, fbxGeometry.meshes, _blendshapeCoefficients));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (_geometry != geometry || _blendedVertexBuffers.isEmpty()) {
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Model::setBlendedVertices(int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
|
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||||
|
if (_geometry != geometry || _blendedVertexBuffers.isEmpty() || blendNumber < _appliedBlendNumber) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
_appliedBlendNumber = blendNumber;
|
||||||
|
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
|
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
|
||||||
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
|
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
|
||||||
|
@ -1354,6 +1354,8 @@ void Model::deleteGeometry() {
|
||||||
if (_geometry) {
|
if (_geometry) {
|
||||||
_geometry->clearLoadPriority(this);
|
_geometry->clearLoadPriority(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_blendedBlendshapeCoefficients.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Model::renderMeshes(RenderMode mode, bool translucent, bool receiveShadows) {
|
void Model::renderMeshes(RenderMode mode, bool translucent, bool receiveShadows) {
|
||||||
|
|
|
@ -165,9 +165,11 @@ public:
|
||||||
|
|
||||||
virtual void renderJointCollisionShapes(float alpha);
|
virtual void renderJointCollisionShapes(float alpha);
|
||||||
|
|
||||||
|
bool maybeStartBlender();
|
||||||
|
|
||||||
/// Sets blended vertices computed in a separate thread.
|
/// Sets blended vertices computed in a separate thread.
|
||||||
void setBlendedVertices(const QWeakPointer<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices,
|
void setBlendedVertices(int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||||
const QVector<glm::vec3>& normals);
|
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||||
|
|
||||||
class LocalLight {
|
class LocalLight {
|
||||||
public:
|
public:
|
||||||
|
@ -285,8 +287,9 @@ private:
|
||||||
glm::vec4 _localLightColors[MAX_LOCAL_LIGHTS];
|
glm::vec4 _localLightColors[MAX_LOCAL_LIGHTS];
|
||||||
glm::vec4 _localLightDirections[MAX_LOCAL_LIGHTS];
|
glm::vec4 _localLightDirections[MAX_LOCAL_LIGHTS];
|
||||||
|
|
||||||
bool _blenderPending;
|
QVector<float> _blendedBlendshapeCoefficients;
|
||||||
bool _blendRequired;
|
int _blendNumber;
|
||||||
|
int _appliedBlendNumber;
|
||||||
|
|
||||||
static ProgramObject _program;
|
static ProgramObject _program;
|
||||||
static ProgramObject _normalMapProgram;
|
static ProgramObject _normalMapProgram;
|
||||||
|
|
|
@ -65,6 +65,24 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
||||||
|
QFile* snapshotFile = savedFileForSnapshot(widget, avatar, false);
|
||||||
|
|
||||||
|
// we don't need the snapshot file, so close it, grab its filename and delete it
|
||||||
|
snapshotFile->close();
|
||||||
|
|
||||||
|
QString snapshotPath = QFileInfo(*snapshotFile).absoluteFilePath();
|
||||||
|
|
||||||
|
delete snapshotFile;
|
||||||
|
|
||||||
|
return snapshotPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTemporaryFile* Snapshot::saveTempSnapshot(QGLWidget* widget, Avatar* avatar) {
|
||||||
|
// return whatever we get back from saved file for snapshot
|
||||||
|
return static_cast<QTemporaryFile*>(savedFileForSnapshot(widget, avatar, true));;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile* Snapshot::savedFileForSnapshot(QGLWidget* widget, Avatar* avatar, bool isTemporary) {
|
||||||
QImage shot = widget->grabFrameBuffer();
|
QImage shot = widget->grabFrameBuffer();
|
||||||
|
|
||||||
glm::vec3 location = avatar->getPosition();
|
glm::vec3 location = avatar->getPosition();
|
||||||
|
@ -91,16 +109,40 @@ QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
||||||
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
|
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
|
||||||
|
|
||||||
QDateTime now = QDateTime::currentDateTime();
|
QDateTime now = QDateTime::currentDateTime();
|
||||||
QString fileName = Menu::getInstance()->getSnapshotsLocation();
|
|
||||||
|
|
||||||
if (!fileName.endsWith(QDir::separator())) {
|
|
||||||
fileName.append(QDir::separator());
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName.append(QString(FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation)));
|
|
||||||
shot.save(fileName, 0, 100);
|
|
||||||
|
|
||||||
return fileName;
|
QString filename = FILENAME_PATH_FORMAT.arg(username, now.toString(DATETIME_FORMAT), formattedLocation);
|
||||||
|
|
||||||
|
const int IMAGE_QUALITY = 100;
|
||||||
|
|
||||||
|
if (!isTemporary) {
|
||||||
|
QString snapshotFullPath = Menu::getInstance()->getSnapshotsLocation();
|
||||||
|
|
||||||
|
if (!snapshotFullPath.endsWith(QDir::separator())) {
|
||||||
|
snapshotFullPath.append(QDir::separator());
|
||||||
|
}
|
||||||
|
|
||||||
|
snapshotFullPath.append(filename);
|
||||||
|
|
||||||
|
QFile* imageFile = new QFile(snapshotFullPath);
|
||||||
|
imageFile->open(QIODevice::WriteOnly);
|
||||||
|
|
||||||
|
shot.save(imageFile, 0, IMAGE_QUALITY);
|
||||||
|
imageFile->close();
|
||||||
|
|
||||||
|
return imageFile;
|
||||||
|
} else {
|
||||||
|
QTemporaryFile* imageTempFile = new QTemporaryFile(QDir::tempPath() + "/XXXXXX-" + filename);
|
||||||
|
|
||||||
|
if (!imageTempFile->open()) {
|
||||||
|
qDebug() << "Unable to open QTemporaryFile for temp snapshot. Will not save.";
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
shot.save(imageTempFile, 0, IMAGE_QUALITY);
|
||||||
|
imageTempFile->close();
|
||||||
|
|
||||||
|
return imageTempFile;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,11 @@ class Snapshot {
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static QString saveSnapshot(QGLWidget* widget, Avatar* avatar);
|
static QString saveSnapshot(QGLWidget* widget, Avatar* avatar);
|
||||||
|
static QTemporaryFile* saveTempSnapshot(QGLWidget* widget, Avatar* avatar);
|
||||||
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static QFile* savedFileForSnapshot(QGLWidget* widget, Avatar* avatar, bool isTemporary);
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif // hifi_Snapshot_h
|
#endif // hifi_Snapshot_h
|
||||||
|
|
|
@ -55,18 +55,21 @@ typedef unsigned long long quint64;
|
||||||
#include "HandData.h"
|
#include "HandData.h"
|
||||||
|
|
||||||
// avatar motion behaviors
|
// avatar motion behaviors
|
||||||
const quint32 AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED = 1U << 0;
|
const quint32 AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED = 1U << 0;
|
||||||
|
const quint32 AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED = 1U << 1;
|
||||||
|
|
||||||
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 1;
|
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 2;
|
||||||
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 2;
|
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 3;
|
||||||
const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 3;
|
const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 4;
|
||||||
|
|
||||||
const quint32 AVATAR_MOTION_DEFAULTS =
|
const quint32 AVATAR_MOTION_DEFAULTS =
|
||||||
AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED |
|
AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED |
|
||||||
|
AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED |
|
||||||
AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;
|
AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;
|
||||||
|
|
||||||
// these bits will be expanded as features are exposed
|
// these bits will be expanded as features are exposed
|
||||||
const quint32 AVATAR_MOTION_SCRIPTABLE_BITS =
|
const quint32 AVATAR_MOTION_SCRIPTABLE_BITS =
|
||||||
|
AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED |
|
||||||
AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY |
|
AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY |
|
||||||
AVATAR_MOTION_OBEY_LOCAL_GRAVITY |
|
AVATAR_MOTION_OBEY_LOCAL_GRAVITY |
|
||||||
AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;
|
AVATAR_MOTION_STAND_ON_NEARBY_FLOORS;
|
||||||
|
|
|
@ -1755,12 +1755,24 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
|
|
||||||
// look for an unused slot in the weights vector
|
// look for an unused slot in the weights vector
|
||||||
glm::vec4& weights = extracted.mesh.clusterWeights[it.value()];
|
glm::vec4& weights = extracted.mesh.clusterWeights[it.value()];
|
||||||
for (int k = 0; k < 4; k++) {
|
int lowestIndex = -1;
|
||||||
|
float lowestWeight = FLT_MAX;
|
||||||
|
int k = 0;
|
||||||
|
for (; k < 4; k++) {
|
||||||
if (weights[k] == 0.0f) {
|
if (weights[k] == 0.0f) {
|
||||||
extracted.mesh.clusterIndices[it.value()][k] = i;
|
extracted.mesh.clusterIndices[it.value()][k] = i;
|
||||||
weights[k] = weight;
|
weights[k] = weight;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
if (weights[k] < lowestWeight) {
|
||||||
|
lowestIndex = k;
|
||||||
|
lowestWeight = weights[k];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (k == 4) {
|
||||||
|
// no space for an additional weight; we must replace the lowest
|
||||||
|
weights[lowestIndex] = weight;
|
||||||
|
extracted.mesh.clusterIndices[it.value()][lowestIndex] = i;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1769,6 +1781,14 @@ FBXGeometry extractFBXGeometry(const FBXNode& node, const QVariantHash& mapping)
|
||||||
maxJointIndex = jointIndex;
|
maxJointIndex = jointIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// normalize the weights if they don't add up to one
|
||||||
|
for (int i = 0; i < extracted.mesh.clusterWeights.size(); i++) {
|
||||||
|
glm::vec4& weights = extracted.mesh.clusterWeights[i];
|
||||||
|
float total = weights.x + weights.y + weights.z + weights.w;
|
||||||
|
if (total != 1.0f && total != 0.0f) {
|
||||||
|
weights /= total;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int jointIndex = maxJointIndex;
|
int jointIndex = maxJointIndex;
|
||||||
FBXJoint& joint = geometry.joints[jointIndex];
|
FBXJoint& joint = geometry.joints[jointIndex];
|
||||||
|
|
|
@ -25,6 +25,8 @@
|
||||||
|
|
||||||
const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false;
|
const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false;
|
||||||
|
|
||||||
|
const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization";
|
||||||
|
|
||||||
AccountManager& AccountManager::getInstance() {
|
AccountManager& AccountManager::getInstance() {
|
||||||
static AccountManager sharedInstance;
|
static AccountManager sharedInstance;
|
||||||
return sharedInstance;
|
return sharedInstance;
|
||||||
|
@ -188,7 +190,8 @@ void AccountManager::invokedRequest(const QString& path,
|
||||||
|
|
||||||
if (requiresAuthentication) {
|
if (requiresAuthentication) {
|
||||||
if (hasValidAccessToken()) {
|
if (hasValidAccessToken()) {
|
||||||
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
|
||||||
|
_accountInfo.getAccessToken().authorizationHeaderValue());
|
||||||
} else {
|
} else {
|
||||||
qDebug() << "No valid access token present. Bailing on authenticated invoked request.";
|
qDebug() << "No valid access token present. Bailing on authenticated invoked request.";
|
||||||
return;
|
return;
|
||||||
|
@ -405,9 +408,11 @@ void AccountManager::requestProfile() {
|
||||||
|
|
||||||
QUrl profileURL = _authURL;
|
QUrl profileURL = _authURL;
|
||||||
profileURL.setPath("/api/v1/users/profile");
|
profileURL.setPath("/api/v1/users/profile");
|
||||||
profileURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
|
||||||
|
QNetworkRequest profileRequest(profileURL);
|
||||||
|
profileRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, _accountInfo.getAccessToken().authorizationHeaderValue());
|
||||||
|
|
||||||
QNetworkReply* profileReply = networkAccessManager.get(QNetworkRequest(profileURL));
|
QNetworkReply* profileReply = networkAccessManager.get(profileRequest);
|
||||||
connect(profileReply, &QNetworkReply::finished, this, &AccountManager::requestProfileFinished);
|
connect(profileReply, &QNetworkReply::finished, this, &AccountManager::requestProfileFinished);
|
||||||
connect(profileReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError)));
|
connect(profileReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,6 +23,8 @@ public:
|
||||||
OAuthAccessToken(const QJsonObject& jsonObject);
|
OAuthAccessToken(const QJsonObject& jsonObject);
|
||||||
OAuthAccessToken(const OAuthAccessToken& otherToken);
|
OAuthAccessToken(const OAuthAccessToken& otherToken);
|
||||||
OAuthAccessToken& operator=(const OAuthAccessToken& otherToken);
|
OAuthAccessToken& operator=(const OAuthAccessToken& otherToken);
|
||||||
|
|
||||||
|
QByteArray authorizationHeaderValue() const { return QString("Bearer %1").arg(token).toUtf8(); }
|
||||||
|
|
||||||
bool isExpired() const { return expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); }
|
bool isExpired() const { return expiryTimestamp <= QDateTime::currentMSecsSinceEpoch(); }
|
||||||
|
|
||||||
|
|
|
@ -614,6 +614,7 @@ void ScriptEngine::timerFired() {
|
||||||
|
|
||||||
if (!callingTimer->isActive()) {
|
if (!callingTimer->isActive()) {
|
||||||
// this timer is done, we can kill it
|
// this timer is done, we can kill it
|
||||||
|
_timerFunctionMap.remove(callingTimer);
|
||||||
delete callingTimer;
|
delete callingTimer;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue