Files
2025-09-18 17:55:52 -04:00

394 lines
13 KiB
C++

/* Copyright 2003-2005 ROBLOX Corporation, All Rights Reserved */
#include "stdafx.h"
#include "Humanoid/RunningBase.h"
#include "Humanoid/Humanoid.h"
#include "V8DataModel/Workspace.h"
#include "V8DataModel/PartInstance.h"
#include "V8DataModel/Filters.h"
#include "V8World/Primitive.h"
#include "V8World/Assembly.h"
#include "V8World/World.h"
#include "v8World/MaterialProperties.h"
#include "v8World/Geometry.h"
#include "V8Kernel/Body.h"
#include "V8Kernel/Constants.h"
#include "V8Kernel/Kernel.h"
LOGVARIABLE(HumanoidFloorProcess, 0)
DYNAMIC_FASTFLAG(HumanoidFloorPVUpdateSignal)
DYNAMIC_FASTFLAGVARIABLE(CheckForHeadHit, false)
DYNAMIC_FASTFLAGVARIABLE(PGSFixGroundSinking, false)
DYNAMIC_FASTFLAGVARIABLE(HumanoidFeetIsPlastic, false);
DYNAMIC_FASTFLAGVARIABLE(FixSlowLadderClimb, false);
DYNAMIC_FASTFLAG(UseTerrainCustomPhysicalProperties)
namespace RBX {
namespace HUMAN {
const float kAltitudeP = 30000.0f; // units: 1/sec^2 force = kAltitudeP * mass * position
const float kAltitudeD = 1100.0f; // units: 1/sec force = kAltitudeD * mass * velocity
RunningBase::RunningBase(Humanoid* humanoid, StateType priorState)
:Balancing(humanoid, priorState)
,desiredAltitude(0.0f)
{
desiredAltitude = std::numeric_limits<float>::infinity();
fireMovementSignal(getHumanoid()->runningSignal, getRelativeMovementVelocity().xz().length() );
// see if there's any residual impulse that needs applying
if (humanoid)
{
Vector3 impulse = humanoid->getActivatePhysicsImpulse();
if (!impulse.isZero())
{
if (PartInstance *instance = humanoid->getTorsoFast())
{
if (Primitive *prim = instance->getPartPrimitive())
{
if (Body *body = prim->getBody())
{
body->accumulateImpulseAtBranchCofm(impulse);
humanoid->setActivatePhysics(false, Vector3::zero());
}
}
}
}
}
RBXASSERT(!humanoid->getTorsoSlow() || humanoid->getTorsoSlow()->getPartPrimitive()->getEngineType() == Primitive::DYNAMICS_ENGINE);
}
RunningBase::RunningBase(Humanoid* humanoid, StateType priorState, const float kP, const float kD)
:Balancing(humanoid, priorState, kP, kD)
,desiredAltitude(0.0f)
{
desiredAltitude = std::numeric_limits<float>::infinity();
fireMovementSignal(getHumanoid()->runningSignal, getRelativeMovementVelocity().xz().length() );
RBXASSERT(!humanoid->getTorsoSlow() || humanoid->getTorsoSlow()->getPartPrimitive()->getEngineType() == Primitive::DYNAMICS_ENGINE);
}
void RunningBase::onComputeForceImpl()
{
Super::onComputeForceImpl();
// Now move
Humanoid *humanoid = getHumanoid();
if (!humanoid)
return;
Body* torsoBody = humanoid->getTorsoBodyFast();
if (!torsoBody)
return;
Body* root = humanoid->getRootBodyFast();
if (!root)
return;
#if CHARACTER_FORCE_DEBUG
if (getFloorPrimitive())
{
Vector3 force = root->getBranchForce();
debugRayList.push_back(DebugRay(RbxRay(getFloorTouchInWorld(), (force) / 500.0f ), Color3::green()));
}
#endif
Primitive* floor = getFloorPrimitive();
if (DFFlag::HumanoidFloorPVUpdateSignal)
{
// Helps transition when going from BufferZone to NotSimulating
getHumanoid()->updateFloorSimPhaseCharVelocity(floor);
}
// Rotate with the ground
{
float kP;
if (getHumanoid()->getWorld()->getUsingPGSSolver())
kP = RunningBase::kTurnPForRotatePGS();
else
kP = RunningBase::kTurnP();
float desiredTorqueY = kP * root->getBranchIBodyV3().y * (floorVelocity.rotational.y + desiredVelocity.rotational.y - root->getVelocity().rotational.y);
const float torqueMax = 1e5f; // tunable value - this is how strongly a humanoid can twist around
desiredTorqueY = G3D::clamp(desiredTorqueY, -torqueMax, torqueMax);
root->accumulateTorque(Vector3(0.0, desiredTorqueY, 0.0));
}
// Pick the smaller of the 2 masses to ensure we don't apply too high a force.
// Perhaps we could apply a different cap to each body.
// Perhaps the engine could do something clever like cap total accels and dribble excess accel to the next frame???
bool solidFloor = true;
float mass;
if ((getHumanoid()->getWorld()->getUsingPGSSolver()) ||
floor == NULL ||
floor->getAnchoredProperty() ||
(floor->getAssembly() && floor->getAssembly()->getAssemblyState() == Sim::ANCHORED)) {
mass = root->getBranchMass();
} else {
float humanMass = root->getBranchMass();
float floorMass = floor->getBody()->getRoot()->getBranchMass();
if (floorMass < humanMass) {
mass = floorMass;
solidFloor = false;
} else {
mass = humanMass;
}
}
// Maintain height above the floor
if (floor && desiredAltitude < std::numeric_limits<float>::infinity())
{
RBXASSERT(floor->getAssembly()->getAssemblyPrimitive()->getBody() != root);
float yAccelDesired = (kAltitudeP * (desiredAltitude - torsoBody->getPos().y)) - (kAltitudeD * (root->getVelocity().linear.y - floorVelocity.linear.y));
if (yAccelDesired > 0.0) { // If yAccelDesired <= 0.0 then just free-fall
const float currentAccelY = root->getBranchForce().y / root->getBranchMass();
if (yAccelDesired > currentAccelY) {
if (getHeadClear()) {
float deltaForce;
if (getHumanoid()->getWorld()->getUsingPGSSolver())
{
float scaleFactor = 0.1f;
float accelerationDelta = 0.0f;
if (DFFlag::PGSFixGroundSinking)
{
scaleFactor = 1.0f;
static float desiredScaleFactor = 0.2f;
accelerationDelta = (yAccelDesired * desiredScaleFactor) - currentAccelY;
} else {
accelerationDelta = yAccelDesired - currentAccelY;
}
if (solidFloor) {
deltaForce = mass * accelerationDelta;
deltaForce = G3D::clamp(deltaForce, -1e7f, 1e7f);
} else {
deltaForce = mass * std::min(accelerationDelta, maxMoveForce().y);
deltaForce = G3D::clamp(deltaForce, -1e5f, 1e5f);
}
root->accumulateForceAtBranchCofm(Vector3(0.0, deltaForce * scaleFactor, 0.0));
floor->getBody()->accumulateForce(Vector3(0.0, -deltaForce * scaleFactor, 0.0), getFloorTouchInWorld());
} else {
if (solidFloor) {
deltaForce = mass * (yAccelDesired - currentAccelY);
deltaForce = G3D::clamp(deltaForce, -1e7f, 1e7f);
} else {
deltaForce = mass * std::min(yAccelDesired - currentAccelY, maxMoveForce().y);
deltaForce = G3D::clamp(deltaForce, -1e5f, 1e5f);
}
root->accumulateForceAtBranchCofm(Vector3(0.0, deltaForce, 0.0));
floor->getBody()->accumulateForce(Vector3(0.0, -deltaForce * 0.5f, 0.0), getFloorTouchInWorld());
}
}
#if CHARACTER_FORCE_DEBUG
debugRayList.push_back(DebugRay(RbxRay(getFloorTouchInWorld(), Vector3(0.0, deltaForce/100.0f, 0.0)), Color3::red()));
Vector3 force = root->getBranchForce();
debugRayList.push_back(DebugRay(RbxRay(getFloorTouchInWorld() + Vector3(3.0f, 0.0f, 0.0f) , (force) / 500.0f ), Color3::yellow()));
#endif
}
}
}
// Move forward-backward (and up)
{
Vector3 desiredVelocityInWorld = floorVelocity.linear + desiredVelocity.linear;
// Humanoid Network Update requires to ignore floor velocity of part is not simulated.
const Vector3& currentVelocityInWorld = root->getBranchVelocity().linear; // velocity at COFM of the assembly
Vector3 currentAccel;
Vector3 desiredAccel;
if (getHumanoid()->getWorld()->getUsingPGSSolver())
{
desiredAccel = runningKMovePForPGS() * (desiredVelocityInWorld - currentVelocityInWorld);
if (DFFlag::FixSlowLadderClimb && getStateType() == CLIMBING)
{
// recalculate Accel
currentAccel = (root->getBranchForce() - root->getRootSimBody()->getWorldGravityForce()) / root->getBranchMass();
} else {
currentAccel = root->getBranchForce() / root->getBranchMass();
}
}
else
{
currentAccel = root->getBranchForce() / root->getBranchMass();
desiredAccel = runningKMoveP() * (desiredVelocityInWorld - currentVelocityInWorld);
}
if (desiredVelocity.linear.y <= 0.0f) {
if (!getFacingLadder()) {
desiredAccel.y = currentAccel.y;
}
}
Vector3 deltaAccel = desiredAccel - currentAccel;
// clamp Y and XZ independantly
if (!getFacingLadder()) {
deltaAccel.y = G3D::clamp(deltaAccel.y, minMoveForce().y, maxMoveForce().y);
}
Vector3 horzontalAccel(deltaAccel.x, 0.0f, deltaAccel.z);
float maxHorizontalForce;
if (floor != NULL) {
maxHorizontalForce = maxLinearGroundMoveForce();
} else {
maxHorizontalForce = maxLinearMoveForce();
}
if (horzontalAccel.squaredMagnitude() > maxHorizontalForce * maxHorizontalForce)
{
horzontalAccel = horzontalAccel.directionOrZero() * maxHorizontalForce;
deltaAccel.x = horzontalAccel.x;
deltaAccel.z = horzontalAccel.z;
}
Vector3 deltaForce = mass * (deltaAccel);
if (getStateType() == CLIMBING && getHumanoid()->getWorld()->getUsingPGSSolver())
{
deltaForce -= root->getRootSimBody()->getWorldGravityForce();
}
if (floor) {
float kFric;
if (getHumanoid()->getWorld()->getUsingNewPhysicalProperties())
{
if (DFFlag::HumanoidFeetIsPlastic)
{
if (!DFFlag::UseTerrainCustomPhysicalProperties || !floor->getPhysicalProperties().getCustomEnabled())
kFric = MaterialProperties::frictionBetweenMaterials(getFloorMaterial(), PLASTIC_MATERIAL);
else
kFric = MaterialProperties::frictionBetweenPrimAndMaterial(floor, PLASTIC_MATERIAL);
}
else
{
kFric = getFloorFrictionProperty(floor);
}
}
else
{
kFric = floor->getFriction();
}
if (getHumanoid()->getWorld()->getUsingPGSSolver() || getHumanoid()->getWorld()->getUsingNewPhysicalProperties())
{
deltaForce.x *= kFric;
deltaForce.z *= kFric;
} else {
if ( kFric < .3f) { // omg hax
kFric *= kFric;
deltaForce.x *= kFric;
deltaForce.z *= kFric;
}
}
}
// To conserve momentum, we apply an equal and opposite force on the "floor"
root->accumulateForceAtBranchCofm(deltaForce);
if (getHumanoid()->getWorld()->getUsingPGSSolver()) // For PGS kernel bodies we apply 1/10th the force
{
if (floor)
{
if (deltaForce.y > 0.0f)
floor->getBody()->accumulateForce(-deltaForce * 0.1, getFloorTouchInWorld());
else
floor->getBody()->accumulateForce(Vector3::zero(), getFloorTouchInWorld());
}
}
else
{
if (floor)
{
if (deltaForce.y > 0.0f)
floor->getBody()->accumulateForce(-deltaForce, getFloorTouchInWorld());
else
floor->getBody()->accumulateForce(Vector3::zero(), getFloorTouchInWorld());
}
}
PartInstance *p = PartInstance::fromPrimitive(floor);
PartInstance *torso = getHumanoid()->getTorsoSlow();
if (p && torso)
{
p->reportTouch(shared_from(torso));
root->getRootSimBody()->updateIfDirty();
}
}
}
void RunningBase::onSimulatorStepImpl(float stepDt)
{
// TODO: Determine length of legs and height of torso!
desiredAltitude = getFloorPrimitive() ? getDesiredAltitude() : std::numeric_limits<float>::infinity();
floorVelocity = getFloorPointVelocity();
if (getFloorPrimitive() || getStateType() == CLIMBING || getHumanoid()->getPreviousStateType() == CLIMBING)
{
desiredVelocity = getHumanoid()->calcDesiredWalkVelocity();
}
if (getFacingLadder() && (!DFFlag::FixSlowLadderClimb || desiredVelocity.linear.length() > 0.0f))
{
Vector3 forwardDir = getHumanoid()->getTorsoSlow()->getCoordinateFrame().lookVector();
forwardDir.y = 0;
forwardDir = forwardDir.unit();
float moveDir = 1.0f;
float dot = forwardDir.dot(desiredVelocity.linear.unit());
if (dot < -0.2f)
{
moveDir = -1.0f;
}
if (moveDir > 0.0f || !getFloorPrimitive()) {
const float speed = desiredVelocity.linear.length();
if (speed < 0.1f) {
desiredVelocity.linear.y = 0.01f * moveDir;
} else {
desiredVelocity.linear.y = 0.7f * speed * moveDir;
}
desiredVelocity.linear.x = 0;
desiredVelocity.linear.z = 0;
desiredVelocity.rotational = Vector3::zero();
}
}
if (getFloorPrimitive())
{
Vector3 normal = getFloorTouchNormal();
float dot = normal.dot(Vector3::unitY());
if (dot < steepSlopeAngle())
{
// too steep - move down hill
Vector3 downhill = -(Vector3::unitY() - (dot * normal));
if (downhill.unitize() > Math::epsilonf())
{
Vector3 inputOntoSurface = desiredVelocity.linear - (desiredVelocity.linear.dot(normal) * normal);
desiredVelocity.linear = inputOntoSurface + (downhill * getHumanoid()->getWalkSpeed()) - (inputOntoSurface.dot(downhill) * downhill);
}
}
}
}
void RunningBase::onCFrameChangedFromReflection()
{
Super::onCFrameChangedFromReflection();
// TODO: Determine length of legs and height of torso!
desiredAltitude = getFloorPrimitive() ? getDesiredAltitude() : std::numeric_limits<float>::infinity();
floorVelocity = getFloorPointVelocity();
desiredVelocity = getHumanoid()->calcDesiredWalkVelocity();
if (!desiredVelocity.linear.isZero() && getFacingLadder()) {
const float speed = desiredVelocity.linear.length();
desiredVelocity.linear.x = 0;
desiredVelocity.linear.y = 0.7f * speed;
desiredVelocity.linear.z = 0;
}
}
} // namespace HUMAN
} // namespace RBX