Switch to Vec3fp and fp variables for character

This commit is contained in:
Vladislav Khorev committed 2026-11-13 18:56:44 +06:00
1 parent 8df016c0a4
commit 889c6d465b
23 files changed
+384 -406

No files matched your search

+114 -84
View File
@@ -9,7 +9,13 @@ namespace {
// Tick counts are authoritative and therefore independent of render rate.
constexpr float ATTACK_COOLDOWN_TIME = 1.6f;
constexpr float HOME_DRIFT_CHECK_INTERVAL = 5.0f;
fp HOME_DRIFT_CHECK_INTERVAL_FP = static_cast<fp>(HOME_DRIFT_CHECK_INTERVAL);
constexpr float TARGET_REPLAN_THRESHOLD = 0.25f;
fp TARGET_REPLAN_THRESHOLD_SQUARED = static_cast<fp>(TARGET_REPLAN_THRESHOLD * TARGET_REPLAN_THRESHOLD);
constexpr float PI = 3.14159265358979323846f;
constexpr int BLOCKED_REPLAN_TICKS = 3;
@@ -25,8 +31,8 @@ std::uint32_t stableIdHash(const std::string& value)
}
void CharacterState::configureAsPlayer() {
walkSpeed = 3.0f;
rotationSpeed = 8.0f;
walkSpeed = static_cast<fp>(3.0f);
rotationSpeed = static_cast < fp>(8.0f);
modelScale = 1.f;
modelCorrectionRotation = Eigen::Quaternionf(
Eigen::AngleAxisf(PI, Eigen::Vector3f::UnitY()) *
@@ -66,12 +72,12 @@ void CharacterState::scheduleNextNavigationRetry()
}
bool CharacterState::isMoving() const {
Eigen::Vector3f toTarget = walkTarget - position;
toTarget.y() = 0.f;
return !pathWaypoints.empty() || toTarget.norm() > kWalkThreshold;
Vec3fp toTarget = walkTarget - position;
toTarget.y = static_cast<fp>(0.f);
return !pathWaypoints.empty() || toTarget.normSquared() > kWalkThreshold_SQUARED();
}
Eigen::Vector3f CharacterState::getCurrentNavigationTarget() const {
Vec3fp CharacterState::getCurrentNavigationTarget() const {
if (!pathWaypoints.empty() && currentWaypointIndex < pathWaypoints.size()) {
return pathWaypoints[currentWaypointIndex];
}
@@ -79,7 +85,7 @@ Eigen::Vector3f CharacterState::getCurrentNavigationTarget() const {
}
void CharacterState::stopInPlace() {
walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z());
walkTarget = Vec3fp(position.x, static_cast<fp>(0.f), position.z);
requestedWalkTarget = walkTarget;
pathWaypoints.clear();
currentWaypointIndex = 0;
@@ -90,20 +96,20 @@ void CharacterState::stopInPlace() {
}
void CharacterState::setTarget(
const Eigen::Vector3f& target,
const Vec3fp& target,
std::string callbackName,
const LocationNetworkState& location,
const std::string& entityId)
{
const Eigen::Vector3f normalizedTarget(target.x(), 0.f, target.z());
const Vec3fp normalizedTarget(target.x, static_cast<fp>(0.f), target.z);
const bool sameRequestedTarget =
(normalizedTarget - requestedWalkTarget).norm() <= TARGET_REPLAN_THRESHOLD;
(normalizedTarget - requestedWalkTarget).normSquared() <= TARGET_REPLAN_THRESHOLD_SQUARED;
const bool alreadyMovingToTarget =
!pathWaypoints.empty() ||
(walkTarget - normalizedTarget).norm() <= TARGET_REPLAN_THRESHOLD;
(walkTarget - normalizedTarget).normSquared() <= TARGET_REPLAN_THRESHOLD_SQUARED;
const bool stoppedAfterFailedPath =
pathWaypoints.empty() &&
(walkTarget - position).norm() <= TARGET_REPLAN_THRESHOLD;
(walkTarget - position).normSquared() <= TARGET_REPLAN_THRESHOLD_SQUARED;
if (callbackName.empty() && sameRequestedTarget &&
(alreadyMovingToTarget || stoppedAfterFailedPath)) {
return;
@@ -119,12 +125,15 @@ void CharacterState::setTarget(
currentWaypointIndex = 0;
if (!pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f;
for (Vec3fp& waypoint : pathWaypoints)
{
waypoint.y = static_cast<fp>(0.f);
}
walkTarget = pathWaypoints.back();
return;
}
walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z());
walkTarget = Vec3fp(position.x, static_cast<fp>(0.f), position.z);
if (!isPlayer) scheduleNextNavigationRetry();
}
@@ -132,8 +141,8 @@ void CharacterState::forceReplan(
const LocationNetworkState& location,
const std::string& entityId)
{
const Eigen::Vector3f normalizedTarget(
requestedWalkTarget.x(), 0.f, requestedWalkTarget.z());
const Vec3fp normalizedTarget(
requestedWalkTarget.x, static_cast<fp>(0.f), requestedWalkTarget.z);
pathWaypoints = location.navigationMaps[location.activeNavigationIndex].findPathToNearest(this->position, normalizedTarget);
@@ -141,13 +150,16 @@ void CharacterState::forceReplan(
movementBlockedTicks = 0;
navigationRetryTicks = 0;
if (!pathWaypoints.empty()) {
for (Eigen::Vector3f& waypoint : pathWaypoints) waypoint.y() = 0.f;
for (Vec3fp& waypoint : pathWaypoints)
{
waypoint.y = static_cast<fp>(0.f);
}
walkTarget = pathWaypoints.back();
blockingCharacterId.clear();
return;
}
walkTarget = Eigen::Vector3f(position.x(), 0.f, position.z());
walkTarget = Vec3fp(position.x, static_cast<fp>(0.f), position.z);
if (!isPlayer) scheduleNextNavigationRetry();
}
@@ -209,16 +221,16 @@ CharacterUpdateEvents CharacterState::update(
attack = 0;
attackTargetId.clear();
} else {
const float distance = (attackTarget->position - position).norm();
if (distance >= visibleRange) {
if ((attackTarget->position - requestedWalkTarget).norm() >
TARGET_REPLAN_THRESHOLD) {
const fp distanceSquared = (attackTarget->position - position).normSquared();
if (distanceSquared >= visibleRange* visibleRange) {
if ((attackTarget->position - requestedWalkTarget).normSquared() >
TARGET_REPLAN_THRESHOLD_SQUARED) {
events.navigationRequested = true;
events.navigationTarget = attackTarget->position;
}
battle_state = 0;
} else if (distance >= 1.25f) {
if ((attackTarget->position - requestedWalkTarget).norm() > TARGET_REPLAN_THRESHOLD) {
} else if (distanceSquared >= static_cast<fp>(1.25f*1.25f)) {
if ((attackTarget->position - requestedWalkTarget).normSquared() > TARGET_REPLAN_THRESHOLD_SQUARED) {
events.navigationRequested = true;
events.navigationTarget = attackTarget->position;
}
@@ -228,21 +240,22 @@ CharacterUpdateEvents CharacterState::update(
}
}
const Eigen::Vector3f activeTarget = getCurrentNavigationTarget();
const Eigen::Vector3f lookTarget =
const Vec3fp activeTarget = getCurrentNavigationTarget();
const Vec3fp lookTarget =
battle_state == 1 && attackTarget ? attackTarget->position : activeTarget;
if (currentState == AnimationState::WALK || currentState == AnimationState::STAND) {
Eigen::Vector3f toTarget = activeTarget - position;
toTarget.y() = 0.f;
const float distance = toTarget.norm();
if (distance > kWalkThreshold) {
const Eigen::Vector3f direction = toTarget / distance;
const float moveAmount = walkSpeed * static_cast<float>(deltaMs) / 1000.f;
Eigen::Vector3f candidate =
Vec3fp toTarget = activeTarget - position;
toTarget.y = static_cast<fp>(0.f);
const fp distanceSquared = toTarget.normSquared();
if (distanceSquared > kWalkThreshold_SQUARED()) {
fp distance = sqrt(distanceSquared); //FP SQRT
const Vec3fp direction = toTarget / distance;
const fp moveAmount = walkSpeed * static_cast<fp>(deltaMs) / static_cast < fp>(1000.f);
Vec3fp candidate =
moveAmount >= distance ? activeTarget : position + direction * moveAmount;
candidate.y() = 0.f;
const CharacterMovementObstacle* blocker = nullptr;
candidate.y = static_cast<fp>(0.f);
/*const CharacterMovementObstacle* blocker = nullptr;
if (context.movementObstacles) {
for (const CharacterMovementObstacle& obstacle :
*context.movementObstacles) {
@@ -296,15 +309,15 @@ CharacterUpdateEvents CharacterState::update(
navigationPausedThisTick = true;
}
}
} else {
} else*/{
movedLastTick =
(candidate - position).squaredNorm() > 1e-8f;
(candidate - position).normSquared() > static_cast<fp>(1e-8f);
position = candidate;
movementBlockedTicks = 0;
blockingCharacterId.clear();
}
targetFacingAngle = std::atan2(direction.x(), -direction.z());
if (!blocker) currentState = AnimationState::WALK;
targetFacingAngle = fpm::atan2(direction.x, -direction.z);
currentState = AnimationState::WALK;
} else {
currentState = AnimationState::STAND;
const bool completedPath = !pathWaypoints.empty();
@@ -316,8 +329,8 @@ CharacterUpdateEvents CharacterState::update(
pathWaypoints.clear();
currentWaypointIndex = 0;
if (completedPath &&
(requestedWalkTarget - position).norm() >
TARGET_REPLAN_THRESHOLD &&
(requestedWalkTarget - position).normSquared() >
TARGET_REPLAN_THRESHOLD_SQUARED &&
!isPlayer)
scheduleNextNavigationRetry();
if (completedPath && !onArrivedCallbackName.empty()) {
@@ -327,11 +340,11 @@ CharacterUpdateEvents CharacterState::update(
}
if (faceTarget) {
Eigen::Vector3f toFace = faceTarget->position - position;
toFace.y() = 0.f;
const float distanceToFace = toFace.norm();
targetFacingAngle = distanceToFace > 1e-3f
? std::atan2(toFace.x() / distanceToFace, -toFace.z() / distanceToFace)
Vec3fp toFace = faceTarget->position - position;
toFace.y = static_cast<fp>(0.f);
const fp distanceToFace = sqrt(toFace.normSquared()); //FP SQRT
targetFacingAngle = distanceToFace > static_cast<fp>(1e-3f)
? fpm::atan2(toFace.x / distanceToFace, -toFace.z / distanceToFace)
: facingAngle;
} else if (!faceTargetId.empty()) {
faceTargetId.clear();
@@ -340,7 +353,7 @@ CharacterUpdateEvents CharacterState::update(
}
if (!isPlayer && !navigationPausedThisTick && pathWaypoints.empty() &&
(requestedWalkTarget - position).norm() > TARGET_REPLAN_THRESHOLD &&
(requestedWalkTarget - position).normSquared() > TARGET_REPLAN_THRESHOLD_SQUARED &&
!events.navigationRequested) {
if (navigationRetryTicks > 0) --navigationRetryTicks;
if (navigationRetryTicks == 0) {
@@ -359,8 +372,8 @@ CharacterUpdateEvents CharacterState::update(
attackTargetId.clear();
}
} else if (battle_state == 1 && attackTarget) {
targetFacingAngle = std::atan2(
lookTarget.x() - position.x(), -(lookTarget.z() - position.z()));
targetFacingAngle = fpm::atan2(
lookTarget.x - position.x, -(lookTarget.z - position.z));
if (currentState == AnimationState::STAND || currentState == AnimationState::WALK) {
currentState = AnimationState::STAND_TO_ACTION;
resetAnim = true;
@@ -384,24 +397,25 @@ CharacterUpdateEvents CharacterState::update(
resetAnim = true;
}
float angleDiff = targetFacingAngle - facingAngle;
while (angleDiff > PI) angleDiff -= 2.f * PI;
while (angleDiff < -PI) angleDiff += 2.f * PI;
const float rotationStep = rotationSpeed * static_cast<float>(deltaMs) / 1000.f;
if (std::fabs(angleDiff) <= rotationStep) {
fp angleDiff = targetFacingAngle - facingAngle;
const fp PIfp = static_cast<fp>(PI);
while (angleDiff > PIfp) angleDiff -= static_cast<fp>(2.f) * PIfp;
while (angleDiff < -PIfp) angleDiff += static_cast<fp>(2.f) * PIfp;
const fp rotationStep = rotationSpeed * static_cast<fp>(deltaMs) / static_cast<fp>(1000.f);
if (fpm::abs(angleDiff) <= rotationStep) {
facingAngle = targetFacingAngle;
faceTargetId.clear();
} else {
facingAngle += angleDiff > 0.f ? rotationStep : -rotationStep;
facingAngle += angleDiff > static_cast<fp>(0.f) ? rotationStep : -rotationStep;
}
if (!isPlayer && hp > 0.f && battle_state == 0 && maxHomeDrift > 0.f) {
homeDriftCheckTimer -= static_cast<float>(deltaMs) / 1000.f;
if (homeDriftCheckTimer <= 0.f) {
homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL;
Eigen::Vector3f toHome = homePosition - position;
toHome.y() = 0.f;
if (!isMoving() && toHome.norm() > maxHomeDrift) {
if (!isPlayer && hp > 0.f && battle_state == 0 && maxHomeDriftSquared > static_cast<fp>(0.f)) {
homeDriftCheckTimer -= static_cast<fp>(deltaMs) / static_cast<fp>(1000.f);
if (homeDriftCheckTimer <= static_cast <fp>(0.f)) {
homeDriftCheckTimer = HOME_DRIFT_CHECK_INTERVAL_FP;
Vec3fp toHome = homePosition - position;
toHome.y = static_cast<fp>(0.f);
if (!isMoving() && toHome.normSquared() > maxHomeDriftSquared) {
events.navigationRequested = true;
events.navigationTarget = homePosition;
}
@@ -424,10 +438,18 @@ CharacterUpdateEvents CharacterState::update(
events.attackImpact = true;
events.attackTargetId = attackTargetId;
events.attackDirection = attackTarget->position - position;
events.attackDirection.y() = 0.f;
const float length = events.attackDirection.norm();
if (length > 1e-3f) events.attackDirection /= length;
else events.attackDirection = Eigen::Vector3f::Zero();
events.attackDirection.y = static_cast<fp>(0.f);
const fp lengthSquared = fpm::sqrt(events.attackDirection.normSquared());
if (lengthSquared > static_cast<fp>(1e-3f * 1e-3f))
{
const fp length = fpm::sqrt(lengthSquared); //FP SQRT
events.attackDirection = events.attackDirection / length;
}
else
{
events.attackDirection = Vec3fp{};
}
}
showWeapon =
@@ -463,9 +485,9 @@ CharacterUpdateEvents CharacterState::update(
void CharacterState::save(nlohmann::json& out) const
{
out["position"] = { position.x(), position.y(), position.z() };
out["facingAngle"] = facingAngle;
out["targetFacingAngle"] = targetFacingAngle;
out["position"] = { static_cast<double>(position.x), static_cast<double>(position.y), static_cast<double>(position.z) };
out["facingAngle"] = static_cast<double>(facingAngle);
out["targetFacingAngle"] = static_cast<double>(targetFacingAngle);
out["hp"] = hp;
out["enabled"] = enabled;
out["battle_state"] = battle_state;
@@ -474,16 +496,16 @@ void CharacterState::save(nlohmann::json& out) const
out["showWeapon"] = showWeapon;
out["attackTargetId"] = attackTargetId;
out["faceTargetId"] = faceTargetId;
out["homePosition"] = { homePosition.x(), homePosition.y(), homePosition.z() };
out["walkTarget"] = { walkTarget.x(), walkTarget.y(), walkTarget.z() };
out["requestedWalkTarget"] = { requestedWalkTarget.x(), requestedWalkTarget.y(), requestedWalkTarget.z() };
out["homePosition"] = { static_cast<double>(homePosition.x), static_cast<double>(homePosition.y), static_cast<double>(homePosition.z) };
out["walkTarget"] = { static_cast<double>(walkTarget.x), static_cast<double>(walkTarget.y), static_cast<double>(walkTarget.z) };
out["requestedWalkTarget"] = { static_cast<double>(requestedWalkTarget.x), static_cast<double>(requestedWalkTarget.y), static_cast<double>(requestedWalkTarget.z) };
nlohmann::json wpArr = nlohmann::json::array();
for (const auto& wp : pathWaypoints) {
wpArr.push_back({ wp.x(), wp.y(), wp.z() });
wpArr.push_back({ static_cast<double>(wp.x), static_cast<double>(wp.y), static_cast<double>(wp.z) });
}
out["pathWaypoints"] = std::move(wpArr);
out["currentWaypointIndex"] = currentWaypointIndex;
out["homeDriftCheckTimer"] = homeDriftCheckTimer;
out["homeDriftCheckTimer"] = static_cast<double>(homeDriftCheckTimer);
out["movementBlockedTicks"] = movementBlockedTicks;
out["navigationRetryTicks"] = navigationRetryTicks;
out["movedLastTick"] = movedLastTick;
@@ -492,7 +514,7 @@ void CharacterState::save(nlohmann::json& out) const
if (!blockingCharacterId.empty())
out["blockingCharacterId"] = blockingCharacterId;
out["animationFrame"] = animationFrame;
out["visibleRange"] = visibleRange;
out["visibleRange"] = static_cast<double>(visibleRange);
if (!onArrivedCallbackName.empty())
out["onArrivedCallbackName"] = onArrivedCallbackName;
}
@@ -505,11 +527,19 @@ void CharacterState::load(const nlohmann::json& in)
return def;
};
if (in.contains("position")) position = readVec3(in["position"], position);
facingAngle = in.value("facingAngle", facingAngle);
targetFacingAngle = in.value("targetFacingAngle", targetFacingAngle);
auto readVec3fp = [](const nlohmann::json& j, Vec3fp def) -> Vec3fp {
if (j.is_array() && j.size() == 3)
return Vec3fp{ static_cast<fp>(j[0].get<double>()), static_cast<fp>(j[1].get<double>()), static_cast<fp>(j[2].get<double>()) };
return def;
};
if (in.contains("position")) {
position = readVec3fp(in["position"], position);
}
facingAngle = static_cast<fp>(in.value("facingAngle", static_cast<double>(facingAngle)));
targetFacingAngle = static_cast<fp>(in.value("targetFacingAngle", static_cast<double>(targetFacingAngle)));
hp = in.value("hp", hp);
visibleRange = in.value("visibleRange", visibleRange);
visibleRange = static_cast<fp>(in.value("visibleRange", static_cast<double>(visibleRange)));
enabled = in.value("enabled", enabled);
battle_state = in.value("battle_state", battle_state);
currentState = static_cast<AnimationState>(in.value("currentState", static_cast<int>(currentState)));
@@ -517,17 +547,17 @@ void CharacterState::load(const nlohmann::json& in)
showWeapon = in.value("showWeapon", showWeapon);
attackTargetId = in.value("attackTargetId", attackTargetId);
faceTargetId = in.value("faceTargetId", faceTargetId);
if (in.contains("homePosition")) homePosition = readVec3(in["homePosition"], homePosition);
if (in.contains("walkTarget")) walkTarget = readVec3(in["walkTarget"], walkTarget);
if (in.contains("requestedWalkTarget")) requestedWalkTarget = readVec3(in["requestedWalkTarget"], requestedWalkTarget);
if (in.contains("homePosition")) homePosition = readVec3fp(in["homePosition"], homePosition);
if (in.contains("walkTarget")) walkTarget = readVec3fp(in["walkTarget"], walkTarget);
if (in.contains("requestedWalkTarget")) requestedWalkTarget = readVec3fp(in["requestedWalkTarget"], requestedWalkTarget);
pathWaypoints.clear();
if (in.contains("pathWaypoints") && in["pathWaypoints"].is_array()) {
for (const auto& wp : in["pathWaypoints"]) {
pathWaypoints.push_back(readVec3(wp, Eigen::Vector3f::Zero()));
pathWaypoints.push_back(readVec3fp(wp, Vec3fp()));
}
}
currentWaypointIndex = in.value("currentWaypointIndex", currentWaypointIndex);
homeDriftCheckTimer = in.value("homeDriftCheckTimer", homeDriftCheckTimer);
homeDriftCheckTimer = static_cast<fp>(in.value("homeDriftCheckTimer", static_cast<double>(homeDriftCheckTimer)));
movementBlockedTicks = in.value("movementBlockedTicks", movementBlockedTicks);
navigationRetryTicks = in.value("navigationRetryTicks", navigationRetryTicks);
movedLastTick = in.value("movedLastTick", movedLastTick);