// // MyAvatar.cpp // interface/src/avatar // // Created by Mark Peng on 8/16/13. // Copyright 2012 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "Application.h" #include "Audio.h" #include "Environment.h" #include "Menu.h" #include "ModelReferential.h" #include "MyAvatar.h" #include "Physics.h" #include "Recorder.h" #include "devices/Faceshift.h" #include "devices/OculusManager.h" #include "ui/TextRenderer.h" using namespace std; const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f); const float YAW_SPEED = 500.0f; // degrees/sec const float PITCH_SPEED = 100.0f; // degrees/sec const float COLLISION_RADIUS_SCALAR = 1.2f; // pertains to avatar-to-avatar collisions const float COLLISION_RADIUS_SCALE = 0.125f; const float MIN_KEYBOARD_CONTROL_SPEED = 2.0f; 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; MyAvatar::MyAvatar() : Avatar(), _mousePressed(false), _bodyPitchDelta(0.0f), _bodyRollDelta(0.0f), _shouldJump(false), _gravity(0.0f, 0.0f, 0.0f), _distanceToNearestAvatar(std::numeric_limits::max()), _wasPushing(false), _isPushing(false), _isBraking(false), _trapDuration(0.0f), _thrust(0.0f), _motorVelocity(0.0f), _motorTimescale(DEFAULT_MOTOR_TIMESCALE), _maxMotorSpeed(MAX_MOTOR_SPEED), _motionBehaviors(AVATAR_MOTION_DEFAULTS), _lastBodyPenetration(0.0f), _lastFloorContactPoint(0.0f), _lookAtTargetAvatar(), _shouldRender(true), _billboardValid(false), _physicsSimulation() { for (int i = 0; i < MAX_DRIVE_KEYS; i++) { _driveKeys[i] = 0.0f; } _physicsSimulation.setEntity(&_skeletonModel); _skeletonModel.setEnableShapes(true); Ragdoll* ragdoll = _skeletonModel.buildRagdoll(); _physicsSimulation.setRagdoll(ragdoll); } MyAvatar::~MyAvatar() { _physicsSimulation.setRagdoll(NULL); _physicsSimulation.setEntity(NULL); _lookAtTargetAvatar.clear(); } void MyAvatar::reset() { _skeletonModel.reset(); getHead()->reset(); setVelocity(glm::vec3(0.0f)); setThrust(glm::vec3(0.0f)); // Reset the pitch and roll components of the avatar's orientation, preserve yaw direction glm::vec3 eulers = safeEulerAngles(getOrientation()); eulers.x = 0.f; eulers.z = 0.f; setOrientation(glm::quat(eulers)); } void MyAvatar::update(float deltaTime) { if (_referential) { _referential->update(); } Head* head = getHead(); head->relaxLean(deltaTime); updateFromTrackers(deltaTime); if (Menu::getInstance()->isOptionChecked(MenuOption::MoveWithLean)) { // Faceshift drive is enabled, set the avatar drive based on the head position moveWithLean(); } // Get audio loudness data from audio input device Audio* audio = Application::getInstance()->getAudio(); head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) { setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition())); } simulate(deltaTime); } void MyAvatar::simulate(float deltaTime) { PerformanceTimer perfTimer("simulate"); // Play back recording if (_player && _player->isPlaying()) { _player->play(); } if (_scale != _targetScale) { float scale = (1.0f - SMOOTHING_RATIO) * _scale + SMOOTHING_RATIO * _targetScale; setScale(scale); Application::getInstance()->getCamera()->setScale(scale); } _skeletonModel.setShowTrueJointTransforms(! Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)); { PerformanceTimer perfTimer("transform"); updateOrientation(deltaTime); updatePosition(deltaTime); } { PerformanceTimer perfTimer("hand"); // update avatar skeleton and simulate hand and head getHand()->simulate(deltaTime, true); } { PerformanceTimer perfTimer("skeleton"); _skeletonModel.simulate(deltaTime); } { PerformanceTimer perfTimer("attachments"); simulateAttachments(deltaTime); } { PerformanceTimer perfTimer("joints"); // copy out the skeleton joints from the model _jointData.resize(_skeletonModel.getJointStateCount()); if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { for (int i = 0; i < _jointData.size(); i++) { JointData& data = _jointData[i]; data.valid = _skeletonModel.getVisibleJointState(i, data.rotation); } } else { for (int i = 0; i < _jointData.size(); i++) { JointData& data = _jointData[i]; data.valid = _skeletonModel.getJointState(i, data.rotation); } } } { PerformanceTimer perfTimer("head"); Head* head = getHead(); glm::vec3 headPosition; if (!_skeletonModel.getHeadPosition(headPosition)) { headPosition = _position; } head->setPosition(headPosition); head->setScale(_scale); head->simulate(deltaTime, true); } { PerformanceTimer perfTimer("hair"); if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { _hair.setAcceleration(getAcceleration() * getHead()->getFinalOrientationInWorldFrame()); _hair.setAngularVelocity((getAngularVelocity() + getHead()->getAngularVelocity()) * getHead()->getFinalOrientationInWorldFrame()); _hair.setAngularAcceleration(getAngularAcceleration() * getHead()->getFinalOrientationInWorldFrame()); _hair.setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition()) * getHead()->getFinalOrientationInWorldFrame()); _hair.simulate(deltaTime); } } { PerformanceTimer perfTimer("ragdoll"); Ragdoll* ragdoll = _skeletonModel.getRagdoll(); if (ragdoll && Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { const float minError = 0.00001f; const float maxIterations = 3; const quint64 maxUsec = 4000; _physicsSimulation.setTranslation(_position); _physicsSimulation.stepForward(deltaTime, minError, maxIterations, maxUsec); // harvest any displacement of the Ragdoll that is a result of collisions glm::vec3 ragdollDisplacement = ragdoll->getAndClearAccumulatedMovement(); const float MAX_RAGDOLL_DISPLACEMENT_2 = 1.0f; float length2 = glm::length2(ragdollDisplacement); if (length2 > EPSILON && length2 < MAX_RAGDOLL_DISPLACEMENT_2) { setPosition(getPosition() + ragdollDisplacement); } } else { _skeletonModel.moveShapesTowardJoints(1.0f); } } // now that we're done stepping the avatar forward in time, compute new collisions if (_collisionGroups != 0) { PerformanceTimer perfTimer("collisions"); Camera* myCamera = Application::getInstance()->getCamera(); float radius = getSkeletonHeight() * COLLISION_RADIUS_SCALE; if (myCamera->getMode() == CAMERA_MODE_FIRST_PERSON && !OculusManager::isConnected()) { radius = myCamera->getAspectRatio() * (myCamera->getNearClip() / cos(myCamera->getFieldOfView() / 2.0f)); radius *= COLLISION_RADIUS_SCALAR; } if (_collisionGroups & COLLISION_GROUP_ENVIRONMENT) { PerformanceTimer perfTimer("environment"); updateCollisionWithEnvironment(deltaTime, radius); } if (_collisionGroups & COLLISION_GROUP_VOXELS) { PerformanceTimer perfTimer("voxels"); updateCollisionWithVoxels(deltaTime, radius); } else { _trapDuration = 0.0f; } if (_collisionGroups & COLLISION_GROUP_AVATARS) { PerformanceTimer perfTimer("avatars"); updateCollisionWithAvatars(deltaTime); } } // Record avatars movements. if (_recorder && _recorder->isRecording()) { _recorder->record(); } // consider updating our billboard maybeUpdateBillboard(); } // Update avatar head rotation with sensor data void MyAvatar::updateFromTrackers(float deltaTime) { glm::vec3 estimatedPosition, estimatedRotation; if (isPlaying()) { estimatedRotation = glm::degrees(safeEulerAngles(_player->getHeadRotation())); } else if (Application::getInstance()->getPrioVR()->hasHeadRotation()) { estimatedRotation = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getHeadRotation())); estimatedRotation.x *= -1.0f; estimatedRotation.z *= -1.0f; } else if (OculusManager::isConnected()) { estimatedPosition = OculusManager::getRelativePosition(); estimatedPosition.x *= -1.0f; const float OCULUS_LEAN_SCALE = 0.05f; estimatedPosition /= OCULUS_LEAN_SCALE; } else { FaceTracker* tracker = Application::getInstance()->getActiveFaceTracker(); if (tracker) { estimatedPosition = tracker->getHeadTranslation(); estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation())); } } // Rotate the body if the head is turned beyond the screen if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) { const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f; const float TRACKER_MIN_YAW_TURN = 15.0f; const float TRACKER_MAX_YAW_TURN = 50.0f; if ( (fabs(estimatedRotation.y) > TRACKER_MIN_YAW_TURN) && (fabs(estimatedRotation.y) < TRACKER_MAX_YAW_TURN) ) { if (estimatedRotation.y > 0.0f) { _bodyYawDelta += (estimatedRotation.y - TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY; } else { _bodyYawDelta += (estimatedRotation.y + TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY; } } } // Set the rotation of the avatar's head (as seen by others, not affecting view frustum) // to be scaled such that when the user's physical head is pointing at edge of screen, the // avatar head is at the edge of the in-world view frustum. So while a real person may move // their head only 30 degrees or so, this may correspond to a 90 degree field of view. // Note that roll is magnified by a constant because it is not related to field of view. Head* head = getHead(); if (OculusManager::isConnected() || isPlaying()) { head->setDeltaPitch(estimatedRotation.x); head->setDeltaYaw(estimatedRotation.y); } else { float magnifyFieldOfView = Menu::getInstance()->getFieldOfView() / Menu::getInstance()->getRealWorldFieldOfView(); head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView); head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView); } head->setDeltaRoll(estimatedRotation.z); if (isPlaying()) { head->setLeanSideways(_player->getLeanSideways()); head->setLeanForward(_player->getLeanForward()); return; } // the priovr can give us exact lean if (Application::getInstance()->getPrioVR()->isActive()) { glm::vec3 eulers = glm::degrees(safeEulerAngles(Application::getInstance()->getPrioVR()->getTorsoRotation())); head->setLeanSideways(eulers.z); head->setLeanForward(eulers.x); return; } // Update torso lean distance based on accelerometer data const float TORSO_LENGTH = 0.5f; glm::vec3 relativePosition = estimatedPosition - glm::vec3(0.0f, -TORSO_LENGTH, 0.0f); const float MAX_LEAN = 45.0f; head->setLeanSideways(glm::clamp(glm::degrees(atanf(relativePosition.x * _leanScale / TORSO_LENGTH)), -MAX_LEAN, MAX_LEAN)); head->setLeanForward(glm::clamp(glm::degrees(atanf(relativePosition.z * _leanScale / TORSO_LENGTH)), -MAX_LEAN, MAX_LEAN)); } void MyAvatar::moveWithLean() { // Move with Lean by applying thrust proportional to leaning Head* head = getHead(); glm::quat orientation = head->getCameraOrientation(); glm::vec3 front = orientation * IDENTITY_FRONT; glm::vec3 right = orientation * IDENTITY_RIGHT; float leanForward = head->getLeanForward(); float leanSideways = head->getLeanSideways(); // Degrees of 'dead zone' when leaning, and amount of acceleration to apply to lean angle const float LEAN_FWD_DEAD_ZONE = 15.0f; const float LEAN_SIDEWAYS_DEAD_ZONE = 10.0f; const float LEAN_FWD_THRUST_SCALE = 4.0f; const float LEAN_SIDEWAYS_THRUST_SCALE = 3.0f; if (fabs(leanForward) > LEAN_FWD_DEAD_ZONE) { if (leanForward > 0.0f) { addThrust(front * -(leanForward - LEAN_FWD_DEAD_ZONE) * LEAN_FWD_THRUST_SCALE); } else { addThrust(front * -(leanForward + LEAN_FWD_DEAD_ZONE) * LEAN_FWD_THRUST_SCALE); } } if (fabs(leanSideways) > LEAN_SIDEWAYS_DEAD_ZONE) { if (leanSideways > 0.0f) { addThrust(right * -(leanSideways - LEAN_SIDEWAYS_DEAD_ZONE) * LEAN_SIDEWAYS_THRUST_SCALE); } else { addThrust(right * -(leanSideways + LEAN_SIDEWAYS_DEAD_ZONE) * LEAN_SIDEWAYS_THRUST_SCALE); } } } void MyAvatar::renderDebugBodyPoints() { glm::vec3 torsoPosition(getPosition()); glm::vec3 headPosition(getHead()->getEyePosition()); float torsoToHead = glm::length(headPosition - torsoPosition); glm::vec3 position; qDebug("head-above-torso %.2f, scale = %0.2f", torsoToHead, getScale()); // Torso Sphere position = torsoPosition; glPushMatrix(); glColor4f(0, 1, 0, .5f); glTranslatef(position.x, position.y, position.z); glutSolidSphere(0.2, 10, 10); glPopMatrix(); // Head Sphere position = headPosition; glPushMatrix(); glColor4f(0, 1, 0, .5f); glTranslatef(position.x, position.y, position.z); glutSolidSphere(0.15, 10, 10); glPopMatrix(); } // virtual void MyAvatar::render(const glm::vec3& cameraPosition, RenderMode renderMode) { // don't render if we've been asked to disable local rendering if (!_shouldRender) { return; // exit early } Avatar::render(cameraPosition, renderMode); // don't display IK constraints in shadow mode if (Menu::getInstance()->isOptionChecked(MenuOption::ShowIKConstraints) && renderMode != SHADOW_RENDER_MODE) { _skeletonModel.renderIKConstraints(); } } void MyAvatar::renderHeadMouse(int screenWidth, int screenHeight) const { Faceshift* faceshift = Application::getInstance()->getFaceshift(); float pixelsPerDegree = screenHeight / Menu::getInstance()->getFieldOfView(); // Display small target box at center or head mouse target that can also be used to measure LOD float headPitch = getHead()->getFinalPitch(); float headYaw = getHead()->getFinalYaw(); float aspectRatio = (float) screenWidth / (float) screenHeight; int headMouseX = (int)((float)screenWidth / 2.0f - headYaw * aspectRatio * pixelsPerDegree); int headMouseY = (int)((float)screenHeight / 2.0f - headPitch * pixelsPerDegree); glColor3f(1.0f, 1.0f, 1.0f); glDisable(GL_LINE_SMOOTH); const int PIXEL_BOX = 16; glBegin(GL_LINES); glVertex2f(headMouseX - PIXEL_BOX/2, headMouseY); glVertex2f(headMouseX + PIXEL_BOX/2, headMouseY); glVertex2f(headMouseX, headMouseY - PIXEL_BOX/2); glVertex2f(headMouseX, headMouseY + PIXEL_BOX/2); glEnd(); glEnable(GL_LINE_SMOOTH); // If Faceshift is active, show eye pitch and yaw as separate pointer if (faceshift->isActive()) { float avgEyePitch = faceshift->getEstimatedEyePitch(); float avgEyeYaw = faceshift->getEstimatedEyeYaw(); int eyeTargetX = (int)((float)(screenWidth) / 2.0f - avgEyeYaw * aspectRatio * pixelsPerDegree); int eyeTargetY = (int)((float)(screenHeight) / 2.0f - avgEyePitch * pixelsPerDegree); glColor3f(0.0f, 1.0f, 1.0f); glDisable(GL_LINE_SMOOTH); glBegin(GL_LINES); glVertex2f(eyeTargetX - PIXEL_BOX/2, eyeTargetY); glVertex2f(eyeTargetX + PIXEL_BOX/2, eyeTargetY); glVertex2f(eyeTargetX, eyeTargetY - PIXEL_BOX/2); glVertex2f(eyeTargetX, eyeTargetY + PIXEL_BOX/2); glEnd(); } } const glm::vec3 HAND_TO_PALM_OFFSET(0.0f, 0.12f, 0.08f); glm::vec3 MyAvatar::getLeftPalmPosition() { glm::vec3 leftHandPosition; getSkeletonModel().getLeftHandPosition(leftHandPosition); glm::quat leftRotation; getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getLeftHandJointIndex(), leftRotation); leftHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(leftRotation); return leftHandPosition; } glm::vec3 MyAvatar::getRightPalmPosition() { glm::vec3 rightHandPosition; getSkeletonModel().getRightHandPosition(rightHandPosition); glm::quat rightRotation; getSkeletonModel().getJointRotationInWorldFrame(getSkeletonModel().getRightHandJointIndex(), rightRotation); rightHandPosition += HAND_TO_PALM_OFFSET * glm::inverse(rightRotation); return rightHandPosition; } void MyAvatar::clearReferential() { changeReferential(NULL); } bool MyAvatar::setModelReferential(int id) { ModelTree* tree = Application::getInstance()->getModels()->getTree(); changeReferential(new ModelReferential(id, tree, this)); if (_referential->isValid()) { return true; } else { changeReferential(NULL); return false; } } bool MyAvatar::setJointReferential(int id, int jointIndex) { ModelTree* tree = Application::getInstance()->getModels()->getTree(); changeReferential(new JointReferential(jointIndex, id, tree, this)); if (!_referential->isValid()) { return true; } else { changeReferential(NULL); return false; } } bool MyAvatar::isRecording() { if (!_recorder) { return false; } if (QThread::currentThread() != thread()) { bool result; QMetaObject::invokeMethod(this, "isRecording", Qt::BlockingQueuedConnection, Q_RETURN_ARG(bool, result)); return result; } return _recorder && _recorder->isRecording(); } qint64 MyAvatar::recorderElapsed() { if (!_recorder) { return 0; } if (QThread::currentThread() != thread()) { qint64 result; QMetaObject::invokeMethod(this, "recorderElapsed", Qt::BlockingQueuedConnection, Q_RETURN_ARG(qint64, result)); return result; } return _recorder->elapsed(); } void MyAvatar::startRecording() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startRecording", Qt::BlockingQueuedConnection); return; } if (!_recorder) { _recorder = RecorderPointer(new Recorder(this)); } Application::getInstance()->getAudio()->setRecorder(_recorder); _recorder->startRecording(); } void MyAvatar::stopRecording() { if (!_recorder) { return; } if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "stopRecording", Qt::BlockingQueuedConnection); return; } if (_recorder) { _recorder->stopRecording(); } } void MyAvatar::saveRecording(QString filename) { if (!_recorder) { qDebug() << "There is no recording to save"; return; } if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "saveRecording", Qt::BlockingQueuedConnection, Q_ARG(QString, filename)); return; } if (_recorder) { _recorder->saveToFile(filename); } } void MyAvatar::loadLastRecording() { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "loadLastRecording", Qt::BlockingQueuedConnection); return; } if (!_recorder) { qDebug() << "There is no recording to load"; return; } if (!_player) { _player = PlayerPointer(new Player(this)); } _player->loadRecording(_recorder->getRecording()); } void MyAvatar::setLocalGravity(glm::vec3 gravity) { _motionBehaviors |= AVATAR_MOTION_OBEY_LOCAL_GRAVITY; // Environmental and Local gravities are incompatible. Since Local is being set here // the environmental setting must be removed. _motionBehaviors &= ~AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; setGravity(gravity); } void MyAvatar::setGravity(const glm::vec3& gravity) { _gravity = gravity; // use the gravity to determine the new world up direction, if possible float gravityLength = glm::length(gravity); if (gravityLength > EPSILON) { _worldUpDirection = _gravity / -gravityLength; } // NOTE: the else case here it to leave _worldUpDirection unchanged // so it continues to point opposite to the previous gravity setting. } AnimationHandlePointer MyAvatar::addAnimationHandle() { AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); _animationHandles.append(handle); return handle; } void MyAvatar::removeAnimationHandle(const AnimationHandlePointer& handle) { handle->stop(); _animationHandles.removeOne(handle); } void MyAvatar::startAnimation(const QString& url, float fps, float priority, bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startAnimation", Q_ARG(const QString&, url), Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints)); return; } AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); handle->setURL(url); handle->setFPS(fps); handle->setPriority(priority); handle->setLoop(loop); handle->setHold(hold); handle->setFirstFrame(firstFrame); handle->setLastFrame(lastFrame); handle->setMaskedJoints(maskedJoints); handle->start(); } void MyAvatar::startAnimationByRole(const QString& role, const QString& url, float fps, float priority, bool loop, bool hold, float firstFrame, float lastFrame, const QStringList& maskedJoints) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "startAnimationByRole", Q_ARG(const QString&, role), Q_ARG(const QString&, url), Q_ARG(float, fps), Q_ARG(float, priority), Q_ARG(bool, loop), Q_ARG(bool, hold), Q_ARG(float, firstFrame), Q_ARG(float, lastFrame), Q_ARG(const QStringList&, maskedJoints)); return; } // check for a configured animation for the role foreach (const AnimationHandlePointer& handle, _animationHandles) { if (handle->getRole() == role) { handle->start(); return; } } // no joy; use the parameters provided AnimationHandlePointer handle = _skeletonModel.createAnimationHandle(); handle->setRole(role); handle->setURL(url); handle->setFPS(fps); handle->setPriority(priority); handle->setLoop(loop); handle->setHold(hold); handle->setFirstFrame(firstFrame); handle->setLastFrame(lastFrame); handle->setMaskedJoints(maskedJoints); handle->start(); } void MyAvatar::stopAnimationByRole(const QString& role) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "stopAnimationByRole", Q_ARG(const QString&, role)); return; } foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) { if (handle->getRole() == role) { handle->stop(); } } } void MyAvatar::stopAnimation(const QString& url) { if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "stopAnimation", Q_ARG(const QString&, url)); return; } foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) { if (handle->getURL() == url) { handle->stop(); } } } AnimationDetails MyAvatar::getAnimationDetailsByRole(const QString& role) { AnimationDetails result; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "getAnimationDetailsByRole", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AnimationDetails, result), Q_ARG(const QString&, role)); return result; } foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) { if (handle->getRole() == role) { result = handle->getAnimationDetails(); break; } } return result; } AnimationDetails MyAvatar::getAnimationDetails(const QString& url) { AnimationDetails result; if (QThread::currentThread() != thread()) { QMetaObject::invokeMethod(this, "getAnimationDetails", Qt::BlockingQueuedConnection, Q_RETURN_ARG(AnimationDetails, result), Q_ARG(const QString&, url)); return result; } foreach (const AnimationHandlePointer& handle, _skeletonModel.getRunningAnimations()) { if (handle->getURL() == url) { result = handle->getAnimationDetails(); break; } } return result; } void MyAvatar::saveData(QSettings* settings) { settings->beginGroup("Avatar"); settings->setValue("bodyYaw", _bodyYaw); settings->setValue("bodyPitch", _bodyPitch); settings->setValue("bodyRoll", _bodyRoll); settings->setValue("headPitch", getHead()->getBasePitch()); settings->setValue("position_x", _position.x); settings->setValue("position_y", _position.y); settings->setValue("position_z", _position.z); settings->setValue("pupilDilation", getHead()->getPupilDilation()); settings->setValue("leanScale", _leanScale); settings->setValue("scale", _targetScale); settings->setValue("faceModelURL", _faceModelURL); settings->setValue("skeletonModelURL", _skeletonModelURL); settings->beginWriteArray("attachmentData"); for (int i = 0; i < _attachmentData.size(); i++) { settings->setArrayIndex(i); const AttachmentData& attachment = _attachmentData.at(i); settings->setValue("modelURL", attachment.modelURL); settings->setValue("jointName", attachment.jointName); settings->setValue("translation_x", attachment.translation.x); settings->setValue("translation_y", attachment.translation.y); settings->setValue("translation_z", attachment.translation.z); glm::vec3 eulers = safeEulerAngles(attachment.rotation); settings->setValue("rotation_x", eulers.x); settings->setValue("rotation_y", eulers.y); settings->setValue("rotation_z", eulers.z); settings->setValue("scale", attachment.scale); } settings->endArray(); settings->beginWriteArray("animationHandles"); for (int i = 0; i < _animationHandles.size(); i++) { settings->setArrayIndex(i); const AnimationHandlePointer& pointer = _animationHandles.at(i); settings->setValue("role", pointer->getRole()); settings->setValue("url", pointer->getURL()); settings->setValue("fps", pointer->getFPS()); settings->setValue("priority", pointer->getPriority()); settings->setValue("loop", pointer->getLoop()); settings->setValue("hold", pointer->getHold()); settings->setValue("startAutomatically", pointer->getStartAutomatically()); settings->setValue("firstFrame", pointer->getFirstFrame()); settings->setValue("lastFrame", pointer->getLastFrame()); settings->setValue("maskedJoints", pointer->getMaskedJoints()); } settings->endArray(); settings->setValue("displayName", _displayName); settings->endGroup(); } void MyAvatar::loadData(QSettings* settings) { settings->beginGroup("Avatar"); // in case settings is corrupt or missing loadSetting() will check for NaN _bodyYaw = loadSetting(settings, "bodyYaw", 0.0f); _bodyPitch = loadSetting(settings, "bodyPitch", 0.0f); _bodyRoll = loadSetting(settings, "bodyRoll", 0.0f); getHead()->setBasePitch(loadSetting(settings, "headPitch", 0.0f)); _position.x = loadSetting(settings, "position_x", START_LOCATION.x); _position.y = loadSetting(settings, "position_y", START_LOCATION.y); _position.z = loadSetting(settings, "position_z", START_LOCATION.z); getHead()->setPupilDilation(loadSetting(settings, "pupilDilation", 0.0f)); _leanScale = loadSetting(settings, "leanScale", 0.05f); _targetScale = loadSetting(settings, "scale", 1.0f); setScale(_scale); Application::getInstance()->getCamera()->setScale(_scale); setFaceModelURL(settings->value("faceModelURL", DEFAULT_HEAD_MODEL_URL).toUrl()); setSkeletonModelURL(settings->value("skeletonModelURL").toUrl()); QVector attachmentData; int attachmentCount = settings->beginReadArray("attachmentData"); for (int i = 0; i < attachmentCount; i++) { settings->setArrayIndex(i); AttachmentData attachment; attachment.modelURL = settings->value("modelURL").toUrl(); attachment.jointName = settings->value("jointName").toString(); attachment.translation.x = loadSetting(settings, "translation_x", 0.0f); attachment.translation.y = loadSetting(settings, "translation_y", 0.0f); attachment.translation.z = loadSetting(settings, "translation_z", 0.0f); glm::vec3 eulers; eulers.x = loadSetting(settings, "rotation_x", 0.0f); eulers.y = loadSetting(settings, "rotation_y", 0.0f); eulers.z = loadSetting(settings, "rotation_z", 0.0f); attachment.rotation = glm::quat(eulers); attachment.scale = loadSetting(settings, "scale", 1.0f); attachmentData.append(attachment); } settings->endArray(); setAttachmentData(attachmentData); int animationCount = settings->beginReadArray("animationHandles"); while (_animationHandles.size() > animationCount) { _animationHandles.takeLast()->stop(); } while (_animationHandles.size() < animationCount) { addAnimationHandle(); } for (int i = 0; i < animationCount; i++) { settings->setArrayIndex(i); const AnimationHandlePointer& handle = _animationHandles.at(i); handle->setRole(settings->value("role", "idle").toString()); handle->setURL(settings->value("url").toUrl()); handle->setFPS(loadSetting(settings, "fps", 30.0f)); handle->setPriority(loadSetting(settings, "priority", 1.0f)); handle->setLoop(settings->value("loop", true).toBool()); handle->setHold(settings->value("hold", false).toBool()); handle->setStartAutomatically(settings->value("startAutomatically", true).toBool()); handle->setFirstFrame(settings->value("firstFrame", 0.0f).toFloat()); handle->setLastFrame(settings->value("lastFrame", INT_MAX).toFloat()); handle->setMaskedJoints(settings->value("maskedJoints").toStringList()); } settings->endArray(); setDisplayName(settings->value("displayName").toString()); settings->endGroup(); } void MyAvatar::saveAttachmentData(const AttachmentData& attachment) const { QSettings* settings = Application::getInstance()->lockSettings(); settings->beginGroup("savedAttachmentData"); settings->beginGroup(_skeletonModel.getURL().toString()); settings->beginGroup(attachment.modelURL.toString()); settings->setValue("jointName", attachment.jointName); settings->beginGroup(attachment.jointName); settings->setValue("translation_x", attachment.translation.x); settings->setValue("translation_y", attachment.translation.y); settings->setValue("translation_z", attachment.translation.z); glm::vec3 eulers = safeEulerAngles(attachment.rotation); settings->setValue("rotation_x", eulers.x); settings->setValue("rotation_y", eulers.y); settings->setValue("rotation_z", eulers.z); settings->setValue("scale", attachment.scale); settings->endGroup(); settings->endGroup(); settings->endGroup(); settings->endGroup(); Application::getInstance()->unlockSettings(); } AttachmentData MyAvatar::loadAttachmentData(const QUrl& modelURL, const QString& jointName) const { QSettings* settings = Application::getInstance()->lockSettings(); settings->beginGroup("savedAttachmentData"); settings->beginGroup(_skeletonModel.getURL().toString()); settings->beginGroup(modelURL.toString()); AttachmentData attachment; attachment.modelURL = modelURL; if (jointName.isEmpty()) { attachment.jointName = settings->value("jointName").toString(); } else { attachment.jointName = jointName; } settings->beginGroup(attachment.jointName); if (settings->contains("translation_x")) { attachment.translation.x = loadSetting(settings, "translation_x", 0.0f); attachment.translation.y = loadSetting(settings, "translation_y", 0.0f); attachment.translation.z = loadSetting(settings, "translation_z", 0.0f); glm::vec3 eulers; eulers.x = loadSetting(settings, "rotation_x", 0.0f); eulers.y = loadSetting(settings, "rotation_y", 0.0f); eulers.z = loadSetting(settings, "rotation_z", 0.0f); attachment.rotation = glm::quat(eulers); attachment.scale = loadSetting(settings, "scale", 1.0f); } else { attachment = AttachmentData(); } settings->endGroup(); settings->endGroup(); settings->endGroup(); settings->endGroup(); Application::getInstance()->unlockSettings(); return attachment; } int MyAvatar::parseDataAtOffset(const QByteArray& packet, int offset) { qDebug() << "Error: ignoring update packet for MyAvatar" << " packetLength = " << packet.size() << " offset = " << offset; // this packet is just bad, so we pretend that we unpacked it ALL return packet.size() - offset; } void MyAvatar::sendKillAvatar() { QByteArray killPacket = byteArrayWithPopulatedHeader(PacketTypeKillAvatar); NodeList::getInstance()->broadcastToNodes(killPacket, NodeSet() << NodeType::AvatarMixer); } void MyAvatar::updateLookAtTargetAvatar() { // // Look at the avatar whose eyes are closest to the ray in direction of my avatar's head // _lookAtTargetAvatar.clear(); _targetAvatarPosition = glm::vec3(0.0f); const float MIN_LOOKAT_ANGLE = PI / 4.0f; // Smallest angle between face and person where we will look at someone float smallestAngleTo = MIN_LOOKAT_ANGLE; foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) { Avatar* avatar = static_cast(avatarPointer.data()); avatar->setIsLookAtTarget(false); if (!avatar->isMyAvatar()) { glm::vec3 DEFAULT_GAZE_IN_HEAD_FRAME = glm::vec3(0.0f, 0.0f, -1.0f); float angleTo = glm::angle(getHead()->getFinalOrientationInWorldFrame() * DEFAULT_GAZE_IN_HEAD_FRAME, glm::normalize(avatar->getHead()->getEyePosition() - getHead()->getEyePosition())); if (angleTo < smallestAngleTo) { _lookAtTargetAvatar = avatarPointer; _targetAvatarPosition = avatarPointer->getPosition(); smallestAngleTo = angleTo; } } } if (_lookAtTargetAvatar) { static_cast(_lookAtTargetAvatar.data())->setIsLookAtTarget(true); } } void MyAvatar::clearLookAtTargetAvatar() { _lookAtTargetAvatar.clear(); } glm::vec3 MyAvatar::getUprightHeadPosition() const { return _position + getWorldAlignedOrientation() * glm::vec3(0.0f, getPelvisToHeadLength(), 0.0f); } const float SCRIPT_PRIORITY = DEFAULT_PRIORITY + 1.0f; const float RECORDER_PRIORITY = SCRIPT_PRIORITY + 1.0f; void MyAvatar::setJointRotations(QVector jointRotations) { int numStates = glm::min(_skeletonModel.getJointStateCount(), jointRotations.size()); for (int i = 0; i < numStates; ++i) { // HACK: ATM only Recorder calls setJointRotations() so we hardcode its priority here _skeletonModel.setJointState(i, true, jointRotations[i], RECORDER_PRIORITY); } } void MyAvatar::setJointData(int index, const glm::quat& rotation) { if (QThread::currentThread() == thread()) { // HACK: ATM only JS scripts call setJointData() on MyAvatar so we hardcode the priority _skeletonModel.setJointState(index, true, rotation, SCRIPT_PRIORITY); } } void MyAvatar::clearJointData(int index) { if (QThread::currentThread() == thread()) { // HACK: ATM only JS scripts call clearJointData() on MyAvatar so we hardcode the priority _skeletonModel.setJointState(index, false, glm::quat(), 0.0f); } } void MyAvatar::clearJointsData() { clearJointAnimationPriorities(); } void MyAvatar::clearJointAnimationPriorities() { int numStates = _skeletonModel.getJointStateCount(); for (int i = 0; i < numStates; ++i) { _skeletonModel.clearJointAnimationPriority(i); } } void MyAvatar::setFaceModelURL(const QUrl& faceModelURL) { Avatar::setFaceModelURL(faceModelURL); _billboardValid = false; } void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) { Avatar::setSkeletonModelURL(skeletonModelURL); _billboardValid = false; } void MyAvatar::setAttachmentData(const QVector& attachmentData) { Avatar::setAttachmentData(attachmentData); if (QThread::currentThread() != thread()) { return; } _billboardValid = false; } 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()) { Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved); return; } if (useSaved) { AttachmentData attachment = loadAttachmentData(modelURL, jointName); if (attachment.isValid()) { Avatar::attach(modelURL, attachment.jointName, attachment.translation, attachment.rotation, attachment.scale, allowDuplicates, useSaved); return; } } Avatar::attach(modelURL, jointName, translation, rotation, scale, allowDuplicates, useSaved); } void MyAvatar::renderBody(RenderMode renderMode, float glowLevel) { if (!(_skeletonModel.isRenderable() && getHead()->getFaceModel().isRenderable())) { return; // wait until both models are loaded } // Render the body's voxels and head Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; _skeletonModel.render(1.0f, modelRenderMode, Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows)); renderAttachments(renderMode); // Render head so long as the camera isn't inside it const Camera *camera = Application::getInstance()->getCamera(); const glm::vec3 cameraPos = camera->getPosition() + (camera->getRotation() * glm::vec3(0.0f, 0.0f, 1.0f)) * camera->getDistance(); if (shouldRenderHead(cameraPos, renderMode)) { getHead()->render(1.0f, modelRenderMode); if (Menu::getInstance()->isOptionChecked(MenuOption::StringHair)) { // Render Hair glPushMatrix(); glm::vec3 headPosition = getHead()->getPosition(); glTranslatef(headPosition.x, headPosition.y, headPosition.z); const glm::quat& rotation = getHead()->getFinalOrientationInWorldFrame(); glm::vec3 axis = glm::axis(rotation); glRotatef(glm::degrees(glm::angle(rotation)), axis.x, axis.y, axis.z); _hair.render(); glPopMatrix(); } } getHand()->render(true, modelRenderMode); } const float RENDER_HEAD_CUTOFF_DISTANCE = 0.50f; bool MyAvatar::shouldRenderHead(const glm::vec3& cameraPosition, RenderMode renderMode) const { const Head* head = getHead(); return (renderMode != NORMAL_RENDER_MODE) || (glm::length(cameraPosition - head->getEyePosition()) > RENDER_HEAD_CUTOFF_DISTANCE * _scale); } float MyAvatar::computeDistanceToFloor(const glm::vec3& startPoint) { glm::vec3 direction = -_worldUpDirection; OctreeElement* elementHit; // output from findRayIntersection float distance = FLT_MAX; // output from findRayIntersection BoxFace face; // output from findRayIntersection Application::getInstance()->getVoxelTree()->findRayIntersection(startPoint, direction, elementHit, distance, face); return distance; } void MyAvatar::updateOrientation(float deltaTime) { // Gather rotation information from keyboard _bodyYawDelta -= _driveKeys[ROT_RIGHT] * YAW_SPEED * deltaTime; _bodyYawDelta += _driveKeys[ROT_LEFT] * YAW_SPEED * deltaTime; getHead()->setBasePitch(getHead()->getBasePitch() + (_driveKeys[ROT_UP] - _driveKeys[ROT_DOWN]) * PITCH_SPEED * deltaTime); // update body yaw by body yaw delta glm::quat orientation = getOrientation() * glm::quat(glm::radians( glm::vec3(_bodyPitchDelta, _bodyYawDelta, _bodyRollDelta) * deltaTime)); // decay body rotation momentum const float BODY_SPIN_FRICTION = 7.5f; float bodySpinMomentum = 1.0f - BODY_SPIN_FRICTION * deltaTime; if (bodySpinMomentum < 0.0f) { bodySpinMomentum = 0.0f; } _bodyPitchDelta *= bodySpinMomentum; _bodyYawDelta *= bodySpinMomentum; _bodyRollDelta *= bodySpinMomentum; float MINIMUM_ROTATION_RATE = 2.0f; if (fabs(_bodyYawDelta) < MINIMUM_ROTATION_RATE) { _bodyYawDelta = 0.0f; } if (fabs(_bodyRollDelta) < MINIMUM_ROTATION_RATE) { _bodyRollDelta = 0.0f; } if (fabs(_bodyPitchDelta) < MINIMUM_ROTATION_RATE) { _bodyPitchDelta = 0.0f; } if (OculusManager::isConnected()) { // these angles will be in radians float yaw, pitch, roll; OculusManager::getEulerAngles(yaw, pitch, roll); // ... so they need to be converted to degrees before we do math... yaw *= DEGREES_PER_RADIAN; pitch *= DEGREES_PER_RADIAN; roll *= DEGREES_PER_RADIAN; // Record the angular velocity Head* head = getHead(); glm::vec3 angularVelocity(yaw - head->getBaseYaw(), pitch - head->getBasePitch(), roll - head->getBaseRoll()); head->setAngularVelocity(angularVelocity); //Invert yaw and roll when in mirror mode if (Application::getInstance()->getCamera()->getMode() == CAMERA_MODE_MIRROR) { head->setBaseYaw(-yaw); head->setBasePitch(pitch); head->setBaseRoll(-roll); } else { head->setBaseYaw(yaw); head->setBasePitch(pitch); head->setBaseRoll(roll); } } // update the euler angles setOrientation(orientation); } const float NEARBY_FLOOR_THRESHOLD = 5.0f; void MyAvatar::updatePosition(float deltaTime) { float keyboardInput = fabsf(_driveKeys[FWD] - _driveKeys[BACK]) + fabsf(_driveKeys[RIGHT] - _driveKeys[LEFT]) + fabsf(_driveKeys[UP] - _driveKeys[DOWN]); bool walkingOnFloor = false; float gravityLength = glm::length(_gravity) * GRAVITY_EARTH; const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); glm::vec3 startCap; boundingShape.getStartPoint(startCap); glm::vec3 bottom = startCap - boundingShape.getRadius() * _worldUpDirection; if (gravityLength > EPSILON) { float speedFromGravity = _scale * deltaTime * gravityLength; float distanceToFall = glm::distance(bottom, _lastFloorContactPoint); walkingOnFloor = (distanceToFall < 2.0f * deltaTime * speedFromGravity); if (walkingOnFloor) { // BEGIN HACK: to prevent the avatar from bouncing on a floor surface if (distanceToFall < deltaTime * speedFromGravity) { float verticalSpeed = glm::dot(_velocity, _worldUpDirection); if (fabs(verticalSpeed) < speedFromGravity) { // we're standing on a floor, and nearly at rest so we zero the vertical velocity component _velocity -= verticalSpeed * _worldUpDirection; } } else { // fall with gravity against floor _velocity -= speedFromGravity * _worldUpDirection; } // END HACK } else { if (!_isBraking) { // fall with gravity toward floor _velocity -= speedFromGravity * _worldUpDirection; } if (_motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { const float MAX_VERTICAL_FLOOR_DETECTION_SPEED = _scale * MAX_WALKING_SPEED; if (keyboardInput && glm::dot(_motorVelocity, _worldUpDirection) > 0.0f && glm::dot(_velocity, _worldUpDirection) > MAX_VERTICAL_FLOOR_DETECTION_SPEED) { // disable local gravity when flying up setLocalGravity(glm::vec3(0.0f)); } else { const float maxFloorDistance = _scale * NEARBY_FLOOR_THRESHOLD; if (computeDistanceToFloor(bottom) > maxFloorDistance) { // disable local gravity when floor is too far setLocalGravity(glm::vec3(0.0f)); } } } } } else { if ((_collisionGroups & COLLISION_GROUP_VOXELS) && _motionBehaviors & AVATAR_MOTION_STAND_ON_NEARBY_FLOORS) { const float MIN_FLOOR_DETECTION_SPEED = _scale * 1.0f; if (glm::length(_velocity) < MIN_FLOOR_DETECTION_SPEED ) { // scan for floor under avatar const float maxFloorDistance = _scale * NEARBY_FLOOR_THRESHOLD; if (computeDistanceToFloor(bottom) < maxFloorDistance) { // enable local gravity setLocalGravity(-_worldUpDirection); } } } } if (keyboardInput > 0.0f || glm::length2(_velocity) > 0.0f || glm::length2(_thrust) > 0.0f || ! walkingOnFloor) { // update motor and thrust updateMotorFromKeyboard(deltaTime, walkingOnFloor); applyMotor(deltaTime); applyThrust(deltaTime); // update position if (glm::length2(_velocity) < EPSILON) { _velocity = glm::vec3(0.0f); } else { _position += _velocity * deltaTime; } updateAcceleration(deltaTime); } // update moving flag based on speed const float MOVING_SPEED_THRESHOLD = 0.01f; _moving = glm::length(_velocity) > MOVING_SPEED_THRESHOLD; updateChatCircle(deltaTime); } void MyAvatar::updateMotorFromKeyboard(float deltaTime, bool walking) { // Increase motor velocity until its length is equal to _maxMotorSpeed. if (!(_motionBehaviors & AVATAR_MOTION_MOTOR_KEYBOARD_ENABLED)) { // nothing to do return; } glm::vec3 localVelocity = _velocity; if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { glm::quat orientation = getHead()->getCameraOrientation(); localVelocity = glm::inverse(orientation) * _velocity; } // 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; // the finalMotorSpeed depends on whether we are walking or not float finalMaxMotorSpeed = walking ? _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; } else { float MOTOR_LENGTH_TIMESCALE = 1.5f; float tau = glm::clamp(deltaTime / MOTOR_LENGTH_TIMESCALE, 0.0f, 1.0f); float INCREASE_FACTOR = 2.0f; //_motorVelocity *= 1.0f + tau * INCREASE_FACTOR; motorLength *= 1.0f + tau * INCREASE_FACTOR; if (motorLength > finalMaxMotorSpeed) { motorLength = finalMaxMotorSpeed; } _motorVelocity = motorLength * direction; } _isPushing = true; } else { // motor opposes motion (wants to be at rest) _motorVelocity = - localVelocity; } } float MyAvatar::computeMotorTimescale() { // 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.5f; float MIN_BRAKE_SPEED = 0.4f; 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::applyMotor(float deltaTime) { // TODO: recover extra braking behavior when flying close to nearest avatar if (!( _motionBehaviors & AVATAR_MOTION_MOTOR_ENABLED)) { // nothing to do --> early exit return; } glm::vec3 targetVelocity = _motorVelocity; if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { // rotate _motorVelocity into world frame glm::quat rotation = getHead()->getCameraOrientation(); targetVelocity = rotation * _motorVelocity; } glm::vec3 targetDirection(0.0f); if (glm::length2(targetVelocity) > EPSILON) { targetDirection = glm::normalize(targetVelocity); } glm::vec3 deltaVelocity = targetVelocity - _velocity; if (_motionBehaviors & AVATAR_MOTION_MOTOR_COLLISION_SURFACE_ONLY && glm::length2(_gravity) > EPSILON) { // For now we subtract the component parallel to gravity but what we need to do is: // TODO: subtract the component perp to the local surface normal (motor only pushes in surface plane). glm::vec3 gravityDirection = glm::normalize(_gravity); glm::vec3 parallelDelta = glm::dot(deltaVelocity, gravityDirection) * gravityDirection; if (glm::dot(targetVelocity, _velocity) > 0.0f) { // remove parallel part from deltaVelocity deltaVelocity -= parallelDelta; } } // simple critical damping float timescale = computeMotorTimescale(); float tau = glm::clamp(deltaTime / timescale, 0.0f, 1.0f); _velocity += tau * deltaVelocity; } void MyAvatar::applyThrust(float deltaTime) { _velocity += _thrust * deltaTime; float speed = glm::length(_velocity); // cap the speed that thrust can achieve if (speed > MAX_AVATAR_SPEED) { _velocity *= MAX_AVATAR_SPEED / speed; } // zero thrust so we don't pile up thrust from other sources _thrust = glm::vec3(0.0f); } /* Keep this code for the short term as reference in case we need to further tune the new model * to achieve legacy movement response. void MyAvatar::updateThrust(float deltaTime) { // // Gather thrust information from keyboard and sensors to apply to avatar motion // glm::quat orientation = getHead()->getCameraOrientation(); glm::vec3 front = orientation * IDENTITY_FRONT; glm::vec3 right = orientation * IDENTITY_RIGHT; glm::vec3 up = orientation * IDENTITY_UP; const float THRUST_MAG_UP = 800.0f; const float THRUST_MAG_DOWN = 300.0f; const float THRUST_MAG_FWD = 500.0f; const float THRUST_MAG_BACK = 300.0f; const float THRUST_MAG_LATERAL = 250.0f; const float THRUST_JUMP = 120.0f; // Add Thrusts from keyboard _thrust += _driveKeys[FWD] * _scale * THRUST_MAG_FWD * _thrustMultiplier * deltaTime * front; _thrust -= _driveKeys[BACK] * _scale * THRUST_MAG_BACK * _thrustMultiplier * deltaTime * front; _thrust += _driveKeys[RIGHT] * _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right; _thrust -= _driveKeys[LEFT] * _scale * THRUST_MAG_LATERAL * _thrustMultiplier * deltaTime * right; _thrust += _driveKeys[UP] * _scale * THRUST_MAG_UP * _thrustMultiplier * deltaTime * up; _thrust -= _driveKeys[DOWN] * _scale * THRUST_MAG_DOWN * _thrustMultiplier * deltaTime * up; // attenuate thrust when in penetration if (glm::dot(_thrust, _lastBodyPenetration) > EPSILON) { const float MAX_BODY_PENETRATION_DEPTH = 0.6f * _skeletonModel.getBoundingShapeRadius(); float penetrationFactor = glm::min(1.0f, glm::length(_lastBodyPenetration) / MAX_BODY_PENETRATION_DEPTH); glm::vec3 penetrationDirection = glm::normalize(_lastBodyPenetration); // attenuate parallel component glm::vec3 parallelThrust = glm::dot(_thrust, penetrationDirection) * penetrationDirection; // attenuate perpendicular component (friction) glm::vec3 perpendicularThrust = _thrust - parallelThrust; // recombine to get the final thrust _thrust = (1.0f - penetrationFactor) * parallelThrust + (1.0f - penetrationFactor * penetrationFactor) * perpendicularThrust; // attenuate the growth of _thrustMultiplier when in penetration // otherwise the avatar will eventually be able to tunnel through the obstacle _thrustMultiplier *= (1.0f - penetrationFactor * penetrationFactor); } else if (_thrustMultiplier < 1.0f) { // rapid healing of attenuated thrustMultiplier after penetration event _thrustMultiplier = 1.0f; } _lastBodyPenetration = glm::vec3(0.0f); // If thrust keys are being held down, slowly increase thrust to allow reaching great speeds if (_driveKeys[FWD] || _driveKeys[BACK] || _driveKeys[RIGHT] || _driveKeys[LEFT] || _driveKeys[UP] || _driveKeys[DOWN]) { const float THRUST_INCREASE_RATE = 1.05f; const float MAX_THRUST_MULTIPLIER = 75.0f; _thrustMultiplier *= 1.0f + deltaTime * THRUST_INCREASE_RATE; if (_thrustMultiplier > MAX_THRUST_MULTIPLIER) { _thrustMultiplier = MAX_THRUST_MULTIPLIER; } } else { _thrustMultiplier = 1.0f; } // Add one time jumping force if requested if (_shouldJump) { if (glm::length(_gravity) > EPSILON) { _thrust += _scale * THRUST_JUMP * up; } _shouldJump = false; } // Update speed brake status const float MIN_SPEED_BRAKE_VELOCITY = _scale * 0.4f; if ((glm::length(_thrust) == 0.0f) && _isThrustOn && (glm::length(_velocity) > MIN_SPEED_BRAKE_VELOCITY)) { _speedBrakes = true; } _isThrustOn = (glm::length(_thrust) > EPSILON); if (_isThrustOn || (_speedBrakes && (glm::length(_velocity) < MIN_SPEED_BRAKE_VELOCITY))) { _speedBrakes = false; } _velocity += _thrust * deltaTime; // Zero thrust out now that we've added it to velocity in this frame _thrust = glm::vec3(0.0f); // apply linear damping const float MAX_STATIC_FRICTION_SPEED = 0.5f; const float STATIC_FRICTION_STRENGTH = _scale * 20.0f; applyStaticFriction(deltaTime, _velocity, MAX_STATIC_FRICTION_SPEED, STATIC_FRICTION_STRENGTH); const float LINEAR_DAMPING_STRENGTH = 0.5f; const float SPEED_BRAKE_POWER = _scale * 10.0f; const float SQUARED_DAMPING_STRENGTH = 0.007f; const float SLOW_NEAR_RADIUS = 5.0f; float linearDamping = LINEAR_DAMPING_STRENGTH; const float NEAR_AVATAR_DAMPING_FACTOR = 50.0f; if (_distanceToNearestAvatar < _scale * SLOW_NEAR_RADIUS) { linearDamping *= 1.0f + NEAR_AVATAR_DAMPING_FACTOR * ((SLOW_NEAR_RADIUS - _distanceToNearestAvatar) / SLOW_NEAR_RADIUS); } if (_speedBrakes) { applyDamping(deltaTime, _velocity, linearDamping * SPEED_BRAKE_POWER, SQUARED_DAMPING_STRENGTH * SPEED_BRAKE_POWER); } else { applyDamping(deltaTime, _velocity, linearDamping, SQUARED_DAMPING_STRENGTH); } } */ void MyAvatar::updateCollisionWithEnvironment(float deltaTime, float radius) { glm::vec3 up = getBodyUpDirection(); const float ENVIRONMENT_SURFACE_ELASTICITY = 0.0f; const float ENVIRONMENT_SURFACE_DAMPING = 0.01f; const float ENVIRONMENT_COLLISION_FREQUENCY = 0.05f; glm::vec3 penetration; float pelvisFloatingHeight = getPelvisFloatingHeight(); if (Application::getInstance()->getEnvironment()->findCapsulePenetration( _position - up * (pelvisFloatingHeight - radius), _position + up * (getSkeletonHeight() - pelvisFloatingHeight + radius), radius, penetration)) { updateCollisionSound(penetration, deltaTime, ENVIRONMENT_COLLISION_FREQUENCY); applyHardCollision(penetration, ENVIRONMENT_SURFACE_ELASTICITY, ENVIRONMENT_SURFACE_DAMPING); } } static CollisionList myCollisions(64); void MyAvatar::updateCollisionWithVoxels(float deltaTime, float radius) { const float MAX_VOXEL_COLLISION_SPEED = 100.0f; float speed = glm::length(_velocity); if (speed > MAX_VOXEL_COLLISION_SPEED) { // don't even bother to try to collide against voxles when moving very fast _trapDuration = 0.0f; return; } bool isTrapped = false; myCollisions.clear(); const CapsuleShape& boundingShape = _skeletonModel.getBoundingShape(); if (Application::getInstance()->getVoxelTree()->findShapeCollisions(&boundingShape, myCollisions, Octree::TryLock)) { const float VOXEL_ELASTICITY = 0.0f; const float VOXEL_DAMPING = 0.0f; float capsuleRadius = boundingShape.getRadius(); float capsuleHalfHeight = boundingShape.getHalfHeight(); const float MAX_STEP_HEIGHT = capsuleRadius + capsuleHalfHeight; const float MIN_STEP_HEIGHT = 0.0f; glm::vec3 footBase = boundingShape.getTranslation() - (capsuleRadius + capsuleHalfHeight) * _worldUpDirection; float highestStep = 0.0f; float lowestStep = MAX_STEP_HEIGHT; glm::vec3 floorPoint; glm::vec3 stepPenetration(0.0f); glm::vec3 totalPenetration(0.0f); for (int i = 0; i < myCollisions.size(); ++i) { CollisionInfo* collision = myCollisions[i]; glm::vec3 cubeCenter = collision->_vecData; float cubeSide = collision->_floatData; float verticalDepth = glm::dot(collision->_penetration, _worldUpDirection); float horizontalDepth = glm::length(collision->_penetration - verticalDepth * _worldUpDirection); const float MAX_TRAP_PERIOD = 0.125f; if (horizontalDepth > capsuleRadius || fabsf(verticalDepth) > MAX_STEP_HEIGHT) { isTrapped = true; if (_trapDuration > MAX_TRAP_PERIOD) { float distance = glm::dot(boundingShape.getTranslation() - cubeCenter, _worldUpDirection); if (distance < 0.0f) { distance = fabsf(distance) + 0.5f * cubeSide; } distance += capsuleRadius + capsuleHalfHeight; totalPenetration = addPenetrations(totalPenetration, - distance * _worldUpDirection); continue; } } else if (_trapDuration > MAX_TRAP_PERIOD) { // we're trapped, ignore this collision continue; } totalPenetration = addPenetrations(totalPenetration, collision->_penetration); if (glm::dot(collision->_penetration, _velocity) >= 0.0f) { glm::vec3 cubeTop = cubeCenter + (0.5f * cubeSide) * _worldUpDirection; float stepHeight = glm::dot(_worldUpDirection, cubeTop - footBase); if (stepHeight > highestStep) { highestStep = stepHeight; stepPenetration = collision->_penetration; } if (stepHeight < lowestStep) { lowestStep = stepHeight; floorPoint = collision->_contactPoint - collision->_penetration; } } } if (lowestStep < MAX_STEP_HEIGHT) { _lastFloorContactPoint = floorPoint; } float penetrationLength = glm::length(totalPenetration); if (penetrationLength < EPSILON) { _trapDuration = 0.0f; return; } float verticalPenetration = glm::dot(totalPenetration, _worldUpDirection); if (highestStep > MIN_STEP_HEIGHT && highestStep < MAX_STEP_HEIGHT && verticalPenetration <= 0.0f) { // we're colliding against an edge glm::vec3 targetVelocity = _motorVelocity; if (_motionBehaviors & AVATAR_MOTION_MOTOR_USE_LOCAL_FRAME) { // rotate _motorVelocity into world frame glm::quat rotation = getHead()->getCameraOrientation(); targetVelocity = rotation * _motorVelocity; } if (_wasPushing && glm::dot(targetVelocity, totalPenetration) > EPSILON) { // we're puhing into the edge, so we want to lift // remove unhelpful horizontal component of the step's penetration totalPenetration -= stepPenetration - (glm::dot(stepPenetration, _worldUpDirection) * _worldUpDirection); // further adjust penetration to help lift float liftSpeed = glm::max(MAX_WALKING_SPEED, speed); float thisStep = glm::min(liftSpeed * deltaTime, highestStep); float extraStep = glm::dot(totalPenetration, _worldUpDirection) + thisStep; if (extraStep > 0.0f) { totalPenetration -= extraStep * _worldUpDirection; } _position -= totalPenetration; } else { // we're not pushing into the edge, so let the avatar fall applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); } } else { applyHardCollision(totalPenetration, VOXEL_ELASTICITY, VOXEL_DAMPING); } // Don't make a collision sound against voxlels by default -- too annoying when walking //const float VOXEL_COLLISION_FREQUENCY = 0.5f; //updateCollisionSound(myCollisions[0]->_penetration, deltaTime, VOXEL_COLLISION_FREQUENCY); } _trapDuration = isTrapped ? _trapDuration + deltaTime : 0.0f; } void MyAvatar::applyHardCollision(const glm::vec3& penetration, float elasticity, float damping) { // // Update the avatar in response to a hard collision. Position will be reset exactly // to outside the colliding surface. Velocity will be modified according to elasticity. // // if elasticity = 0.0, collision is 100% inelastic. // if elasticity = 1.0, collision is elastic. // _position -= penetration; static float HALTING_VELOCITY = 0.2f; // cancel out the velocity component in the direction of penetration float penetrationLength = glm::length(penetration); if (penetrationLength > EPSILON) { glm::vec3 direction = penetration / penetrationLength; _velocity -= glm::dot(_velocity, direction) * direction * (1.0f + elasticity); _velocity *= glm::clamp(1.0f - damping, 0.0f, 1.0f); if ((glm::length(_velocity) < HALTING_VELOCITY) && (glm::length(_thrust) == 0.0f)) { // If moving really slowly after a collision, and not applying forces, stop altogether _velocity *= 0.0f; } } } void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) { // consider whether to have the collision make a sound const float AUDIBLE_COLLISION_THRESHOLD = 0.02f; const float COLLISION_LOUDNESS = 1.0f; const float DURATION_SCALING = 0.004f; const float NOISE_SCALING = 0.1f; glm::vec3 velocity = _velocity; glm::vec3 gravity = getGravity(); if (glm::length(gravity) > EPSILON) { // If gravity is on, remove the effect of gravity on velocity for this // frame, so that we are not constantly colliding with the surface velocity -= _scale * glm::length(gravity) * GRAVITY_EARTH * deltaTime * glm::normalize(gravity); } float velocityTowardCollision = glm::dot(velocity, glm::normalize(penetration)); float velocityTangentToCollision = glm::length(velocity) - velocityTowardCollision; if (velocityTowardCollision > AUDIBLE_COLLISION_THRESHOLD) { // Volume is proportional to collision velocity // Base frequency is modified upward by the angle of the collision // Noise is a function of the angle of collision // Duration of the sound is a function of both base frequency and velocity of impact Application::getInstance()->getAudio()->startCollisionSound( std::min(COLLISION_LOUDNESS * velocityTowardCollision, 1.0f), frequency * (1.0f + velocityTangentToCollision / velocityTowardCollision), std::min(velocityTangentToCollision / velocityTowardCollision * NOISE_SCALING, 1.0f), 1.0f - DURATION_SCALING * powf(frequency, 0.5f) / velocityTowardCollision, false); } } bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float heightA, const glm::vec3 positionB, float radiusB, float heightB, glm::vec3& penetration) { glm::vec3 positionBA = positionB - positionA; float xzDistance = sqrt(positionBA.x * positionBA.x + positionBA.z * positionBA.z); if (xzDistance < (radiusA + radiusB)) { float yDistance = fabs(positionBA.y); float halfHeights = 0.5 * (heightA + heightB); if (yDistance < halfHeights) { // cylinders collide if (xzDistance > 0.0f) { positionBA.y = 0.0f; // note, penetration should point from A into B penetration = positionBA * ((radiusA + radiusB - xzDistance) / xzDistance); return true; } else { // exactly coaxial -- we'll return false for this case return false; } } else if (yDistance < halfHeights + radiusA + radiusB) { // caps collide if (positionBA.y < 0.0f) { // A is above B positionBA.y += halfHeights; float BA = glm::length(positionBA); penetration = positionBA * (radiusA + radiusB - BA) / BA; return true; } else { // A is below B positionBA.y -= halfHeights; float BA = glm::length(positionBA); penetration = positionBA * (radiusA + radiusB - BA) / BA; return true; } } } return false; } void MyAvatar::updateCollisionWithAvatars(float deltaTime) { // Reset detector for nearest avatar _distanceToNearestAvatar = std::numeric_limits::max(); const AvatarHash& avatars = Application::getInstance()->getAvatarManager().getAvatarHash(); if (avatars.size() <= 1) { // no need to compute a bunch of stuff if we have one or fewer avatars return; } float myBoundingRadius = getBoundingRadius(); // find nearest avatar float nearestDistance2 = std::numeric_limits::max(); Avatar* nearestAvatar = NULL; foreach (const AvatarSharedPointer& avatarPointer, avatars) { Avatar* avatar = static_cast(avatarPointer.data()); if (static_cast(this) == avatar) { // don't collide with ourselves continue; } float distance2 = glm::distance2(_position, avatar->getPosition()); if (nearestDistance2 > distance2) { nearestDistance2 = distance2; nearestAvatar = avatar; } } _distanceToNearestAvatar = glm::sqrt(nearestDistance2); if (Menu::getInstance()->isOptionChecked(MenuOption::CollideAsRagdoll)) { if (nearestAvatar != NULL) { if (_distanceToNearestAvatar > myBoundingRadius + nearestAvatar->getBoundingRadius()) { // they aren't close enough to put into the _physicsSimulation // so we clear the pointer nearestAvatar = NULL; } } foreach (const AvatarSharedPointer& avatarPointer, avatars) { Avatar* avatar = static_cast(avatarPointer.data()); if (static_cast(this) == avatar) { // don't collide with ourselves continue; } SkeletonModel* skeleton = &(avatar->getSkeletonModel()); PhysicsSimulation* simulation = skeleton->getSimulation(); if (avatar == nearestAvatar) { if (simulation != &(_physicsSimulation)) { skeleton->setEnableShapes(true); _physicsSimulation.addEntity(skeleton); _physicsSimulation.addRagdoll(skeleton->getRagdoll()); } } else if (simulation == &(_physicsSimulation)) { _physicsSimulation.removeRagdoll(skeleton->getRagdoll()); _physicsSimulation.removeEntity(skeleton); skeleton->setEnableShapes(false); } } } } class SortedAvatar { public: Avatar* avatar; float distance; glm::vec3 accumulatedCenter; }; bool operator<(const SortedAvatar& s1, const SortedAvatar& s2) { return s1.distance < s2.distance; } void MyAvatar::updateChatCircle(float deltaTime) { if (!(_isChatCirclingEnabled = Menu::getInstance()->isOptionChecked(MenuOption::ChatCircling))) { return; } // find all circle-enabled members and sort by distance QVector sortedAvatars; foreach (const AvatarSharedPointer& avatarPointer, Application::getInstance()->getAvatarManager().getAvatarHash()) { Avatar* avatar = static_cast(avatarPointer.data()); if ( ! avatar->isChatCirclingEnabled() || avatar == static_cast(this)) { continue; } SortedAvatar sortedAvatar; sortedAvatar.avatar = avatar; sortedAvatar.distance = glm::distance(_position, sortedAvatar.avatar->getPosition()); sortedAvatars.append(sortedAvatar); } qSort(sortedAvatars.begin(), sortedAvatars.end()); // compute the accumulated centers glm::vec3 center = _position; for (int i = 0; i < sortedAvatars.size(); i++) { SortedAvatar& sortedAvatar = sortedAvatars[i]; sortedAvatar.accumulatedCenter = (center += sortedAvatar.avatar->getPosition()) / (i + 2.0f); } // remove members whose accumulated circles are too far away to influence us const float CIRCUMFERENCE_PER_MEMBER = 0.5f; const float CIRCLE_INFLUENCE_SCALE = 2.0f; const float MIN_RADIUS = 0.3f; for (int i = sortedAvatars.size() - 1; i >= 0; i--) { float radius = qMax(MIN_RADIUS, (CIRCUMFERENCE_PER_MEMBER * (i + 2)) / TWO_PI); if (glm::distance(_position, sortedAvatars[i].accumulatedCenter) > radius * CIRCLE_INFLUENCE_SCALE) { sortedAvatars.remove(i); } else { break; } } if (sortedAvatars.isEmpty()) { return; } center = sortedAvatars.last().accumulatedCenter; float radius = qMax(MIN_RADIUS, (CIRCUMFERENCE_PER_MEMBER * (sortedAvatars.size() + 1)) / TWO_PI); // compute the average up vector glm::vec3 up = getWorldAlignedOrientation() * IDENTITY_UP; foreach (const SortedAvatar& sortedAvatar, sortedAvatars) { up += sortedAvatar.avatar->getWorldAlignedOrientation() * IDENTITY_UP; } up = glm::normalize(up); // find reasonable corresponding right/front vectors glm::vec3 front = glm::cross(up, IDENTITY_RIGHT); if (glm::length(front) < EPSILON) { front = glm::cross(up, IDENTITY_FRONT); } front = glm::normalize(front); glm::vec3 right = glm::cross(front, up); // find our angle and the angular distances to our closest neighbors glm::vec3 delta = _position - center; glm::vec3 projected = glm::vec3(glm::dot(right, delta), glm::dot(front, delta), 0.0f); float myAngle = glm::length(projected) > EPSILON ? atan2f(projected.y, projected.x) : 0.0f; float leftDistance = TWO_PI; float rightDistance = TWO_PI; foreach (const SortedAvatar& sortedAvatar, sortedAvatars) { delta = sortedAvatar.avatar->getPosition() - center; projected = glm::vec3(glm::dot(right, delta), glm::dot(front, delta), 0.0f); float angle = glm::length(projected) > EPSILON ? atan2f(projected.y, projected.x) : 0.0f; if (angle < myAngle) { leftDistance = min(myAngle - angle, leftDistance); rightDistance = min(TWO_PI - (myAngle - angle), rightDistance); } else { leftDistance = min(TWO_PI - (angle - myAngle), leftDistance); rightDistance = min(angle - myAngle, rightDistance); } } // if we're on top of a neighbor, we need to randomize so that they don't both go in the same direction if (rightDistance == 0.0f && randomBoolean()) { swap(leftDistance, rightDistance); } // split the difference between our neighbors float targetAngle = myAngle + (rightDistance - leftDistance) / 4.0f; glm::vec3 targetPosition = center + (front * sinf(targetAngle) + right * cosf(targetAngle)) * radius; // approach the target position const float APPROACH_RATE = 0.05f; _position = glm::mix(_position, targetPosition, APPROACH_RATE); } void MyAvatar::maybeUpdateBillboard() { if (_billboardValid || !(_skeletonModel.isLoadedWithTextures() && getHead()->getFaceModel().isLoadedWithTextures())) { return; } foreach (Model* model, _attachmentModels) { if (!model->isLoadedWithTextures()) { return; } } QImage image = Application::getInstance()->renderAvatarBillboard(); _billboard.clear(); QBuffer buffer(&_billboard); buffer.open(QIODevice::WriteOnly); image.save(&buffer, "PNG"); _billboardValid = true; sendBillboardPacket(); } void MyAvatar::goHome() { qDebug("Going Home!"); setPosition(START_LOCATION); } void MyAvatar::increaseSize() { if ((1.0f + SCALING_RATIO) * _targetScale < MAX_AVATAR_SCALE) { _targetScale *= (1.0f + SCALING_RATIO); qDebug("Changed scale to %f", _targetScale); } } void MyAvatar::decreaseSize() { if (MIN_AVATAR_SCALE < (1.0f - SCALING_RATIO) * _targetScale) { _targetScale *= (1.0f - SCALING_RATIO); qDebug("Changed scale to %f", _targetScale); } } void MyAvatar::resetSize() { _targetScale = 1.0f; qDebug("Reseted scale to %f", _targetScale); } void MyAvatar::goToLocationFromResponse(const QJsonObject& jsonObject) { if (jsonObject["status"].toString() == "success") { QJsonObject locationObject = jsonObject["data"].toObject()["address"].toObject(); goToLocationFromAddress(locationObject); } else { QMessageBox::warning(Application::getInstance()->getWindow(), "", "That user or location could not be found."); } } void MyAvatar::goToLocationFromAddress(const QJsonObject& locationObject) { // send a node kill request, indicating to other clients that they should play the "disappeared" effect sendKillAvatar(); QString positionString = locationObject["position"].toString(); QString orientationString = locationObject["orientation"].toString(); QString domainHostnameString = locationObject["domain"].toString(); qDebug() << "Changing domain to" << domainHostnameString << ", position to" << positionString << ", and orientation to" << orientationString; QStringList coordinateItems = positionString.split(','); QStringList orientationItems = orientationString.split(','); NodeList::getInstance()->getDomainHandler().setHostname(domainHostnameString); // orient the user to face the target glm::quat newOrientation = glm::quat(glm::radians(glm::vec3(orientationItems[0].toFloat(), orientationItems[1].toFloat(), orientationItems[2].toFloat()))) * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f)); setOrientation(newOrientation); // move the user a couple units away const float DISTANCE_TO_USER = 2.0f; glm::vec3 newPosition = glm::vec3(coordinateItems[0].toFloat(), coordinateItems[1].toFloat(), coordinateItems[2].toFloat()) - newOrientation * IDENTITY_FRONT * DISTANCE_TO_USER; setPosition(newPosition); emit transformChanged(); } void MyAvatar::updateMotionBehaviorsFromMenu() { Menu* menu = Menu::getInstance(); if (menu->isOptionChecked(MenuOption::ObeyEnvironmentalGravity)) { _motionBehaviors |= AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; // Environmental and Local gravities are incompatible. Environmental setting trumps local. _motionBehaviors &= ~AVATAR_MOTION_OBEY_LOCAL_GRAVITY; } else { _motionBehaviors &= ~AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY; } if (! (_motionBehaviors & (AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY | AVATAR_MOTION_OBEY_LOCAL_GRAVITY))) { setGravity(glm::vec3(0.0f)); } if (menu->isOptionChecked(MenuOption::StandOnNearbyFloors)) { _motionBehaviors |= AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; // standing on floors requires collision with voxels _collisionGroups |= COLLISION_GROUP_VOXELS; menu->setIsOptionChecked(MenuOption::CollideWithVoxels, true); } else { _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; } } void MyAvatar::renderAttachments(RenderMode renderMode) { if (Application::getInstance()->getCamera()->getMode() != CAMERA_MODE_FIRST_PERSON || renderMode == MIRROR_RENDER_MODE) { Avatar::renderAttachments(renderMode); return; } const FBXGeometry& geometry = _skeletonModel.getGeometry()->getFBXGeometry(); QString headJointName = (geometry.headJointIndex == -1) ? QString() : geometry.joints.at(geometry.headJointIndex).name; Model::RenderMode modelRenderMode = (renderMode == SHADOW_RENDER_MODE) ? Model::SHADOW_RENDER_MODE : Model::DEFAULT_RENDER_MODE; bool receiveShadows = Menu::getInstance()->isOptionChecked(MenuOption::AvatarsReceiveShadows); for (int i = 0; i < _attachmentData.size(); i++) { const QString& jointName = _attachmentData.at(i).jointName; if (jointName != headJointName && jointName != "Head") { _attachmentModels.at(i)->render(1.0f, modelRenderMode, receiveShadows); } } } void MyAvatar::setCollisionGroups(quint32 collisionGroups) { Avatar::setCollisionGroups(collisionGroups & VALID_COLLISION_GROUPS); Menu* menu = Menu::getInstance(); menu->setIsOptionChecked(MenuOption::CollideWithEnvironment, (bool)(_collisionGroups & COLLISION_GROUP_ENVIRONMENT)); menu->setIsOptionChecked(MenuOption::CollideWithAvatars, (bool)(_collisionGroups & COLLISION_GROUP_AVATARS)); menu->setIsOptionChecked(MenuOption::CollideWithVoxels, (bool)(_collisionGroups & COLLISION_GROUP_VOXELS)); menu->setIsOptionChecked(MenuOption::CollideWithParticles, (bool)(_collisionGroups & COLLISION_GROUP_PARTICLES)); if (! (_collisionGroups & COLLISION_GROUP_VOXELS)) { // no collision with voxels --> disable standing on floors _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; menu->setIsOptionChecked(MenuOption::StandOnNearbyFloors, false); } } 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); if (leverLength > EPSILON) { // compute lean perturbation angles glm::quat bodyRotation = getOrientation(); glm::vec3 xAxis = bodyRotation * glm::vec3(1.0f, 0.0f, 0.0f); glm::vec3 zAxis = bodyRotation * glm::vec3(0.0f, 0.0f, 1.0f); leverAxis = leverAxis / leverLength; glm::vec3 effectivePenetration = penetration - glm::dot(penetration, leverAxis) * leverAxis; // use the small-angle approximation for sine float sideways = - glm::dot(effectivePenetration, xAxis) / leverLength; float forward = glm::dot(effectivePenetration, zAxis) / leverLength; getHead()->addLeanDeltas(sideways, forward); } } //Renders sixense laser pointers for UI selection with controllers void MyAvatar::renderLaserPointers() { const float PALM_TIP_ROD_RADIUS = 0.002f; //If the Oculus is enabled, we will draw a blue cursor ray for (size_t i = 0; i < getHand()->getNumPalms(); ++i) { PalmData& palm = getHand()->getPalms()[i]; if (palm.isActive()) { glColor4f(0, 1, 1, 1); glm::vec3 tip = getLaserPointerTipPosition(&palm); glm::vec3 root = palm.getPosition(); //Scale the root vector with the avatar scale scaleVectorRelativeToPosition(root); Avatar::renderJointConnectingCone(root, tip, PALM_TIP_ROD_RADIUS, PALM_TIP_ROD_RADIUS); } } } //Gets the tip position for the laser pointer glm::vec3 MyAvatar::getLaserPointerTipPosition(const PalmData* palm) { const ApplicationOverlay& applicationOverlay = Application::getInstance()->getApplicationOverlay(); glm::vec3 direction = glm::normalize(palm->getTipPosition() - palm->getPosition()); glm::vec3 position = palm->getPosition(); //scale the position with the avatar scaleVectorRelativeToPosition(position); glm::vec3 result; if (applicationOverlay.calculateRayUICollisionPoint(position, direction, result)) { return result; } return palm->getPosition(); }