Merge pull request #16144 from luiscuenca/addLookAtThirdCamera

DEV-391: Keyboard Input Controls Camera Not Avatar Orientation
This commit is contained in:
Shannon Romano 2019-09-20 13:03:03 -07:00 committed by GitHub
commit bd488f0196
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 4894 additions and 4343 deletions

File diff suppressed because it is too large Load diff

View file

@ -3,8 +3,8 @@
"channels": [
{ "from": "Keyboard.A", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.D", "when": ["Keyboard.RightMouseButton", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.E", "when": "!Keyboard.Control", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Q", "when": "!Keyboard.Control", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.E", "when": ["!Application.CameraSelfie", "!Application.CameraLookAt", "!Keyboard.Control"], "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Q", "when": ["!Application.CameraSelfie"," !Application.CameraLookAt", "!Keyboard.Control"], "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.T", "when": "!Keyboard.Control", "to": "Actions.TogglePushToTalk" },
{ "comment" : "Mouse turn need to be small continuous increments",
@ -39,7 +39,6 @@
]
},
{ "from": { "makeAxis" : [
["Keyboard.Left" ],
["Keyboard.Right"]
@ -85,6 +84,24 @@
"when": ["Application.CameraThirdPerson", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
},
"when": ["Application.CameraLookAt", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.Left"],
["Keyboard.Right"]
]
},
"when": ["Application.CameraSelfie", "!Keyboard.Shift"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.A"],
@ -104,6 +121,24 @@
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.Q"],
["Keyboard.E"]
]
},
"when": ["Application.CameraLookAt", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.E"],
["Keyboard.Q"]
]
},
"when": ["Application.CameraSelfie", "!Keyboard.Control"],
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
@ -122,6 +157,24 @@
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraLookAt",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : [
["Keyboard.TouchpadLeft"],
["Keyboard.TouchpadRight"]
]
},
"when": "Application.CameraSelfie",
"to": "Actions.Yaw"
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveLeft", "Keyboard.MouseMoveRight"] },
"when": "Keyboard.RightMouseButton",
"to": "Actions.DeltaYaw",
@ -132,7 +185,7 @@
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] },
"when": "Keyboard.RightMouseButton",
"when": ["!Application.CameraSelfie", "!Application.CameraLookAt", "Keyboard.RightMouseButton"],
"to": "Actions.DeltaPitch",
"filters":
[
@ -140,16 +193,44 @@
]
},
{ "from": "Keyboard.W", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "when": "!Keyboard.Control", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": { "makeAxis" : ["Keyboard.MouseMoveUp", "Keyboard.MouseMoveDown"] },
"when": ["Application.CameraLookAt", "Keyboard.RightMouseButton"],
"to": "Actions.DeltaPitch",
"filters":
[
{ "type": "scale", "scale": 0.3 }
]
},
{ "from": { "makeAxis" : ["Keyboard.MouseMoveDown", "Keyboard.MouseMoveUp"] },
"when": ["Application.CameraSelfie", "Keyboard.RightMouseButton"],
"to": "Actions.DeltaPitch",
"filters":
[
{ "type": "scale", "scale": 0.3 }
]
},
{ "from": "Keyboard.W", "when": ["!Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.S", "when": ["!Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.S", "when": ["Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.W", "when": ["Application.CameraSelfie", "!Keyboard.Control"], "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.A", "when": "Application.CameraLookAt", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.D", "when": "Application.CameraLookAt", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.A", "when": "Application.CameraSelfie", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.D", "when": "Application.CameraSelfie", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Shift", "when": ["!Keyboard.Left", "!Keyboard.Right"], "to": "Actions.SPRINT" },
{ "from": "Keyboard.C", "when": "!Keyboard.Control", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.Left", "when": "Keyboard.Shift", "to": "Actions.LATERAL_LEFT" },
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
{ "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.Up", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.Up", "when": "Application.CameraLookAt", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.Up", "when": "Application.CameraSelfie", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Down", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Down", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Down", "when": "Application.CameraLookAt", "to": "Actions.LONGITUDINAL_BACKWARD" },
{ "from": "Keyboard.Down", "when": "Application.CameraSelfie", "to": "Actions.LONGITUDINAL_FORWARD" },
{ "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
{ "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },

View file

@ -709,6 +709,8 @@ static const QString STATE_CAMERA_FIRST_PERSON = "CameraFirstPerson";
static const QString STATE_CAMERA_THIRD_PERSON = "CameraThirdPerson";
static const QString STATE_CAMERA_ENTITY = "CameraEntity";
static const QString STATE_CAMERA_INDEPENDENT = "CameraIndependent";
static const QString STATE_CAMERA_LOOK_AT = "CameraLookAt";
static const QString STATE_CAMERA_SELFIE = "CameraSelfie";
static const QString STATE_SNAP_TURN = "SnapTurn";
static const QString STATE_ADVANCED_MOVEMENT_CONTROLS = "AdvancedMovement";
static const QString STATE_GROUNDED = "Grounded";
@ -924,7 +926,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<AudioInjectorManager>();
DependencyManager::set<MessagesClient>();
controller::StateController::setStateVariables({ { STATE_IN_HMD, STATE_CAMERA_FULL_SCREEN_MIRROR,
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT,
STATE_CAMERA_FIRST_PERSON, STATE_CAMERA_THIRD_PERSON, STATE_CAMERA_ENTITY, STATE_CAMERA_INDEPENDENT, STATE_CAMERA_LOOK_AT, STATE_CAMERA_SELFIE,
STATE_SNAP_TURN, STATE_ADVANCED_MOVEMENT_CONTROLS, STATE_GROUNDED, STATE_NAV_FOCUSED,
STATE_PLATFORM_WINDOWS, STATE_PLATFORM_MAC, STATE_PLATFORM_ANDROID, STATE_LEFT_HAND_DOMINANT, STATE_RIGHT_HAND_DOMINANT, STATE_STRAFE_ENABLED } });
DependencyManager::set<UserInputMapper>();
@ -1874,6 +1876,12 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_applicationStateDevice->setInputVariant(STATE_CAMERA_THIRD_PERSON, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_THIRD_PERSON ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_CAMERA_LOOK_AT, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_LOOK_AT ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_CAMERA_SELFIE, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_SELFIE ? 1 : 0;
});
_applicationStateDevice->setInputVariant(STATE_CAMERA_ENTITY, []() -> float {
return qApp->getCamera().getMode() == CAMERA_MODE_ENTITY ? 1 : 0;
});
@ -3586,7 +3594,8 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
// Always use the default eye position, not the actual head eye position.
// Using the latter will cause the camera to wobble with idle animations,
// or with changes from the face tracker
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON) {
CameraMode mode = _myCamera.getMode();
if (mode == CAMERA_MODE_FIRST_PERSON) {
_thirdPersonHMDCameraBoomValid= false;
if (isHMDMode()) {
mat4 camMat = myAvatar->getSensorToWorldMatrix() * myAvatar->getHMDSensorMatrix();
@ -3597,10 +3606,8 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
_myCamera.setPosition(myAvatar->getDefaultEyePosition());
_myCamera.setOrientation(myAvatar->getMyHead()->getHeadOrientation());
}
}
else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON) {
} else if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
if (isHMDMode()) {
if (!_thirdPersonHMDCameraBoomValid) {
const glm::vec3 CAMERA_OFFSET = glm::vec3(0.0f, 0.0f, 0.7f);
_thirdPersonHMDCameraBoom = cancelOutRollAndPitch(myAvatar->getHMDSensorOrientation()) * CAMERA_OFFSET;
@ -3615,22 +3622,28 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
_myCamera.setOrientation(glm::normalize(glmExtractRotation(worldCameraMat)));
_myCamera.setPosition(extractTranslation(worldCameraMat));
}
else {
} else {
_thirdPersonHMDCameraBoomValid = false;
_myCamera.setOrientation(myAvatar->getHead()->getOrientation());
if (isOptionChecked(MenuOption::CenterPlayerInView)) {
if (mode == CAMERA_MODE_THIRD_PERSON) {
_myCamera.setOrientation(myAvatar->getHead()->getOrientation());
if (isOptionChecked(MenuOption::CenterPlayerInView)) {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ _myCamera.getOrientation() * boomOffset);
} else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ myAvatar->getWorldOrientation() * boomOffset);
}
} else {
glm::quat lookAtRotation = myAvatar->getLookAtRotation();
if (mode == CAMERA_MODE_SELFIE) {
lookAtRotation = lookAtRotation * glm::angleAxis(PI, myAvatar->getWorldOrientation() * Vectors::UP);
}
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ _myCamera.getOrientation() * boomOffset);
}
else {
_myCamera.setPosition(myAvatar->getDefaultEyePosition()
+ myAvatar->getWorldOrientation() * boomOffset);
+ lookAtRotation * boomOffset);
_myCamera.lookAt(myAvatar->getDefaultEyePosition());
}
}
}
else if (_myCamera.getMode() == CAMERA_MODE_MIRROR) {
} else if (mode == CAMERA_MODE_MIRROR) {
_thirdPersonHMDCameraBoomValid= false;
if (isHMDMode()) {
@ -3668,8 +3681,7 @@ void Application::updateCamera(RenderArgs& renderArgs, float deltaTime) {
glm::vec3(0.0f, 0.0f, -1.0f) * myAvatar->getBoomLength() * _scaleMirror);
}
renderArgs._renderMode = RenderArgs::MIRROR_RENDER_MODE;
}
else if (_myCamera.getMode() == CAMERA_MODE_ENTITY) {
} else if (mode == CAMERA_MODE_ENTITY) {
_thirdPersonHMDCameraBoomValid= false;
EntityItemPointer cameraEntity = _myCamera.getCameraEntityPointer();
if (cameraEntity != nullptr) {
@ -4387,12 +4399,12 @@ void Application::keyPressEvent(QKeyEvent* event) {
}
case Qt::Key_2: {
Menu* menu = Menu::getInstance();
menu->triggerOption(MenuOption::FullscreenMirror);
menu->triggerOption(MenuOption::SelfieCamera);
break;
}
case Qt::Key_3: {
Menu* menu = Menu::getInstance();
menu->triggerOption(MenuOption::ThirdPerson);
menu->triggerOption(MenuOption::LookAtCamera);
break;
}
case Qt::Key_4:
@ -5484,7 +5496,7 @@ void Application::loadSettings() {
// dictated that we should be in first person
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, isFirstPerson);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, !isFirstPerson);
_myCamera.setMode((isFirstPerson) ? CAMERA_MODE_FIRST_PERSON : CAMERA_MODE_THIRD_PERSON);
_myCamera.setMode((isFirstPerson) ? CAMERA_MODE_FIRST_PERSON : CAMERA_MODE_LOOK_AT);
cameraMenuChanged();
auto inputs = pluginManager->getInputPlugins();
@ -5842,11 +5854,16 @@ void Application::cycleCamera() {
} else if (menu->isOptionChecked(MenuOption::FirstPerson)) {
menu->setIsOptionChecked(MenuOption::FirstPerson, false);
menu->setIsOptionChecked(MenuOption::ThirdPerson, true);
menu->setIsOptionChecked(MenuOption::LookAtCamera, true);
} else if (menu->isOptionChecked(MenuOption::ThirdPerson)) {
} else if (menu->isOptionChecked(MenuOption::LookAtCamera)) {
menu->setIsOptionChecked(MenuOption::ThirdPerson, false);
menu->setIsOptionChecked(MenuOption::LookAtCamera, false);
menu->setIsOptionChecked(MenuOption::SelfieCamera, true);
} else if (menu->isOptionChecked(MenuOption::SelfieCamera)) {
menu->setIsOptionChecked(MenuOption::SelfieCamera, false);
menu->setIsOptionChecked(MenuOption::FullscreenMirror, true);
}
@ -5858,11 +5875,11 @@ void Application::cameraModeChanged() {
case CAMERA_MODE_FIRST_PERSON:
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true);
break;
case CAMERA_MODE_THIRD_PERSON:
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
case CAMERA_MODE_LOOK_AT:
Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, true);
break;
case CAMERA_MODE_MIRROR:
Menu::getInstance()->setIsOptionChecked(MenuOption::FullscreenMirror, true);
case CAMERA_MODE_SELFIE:
Menu::getInstance()->setIsOptionChecked(MenuOption::SelfieCamera, true);
break;
default:
// we don't have menu items for the others, so just leave it alone.
@ -5878,32 +5895,32 @@ void Application::changeViewAsNeeded(float boomLength) {
if (_myCamera.getMode() == CAMERA_MODE_FIRST_PERSON && boomLengthGreaterThanMinimum) {
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, false);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, true);
Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, true);
cameraMenuChanged();
} else if (_myCamera.getMode() == CAMERA_MODE_THIRD_PERSON && !boomLengthGreaterThanMinimum) {
} else if (_myCamera.getMode() == CAMERA_MODE_LOOK_AT && !boomLengthGreaterThanMinimum) {
Menu::getInstance()->setIsOptionChecked(MenuOption::FirstPerson, true);
Menu::getInstance()->setIsOptionChecked(MenuOption::ThirdPerson, false);
Menu::getInstance()->setIsOptionChecked(MenuOption::LookAtCamera, false);
cameraMenuChanged();
}
}
void Application::cameraMenuChanged() {
auto menu = Menu::getInstance();
if (menu->isOptionChecked(MenuOption::FullscreenMirror)) {
if (!isHMDMode() && _myCamera.getMode() != CAMERA_MODE_MIRROR) {
_mirrorYawOffset = 0.0f;
_myCamera.setMode(CAMERA_MODE_MIRROR);
getMyAvatar()->reset(false, false, false); // to reset any active MyAvatar::FollowHelpers
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
}
} else if (menu->isOptionChecked(MenuOption::FirstPerson)) {
if (menu->isOptionChecked(MenuOption::FirstPerson)) {
if (_myCamera.getMode() != CAMERA_MODE_FIRST_PERSON) {
_myCamera.setMode(CAMERA_MODE_FIRST_PERSON);
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_MIN);
}
} else if (menu->isOptionChecked(MenuOption::ThirdPerson)) {
if (_myCamera.getMode() != CAMERA_MODE_THIRD_PERSON) {
_myCamera.setMode(CAMERA_MODE_THIRD_PERSON);
} else if (menu->isOptionChecked(MenuOption::LookAtCamera)) {
if (_myCamera.getMode() != CAMERA_MODE_LOOK_AT) {
_myCamera.setMode(CAMERA_MODE_LOOK_AT);
if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) {
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
}
}
} else if (menu->isOptionChecked(MenuOption::SelfieCamera)) {
if (_myCamera.getMode() != CAMERA_MODE_SELFIE) {
_myCamera.setMode(CAMERA_MODE_SELFIE);
if (getMyAvatar()->getBoomLength() == MyAvatar::ZOOM_MIN) {
getMyAvatar()->setBoomLength(MyAvatar::ZOOM_DEFAULT);
}
@ -8993,9 +9010,9 @@ void Application::setDisplayPlugin(DisplayPluginPointer newDisplayPlugin) {
cameraMenuChanged();
}
// Remove the mirror camera option from menu if in HMD mode
auto mirrorAction = menu->getActionForOption(MenuOption::FullscreenMirror);
mirrorAction->setVisible(!isHmd);
// Remove the selfie camera options from menu if in HMD mode
auto selfieAction = menu->getActionForOption(MenuOption::SelfieCamera);
selfieAction->setVisible(!isHmd);
}
Q_ASSERT_X(_displayPlugin, "Application::updateDisplayMode", "could not find an activated display plugin");

View file

@ -177,19 +177,19 @@ Menu::Menu() {
firstPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Third Person
auto thirdPersonAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::ThirdPerson, 0,
// View > Look At
auto lookAtAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::LookAtCamera, 0,
false, qApp, SLOT(cameraMenuChanged())));
thirdPersonAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
lookAtAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
// View > Mirror
auto viewMirrorAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::FullscreenMirror, 0,
false, qApp, SLOT(cameraMenuChanged())));
// View > Selfie
auto selfieAction = cameraModeGroup->addAction(addCheckableActionToQMenuAndActionHash(
viewMenu, MenuOption::SelfieCamera, 0,
false, qApp, SLOT(cameraMenuChanged())));
viewMirrorAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
selfieAction->setProperty(EXCLUSION_GROUP_KEY, QVariant::fromValue(cameraModeGroup));
viewMenu->addSeparator();

