Camera collision fixes
This commit is contained in:
@@ -334,6 +334,17 @@ system that should drive player locomotion animation.
|
||||
(`orientation * UNIT_Z`) so the camera starts behind the character whatever
|
||||
its rotation; an identity-rotation character keeps the old fixed yaw-180
|
||||
behaviour.
|
||||
- The TPS camera boom is collision-clamped: every frame
|
||||
`updateTPSCamera` raycasts (`JoltPhysicsWrapper::raycastQuery`, which only
|
||||
sees `Layers::NON_MOVING`) from the pivot towards the desired camera
|
||||
position and clamps the boom at the first wall/floor/terrain hit minus a
|
||||
small margin. Pull-in is instant, recovery towards the full `tpsDistance`
|
||||
is eased (`CAMERA_RECOVER_SPEED`), and the clamped length is kept in
|
||||
`ControllerState::tpsCurrentDistance`. Below `CAMERA_HIDE_DISTANCE` the
|
||||
character geometry is hidden via
|
||||
`CharacterSlotSystem::setCharacterVisible` (with hysteresis) so the camera
|
||||
never renders the inside of the body; `shutdownController` restores
|
||||
visibility because the character can survive scene switches.
|
||||
|
||||
### CharacterSpawnerSystem
|
||||
|
||||
|
||||
@@ -814,3 +814,14 @@ void CharacterSlotSystem::setSlotVisible(flecs::entity e,
|
||||
ent->setVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterSlotSystem::setCharacterVisible(flecs::entity e, bool visible)
|
||||
{
|
||||
auto it = m_entities.find(e.id());
|
||||
if (it == m_entities.end())
|
||||
return;
|
||||
for (auto &pair : it->second.parts) {
|
||||
if (pair.second)
|
||||
pair.second->setVisible(visible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,6 +91,11 @@ public:
|
||||
void setSlotVisible(flecs::entity e, const Ogre::String &slot,
|
||||
bool visible);
|
||||
|
||||
/* Show/hide all part entities of a character at once (used when
|
||||
* the TPS camera is pushed so close that it would end up inside
|
||||
* the character geometry). */
|
||||
void setCharacterVisible(flecs::entity e, bool visible);
|
||||
|
||||
private:
|
||||
static CharacterSlotSystem *ms_singleton;
|
||||
static bool s_catalogLoaded;
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "CharacterSlotSystem.hpp"
|
||||
#include "CharacterSpawnerSystem.hpp"
|
||||
#include "../camera/EditorCamera.hpp"
|
||||
#include "../physics/physics.h"
|
||||
#include <OgreEntity.h>
|
||||
#include <OgreCamera.h>
|
||||
#include <OgreLogManager.h>
|
||||
@@ -19,6 +20,20 @@
|
||||
#include <OgreSkeletonInstance.h>
|
||||
#include <cmath>
|
||||
|
||||
/* TPS camera collision tuning: the boom is clamped by a physics
|
||||
* raycast from the pivot to the desired camera position; the hit is
|
||||
* pulled back by CAMERA_COLLISION_MARGIN so the near plane stays out
|
||||
* of the wall, pull-in is instant while recovery back to the full
|
||||
* tpsDistance is eased at CAMERA_RECOVER_SPEED units/s to avoid
|
||||
* popping. Below CAMERA_HIDE_DISTANCE the character geometry is
|
||||
* hidden (with hysteresis) so the camera never renders the inside of
|
||||
* the body. */
|
||||
static constexpr float CAMERA_COLLISION_MARGIN = 0.25f;
|
||||
static constexpr float CAMERA_MIN_DISTANCE = 0.2f;
|
||||
static constexpr float CAMERA_HIDE_DISTANCE = 1.15f;
|
||||
static constexpr float CAMERA_HIDE_HYSTERESIS = 0.15f;
|
||||
static constexpr float CAMERA_RECOVER_SPEED = 5.0f;
|
||||
|
||||
#ifdef EDITSCENE_VERBOSE_DEBUG
|
||||
#define EDITSCENE_LOG_PC(msg) \
|
||||
Ogre::LogManager::getSingleton().logMessage(msg)
|
||||
@@ -66,6 +81,19 @@ void PlayerControllerSystem::shutdownController(ControllerState &state)
|
||||
{
|
||||
if (state.targetEntity.is_alive())
|
||||
state.targetEntity.remove<PlayerControlledComponent>();
|
||||
|
||||
/* Restore character geometry hidden by the TPS camera
|
||||
* proximity handling; the character may survive the controller
|
||||
* shutdown (game-mode scene switches carry it over). */
|
||||
if (state.geometryHidden && state.targetEntity.is_alive()) {
|
||||
CharacterSlotSystem *css = m_editorApp ?
|
||||
m_editorApp->getCharacterSlotSystem() :
|
||||
nullptr;
|
||||
if (css)
|
||||
css->setCharacterVisible(state.targetEntity, true);
|
||||
}
|
||||
state.geometryHidden = false;
|
||||
state.tpsCurrentDistance = -1.0f;
|
||||
state.targetEntity = flecs::entity::null();
|
||||
|
||||
if (state.pivotNode) {
|
||||
@@ -281,12 +309,66 @@ void PlayerControllerSystem::updateTPSCamera(PlayerControllerComponent &pc,
|
||||
Ogre::Quaternion yawRot(Ogre::Degree(state.yaw), Ogre::Vector3::UNIT_Y);
|
||||
Ogre::Quaternion pitchRot(Ogre::Degree(state.pitch),
|
||||
Ogre::Vector3::UNIT_X);
|
||||
Ogre::Vector3 offset =
|
||||
yawRot * pitchRot * Ogre::Vector3(0, 0, pc.tpsDistance);
|
||||
Ogre::Vector3 goalPos = pivotPos + offset;
|
||||
Ogre::Vector3 boomDir =
|
||||
yawRot * pitchRot * Ogre::Vector3(0, 0, 1.0f);
|
||||
|
||||
/* Camera collision: raycast from the pivot towards the desired
|
||||
* camera position and clamp the boom at the first static
|
||||
* obstacle. raycastQuery only sees Layers::NON_MOVING (walls,
|
||||
* floor, terrain), so the character itself never blocks the
|
||||
* camera. Pull-in is instant; recovery towards the full
|
||||
* tpsDistance is eased to avoid popping. */
|
||||
if (state.tpsCurrentDistance < 0.0f)
|
||||
state.tpsCurrentDistance = pc.tpsDistance;
|
||||
|
||||
float allowedDist = pc.tpsDistance;
|
||||
JoltPhysicsWrapper *physics = JoltPhysicsWrapper::getSingletonPtr();
|
||||
if (physics) {
|
||||
Ogre::Vector3 hitPos;
|
||||
JPH::BodyID hitBody;
|
||||
Ogre::Vector3 goalPos =
|
||||
pivotPos + boomDir * pc.tpsDistance;
|
||||
if (physics->raycastQuery(pivotPos, goalPos, hitPos,
|
||||
hitBody)) {
|
||||
float hitDist = (hitPos - pivotPos).length() -
|
||||
CAMERA_COLLISION_MARGIN;
|
||||
if (hitDist < allowedDist)
|
||||
allowedDist = hitDist;
|
||||
}
|
||||
}
|
||||
if (allowedDist < state.tpsCurrentDistance)
|
||||
state.tpsCurrentDistance = allowedDist;
|
||||
else if (state.tpsCurrentDistance < allowedDist) {
|
||||
state.tpsCurrentDistance += CAMERA_RECOVER_SPEED * deltaTime;
|
||||
if (state.tpsCurrentDistance > allowedDist)
|
||||
state.tpsCurrentDistance = allowedDist;
|
||||
}
|
||||
if (state.tpsCurrentDistance < CAMERA_MIN_DISTANCE)
|
||||
state.tpsCurrentDistance = CAMERA_MIN_DISTANCE;
|
||||
|
||||
Ogre::Vector3 goalPos =
|
||||
pivotPos + boomDir * state.tpsCurrentDistance;
|
||||
|
||||
state.goalNode->setPosition(goalPos);
|
||||
|
||||
/* Hide the character geometry when the clamped boom is so
|
||||
* short that the camera would end up inside the body; restore
|
||||
* it (with a bit of hysteresis) once there is room again. */
|
||||
CharacterSlotSystem *css = m_editorApp->getCharacterSlotSystem();
|
||||
if (css) {
|
||||
if (!state.geometryHidden &&
|
||||
state.tpsCurrentDistance < CAMERA_HIDE_DISTANCE) {
|
||||
css->setCharacterVisible(state.targetEntity, false);
|
||||
state.geometryHidden = true;
|
||||
} else if (state.geometryHidden &&
|
||||
state.tpsCurrentDistance >
|
||||
CAMERA_HIDE_DISTANCE +
|
||||
CAMERA_HIDE_HYSTERESIS) {
|
||||
css->setCharacterVisible(state.targetEntity, true);
|
||||
state.geometryHidden = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Snap camera directly to goal to avoid jitter
|
||||
// caused by lerp smoothing with varying dt.
|
||||
camNode->setPosition(goalPos);
|
||||
|
||||
@@ -68,6 +68,13 @@ private:
|
||||
Ogre::SceneNode *goalNode = nullptr;
|
||||
bool initialized = false;
|
||||
bool faceHidden = false;
|
||||
/* Current TPS boom length after collision clamping;
|
||||
* negative until the first updateTPSCamera initializes it
|
||||
* to PlayerControllerComponent::tpsDistance. */
|
||||
float tpsCurrentDistance = -1.0f;
|
||||
/* True while the character geometry is hidden because the
|
||||
* clamped TPS camera sits inside/too close to the body. */
|
||||
bool geometryHidden = false;
|
||||
};
|
||||
|
||||
void initController(flecs::entity controllerEntity,
|
||||
|
||||
Reference in New Issue
Block a user