mirror of
https://github.com/overte-org/overte.git
synced 2025-04-22 15:13:30 +02:00
merge fix
This commit is contained in:
commit
432622d168
26 changed files with 580 additions and 281 deletions
domain-server/src
examples
interface/src
libraries
avatars/src
fbx/src
networking/src
script-engine/src
|
@ -309,8 +309,15 @@ void DomainServer::setupDynamicIPAddressUpdating() {
|
|||
// send public socket changes to the data server so nodes can find us at our new IP
|
||||
connect(nodeList, &LimitedNodeList::publicSockAddrChanged, this, &DomainServer::sendNewPublicSocketToDataServer);
|
||||
|
||||
// check our IP address right away
|
||||
requestCurrentIPAddressViaSTUN();
|
||||
if (!AccountManager::getInstance().hasValidAccessToken()) {
|
||||
// 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 {
|
||||
qDebug() << "Cannot enable dynamic domain-server IP address updating without a domain ID."
|
||||
|
|
|
@ -16,17 +16,30 @@ var debug = false;
|
|||
var movingWithHead = false;
|
||||
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_ROTATE_DEAD_ZONE = 0.0;
|
||||
var HEAD_THRUST_FWD_SCALE = 12000.0;
|
||||
var HEAD_THRUST_STRAFE_SCALE = 2000.0;
|
||||
//var HEAD_THRUST_FWD_SCALE = 12000.0;
|
||||
//var HEAD_THRUST_STRAFE_SCALE = 0.0;
|
||||
var HEAD_YAW_RATE = 1.0;
|
||||
var HEAD_PITCH_RATE = 1.0;
|
||||
var HEAD_ROLL_THRUST_SCALE = 75.0;
|
||||
var HEAD_PITCH_LIFT_THRUST = 3.0;
|
||||
//var HEAD_ROLL_THRUST_SCALE = 75.0;
|
||||
//var HEAD_PITCH_LIFT_THRUST = 3.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
|
||||
var maxVelocity = 1.25;
|
||||
var noFly = true;
|
||||
|
@ -51,8 +64,8 @@ function isInRoom(position) {
|
|||
}
|
||||
|
||||
function moveWithHead(deltaTime) {
|
||||
var thrust = { x: 0, y: 0, z: 0 };
|
||||
var position = MyAvatar.position;
|
||||
var motorTimescale = VERY_LARGE_TIMESCALE;
|
||||
if (movingWithHead) {
|
||||
var deltaYaw = MyAvatar.getHeadFinalYaw() - headStartYaw;
|
||||
var deltaPitch = MyAvatar.getHeadDeltaPitch() - headStartDeltaPitch;
|
||||
|
@ -64,44 +77,36 @@ function moveWithHead(deltaTime) {
|
|||
headDelta = Vec3.multiplyQbyV(Quat.inverse(Camera.getOrientation()), headDelta);
|
||||
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
|
||||
var targetVelocity = {x:0.0, y:0.0, z:0.0};
|
||||
if (Math.abs(headDelta.z) > HEAD_MOVE_DEAD_ZONE) {
|
||||
if (Math.abs(Vec3.dot(velocity, forward)) < maxVelocity) {
|
||||
thrust = Vec3.sum(thrust, Vec3.multiply(forward, -headDelta.z * HEAD_THRUST_FWD_SCALE * deltaTime));
|
||||
}
|
||||
targetVelocity = Vec3.multiply(zAxis, -headDelta.z * HEAD_VELOCITY_FWD_FACTOR);
|
||||
}
|
||||
if (Math.abs(headDelta.x) > HEAD_STRAFE_DEAD_ZONE) {
|
||||
if (Math.abs(Vec3.dot(velocity, right)) < maxVelocity) {
|
||||
thrust = Vec3.sum(thrust, Vec3.multiply(right, headDelta.x * HEAD_THRUST_STRAFE_SCALE * deltaTime));
|
||||
}
|
||||
var deltaVelocity = Vec3.multiply(xAxis, -headDelta.x * HEAD_VELOCITY_LEFT_FACTOR);
|
||||
targetVelocity = Vec3.sum(targetVelocity, deltaVelocity);
|
||||
}
|
||||
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;
|
||||
}
|
||||
// Thrust Up/Down based on head pitch
|
||||
if (!noFly) {
|
||||
if ((Math.abs(Vec3.dot(velocity, up)) < maxVelocity)) {
|
||||
thrust = Vec3.sum(thrust, Vec3.multiply({ x:0, y:1, z:0 }, (MyAvatar.getHeadFinalPitch() - headStartFinalPitch) * HEAD_PITCH_LIFT_THRUST * deltaTime));
|
||||
}
|
||||
var deltaVelocity = Vec3.multiply(yAxis, headDelta.y * HEAD_VELOCITY_UP_FACTOR);
|
||||
targetVelocity = Vec3.sum(targetVelocity, deltaVelocity);
|
||||
}
|
||||
// For head trackers, adjust pitch by head pitch
|
||||
MyAvatar.headPitch += deltaPitch * HEAD_PITCH_RATE * deltaTime;
|
||||
|
||||
// apply the motor
|
||||
MyAvatar.motorVelocity = targetVelocity;
|
||||
motorTimescale = SHORT_TIMESCALE;
|
||||
}
|
||||
|
||||
// Check against movement box limits
|
||||
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) {
|
||||
thrust.x += (roomLimits.xMin - position.x) * WALL_BOUNCE * deltaTime;
|
||||
} else if (position.x > roomLimits.xMax) {
|
||||
|
@ -112,11 +117,14 @@ function moveWithHead(deltaTime) {
|
|||
} else if (position.z > roomLimits.zMax) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// Check against movement box limits
|
||||
|
||||
MyAvatar.addThrust(thrust);
|
||||
MyAvatar.motorTimescale = motorTimescale;
|
||||
}
|
||||
|
||||
Controller.keyPressEvent.connect(function(event) {
|
||||
|
@ -126,13 +134,21 @@ Controller.keyPressEvent.connect(function(event) {
|
|||
headStartDeltaPitch = MyAvatar.getHeadDeltaPitch();
|
||||
headStartFinalPitch = MyAvatar.getHeadFinalPitch();
|
||||
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) {
|
||||
if (event.text == "SPACE") {
|
||||
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);
|
||||
|
|
|
@ -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
|
||||
accountManager.setAuthURL(DEFAULT_NODE_AUTH_URL);
|
||||
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
|
||||
QMetaObject::invokeMethod(&accountManager, "checkAndSignalForAccessToken", Qt::QueuedConnection);
|
||||
|
@ -927,6 +930,13 @@ void Application::keyPressEvent(QKeyEvent* event) {
|
|||
}
|
||||
|
||||
break;
|
||||
|
||||
case Qt::Key_N:
|
||||
if (isMeta) {
|
||||
Menu::getInstance()->triggerOption(MenuOption::NameLocation);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Qt::Key_Up:
|
||||
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
|
||||
// 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.
|
||||
_myAvatar->clearJointAnimationPriorities();
|
||||
_myAvatar->clearScriptableSettings();
|
||||
}
|
||||
|
||||
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.
|
||||
_myAvatar->clearJointAnimationPriorities();
|
||||
}
|
||||
if (_scriptEnginesHash.empty()) {
|
||||
_myAvatar->clearScriptableSettings();
|
||||
}
|
||||
}
|
||||
|
||||
void Application::reloadAllScripts() {
|
||||
|
|
|
@ -268,13 +268,17 @@ Menu::Menu() :
|
|||
SLOT(resetSize()));
|
||||
|
||||
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::GlowWhenSpeaking, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true);
|
||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false,
|
||||
avatar, SLOT(updateMotionBehaviorsFromMenu()));
|
||||
avatar, SLOT(updateMotionBehavior()));
|
||||
addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true,
|
||||
avatar, SLOT(updateMotionBehaviorsFromMenu()));
|
||||
avatar, SLOT(updateMotionBehavior()));
|
||||
|
||||
QMenu* collisionsMenu = avatarMenu->addMenu("Collide With...");
|
||||
addCheckableActionToQMenuAndActionHash(collisionsMenu, MenuOption::CollideAsRagdoll, 0, false,
|
||||
|
@ -328,9 +332,9 @@ Menu::Menu() :
|
|||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersVoxelNodes,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_1, false,
|
||||
&nodeBounds, SLOT(setShowVoxelNodes(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersModelNodes,
|
||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersEntityNodes,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_2, false,
|
||||
&nodeBounds, SLOT(setShowModelNodes(bool)));
|
||||
&nodeBounds, SLOT(setShowEntityNodes(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(nodeBordersMenu, MenuOption::ShowBordersParticleNodes,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_3, false,
|
||||
&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.
|
||||
// TODO: cache more settings in MyAvatar that are checked with very high frequency.
|
||||
setIsOptionChecked(MenuOption::KeyboardMotorControl , true);
|
||||
MyAvatar* myAvatar = Application::getInstance()->getAvatar();
|
||||
myAvatar->updateCollisionGroups();
|
||||
myAvatar->onToggleRagdoll();
|
||||
myAvatar->updateMotionBehavior();
|
||||
|
||||
if (lockedSettings) {
|
||||
Application::getInstance()->unlockSettings();
|
||||
|
|
|
@ -399,6 +399,7 @@ namespace MenuOption {
|
|||
const QString HeadMouse = "Head Mouse";
|
||||
const QString IncreaseAvatarSize = "Increase Avatar Size";
|
||||
const QString IncreaseVoxelSize = "Increase Voxel Size";
|
||||
const QString KeyboardMotorControl = "Enable Keyboard Motor Control";
|
||||
const QString LoadScript = "Open and Run Script File...";
|
||||
const QString LoadScriptURL = "Open and Run Script from URL...";
|
||||
const QString LodTools = "LOD Tools";
|
||||
|
@ -437,9 +438,10 @@ namespace MenuOption {
|
|||
const QString RunningScripts = "Running Scripts";
|
||||
const QString RunTimingTests = "Run Timing Tests";
|
||||
const QString ScriptEditor = "Script Editor...";
|
||||
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
||||
const QString SettingsExport = "Export 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 ShowBordersVoxelNodes = "Show Voxel Nodes";
|
||||
const QString ShowIKConstraints = "Show IK Constraints";
|
||||
|
|
|
@ -42,17 +42,15 @@ ScriptsModel::ScriptsModel(QObject* parent) :
|
|||
_localDirectory(),
|
||||
_fsWatcher(),
|
||||
_localFiles(),
|
||||
_remoteFiles() {
|
||||
|
||||
QString scriptPath = Menu::getInstance()->getScriptsLocation();
|
||||
|
||||
_localDirectory.setPath(scriptPath);
|
||||
_remoteFiles()
|
||||
{
|
||||
|
||||
_localDirectory.setFilter(QDir::Files | QDir::Readable);
|
||||
_localDirectory.setNameFilters(QStringList("*.js"));
|
||||
|
||||
_fsWatcher.addPath(_localDirectory.absolutePath());
|
||||
updateScriptsLocation(Menu::getInstance()->getScriptsLocation());
|
||||
|
||||
connect(&_fsWatcher, &QFileSystemWatcher::directoryChanged, this, &ScriptsModel::reloadLocalFiles);
|
||||
|
||||
connect(Menu::getInstance(), &Menu::scriptLocationChanged, this, &ScriptsModel::updateScriptsLocation);
|
||||
|
||||
reloadLocalFiles();
|
||||
|
@ -88,8 +86,13 @@ int ScriptsModel::rowCount(const QModelIndex& parent) const {
|
|||
|
||||
void ScriptsModel::updateScriptsLocation(const QString& newPath) {
|
||||
_fsWatcher.removePath(_localDirectory.absolutePath());
|
||||
|
||||
_localDirectory.setPath(newPath);
|
||||
_fsWatcher.addPath(_localDirectory.absolutePath());
|
||||
|
||||
if (!_localDirectory.absolutePath().isEmpty()) {
|
||||
_fsWatcher.addPath(_localDirectory.absolutePath());
|
||||
}
|
||||
|
||||
reloadLocalFiles();
|
||||
}
|
||||
|
||||
|
|
|
@ -57,15 +57,15 @@ void FaceModel::maybeUpdateNeckRotation(const JointState& parentState, const FBX
|
|||
* 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
|
||||
// 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()) *
|
||||
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 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);
|
||||
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)) *
|
||||
|
@ -82,7 +82,7 @@ void FaceModel::updateJointState(int index) {
|
|||
maybeUpdateNeckRotation(parentState, joint, state);
|
||||
|
||||
} 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 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);
|
||||
|
||||
/// 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) {
|
||||
if (_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)) &&
|
||||
_renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) {
|
||||
_faceModel.render(alpha, mode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows));
|
||||
if (_renderLookatVectors && mode != Model::SHADOW_RENDER_MODE) {
|
||||
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
|
||||
// to properly follow avatar size.
|
||||
float DEFAULT_MOTOR_TIMESCALE = 0.25f;
|
||||
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() :
|
||||
Avatar(),
|
||||
|
@ -71,9 +76,11 @@ MyAvatar::MyAvatar() :
|
|||
_isBraking(false),
|
||||
_trapDuration(0.0f),
|
||||
_thrust(0.0f),
|
||||
_motorVelocity(0.0f),
|
||||
_motorTimescale(DEFAULT_MOTOR_TIMESCALE),
|
||||
_maxMotorSpeed(MAX_MOTOR_SPEED),
|
||||
_keyboardMotorVelocity(0.0f),
|
||||
_keyboardMotorTimescale(DEFAULT_KEYBOARD_MOTOR_TIMESCALE),
|
||||
_scriptedMotorVelocity(0.0f),
|
||||
_scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE),
|
||||
_scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME),
|
||||
_motionBehaviors(AVATAR_MOTION_DEFAULTS),
|
||||
_lookAtTargetAvatar(),
|
||||
_shouldRender(true),
|
||||
|
@ -1028,6 +1035,48 @@ void MyAvatar::setAttachmentData(const QVector<AttachmentData>& attachmentData)
|
|||
_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,
|
||||
const glm::quat& rotation, float scale, bool allowDuplicates, bool useSaved) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
|
@ -1139,41 +1188,133 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
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;
|
||||
|
||||
void MyAvatar::updatePosition(float deltaTime) {
|
||||
|
||||
// check for floor by casting a ray straight down from avatar's position
|
||||
float heightAboveFloor = FLT_MAX;
|
||||
bool hasFloor = false;
|
||||
const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape();
|
||||
const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD;
|
||||
|
||||
RayIntersectionInfo intersection;
|
||||
// NOTE: avatar is center of PhysicsSimulation, so rayStart is the origin for the purposes of the raycast
|
||||
intersection._rayStart = glm::vec3(0.0f);
|
||||
intersection._rayDirection = - _worldUpDirection;
|
||||
intersection._rayLength = 5.0f * boundingShape.getBoundingRadius();
|
||||
intersection._rayLength = 4.0f * boundingShape.getBoundingRadius();
|
||||
if (_physicsSimulation.findFloorRayIntersection(intersection)) {
|
||||
// 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
|
||||
glm::vec3 velocity = _velocity;
|
||||
|
||||
bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f);
|
||||
bool walkingOnFloor = false;
|
||||
bool pushingUp = (_driveKeys[UP] - _driveKeys[DOWN] > 0.0f) || _scriptedMotorVelocity.y > 0.0f;
|
||||
if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) {
|
||||
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) {
|
||||
// we're pushing up or moving quickly, so disable gravity
|
||||
setLocalGravity(glm::vec3(0.0f));
|
||||
hasFloor = false;
|
||||
} else {
|
||||
const float maxFloorDistance = boundingShape.getBoundingRadius() * NEARBY_FLOOR_THRESHOLD;
|
||||
if (heightAboveFloor > maxFloorDistance) {
|
||||
// disable local gravity when floor is too far away
|
||||
setLocalGravity(glm::vec3(0.0f));
|
||||
hasFloor = false;
|
||||
} else {
|
||||
// enable gravity
|
||||
walkingOnFloor = true;
|
||||
setLocalGravity(-_worldUpDirection);
|
||||
}
|
||||
}
|
||||
|
@ -1182,76 +1323,35 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
bool zeroDownwardVelocity = false;
|
||||
bool gravityEnabled = (glm::length2(_gravity) > EPSILON);
|
||||
if (gravityEnabled) {
|
||||
if (heightAboveFloor < 0.0f) {
|
||||
// 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).
|
||||
float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime);
|
||||
|
||||
// We don't use applyPositionDelta() for this lift distance because we don't want the avatar
|
||||
// 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 - EPSILON) * _worldUpDirection;
|
||||
const float SLOP = 0.002f;
|
||||
if (heightAboveFloor < SLOP) {
|
||||
if (heightAboveFloor < 0.0) {
|
||||
// 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).
|
||||
float distanceToLift = glm::min(-heightAboveFloor, MAX_WALKING_SPEED * deltaTime);
|
||||
|
||||
// We don't use applyPositionDelta() for this lift distance because we don't want the avatar
|
||||
// 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;
|
||||
}
|
||||
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);
|
||||
if (!zeroDownwardVelocity) {
|
||||
velocity += (deltaTime * GRAVITY_EARTH) * _gravity;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
// remove vertical component of deltaVelocity
|
||||
deltaVelocity -= glm::dot(deltaVelocity, _worldUpDirection) * _worldUpDirection;
|
||||
}
|
||||
// apply motors in camera frame
|
||||
glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor);
|
||||
newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity);
|
||||
|
||||
// apply motor
|
||||
velocity += motorEfficiency * deltaVelocity;
|
||||
// rotate back into world-frame
|
||||
velocity = rotation * newLocalVelocity;
|
||||
|
||||
// apply thrust
|
||||
velocity += _thrust * deltaTime;
|
||||
|
@ -1260,8 +1360,8 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
// remove downward velocity so we don't push into floor
|
||||
if (zeroDownwardVelocity) {
|
||||
float verticalSpeed = glm::dot(velocity, _worldUpDirection);
|
||||
if (verticalSpeed < 0.0f) {
|
||||
velocity += verticalSpeed * _worldUpDirection;
|
||||
if (verticalSpeed < 0.0f || !pushingUp) {
|
||||
velocity -= verticalSpeed * _worldUpDirection;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1286,37 +1386,6 @@ void MyAvatar::updatePosition(float 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) {
|
||||
glm::vec3 up = getBodyUpDirection();
|
||||
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) {
|
||||
// we're colliding against an edge
|
||||
|
||||
// rotate _motorVelocity into world frame
|
||||
glm::vec3 targetVelocity = _motorVelocity;
|
||||
// rotate _keyboardMotorVelocity into world frame
|
||||
glm::vec3 targetVelocity = _keyboardMotorVelocity;
|
||||
glm::quat rotation = getHead()->getCameraOrientation();
|
||||
targetVelocity = rotation * _motorVelocity;
|
||||
targetVelocity = rotation * _keyboardMotorVelocity;
|
||||
if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) {
|
||||
// we're puhing into the edge, so we want to lift
|
||||
|
||||
|
@ -1799,7 +1868,7 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition,
|
|||
emit transformChanged();
|
||||
}
|
||||
|
||||
void MyAvatar::updateMotionBehaviorsFromMenu() {
|
||||
void MyAvatar::updateMotionBehavior() {
|
||||
Menu* menu = Menu::getInstance();
|
||||
if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) {
|
||||
_motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY;
|
||||
|
@ -1822,6 +1891,16 @@ void MyAvatar::updateMotionBehaviorsFromMenu() {
|
|||
if (!(_collisionGroups | COLLISION_GROUP_VOXELS)) {
|
||||
_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() {
|
||||
|
@ -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) {
|
||||
glm::vec3 leverAxis = contactPoint - getPosition();
|
||||
float leverLength = glm::length(leverAxis);
|
||||
|
|
|
@ -33,7 +33,9 @@ enum AvatarHandState
|
|||
class MyAvatar : public Avatar {
|
||||
Q_OBJECT
|
||||
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)
|
||||
|
||||
public:
|
||||
|
@ -131,15 +133,22 @@ public:
|
|||
|
||||
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(),
|
||||
const glm::vec3& translation = glm::vec3(), const glm::quat& rotation = glm::quat(), float scale = 1.0f,
|
||||
bool allowDuplicates = false, bool useSaved = true);
|
||||
|
||||
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);
|
||||
|
||||
/// Renders a laser pointer for UI picking
|
||||
|
@ -165,7 +174,7 @@ public slots:
|
|||
|
||||
void setVelocity(const glm::vec3 velocity) { _velocity = velocity; }
|
||||
|
||||
void updateMotionBehaviorsFromMenu();
|
||||
void updateMotionBehavior();
|
||||
void onToggleRagdoll();
|
||||
|
||||
glm::vec3 getLeftPalmPosition();
|
||||
|
@ -204,9 +213,11 @@ private:
|
|||
float _trapDuration; // seconds that avatar has been trapped by collisions
|
||||
glm::vec3 _thrust; // impulse accumulator for outside sources
|
||||
|
||||
glm::vec3 _motorVelocity; // intended velocity of avatar motion (relative to what it's standing on)
|
||||
float _motorTimescale; // timescale for avatar motor to achieve its desired velocity
|
||||
float _maxMotorSpeed;
|
||||
glm::vec3 _keyboardMotorVelocity; // target local-frame velocity of avatar (keyboard)
|
||||
float _keyboardMotorTimescale; // timescale for avatar to achieve its target velocity
|
||||
glm::vec3 _scriptedMotorVelocity; // target local-frame velocity of avatar (script)
|
||||
float _scriptedMotorTimescale; // timescale for avatar to achieve its target velocity
|
||||
int _scriptedMotorFrame;
|
||||
quint32 _motionBehaviors;
|
||||
|
||||
QWeakPointer<AvatarData> _lookAtTargetAvatar;
|
||||
|
@ -223,8 +234,9 @@ private:
|
|||
|
||||
// private methods
|
||||
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);
|
||||
float computeMotorTimescale(const glm::vec3& velocity);
|
||||
void updateCollisionWithAvatars(float deltaTime);
|
||||
void updateCollisionWithEnvironment(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) {
|
||||
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(parentState, joint, state);
|
||||
_owningAvatar->getHead()->getFaceModel().maybeUpdateEyeRotation(this, parentState, joint, state);
|
||||
}
|
||||
|
||||
void SkeletonModel::renderJointConstraints(int jointIndex) {
|
||||
|
|
|
@ -109,6 +109,7 @@ public:
|
|||
void renderJointCollisionShapes(float alpha);
|
||||
float getBoundingShapeRadius() const { return _boundingShape.getRadius(); }
|
||||
const CapsuleShape& getBoundingShape() const { return _boundingShape; }
|
||||
const glm::vec3 getBoundingShapeOffset() const { return _boundingShapeLocalOffset; }
|
||||
|
||||
void resetShapePositionsToDefaultPose(); // DEBUG method
|
||||
|
||||
|
|
|
@ -9,10 +9,14 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include <qhttpmultipart.h>
|
||||
#include <qjsonobject.h>
|
||||
|
||||
#include <AccountManager.h>
|
||||
|
||||
#include "Application.h"
|
||||
#include "ui/Snapshot.h"
|
||||
|
||||
#include "LocationManager.h"
|
||||
|
||||
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!";
|
||||
|
||||
void LocationManager::namedLocationDataReceived(const QJsonObject& data) {
|
||||
if (data.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
const QString LOCATION_OBJECT_KEY = "location";
|
||||
const QString LOCATION_ID_KEY = "id";
|
||||
|
||||
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());
|
||||
|
||||
// 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 {
|
||||
emit creationCompleted(UNKNOWN_ERROR_MESSAGE);
|
||||
}
|
||||
|
@ -87,3 +95,57 @@ void LocationManager::errorDataReceived(QNetworkReply& errorReply) {
|
|||
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);
|
||||
|
||||
private slots:
|
||||
void namedLocationDataReceived(const QJsonObject& data);
|
||||
void namedLocationDataReceived(const QJsonObject& jsonObject);
|
||||
void errorDataReceived(QNetworkReply& errorReply);
|
||||
void locationImageUpdateSuccess(const QJsonObject& jsonObject);
|
||||
|
||||
private:
|
||||
void updateSnapshotForExistingLocation(const QString& locationID);
|
||||
|
||||
};
|
||||
|
||||
|
|
|
@ -20,6 +20,10 @@
|
|||
#include "Model.h"
|
||||
#include "world.h"
|
||||
|
||||
GeometryCache::GeometryCache() :
|
||||
_pendingBlenders(0) {
|
||||
}
|
||||
|
||||
GeometryCache::~GeometryCache() {
|
||||
foreach (const VerticesIndices& vbo, _hemisphereVBOs) {
|
||||
glDeleteBuffers(1, &vbo.first);
|
||||
|
@ -296,10 +300,30 @@ QSharedPointer<NetworkGeometry> GeometryCache::getGeometry(const QUrl& url, cons
|
|||
return getResource(url, fallback, delayLoad).staticCast<NetworkGeometry>();
|
||||
}
|
||||
|
||||
void GeometryCache::setBlendedVertices(const QPointer<Model>& model, const QWeakPointer<NetworkGeometry>& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals) {
|
||||
void GeometryCache::noteRequiresBlend(Model* model) {
|
||||
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()) {
|
||||
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:
|
||||
|
||||
GeometryCache();
|
||||
virtual ~GeometryCache();
|
||||
|
||||
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
|
||||
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:
|
||||
|
||||
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);
|
||||
|
||||
protected:
|
||||
|
@ -68,6 +72,9 @@ private:
|
|||
QHash<IntPair, QOpenGLBuffer> _gridBuffers;
|
||||
|
||||
QHash<QUrl, QWeakPointer<NetworkGeometry> > _networkGeometry;
|
||||
|
||||
QList<QPointer<Model> > _modelsRequiringBlends;
|
||||
int _pendingBlenders;
|
||||
};
|
||||
|
||||
/// Geometry loaded from the network.
|
||||
|
|
|
@ -62,8 +62,8 @@ Model::Model(QObject* parent) :
|
|||
_lodDistance(0.0f),
|
||||
_pupilDilation(0.0f),
|
||||
_url("http://invalid.com"),
|
||||
_blenderPending(false),
|
||||
_blendRequired(false) {
|
||||
_blendNumber(0),
|
||||
_appliedBlendNumber(0) {
|
||||
|
||||
// we may have been created in the network thread, but we live in the main thread
|
||||
moveToThread(Application::getInstance()->thread());
|
||||
|
@ -826,7 +826,7 @@ void Model::updateShapePositions() {
|
|||
class Blender : public QRunnable {
|
||||
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);
|
||||
|
||||
virtual void run();
|
||||
|
@ -834,55 +834,55 @@ public:
|
|||
private:
|
||||
|
||||
QPointer<Model> _model;
|
||||
int _blendNumber;
|
||||
QWeakPointer<NetworkGeometry> _geometry;
|
||||
QVector<FBXMesh> _meshes;
|
||||
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) :
|
||||
_model(model),
|
||||
_blendNumber(blendNumber),
|
||||
_geometry(geometry),
|
||||
_meshes(meshes),
|
||||
_blendshapeCoefficients(blendshapeCoefficients) {
|
||||
}
|
||||
|
||||
void Blender::run() {
|
||||
// make sure the model still exists
|
||||
if (_model.isNull()) {
|
||||
return;
|
||||
}
|
||||
QVector<glm::vec3> vertices, normals;
|
||||
int offset = 0;
|
||||
foreach (const FBXMesh& mesh, _meshes) {
|
||||
if (mesh.blendshapes.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
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) {
|
||||
if (!_model.isNull()) {
|
||||
int offset = 0;
|
||||
foreach (const FBXMesh& mesh, _meshes) {
|
||||
if (mesh.blendshapes.isEmpty()) {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
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
|
||||
QMetaObject::invokeMethod(Application::getInstance()->getGeometryCache(), "setBlendedVertices",
|
||||
Q_ARG(const QPointer<Model>&, _model), Q_ARG(const QWeakPointer<NetworkGeometry>&, _geometry),
|
||||
Q_ARG(const QVector<glm::vec3>&, vertices), Q_ARG(const QVector<glm::vec3>&, normals));
|
||||
Q_ARG(const QPointer<Model>&, _model), Q_ARG(int, _blendNumber),
|
||||
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) {
|
||||
|
@ -894,6 +894,10 @@ void Model::setScaleToFit(bool scaleToFit, const glm::vec3& dimensions) {
|
|||
}
|
||||
|
||||
void Model::setScaleToFit(bool scaleToFit, float largestDimension) {
|
||||
if (!isActive()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (_scaleToFit != scaleToFit || glm::length(_scaleToFitDimensions) != largestDimension) {
|
||||
_scaleToFit = scaleToFit;
|
||||
|
||||
|
@ -1016,14 +1020,9 @@ void Model::simulateInternal(float deltaTime) {
|
|||
}
|
||||
|
||||
// post the blender if we're not currently waiting for one to finish
|
||||
if (geometry.hasBlendedMeshes()) {
|
||||
if (_blenderPending) {
|
||||
_blendRequired = true;
|
||||
} else {
|
||||
_blendRequired = false;
|
||||
_blenderPending = true;
|
||||
QThreadPool::globalInstance()->start(new Blender(this, _geometry, geometry.meshes, _blendshapeCoefficients));
|
||||
}
|
||||
if (geometry.hasBlendedMeshes() && _blendshapeCoefficients != _blendedBlendshapeCoefficients) {
|
||||
_blendedBlendshapeCoefficients = _blendshapeCoefficients;
|
||||
Application::getInstance()->getGeometryCache()->noteRequiresBlend(this);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1286,22 +1285,23 @@ void Model::renderJointCollisionShapes(float alpha) {
|
|||
// implement this when we have shapes for regular models
|
||||
}
|
||||
|
||||
void Model::setBlendedVertices(const QWeakPointer<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices,
|
||||
const QVector<glm::vec3>& normals) {
|
||||
_blenderPending = false;
|
||||
|
||||
// start the next blender if required
|
||||
bool Model::maybeStartBlender() {
|
||||
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
|
||||
if (_blendRequired) {
|
||||
_blendRequired = false;
|
||||
if (fbxGeometry.hasBlendedMeshes()) {
|
||||
_blenderPending = true;
|
||||
QThreadPool::globalInstance()->start(new Blender(this, _geometry, fbxGeometry.meshes, _blendshapeCoefficients));
|
||||
}
|
||||
if (fbxGeometry.hasBlendedMeshes()) {
|
||||
QThreadPool::globalInstance()->start(new Blender(this, ++_blendNumber, _geometry,
|
||||
fbxGeometry.meshes, _blendshapeCoefficients));
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
}
|
||||
_appliedBlendNumber = blendNumber;
|
||||
const FBXGeometry& fbxGeometry = _geometry->getFBXGeometry();
|
||||
int index = 0;
|
||||
for (int i = 0; i < fbxGeometry.meshes.size(); i++) {
|
||||
const FBXMesh& mesh = fbxGeometry.meshes.at(i);
|
||||
|
@ -1354,6 +1354,8 @@ void Model::deleteGeometry() {
|
|||
if (_geometry) {
|
||||
_geometry->clearLoadPriority(this);
|
||||
}
|
||||
|
||||
_blendedBlendshapeCoefficients.clear();
|
||||
}
|
||||
|
||||
void Model::renderMeshes(RenderMode mode, bool translucent, bool receiveShadows) {
|
||||
|
|
|
@ -165,9 +165,11 @@ public:
|
|||
|
||||
virtual void renderJointCollisionShapes(float alpha);
|
||||
|
||||
bool maybeStartBlender();
|
||||
|
||||
/// Sets blended vertices computed in a separate thread.
|
||||
void setBlendedVertices(const QWeakPointer<NetworkGeometry>& geometry, const QVector<glm::vec3>& vertices,
|
||||
const QVector<glm::vec3>& normals);
|
||||
void setBlendedVertices(int blendNumber, const QWeakPointer<NetworkGeometry>& geometry,
|
||||
const QVector<glm::vec3>& vertices, const QVector<glm::vec3>& normals);
|
||||
|
||||
class LocalLight {
|
||||
public:
|
||||
|
@ -285,8 +287,9 @@ private:
|
|||
glm::vec4 _localLightColors[MAX_LOCAL_LIGHTS];
|
||||
glm::vec4 _localLightDirections[MAX_LOCAL_LIGHTS];
|
||||
|
||||
bool _blenderPending;
|
||||
bool _blendRequired;
|
||||
QVector<float> _blendedBlendshapeCoefficients;
|
||||
int _blendNumber;
|
||||
int _appliedBlendNumber;
|
||||
|
||||
static ProgramObject _program;
|
||||
static ProgramObject _normalMapProgram;
|
||||
|
|
|
@ -65,6 +65,24 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
|
|||
}
|
||||
|
||||
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();
|
||||
|
||||
glm::vec3 location = avatar->getPosition();
|
||||
|
@ -91,16 +109,40 @@ QString Snapshot::saveSnapshot(QGLWidget* widget, Avatar* avatar) {
|
|||
username.replace(QRegExp("[^A-Za-z0-9_]"), "-");
|
||||
|
||||
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:
|
||||
static QString saveSnapshot(QGLWidget* widget, Avatar* avatar);
|
||||
static QTemporaryFile* saveTempSnapshot(QGLWidget* widget, Avatar* avatar);
|
||||
static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
|
||||
|
||||
private:
|
||||
static QFile* savedFileForSnapshot(QGLWidget* widget, Avatar* avatar, bool isTemporary);
|
||||
};
|
||||
|
||||
#endif // hifi_Snapshot_h
|
||||
|
|
|
@ -55,18 +55,21 @@ typedef unsigned long long quint64;
|
|||
#include "HandData.h"
|
||||
|
||||
// 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_LOCAL_GRAVITY = 1U << 2;
|
||||
const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 3;
|
||||
const quint32 AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY = 1U << 2;
|
||||
const quint32 AVATAR_MOTION_OBEY_LOCAL_GRAVITY = 1U << 3;
|
||||
const quint32 AVATAR_MOTION_STAND_ON_NEARBY_FLOORS = 1U << 4;
|
||||
|
||||
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;
|
||||
|
||||
// these bits will be expanded as features are exposed
|
||||
const quint32 AVATAR_MOTION_SCRIPTABLE_BITS =
|
||||
AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED |
|
||||
AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY |
|
||||
AVATAR_MOTION_OBEY_LOCAL_GRAVITY |
|
||||
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
|
||||
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) {
|
||||
extracted.mesh.clusterIndices[it.value()][k] = i;
|
||||
weights[k] = weight;
|
||||
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;
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
int jointIndex = maxJointIndex;
|
||||
FBXJoint& joint = geometry.joints[jointIndex];
|
||||
|
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false;
|
||||
|
||||
const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
AccountManager& AccountManager::getInstance() {
|
||||
static AccountManager sharedInstance;
|
||||
return sharedInstance;
|
||||
|
@ -188,7 +190,8 @@ void AccountManager::invokedRequest(const QString& path,
|
|||
|
||||
if (requiresAuthentication) {
|
||||
if (hasValidAccessToken()) {
|
||||
requestURL.setQuery("access_token=" + _accountInfo.getAccessToken().token);
|
||||
networkRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
|
||||
_accountInfo.getAccessToken().authorizationHeaderValue());
|
||||
} else {
|
||||
qDebug() << "No valid access token present. Bailing on authenticated invoked request.";
|
||||
return;
|
||||
|
@ -405,9 +408,11 @@ void AccountManager::requestProfile() {
|
|||
|
||||
QUrl profileURL = _authURL;
|
||||
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, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(requestProfileError(QNetworkReply::NetworkError)));
|
||||
}
|
||||
|
|
|
@ -23,6 +23,8 @@ public:
|
|||
OAuthAccessToken(const QJsonObject& jsonObject);
|
||||
OAuthAccessToken(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(); }
|
||||
|
||||
|
|
|
@ -614,6 +614,7 @@ void ScriptEngine::timerFired() {
|
|||
|
||||
if (!callingTimer->isActive()) {
|
||||
// this timer is done, we can kill it
|
||||
_timerFunctionMap.remove(callingTimer);
|
||||
delete callingTimer;
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue