diff --git a/interface/resources/html/interface-welcome.html b/interface/resources/html/interface-welcome.html index ed905eb392..26ae6ff5c0 100644 --- a/interface/resources/html/interface-welcome.html +++ b/interface/resources/html/interface-welcome.html @@ -138,7 +138,7 @@

Import models

- Use the editEntitles.js script to
+ Use the edit.js script to
add FBX models in-world. You
can use grids and fine tune
placement-related parameters
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d50648e4d7..6e18c45468 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -987,12 +987,6 @@ void Application::keyPressEvent(QKeyEvent* event) { resetSensors(); break; - case Qt::Key_G: - if (isShifted) { - Menu::getInstance()->triggerOption(MenuOption::ObeyEnvironmentalGravity); - } - break; - case Qt::Key_A: if (isShifted) { Menu::getInstance()->triggerOption(MenuOption::Atmosphere); @@ -1165,10 +1159,6 @@ void Application::keyPressEvent(QKeyEvent* event) { break; } - case Qt::Key_Comma: { - _myAvatar->togglePhysicsEnabled(); - } - default: event->ignore(); break; @@ -2192,7 +2182,6 @@ void Application::update(float deltaTime) { { PerformanceTimer perfTimer("physics"); - _myAvatar->preSimulation(); _physicsEngine.stepSimulation(); } diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 2ec5c12ca0..5867dd29e2 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -201,9 +201,7 @@ Menu::Menu() { addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::NamesAboveHeads, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::GlowWhenSpeaking, 0, true); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::BlueSpeechSphere, 0, true); - addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ObeyEnvironmentalGravity, Qt::SHIFT | Qt::Key_G, false, - avatar, SLOT(updateMotionBehavior())); - addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::StandOnNearbyFloors, 0, true, + addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::EnableCharacterController, 0, true, avatar, SLOT(updateMotionBehavior())); addCheckableActionToQMenuAndActionHash(avatarMenu, MenuOption::ShiftHipsForIdleAnimations, 0, false, avatar, SLOT(updateMotionBehavior())); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 508d684c3e..63dab63711 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -153,6 +153,7 @@ namespace MenuOption { const QString EchoServerAudio = "Echo Server Audio"; const QString EditEntitiesHelp = "Edit Entities Help..."; const QString Enable3DTVMode = "Enable 3DTV Mode"; + const QString EnableCharacterController = "Enable avatar collisions"; const QString EnableGlowEffect = "Enable Glow Effect (Warning: Poor Oculus Performance)"; const QString EnableVRMode = "Enable VR Mode"; const QString Entities = "Entities"; @@ -185,7 +186,6 @@ namespace MenuOption { const QString MuteAudio = "Mute Microphone"; const QString MuteEnvironment = "Mute Environment"; const QString NoFaceTracking = "None"; - const QString ObeyEnvironmentalGravity = "Obey Environmental Gravity"; const QString OctreeStats = "Entity Statistics"; const QString OffAxisProjection = "Off-Axis Projection"; const QString OnlyDisplayTopTen = "Only Display Top Ten"; @@ -236,7 +236,6 @@ namespace MenuOption { const QString SixenseEnabled = "Enable Hydra Support"; const QString SixenseMouseInput = "Enable Sixense Mouse Input"; const QString SixenseLasers = "Enable Sixense UI Lasers"; - const QString StandOnNearbyFloors = "Stand on nearby floors"; const QString ShiftHipsForIdleAnimations = "Shift hips for idle animations"; const QString Stars = "Stars"; const QString Stats = "Stats"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 2f42544f28..26b777b35b 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -81,7 +81,6 @@ MyAvatar::MyAvatar() : _scriptedMotorTimescale(DEFAULT_SCRIPTED_MOTOR_TIMESCALE), _scriptedMotorFrame(SCRIPTED_MOTOR_CAMERA_FRAME), _motionBehaviors(AVATAR_MOTION_DEFAULTS), - _enablePhysics(false), _characterController(this), _lookAtTargetAvatar(), _shouldRender(true), @@ -101,6 +100,7 @@ MyAvatar::MyAvatar() : // connect to AddressManager signal for location jumps connect(DependencyManager::get().data(), &AddressManager::locationChangeRequired, this, &MyAvatar::goToLocation); + _characterController.setEnabled(true); } MyAvatar::~MyAvatar() { @@ -147,10 +147,6 @@ void MyAvatar::update(float deltaTime) { head->setAudioLoudness(audio->getLastInputLoudness()); head->setAudioAverageLoudness(audio->getAudioAverageInputLoudness()); - if (_motionBehaviors & AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY) { - setGravity(Application::getInstance()->getEnvironment()->getGravity(getPosition())); - } - simulate(deltaTime); if (_feetTouchFloor) { _skeletonModel.updateStandingFoot(); @@ -174,11 +170,7 @@ void MyAvatar::simulate(float deltaTime) { { PerformanceTimer perfTimer("transform"); updateOrientation(deltaTime); - if (isPhysicsEnabled()) { - updatePositionWithPhysics(deltaTime); - } else { - updatePosition(deltaTime); - } + updatePosition(deltaTime); } { @@ -484,26 +476,6 @@ void MyAvatar::loadLastRecording() { _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); @@ -1258,128 +1230,38 @@ glm::vec3 MyAvatar::applyScriptedMotor(float deltaTime, const glm::vec3& localVe return localVelocity + motorEfficiency * deltaVelocity; } -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 = 4.0f * boundingShape.getBoundingRadius(); - - // 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) || _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 { - if (heightAboveFloor > maxFloorDistance) { - // disable local gravity when floor is too far away - setLocalGravity(glm::vec3(0.0f)); - hasFloor = false; - } else { - // enable gravity - setLocalGravity(-_worldUpDirection); - } - } - } - - bool zeroDownwardVelocity = false; - bool gravityEnabled = (glm::length2(_gravity) > EPSILON); - if (gravityEnabled) { - 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; - } - if (!zeroDownwardVelocity) { - velocity += (deltaTime * GRAVITY_EARTH) * _gravity; - } - } - - // rotate velocity into camera frame - glm::quat rotation = getHead()->getCameraOrientation(); - glm::vec3 localVelocity = glm::inverse(rotation) * velocity; - - // apply motors in camera frame - glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor); - newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); - - // rotate back into world-frame - velocity = rotation * newLocalVelocity; - - // apply thrust - velocity += _thrust * deltaTime; - _thrust = glm::vec3(0.0f); - - // remove downward velocity so we don't push into floor - if (zeroDownwardVelocity) { - float verticalSpeed = glm::dot(velocity, _worldUpDirection); - if (verticalSpeed < 0.0f || !pushingUp) { - velocity -= verticalSpeed * _worldUpDirection; - } - } - - // cap avatar speed - float speed = glm::length(velocity); - if (speed > MAX_AVATAR_SPEED) { - velocity *= MAX_AVATAR_SPEED / speed; - speed = MAX_AVATAR_SPEED; - } - - // update position - if (speed > MIN_AVATAR_SPEED) { - applyPositionDelta(deltaTime * velocity); - } - - // update _moving flag based on speed - const float MOVING_SPEED_THRESHOLD = 0.01f; - _moving = speed > MOVING_SPEED_THRESHOLD; - - measureMotionDerivatives(deltaTime); -} - -void MyAvatar::updatePositionWithPhysics(float deltaTime) { // rotate velocity into camera frame glm::quat rotation = getHead()->getCameraOrientation(); glm::vec3 localVelocity = glm::inverse(rotation) * _velocity; - bool hasFloor = false; - glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, hasFloor); + bool isOnGround = _characterController.onGround(); + glm::vec3 newLocalVelocity = applyKeyboardMotor(deltaTime, localVelocity, isOnGround); newLocalVelocity = applyScriptedMotor(deltaTime, newLocalVelocity); - // cap avatar speed - float speed = glm::length(newLocalVelocity); - if (speed > MAX_WALKING_SPEED) { - newLocalVelocity *= MAX_WALKING_SPEED / speed; - } - // rotate back into world-frame _velocity = rotation * newLocalVelocity; _velocity += _thrust * deltaTime; _thrust = glm::vec3(0.0f); + + // cap avatar speed + float speed = glm::length(_velocity); + if (speed > MAX_AVATAR_SPEED) { + _velocity *= MAX_AVATAR_SPEED / speed; + speed = MAX_AVATAR_SPEED; + } + + if (speed > MIN_AVATAR_SPEED && !_characterController.isEnabled()) { + // update position ourselves + applyPositionDelta(deltaTime * _velocity); + measureMotionDerivatives(deltaTime); + } // else physics will move avatar later + + // update _moving flag based on speed + const float MOVING_SPEED_THRESHOLD = 0.01f; + _moving = speed > MOVING_SPEED_THRESHOLD; + } void MyAvatar::updateCollisionSound(const glm::vec3 &penetration, float deltaTime, float frequency) { @@ -1496,23 +1378,6 @@ void MyAvatar::goToLocation(const glm::vec3& newPosition, void MyAvatar::updateMotionBehavior() { 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 - // TODO: determine what to do with this now that voxels are gone - } else { - _motionBehaviors &= ~AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; - } if (menu->isOptionChecked(MenuOption::KeyboardMotorControl)) { _motionBehaviors |= AVATAR_MOTION_KEYBOARD_MOTOR_ENABLED; } else { @@ -1523,6 +1388,7 @@ void MyAvatar::updateMotionBehavior() { } else { _motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; } + _characterController.setEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController)); _feetTouchFloor = menu->isOptionChecked(MenuOption::ShiftHipsForIdleAnimations); } @@ -1581,10 +1447,6 @@ glm::vec3 MyAvatar::getLaserPointerTipPosition(const PalmData* palm) { return palm->getPosition(); } -void MyAvatar::preSimulation() { - _characterController.setEnabled(_enablePhysics); -} - void MyAvatar::clearDriveKeys() { for (int i = 0; i < MAX_DRIVE_KEYS; ++i) { _driveKeys[i] = 0.0f; diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index a37d1c6a30..99c0bdb5df 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -25,7 +25,7 @@ class MyAvatar : public Avatar { 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) + //TODO: make gravity feature work Q_PROPERTY(glm::vec3 gravity READ getGravity WRITE setGravity) public: MyAvatar(); @@ -44,13 +44,11 @@ public: // setters void setLeanScale(float scale) { _leanScale = scale; } - void setLocalGravity(glm::vec3 gravity); void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; } void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); } // getters float getLeanScale() const { return _leanScale; } - glm::vec3 getGravity() const { return _gravity; } Q_INVOKABLE glm::vec3 getDefaultEyePosition() const; bool getShouldRenderLocally() const { return _shouldRender; } float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); } @@ -148,11 +146,6 @@ public: const RecorderPointer getRecorder() const { return _recorder; } const PlayerPointer getPlayer() const { return _player; } - - void togglePhysicsEnabled() { _enablePhysics = !_enablePhysics; } - bool isPhysicsEnabled() { return _enablePhysics; } - void setPhysicsEnabled(bool enablePhysics) { _enablePhysics = enablePhysics; } - void preSimulation(); public slots: void increaseSize(); @@ -209,7 +202,6 @@ private: int _scriptedMotorFrame; quint32 _motionBehaviors; - bool _enablePhysics; CharacterController _characterController; QWeakPointer _lookAtTargetAvatar; @@ -234,10 +226,8 @@ private: glm::vec3 applyKeyboardMotor(float deltaTime, const glm::vec3& velocity, bool walkingOnFloor); glm::vec3 applyScriptedMotor(float deltaTime, const glm::vec3& velocity); void updatePosition(float deltaTime); - void updatePositionWithPhysics(float deltaTime); void updateCollisionSound(const glm::vec3& penetration, float deltaTime, float frequency); void maybeUpdateBillboard(); - void setGravity(const glm::vec3& gravity); }; #endif // hifi_MyAvatar_h diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index a2feb98798..ad90c88aaa 100644 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -61,21 +61,13 @@ typedef unsigned long long quint64; 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 << 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_KEYBOARD_MOTOR_ENABLED | - AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED | - AVATAR_MOTION_STAND_ON_NEARBY_FLOORS; + AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; // 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; + AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED; // Bitset of state flags - we store the key state, hand state, faceshift, chat circling, and existance of @@ -365,8 +357,8 @@ protected: HeadData* _headData; HandData* _handData; - QUrl _faceModelURL = DEFAULT_HEAD_MODEL_URL; - QUrl _skeletonModelURL = DEFAULT_BODY_MODEL_URL; + QUrl _faceModelURL; // These need to be empty so that on first time setting them they will not short circuit + QUrl _skeletonModelURL; // These need to be empty so that on first time setting them they will not short circuit QVector _attachmentData; QString _displayName; diff --git a/libraries/networking/src/AccountManager.cpp b/libraries/networking/src/AccountManager.cpp index 5858c0926d..46c6418c63 100644 --- a/libraries/networking/src/AccountManager.cpp +++ b/libraries/networking/src/AccountManager.cpp @@ -282,7 +282,7 @@ void AccountManager::processReply() { } else { passErrorToCallback(requestReply); } - delete requestReply; + requestReply->deleteLater(); } void AccountManager::passSuccessToCallback(QNetworkReply* requestReply) { diff --git a/libraries/physics/src/CharacterController.cpp b/libraries/physics/src/CharacterController.cpp index e84df7d644..09c6b5599f 100644 --- a/libraries/physics/src/CharacterController.cpp +++ b/libraries/physics/src/CharacterController.cpp @@ -40,6 +40,24 @@ static btVector3 getNormalizedVector(const btVector3& v) { return n; } +class btKinematicClosestNotMeRayResultCallback : public btCollisionWorld::ClosestRayResultCallback { +public: + btKinematicClosestNotMeRayResultCallback (btCollisionObject* me) : + btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) { + _me = me; + } + + virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) { + if (rayResult.m_collisionObject == _me) { + return 1.0f; + } + return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace); + } +protected: + btCollisionObject* _me; +}; + + class btKinematicClosestNotMeConvexResultCallback : public btCollisionWorld::ClosestConvexResultCallback { public: btKinematicClosestNotMeConvexResultCallback(btCollisionObject* me, const btVector3& up, btScalar minSlopeDot) @@ -199,9 +217,7 @@ CharacterController::CharacterController(AvatarData* avatarData) { assert(avatarData); _avatarData = avatarData; - // cache the "PhysicsEnabled" state of _avatarData _enabled = false; - _ghostObject = NULL; _convexShape = NULL; @@ -210,11 +226,13 @@ CharacterController::CharacterController(AvatarData* avatarData) { _velocityTimeInterval = 0.0f; _verticalVelocity = 0.0f; _verticalOffset = 0.0f; - _gravity = 9.8f; + _gravity = 5.0f; // slower than Earth's _maxFallSpeed = 55.0f; // Terminal velocity of a sky diver in m/s. - _jumpSpeed = 7.0f; - _wasOnGround = false; - _wasJumping = false; + _jumpSpeed = 5.0f; + _isOnGround = false; + _isJumping = false; + _isHovering = true; + _jumpToHoverStart = 0; setMaxSlope(btRadians(45.0f)); _lastStepUp = 0.0f; _pendingFlags = 0; @@ -325,6 +343,26 @@ bool CharacterController::recoverFromPenetration(btCollisionWorld* collisionWorl return penetration; } +void CharacterController::scanDown(btCollisionWorld* world) { + // we test with downward raycast and if we don't find floor close enough then turn on "hover" + btKinematicClosestNotMeRayResultCallback callback(_ghostObject); + callback.m_collisionFilterGroup = getGhostObject()->getBroadphaseHandle()->m_collisionFilterGroup; + callback.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; + + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + btVector3 start = _currentPosition; + const btScalar MAX_SCAN_HEIGHT = 20.0f + _halfHeight + _radius; // closest possible floor for disabling hover + const btScalar MIN_HOVER_HEIGHT = 3.0f + _halfHeight + _radius; // distance to floor for enabling hover + btVector3 end = start - MAX_SCAN_HEIGHT * up; + + world->rayTest(start, end, callback); + if (!callback.hasHit()) { + _isHovering = true; + } else if (_isHovering && callback.m_closestHitFraction * MAX_SCAN_HEIGHT < MIN_HOVER_HEIGHT) { + _isHovering = false; + } +} + void CharacterController::stepUp(btCollisionWorld* world) { // phase 1: up @@ -334,7 +372,7 @@ void CharacterController::stepUp(btCollisionWorld* world) { btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); start.setOrigin(_currentPosition + up * (_convexShape->getMargin() + _addedMargin)); - _targetPosition = _currentPosition + up * _stepHeight; + _targetPosition = _currentPosition + up * _stepUpHeight; end.setIdentity(); end.setOrigin(_targetPosition); @@ -352,15 +390,15 @@ void CharacterController::stepUp(btCollisionWorld* world) { // Only modify the position if the hit was a slope and not a wall or ceiling. if (callback.m_hitNormalWorld.dot(up) > 0.0f) { - _lastStepUp = _stepHeight * callback.m_closestHitFraction; + _lastStepUp = _stepUpHeight * callback.m_closestHitFraction; _currentPosition.setInterpolate3(_currentPosition, _targetPosition, callback.m_closestHitFraction); } else { - _lastStepUp = _stepHeight; + _lastStepUp = _stepUpHeight; _currentPosition = _targetPosition; } } else { _currentPosition = _targetPosition; - _lastStepUp = _stepHeight; + _lastStepUp = _stepUpHeight; } } @@ -411,12 +449,12 @@ void CharacterController::stepForward(btCollisionWorld* collisionWorld, const bt btScalar margin = _convexShape->getMargin(); _convexShape->setMargin(margin + _addedMargin); - const btScalar MIN_STEP_DISTANCE = 0.0001f; + const btScalar MIN_STEP_DISTANCE_SQUARED = 1.0e-6f; btVector3 step = _targetPosition - _currentPosition; btScalar stepLength2 = step.length2(); int maxIter = 10; - while (stepLength2 > MIN_STEP_DISTANCE && maxIter-- > 0) { + while (stepLength2 > MIN_STEP_DISTANCE_SQUARED && maxIter-- > 0) { start.setOrigin(_currentPosition); end.setOrigin(_targetPosition); @@ -477,13 +515,16 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt end.setOrigin(_targetPosition); _ghostObject->convexSweepTest(_convexShape, start, end, callback, collisionWorld->getDispatchInfo().m_allowedCcdPenetration); + _isOnGround = false; if (callback.hasHit()) { _currentPosition += callback.m_closestHitFraction * step; _verticalVelocity = 0.0f; _verticalOffset = 0.0f; - _wasJumping = false; - } else if (!_wasJumping) { + _isJumping = false; + _isOnGround = true; + } else if (!_isJumping) { // sweep again for floor within downStep threshold + step = -_stepDownHeight * up; StepDownConvexResultCallback callback2 (_ghostObject, up, _currentPosition, step, @@ -495,8 +536,6 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt callback2.m_collisionFilterMask = getGhostObject()->getBroadphaseHandle()->m_collisionFilterMask; _currentPosition = _targetPosition; - btVector3 oldPosition = _currentPosition; - step = (- _stepHeight) * up; _targetPosition = _currentPosition + step; start.setOrigin(_currentPosition); @@ -507,10 +546,10 @@ void CharacterController::stepDown(btCollisionWorld* collisionWorld, btScalar dt _currentPosition += callback2.m_closestHitFraction * step; _verticalVelocity = 0.0f; _verticalOffset = 0.0f; - _wasJumping = false; + _isJumping = false; + _isOnGround = true; } else { - // nothing to step down on, so remove the stepUp effect - _currentPosition = oldPosition - _lastStepUp * up; + // nothing to step down on _lastStepUp = 0.0f; } } else { @@ -534,8 +573,8 @@ void CharacterController::setVelocityForTimeInterval(const btVector3& velocity, void CharacterController::reset(btCollisionWorld* collisionWorld) { _verticalVelocity = 0.0; _verticalOffset = 0.0; - _wasOnGround = false; - _wasJumping = false; + _isOnGround = false; + _isJumping = false; _walkDirection.setValue(0,0,0); _velocityTimeInterval = 0.0; @@ -581,14 +620,17 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar return; // no motion } - _wasOnGround = onGround(); - // Update fall velocity. - _verticalVelocity -= _gravity * dt; - if (_verticalVelocity > _jumpSpeed) { - _verticalVelocity = _jumpSpeed; - } else if (_verticalVelocity < -_maxFallSpeed) { - _verticalVelocity = -_maxFallSpeed; + if (_isHovering) { + const btScalar HOVER_RELAXATION_TIMESCALE = 1.0f; + _verticalVelocity *= (1.0f - dt / HOVER_RELAXATION_TIMESCALE); + } else { + _verticalVelocity -= _gravity * dt; + if (_verticalVelocity > _jumpSpeed) { + _verticalVelocity = _jumpSpeed; + } else if (_verticalVelocity < -_maxFallSpeed) { + _verticalVelocity = -_maxFallSpeed; + } } _verticalOffset = _verticalVelocity * dt; @@ -600,11 +642,14 @@ void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar // (2) step the character forward // (3) step the character down looking for new ledges, the original floor, or a floor one step below where we started + scanDown(collisionWorld); + stepUp(collisionWorld); // compute substep and decrement total interval btScalar dtMoving = (dt < _velocityTimeInterval) ? dt : _velocityTimeInterval; _velocityTimeInterval -= dt; + _stepDt += dt; // stepForward substep btVector3 move = _walkDirection * dtMoving; @@ -629,11 +674,25 @@ void CharacterController::setMaxJumpHeight(btScalar maxJumpHeight) { } bool CharacterController::canJump() const { - return onGround(); + return _isOnGround; } void CharacterController::jump() { _pendingFlags |= PENDING_FLAG_JUMP; + + // check for case where user is holding down "jump" key... + // we'll eventually tansition to "hover" + if (!_isHovering) { + if (!_isJumping) { + _jumpToHoverStart = usecTimestampNow(); + } else { + quint64 now = usecTimestampNow(); + const quint64 JUMP_TO_HOVER_PERIOD = USECS_PER_SECOND; + if (now - _jumpToHoverStart < JUMP_TO_HOVER_PERIOD) { + _isHovering = true; + } + } + } } void CharacterController::setGravity(btScalar gravity) { @@ -654,7 +713,7 @@ btScalar CharacterController::getMaxSlope() const { } bool CharacterController::onGround() const { - return _enabled && _verticalVelocity == 0.0f && _verticalOffset == 0.0f; + return _isOnGround; } void CharacterController::debugDraw(btIDebugDraw* debugDrawer) { @@ -684,10 +743,12 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) { // shape hasn't changed --> nothing to do } else { - // we always need to: REMOVE when UPDATE_SHAPE, to avoid deleting shapes out from under the PhysicsEngine - _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION - | PENDING_FLAG_UPDATE_SHAPE; - // but only need to ADD back when we happen to be enabled + if (_dynamicsWorld) { + // must REMOVE from world prior to shape update + _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; + } + _pendingFlags |= PENDING_FLAG_UPDATE_SHAPE; + // only need to ADD back when we happen to be enabled if (_enabled) { _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; } @@ -711,11 +772,13 @@ void CharacterController::setEnabled(bool enabled) { // Don't bother clearing REMOVE bit since it might be paired with an UPDATE_SHAPE bit. // Setting the ADD bit here works for all cases so we don't even bother checking other bits. _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION; + _isHovering = true; } else { - // Always set REMOVE bit when going disabled, and we always clear the ADD bit just in case - // it was previously set by something else (e.g. an UPDATE_SHAPE event). - _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; + if (_dynamicsWorld) { + _pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION; + } _pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION; + _isOnGround = false; } _enabled = enabled; } @@ -729,17 +792,23 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) { } _dynamicsWorld = world; if (_dynamicsWorld) { - _pendingFlags &= ~ (PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_JUMP); + _pendingFlags &= ~ PENDING_FLAG_JUMP; _dynamicsWorld->addCollisionObject(getGhostObject(), btBroadphaseProxy::CharacterFilter, btBroadphaseProxy::StaticFilter | btBroadphaseProxy::DefaultFilter); _dynamicsWorld->addAction(this); reset(_dynamicsWorld); + } + } + if (_dynamicsWorld) { + if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) { + // shouldn't fall in here, but if we do make sure both ADD and REMOVE bits are still set + _pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_REMOVE_FROM_SIMULATION; } else { - _pendingFlags &= ~ PENDING_FLAG_REMOVE_FROM_SIMULATION; + _pendingFlags &= ~PENDING_FLAG_ADD_TO_SIMULATION; } } else { - _pendingFlags &= ~ (PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_ADD_TO_SIMULATION); + _pendingFlags &= ~ PENDING_FLAG_REMOVE_FROM_SIMULATION; } } @@ -772,10 +841,8 @@ void CharacterController::updateShapeIfNecessary() { _ghostObject->setWorldTransform(btTransform(glmToBullet(_avatarData->getOrientation()), glmToBullet(_avatarData->getPosition()))); // stepHeight affects the heights of ledges that the character can ascend - // however the actual ledge height is some function of _stepHeight - // due to character shape and this CharacterController algorithm - // (the function is approximately 2*_stepHeight) - _stepHeight = 0.1f * (_radius + _halfHeight) + 0.1f; + _stepUpHeight = _radius + 0.25f * _halfHeight + 0.1f; + _stepDownHeight = _radius; // create new shape _convexShape = new btCapsuleShape(_radius, 2.0f * _halfHeight); @@ -799,9 +866,12 @@ void CharacterController::preSimulation(btScalar timeStep) { _pendingFlags &= ~ PENDING_FLAG_JUMP; if (canJump()) { _verticalVelocity = _jumpSpeed; - _wasJumping = true; + _isJumping = true; } } + // remember last position so we can throttle the total motion from the next step + _lastPosition = position; + _stepDt = 0.0f; } } @@ -809,9 +879,24 @@ void CharacterController::postSimulation() { if (_enabled) { const btTransform& avatarTransform = _ghostObject->getWorldTransform(); glm::quat rotation = bulletToGLM(avatarTransform.getRotation()); - glm::vec3 offset = rotation * _shapeLocalOffset; + glm::vec3 position = bulletToGLM(avatarTransform.getOrigin()); + + // cap the velocity of the step so that the character doesn't POP! so hard on steps + glm::vec3 finalStep = position - _lastPosition; + btVector3 finalVelocity = _walkDirection; + btVector3 up = quatRotate(_currentRotation, LOCAL_UP_AXIS); + finalVelocity += _verticalVelocity * up; + const btScalar MAX_RESOLUTION_SPEED = 5.0f; // m/sec + btScalar maxStepLength = glm::max(MAX_RESOLUTION_SPEED, 2.0f * finalVelocity.length()) * _stepDt; + btScalar stepLength = glm::length(finalStep); + if (stepLength > maxStepLength) { + position = _lastPosition + (maxStepLength / stepLength) * finalStep; + // NOTE: we don't need to move ghostObject to throttled position unless + // we want to support do async ray-traces/collision-queries against character + } + _avatarData->setOrientation(rotation); - _avatarData->setPosition(bulletToGLM(avatarTransform.getOrigin()) - offset); + _avatarData->setPosition(position - rotation * _shapeLocalOffset); } } diff --git a/libraries/physics/src/CharacterController.h b/libraries/physics/src/CharacterController.h index 323529b1cd..eeaa5836dd 100644 --- a/libraries/physics/src/CharacterController.h +++ b/libraries/physics/src/CharacterController.h @@ -59,7 +59,8 @@ protected: btScalar _maxSlopeCosine; // Cosine equivalent of _maxSlopeRadians (calculated once when set, for optimization) btScalar _gravity; - btScalar _stepHeight; // height of stepUp prior to stepForward + btScalar _stepUpHeight; // height of stepUp prior to stepForward + btScalar _stepDownHeight; // height of stepDown btScalar _addedMargin;//@todo: remove this and fix the code @@ -71,6 +72,7 @@ protected: btVector3 _currentPosition; btQuaternion _currentRotation; btVector3 _targetPosition; + glm::vec3 _lastPosition; btScalar _lastStepUp; ///keep track of the contact manifolds @@ -80,9 +82,12 @@ protected: btVector3 _floorNormal; // points from object to character bool _enabled; - bool _wasOnGround; - bool _wasJumping; + bool _isOnGround; + bool _isJumping; + bool _isHovering; + quint64 _jumpToHoverStart; btScalar _velocityTimeInterval; + btScalar _stepDt; uint32_t _pendingFlags; glm::vec3 _shapeLocalOffset; @@ -95,6 +100,7 @@ protected: btVector3 perpindicularComponent(const btVector3& direction, const btVector3& normal); bool recoverFromPenetration(btCollisionWorld* collisionWorld); + void scanDown(btCollisionWorld* collisionWorld); void stepUp(btCollisionWorld* collisionWorld); void updateTargetPositionBasedOnCollision(const btVector3& hit_normal, btScalar tangentMag = btScalar(0.0), btScalar normalMag = btScalar(1.0)); void stepForward(btCollisionWorld* collisionWorld, const btVector3& walkMove); @@ -161,6 +167,7 @@ public: bool needsRemoval() const; bool needsAddition() const; void setEnabled(bool enabled); + bool isEnabled() const { return _enabled; } void setDynamicsWorld(btDynamicsWorld* world); void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale); diff --git a/libraries/script-engine/src/ScriptEngine.cpp b/libraries/script-engine/src/ScriptEngine.cpp index 716af1d188..831db73a0a 100644 --- a/libraries/script-engine/src/ScriptEngine.cpp +++ b/libraries/script-engine/src/ScriptEngine.cpp @@ -360,10 +360,6 @@ void ScriptEngine::init() { globalObject().setProperty("TREE_SCALE", newVariant(QVariant(TREE_SCALE))); globalObject().setProperty("COLLISION_GROUP_ENVIRONMENT", newVariant(QVariant(COLLISION_GROUP_ENVIRONMENT))); globalObject().setProperty("COLLISION_GROUP_AVATARS", newVariant(QVariant(COLLISION_GROUP_AVATARS))); - - globalObject().setProperty("AVATAR_MOTION_OBEY_LOCAL_GRAVITY", newVariant(QVariant(AVATAR_MOTION_OBEY_LOCAL_GRAVITY))); - globalObject().setProperty("AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY", newVariant(QVariant(AVATAR_MOTION_OBEY_ENVIRONMENTAL_GRAVITY))); - } QScriptValue ScriptEngine::registerGlobalObject(const QString& name, QObject* object) { diff --git a/libraries/script-engine/src/XMLHttpRequestClass.cpp b/libraries/script-engine/src/XMLHttpRequestClass.cpp index 8755527860..3054472a3c 100644 --- a/libraries/script-engine/src/XMLHttpRequestClass.cpp +++ b/libraries/script-engine/src/XMLHttpRequestClass.cpp @@ -22,6 +22,8 @@ #include "XMLHttpRequestClass.h" #include "ScriptEngine.h" +const QString METAVERSE_API_URL = "https://metaverse.highfidelity.com/api/"; + Q_DECLARE_METATYPE(QByteArray*) XMLHttpRequestClass::XMLHttpRequestClass(QScriptEngine* engine) : @@ -207,7 +209,7 @@ void XMLHttpRequestClass::open(const QString& method, const QString& url, bool a notImplemented(); } } else { - if (url.toLower().left(33) == "https://metaverse.highfidelity.com/api/") { + if (url.toLower().left(METAVERSE_API_URL.length()) == METAVERSE_API_URL) { AccountManager& accountManager = AccountManager::getInstance(); if (accountManager.hasValidAccessToken()) {