2731 lines
111 KiB
C++
2731 lines
111 KiB
C++
/*
|
|
** Command & Conquer Renegade(tm)
|
|
** Copyright 2025 Electronic Arts Inc.
|
|
**
|
|
** This program is free software: you can redistribute it and/or modify
|
|
** it under the terms of the GNU General Public License as published by
|
|
** the Free Software Foundation, either version 3 of the License, or
|
|
** (at your option) any later version.
|
|
**
|
|
** This program is distributed in the hope that it will be useful,
|
|
** but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
** GNU General Public License for more details.
|
|
**
|
|
** You should have received a copy of the GNU General Public License
|
|
** along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
/***********************************************************************************************
|
|
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
|
|
***********************************************************************************************
|
|
* *
|
|
* Project Name : WWPhys *
|
|
* *
|
|
* $Archive:: /Commando/Code/wwphys/phys3.cpp $*
|
|
* *
|
|
* Author:: Greg Hjelstrom *
|
|
* *
|
|
* $Modtime:: 1/08/02 5:52p $*
|
|
* *
|
|
* $Revision:: 137 $*
|
|
* *
|
|
*---------------------------------------------------------------------------------------------*
|
|
* Functions: *
|
|
* Phys3Class::Phys3Class -- Constructor *
|
|
* Phys3Class::Phys3Class -- Constructor - from a Definition *
|
|
* Phys3Class::~Phys3Class -- Destructor *
|
|
* Phys3Class::Get_Bounding_Box -- Returns bounding box of the model *
|
|
* Phys3Class::Get_Transform -- Returns the current transform *
|
|
* Phys3Class::Set_Transform -- Sets the current transform *
|
|
* Phys3Class::Set_Position -- Sets the position *
|
|
* Phys3Class::Get_Position -- Returns the current position *
|
|
* Phys3Class::Set_Heading -- Set the heading of this object *
|
|
* Phys3Class::Get_Heading -- returns the heading of this object *
|
|
* Phys3Class::Set_Slide_Angle -- Sets the maximum angle this object can climb *
|
|
* Phys3Class::Get_Slide_Angle -- returns the slide angle *
|
|
* Phys3Class::Update_Transform -- Recalculate the transform *
|
|
* Phys3Class::Set_Model -- Set the model being used *
|
|
* Phys3Class::Update_Cached_Model_Parameters -- Caches some data related to the model *
|
|
* Phys3Class::Apply_Impulse -- Apply an impulse to this object *
|
|
* Phys3Class::Apply_Impulse -- Apply an impulse to this object *
|
|
* Phys3Class::Cast_Ray -- Check a ray for intersection with this object *
|
|
* Phys3Class::Cast_AABox -- Check a swept AABox for intersection with this obj *
|
|
* Phys3Class::Cast_OBBox -- Check a swept OBBox for intersection with this obj *
|
|
* Phys3Class::Intersection_Test -- Check an AABox for intersection with this object *
|
|
* Phys3Class::Intersection_Test -- Check an OBBox for intersection with this object *
|
|
* Phys3Class::Intersection_Test -- Check a mesh for intersection with this object *
|
|
* Phys3Class::Timestep -- Simulate for time dt *
|
|
* Phys3Class::Get_Ground_State -- validates and returns the gound state structure *
|
|
* Phys3Class::Invalidate_Ground_State -- Marks the ground state as dirty *
|
|
* Phys3Class::Check_Ground -- Update the ground state *
|
|
* Phys3Class::User_Move -- User Move mode. *
|
|
* Phys3Class::Ballistic_Move -- Ballistic motion *
|
|
* Phys3Class::Slide_Move -- Sliding motion, down a slope *
|
|
* Phys3Class::Normal_Move -- Normal motion, phys3 does nothing (infinite friction) *
|
|
* Phys3Class::Collide_Move -- Move in response to someone colliding with me *
|
|
* Phys3Class::Apply_Move -- Apply a movement vector, constrained by world geometry *
|
|
* Phys3Class::Debug_Verify_Position -- Verify that I am in a valid position *
|
|
* Phys3Class::Snap_To_Ground -- Following a move, snap back down to the ground *
|
|
* Phys3Class::Clip_Move -- Clip a move vector by all of the active contact normals *
|
|
* Phys3Class::Get_Shadow_Blob_Box -- Returns the bounding box to use for blob shadows *
|
|
* Phys3Class::Get_Collision_Box -- returns the current world space collision box *
|
|
* Phys3Class::Push -- Move in response to an animated object colliding with me *
|
|
* Phys3Class::Collide -- Move in response to a collision *
|
|
* Phys3Class::Can_Teleport -- Checks if this object can occupy the specified position *
|
|
* Phys3Class::Can_Teleport_And_Stand -- Checks if this object can occupy the specified pos *
|
|
* Phys3Class::Find_Teleport_Location -- Searches for a valid position near the given one *
|
|
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
|
|
|
|
#include "phys3.h"
|
|
#include "wwdebug.h"
|
|
#include "wwhack.h"
|
|
#include "wwprofile.h"
|
|
#include "wwphystrig.h"
|
|
#include "physcoltest.h"
|
|
#include "physinttest.h"
|
|
#include "colmath.h"
|
|
#include "pscene.h"
|
|
#include "physcon.h"
|
|
#include "physcontrol.h"
|
|
#include "bitstream.h"
|
|
#include "persistfactory.h"
|
|
#include "simpledefinitionfactory.h"
|
|
#include "wwphysids.h"
|
|
#include "w3d_file.h"
|
|
|
|
|
|
DECLARE_FORCE_LINK(phys3);
|
|
const float STEP_HEIGHT = 0.25f;
|
|
|
|
|
|
#define VERBOSE_LOGGING 0
|
|
|
|
#if VERBOSE_LOGGING
|
|
#define VERBOSE_LOG(x) if (/*WWDEBUG_TRIGGER(WWDEBUG_TRIGGER_GENERIC0) &&*/ (strstr(Model->Get_Name(),"lt"))) { WWDEBUG_SAY(x); }
|
|
#else
|
|
#define VERBOSE_LOG(x)
|
|
#endif
|
|
|
|
|
|
/*
|
|
** Phys3 Networking constants:
|
|
*/
|
|
float Phys3Class::_CorrectionTime = 0.5f;
|
|
float Phys3Class::_AllowableError = 0.5f;
|
|
float Phys3Class::_PopError = 2.5f;
|
|
|
|
|
|
/*
|
|
** Phys3HistoryClass Parameters
|
|
** Control the implementation of the phys3 history tracking system with the following
|
|
** parameters.
|
|
*/
|
|
const int PHYS3_SNAPSHOT_COUNT = 16; // must be power of 2!
|
|
const float PHYS3_HISTORY_MIN_TIME = 1.25f; // seconds of history to store
|
|
|
|
const int PHYS3_SNAPSHOT_MASK = PHYS3_SNAPSHOT_COUNT - 1;
|
|
const float PHYS3_SNAPSHOT_INTERVAL = PHYS3_HISTORY_MIN_TIME / PHYS3_SNAPSHOT_COUNT;
|
|
|
|
#define PHYS3HISTORY_NO_CORRECTION 0
|
|
#define PHYS3HISTORY_ABSOLUTE_CORRECTION 0
|
|
#define PHYS3HISTORY_LERP_CORRECTION 1
|
|
|
|
|
|
/**
|
|
** Phys3HistoryClass
|
|
** This class is used to store a history of the state of a Phys3 object. The network
|
|
** update code uses this history to intelligently update the state of the object when
|
|
** a packet is received
|
|
*/
|
|
class Phys3HistoryClass
|
|
{
|
|
public:
|
|
Phys3HistoryClass(void);
|
|
~Phys3HistoryClass(void);
|
|
|
|
void Init(const Vector3 & pos,const Vector3 & vel);
|
|
void Update_History(const Vector3 & pos,const Vector3 & vel, float dt);
|
|
void Compute_Old_State(float dt,Vector3 * set_pos,Vector3 * set_vel);
|
|
void Apply_Correction(const Vector3 & pos_correction);
|
|
|
|
int History_Count(void) { return PHYS3_SNAPSHOT_COUNT; }
|
|
Vector3 Get_Historical_Position(int i) { return SnapshotArray[Wrap_Index(HeadIndex + i)].Position; }
|
|
|
|
float Find_Time_Of_Nearest_Point(const Vector3 & pos);
|
|
void Find_Nearest_Point(const Vector3 & pos,const Vector3 & vel,Vector3 * set_point);
|
|
|
|
private:
|
|
|
|
int Wrap_Index(int index) { return (index + PHYS3_SNAPSHOT_COUNT) & PHYS3_SNAPSHOT_MASK; }
|
|
|
|
class StateSnapshotClass
|
|
{
|
|
public:
|
|
StateSnapshotClass(void) : Position(0,0,0), Velocity(0,0,0), Age(0) { }
|
|
StateSnapshotClass(const Vector3 & pos,const Vector3 & vel, float age) : Position(pos), Velocity(vel), Age(age) { }
|
|
StateSnapshotClass & operator = (const StateSnapshotClass & that) { Position = that.Position; Velocity = that.Velocity; Age = that.Age; }
|
|
|
|
void Lerp(const StateSnapshotClass & a, const StateSnapshotClass & b, float fraction);
|
|
void Update_Age(float dt) { Age += dt; }
|
|
|
|
Vector3 Position;
|
|
Vector3 Velocity;
|
|
float Age;
|
|
};
|
|
|
|
bool Initialized;
|
|
StateSnapshotClass * SnapshotArray;
|
|
int HeadIndex; // history buffer is circular, this is the "head"
|
|
};
|
|
|
|
Phys3HistoryClass::Phys3HistoryClass(void) :
|
|
Initialized(false),
|
|
SnapshotArray(NULL),
|
|
HeadIndex(0)
|
|
{
|
|
SnapshotArray = new StateSnapshotClass[PHYS3_SNAPSHOT_COUNT];
|
|
|
|
SnapshotArray[1].Age = 10000.0f;
|
|
}
|
|
|
|
Phys3HistoryClass::~Phys3HistoryClass(void)
|
|
{
|
|
if (SnapshotArray != NULL) {
|
|
delete[] SnapshotArray;
|
|
SnapshotArray = NULL;
|
|
}
|
|
}
|
|
|
|
void Phys3HistoryClass::Init(const Vector3 & pos,const Vector3 & vel)
|
|
{
|
|
Initialized = true;
|
|
int next_older_index = Wrap_Index(HeadIndex + 1);
|
|
SnapshotArray[next_older_index].Age = 1000.0f;
|
|
SnapshotArray[next_older_index].Position = pos;
|
|
SnapshotArray[next_older_index].Velocity = vel;
|
|
}
|
|
|
|
void Phys3HistoryClass::Update_History(const Vector3 & pos,const Vector3 & vel, float dt)
|
|
{
|
|
int next_older_index = Wrap_Index(HeadIndex + 1);
|
|
|
|
/*
|
|
** Special case the first time we insert a state
|
|
*/
|
|
if (SnapshotArray[next_older_index].Age == 0.0f) {
|
|
SnapshotArray[next_older_index].Age = 1000.0f;
|
|
SnapshotArray[next_older_index].Position = pos;
|
|
SnapshotArray[next_older_index].Velocity = vel;
|
|
}
|
|
|
|
/*
|
|
** See if enough time has passed so we need to ratchet forward in the circular buffer
|
|
*/
|
|
if (SnapshotArray[next_older_index].Age + dt > PHYS3_SNAPSHOT_INTERVAL) {
|
|
HeadIndex = Wrap_Index(HeadIndex - 1);
|
|
SnapshotArray[HeadIndex].Age = 0;
|
|
}
|
|
|
|
SnapshotArray[HeadIndex].Position = pos;
|
|
SnapshotArray[HeadIndex].Velocity = vel;
|
|
|
|
/*
|
|
** Add age to all existing snapshots
|
|
*/
|
|
for (int i=0; i<PHYS3_SNAPSHOT_COUNT; i++) {
|
|
if (i != HeadIndex) {
|
|
SnapshotArray[i].Update_Age(dt);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Phys3HistoryClass::Compute_Old_State(float t,Vector3 * set_pos,Vector3 * set_vel)
|
|
{
|
|
int index = HeadIndex;
|
|
bool done = false;
|
|
|
|
while (!done) {
|
|
if (SnapshotArray[index].Age <= t) {
|
|
index = Wrap_Index(index + 1);
|
|
|
|
/*
|
|
** past the end of our history, just return the oldest known state
|
|
*/
|
|
if (index == HeadIndex) {
|
|
int tail_index = Wrap_Index(HeadIndex - 1);
|
|
if (set_pos != NULL) {
|
|
*set_pos = SnapshotArray[tail_index].Position;
|
|
}
|
|
if (set_vel != NULL) {
|
|
*set_vel = SnapshotArray[tail_index].Velocity;
|
|
}
|
|
return;
|
|
}
|
|
|
|
} else {
|
|
done = true;
|
|
}
|
|
}
|
|
|
|
int prev_index = Wrap_Index(index - 1);
|
|
int next_index = index;
|
|
|
|
StateSnapshotClass lerp;
|
|
float fraction = (t - SnapshotArray[prev_index].Age) / (SnapshotArray[next_index].Age - SnapshotArray[prev_index].Age);
|
|
lerp.Lerp(SnapshotArray[prev_index],SnapshotArray[next_index],fraction);
|
|
if (set_pos != NULL) {
|
|
*set_pos = lerp.Position;
|
|
}
|
|
if (set_vel != NULL) {
|
|
*set_vel = lerp.Velocity;
|
|
}
|
|
}
|
|
|
|
void Phys3HistoryClass::Apply_Correction(const Vector3 & pos_correction)
|
|
{
|
|
#if PHYS3HISTORY_ABSOLUTE_CORRECTION
|
|
for (int counter=0; counter<PHYS3_SNAPSHOT_COUNT; counter++) {
|
|
int index = Wrap_Index(HeadIndex + counter);
|
|
SnapshotArray[index].Position += pos_correction;
|
|
}
|
|
#endif
|
|
#if PHYS3HISTORY_LERP_CORRECTION
|
|
for (int counter=0; counter<PHYS3_SNAPSHOT_COUNT; counter++) {
|
|
int index = Wrap_Index(HeadIndex + counter);
|
|
|
|
float fraction = (PHYS3_HISTORY_MIN_TIME - SnapshotArray[index].Age) / PHYS3_HISTORY_MIN_TIME;
|
|
if (fraction > 0.0f) {
|
|
SnapshotArray[index].Position += fraction * pos_correction;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
float Phys3HistoryClass::Find_Time_Of_Nearest_Point(const Vector3 & pos)
|
|
{
|
|
/*
|
|
** Find the nearest line segment
|
|
*/
|
|
float min_dist = 10000.0f;
|
|
float time = 0.0f;
|
|
|
|
for (int counter=0; counter<PHYS3_SNAPSHOT_COUNT-1; counter++) {
|
|
int index0 = Wrap_Index(HeadIndex + counter);
|
|
int index1 = Wrap_Index(index0 + 1);
|
|
|
|
LineSegClass segment(SnapshotArray[index0].Position,SnapshotArray[index1].Position);
|
|
|
|
Vector3 point = segment.Find_Point_Closest_To(pos);
|
|
|
|
float dist = (point - pos).Length();
|
|
|
|
if (dist < min_dist) {
|
|
float fraction = (point - segment.Get_P0()).Length() / segment.Get_Length();
|
|
min_dist = dist;
|
|
time = WWMath::Lerp(SnapshotArray[index0].Age, SnapshotArray[index1].Age, fraction);
|
|
}
|
|
}
|
|
|
|
/*
|
|
** Return the age of the nearest point
|
|
*/
|
|
return time;
|
|
}
|
|
|
|
void Phys3HistoryClass::Find_Nearest_Point(const Vector3 & pos,const Vector3 & vel,Vector3 * set_point)
|
|
{
|
|
/*
|
|
** Find the nearest line segment
|
|
*/
|
|
float min_dist = 10000.0f;
|
|
|
|
for (int counter=0; counter<PHYS3_SNAPSHOT_COUNT-1; counter++) {
|
|
int index0 = Wrap_Index(HeadIndex + counter);
|
|
int index1 = Wrap_Index(index0 + 1);
|
|
|
|
LineSegClass segment(SnapshotArray[index0].Position,SnapshotArray[index1].Position);
|
|
|
|
Vector3 point = segment.Find_Point_Closest_To(pos);
|
|
float dist = (point - pos).Length();
|
|
|
|
/*
|
|
** Ignore points with velocity more than 90 deg away from server vel
|
|
*/
|
|
float vdot = 0.0f;
|
|
Vector3 history_vel;
|
|
float fraction = 0.0f;
|
|
if (segment.Get_Length() > 0.0f) {
|
|
fraction = (point - segment.Get_P0()).Length() / segment.Get_Length();
|
|
}
|
|
Vector3::Lerp(SnapshotArray[index0].Velocity,SnapshotArray[index1].Velocity,fraction,&history_vel);
|
|
vdot = Vector3::Dot_Product(vel,history_vel);
|
|
|
|
if ((dist < min_dist) && (vdot >= 0.0f)) {
|
|
*set_point = point;
|
|
min_dist = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void Phys3HistoryClass::StateSnapshotClass::Lerp(const StateSnapshotClass & a, const StateSnapshotClass & b, float fraction)
|
|
{
|
|
Vector3::Lerp(a.Position,b.Position,fraction,&Position);
|
|
Vector3::Lerp(a.Velocity,b.Velocity,fraction,&Velocity);
|
|
Age = WWMath::Lerp(a.Age,b.Age,fraction);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
**
|
|
** Phys3Class Implementation
|
|
**
|
|
***********************************************************************************************/
|
|
|
|
/*
|
|
** Declare a PersistFactory for Phys3Classes
|
|
*/
|
|
SimplePersistFactoryClass<Phys3Class,PHYSICS_CHUNKID_PHYS3> _Phys3Factory;
|
|
|
|
/*
|
|
** Definition factory for Phys3DefClass. This makes it creatable in the editor
|
|
*/
|
|
DECLARE_DEFINITION_FACTORY(Phys3DefClass, CLASSID_PHYS3DEF, "Phys3") _Phys3DefDefFactory;
|
|
|
|
/*
|
|
** Chunk ID's for Phys3Class
|
|
*/
|
|
enum
|
|
{
|
|
PHYS3_CHUNK_MOVEABLEPHYS = 0x00483200,
|
|
PHYS3_CHUNK_VARIABLES,
|
|
|
|
PHYS3_VARIABLE_COLLISION_AABOX = 0x00,
|
|
PHYS3_VARIABLE_ONGROUND,
|
|
PHYS3_VARIABLE_INCOLLISION,
|
|
PHYS3_VARIABLE_HEADING,
|
|
PHYS3_VARIABLE_NORMSPEED,
|
|
PHYS3_VARIABLE_SLIDEANGLE,
|
|
PHYS3_VARIABLE_SLIDENORMALZ,
|
|
PHYS3_VARIABLE_SLIDEANGLETAN,
|
|
PHYS3_VARIABLE_STEPHEIGHT,
|
|
PHYS3_VARIABLE_MOVEMODE,
|
|
PHYS3_VARIABLE_POSITION,
|
|
PHYS3_VARIABLE_VELOCITY
|
|
};
|
|
|
|
|
|
static const float DEFAULT_STEP_HEIGHT = 0.25f; // the distance an object will "step up" over an obstacle
|
|
static const float DEFAULT_SLIDE_ANGLE = DEG_TO_RADF(45.0f); // steepest angle the character can walk up
|
|
static const float DEFAULT_NORMALIZED_SPEED = 10.0f;
|
|
|
|
static const float GROUND_DISTANCE = 0.1f; // On ground if within this distance
|
|
static const float GROUND_EPSILON = (GROUND_DISTANCE) / 5.0f; // Stop at this distance from ground
|
|
static const float WALL_EPSILON = 0.5f;//(GROUND_DISTANCE - 0.001f); // Stop at this distance from walls/slides
|
|
|
|
static const float MIN_STEP_MOVE = 0.25f; // only try to step if we could move this distance
|
|
static const float MAX_STEP_MOVE_ANGLE_TAN = 1.0f; // only step if moving at an angle close to the x-y plane
|
|
|
|
// Debug Vector colors
|
|
static const Vector3 VELOCITY_COLOR(1,0,0); // color for the velocity debug vector
|
|
static const Vector3 CONTACT_COLOR(0.25f,0.7f,0.2f); // color for contact vectors
|
|
static const Vector3 GROUND_COLOR(0.0f,1.0f,1.0f);
|
|
|
|
static inline void Clip_Move(const Vector3 * contacts,int contact_count,Vector3 * move);
|
|
|
|
|
|
|
|
/*************************************************************************************
|
|
**
|
|
** Phys3Class Implementation
|
|
**
|
|
*************************************************************************************/
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Phys3Class -- Constructor *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/15/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
Phys3Class::Phys3Class(void)
|
|
{
|
|
CollisionBox.Center.Set(0,0,1);
|
|
CollisionBox.Extent.Set(1,1,1);
|
|
OnGround = false;
|
|
InCollision = false;
|
|
HeadingChanged = false;
|
|
GroundSurface = 0;
|
|
Heading = 0.0f;
|
|
NormSpeed = DEFAULT_NORMALIZED_SPEED;
|
|
SlideAngle = DEFAULT_SLIDE_ANGLE;
|
|
SlideNormalZ = WWMath::Cos(SlideAngle);
|
|
SlideAngleTan = tan(SlideAngle);
|
|
StepHeight = DEFAULT_STEP_HEIGHT;
|
|
MoveMode = NORMAL_MOVE;
|
|
GroundObject = NULL;
|
|
AnimationMove.Set(0,0,0);
|
|
History = NULL;
|
|
LatencyError.Set(0,0,0);
|
|
LastKnownPosition.Set(0,0,0);
|
|
LastKnownVelocity.Set(0,0,0);
|
|
Invalidate_Ground_State();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Init -- initialize from a Definition *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/15/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Init(const Phys3DefClass & def)
|
|
{
|
|
MoveablePhysClass::Init(def);
|
|
|
|
CollisionBox.Center.Set(0,0,1);
|
|
CollisionBox.Extent.Set(1,1,1);
|
|
OnGround = false;
|
|
InCollision = false;
|
|
GroundSurface = 0;
|
|
Heading = 0.0f;
|
|
NormSpeed = def.NormSpeed;
|
|
SlideAngle = def.SlideAngle;
|
|
SlideNormalZ = WWMath::Cos(SlideAngle);
|
|
SlideAngleTan = tan(SlideAngle);
|
|
StepHeight = def.StepHeight;
|
|
MoveMode = NORMAL_MOVE;
|
|
GroundObject = NULL;
|
|
AnimationMove.Set(0,0,0);
|
|
LatencyError.Set(0,0,0);
|
|
LastKnownPosition.Set(0,0,0);
|
|
LastKnownVelocity.Set(0,0,0);
|
|
|
|
Update_Cached_Model_Parameters();
|
|
Invalidate_Ground_State();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::~Phys3Class -- Destructor *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
*=============================================================================================*/
|
|
Phys3Class::~Phys3Class(void)
|
|
{
|
|
if (History != NULL) {
|
|
delete History;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Get_Bounding_Box -- Returns bounding box of the model *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/15/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
const AABoxClass & Phys3Class::Get_Bounding_Box(void) const
|
|
{
|
|
assert(Model);
|
|
return Model->Get_Bounding_Box();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Get_Transform -- Returns the current transform *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/15/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
const Matrix3D & Phys3Class::Get_Transform(void) const
|
|
{
|
|
assert(Model);
|
|
return Model->Get_Transform();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Set_Transform -- Sets the current transform *
|
|
* *
|
|
* Note that this "warps" the object to the specified position. The user is responsible *
|
|
* for providing a valid position. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/15/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Set_Transform(const Matrix3D & m)
|
|
{
|
|
// copy the translation portion of the transform into our state
|
|
m.Get_Translation( &State.Position );
|
|
|
|
#ifdef WWDEBUG
|
|
if (!State.Position.Is_Valid()) {
|
|
WWDEBUG_SAY(("Phys3Class::Set_Transform got an invalid position: %f, %f, %f\r\n",State.Position.X,State.Position.Y,State.Position.Z));
|
|
}
|
|
#endif
|
|
|
|
// copy the Z rotation portion of the transform into our heading variable (ugh...)
|
|
Heading = m.Get_Z_Rotation();
|
|
Update_Transform();
|
|
Update_Cull_Box();
|
|
|
|
// Wake the object up whenever it moves
|
|
Set_Flag(ASLEEP,false);
|
|
Invalidate_Ground_State();
|
|
|
|
Assert_State_Valid();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Set_Position -- Sets the position *
|
|
* *
|
|
* This blindly warps the object to the given position. The user is responsible for *
|
|
* providing a valid position. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Set_Position(const Vector3 & position)
|
|
{
|
|
State.Position = position;
|
|
VERBOSE_LOG(("Phys3::Set_Position %s, (%f, %f, %f)\r\n\r\n",Model->Get_Name(),position.X,position.Y,position.Z));
|
|
Update_Transform(true);
|
|
Update_Cull_Box();
|
|
Set_Flag(ASLEEP,false);
|
|
Invalidate_Ground_State();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Get_Position -- Returns the current position *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
const Vector3 & Phys3Class::Get_Position(void) const
|
|
{
|
|
return State.Position;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Set_Heading -- Set the heading of this object *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Set_Heading(float heading)
|
|
{
|
|
WWASSERT(WWMath::Is_Valid_Float(heading));
|
|
if (heading != Heading) {
|
|
Heading = heading;
|
|
HeadingChanged = true;
|
|
}
|
|
Update_Transform();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Get_Heading -- returns the heading of this object *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
float Phys3Class::Get_Heading(void) const
|
|
{
|
|
return Heading;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Set_Slide_Angle -- Sets the maximum angle this object can climb *
|
|
* *
|
|
* If a phys3 object is resting on a slope greater than this angle, it will slide down the *
|
|
* slope. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Set_Slide_Angle(float angle)
|
|
{
|
|
SlideAngle = angle;
|
|
SlideNormalZ = WWMath::Cos(angle);
|
|
SlideAngleTan = tan(angle);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Get_Slide_Angle -- returns the slide angle *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
float Phys3Class::Get_Slide_Angle(void) const
|
|
{
|
|
return SlideAngle;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Update_Transform -- Recalculate the transform *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Update_Transform(bool position_only)
|
|
{
|
|
Vector3 pos_dif = Model->Get_Position();
|
|
pos_dif -= State.Position;
|
|
|
|
if ((position_only) && (!HeadingChanged)) {
|
|
|
|
Model->Set_Position(State.Position);
|
|
|
|
} else {
|
|
|
|
Matrix3D tm(1);
|
|
tm.Set_Translation(State.Position);
|
|
tm.Rotate_Z(Heading);
|
|
Model->Set_Transform(tm);
|
|
}
|
|
|
|
if ( pos_dif.Length2() > WWMATH_EPSILON2 ) {
|
|
Update_Visibility_Status();
|
|
}
|
|
|
|
#ifdef WWDEBUG
|
|
for (int j=0;j<3;j++) {
|
|
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Position.X));
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Position.Y));
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Position.Z));
|
|
WWASSERT(WWMath::Is_Valid_Float(Heading));
|
|
}
|
|
#endif
|
|
|
|
HeadingChanged = false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Set_Model -- Set the model being used *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Set_Model(RenderObjClass * model)
|
|
{
|
|
// Let the base class have the model
|
|
MoveablePhysClass::Set_Model(model);
|
|
|
|
// update any members that depend on the model
|
|
Update_Cached_Model_Parameters();
|
|
|
|
// Update our culling box
|
|
Update_Cull_Box();
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Update_Cached_Model_Parameters -- Caches some data related to the model *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Update_Cached_Model_Parameters(void)
|
|
{
|
|
// if we don't have a model yet, just return
|
|
if (Model == NULL) {
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
// Construct our collision box from the model
|
|
RenderObjClass * box = NULL;
|
|
|
|
if (Model->Class_ID() == RenderObjClass::CLASSID_DISTLOD) {
|
|
RenderObjClass * lod0 = Model->Get_Sub_Object(0);
|
|
box = lod0->Get_Sub_Object_By_Name("WORLDBOX");
|
|
lod0->Release_Ref();
|
|
} else {
|
|
box = Model->Get_Sub_Object_By_Name("WORLDBOX");
|
|
}
|
|
|
|
if (box) {
|
|
|
|
Matrix3D old_transform = Model->Get_Transform();
|
|
Model->Set_Transform(Matrix3D(1));
|
|
|
|
CollisionBox = box->Get_Bounding_Box();
|
|
box->Release_Ref();
|
|
Model->Set_Transform(old_transform);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Apply_Impulse -- Apply an impulse to this object *
|
|
* *
|
|
* As the code comment says... since we changed Phys3 to essentially have infinite friction, *
|
|
* this function will only work if the object is in the air currently. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Apply_Impulse(const Vector3 & impulse)
|
|
{
|
|
// Impluse applied to center of mass simply adds to the linear momentum
|
|
// NOTE: the current algorithm does not maintain velocity uless undergoing
|
|
// ballistic movement... this routine will often have no effect...
|
|
State.Velocity += impulse * MassInv;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Apply_Impulse -- Apply an impulse to this object *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Apply_Impulse(const Vector3 & impulse, const Vector3 & wpos)
|
|
{
|
|
// Phys3 has only linear momentum so just apply the impulse to the CM...
|
|
Apply_Impulse(impulse);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Cast_Ray -- Check a ray for intersection with this object *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Cast_Ray(PhysRayCollisionTestClass & raytest)
|
|
{
|
|
if (Model->Cast_Ray(raytest)) {
|
|
raytest.CollidedPhysObj = this;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Cast_AABox -- Check a swept AABox for intersection with this obj *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Cast_AABox(PhysAABoxCollisionTestClass & boxtest)
|
|
{
|
|
AABoxClass wrldbox;
|
|
wrldbox.Extent = CollisionBox.Extent;
|
|
wrldbox.Center = State.Position + CollisionBox.Center;
|
|
|
|
if (CollisionMath::Collide(boxtest.Box,boxtest.Move,wrldbox,boxtest.Result)) {
|
|
boxtest.CollidedPhysObj = this;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Cast_OBBox -- Check a swept OBBox for intersection with this obj *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Cast_OBBox(PhysOBBoxCollisionTestClass & boxtest)
|
|
{
|
|
AABoxClass wrldbox;
|
|
wrldbox.Extent = CollisionBox.Extent;
|
|
wrldbox.Center = State.Position + CollisionBox.Center;
|
|
|
|
if (CollisionMath::Collide(boxtest.Box,boxtest.Move,wrldbox,Vector3(0,0,0),boxtest.Result)) {
|
|
boxtest.CollidedPhysObj = this;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Intersection_Test -- Check an AABox for intersection with this object *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Intersection_Test(PhysAABoxIntersectionTestClass & test)
|
|
{
|
|
AABoxClass wrldbox;
|
|
wrldbox.Extent = CollisionBox.Extent;
|
|
wrldbox.Center = State.Position + CollisionBox.Center;
|
|
|
|
if (CollisionMath::Intersection_Test(wrldbox,test.Box)) {
|
|
test.Add_Intersected_Object(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Intersection_Test -- Check an OBBox for intersection with this object *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Intersection_Test(PhysOBBoxIntersectionTestClass & test)
|
|
{
|
|
AABoxClass wrldbox;
|
|
wrldbox.Extent = CollisionBox.Extent;
|
|
wrldbox.Center = State.Position + CollisionBox.Center;
|
|
|
|
if (CollisionMath::Intersection_Test(wrldbox,test.Box)) {
|
|
test.Add_Intersected_Object(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Intersection_Test -- Check a mesh for intersection with this object *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Intersection_Test(PhysMeshIntersectionTestClass & test)
|
|
{
|
|
AABoxClass wrldbox;
|
|
wrldbox.Extent = CollisionBox.Extent;
|
|
wrldbox.Center = State.Position + CollisionBox.Center;
|
|
|
|
AABoxIntersectionTestClass our_test(wrldbox,test.CollisionType);
|
|
if (test.Mesh->Intersect_AABox(our_test)) {
|
|
test.Add_Intersected_Object(this);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************
|
|
**
|
|
**
|
|
** Simulation Code
|
|
**
|
|
**
|
|
***********************************************************************************/
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Timestep -- Simulate for time dt *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Timestep(float dt)
|
|
{
|
|
WWPROFILE("Phys3::Timestep");
|
|
VERBOSE_LOG(("\r\n***** Phys3::Timestep. %s position: %f %f %f\r\n",Model->Get_Name(),State.Position.X,State.Position.Y,State.Position.Z));
|
|
|
|
/*
|
|
** Update our history buffer
|
|
*/
|
|
if (History != NULL) {
|
|
History->Update_History(State.Position,State.Velocity,dt);
|
|
|
|
/*
|
|
** Debugging, Draw our history if present
|
|
*/
|
|
Vector3 p0 = State.Position;
|
|
Vector3 p1;
|
|
|
|
for (int i=1; i<History->History_Count(); i++) {
|
|
p1 = History->Get_Historical_Position(i);
|
|
DEBUG_RENDER_VECTOR(p0,p1-p0,VELOCITY_COLOR);
|
|
p0 = p1;
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If there is remaining latency error, correct some of it
|
|
*/
|
|
if (LatencyError.Length2() > 0.001f) {
|
|
|
|
Vector3 correction;
|
|
float pop_error2 = _PopError * _PopError;
|
|
float allowable_error2 = _AllowableError * _AllowableError;
|
|
float latency_error2 = LatencyError.Length2();
|
|
|
|
if (State.Velocity.Length2() > 1.0f) {
|
|
allowable_error2 = 0.0f;
|
|
}
|
|
|
|
if (latency_error2 > pop_error2) {
|
|
|
|
/*
|
|
** Teleport to the network position:
|
|
*/
|
|
Network_Teleport_Correction();
|
|
|
|
} else if (latency_error2 > allowable_error2) {
|
|
|
|
/*
|
|
** Try to gracefully correct the error:
|
|
*/
|
|
float frame_time = dt;
|
|
float correction_time = _CorrectionTime;
|
|
float correction_fraction;
|
|
|
|
if (correction_time <= frame_time) {
|
|
correction_fraction = 1.0f;
|
|
} else {
|
|
correction_fraction = WWMath::Clamp(frame_time / correction_time);
|
|
}
|
|
|
|
correction_fraction = WWMath::Clamp(correction_fraction,0.0f,0.5f);
|
|
correction = correction_fraction * LatencyError;
|
|
|
|
/*
|
|
** Smoothly correct our position
|
|
*/
|
|
Vector3 start = State.Position;
|
|
this->Apply_Move(correction,1.0f,true,false,false);
|
|
Update_Transform();
|
|
Vector3 delta = State.Position - start;
|
|
History->Apply_Correction(delta);
|
|
|
|
/*
|
|
** If the correction completely fails, teleport
|
|
*/
|
|
float correction_len = correction.Length();
|
|
float delta_len = delta.Length();
|
|
if ((correction_len > 0.0f) && (delta_len/correction_len < 0.1f)) {
|
|
|
|
Network_Teleport_Correction();
|
|
|
|
} else {
|
|
LatencyError -= delta;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
** If we're asleep, short circuit
|
|
*/
|
|
if (Is_Asleep() && !Is_User_Control_Enabled()) {
|
|
return;
|
|
}
|
|
|
|
Vector3 start_pos = State.Position;
|
|
Vector3 move;
|
|
AABoxClass box;
|
|
CastResultStruct res;
|
|
|
|
GroundStateStruct & gs = Get_Ground_State();
|
|
|
|
OnGround = gs.OnGround;
|
|
GroundSurface = gs.SurfaceType;
|
|
WWASSERT(GroundSurface >= 0);
|
|
WWASSERT(GroundSurface < SURFACE_TYPE_MAX);
|
|
|
|
bool moved = false;
|
|
|
|
if (Is_User_Control_Enabled()) {
|
|
|
|
MoveMode = USER_OVERRIDE;
|
|
moved = User_Move(dt);
|
|
|
|
} else {
|
|
|
|
if (!gs.OnGround) {
|
|
|
|
MoveMode = BALLISTIC_MOVE;
|
|
moved = Ballistic_Move(dt);
|
|
|
|
} else {
|
|
|
|
if (gs.Normal.Z < SlideNormalZ) {
|
|
|
|
MoveMode = SLIDE_MOVE;
|
|
moved = Slide_Move(gs,dt);
|
|
|
|
} else {
|
|
|
|
MoveMode = NORMAL_MOVE;
|
|
moved = Normal_Move(gs,dt);
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
if ((moved) || (HeadingChanged)) {
|
|
WWPROFILE("Phys3Class::Timestep cleanup");
|
|
Update_Transform(true);
|
|
Update_Cull_Box();
|
|
Add_Animation_Move(State.Position - start_pos);
|
|
|
|
if (!OnGround) {
|
|
Link_To_Carrier(NULL);
|
|
}
|
|
}
|
|
|
|
if (GroundState.OnDynamicObj) {
|
|
Invalidate_Ground_State();
|
|
Set_Flag(ASLEEP,false);
|
|
}
|
|
|
|
|
|
// DEBUG_RENDER_VECTOR(State.Position,State.Velocity,VELOCITY_COLOR);
|
|
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Position.X));
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Position.Y));
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Position.Z));
|
|
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Velocity.X));
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Velocity.Y));
|
|
WWASSERT(WWMath::Is_Valid_Float(State.Velocity.Z));
|
|
|
|
VERBOSE_LOG((" ***** Phys3::Timestep ended. Position: %f %f %f\r\n\r\n",State.Position.X,State.Position.Y,State.Position.Z));
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Get_Ground_State -- validates and returns the gound state structure *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
Phys3Class::GroundStateStruct & Phys3Class::Get_Ground_State(void)
|
|
{
|
|
WWPROFILE("Phys3::Get_Ground_State");
|
|
if (GroundState.IsDirty == true) {
|
|
GroundState.IsDirty = false;
|
|
AABoxClass box;
|
|
Compute_WS_Collision_Box(State,&box);
|
|
Check_Ground(box,&GroundState,GROUND_DISTANCE);
|
|
}
|
|
|
|
VERBOSE_LOG((" GS: OnGround = %d Normal = %f %f %f\r\n",GroundState.OnGround,GroundState.Normal.X,GroundState.Normal.Y,GroundState.Normal.Z));
|
|
|
|
return GroundState;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Invalidate_Ground_State -- Marks the ground state as dirty *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Invalidate_Ground_State(void)
|
|
{
|
|
GroundState.IsDirty = true;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Check_Ground -- Update the ground state *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Check_Ground(const AABoxClass & box,GroundStateStruct * gs,float check_dist)
|
|
{
|
|
WWPROFILE("Check_Ground");
|
|
VERBOSE_LOG(("Phys3::Check_Ground\r\n"));
|
|
CastResultStruct result;
|
|
PhysAABoxCollisionTestClass test( box,
|
|
Vector3(0,0,-check_dist),
|
|
&result,
|
|
Get_Collision_Group(),
|
|
COLLISION_TYPE_PHYSICAL );
|
|
|
|
Inc_Ignore_Counter();
|
|
PhysicsSceneClass::Get_Instance()->Cast_AABox(test);
|
|
Dec_Ignore_Counter();
|
|
|
|
if (result.Fraction < 1.0f) {
|
|
|
|
gs->Init_From_Collision_Result(test,result.Fraction * check_dist);
|
|
|
|
if (!result.StartBad) {
|
|
Link_To_Carrier(test.CollidedPhysObj,test.CollidedRenderObj);
|
|
DEBUG_RENDER_VECTOR(State.Position,gs->Normal,GROUND_COLOR);
|
|
VERBOSE_LOG((" not intersecting\r\n"));
|
|
}
|
|
|
|
} else {
|
|
|
|
gs->OnGround = false;
|
|
gs->SurfaceType = SURFACE_TYPE_DEFAULT;
|
|
VERBOSE_LOG((" not on ground!\r\n"));
|
|
}
|
|
|
|
if (result.StartBad) {
|
|
DEBUG_RENDER_AABOX(box,Vector3(1,0,0),0.25f);
|
|
VERBOSE_LOG((" INTERSECTING!\r\n"));
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::User_Move -- User Move mode. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::User_Move(float dt)
|
|
{
|
|
VERBOSE_LOG(("Phys3::User_Move\r\n"));
|
|
if (Controller) {
|
|
|
|
Vector3 move(Controller->Get_Move_Vector());
|
|
move.Rotate_Z(Heading);
|
|
|
|
State.Velocity = NormSpeed * move;
|
|
move = State.Velocity * dt;
|
|
|
|
return Apply_Move(move,dt);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Ballistic_Move -- Ballistic motion *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Ballistic_Move(float dt)
|
|
{
|
|
// Compute a move vector for the object flying through the air...
|
|
WWPROFILE("Phys3::Ballistic_Move");
|
|
VERBOSE_LOG(("Phys3::Ballistic_Move\r\n"));
|
|
|
|
Vector3 move;
|
|
float accel = PhysicsConstants::GravityAcceleration.Z * GravScale;
|
|
|
|
Vector3 start_vel = State.Velocity;
|
|
Vector3 start_pos = State.Position;
|
|
|
|
move.X = State.Velocity.X * dt;
|
|
move.Y = State.Velocity.Y * dt;
|
|
move.Z = 0.5f * accel * dt * dt + State.Velocity.Z * dt;
|
|
|
|
bool moved = Apply_Move(move,dt);
|
|
|
|
// Compute X,Y velocities from the actual move that was performed
|
|
State.Velocity.X = (State.Position.X - start_pos.X) / dt;
|
|
State.Velocity.Y = (State.Position.Y - start_pos.Y) / dt;
|
|
|
|
// Compute the analytical Z velocity and the ad-hoc Z velocity, the
|
|
// more negative one is the one to keep. What this does is use the
|
|
// analytical velocity unless the character hits a roof.
|
|
// if (CollidedThisFrame) {
|
|
// State.Velocity.Z = (State.Position.Z - start_pos.Z) / dt;
|
|
// } else {
|
|
State.Velocity.Z = start_vel.Z + accel * dt;
|
|
// }
|
|
return moved;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Slide_Move -- Sliding motion, down a slope *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Slide_Move(const GroundStateStruct & gs,float dt)
|
|
{
|
|
// Compute a move vector which causes the object to slide down the slope...
|
|
WWPROFILE("Phys3::Slide_Move");
|
|
VERBOSE_LOG(("Phys3::Slide_Move\r\n"));
|
|
|
|
Vector3 start_pos = State.Position;
|
|
Vector3 move = NormSpeed * dt * gs.Down;
|
|
|
|
bool moved = Apply_Move(move,dt,true,false);
|
|
State.Velocity = (State.Position - start_pos) / dt;
|
|
return moved;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Normal_Move -- Normal motion, phys3 does nothing (infinite grip) *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Normal_Move(const GroundStateStruct & gs,float dt)
|
|
{
|
|
WWPROFILE("Phys3::Normal_Move");
|
|
VERBOSE_LOG(("Phys3::Normal_Move\r\n"));
|
|
|
|
// phys3 doesn't move unless on a slope or falling.
|
|
State.Velocity.Set(0,0,0);
|
|
return false;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Collide_Move -- Move in response to someone colliding with me *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Collide_Move(const Vector3 & requested_move,float dt)
|
|
{
|
|
WWPROFILE("Phys3::Collide_Move");
|
|
VERBOSE_LOG(("Phys3::Collide_Move\r\n"));
|
|
|
|
// if we're already being considered in a collision (collisions looped back to us) then return
|
|
if (InCollision) {
|
|
return false;
|
|
}
|
|
|
|
InCollision = true;
|
|
MoveMode = COLLIDE_MOVE;
|
|
Vector3 start_position = State.Position;
|
|
bool moved = Apply_Move(requested_move,dt,true,false);
|
|
|
|
/*
|
|
** if we moved and we started on the ground, try to stick to the ground
|
|
*/
|
|
if (moved && OnGround) {
|
|
Snap_To_Ground(State.Position - start_position,true);
|
|
}
|
|
InCollision = false;
|
|
|
|
State.Velocity = (State.Position - start_position) / dt;
|
|
Add_Animation_Move(State.Position - start_position);
|
|
|
|
Update_Transform(true);
|
|
Update_Cull_Box();
|
|
|
|
return moved;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Apply_Move -- Apply a movement vector, constrained by world geometry *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Apply_Move
|
|
(
|
|
const Vector3 & input_move,
|
|
float dt,
|
|
bool allow_sliding,
|
|
bool allow_stepping,
|
|
bool stop_on_walkable
|
|
)
|
|
{
|
|
WWPROFILE("Phys3::Apply_Move");
|
|
VERBOSE_LOG(("Phys3::Apply_Move: %f %f %f\r\n",input_move.X,input_move.Y,input_move.Z));
|
|
|
|
#ifdef WWDEBUG
|
|
if (!input_move.Is_Valid()) {
|
|
WWDEBUG_SAY(("Phys3::Apply_Move called with invalid move vector: %f, %f, %f\r\n",input_move.X,input_move.Y,input_move.Z));
|
|
// WWASSERT(input_move.Is_Valid());
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
** Early exit if we're not really moving!
|
|
*/
|
|
float move_len = input_move.Length();
|
|
if (move_len < WWMATH_EPSILON) {
|
|
if (GroundState.OnGround) {
|
|
Set_Flag(ASLEEP,true);
|
|
}
|
|
VERBOSE_LOG((" no motion\r\n"));
|
|
return false;
|
|
} else {
|
|
VERBOSE_LOG((" applying move (%f,%f,%f)\r\n",input_move.X,input_move.Y,input_move.Z));
|
|
Set_Flag(ASLEEP,false);
|
|
Invalidate_Ground_State();
|
|
}
|
|
|
|
bool moved = false;
|
|
bool done = false;
|
|
bool collided = false;
|
|
|
|
const int MAX_CONTACTS = 4;
|
|
const int MAX_ITERATIONS = 4;
|
|
|
|
int total_iterations = 0;
|
|
int contact_count = 0;
|
|
Vector3 contacts[MAX_CONTACTS];
|
|
|
|
Vector3 start_position = State.Position;
|
|
Vector3 move = input_move;
|
|
Vector3 move_dir = move / move_len;
|
|
|
|
bool already_stepped = false;
|
|
|
|
Inc_Ignore_Counter();
|
|
|
|
/*
|
|
** Apply Move:
|
|
**
|
|
** - WHILE (I still have movement to apply)
|
|
** - sweep our collision box up to the first collision
|
|
** - IF (I started out intersecting) give up, done
|
|
** - ELSE
|
|
** - IF (I was able to take the entire move without collision) accept the move, done
|
|
** - ELSE
|
|
** - Accept the portion of the move up to the collision
|
|
** - Call the collision callbacks
|
|
** - IF (I have no movement remaining OR I'm not allowed to slide) done
|
|
** - ELSE
|
|
** - If (I can push the obstacle out of my way) push obstacle, go back to top of loop
|
|
** - If (I can shatter this object) shatter it, go back to top of loop
|
|
** - If (This contact is a slope, and I'm trying to walk up it) modify contact to act like a wall
|
|
** - Reflect the movement vector so that it does not violate the current contacts
|
|
**
|
|
** Notes:
|
|
** - Try to stop movement some distance (COLLISION_EPSILON) away from collisions.
|
|
** - When approaching slides (slopes that we'll slide off of), try to treat them like walls.
|
|
** - Can't always treat slides like walls since we can fall onto them or walk onto them from the top...
|
|
** - Trying to get stepping for free usually by just always bumping the character up before taking his move
|
|
**
|
|
*/
|
|
|
|
while (!done) {
|
|
|
|
if ( ++total_iterations > MAX_ITERATIONS ) {
|
|
VERBOSE_LOG(( "Phys3 Exceeded max iterations!\r\n" ));
|
|
done = true;
|
|
}
|
|
|
|
/*
|
|
** Get the collision box for the object
|
|
*/
|
|
AABoxClass box;
|
|
Compute_WS_Collision_Box(State,&box);
|
|
|
|
CastResultStruct result;
|
|
PhysAABoxCollisionTestClass test( box,
|
|
move,
|
|
&result,
|
|
Get_Collision_Group(),
|
|
COLLISION_TYPE_PHYSICAL );
|
|
|
|
/*
|
|
** If we're allowed to step up, try to
|
|
*/
|
|
if (OnGround && allow_stepping && !already_stepped) {
|
|
|
|
already_stepped = true;
|
|
|
|
/*
|
|
** Bump the box up Z by STEP_HEIGHT
|
|
*/
|
|
test.Translate(Vector3(0,0,STEP_HEIGHT));
|
|
|
|
/*
|
|
** Sweep the box
|
|
*/
|
|
PhysicsSceneClass::Get_Instance()->Cast_AABox(test);
|
|
|
|
/*
|
|
** IF: the 'step' test did not start out intersecting (no low roof) then
|
|
** warp our position up and continue
|
|
**
|
|
** ELSE: Since the step test started out intersecting we must be under a low
|
|
** roof. In this case, perform the move from our original position; no
|
|
** stepping over obstacles.
|
|
*/
|
|
if (!result.StartBad) {
|
|
|
|
State.Position.Z += STEP_HEIGHT;
|
|
moved = true;
|
|
|
|
} else {
|
|
|
|
result.Reset();
|
|
test.Translate(Vector3(0,0,-STEP_HEIGHT));
|
|
PhysicsSceneClass::Get_Instance()->Cast_AABox(test);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
PhysicsSceneClass::Get_Instance()->Cast_AABox(test);
|
|
|
|
}
|
|
|
|
VERBOSE_LOG((" Checking collision results: "));
|
|
if (result.StartBad) {
|
|
//WWDEBUG_WARNING(("result.StartBad\n"));
|
|
//PhysicsSceneClass::Get_Instance()->Add_Debug_AABox(box,Vector3(1,0,0));
|
|
VERBOSE_LOG(("StartBad!\r\n"));
|
|
done = true;
|
|
|
|
} else {
|
|
|
|
if (result.Fraction == 1.0f) {
|
|
|
|
/*
|
|
** the move was successful, accept it.
|
|
*/
|
|
VERBOSE_LOG(("Entire move accepted.\r\n"));
|
|
State.Position += move;
|
|
|
|
done = true;
|
|
moved = true;
|
|
|
|
} else {
|
|
|
|
VERBOSE_LOG(("Collided with %s. normal=(%f,%f,%f)\r\n",test.CollidedRenderObj->Get_Name(),result.Normal.X,result.Normal.Y,result.Normal.Z));
|
|
collided = true;
|
|
|
|
/*
|
|
** How far should we stay away from this obstacle
|
|
*/
|
|
float epsilon = GROUND_EPSILON;
|
|
if (result.Normal.Z < SlideNormalZ) { // TODO: base this on surface type?
|
|
epsilon = WALL_EPSILON;
|
|
}
|
|
|
|
/*
|
|
** Try to move up to the obstacle
|
|
*/
|
|
float move_len = result.Fraction * move.Length();
|
|
if (move_len > epsilon) {
|
|
move_len -= epsilon;
|
|
Vector3 direction = move;
|
|
direction.Normalize();
|
|
|
|
State.Position += move_len * direction;
|
|
|
|
move -= result.Fraction * move;
|
|
dt -= result.Fraction * dt;
|
|
moved = true;
|
|
contact_count = 0; // since we moved, reset the contacts
|
|
}
|
|
|
|
/*
|
|
** If there is no time remaining, no movement remaining,
|
|
** or we're not allowed to "slide" along contacts then we're done
|
|
*/
|
|
if ( (dt < WWMATH_EPSILON) ||
|
|
(move.Length2() < WWMATH_EPSILON) ||
|
|
(allow_sliding == false) )
|
|
{
|
|
|
|
/*
|
|
** we're close enough to done
|
|
*/
|
|
done = true;
|
|
|
|
} else {
|
|
|
|
/*
|
|
** We hit something, call the collision callbacks.
|
|
*/
|
|
WWASSERT(test.CollidedPhysObj != NULL);
|
|
CollisionReactionType reaction = COLLISION_REACTION_DEFAULT;
|
|
|
|
CollisionEventClass event;
|
|
event.OtherObj = test.CollidedPhysObj;
|
|
reaction |= Collision_Occurred(event);
|
|
event.OtherObj = this;
|
|
reaction |= test.CollidedPhysObj->Collision_Occurred(event);
|
|
|
|
if (reaction & COLLISION_REACTION_NO_BOUNCE) {
|
|
|
|
/*
|
|
** If we're instructed to not bounce, just erase the remaining time
|
|
*/
|
|
dt = 0.0f;
|
|
|
|
} else {
|
|
|
|
/*
|
|
** assume we're going to have to clip the move
|
|
*/
|
|
bool clip_move = true;
|
|
|
|
/*
|
|
** PUSH: Try to push the obstacle
|
|
*/
|
|
if (clip_move) {
|
|
Phys3Class * p3obj = test.CollidedPhysObj->As_Phys3Class();
|
|
|
|
if (p3obj != NULL) {
|
|
|
|
/*
|
|
** Compute a move for the object we're contacting which makes it slide out
|
|
** of our way if we're moving at an angle to it.
|
|
*/
|
|
Vector3 dc;
|
|
Vector3::Subtract(p3obj->Get_Position(),State.Position,&dc);
|
|
dc.Normalize();
|
|
float fraction = 1.0f;
|
|
if ((Mass > WWMATH_EPSILON) && (p3obj->Get_Mass() > WWMATH_EPSILON)) {
|
|
fraction = (Mass / (Mass + p3obj->Get_Mass()));
|
|
}
|
|
dc *= fraction * move.Length();
|
|
|
|
Dec_Ignore_Counter();
|
|
if (p3obj->Collide_Move(dc,dt)) {
|
|
clip_move = false;
|
|
}
|
|
Inc_Ignore_Counter();
|
|
}
|
|
}
|
|
|
|
/*
|
|
** SHATTER: If the mesh is shatterable, shatter it. We are doing this so that
|
|
** shatterable meshes are purely cosmetic for multiplay.
|
|
*/
|
|
MeshClass * mesh = NULL;
|
|
if ((test.CollidedRenderObj != NULL) && (test.CollidedRenderObj->Class_ID() == RenderObjClass::CLASSID_MESH)) {
|
|
mesh = (MeshClass*)test.CollidedRenderObj;
|
|
}
|
|
|
|
if ( (mesh != NULL) &&
|
|
(mesh->Get_W3D_Flags() & W3D_MESH_FLAG_SHATTERABLE) &&
|
|
(mesh->Is_Not_Hidden_At_All()))
|
|
{
|
|
|
|
PhysicsSceneClass::Get_Instance()->Shatter_Mesh( mesh,
|
|
State.Position,
|
|
result.Normal,
|
|
State.Velocity );
|
|
if (Observer != NULL) {
|
|
Observer->Object_Shattered_Something(this,test.CollidedPhysObj,result.SurfaceType);
|
|
}
|
|
|
|
clip_move = false;
|
|
}
|
|
|
|
|
|
/*
|
|
** CLIP: If the obstacle didn't move out of our way and we didn't step over it,
|
|
** add the new contact and adjust our move to not hit it again
|
|
*/
|
|
if (clip_move) {
|
|
|
|
if (contact_count < MAX_CONTACTS) {
|
|
|
|
Vector3 normal = result.Normal;
|
|
if ( (normal.Z < SlideNormalZ) &&
|
|
(normal.Z > 0.0f) &&
|
|
(GroundState.OnGround) &&
|
|
(GroundState.Normal.Z > SlideNormalZ) &&
|
|
(MoveMode == BALLISTIC_MOVE))
|
|
{
|
|
normal.Z = 0.0f;
|
|
normal.Normalize();
|
|
}
|
|
contacts[contact_count++] = normal;
|
|
DEBUG_RENDER_VECTOR(State.Position,1.5*normal,CONTACT_COLOR);
|
|
|
|
} else {
|
|
|
|
VERBOSE_LOG(("exceeded max contacts in Phys3::Apply_Move! position: %f, %f, %f\r\n",State.Position.X,State.Position.Y,State.Position.Z));
|
|
done = true;
|
|
|
|
}
|
|
|
|
Clip_Move(contacts,contact_count,&move);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Dec_Ignore_Counter();
|
|
|
|
return moved;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Debug_Verify_Position -- Verify that I am in a valid position *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
inline bool Phys3Class::Debug_Verify_Position(void)
|
|
{
|
|
#ifdef VERBOSE_LOGGING
|
|
PhysicsSceneClass * scene = PhysicsSceneClass::Get_Instance();
|
|
|
|
AABoxClass box;
|
|
Compute_WS_Collision_Box(State,&box);
|
|
|
|
NonRefPhysListClass list;
|
|
|
|
PhysAABoxIntersectionTestClass test(box,Get_Collision_Group(),COLLISION_TYPE_PHYSICAL,&list);
|
|
|
|
Inc_Ignore_Counter();
|
|
bool intersecting = scene->Intersection_Test(test);
|
|
Dec_Ignore_Counter();
|
|
|
|
if (intersecting) {
|
|
VERBOSE_LOG((" %s is intersecting %s!\r\n",Model->Get_Name(),list.Peek_Head()->Peek_Model()->Get_Name()));
|
|
} else {
|
|
VERBOSE_LOG((" %s not intersecting\r\n",Model->Get_Name()));
|
|
}
|
|
return intersecting;
|
|
#else
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Snap_To_Ground -- Following a move, snap back down to the ground *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Snap_To_Ground(const Vector3 & actual_move,bool was_stepping)
|
|
{
|
|
if (Get_Flag(ASLEEP)) {
|
|
return;
|
|
}
|
|
|
|
VERBOSE_LOG(("Phys3::Snap_To_Ground\r\n"));
|
|
|
|
/*
|
|
** Call this if you started a timestep on the ground and want to stay on the ground...
|
|
** If we were on the ground, we snap down to the ground to ensure that the object does not
|
|
** fly unrealistically off edges, etc. The logic works like this:
|
|
** - the object may have to snap at least back down to the same altitude it started at
|
|
** - in addition to this, we want to keep the object "stuck" on slopes up to SlideAngle
|
|
** - so, cast its box down this much: delta_z + delta_xy * tan(SlideAngle)
|
|
** - if what we hit has a slope that less than SlideAngle, snap down!
|
|
*/
|
|
float delta_z = actual_move.Z;
|
|
float delta_xy = WWMath::Sqrt(actual_move.X * actual_move.X + actual_move.Y * actual_move.Y);
|
|
|
|
/*
|
|
** If we don't need to snap down, just return
|
|
*/
|
|
if (delta_z + delta_xy > 0.0f) {
|
|
|
|
float snap_dist = delta_z + delta_xy * SlideAngleTan + GROUND_DISTANCE;
|
|
|
|
Inc_Ignore_Counter();
|
|
|
|
AABoxClass box;
|
|
Compute_WS_Collision_Box(State,&box);
|
|
CastResultStruct result;
|
|
PhysAABoxCollisionTestClass test(box,Vector3(0,0,-snap_dist),&result,Get_Collision_Group());
|
|
PhysicsSceneClass::Get_Instance()->Cast_AABox(test);
|
|
|
|
if ((result.Fraction > 0.0f) && (result.Fraction <= 1.0f) && (!result.StartBad)) {
|
|
|
|
float snap_down = 0;
|
|
if (result.Normal.Z > SlideNormalZ) {
|
|
|
|
/*
|
|
** Case 1: ground is within snap distance and that ground is a walkable slope
|
|
** Snap down to within GROUND_EPSILON of the ground.
|
|
*/
|
|
snap_down = snap_dist * result.Fraction;
|
|
if (snap_down > GROUND_EPSILON) {
|
|
State.Position.Z -= snap_down - GROUND_EPSILON;
|
|
}
|
|
OnGround = true;
|
|
Link_To_Carrier(test.CollidedPhysObj,test.CollidedRenderObj);
|
|
|
|
DEBUG_RENDER_VECTOR(State.Position,result.Normal,GROUND_COLOR);
|
|
VERBOSE_LOG((" now above walkable slope, snapped down %f\r\n",snap_down - GROUND_EPSILON));
|
|
|
|
} else {
|
|
|
|
/*
|
|
** Case 2: ground is within snap distance but it is not a walkable slope
|
|
** If we were stepping, we should snap down STEP_HEIGHT if we can.
|
|
*/
|
|
OnGround = false;
|
|
if (was_stepping) {
|
|
// (gth) just let the character snap down to slopes too!
|
|
//snap_down = WWMath::Min(STEP_HEIGHT,snap_dist * result.Fraction);
|
|
if (snap_down > GROUND_EPSILON) {
|
|
State.Position.Z -= snap_down - GROUND_EPSILON;
|
|
}
|
|
}
|
|
|
|
Link_To_Carrier(NULL);
|
|
|
|
VERBOSE_LOG((" now above non-walkable slope, snapped only up to step-height %f\r\n",snap_down-GROUND_EPSILON));
|
|
}
|
|
|
|
} else {
|
|
|
|
/*
|
|
** Case 3: ground is not within snap distance.
|
|
** If we were stepping, snap back down the step distance.
|
|
*/
|
|
OnGround = false;
|
|
if (was_stepping) {
|
|
State.Position.Z -= STEP_HEIGHT;
|
|
}
|
|
|
|
VERBOSE_LOG((" now airborne\r\n"));
|
|
}
|
|
|
|
/*
|
|
** Update the GroundState
|
|
*/
|
|
GroundState.Init_From_Collision_Result(test,GROUND_EPSILON);
|
|
|
|
Dec_Ignore_Counter();
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Clip_Move -- Clip a move vector by all of the active contact normals *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Clip_Move(const Vector3 * contacts,int contact_count,Vector3 * move)
|
|
{
|
|
WWASSERT(contacts != NULL);
|
|
VERBOSE_LOG(("Phys3::Clip_Move\r\n"));
|
|
|
|
#ifdef WWDEBUG
|
|
float move_len2 = move->Length2();
|
|
#endif
|
|
|
|
// Loop over all of the contacts.
|
|
// On each one we modify our move vector to parallel that plane.
|
|
// Immediately after each modification, we check if the other planes are a problem.
|
|
// At the first time all planes are satisfied, we're done
|
|
for (int i=0; i<contact_count; i++) {
|
|
|
|
#ifdef WWDEBUG
|
|
if (fabs(contacts[i].Length() - 1.0f) > WWMATH_EPSILON) {
|
|
WWDEBUG_SAY(("Bad Contact Normal: %f %f %f Length = %f\r\n",contacts[i].X,contacts[i].Y,contacts[i].Z,contacts[i].Length()));
|
|
}
|
|
#endif
|
|
if (!( fabs(contacts[i].Length() - 1.0f) < WWMATH_EPSILON )) {
|
|
*move = Vector3(0,0,0);
|
|
return;
|
|
}
|
|
|
|
VERBOSE_LOG((" normal[%d] = %f %f %f\r\n",i,contacts[i].X,contacts[i].Y,contacts[i].Z));
|
|
|
|
// push the velocity a little bit away from the plane
|
|
float dot = Vector3::Dot_Product(*move,contacts[i]);
|
|
if (dot < 0.0f) {
|
|
Vector3 adjustment = 1.01f * dot * contacts[i];
|
|
*move -= adjustment;
|
|
//WWASSERT(Vector3::Dot_Product(*move,contacts[i]) >= 0.0f);
|
|
}
|
|
|
|
for (int j=0; j<contact_count; j++) {
|
|
float check = Vector3::Dot_Product(*move,contacts[j]);
|
|
if (check < 0.0f) {
|
|
break; // this contact isn't happy yet... keep choppin.
|
|
}
|
|
}
|
|
if (j == contact_count) {
|
|
break; // all contacts are happy!
|
|
}
|
|
}
|
|
|
|
#ifdef WWDEBUG
|
|
float new_move_len2 = move->Length2();
|
|
WWASSERT(new_move_len2 <= move_len2 + WWMATH_EPSILON);
|
|
#endif
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Get_Shadow_Blob_Box -- Returns the bounding box to use for blob shadows *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Get_Shadow_Blob_Box(AABoxClass * set_obj_space_box)
|
|
{
|
|
WWASSERT(set_obj_space_box != NULL);
|
|
*set_obj_space_box = CollisionBox;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Get_Collision_Box -- returns the current world space collision box *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 1/4/2001 gth : Created. *
|
|
*=============================================================================================*/
|
|
void Phys3Class::Get_Collision_Box(AABoxClass * set_box)
|
|
{
|
|
WWASSERT(set_box != NULL);
|
|
Compute_WS_Collision_Box(State,set_box);
|
|
}
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Push -- Move in response to an animated object colliding with me *
|
|
* *
|
|
* This function is called when StaticAnimPhys objects detect a collision. The key is that *
|
|
* we don't want to slide along any contacts; just move as far as we can along the given *
|
|
* move vector. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Push(const Vector3 & move)
|
|
{
|
|
Vector3 old_pos = State.Position;
|
|
Apply_Move(move,1.0f,false,false);
|
|
Update_Transform(true);
|
|
Update_Cull_Box();
|
|
|
|
Vector3 error = (State.Position - old_pos) - move;
|
|
|
|
#if VERBOSE_LOGGING
|
|
Vector3 actual_move = State.Position - old_pos;
|
|
bool success = (State.Position - old_pos) == move;
|
|
VERBOSE_LOG((" Phys3::Push. Apparent success: %d Error: %f %f %f\r\n",success, error.X,error.Y,error.Z));
|
|
#endif
|
|
|
|
return (error.Length2() < WWMATH_EPSILON * WWMATH_EPSILON);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Collide -- Move in response to a collision *
|
|
* *
|
|
* This function is similar to the 'Push' function, however the phys3 object is allowed to *
|
|
* slide along geometry in order to get out of the way. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 1/4/2001 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Collide(const Vector3 & move)
|
|
{
|
|
Vector3 old_pos = State.Position;
|
|
Collide_Move(move,1.0f);
|
|
return ((State.Position - old_pos) == move);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Can_Teleport -- Checks if this object can occupy the specified position *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Can_Teleport(const Matrix3D &test_tm, bool check_dyn_only, NonRefPhysListClass * result_list)
|
|
{
|
|
//
|
|
// Determine what the world-space collision box for
|
|
// this object would be at the given transform.
|
|
//
|
|
StateStruct test_state;
|
|
test_state.Position.Set (test_tm.Get_Translation ());
|
|
test_state.Velocity.Set (0, 0, 0);
|
|
|
|
AABoxClass collision_box;
|
|
Compute_WS_Collision_Box (test_state, &collision_box);
|
|
|
|
//
|
|
// Intersect the box with the world.
|
|
//
|
|
PhysAABoxIntersectionTestClass test(collision_box,
|
|
Get_Collision_Group(),
|
|
COLLISION_TYPE_PHYSICAL,
|
|
result_list);
|
|
|
|
if (check_dyn_only) {
|
|
test.CheckStaticObjs = false;
|
|
}
|
|
|
|
Inc_Ignore_Counter ();
|
|
bool intersect = PhysicsSceneClass::Get_Instance ()->Intersection_Test(test);
|
|
Dec_Ignore_Counter ();
|
|
|
|
return (intersect == false);
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Can_Teleport_And_Stand -- Checks if this object can occupy the specified pos *
|
|
* *
|
|
* Also returns an alternate position if the object cannot occupy the specified position. *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Can_Teleport_And_Stand(const Matrix3D &test_tm, Matrix3D *new_tm)
|
|
{
|
|
//
|
|
// Determine what the world-space collision box for
|
|
// this object would be at the given transform.
|
|
//
|
|
StateStruct test_state;
|
|
test_state.Position.Set (test_tm.Get_Translation ());
|
|
test_state.Velocity.Set (0, 0, 0);
|
|
|
|
AABoxClass collision_box;
|
|
Compute_WS_Collision_Box (test_state, &collision_box);
|
|
|
|
Inc_Ignore_Counter ();
|
|
|
|
//
|
|
// Cast a box 'down' from this location.
|
|
// This checks to make sure it doesn't intersect anything at its
|
|
// starting position, and its not off the edge of a cliff
|
|
//
|
|
CastResultStruct result;
|
|
Vector3 move_vector (0, 0, -5.0F);
|
|
PhysAABoxCollisionTestClass test( collision_box,
|
|
move_vector,
|
|
&result,
|
|
0,
|
|
COLLISION_TYPE_PHYSICAL);
|
|
|
|
PhysicsSceneClass::Get_Instance ()->Cast_AABox (test);
|
|
|
|
Dec_Ignore_Counter ();
|
|
|
|
//
|
|
// Did the box land on a suface we can stand on?
|
|
//
|
|
bool retval = false;
|
|
if ((result.StartBad == false) && ((result.Fraction == 1.0F) || (result.Normal.Z >= SlideNormalZ))) {
|
|
|
|
//
|
|
// Calculate a new transform for the object
|
|
//
|
|
Vector3 new_pos = test_tm.Get_Translation () + (result.Fraction * move_vector);
|
|
(*new_tm) = test_tm;
|
|
new_tm->Set_Translation (new_pos);
|
|
|
|
retval = true;
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
* Phys3Class::Find_Teleport_Location -- Searches for a valid position near the given one *
|
|
* *
|
|
* INPUT: *
|
|
* *
|
|
* OUTPUT: *
|
|
* *
|
|
* WARNINGS: *
|
|
* *
|
|
* HISTORY: *
|
|
* 9/16/2000 gth : Created. *
|
|
*=============================================================================================*/
|
|
bool Phys3Class::Find_Teleport_Location
|
|
(
|
|
const Vector3 & start,
|
|
float radius,
|
|
Vector3 * out
|
|
)
|
|
{
|
|
bool retval = false;
|
|
|
|
//
|
|
// Test the actual point the caller passed to us.
|
|
//
|
|
Matrix3D initial_tm (start);
|
|
if (Can_Teleport_And_Stand (initial_tm, &initial_tm)) {
|
|
(*out) = initial_tm.Get_Translation ();
|
|
return true;
|
|
}
|
|
|
|
const int MAX_ATTEMPTS = 10;
|
|
const float MIN_DIST = 0.1F;
|
|
|
|
//
|
|
// Try a number of times to find a valid location
|
|
//
|
|
for (int index = 0; !retval && index < MAX_ATTEMPTS; index ++) {
|
|
|
|
//
|
|
// Get a random distance and direction to make the attempt in
|
|
//
|
|
float dist = WWMath::Random_Float (MIN_DIST, radius);
|
|
float angle = WWMath::Random_Float (0, DEG_TO_RADF (360));
|
|
float z_delta = dist * SlideNormalZ;
|
|
|
|
//
|
|
// Calculate what the new transform would be at this location
|
|
//
|
|
Vector3 test_pos;
|
|
test_pos.X = start.X + WWMath::Cos (angle) * dist;
|
|
test_pos.Y = start.Y + WWMath::Sin (angle) * dist;
|
|
test_pos.Z = start.Z + z_delta;
|
|
Matrix3D test_tm (test_pos);
|
|
|
|
//
|
|
// Do the test to make sure this is a valid teleport location.
|
|
//
|
|
Matrix3D new_tm (1);
|
|
if (Can_Teleport_And_Stand (test_tm, &new_tm)) {
|
|
(*out) = new_tm.Get_Translation ();
|
|
retval = true;
|
|
}
|
|
}
|
|
|
|
return retval;
|
|
}
|
|
|
|
bool Phys3Class::Can_Move_To(const Matrix3D &new_tm)
|
|
{
|
|
//
|
|
// Do a sweep from our current position to the new position.
|
|
//
|
|
Vector3 start_position = Get_Position();
|
|
Vector3 new_position = new_tm.Get_Translation();
|
|
Vector3 move = new_position - start_position;
|
|
|
|
AABoxClass box;
|
|
Compute_WS_Collision_Box(State,&box);
|
|
|
|
Inc_Ignore_Counter();
|
|
CastResultStruct result;
|
|
PhysAABoxCollisionTestClass test( box,
|
|
move,
|
|
&result,
|
|
0,
|
|
COLLISION_TYPE_PHYSICAL );
|
|
PhysicsSceneClass::Get_Instance()->Cast_AABox(test);
|
|
Dec_Ignore_Counter();
|
|
|
|
return (result.Fraction == 1.0f);
|
|
}
|
|
|
|
|
|
|
|
void Phys3Class::Assert_State_Valid(void)
|
|
{
|
|
AABoxClass box;
|
|
Compute_WS_Collision_Box(State,&box);
|
|
|
|
CastResultStruct result;
|
|
PhysAABoxCollisionTestClass test( box,
|
|
Vector3(0,0,0),
|
|
&result,
|
|
0,
|
|
COLLISION_TYPE_PHYSICAL );
|
|
|
|
Inc_Ignore_Counter();
|
|
PhysicsSceneClass::Get_Instance()->Cast_AABox(test);
|
|
Dec_Ignore_Counter();
|
|
|
|
#if VERBOSE_LOGGING
|
|
if (result.StartBad) {
|
|
VERBOSE_LOG(("Phys3 Intersection! ModelName = %s\r\n",Model->Get_Name()));
|
|
}
|
|
#endif
|
|
|
|
WWASSERT(State.Position.Is_Valid());
|
|
WWASSERT(State.Velocity.Is_Valid());
|
|
|
|
|
|
}
|
|
|
|
void Phys3Class::Network_State_Update(const Vector3 & pos,const Vector3 & vel)
|
|
{
|
|
Vector3 delta = pos - State.Position;
|
|
Set_Position(pos);
|
|
Set_Velocity(vel);
|
|
Snap_To_Ground(delta,false);
|
|
Update_Transform( true );
|
|
}
|
|
|
|
|
|
void Phys3Class::Network_Latency_State_Update(const Vector3 & net_pos,const Vector3 & net_vel)
|
|
{
|
|
DEBUG_RENDER_AABOX(AABoxClass(CollisionBox.Center + net_pos,CollisionBox.Extent),Vector3(0,1,0),0.25f);
|
|
|
|
/*
|
|
** Allocate a history object if needed
|
|
*/
|
|
if (History == NULL) {
|
|
History = new Phys3HistoryClass;
|
|
History->Init(net_pos,net_vel);
|
|
}
|
|
WWASSERT(History != NULL);
|
|
|
|
/*
|
|
** Search our history to find the point nearest this server update
|
|
*/
|
|
Vector3 old_pos;
|
|
History->Find_Nearest_Point(net_pos,net_vel,&old_pos);
|
|
LatencyError = net_pos - old_pos;
|
|
LastKnownPosition = net_pos;
|
|
LastKnownVelocity = net_vel;
|
|
}
|
|
|
|
void Phys3Class::Network_Teleport_Correction(void)
|
|
{
|
|
Vector3 new_pos = LastKnownPosition;
|
|
Vector3 correction = new_pos - State.Position;
|
|
|
|
Set_Position(new_pos);
|
|
Set_Velocity(LastKnownVelocity);
|
|
#if 0
|
|
History->Apply_Correction(correction);
|
|
#else
|
|
History->Init(LastKnownPosition,LastKnownVelocity);
|
|
#endif
|
|
LatencyError.Set(0,0,0);
|
|
}
|
|
|
|
|
|
/*************************************************************************************
|
|
**
|
|
** Phys3Class Save-Load support
|
|
**
|
|
*************************************************************************************/
|
|
|
|
const PersistFactoryClass & Phys3Class::Get_Factory (void) const
|
|
{
|
|
return _Phys3Factory;
|
|
}
|
|
|
|
bool Phys3Class::Save (ChunkSaveClass &csave)
|
|
{
|
|
csave.Begin_Chunk(PHYS3_CHUNK_MOVEABLEPHYS);
|
|
MoveablePhysClass::Save(csave);
|
|
csave.End_Chunk();
|
|
|
|
csave.Begin_Chunk(PHYS3_CHUNK_VARIABLES);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_COLLISION_AABOX,CollisionBox);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_ONGROUND,OnGround);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_INCOLLISION,InCollision);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_HEADING,Heading);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_NORMSPEED,NormSpeed);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_SLIDEANGLE,SlideAngle);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_SLIDENORMALZ,SlideNormalZ);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_SLIDEANGLETAN,SlideAngleTan);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_STEPHEIGHT,StepHeight);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_MOVEMODE,MoveMode);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_POSITION,State.Position);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3_VARIABLE_VELOCITY,State.Velocity);
|
|
csave.End_Chunk();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Phys3Class::Load (ChunkLoadClass &cload)
|
|
{
|
|
while (cload.Open_Chunk()) {
|
|
|
|
switch(cload.Cur_Chunk_ID())
|
|
{
|
|
case PHYS3_CHUNK_MOVEABLEPHYS:
|
|
MoveablePhysClass::Load(cload);
|
|
break;
|
|
|
|
case PHYS3_CHUNK_VARIABLES:
|
|
while (cload.Open_Micro_Chunk()) {
|
|
switch(cload.Cur_Micro_Chunk_ID()) {
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_COLLISION_AABOX,CollisionBox);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_ONGROUND,OnGround);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_INCOLLISION,InCollision);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_HEADING,Heading);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_NORMSPEED,NormSpeed);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_SLIDEANGLE,SlideAngle);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_SLIDENORMALZ,SlideNormalZ);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_SLIDEANGLETAN,SlideAngleTan);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_STEPHEIGHT,StepHeight);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_MOVEMODE,MoveMode);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_POSITION,State.Position);
|
|
READ_MICRO_CHUNK(cload,PHYS3_VARIABLE_VELOCITY,State.Velocity);
|
|
}
|
|
cload.Close_Micro_Chunk();
|
|
}
|
|
break;
|
|
default:
|
|
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
|
|
break;
|
|
}
|
|
|
|
cload.Close_Chunk();
|
|
}
|
|
|
|
Invalidate_Ground_State();
|
|
SaveLoadSystemClass::Register_Post_Load_Callback(this);
|
|
return true;
|
|
}
|
|
|
|
void Phys3Class::On_Post_Load (void)
|
|
{
|
|
MoveablePhysClass::On_Post_Load();
|
|
Update_Cached_Model_Parameters();
|
|
}
|
|
|
|
|
|
|
|
/*************************************************************************************
|
|
**
|
|
** Phys3Class::GroundStateStruct Implementation
|
|
**
|
|
*************************************************************************************/
|
|
Phys3Class::GroundStateStruct::GroundStateStruct(void) :
|
|
IsDirty(true),
|
|
OnGround(false),
|
|
OnDynamicObj(false),
|
|
SurfaceType(SURFACE_TYPE_DEFAULT),
|
|
Height(0.0f),
|
|
Normal(0,0,1),
|
|
Down(1,0,0),
|
|
GroundObject(NULL),
|
|
GroundRenderObject(NULL)
|
|
{
|
|
Reset();
|
|
}
|
|
|
|
void Phys3Class::GroundStateStruct::Reset(void)
|
|
{
|
|
IsDirty = true;
|
|
OnGround = false;
|
|
OnDynamicObj = false;
|
|
SurfaceType = SURFACE_TYPE_DEFAULT;
|
|
Height = 0.0f;
|
|
Normal.Set(0,0,1);
|
|
Down.Set(1,0,0);
|
|
GroundObject = NULL;
|
|
GroundRenderObject = NULL;
|
|
}
|
|
|
|
void Phys3Class::GroundStateStruct::Init_From_Collision_Result(PhysAABoxCollisionTestClass & test,float height)
|
|
{
|
|
Reset();
|
|
IsDirty = false;
|
|
|
|
if (test.Result->Fraction < 1.0f) {
|
|
OnGround = true;
|
|
SurfaceType = test.Result->SurfaceType;
|
|
Normal = test.Result->Normal;
|
|
GroundObject = test.CollidedPhysObj;
|
|
GroundRenderObject = test.CollidedRenderObj;
|
|
Height = height;
|
|
|
|
if (GroundObject != NULL) {
|
|
if ( (GroundObject->As_HumanPhysClass() != NULL) ||
|
|
(GroundObject->As_RigidBodyClass() != NULL))
|
|
{
|
|
OnDynamicObj = true;
|
|
}
|
|
}
|
|
|
|
if (test.Result->StartBad) {
|
|
|
|
IsDirty = true;
|
|
Normal.Set(0,0,1);
|
|
// VERBOSE_LOG(("ERROR - intersecting object: %s!\r\n",GroundRenderObject->Get_Name()));
|
|
|
|
} else {
|
|
|
|
if (Normal.Length2() <= 0.0f) {
|
|
WWDEBUG_SAY(("ERROR - detected non-unit normal!\r\n"));
|
|
}
|
|
|
|
// compute the down vector for this plane
|
|
// down = N x -Z x N
|
|
Vector3 tmp;
|
|
Vector3::Cross_Product(Normal,Vector3(0,0,-1),&tmp);
|
|
Vector3::Cross_Product(tmp,Normal,&(Down));
|
|
Down.Normalize();
|
|
|
|
// VERBOSE_LOG((" on ground, normal=(%f,%f,%f)sliding=%d\r\n",gs->Normal.X,gs->Normal.Y,gs->Normal.Z,(gs->Normal.Z>SlideNormalZ ? 0 : 1)));
|
|
}
|
|
|
|
} else {
|
|
OnGround = false;
|
|
OnDynamicObj = false;
|
|
GroundObject = NULL;
|
|
GroundRenderObject = NULL;
|
|
SurfaceType = SURFACE_TYPE_DEFAULT;
|
|
}
|
|
}
|
|
|
|
|
|
/***********************************************************************************************
|
|
**
|
|
** Phys3DefClass Implementation
|
|
**
|
|
***********************************************************************************************/
|
|
|
|
/*
|
|
** Declare a PersistFactory for Phys3DefClasses
|
|
*/
|
|
SimplePersistFactoryClass<Phys3DefClass,PHYSICS_CHUNKID_PHYS3DEF> _Phys3DefFactory;
|
|
|
|
|
|
/*
|
|
** Chunk ID's used by MoveablePhysDefClass
|
|
*/
|
|
enum
|
|
{
|
|
PHYS3DEF_CHUNK_MOVEABLEPHYSDEF = 0x04486000, // (parent class)
|
|
PHYS3DEF_CHUNK_VARIABLES,
|
|
|
|
PHYS3DEF_VARIABLE_NORMSPEED = 0x00,
|
|
PHYS3DEF_VARIABLE_SLIDEANGLE,
|
|
PHYS3DEF_VARIABLE_STEPHEIGHT,
|
|
};
|
|
|
|
|
|
Phys3DefClass::Phys3DefClass(void) :
|
|
NormSpeed(DEFAULT_NORMALIZED_SPEED),
|
|
SlideAngle(DEFAULT_SLIDE_ANGLE),
|
|
StepHeight(DEFAULT_STEP_HEIGHT)
|
|
{
|
|
// make our parameters editable!
|
|
EDITABLE_PARAM(Phys3DefClass, ParameterClass::TYPE_FLOAT, NormSpeed);
|
|
ANGLE_EDITABLE_PARAM(Phys3DefClass, SlideAngle, DEG_TO_RADF(0.0f), DEG_TO_RADF(90.0f));
|
|
FLOAT_EDITABLE_PARAM(Phys3DefClass, StepHeight, 0.0f, 10.0f);
|
|
}
|
|
|
|
|
|
const PersistFactoryClass & Phys3DefClass::Get_Factory (void) const
|
|
{
|
|
return _Phys3DefFactory;
|
|
}
|
|
|
|
uint32 Phys3DefClass::Get_Class_ID (void) const
|
|
{
|
|
return CLASSID_PHYS3DEF;
|
|
}
|
|
|
|
PersistClass * Phys3DefClass::Create(void) const
|
|
{
|
|
Phys3Class * obj = NEW_REF(Phys3Class,());
|
|
obj->Init(*this);
|
|
return obj;
|
|
}
|
|
|
|
bool Phys3DefClass::Save(ChunkSaveClass &csave)
|
|
{
|
|
csave.Begin_Chunk(PHYS3DEF_CHUNK_MOVEABLEPHYSDEF);
|
|
MoveablePhysDefClass::Save(csave);
|
|
csave.End_Chunk();
|
|
|
|
csave.Begin_Chunk(PHYS3DEF_CHUNK_VARIABLES);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3DEF_VARIABLE_NORMSPEED,NormSpeed);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3DEF_VARIABLE_SLIDEANGLE,SlideAngle);
|
|
WRITE_MICRO_CHUNK(csave,PHYS3DEF_VARIABLE_STEPHEIGHT,StepHeight);
|
|
csave.End_Chunk();
|
|
return true;
|
|
}
|
|
|
|
|
|
bool Phys3DefClass::Load(ChunkLoadClass &cload)
|
|
{
|
|
while (cload.Open_Chunk()) {
|
|
|
|
switch(cload.Cur_Chunk_ID()) {
|
|
|
|
case PHYS3DEF_CHUNK_MOVEABLEPHYSDEF:
|
|
MoveablePhysDefClass::Load(cload);
|
|
break;
|
|
|
|
case PHYS3DEF_CHUNK_VARIABLES:
|
|
while (cload.Open_Micro_Chunk()) {
|
|
switch(cload.Cur_Micro_Chunk_ID()) {
|
|
READ_MICRO_CHUNK(cload,PHYS3DEF_VARIABLE_NORMSPEED,NormSpeed);
|
|
READ_MICRO_CHUNK(cload,PHYS3DEF_VARIABLE_SLIDEANGLE,SlideAngle);
|
|
READ_MICRO_CHUNK(cload,PHYS3DEF_VARIABLE_STEPHEIGHT,StepHeight);
|
|
}
|
|
cload.Close_Micro_Chunk();
|
|
}
|
|
break;
|
|
|
|
default:
|
|
WWDEBUG_SAY(("Unhandled Chunk: 0x%X File: %s Line: %d\r\n",cload.Cur_Chunk_ID(),__FILE__,__LINE__));
|
|
break;
|
|
}
|
|
|
|
cload.Close_Chunk();
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool Phys3DefClass::Is_Type(const char * type_name)
|
|
{
|
|
if (stricmp(type_name,Phys3DefClass::Get_Type_Name()) == 0) {
|
|
return true;
|
|
} else {
|
|
return MoveablePhysDefClass::Is_Type(type_name);
|
|
}
|
|
}
|