mirror of
https://github.com/lubosz/overte.git
synced 2025-04-24 09:23:17 +02:00
Merge remote-tracking branch 'refs/remotes/highfidelity/master' into last-edited-property
This commit is contained in:
commit
9289dd36d4
91 changed files with 1166 additions and 2881 deletions
4
cmake/externals/wasapi/CMakeLists.txt
vendored
4
cmake/externals/wasapi/CMakeLists.txt
vendored
|
@ -6,8 +6,8 @@ if (WIN32)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi4.zip
|
||||
URL_MD5 2abde5340a64d387848f12b9536a7e85
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi5.zip
|
||||
URL_MD5 0530753e855ffc00232cc969bf1c84a8
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
|
@ -255,10 +255,10 @@
|
|||
},
|
||||
{
|
||||
"id": "idleToWalkFwd",
|
||||
"interpTarget": 4,
|
||||
"interpDuration": 3.0,
|
||||
"interpTarget": 3,
|
||||
"interpDuration": 3,
|
||||
"transitions": [
|
||||
{ "var": "idleToWalkFwdClipOnDone", "state": "walkFwd" },
|
||||
{ "var": "idleToWalkFwdOnDone", "state": "walkFwd" },
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingBackward", "state": "walkBwd" },
|
||||
{ "var": "isMovingRight", "state": "strafeRight" },
|
||||
|
@ -292,8 +292,8 @@
|
|||
},
|
||||
{
|
||||
"id": "walkBwd",
|
||||
"interpTarget": 4,
|
||||
"interpDuration": 4,
|
||||
"interpTarget": 6,
|
||||
"interpDuration": 6,
|
||||
"transitions": [
|
||||
{ "var": "isNotMoving", "state": "idle" },
|
||||
{ "var": "isMovingForward", "state": "walkFwd" },
|
||||
|
@ -523,7 +523,7 @@
|
|||
"data": {
|
||||
"alpha": 0.0,
|
||||
"desiredSpeed": 1.4,
|
||||
"characteristicSpeeds": [0.5, 1.3, 4.5],
|
||||
"characteristicSpeeds": [0.5, 1.4, 4.5],
|
||||
"alphaVar": "moveForwardAlpha",
|
||||
"desiredSpeedVar": "moveForwardSpeed"
|
||||
},
|
||||
|
@ -568,28 +568,15 @@
|
|||
},
|
||||
{
|
||||
"id": "idleToWalkFwd",
|
||||
"type": "blendLinearMove",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"alpha": 0.0,
|
||||
"desiredSpeed": 1.2,
|
||||
"characteristicSpeeds": [1.2],
|
||||
"alphaVar": 0.0,
|
||||
"desiredSpeedVar": "moveForwardSpeed"
|
||||
"url": "animations/idle_to_walk.fbx",
|
||||
"startFrame": 1.0,
|
||||
"endFrame": 13.0,
|
||||
"timeScale": 1.0,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"id": "idleToWalkFwdClip",
|
||||
"type": "clip",
|
||||
"data": {
|
||||
"url": "animations/idle_to_walk.fbx",
|
||||
"startFrame": 1.0,
|
||||
"endFrame": 13.0,
|
||||
"timeScale": 0.9,
|
||||
"loopFlag": false
|
||||
},
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"id": "walkBwd",
|
||||
|
@ -597,7 +584,7 @@
|
|||
"data": {
|
||||
"alpha": 0.0,
|
||||
"desiredSpeed": 1.4,
|
||||
"characteristicSpeeds": [0.6, 1.05],
|
||||
"characteristicSpeeds": [0.6, 1.45],
|
||||
"alphaVar": "moveBackwardAlpha",
|
||||
"desiredSpeedVar": "moveBackwardSpeed"
|
||||
},
|
||||
|
@ -659,7 +646,7 @@
|
|||
"data": {
|
||||
"alpha": 0.0,
|
||||
"desiredSpeed": 1.4,
|
||||
"characteristicSpeeds": [0.2, 0.5],
|
||||
"characteristicSpeeds": [0.2, 0.65],
|
||||
"alphaVar": "moveLateralAlpha",
|
||||
"desiredSpeedVar": "moveLateralSpeed"
|
||||
},
|
||||
|
@ -696,7 +683,7 @@
|
|||
"data": {
|
||||
"alpha": 0.0,
|
||||
"desiredSpeed": 1.4,
|
||||
"characteristicSpeeds": [0.2, 0.5],
|
||||
"characteristicSpeeds": [0.2, 0.65],
|
||||
"alphaVar": "moveLateralAlpha",
|
||||
"desiredSpeedVar": "moveLateralSpeed"
|
||||
},
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
"channels": [
|
||||
{ "from": "OculusTouch.A", "to": "Standard.RightPrimaryThumb", "peek": true },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.LeftPrimaryThumb", "peek": true },
|
||||
{ "from": "OculusTouch.B", "to": "Standard.RightSecondaryThumb", "peek": true},
|
||||
{ "from": "OculusTouch.Y", "to": "Standard.LeftSecondaryThumb", "peek": true},
|
||||
|
||||
{ "from": "OculusTouch.A", "to": "Standard.A" },
|
||||
{ "from": "OculusTouch.B", "to": "Standard.B" },
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
]
|
||||
},
|
||||
|
||||
{ "from": "GamePad.RX", "to": "Actions.Yaw" },
|
||||
{ "from": "GamePad.RX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Actions.Yaw" },
|
||||
|
||||
{ "from": "GamePad.RY",
|
||||
"to": "Actions.VERTICAL_UP",
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
#include <ErrorDialog.h>
|
||||
#include <FileScriptingInterface.h>
|
||||
#include <Finally.h>
|
||||
#include <FingerprintUtils.h>
|
||||
#include <FramebufferCache.h>
|
||||
#include <gpu/Batch.h>
|
||||
#include <gpu/Context.h>
|
||||
|
@ -568,7 +569,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_window->setWindowTitle("Interface");
|
||||
|
||||
Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us
|
||||
|
||||
|
||||
// TODO: This is temporary, while developing
|
||||
FingerprintUtils::getMachineFingerprint();
|
||||
// End TODO
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// Set up a watchdog thread to intentionally crash the application on deadlocks
|
||||
|
@ -3882,6 +3886,13 @@ void Application::update(float deltaTime) {
|
|||
if (nearbyEntitiesAreReadyForPhysics()) {
|
||||
_physicsEnabled = true;
|
||||
getMyAvatar()->updateMotionBehaviorFromMenu();
|
||||
} else {
|
||||
auto characterController = getMyAvatar()->getCharacterController();
|
||||
if (characterController) {
|
||||
// if we have a character controller, disable it here so the avatar doesn't get stuck due to
|
||||
// a non-loading collision hull.
|
||||
characterController->setEnabled(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4010,7 +4021,7 @@ void Application::update(float deltaTime) {
|
|||
avatarManager->getObjectsToChange(motionStates);
|
||||
_physicsEngine->changeObjects(motionStates);
|
||||
|
||||
myAvatar->prepareForPhysicsSimulation(deltaTime);
|
||||
myAvatar->prepareForPhysicsSimulation();
|
||||
_physicsEngine->forEachAction([&](EntityActionPointer action) {
|
||||
action->prepareForPhysicsSimulation();
|
||||
});
|
||||
|
|
|
@ -363,6 +363,7 @@ Menu::Menu() {
|
|||
QActionGroup* textureGroup = new QActionGroup(textureMenu);
|
||||
textureGroup->setExclusive(true);
|
||||
textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTextureAutomatic, 0, true));
|
||||
textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture4MB, 0, false));
|
||||
textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture64MB, 0, false));
|
||||
textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture256MB, 0, false));
|
||||
textureGroup->addAction(addCheckableActionToQMenuAndActionHash(textureMenu, MenuOption::RenderMaxTexture512MB, 0, false));
|
||||
|
@ -372,7 +373,9 @@ Menu::Menu() {
|
|||
auto checked = textureGroup->checkedAction();
|
||||
auto text = checked->text();
|
||||
gpu::Context::Size newMaxTextureMemory { 0 };
|
||||
if (MenuOption::RenderMaxTexture64MB == text) {
|
||||
if (MenuOption::RenderMaxTexture4MB == text) {
|
||||
newMaxTextureMemory = MB_TO_BYTES(4);
|
||||
} else if (MenuOption::RenderMaxTexture64MB == text) {
|
||||
newMaxTextureMemory = MB_TO_BYTES(64);
|
||||
} else if (MenuOption::RenderMaxTexture256MB == text) {
|
||||
newMaxTextureMemory = MB_TO_BYTES(256);
|
||||
|
@ -498,10 +501,6 @@ Menu::Menu() {
|
|||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderMyLookAtVectors, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderOtherLookAtVectors, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::FixGaze, 0, false);
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisplayLeftFootTrace, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawLeftFootTrace(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::DisplayRightFootTrace, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawRightFootTrace(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawDefaultPose, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawDefaultPose(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AnimDebugDrawAnimPose, 0, false,
|
||||
|
@ -518,8 +517,6 @@ Menu::Menu() {
|
|||
avatar.get(), SLOT(setEnableInverseKinematics(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderSensorToWorldMatrix, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawSensorToWorldMatrix(bool)));
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::RenderIKTargets, 0, false,
|
||||
avatar.get(), SLOT(setEnableDebugDrawIKTargets(bool)));
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ActionMotorControl,
|
||||
Qt::CTRL | Qt::SHIFT | Qt::Key_K, true, avatar.get(), SLOT(updateMotionBehaviorFromMenu()),
|
||||
|
@ -529,18 +526,10 @@ Menu::Menu() {
|
|||
avatar.get(), SLOT(updateMotionBehaviorFromMenu()),
|
||||
UNSPECIFIED_POSITION, "Developer");
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableAvatarCollisions, 0, true,
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableCharacterController, 0, true,
|
||||
avatar.get(), SLOT(updateMotionBehaviorFromMenu()),
|
||||
UNSPECIFIED_POSITION, "Developer");
|
||||
|
||||
// KINEMATIC_CONTROLLER_HACK
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::MoveKinematically, 0, false,
|
||||
avatar.get(), SLOT(updateMotionBehaviorFromMenu()),
|
||||
UNSPECIFIED_POSITION, "Developer");
|
||||
|
||||
addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::EnableVerticalComfortMode, 0, false,
|
||||
avatar.get(), SLOT(setEnableVerticalComfortMode(bool)));
|
||||
|
||||
// Developer > Hands >>>
|
||||
MenuWrapper* handOptionsMenu = developerMenu->addMenu("Hands");
|
||||
addCheckableActionToQMenuAndActionHash(handOptionsMenu, MenuOption::DisplayHandTargets, 0, false,
|
||||
|
@ -589,7 +578,9 @@ Menu::Menu() {
|
|||
#endif
|
||||
|
||||
|
||||
|
||||
// Developer >> Tests >>>
|
||||
MenuWrapper* testMenu = developerMenu->addMenu("Tests");
|
||||
addActionToQMenuAndActionHash(testMenu, MenuOption::RunClientScriptTests, 0, dialogsManager.data(), SLOT(showTestingResults()));
|
||||
|
||||
// Developer > Timing >>>
|
||||
MenuWrapper* timingMenu = developerMenu->addMenu("Timing");
|
||||
|
|
|
@ -87,8 +87,6 @@ namespace MenuOption {
|
|||
const QString DiskCacheEditor = "Disk Cache Editor";
|
||||
const QString DisplayCrashOptions = "Display Crash Options";
|
||||
const QString DisplayHandTargets = "Show Hand Targets";
|
||||
const QString DisplayLeftFootTrace = "Show Left Foot Trace";
|
||||
const QString DisplayRightFootTrace = "Show Right Foot Trace";
|
||||
const QString DisplayModelBounds = "Display Model Bounds";
|
||||
const QString DisplayModelTriangles = "Display Model Triangles";
|
||||
const QString DisplayModelElementChildProxies = "Display Model Element Children";
|
||||
|
@ -98,11 +96,8 @@ namespace MenuOption {
|
|||
const QString DontRenderEntitiesAsScene = "Don't Render Entities as Scene";
|
||||
const QString EchoLocalAudio = "Echo Local Audio";
|
||||
const QString EchoServerAudio = "Echo Server Audio";
|
||||
const QString EnableAvatarCollisions = "Enable Avatar Collisions";
|
||||
const QString EnableIncrementalTextureTransfer = "Enable Incremental Texture Transfer";
|
||||
const QString EnableDynamicTextureManagement = "Enable Dynamic Texture Management";
|
||||
const QString EnableCharacterController = "Enable avatar collisions";
|
||||
const QString EnableInverseKinematics = "Enable Inverse Kinematics";
|
||||
const QString EnableVerticalComfortMode = "Enable Vertical Comfort Mode";
|
||||
const QString ExpandMyAvatarSimulateTiming = "Expand /myAvatar/simulation";
|
||||
const QString ExpandMyAvatarTiming = "Expand /myAvatar";
|
||||
const QString ExpandOtherAvatarTiming = "Expand /otherAvatar";
|
||||
|
@ -156,6 +151,7 @@ namespace MenuOption {
|
|||
const QString RenderOtherLookAtVectors = "Show Other Eye Vectors";
|
||||
const QString RenderMaxTextureMemory = "Maximum Texture Memory";
|
||||
const QString RenderMaxTextureAutomatic = "Automatic Texture Memory";
|
||||
const QString RenderMaxTexture4MB = "4 MB";
|
||||
const QString RenderMaxTexture64MB = "64 MB";
|
||||
const QString RenderMaxTexture256MB = "256 MB";
|
||||
const QString RenderMaxTexture512MB = "512 MB";
|
||||
|
@ -168,10 +164,10 @@ namespace MenuOption {
|
|||
const QString RenderResolutionThird = "1/3";
|
||||
const QString RenderResolutionQuarter = "1/4";
|
||||
const QString RenderSensorToWorldMatrix = "Show SensorToWorld Matrix";
|
||||
const QString RenderIKTargets = "Show IK Targets";
|
||||
const QString ResetAvatarSize = "Reset Avatar Size";
|
||||
const QString ResetSensors = "Reset Sensors";
|
||||
const QString RunningScripts = "Running Scripts...";
|
||||
const QString RunClientScriptTests = "Run Client Script Tests";
|
||||
const QString RunTimingTests = "Run Timing Tests";
|
||||
const QString ScriptEditor = "Script Editor...";
|
||||
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
|
||||
|
@ -197,7 +193,6 @@ namespace MenuOption {
|
|||
const QString UseAudioForMouth = "Use Audio for Mouth";
|
||||
const QString UseCamera = "Use Camera";
|
||||
const QString UseAnimPreAndPostRotations = "Use Anim Pre and Post Rotations";
|
||||
const QString MoveKinematically = "Move Kinematically"; // KINEMATIC_CONTROLLER_HACK
|
||||
const QString VelocityFilter = "Velocity Filter";
|
||||
const QString VisibleToEveryone = "Everyone";
|
||||
const QString VisibleToFriends = "Friends";
|
||||
|
|
465
interface/src/avatar/MyAvatar.cpp
Executable file → Normal file
465
interface/src/avatar/MyAvatar.cpp
Executable file → Normal file
|
@ -36,7 +36,6 @@
|
|||
#include <UserActivityLogger.h>
|
||||
#include <AnimDebugDraw.h>
|
||||
#include <AnimClip.h>
|
||||
#include <AnimUtil.h>
|
||||
#include <recording/Deck.h>
|
||||
#include <recording/Recorder.h>
|
||||
#include <recording/Clip.h>
|
||||
|
@ -61,7 +60,7 @@ using namespace std;
|
|||
const glm::vec3 DEFAULT_UP_DIRECTION(0.0f, 1.0f, 0.0f);
|
||||
const float DEFAULT_REAL_WORLD_FIELD_OF_VIEW_DEGREES = 30.0f;
|
||||
|
||||
const float MAX_WALKING_SPEED = 2.0f; // human walking speed
|
||||
const float MAX_WALKING_SPEED = 2.6f; // human walking speed
|
||||
const float MAX_BOOST_SPEED = 0.5f * MAX_WALKING_SPEED; // action motor gets additive boost below this speed
|
||||
const float MIN_AVATAR_SPEED = 0.05f;
|
||||
const float MIN_AVATAR_SPEED_SQUARED = MIN_AVATAR_SPEED * MIN_AVATAR_SPEED; // speed is set to zero below this
|
||||
|
@ -84,13 +83,6 @@ const float MyAvatar::ZOOM_MIN = 0.5f;
|
|||
const float MyAvatar::ZOOM_MAX = 25.0f;
|
||||
const float MyAvatar::ZOOM_DEFAULT = 1.5f;
|
||||
|
||||
// OUTOFBODY_HACK defined in SkeletonModel.cpp
|
||||
extern glm::vec3 TRUNCATE_IK_CAPSULE_POSITION;
|
||||
extern float TRUNCATE_IK_CAPSULE_LENGTH;
|
||||
extern float TRUNCATE_IK_CAPSULE_RADIUS;
|
||||
extern float MIN_OUT_OF_BODY_DISTANCE;
|
||||
extern float MAX_OUT_OF_BODY_DISTANCE;
|
||||
|
||||
MyAvatar::MyAvatar(RigPointer rig) :
|
||||
Avatar(rig),
|
||||
_wasPushing(false),
|
||||
|
@ -147,6 +139,8 @@ MyAvatar::MyAvatar(RigPointer rig) :
|
|||
// when we leave a domain we lift whatever restrictions that domain may have placed on our scale
|
||||
connect(&domainHandler, &DomainHandler::disconnectedFromDomain, this, &MyAvatar::clearScaleRestriction);
|
||||
|
||||
_characterController.setEnabled(true);
|
||||
|
||||
_bodySensorMatrix = deriveBodyFromHMDSensor();
|
||||
|
||||
using namespace recording;
|
||||
|
@ -376,27 +370,6 @@ void MyAvatar::update(float deltaTime) {
|
|||
|
||||
simulate(deltaTime);
|
||||
|
||||
// Request to show the hand controllers if we're out-of-body for more then HAND_CONTROLLER_SHOW_TIME.
|
||||
// Similarlly request to hide the controllers when we return to our bodies.
|
||||
const float HAND_CONTROLLER_SHOW_TIME = 0.75f;
|
||||
auto hmdInterface = DependencyManager::get<HMDScriptingInterface>();
|
||||
if (isOutOfBody() != _handControllerShow) {
|
||||
_handControllerShowTimer += deltaTime;
|
||||
if (_handControllerShowTimer > HAND_CONTROLLER_SHOW_TIME) {
|
||||
if (isOutOfBody()) {
|
||||
hmdInterface->requestShowHandControllers();
|
||||
_handControllerShow = true;
|
||||
_handControllerShowTimer = 0.0f;
|
||||
} else {
|
||||
hmdInterface->requestHideHandControllers();
|
||||
_handControllerShow = false;
|
||||
_handControllerShowTimer = 0.0f;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
_handControllerShowTimer = 0.0f;
|
||||
}
|
||||
|
||||
currentEnergy += energyChargeRate;
|
||||
currentEnergy -= getAccelerationEnergy();
|
||||
currentEnergy -= getAudioEnergy();
|
||||
|
@ -464,18 +437,10 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
|
||||
// update sensorToWorldMatrix for camera and hand controllers
|
||||
// before we perform rig animations and IK.
|
||||
|
||||
if (_characterController.getState() != CharacterController::State::Hover) {
|
||||
updateSensorToWorldMatrix(_enableVerticalComfortMode ? SensorToWorldUpdateMode::VerticalComfort : SensorToWorldUpdateMode::Vertical);
|
||||
}
|
||||
updateSensorToWorldMatrix();
|
||||
|
||||
{
|
||||
PerformanceTimer perfTimer("skeleton");
|
||||
|
||||
if (_rig) {
|
||||
_rig->setEnableDebugDrawIKTargets(_enableDebugDrawIKTargets);
|
||||
}
|
||||
|
||||
_skeletonModel->simulate(deltaTime);
|
||||
}
|
||||
|
||||
|
@ -556,8 +521,8 @@ void MyAvatar::simulate(float deltaTime) {
|
|||
}
|
||||
});
|
||||
_characterController.setFlyingAllowed(flyingAllowed);
|
||||
if (!ghostingAllowed && _characterController.getCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
_characterController.setCollisionGroup(BULLET_COLLISION_GROUP_MY_AVATAR);
|
||||
if (!_characterController.isEnabled() && !ghostingAllowed) {
|
||||
_characterController.setEnabled(true);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -600,27 +565,11 @@ void MyAvatar::updateJointFromController(controller::Action poseKey, ThreadSafeV
|
|||
// best called at end of main loop, after physics.
|
||||
// update sensor to world matrix from current body position and hmd sensor.
|
||||
// This is so the correct camera can be used for rendering.
|
||||
void MyAvatar::updateSensorToWorldMatrix(SensorToWorldUpdateMode mode) {
|
||||
if (mode == SensorToWorldUpdateMode::Full) {
|
||||
glm::mat4 bodyToWorld = createMatFromQuatAndPos(getOrientation(), getPosition());
|
||||
setSensorToWorldMatrix(bodyToWorld * glm::inverse(_bodySensorMatrix));
|
||||
} else if (mode == SensorToWorldUpdateMode::Vertical ||
|
||||
mode == SensorToWorldUpdateMode::VerticalComfort) {
|
||||
glm::mat4 bodyToWorld = createMatFromQuatAndPos(getOrientation(), getPosition());
|
||||
glm::mat4 newSensorToWorldMat = bodyToWorld * glm::inverse(_bodySensorMatrix);
|
||||
glm::mat4 sensorToWorldMat = _sensorToWorldMatrix;
|
||||
sensorToWorldMat[3][1] = newSensorToWorldMat[3][1];
|
||||
if (mode == SensorToWorldUpdateMode::VerticalComfort &&
|
||||
fabsf(_sensorToWorldMatrix[3][1] - newSensorToWorldMat[3][1]) > 0.1f) {
|
||||
setSensorToWorldMatrix(sensorToWorldMat);
|
||||
} else if (mode == SensorToWorldUpdateMode::Vertical) {
|
||||
setSensorToWorldMatrix(sensorToWorldMat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setSensorToWorldMatrix(const glm::mat4& sensorToWorld) {
|
||||
_sensorToWorldMatrix = sensorToWorld;
|
||||
void MyAvatar::updateSensorToWorldMatrix() {
|
||||
// update the sensor mat so that the body position will end up in the desired
|
||||
// position when driven from the head.
|
||||
glm::mat4 desiredMat = createMatFromQuatAndPos(getOrientation(), getPosition());
|
||||
_sensorToWorldMatrix = desiredMat * glm::inverse(_bodySensorMatrix);
|
||||
|
||||
lateUpdatePalms();
|
||||
|
||||
|
@ -862,14 +811,6 @@ float loadSetting(Settings& settings, const QString& name, float defaultValue) {
|
|||
return value;
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawLeftFootTrace(bool isEnabled) {
|
||||
_enableDebugDrawLeftFootTrace = isEnabled;
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawRightFootTrace(bool isEnabled) {
|
||||
_enableDebugDrawRightFootTrace = isEnabled;
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawDefaultPose(bool isEnabled) {
|
||||
_enableDebugDrawDefaultPose = isEnabled;
|
||||
|
||||
|
@ -912,11 +853,6 @@ void MyAvatar::setEnableDebugDrawSensorToWorldMatrix(bool isEnabled) {
|
|||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableDebugDrawIKTargets(bool isEnabled) {
|
||||
_enableDebugDrawIKTargets = isEnabled;
|
||||
}
|
||||
|
||||
|
||||
void MyAvatar::setEnableMeshVisible(bool isEnabled) {
|
||||
render::ScenePointer scene = qApp->getMain3DScene();
|
||||
_skeletonModel->setVisibleInScene(isEnabled, scene);
|
||||
|
@ -931,10 +867,6 @@ void MyAvatar::setEnableInverseKinematics(bool isEnabled) {
|
|||
_rig->setEnableInverseKinematics(isEnabled);
|
||||
}
|
||||
|
||||
void MyAvatar::setEnableVerticalComfortMode(bool isEnabled) {
|
||||
_enableVerticalComfortMode = isEnabled;
|
||||
}
|
||||
|
||||
void MyAvatar::loadData() {
|
||||
Settings settings;
|
||||
settings.beginGroup("Avatar");
|
||||
|
@ -1282,8 +1214,6 @@ void MyAvatar::rebuildCollisionShape() {
|
|||
float scale = getUniformScale();
|
||||
float radius = scale * _skeletonModel->getBoundingCapsuleRadius();
|
||||
float height = scale * _skeletonModel->getBoundingCapsuleHeight() + 2.0f * radius;
|
||||
const float CANONICAL_AVATAR_HEIGHT = 2.0f;
|
||||
_canonicalScale = height / CANONICAL_AVATAR_HEIGHT;
|
||||
glm::vec3 corner(-radius, -0.5f * height, -radius);
|
||||
corner += scale * _skeletonModel->getBoundingCapsuleOffset();
|
||||
glm::vec3 diagonal(2.0f * radius, height, 2.0f * radius);
|
||||
|
@ -1341,14 +1271,10 @@ controller::Pose MyAvatar::getRightHandControllerPoseInAvatarFrame() const {
|
|||
}
|
||||
|
||||
void MyAvatar::updateMotors() {
|
||||
const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
|
||||
_characterController.clearMotors();
|
||||
glm::quat motorRotation;
|
||||
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
|
||||
if (_characterController.getState() == CharacterController::State::Hover ||
|
||||
_characterController.getCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
if (_characterController.getState() == CharacterController::State::Hover) {
|
||||
motorRotation = getHead()->getCameraOrientation();
|
||||
} else {
|
||||
// non-hovering = walking: follow camera twist about vertical but not lift
|
||||
|
@ -1356,18 +1282,14 @@ void MyAvatar::updateMotors() {
|
|||
glm::quat liftRotation;
|
||||
swingTwistDecomposition(getHead()->getCameraOrientation(), _worldUpDirection, liftRotation, motorRotation);
|
||||
}
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
// OUTOFBODY_HACK: in HMDMode motors are applied differently: a "follow" motor is added
|
||||
// during the CharacterController's substep
|
||||
const float DEFAULT_MOTOR_TIMESCALE = 0.2f;
|
||||
const float INVALID_MOTOR_TIMESCALE = 1.0e6f;
|
||||
if (_isPushing || _isBraking || !_isBeingPushed) {
|
||||
_characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE);
|
||||
} else {
|
||||
if (_isPushing || _isBraking || !_isBeingPushed) {
|
||||
_characterController.addMotor(_actionMotorVelocity, motorRotation, DEFAULT_MOTOR_TIMESCALE, INVALID_MOTOR_TIMESCALE);
|
||||
} else {
|
||||
// _isBeingPushed must be true --> disable action motor by giving it a long timescale,
|
||||
// otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts
|
||||
_characterController.addMotor(_actionMotorVelocity, motorRotation, INVALID_MOTOR_TIMESCALE);
|
||||
}
|
||||
// _isBeingPushed must be true --> disable action motor by giving it a long timescale,
|
||||
// otherwise it's attempt to "stand in in place" could defeat scripted motor/thrusts
|
||||
_characterController.addMotor(_actionMotorVelocity, motorRotation, INVALID_MOTOR_TIMESCALE);
|
||||
}
|
||||
}
|
||||
if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) {
|
||||
|
@ -1379,19 +1301,16 @@ void MyAvatar::updateMotors() {
|
|||
// world-frame
|
||||
motorRotation = glm::quat();
|
||||
}
|
||||
if (qApp->isHMDMode()) {
|
||||
// OUTOFBODY_HACK: motors are applied differently in HMDMode
|
||||
} else {
|
||||
_characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale);
|
||||
}
|
||||
_characterController.addMotor(_scriptedMotorVelocity, motorRotation, _scriptedMotorTimescale);
|
||||
}
|
||||
|
||||
// legacy support for 'MyAvatar::applyThrust()', which has always been implemented as a
|
||||
// short-lived linearAcceleration
|
||||
_characterController.setLinearAcceleration(glm::vec3(0.0f, _thrust.y, 0.0f));
|
||||
_characterController.setLinearAcceleration(_thrust);
|
||||
_thrust = Vectors::ZERO;
|
||||
}
|
||||
|
||||
void MyAvatar::prepareForPhysicsSimulation(float deltaTime) {
|
||||
void MyAvatar::prepareForPhysicsSimulation() {
|
||||
relayDriveKeysToCharacterController();
|
||||
updateMotors();
|
||||
|
||||
|
@ -1401,54 +1320,32 @@ void MyAvatar::prepareForPhysicsSimulation(float deltaTime) {
|
|||
qDebug() << "Warning: getParentVelocity failed" << getID();
|
||||
parentVelocity = glm::vec3();
|
||||
}
|
||||
_characterController.handleChangedCollisionGroup();
|
||||
_characterController.setParentVelocity(parentVelocity);
|
||||
|
||||
glm::vec3 position = getPosition();
|
||||
glm::quat orientation = getOrientation();
|
||||
|
||||
_characterController.setPositionAndOrientation(position, orientation);
|
||||
_characterController.setPositionAndOrientation(getPosition(), getOrientation());
|
||||
if (qApp->isHMDMode()) {
|
||||
// update the _bodySensorMatrix based on leaning behavior of the avatar.
|
||||
_bodySensorMatrix = _follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput(), deltaTime);
|
||||
|
||||
// The avatar physics body always follows the _bodySensorMatrix.
|
||||
glm::mat4 worldBodyMatrix = _sensorToWorldMatrix * _bodySensorMatrix;
|
||||
getCharacterController()->setFollowParameters(worldBodyMatrix);
|
||||
_follow.prePhysicsUpdate(*this, deriveBodyFromHMDSensor(), _bodySensorMatrix, hasDriveInput());
|
||||
} else {
|
||||
_follow.deactivate();
|
||||
getCharacterController()->disableFollow();
|
||||
}
|
||||
|
||||
_prePhysicsRoomPose = AnimPose(_sensorToWorldMatrix);
|
||||
}
|
||||
|
||||
void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) {
|
||||
// figure out how far the hips can move before they hit something
|
||||
int hipsJoint = getJointIndex("Hips");
|
||||
glm::vec3 hipsPosition; // rig-frame
|
||||
// OUTOFBODY_HACK -- hardcoded maxHipsOffsetRadius (ultimately must exceed FollowHelper lateral/forward/back walk thresholds)
|
||||
float maxHipsOffsetRadius = 3.0f * _characterController.getCapsuleRadius();
|
||||
if (_rig->getJointPosition(hipsJoint, hipsPosition)) {
|
||||
// OUTOFBODY_HACK -- flip PI about yAxis
|
||||
hipsPosition.x *= -1.0f;
|
||||
hipsPosition.z *= -1.0f;
|
||||
maxHipsOffsetRadius = _characterController.measureMaxHipsOffsetRadius(hipsPosition, maxHipsOffsetRadius);
|
||||
}
|
||||
_rig->updateMaxHipsOffsetLength(maxHipsOffsetRadius, deltaTime);
|
||||
|
||||
glm::vec3 position = getPosition();
|
||||
glm::quat orientation = getOrientation();
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
_characterController.getPositionAndOrientation(position, orientation);
|
||||
}
|
||||
nextAttitude(position, orientation);
|
||||
_bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix);
|
||||
|
||||
if (_characterController.isEnabledAndReady()) {
|
||||
setVelocity(_characterController.getLinearVelocity());
|
||||
setVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity());
|
||||
} else {
|
||||
setVelocity(getVelocity() + _characterController.getFollowVelocity());
|
||||
}
|
||||
|
||||
_follow.postPhysicsUpdate(*this);
|
||||
}
|
||||
|
||||
QString MyAvatar::getScriptedMotorFrame() const {
|
||||
|
@ -1674,26 +1571,8 @@ void MyAvatar::postUpdate(float deltaTime) {
|
|||
DebugDraw::getInstance().updateMyAvatarPos(getPosition());
|
||||
DebugDraw::getInstance().updateMyAvatarRot(getOrientation());
|
||||
|
||||
if (_enableDebugDrawLeftFootTrace || _enableDebugDrawRightFootTrace) {
|
||||
int boneIndex = _enableDebugDrawLeftFootTrace ? getJointIndex("LeftFoot") : getJointIndex("RightFoot");
|
||||
const glm::vec4 RED(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
const glm::vec4 WHITE(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
const glm::vec4 TRANS(1.0f, 1.0f, 1.0f, 0.0f);
|
||||
static bool colorBit = true;
|
||||
colorBit = !colorBit;
|
||||
glm::vec4 color = colorBit ? RED : WHITE;
|
||||
|
||||
_debugLineLoop[_debugLineLoopIndex] = DebugDrawVertex(getJointPosition(boneIndex), color);
|
||||
_debugLineLoopIndex = (_debugLineLoopIndex + 1) % DEBUG_LINE_LOOP_SIZE;
|
||||
_debugLineLoop[_debugLineLoopIndex] = DebugDrawVertex(getJointPosition(boneIndex), TRANS);
|
||||
for (size_t prev = DEBUG_LINE_LOOP_SIZE - 1, next = 0; next < DEBUG_LINE_LOOP_SIZE; prev = next, next++) {
|
||||
if (_debugLineLoop[prev].color.w > 0.0f) {
|
||||
DebugDraw::getInstance().drawRay(_debugLineLoop[prev].pos, _debugLineLoop[next].pos, _debugLineLoop[prev].color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimPose postUpdateRoomPose(_sensorToWorldMatrix);
|
||||
|
||||
updateHoldActions(_prePhysicsRoomPose, postUpdateRoomPose);
|
||||
}
|
||||
|
||||
|
@ -1789,20 +1668,11 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
|
||||
|
||||
// update body orientation by movement inputs
|
||||
glm::quat deltaRotation = glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)));
|
||||
setOrientation(getOrientation() * deltaRotation);
|
||||
setOrientation(getOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
|
||||
|
||||
getHead()->setBasePitch(getHead()->getBasePitch() + _driveKeys[PITCH] * _pitchSpeed * deltaTime);
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
|
||||
// rotate the sensorToWorldMatrix about the HMD!
|
||||
glm::vec3 hmdOffset = extractTranslation(getHMDSensorMatrix());
|
||||
_sensorToWorldMatrix = (_sensorToWorldMatrix *
|
||||
createMatFromQuatAndPos(glm::quat(), hmdOffset) *
|
||||
createMatFromQuatAndPos(deltaRotation, glm::vec3()) *
|
||||
createMatFromQuatAndPos(glm::quat(), -hmdOffset));
|
||||
|
||||
glm::quat orientation = glm::quat_cast(getSensorToWorldMatrix()) * getHMDSensorOrientation();
|
||||
glm::quat bodyOrientation = getWorldBodyOrientation();
|
||||
glm::quat localOrientation = glm::inverse(bodyOrientation) * orientation;
|
||||
|
@ -1819,7 +1689,6 @@ void MyAvatar::updateOrientation(float deltaTime) {
|
|||
}
|
||||
|
||||
void MyAvatar::updateActionMotor(float deltaTime) {
|
||||
|
||||
bool thrustIsPushing = (glm::length2(_thrust) > EPSILON);
|
||||
bool scriptedMotorIsPushing = (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED)
|
||||
&& _scriptedMotorTimescale < MAX_CHARACTER_MOTOR_TIMESCALE;
|
||||
|
@ -1859,11 +1728,11 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
|
||||
if (state == CharacterController::State::Hover) {
|
||||
// we're flying --> complex acceleration curve that builds on top of current motor speed and caps at some max speed
|
||||
float motorSpeed = glm::length(glm::vec3(_actionMotorVelocity.x, _actionMotorVelocity.y, _actionMotorVelocity.z));
|
||||
float motorSpeed = glm::length(_actionMotorVelocity);
|
||||
float finalMaxMotorSpeed = getUniformScale() * MAX_ACTION_MOTOR_SPEED;
|
||||
float speedGrowthTimescale = 2.0f;
|
||||
float speedIncreaseFactor = 1.8f;
|
||||
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor;
|
||||
motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale , 0.0f, 1.0f) * speedIncreaseFactor;
|
||||
const float maxBoostSpeed = getUniformScale() * MAX_BOOST_SPEED;
|
||||
|
||||
if (_isPushing) {
|
||||
|
@ -1886,21 +1755,6 @@ void MyAvatar::updateActionMotor(float deltaTime) {
|
|||
_boomLength = glm::clamp<float>(_boomLength, ZOOM_MIN, ZOOM_MAX);
|
||||
}
|
||||
|
||||
void MyAvatar::applyVelocityToSensorToWorldMatrix(const glm::vec3& velocity, float deltaTime) {
|
||||
glm::vec3 newVelocity = velocity;
|
||||
if (_characterController.getState() != CharacterController::State::Hover) {
|
||||
newVelocity -= glm::dot(newVelocity, _worldUpDirection);
|
||||
}
|
||||
float speed2 = glm::length2(newVelocity);
|
||||
if (speed2 > MIN_AVATAR_SPEED_SQUARED) {
|
||||
glm::vec3 position = extractTranslation(_sensorToWorldMatrix) + deltaTime * newVelocity;
|
||||
// update the position column of matrix
|
||||
glm::mat4 newSensorToWorldMatrix = _sensorToWorldMatrix;
|
||||
newSensorToWorldMatrix[3] = glm::vec4(position, 1.0f);
|
||||
setSensorToWorldMatrix(newSensorToWorldMatrix);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::updatePosition(float deltaTime) {
|
||||
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
|
||||
updateActionMotor(deltaTime);
|
||||
|
@ -1921,61 +1775,11 @@ void MyAvatar::updatePosition(float deltaTime) {
|
|||
measureMotionDerivatives(deltaTime);
|
||||
_moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED;
|
||||
} else {
|
||||
// physics physics simulation updated elsewhere
|
||||
float speed2 = glm::length2(velocity);
|
||||
_moving = speed2 > MOVING_SPEED_THRESHOLD_SQUARED;
|
||||
|
||||
if (_moving) {
|
||||
// scan for walkability
|
||||
glm::vec3 position = getPosition();
|
||||
MyCharacterController::RayShotgunResult result;
|
||||
glm::vec3 step = deltaTime * (getRotation() * _actionMotorVelocity);
|
||||
_characterController.testRayShotgun(position, step, result);
|
||||
_characterController.setStepUpEnabled(result.walkable);
|
||||
}
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
glm::quat motorRotation;
|
||||
glm::vec3 worldVelocity = glm::vec3(0.0f);
|
||||
if (_motionBehaviors & AVATAR_MOTION_ACTION_MOTOR_ENABLED) {
|
||||
if (_characterController.getState() == CharacterController::State::Hover ||
|
||||
_characterController.getCollisionGroup() == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
motorRotation = glmExtractRotation(_sensorToWorldMatrix * getHMDSensorMatrix());
|
||||
} else {
|
||||
glm::quat liftRotation;
|
||||
swingTwistDecomposition(glmExtractRotation(_sensorToWorldMatrix * getHMDSensorMatrix()), _worldUpDirection, liftRotation, motorRotation);
|
||||
}
|
||||
worldVelocity = motorRotation * _actionMotorVelocity;
|
||||
}
|
||||
|
||||
if (_motionBehaviors & AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED) {
|
||||
if (_scriptedMotorFrame == SCRIPTED_MOTOR_CAMERA_FRAME) {
|
||||
motorRotation = getHead()->getCameraOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
|
||||
} else if (_scriptedMotorFrame == SCRIPTED_MOTOR_AVATAR_FRAME) {
|
||||
motorRotation = getOrientation() * glm::angleAxis(PI, Vectors::UNIT_Y);
|
||||
} else {
|
||||
// world-frame
|
||||
motorRotation = glm::quat();
|
||||
}
|
||||
worldVelocity += motorRotation * _scriptedMotorVelocity;
|
||||
}
|
||||
|
||||
// OUTOFBODY_HACK: apply scaling factor to _thrust, to get the same behavior as an periodically applied motor.
|
||||
const float THRUST_DAMPING_FACTOR = 0.25f;
|
||||
worldVelocity += THRUST_DAMPING_FACTOR * _thrust;
|
||||
|
||||
// apply velocity directly to _sensorToWorldMatrix.
|
||||
if (glm::length2(worldVelocity) > FLT_EPSILON) {
|
||||
glm::mat4 worldBodyMatrix = _sensorToWorldMatrix * _bodySensorMatrix;
|
||||
glm::vec3 position = extractTranslation(worldBodyMatrix);
|
||||
glm::vec3 step = deltaTime * worldVelocity;
|
||||
glm::vec3 newVelocity = _characterController.computeHMDStep(position, step) / deltaTime;
|
||||
applyVelocityToSensorToWorldMatrix(newVelocity, deltaTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_thrust = Vectors::ZERO;
|
||||
|
||||
// capture the head rotation, in sensor space, when the user first indicates they would like to move/fly.
|
||||
if (!_hoverReferenceCameraFacingIsCaptured && (fabs(_driveKeys[TRANSLATE_Z]) > 0.1f || fabs(_driveKeys[TRANSLATE_X]) > 0.1f)) {
|
||||
_hoverReferenceCameraFacingIsCaptured = true;
|
||||
|
@ -2028,10 +1832,6 @@ bool findAvatarAvatarPenetration(const glm::vec3 positionA, float radiusA, float
|
|||
return false;
|
||||
}
|
||||
|
||||
glm::vec3 MyAvatar::getPreActionVelocity() const {
|
||||
return _characterController.getPreActionLinearVelocity();
|
||||
}
|
||||
|
||||
// There can be a separation between the _targetScale and the actual scale of the rendered avatar in a domain.
|
||||
// When the avatar enters a domain where their target scale is not allowed according to the min/max
|
||||
// we do not change their saved target scale. Instead, we use getDomainLimitedScale() to render the avatar
|
||||
|
@ -2215,17 +2015,13 @@ void MyAvatar::updateMotionBehaviorFromMenu() {
|
|||
_motionBehaviors &= ~AVATAR_MOTION_SCRIPTED_MOTOR_ENABLED;
|
||||
}
|
||||
|
||||
// KINEMATIC_CONTROLLER_HACK
|
||||
bool moveKinematically = menu->isOptionChecked(MenuOption::MoveKinematically);
|
||||
_characterController.setMoveKinematically(moveKinematically);
|
||||
|
||||
setAvatarCollisionsEnabled(menu->isOptionChecked(MenuOption::EnableAvatarCollisions));
|
||||
setCharacterControllerEnabled(menu->isOptionChecked(MenuOption::EnableCharacterController));
|
||||
}
|
||||
|
||||
void MyAvatar::setAvatarCollisionsEnabled(bool enabled) {
|
||||
void MyAvatar::setCharacterControllerEnabled(bool enabled) {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setAvatarCollisionsEnabled", Q_ARG(bool, enabled));
|
||||
QMetaObject::invokeMethod(this, "setCharacterControllerEnabled", Q_ARG(bool, enabled));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2237,12 +2033,11 @@ void MyAvatar::setAvatarCollisionsEnabled(bool enabled) {
|
|||
ghostingAllowed = zone->getGhostingAllowed();
|
||||
}
|
||||
}
|
||||
int16_t group = enabled || !ghostingAllowed ? BULLET_COLLISION_GROUP_MY_AVATAR : BULLET_COLLISION_GROUP_COLLISIONLESS;
|
||||
_characterController.setCollisionGroup(group);
|
||||
_characterController.setEnabled(ghostingAllowed ? enabled : true);
|
||||
}
|
||||
|
||||
bool MyAvatar::getAvatarCollisionsEnabled() {
|
||||
return _characterController.getCollisionGroup() != BULLET_COLLISION_GROUP_COLLISIONLESS;
|
||||
bool MyAvatar::getCharacterControllerEnabled() {
|
||||
return _characterController.isEnabled();
|
||||
}
|
||||
|
||||
void MyAvatar::clearDriveKeys() {
|
||||
|
@ -2330,10 +2125,6 @@ glm::quat MyAvatar::getOrientationForAudio() {
|
|||
return quat();
|
||||
}
|
||||
|
||||
bool MyAvatar::isOutOfBody() const {
|
||||
return _follow._isOutOfBody;
|
||||
}
|
||||
|
||||
void MyAvatar::setAudioListenerMode(AudioListenerMode audioListenerMode) {
|
||||
if (_audioListenerMode != audioListenerMode) {
|
||||
_audioListenerMode = audioListenerMode;
|
||||
|
@ -2354,52 +2145,72 @@ void MyAvatar::lateUpdatePalms() {
|
|||
Avatar::updatePalms();
|
||||
}
|
||||
|
||||
|
||||
static const float FOLLOW_TIME = 0.5f;
|
||||
|
||||
MyAvatar::FollowHelper::FollowHelper() {
|
||||
deactivate();
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::deactivate() {
|
||||
_activeBits = 0;
|
||||
for (int i = 0; i < NumFollowTypes; i++) {
|
||||
deactivate((FollowType)i);
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::deactivate(FollowType type) {
|
||||
assert(type >= 0 && type < NumFollowTypes);
|
||||
_activeBits &= ~(uint8_t)(0x01 << (int)type);
|
||||
_timeRemaining[(int)type] = 0.0f;
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::activate(FollowType type) {
|
||||
assert(type >= 0 && type < NumFollowTypes);
|
||||
_activeBits |= (uint8_t)(0x01 << (int)type);
|
||||
// TODO: Perhaps, the follow time should be proportional to the displacement.
|
||||
_timeRemaining[(int)type] = FOLLOW_TIME;
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::isActive(FollowType type) const {
|
||||
assert(type >= 0 && type < NumFollowTypes);
|
||||
return (bool)(_activeBits & (uint8_t)(0x01 << (int)type));
|
||||
return _timeRemaining[(int)type] > 0.0f;
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::isActive() const {
|
||||
return (bool)_activeBits;
|
||||
for (int i = 0; i < NumFollowTypes; i++) {
|
||||
if (isActive((FollowType)i)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::updateRotationActivation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) {
|
||||
auto cameraMode = qApp->getCamera()->getMode();
|
||||
if (cameraMode == CAMERA_MODE_THIRD_PERSON) {
|
||||
deactivate(Rotation);
|
||||
} else {
|
||||
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
|
||||
const float STOP_FOLLOW_ROTATION_THRESHOLD = cosf(PI / 180.0f); // 1 degree
|
||||
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
|
||||
if (isActive(Rotation)) {
|
||||
if (glm::dot(myAvatar.getHMDSensorFacing(), bodyFacing) > STOP_FOLLOW_ROTATION_THRESHOLD) {
|
||||
deactivate(Rotation);
|
||||
}
|
||||
} else if (glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD) {
|
||||
activate(Rotation);
|
||||
float MyAvatar::FollowHelper::getMaxTimeRemaining() const {
|
||||
float max = 0.0f;
|
||||
for (int i = 0; i < NumFollowTypes; i++) {
|
||||
if (_timeRemaining[i] > max) {
|
||||
max = _timeRemaining[i];
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::decrementTimeRemaining(float dt) {
|
||||
for (int i = 0; i < NumFollowTypes; i++) {
|
||||
_timeRemaining[i] -= dt;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::updateHorizontalActivation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) {
|
||||
bool MyAvatar::FollowHelper::shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
|
||||
auto cameraMode = qApp->getCamera()->getMode();
|
||||
if (cameraMode == CAMERA_MODE_THIRD_PERSON) {
|
||||
return false;
|
||||
} else {
|
||||
const float FOLLOW_ROTATION_THRESHOLD = cosf(PI / 6.0f); // 30 degrees
|
||||
glm::vec2 bodyFacing = getFacingDir2D(currentBodyMatrix);
|
||||
return glm::dot(myAvatar.getHMDSensorFacingMovingAverage(), bodyFacing) < FOLLOW_ROTATION_THRESHOLD;
|
||||
}
|
||||
}
|
||||
|
||||
bool MyAvatar::FollowHelper::shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
|
||||
|
||||
// -z axis of currentBodyMatrix in world space.
|
||||
glm::vec3 forward = glm::normalize(glm::vec3(-currentBodyMatrix[0][2], -currentBodyMatrix[1][2], -currentBodyMatrix[2][2]));
|
||||
|
@ -2408,86 +2219,88 @@ void MyAvatar::FollowHelper::updateHorizontalActivation(const MyAvatar& myAvatar
|
|||
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
|
||||
|
||||
float forwardLeanAmount = glm::dot(forward, offset);
|
||||
float lateralLeanAmount = fabsf(glm::dot(right, offset));
|
||||
float lateralLeanAmount = glm::dot(right, offset);
|
||||
|
||||
const float MAX_LATERAL_LEAN = 0.3f;
|
||||
const float MAX_FORWARD_LEAN = 0.15f;
|
||||
const float MAX_BACKWARD_LEAN = 0.1f;
|
||||
const float MIN_LEAN = 0.02f;
|
||||
|
||||
if (isActive(Horizontal)) {
|
||||
if (fabsf(forwardLeanAmount) < MIN_LEAN && lateralLeanAmount < MIN_LEAN) {
|
||||
deactivate(Horizontal);
|
||||
}
|
||||
} else {
|
||||
if (forwardLeanAmount > MAX_FORWARD_LEAN ||
|
||||
forwardLeanAmount < -MAX_BACKWARD_LEAN ||
|
||||
lateralLeanAmount > MAX_LATERAL_LEAN) {
|
||||
activate(Horizontal);
|
||||
}
|
||||
if (forwardLeanAmount > 0 && forwardLeanAmount > MAX_FORWARD_LEAN) {
|
||||
return true;
|
||||
} else if (forwardLeanAmount < 0 && forwardLeanAmount < -MAX_BACKWARD_LEAN) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return fabs(lateralLeanAmount) > MAX_LATERAL_LEAN;
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::updateVerticalActivation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) {
|
||||
bool MyAvatar::FollowHelper::shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const {
|
||||
|
||||
const float CYLINDER_TOP = 0.1f;
|
||||
const float CYLINDER_BOTTOM = -1.5f;
|
||||
const float MIN_VERTICAL_OFFSET = 0.02f;
|
||||
|
||||
glm::vec3 offset = extractTranslation(desiredBodyMatrix) - extractTranslation(currentBodyMatrix);
|
||||
if (isActive(Vertical)) {
|
||||
if (fabsf(offset.y) < MIN_VERTICAL_OFFSET) {
|
||||
deactivate(Vertical);
|
||||
}
|
||||
} else if (offset.y > CYLINDER_TOP || offset.y < CYLINDER_BOTTOM) {
|
||||
activate(Vertical);
|
||||
}
|
||||
return (offset.y > CYLINDER_TOP) || (offset.y < CYLINDER_BOTTOM);
|
||||
}
|
||||
|
||||
glm::mat4 MyAvatar::FollowHelper::prePhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput, float deltaTime) {
|
||||
void MyAvatar::FollowHelper::prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput) {
|
||||
_desiredBodyMatrix = desiredBodyMatrix;
|
||||
|
||||
if (myAvatar.getHMDLeanRecenterEnabled()) {
|
||||
updateRotationActivation(myAvatar, desiredBodyMatrix, currentBodyMatrix);
|
||||
updateHorizontalActivation(myAvatar, desiredBodyMatrix, currentBodyMatrix);
|
||||
updateVerticalActivation(myAvatar, desiredBodyMatrix, currentBodyMatrix);
|
||||
if (!isActive(Rotation) && shouldActivateRotation(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate(Rotation);
|
||||
}
|
||||
if (!isActive(Horizontal) && shouldActivateHorizontal(myAvatar, desiredBodyMatrix, currentBodyMatrix)) {
|
||||
activate(Horizontal);
|
||||
}
|
||||
if (!isActive(Vertical) && (shouldActivateVertical(myAvatar, desiredBodyMatrix, currentBodyMatrix) || hasDriveInput)) {
|
||||
activate(Vertical);
|
||||
}
|
||||
}
|
||||
|
||||
AnimPose currentBodyPose(currentBodyMatrix);
|
||||
AnimPose desiredBodyPose(desiredBodyMatrix);
|
||||
AnimPose followBodyPose(currentBodyMatrix);
|
||||
glm::mat4 desiredWorldMatrix = myAvatar.getSensorToWorldMatrix() * _desiredBodyMatrix;
|
||||
glm::mat4 currentWorldMatrix = myAvatar.getSensorToWorldMatrix() * currentBodyMatrix;
|
||||
|
||||
if (isActive(Rotation) || hasDriveInput) {
|
||||
followBodyPose.rot = desiredBodyPose.rot;
|
||||
}
|
||||
if (isActive(Horizontal) || hasDriveInput) {
|
||||
followBodyPose.trans.x = desiredBodyPose.trans.x;
|
||||
followBodyPose.trans.z = desiredBodyPose.trans.z;
|
||||
}
|
||||
if (isActive(Vertical) || hasDriveInput) {
|
||||
followBodyPose.trans.y = desiredBodyPose.trans.y;
|
||||
}
|
||||
AnimPose followWorldPose(currentWorldMatrix);
|
||||
if (isActive(Rotation)) {
|
||||
followWorldPose.rot = glmExtractRotation(desiredWorldMatrix);
|
||||
}
|
||||
if (isActive(Horizontal)) {
|
||||
glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix);
|
||||
followWorldPose.trans.x = desiredTranslation.x;
|
||||
followWorldPose.trans.z = desiredTranslation.z;
|
||||
}
|
||||
if (isActive(Vertical)) {
|
||||
glm::vec3 desiredTranslation = extractTranslation(desiredWorldMatrix);
|
||||
followWorldPose.trans.y = desiredTranslation.y;
|
||||
}
|
||||
|
||||
myAvatar.getCharacterController()->setFollowParameters(followWorldPose, getMaxTimeRemaining());
|
||||
}
|
||||
|
||||
glm::mat4 MyAvatar::FollowHelper::postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix) {
|
||||
if (isActive()) {
|
||||
float dt = myAvatar.getCharacterController()->getFollowTime();
|
||||
decrementTimeRemaining(dt);
|
||||
|
||||
// apply follow displacement to the body matrix.
|
||||
glm::vec3 worldLinearDisplacement = myAvatar.getCharacterController()->getFollowLinearDisplacement();
|
||||
glm::quat worldAngularDisplacement = myAvatar.getCharacterController()->getFollowAngularDisplacement();
|
||||
glm::quat sensorToWorld = glmExtractRotation(myAvatar.getSensorToWorldMatrix());
|
||||
glm::quat worldToSensor = glm::inverse(sensorToWorld);
|
||||
|
||||
glm::vec3 sensorLinearDisplacement = worldToSensor * worldLinearDisplacement;
|
||||
glm::quat sensorAngularDisplacement = worldToSensor * worldAngularDisplacement * sensorToWorld;
|
||||
|
||||
glm::mat4 newBodyMat = createMatFromQuatAndPos(sensorAngularDisplacement * glmExtractRotation(currentBodyMatrix),
|
||||
sensorLinearDisplacement + extractTranslation(currentBodyMatrix));
|
||||
return newBodyMat;
|
||||
|
||||
if (isActive() || hasDriveInput) {
|
||||
const float TIMESCALE = 0.2f;
|
||||
const float tau = glm::clamp(deltaTime / TIMESCALE, 0.0f, 1.0f);
|
||||
AnimPose newBodyPose;
|
||||
blend(1, ¤tBodyPose, &followBodyPose, tau, &newBodyPose);
|
||||
return (glm::mat4)newBodyPose;
|
||||
} else {
|
||||
return currentBodyMatrix;
|
||||
}
|
||||
} else {
|
||||
deactivate();
|
||||
return currentBodyMatrix;
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::FollowHelper::postPhysicsUpdate(MyAvatar& myAvatar) {
|
||||
glm::mat4 worldHMDMat = myAvatar.getSensorToWorldMatrix() * myAvatar.getHMDSensorMatrix();
|
||||
glm::vec3 worldHMDPosition = extractTranslation(worldHMDMat);
|
||||
glm::vec3 capsuleStart = myAvatar.getPosition() + Vectors::UNIT_Y * (TRUNCATE_IK_CAPSULE_LENGTH / 2.0f);
|
||||
glm::vec3 capsuleEnd = myAvatar.getPosition() - Vectors::UNIT_Y * (TRUNCATE_IK_CAPSULE_LENGTH / 2.0f);
|
||||
_isOutOfBody = !pointIsInsideCapsule(worldHMDPosition, capsuleStart, capsuleEnd, TRUNCATE_IK_CAPSULE_RADIUS);
|
||||
_outOfBodyDistance = distanceFromCapsule(worldHMDPosition, capsuleStart, capsuleEnd, TRUNCATE_IK_CAPSULE_RADIUS);
|
||||
}
|
||||
|
||||
float MyAvatar::getAccelerationEnergy() {
|
||||
glm::vec3 velocity = getVelocity();
|
||||
int changeInVelocity = abs(velocity.length() - priorVelocity.length());
|
||||
|
|
|
@ -56,8 +56,6 @@ enum AudioListenerMode {
|
|||
};
|
||||
Q_DECLARE_METATYPE(AudioListenerMode);
|
||||
|
||||
const size_t DEBUG_LINE_LOOP_SIZE = 500;
|
||||
|
||||
class MyAvatar : public Avatar {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally)
|
||||
|
@ -86,58 +84,7 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(float energy READ getEnergy WRITE setEnergy)
|
||||
|
||||
Q_PROPERTY(bool hmdLeanRecenterEnabled READ getHMDLeanRecenterEnabled WRITE setHMDLeanRecenterEnabled)
|
||||
Q_PROPERTY(bool avatarCollisionsEnabled READ getAvatarCollisionsEnabled WRITE setAvatarCollisionsEnabled)
|
||||
|
||||
/**jsdoc
|
||||
* Your avatar is your in-world representation of you. The MyAvatar API is used to manipulate the avatar.
|
||||
* For example, using the MyAvatar API you can customize the avatar's appearance, run custom avatar animations,
|
||||
* change the avatar's position within the domain, or manage the avatar's collisions with other objects.
|
||||
* NOTE: MyAvatar extends Avatar and AvatarData, see those namespace for more properties/methods.
|
||||
*
|
||||
* @namespace MyAvatar
|
||||
* @augments Avatar
|
||||
* @property shouldRenderLocally {bool} Set it to true if you would like to see MyAvatar in your local interface,
|
||||
* and false if you would not like to see MyAvatar in your local interface.
|
||||
* @property motorVelocity {Vec3} Can be used to move the avatar with this velocity.
|
||||
* @property motorTimescale {float} Specifies how quickly the avatar should accelerate to meet the motorVelocity,
|
||||
* smaller values will result in higher acceleration.
|
||||
* @property motorReferenceFrame {string} Reference frame of the motorVelocity, must be one of the following: "avatar", "camera", "world"
|
||||
* @property collisionSoundURL {string} Specifies the sound to play when the avatar experiences a collision.
|
||||
* You can provide a mono or stereo 16-bit WAV file running at either 24 Khz or 48 Khz.
|
||||
* The latter is downsampled by the audio mixer, so all audio effectively plays back at a 24 Khz sample rate.
|
||||
* 48 Khz RAW files are also supported.
|
||||
* @property audioListenerMode {number} When hearing spatialized audio this determines where the listener placed.
|
||||
* Should be one of the following values:
|
||||
* MyAvatar.audioListenerModeHead - the listener located at the avatar's head.
|
||||
* MyAvatar.audioListenerModeCamera - the listener is relative to the camera.
|
||||
* MyAvatar.audioListenerModeCustom - the listener is at a custom location specified by the MyAvatar.customListenPosition
|
||||
* and MyAvatar.customListenOrientation properties.
|
||||
* @property customListenPosition {Vec3} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the position
|
||||
* of audio spatialization listener.
|
||||
* @property customListenOreintation {Quat} If MyAvatar.audioListenerMode == MyAvatar.audioListenerModeHead, then this determines the orientation
|
||||
* of the audio spatialization listener.
|
||||
* @property audioListenerModeHead {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener
|
||||
* around the avatar's head.
|
||||
* @property audioListenerModeCamera {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener
|
||||
* around the camera.
|
||||
* @property audioListenerModeCustom {number} READ-ONLY. When passed to MyAvatar.audioListenerMode, it will set the audio listener
|
||||
* around the value specified by MyAvatar.customListenPosition and MyAvatar.customListenOrientation.
|
||||
* @property leftHandPosition {Vec3} READ-ONLY. The desired position of the left wrist in avatar space, determined by the hand controllers.
|
||||
* Note: only valid if hand controllers are in use.
|
||||
* @property rightHandPosition {Vec3} READ-ONLY. The desired position of the right wrist in avatar space, determined by the hand controllers.
|
||||
* Note: only valid if hand controllers are in use.
|
||||
* @property leftHandTipPosition {Vec3} READ-ONLY. A position 30 cm offset from MyAvatar.leftHandPosition
|
||||
* @property rightHandTipPosition {Vec3} READ-ONLY. A position 30 cm offset from MyAvatar.rightHandPosition
|
||||
* @property leftHandPose {Pose} READ-ONLY. Returns full pose (translation, orientation, velocity & angularVelocity) of the desired
|
||||
* wrist position, determined by the hand controllers.
|
||||
* @property rightHandPose {Pose} READ-ONLY. Returns full pose (translation, orientation, velocity & angularVelocity) of the desired
|
||||
* wrist position, determined by the hand controllers.
|
||||
* @property leftHandTipPose {Pose} READ-ONLY. Returns a pose offset 30 cm from MyAvatar.leftHandPose
|
||||
* @property rightHandTipPose {Pose} READ-ONLY. Returns a pose offset 30 cm from MyAvatar.rightHandPose
|
||||
* @property hmdLeanRecenterEnabled {bool} This can be used disable the hmd lean recenter behavior. This behavior is what causes your avatar
|
||||
* to follow your HMD as you walk around the room, in room scale VR. Disabling this is useful if you desire to pin the avatar to a fixed location.
|
||||
* @property avatarCollisionsEnabled {bool} This can be used to disable collisions between the avatar and the world.
|
||||
*/
|
||||
Q_PROPERTY(bool characterControllerEnabled READ getCharacterControllerEnabled WRITE setCharacterControllerEnabled)
|
||||
|
||||
public:
|
||||
explicit MyAvatar(RigPointer rig);
|
||||
|
@ -151,17 +98,7 @@ public:
|
|||
|
||||
void reset(bool andRecenter = false, bool andReload = true, bool andHead = true);
|
||||
|
||||
/**jsdoc
|
||||
* Moves and orients the avatar, such that it is directly underneath the HMD, with toes pointed forward.
|
||||
* @function MyAvatar.centerBody
|
||||
*/
|
||||
Q_INVOKABLE void centerBody(); // thread-safe
|
||||
|
||||
/**jsdoc
|
||||
* The internal inverse-kinematics system maintains a record of which joints are "locked". Sometimes it is useful to forget this history, to prevent
|
||||
* contorted joints.
|
||||
* @function MyAvatar.centerBody
|
||||
*/
|
||||
Q_INVOKABLE void clearIKJointLimitHistory(); // thread-safe
|
||||
|
||||
void update(float deltaTime);
|
||||
|
@ -171,7 +108,6 @@ public:
|
|||
const glm::mat4& getHMDSensorMatrix() const { return _hmdSensorMatrix; }
|
||||
const glm::vec3& getHMDSensorPosition() const { return _hmdSensorPosition; }
|
||||
const glm::quat& getHMDSensorOrientation() const { return _hmdSensorOrientation; }
|
||||
const glm::vec2& getHMDSensorFacing() const { return _hmdSensorFacing; }
|
||||
const glm::vec2& getHMDSensorFacingMovingAverage() const { return _hmdSensorFacingMovingAverage; }
|
||||
|
||||
Q_INVOKABLE void setOrientationVar(const QVariant& newOrientationVar);
|
||||
|
@ -189,120 +125,27 @@ public:
|
|||
// best called at end of main loop, just before rendering.
|
||||
// update sensor to world matrix from current body position and hmd sensor.
|
||||
// This is so the correct camera can be used for rendering.
|
||||
enum class SensorToWorldUpdateMode {
|
||||
Full = 0,
|
||||
Vertical,
|
||||
VerticalComfort
|
||||
};
|
||||
void updateSensorToWorldMatrix(SensorToWorldUpdateMode mode = SensorToWorldUpdateMode::Full);
|
||||
|
||||
void setSensorToWorldMatrix(const glm::mat4& sensorToWorld);
|
||||
void updateSensorToWorldMatrix();
|
||||
|
||||
void setRealWorldFieldOfView(float realWorldFov) { _realWorldFieldOfView.set(realWorldFov); }
|
||||
|
||||
/**jsdoc
|
||||
* The default position in world coordinates of the point directly between the avatar's eyes
|
||||
* @function MyAvatar.getDefaultEyePosition
|
||||
* @example <caption>This example gets the default eye position and prints it to the debug log.</caption>
|
||||
* var defaultEyePosition = MyAvatar.getDefaultEyePosition();
|
||||
* print (JSON.stringify(defaultEyePosition));
|
||||
* @returns {Vec3} Position between the avatar's eyes.
|
||||
*/
|
||||
Q_INVOKABLE glm::vec3 getDefaultEyePosition() const;
|
||||
|
||||
float getRealWorldFieldOfView() { return _realWorldFieldOfView.get(); }
|
||||
|
||||
/**jsdoc
|
||||
* The avatar animation system includes a set of default animations along with rules for how those animations are blended
|
||||
* together with procedural data (such as look at vectors, hand sensors etc.). overrideAnimation() is used to completely
|
||||
* override all motion from the default animation system (including inverse kinematics for hand and head controllers) and
|
||||
* play a specified animation. To end this animation and restore the default animations, use MyAvatar.restoreAnimation.
|
||||
* @function MyAvatar.overrideAnimation
|
||||
* @example <caption> Play a clapping animation on your avatar for three seconds. </caption>
|
||||
* // Clap your hands for 3 seconds then restore animation back to the avatar.
|
||||
* var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx";
|
||||
* MyAvatar.overrideAnimation(ANIM_URL, 30, true, 0, 53);
|
||||
* Script.setTimeout(function () {
|
||||
* MyAvatar.restoreAnimation();
|
||||
* }, 3000);
|
||||
* @param url {string} The URL to the animation file. Animation files need to be .FBX format, but only need to contain the avatar skeleton and animation data.
|
||||
* @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed.
|
||||
* @param loop {bool} Set to true if the animation should loop.
|
||||
* @param firstFrame {number} The frame the animation should start at.
|
||||
* @param lastFrame {number} The frame the animation should end at.
|
||||
*/
|
||||
// Interrupt the current animation with a custom animation.
|
||||
Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
|
||||
/**jsdoc
|
||||
* The avatar animation system includes a set of default animations along with rules for how those animations are blended together with
|
||||
* procedural data (such as look at vectors, hand sensors etc.). Playing your own custom animations will override the default animations.
|
||||
* restoreAnimation() is used to restore all motion from the default animation system including inverse kinematics for hand and head
|
||||
* controllers. If you aren't currently playing an override animation, this function will have no effect.
|
||||
* @function MyAvatar.restoreAnimation
|
||||
* @example <caption> Play a clapping animation on your avatar for three seconds. </caption>
|
||||
* // Clap your hands for 3 seconds then restore animation back to the avatar.
|
||||
* var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx";
|
||||
* MyAvatar.overrideAnimation(ANIM_URL, 30, true, 0, 53);
|
||||
* Script.setTimeout(function () {
|
||||
* MyAvatar.restoreAnimation();
|
||||
* }, 3000);
|
||||
*/
|
||||
// Stop the animation that was started with overrideAnimation and go back to the standard animation.
|
||||
Q_INVOKABLE void restoreAnimation();
|
||||
|
||||
/**jsdoc
|
||||
* Each avatar has an avatar-animation.json file that defines which animations are used and how they are blended together with procedural data
|
||||
* (such as look at vectors, hand sensors etc.). Each animation specified in the avatar-animation.json file is known as an animation role.
|
||||
* Animation roles map to easily understandable actions that the avatar can perform, such as "idleStand", "idleTalk", or "walkFwd."
|
||||
* getAnimationRoles() is used get the list of animation roles defined in the avatar-animation.json.
|
||||
* @function MyAvatar.getAnimatationRoles
|
||||
* @example <caption>This example prints the list of animation roles defined in the avatar's avatar-animation.json file to the debug log.</caption>
|
||||
* var roles = MyAvatar.getAnimationRoles();
|
||||
* print("Animation Roles:");
|
||||
* for (var i = 0; i < roles.length; i++) {
|
||||
* print(roles[i]);
|
||||
* }
|
||||
* @returns {string[]} Array of role strings
|
||||
*/
|
||||
// Returns a list of all clips that are available
|
||||
Q_INVOKABLE QStringList getAnimationRoles();
|
||||
|
||||
/**jsdoc
|
||||
* Each avatar has an avatar-animation.json file that defines a set of animation roles. Animation roles map to easily understandable actions
|
||||
* that the avatar can perform, such as "idleStand", "idleTalk", or "walkFwd". To get the full list of roles, use getAnimationRoles().
|
||||
* For each role, the avatar-animation.json defines when the animation is used, the animation clip (.FBX) used, and how animations are blended
|
||||
* together with procedural data (such as look at vectors, hand sensors etc.).
|
||||
* overrideRoleAnimation() is used to change the animation clip (.FBX) associated with a specified animation role.
|
||||
* Note: Hand roles only affect the hand. Other 'main' roles, like 'idleStand', 'idleTalk', 'takeoffStand' are full body.
|
||||
* @function MyAvatar.overrideRoleAnimation
|
||||
* @example <caption>The default avatar-animation.json defines an "idleStand" animation role. This role specifies that when the avatar is not moving,
|
||||
* an animation clip of the avatar idling with hands hanging at its side will be used. It also specifies that when the avatar moves, the animation
|
||||
* will smoothly blend to the walking animation used by the "walkFwd" animation role.
|
||||
* In this example, the "idleStand" role animation clip has been replaced with a clapping animation clip. Now instead of standing with its arms
|
||||
* hanging at its sides when it is not moving, the avatar will stand and clap its hands. Note that just as it did before, as soon as the avatar
|
||||
* starts to move, the animation will smoothly blend into the walk animation used by the "walkFwd" animation role.</caption>
|
||||
* // An animation of the avatar clapping its hands while standing
|
||||
* var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx";
|
||||
* MyAvatar.overrideRoleAnimation("idleStand", ANIM_URL, 30, true, 0, 53);
|
||||
* // To restore the default animation, use MyAvatar.restoreRoleAnimation().
|
||||
* @param role {string} The animation role to override
|
||||
* @param url {string} The URL to the animation file. Animation files need to be .FBX format, but only need to contain the avatar skeleton and animation data.
|
||||
* @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed.
|
||||
* @param loop {bool} Set to true if the animation should loop
|
||||
* @param firstFrame {number} The frame the animation should start at
|
||||
* @param lastFrame {number} The frame the animation should end at
|
||||
*/
|
||||
// Replace an existing standard role animation with a custom one.
|
||||
Q_INVOKABLE void overrideRoleAnimation(const QString& role, const QString& url, float fps, bool loop, float firstFrame, float lastFrame);
|
||||
|
||||
/**jsdoc
|
||||
* Each avatar has an avatar-animation.json file that defines a set of animation roles. Animation roles map to easily understandable actions that
|
||||
* the avatar can perform, such as "idleStand", "idleTalk", or "walkFwd". To get the full list of roles, use getAnimationRoles(). For each role,
|
||||
* the avatar-animation.json defines when the animation is used, the animation clip (.FBX) used, and how animations are blended together with
|
||||
* procedural data (such as look at vectors, hand sensors etc.). You can change the animation clip (.FBX) associated with a specified animation
|
||||
* role using overrideRoleAnimation().
|
||||
* restoreRoleAnimation() is used to restore a specified animation role's default animation clip. If you have not specified an override animation
|
||||
* for the specified role, this function will have no effect.
|
||||
* @function MyAvatar.restoreRoleAnimation
|
||||
* @param rule {string} The animation role clip to restore
|
||||
*/
|
||||
// remove an animation role override and return to the standard animation.
|
||||
Q_INVOKABLE void restoreRoleAnimation(const QString& role);
|
||||
|
||||
// Adds handler(animStateDictionaryIn) => animStateDictionaryOut, which will be invoked just before each animGraph state update.
|
||||
|
@ -384,7 +227,7 @@ public:
|
|||
const MyCharacterController* getCharacterController() const { return &_characterController; }
|
||||
|
||||
void updateMotors();
|
||||
void prepareForPhysicsSimulation(float deltaTime);
|
||||
void prepareForPhysicsSimulation();
|
||||
void harvestResultsFromPhysicsSimulation(float deltaTime);
|
||||
|
||||
const QString& getCollisionSoundURL() { return _collisionSoundURL; }
|
||||
|
@ -429,14 +272,12 @@ public:
|
|||
|
||||
bool hasDriveInput() const;
|
||||
|
||||
Q_INVOKABLE void setAvatarCollisionsEnabled(bool enabled);
|
||||
Q_INVOKABLE bool getAvatarCollisionsEnabled();
|
||||
Q_INVOKABLE void setCharacterControllerEnabled(bool enabled);
|
||||
Q_INVOKABLE bool getCharacterControllerEnabled();
|
||||
|
||||
virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override;
|
||||
virtual glm::vec3 getAbsoluteJointTranslationInObjectFrame(int index) const override;
|
||||
|
||||
glm::vec3 getPreActionVelocity() const;
|
||||
|
||||
void addHoldAction(AvatarActionHold* holdAction); // thread-safe
|
||||
void removeHoldAction(AvatarActionHold* holdAction); // thread-safe
|
||||
void updateHoldActions(const AnimPose& prePhysicsPose, const AnimPose& postUpdatePose);
|
||||
|
@ -461,19 +302,15 @@ public slots:
|
|||
|
||||
Q_INVOKABLE void updateMotionBehaviorFromMenu();
|
||||
|
||||
void setEnableDebugDrawLeftFootTrace(bool isEnabled);
|
||||
void setEnableDebugDrawRightFootTrace(bool isEnabled);
|
||||
void setEnableDebugDrawDefaultPose(bool isEnabled);
|
||||
void setEnableDebugDrawAnimPose(bool isEnabled);
|
||||
void setEnableDebugDrawPosition(bool isEnabled);
|
||||
void setEnableDebugDrawHandControllers(bool isEnabled);
|
||||
void setEnableDebugDrawSensorToWorldMatrix(bool isEnabled);
|
||||
void setEnableDebugDrawIKTargets(bool isEnabled);
|
||||
bool getEnableMeshVisible() const { return _skeletonModel->isVisible(); }
|
||||
void setEnableMeshVisible(bool isEnabled);
|
||||
void setUseAnimPreAndPostRotations(bool isEnabled);
|
||||
void setEnableInverseKinematics(bool isEnabled);
|
||||
void setEnableVerticalComfortMode(bool isEnabled);
|
||||
|
||||
QUrl getAnimGraphOverrideUrl() const; // thread-safe
|
||||
void setAnimGraphOverrideUrl(QUrl value); // thread-safe
|
||||
|
@ -483,8 +320,6 @@ public slots:
|
|||
glm::vec3 getPositionForAudio();
|
||||
glm::quat getOrientationForAudio();
|
||||
|
||||
bool isOutOfBody() const;
|
||||
|
||||
signals:
|
||||
void audioListenerModeChanged();
|
||||
void transformChanged();
|
||||
|
@ -537,8 +372,6 @@ private:
|
|||
virtual void updatePalms() override {}
|
||||
void lateUpdatePalms();
|
||||
|
||||
void applyVelocityToSensorToWorldMatrix(const glm::vec3& velocity, float deltaTime);
|
||||
|
||||
void clampTargetScaleToDomainLimits();
|
||||
void clampScaleChangeToDomainLimits(float desiredScale);
|
||||
glm::mat4 computeCameraRelativeHandControllerMatrix(const glm::mat4& controllerSensorMatrix) const;
|
||||
|
@ -619,9 +452,8 @@ private:
|
|||
Vertical,
|
||||
NumFollowTypes
|
||||
};
|
||||
uint8_t _activeBits { 0 };
|
||||
bool _isOutOfBody { false };
|
||||
float _outOfBodyDistance { 0.0f };
|
||||
glm::mat4 _desiredBodyMatrix;
|
||||
float _timeRemaining[NumFollowTypes];
|
||||
|
||||
void deactivate();
|
||||
void deactivate(FollowType type);
|
||||
|
@ -629,11 +461,13 @@ private:
|
|||
void activate(FollowType type);
|
||||
bool isActive() const;
|
||||
bool isActive(FollowType followType) const;
|
||||
void updateRotationActivation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix);
|
||||
void updateHorizontalActivation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix);
|
||||
void updateVerticalActivation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix);
|
||||
glm::mat4 prePhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput, float deltaTime);
|
||||
void postPhysicsUpdate(MyAvatar& myAvatar);
|
||||
float getMaxTimeRemaining() const;
|
||||
void decrementTimeRemaining(float dt);
|
||||
bool shouldActivateRotation(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
bool shouldActivateVertical(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
bool shouldActivateHorizontal(const MyAvatar& myAvatar, const glm::mat4& desiredBodyMatrix, const glm::mat4& currentBodyMatrix) const;
|
||||
void prePhysicsUpdate(MyAvatar& myAvatar, const glm::mat4& bodySensorMatrix, const glm::mat4& currentBodyMatrix, bool hasDriveInput);
|
||||
glm::mat4 postPhysicsUpdate(const MyAvatar& myAvatar, const glm::mat4& currentBodyMatrix);
|
||||
};
|
||||
FollowHelper _follow;
|
||||
|
||||
|
@ -645,14 +479,10 @@ private:
|
|||
RigPointer _rig;
|
||||
bool _prevShouldDrawHead;
|
||||
|
||||
bool _enableDebugDrawLeftFootTrace { false };
|
||||
bool _enableDebugDrawRightFootTrace { false };
|
||||
bool _enableDebugDrawDefaultPose { false };
|
||||
bool _enableDebugDrawAnimPose { false };
|
||||
bool _enableDebugDrawHandControllers { false };
|
||||
bool _enableDebugDrawSensorToWorldMatrix { false };
|
||||
bool _enableDebugDrawIKTargets { false };
|
||||
bool _enableVerticalComfortMode { false };
|
||||
|
||||
AudioListenerMode _audioListenerMode;
|
||||
glm::vec3 _customListenPosition;
|
||||
|
@ -668,8 +498,6 @@ private:
|
|||
ThreadSafeValueCache<controller::Pose> _rightHandControllerPoseInSensorFrameCache { controller::Pose() };
|
||||
|
||||
bool _hmdLeanRecenterEnabled = true;
|
||||
bool _moveKinematically { false }; // KINEMATIC_CONTROLLER_HACK
|
||||
float _canonicalScale { 1.0f };
|
||||
|
||||
AnimPose _prePhysicsRoomPose;
|
||||
std::mutex _holdActionsMutex;
|
||||
|
@ -687,18 +515,6 @@ private:
|
|||
float getEnergy();
|
||||
void setEnergy(float value);
|
||||
bool didTeleport();
|
||||
|
||||
struct DebugDrawVertex {
|
||||
DebugDrawVertex() : pos(), color() {}
|
||||
DebugDrawVertex(const glm::vec3& posIn, const glm::vec4& colorIn) : pos(posIn), color(colorIn) {}
|
||||
glm::vec3 pos;
|
||||
glm::vec4 color;
|
||||
};
|
||||
DebugDrawVertex _debugLineLoop[DEBUG_LINE_LOOP_SIZE];
|
||||
size_t _debugLineLoopIndex { 0 };
|
||||
|
||||
bool _handControllerShow { false };
|
||||
float _handControllerShowTimer { 0.0f };
|
||||
};
|
||||
|
||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||
|
|
443
interface/src/avatar/MyCharacterController.cpp
Executable file → Normal file
443
interface/src/avatar/MyCharacterController.cpp
Executable file → Normal file
|
@ -15,15 +15,11 @@
|
|||
|
||||
#include "MyAvatar.h"
|
||||
|
||||
// TODO: improve walking up steps
|
||||
// TODO: make avatars able to walk up and down steps/slopes
|
||||
// TODO: make avatars stand on steep slope
|
||||
// TODO: make avatars not snag on low ceilings
|
||||
|
||||
|
||||
void MyCharacterController::RayShotgunResult::reset() {
|
||||
hitFraction = 1.0f;
|
||||
walkable = true;
|
||||
}
|
||||
|
||||
MyCharacterController::MyCharacterController(MyAvatar* avatar) {
|
||||
|
||||
assert(avatar);
|
||||
|
@ -34,33 +30,37 @@ MyCharacterController::MyCharacterController(MyAvatar* avatar) {
|
|||
MyCharacterController::~MyCharacterController() {
|
||||
}
|
||||
|
||||
void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
CharacterController::setDynamicsWorld(world);
|
||||
if (world) {
|
||||
initRayShotgun(world);
|
||||
}
|
||||
}
|
||||
|
||||
void MyCharacterController::updateShapeIfNecessary() {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
_pendingFlags &= ~PENDING_FLAG_UPDATE_SHAPE;
|
||||
|
||||
// compute new dimensions from avatar's bounding box
|
||||
float x = _boxScale.x;
|
||||
float z = _boxScale.z;
|
||||
_radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
|
||||
_halfHeight = 0.5f * _boxScale.y - _radius;
|
||||
float MIN_HALF_HEIGHT = 0.1f;
|
||||
if (_halfHeight < MIN_HALF_HEIGHT) {
|
||||
_halfHeight = MIN_HALF_HEIGHT;
|
||||
}
|
||||
// NOTE: _shapeLocalOffset is already computed
|
||||
|
||||
if (_radius > 0.0f) {
|
||||
// create RigidBody if it doesn't exist
|
||||
if (!_rigidBody) {
|
||||
btCollisionShape* shape = computeShape();
|
||||
|
||||
// HACK: use some simple mass property defaults for now
|
||||
const btScalar DEFAULT_AVATAR_MASS = 100.0f;
|
||||
const float DEFAULT_AVATAR_MASS = 100.0f;
|
||||
const btVector3 DEFAULT_AVATAR_INERTIA_TENSOR(30.0f, 8.0f, 30.0f);
|
||||
|
||||
btCollisionShape* shape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
|
||||
_rigidBody = new btRigidBody(DEFAULT_AVATAR_MASS, nullptr, shape, DEFAULT_AVATAR_INERTIA_TENSOR);
|
||||
} else {
|
||||
btCollisionShape* shape = _rigidBody->getCollisionShape();
|
||||
if (shape) {
|
||||
delete shape;
|
||||
}
|
||||
shape = computeShape();
|
||||
shape = new btCapsuleShape(_radius, 2.0f * _halfHeight);
|
||||
_rigidBody->setCollisionShape(shape);
|
||||
}
|
||||
|
||||
|
@ -72,419 +72,12 @@ void MyCharacterController::updateShapeIfNecessary() {
|
|||
if (_state == State::Hover) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
}
|
||||
// KINEMATIC_CONTROLLER_HACK
|
||||
if (_moveKinematically) {
|
||||
_rigidBody->setCollisionFlags(btCollisionObject::CF_KINEMATIC_OBJECT);
|
||||
} else {
|
||||
_rigidBody->setCollisionFlags(_rigidBody->getCollisionFlags() &
|
||||
~(btCollisionObject::CF_KINEMATIC_OBJECT | btCollisionObject::CF_STATIC_OBJECT));
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
}
|
||||
//_rigidBody->setCollisionFlags(btCollisionObject::CF_CHARACTER_OBJECT);
|
||||
} else {
|
||||
// TODO: handle this failure case
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result) {
|
||||
btVector3 rayDirection = glmToBullet(step);
|
||||
btScalar stepLength = rayDirection.length();
|
||||
if (stepLength < FLT_EPSILON) {
|
||||
return false;
|
||||
}
|
||||
rayDirection /= stepLength;
|
||||
|
||||
// get _ghost ready for ray traces
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
btVector3 newPosition = glmToBullet(position);
|
||||
transform.setOrigin(newPosition);
|
||||
_ghost.setWorldTransform(transform);
|
||||
btMatrix3x3 rotation = transform.getBasis();
|
||||
_ghost.refreshOverlappingPairCache();
|
||||
|
||||
CharacterRayResult rayResult(&_ghost);
|
||||
CharacterRayResult closestRayResult(&_ghost);
|
||||
btVector3 rayStart;
|
||||
btVector3 rayEnd;
|
||||
|
||||
// compute rotation that will orient local ray start points to face step direction
|
||||
btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f);
|
||||
btVector3 adjustedDirection = rayDirection - rayDirection.dot(_currentUp) * _currentUp;
|
||||
btVector3 axis = forward.cross(adjustedDirection);
|
||||
btScalar lengthAxis = axis.length();
|
||||
if (lengthAxis > FLT_EPSILON) {
|
||||
// we're walking sideways
|
||||
btScalar angle = acosf(lengthAxis / adjustedDirection.length());
|
||||
if (rayDirection.dot(forward) < 0.0f) {
|
||||
angle = PI - angle;
|
||||
}
|
||||
axis /= lengthAxis;
|
||||
rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation;
|
||||
} else if (rayDirection.dot(forward) < 0.0f) {
|
||||
// we're walking backwards
|
||||
rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation;
|
||||
}
|
||||
|
||||
// scan the top
|
||||
// NOTE: if we scan an extra distance forward we can detect flat surfaces that are too steep to walk on.
|
||||
// The approximate extra distance can be derived with trigonometry.
|
||||
//
|
||||
// minimumForward = [ (maxStepHeight + radius / cosTheta - radius) * (cosTheta / sinTheta) - radius ]
|
||||
//
|
||||
// where: theta = max angle between floor normal and vertical
|
||||
//
|
||||
// if stepLength is not long enough we can add the difference.
|
||||
//
|
||||
btScalar cosTheta = _minFloorNormalDotUp;
|
||||
btScalar sinTheta = sqrtf(1.0f - cosTheta * cosTheta);
|
||||
const btScalar MIN_FORWARD_SLOP = 0.12f; // HACK: not sure why this is necessary to detect steepest walkable slope
|
||||
btScalar forwardSlop = (_maxStepHeight + _radius / cosTheta - _radius) * (cosTheta / sinTheta) - (_radius + stepLength) + MIN_FORWARD_SLOP;
|
||||
if (forwardSlop < 0.0f) {
|
||||
// BIG step, no slop necessary
|
||||
forwardSlop = 0.0f;
|
||||
}
|
||||
|
||||
const btScalar backSlop = 0.04f;
|
||||
for (int32_t i = 0; i < _topPoints.size(); ++i) {
|
||||
rayStart = newPosition + rotation * _topPoints[i] - backSlop * rayDirection;
|
||||
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
||||
closestRayResult = rayResult;
|
||||
}
|
||||
if (result.walkable) {
|
||||
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
|
||||
result.walkable = false;
|
||||
// the top scan wasn't walkable so don't bother scanning the bottom
|
||||
// remove both forwardSlop and backSlop
|
||||
result.hitFraction = glm::min(1.0f, (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop) - backSlop) / stepLength);
|
||||
return result.hitFraction < 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_state == State::Hover) {
|
||||
// scan the bottom just like the top
|
||||
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
|
||||
rayStart = newPosition + rotation * _bottomPoints[i] - backSlop * rayDirection;
|
||||
rayEnd = rayStart + (backSlop + stepLength + forwardSlop) * rayDirection;
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
||||
closestRayResult = rayResult;
|
||||
}
|
||||
if (result.walkable) {
|
||||
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
|
||||
result.walkable = false;
|
||||
// the bottom scan wasn't walkable
|
||||
// remove both forwardSlop and backSlop
|
||||
result.hitFraction = glm::min(1.0f, (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop) - backSlop) / stepLength);
|
||||
return result.hitFraction < 1.0f;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// scan the bottom looking for nearest step point
|
||||
// remove forwardSlop
|
||||
result.hitFraction = (closestRayResult.m_closestHitFraction * (backSlop + stepLength + forwardSlop)) / (backSlop + stepLength);
|
||||
|
||||
for (int32_t i = 0; i < _bottomPoints.size(); ++i) {
|
||||
rayStart = newPosition + rotation * _bottomPoints[i] - backSlop * rayDirection;
|
||||
rayEnd = rayStart + (backSlop + stepLength) * rayDirection;
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
if (rayResult.m_closestHitFraction < closestRayResult.m_closestHitFraction) {
|
||||
closestRayResult = rayResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
// remove backSlop
|
||||
// NOTE: backSlop removal can produce a NEGATIVE hitFraction!
|
||||
// which means the shape is actually in interpenetration
|
||||
result.hitFraction = ((closestRayResult.m_closestHitFraction * (backSlop + stepLength)) - backSlop) / stepLength;
|
||||
}
|
||||
return result.hitFraction < 1.0f;
|
||||
}
|
||||
|
||||
glm::vec3 MyCharacterController::computeHMDStep(const glm::vec3& position, const glm::vec3& step) {
|
||||
btVector3 stepDirection = glmToBullet(step);
|
||||
btScalar stepLength = stepDirection.length();
|
||||
if (stepLength < FLT_EPSILON) {
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
stepDirection /= stepLength;
|
||||
|
||||
// get _ghost ready for ray traces
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
btVector3 newPosition = glmToBullet(position);
|
||||
transform.setOrigin(newPosition);
|
||||
btMatrix3x3 rotation = transform.getBasis();
|
||||
_ghost.setWorldTransform(transform);
|
||||
_ghost.refreshOverlappingPairCache();
|
||||
|
||||
// compute rotation that will orient local ray start points to face stepDirection
|
||||
btVector3 forward = rotation * btVector3(0.0f, 0.0f, -1.0f);
|
||||
btVector3 horizontalDirection = stepDirection - stepDirection.dot(_currentUp) * _currentUp;
|
||||
btVector3 axis = forward.cross(horizontalDirection);
|
||||
btScalar lengthAxis = axis.length();
|
||||
if (lengthAxis > FLT_EPSILON) {
|
||||
// non-zero sideways component
|
||||
btScalar angle = asinf(lengthAxis / horizontalDirection.length());
|
||||
if (stepDirection.dot(forward) < 0.0f) {
|
||||
angle = PI - angle;
|
||||
}
|
||||
axis /= lengthAxis;
|
||||
rotation = btMatrix3x3(btQuaternion(axis, angle)) * rotation;
|
||||
} else if (stepDirection.dot(forward) < 0.0f) {
|
||||
// backwards
|
||||
rotation = btMatrix3x3(btQuaternion(_currentUp, PI)) * rotation;
|
||||
}
|
||||
|
||||
CharacterRayResult rayResult(&_ghost);
|
||||
btVector3 rayStart;
|
||||
btVector3 rayEnd;
|
||||
btVector3 penetration = btVector3(0.0f, 0.0f, 0.0f);
|
||||
int32_t numPenetrations = 0;
|
||||
|
||||
{ // first we scan straight out from capsule center to see if we're stuck on anything
|
||||
btScalar forwardRatio = 0.5f;
|
||||
btScalar backRatio = 0.25f;
|
||||
|
||||
btVector3 radial;
|
||||
bool stuck = false;
|
||||
for (int32_t i = 0; i < _topPoints.size(); ++i) {
|
||||
rayStart = rotation * _topPoints[i];
|
||||
radial = rayStart - rayStart.dot(_currentUp) * _currentUp;
|
||||
rayEnd = newPosition + rayStart + forwardRatio * radial;
|
||||
rayStart += newPosition - backRatio * radial;
|
||||
|
||||
// reset rayResult for next test
|
||||
rayResult.m_closestHitFraction = 1.0f;
|
||||
rayResult.m_collisionObject = nullptr;
|
||||
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
btScalar totalRatio = backRatio + forwardRatio;
|
||||
btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * totalRatio - backRatio) / forwardRatio;
|
||||
if (adjustedHitFraction < 0.0f) {
|
||||
penetration += adjustedHitFraction * radial;
|
||||
++numPenetrations;
|
||||
} else {
|
||||
stuck = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (numPenetrations > 0) {
|
||||
if (numPenetrations > 1) {
|
||||
penetration /= (btScalar)numPenetrations;
|
||||
}
|
||||
return bulletToGLM(penetration);
|
||||
} else if (stuck) {
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
// if we get here then we're not stuck pushing into any surface
|
||||
// so now we scan to see if the way before us is "walkable"
|
||||
|
||||
// scan the top
|
||||
// NOTE: if we scan an extra distance forward we can detect flat surfaces that are too steep to walk on.
|
||||
// The approximate extra distance can be derived with trigonometry.
|
||||
//
|
||||
// minimumForward = [ (maxStepHeight + radius / cosTheta - radius) * (cosTheta / sinTheta) - radius ]
|
||||
//
|
||||
// where: theta = max angle between floor normal and vertical
|
||||
//
|
||||
// if stepLength is not long enough we can add the difference.
|
||||
//
|
||||
btScalar cosTheta = _minFloorNormalDotUp;
|
||||
btScalar sinTheta = sqrtf(1.0f - cosTheta * cosTheta);
|
||||
const btScalar MIN_FORWARD_SLOP = 0.10f; // HACK: not sure why this is necessary to detect steepest walkable slope
|
||||
btScalar forwardSlop = (_maxStepHeight + _radius / cosTheta - _radius) * (cosTheta / sinTheta) - (_radius + stepLength) + MIN_FORWARD_SLOP;
|
||||
if (forwardSlop < 0.0f) {
|
||||
// BIG step, no slop necessary
|
||||
forwardSlop = 0.0f;
|
||||
}
|
||||
|
||||
// we push the step forward by stepMargin to help reduce accidental penetration
|
||||
const btScalar MIN_STEP_MARGIN = 0.04f;
|
||||
btScalar stepMargin = glm::max(_radius, MIN_STEP_MARGIN);
|
||||
btScalar expandedStepLength = stepLength + forwardSlop + stepMargin;
|
||||
|
||||
// loop over topPoints
|
||||
bool walkable = true;
|
||||
for (int32_t i = 0; i < _topPoints.size(); ++i) {
|
||||
rayStart = newPosition + rotation * _topPoints[i];
|
||||
rayEnd = rayStart + expandedStepLength * stepDirection;
|
||||
|
||||
// reset rayResult for next test
|
||||
rayResult.m_closestHitFraction = 1.0f;
|
||||
rayResult.m_collisionObject = nullptr;
|
||||
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
if (rayResult.m_hitNormalWorld.dot(_currentUp) < _minFloorNormalDotUp) {
|
||||
walkable = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// scan the bottom
|
||||
// TODO: implement sliding along sloped floors
|
||||
bool steppingUp = false;
|
||||
expandedStepLength = stepLength + MIN_FORWARD_SLOP + MIN_STEP_MARGIN;
|
||||
for (int32_t i = _bottomPoints.size() - 1; i > -1; --i) {
|
||||
rayStart = newPosition + rotation * _bottomPoints[i] - MIN_STEP_MARGIN * stepDirection;
|
||||
rayEnd = rayStart + expandedStepLength * stepDirection;
|
||||
|
||||
// reset rayResult for next test
|
||||
rayResult.m_closestHitFraction = 1.0f;
|
||||
rayResult.m_collisionObject = nullptr;
|
||||
|
||||
if (_ghost.rayTest(rayStart, rayEnd, rayResult)) {
|
||||
btScalar adjustedHitFraction = (rayResult.m_closestHitFraction * expandedStepLength - MIN_STEP_MARGIN) / (stepLength + MIN_FORWARD_SLOP);
|
||||
if (adjustedHitFraction < 1.0f) {
|
||||
steppingUp = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!walkable && steppingUp ) {
|
||||
return glm::vec3(0.0f);
|
||||
}
|
||||
// else it might not be walkable, but we aren't steppingUp yet which means we can still move forward
|
||||
|
||||
// TODO: slide up ramps and fall off edges (then we can remove the vertical follow of Avatar's RigidBody)
|
||||
return step;
|
||||
}
|
||||
|
||||
btConvexHullShape* MyCharacterController::computeShape() const {
|
||||
// HACK: the avatar collides using convex hull with a collision margin equal to
|
||||
// the old capsule radius. Two points define a capsule and additional points are
|
||||
// spread out at chest level to produce a slight taper toward the feet. This
|
||||
// makes the avatar more likely to collide with vertical walls at a higher point
|
||||
// and thus less likely to produce a single-point collision manifold below the
|
||||
// _maxStepHeight when walking into against vertical surfaces --> fixes a bug
|
||||
// where the "walk up steps" feature would allow the avatar to walk up vertical
|
||||
// walls.
|
||||
const int32_t NUM_POINTS = 6;
|
||||
btVector3 points[NUM_POINTS];
|
||||
btVector3 xAxis = btVector3(1.0f, 0.0f, 0.0f);
|
||||
btVector3 yAxis = btVector3(0.0f, 1.0f, 0.0f);
|
||||
btVector3 zAxis = btVector3(0.0f, 0.0f, 1.0f);
|
||||
points[0] = _halfHeight * yAxis;
|
||||
points[1] = -_halfHeight * yAxis;
|
||||
points[2] = (0.75f * _halfHeight) * yAxis - (0.1f * _radius) * zAxis;
|
||||
points[3] = (0.75f * _halfHeight) * yAxis + (0.1f * _radius) * zAxis;
|
||||
points[4] = (0.75f * _halfHeight) * yAxis - (0.1f * _radius) * xAxis;
|
||||
points[5] = (0.75f * _halfHeight) * yAxis + (0.1f * _radius) * xAxis;
|
||||
btConvexHullShape* shape = new btConvexHullShape(reinterpret_cast<btScalar*>(points), NUM_POINTS);
|
||||
shape->setMargin(_radius);
|
||||
return shape;
|
||||
}
|
||||
|
||||
void MyCharacterController::initRayShotgun(const btCollisionWorld* world) {
|
||||
// In order to trace rays out from the avatar's shape surface we need to know where the start points are in
|
||||
// the local-frame. Since the avatar shape is somewhat irregular computing these points by hand is a hassle
|
||||
// so instead we ray-trace backwards to the avatar to find them.
|
||||
//
|
||||
// We trace back a regular grid (see below) of points against the shape and keep any that hit.
|
||||
// ___
|
||||
// + / + \ +
|
||||
// |+ +|
|
||||
// +| + | +
|
||||
// |+ +|
|
||||
// +| + | +
|
||||
// |+ +|
|
||||
// + \ + / +
|
||||
// ---
|
||||
// The shotgun will send rays out from these same points to see if the avatar's shape can proceed through space.
|
||||
|
||||
// helper class for simple ray-traces against character
|
||||
class MeOnlyResultCallback : public btCollisionWorld::ClosestRayResultCallback {
|
||||
public:
|
||||
MeOnlyResultCallback (btRigidBody* me) : btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)) {
|
||||
_me = me;
|
||||
m_collisionFilterGroup = BULLET_COLLISION_GROUP_DYNAMIC;
|
||||
m_collisionFilterMask = BULLET_COLLISION_MASK_DYNAMIC;
|
||||
}
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult,bool normalInWorldSpace) override {
|
||||
if (rayResult.m_collisionObject != _me) {
|
||||
return 1.0f;
|
||||
}
|
||||
return ClosestRayResultCallback::addSingleResult(rayResult, normalInWorldSpace);
|
||||
}
|
||||
btRigidBody* _me;
|
||||
};
|
||||
|
||||
const btScalar fullHalfHeight = _radius + _halfHeight;
|
||||
const btScalar divisionLine = -fullHalfHeight + _maxStepHeight; // line between top and bottom
|
||||
const btScalar topHeight = fullHalfHeight - divisionLine;
|
||||
const btScalar slop = 0.02f;
|
||||
|
||||
const int32_t NUM_ROWS = 5; // must be odd number > 1
|
||||
const int32_t NUM_COLUMNS = 5; // must be odd number > 1
|
||||
btVector3 reach = (2.0f * _radius) * btVector3(0.0f, 0.0f, 1.0f);
|
||||
|
||||
{ // top points
|
||||
_topPoints.clear();
|
||||
_topPoints.reserve(NUM_ROWS * NUM_COLUMNS);
|
||||
btScalar stepY = (topHeight - slop) / (btScalar)(NUM_ROWS - 1);
|
||||
btScalar stepX = 2.0f * (_radius - slop) / (btScalar)(NUM_COLUMNS - 1);
|
||||
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
btVector3 position = transform.getOrigin();
|
||||
btMatrix3x3 rotation = transform.getBasis();
|
||||
|
||||
for (int32_t i = 0; i < NUM_ROWS; ++i) {
|
||||
int32_t maxJ = NUM_COLUMNS;
|
||||
btScalar offsetX = -(btScalar)((NUM_COLUMNS - 1) / 2) * stepX;
|
||||
if (i % 2 == 1) {
|
||||
// odd rows have one less point and start a halfStep closer
|
||||
maxJ -= 1;
|
||||
offsetX += 0.5f * stepX;
|
||||
}
|
||||
for (int32_t j = 0; j < maxJ; ++j) {
|
||||
btVector3 localRayEnd(offsetX + (btScalar)(j) * stepX, divisionLine + (btScalar)(i) * stepY, 0.0f);
|
||||
btVector3 localRayStart = localRayEnd - reach;
|
||||
MeOnlyResultCallback result(_rigidBody);
|
||||
world->rayTest(position + rotation * localRayStart, position + rotation * localRayEnd, result);
|
||||
if (result.m_closestHitFraction < 1.0f) {
|
||||
_topPoints.push_back(localRayStart + result.m_closestHitFraction * reach);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{ // bottom points
|
||||
_bottomPoints.clear();
|
||||
_bottomPoints.reserve(NUM_ROWS * NUM_COLUMNS);
|
||||
|
||||
btScalar steepestStepHitHeight = (_radius + 0.04f) * (1.0f - DEFAULT_MIN_FLOOR_NORMAL_DOT_UP);
|
||||
btScalar stepY = (_maxStepHeight - slop - steepestStepHitHeight) / (btScalar)(NUM_ROWS - 1);
|
||||
btScalar stepX = 2.0f * (_radius - slop) / (btScalar)(NUM_COLUMNS - 1);
|
||||
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
btVector3 position = transform.getOrigin();
|
||||
btMatrix3x3 rotation = transform.getBasis();
|
||||
|
||||
for (int32_t i = 0; i < NUM_ROWS; ++i) {
|
||||
int32_t maxJ = NUM_COLUMNS;
|
||||
btScalar offsetX = -(btScalar)((NUM_COLUMNS - 1) / 2) * stepX;
|
||||
if (i % 2 == 1) {
|
||||
// odd rows have one less point and start a halfStep closer
|
||||
maxJ -= 1;
|
||||
offsetX += 0.5f * stepX;
|
||||
}
|
||||
for (int32_t j = 0; j < maxJ; ++j) {
|
||||
btVector3 localRayEnd(offsetX + (btScalar)(j) * stepX, (divisionLine - slop) - (btScalar)(i) * stepY, 0.0f);
|
||||
btVector3 localRayStart = localRayEnd - reach;
|
||||
MeOnlyResultCallback result(_rigidBody);
|
||||
world->rayTest(position + rotation * localRayStart, position + rotation * localRayEnd, result);
|
||||
if (result.m_closestHitFraction < 1.0f) {
|
||||
_bottomPoints.push_back(localRayStart + result.m_closestHitFraction * reach);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,37 +24,10 @@ public:
|
|||
explicit MyCharacterController(MyAvatar* avatar);
|
||||
~MyCharacterController ();
|
||||
|
||||
void setDynamicsWorld(btDynamicsWorld* world) override;
|
||||
void updateShapeIfNecessary() override;
|
||||
|
||||
// Sweeping a convex shape through the physics simulation can expensive when the obstacles are too complex
|
||||
// (e.g. small 20k triangle static mesh) so instead as a fallback we cast several rays forward and if they
|
||||
// don't hit anything we consider it a clean sweep. Hence the "Shotgun" code.
|
||||
class RayShotgunResult {
|
||||
public:
|
||||
void reset();
|
||||
|
||||
float hitFraction { 1.0f };
|
||||
bool walkable { true };
|
||||
};
|
||||
|
||||
/// return true if RayShotgun hits anything
|
||||
bool testRayShotgun(const glm::vec3& position, const glm::vec3& step, RayShotgunResult& result);
|
||||
|
||||
glm::vec3 computeHMDStep(const glm::vec3& position, const glm::vec3& step);
|
||||
|
||||
protected:
|
||||
void initRayShotgun(const btCollisionWorld* world);
|
||||
|
||||
private:
|
||||
btConvexHullShape* computeShape() const;
|
||||
virtual void updateShapeIfNecessary() override;
|
||||
|
||||
protected:
|
||||
MyAvatar* _avatar { nullptr };
|
||||
|
||||
// shotgun scan data
|
||||
btAlignedObjectArray<btVector3> _topPoints;
|
||||
btAlignedObjectArray<btVector3> _bottomPoints;
|
||||
};
|
||||
|
||||
#endif // hifi_MyCharacterController_h
|
||||
|
|
|
@ -23,12 +23,6 @@
|
|||
#include "InterfaceLogging.h"
|
||||
#include "AnimDebugDraw.h"
|
||||
|
||||
glm::vec3 TRUNCATE_IK_CAPSULE_POSITION(0.0f, 0.0f, 0.0f);
|
||||
float TRUNCATE_IK_CAPSULE_LENGTH = 1000.0f;
|
||||
float TRUNCATE_IK_CAPSULE_RADIUS = 0.25f;
|
||||
float MIN_OUT_OF_BODY_DISTANCE = TRUNCATE_IK_CAPSULE_RADIUS - 0.1f;
|
||||
float MAX_OUT_OF_BODY_DISTANCE = TRUNCATE_IK_CAPSULE_RADIUS + 0.1f;
|
||||
|
||||
SkeletonModel::SkeletonModel(Avatar* owningAvatar, QObject* parent, RigPointer rig) :
|
||||
Model(rig, parent),
|
||||
_owningAvatar(owningAvatar),
|
||||
|
@ -92,6 +86,7 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
// Called within Model::simulate call, below.
|
||||
void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
const FBXGeometry& geometry = getFBXGeometry();
|
||||
|
@ -112,9 +107,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
|
||||
Rig::HeadParameters headParams;
|
||||
|
||||
glm::vec3 hmdPositionInRigSpace;
|
||||
glm::vec3 truncatedHMDPositionInRigSpace;
|
||||
|
||||
if (qApp->isHMDMode()) {
|
||||
headParams.isInHMD = true;
|
||||
|
||||
|
@ -124,22 +116,9 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
glm::mat4 worldToRig = glm::inverse(rigToWorld);
|
||||
glm::mat4 rigHMDMat = worldToRig * worldHMDMat;
|
||||
|
||||
hmdPositionInRigSpace = extractTranslation(rigHMDMat);
|
||||
|
||||
glm::vec3 capsuleStart = Vectors::UNIT_Y * (TRUNCATE_IK_CAPSULE_LENGTH / 2.0f);
|
||||
glm::vec3 capsuleEnd = -Vectors::UNIT_Y * (TRUNCATE_IK_CAPSULE_LENGTH / 2.0f);
|
||||
|
||||
// truncate head IK target if it's out of body
|
||||
if (myAvatar->isOutOfBody()) {
|
||||
truncatedHMDPositionInRigSpace = projectPointOntoCapsule(hmdPositionInRigSpace, capsuleStart, capsuleEnd, TRUNCATE_IK_CAPSULE_RADIUS);
|
||||
} else {
|
||||
truncatedHMDPositionInRigSpace = hmdPositionInRigSpace;
|
||||
}
|
||||
|
||||
headParams.rigHeadPosition = truncatedHMDPositionInRigSpace;
|
||||
headParams.rigHeadPosition = extractTranslation(rigHMDMat);
|
||||
headParams.rigHeadOrientation = extractRotation(rigHMDMat);
|
||||
headParams.worldHeadOrientation = extractRotation(worldHMDMat);
|
||||
|
||||
} else {
|
||||
headParams.isInHMD = false;
|
||||
|
||||
|
@ -153,42 +132,13 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
|
||||
_rig->updateFromHeadParameters(headParams, deltaTime);
|
||||
|
||||
// OUTOFBODY_HACK: clamp horizontal component of head by maxHipsOffset.
|
||||
// This is to prevent the hands from being incorrect relative to the head because
|
||||
// the hips are being constrained by a small maxHipsOffset due to collision.
|
||||
if (myAvatar->isOutOfBody()) {
|
||||
float headOffsetLength2D = glm::length(glm::vec2(truncatedHMDPositionInRigSpace.x, truncatedHMDPositionInRigSpace.z));
|
||||
if (headOffsetLength2D > _rig->getMaxHipsOffsetLength()) {
|
||||
truncatedHMDPositionInRigSpace.x *= _rig->getMaxHipsOffsetLength() / headOffsetLength2D;
|
||||
truncatedHMDPositionInRigSpace.z *= _rig->getMaxHipsOffsetLength() / headOffsetLength2D;
|
||||
}
|
||||
}
|
||||
|
||||
Rig::HandParameters handParams;
|
||||
|
||||
// compute interp factor between in body and out of body hand positions.
|
||||
glm::vec3 capsuleStart = Vectors::UNIT_Y * (TRUNCATE_IK_CAPSULE_LENGTH / 2.0f);
|
||||
glm::vec3 capsuleEnd = -Vectors::UNIT_Y * (TRUNCATE_IK_CAPSULE_LENGTH / 2.0f);
|
||||
float outOfBodyAlpha = distanceFromCapsule(hmdPositionInRigSpace, capsuleStart, capsuleEnd, TRUNCATE_IK_CAPSULE_RADIUS);
|
||||
outOfBodyAlpha = (glm::clamp(outOfBodyAlpha, MIN_OUT_OF_BODY_DISTANCE, MAX_OUT_OF_BODY_DISTANCE) - MIN_OUT_OF_BODY_DISTANCE) /
|
||||
(MAX_OUT_OF_BODY_DISTANCE - MIN_OUT_OF_BODY_DISTANCE);
|
||||
|
||||
auto leftPose = myAvatar->getLeftHandControllerPoseInAvatarFrame();
|
||||
if (leftPose.isValid()) {
|
||||
handParams.isLeftEnabled = true;
|
||||
handParams.leftPosition = Quaternions::Y_180 * leftPose.getTranslation();
|
||||
handParams.leftOrientation = Quaternions::Y_180 * leftPose.getRotation();
|
||||
|
||||
// adjust hand position if head is out of body.
|
||||
if (qApp->isHMDMode()) {
|
||||
|
||||
// compute the out of body hand position.
|
||||
glm::vec3 offset = handParams.leftPosition - hmdPositionInRigSpace;
|
||||
glm::vec3 outOfBodyLeftPosition = truncatedHMDPositionInRigSpace + offset;
|
||||
|
||||
// interpolate between in body and out of body hand position.
|
||||
handParams.leftPosition = lerp(handParams.leftPosition, outOfBodyLeftPosition, outOfBodyAlpha);
|
||||
}
|
||||
} else {
|
||||
handParams.isLeftEnabled = false;
|
||||
}
|
||||
|
@ -198,17 +148,6 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
handParams.isRightEnabled = true;
|
||||
handParams.rightPosition = Quaternions::Y_180 * rightPose.getTranslation();
|
||||
handParams.rightOrientation = Quaternions::Y_180 * rightPose.getRotation();
|
||||
|
||||
// adjust hand position if head is out of body.
|
||||
if (qApp->isHMDMode()) {
|
||||
|
||||
// compute the out of body hand position.
|
||||
glm::vec3 offset = handParams.rightPosition - hmdPositionInRigSpace;
|
||||
glm::vec3 outOfBodyRightPosition = truncatedHMDPositionInRigSpace + offset;
|
||||
|
||||
// interpolate between in body and out of body hand position.
|
||||
handParams.rightPosition = lerp(handParams.rightPosition, outOfBodyRightPosition, outOfBodyAlpha);
|
||||
}
|
||||
} else {
|
||||
handParams.isRightEnabled = false;
|
||||
}
|
||||
|
@ -221,7 +160,7 @@ void SkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
|
||||
Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());
|
||||
|
||||
auto velocity = myAvatar->getPreActionVelocity();
|
||||
auto velocity = myAvatar->getLocalVelocity();
|
||||
auto position = myAvatar->getLocalPosition();
|
||||
auto orientation = myAvatar->getLocalOrientation();
|
||||
_rig->computeMotionAnimationState(deltaTime, position, velocity, orientation, ccState);
|
||||
|
|
|
@ -114,21 +114,6 @@ glm::vec3 HMDScriptingInterface::getPosition() const {
|
|||
return glm::vec3();
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::setPosition(const glm::vec3& position) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setPosition", Qt::QueuedConnection, Q_ARG(const glm::vec3&, position));
|
||||
return;
|
||||
} else {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
glm::mat4 hmdToSensor = myAvatar->getHMDSensorMatrix();
|
||||
glm::mat4 sensorToWorld = myAvatar->getSensorToWorldMatrix();
|
||||
glm::mat4 hmdToWorld = sensorToWorld * hmdToSensor;
|
||||
setTranslation(hmdToWorld, position);
|
||||
sensorToWorld = hmdToWorld * glm::inverse(hmdToSensor);
|
||||
myAvatar->setSensorToWorldMatrix(sensorToWorld);
|
||||
}
|
||||
}
|
||||
|
||||
glm::quat HMDScriptingInterface::getOrientation() const {
|
||||
if (qApp->getActiveDisplayPlugin()->isHmd()) {
|
||||
return glm::normalize(glm::quat_cast(getWorldHMDMatrix()));
|
||||
|
@ -199,8 +184,3 @@ bool HMDScriptingInterface::isKeyboardVisible() {
|
|||
void HMDScriptingInterface::centerUI() {
|
||||
QMetaObject::invokeMethod(qApp, "centerUI", Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void HMDScriptingInterface::snapToAvatar() {
|
||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
myAvatar->updateSensorToWorldMatrix();
|
||||
}
|
||||
|
|
|
@ -25,7 +25,7 @@ class QScriptEngine;
|
|||
|
||||
class HMDScriptingInterface : public AbstractHMDScriptingInterface, public Dependency {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(glm::vec3 position READ getPosition WRITE setPosition)
|
||||
Q_PROPERTY(glm::vec3 position READ getPosition)
|
||||
Q_PROPERTY(glm::quat orientation READ getOrientation)
|
||||
Q_PROPERTY(bool mounted READ isMounted)
|
||||
|
||||
|
@ -56,7 +56,7 @@ public:
|
|||
/// not be interrupted by a keyboard popup
|
||||
/// Returns false if there is already an active keyboard displayed.
|
||||
/// Clients should re-enable the keyboard when the operation is complete and ensure
|
||||
/// that they balance any call to suppressKeyboard() that returns true with a corresponding
|
||||
/// that they balance any call to suppressKeyboard() that returns true with a corresponding
|
||||
/// call to unsuppressKeyboard() within a reasonable amount of time
|
||||
Q_INVOKABLE bool suppressKeyboard();
|
||||
|
||||
|
@ -69,9 +69,6 @@ public:
|
|||
// rotate the overlay UI sphere so that it is centered about the the current HMD position and orientation
|
||||
Q_INVOKABLE void centerUI();
|
||||
|
||||
// snap HMD to align with Avatar's current position in world-frame
|
||||
Q_INVOKABLE void snapToAvatar();
|
||||
|
||||
signals:
|
||||
bool shouldShowHandControllersChanged();
|
||||
|
||||
|
@ -85,10 +82,7 @@ public:
|
|||
private:
|
||||
// Get the position of the HMD
|
||||
glm::vec3 getPosition() const;
|
||||
|
||||
// Set the position of the HMD
|
||||
Q_INVOKABLE void setPosition(const glm::vec3& position);
|
||||
|
||||
|
||||
// Get the orientation of the HMD
|
||||
glm::quat getOrientation() const;
|
||||
|
||||
|
|
|
@ -158,6 +158,15 @@ void DialogsManager::showScriptEditor() {
|
|||
_scriptEditor->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::showTestingResults() {
|
||||
if (!_testingDialog) {
|
||||
_testingDialog = new TestingDialog(qApp->getWindow());
|
||||
connect(_testingDialog, SIGNAL(closed()), _testingDialog, SLOT(deleteLater()));
|
||||
}
|
||||
_testingDialog->show();
|
||||
_testingDialog->raise();
|
||||
}
|
||||
|
||||
void DialogsManager::showDomainConnectionDialog() {
|
||||
// if the dialog already exists we delete it so the connection data is refreshed
|
||||
if (_domainConnectionDialog) {
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#include <DependencyManager.h>
|
||||
|
||||
#include "HMDToolsDialog.h"
|
||||
#include "TestingDialog.h"
|
||||
|
||||
class AnimationsDialog;
|
||||
class AttachmentsDialog;
|
||||
|
@ -26,6 +27,7 @@ class DiskCacheEditor;
|
|||
class LodToolsDialog;
|
||||
class OctreeStatsDialog;
|
||||
class ScriptEditorWindow;
|
||||
class TestingDialog;
|
||||
class QMessageBox;
|
||||
class DomainConnectionDialog;
|
||||
|
||||
|
@ -38,6 +40,7 @@ public:
|
|||
QPointer<HMDToolsDialog> getHMDToolsDialog() const { return _hmdToolsDialog; }
|
||||
QPointer<LodToolsDialog> getLodToolsDialog() const { return _lodToolsDialog; }
|
||||
QPointer<OctreeStatsDialog> getOctreeStatsDialog() const { return _octreeStatsDialog; }
|
||||
QPointer<TestingDialog> getTestingDialog() const { return _testingDialog; }
|
||||
void emitAddressBarShown(bool visible) { emit addressBarShown(visible); }
|
||||
|
||||
public slots:
|
||||
|
@ -55,6 +58,7 @@ public slots:
|
|||
void hmdTools(bool showTools);
|
||||
void showScriptEditor();
|
||||
void showDomainConnectionDialog();
|
||||
void showTestingResults();
|
||||
|
||||
// Application Update
|
||||
void showUpdateDialog();
|
||||
|
@ -83,6 +87,7 @@ private:
|
|||
QPointer<LodToolsDialog> _lodToolsDialog;
|
||||
QPointer<OctreeStatsDialog> _octreeStatsDialog;
|
||||
QPointer<ScriptEditorWindow> _scriptEditor;
|
||||
QPointer<TestingDialog> _testingDialog;
|
||||
QPointer<DomainConnectionDialog> _domainConnectionDialog;
|
||||
};
|
||||
|
||||
|
|
35
interface/src/ui/TestingDialog.cpp
Normal file
35
interface/src/ui/TestingDialog.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// TestingDialog.cpp
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Ryan Jones on 12/3/2016.
|
||||
// Copyright 2016 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 "ScriptEngines.h"
|
||||
|
||||
#include "ui/TestingDialog.h"
|
||||
#include "Application.h"
|
||||
|
||||
TestingDialog::TestingDialog(QWidget* parent) :
|
||||
QDialog(parent, Qt::Window | Qt::WindowCloseButtonHint | Qt::WindowStaysOnTopHint),
|
||||
_console(new JSConsole(this))
|
||||
{
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setWindowTitle(windowLabel);
|
||||
|
||||
_console->setFixedHeight(TESTING_CONSOLE_HEIGHT);
|
||||
|
||||
auto _engines = DependencyManager::get<ScriptEngines>();
|
||||
_engine = _engines->loadScript(qApp->applicationDirPath() + testRunnerRelativePath);
|
||||
_console->setScriptEngine(_engine);
|
||||
connect(_engine, &ScriptEngine::finished, this, &TestingDialog::onTestingFinished);
|
||||
}
|
||||
|
||||
void TestingDialog::onTestingFinished(const QString& scriptPath) {
|
||||
_engine = nullptr;
|
||||
_console->setScriptEngine(nullptr);
|
||||
}
|
35
interface/src/ui/TestingDialog.h
Normal file
35
interface/src/ui/TestingDialog.h
Normal file
|
@ -0,0 +1,35 @@
|
|||
//
|
||||
// TestingDialog.h
|
||||
// interface/src/ui
|
||||
//
|
||||
// Created by Ryan Jones on 12/3/2016.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_TestingDialog_h
|
||||
#define hifi_TestingDialog_h
|
||||
|
||||
#include <QDialog>
|
||||
#include "ScriptEngine.h"
|
||||
#include "JSConsole.h"
|
||||
|
||||
const QString windowLabel = "Client Script Tests";
|
||||
const QString testRunnerRelativePath = "/scripts/developer/tests/unit_tests/testRunner.js";
|
||||
const unsigned int TESTING_CONSOLE_HEIGHT = 400;
|
||||
|
||||
class TestingDialog : public QDialog {
|
||||
Q_OBJECT
|
||||
public:
|
||||
TestingDialog(QWidget* parent);
|
||||
|
||||
void onTestingFinished(const QString& scriptPath);
|
||||
|
||||
private:
|
||||
std::unique_ptr<JSConsole> _console;
|
||||
ScriptEngine* _engine;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -24,7 +24,7 @@ AnimBlendLinear::~AnimBlendLinear() {
|
|||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
|
||||
const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
_alpha = animVars.lookup(_alphaVar, _alpha);
|
||||
|
||||
|
@ -33,7 +33,7 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con
|
|||
pose = AnimPose::identity;
|
||||
}
|
||||
} else if (_children.size() == 1) {
|
||||
_poses = _children[0]->evaluate(animVars, context, dt, triggersOut);
|
||||
_poses = _children[0]->evaluate(animVars, dt, triggersOut);
|
||||
} else {
|
||||
|
||||
float clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1));
|
||||
|
@ -41,7 +41,7 @@ const AnimPoseVec& AnimBlendLinear::evaluate(const AnimVariantMap& animVars, con
|
|||
size_t nextPoseIndex = glm::ceil(clampedAlpha);
|
||||
float alpha = glm::fract(clampedAlpha);
|
||||
|
||||
evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt);
|
||||
evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, dt);
|
||||
}
|
||||
return _poses;
|
||||
}
|
||||
|
@ -51,15 +51,15 @@ const AnimPoseVec& AnimBlendLinear::getPosesInternal() const {
|
|||
return _poses;
|
||||
}
|
||||
|
||||
void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha,
|
||||
void AnimBlendLinear::evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha,
|
||||
size_t prevPoseIndex, size_t nextPoseIndex, float dt) {
|
||||
if (prevPoseIndex == nextPoseIndex) {
|
||||
// this can happen if alpha is on an integer boundary
|
||||
_poses = _children[prevPoseIndex]->evaluate(animVars, context, dt, triggersOut);
|
||||
_poses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut);
|
||||
} else {
|
||||
// need to eval and blend between two children.
|
||||
auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, context, dt, triggersOut);
|
||||
auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, context, dt, triggersOut);
|
||||
auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, dt, triggersOut);
|
||||
auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, dt, triggersOut);
|
||||
|
||||
if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) {
|
||||
_poses.resize(prevPoses.size());
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
AnimBlendLinear(const QString& id, float alpha);
|
||||
virtual ~AnimBlendLinear() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
||||
|
||||
|
@ -38,7 +38,7 @@ protected:
|
|||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
||||
|
||||
void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha,
|
||||
void evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha,
|
||||
size_t prevPoseIndex, size_t nextPoseIndex, float dt);
|
||||
|
||||
AnimPoseVec _poses;
|
||||
|
|
|
@ -26,7 +26,7 @@ AnimBlendLinearMove::~AnimBlendLinearMove() {
|
|||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
|
||||
const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
assert(_children.size() == _characteristicSpeeds.size());
|
||||
|
||||
|
@ -43,7 +43,7 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars,
|
|||
const int nextPoseIndex = 0;
|
||||
float prevDeltaTime, nextDeltaTime;
|
||||
setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut);
|
||||
evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime);
|
||||
evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime);
|
||||
} else {
|
||||
|
||||
auto clampedAlpha = glm::clamp(_alpha, 0.0f, (float)(_children.size() - 1));
|
||||
|
@ -52,7 +52,7 @@ const AnimPoseVec& AnimBlendLinearMove::evaluate(const AnimVariantMap& animVars,
|
|||
auto alpha = glm::fract(clampedAlpha);
|
||||
float prevDeltaTime, nextDeltaTime;
|
||||
setFrameAndPhase(dt, alpha, prevPoseIndex, nextPoseIndex, &prevDeltaTime, &nextDeltaTime, triggersOut);
|
||||
evaluateAndBlendChildren(animVars, context, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime);
|
||||
evaluateAndBlendChildren(animVars, triggersOut, alpha, prevPoseIndex, nextPoseIndex, prevDeltaTime, nextDeltaTime);
|
||||
}
|
||||
return _poses;
|
||||
}
|
||||
|
@ -62,16 +62,16 @@ const AnimPoseVec& AnimBlendLinearMove::getPosesInternal() const {
|
|||
return _poses;
|
||||
}
|
||||
|
||||
void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha,
|
||||
void AnimBlendLinearMove::evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha,
|
||||
size_t prevPoseIndex, size_t nextPoseIndex,
|
||||
float prevDeltaTime, float nextDeltaTime) {
|
||||
if (prevPoseIndex == nextPoseIndex) {
|
||||
// this can happen if alpha is on an integer boundary
|
||||
_poses = _children[prevPoseIndex]->evaluate(animVars, context, prevDeltaTime, triggersOut);
|
||||
_poses = _children[prevPoseIndex]->evaluate(animVars, prevDeltaTime, triggersOut);
|
||||
} else {
|
||||
// need to eval and blend between two children.
|
||||
auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, context, prevDeltaTime, triggersOut);
|
||||
auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, context, nextDeltaTime, triggersOut);
|
||||
auto prevPoses = _children[prevPoseIndex]->evaluate(animVars, prevDeltaTime, triggersOut);
|
||||
auto nextPoses = _children[nextPoseIndex]->evaluate(animVars, nextDeltaTime, triggersOut);
|
||||
|
||||
if (prevPoses.size() > 0 && prevPoses.size() == nextPoses.size()) {
|
||||
_poses.resize(prevPoses.size());
|
||||
|
|
|
@ -39,7 +39,7 @@ public:
|
|||
AnimBlendLinearMove(const QString& id, float alpha, float desiredSpeed, const std::vector<float>& characteristicSpeeds);
|
||||
virtual ~AnimBlendLinearMove() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
||||
void setDesiredSpeedVar(const QString& desiredSpeedVar) { _desiredSpeedVar = desiredSpeedVar; }
|
||||
|
@ -48,7 +48,7 @@ protected:
|
|||
// for AnimDebugDraw rendering
|
||||
virtual const AnimPoseVec& getPosesInternal() const override;
|
||||
|
||||
void evaluateAndBlendChildren(const AnimVariantMap& animVars, const AnimContext& context, Triggers& triggersOut, float alpha,
|
||||
void evaluateAndBlendChildren(const AnimVariantMap& animVars, Triggers& triggersOut, float alpha,
|
||||
size_t prevPoseIndex, size_t nextPoseIndex,
|
||||
float prevDeltaTime, float nextDeltaTime);
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@ AnimClip::~AnimClip() {
|
|||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
|
||||
const AnimPoseVec& AnimClip::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
// lookup parameters from animVars, using current instance variables as defaults.
|
||||
_startFrame = animVars.lookup(_startFrameVar, _startFrame);
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
AnimClip(const QString& id, const QString& url, float startFrame, float endFrame, float timeScale, bool loopFlag, bool mirrorFlag);
|
||||
virtual ~AnimClip() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setStartFrameVar(const QString& startFrameVar) { _startFrameVar = startFrameVar; }
|
||||
void setEndFrameVar(const QString& endFrameVar) { _endFrameVar = endFrameVar; }
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// AnimContext.cpp
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/19/16.
|
||||
// Copyright (c) 2016 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#include "AnimContext.h"
|
||||
|
||||
AnimContext::AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix) :
|
||||
_enableDebugDrawIKTargets(enableDebugDrawIKTargets),
|
||||
_geometryToRigMatrix(geometryToRigMatrix) {
|
||||
}
|
|
@ -1,30 +0,0 @@
|
|||
//
|
||||
// AnimContext.h
|
||||
//
|
||||
// Created by Anthony J. Thibault on 9/19/16.
|
||||
// Copyright (c) 2016 High Fidelity, Inc. All rights reserved.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AnimContext_h
|
||||
#define hifi_AnimContext_h
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
|
||||
class AnimContext {
|
||||
public:
|
||||
AnimContext(bool enableDebugDrawIKTargets, const glm::mat4& geometryToRigMatrix);
|
||||
|
||||
bool getEnableDebugDrawIKTargets() const { return _enableDebugDrawIKTargets; }
|
||||
const glm::mat4& getGeometryToRigMatrix() const { return _geometryToRigMatrix; }
|
||||
|
||||
protected:
|
||||
|
||||
bool _enableDebugDrawIKTargets { false };
|
||||
glm::mat4 _geometryToRigMatrix;
|
||||
};
|
||||
|
||||
#endif // hifi_AnimContext_h
|
|
@ -14,8 +14,6 @@
|
|||
#include <NumericalConstants.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <shared/NsightHelpers.h>
|
||||
#include <DebugDraw.h>
|
||||
#include "Rig.h"
|
||||
|
||||
#include "ElbowConstraint.h"
|
||||
#include "SwingTwistConstraint.h"
|
||||
|
@ -378,14 +376,14 @@ int AnimInverseKinematics::solveTargetWithCCD(const IKTarget& target, AnimPoseVe
|
|||
}
|
||||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) {
|
||||
const AnimPoseVec& AnimInverseKinematics::evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) {
|
||||
// don't call this function, call overlay() instead
|
||||
assert(false);
|
||||
return _relativePoses;
|
||||
}
|
||||
|
||||
//virtual
|
||||
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
|
||||
const float MAX_OVERLAY_DT = 1.0f / 30.0f; // what to clamp delta-time to in AnimInverseKinematics::overlay
|
||||
if (dt > MAX_OVERLAY_DT) {
|
||||
|
@ -439,28 +437,6 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
computeTargets(animVars, targets, underPoses);
|
||||
}
|
||||
|
||||
// debug render ik targets
|
||||
if (context.getEnableDebugDrawIKTargets()) {
|
||||
const vec4 WHITE(1.0f);
|
||||
glm::mat4 rigToAvatarMat = createMatFromQuatAndPos(Quaternions::Y_180, glm::vec3());
|
||||
|
||||
for (auto& target : targets) {
|
||||
glm::mat4 geomTargetMat = createMatFromQuatAndPos(target.getRotation(), target.getTranslation());
|
||||
glm::mat4 avatarTargetMat = rigToAvatarMat * context.getGeometryToRigMatrix() * geomTargetMat;
|
||||
|
||||
std::string name = "ikTarget" + std::to_string(target.getIndex());
|
||||
DebugDraw::getInstance().addMyAvatarMarker(name, glmExtractRotation(avatarTargetMat), extractTranslation(avatarTargetMat), WHITE);
|
||||
}
|
||||
} else if (context.getEnableDebugDrawIKTargets() != _previousEnableDebugIKTargets) {
|
||||
// remove markers if they were added last frame.
|
||||
for (auto& target : targets) {
|
||||
std::string name = "ikTarget" + std::to_string(target.getIndex());
|
||||
DebugDraw::getInstance().removeMyAvatarMarker(name);
|
||||
}
|
||||
}
|
||||
|
||||
_previousEnableDebugIKTargets = context.getEnableDebugDrawIKTargets();
|
||||
|
||||
if (targets.empty()) {
|
||||
// no IK targets but still need to enforce constraints
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.begin();
|
||||
|
@ -510,13 +486,7 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
// measure new _hipsOffset for next frame
|
||||
// by looking for discrepancies between where a targeted endEffector is
|
||||
// and where it wants to be (after IK solutions are done)
|
||||
|
||||
// OUTOFBODY_HACK:use weighted average between HMD and other targets
|
||||
float HMD_WEIGHT = 10.0f;
|
||||
float OTHER_WEIGHT = 1.0f;
|
||||
float totalWeight = 0.0f;
|
||||
|
||||
glm::vec3 additionalHipsOffset = Vectors::ZERO;
|
||||
glm::vec3 newHipsOffset = Vectors::ZERO;
|
||||
for (auto& target: targets) {
|
||||
int targetIndex = target.getIndex();
|
||||
if (targetIndex == _headIndex && _headIndex != -1) {
|
||||
|
@ -527,45 +497,26 @@ const AnimPoseVec& AnimInverseKinematics::overlay(const AnimVariantMap& animVars
|
|||
glm::vec3 under = _skeleton->getAbsolutePose(_headIndex, underPoses).trans;
|
||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
|
||||
const float HEAD_OFFSET_SLAVE_FACTOR = 0.65f;
|
||||
additionalHipsOffset += (OTHER_WEIGHT * HEAD_OFFSET_SLAVE_FACTOR) * (under- actual);
|
||||
totalWeight += OTHER_WEIGHT;
|
||||
newHipsOffset += HEAD_OFFSET_SLAVE_FACTOR * (actual - under);
|
||||
} else if (target.getType() == IKTarget::Type::HmdHead) {
|
||||
// we want to shift the hips to bring the head to its designated position
|
||||
glm::vec3 actual = _skeleton->getAbsolutePose(_headIndex, _relativePoses).trans;
|
||||
glm::vec3 thisOffset = target.getTranslation() - actual;
|
||||
glm::vec3 futureHipsOffset = _hipsOffset + thisOffset;
|
||||
if (glm::length(glm::vec2(futureHipsOffset.x, futureHipsOffset.z)) < _maxHipsOffsetLength) {
|
||||
// it is imperative to shift the hips and bring the head to its designated position
|
||||
// so we slam newHipsOffset here and ignore all other targets
|
||||
additionalHipsOffset = futureHipsOffset - _hipsOffset;
|
||||
totalWeight = 0.0f;
|
||||
break;
|
||||
} else {
|
||||
additionalHipsOffset += HMD_WEIGHT * (target.getTranslation() - actual);
|
||||
totalWeight += HMD_WEIGHT;
|
||||
}
|
||||
_hipsOffset += target.getTranslation() - actual;
|
||||
// and ignore all other targets
|
||||
newHipsOffset = _hipsOffset;
|
||||
break;
|
||||
}
|
||||
} else if (target.getType() == IKTarget::Type::RotationAndPosition) {
|
||||
glm::vec3 actualPosition = _skeleton->getAbsolutePose(targetIndex, _relativePoses).trans;
|
||||
glm::vec3 targetPosition = target.getTranslation();
|
||||
additionalHipsOffset += OTHER_WEIGHT * (targetPosition - actualPosition);
|
||||
totalWeight += OTHER_WEIGHT;
|
||||
newHipsOffset += targetPosition - actualPosition;
|
||||
}
|
||||
}
|
||||
if (totalWeight > 1.0f) {
|
||||
additionalHipsOffset /= totalWeight;
|
||||
}
|
||||
|
||||
// smooth transitions by relaxing _hipsOffset toward the new value
|
||||
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.10f;
|
||||
const float HIPS_OFFSET_SLAVE_TIMESCALE = 0.15f;
|
||||
float tau = dt < HIPS_OFFSET_SLAVE_TIMESCALE ? dt / HIPS_OFFSET_SLAVE_TIMESCALE : 1.0f;
|
||||
_hipsOffset += additionalHipsOffset * tau;
|
||||
|
||||
// clamp the horizontal component of the hips offset
|
||||
float hipsOffsetLength2D = glm::length(glm::vec2(_hipsOffset.x, _hipsOffset.z));
|
||||
if (hipsOffsetLength2D > _maxHipsOffsetLength) {
|
||||
_hipsOffset.x *= _maxHipsOffsetLength / hipsOffsetLength2D;
|
||||
_hipsOffset.z *= _maxHipsOffsetLength / hipsOffsetLength2D;
|
||||
}
|
||||
_hipsOffset += (newHipsOffset - _hipsOffset) * tau;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -578,12 +529,6 @@ void AnimInverseKinematics::clearIKJointLimitHistory() {
|
|||
}
|
||||
}
|
||||
|
||||
void AnimInverseKinematics::setMaxHipsOffsetLength(float maxLength) {
|
||||
// OUTOFBODY_HACK: manually adjust scale here
|
||||
const float METERS_TO_CENTIMETERS = 100.0f;
|
||||
_maxHipsOffsetLength = METERS_TO_CENTIMETERS * maxLength;
|
||||
}
|
||||
|
||||
RotationConstraint* AnimInverseKinematics::getConstraint(int index) {
|
||||
RotationConstraint* constraint = nullptr;
|
||||
std::map<int, RotationConstraint*>::iterator constraintItr = _constraints.find(index);
|
||||
|
|
|
@ -34,13 +34,11 @@ public:
|
|||
|
||||
void setTargetVars(const QString& jointName, const QString& positionVar, const QString& rotationVar, const QString& typeVar);
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, AnimNode::Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, AnimNode::Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
|
||||
|
||||
void clearIKJointLimitHistory();
|
||||
|
||||
void setMaxHipsOffsetLength(float maxLength);
|
||||
|
||||
protected:
|
||||
void computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses);
|
||||
void solveWithCyclicCoordinateDescent(const std::vector<IKTarget>& targets);
|
||||
|
@ -85,7 +83,6 @@ protected:
|
|||
|
||||
// experimental data for moving hips during IK
|
||||
glm::vec3 _hipsOffset { Vectors::ZERO };
|
||||
float _maxHipsOffsetLength { 1.0f };
|
||||
int _headIndex { -1 };
|
||||
int _hipsIndex { -1 };
|
||||
int _hipsParentIndex { -1 };
|
||||
|
@ -93,8 +90,6 @@ protected:
|
|||
// _maxTargetIndex is tracked to help optimize the recalculation of absolute poses
|
||||
// during the the cyclic coordinate descent algorithm
|
||||
int _maxTargetIndex { 0 };
|
||||
|
||||
bool _previousEnableDebugIKTargets { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AnimInverseKinematics_h
|
||||
|
|
|
@ -22,11 +22,11 @@ AnimManipulator::~AnimManipulator() {
|
|||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
|
||||
return overlay(animVars, context, dt, triggersOut, _skeleton->getRelativeBindPoses());
|
||||
const AnimPoseVec& AnimManipulator::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
return overlay(animVars, dt, triggersOut, _skeleton->getRelativeBindPoses());
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
const AnimPoseVec& AnimManipulator::overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
_alpha = animVars.lookup(_alphaVar, _alpha);
|
||||
|
||||
_poses = underPoses;
|
||||
|
|
|
@ -22,8 +22,8 @@ public:
|
|||
AnimManipulator(const QString& id, float alpha);
|
||||
virtual ~AnimManipulator() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) override;
|
||||
|
||||
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
||||
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
#include "AnimSkeleton.h"
|
||||
#include "AnimVariant.h"
|
||||
#include "AnimContext.h"
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
|
@ -73,10 +72,9 @@ public:
|
|||
|
||||
AnimSkeleton::ConstPointer getSkeleton() const { return _skeleton; }
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) = 0;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut,
|
||||
const AnimPoseVec& underPoses) {
|
||||
return evaluate(animVars, context, dt, triggersOut);
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) = 0;
|
||||
virtual const AnimPoseVec& overlay(const AnimVariantMap& animVars, float dt, Triggers& triggersOut, const AnimPoseVec& underPoses) {
|
||||
return evaluate(animVars, dt, triggersOut);
|
||||
}
|
||||
|
||||
void setCurrentFrame(float frame);
|
||||
|
|
|
@ -39,7 +39,7 @@ void AnimOverlay::buildBoneSet(BoneSet boneSet) {
|
|||
}
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
|
||||
const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
// lookup parameters from animVars, using current instance variables as defaults.
|
||||
// NOTE: switching bonesets can be an expensive operation, let's try to avoid it.
|
||||
|
@ -51,8 +51,8 @@ const AnimPoseVec& AnimOverlay::evaluate(const AnimVariantMap& animVars, const A
|
|||
_alpha = animVars.lookup(_alphaVar, _alpha);
|
||||
|
||||
if (_children.size() >= 2) {
|
||||
auto& underPoses = _children[1]->evaluate(animVars, context, dt, triggersOut);
|
||||
auto& overPoses = _children[0]->overlay(animVars, context, dt, triggersOut, underPoses);
|
||||
auto& underPoses = _children[1]->evaluate(animVars, dt, triggersOut);
|
||||
auto& overPoses = _children[0]->overlay(animVars, dt, triggersOut, underPoses);
|
||||
|
||||
if (underPoses.size() > 0 && underPoses.size() == overPoses.size()) {
|
||||
_poses.resize(underPoses.size());
|
||||
|
|
|
@ -43,7 +43,7 @@ public:
|
|||
AnimOverlay(const QString& id, BoneSet boneSet, float alpha);
|
||||
virtual ~AnimOverlay() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setBoneSetVar(const QString& boneSetVar) { _boneSetVar = boneSetVar; }
|
||||
void setAlphaVar(const QString& alphaVar) { _alphaVar = alphaVar; }
|
||||
|
|
|
@ -21,7 +21,7 @@ AnimStateMachine::~AnimStateMachine() {
|
|||
|
||||
}
|
||||
|
||||
const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) {
|
||||
const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) {
|
||||
|
||||
QString desiredStateID = animVars.lookup(_currentStateVar, _currentState->getID());
|
||||
if (_currentState->getID() != desiredStateID) {
|
||||
|
@ -29,7 +29,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
|
|||
bool foundState = false;
|
||||
for (auto& state : _states) {
|
||||
if (state->getID() == desiredStateID) {
|
||||
switchState(animVars, context, state);
|
||||
switchState(animVars, state);
|
||||
foundState = true;
|
||||
break;
|
||||
}
|
||||
|
@ -42,7 +42,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
|
|||
// evaluate currentState transitions
|
||||
auto desiredState = evaluateTransitions(animVars);
|
||||
if (desiredState != _currentState) {
|
||||
switchState(animVars, context, desiredState);
|
||||
switchState(animVars, desiredState);
|
||||
}
|
||||
|
||||
assert(_currentState);
|
||||
|
@ -62,7 +62,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
|
|||
} else if (_interpType == InterpType::SnapshotPrev) {
|
||||
// interp between the prev snapshot and evaluated next target.
|
||||
// this is useful for interping into a blend
|
||||
localNextPoses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
localNextPoses = currentStateNode->evaluate(animVars, dt, triggersOut);
|
||||
prevPoses = &_prevPoses;
|
||||
nextPoses = &localNextPoses;
|
||||
} else {
|
||||
|
@ -79,7 +79,7 @@ const AnimPoseVec& AnimStateMachine::evaluate(const AnimVariantMap& animVars, co
|
|||
}
|
||||
}
|
||||
if (!_duringInterp) {
|
||||
_poses = currentStateNode->evaluate(animVars, context, dt, triggersOut);
|
||||
_poses = currentStateNode->evaluate(animVars, dt, triggersOut);
|
||||
}
|
||||
return _poses;
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ void AnimStateMachine::addState(State::Pointer state) {
|
|||
_states.push_back(state);
|
||||
}
|
||||
|
||||
void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimContext& context, State::Pointer desiredState) {
|
||||
void AnimStateMachine::switchState(const AnimVariantMap& animVars, State::Pointer desiredState) {
|
||||
|
||||
const float FRAMES_PER_SECOND = 30.0f;
|
||||
|
||||
|
@ -114,7 +114,7 @@ void AnimStateMachine::switchState(const AnimVariantMap& animVars, const AnimCon
|
|||
_prevPoses = _poses;
|
||||
// snapshot next pose at the target frame.
|
||||
nextStateNode->setCurrentFrame(desiredState->_interpTarget);
|
||||
_nextPoses = nextStateNode->evaluate(animVars, context, dt, triggers);
|
||||
_nextPoses = nextStateNode->evaluate(animVars, dt, triggers);
|
||||
} else if (_interpType == InterpType::SnapshotPrev) {
|
||||
// snapshot previoius pose
|
||||
_prevPoses = _poses;
|
||||
|
|
|
@ -113,7 +113,7 @@ public:
|
|||
explicit AnimStateMachine(const QString& id);
|
||||
virtual ~AnimStateMachine() override;
|
||||
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, const AnimContext& context, float dt, Triggers& triggersOut) override;
|
||||
virtual const AnimPoseVec& evaluate(const AnimVariantMap& animVars, float dt, Triggers& triggersOut) override;
|
||||
|
||||
void setCurrentStateVar(QString& currentStateVar) { _currentStateVar = currentStateVar; }
|
||||
|
||||
|
@ -123,7 +123,7 @@ protected:
|
|||
|
||||
void addState(State::Pointer state);
|
||||
|
||||
void switchState(const AnimVariantMap& animVars, const AnimContext& context, State::Pointer desiredState);
|
||||
void switchState(const AnimVariantMap& animVars, State::Pointer desiredState);
|
||||
State::Pointer evaluateTransitions(const AnimVariantMap& animVars) const;
|
||||
|
||||
// for AnimDebugDraw rendering
|
||||
|
|
|
@ -298,6 +298,7 @@ void Rig::clearJointAnimationPriority(int index) {
|
|||
void Rig::clearIKJointLimitHistory() {
|
||||
if (_animNode) {
|
||||
_animNode->traverse([&](AnimNode::Pointer node) {
|
||||
// only report clip nodes as valid roles.
|
||||
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
|
||||
if (ikNode) {
|
||||
ikNode->clearIKJointLimitHistory();
|
||||
|
@ -307,30 +308,6 @@ void Rig::clearIKJointLimitHistory() {
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::updateMaxHipsOffsetLength(float maxLength, float deltaTime) {
|
||||
|
||||
_desiredMaxHipsOffsetLength = maxLength;
|
||||
|
||||
// OUTOFBODY_HACK: smoothly update update _hipsOffsetLength, otherwise we risk introducing oscillation in the hips offset.
|
||||
const float MAX_HIPS_OFFSET_TIMESCALE = 0.33f;
|
||||
float tau = deltaTime / MAX_HIPS_OFFSET_TIMESCALE;
|
||||
_maxHipsOffsetLength = (1.0f - tau) * _maxHipsOffsetLength + tau * _desiredMaxHipsOffsetLength;
|
||||
|
||||
if (_animNode) {
|
||||
_animNode->traverse([&](AnimNode::Pointer node) {
|
||||
auto ikNode = std::dynamic_pointer_cast<AnimInverseKinematics>(node);
|
||||
if (ikNode) {
|
||||
ikNode->setMaxHipsOffsetLength(_maxHipsOffsetLength);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
float Rig::getMaxHipsOffsetLength() const {
|
||||
return _maxHipsOffsetLength;
|
||||
}
|
||||
|
||||
int Rig::getJointParentIndex(int childIndex) const {
|
||||
if (_animSkeleton && isIndexValid(childIndex)) {
|
||||
return _animSkeleton->getParentIndex(childIndex);
|
||||
|
@ -518,22 +495,15 @@ bool Rig::getRelativeDefaultJointTranslation(int index, glm::vec3& translationOu
|
|||
}
|
||||
|
||||
// animation reference speeds.
|
||||
static const std::vector<float> FORWARD_SPEEDS = { 0.4f, 1.3f, 4.5f }; // m/s
|
||||
static const std::vector<float> BACKWARD_SPEEDS = { 0.6f, 1.05f }; // m/s
|
||||
static const std::vector<float> LATERAL_SPEEDS = { 0.2f, 0.5f }; // m/s
|
||||
static const float DEFAULT_AVATAR_EYE_HEIGHT = 1.65f; // movement speeds are for characters of this eye-height. ~170 cm tall.
|
||||
static const std::vector<float> FORWARD_SPEEDS = { 0.4f, 1.4f, 4.5f }; // m/s
|
||||
static const std::vector<float> BACKWARD_SPEEDS = { 0.6f, 1.45f }; // m/s
|
||||
static const std::vector<float> LATERAL_SPEEDS = { 0.2f, 0.65f }; // m/s
|
||||
|
||||
void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPosition, const glm::vec3& worldVelocity, const glm::quat& worldRotation, CharacterControllerState ccState) {
|
||||
|
||||
glm::vec3 front = worldRotation * IDENTITY_FRONT;
|
||||
glm::vec3 workingVelocity = worldVelocity;
|
||||
|
||||
// TODO: account for avatar scaling
|
||||
int eyeJoint = indexOfJoint("LeftEye");
|
||||
int toeJoint = indexOfJoint("LeftToeBase");
|
||||
const float AVATAR_EYE_HEIGHT = (eyeJoint >= 0 && toeJoint >= 0) ? getAbsoluteDefaultPose(eyeJoint).trans.y - getAbsoluteDefaultPose(toeJoint).trans.y : DEFAULT_AVATAR_EYE_HEIGHT;
|
||||
const float AVATAR_HEIGHT_RATIO = DEFAULT_AVATAR_EYE_HEIGHT / AVATAR_EYE_HEIGHT;
|
||||
|
||||
{
|
||||
glm::vec3 localVel = glm::inverse(worldRotation) * workingVelocity;
|
||||
|
||||
|
@ -553,22 +523,18 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
float moveBackwardAlpha = 0.0f;
|
||||
float moveLateralAlpha = 0.0f;
|
||||
|
||||
float averageForwardSpeed = AVATAR_HEIGHT_RATIO * _averageForwardSpeed.getAverage();
|
||||
float averageBackwardSpeed = -averageForwardSpeed;
|
||||
float averageLateralSpeed = AVATAR_HEIGHT_RATIO * fabsf(_averageLateralSpeed.getAverage());
|
||||
|
||||
// calcuate the animation alpha and timeScale values based on current speeds and animation reference speeds.
|
||||
calcAnimAlpha(averageForwardSpeed, FORWARD_SPEEDS, &moveForwardAlpha);
|
||||
calcAnimAlpha(averageBackwardSpeed, BACKWARD_SPEEDS, &moveBackwardAlpha);
|
||||
calcAnimAlpha(averageLateralSpeed, LATERAL_SPEEDS, &moveLateralAlpha);
|
||||
calcAnimAlpha(_averageForwardSpeed.getAverage(), FORWARD_SPEEDS, &moveForwardAlpha);
|
||||
calcAnimAlpha(-_averageForwardSpeed.getAverage(), BACKWARD_SPEEDS, &moveBackwardAlpha);
|
||||
calcAnimAlpha(fabsf(_averageLateralSpeed.getAverage()), LATERAL_SPEEDS, &moveLateralAlpha);
|
||||
|
||||
_animVars.set("moveForwardSpeed", averageForwardSpeed);
|
||||
_animVars.set("moveForwardSpeed", _averageForwardSpeed.getAverage());
|
||||
_animVars.set("moveForwardAlpha", moveForwardAlpha);
|
||||
|
||||
_animVars.set("moveBackwardSpeed", averageBackwardSpeed);
|
||||
_animVars.set("moveBackwardSpeed", -_averageForwardSpeed.getAverage());
|
||||
_animVars.set("moveBackwardAlpha", moveBackwardAlpha);
|
||||
|
||||
_animVars.set("moveLateralSpeed", averageLateralSpeed);
|
||||
_animVars.set("moveLateralSpeed", fabsf(_averageLateralSpeed.getAverage()));
|
||||
_animVars.set("moveLateralAlpha", moveLateralAlpha);
|
||||
|
||||
const float MOVE_ENTER_SPEED_THRESHOLD = 0.2f; // m/sec
|
||||
|
@ -626,7 +592,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
}
|
||||
}
|
||||
|
||||
const float STATE_CHANGE_HYSTERESIS_TIMER = 1.0f / 60.0f;
|
||||
const float STATE_CHANGE_HYSTERESIS_TIMER = 0.1f;
|
||||
|
||||
// Skip hystersis timer for jump transitions.
|
||||
if (_desiredState == RigRole::Takeoff) {
|
||||
|
@ -925,11 +891,9 @@ void Rig::updateAnimations(float deltaTime, glm::mat4 rootTransform) {
|
|||
updateAnimationStateHandlers();
|
||||
_animVars.setRigToGeometryTransform(_rigToGeometryTransform);
|
||||
|
||||
AnimContext context(_enableDebugDrawIKTargets, getGeometryToRigTransform());
|
||||
|
||||
// evaluate the animation
|
||||
AnimNode::Triggers triggersOut;
|
||||
_internalPoseSet._relativePoses = _animNode->evaluate(_animVars, context, deltaTime, triggersOut);
|
||||
_internalPoseSet._relativePoses = _animNode->evaluate(_animVars, deltaTime, triggersOut);
|
||||
if ((int)_internalPoseSet._relativePoses.size() != _animSkeleton->getNumJoints()) {
|
||||
// animations haven't fully loaded yet.
|
||||
_internalPoseSet._relativePoses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
@ -1058,6 +1022,20 @@ void Rig::updateNeckJoint(int index, const HeadParameters& params) {
|
|||
AnimPose hmdPose(glm::vec3(1.0f), params.rigHeadOrientation * yFlip180, params.rigHeadPosition);
|
||||
computeHeadNeckAnimVars(hmdPose, headPos, headRot, neckPos, neckRot);
|
||||
|
||||
// debug rendering
|
||||
#ifdef DEBUG_RENDERING
|
||||
const glm::vec4 red(1.0f, 0.0f, 0.0f, 1.0f);
|
||||
const glm::vec4 green(0.0f, 1.0f, 0.0f, 1.0f);
|
||||
|
||||
// transform from bone into avatar space
|
||||
AnimPose headPose(glm::vec3(1), headRot, headPos);
|
||||
DebugDraw::getInstance().addMyAvatarMarker("headTarget", headPose.rot, headPose.trans, red);
|
||||
|
||||
// transform from bone into avatar space
|
||||
AnimPose neckPose(glm::vec3(1), neckRot, neckPos);
|
||||
DebugDraw::getInstance().addMyAvatarMarker("neckTarget", neckPose.rot, neckPose.trans, green);
|
||||
#endif
|
||||
|
||||
_animVars.set("headPosition", headPos);
|
||||
_animVars.set("headRotation", headRot);
|
||||
_animVars.set("headType", (int)IKTarget::Type::HmdHead);
|
||||
|
@ -1130,9 +1108,8 @@ void Rig::updateFromHandParameters(const HandParameters& params, float dt) {
|
|||
|
||||
if (params.isLeftEnabled) {
|
||||
|
||||
glm::vec3 handPosition = params.leftPosition;
|
||||
|
||||
// prevent the hand IK targets from intersecting the body capsule
|
||||
glm::vec3 handPosition = params.leftPosition;
|
||||
glm::vec3 displacement;
|
||||
if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) {
|
||||
handPosition -= displacement;
|
||||
|
@ -1149,9 +1126,8 @@ void Rig::updateFromHandParameters(const HandParameters& params, float dt) {
|
|||
|
||||
if (params.isRightEnabled) {
|
||||
|
||||
glm::vec3 handPosition = params.rightPosition;
|
||||
|
||||
// prevent the hand IK targets from intersecting the body capsule
|
||||
glm::vec3 handPosition = params.rightPosition;
|
||||
glm::vec3 displacement;
|
||||
if (findSphereCapsulePenetration(handPosition, HAND_RADIUS, bodyCapsuleStart, bodyCapsuleEnd, bodyCapsuleRadius, displacement)) {
|
||||
handPosition -= displacement;
|
||||
|
@ -1403,10 +1379,9 @@ void Rig::computeAvatarBoundingCapsule(
|
|||
|
||||
// call overlay twice: once to verify AnimPoseVec joints and again to do the IK
|
||||
AnimNode::Triggers triggersOut;
|
||||
AnimContext context(false, glm::mat4());
|
||||
float dt = 1.0f; // the value of this does not matter
|
||||
ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
|
||||
AnimPoseVec finalPoses = ikNode.overlay(animVars, context, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
|
||||
ikNode.overlay(animVars, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
|
||||
AnimPoseVec finalPoses = ikNode.overlay(animVars, dt, triggersOut, _animSkeleton->getRelativeBindPoses());
|
||||
|
||||
// convert relative poses to absolute
|
||||
_animSkeleton->convertRelativePosesToAbsolute(finalPoses);
|
||||
|
|
|
@ -104,8 +104,6 @@ public:
|
|||
void clearJointAnimationPriority(int index);
|
||||
|
||||
void clearIKJointLimitHistory();
|
||||
void updateMaxHipsOffsetLength(float maxLength, float deltaTime);
|
||||
float getMaxHipsOffsetLength() const;
|
||||
|
||||
int getJointParentIndex(int childIndex) const;
|
||||
|
||||
|
@ -215,8 +213,6 @@ public:
|
|||
|
||||
const glm::mat4& getGeometryToRigTransform() const { return _geometryToRigTransform; }
|
||||
|
||||
void setEnableDebugDrawIKTargets(bool enableDebugDrawIKTargets) { _enableDebugDrawIKTargets = enableDebugDrawIKTargets; }
|
||||
|
||||
signals:
|
||||
void onLoadComplete();
|
||||
|
||||
|
@ -319,12 +315,6 @@ protected:
|
|||
bool _enableInverseKinematics { true };
|
||||
|
||||
mutable uint32_t _jointNameWarningCount { 0 };
|
||||
glm::vec3 _desiredRigHeadPosition;
|
||||
bool _truncateIKTargets { false };
|
||||
bool _enableDebugDrawIKTargets { false };
|
||||
|
||||
float _maxHipsOffsetLength { 1.0f };
|
||||
float _desiredMaxHipsOffsetLength { 1.0f };
|
||||
|
||||
private:
|
||||
QMap<int, StateHandler> _stateHandlers;
|
||||
|
|
|
@ -42,16 +42,6 @@ namespace controller {
|
|||
|
||||
Pose transform(const glm::mat4& mat) const;
|
||||
|
||||
/**jsdoc
|
||||
* Represents a hand controller pose typically received from Controller.getPoseValue
|
||||
* Unless otherwise noted all properties are in avatar space.
|
||||
*
|
||||
* @typedef Pose
|
||||
* @property translation {Vec3} position of controller
|
||||
* @property rotation {Quat} orientation of controller
|
||||
* @property velocity {Vec3} current velocity of controller (meters/sec)
|
||||
* @property angularVelocity {Vec3} current angular velocity of controller (radians/sec)
|
||||
*/
|
||||
static QScriptValue toScriptValue(QScriptEngine* engine, const Pose& event);
|
||||
static void fromScriptValue(const QScriptValue& object, Pose& event);
|
||||
};
|
||||
|
|
|
@ -360,7 +360,6 @@ void OpenGLDisplayPlugin::customizeContext() {
|
|||
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
|
||||
cursorData.texture->setUsage(usage.build());
|
||||
cursorData.texture->assignStoredMip(0, gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA), image.byteCount(), image.constBits());
|
||||
cursorData.texture->autoGenerateMips(-1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -147,15 +147,15 @@ QString getCollisionGroupAsString(uint8_t group) {
|
|||
}
|
||||
|
||||
uint8_t getCollisionGroupAsBitMask(const QStringRef& name) {
|
||||
if (0 == name.compare("dynamic")) {
|
||||
if (0 == name.compare(QString("dynamic"))) {
|
||||
return USER_COLLISION_GROUP_DYNAMIC;
|
||||
} else if (0 == name.compare("static")) {
|
||||
} else if (0 == name.compare(QString("static"))) {
|
||||
return USER_COLLISION_GROUP_STATIC;
|
||||
} else if (0 == name.compare("kinematic")) {
|
||||
} else if (0 == name.compare(QString("kinematic"))) {
|
||||
return USER_COLLISION_GROUP_KINEMATIC;
|
||||
} else if (0 == name.compare("myAvatar")) {
|
||||
} else if (0 == name.compare(QString("myAvatar"))) {
|
||||
return USER_COLLISION_GROUP_MY_AVATAR;
|
||||
} else if (0 == name.compare("otherAvatar")) {
|
||||
} else if (0 == name.compare(QString("otherAvatar"))) {
|
||||
return USER_COLLISION_GROUP_OTHER_AVATAR;
|
||||
}
|
||||
return 0;
|
||||
|
|
|
@ -69,3 +69,15 @@ QThread* RENDER_THREAD = nullptr;
|
|||
bool isRenderThread() {
|
||||
return QThread::currentThread() == RENDER_THREAD;
|
||||
}
|
||||
|
||||
namespace gl {
|
||||
void withSavedContext(const std::function<void()>& f) {
|
||||
// Save the original GL context, because creating a QML surface will create a new context
|
||||
QOpenGLContext * savedContext = QOpenGLContext::currentContext();
|
||||
QSurface * savedSurface = savedContext ? savedContext->surface() : nullptr;
|
||||
f();
|
||||
if (savedContext) {
|
||||
savedContext->makeCurrent(savedSurface);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#ifndef hifi_GLHelpers_h
|
||||
#define hifi_GLHelpers_h
|
||||
|
||||
#include <functional>
|
||||
#include <QJsonObject>
|
||||
|
||||
// 16 bits of depth precision
|
||||
|
@ -34,4 +35,8 @@ int glVersionToInteger(QString glVersion);
|
|||
|
||||
bool isRenderThread();
|
||||
|
||||
namespace gl {
|
||||
void withSavedContext(const std::function<void()>& f);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -467,40 +467,41 @@ void OffscreenQmlSurface::resize(const QSize& newSize_, bool forceResize) {
|
|||
}
|
||||
|
||||
qCDebug(glLogging) << "Offscreen UI resizing to " << newSize.width() << "x" << newSize.height();
|
||||
gl::withSavedContext([&] {
|
||||
_canvas->makeCurrent();
|
||||
|
||||
_canvas->makeCurrent();
|
||||
|
||||
// Release hold on the textures of the old size
|
||||
if (uvec2() != _size) {
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureAndFence.first) {
|
||||
offscreenTextures.releaseTexture(_latestTextureAndFence);
|
||||
_latestTextureAndFence = { 0, 0 };
|
||||
// Release hold on the textures of the old size
|
||||
if (uvec2() != _size) {
|
||||
// If the most recent texture was unused, we can directly recycle it
|
||||
if (_latestTextureAndFence.first) {
|
||||
offscreenTextures.releaseTexture(_latestTextureAndFence);
|
||||
_latestTextureAndFence = { 0, 0 };
|
||||
}
|
||||
offscreenTextures.releaseSize(_size);
|
||||
}
|
||||
offscreenTextures.releaseSize(_size);
|
||||
}
|
||||
|
||||
_size = newOffscreenSize;
|
||||
_size = newOffscreenSize;
|
||||
|
||||
// Acquire the new texture size
|
||||
if (uvec2() != _size) {
|
||||
offscreenTextures.acquireSize(_size);
|
||||
if (_depthStencil) {
|
||||
glDeleteRenderbuffers(1, &_depthStencil);
|
||||
_depthStencil = 0;
|
||||
// Acquire the new texture size
|
||||
if (uvec2() != _size) {
|
||||
offscreenTextures.acquireSize(_size);
|
||||
if (_depthStencil) {
|
||||
glDeleteRenderbuffers(1, &_depthStencil);
|
||||
_depthStencil = 0;
|
||||
}
|
||||
glGenRenderbuffers(1, &_depthStencil);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _size.x, _size.y);
|
||||
if (!_fbo) {
|
||||
glGenFramebuffers(1, &_fbo);
|
||||
}
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
glGenRenderbuffers(1, &_depthStencil);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, _depthStencil);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, _size.x, _size.y);
|
||||
if (!_fbo) {
|
||||
glGenFramebuffers(1, &_fbo);
|
||||
}
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _fbo);
|
||||
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, _depthStencil);
|
||||
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
_canvas->doneCurrent();
|
||||
_canvas->doneCurrent();
|
||||
});
|
||||
}
|
||||
|
||||
QQuickItem* OffscreenQmlSurface::getRootItem() {
|
||||
|
|
|
@ -15,6 +15,10 @@ add_dependency_external_projects(tbb)
|
|||
find_package(OpenSSL REQUIRED)
|
||||
find_package(TBB REQUIRED)
|
||||
|
||||
if (APPLE)
|
||||
find_library(FRAMEWORK_IOKIT IOKit)
|
||||
endif ()
|
||||
|
||||
if (APPLE AND ${OPENSSL_INCLUDE_DIR} STREQUAL "/usr/include")
|
||||
# this is a user on OS X using system OpenSSL, which is going to throw warnings since they're deprecating for their common crypto
|
||||
message(WARNING "The found version of OpenSSL is the OS X system version. This will produce deprecation warnings."
|
||||
|
@ -26,6 +30,11 @@ include_directories(SYSTEM "${OPENSSL_INCLUDE_DIR}")
|
|||
# append OpenSSL to our list of libraries to link
|
||||
target_link_libraries(${TARGET_NAME} ${OPENSSL_LIBRARIES} ${TBB_LIBRARIES})
|
||||
|
||||
# IOKit is needed for getting machine fingerprint
|
||||
if (APPLE)
|
||||
target_link_libraries(${TARGET_NAME} ${FRAMEWORK_IOKIT})
|
||||
endif (APPLE)
|
||||
|
||||
# libcrypto uses dlopen in libdl
|
||||
if (UNIX)
|
||||
target_link_libraries(${TARGET_NAME} ${CMAKE_DL_LIBS})
|
||||
|
|
180
libraries/networking/src/FingerprintUtils.cpp
Normal file
180
libraries/networking/src/FingerprintUtils.cpp
Normal file
|
@ -0,0 +1,180 @@
|
|||
//
|
||||
// FingerprintUtils.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by David Kelly on 2016-12-02.
|
||||
// Copyright 2016 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 "FingerprintUtils.h"
|
||||
#include <QDebug>
|
||||
#include <SettingHandle.h>
|
||||
#ifdef Q_OS_WIN
|
||||
#include <comdef.h>
|
||||
#include <Wbemidl.h>
|
||||
#endif //Q_OS_WIN
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
#include <IOKit/IOBSD.h>
|
||||
#include <IOKit/IOKitLib.h>
|
||||
#include <IOKit/storage/IOMedia.h>
|
||||
#endif //Q_OS_MAC
|
||||
|
||||
static const QString FALLBACK_FINGERPRINT_KEY = "fallbackFingerprint";
|
||||
QString FingerprintUtils::getMachineFingerprintString() {
|
||||
QString uuidString;
|
||||
#ifdef Q_OS_LINUX
|
||||
// sadly need to be root to get smbios guid from linux, so
|
||||
// for now lets do nothing.
|
||||
#endif //Q_OS_LINUX
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
io_registry_entry_t ioRegistryRoot = IORegistryEntryFromPath(kIOMasterPortDefault, "IOService:/");
|
||||
CFStringRef uuidCf = (CFStringRef) IORegistryEntryCreateCFProperty(ioRegistryRoot, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0);
|
||||
IOObjectRelease(ioRegistryRoot);
|
||||
uuidString = QString::fromCFString(uuidCf);
|
||||
CFRelease(uuidCf);
|
||||
qDebug() << "Mac serial number: " << uuidString;
|
||||
#endif //Q_OS_MAC
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
HRESULT hres;
|
||||
IWbemLocator *pLoc = NULL;
|
||||
|
||||
// initialize com
|
||||
hres = CoCreateInstance(
|
||||
CLSID_WbemLocator,
|
||||
0,
|
||||
CLSCTX_INPROC_SERVER,
|
||||
IID_IWbemLocator, (LPVOID *) &pLoc);
|
||||
|
||||
if (FAILED(hres)) {
|
||||
qDebug() << "Failed to initialize WbemLocator";
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
// Connect to WMI through the IWbemLocator::ConnectServer method
|
||||
IWbemServices *pSvc = NULL;
|
||||
|
||||
// Connect to the root\cimv2 namespace with
|
||||
// the current user and obtain pointer pSvc
|
||||
// to make IWbemServices calls.
|
||||
hres = pLoc->ConnectServer(
|
||||
_bstr_t(L"ROOT\\CIMV2"), // Object path of WMI namespace
|
||||
NULL, // User name. NULL = current user
|
||||
NULL, // User password. NULL = current
|
||||
0, // Locale. NULL indicates current
|
||||
NULL, // Security flags.
|
||||
0, // Authority (for example, Kerberos)
|
||||
0, // Context object
|
||||
&pSvc // pointer to IWbemServices proxy
|
||||
);
|
||||
|
||||
if (FAILED(hres)) {
|
||||
pLoc->Release();
|
||||
qDebug() << "Failed to connect to WMI";
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
// Set security levels on the proxy
|
||||
hres = CoSetProxyBlanket(
|
||||
pSvc, // Indicates the proxy to set
|
||||
RPC_C_AUTHN_WINNT, // RPC_C_AUTHN_xxx
|
||||
RPC_C_AUTHZ_NONE, // RPC_C_AUTHZ_xxx
|
||||
NULL, // Server principal name
|
||||
RPC_C_AUTHN_LEVEL_CALL, // RPC_C_AUTHN_LEVEL_xxx
|
||||
RPC_C_IMP_LEVEL_IMPERSONATE, // RPC_C_IMP_LEVEL_xxx
|
||||
NULL, // client identity
|
||||
EOAC_NONE // proxy capabilities
|
||||
);
|
||||
|
||||
if (FAILED(hres)) {
|
||||
pSvc->Release();
|
||||
pLoc->Release();
|
||||
qDebug() << "Failed to set security on proxy blanket";
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
// Use the IWbemServices pointer to grab the Win32_BIOS stuff
|
||||
IEnumWbemClassObject* pEnumerator = NULL;
|
||||
hres = pSvc->ExecQuery(
|
||||
bstr_t("WQL"),
|
||||
bstr_t("SELECT * FROM Win32_ComputerSystemProduct"),
|
||||
WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY,
|
||||
NULL,
|
||||
&pEnumerator);
|
||||
|
||||
if (FAILED(hres)) {
|
||||
pSvc->Release();
|
||||
pLoc->Release();
|
||||
qDebug() << "query to get Win32_ComputerSystemProduct info";
|
||||
return uuidString;
|
||||
}
|
||||
|
||||
// Get the SerialNumber from the Win32_BIOS data
|
||||
IWbemClassObject *pclsObj;
|
||||
ULONG uReturn = 0;
|
||||
|
||||
SHORT sRetStatus = -100;
|
||||
|
||||
while (pEnumerator) {
|
||||
HRESULT hr = pEnumerator->Next(WBEM_INFINITE, 1, &pclsObj, &uReturn);
|
||||
|
||||
if(0 == uReturn){
|
||||
break;
|
||||
}
|
||||
|
||||
VARIANT vtProp;
|
||||
|
||||
// Get the value of the Name property
|
||||
hr = pclsObj->Get(L"UUID", 0, &vtProp, 0, 0);
|
||||
if (!FAILED(hres)) {
|
||||
switch (vtProp.vt) {
|
||||
case VT_BSTR:
|
||||
uuidString = QString::fromWCharArray(vtProp.bstrVal);
|
||||
break;
|
||||
}
|
||||
}
|
||||
VariantClear(&vtProp);
|
||||
|
||||
pclsObj->Release();
|
||||
}
|
||||
pEnumerator->Release();
|
||||
|
||||
// Cleanup
|
||||
pSvc->Release();
|
||||
pLoc->Release();
|
||||
|
||||
qDebug() << "Windows BIOS UUID: " << uuidString;
|
||||
#endif //Q_OS_WIN
|
||||
|
||||
return uuidString;
|
||||
|
||||
}
|
||||
|
||||
QUuid FingerprintUtils::getMachineFingerprint() {
|
||||
|
||||
QString uuidString = getMachineFingerprintString();
|
||||
|
||||
// now, turn into uuid. A malformed string will
|
||||
// return QUuid() ("{00000...}"), which handles
|
||||
// any errors in getting the string
|
||||
QUuid uuid(uuidString);
|
||||
if (uuid == QUuid()) {
|
||||
// read fallback key (if any)
|
||||
Settings settings;
|
||||
uuid = QUuid(settings.value(FALLBACK_FINGERPRINT_KEY).toString());
|
||||
qDebug() << "read fallback maching fingerprint: " << uuid.toString();
|
||||
if (uuid == QUuid()) {
|
||||
// no fallback yet, set one
|
||||
uuid = QUuid::createUuid();
|
||||
settings.setValue(FALLBACK_FINGERPRINT_KEY, uuid.toString());
|
||||
qDebug() << "no fallback machine fingerprint, setting it to: " << uuid.toString();
|
||||
}
|
||||
}
|
||||
return uuid;
|
||||
}
|
||||
|
27
libraries/networking/src/FingerprintUtils.h
Normal file
27
libraries/networking/src/FingerprintUtils.h
Normal file
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// FingerprintUtils.h
|
||||
// libraries/networking/src
|
||||
//
|
||||
// Created by David Kelly on 2016-12-02.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_FingerprintUtils_h
|
||||
#define hifi_FingerprintUtils_h
|
||||
|
||||
#include <QString>
|
||||
#include <QUuid>
|
||||
|
||||
class FingerprintUtils {
|
||||
public:
|
||||
static QUuid getMachineFingerprint();
|
||||
|
||||
private:
|
||||
static QString getMachineFingerprintString();
|
||||
};
|
||||
|
||||
#endif // hifi_FingerprintUtils_h
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// BulletUtil.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.11.02
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
600
libraries/physics/src/CharacterController.cpp
Executable file → Normal file
600
libraries/physics/src/CharacterController.cpp
Executable file → Normal file
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CharacterControllerInterface.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.10.21
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
@ -13,8 +13,8 @@
|
|||
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "PhysicsCollisionGroups.h"
|
||||
#include "ObjectMotionState.h"
|
||||
#include "PhysicsHelpers.h"
|
||||
#include "PhysicsLogging.h"
|
||||
|
||||
const btVector3 LOCAL_UP_AXIS(0.0f, 1.0f, 0.0f);
|
||||
|
@ -62,10 +62,15 @@ CharacterController::CharacterMotor::CharacterMotor(const glm::vec3& vel, const
|
|||
}
|
||||
|
||||
CharacterController::CharacterController() {
|
||||
_halfHeight = 1.0f;
|
||||
|
||||
_enabled = false;
|
||||
|
||||
_floorDistance = MAX_FALL_HEIGHT;
|
||||
|
||||
_targetVelocity.setValue(0.0f, 0.0f, 0.0f);
|
||||
_followDesiredBodyTransform.setIdentity();
|
||||
_followTimeRemaining = 0.0f;
|
||||
_jumpSpeed = JUMP_SPEED;
|
||||
_state = State::Hover;
|
||||
_isPushingUp = false;
|
||||
|
@ -73,6 +78,9 @@ CharacterController::CharacterController() {
|
|||
_takeoffToInAirStartTime = 0;
|
||||
_jumpButtonDownStartTime = 0;
|
||||
_jumpButtonDownCount = 0;
|
||||
_followTime = 0.0f;
|
||||
_followLinearDisplacement = btVector3(0, 0, 0);
|
||||
_followAngularDisplacement = btQuaternion::getIdentity();
|
||||
_hasSupport = false;
|
||||
|
||||
_pendingFlags = PENDING_FLAG_UPDATE_SHAPE;
|
||||
|
@ -99,7 +107,6 @@ bool CharacterController::needsAddition() const {
|
|||
|
||||
void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
||||
if (_dynamicsWorld != world) {
|
||||
// remove from old world
|
||||
if (_dynamicsWorld) {
|
||||
if (_rigidBody) {
|
||||
_dynamicsWorld->removeRigidBody(_rigidBody);
|
||||
|
@ -108,26 +115,16 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
|||
_dynamicsWorld = nullptr;
|
||||
}
|
||||
if (world && _rigidBody) {
|
||||
// add to new world
|
||||
_dynamicsWorld = world;
|
||||
_pendingFlags &= ~PENDING_FLAG_JUMP;
|
||||
_dynamicsWorld->addRigidBody(_rigidBody, _collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR);
|
||||
// Before adding the RigidBody to the world we must save its oldGravity to the side
|
||||
// because adding an object to the world will overwrite it with the default gravity.
|
||||
btVector3 oldGravity = _rigidBody->getGravity();
|
||||
_dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, BULLET_COLLISION_MASK_MY_AVATAR);
|
||||
_dynamicsWorld->addAction(this);
|
||||
// restore gravity settings because adding an object to the world overwrites its gravity setting
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
btCollisionShape* shape = _rigidBody->getCollisionShape();
|
||||
assert(shape && shape->getShapeType() == CONVEX_HULL_SHAPE_PROXYTYPE);
|
||||
_ghost.setCharacterShape(static_cast<btConvexHullShape*>(shape));
|
||||
// restore gravity settings
|
||||
_rigidBody->setGravity(oldGravity);
|
||||
}
|
||||
// KINEMATIC_CONTROLLER_HACK
|
||||
_ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup));
|
||||
_ghost.setCollisionWorld(_dynamicsWorld);
|
||||
_ghost.setRadiusAndHalfHeight(_radius, _halfHeight);
|
||||
_ghost.setMaxStepHeight(0.75f * (_radius + _halfHeight)); // HACK
|
||||
_ghost.setMinWallAngle(PI / 4.0f); // HACK
|
||||
_ghost.setUpDirection(_currentUp);
|
||||
_ghost.setMotorOnly(!_moveKinematically);
|
||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||
}
|
||||
if (_dynamicsWorld) {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_SHAPE) {
|
||||
|
@ -141,84 +138,38 @@ void CharacterController::setDynamicsWorld(btDynamicsWorld* world) {
|
|||
}
|
||||
}
|
||||
|
||||
bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld, btScalar dt) {
|
||||
if (_moveKinematically) {
|
||||
// kinematic motion will move() the _ghost later
|
||||
return _ghost.hasSupport();
|
||||
}
|
||||
|
||||
bool pushing = _targetVelocity.length2() > FLT_EPSILON;
|
||||
|
||||
btDispatcher* dispatcher = collisionWorld->getDispatcher();
|
||||
int numManifolds = dispatcher->getNumManifolds();
|
||||
bool hasFloor = false;
|
||||
|
||||
btTransform rotation = _rigidBody->getWorldTransform();
|
||||
rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f)); // clear translation part
|
||||
static const float COS_PI_OVER_THREE = cosf(PI / 3.0f);
|
||||
|
||||
bool CharacterController::checkForSupport(btCollisionWorld* collisionWorld) const {
|
||||
int numManifolds = collisionWorld->getDispatcher()->getNumManifolds();
|
||||
for (int i = 0; i < numManifolds; i++) {
|
||||
btPersistentManifold* contactManifold = dispatcher->getManifoldByIndexInternal(i);
|
||||
if (_rigidBody == contactManifold->getBody1() || _rigidBody == contactManifold->getBody0()) {
|
||||
bool characterIsFirst = _rigidBody == contactManifold->getBody0();
|
||||
btPersistentManifold* contactManifold = collisionWorld->getDispatcher()->getManifoldByIndexInternal(i);
|
||||
const btCollisionObject* obA = static_cast<const btCollisionObject*>(contactManifold->getBody0());
|
||||
const btCollisionObject* obB = static_cast<const btCollisionObject*>(contactManifold->getBody1());
|
||||
if (obA == _rigidBody || obB == _rigidBody) {
|
||||
int numContacts = contactManifold->getNumContacts();
|
||||
int stepContactIndex = -1;
|
||||
float highestStep = _minStepHeight;
|
||||
for (int j = 0; j < numContacts; j++) {
|
||||
// check for "floor"
|
||||
btManifoldPoint& contact = contactManifold->getContactPoint(j);
|
||||
btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
|
||||
btVector3 normal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character
|
||||
btScalar hitHeight = _halfHeight + _radius + pointOnCharacter.dot(_currentUp);
|
||||
if (hitHeight < _maxStepHeight && normal.dot(_currentUp) > _minFloorNormalDotUp) {
|
||||
hasFloor = true;
|
||||
if (!pushing) {
|
||||
// we're not pushing against anything so we can early exit
|
||||
// (all we need to know is that there is a floor)
|
||||
break;
|
||||
}
|
||||
btManifoldPoint& pt = contactManifold->getContactPoint(j);
|
||||
|
||||
// check to see if contact point is touching the bottom sphere of the capsule.
|
||||
// and the contact normal is not slanted too much.
|
||||
float contactPointY = (obA == _rigidBody) ? pt.m_localPointA.getY() : pt.m_localPointB.getY();
|
||||
btVector3 normal = (obA == _rigidBody) ? pt.m_normalWorldOnB : -pt.m_normalWorldOnB;
|
||||
if (contactPointY < -_halfHeight && normal.dot(_currentUp) > COS_PI_OVER_THREE) {
|
||||
return true;
|
||||
}
|
||||
if (pushing && _targetVelocity.dot(normal) < 0.0f) {
|
||||
// remember highest step obstacle
|
||||
if (!_stepUpEnabled || hitHeight > _maxStepHeight) {
|
||||
// this manifold is invalidated by point that is too high
|
||||
stepContactIndex = -1;
|
||||
break;
|
||||
} else if (hitHeight > highestStep && normal.dot(_targetVelocity) < 0.0f ) {
|
||||
highestStep = hitHeight;
|
||||
stepContactIndex = j;
|
||||
hasFloor = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (stepContactIndex > -1 && highestStep > _stepHeight) {
|
||||
// remember step info for later
|
||||
btManifoldPoint& contact = contactManifold->getContactPoint(stepContactIndex);
|
||||
btVector3 pointOnCharacter = characterIsFirst ? contact.m_localPointA : contact.m_localPointB; // object-local-frame
|
||||
_stepNormal = characterIsFirst ? contact.m_normalWorldOnB : -contact.m_normalWorldOnB; // points toward character
|
||||
_stepHeight = highestStep;
|
||||
_stepPoint = rotation * pointOnCharacter; // rotate into world-frame
|
||||
}
|
||||
if (hasFloor && !(pushing && _stepUpEnabled)) {
|
||||
// early exit since all we need to know is that we're on a floor
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hasFloor;
|
||||
}
|
||||
|
||||
void CharacterController::updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) {
|
||||
_preActionVelocity = getLinearVelocity();
|
||||
preStep(collisionWorld);
|
||||
playerStep(collisionWorld, deltaTime);
|
||||
return false;
|
||||
}
|
||||
|
||||
void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
||||
// trace a ray straight down to see if we're standing on the ground
|
||||
const btTransform& transform = _rigidBody->getWorldTransform();
|
||||
const btTransform& xform = _rigidBody->getWorldTransform();
|
||||
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = transform.getOrigin() - _halfHeight * _currentUp;
|
||||
btVector3 rayStart = xform.getOrigin() - _halfHeight * _currentUp;
|
||||
|
||||
// rayEnd is some short distance outside bottom sphere
|
||||
const btScalar FLOOR_PROXIMITY_THRESHOLD = 0.3f * _radius;
|
||||
|
@ -232,117 +183,54 @@ void CharacterController::preStep(btCollisionWorld* collisionWorld) {
|
|||
if (rayCallback.hasHit()) {
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - _radius;
|
||||
}
|
||||
|
||||
_hasSupport = checkForSupport(collisionWorld);
|
||||
}
|
||||
|
||||
const btScalar MIN_TARGET_SPEED = 0.001f;
|
||||
const btScalar MIN_TARGET_SPEED_SQUARED = MIN_TARGET_SPEED * MIN_TARGET_SPEED;
|
||||
|
||||
void CharacterController::playerStep(btCollisionWorld* collisionWorld, btScalar dt) {
|
||||
_stepHeight = _minStepHeight; // clears memory of last step obstacle
|
||||
void CharacterController::playerStep(btCollisionWorld* dynaWorld, btScalar dt) {
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity() - _parentVelocity;
|
||||
if (_following) {
|
||||
_followTimeAccumulator += dt;
|
||||
// linear part uses a motor
|
||||
const float MAX_WALKING_SPEED = 30.5f; // TODO: scale this stuff with avatar size
|
||||
const float MAX_WALKING_SPEED_DISTANCE = 1.0f;
|
||||
const float NORMAL_WALKING_SPEED = 0.5f * MAX_WALKING_SPEED;
|
||||
const float NORMAL_WALKING_SPEED_DISTANCE = 0.5f * MAX_WALKING_SPEED_DISTANCE;
|
||||
const float FEW_SUBSTEPS = 4.0f * dt;
|
||||
computeNewVelocity(dt, velocity);
|
||||
_rigidBody->setLinearVelocity(velocity + _parentVelocity);
|
||||
|
||||
// Dynamicaly compute a follow velocity to move this body toward the _followDesiredBodyTransform.
|
||||
// Rather than add this velocity to velocity the RigidBody, we explicitly teleport the RigidBody towards its goal.
|
||||
// This mirrors the computation done in MyAvatar::FollowHelper::postPhysicsUpdate().
|
||||
|
||||
const float MINIMUM_TIME_REMAINING = 0.005f;
|
||||
const float MAX_DISPLACEMENT = 0.5f * _radius;
|
||||
_followTimeRemaining -= dt;
|
||||
if (_followTimeRemaining >= MINIMUM_TIME_REMAINING) {
|
||||
btTransform bodyTransform = _rigidBody->getWorldTransform();
|
||||
|
||||
btVector3 startPos = bodyTransform.getOrigin();
|
||||
btVector3 deltaPos = _followDesiredBodyTransform.getOrigin() - startPos;
|
||||
btScalar deltaDistance = deltaPos.length();
|
||||
const float MIN_DELTA_DISTANCE = 0.01f; // TODO: scale by avatar size but cap at (NORMAL_WALKING_SPEED * FEW_SUBSTEPS)
|
||||
if (deltaDistance > MIN_DELTA_DISTANCE) {
|
||||
btVector3 vel = deltaPos;
|
||||
if (_state == State::Hover) {
|
||||
btScalar HOVER_FOLLOW_VELOCITY_TIMESCALE = 0.1f;
|
||||
vel /= HOVER_FOLLOW_VELOCITY_TIMESCALE;
|
||||
} else {
|
||||
if (deltaDistance > MAX_WALKING_SPEED_DISTANCE) {
|
||||
// cap max speed
|
||||
vel *= MAX_WALKING_SPEED / deltaDistance;
|
||||
} else if (deltaDistance > NORMAL_WALKING_SPEED_DISTANCE) {
|
||||
// linearly interpolate to NORMAL_WALKING_SPEED
|
||||
btScalar alpha = (deltaDistance - NORMAL_WALKING_SPEED_DISTANCE) / (MAX_WALKING_SPEED_DISTANCE - NORMAL_WALKING_SPEED_DISTANCE);
|
||||
vel *= NORMAL_WALKING_SPEED * (1.0f - alpha) + MAX_WALKING_SPEED * alpha;
|
||||
} else {
|
||||
// use exponential decay but cap at NORMAL_WALKING_SPEED
|
||||
vel /= FEW_SUBSTEPS;
|
||||
btScalar speed = vel.length();
|
||||
if (speed > NORMAL_WALKING_SPEED) {
|
||||
vel *= NORMAL_WALKING_SPEED / speed;
|
||||
}
|
||||
}
|
||||
}
|
||||
vel += _followVelocity;
|
||||
const float HORIZONTAL_FOLLOW_TIMESCALE = 0.05f;
|
||||
float verticalFollowTimescale = 20.0f;
|
||||
if (_state == State::Hover) {
|
||||
verticalFollowTimescale = HORIZONTAL_FOLLOW_TIMESCALE;
|
||||
} else {
|
||||
// remove vertical component
|
||||
vel -= vel.dot(_currentUp) * _currentUp;
|
||||
}
|
||||
glm::quat worldFrameRotation; // identity
|
||||
addMotor(bulletToGLM(vel), worldFrameRotation, HORIZONTAL_FOLLOW_TIMESCALE, verticalFollowTimescale);
|
||||
}
|
||||
btVector3 vel = deltaPos / _followTimeRemaining;
|
||||
btVector3 linearDisplacement = clampLength(vel * dt, MAX_DISPLACEMENT); // clamp displacement to prevent tunneling.
|
||||
btVector3 endPos = startPos + linearDisplacement;
|
||||
|
||||
// angular part uses incremental teleports
|
||||
const float ANGULAR_FOLLOW_TIMESCALE = 0.8f;
|
||||
const float MAX_ANGULAR_SPEED = (PI / 2.0f) / ANGULAR_FOLLOW_TIMESCALE;
|
||||
btQuaternion startRot = bodyTransform.getRotation();
|
||||
glm::vec2 currentFacing = getFacingDir2D(bulletToGLM(startRot));
|
||||
glm::vec2 currentRight(currentFacing.y, - currentFacing.x);
|
||||
glm::vec2 currentRight(currentFacing.y, -currentFacing.x);
|
||||
glm::vec2 desiredFacing = getFacingDir2D(bulletToGLM(_followDesiredBodyTransform.getRotation()));
|
||||
float deltaAngle = acosf(glm::clamp(glm::dot(currentFacing, desiredFacing), -1.0f, 1.0f));
|
||||
float angularSpeed = deltaAngle / FEW_SUBSTEPS;
|
||||
if (angularSpeed > MAX_ANGULAR_SPEED) {
|
||||
angularSpeed *= MAX_ANGULAR_SPEED / angularSpeed;
|
||||
}
|
||||
float angularSpeed = deltaAngle / _followTimeRemaining;
|
||||
float sign = copysignf(1.0f, glm::dot(desiredFacing, currentRight));
|
||||
btQuaternion angularDisplacement = btQuaternion(btVector3(0.0f, 1.0f, 0.0f), sign * angularSpeed * dt);
|
||||
btQuaternion endRot = angularDisplacement * startRot;
|
||||
_rigidBody->setWorldTransform(btTransform(endRot, startPos));
|
||||
}
|
||||
|
||||
_hasSupport = checkForSupport(collisionWorld, dt);
|
||||
computeNewVelocity(dt, velocity);
|
||||
|
||||
if (_moveKinematically) {
|
||||
// KINEMATIC_CONTROLLER_HACK
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
transform.setOrigin(_ghost.getWorldTransform().getOrigin());
|
||||
_ghost.setWorldTransform(transform);
|
||||
_ghost.setMotorVelocity(_targetVelocity);
|
||||
float overshoot = 1.0f * _radius;
|
||||
_ghost.move(dt, overshoot, _gravity);
|
||||
transform.setOrigin(_ghost.getWorldTransform().getOrigin());
|
||||
_rigidBody->setWorldTransform(transform);
|
||||
_rigidBody->setLinearVelocity(_ghost.getLinearVelocity());
|
||||
} else {
|
||||
float stepUpSpeed2 = _stepUpVelocity.length2();
|
||||
if (stepUpSpeed2 > FLT_EPSILON) {
|
||||
// we step up with teleports rather than applying velocity
|
||||
// use a speed that would ballistically reach _stepHeight under gravity
|
||||
_stepUpVelocity /= sqrtf(stepUpSpeed2);
|
||||
btScalar minStepUpSpeed = sqrtf(fabsf(2.0f * _gravity * _stepHeight));
|
||||
|
||||
btTransform transform = _rigidBody->getWorldTransform();
|
||||
transform.setOrigin(transform.getOrigin() + (dt * minStepUpSpeed) * _stepUpVelocity);
|
||||
_rigidBody->setWorldTransform(transform);
|
||||
|
||||
// make sure the upward velocity is large enough to clear the very top of the step
|
||||
const btScalar MAGIC_STEP_OVERSHOOT_SPEED_COEFFICIENT = 0.5f;
|
||||
minStepUpSpeed = MAGIC_STEP_OVERSHOOT_SPEED_COEFFICIENT * sqrtf(fabsf(2.0f * _gravity * _minStepHeight));
|
||||
btScalar vDotUp = velocity.dot(_currentUp);
|
||||
if (vDotUp < minStepUpSpeed) {
|
||||
velocity += (minStepUpSpeed - vDotUp) * _stepUpVelocity;
|
||||
}
|
||||
}
|
||||
_rigidBody->setLinearVelocity(velocity + _parentVelocity);
|
||||
_ghost.setWorldTransform(_rigidBody->getWorldTransform());
|
||||
|
||||
// in order to accumulate displacement of avatar position, we need to take _shapeLocalOffset into account.
|
||||
btVector3 shapeLocalOffset = glmToBullet(_shapeLocalOffset);
|
||||
btVector3 swingDisplacement = rotateVector(endRot, -shapeLocalOffset) - rotateVector(startRot, -shapeLocalOffset);
|
||||
|
||||
_followLinearDisplacement = linearDisplacement + swingDisplacement + _followLinearDisplacement;
|
||||
_followAngularDisplacement = angularDisplacement * _followAngularDisplacement;
|
||||
|
||||
_rigidBody->setWorldTransform(btTransform(endRot, endPos));
|
||||
}
|
||||
_followTime += dt;
|
||||
}
|
||||
|
||||
void CharacterController::jump() {
|
||||
|
@ -384,96 +272,95 @@ void CharacterController::setState(State desiredState) {
|
|||
#ifdef DEBUG_STATE_CHANGE
|
||||
qCDebug(physics) << "CharacterController::setState" << stateToStr(desiredState) << "from" << stateToStr(_state) << "," << reason;
|
||||
#endif
|
||||
if (_rigidBody) {
|
||||
if (desiredState == State::Hover && _state != State::Hover) {
|
||||
// hover enter
|
||||
if (desiredState == State::Hover && _state != State::Hover) {
|
||||
// hover enter
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else if (_state == State::Hover && desiredState != State::Hover) {
|
||||
// hover exit
|
||||
if (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS) {
|
||||
_rigidBody->setGravity(btVector3(0.0f, 0.0f, 0.0f));
|
||||
} else {
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
}
|
||||
}
|
||||
} else if (_state == State::Hover && desiredState != State::Hover) {
|
||||
// hover exit
|
||||
if (_rigidBody) {
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
}
|
||||
}
|
||||
_state = desiredState;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale) {
|
||||
float x = scale.x;
|
||||
float z = scale.z;
|
||||
void CharacterController::setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale) {
|
||||
_boxScale = scale;
|
||||
|
||||
float x = _boxScale.x;
|
||||
float z = _boxScale.z;
|
||||
float radius = 0.5f * sqrtf(0.5f * (x * x + z * z));
|
||||
float halfHeight = 0.5f * scale.y - radius;
|
||||
float halfHeight = 0.5f * _boxScale.y - radius;
|
||||
float MIN_HALF_HEIGHT = 0.1f;
|
||||
if (halfHeight < MIN_HALF_HEIGHT) {
|
||||
halfHeight = MIN_HALF_HEIGHT;
|
||||
}
|
||||
|
||||
// compare dimensions
|
||||
if (glm::abs(radius - _radius) > FLT_EPSILON || glm::abs(halfHeight - _halfHeight) > FLT_EPSILON) {
|
||||
_radius = radius;
|
||||
_halfHeight = halfHeight;
|
||||
const btScalar DEFAULT_MIN_STEP_HEIGHT = 0.041f; // HACK: hardcoded now but should just larger than shape margin
|
||||
const btScalar MAX_STEP_FRACTION_OF_HALF_HEIGHT = 0.56f;
|
||||
_minStepHeight = DEFAULT_MIN_STEP_HEIGHT;
|
||||
_maxStepHeight = MAX_STEP_FRACTION_OF_HALF_HEIGHT * (_halfHeight + _radius);
|
||||
|
||||
float radiusDelta = glm::abs(radius - _radius);
|
||||
float heightDelta = glm::abs(halfHeight - _halfHeight);
|
||||
if (radiusDelta < FLT_EPSILON && heightDelta < FLT_EPSILON) {
|
||||
// shape hasn't changed --> nothing to do
|
||||
} else {
|
||||
if (_dynamicsWorld) {
|
||||
// must REMOVE from world prior to shape update
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
// only need to ADD back when we happen to be enabled
|
||||
if (_enabled) {
|
||||
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
}
|
||||
|
||||
// it's ok to change offset immediately -- there are no thread safety issues here
|
||||
_shapeLocalOffset = minCorner + 0.5f * scale;
|
||||
_shapeLocalOffset = corner + 0.5f * _boxScale;
|
||||
}
|
||||
|
||||
void CharacterController::setCollisionGroup(int16_t group) {
|
||||
if (_collisionGroup != group) {
|
||||
_collisionGroup = group;
|
||||
_pendingFlags |= PENDING_FLAG_UPDATE_COLLISION_GROUP;
|
||||
_ghost.setCollisionGroupAndMask(_collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR & (~ _collisionGroup));
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::handleChangedCollisionGroup() {
|
||||
if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_GROUP) {
|
||||
// ATM the easiest way to update collision groups is to remove/re-add the RigidBody
|
||||
if (_dynamicsWorld) {
|
||||
_dynamicsWorld->removeRigidBody(_rigidBody);
|
||||
_dynamicsWorld->addRigidBody(_rigidBody, _collisionGroup, BULLET_COLLISION_MASK_MY_AVATAR);
|
||||
}
|
||||
_pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_GROUP;
|
||||
|
||||
if (_state != State::Hover && _rigidBody) {
|
||||
_gravity = _collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ? 0.0f : DEFAULT_CHARACTER_GRAVITY;
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
void CharacterController::setEnabled(bool enabled) {
|
||||
if (enabled != _enabled) {
|
||||
if (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;
|
||||
} else {
|
||||
if (_dynamicsWorld) {
|
||||
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION;
|
||||
}
|
||||
_pendingFlags &= ~ PENDING_FLAG_ADD_TO_SIMULATION;
|
||||
}
|
||||
SET_STATE(State::Hover, "setEnabled");
|
||||
_enabled = enabled;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::updateUpAxis(const glm::quat& rotation) {
|
||||
btVector3 oldUp = _currentUp;
|
||||
_currentUp = quatRotate(glmToBullet(rotation), LOCAL_UP_AXIS);
|
||||
_ghost.setUpDirection(_currentUp);
|
||||
if (_state != State::Hover && _rigidBody) {
|
||||
_rigidBody->setGravity(_gravity * _currentUp);
|
||||
if (_state != State::Hover) {
|
||||
const btScalar MIN_UP_ERROR = 0.01f;
|
||||
if (oldUp.distance(_currentUp) > MIN_UP_ERROR) {
|
||||
_rigidBody->setGravity(DEFAULT_CHARACTER_GRAVITY * _currentUp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::setPositionAndOrientation(
|
||||
const glm::vec3& position,
|
||||
const glm::quat& orientation) {
|
||||
// TODO: update gravity if up has changed
|
||||
updateUpAxis(orientation);
|
||||
_rotation = glmToBullet(orientation);
|
||||
_position = glmToBullet(position + orientation * _shapeLocalOffset);
|
||||
|
||||
btQuaternion bodyOrientation = glmToBullet(orientation);
|
||||
btVector3 bodyPosition = glmToBullet(position + orientation * _shapeLocalOffset);
|
||||
_characterBodyTransform = btTransform(bodyOrientation, bodyPosition);
|
||||
}
|
||||
|
||||
void CharacterController::getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const {
|
||||
if (_rigidBody) {
|
||||
if (_enabled && _rigidBody) {
|
||||
const btTransform& avatarTransform = _rigidBody->getWorldTransform();
|
||||
rotation = bulletToGLM(avatarTransform.getRotation());
|
||||
position = bulletToGLM(avatarTransform.getOrigin()) - rotation * _shapeLocalOffset;
|
||||
|
@ -484,43 +371,25 @@ void CharacterController::setParentVelocity(const glm::vec3& velocity) {
|
|||
_parentVelocity = glmToBullet(velocity);
|
||||
}
|
||||
|
||||
void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix) {
|
||||
void CharacterController::setFollowParameters(const glm::mat4& desiredWorldBodyMatrix, float timeRemaining) {
|
||||
_followTimeRemaining = timeRemaining;
|
||||
_followDesiredBodyTransform = glmToBullet(desiredWorldBodyMatrix) * btTransform(btQuaternion::getIdentity(), glmToBullet(_shapeLocalOffset));
|
||||
if (!_following) {
|
||||
_following = true;
|
||||
_followVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
} else if (_followTimeAccumulator > 0.0f) {
|
||||
btVector3 newFollowVelocity = (_followDesiredBodyTransform.getOrigin() - _previousFollowPosition) / _followTimeAccumulator;
|
||||
const float dontDivideByZero = 0.001f;
|
||||
float newSpeed = newFollowVelocity.length() + dontDivideByZero;
|
||||
float oldSpeed = _followVelocity.length();
|
||||
}
|
||||
|
||||
bool successfulSnap = false;
|
||||
btVector3 offset = _followDesiredBodyTransform.getOrigin() - _position;
|
||||
const btScalar MAX_FOLLOW_OFFSET = 10.0f * _radius;
|
||||
if (offset.length() > MAX_FOLLOW_OFFSET) {
|
||||
successfulSnap = queryPenetration(_followDesiredBodyTransform);
|
||||
if (successfulSnap) {
|
||||
_position = _followDesiredBodyTransform.getOrigin();
|
||||
}
|
||||
}
|
||||
glm::vec3 CharacterController::getFollowLinearDisplacement() const {
|
||||
return bulletToGLM(_followLinearDisplacement);
|
||||
}
|
||||
|
||||
const float VERY_SLOW_HOVER_SPEED = 0.25f;
|
||||
const float FAST_CHANGE_SPEED_RATIO = 100.0f;
|
||||
if (successfulSnap || (oldSpeed / newSpeed > FAST_CHANGE_SPEED_RATIO && newSpeed < VERY_SLOW_HOVER_SPEED)) {
|
||||
// character is snapping to avatar position or avatar is stopping quickly
|
||||
// HACK: slam _followVelocity and _rigidBody velocity immediately
|
||||
_followVelocity = newFollowVelocity;
|
||||
newFollowVelocity.setY(0.0f);
|
||||
_rigidBody->setLinearVelocity(newFollowVelocity);
|
||||
} else {
|
||||
// use simple blending to filter noise from the velocity measurement
|
||||
const float blend = 0.2f;
|
||||
_followVelocity = (1.0f - blend) * _followVelocity + blend * newFollowVelocity;
|
||||
}
|
||||
glm::quat CharacterController::getFollowAngularDisplacement() const {
|
||||
return bulletToGLM(_followAngularDisplacement);
|
||||
}
|
||||
|
||||
glm::vec3 CharacterController::getFollowVelocity() const {
|
||||
if (_followTime > 0.0f) {
|
||||
return bulletToGLM(_followLinearDisplacement) / _followTime;
|
||||
} else {
|
||||
return glm::vec3();
|
||||
}
|
||||
_previousFollowPosition = _followDesiredBodyTransform.getOrigin();
|
||||
_followTimeAccumulator = 0.0f;
|
||||
}
|
||||
|
||||
glm::vec3 CharacterController::getLinearVelocity() const {
|
||||
|
@ -531,10 +400,6 @@ glm::vec3 CharacterController::getLinearVelocity() const {
|
|||
return velocity;
|
||||
}
|
||||
|
||||
glm::vec3 CharacterController::getPreActionLinearVelocity() const {
|
||||
return _preActionVelocity;
|
||||
}
|
||||
|
||||
glm::vec3 CharacterController::getVelocityChange() const {
|
||||
if (_rigidBody) {
|
||||
return bulletToGLM(_velocityChange);
|
||||
|
@ -563,18 +428,16 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
btScalar angle = motor.rotation.getAngle();
|
||||
btVector3 velocity = worldVelocity.rotate(axis, -angle);
|
||||
|
||||
if (_collisionGroup == BULLET_COLLISION_GROUP_COLLISIONLESS ||
|
||||
_state == State::Hover || motor.hTimescale == motor.vTimescale) {
|
||||
if (_state == State::Hover || motor.hTimescale == motor.vTimescale) {
|
||||
// modify velocity
|
||||
btScalar tau = dt / motor.hTimescale;
|
||||
if (tau > 1.0f) {
|
||||
tau = 1.0f;
|
||||
}
|
||||
velocity += tau * (motor.velocity - velocity);
|
||||
velocity += (motor.velocity - velocity) * tau;
|
||||
|
||||
// rotate back into world-frame
|
||||
velocity = velocity.rotate(axis, angle);
|
||||
_targetVelocity += (tau * motor.velocity).rotate(axis, angle);
|
||||
|
||||
// store the velocity and weight
|
||||
velocities.push_back(velocity);
|
||||
|
@ -582,32 +445,12 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
} else {
|
||||
// compute local UP
|
||||
btVector3 up = _currentUp.rotate(axis, -angle);
|
||||
btVector3 motorVelocity = motor.velocity;
|
||||
|
||||
// save these non-adjusted components for later
|
||||
btVector3 vTargetVelocity = motorVelocity.dot(up) * up;
|
||||
btVector3 hTargetVelocity = motorVelocity - vTargetVelocity;
|
||||
|
||||
if (_stepHeight > _minStepHeight) {
|
||||
// there is a step --> compute velocity direction to go over step
|
||||
btVector3 motorVelocityWF = motorVelocity.rotate(axis, angle);
|
||||
if (motorVelocityWF.dot(_stepNormal) < 0.0f) {
|
||||
// the motor pushes against step
|
||||
motorVelocityWF = _stepNormal.cross(_stepPoint.cross(motorVelocityWF));
|
||||
btScalar doubleCrossLength2 = motorVelocityWF.length2();
|
||||
if (doubleCrossLength2 > FLT_EPSILON) {
|
||||
// scale the motor in the correct direction and rotate back to motor-frame
|
||||
motorVelocityWF *= (motorVelocity.length() / sqrtf(doubleCrossLength2));
|
||||
_stepUpVelocity += motorVelocityWF.rotate(axis, -angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// split velocity into horizontal and vertical components
|
||||
btVector3 vVelocity = velocity.dot(up) * up;
|
||||
btVector3 hVelocity = velocity - vVelocity;
|
||||
btVector3 vMotorVelocity = motorVelocity.dot(up) * up;
|
||||
btVector3 hMotorVelocity = motorVelocity - vMotorVelocity;
|
||||
btVector3 vTargetVelocity = motor.velocity.dot(up) * up;
|
||||
btVector3 hTargetVelocity = motor.velocity - vTargetVelocity;
|
||||
|
||||
// modify each component separately
|
||||
btScalar maxTau = 0.0f;
|
||||
|
@ -617,7 +460,7 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
tau = 1.0f;
|
||||
}
|
||||
maxTau = tau;
|
||||
hVelocity += (hMotorVelocity - hVelocity) * tau;
|
||||
hVelocity += (hTargetVelocity - hVelocity) * tau;
|
||||
}
|
||||
if (motor.vTimescale < MAX_CHARACTER_MOTOR_TIMESCALE) {
|
||||
btScalar tau = dt / motor.vTimescale;
|
||||
|
@ -627,12 +470,11 @@ void CharacterController::applyMotor(int index, btScalar dt, btVector3& worldVel
|
|||
if (tau > maxTau) {
|
||||
maxTau = tau;
|
||||
}
|
||||
vVelocity += (vMotorVelocity - vVelocity) * tau;
|
||||
vVelocity += (vTargetVelocity - vVelocity) * tau;
|
||||
}
|
||||
|
||||
// add components back together and rotate into world-frame
|
||||
velocity = (hVelocity + vVelocity).rotate(axis, angle);
|
||||
_targetVelocity += maxTau * (hTargetVelocity + vTargetVelocity).rotate(axis, angle);
|
||||
|
||||
// store velocity and weights
|
||||
velocities.push_back(velocity);
|
||||
|
@ -650,8 +492,6 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
|||
velocities.reserve(_motors.size());
|
||||
std::vector<btScalar> weights;
|
||||
weights.reserve(_motors.size());
|
||||
_targetVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
_stepUpVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
for (int i = 0; i < (int)_motors.size(); ++i) {
|
||||
applyMotor(i, dt, velocity, velocities, weights);
|
||||
}
|
||||
|
@ -667,18 +507,14 @@ void CharacterController::computeNewVelocity(btScalar dt, btVector3& velocity) {
|
|||
for (size_t i = 0; i < velocities.size(); ++i) {
|
||||
velocity += (weights[i] / totalWeight) * velocities[i];
|
||||
}
|
||||
_targetVelocity /= totalWeight;
|
||||
}
|
||||
if (velocity.length2() < MIN_TARGET_SPEED_SQUARED) {
|
||||
velocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// 'thrust' is applied at the very end
|
||||
_targetVelocity += dt * _linearAcceleration;
|
||||
velocity += dt * _linearAcceleration;
|
||||
// Note the differences between these two variables:
|
||||
// _targetVelocity = ideal final velocity according to input
|
||||
// velocity = real final velocity after motors are applied to current velocity
|
||||
_targetVelocity = velocity;
|
||||
}
|
||||
|
||||
void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
||||
|
@ -687,54 +523,57 @@ void CharacterController::computeNewVelocity(btScalar dt, glm::vec3& velocity) {
|
|||
velocity = bulletToGLM(btVelocity);
|
||||
}
|
||||
|
||||
void CharacterController::updateState() {
|
||||
if (!_dynamicsWorld) {
|
||||
return;
|
||||
}
|
||||
const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
|
||||
const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
|
||||
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
||||
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
||||
void CharacterController::preSimulation() {
|
||||
if (_enabled && _dynamicsWorld && _rigidBody) {
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// scan for distant floor
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = _position;
|
||||
// slam body to where it is supposed to be
|
||||
_rigidBody->setWorldTransform(_characterBodyTransform);
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
_preSimulationVelocity = velocity;
|
||||
|
||||
// rayEnd is straight down MAX_FALL_HEIGHT
|
||||
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
||||
// scan for distant floor
|
||||
// rayStart is at center of bottom sphere
|
||||
btVector3 rayStart = _characterBodyTransform.getOrigin();
|
||||
|
||||
ClosestNotMe rayCallback(_rigidBody);
|
||||
rayCallback.m_closestHitFraction = 1.0f;
|
||||
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||
bool rayHasHit = rayCallback.hasHit();
|
||||
quint64 now = usecTimestampNow();
|
||||
if (rayHasHit) {
|
||||
_rayHitStartTime = now;
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
|
||||
} else {
|
||||
// rayEnd is straight down MAX_FALL_HEIGHT
|
||||
btScalar rayLength = _radius + MAX_FALL_HEIGHT;
|
||||
btVector3 rayEnd = rayStart - rayLength * _currentUp;
|
||||
|
||||
const btScalar FLY_TO_GROUND_THRESHOLD = 0.1f * _radius;
|
||||
const btScalar GROUND_TO_FLY_THRESHOLD = 0.8f * _radius + _halfHeight;
|
||||
const quint64 TAKE_OFF_TO_IN_AIR_PERIOD = 250 * MSECS_PER_SECOND;
|
||||
const btScalar MIN_HOVER_HEIGHT = 2.5f;
|
||||
const quint64 JUMP_TO_HOVER_PERIOD = 1100 * MSECS_PER_SECOND;
|
||||
const btScalar MAX_WALKING_SPEED = 2.5f;
|
||||
const quint64 RAY_HIT_START_PERIOD = 500 * MSECS_PER_SECOND;
|
||||
if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
||||
|
||||
ClosestNotMe rayCallback(_rigidBody);
|
||||
rayCallback.m_closestHitFraction = 1.0f;
|
||||
_dynamicsWorld->rayTest(rayStart, rayEnd, rayCallback);
|
||||
bool rayHasHit = rayCallback.hasHit();
|
||||
if (rayHasHit) {
|
||||
_rayHitStartTime = now;
|
||||
_floorDistance = rayLength * rayCallback.m_closestHitFraction - (_radius + _halfHeight);
|
||||
} else if ((now - _rayHitStartTime) < RAY_HIT_START_PERIOD) {
|
||||
rayHasHit = true;
|
||||
} else {
|
||||
_floorDistance = FLT_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
// record a time stamp when the jump button was first pressed.
|
||||
bool jumpButtonHeld = _pendingFlags & PENDING_FLAG_JUMP;
|
||||
if ((_previousFlags & PENDING_FLAG_JUMP) != (_pendingFlags & PENDING_FLAG_JUMP)) {
|
||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_jumpButtonDownStartTime = now;
|
||||
_jumpButtonDownCount++;
|
||||
// record a time stamp when the jump button was first pressed.
|
||||
if ((_previousFlags & PENDING_FLAG_JUMP) != (_pendingFlags & PENDING_FLAG_JUMP)) {
|
||||
if (_pendingFlags & PENDING_FLAG_JUMP) {
|
||||
_jumpButtonDownStartTime = now;
|
||||
_jumpButtonDownCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btVector3 velocity = _preSimulationVelocity;
|
||||
bool jumpButtonHeld = _pendingFlags & PENDING_FLAG_JUMP;
|
||||
|
||||
btVector3 actualHorizVelocity = velocity - velocity.dot(_currentUp) * _currentUp;
|
||||
bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f);
|
||||
|
||||
// OUTOFBODY_HACK -- disable normal state transitions while collisionless
|
||||
if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
|
||||
switch (_state) {
|
||||
case State::Ground:
|
||||
if (!rayHasHit && !_hasSupport) {
|
||||
|
@ -774,50 +613,32 @@ void CharacterController::updateState() {
|
|||
break;
|
||||
}
|
||||
case State::Hover:
|
||||
btVector3 actualHorizVelocity = velocity - velocity.dot(_currentUp) * _currentUp;
|
||||
const btScalar MAX_WALKING_SPEED = 2.5f;
|
||||
bool flyingFast = _state == State::Hover && actualHorizVelocity.length() > (MAX_WALKING_SPEED * 0.75f);
|
||||
|
||||
if ((_floorDistance < MIN_HOVER_HEIGHT) &&
|
||||
!(jumpButtonHeld || flyingFast || (now - _jumpButtonDownStartTime) > JUMP_TO_HOVER_PERIOD)) {
|
||||
if ((_floorDistance < MIN_HOVER_HEIGHT) && !jumpButtonHeld && !flyingFast) {
|
||||
SET_STATE(State::InAir, "near ground");
|
||||
} else if (((_floorDistance < FLY_TO_GROUND_THRESHOLD) || _hasSupport) && !flyingFast) {
|
||||
SET_STATE(State::Ground, "touching ground");
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (_moveKinematically && _ghost.isHovering()) {
|
||||
SET_STATE(State::Hover, "kinematic motion"); // HACK
|
||||
}
|
||||
} else {
|
||||
// OUTOFBODY_HACK -- in collisionless state switch only between Ground and Hover states
|
||||
if (rayHasHit) {
|
||||
SET_STATE(State::Ground, "collisionless above ground");
|
||||
} else {
|
||||
SET_STATE(State::Hover, "collisionless in air");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterController::preSimulation() {
|
||||
if (_rigidBody) {
|
||||
// slam body transform and remember velocity
|
||||
_rigidBody->setWorldTransform(btTransform(btTransform(_rotation, _position)));
|
||||
_preSimulationVelocity = _rigidBody->getLinearVelocity();
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
_previousFlags = _pendingFlags;
|
||||
_pendingFlags &= ~PENDING_FLAG_JUMP;
|
||||
|
||||
_followTime = 0.0f;
|
||||
_followLinearDisplacement = btVector3(0, 0, 0);
|
||||
_followAngularDisplacement = btQuaternion::getIdentity();
|
||||
}
|
||||
|
||||
void CharacterController::postSimulation() {
|
||||
if (_rigidBody) {
|
||||
_velocityChange = _rigidBody->getLinearVelocity() - _preSimulationVelocity;
|
||||
// postSimulation() exists for symmetry and just in case we need to do something here later
|
||||
if (_enabled && _dynamicsWorld && _rigidBody) {
|
||||
btVector3 velocity = _rigidBody->getLinearVelocity();
|
||||
_velocityChange = velocity - _preSimulationVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool CharacterController::getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation) {
|
||||
if (!_rigidBody) {
|
||||
return false;
|
||||
|
@ -834,58 +655,7 @@ void CharacterController::setFlyingAllowed(bool value) {
|
|||
_flyingAllowed = value;
|
||||
|
||||
if (!_flyingAllowed && _state == State::Hover) {
|
||||
// OUTOFBODY_HACK -- disable normal state transitions while collisionless
|
||||
if (_collisionGroup == BULLET_COLLISION_GROUP_MY_AVATAR) {
|
||||
SET_STATE(State::InAir, "flying not allowed");
|
||||
}
|
||||
SET_STATE(State::InAir, "flying not allowed");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
float CharacterController::measureMaxHipsOffsetRadius(const glm::vec3& currentHipsOffset, float maxSweepDistance) {
|
||||
btVector3 hipsOffset = glmToBullet(currentHipsOffset); // rig-frame
|
||||
btScalar hipsOffsetLength = hipsOffset.length();
|
||||
if (hipsOffsetLength > FLT_EPSILON) {
|
||||
const btTransform& transform = _rigidBody->getWorldTransform();
|
||||
|
||||
// rotate into world-frame
|
||||
btTransform rotation = transform;
|
||||
rotation.setOrigin(btVector3(0.0f, 0.0f, 0.0f));
|
||||
btVector3 startPos = transform.getOrigin() - rotation * glmToBullet(_shapeLocalOffset);
|
||||
btTransform startTransform = transform;
|
||||
startTransform.setOrigin(startPos);
|
||||
btVector3 endPos = startPos + rotation * ((maxSweepDistance / hipsOffsetLength) * hipsOffset);
|
||||
|
||||
// sweep test a sphere
|
||||
btSphereShape sphere(_radius);
|
||||
CharacterSweepResult result(&_ghost);
|
||||
btTransform endTransform = startTransform;
|
||||
endTransform.setOrigin(endPos);
|
||||
_ghost.sweepTest(&sphere, startTransform, endTransform, result);
|
||||
|
||||
// measure sweep success
|
||||
if (result.hasHit()) {
|
||||
maxSweepDistance *= result.m_closestHitFraction;
|
||||
}
|
||||
}
|
||||
return maxSweepDistance;
|
||||
}
|
||||
|
||||
void CharacterController::setMoveKinematically(bool kinematic) {
|
||||
if (kinematic != _moveKinematically) {
|
||||
_moveKinematically = kinematic;
|
||||
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
|
||||
_ghost.setMotorOnly(!_moveKinematically);
|
||||
}
|
||||
}
|
||||
|
||||
bool CharacterController::queryPenetration(const btTransform& transform) {
|
||||
btVector3 minBox;
|
||||
btVector3 maxBox;
|
||||
_ghost.setWorldTransform(transform);
|
||||
_ghost.measurePenetration(minBox, maxBox);
|
||||
btVector3 penetration = minBox;
|
||||
penetration.setMax(maxBox.absolute());
|
||||
const btScalar MIN_PENETRATION_SQUARED = 0.0016f; // 0.04^2
|
||||
return penetration.length2() < MIN_PENETRATION_SQUARED;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CharacterControllerInterface.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.10.21
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
@ -9,8 +9,8 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterController_h
|
||||
#define hifi_CharacterController_h
|
||||
#ifndef hifi_CharacterControllerInterface_h
|
||||
#define hifi_CharacterControllerInterface_h
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdint.h>
|
||||
|
@ -19,18 +19,12 @@
|
|||
#include <BulletDynamics/Character/btCharacterControllerInterface.h>
|
||||
|
||||
#include <GLMHelpers.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <PhysicsCollisionGroups.h>
|
||||
|
||||
#include "BulletUtil.h"
|
||||
#include "CharacterGhostObject.h"
|
||||
|
||||
const uint32_t PENDING_FLAG_ADD_TO_SIMULATION = 1U << 0;
|
||||
const uint32_t PENDING_FLAG_REMOVE_FROM_SIMULATION = 1U << 1;
|
||||
const uint32_t PENDING_FLAG_UPDATE_SHAPE = 1U << 2;
|
||||
const uint32_t PENDING_FLAG_JUMP = 1U << 3;
|
||||
const uint32_t PENDING_FLAG_UPDATE_COLLISION_GROUP = 1U << 4;
|
||||
const float DEFAULT_MIN_FLOOR_NORMAL_DOT_UP = cosf(PI / 3.0f);
|
||||
|
||||
const float DEFAULT_CHARACTER_GRAVITY = -5.0f;
|
||||
|
||||
|
@ -50,7 +44,7 @@ public:
|
|||
|
||||
bool needsRemoval() const;
|
||||
bool needsAddition() const;
|
||||
virtual void setDynamicsWorld(btDynamicsWorld* world);
|
||||
void setDynamicsWorld(btDynamicsWorld* world);
|
||||
btCollisionObject* getCollisionObject() { return _rigidBody; }
|
||||
|
||||
virtual void updateShapeIfNecessary() = 0;
|
||||
|
@ -62,7 +56,10 @@ public:
|
|||
virtual void warp(const btVector3& origin) override { }
|
||||
virtual void debugDraw(btIDebugDraw* debugDrawer) override { }
|
||||
virtual void setUpInterpolate(bool value) override { }
|
||||
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) override;
|
||||
virtual void updateAction(btCollisionWorld* collisionWorld, btScalar deltaTime) override {
|
||||
preStep(collisionWorld);
|
||||
playerStep(collisionWorld, deltaTime);
|
||||
}
|
||||
virtual void preStep(btCollisionWorld *collisionWorld) override;
|
||||
virtual void playerStep(btCollisionWorld *collisionWorld, btScalar dt) override;
|
||||
virtual bool canJump() const override { assert(false); return false; } // never call this
|
||||
|
@ -72,7 +69,6 @@ public:
|
|||
void clearMotors();
|
||||
void addMotor(const glm::vec3& velocity, const glm::quat& rotation, float horizTimescale, float vertTimescale = -1.0f);
|
||||
void applyMotor(int index, btScalar dt, btVector3& worldVelocity, std::vector<btVector3>& velocities, std::vector<btScalar>& weights);
|
||||
void setStepUpEnabled(bool enabled) { _stepUpEnabled = enabled; }
|
||||
void computeNewVelocity(btScalar dt, btVector3& velocity);
|
||||
void computeNewVelocity(btScalar dt, glm::vec3& velocity);
|
||||
|
||||
|
@ -86,11 +82,13 @@ public:
|
|||
void getPositionAndOrientation(glm::vec3& position, glm::quat& rotation) const;
|
||||
|
||||
void setParentVelocity(const glm::vec3& parentVelocity);
|
||||
void setFollowParameters(const glm::mat4& desiredWorldBodyMatrix);
|
||||
void disableFollow() { _following = false; }
|
||||
void setFollowParameters(const glm::mat4& desiredWorldMatrix, float timeRemaining);
|
||||
float getFollowTime() const { return _followTime; }
|
||||
glm::vec3 getFollowLinearDisplacement() const;
|
||||
glm::quat getFollowAngularDisplacement() const;
|
||||
glm::vec3 getFollowVelocity() const;
|
||||
|
||||
glm::vec3 getLinearVelocity() const;
|
||||
glm::vec3 getPreActionLinearVelocity() const;
|
||||
glm::vec3 getVelocityChange() const;
|
||||
|
||||
float getCapsuleRadius() const { return _radius; }
|
||||
|
@ -105,24 +103,17 @@ public:
|
|||
};
|
||||
|
||||
State getState() const { return _state; }
|
||||
void updateState();
|
||||
|
||||
void setLocalBoundingBox(const glm::vec3& minCorner, const glm::vec3& scale);
|
||||
void setLocalBoundingBox(const glm::vec3& corner, const glm::vec3& scale);
|
||||
|
||||
bool isEnabledAndReady() const { return _dynamicsWorld; }
|
||||
|
||||
void setCollisionGroup(int16_t group);
|
||||
int16_t getCollisionGroup() const { return _collisionGroup; }
|
||||
void handleChangedCollisionGroup();
|
||||
bool isEnabled() const { return _enabled; } // thread-safe
|
||||
void setEnabled(bool enabled);
|
||||
bool isEnabledAndReady() const { return _enabled && _dynamicsWorld; }
|
||||
|
||||
bool getRigidBodyLocation(glm::vec3& avatarRigidBodyPosition, glm::quat& avatarRigidBodyRotation);
|
||||
|
||||
void setFlyingAllowed(bool value);
|
||||
|
||||
float measureMaxHipsOffsetRadius(const glm::vec3& currentHipsOffset, float maxSweepDistance);
|
||||
void setMoveKinematically(bool kinematic); // KINEMATIC_CONTROLLER_HACK
|
||||
|
||||
bool queryPenetration(const btTransform& transform);
|
||||
|
||||
protected:
|
||||
#ifdef DEBUG_STATE_CHANGE
|
||||
|
@ -132,7 +123,7 @@ protected:
|
|||
#endif
|
||||
|
||||
void updateUpAxis(const glm::quat& rotation);
|
||||
bool checkForSupport(btCollisionWorld* collisionWorld, btScalar dt);
|
||||
bool checkForSupport(btCollisionWorld* collisionWorld) const;
|
||||
|
||||
protected:
|
||||
struct CharacterMotor {
|
||||
|
@ -145,18 +136,14 @@ protected:
|
|||
};
|
||||
|
||||
std::vector<CharacterMotor> _motors;
|
||||
CharacterGhostObject _ghost; // KINEMATIC_CONTROLLER_HACK
|
||||
btVector3 _currentUp;
|
||||
btVector3 _targetVelocity;
|
||||
btVector3 _parentVelocity;
|
||||
btVector3 _preSimulationVelocity;
|
||||
glm::vec3 _preActionVelocity;
|
||||
btVector3 _velocityChange;
|
||||
btTransform _followDesiredBodyTransform;
|
||||
btVector3 _followVelocity { 0.0f, 0.0f, 0.0f };
|
||||
btVector3 _previousFollowPosition { 0.0f, 0.0f, 0.0f };
|
||||
btVector3 _position;
|
||||
btQuaternion _rotation;
|
||||
btScalar _followTimeRemaining;
|
||||
btTransform _characterBodyTransform;
|
||||
|
||||
glm::vec3 _shapeLocalOffset;
|
||||
|
||||
|
@ -168,29 +155,21 @@ protected:
|
|||
quint32 _jumpButtonDownCount;
|
||||
quint32 _takeoffJumpButtonID;
|
||||
|
||||
// data for walking up steps
|
||||
btVector3 _stepPoint { 0.0f, 0.0f, 0.0f };
|
||||
btVector3 _stepNormal { 0.0f, 0.0f, 0.0f };
|
||||
btVector3 _stepUpVelocity { 0.0f, 0.0f, 0.0f };
|
||||
btScalar _stepHeight { 0.0f };
|
||||
btScalar _minStepHeight { 0.0f };
|
||||
btScalar _maxStepHeight { 0.0f };
|
||||
btScalar _minFloorNormalDotUp { DEFAULT_MIN_FLOOR_NORMAL_DOT_UP };
|
||||
|
||||
btScalar _halfHeight { 0.0f };
|
||||
btScalar _radius { 0.0f };
|
||||
btScalar _halfHeight;
|
||||
btScalar _radius;
|
||||
|
||||
btScalar _floorDistance;
|
||||
bool _stepUpEnabled { true };
|
||||
bool _hasSupport;
|
||||
|
||||
btScalar _gravity { DEFAULT_CHARACTER_GRAVITY };
|
||||
btScalar _followTimeAccumulator { 0.0f };
|
||||
btScalar _gravity;
|
||||
|
||||
btScalar _jumpSpeed;
|
||||
btScalar _followTime;
|
||||
btVector3 _followLinearDisplacement;
|
||||
btQuaternion _followAngularDisplacement;
|
||||
btVector3 _linearAcceleration;
|
||||
bool _following { false };
|
||||
|
||||
std::atomic_bool _enabled;
|
||||
State _state;
|
||||
bool _isPushingUp;
|
||||
|
||||
|
@ -200,8 +179,6 @@ protected:
|
|||
uint32_t _previousFlags { 0 };
|
||||
|
||||
bool _flyingAllowed { true };
|
||||
bool _moveKinematically { false }; // KINEMATIC_CONTROLLER_HACK
|
||||
int16_t _collisionGroup { BULLET_COLLISION_GROUP_MY_AVATAR };
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterController_h
|
||||
#endif // hifi_CharacterControllerInterface_h
|
||||
|
|
|
@ -1,415 +0,0 @@
|
|||
//
|
||||
// CharacterGhostObject.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.08.26
|
||||
// Copyright 2016 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 "CharacterGhostObject.h"
|
||||
|
||||
#include <stdint.h>
|
||||
#include <assert.h>
|
||||
|
||||
#include <PhysicsHelpers.h>
|
||||
|
||||
#include "CharacterRayResult.h"
|
||||
#include "CharacterGhostShape.h"
|
||||
|
||||
|
||||
CharacterGhostObject::~CharacterGhostObject() {
|
||||
removeFromWorld();
|
||||
if (_ghostShape) {
|
||||
delete _ghostShape;
|
||||
_ghostShape = nullptr;
|
||||
setCollisionShape(nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::setCollisionGroupAndMask(int16_t group, int16_t mask) {
|
||||
_collisionFilterGroup = group;
|
||||
_collisionFilterMask = mask;
|
||||
// TODO: if this probe is in the world reset ghostObject overlap cache
|
||||
}
|
||||
|
||||
void CharacterGhostObject::getCollisionGroupAndMask(int16_t& group, int16_t& mask) const {
|
||||
group = _collisionFilterGroup;
|
||||
mask = _collisionFilterMask;
|
||||
}
|
||||
|
||||
void CharacterGhostObject::setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight) {
|
||||
_radius = radius;
|
||||
_halfHeight = halfHeight;
|
||||
}
|
||||
|
||||
void CharacterGhostObject::setUpDirection(const btVector3& up) {
|
||||
btScalar length = up.length();
|
||||
if (length > FLT_EPSILON) {
|
||||
_upDirection /= length;
|
||||
} else {
|
||||
_upDirection = btVector3(0.0f, 1.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::setMotorVelocity(const btVector3& velocity) {
|
||||
_motorVelocity = velocity;
|
||||
if (_motorOnly) {
|
||||
_linearVelocity = _motorVelocity;
|
||||
}
|
||||
}
|
||||
|
||||
// override of btCollisionObject::setCollisionShape()
|
||||
void CharacterGhostObject::setCharacterShape(btConvexHullShape* shape) {
|
||||
assert(shape);
|
||||
// we create our own shape with an expanded Aabb for more reliable sweep tests
|
||||
if (_ghostShape) {
|
||||
delete _ghostShape;
|
||||
}
|
||||
|
||||
_ghostShape = new CharacterGhostShape(static_cast<const btConvexHullShape*>(shape));
|
||||
setCollisionShape(_ghostShape);
|
||||
}
|
||||
|
||||
void CharacterGhostObject::setCollisionWorld(btCollisionWorld* world) {
|
||||
if (world != _world) {
|
||||
removeFromWorld();
|
||||
_world = world;
|
||||
addToWorld();
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::move(btScalar dt, btScalar overshoot, btScalar gravity) {
|
||||
bool oldOnFloor = _onFloor;
|
||||
_onFloor = false;
|
||||
_steppingUp = false;
|
||||
assert(_world && _inWorld);
|
||||
updateVelocity(dt, gravity);
|
||||
|
||||
// resolve any penetrations before sweeping
|
||||
int32_t MAX_LOOPS = 4;
|
||||
int32_t numExtractions = 0;
|
||||
btVector3 totalPosition(0.0f, 0.0f, 0.0f);
|
||||
while (numExtractions < MAX_LOOPS) {
|
||||
if (resolvePenetration(numExtractions)) {
|
||||
numExtractions = 0;
|
||||
break;
|
||||
}
|
||||
totalPosition += getWorldTransform().getOrigin();
|
||||
++numExtractions;
|
||||
}
|
||||
if (numExtractions > 1) {
|
||||
// penetration resolution was probably oscillating between opposing objects
|
||||
// so we use the average of the solutions
|
||||
totalPosition /= btScalar(numExtractions);
|
||||
btTransform transform = getWorldTransform();
|
||||
transform.setOrigin(totalPosition);
|
||||
setWorldTransform(transform);
|
||||
|
||||
// TODO: figure out how to untrap character
|
||||
}
|
||||
btTransform startTransform = getWorldTransform();
|
||||
btVector3 startPosition = startTransform.getOrigin();
|
||||
if (_onFloor) {
|
||||
// resolvePenetration() pushed the avatar out of a floor so
|
||||
// we must updateTraction() before using _linearVelocity
|
||||
updateTraction(startPosition);
|
||||
}
|
||||
|
||||
btScalar speed = _linearVelocity.length();
|
||||
btVector3 forwardSweep = dt * _linearVelocity;
|
||||
btScalar stepDistance = dt * speed;
|
||||
btScalar MIN_SWEEP_DISTANCE = 0.0001f;
|
||||
if (stepDistance < MIN_SWEEP_DISTANCE) {
|
||||
// not moving, no need to sweep
|
||||
updateTraction(startPosition);
|
||||
return;
|
||||
}
|
||||
|
||||
// augment forwardSweep to help slow moving sweeps get over steppable ledges
|
||||
const btScalar MIN_OVERSHOOT = 0.04f; // default margin
|
||||
if (overshoot < MIN_OVERSHOOT) {
|
||||
overshoot = MIN_OVERSHOOT;
|
||||
}
|
||||
btScalar longSweepDistance = stepDistance + overshoot;
|
||||
forwardSweep *= longSweepDistance / stepDistance;
|
||||
|
||||
// step forward
|
||||
CharacterSweepResult result(this);
|
||||
btTransform nextTransform = startTransform;
|
||||
nextTransform.setOrigin(startPosition + forwardSweep);
|
||||
sweepTest(_characterShape, startTransform, nextTransform, result); // forward
|
||||
|
||||
if (!result.hasHit()) {
|
||||
nextTransform.setOrigin(startPosition + (stepDistance / longSweepDistance) * forwardSweep);
|
||||
setWorldTransform(nextTransform);
|
||||
updateTraction(nextTransform.getOrigin());
|
||||
return;
|
||||
}
|
||||
bool verticalOnly = btFabs(btFabs(_linearVelocity.dot(_upDirection)) - speed) < MIN_OVERSHOOT;
|
||||
if (verticalOnly) {
|
||||
// no need to step
|
||||
nextTransform.setOrigin(startPosition + (result.m_closestHitFraction * stepDistance / longSweepDistance) * forwardSweep);
|
||||
setWorldTransform(nextTransform);
|
||||
|
||||
if (result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) {
|
||||
_floorNormal = result.m_hitNormalWorld;
|
||||
_floorContact = result.m_hitPointWorld;
|
||||
_steppingUp = false;
|
||||
_onFloor = true;
|
||||
_hovering = false;
|
||||
}
|
||||
updateTraction(nextTransform.getOrigin());
|
||||
return;
|
||||
}
|
||||
|
||||
// check if this hit is obviously unsteppable
|
||||
btVector3 hitFromBase = result.m_hitPointWorld - (startPosition - ((_radius + _halfHeight) * _upDirection));
|
||||
btScalar hitHeight = hitFromBase.dot(_upDirection);
|
||||
if (hitHeight > _maxStepHeight) {
|
||||
// shape can't step over the obstacle so move forward as much as possible before we bail
|
||||
btVector3 forwardTranslation = result.m_closestHitFraction * forwardSweep;
|
||||
btScalar forwardDistance = forwardTranslation.length();
|
||||
if (forwardDistance > stepDistance) {
|
||||
forwardTranslation *= stepDistance / forwardDistance;
|
||||
}
|
||||
nextTransform.setOrigin(startPosition + forwardTranslation);
|
||||
setWorldTransform(nextTransform);
|
||||
_onFloor = _onFloor || oldOnFloor;
|
||||
return;
|
||||
}
|
||||
// if we get here then we hit something that might be steppable
|
||||
|
||||
// remember the forward sweep hit fraction for later
|
||||
btScalar forwardSweepHitFraction = result.m_closestHitFraction;
|
||||
|
||||
// figure out how high we can step up
|
||||
btScalar availableStepHeight = measureAvailableStepHeight();
|
||||
|
||||
// raise by availableStepHeight before sweeping forward
|
||||
result.resetHitHistory();
|
||||
startTransform.setOrigin(startPosition + availableStepHeight * _upDirection);
|
||||
nextTransform.setOrigin(startTransform.getOrigin() + forwardSweep);
|
||||
sweepTest(_characterShape, startTransform, nextTransform, result);
|
||||
if (result.hasHit()) {
|
||||
startTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * forwardSweep);
|
||||
} else {
|
||||
startTransform = nextTransform;
|
||||
}
|
||||
|
||||
// sweep down in search of future landing spot
|
||||
result.resetHitHistory();
|
||||
btVector3 downSweep = (- availableStepHeight) * _upDirection;
|
||||
nextTransform.setOrigin(startTransform.getOrigin() + downSweep);
|
||||
sweepTest(_characterShape, startTransform, nextTransform, result);
|
||||
if (result.hasHit() && result.m_hitNormalWorld.dot(_upDirection) > _maxWallNormalUpComponent) {
|
||||
// can stand on future landing spot, so we interpolate toward it
|
||||
_floorNormal = result.m_hitNormalWorld;
|
||||
_floorContact = result.m_hitPointWorld;
|
||||
_steppingUp = true;
|
||||
_onFloor = true;
|
||||
_hovering = false;
|
||||
nextTransform.setOrigin(startTransform.getOrigin() + result.m_closestHitFraction * downSweep);
|
||||
btVector3 totalStep = nextTransform.getOrigin() - startPosition;
|
||||
nextTransform.setOrigin(startPosition + (stepDistance / totalStep.length()) * totalStep);
|
||||
updateTraction(nextTransform.getOrigin());
|
||||
} else {
|
||||
// either there is no future landing spot, or there is but we can't stand on it
|
||||
// in any case: we go forward as much as possible
|
||||
nextTransform.setOrigin(startPosition + forwardSweepHitFraction * (stepDistance / longSweepDistance) * forwardSweep);
|
||||
_onFloor = _onFloor || oldOnFloor;
|
||||
updateTraction(nextTransform.getOrigin());
|
||||
}
|
||||
setWorldTransform(nextTransform);
|
||||
}
|
||||
|
||||
bool CharacterGhostObject::sweepTest(
|
||||
const btConvexShape* shape,
|
||||
const btTransform& start,
|
||||
const btTransform& end,
|
||||
CharacterSweepResult& result) const {
|
||||
if (_world && _inWorld) {
|
||||
assert(shape);
|
||||
btScalar allowedPenetration = _world->getDispatchInfo().m_allowedCcdPenetration;
|
||||
convexSweepTest(shape, start, end, result, allowedPenetration);
|
||||
return result.hasHit();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool CharacterGhostObject::rayTest(const btVector3& start,
|
||||
const btVector3& end,
|
||||
CharacterRayResult& result) const {
|
||||
if (_world && _inWorld) {
|
||||
_world->rayTest(start, end, result);
|
||||
}
|
||||
return result.hasHit();
|
||||
}
|
||||
|
||||
void CharacterGhostObject::measurePenetration(btVector3& minBoxOut, btVector3& maxBoxOut) {
|
||||
// minBoxOut and maxBoxOut will be updated with penetration envelope.
|
||||
// If one of the corner points is <0,0,0> then the penetration is resolvable in a single step,
|
||||
// but if the space spanned by the two corners extends in both directions along at least one
|
||||
// component then we the object is sandwiched between two opposing objects.
|
||||
|
||||
// We assume this object has just been moved to its current location, or else objects have been
|
||||
// moved around it since the last step so we must update the overlapping pairs.
|
||||
refreshOverlappingPairCache();
|
||||
|
||||
// compute collision details
|
||||
btHashedOverlappingPairCache* pairCache = getOverlappingPairCache();
|
||||
_world->getDispatcher()->dispatchAllCollisionPairs(pairCache, _world->getDispatchInfo(), _world->getDispatcher());
|
||||
|
||||
// loop over contact manifolds to compute the penetration box
|
||||
minBoxOut = btVector3(0.0f, 0.0f, 0.0f);
|
||||
maxBoxOut = btVector3(0.0f, 0.0f, 0.0f);
|
||||
btManifoldArray manifoldArray;
|
||||
|
||||
int numPairs = pairCache->getNumOverlappingPairs();
|
||||
for (int i = 0; i < numPairs; i++) {
|
||||
manifoldArray.resize(0);
|
||||
btBroadphasePair* collisionPair = &(pairCache->getOverlappingPairArray()[i]);
|
||||
|
||||
btCollisionObject* obj0 = static_cast<btCollisionObject*>(collisionPair->m_pProxy0->m_clientObject);
|
||||
btCollisionObject* obj1 = static_cast<btCollisionObject*>(collisionPair->m_pProxy1->m_clientObject);
|
||||
|
||||
if ((obj0 && !obj0->hasContactResponse()) && (obj1 && !obj1->hasContactResponse())) {
|
||||
// we know this probe has no contact response
|
||||
// but neither does the other object so skip this manifold
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!collisionPair->m_algorithm) {
|
||||
// null m_algorithm means the two shape types don't know how to collide!
|
||||
// shouldn't fall in here but just in case
|
||||
continue;
|
||||
}
|
||||
|
||||
btScalar mostFloorPenetration = 0.0f;
|
||||
collisionPair->m_algorithm->getAllContactManifolds(manifoldArray);
|
||||
for (int j = 0; j < manifoldArray.size(); j++) {
|
||||
btPersistentManifold* manifold = manifoldArray[j];
|
||||
btScalar directionSign = (manifold->getBody0() == this) ? btScalar(1.0) : btScalar(-1.0);
|
||||
for (int p = 0; p < manifold->getNumContacts(); p++) {
|
||||
const btManifoldPoint& pt = manifold->getContactPoint(p);
|
||||
if (pt.getDistance() > 0.0f) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// normal always points from object to character
|
||||
btVector3 normal = directionSign * pt.m_normalWorldOnB;
|
||||
|
||||
btScalar penetrationDepth = pt.getDistance();
|
||||
if (penetrationDepth < mostFloorPenetration) { // remember penetrationDepth is negative
|
||||
btScalar normalDotUp = normal.dot(_upDirection);
|
||||
if (normalDotUp > _maxWallNormalUpComponent) {
|
||||
mostFloorPenetration = penetrationDepth;
|
||||
_floorNormal = normal;
|
||||
if (directionSign > 0.0f) {
|
||||
_floorContact = pt.m_positionWorldOnA;
|
||||
} else {
|
||||
_floorContact = pt.m_positionWorldOnB;
|
||||
}
|
||||
_onFloor = true;
|
||||
}
|
||||
}
|
||||
|
||||
btVector3 penetration = (-penetrationDepth) * normal;
|
||||
minBoxOut.setMin(penetration);
|
||||
maxBoxOut.setMax(penetration);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::refreshOverlappingPairCache() {
|
||||
assert(_world && _inWorld);
|
||||
btVector3 minAabb, maxAabb;
|
||||
getCollisionShape()->getAabb(getWorldTransform(), minAabb, maxAabb);
|
||||
// this updates both pairCaches: world broadphase and ghostobject
|
||||
_world->getBroadphase()->setAabb(getBroadphaseHandle(), minAabb, maxAabb, _world->getDispatcher());
|
||||
}
|
||||
|
||||
void CharacterGhostObject::removeFromWorld() {
|
||||
if (_world && _inWorld) {
|
||||
_world->removeCollisionObject(this);
|
||||
_inWorld = false;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::addToWorld() {
|
||||
if (_world && !_inWorld) {
|
||||
assert(getCollisionShape());
|
||||
setCollisionFlags(getCollisionFlags() | btCollisionObject::CF_NO_CONTACT_RESPONSE);
|
||||
_world->addCollisionObject(this, _collisionFilterGroup, _collisionFilterMask);
|
||||
_inWorld = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool CharacterGhostObject::resolvePenetration(int numTries) {
|
||||
btVector3 minBox, maxBox;
|
||||
measurePenetration(minBox, maxBox);
|
||||
btVector3 restore = maxBox + minBox;
|
||||
if (restore.length2() > 0.0f) {
|
||||
btTransform transform = getWorldTransform();
|
||||
transform.setOrigin(transform.getOrigin() + restore);
|
||||
setWorldTransform(transform);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CharacterGhostObject::updateVelocity(btScalar dt, btScalar gravity) {
|
||||
if (!_motorOnly) {
|
||||
if (_hovering) {
|
||||
_linearVelocity *= 0.999f; // HACK damping
|
||||
} else {
|
||||
_linearVelocity += (dt * gravity) * _upDirection;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::updateHoverState(const btVector3& position) {
|
||||
if (_onFloor) {
|
||||
_hovering = false;
|
||||
} else {
|
||||
// cast a ray down looking for floor support
|
||||
CharacterRayResult rayResult(this);
|
||||
btScalar distanceToFeet = _radius + _halfHeight;
|
||||
btScalar slop = 2.0f * getCollisionShape()->getMargin(); // slop to help ray start OUTSIDE the floor object
|
||||
btVector3 startPos = position - ((distanceToFeet - slop) * _upDirection);
|
||||
btVector3 endPos = startPos - (2.0f * distanceToFeet) * _upDirection;
|
||||
rayTest(startPos, endPos, rayResult);
|
||||
// we're hovering if the ray didn't hit anything or hit unstandable slope
|
||||
_hovering = !rayResult.hasHit() || rayResult.m_hitNormalWorld.dot(_upDirection) < _maxWallNormalUpComponent;
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterGhostObject::updateTraction(const btVector3& position) {
|
||||
updateHoverState(position);
|
||||
if (_hovering || _motorOnly) {
|
||||
_linearVelocity = _motorVelocity;
|
||||
} else if (_onFloor) {
|
||||
// compute a velocity that swings the shape around the _floorContact
|
||||
btVector3 leverArm = _floorContact - position;
|
||||
btVector3 pathDirection = leverArm.cross(_motorVelocity.cross(leverArm));
|
||||
btScalar pathLength = pathDirection.length();
|
||||
if (pathLength > FLT_EPSILON) {
|
||||
_linearVelocity = (_motorVelocity.length() / pathLength) * pathDirection;
|
||||
} else {
|
||||
_linearVelocity = btVector3(0.0f, 0.0f, 0.0f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
btScalar CharacterGhostObject::measureAvailableStepHeight() const {
|
||||
CharacterSweepResult result(this);
|
||||
btTransform transform = getWorldTransform();
|
||||
btTransform nextTransform = transform;
|
||||
nextTransform.setOrigin(transform.getOrigin() + _maxStepHeight * _upDirection);
|
||||
sweepTest(_characterShape, transform, nextTransform, result);
|
||||
return result.m_closestHitFraction * _maxStepHeight;
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
//
|
||||
// CharacterGhostObject.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.08.26
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterGhostObject_h
|
||||
#define hifi_CharacterGhostObject_h
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btGhostObject.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "CharacterSweepResult.h"
|
||||
#include "CharacterRayResult.h"
|
||||
|
||||
class CharacterGhostShape;
|
||||
|
||||
class CharacterGhostObject : public btPairCachingGhostObject {
|
||||
public:
|
||||
CharacterGhostObject() { }
|
||||
~CharacterGhostObject();
|
||||
|
||||
void setCollisionGroupAndMask(int16_t group, int16_t mask);
|
||||
void getCollisionGroupAndMask(int16_t& group, int16_t& mask) const;
|
||||
|
||||
void setRadiusAndHalfHeight(btScalar radius, btScalar halfHeight);
|
||||
void setUpDirection(const btVector3& up);
|
||||
void setMotorVelocity(const btVector3& velocity);
|
||||
void setMinWallAngle(btScalar angle) { _maxWallNormalUpComponent = cosf(angle); }
|
||||
void setMaxStepHeight(btScalar height) { _maxStepHeight = height; }
|
||||
|
||||
void setLinearVelocity(const btVector3& velocity) { _linearVelocity = velocity; }
|
||||
const btVector3& getLinearVelocity() const { return _linearVelocity; }
|
||||
|
||||
void setCharacterShape(btConvexHullShape* shape);
|
||||
|
||||
void setCollisionWorld(btCollisionWorld* world);
|
||||
|
||||
void move(btScalar dt, btScalar overshoot, btScalar gravity);
|
||||
|
||||
bool sweepTest(const btConvexShape* shape,
|
||||
const btTransform& start,
|
||||
const btTransform& end,
|
||||
CharacterSweepResult& result) const;
|
||||
|
||||
bool rayTest(const btVector3& start,
|
||||
const btVector3& end,
|
||||
CharacterRayResult& result) const;
|
||||
|
||||
bool isHovering() const { return _hovering; }
|
||||
void setHovering(bool hovering) { _hovering = hovering; }
|
||||
void setMotorOnly(bool motorOnly) { _motorOnly = motorOnly; }
|
||||
|
||||
bool hasSupport() const { return _onFloor; }
|
||||
bool isSteppingUp() const { return _steppingUp; }
|
||||
const btVector3& getFloorNormal() const { return _floorNormal; }
|
||||
|
||||
void measurePenetration(btVector3& minBoxOut, btVector3& maxBoxOut);
|
||||
void refreshOverlappingPairCache();
|
||||
|
||||
protected:
|
||||
void removeFromWorld();
|
||||
void addToWorld();
|
||||
|
||||
bool resolvePenetration(int numTries);
|
||||
void updateVelocity(btScalar dt, btScalar gravity);
|
||||
void updateTraction(const btVector3& position);
|
||||
btScalar measureAvailableStepHeight() const;
|
||||
void updateHoverState(const btVector3& position);
|
||||
|
||||
protected:
|
||||
btVector3 _upDirection { 0.0f, 1.0f, 0.0f }; // input, up in world-frame
|
||||
btVector3 _motorVelocity { 0.0f, 0.0f, 0.0f }; // input, velocity character is trying to achieve
|
||||
btVector3 _linearVelocity { 0.0f, 0.0f, 0.0f }; // internal, actual character velocity
|
||||
btVector3 _floorNormal { 0.0f, 0.0f, 0.0f }; // internal, probable floor normal
|
||||
btVector3 _floorContact { 0.0f, 0.0f, 0.0f }; // internal, last floor contact point
|
||||
btCollisionWorld* _world { nullptr }; // input, pointer to world
|
||||
//btScalar _distanceToFeet { 0.0f }; // input, distance from object center to lowest point on shape
|
||||
btScalar _halfHeight { 0.0f };
|
||||
btScalar _radius { 0.0f };
|
||||
btScalar _maxWallNormalUpComponent { 0.0f }; // input: max vertical component of wall normal
|
||||
btScalar _maxStepHeight { 0.0f }; // input, max step height the character can climb
|
||||
btConvexHullShape* _characterShape { nullptr }; // input, shape of character
|
||||
CharacterGhostShape* _ghostShape { nullptr }; // internal, shape whose Aabb is used for overlap cache
|
||||
int16_t _collisionFilterGroup { 0 };
|
||||
int16_t _collisionFilterMask { 0 };
|
||||
bool _inWorld { false }; // internal, was added to world
|
||||
bool _hovering { false }; // internal,
|
||||
bool _onFloor { false }; // output, is actually standing on floor
|
||||
bool _steppingUp { false }; // output, future sweep hit a steppable ledge
|
||||
bool _hasFloor { false }; // output, has floor underneath to fall on
|
||||
bool _motorOnly { false }; // input, _linearVelocity slaves to _motorVelocity
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterGhostObject_h
|
|
@ -1,31 +0,0 @@
|
|||
//
|
||||
// CharacterGhostShape.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.14
|
||||
// Copyright 2016 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 "CharacterGhostShape.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
|
||||
CharacterGhostShape::CharacterGhostShape(const btConvexHullShape* shape) :
|
||||
btConvexHullShape(reinterpret_cast<const btScalar*>(shape->getUnscaledPoints()), shape->getNumPoints(), sizeof(btVector3)) {
|
||||
assert(shape);
|
||||
assert(shape->getUnscaledPoints());
|
||||
assert(shape->getNumPoints() > 0);
|
||||
setMargin(shape->getMargin());
|
||||
}
|
||||
|
||||
void CharacterGhostShape::getAabb (const btTransform& t, btVector3& aabbMin, btVector3& aabbMax) const {
|
||||
btConvexHullShape::getAabb(t, aabbMin, aabbMax);
|
||||
// double the size of the Aabb by expanding both corners by half the extent
|
||||
btVector3 expansion = 0.5f * (aabbMax - aabbMin);
|
||||
aabbMin -= expansion;
|
||||
aabbMax += expansion;
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// CharacterGhostShape.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.14
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterGhostShape_h
|
||||
#define hifi_CharacterGhostShape_h
|
||||
|
||||
#include <BulletCollision/CollisionShapes/btConvexHullShape.h>
|
||||
|
||||
class CharacterGhostShape : public btConvexHullShape {
|
||||
// Same as btConvexHullShape but reports an expanded Aabb for larger ghost overlap cache
|
||||
public:
|
||||
CharacterGhostShape(const btConvexHullShape* shape);
|
||||
|
||||
virtual void getAabb (const btTransform& t, btVector3& aabbMin, btVector3& aabbMax) const override;
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterGhostShape_h
|
|
@ -1,31 +0,0 @@
|
|||
//
|
||||
// CharaterRayResult.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.05
|
||||
// Copyright 2016 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 "CharacterRayResult.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "CharacterGhostObject.h"
|
||||
|
||||
CharacterRayResult::CharacterRayResult (const CharacterGhostObject* character) :
|
||||
btCollisionWorld::ClosestRayResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)),
|
||||
_character(character)
|
||||
{
|
||||
assert(_character);
|
||||
_character->getCollisionGroupAndMask(m_collisionFilterGroup, m_collisionFilterMask);
|
||||
}
|
||||
|
||||
btScalar CharacterRayResult::addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) {
|
||||
if (rayResult.m_collisionObject == _character) {
|
||||
return 1.0f;
|
||||
}
|
||||
return ClosestRayResultCallback::addSingleResult (rayResult, normalInWorldSpace);
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
//
|
||||
// CharaterRayResult.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.05
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterRayResult_h
|
||||
#define hifi_CharacterRayResult_h
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
class CharacterGhostObject;
|
||||
|
||||
class CharacterRayResult : public btCollisionWorld::ClosestRayResultCallback {
|
||||
public:
|
||||
CharacterRayResult (const CharacterGhostObject* character);
|
||||
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalRayResult& rayResult, bool normalInWorldSpace) override;
|
||||
|
||||
protected:
|
||||
const CharacterGhostObject* _character;
|
||||
|
||||
// Note: Public data members inherited from ClosestRayResultCallback
|
||||
//
|
||||
// btVector3 m_rayFromWorld;//used to calculate hitPointWorld from hitFraction
|
||||
// btVector3 m_rayToWorld;
|
||||
// btVector3 m_hitNormalWorld;
|
||||
// btVector3 m_hitPointWorld;
|
||||
//
|
||||
// Note: Public data members inherited from RayResultCallback
|
||||
//
|
||||
// btScalar m_closestHitFraction;
|
||||
// const btCollisionObject* m_collisionObject;
|
||||
// short int m_collisionFilterGroup;
|
||||
// short int m_collisionFilterMask;
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterRayResult_h
|
|
@ -1,42 +0,0 @@
|
|||
//
|
||||
// CharaterSweepResult.cpp
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.01
|
||||
// Copyright 2016 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 "CharacterSweepResult.h"
|
||||
|
||||
#include <assert.h>
|
||||
|
||||
#include "CharacterGhostObject.h"
|
||||
|
||||
CharacterSweepResult::CharacterSweepResult(const CharacterGhostObject* character)
|
||||
: btCollisionWorld::ClosestConvexResultCallback(btVector3(0.0f, 0.0f, 0.0f), btVector3(0.0f, 0.0f, 0.0f)),
|
||||
_character(character)
|
||||
{
|
||||
// set collision group and mask to match _character
|
||||
assert(_character);
|
||||
_character->getCollisionGroupAndMask(m_collisionFilterGroup, m_collisionFilterMask);
|
||||
}
|
||||
|
||||
btScalar CharacterSweepResult::addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool useWorldFrame) {
|
||||
// skip objects that we shouldn't collide with
|
||||
if (!convexResult.m_hitCollisionObject->hasContactResponse()) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
if (convexResult.m_hitCollisionObject == _character) {
|
||||
return btScalar(1.0);
|
||||
}
|
||||
|
||||
return ClosestConvexResultCallback::addSingleResult(convexResult, useWorldFrame);
|
||||
}
|
||||
|
||||
void CharacterSweepResult::resetHitHistory() {
|
||||
m_hitCollisionObject = nullptr;
|
||||
m_closestHitFraction = btScalar(1.0f);
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
//
|
||||
// CharaterSweepResult.h
|
||||
// libraries/physics/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.09.01
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_CharacterSweepResult_h
|
||||
#define hifi_CharacterSweepResult_h
|
||||
|
||||
#include <btBulletDynamicsCommon.h>
|
||||
#include <BulletCollision/CollisionDispatch/btCollisionWorld.h>
|
||||
|
||||
|
||||
class CharacterGhostObject;
|
||||
|
||||
class CharacterSweepResult : public btCollisionWorld::ClosestConvexResultCallback {
|
||||
public:
|
||||
CharacterSweepResult(const CharacterGhostObject* character);
|
||||
virtual btScalar addSingleResult(btCollisionWorld::LocalConvexResult& convexResult, bool useWorldFrame) override;
|
||||
void resetHitHistory();
|
||||
protected:
|
||||
const CharacterGhostObject* _character;
|
||||
|
||||
// NOTE: Public data members inherited from ClosestConvexResultCallback:
|
||||
//
|
||||
// btVector3 m_convexFromWorld; // unused except by btClosestNotMeConvexResultCallback
|
||||
// btVector3 m_convexToWorld; // unused except by btClosestNotMeConvexResultCallback
|
||||
// btVector3 m_hitNormalWorld;
|
||||
// btVector3 m_hitPointWorld;
|
||||
// const btCollisionObject* m_hitCollisionObject;
|
||||
//
|
||||
// NOTE: Public data members inherited from ConvexResultCallback:
|
||||
//
|
||||
// btScalar m_closestHitFraction;
|
||||
// short int m_collisionFilterGroup;
|
||||
// short int m_collisionFilterMask;
|
||||
|
||||
};
|
||||
|
||||
#endif // hifi_CharacterSweepResult_h
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CollisionRenderMeshCache.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.07.13
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// CollisionRenderMeshCache.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2016.07.13
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ContactEvent.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.01.20
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ContactEvent.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.01.20
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ObjectAction.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Seth Alves 2015-6-2
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ObjectAction.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Seth Alves 2015-6-2
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ObjectMotionState.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.11.05
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ObjectMotionState.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.11.05
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// PhysicalEntitySimulation.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.04.27
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// PhysicalEntitySimulation.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2015.04.27
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// PhysicsEngine.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// PhysicsEngine.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeFactory.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.12.01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeFactory.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.12.01
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeManager.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeManager.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
|
||||
<@include LightClusterGrid.slh@>
|
||||
|
||||
|
||||
in vec2 _texCoord0;
|
||||
out vec4 _fragColor;
|
||||
|
||||
|
@ -39,16 +38,14 @@ void main(void) {
|
|||
// Grab the fragment data from the uv
|
||||
vec2 texCoord = _texCoord0.st;
|
||||
|
||||
vec4 fragPosition = unpackDeferredPositionFromZeye(texCoord);
|
||||
DeferredFragment frag = unpackDeferredFragmentNoPosition(texCoord);
|
||||
DeferredFrameTransform deferredTransform = getDeferredFrameTransform();
|
||||
DeferredFragment frag = unpackDeferredFragment(deferredTransform, texCoord);
|
||||
vec4 fragPosition = frag.position;
|
||||
|
||||
if (frag.mode == FRAG_MODE_UNLIT) {
|
||||
discard;
|
||||
}
|
||||
|
||||
frag.position = fragPosition;
|
||||
|
||||
|
||||
// Frag pos in world
|
||||
mat4 invViewMat = getViewInverse();
|
||||
vec4 fragPos = invViewMat * fragPosition;
|
||||
|
@ -57,7 +54,6 @@ void main(void) {
|
|||
vec4 clusterEyePos = frustumGrid_worldToEye(fragPos);
|
||||
ivec3 clusterPos = frustumGrid_eyeToClusterPos(clusterEyePos.xyz);
|
||||
|
||||
|
||||
ivec3 cluster = clusterGrid_getCluster(frustumGrid_clusterToIndex(clusterPos));
|
||||
int numLights = cluster.x + cluster.y;
|
||||
if (numLights <= 0) {
|
||||
|
|
|
@ -210,7 +210,6 @@ void Font::read(QIODevice& in) {
|
|||
_texture = gpu::TexturePointer(gpu::Texture::create2D(formatGPU, image.width(), image.height(),
|
||||
gpu::Sampler(gpu::Sampler::FILTER_MIN_POINT_MAG_LINEAR)));
|
||||
_texture->assignStoredMip(0, formatMip, image.byteCount(), image.constBits());
|
||||
_texture->autoGenerateMips(-1);
|
||||
}
|
||||
|
||||
void Font::setupGPU() {
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// BackgroundMode.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Copyright 2015 High Fidelity, Inc.
|
||||
//
|
||||
|
|
|
@ -578,42 +578,3 @@ float coneSphereAngle(const glm::vec3& coneCenter, const glm::vec3& coneDirectio
|
|||
|
||||
return glm::max(0.0f, theta - phi);
|
||||
}
|
||||
|
||||
glm::vec3 nearestPointOnLineSegment(const glm::vec3& point, const glm::vec3& segmentStart, const glm::vec3& segmentEnd) {
|
||||
glm::vec3 n = segmentEnd - segmentStart;
|
||||
float len = glm::length(n);
|
||||
if (len < EPSILON) {
|
||||
return segmentStart;
|
||||
} else {
|
||||
n /= len;
|
||||
float projection = glm::dot(point - segmentStart, n);
|
||||
projection = glm::clamp(projection, 0.0f, len);
|
||||
return segmentStart + n * projection;
|
||||
}
|
||||
}
|
||||
|
||||
float distanceFromLineSegment(const glm::vec3& point, const glm::vec3& segmentStart, const glm::vec3& segmentEnd) {
|
||||
return glm::length(point - nearestPointOnLineSegment(point, segmentStart, segmentEnd));
|
||||
}
|
||||
|
||||
float distanceFromCapsule(const glm::vec3& point, const glm::vec3& segmentStart, const glm::vec3& segmentEnd, float capsuleRadius) {
|
||||
return distanceFromLineSegment(point, segmentStart, segmentEnd) - capsuleRadius;
|
||||
}
|
||||
|
||||
bool pointIsInsideCapsule(const glm::vec3& point, const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float capsuleRadius) {
|
||||
return distanceFromLineSegment(point, capsuleStart, capsuleEnd) < capsuleRadius;
|
||||
}
|
||||
|
||||
glm::vec3 projectPointOntoCapsule(const glm::vec3& point, const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float capsuleRadius) {
|
||||
glm::vec3 nearestPoint = nearestPointOnLineSegment(point, capsuleStart, capsuleEnd);
|
||||
glm::vec3 d = point - nearestPoint;
|
||||
float dLen = glm::length(d);
|
||||
if (dLen < EPSILON) {
|
||||
return nearestPoint; // TODO: maybe we should pick a point actually on the surface...
|
||||
} else {
|
||||
return nearestPoint + d * (capsuleRadius / dLen);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -160,10 +160,5 @@ private:
|
|||
static void copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& lengthB, glm::vec2* vertexArrayB);
|
||||
};
|
||||
|
||||
glm::vec3 nearestPointOnLineSegment(const glm::vec3& point, const glm::vec3& segmentStart, const glm::vec3& segmentEnd);
|
||||
float distanceFromLineSegment(const glm::vec3& point, const glm::vec3& segmentStart, const glm::vec3& segmentEnd);
|
||||
float distanceFromCapsule(const glm::vec3& point, const glm::vec3& segmentStart, const glm::vec3& segmentEnd, float capsuleRadius);
|
||||
bool pointIsInsideCapsule(const glm::vec3& point, const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float capsuleRadius);
|
||||
glm::vec3 projectPointOntoCapsule(const glm::vec3& point, const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float capsuleRadius);
|
||||
|
||||
#endif // hifi_GeometryUtil_h
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeInfo.cpp
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//
|
||||
// ShapeInfo.h
|
||||
// libraries/physics/src
|
||||
// libraries/physcis/src
|
||||
//
|
||||
// Created by Andrew Meadows 2014.10.29
|
||||
// Copyright 2014 High Fidelity, Inc.
|
||||
|
|
|
@ -26,6 +26,15 @@
|
|||
#include <windows.h>
|
||||
#include "CPUIdent.h"
|
||||
#include <Psapi.h>
|
||||
|
||||
#if _MSC_VER >= 1900
|
||||
#pragma comment(lib, "legacy_stdio_definitions.lib")
|
||||
FILE _iob[] = {*stdin, *stdout, *stderr};
|
||||
extern "C" FILE * __cdecl __iob_func(void) {
|
||||
return _iob;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
|
|
|
@ -1,27 +1,50 @@
|
|||
|
||||
(function() {
|
||||
var BALLOT_X = '✗';
|
||||
var CHECKMARK = '✓';
|
||||
var DOWN_RIGHT_ARROW = '↳';
|
||||
var PASSED = 'passed';
|
||||
var lastSpecStartTime;
|
||||
function ConsoleReporter(options) {
|
||||
var startTime = new Date().getTime();
|
||||
var errorCount = 0;
|
||||
this.jasmineStarted = function (obj) {
|
||||
print("jasmineStarted: numSpecs = " + obj.totalSpecsDefined);
|
||||
print('Jasmine started with ' + obj.totalSpecsDefined + ' tests.');
|
||||
};
|
||||
this.jasmineDone = function (obj) {
|
||||
print("jasmineDone");
|
||||
var ERROR = errorCount === 1 ? 'error' : 'errors';
|
||||
var endTime = new Date().getTime();
|
||||
print('<hr />');
|
||||
if (errorCount === 0) {
|
||||
print ('<span style="color:green">All tests passed!</span>');
|
||||
} else {
|
||||
print('<span style="color:red">Tests completed with ' +
|
||||
errorCount + ' ' + ERROR + '.<span>');
|
||||
}
|
||||
print('Tests completed in ' + (endTime - startTime) + 'ms.');
|
||||
};
|
||||
this.suiteStarted = function(obj) {
|
||||
print("suiteStarted: \"" + obj.fullName + "\"");
|
||||
print(obj.fullName);
|
||||
};
|
||||
this.suiteDone = function(obj) {
|
||||
print("suiteDone: \"" + obj.fullName + "\" " + obj.status);
|
||||
print('');
|
||||
};
|
||||
this.specStarted = function(obj) {
|
||||
print("specStarted: \"" + obj.fullName + "\"");
|
||||
lastSpecStartTime = new Date().getTime();
|
||||
};
|
||||
this.specDone = function(obj) {
|
||||
print("specDone: \"" + obj.fullName + "\" " + obj.status);
|
||||
var specEndTime = new Date().getTime();
|
||||
var symbol = obj.status === PASSED ?
|
||||
'<span style="color:green">' + CHECKMARK + '</span>' :
|
||||
'<span style="color:red">' + BALLOT_X + '</span>';
|
||||
print('... ' + obj.fullName + ' ' + symbol + ' ' + '<span style="color:orange">[' +
|
||||
(specEndTime - lastSpecStartTime) + 'ms]</span>');
|
||||
|
||||
var i, l = obj.failedExpectations.length;
|
||||
for (i = 0; i < l; i++) {
|
||||
print(" " + obj.failedExpectations[i].message);
|
||||
var specErrors = obj.failedExpectations.length;
|
||||
errorCount += specErrors;
|
||||
for (var i = 0; i < specErrors; i++) {
|
||||
print('<span style="margin-right:0.5em"></span>' + DOWN_RIGHT_ARROW +
|
||||
'<span style="color:red"> ' +
|
||||
obj.failedExpectations[i].message + '</span>');
|
||||
}
|
||||
};
|
||||
return this;
|
||||
|
@ -44,10 +67,11 @@
|
|||
|
||||
function extend(destination, source) {
|
||||
for (var property in source) {
|
||||
destination[property] = source[property];
|
||||
if (source.hasOwnProperty(property)) {
|
||||
destination[property] = source[property];
|
||||
}
|
||||
}
|
||||
return destination;
|
||||
}
|
||||
|
||||
}());
|
||||
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
|
||||
Script.include("../libraries/jasmine/jasmine.js");
|
||||
Script.include("../libraries/jasmine/hifi-boot.js");
|
||||
|
||||
// Art3mis
|
||||
var DEFAULT_AVATAR_URL = "https://hifi-metaverse.s3-us-west-1.amazonaws.com/marketplace/contents/e76946cc-c272-4adf-9bb6-02cde0a4b57d/8fd984ea6fe1495147a3303f87fa6e23.fst?1460131758";
|
||||
|
||||
|
@ -55,5 +52,3 @@ describe("MyAvatar", function () {
|
|||
|
||||
});
|
||||
|
||||
jasmine.getEnv().execute();
|
||||
|
|
@ -1,9 +1,12 @@
|
|||
Script.include('../libraries/jasmine/jasmine.js');
|
||||
Script.include('../libraries/jasmine/hifi-boot.js');
|
||||
Script.include('../../system/libraries/utils.js');
|
||||
Script.include('../../../system/libraries/utils.js');
|
||||
|
||||
describe('Bind', function() {
|
||||
it('functions should have bind available', function() {
|
||||
it('exists for functions', function() {
|
||||
var FUNC = 'function';
|
||||
expect(typeof(function() {}.bind)).toEqual(FUNC);
|
||||
});
|
||||
|
||||
it('should allow for setting context of this', function() {
|
||||
var foo = 'bar';
|
||||
|
||||
function callAnotherFn(anotherFn) {
|
||||
|
@ -22,10 +25,6 @@ describe('Bind', function() {
|
|||
|
||||
var instance = new TestConstructor();
|
||||
|
||||
expect(typeof(instance.doSomething.bind) !== 'undefined');
|
||||
expect(instance.doSomething()).toEqual(foo);
|
||||
});
|
||||
});
|
||||
|
||||
jasmine.getEnv().execute();
|
||||
Script.stop();
|
53
scripts/developer/tests/unit_tests/entityUnitTests.js
Normal file
53
scripts/developer/tests/unit_tests/entityUnitTests.js
Normal file
|
@ -0,0 +1,53 @@
|
|||
describe('Entity', function() {
|
||||
var center = Vec3.sum(
|
||||
MyAvatar.position,
|
||||
Vec3.multiply(3, Quat.getFront(Camera.getOrientation()))
|
||||
);
|
||||
var boxEntity;
|
||||
var boxProps = {
|
||||
type: 'Box',
|
||||
color: {
|
||||
red: 255,
|
||||
green: 255,
|
||||
blue: 255,
|
||||
},
|
||||
position: center,
|
||||
dimensions: {
|
||||
x: 1,
|
||||
y: 1,
|
||||
z: 1,
|
||||
},
|
||||
};
|
||||
|
||||
beforeEach(function() {
|
||||
boxEntity = Entities.addEntity(boxProps);
|
||||
});
|
||||
|
||||
afterEach(function() {
|
||||
Entities.deleteEntity(boxEntity);
|
||||
boxEntity = null;
|
||||
});
|
||||
|
||||
it('can be added programmatically', function() {
|
||||
expect(typeof(boxEntity)).toEqual('string');
|
||||
});
|
||||
|
||||
it('instantiates itself correctly', function() {
|
||||
var props = Entities.getEntityProperties(boxEntity);
|
||||
expect(props.type).toEqual(boxProps.type);
|
||||
});
|
||||
|
||||
it('can be modified after creation', function() {
|
||||
var newPos = {
|
||||
x: boxProps.position.x,
|
||||
y: boxProps.position.y,
|
||||
z: boxProps.position.z + 1.0,
|
||||
};
|
||||
Entities.editEntity(boxEntity, {
|
||||
position: newPos,
|
||||
});
|
||||
|
||||
var props = Entities.getEntityProperties(boxEntity);
|
||||
expect(Math.round(props.position.z)).toEqual(Math.round(newPos.z));
|
||||
});
|
||||
});
|
13
scripts/developer/tests/unit_tests/testRunner.js
Normal file
13
scripts/developer/tests/unit_tests/testRunner.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
// Include testing library
|
||||
Script.include('../../libraries/jasmine/jasmine.js');
|
||||
Script.include('../../libraries/jasmine/hifi-boot.js')
|
||||
|
||||
// Include unit tests
|
||||
// FIXME: Figure out why jasmine done() is not working.
|
||||
// Script.include('avatarUnitTests.js');
|
||||
Script.include('bindUnitTest.js');
|
||||
Script.include('entityUnitTests.js');
|
||||
|
||||
// Run the tests
|
||||
jasmine.getEnv().execute();
|
||||
Script.stop();
|
|
@ -1,126 +1,123 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 19.2.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 50 200.1" style="enable-background:new 0 0 50 200.1;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#414042;}
|
||||
.st1{fill:#1E1E1E;}
|
||||
.st2{fill:#FFFFFF;}
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:#414042;}
|
||||
.st2{fill:#1E1E1E;}
|
||||
.st3{fill:#333333;}
|
||||
</style>
|
||||
<g id="Layer_2">
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50,196.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V196.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st1" d="M50,96.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g id="Layer_3">
|
||||
</g>
|
||||
<g id="Layer_1">
|
||||
<g>
|
||||
<g>
|
||||
<path class="st2" d="M18,192.8v-4.2l-1.6,3.1h-0.7l-1.6-3.1v4.2h-1.2v-6.4h1.3l1.9,3.6l1.9-3.6h1.3v6.4H18z"/>
|
||||
<path class="st2" d="M23.5,191.8c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
|
||||
c-0.4-0.2-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3H22v3.3c0,0.3,0,0.5,0.1,0.8
|
||||
c0.1,0.2,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.3,0.5,0.5S23.2,191.8,23.5,191.8z"/>
|
||||
<path class="st2" d="M32.6,187.5h-2v5.3h-1.2v-5.3h-2v-1.1h5.3V187.5z"/>
|
||||
<path class="st2" d="M38,191.8v1.1h-4.4v-6.4H38v1.1h-3.1v1.5h2.7v1h-2.7v1.7H38z"/>
|
||||
</g>
|
||||
<path class="st2" d="M28.2,115.4l1.1-1.1c-1.1-1.3-2.7-2.1-4.6-2.1c-1.7,0-3.3,0.8-4.4,2c0.4,0.4,0.8,0.8,1.1,1.1
|
||||
c0.8-0.9,2-1.5,3.3-1.5C26.2,113.7,27.4,114.4,28.2,115.4z"/>
|
||||
<path class="st2" d="M30.8,112.8l1.1-1.1c-1.8-2-4.4-3.2-7.2-3.2c-2.8,0-5.3,1.2-7,3c0.4,0.4,0.8,0.8,1.1,1.1
|
||||
c1.5-1.6,3.6-2.6,5.9-2.6C27.2,110,29.4,111.1,30.8,112.8z"/>
|
||||
<path class="st2" d="M26.7,119.9v-2.4c0-1.1-0.9-1.9-1.9-1.9h0c-1.1,0-1.9,0.9-1.9,1.9v2.4H26.7z"/>
|
||||
<path class="st2" d="M22.9,121.5v2.4c0,1.1,0.9,1.9,1.9,1.9h0c1.1,0,1.9-0.9,1.9-1.9v-2.4H22.9z"/>
|
||||
<path class="st2" d="M30.6,124.3c0,0,0-2.6,0-2.7c0-0.5-0.4-0.9-1-0.9c-0.5,0-0.9,0.4-0.9,0.9c0,0.2,0,2.5,0,2.7
|
||||
c0,1.9-1.7,3.5-3.9,3.5c-2.1,0-3.9-1.6-3.9-3.5c0-0.2,0-2.6,0-2.8c0-0.5-0.4-0.9-0.9-1c-0.5,0-0.9,0.3-1,0.9c0,0.1,0,2.8,0,2.9
|
||||
c0,2.6,2.1,4.8,4.8,5.2l0,1.8h-2.2c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9H28c0.5,0,0.9-0.4,0.9-0.9c0-0.5-0.4-0.9-0.9-0.9
|
||||
h-2.3l0-1.8C28.5,129.1,30.6,126.9,30.6,124.3z"/>
|
||||
<path class="st2" d="M28.3,65.3l1.1-1.1c-1.1-1.3-2.7-2.1-4.6-2.1c-1.7,0-3.3,0.8-4.4,2c0.4,0.4,0.8,0.8,1.1,1.1
|
||||
c0.8-0.9,2-1.5,3.3-1.5C26.3,63.6,27.5,64.3,28.3,65.3z"/>
|
||||
<path class="st2" d="M31,62.7l1.1-1.1c-1.8-2-4.4-3.2-7.2-3.2c-2.8,0-5.3,1.2-7,3c0.4,0.4,0.8,0.8,1.1,1.1c1.5-1.6,3.6-2.6,5.9-2.6
|
||||
C27.3,59.9,29.5,61,31,62.7z"/>
|
||||
<path class="st2" d="M26.9,69.8v-2.4c0-1.1-0.9-1.9-1.9-1.9h0c-1.1,0-1.9,0.9-1.9,1.9v2.4H26.9z"/>
|
||||
<path class="st2" d="M23,71.4v2.4c0,1.1,0.9,1.9,1.9,1.9h0c1.1,0,1.9-0.9,1.9-1.9v-2.4H23z"/>
|
||||
<path class="st2" d="M30.7,74.2c0,0,0-2.6,0-2.7c0-0.5-0.4-0.9-1-0.9c-0.5,0-0.9,0.4-0.9,0.9c0,0.2,0,2.5,0,2.7
|
||||
c0,1.9-1.7,3.5-3.9,3.5c-2.1,0-3.9-1.6-3.9-3.5c0-0.2,0-2.6,0-2.8c0-0.5-0.4-0.9-0.9-1c-0.5,0-0.9,0.3-1,0.9c0,0.1,0,2.8,0,2.9
|
||||
c0,2.6,2.1,4.8,4.8,5.2l0,1.8h-2.2c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9h6.4c0.5,0,0.9-0.4,0.9-0.9c0-0.5-0.4-0.9-0.9-0.9
|
||||
h-2.3l0-1.8C28.6,79,30.7,76.9,30.7,74.2z"/>
|
||||
<g>
|
||||
<path class="st2" d="M18,92.8v-4.2l-1.6,3.1h-0.7l-1.6-3.1v4.2h-1.2v-6.4h1.3l1.9,3.6l1.9-3.6h1.3v6.4H18z"/>
|
||||
<path class="st2" d="M23.5,91.8c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
|
||||
c-0.4-0.2-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3H22v3.3c0,0.3,0,0.5,0.1,0.8
|
||||
c0.1,0.2,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.3,0.5,0.5S23.2,91.8,23.5,91.8z"/>
|
||||
<path class="st2" d="M32.6,87.5h-2v5.3h-1.2v-5.3h-2v-1.1h5.3V87.5z"/>
|
||||
<path class="st2" d="M38,91.8v1.1h-4.4v-6.4H38v1.1h-3.1v1.5h2.7v1h-2.7v1.7H38z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M8.3,41.9c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7C9.3,42.9,8.8,43,8.3,43c-0.5,0-0.9-0.1-1.3-0.3
|
||||
c-0.4-0.2-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8
|
||||
c0.1,0.2,0.1,0.5,0.3,0.7s0.3,0.3,0.5,0.5S8,41.9,8.3,41.9z"/>
|
||||
<path class="st2" d="M13.8,38.8v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L13.8,38.8z"/>
|
||||
<path class="st2" d="M24.9,42.9v-4.2l-1.6,3.1h-0.7l-1.6-3.1v4.2h-1.2v-6.4H21l1.9,3.6l1.9-3.6h1.3v6.4H24.9z"/>
|
||||
<path class="st2" d="M30.4,41.9c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
|
||||
c-0.4-0.2-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8
|
||||
c0.1,0.2,0.1,0.5,0.3,0.7s0.3,0.3,0.5,0.5S30.1,41.9,30.4,41.9z"/>
|
||||
<path class="st2" d="M39.4,37.6h-2v5.3h-1.2v-5.3h-2v-1.1h5.3V37.6z"/>
|
||||
<path class="st2" d="M44.9,41.8v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H44.9z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon class="st2" points="27.3,18.7 27.3,18.7 27.3,18.7 "/>
|
||||
<path class="st2" d="M26.1,14.4l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
s-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0
|
||||
c0.4-0.4,0.4-1.1,0-1.5L26.1,14.4z"/>
|
||||
</g>
|
||||
<path class="st2" d="M22.9,21.6V24c0,1.1,0.9,1.9,1.9,1.9h0c1.1,0,1.9-0.9,1.9-1.9v-2.4H22.9z"/>
|
||||
<path class="st2" d="M30.6,24.4c0,0,0-2.6,0-2.7c0-0.5-0.4-0.9-1-0.9c-0.5,0-0.9,0.4-0.9,0.9c0,0.2,0,2.5,0,2.7
|
||||
c0,1.9-1.7,3.5-3.9,3.5c-2.1,0-3.9-1.6-3.9-3.5c0-0.2,0-2.6,0-2.8c0-0.5-0.4-0.9-0.9-1c-0.5,0-0.9,0.3-1,0.9c0,0.1,0,2.8,0,2.9
|
||||
c0,2.6,2.1,4.8,4.8,5.2l0,1.8h-2.2c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9H28c0.5,0,0.9-0.4,0.9-0.9c0-0.5-0.4-0.9-0.9-0.9
|
||||
h-2.3l0-1.8C28.5,29.2,30.6,27,30.6,24.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon class="st2" points="27.3,168.6 27.3,168.6 27.3,168.6 "/>
|
||||
<path class="st2" d="M26.1,164.3l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5c-0.4-0.4-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7
|
||||
c-0.4-0.4-1.1-0.4-1.5,0c-0.4,0.4-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5c0.4,0.4,1.1,0.4,1.5,0l2.7-2.7l2.7,2.7
|
||||
c0.4,0.4,1.1,0.4,1.5,0c0.4-0.4,0.4-1.1,0-1.5L26.1,164.3z"/>
|
||||
</g>
|
||||
<path class="st2" d="M22.9,171.5v2.4c0,1.1,0.9,1.9,1.9,1.9h0c1.1,0,1.9-0.9,1.9-1.9v-2.4H22.9z"/>
|
||||
<path class="st2" d="M30.6,174.3c0,0,0-2.6,0-2.7c0-0.5-0.4-0.9-1-0.9c-0.5,0-0.9,0.4-0.9,0.9c0,0.2,0,2.5,0,2.7
|
||||
c0,1.9-1.7,3.5-3.9,3.5c-2.1,0-3.9-1.6-3.9-3.5c0-0.2,0-2.6,0-2.8c0-0.5-0.4-0.9-0.9-1c-0.5,0-0.9,0.3-1,0.9c0,0.1,0,2.8,0,2.9
|
||||
c0,2.6,2.1,4.8,4.8,5.2l0,1.8h-2.2c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9H28c0.5,0,0.9-0.4,0.9-0.9c0-0.5-0.4-0.9-0.9-0.9
|
||||
h-2.3l0-1.8C28.5,179.1,30.6,176.9,30.6,174.3z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M8.2,141.9c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3H11v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
|
||||
S6.3,142.3,6,142c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8c0.1,0.2,0.1,0.5,0.3,0.7
|
||||
c0.1,0.2,0.3,0.3,0.5,0.5S7.9,141.9,8.2,141.9z"/>
|
||||
<path class="st2" d="M13.7,138.8v4.1h-1.2v-6.4h1l3.3,4.2v-4.2H18v6.4h-1L13.7,138.8z"/>
|
||||
<path class="st2" d="M24.8,142.9v-4.2l-1.6,3.2h-0.7l-1.6-3.2v4.2h-1.2v-6.4h1.3l1.9,3.6l1.9-3.6H26v6.4H24.8z"/>
|
||||
<path class="st2" d="M30.3,141.9c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5c0.1-0.2,0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1c-0.2,0.3-0.5,0.5-0.9,0.7c-0.4,0.2-0.8,0.3-1.3,0.3c-0.5,0-0.9-0.1-1.3-0.3
|
||||
s-0.6-0.4-0.9-0.7c-0.2-0.3-0.4-0.6-0.5-1c-0.1-0.4-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8c0.1,0.2,0.1,0.5,0.3,0.7
|
||||
c0.1,0.2,0.3,0.3,0.5,0.5S30,141.9,30.3,141.9z"/>
|
||||
<path class="st2" d="M39.3,137.6h-2v5.3H36v-5.3h-2v-1.1h5.3V137.6z"/>
|
||||
<path class="st2" d="M44.8,141.8v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7H44.8z"/>
|
||||
<path class="st0" d="M50.1,146.1c0,2.2-1.8,4-4,4h-42c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V146.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st1" d="M50,196.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V196.1z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M50,46c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4V4c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V46z"/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st2" d="M50,96.1c0,2.2-1.8,4-4,4H4c-2.2,0-4-1.8-4-4v-42c0-2.2,1.8-4,4-4h42c2.2,0,4,1.8,4,4V96.1z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st0" d="M18,192.8v-4.2l-1.6,3.1h-0.7l-1.6-3.1v4.2h-1.2v-6.4h1.3l1.9,3.6l1.9-3.6h1.3v6.4H18z"/>
|
||||
<path class="st0" d="M23.5,191.8c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5s0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2s-0.3,0.7-0.5,1s-0.5,0.5-0.9,0.7s-0.8,0.3-1.3,0.3s-0.9-0.1-1.3-0.3s-0.6-0.4-0.9-0.7
|
||||
c-0.2-0.3-0.4-0.6-0.5-1s-0.1-0.8-0.1-1.2v-3.3H22v3.3c0,0.3,0,0.5,0.1,0.8c0.1,0.2,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.3,0.5,0.5
|
||||
S23.2,191.8,23.5,191.8z"/>
|
||||
<path class="st0" d="M32.6,187.5h-2v5.3h-1.2v-5.3h-2v-1.1h5.3L32.6,187.5L32.6,187.5z"/>
|
||||
<path class="st0" d="M38,191.8v1.1h-4.4v-6.4H38v1.1h-3.1v1.5h2.7v1h-2.7v1.7L38,191.8L38,191.8z"/>
|
||||
</g>
|
||||
<path class="st3" d="M28.2,115.4l1.1-1.1c-1.1-1.3-2.7-2.1-4.6-2.1c-1.7,0-3.3,0.8-4.4,2c0.4,0.4,0.8,0.8,1.1,1.1
|
||||
c0.8-0.9,2-1.5,3.3-1.5C26.2,113.7,27.4,114.4,28.2,115.4z"/>
|
||||
<path class="st3" d="M30.8,112.8l1.1-1.1c-1.8-2-4.4-3.2-7.2-3.2s-5.3,1.2-7,3c0.4,0.4,0.8,0.8,1.1,1.1c1.5-1.6,3.6-2.6,5.9-2.6
|
||||
C27.2,110,29.4,111.1,30.8,112.8z"/>
|
||||
<path class="st3" d="M26.7,119.9v-2.4c0-1.1-0.9-1.9-1.9-1.9l0,0c-1.1,0-1.9,0.9-1.9,1.9v2.4H26.7z"/>
|
||||
<path class="st3" d="M22.9,121.5v2.4c0,1.1,0.9,1.9,1.9,1.9l0,0c1.1,0,1.9-0.9,1.9-1.9v-2.4H22.9z"/>
|
||||
<path class="st3" d="M30.6,124.3c0,0,0-2.6,0-2.7c0-0.5-0.4-0.9-1-0.9c-0.5,0-0.9,0.4-0.9,0.9c0,0.2,0,2.5,0,2.7
|
||||
c0,1.9-1.7,3.5-3.9,3.5c-2.1,0-3.9-1.6-3.9-3.5c0-0.2,0-2.6,0-2.8c0-0.5-0.4-0.9-0.9-1c-0.5,0-0.9,0.3-1,0.9c0,0.1,0,2.8,0,2.9
|
||||
c0,2.6,2.1,4.8,4.8,5.2v1.8h-2.2c-0.5,0-0.9,0.4-0.9,0.9s0.4,0.9,0.9,0.9H28c0.5,0,0.9-0.4,0.9-0.9s-0.4-0.9-0.9-0.9h-2.3v-1.8
|
||||
C28.5,129.1,30.6,126.9,30.6,124.3z"/>
|
||||
<path class="st0" d="M28.3,65.3l1.1-1.1c-1.1-1.3-2.7-2.1-4.6-2.1c-1.7,0-3.3,0.8-4.4,2c0.4,0.4,0.8,0.8,1.1,1.1
|
||||
c0.8-0.9,2-1.5,3.3-1.5C26.3,63.6,27.5,64.3,28.3,65.3z"/>
|
||||
<path class="st0" d="M31,62.7l1.1-1.1c-1.8-2-4.4-3.2-7.2-3.2s-5.3,1.2-7,3c0.4,0.4,0.8,0.8,1.1,1.1c1.5-1.6,3.6-2.6,5.9-2.6
|
||||
C27.3,59.9,29.5,61,31,62.7z"/>
|
||||
<path class="st0" d="M26.9,69.8v-2.4c0-1.1-0.9-1.9-1.9-1.9l0,0c-1.1,0-1.9,0.9-1.9,1.9v2.4H26.9z"/>
|
||||
<path class="st0" d="M23,71.4v2.4c0,1.1,0.9,1.9,1.9,1.9l0,0c1.1,0,1.9-0.9,1.9-1.9v-2.4H23z"/>
|
||||
<path class="st0" d="M30.7,74.2c0,0,0-2.6,0-2.7c0-0.5-0.4-0.9-1-0.9c-0.5,0-0.9,0.4-0.9,0.9c0,0.2,0,2.5,0,2.7
|
||||
c0,1.9-1.7,3.5-3.9,3.5c-2.1,0-3.9-1.6-3.9-3.5c0-0.2,0-2.6,0-2.8c0-0.5-0.4-0.9-0.9-1c-0.5,0-0.9,0.3-1,0.9c0,0.1,0,2.8,0,2.9
|
||||
c0,2.6,2.1,4.8,4.8,5.2v1.8h-2.2c-0.5,0-0.9,0.4-0.9,0.9s0.4,0.9,0.9,0.9h6.4c0.5,0,0.9-0.4,0.9-0.9s-0.4-0.9-0.9-0.9h-2.3v-1.8
|
||||
C28.6,79,30.7,76.9,30.7,74.2z"/>
|
||||
<g>
|
||||
<path class="st0" d="M18,92.8v-4.2l-1.6,3.1h-0.7l-1.6-3.1v4.2h-1.2v-6.4h1.3l1.9,3.6l1.9-3.6h1.3v6.4H18z"/>
|
||||
<path class="st0" d="M23.5,91.8c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5s0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1s-0.5,0.5-0.9,0.7s-0.8,0.3-1.3,0.3s-0.9-0.1-1.3-0.3c-0.4-0.2-0.6-0.4-0.9-0.7
|
||||
c-0.2-0.3-0.4-0.6-0.5-1s-0.1-0.8-0.1-1.2v-3.3H22v3.3c0,0.3,0,0.5,0.1,0.8c0.1,0.2,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.3,0.5,0.5
|
||||
S23.2,91.8,23.5,91.8z"/>
|
||||
<path class="st0" d="M32.6,87.5h-2v5.3h-1.2v-5.3h-2v-1.1h5.3L32.6,87.5L32.6,87.5z"/>
|
||||
<path class="st0" d="M38,91.8v1.1h-4.4v-6.4H38v1.1h-3.1v1.5h2.7v1h-2.7v1.7C34.9,91.8,38,91.8,38,91.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M8.3,41.9c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5s0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1s-0.5,0.5-0.9,0.7C9.3,42.9,8.8,43,8.3,43S7.4,42.9,7,42.7S6.4,42.3,6.1,42
|
||||
c-0.2-0.3-0.4-0.6-0.5-1s-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8c0.1,0.2,0.1,0.5,0.3,0.7s0.3,0.3,0.5,0.5
|
||||
S8,41.9,8.3,41.9z"/>
|
||||
<path class="st3" d="M13.8,38.8v4.1h-1.2v-6.4h1l3.3,4.2v-4.2h1.2v6.4h-1L13.8,38.8z"/>
|
||||
<path class="st3" d="M24.9,42.9v-4.2l-1.6,3.1h-0.7L21,38.7v4.2h-1.2v-6.4H21l1.9,3.6l1.9-3.6h1.3v6.4H24.9z"/>
|
||||
<path class="st3" d="M30.4,41.9c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5s0.2-0.4,0.3-0.7S32,40,32,39.7v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1s-0.5,0.5-0.9,0.7s-0.8,0.3-1.3,0.3s-0.9-0.1-1.3-0.3s-0.6-0.4-0.9-0.7
|
||||
c-0.2-0.3-0.4-0.6-0.5-1s-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8c0.1,0.2,0.1,0.5,0.3,0.7s0.3,0.3,0.5,0.5
|
||||
S30.1,41.9,30.4,41.9z"/>
|
||||
<path class="st3" d="M39.4,37.6h-2v5.3h-1.2v-5.3h-2v-1.1h5.3L39.4,37.6L39.4,37.6z"/>
|
||||
<path class="st3" d="M44.9,41.8v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7C41.8,41.8,44.9,41.8,44.9,41.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st3" d="M27.3,18.7L27.3,18.7L27.3,18.7z"/>
|
||||
<path class="st3" d="M26.1,14.4l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5s-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
s-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5s1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0s0.4-1.1,0-1.5
|
||||
L26.1,14.4z"/>
|
||||
</g>
|
||||
<path class="st3" d="M22.9,21.6V24c0,1.1,0.9,1.9,1.9,1.9l0,0c1.1,0,1.9-0.9,1.9-1.9v-2.4H22.9z"/>
|
||||
<path class="st3" d="M30.6,24.4c0,0,0-2.6,0-2.7c0-0.5-0.4-0.9-1-0.9c-0.5,0-0.9,0.4-0.9,0.9c0,0.2,0,2.5,0,2.7
|
||||
c0,1.9-1.7,3.5-3.9,3.5c-2.1,0-3.9-1.6-3.9-3.5c0-0.2,0-2.6,0-2.8c0-0.5-0.4-0.9-0.9-1c-0.5,0-0.9,0.3-1,0.9c0,0.1,0,2.8,0,2.9
|
||||
c0,2.6,2.1,4.8,4.8,5.2v1.8h-2.2c-0.5,0-0.9,0.4-0.9,0.9c0,0.5,0.4,0.9,0.9,0.9H28c0.5,0,0.9-0.4,0.9-0.9s-0.4-0.9-0.9-0.9h-2.3
|
||||
v-1.8C28.5,29.2,30.6,27,30.6,24.4z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M27.3,168.6L27.3,168.6L27.3,168.6z"/>
|
||||
<path class="st0" d="M26.1,164.3l2.7-2.7c0.4-0.4,0.4-1.1,0-1.5s-1.1-0.4-1.5,0l-2.7,2.7l-2.7-2.7c-0.4-0.4-1.1-0.4-1.5,0
|
||||
s-0.4,1.1,0,1.5l2.7,2.7l-2.7,2.7c-0.4,0.4-0.4,1.1,0,1.5s1.1,0.4,1.5,0l2.7-2.7l2.7,2.7c0.4,0.4,1.1,0.4,1.5,0s0.4-1.1,0-1.5
|
||||
L26.1,164.3z"/>
|
||||
</g>
|
||||
<path class="st0" d="M22.9,171.5v2.4c0,1.1,0.9,1.9,1.9,1.9l0,0c1.1,0,1.9-0.9,1.9-1.9v-2.4H22.9z"/>
|
||||
<path class="st0" d="M30.6,174.3c0,0,0-2.6,0-2.7c0-0.5-0.4-0.9-1-0.9c-0.5,0-0.9,0.4-0.9,0.9c0,0.2,0,2.5,0,2.7
|
||||
c0,1.9-1.7,3.5-3.9,3.5c-2.1,0-3.9-1.6-3.9-3.5c0-0.2,0-2.6,0-2.8c0-0.5-0.4-0.9-0.9-1c-0.5,0-0.9,0.3-1,0.9c0,0.1,0,2.8,0,2.9
|
||||
c0,2.6,2.1,4.8,4.8,5.2v1.8h-2.2c-0.5,0-0.9,0.4-0.9,0.9s0.4,0.9,0.9,0.9H28c0.5,0,0.9-0.4,0.9-0.9s-0.4-0.9-0.9-0.9h-2.3v-1.8
|
||||
C28.5,179.1,30.6,176.9,30.6,174.3z"/>
|
||||
</g>
|
||||
<g>
|
||||
<path class="st3" d="M8.2,141.9c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5s0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3H11v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1s-0.5,0.5-0.9,0.7s-0.8,0.3-1.3,0.3s-0.9-0.1-1.3-0.3S6.3,142.3,6,142
|
||||
c-0.2-0.3-0.4-0.6-0.5-1s-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8c0.1,0.2,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.3,0.5,0.5
|
||||
S7.9,141.9,8.2,141.9z"/>
|
||||
<path class="st3" d="M13.7,138.8v4.1h-1.2v-6.4h1l3.3,4.2v-4.2H18v6.4h-1L13.7,138.8z"/>
|
||||
<path class="st3" d="M24.8,142.9v-4.2l-1.6,3.2h-0.7l-1.6-3.2v4.2h-1.2v-6.4H21l1.9,3.6l1.9-3.6H26v6.4H24.8z"/>
|
||||
<path class="st3" d="M30.3,141.9c0.3,0,0.5-0.1,0.7-0.2s0.4-0.3,0.5-0.5s0.2-0.4,0.3-0.7s0.1-0.5,0.1-0.8v-3.3h1.2v3.3
|
||||
c0,0.4-0.1,0.8-0.2,1.2c-0.1,0.4-0.3,0.7-0.5,1s-0.5,0.5-0.9,0.7s-0.8,0.3-1.3,0.3s-0.9-0.1-1.3-0.3s-0.6-0.4-0.9-0.7
|
||||
c-0.2-0.3-0.4-0.6-0.5-1s-0.1-0.8-0.1-1.2v-3.3h1.2v3.3c0,0.3,0,0.5,0.1,0.8c0.1,0.2,0.1,0.5,0.3,0.7c0.1,0.2,0.3,0.3,0.5,0.5
|
||||
S30,141.9,30.3,141.9z"/>
|
||||
<path class="st3" d="M39.3,137.6h-2v5.3H36v-5.3h-2v-1.1h5.3V137.6z"/>
|
||||
<path class="st3" d="M44.8,141.8v1.1h-4.4v-6.4h4.4v1.1h-3.1v1.5h2.7v1h-2.7v1.7L44.8,141.8L44.8,141.8z"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
|
Before Width: | Height: | Size: 8.4 KiB After Width: | Height: | Size: 8 KiB |
|
@ -1009,7 +1009,10 @@ function MyController(hand) {
|
|||
|
||||
this.secondaryPress = function(value) {
|
||||
_this.rawSecondaryValue = value;
|
||||
if (value > 0) {
|
||||
|
||||
// The value to check if we will allow the release function to be called
|
||||
var allowReleaseValue = 0.1;
|
||||
if (value > 0 && _this.state == STATE_HOLD) {
|
||||
_this.release();
|
||||
}
|
||||
};
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
var inTeleportMode = false;
|
||||
|
||||
var SMOOTH_ARRIVAL_SPACING = 33;
|
||||
var NUMBER_OF_STEPS_FOR_TELEPORT = 6;
|
||||
var NUMBER_OF_STEPS = 6;
|
||||
|
||||
var TARGET_MODEL_URL = Script.resolvePath("../assets/models/teleport-destination.fbx");
|
||||
var TOO_CLOSE_MODEL_URL = Script.resolvePath("../assets/models/teleport-cancel.fbx");
|
||||
|
@ -44,7 +44,6 @@ var COLORS_TELEPORT_TOO_CLOSE = {
|
|||
var TELEPORT_CANCEL_RANGE = 1;
|
||||
var USE_COOL_IN = true;
|
||||
var COOL_IN_DURATION = 500;
|
||||
var MAX_HMD_AVATAR_SEPARATION = 10.0;
|
||||
|
||||
function ThumbPad(hand) {
|
||||
this.hand = hand;
|
||||
|
@ -89,7 +88,6 @@ function Teleporter() {
|
|||
this.updateConnected = null;
|
||||
this.smoothArrivalInterval = null;
|
||||
this.teleportHand = null;
|
||||
this.teleportMode = "HMDAndAvatarTogether";
|
||||
this.tooClose = false;
|
||||
this.inCoolIn = false;
|
||||
|
||||
|
@ -545,68 +543,43 @@ function Teleporter() {
|
|||
var offset = getAvatarFootOffset();
|
||||
this.intersection.intersection.y += offset;
|
||||
this.exitTeleportMode();
|
||||
if (MyAvatar.hmdLeanRecenterEnabled) {
|
||||
if (Vec3.distance(MyAvatar.position, _this.intersection.intersection) > MAX_HMD_AVATAR_SEPARATION) {
|
||||
this.teleportMode = "HMDAndAvatarTogether";
|
||||
} else {
|
||||
this.teleportMode = "HMDFirstAvatarWillFollow";
|
||||
}
|
||||
} else {
|
||||
this.teleportMode = "AvatarOnly";
|
||||
}
|
||||
|
||||
// Disable smooth arrival, possibly temporarily
|
||||
//this.smoothArrival();
|
||||
// Instead jump to the intersection directly.
|
||||
var landingPoint = _this.intersection.intersection;
|
||||
_this.teleportTo(landingPoint);
|
||||
if (this.teleportMode === "HMDFirstAvatarWillFollow") {
|
||||
MyAvatar.position = landingPoint;
|
||||
}
|
||||
|
||||
// cleanup UI
|
||||
MyAvatar.position = _this.intersection.intersection;
|
||||
_this.hideTargetOverlay();
|
||||
_this.hideCancelOverlay();
|
||||
HMD.centerUI();
|
||||
}
|
||||
};
|
||||
|
||||
this.getWayPoints = function(startPoint, endPoint, numberOfSteps) {
|
||||
var travel = Vec3.subtract(endPoint, startPoint);
|
||||
var distance = Vec3.length(travel);
|
||||
var wayPoints = [];
|
||||
if (distance > 1.0) {
|
||||
var base = Math.exp(Math.log(distance + 1.0) / numberOfSteps);
|
||||
var i;
|
||||
|
||||
// this fancy math generates regular points in logarithmic space
|
||||
for (i = 0; i < numberOfSteps - 1; i++) {
|
||||
var backFraction = (1.0 - Math.exp((numberOfSteps - 1 - i) * Math.log(base))) / distance;
|
||||
wayPoints.push(Vec3.sum(endPoint, Vec3.multiply(backFraction, travel)));
|
||||
}
|
||||
}
|
||||
wayPoints.push(endPoint);
|
||||
return wayPoints;
|
||||
this.findMidpoint = function(start, end) {
|
||||
var xy = Vec3.sum(start, end);
|
||||
var midpoint = Vec3.multiply(0.5, xy);
|
||||
return midpoint
|
||||
};
|
||||
|
||||
this.teleportTo = function(landingPoint) {
|
||||
if (this.teleportMode === "AvatarOnly") {
|
||||
MyAvatar.position = landingPoint;
|
||||
} else if (this.teleportMode === "HMDAndAvatarTogether") {
|
||||
var leanEnabled = MyAvatar.hmdLeanRecenterEnabled;
|
||||
MyAvatar.setHMDLeanRecenterEnabled(false);
|
||||
MyAvatar.position = landingPoint;
|
||||
HMD.snapToAvatar();
|
||||
MyAvatar.hmdLeanRecenterEnabled = leanEnabled;
|
||||
} else if (this.teleportMode === "HMDFirstAvatarWillFollow") {
|
||||
var eyeOffset = Vec3.subtract(MyAvatar.getEyePosition(), MyAvatar.position);
|
||||
landingPoint = Vec3.sum(landingPoint, eyeOffset);
|
||||
HMD.position = landingPoint;
|
||||
this.getArrivalPoints = function(startPoint, endPoint) {
|
||||
var arrivalPoints = [];
|
||||
var i;
|
||||
var lastPoint;
|
||||
|
||||
for (i = 0; i < NUMBER_OF_STEPS; i++) {
|
||||
if (i === 0) {
|
||||
lastPoint = startPoint;
|
||||
}
|
||||
var newPoint = _this.findMidpoint(lastPoint, endPoint);
|
||||
lastPoint = newPoint;
|
||||
arrivalPoints.push(newPoint);
|
||||
}
|
||||
}
|
||||
|
||||
arrivalPoints.push(endPoint);
|
||||
|
||||
return arrivalPoints;
|
||||
};
|
||||
|
||||
this.smoothArrival = function() {
|
||||
_this.arrivalPoints = _this.getWayPoints(MyAvatar.position, _this.intersection.intersection, NUMBER_OF_STEPS_FOR_TELEPORT);
|
||||
|
||||
_this.arrivalPoints = _this.getArrivalPoints(MyAvatar.position, _this.intersection.intersection);
|
||||
_this.smoothArrivalInterval = Script.setInterval(function() {
|
||||
if (_this.arrivalPoints.length === 0) {
|
||||
Script.clearInterval(_this.smoothArrivalInterval);
|
||||
|
@ -614,7 +587,7 @@ function Teleporter() {
|
|||
return;
|
||||
}
|
||||
var landingPoint = _this.arrivalPoints.shift();
|
||||
_this.teleportTo(landingPoint);
|
||||
MyAvatar.position = landingPoint;
|
||||
|
||||
if (_this.arrivalPoints.length === 1 || _this.arrivalPoints.length === 0) {
|
||||
_this.hideTargetOverlay();
|
||||
|
@ -626,6 +599,7 @@ function Teleporter() {
|
|||
|
||||
this.createTargetOverlay(false);
|
||||
this.createCancelOverlay(false);
|
||||
|
||||
}
|
||||
|
||||
//related to repositioning the avatar after you teleport
|
||||
|
|
|
@ -1086,7 +1086,7 @@ private:
|
|||
QSize _size;
|
||||
QSettings _settings;
|
||||
|
||||
std::atomic<size_t> _renderCount;
|
||||
std::atomic<size_t> _renderCount{ 0 };
|
||||
gl::OffscreenContext _initContext;
|
||||
RenderThread _renderThread;
|
||||
QWindowCamera _camera;
|
||||
|
@ -1152,3 +1152,4 @@ int main(int argc, char** argv) {
|
|||
}
|
||||
|
||||
#include "main.moc"
|
||||
|
||||
|
|
|
@ -15,15 +15,12 @@ exports.handlers = {
|
|||
// directories to scan for jsdoc comments
|
||||
var dirList = [
|
||||
'../../interface/src',
|
||||
'../../interface/src/avatar',
|
||||
'../../interface/src/scripting',
|
||||
'../../interface/src/ui/overlays',
|
||||
'../../libraries/animation/src',
|
||||
'../../libraries/avatars/src',
|
||||
'../../libraries/controllers/src/controllers/',
|
||||
'../../libraries/entities/src',
|
||||
'../../libraries/script-engine/src',
|
||||
'../../libraries/networking/src',
|
||||
'../../libraries/script-engine/src'
|
||||
'../../libraries/animation/src',
|
||||
'../../libraries/entities/src',
|
||||
];
|
||||
var exts = ['.h', '.cpp'];
|
||||
|
||||
|
|
Loading…
Reference in a new issue