View file

@ -129,6 +129,7 @@ namespace MenuOption {
const QString Login = "Login/Sign Up";
const QString Log = "Log";
const QString LogExtraTimings = "Log Extra Timing Details";
const QString LookAtCamera = "Third Person";
const QString LowVelocityFilter = "Low Velocity Filter";
const QString MeshVisible = "Draw Mesh";
const QString MuteEnvironment = "Mute Environment";
@ -181,6 +182,7 @@ namespace MenuOption {
const QString RunTimingTests = "Run Timing Tests";
const QString ScriptedMotorControl = "Enable Scripted Motor Control";
const QString ShowTrackedObjects = "Show Tracked Objects";
const QString SelfieCamera = "Selfie";
const QString SendWrongDSConnectVersion = "Send wrong DS connect version";
const QString SendWrongProtocolVersion = "Send wrong protocol version";
const QString SetHomeLocation = "Set Home Location";
@ -201,7 +203,7 @@ namespace MenuOption {
const QString AnimStats = "Show Animation Stats";
const QString StopAllScripts = "Stop All Scripts";
const QString SuppressShortTimings = "Suppress Timings Less than 10ms";
const QString ThirdPerson = "Third Person";
const QString ThirdPerson = "Third Person Legacy";
const QString ThreePointCalibration = "3 Point Calibration";
const QString ThrottleFPSIfNotFocus = "Throttle FPS If Not Focus"; // FIXME - this value duplicated in Basic2DWindowOpenGLDisplayPlugin.cpp
const QString ToggleHipsFollowing = "Toggle Hips Following";

View file

@ -99,6 +99,10 @@ static const QString USER_RECENTER_MODEL_FORCE_STAND = QStringLiteral("ForceStan
static const QString USER_RECENTER_MODEL_AUTO = QStringLiteral("Auto");
static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("DisableHMDLean");
const QString HEAD_BLENDING_NAME = "lookAroundAlpha";
const QString HEAD_ALPHA_NAME = "additiveBlendAlpha";
const float HEAD_ALPHA_BLENDING = 1.0f;
MyAvatar::SitStandModelType stringToUserRecenterModel(const QString& str) {
if (str == USER_RECENTER_MODEL_FORCE_SIT) {
return MyAvatar::ForceSit;
@ -451,7 +455,7 @@ QByteArray MyAvatar::toByteArrayStateful(AvatarDataDetail dataDetail, bool dropF
_globalBoundingBoxDimensions.y = _characterController.getCapsuleHalfHeight();
_globalBoundingBoxDimensions.z = _characterController.getCapsuleRadius();
_globalBoundingBoxOffset = _characterController.getCapsuleLocalOffset();
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
// fake the avatar position that is sent up to the AvatarMixer
glm::vec3 oldPosition = getWorldPosition();
setWorldPosition(getSkeletonPosition());
@ -948,6 +952,13 @@ void MyAvatar::simulate(float deltaTime, bool inView) {
head->setPosition(headPosition);
head->setScale(getModelScale());
head->simulate(deltaTime);
CameraMode mode = qApp->getCamera().getMode();
if (_scriptControlsHeadLookAt || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
updateHeadLookAt(deltaTime);
} else if (_headLookAtActive){
resetHeadLookAt();
_headLookAtActive = false;
}
}
// Record avatars movements.
@ -2610,7 +2621,7 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
glm::vec3 MyAvatar::getSkeletonPosition() const {
CameraMode mode = qApp->getCamera().getMode();
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT) {
if (mode == CAMERA_MODE_THIRD_PERSON || mode == CAMERA_MODE_INDEPENDENT || mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) {
// The avatar is rotated PI about the yAxis, so we have to correct for it
// to get the skeleton offset contribution in the world-frame.
const glm::quat FLIP = glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
@ -3133,7 +3144,6 @@ void MyAvatar::initAnimGraph() {
}
emit animGraphUrlChanged(graphUrl);
_skeletonModel->getRig().initAnimGraph(graphUrl);
_currentAnimGraphUrl.set(graphUrl);
connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SLOT(animGraphLoaded()));
@ -3416,11 +3426,19 @@ void MyAvatar::setRotationThreshold(float angleRadians) {
}
void MyAvatar::updateOrientation(float deltaTime) {
// Smoothly rotate body with arrow keys
float targetSpeed = getDriveKey(YAW) * _yawSpeed;
CameraMode mode = qApp->getCamera().getMode();
bool computeLookAt = (mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE) && isReadyForPhysics() && !qApp->isHMDMode();
if (computeLookAt) {
// For "Look At" and "Selfie" camera modes we also smooth the yaw rotation from right-click mouse movement.
float speedFromDeltaYaw = deltaTime > FLT_EPSILON ? getDriveKey(DELTA_YAW) / deltaTime : 0.0f;
speedFromDeltaYaw *= _yawSpeed / YAW_SPEED_DEFAULT;
targetSpeed += speedFromDeltaYaw;
}
if (targetSpeed != 0.0f) {
const float ROTATION_RAMP_TIMESCALE = 0.1f;
const float ROTATION_RAMP_TIMESCALE = 0.5f;
float blend = deltaTime / ROTATION_RAMP_TIMESCALE;
if (blend > 1.0f) {
blend = 1.0f;
@ -3441,10 +3459,10 @@ void MyAvatar::updateOrientation(float deltaTime) {
}
}
float totalBodyYaw = _bodyYawDelta * deltaTime;
// Rotate directly proportional to delta yaw and delta pitch from right-click mouse movement.
totalBodyYaw += getDriveKey(DELTA_YAW) * _yawSpeed / YAW_SPEED_DEFAULT;
if (!computeLookAt) {
// Rotate directly proportional to delta yaw and delta pitch from right-click mouse movement.
totalBodyYaw += getDriveKey(DELTA_YAW) * _yawSpeed / YAW_SPEED_DEFAULT;
}
// Comfort Mode: If you press any of the left/right rotation drive keys or input, you'll
// get an instantaneous 15 degree turn. If you keep holding the key down you'll get another
// snap turn every half second.
@ -3453,7 +3471,6 @@ void MyAvatar::updateOrientation(float deltaTime) {
totalBodyYaw += getDriveKey(STEP_YAW);
snapTurn = true;
}
// Use head/HMD roll to turn while flying, but not when standing still.
if (qApp->isHMDMode() && getCharacterController()->getState() == CharacterController::State::Hover && _hmdRollControlEnabled && hasDriveInput()) {
@ -3488,7 +3505,60 @@ void MyAvatar::updateOrientation(float deltaTime) {
// update body orientation by movement inputs
glm::quat initialOrientation = getOrientationOutbound();
setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
glm::vec3 eyesPosition = getDefaultEyePosition();
const float FPS = 60.0f;
float timeScale = deltaTime * FPS;
bool faceForward = false;
if (!computeLookAt) {
setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f))));
_lookAtCameraTarget = eyesPosition + getWorldOrientation() * Vectors::FRONT;
_lookAtYaw = getWorldOrientation();
_lookAtPitch = Quaternions::IDENTITY;
} else {
// Compute new look at vectors
if (totalBodyYaw != 0.0f) {
_lookAtYaw = _lookAtYaw * glm::quat(glm::radians(glm::vec3(0.0f, totalBodyYaw, 0.0f)));
}
float pitchIncrement = getDriveKey(PITCH) * _pitchSpeed * deltaTime
+ getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT;
if (pitchIncrement != 0.0f) {
glm::quat _previousLookAtPitch = _lookAtPitch;
_lookAtPitch = _lookAtPitch * glm::quat(glm::radians(glm::vec3(pitchIncrement, 0.0f, 0.0f)));
// Limit the camera horizontal pitch
float MAX_LOOK_AT_PITCH_DEGREES = 80.0f;
float pitchFromHorizont = glm::degrees(angleBetween(getLookAtRotation() * Vectors::FRONT, _lookAtYaw * Vectors::FRONT));
if (pitchFromHorizont > MAX_LOOK_AT_PITCH_DEGREES) {
_lookAtPitch = _previousLookAtPitch;
}
}
bool isMovingFwdBwd = getDriveKey(TRANSLATE_Z) != 0.0f;
bool isMovingSideways = getDriveKey(TRANSLATE_X) != 0.0f;
bool isRotatingWhileSeated = isMovingSideways && _characterController.getSeated();
faceForward = isMovingFwdBwd || (isMovingSideways && !isRotatingWhileSeated);
// Blend the avatar orientation with the camera look at if moving forward.
if (faceForward || _shouldTurnToFaceCamera) {
const float REORIENT_FORWARD_BLEND = 0.25f;
const float REORIENT_TURN_BLEND = 0.03f;
const float DIAGONAL_TURN_BLEND = 0.02f;
float blend = (_shouldTurnToFaceCamera ? REORIENT_TURN_BLEND : REORIENT_FORWARD_BLEND) * timeScale;
if (blend > 1.0f) {
blend = 1.0f;
}
glm::quat faceRotation = _lookAtYaw;
if (isMovingFwdBwd && isMovingSideways) {
// Reorient avatar to face camera diagonal
blend = DIAGONAL_TURN_BLEND;
float turnSign = getDriveKey(TRANSLATE_Z) < 0.0f ? -1.0f : 1.0f;
turnSign = getDriveKey(TRANSLATE_X) > 0.0f ? -turnSign : turnSign;
faceRotation = _lookAtYaw * glm::angleAxis(turnSign * 0.25f * PI, Vectors::UP);
}
setWorldOrientation(glm::slerp(getWorldOrientation(), faceRotation, blend));
} else if (isRotatingWhileSeated) {
float rotatingWhileSeatedYaw = -getDriveKey(TRANSLATE_X) * _yawSpeed * deltaTime;
setWorldOrientation(getWorldOrientation() * glm::quat(glm::radians(glm::vec3(0.0f, rotatingWhileSeatedYaw, 0.0f))));
}
}
if (snapTurn) {
// Whether or not there is an existing smoothing going on, just reset the smoothing timer and set the starting position as the avatar's current position, then smooth to the new position.
@ -3509,9 +3579,74 @@ void MyAvatar::updateOrientation(float deltaTime) {
head->setBaseYaw(YAW(euler));
head->setBasePitch(PITCH(euler));
head->setBaseRoll(ROLL(euler));
} else if (computeLookAt) {
// Reset head orientation before applying the blending offset
head->setBaseYaw(0.0f);
head->setBasePitch(0.0f);
head->setBaseRoll(0.0f);
glm::vec3 cameraVector = (faceForward ? _lookAtPitch * getWorldOrientation() : getLookAtRotation()) * Vectors::FRONT;
glm::vec3 cameraYawVector = _lookAtYaw * Vectors::FRONT;
// Cap and attenuate head's lookat pitch angle
const float START_LOOKING_UP_DEGREES = 5.0f;
const float START_LOOKING_DOWN_DEGREES = 15.0f;
const float MAX_UP_DOWN_DEGREES = 90.0f;
glm::vec3 avatarVectorUp = getWorldOrientation() * Vectors::UP;
float upDownDot = glm::dot(cameraVector, avatarVectorUp);
float upDownDegrees = MAX_UP_DOWN_DEGREES - glm::degrees(acosf(abs(upDownDot)));
float lookAttenuation = 0.0f;
if (upDownDot <= 0.0f) {
if (upDownDegrees > START_LOOKING_DOWN_DEGREES) {
lookAttenuation = (upDownDegrees - START_LOOKING_DOWN_DEGREES) / (MAX_UP_DOWN_DEGREES - START_LOOKING_DOWN_DEGREES);
}
} else {
if (upDownDegrees > START_LOOKING_UP_DEGREES) {
lookAttenuation = (upDownDegrees - START_LOOKING_UP_DEGREES) / (MAX_UP_DOWN_DEGREES - START_LOOKING_UP_DEGREES);
}
}
glm::vec3 avatarVectorFront = getWorldOrientation() * Vectors::FRONT;
float frontBackDot = glm::dot(cameraYawVector, avatarVectorFront);
glm::vec3 avatarVectorRight = getWorldOrientation() * Vectors::RIGHT;
float leftRightDot = glm::dot(cameraYawVector, avatarVectorRight);
const float REORIENT_ANGLE = 65.0f;
const float TRIGGER_REORIENT_ANGLE = 45.0f;
glm::vec3 ajustedYawVector = cameraYawVector;
if (frontBackDot < 0.0f) {
ajustedYawVector = (leftRightDot < 0.0f ? -avatarVectorRight : avatarVectorRight);
cameraVector = (ajustedYawVector * _lookAtPitch) * Vectors::FRONT;
if (frontBackDot < -glm::sin(glm::radians(TRIGGER_REORIENT_ANGLE))) {
_shouldTurnToFaceCamera = true;
}
} else if (frontBackDot > glm::sin(glm::radians(REORIENT_ANGLE))) {
_shouldTurnToFaceCamera = false;
}
cameraVector = glm::mix(cameraVector, ajustedYawVector, 1.0f - lookAttenuation);
// Calculate the camera target point.
glm::vec3 targetPoint = eyesPosition + glm::normalize(cameraVector);
const float LOOKAT_MIX_ALPHA = 0.25f;
if (getDriveKey(TRANSLATE_Y) == 0.0f) {
// Approximate the head's look at vector to the camera look at vector with some delay.
float mixAlpha = LOOKAT_MIX_ALPHA * timeScale;
if (mixAlpha > 1.0f) {
mixAlpha = 1.0f;
}
_lookAtCameraTarget = glm::mix(_lookAtCameraTarget, targetPoint, mixAlpha);
} else {
_lookAtCameraTarget = targetPoint;
}
_headLookAtActive = true;
} else {
head->setBaseYaw(0.0f);
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime
head->setBasePitch(getHead()->getBasePitch() + getDriveKey(PITCH) * _pitchSpeed * deltaTime
+ getDriveKey(DELTA_PITCH) * _pitchSpeed / PITCH_SPEED_DEFAULT);
head->setBaseRoll(0.0f);
}
@ -3543,7 +3678,7 @@ float MyAvatar::calculateGearedSpeed(const float driveKey) {
glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 right) {
float stickFullOn = 0.85f;
auto zSpeed = getDriveKey(TRANSLATE_Z);
auto xSpeed = getDriveKey(TRANSLATE_X);
auto xSpeed = !_characterController.getSeated() ? getDriveKey(TRANSLATE_X) : 0.0f;
glm::vec3 direction;
if (!useAdvancedMovementControls() && qApp->isHMDMode()) {
// Walking disabled in settings.
@ -3581,6 +3716,10 @@ glm::vec3 MyAvatar::scaleMotorSpeed(const glm::vec3 forward, const glm::vec3 rig
} else {
// Desktop mode.
direction = (zSpeed * forward) + (xSpeed * right);
if (qApp->getCamera().getMode() == CAMERA_MODE_LOOK_AT && zSpeed != 0.0f && xSpeed != 0.0f){
direction = (zSpeed * forward);
}
auto length = glm::length(direction);
if (length > EPSILON) {
direction /= length;
@ -5236,9 +5375,13 @@ glm::quat MyAvatar::getOrientationForAudio() {
glm::quat result;
switch (_audioListenerMode) {
case AudioListenerMode::FROM_HEAD:
result = getHead()->getFinalOrientationInWorldFrame();
case AudioListenerMode::FROM_HEAD: {
// Using the camera's orientation instead, when the current mode is controlling the avatar's head.
CameraMode mode = qApp->getCamera().getMode();
bool headFollowsCamera = mode == CAMERA_MODE_LOOK_AT || mode == CAMERA_MODE_SELFIE;
result = headFollowsCamera ? qApp->getCamera().getOrientation() : getHead()->getFinalOrientationInWorldFrame();
break;
}
case AudioListenerMode::FROM_CAMERA:
result = qApp->getCamera().getOrientation();
break;
@ -6334,7 +6477,6 @@ void MyAvatar::sendPacket(const QUuid& entityID) const {
void MyAvatar::setSitDriveKeysStatus(bool enabled) {
const std::vector<DriveKeys> DISABLED_DRIVE_KEYS_DURING_SIT = {
DriveKeys::TRANSLATE_X,
DriveKeys::TRANSLATE_Y,
DriveKeys::TRANSLATE_Z,
DriveKeys::STEP_TRANSLATE_X,
@ -6512,3 +6654,70 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera)
getHead()->setLookAtPosition(lookAtSpot);
}
void MyAvatar::resetHeadLookAt() {
if (_skeletonModelLoaded) {
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLENDING_NAME, glm::vec3(),
HEAD_ALPHA_NAME, HEAD_ALPHA_BLENDING);
}
}
void MyAvatar::updateHeadLookAt(float deltaTime) {
if (_skeletonModelLoaded) {
glm::vec3 lookAtTarget = _scriptControlsHeadLookAt ? _lookAtScriptTarget : _lookAtCameraTarget;
glm::vec3 avatarXVector = glm::normalize(getWorldOrientation() * Vectors::UNIT_X);
glm::vec3 avatarYVector = glm::normalize(getWorldOrientation() * Vectors::UNIT_Y);
glm::vec3 avatarZVector = glm::normalize(getWorldOrientation() * Vectors::UNIT_Z);
glm::vec3 headToTargetVector = lookAtTarget - getDefaultEyePosition();
if (glm::length(headToTargetVector) > EPSILON) {
headToTargetVector = glm::normalize(headToTargetVector);
} else {
// The target point is the avatar head
return;
}
float xDot = glm::dot(avatarXVector, headToTargetVector);
float yDot = glm::dot(avatarYVector, headToTargetVector);
float zDot = glm::dot(avatarZVector, headToTargetVector);
// Force the head to look at one of the sides when the look at point is behind the avatar
if (zDot > 0.0f && xDot != 0.0f) {
//xDot /= fabsf(xDot);
}
// Make sure dot products are in range to avoid acosf returning NaN
xDot = glm::min(glm::max(xDot, -1.0f), 1.0f);
yDot = glm::min(glm::max(yDot, -1.0f), 1.0f);
float xAngle = acosf(xDot);
float yAngle = acosf(yDot);
// xBlend and yBlend are the values from -1.0 to 1.0 that set the directional blending.
// We compute them using the angles (0 to PI/2) => (1.0 to 0.0) and (PI/2 to PI) => (0.0 to -1.0)
float xBlend = -(xAngle - 0.5f * PI) / (0.5f * PI);
float yBlend = -(yAngle - 0.5f * PI) / (0.5f * PI);
glm::vec3 lookAtBlend = glm::vec3(xBlend, yBlend, 0.0f);
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLENDING_NAME, lookAtBlend,
HEAD_ALPHA_NAME, HEAD_ALPHA_BLENDING);
if (_scriptControlsHeadLookAt) {
_scriptHeadControlTimer += deltaTime;
if (_scriptHeadControlTimer > MAX_LOOK_AT_TIME_SCRIPT_CONTROL) {
_scriptHeadControlTimer = 0.0f;
_scriptControlsHeadLookAt = false;
_lookAtCameraTarget = _lookAtScriptTarget;
}
}
}
}
void MyAvatar::setHeadLookAt(const glm::vec3& lookAtTarget) {
if (QThread::currentThread() != thread()) {
BLOCKING_INVOKE_METHOD(this, "setHeadLookAt",
Q_ARG(const glm::vec3&, lookAtTarget));
return;
}
_headLookAtActive = true;
_scriptControlsHeadLookAt = true;
_scriptHeadControlTimer = 0.0f;
_lookAtScriptTarget = lookAtTarget;
}

View file

@ -1749,6 +1749,24 @@ public:
glm::vec3 getNextPosition() { return _goToPending ? _goToPosition : getWorldPosition(); }
void prepareAvatarEntityDataForReload();
/**jsdoc
* Turn the avatar's head until it faces the target point within the 90/-90 degree range.
* Once this method is called, API calls will have full control of the head for a limited time.
* If this method is not called for two seconds, the engine will regain control of the head.
* @function MyAvatar.setHeadLookAt
* @param {Vec3} lookAtTarget - The target point in world coordinates.
*/
Q_INVOKABLE void setHeadLookAt(const glm::vec3& lookAtTarget);
/**jsdoc
* Returns the current head look at target point in world coordinates.
* @function MyAvatar.getHeadLookAt
* @returns {Vec3} Default position between your avatar's eyes in world coordinates.
*/
Q_INVOKABLE glm::vec3 getHeadLookAt() { return _lookAtCameraTarget; }
glm::quat getLookAtRotation() { return _lookAtYaw * _lookAtPitch; }
/**jsdoc
* Creates a new grab that grabs an entity.
* @function MyAvatar.grab
@ -2626,6 +2644,21 @@ private:
glm::vec3 _trackedHeadPosition;
const float MAX_LOOK_AT_TIME_SCRIPT_CONTROL = 2.0f;
glm::quat _lookAtPitch;
glm::quat _lookAtYaw;
glm::vec3 _lookAtCameraTarget;
glm::vec3 _lookAtScriptTarget;
bool _headLookAtActive { false };
bool _shouldTurnToFaceCamera { false };
bool _scriptControlsHeadLookAt { false };
float _scriptHeadControlTimer { 0.0f };
// LookAt camera data
float _selfieTriggerAngle { 55.0f };
float _frontLookAtSpeed { 0.15f };
float _backLookAtSpeed { 0.25f };
Setting::Handle<float> _realWorldFieldOfView;
Setting::Handle<bool> _useAdvancedMovementControls;
Setting::Handle<bool> _showPlayArea;
@ -2650,6 +2683,8 @@ private:
void initHeadBones();
void initAnimGraph();
void initFlowFromFST();
void updateHeadLookAt(float deltaTime);
void resetHeadLookAt();
// Avatar Preferences
QUrl _fullAvatarURLFromPreferences;

View file

@ -1658,7 +1658,7 @@ void Rig::updateHead(bool headEnabled, bool hipsEnabled, const AnimPose& headPos
_animVars.set("splineIKEnabled", false);
_animVars.unset("headPosition");
_animVars.set("headRotation", headPose.rot());
_animVars.set("headType", (int)IKTarget::Type::RotationOnly);
_animVars.set("headType", (int)IKTarget::Type::Unknown);
}
}
}
@ -2645,3 +2645,8 @@ float Rig::getUnscaledEyeHeight() const {
return DEFAULT_AVATAR_EYE_HEIGHT;
}
}
void Rig::setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha) {
_animVars.set(targetName, blendingTarget);
_animVars.set(alphaName, alpha);
}

View file

@ -254,6 +254,7 @@ public:
int getOverrideJointCount() const;
bool getFlowActive() const;
bool getNetworkGraphActive() const;
void setDirectionalBlending(const QString& targetName, const glm::vec3& blendingTarget, const QString& alphaName, float alpha);
signals:
void onLoadComplete();

View file

@ -35,6 +35,18 @@
* your avatar.</td>
* </tr>
* <tr>
* <td><strong>Look&nbsp;At</strong></td>
* <td><code>"look&nbsp;at"</code></td>
* <td>The camera is positioned behind your avatar. The camera moves and rotates independently from your avatar.
* The avatar's head always faces the camera look at point.</td>
* </tr>
* <tr>
* <td><strong>Selfie</strong></td>
* <td><code>"selfie"</code></td>
* <td>The camera is positioned in front of your avatar. The camera moves and rotates independently from your avatar.
* Your avatar's head is always facing the camera.</td>
* </tr>
* <tr>
* <td><strong>Mirror</strong></td>
* <td><code>"mirror"</code></td>
* <td>The camera is positioned such that you are looking directly at your avatar. The camera moves and rotates with your
@ -67,6 +79,10 @@ CameraMode stringToMode(const QString& mode) {
return CAMERA_MODE_INDEPENDENT;
} else if (mode == "entity") {
return CAMERA_MODE_ENTITY;
} else if (mode == "look at") {
return CAMERA_MODE_LOOK_AT;
} else if (mode == "selfie") {
return CAMERA_MODE_SELFIE;
}
return CAMERA_MODE_NULL;
}
@ -82,6 +98,10 @@ QString modeToString(CameraMode mode) {
return "independent";
} else if (mode == CAMERA_MODE_ENTITY) {
return "entity";
} else if (mode == CAMERA_MODE_LOOK_AT) {
return "look at";
} else if (mode == CAMERA_MODE_SELFIE) {
return "selfie";
}
return "unknown";
}

View file

@ -23,6 +23,8 @@ enum CameraMode
CAMERA_MODE_MIRROR,
CAMERA_MODE_INDEPENDENT,
CAMERA_MODE_ENTITY,
CAMERA_MODE_LOOK_AT,
CAMERA_MODE_SELFIE,
NUM_CAMERA_MODES
};
@ -182,7 +184,7 @@ private:
void recompose();
void decompose();
CameraMode _mode{ CAMERA_MODE_THIRD_PERSON };
CameraMode _mode{ CAMERA_MODE_LOOK_AT };
glm::mat4 _transform;
glm::mat4 _projection;