thug/Code/Sk/Objects/rail.cpp
2016-02-14 08:39:12 +11:00

3155 lines
95 KiB
C++

///////////////////////////////////////////////////////
// rail.cpp
#include <sk/objects/rail.h>
#include <core/defines.h>
#include <core/singleton.h>
#include <core/math.h>
#include <core/math/geometry.h>
#include <gel/modman.h>
#include <gel/scripting/script.h>
#include <gel/scripting/array.h>
#include <gel/scripting/symboltable.h>
#include <gel/scripting/struct.h>
#include <gel/scripting/utils.h>
#include <gel/environment/terrain.h>
#include <gel/collision/collision.h>
#include <gel/components/walkcomponent.h>
#include <gfx/nx.h>
#include <gfx/debuggfx.h>
#include <sk/engine/feeler.h>
#include <sk/objects/trickobject.h>
#include <sk/modules/skate/skate.h>
#include <sk/ParkEditor2/ParkEd.h>
#include <sk/scripting/nodearray.h>
// REMOVE
#include <sk/objects/skater.h>
#ifdef __PLAT_NGPS__
#include <gfx/nxviewman.h> // for camera
#include <gfx/ngps/nx/line.h>
#include <gfx/ngps/nx/render.h>
#endif
//#define DEBUG_EDITOR_RAILS
namespace Obj
{
void Rail_ComputeBB(Mth::Vector &pos1, Mth::Vector &pos2, Mth::Vector &bb_min, Mth::Vector &bb_max)
{
float x0 = pos1.GetX();
float x1 = pos2.GetX();
float min_x, max_x;
if (x0 < x1)
{
min_x = x0; max_x = x1;
}
else
{
min_x = x1; max_x = x0;
}
float y0 = pos1.GetY();
float y1 = pos2.GetY();
float min_y, max_y;
if (y0 < y1)
{
min_y = y0; max_y = y1;
}
else
{
min_y = y1; max_y = y0;
}
float z0 = pos1.GetZ();
float z1 = pos2.GetZ();
float min_z, max_z;
if (z0 < z1)
{
min_z = z0; max_z = z1;
}
else
{
min_z = z1; max_z = z0;
}
bb_min.Set(min_x, min_y, min_z);
bb_max.Set(max_x, max_y, max_z);
}
CRailManager::CRailManager()
{
// mp_first_node = NULL;
mp_nodes = NULL;
mp_links = NULL;
m_num_nodes = 0;
m_is_transformed = false;
mp_node_array = NULL;
}
CRailManager::~CRailManager()
{
Cleanup();
}
void CRailManager::Cleanup()
{
// while (mp_first_node)
// {
// CRailNode *pNext = mp_first_node->m_pNext;
// delete mp_first_node;
// mp_first_node = pNext;
// }
m_num_nodes = 0;
Mem::Free(mp_nodes);
mp_nodes = NULL;
m_is_transformed = false;
// Unhook from node array
mp_node_array = NULL;
}
#define CHECKSUM_TERRAIN_TYPE 0x54cf8532 // "terrainType"
// Given two rail nodes that should be linked in a rail segment
// then link them up as well as we are able
// given that we only have a NEXT and a PREV pointer
// so we give precedence to links to nodes that
// are flagged "DefaultLine"
void CRailManager::NewLink(CRailNode *p_from, CRailNode *p_to)
{
// one of our links links to this node
// so we need to establish next and prev links
// or if they are established, then possibly override them
if (!p_from->m_pNextLink)
{
// this is the first time we are linking to something
p_from->m_pNextLink = p_to;
Rail_ComputeBB(p_from->m_pos, p_to->m_pos, p_from->m_BBMin, p_from->m_BBMax);
}
else
{
// already linked to something, so only override it
// if the new node is not DEFAULT_LINE
if (p_to->GetFlag(DEFAULT_LINE))
{
// This new link is DEFAULT_LINE
// so, as long as the previous link was not, we can override it
Dbg_MsgAssert(!p_from->m_pNextLink->GetFlag(DEFAULT_LINE),
("Node %d links to both %d and %d and both are DEFAULT_LINE",
p_from->m_node,p_from->m_pNextLink->m_node,p_to->m_node));
// Overriding an existing NEXT link
p_from->m_pNextLink = p_to;
Rail_ComputeBB(p_from->m_pos, p_to->m_pos, p_from->m_BBMin, p_from->m_BBMax);
}
else
{
// The new link is not DEFAULT_LINE
// if the old one was, then leave it
// if neither is, then leave it, and maybe print a warning?
}
}
// Now handle the PREV link back from p_to to p_from
if (!p_to->m_pPrevLink)
{
p_to->m_pPrevLink = p_from;
}
else
{
// something already linked to p_from via prev from p_to
if (p_from->GetFlag(DEFAULT_LINE))
{
// this is the default line, so make sure the old PREV node was not
Dbg_MsgAssert(!p_to->m_pPrevLink->GetFlag(DEFAULT_LINE),
("Node %d linked from both %d and %d and both are DEFAULT_LINE",
p_to->m_node,p_to->m_pPrevLink->m_node,p_from->m_node));
p_to->m_pPrevLink = p_from;
// Note, we don't re-calculate the bounding box here
// as that only applies to NEXT links
}
}
}
void CRailManager::AddRailNode(int node_number, Script::CStruct *p_node_struct)
{
// create a new rail node
// CRailNode *pRailNode = new CRailNode();
// Link into the head of the rail manager
// Note, this must be done before we attempt to link up with other nodes
// pRailNode->m_pNext = mp_first_node;
// mp_first_node = pRailNode;
CRailNode *pRailNode = &mp_nodes[m_current_node];
CRailLinks *pLinkNode = &mp_links[m_current_node++];
uint32 class_checksum;
p_node_struct->GetChecksum(CRCD(0x12b4e660, "class"), &class_checksum);
bool climbing = class_checksum == CRCD(0x30c19600, "ClimbingNode");
uint32 type_checksum;
p_node_struct->GetChecksum(CRCD(0x7321a8d6, "type"), &type_checksum);
pRailNode->m_pNextLink = NULL;
pRailNode->m_pPrevLink = NULL;
for (int i=0;i<MAX_RAIL_LINKS;i++)
{
pLinkNode->m_link[i] = -1; // say it's not linked to anything, initially
}
pRailNode->m_node = node_number; // The node_number is use primarily for calculating links
pRailNode->m_flags = 0; // all flags off by default
pRailNode->SetActive(p_node_struct->ContainsFlag( 0x7c2552b9 /*"CreatedAtStart"*/ )); // created at start or not?
// set the position from the node structure
SkateScript::GetPosition(p_node_struct,&pRailNode->m_pos);
if (p_node_struct->ContainsFlag( 0xf4c67f0f /*"LipOverride"*/))
{
pRailNode->m_flags.Set(LIP_OVERRIDE);
}
if (p_node_struct->ContainsFlag( 0x4de721de/*"DefaultLine"*/))
{
pRailNode->m_flags.Set(DEFAULT_LINE);
}
if (climbing)
{
pRailNode->m_flags.Set(ONLY_CLIMBING);
pRailNode->m_flags.Set(LADDER, type_checksum == CRCD(0xc84243da, "Ladder"));
}
pRailNode->m_flags.Set(NO_HANG_WITH_RIGHT_ALONG_RAIL, p_node_struct->ContainsFlag(CRCD(0xf6dd21ca, "HangLeft")));
pRailNode->m_flags.Set(NO_HANG_WITH_LEFT_ALONG_RAIL, p_node_struct->ContainsFlag(CRCD(0x5e9ce29b, "HangRight")));
pRailNode->m_flags.Set(NO_CLIMBING, p_node_struct->ContainsFlag(CRCD(0xf22bfc6d, "NoClimbing")));
if (climbing)
{
// set the orientation from the node structure
Mth::Vector angles;
SkateScript::GetAngles(p_node_struct, &angles);
Mth::Matrix matrix;
pRailNode->m_orientation = matrix.SetFromAngles(angles);
}
// no terrain types for climbing nodes
if (!climbing)
{
pRailNode->m_terrain_type = 0; // default terrain type...
uint32 terrain_checksum;
int terrain = 0;
p_node_struct->GetChecksum( CHECKSUM_TERRAIN_TYPE, &terrain_checksum, Script::ASSERT );
terrain = Env::CTerrainManager::sGetTerrainFromChecksum(terrain_checksum);
Dbg_MsgAssert(terrain >= 0 && terrain < 256,("Rail node %d, terrain type %d too big\n",node_number,terrain));
pRailNode->m_terrain_type = (uint8) terrain ;
}
int links = SkateScript::GetNumLinks(p_node_struct);
if (links)
{
Dbg_MsgAssert(links <= MAX_RAIL_LINKS,("Rail node %d has %d linkes, max is %d",node_number,links,MAX_RAIL_LINKS));
for (int i=0;i<links;i++)
{
pLinkNode->m_link[i] = SkateScript::GetLink(p_node_struct,i);
}
}
Rail_ComputeBB(pRailNode->m_pos, pRailNode->m_pos, pRailNode->m_BBMin, pRailNode->m_BBMax);
// Linking is now donw after they are all loaded
// Making it O(n) rather than O(n^2)
/*
// try going through all the nodes to find one linked to this one
// and to find the target node, if it exists
CRailNode *p_link_node = pRailNode->m_pNext;
while (p_link_node)
{
// Check links from the new node to all the existing nodes
for (int i=0;i<MAX_RAIL_LINKS;i++)
{
if (pRailNode->m_link[i] == p_link_node->m_node)
{
NewLink(pRailNode,p_link_node);
}
}
// check links from the existing nodes to the new node
for (int i=0;i<MAX_RAIL_LINKS;i++)
{
if (p_link_node->m_link[i] == pRailNode->m_node)
{
NewLink(p_link_node,pRailNode);
}
}
p_link_node = p_link_node->m_pNext;
}
*/
}
// returns a positive number that is the amount of an axis
// required to totally encompass the 1D line segments a-b and c-d
static inline float span_4(float a, float b, float c, float d)
{
float min = a;
if (b < min) min = b;
if (c < min) min = c;
if (d < min) min = d;
float max = a;
if (b > max) max = b;
if (c > max) max = c;
if (d > max) max = d;
return max-min;
}
// returns the amount two 1D line segments overlap
// works by adding together the length of both lines
// and then subtracting the "span" of the the min and max points of the lines
static inline float overlap(float a, float b, float c, float d)
{
return Mth::Abs(b-a) + Mth::Abs(d-c) - span_4(a,b,c,d);
}
static inline bool nearly_the_same(float a, float b, float c, float d, float nearly = 1.0f)
{
return ( Mth::Abs(a-b) < nearly
&& Mth::Abs(b-c) < nearly
&& Mth::Abs(c-d) < nearly);
}
void CRailManager::RemoveOverlapping()
{
printf ("Starting overlapping rail removal\n");
int removed =0;
// CRailNode *pRailNode = mp_first_node;
// while (pRailNode)
// {
for (int node=0;node<m_num_nodes;node++)
{
CRailNode *pRailNode = &mp_nodes[node];
if (pRailNode->m_pNextLink) // it's a segment
{
Mth::Vector start = pRailNode->m_pos;
Mth::Vector end = pRailNode->m_pNextLink->m_pos;
Mth::Vector dir = end-start;
dir.Normalize();
// we expand the bounding box, as they might not really be that close
Mth::Vector bb_min = pRailNode->m_BBMin;
bb_min[X] -= 16.0f;
bb_min[Y] -= 16.0f;
bb_min[Z] -= 16.0f;
Mth::Vector bb_max = pRailNode->m_BBMax;
bb_max[X] += 16.0f;
bb_max[Y] += 16.0f;
bb_max[Z] += 16.0f;
int check_node = node+1;
// CRailNode *pCheckNode = pRailNode->m_pNext;
while (check_node<m_num_nodes && pRailNode->IsActive())
{
CRailNode *pCheckNode = &mp_nodes[check_node];
if (pCheckNode->m_pNextLink) // it's a segment
{
// first check to see if bounding boxes overlap
if (!(pCheckNode->m_BBMin.GetX() > bb_max.GetX()))
if (!(pCheckNode->m_BBMax.GetX() < bb_min.GetX()))
if (!(pCheckNode->m_BBMin.GetZ() > bb_max.GetZ()))
if (!(pCheckNode->m_BBMax.GetZ() < bb_min.GetZ()))
if (!(pCheckNode->m_BBMin.GetY() > bb_max.GetY()))
if (!(pCheckNode->m_BBMax.GetY() < bb_min.GetY()))
{
// bounding boxes overlap
// check to see if rails are roughly parallel
Mth::Vector check_start = pCheckNode->m_pos;
Mth::Vector check_end = pCheckNode->m_pNextLink->m_pos;
Mth::Vector check_dir = check_end - check_start;
check_dir.Normalize();
float dot = Mth::DotProduct(dir, check_dir);
// printf ("Bounding Box Overlap, dot = %f\n",dot);
if (dot < -0.99f || dot > 0.99f)
{
// lines are roughly parallel
// so check if they overlap significantly in at least one axis
// and/or are very close in one other axis
// we only check X and Z
// as we don't have any vertical rails in the park
// editor
// (note, I'm prematurely optimizing here...)
int overlaps = 0;
int close = 0;
// now check the distance between the lines
// which is the components of start -> check_start
// that is perpendicular to start -> end
const float significant = 6.0f; // six inches is significant, I feel...
Mth::Vector s_cs = check_start - start;
Mth::Vector s_e = end - start;
s_e.Normalize();
Mth::Vector perp = s_cs;
perp.ProjectToPlane(s_e);
if ( perp.Length()<significant)
{
// printf ("(%.2f,%.2f),(%.2f,%.2f),(%.2f,%.2f),(%.2f,%.2f)\n",
// start[X],start[Z],end[X],end[Z],
// check_start[X],check_start[Z],check_end[X],check_end[Z]);
if (overlap(start[X],end[X],check_start[X],check_end[X]) > significant)
overlaps++;
if (overlap(start[Z],end[Z],check_start[Z],check_end[Z]) > significant)
overlaps++;
if (nearly_the_same(start[X],end[X],check_start[X],check_end[X],6.0f))
close++;
if (nearly_the_same(start[Z],end[Z],check_start[Z],check_end[Z],6.0f))
close++;
// printf ("dot close, overlaps = %d, close = %d\n",overlaps,close);
if (overlaps + close >= 2)
{
// it's a duplicate
// printf("Removing duplicate rail !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
// add a magenta line for rails that have been removed
#ifdef DEBUG_EDITOR_RAILS
pRailNode->m_pos += Mth::Vector(0.0f,2.0f,0.0f);
pRailNode->m_pNextLink->m_pos += Mth::Vector(0.0f,2.0f,0.0f);
Gfx::AddDebugLine( pRailNode->m_pos, pRailNode->m_pNextLink->m_pos, MAKE_RGB( 255, 0, 255 ),MAKE_RGB( 255,0, 255 ) );
pRailNode->m_pos -= Mth::Vector(0.0f,2.0f,0.0f);
pRailNode->m_pNextLink->m_pos -= Mth::Vector(0.0f,2.0f,0.0f);
#endif
removed ++;
// Remove the one that is lower
if (pRailNode->m_pos[Y] < pCheckNode->m_pos[Y])
{
pRailNode->SetActive(false);
}
else
{
pCheckNode->SetActive(false);
}
break;
}
}
}
}
}
check_node++;
// pCheckNode = pCheckNode->m_pNext;
}
}
// pRailNode = pRailNode->m_pNext;
}
printf ("Done overlapping rail removal, removed %d\n", removed);
}
void CRailManager::AddedAll()
{
// printf("Added all rails\n");
#if 1
if (Ed::CParkEditor::Instance()->UsingCustomPark()) // is it a custom park???
{
// yes, so remove overlapping rails
RemoveOverlapping();
}
#endif
}
void CRailManager::SetActive( int node, int active, bool wholeRail )
{
// CRailNode *pRailNode = mp_first_node;
// while (pRailNode)
for (int check_node = 0;check_node<m_num_nodes;check_node++)
{
CRailNode *pRailNode = &mp_nodes[check_node];
if (pRailNode->m_node == node)
{
if ( wholeRail )
{
while ( pRailNode )
{
pRailNode->SetActive(active);
pRailNode = pRailNode->m_pNextLink;
}
return;
}
pRailNode->SetActive(active);
return;
}
// pRailNode = pRailNode->m_pNext;
}
}
bool CRailManager::IsActive( int node )
{
// CRailNode *pRailNode = mp_first_node;
// while (pRailNode)
for (int check_node = 0;check_node<m_num_nodes;check_node++)
{
CRailNode *pRailNode = &mp_nodes[check_node];
if (pRailNode->m_node == node)
{
return ( pRailNode->GetActive() );
}
// pRailNode = pRailNode->m_pNext;
}
return ( false );
}
// A node in the node array has moved (due to realtime editing)
// so find that node here, and update the position
// and update bounding boxes as needed
//
void CRailManager::MoveNode( int node, Mth::Vector &pos )
{
for (int check_node = 0;check_node<m_num_nodes;check_node++)
{
CRailNode *pRailNode = &mp_nodes[check_node];
if (pRailNode->m_node == node)
{
// Set The position
pRailNode->m_pos = pos;
// calculate the bounding box of any segment this node starts
// (there can be only one)
if (pRailNode->GetNextLink())
{
Mth::Vector from_pos = pRailNode->m_pos;
Mth::Vector to_pos = pRailNode->GetNextLink()->m_pos;
Rail_ComputeBB(from_pos ,to_pos , pRailNode->m_BBMin, pRailNode->m_BBMax);
}
// Check to see if there are any nodes leading to this one
// (this initial check is only for speed)
if (pRailNode->GetPrevLink())
{
// There is one, but there might be more that one
// the only way to know, is to go through all the nodes and see if any points to this one
//
for (int from_node = 0;from_node<m_num_nodes;from_node++)
{
CRailNode *p_from = &mp_nodes[check_node];
if (p_from->GetNextLink() == pRailNode)
{
Rail_ComputeBB(p_from->m_pos, pRailNode->m_pos, p_from->m_BBMin, p_from->m_BBMax);
}
}
}
// There is only going to be one mathcing node, so we can now return
return;
} // end if (pRailNode->m_node == node)
}
}
// Given a velocity, and a rail node
// then see which side of the rail (left or right)
// has no collision
// returns -1 if left, 0 if neither or both, and +1 if right
// Used in the park editor to give preference to those
// rails that keep the same sideness as the prior rail we were on
int CRailNode::Side(const Mth::Vector &vel) const
{
Mth::Vector Start = m_pos;
Mth::Vector End = m_pNextLink->m_pos;
CFeeler feeler;
// Find a point "Mid", which is 1/4 of the way along the rail
// and lowered six inches
Mth::Vector Mid = (Start + End) / 2.0f; // Half way along
Mid = Start + ((Mid - Start) / 2.0f); // quarter of the way along
Mid[Y] -= 6.0f; // lowered a bit
// Find a vector "Side" which is horizontal, perpendicular to the rail, and 4 inches long
Mth::Vector Side = End - Start; // vector along the rail
Side[Y] = 0.0f; // horizontal
Side.Normalize(4.0f); // this is half the width of the thickest rail. Was 4.0, changed for Tokyo mega funbox
float temp = Side[X]; // make perpendicular to rail
Side[X] = Side[Z];
Side[Z] = -temp;
bool left = false;
bool right = false;
// if collision above me, left to right
feeler.SetLine(Mid + Side, Mid-Side);
if (feeler.GetCollision())
{
left = true;
}
// if collision above me, right to left
feeler.FlipDirection();
if (feeler.GetCollision())
{
right = true;
}
else
{
}
int side = 0;
if (left && !right)
{
side = -1;
}
else
{
if (right && ! left)
{
side = 1;
}
}
// flip is we are going the opposite direction on the rail
if ( side && (Mth::DotProduct(vel,End-Start) < 0.0f) )
{
side = -side;
}
return side;
}
void CRailManager::UpdateTransform(Mth::Matrix& transform)
{
m_is_transformed = true;
m_transform = transform;
}
bool CRailManager::CheckForLadderRail ( const Mth::Vector& pos, float max_horizontal_snap_distance, float max_vertical_snap_distance, bool up, CWalkComponent* p_walk_component, SLadderRailData& rail_data, const CRailNode** pp_rail_node )
{
float max_horizontal_snap_distance_sqr = Mth::Sqr(max_horizontal_snap_distance);
for (int check_node_idx = m_num_nodes; check_node_idx--; )
{
CRailNode* p_node = &mp_nodes[check_node_idx];
if (!p_node->GetFlag(LADDER) || !p_node->GetActive()) continue;
// quick oriented bounding box check
if (Mth::Abs(p_node->m_pos[X] - pos[X]) > max_horizontal_snap_distance) continue;
if (Mth::Abs(p_node->m_pos[Z] - pos[Z]) > max_horizontal_snap_distance) continue;
if (Mth::Abs(p_node->m_pos[Y] - pos[Y]) > max_vertical_snap_distance) continue;
// actual distance check
Mth::Vector offset = p_node->m_pos - pos;
if (offset[X] * offset[X] + offset[Z] * offset[Z] > max_horizontal_snap_distance_sqr) continue;
// find the ladder's partiner node
const CRailNode* p_partiner_node = p_node->GetNextLink() ? p_node->GetNextLink() : p_node->GetPrevLink();
Dbg_Assert(p_partiner_node);
if (up)
{
if (!p_walk_component->FilterLadderUpRail(p_node->m_pos, p_partiner_node->m_pos, GetMatrix(p_node), rail_data)) continue;
}
else
{
if (!p_walk_component->FilterLadderDownRail(p_partiner_node->m_pos, p_node->m_pos, GetMatrix(p_partiner_node), rail_data)) continue;
}
// for now we do no comparison between ladders; they should be sparce enough that we can just take the first ladder rail we find
// return the bottom node of the ladder
if (up)
{
*pp_rail_node = p_node;
}
else
{
*pp_rail_node = p_partiner_node;
}
return true;
}
return false;
}
bool CRailManager::CheckForAirGrabLadderRail ( const Mth::Vector& start_pos, const Mth::Vector& end_pos, CWalkComponent* p_walk_component, SLadderRailData& rail_data, const CRailNode** pp_rail_node )
{
*pp_rail_node = NULL;
Mth::Line movement;
movement.m_start = start_pos;
movement.m_end = end_pos;
// find bounding box for movement
Mth::Vector bb_min, bb_max;
Rail_ComputeBB(movement.m_start, movement.m_end, bb_min, bb_max);
float snap_dist_sqr = Script::GetFloat(CRCD(0x30ce7f2c, "Climb_Max_Snap"));
bb_min.Set(bb_min.GetX() - snap_dist_sqr, bb_min.GetY() - snap_dist_sqr, bb_min.GetZ() - snap_dist_sqr);
bb_max.Set(bb_max.GetX() + snap_dist_sqr, bb_max.GetY() + snap_dist_sqr, bb_max.GetZ() + snap_dist_sqr);
snap_dist_sqr *= snap_dist_sqr;
float closest_dist_sqr = 10000000.0f * 10000000.0f;
for (int check_node = 0; check_node < m_num_nodes; check_node++)
{
CRailNode* pRailNode = &mp_nodes[check_node];
if (!pRailNode->GetFlag(LADDER) || !pRailNode->GetActive() || !pRailNode->m_pNextLink) continue;
// First do bounding box test, before time-intensive LineLineIntersect test
if (bb_min.GetX() > pRailNode->m_BBMax.GetX()) continue;
if (bb_max.GetX() < pRailNode->m_BBMin.GetX()) continue;
if (bb_min.GetZ() > pRailNode->m_BBMax.GetZ()) continue;
if (bb_max.GetZ() < pRailNode->m_BBMin.GetZ()) continue;
if (bb_min.GetY() > pRailNode->m_BBMax.GetY()) continue;
if (bb_max.GetY() < pRailNode->m_BBMin.GetY()) continue;
// we have a rail segment with a BB match
// so see if we are close to it
Mth::Line segment;
segment.m_start = pRailNode->m_pos;
segment.m_end = pRailNode->m_pNextLink->m_pos;
Mth::Vector p1, p2;
float f1,f2;
if (!Mth::LineLineIntersect(movement, segment, &p1, &p2, &f1, &f2)) continue;
Mth::Vector to_rail = p2 - p1;
float dist_sqr = to_rail.LengthSqr();
if (dist_sqr > snap_dist_sqr || dist_sqr > closest_dist_sqr) continue;
SLadderRailData this_data;
if (pRailNode->m_pos[Y] < pRailNode->m_pNextLink->m_pos[Y])
{
if (!p_walk_component->FilterLadderAirGrabRail(pRailNode->m_pos, pRailNode->m_pNextLink->m_pos, GetMatrix(pRailNode), p2, f2, this_data)) continue;
*pp_rail_node = pRailNode;
}
else
{
if (!p_walk_component->FilterLadderAirGrabRail(pRailNode->m_pNextLink->m_pos, pRailNode->m_pos, GetMatrix(pRailNode->m_pNextLink), p2, 1.0f - f2, this_data)) continue;
*pp_rail_node = pRailNode->m_pNextLink;
}
closest_dist_sqr = dist_sqr;
rail_data = this_data;
}
return *pp_rail_node;
}
/*
// check for rails near but perpendicular to check_line
bool CRailManager::CheckForHopToHangRail ( const Mth::Vector& check_line_start, float check_line_height, const Mth::Vector& facing, float max_forward_reach, float max_backward_reach, CWalkComponent* p_walk_component, SHangRailData& rail_data, const CRailNode** pp_rail_node )
{
// this logic assumes Y is up, which might not be true if we're checking a movable rail manager
// for now, no hopping to hang from movable rail managers which are not nicely oriented
if (Mth::Abs(facing[Y]) > 0.1f) return false;
// The check box is aligned with the Y axis and the facing, is check_line_height tall,
// and has a max_foward_reach + max_backward_reach by vCHECK_BOX_WIDTH foot print in the horizontal plane.
*pp_rail_node = NULL;
float best_facing_distance = 0.0f;
float best_height = 0.0f;
float check_line_X_start = check_line_start[X] - max_backward_reach * facing[X];
float check_line_X_delta = (max_backward_reach + max_forward_reach) * facing[X];
float check_line_Z_start = check_line_start[Z] - max_backward_reach * facing[Z];
float check_line_Z_delta = (max_backward_reach + max_forward_reach) * facing[Z];
for (int check_node_idx = m_num_nodes; check_node_idx--; )
{
CRailNode* p_node = &mp_nodes[check_node_idx];
if (p_node->GetFlag(LADDER) || p_node->GetFlag(NO_CLIMBING) || !p_node->GetActive() || !p_node->m_pNextLink) continue;
// determine the clostest point on the rail in the horizontal plane
float s;
if (!Nx::CCollObj::s2DLineLineCollision(
p_node->m_pos[X],
p_node->m_pos[Z],
p_node->m_pNextLink->m_pos[X] - p_node->m_pos[X],
p_node->m_pNextLink->m_pos[Z] - p_node->m_pos[Z],
check_line_X_start,
check_line_Z_start,
check_line_X_delta,
check_line_Z_delta,
&s
)) continue;
Mth::Vector closest_point = p_node->m_pos + s * (p_node->m_pNextLink->m_pos - p_node->m_pos);
// calculate the offset vector from the bottom of the check line to the closest point
Mth::Vector offset_to_rail = closest_point - check_line_start;
// calculate distance along our facing to the closest point
float facing_distance = offset_to_rail[X] * facing[X] + offset_to_rail[Z] * facing[Z];
// check if the closest point is within reach
if (facing_distance > max_forward_reach) continue;
if (facing_distance < -max_backward_reach) continue;
// check to see if the closest point is within the allowed height range
if (closest_point[Y] < check_line_start[Y]) continue;
if (closest_point[Y] > check_line_start[Y] + check_line_height) continue;
SHangRailData this_data;
if (!p_walk_component->FilterHangRail(GetPos(p_node), GetPos(p_node->m_pNextLink), LocalToWorldTransform(closest_point), s, this_data, false)) continue;
if (*pp_rail_node)
{
// logic determining if this rail is better
// take rail in front over rail behind
if (best_facing_distance >= 0.0f && facing_distance < 0.0f) continue;
if (!(facing_distance >= 0.0f && best_facing_distance < 0.0f))
{
// otherwise, take lowest rail
if (offset_to_rail[Y] > best_height) continue;
if (offset_to_rail[Y] == best_height)
{
// otherwise, take the closest rail horizontally
if (Mth::Abs(facing_distance) > Mth::Abs(best_facing_distance)) continue;
}
}
}
// this is the best (or first) rail so far
*pp_rail_node = p_node;
best_facing_distance = facing_distance;
best_height = offset_to_rail[Y];
rail_data = this_data;
Gfx::AddDebugStar(closest_point, 36.0f, MAKE_RGB(255, 0, 0), 1);
}
return *pp_rail_node;
}
*/
bool CRailManager::CheckForHangRail ( const Mth::Vector& start_pos, const Mth::Vector& end_pos, const Mth::Vector& facing, CWalkComponent* p_walk_component, SHangRailData& rail_data, float snap_distance )
{
bool rail_found = false;
Mth::Line movement;
movement.m_start = start_pos;
movement.m_end = end_pos;
// find bounding box for movement
Mth::Vector bb_min, bb_max;
Rail_ComputeBB(movement.m_start, movement.m_end, bb_min, bb_max);
float snap_dist_sqr = snap_distance;
bb_min.Set(bb_min.GetX() - snap_dist_sqr, bb_min.GetY() - snap_dist_sqr, bb_min.GetZ() - snap_dist_sqr);
bb_max.Set(bb_max.GetX() + snap_dist_sqr, bb_max.GetY() + snap_dist_sqr, bb_max.GetZ() + snap_dist_sqr);
snap_dist_sqr *= snap_dist_sqr;
float closest_dist_sqr = 10000000.0f * 10000000.0f;
for (int check_node = 0; check_node < m_num_nodes; check_node++)
{
CRailNode* pRailNode = &mp_nodes[check_node];
if (pRailNode->GetFlag(LADDER) || pRailNode->GetFlag(NO_CLIMBING) || !pRailNode->GetActive() || !pRailNode->m_pNextLink) continue;
// First do bounding box test, before time-intensive LineLineIntersect test
if (bb_min.GetX() > pRailNode->m_BBMax.GetX()) continue;
if (bb_max.GetX() < pRailNode->m_BBMin.GetX()) continue;
if (bb_min.GetZ() > pRailNode->m_BBMax.GetZ()) continue;
if (bb_max.GetZ() < pRailNode->m_BBMin.GetZ()) continue;
if (bb_min.GetY() > pRailNode->m_BBMax.GetY()) continue;
if (bb_max.GetY() < pRailNode->m_BBMin.GetY()) continue;
// we have a rail segment with a BB match
// so see if we are close to it
Mth::Line segment;
segment.m_start = pRailNode->m_pos;
segment.m_end = pRailNode->m_pNextLink->m_pos;
Mth::Vector p1, p2;
float f1,f2;
if (!Mth::LineLineIntersect(movement, segment, &p1, &p2, &f1, &f2)) continue;
Mth::Vector to_rail = p2 - p1;
if (to_rail.LengthSqr() > snap_dist_sqr) continue;
// count rails in front of us as three times closer
float dot = Mth::DotProduct(facing, to_rail);
if (dot > 0.0f)
{
to_rail -= (2.0f / 3.0f * dot) * facing;
}
float dist_sqr = to_rail.LengthSqr();
if (dist_sqr > closest_dist_sqr) continue;
if (!Rail_ValidInEditor(GetPos(pRailNode), GetPos(pRailNode->m_pNextLink))) continue;
SHangRailData this_data;
this_data.p_rail_node = pRailNode;
if (!p_walk_component->FilterHangRail(GetPos(pRailNode), GetPos(pRailNode->m_pNextLink), LocalToWorldTransform(p2), LocalToWorldTransform(p1), f2, this_data, false)) continue;
closest_dist_sqr = dist_sqr;
rail_data = this_data;
rail_found = true;
}
return rail_found;
}
bool CRailManager::RailNodesAreCoincident ( const CRailNode* p_node_a, const CRailNode* p_node_b )
{
const float epsilon = 1.0f;
if (Mth::Abs(p_node_a->m_pos[X] - p_node_b->m_pos[X]) > epsilon) return false;
if (Mth::Abs(p_node_a->m_pos[Y] - p_node_b->m_pos[Y]) > epsilon) return false;
if (Mth::Abs(p_node_a->m_pos[Z] - p_node_b->m_pos[Z]) > epsilon) return false;
return true;
}
// look for another rail node within a few inches of the given node's position
bool CRailManager::CheckForCoincidentRailNode ( const CRailNode* p_node, uint32 ignore_mask, const CRailNode** pp_next_node )
{
for (int check_node = 0; check_node<m_num_nodes; check_node++)
{
CRailNode *pRailNode = &mp_nodes[check_node];
if (!RailNodesAreCoincident(pRailNode, p_node)) continue;
if (!pRailNode->m_pNextLink && !pRailNode->m_pPrevLink) continue;
if (pRailNode == p_node) continue;
if (pRailNode->m_pNextLink && pRailNode->IsActive() && !pRailNode->m_flags.TestMask(ignore_mask))
{
// MESSAGE("found potential coincident");
if (Rail_ValidInEditor(GetPos(pRailNode), GetPos(pRailNode->m_pNextLink)))
{
*pp_next_node = pRailNode;
// MESSAGE("found good coincident");
return true;
}
else
{
// MESSAGE("not valid coincident");
}
}
if (pRailNode->m_pPrevLink && pRailNode->m_pPrevLink->IsActive() && !pRailNode->m_pPrevLink->m_flags.TestMask(ignore_mask))
{
// MESSAGE("found potential coincident");
if (Rail_ValidInEditor(GetPos(pRailNode->m_pPrevLink), GetPos(pRailNode)))
{
*pp_next_node = pRailNode->m_pPrevLink;
// MESSAGE("found good coincident");
return true;
}
else
{
// MESSAGE("not valid coincident");
}
}
}
return false;
}
// see if we can stick to a rail
bool CRailManager::StickToRail(const Mth::Vector &pos1, const Mth::Vector &pos2, Mth::Vector *p_point, CRailNode **pp_rail_node, const CRailNode *p_ignore_node, float min_dot, int side)
{
// Go through all the rail segments, and find the shortest distance to line
// and there is your rail
Mth::Line movement;
movement.m_start = pos1;
movement.m_end = pos2;
// find bounding box for skater
Mth::Vector bb_min, bb_max;
Rail_ComputeBB(movement.m_start, movement.m_end, bb_min, bb_max);
float snap_dist = Script::GetFloat(CRCD(0xf840753e, "Rail_Max_Snap"));
bb_min.Set(bb_min.GetX() - snap_dist, bb_min.GetY() - snap_dist, bb_min.GetZ() - snap_dist);
bb_max.Set(bb_max.GetX() + snap_dist, bb_max.GetY() + snap_dist, bb_max.GetZ() + snap_dist);
float closest_dist = 10000000.0f;
CRailNode * p_closest_rail = NULL;
Mth::Vector closest_point;
bool found = false;
// CRailNode *pRailNode = mp_first_node;
// while (pRailNode)
for (int check_node = 0;check_node<m_num_nodes;check_node++)
{
CRailNode *pRailNode = &mp_nodes[check_node];
if (!pRailNode->GetFlag(ONLY_CLIMBING) && pRailNode != p_ignore_node && pRailNode->GetActive())
{
if (pRailNode->m_pNextLink)
{
// First do bounding box test, before time-intensive LineLineIntersect test
// *** IMPORTANT ***
// There should be indentations for each 'if', but I left them out for readability
if (!(bb_min.GetX() > pRailNode->m_BBMax.GetX()))
if (!(bb_max.GetX() < pRailNode->m_BBMin.GetX()))
if (!(bb_min.GetZ() > pRailNode->m_BBMax.GetZ()))
if (!(bb_max.GetZ() < pRailNode->m_BBMin.GetZ()))
if (!(bb_min.GetY() > pRailNode->m_BBMax.GetY()))
if (!(bb_max.GetY() < pRailNode->m_BBMin.GetY()))
{
// we have a rail segment with a BB match
// so see if we are close to it
Mth::Line segment;
segment.m_start = pRailNode->m_pos;
segment.m_end = pRailNode->m_pNextLink->m_pos;
if (Rail_ValidInEditor(pRailNode->m_pos,pRailNode->m_pNextLink->m_pos))
{
Mth::Vector p1,p2;
float f1,f2;
if (Mth::LineLineIntersect(movement, segment, &p1, &p2, &f1, &f2))
{
Mth::Vector to_rail = p2-p1;
float dist = to_rail.Length();
float old_dist = dist;
// calculate the dot product of the
// the movement and the rail in the XZ plane
Mth::Vector v1,v2;
v1 = segment.m_end - segment.m_start;
v2 = pos2 - pos1;
v1[Y] = 0.0f;
v2[Y] = 0.0f;
v1.Normalize();
v2.Normalize();
float dot = Mth::Abs(Mth::DotProduct(v1,v2));
// if now v2 (the skater's movement vector) is all zero
// and the dot is 0.0f
// then that means we were going precisely straght up
// so we set the dot to 1,
// as normally (if we we slightly left or right)
// we would be going along the rail at the top of the pipe
// in the XZ plane (albeit slowly)
if (v2[X] == 0.0f && v2[Z] == 0.0f && dot == 0.0f)
{
dot = 1.0f;
}
dist = dist * (0.122f + 1.0f-dot); // was (50 + (4096-dist)) on PS1
old_dist = old_dist * (2.0f - dot); // was (8192-dot) on PS1
// printf ("dot=%.2f, dist=%.2f, old_dist=%.2f, min_dot=%.2f\n",dot,dist,old_dist,min_dot);
if (side)
{
Mth::Vector vel = pos2-pos1;
if (pRailNode->Side(vel) != side)
{
dist *= 2.0f;
// printf ("side change, dist doubled to %.2f\n",dist);
}
}
if (dist < closest_dist)
{
bool close = true;
// if we have a maximum dot, then check we don't go over it
if (min_dot != 1.0f)
{
if (Mth::Abs(dot) < min_dot)
{
close = false;
}
}
if (close)
{
if (old_dist > snap_dist)
{
// there is a good rail, but too far away
// so kill any rail we've found so far
// and make this the new target
closest_dist = dist;
found = false;
}
else
{
// good rail, and close enough
closest_dist = dist;
closest_point = p2;
p_closest_rail = pRailNode;
found = true;
}
}
}
} // end if (bb_test && Mth::LineLineIntersect(movement, segment, &p1, &p2, &f1, &f2))
} // end if (Rail_ValidInEditor(pRailNode->m_pos,pRailNode->m_pNextLink->m_pos))
} // end whole big bounding box test
} // end if (pRailNode->m_pNextLink)
else if (!pRailNode->m_pPrevLink && !pRailNode->m_pNextLink)
{
// special logic for a single node rail
if (!(bb_min.GetX() > pRailNode->m_BBMax.GetX()))
if (!(bb_max.GetX() < pRailNode->m_BBMin.GetX()))
if (!(bb_min.GetZ() > pRailNode->m_BBMax.GetZ()))
if (!(bb_max.GetZ() < pRailNode->m_BBMin.GetZ()))
if (!(bb_min.GetY() > pRailNode->m_BBMax.GetY()))
if (!(bb_max.GetY() < pRailNode->m_BBMin.GetY()))
{
// calculate the distance from the movement to the rail point
Mth::Vector offset = pRailNode->m_pos - pos1;
Mth::Vector movement_direction = (pos2 - pos1).Normalize();
offset -= Mth::DotProduct(offset, movement_direction) * movement_direction;
float distance = offset.Length();
if (distance > snap_dist) continue;
// single node rails count as twice the distance when looking for the closest rail
if (closest_dist < 2.0f * distance) continue;
closest_dist = 2.0f * distance;
closest_point = pRailNode->m_pos;
p_closest_rail = pRailNode;
found = true;
}
}
} // end if (active && etc)
//pRailNode = pRailNode->m_pNext;
}
if (found)
{
// note, the line from pos1 to closest_point will not reflect the line segment found above
// as the line segment will actually start somewhere between pos1 and pos2, and not always on pos1
// if ( closest_dist > Script::GetFloat("Rail_Max_Snap"))
// {
// found = false;
// }
// else
{
*p_point = closest_point;
*pp_rail_node = p_closest_rail;
}
}
return found;
}
// Added by Ken.
// Returns true if the passed Node is on the same rail as the rail node.
// Note, with the "merging" rails, where two nodes can link to
// a new node, then this is not guarenteed to work
//
// Given that the rails can now form a "loop with a tail"
// we now detect loops by traversign the list with two pointers
// one moving at half the speed of the other
bool CRailNode::ProbablyOnSameRailAs(int SearchNode) const
{
// First check if this node is the required node.
if (m_node==SearchNode)
{
// Well that was easy.
return true;
}
// MICK: Modified to return true only if on the same rail segment
return false;
const CRailNode *p_trailing = this;
bool advance_trailing = false;
// Scan forwards.
CRailNode *pNode=m_pNextLink;
while (pNode)
{
if (pNode->m_node==SearchNode)
{
// Found it.
return true;
}
if (pNode == p_trailing)
{
// We've got back where we started without finding
// the node, so return false.
return false;
}
pNode=pNode->m_pNextLink;
// Advance the trailing node every other time
// we advance the search node
if (advance_trailing)
{
p_trailing = p_trailing->m_pNextLink;
}
advance_trailing = !advance_trailing;
}
p_trailing = this;
advance_trailing = false;
// Didn't find anything that way, so now scan backwards.
pNode=m_pPrevLink;
while (pNode)
{
if (pNode->m_node==SearchNode)
{
// Found it.
return true;
}
if (pNode == p_trailing)
{
// We've got back where we started without finding
// the node, so return false.
return false;
}
pNode=pNode->m_pPrevLink;
// Advance the trailing node every other time
// we advance the search node
if (advance_trailing)
{
p_trailing = p_trailing->m_pPrevLink;
}
advance_trailing = !advance_trailing;
}
return false;
}
// use in-game collision checks to determine if a rail is valid
bool Rail_ValidInEditor(Mth::Vector Start, Mth::Vector End)
{
// MAYBE TODO: a rail should only need disqualifying if it is along the edge of a cell
// (or even more accurately, a meta-piece
// so could maybe check for that before we try to disqualify it.
if (!Ed::CParkEditor::Instance()->UsingCustomPark()) // is it a custom park???
{
#ifdef DEBUG_EDITOR_RAILS
printf ("not in editor\n");
#endif
// if not a custom park, then everything is valid.
return true;
}
CFeeler feeler;
// Find a point "Mid", which is 1/4 of the way along the rail
Mth::Vector Mid = (Start + End) / 2.0f; // Half way along
Mid = Start + ((Mid - Start) / 2.0f); // quarter of the way along
Mid[Y] += 6.0f; // raised up a bit
// Find a vector "Side" which is horizontal, perpendicular to the rail, and 4 inches long
Mth::Vector Side = End - Start; // vector along the rail
Side[Y] = 0.0f; // horizontal
Side.Normalize(7.0f); // this is half the width of the thickest rail. Was 4.0, changed for Tokyo mega funbox
float temp = Side[X]; // make perpendicular to rail
Side[X] = Side[Z];
Side[Z] = -temp;
// if collision above me, left to right, then invalid
feeler.SetLine(Mid + Side, Mid-Side);
if (feeler.GetCollision())
{
#ifdef DEBUG_EDITOR_RAILS
printf ("l-r above collision, invalid\n");
feeler.DebugLine(255,0,0);
#endif
return false;
}
else
{
#ifdef DEBUG_EDITOR_RAILS
feeler.DebugLine();
#endif
}
// if collision above me, right to left, then invalid
feeler.FlipDirection();
if (feeler.GetCollision())
{
#ifdef DEBUG_EDITOR_RAILS
printf ("r-l above collision, invalid\n");
feeler.DebugLine(255,0,0);
#endif
return false;
}
else
{
#ifdef DEBUG_EDITOR_RAILS
feeler.DebugLine();
#endif
}
#if 1
// Get a vector "Down", which is a line straight down 7 inches
Mth::Vector DeepBelow(0.0f,-12.0f,0.0f); // six inches below the rail
float left_height = 0.0f;
float right_height = 0.0f;
// find reltive height of slope (to rail) on left side
feeler.SetLine(Mid + Side, Mid + Side + DeepBelow);
if (feeler.GetCollision())
{
#ifdef DEBUG_EDITOR_RAILS
feeler.DebugLine(0,255,0); // green = possible bad one side
#endif
left_height = feeler.GetPoint().GetY() - (Mid[Y] -6.0f);
}
else
{
return true;
}
feeler.SetLine(Mid - Side, Mid - Side + DeepBelow);
if (feeler.GetCollision())
{
#ifdef DEBUG_EDITOR_RAILS
feeler.DebugLine(0,255,0); // green = possible bad one side
#endif
right_height = feeler.GetPoint().GetY() - (Mid[Y]-6.0f);
}
else
{
return true;
}
// printf("Left = %f, Right = %f\n",left_height, right_height);
if (left_height > -1.0f && right_height > -1.0f)
{
// both faces tilt upwards, or are roughtly horizontal
// so return false
return false;
}
// Make it so left is higher than right for subsequent tests
if (left_height < right_height)
{
float t = left_height;
left_height = right_height;
right_height = t;
}
// check for steep left side
if (left_height > 1.0f)
{
// sloped down right side, so return
if (right_height > -2.0f)
{
// printf ("Sloped, false\n");
return false;
}
}
#else
// Get a vector "Down", which is a line straight down 7 inches
Mth::Vector DownBelow(0.0f,-7.0f,0.0f); // inch below the rail
Mth::Vector DownAbove(0.0f,-4.0f,0.0f); // 2 inch above the rail
// we do fource collision checks, two on each side of the rail
// one to a height that goes below the rail
// and the other that goes to a height above it
// if two or more of these return a collision
// then the rail is invalid
// the majority of bad rails will be eliminated with two checks
int cols = 0;
feeler.SetLine(Mid + Side, Mid + Side + DownBelow);
if (feeler.GetCollision())
{
cols++;
#ifdef DEBUG_EDITOR_RAILS
feeler.DebugLine(0,255,0); // green = possible bad one side
printf ("first side collision (greeen), checking other side.....\n");
#endif
}
feeler.SetLine(Mid-Side, Mid-Side+DownBelow);
if (feeler.GetCollision())
{
cols++;
#ifdef DEBUG_EDITOR_RAILS
feeler.DebugLine(0,255,0); // green = possible bad one side
printf ("second side collision (greeen), checking other side.....\n");
#endif
if (cols > 1)
{
return false;
}
}
feeler.SetLine(Mid+Side, Mid+Side+DownAbove);
if (feeler.GetCollision())
{
cols++;
#ifdef DEBUG_EDITOR_RAILS
feeler.DebugLine(0,255,0); // green = possible bad one side
printf ("third side collision (greeen), checking other side.....\n");
#endif
if (cols > 1)
{
return false;
}
}
feeler.SetLine(Mid-Side, Mid-Side+DownAbove);
if (feeler.GetCollision())
{
cols++;
#ifdef DEBUG_EDITOR_RAILS
feeler.DebugLine(0,255,0); // green = possible bad one side
printf ("forth side collision (greeen)\n");
#endif
if (cols > 1)
{
return false;
}
}
#endif
// Not found two collisions
// so we can return true, indicating this rail is valid in the park editor
// printf ("everything ok, valid\n");
return true;
}
void CRailManager::DebugRender(Mth::Matrix *p_transform)
{
#ifdef __DEBUG_CODE__
#ifdef __PLAT_NGPS__
NxPs2::DMAOverflowOK = 2;
Mth::Vector cam_fwd;
if (Nx::CViewportManager::sGetActiveCamera())
{
cam_fwd = Nx::CViewportManager::sGetActiveCamera()->GetMatrix()[Z];
}
else
{
printf("WARNING: called CRailManager::DebugRender without a camera\n");
return;
}
bool draw_arrows = Script::GetInt( CRCD(0xc57f95d7,"rail_arrows"), false );
Mth::Vector up(0.0f,1.0f,0.0f);
uint32 rgb = 0x0000ff;
uint32 current = 12345678;
// CRailNode *pRailNode;
// pRailNode = mp_first_node;
int count = 0;
for (int check_node = 0;check_node<m_num_nodes;check_node++)
{
CRailNode *pRailNode = &mp_nodes[check_node];
count++;
CRailNode *pNext = pRailNode->m_pNextLink;
int terrain = pRailNode->GetTerrain();
if (pNext)
{
switch (terrain)
{
case vTERRAIN_CONCSMOOTH:
case vTERRAIN_CONCROUGH:
rgb = 0x0000ff; // red = concrete
break;
case vTERRAIN_METALSMOOTH:
case vTERRAIN_METALROUGH:
case vTERRAIN_METALCORRUGATED:
case vTERRAIN_METALGRATING:
case vTERRAIN_METALTIN:
rgb = 0xff0000; // blue = metal
break;
case vTERRAIN_WOOD:
case vTERRAIN_WOODMASONITE:
case vTERRAIN_WOODPLYWOOD:
case vTERRAIN_WOODFLIMSY:
case vTERRAIN_WOODSHINGLE:
case vTERRAIN_WOODPIER:
rgb = 0x00ff00; // green = wood
break;
default:
rgb = 0xffffff; // white = everything else
break;
}
if (pRailNode->m_flags.Test(LADDER))
{
rgb = 0xffff00;
}
// check for context changes
if (current != rgb)
{
if ( current != 12345678)
{
NxPs2::EndLines3D();
}
current = rgb;
NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));
}
Mth::Vector v0, v1;
v0 = pRailNode->m_pos/* + up*/; // the +up is the lift them off the surface, so they render better
v1 = pNext->m_pos/* + up*/;
v0[W] = 1.0f; // Make homgeneous
v1[W] = 1.0f;
if (p_transform)
{
v0 = p_transform->Transform(v0);
v1 = p_transform->Transform(v1);
}
if (pRailNode->GetTerrain() || ((Tmr::GetTime() % 100) > 50))
{
NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
if (draw_arrows)
{
// Calculate and draw an arrowhead
// 1/4th the length, at ~30 degrees
Mth::Vector a = v0;
Mth::Vector b = v1;
Mth::Vector ab = (b-a);
ab /= 4.0f;
Mth::Vector out;
out = ab;
out.Normalize();
out = Mth::CrossProduct(out, cam_fwd);
out *= ab.Length()/3.0f;
Mth::Vector left = b - ab + out;
Mth::Vector right = b - ab - out;
NxPs2::DrawLine3D(left[X],left[Y],left[Z],b[X],b[Y],b[Z]);
NxPs2::DrawLine3D(right[X],right[Y],right[Z],b[X],b[Y],b[Z]);
NxPs2::DrawLine3D(right[X],right[Y],right[Z],left[X],left[Y],left[Z]); // crossbar
// have to draw it twice for some stupid reason (final segment of a particular color is not rendered)
NxPs2::DrawLine3D(left[X],left[Y],left[Z],b[X],b[Y],b[Z]);
NxPs2::DrawLine3D(right[X],right[Y],right[Z],b[X],b[Y],b[Z]);
NxPs2::DrawLine3D(right[X],right[Y],right[Z],left[X],left[Y],left[Z]);
}
}
}
// pRailNode = pRailNode->m_pNext;
}
// then draw the node positions as litle red lines
// pRailNode = mp_first_node;
count = 0;
for (int check_node = 0;check_node<m_num_nodes;check_node++)
{
CRailNode *pRailNode = &mp_nodes[check_node];
count++;
if (pRailNode->GetActive())
{
rgb = 0xff0000; // blue for active
}
else
{
rgb = 0x00ff00; // green for inactive
}
if (!pRailNode->GetPrevLink())
{
rgb |= 0x0000ff; // blue+red = cyan = no prev node (or green+red = yellow for no prev inactive)
}
// check for context changes
if (current != rgb)
{
if ( current != 12345678)
{
NxPs2::EndLines3D();
}
current = rgb;
NxPs2::BeginLines3D(0x80000000 + (0x00ffffff & rgb));
}
Mth::Vector v0, v1;
v0 = pRailNode->m_pos;
v1 = v0 + up * 24.0f;
v0[W] = 1.0f; // Make homgeneous
v1[W] = 1.0f;
if (p_transform)
{
v0 = p_transform->Transform(v0);
v1 = p_transform->Transform(v1);
}
if (pRailNode->GetTerrain() || ((Tmr::GetTime() % 100) > 50))
{
NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
NxPs2::DrawLine3D(v0[X],v0[Y],v0[Z],v1[X],v1[Y],v1[Z]);
}
// pRailNode = pRailNode->m_pNext;
}
// only if we actually drew some
if ( current != 12345678)
{
NxPs2::EndLines3D();
}
#endif
#endif
}
void CRailManager::AddRailsFromNodeArray(Script::CArray * p_nodearray)
{
Dbg_MsgAssert(!mp_node_array,("Setting two node arrays in rail manager"));
mp_node_array = p_nodearray;
Mdl::Skate * skate_mod = Mdl::Skate::Instance();
uint32 i;
Dbg_MsgAssert(m_num_nodes == 0,("Can only addd nodes once, already %d there\n",m_num_nodes));
m_num_nodes = 0;
// First iterate over the node array, and count the number of nodes needed
for (i=0; i<p_nodearray->GetSize(); ++i)
{
Script::CStruct *p_node_struct=p_nodearray->GetStructure(i);
Dbg_MsgAssert(p_node_struct,("Error getting node from node array for rail generation"));
if ( !skate_mod->ShouldBeAbsentNode( p_node_struct ) )
{
uint32 class_checksum = 0;
p_node_struct->GetChecksum( 0x12b4e660 /*"Class"*/, &class_checksum );
if (class_checksum == CRCD(0x8e6b02ad, "railnode") || class_checksum == CRCD(0x30c19600, "ClimbingNode"))
{
m_num_nodes++;
}
// if (class_checksum == CRCD(0x16d1e502, "climbnode"))
// {
// printf("Found climb node!\n");
// }
}
}
// No nodes found, so just return
if (!m_num_nodes)
{
return;
}
mp_nodes = (CRailNode*)Mem::Malloc(m_num_nodes * sizeof(CRailNode));
mp_links = (CRailLinks*)Mem::Malloc(m_num_nodes * sizeof(CRailLinks));
// iterate over the nodes and add them to the array
m_current_node = 0;
for (i=0; i<p_nodearray->GetSize(); ++i)
{
Script::CStruct *p_node_struct=p_nodearray->GetStructure(i);
Dbg_MsgAssert(p_node_struct,("Error getting node from node array for rail generation"));
if ( !skate_mod->ShouldBeAbsentNode( p_node_struct ) )
{
uint32 class_checksum = 0;
p_node_struct->GetChecksum( 0x12b4e660 /*"Class"*/, &class_checksum );
if (class_checksum == CRCD(0x8e6b02ad, "railnode") || class_checksum == CRCD(0x30c19600, "ClimbingNode"))
{
AddRailNode( i, p_node_struct);
bool is_trick_object = p_node_struct->ContainsComponentNamed( "TrickObject" );
#if 1
bool debug_graffiti = Script::GetInt( "create_all_trick_objects", false );
if ( debug_graffiti )
is_trick_object = true;
#endif
if ( is_trick_object )
{
// get the node name
uint32 checksumName;
p_node_struct->GetChecksum( "name", &checksumName, true );
// must have a cluster associated with it
uint32 clusterName = checksumName;
#if 0
// automatically link all rail nodes to the TestCluster for now
clusterName = Script::GenerateCRC("TestCluster");
#else
p_node_struct->GetChecksum( "Cluster", &clusterName, true );
#endif
skate_mod->GetTrickObjectManager()->AddTrickCluster( clusterName );
// bind the name of the rail node to the cluster name
skate_mod->GetTrickObjectManager()->AddTrickAlias( checksumName, clusterName );
}
}
}
}
int num_nodes = p_nodearray->GetSize();
// we are creating a table of all nodes, and the pointer to the CRailNode
// for that node, so we can do a reverse lookup
CRailNode **pp_railnodes = (CRailNode **) Mem::Malloc(num_nodes * sizeof(CRailNode*));
for (int i=0;i<num_nodes;i++)
{
pp_railnodes[i] = NULL;
}
// now fill it in
CRailNode *p_node; // = mp_first_node;
// while (p_node)
for (int node=0;node<m_num_nodes;node++)
{
p_node = &mp_nodes[node];
pp_railnodes[p_node->GetNode()] = p_node;
// p_node = p_node->m_pNext;
}
// now go through all the node
// p_node = mp_first_node;
for (int node=0;node<m_num_nodes;node++)
{
p_node = &mp_nodes[node];
for (int i=0;i<MAX_RAIL_LINKS;i++)
{
if (mp_links[node].m_link[i] != - 1)
{
Dbg_MsgAssert(mp_links[node].m_link[i] < num_nodes, ("Node %d,Rail link node (%d) out of range (0 .. %d). Bad Node array?",
p_node->m_node, mp_links[node].m_link[i], num_nodes));
Dbg_MsgAssert(pp_railnodes[mp_links[node].m_link[i]], ("RailNode %d linked to something (node %d) that is not a RailNode",
p_node->m_node, mp_links[node].m_link[i]));
NewLink(p_node,pp_railnodes[mp_links[node].m_link[i]]);
}
}
// p_node = p_node->m_pNext;
}
Mem::Free(pp_railnodes);
Mem::Free(mp_links);
mp_links = NULL;
//printf ("THERE ARE %d rails\n",m_num_nodes);
}
Mth::Vector CRailManager::LocalToWorldTransform ( const Mth::Vector& vector ) const
{
if (!m_is_transformed)
{
return vector;
}
else
{
return m_transform.Transform(vector);
}
}
Mth::Vector CRailManager::GetPos(const CRailNode *p_node) const
{
Dbg_Assert(p_node);
if (!m_is_transformed)
{
return p_node->GetPos();
}
else
{
Mth::Vector v = m_transform.Transform(p_node->GetPos());
return v;
}
}
Mth::Matrix CRailManager::GetMatrix(const CRailNode *p_node) const
{
Dbg_Assert(p_node);
if (!m_is_transformed)
{
return Mth::Matrix(p_node->GetOrientation());
}
else
{
Mth::Matrix m = m_transform * Mth::Matrix(p_node->GetOrientation());
return m;
}
}
Mth::Vector CRailManager::GetBBMin(const CRailNode *p_node) const
{
if (!m_is_transformed)
{
return p_node->GetBBMin();
}
else
{
Mth::Vector v = m_transform.Transform(p_node->GetBBMin());
return v;
}
}
Mth::Vector CRailManager::GetBBMax(const CRailNode *p_node) const
{
if (!m_is_transformed)
{
return p_node->GetBBMax();
}
else
{
Mth::Vector v = m_transform.Transform(p_node->GetBBMax());
return v;
}
}
// Auto-generation of rails
// a binning data structure similar to a hashtable which will allow us to quickly find nearby rail endpoints
// differs from the standard hashtable in that we will be able to add an element multiple times
// its algorithm is just as inefficient as the standard hashtable
struct SAutoRail;
class BinTable;
class BinItem
{
friend class BinTable;
private:
BinItem ( )
: mp_value(NULL), mp_next(NULL)
{ }
SAutoRail* mp_value;
BinItem* mp_next;
};
class BinTable
{
public:
BinTable ( uint32 numBits );
~BinTable ( );
bool PutItem ( const uint32 key, SAutoRail* item );
bool PutItemTwice ( const uint32 first_key, const uint32 second_key, SAutoRail* item );
SAutoRail* GetFirstItem ( const uint32 key );
SAutoRail* GetNextItem ( const uint32 key, SAutoRail* item );
void FlushAllItems ( );
int GetSize ( ) { return m_size; }
protected:
uint32 m_numBits;
BinItem* mp_table;
int m_size;
private:
uint32 key_to_bin ( const uint32 key )
{ return key & ((1 << m_numBits) - 1); }
};
BinTable::BinTable ( uint32 numBits )
: m_numBits(numBits), mp_table(new BinItem[1 << numBits]), m_size(0)
{ }
BinTable::~BinTable ( )
{
Dbg_AssertPtr(mp_table);
FlushAllItems();
delete [] mp_table;
mp_table = NULL;
}
bool BinTable::PutItem ( const uint32 key, SAutoRail* item )
{
Dbg_AssertPtr(item);
Dbg_AssertPtr(mp_table);
Dbg_MsgAssert(key || item, ("Both key and item are 0 (NULL) in bin table"));
Dbg_MsgAssert(item, ("NULL item added to bin table"));
BinItem *pEntry = &mp_table[key_to_bin(key)];
if (pEntry->mp_value)
{
#ifndef __PLAT_WN32__
BinItem *pNew = new (Mem::PoolManager::SCreateItem(Mem::PoolManager::vHASH_ITEM_POOL)) BinItem();
#else
BinItem *pNew = new BinItem;
#endif
pNew->mp_value = item;
pNew->mp_next = pEntry->mp_next;
pEntry->mp_next = pNew;
}
else
{
pEntry->mp_value = item;
}
m_size++;
return true;
}
// this feature is needed to insure a rail is never added twice to the same bin; if it were, it would become
// impossible to traverse the bin
bool BinTable::PutItemTwice ( const uint32 first_key, const uint32 second_key, SAutoRail* item )
{
PutItem(first_key, item);
if (key_to_bin(first_key) != key_to_bin(second_key))
PutItem(second_key, item);
return true;
}
SAutoRail* BinTable::GetFirstItem ( const uint32 key )
{
Dbg_AssertPtr(mp_table);
BinItem* pEntry = &mp_table[key_to_bin(key)];
while (pEntry)
{
if (pEntry->mp_value)
{
return pEntry->mp_value;
}
pEntry = pEntry->mp_next;
}
return NULL;
}
SAutoRail* BinTable::GetNextItem ( const uint32 key, SAutoRail* p_item )
{
Dbg_AssertPtr(mp_table);
BinItem* pEntry = &mp_table[key_to_bin(key)];
while (pEntry)
{
if (pEntry->mp_value == p_item)
{
break;
}
pEntry = pEntry->mp_next;
}
if (!pEntry)
{
return NULL;
}
pEntry = pEntry->mp_next;
while (pEntry)
{
if (pEntry->mp_value)
{
return pEntry->mp_value;
}
pEntry = pEntry->mp_next;
}
return NULL;
}
void BinTable::FlushAllItems ( )
{
Dbg_AssertPtr(mp_table);
if (!mp_table)
return;
BinItem* pMainEntry = mp_table;
uint32 hashTableSize = 1 << m_numBits;
for (uint32 i = 0; i < hashTableSize; ++i)
{
BinItem* pLinkedEntry = pMainEntry->mp_next;
while (pLinkedEntry)
{
BinItem* pNext = pLinkedEntry->mp_next;
#ifndef __PLAT_WN32__
Mem::PoolManager::SFreeItem(Mem::PoolManager::vHASH_ITEM_POOL, pLinkedEntry);
#else
delete pLinkedEntry;
#endif
pLinkedEntry = pNext;
}
++pMainEntry;
}
m_size = 0;
}
// memory managment defines; control temporary memory usage
// maximum number of edges in a sector
#define MAX_NUM_EDGES (5000)
// maximum number of rails
#define MAX_NUM_RAILS (30000)
// maximum number of railsets
#define MAX_NUM_RAILSETS (6000)
// size of the rail endpoint bintable
#define ENDPOINT_BINTABLE_SIZE_BITS (12) // 4096 entries
// default rail generation parameter values
// when changing these, don't forget to CHANGE THE DOCUMENTATION of the default script parameter values in cfuncs.cpp
// angle below which edges do not generate rails
#define MIN_RAIL_EDGE_ANGLE (30.0f)
// maximum rail assent
// NOTE: can't sync with max skate slop because not a explicitly defined physics value (I think)
#define MAX_RAIL_ANGLE_OF_ASSENT (45.0f)
// minimum length of a independent rail or a railset
#define MIN_RAILSET_LENGTH (36.0f)
// minimum length of a rail, whether in a railset or not
#define MIN_EDGE_LENGTH (0.0f)
// distance increment along rails at which collision detection is done
#define FEELER_INCREMENT (36.0f)
// maximum heigh of a curb before it will get a rail
// NOTE: sync with low-curb skate height from physics engine? i think that's 8.0f but I don't really know what I'm talking about
#define MAX_LOW_CURB_HEIGHT (8.0f)
// angle of assent above face of low-curb feelers
#define LOW_CURB_FEELER_ANGLE_OF_ASSENT (30.0f)
// maximum angle between two connected rails in a railset
#define MAX_CORNER_IN_RAILSET (50.0f)
// height of vertical feeler
#define VERTICAL_FEELER_LENGTH (5.0f * 12.0f)
// distance above rail at which crossbar feeler is used
#define CROSSBAR_FEELER_ELEVATION (12.0f)
// half length of crossbar feeler
#define CROSSBAR_FEELER_LENGTH (1.0f * 12.0f)
// farthest distance between an auto-generated rail and an old rail for which the auto-generated rail will be culled
#define FARTHEST_DEGENERATE_RAIL (12.0f)
// angle between an old and and auto-generated rail below which they may be considered degenerate
#define MAX_DEGENERATE_RAIL_ANGLE (20.0f)
// distance below which verticies are concidered equal
#define CONNECTION_SLOP (0.1f)
// feeler usage bits
#define VERTICAL_FEELER_BIT (1 << 0)
#define CROSSBAR_FEELER_BIT (1 << 1)
#define LOW_CURB_FEELER_BIT (1 << 2)
#define ALL_FEELERS_ON (-1)
struct SEdge {
// has a match for this edge been found?
bool matched;
// edge is from a vert poly; maybe I should just save all the face flag bits
bool vert;
// edge endpoints
Mth::Vector p0, p1;
// edge's face's unit normal
Mth::Vector normal;
// edge's unit perp vector along face
Mth::Vector perp;
};
struct SAutoRailEndpoint {
// endpoint position
Mth::Vector p;
// connection at this end; -1 is no connection
int connection;
};
struct SAutoRail {
// rail endpoints
SAutoRailEndpoint endpoints[2];
// unit vector along rail
Mth::Vector para;
// railset id; -1 for solo rail
int railset;
// rail length
float length;
// if found to be too sort or once added to the rail nodes, a rail is disabled
bool disabled;
};
// dereference endpoints
enum { START = 0, END };
struct SRailSet {
// length of rail set
float length;
};
// collect the autorail state into a single structure to ease transportation
struct SAutoRailGeneratorState {
SEdge* p_edges;
SAutoRail* p_rails;
SRailSet* p_railsets;
// hash of rail endpoints; most rails are in hash twice, keyed off endpoint's X*Y rounded to nearest inch
BinTable* p_rail_endpoint_list;
int num_rails;
int num_active_rails;
int num_railsets;
};
// collect all the algorithm's input parameters into a structure
// see default values for explaination of meanings
struct SAutoRailGeneratorParameters {
// input parameters
float min_rail_edge_angle;
float max_rail_angle_of_assent;
float min_railset_length;
float min_edge_length;
float feeler_increment;
float max_low_curb_height;
float curb_feeler_angle_of_assent;
float max_corner_in_railset;
float vertical_feeler_length;
float crossbar_feeler_elevation;
float half_crossbar_feeler_length;
float farthest_degenerate_rail;
float max_degenerate_rail_angle;
float connection_slop;
int feeler_usage;
// dependent parameters
float sin_max_rail_angle_of_assent;
float low_curb_feeler_length;
float cos_min_rail_edge_angle;
float cos_max_corner_in_railset;
float sin_curb_feeler_angle_of_assent;
float cos_curb_feeler_angle_of_assent;
float cos_max_degenerate_rail_angle;
// additional flags
bool remove_old_rails;
};
inline bool very_close ( Mth::Vector a, Mth::Vector b, const SAutoRailGeneratorParameters& arp )
{
return(Mth::Abs(a[X]-b[X]) < arp.connection_slop
&& Mth::Abs(a[Y]-b[Y]) < arp.connection_slop
&& Mth::Abs(a[Z]-b[Z]) < arp.connection_slop
);
}
inline int generate_endpoint_key ( Mth::Vector& end_point )
{
return static_cast<int>((end_point[X] * end_point[Z]) * (1.0f / 6.0f));
}
// checks for duplicate rails and then adds rails to rail array and endpoint bintable
inline bool add_rail ( const Mth::Vector& pa, const Mth::Vector& pb, SAutoRailGeneratorState& arg, const SAutoRailGeneratorParameters& arp )
{
// cull out duplicate rails
// NOTE: what is causing these? some levels do not have them; perhaps sloppy geometry
for (int n = arg.num_rails; n--; )
{
// if this is a duplicate, bail
if (very_close(arg.p_rails[n].endpoints[START].p, pa, arp) && very_close(arg.p_rails[n].endpoints[END].p, pb, arp)
|| very_close(arg.p_rails[n].endpoints[START].p, pb, arp) && very_close(arg.p_rails[n].endpoints[END].p, pa, arp))
{
return true;
}
}
// if we get to here, we've decided this is a valid rail
if (arg.num_rails + 1 == MAX_NUM_RAILS) return false;
// add rail to rail array
arg.p_rails[arg.num_rails].endpoints[START].p = pa;
arg.p_rails[arg.num_rails].endpoints[END].p = pb;
arg.p_rails[arg.num_rails].endpoints[START].connection = -1;
arg.p_rails[arg.num_rails].endpoints[END].connection = -1;
arg.p_rails[arg.num_rails].railset = -1;
arg.p_rails[arg.num_rails].disabled = false;
arg.p_rails[arg.num_rails].para = pb - pa;
arg.p_rails[arg.num_rails].length = arg.p_rails[arg.num_rails].para.Length();
arg.p_rails[arg.num_rails].para.Normalize();
// add rail to endpoint bintable
arg.p_rail_endpoint_list->PutItemTwice(
generate_endpoint_key(arg.p_rails[arg.num_rails].endpoints[START].p),
generate_endpoint_key(arg.p_rails[arg.num_rails].endpoints[END].p),
&arg.p_rails[arg.num_rails]
);
arg.num_rails++;
return true;
}
// handles nearby collision detection
// use feelers to insure that this is a good rail; if the full rail doesn't work, attempt to snip it up into smaller
// valid rails
inline bool consider_rail ( const Mth::Vector& pa, const Mth::Vector& pb, int matched_edge, const Mth::Vector& para, const Mth::Vector& normal,
const Mth::Vector& perp, float edge_length, SAutoRailGeneratorState& arg, SAutoRailGeneratorParameters& arp )
{
// four feelers are used at each step along the edge
// 1, 2) short feelers up from the edge's two faces
// 3) longer feeler straight up from the edge
// 4) medium feeler crossbar on feeler 3
// there are full_step_length + 2 feeler points along an edge
// 0 corresponds the the edge's start point
// n corresponds to a points n steps in from the start point
// full_step_length + 1 corresponds to the edge's end point
int full_step_length = static_cast<int>(edge_length / arp.feeler_increment);
int start_step = 0;
Mth::Vector r0 = pa;
// attempt to create rail snippets until we've used up the rail
do
{
// start a new rail snippet
int end_step = start_step;
Mth::Vector r1 = r0;
// attempt to extent the rail snippet along the edge
do
{
bool fail = false;
// check for nearby collidables
CFeeler feeler;
// first side feeler
// angled up from face in perp-normal plane starting a fraction of an inch from edge
if ((arp.feeler_usage & LOW_CURB_FEELER_BIT))
{
Mth::Vector feeler_direction = arp.sin_curb_feeler_angle_of_assent * normal + arp.cos_curb_feeler_angle_of_assent * perp;
feeler.m_start = r1 + 0.1f * feeler_direction;
feeler.m_end = r1 + arp.low_curb_feeler_length * feeler_direction;
fail = feeler.GetCollision(false);
}
// second side feeler
// angled up from face in perp-normal plane starting a fraction of an inch from edge
if (!fail && (arp.feeler_usage & LOW_CURB_FEELER_BIT))
{
Mth::Vector feeler_direction = arp.sin_curb_feeler_angle_of_assent * arg.p_edges[matched_edge].normal
+ arp.cos_curb_feeler_angle_of_assent * arg.p_edges[matched_edge].perp;
feeler.m_start = r1 + 0.2f * feeler_direction;
feeler.m_end = r1 + arp.low_curb_feeler_length * feeler_direction;
fail = feeler.GetCollision(false);
}
// vertical feeler
// straight up starting a fraction of an inch above the edge
if (!fail && (arp.feeler_usage & VERTICAL_FEELER_BIT))
{
feeler.m_start = r1;
feeler.m_start[Y] += 0.2f;
feeler.m_end = r1;
feeler.m_end[Y] += arp.vertical_feeler_length;
}
// crossbar feeler
// some height above rail, flush with XZ plane, perp to edge
if (!fail && (arp.feeler_usage & CROSSBAR_FEELER_BIT))
{
Mth::Vector bar;
bar[X] = para[Z];
bar[Y] = 0.0f;
bar[Z] = -para[X];
bar.Normalize();
feeler.m_start = r1;
feeler.m_start[Y] += arp.crossbar_feeler_elevation;
feeler.m_start += arp.half_crossbar_feeler_length * bar;
feeler.m_end = r1;
feeler.m_end[Y] += arp.crossbar_feeler_elevation;
feeler.m_end += -arp.half_crossbar_feeler_length * bar;
fail = feeler.GetCollision(false);
}
// if there is collidables, the extention attempt failed
if (fail) break;
// there are no nearby collidables
// so we'll attempt to stretch the snippet one step or to the end of the edge
end_step++;
// if we're beyond the end of the edge, we're done
if (end_step == full_step_length + 2) break;
if (end_step == full_step_length + 1)
{
r1 = pb;
}
else
{
r1 = pa + end_step * arp.feeler_increment * para;
}
} while (true);
// restore r1 to the last value it had before the failed extention
end_step--;
if (end_step == full_step_length + 1)
r1 = pb;
else
r1 = pa + end_step * arp.feeler_increment * para;
// the snippet is now as long as it can be
// if the snippet is long enough
if (end_step - start_step > 0 && (r0 - r1).LengthSqr() >= (arp.min_edge_length * arp.min_edge_length))
{
// use it
if (!add_rail(r0, r1, arg, arp)) return false;
}
// jump to next start point
start_step = end_step + 2;
r0 = pa + start_step * arp.feeler_increment * para;
} while (start_step < full_step_length + 1);
return true;
}
CRailNode* CRailManager::GetRailNodeByNodeNumber( int node_num )
{
int i;
if( mp_nodes )
{
for( i = 0; i < m_num_nodes; i++ )
{
if( mp_nodes[i].GetNode() == node_num )
{
return &mp_nodes[i];
}
}
}
return NULL;
}
// knowns issues or ideas:
// snap rail endpoints - do we want to snap the endpoints of railset together; if so, definitely make the slop parameter adjustable
// vertical feeler - the vertical feeler is causing random snipping up of rails in odd places
// don't destory old rails - instead, when considering a rail, check for nearly parallel rails and don't add a new rail if you find one; bin old rails first
// cross-section edges finding - look for potential rails by joining up across sectors; time consuming; edge endpoints won't match, so we have to look for overlapping edges instead of just looking for matching endpoints
void CRailManager::AutoGenerateRails ( Script::CStruct* pParams )
{
// 27K
#ifdef __DEBUG_CODE__
printf("-----------------------\n");
// let's use the Debug heap, since we might need a lot of memory
Mem::Manager::sHandle().PushContext(Mem::Manager::sHandle().DebugHeap());
// auto-generator's state
SAutoRailGeneratorState arg;
arg.num_rails = 0;
arg.num_railsets = 0;
arg.num_active_rails = 0;
arg.p_edges = new SEdge[MAX_NUM_EDGES];
arg.p_rails = new SAutoRail[MAX_NUM_RAILS];
arg.p_railsets = new SRailSet[MAX_NUM_RAILSETS];
arg.p_rail_endpoint_list = new BinTable(ENDPOINT_BINTABLE_SIZE_BITS);
// auto-generator's modifiable parameters
SAutoRailGeneratorParameters arp;
// set parameters to defaults
arp.min_rail_edge_angle = MIN_RAIL_EDGE_ANGLE;
arp.max_rail_angle_of_assent = MAX_RAIL_ANGLE_OF_ASSENT;
arp.min_railset_length = MIN_RAILSET_LENGTH;
arp.min_edge_length = MIN_EDGE_LENGTH;
arp.feeler_increment = FEELER_INCREMENT;
arp.max_low_curb_height = MAX_LOW_CURB_HEIGHT;
arp.curb_feeler_angle_of_assent = LOW_CURB_FEELER_ANGLE_OF_ASSENT;
arp.max_corner_in_railset = MAX_CORNER_IN_RAILSET;
arp.vertical_feeler_length = VERTICAL_FEELER_LENGTH;
arp.crossbar_feeler_elevation = CROSSBAR_FEELER_ELEVATION;
arp.half_crossbar_feeler_length = CROSSBAR_FEELER_LENGTH;
arp.farthest_degenerate_rail = FARTHEST_DEGENERATE_RAIL;
arp.max_degenerate_rail_angle = MAX_DEGENERATE_RAIL_ANGLE;
arp.connection_slop = CONNECTION_SLOP;
// look for overrides in script parameters
pParams->GetFloat("min_rail_edge_angle", &arp.min_rail_edge_angle, Script::NO_ASSERT);
pParams->GetFloat("max_rail_angle_of_assent", &arp.max_rail_angle_of_assent, Script::NO_ASSERT);
pParams->GetFloat("min_railset_length", &arp.min_railset_length, Script::NO_ASSERT);
pParams->GetFloat("min_edge_length", &arp.min_edge_length, Script::NO_ASSERT);
pParams->GetFloat("feeler_increment", &arp.feeler_increment, Script::NO_ASSERT);
pParams->GetFloat("max_low_curb_height", &arp.max_low_curb_height, Script::NO_ASSERT);
pParams->GetFloat("curb_feeler_angle_of_assent", &arp.curb_feeler_angle_of_assent, Script::NO_ASSERT);
pParams->GetFloat("max_corner_in_railset", &arp.max_corner_in_railset, Script::NO_ASSERT);
pParams->GetFloat("vertical_feeler_length", &arp.vertical_feeler_length, Script::NO_ASSERT);
pParams->GetFloat("crossbar_feeler_elevation", &arp.crossbar_feeler_elevation, Script::NO_ASSERT);
pParams->GetFloat("crossbar_feeler_length", &arp.half_crossbar_feeler_length, Script::NO_ASSERT);
pParams->GetFloat("farthest_degenerate_rail", &arp.farthest_degenerate_rail, Script::NO_ASSERT);
pParams->GetFloat("max_degenerate_rail_angle", &arp.max_degenerate_rail_angle, Script::NO_ASSERT);
pParams->GetFloat("connection_slop", &arp.connection_slop, Script::NO_ASSERT);
arp.half_crossbar_feeler_length /= 2.0f;
arp.low_curb_feeler_length = arp.max_low_curb_height / cosf(DEGREES_TO_RADIANS(arp.curb_feeler_angle_of_assent));
arp.sin_max_rail_angle_of_assent = sinf(DEGREES_TO_RADIANS(arp.max_rail_angle_of_assent));
arp.cos_min_rail_edge_angle = cosf(DEGREES_TO_RADIANS(arp.min_rail_edge_angle));
arp.cos_max_corner_in_railset = cosf(DEGREES_TO_RADIANS(arp.max_corner_in_railset));
arp.sin_curb_feeler_angle_of_assent = sinf(DEGREES_TO_RADIANS(arp.curb_feeler_angle_of_assent));
arp.cos_curb_feeler_angle_of_assent = cosf(DEGREES_TO_RADIANS(arp.curb_feeler_angle_of_assent));
arp.cos_max_degenerate_rail_angle = cosf(DEGREES_TO_RADIANS(arp.max_degenerate_rail_angle));
// turn on all feelers by default
arp.feeler_usage = ALL_FEELERS_ON;
// check for overrides in script parameters
if (pParams->ContainsFlag("no_vertical_feeler"))
arp.feeler_usage &= ~VERTICAL_FEELER_BIT;
if (pParams->ContainsFlag("no_crossbar_feeler"))
arp.feeler_usage &= ~CROSSBAR_FEELER_BIT;
if (pParams->ContainsFlag("no_low_curb_feeler"))
arp.feeler_usage &= ~LOW_CURB_FEELER_BIT;
if (pParams->ContainsFlag("no_feelers"))
arp.feeler_usage = 0;
// check additional flags
arp.remove_old_rails = false;
if (pParams->ContainsFlag("overwrite"))
{
arp.remove_old_rails = true;
}
// dump parameter state
printf("min_rail_edge_angle = %f\n", arp.min_rail_edge_angle);
printf("max_rail_angle_of_assent = %f\n", arp.max_rail_angle_of_assent);
printf("min_railset_length = %f\n", arp.min_railset_length);
printf("min_edge_length = %f\n", arp.min_edge_length);
printf("connection_slop = %f\n", arp.connection_slop);
printf("feeler_increment = %f\n", arp.feeler_increment);
printf("max_low_curb_height = %f\n", arp.max_low_curb_height);
printf("curb_feeler_angle_of_assent = %f\n", arp.curb_feeler_angle_of_assent);
printf("max_corner_in_railset = %f\n", arp.max_corner_in_railset);
printf("vertical_feeler_length = %f\n", arp.vertical_feeler_length);
printf("crossbar_feeler_length = %f\n", 2.0f * arp.half_crossbar_feeler_length);
printf("crossbar_feeler_elevation = %f\n", arp.crossbar_feeler_elevation);
if (!arp.remove_old_rails)
{
printf("farthest_degenerate_rail = %f\n", arp.farthest_degenerate_rail);
printf("max_degenerate_rail_angle = %f\n", arp.max_degenerate_rail_angle);
}
if (!(arp.feeler_usage & VERTICAL_FEELER_BIT))
printf("vertical feeler deactivated\n");
if (!(arp.feeler_usage & CROSSBAR_FEELER_BIT))
printf("crossbar feeler deactivated\n");
if (!(arp.feeler_usage & LOW_CURB_FEELER_BIT))
printf("low-curb feeler deactivated\n");
if (arp.feeler_usage == ALL_FEELERS_ON)
printf("all feelers active\n");
if (arp.remove_old_rails)
{
printf("removing existing rails\n");
}
else
{
printf("retaining existing rails\n");
}
printf("generating rails...\n");
Nx::CScene *p_scene = Nx::CEngine::sGetMainScene();
Lst::HashTable<Nx::CSector>* p_sector_list = p_scene->GetSectorList();
if (!p_sector_list)
return;
// loop through every sector in the scene
// NOTE: to do a better job, we may need to attempt to match edges between sectors
// if so, we'd have to deal with the issue of not being able to expect edge vertices to match up
p_sector_list->IterateStart();
Nx::CSector *p_sector = p_sector_list->IterateNext();
while(p_sector)
{
Nx::CCollObjTriData *p_coll_obj = p_sector->GetCollSector()->GetGeometry();
if (!p_coll_obj || !p_sector->IsActive() || !p_sector->IsCollidable())
{
p_sector = p_sector_list->IterateNext();
continue;
}
// we have the collision data, now build a list of edges
// based solely on positions of verts
int num_edges = 0;
Mth::Vector p[3];
// loop though the faces of the sector
int num_faces = p_coll_obj->GetNumFaces();
for (int face = 0; face < num_faces; face++)
{
// get the normal
Mth::Vector normal = p_coll_obj->GetFaceNormal(face);
// Get the three vertex indicies for this face
// and the vertex position into the above arrays
int v[3];
for (int i = 0; i < 3; i++)
{
v[i] = p_coll_obj->GetFaceVertIndex(face,i);
p[i] = p_coll_obj->GetRawVertexPos(v[i]);
}
// iterate over the edges (0->1, 1->2, 2->0)
for (int a = 0; a < 3; a++) // 'a' is the first point on the edge
{
int b = (a + 1) % 3; // 'b' is the second point on the edge
int c = (b + 1) % 3; // 'c' is the opposite point on the triangle
// we are considering the edge a->b
// generate a unit vector along the edge from a to b, from start to end
Mth::Vector para = (p[b] - p[a]);
float edge_length = para.Length();
if (edge_length != 0.0f) para /= edge_length; // normalize by hand since we have the length
// cull certain edges at this point; we don't cull faces at this point in
// order to better match edges
// cull very short edges
if (edge_length < arp.min_edge_length) continue;
// use edges only if less than some angle from horizontal
if (Mth::Abs(para[Y]) > arp.sin_max_rail_angle_of_assent) continue;
// generate a unit vector along the triangle face perpendicular to the edge
Mth::Vector perp = (p[c] - p[a]);
perp -= Mth::DotProduct(perp, para) * para;
perp.Normalize();
// check to see if we already have this edge's match; bail if we find a match
bool new_edge = true;
for (int edge = 0; edge < num_edges && new_edge; edge++)
{
if (arg.p_edges[edge].matched) continue;
// check for matched edge
if (very_close(arg.p_edges[edge].p0, p[a], arp) && very_close(arg.p_edges[edge].p1, p[b], arp)
|| very_close(arg.p_edges[edge].p0, p[b], arp) && very_close(arg.p_edges[edge].p1, p[a], arp))
{
new_edge = false;
arg.p_edges[edge].matched = true;
// we've found a match, let's see if the edge is a good rail
{
// ignore upside down faces
// NOTE: we could probably come up with a better heuristic using both edges' normals
if (normal[Y] < -0.707 || arg.p_edges[edge].normal[Y] < -0.707) break;
// ignore face if non-collidable
if (p_coll_obj->GetFaceFlags(face) & mFD_NON_COLLIDABLE) break;
// exclude roughly coplanar polygons
if (Mth::DotProduct(normal, arg.p_edges[edge].normal) > arp.cos_min_rail_edge_angle) break;
// cull out interior edges; that is, if sum of the normals points along the sum of the perps
if (Mth::DotProduct(perp + arg.p_edges[edge].perp, normal + arg.p_edges[edge].normal) > 0.0f) break;
// we don't want the edges of verts to generate rails, only the tops; so cull non-horizontal vert rails
if ((arg.p_edges[edge].vert || (p_coll_obj->GetFaceFlags(face) & mFD_VERT)) && Mth::Abs(para[Y]) > 0.1f) break;
// if we haven't removed the old rails
if (!arp.remove_old_rails)
{
// loop over all old rails and check for degeneracy
int check_node = 0;
for (check_node = 0; check_node < m_num_nodes; check_node++)
{
CRailNode *pRailNode = &mp_nodes[check_node];
if (!pRailNode->m_pNextLink) continue;
// is the new rail within a loose bounding box of the old rail
if (p[a][X] < pRailNode->m_BBMin[X] - arp.farthest_degenerate_rail) continue;
if (p[a][Y] < pRailNode->m_BBMin[Y] - arp.farthest_degenerate_rail) continue;
if (p[a][Z] < pRailNode->m_BBMin[Z] - arp.farthest_degenerate_rail) continue;
if (p[a][X] > pRailNode->m_BBMax[X] + arp.farthest_degenerate_rail) continue;
if (p[a][Y] > pRailNode->m_BBMax[Y] + arp.farthest_degenerate_rail) continue;
if (p[a][Z] > pRailNode->m_BBMax[Z] + arp.farthest_degenerate_rail) continue;
if (p[b][X] < pRailNode->m_BBMin[X] - arp.farthest_degenerate_rail) continue;
if (p[b][Y] < pRailNode->m_BBMin[Y] - arp.farthest_degenerate_rail) continue;
if (p[b][Z] < pRailNode->m_BBMin[Z] - arp.farthest_degenerate_rail) continue;
if (p[b][X] > pRailNode->m_BBMax[X] + arp.farthest_degenerate_rail) continue;
if (p[b][Y] > pRailNode->m_BBMax[Y] + arp.farthest_degenerate_rail) continue;
if (p[b][Z] > pRailNode->m_BBMax[Z] + arp.farthest_degenerate_rail) continue;
// is the new rail parallel to the old rail
Mth::Vector old_para = pRailNode->m_pNextLink->m_pos - pRailNode->m_pos;
old_para.Normalize();
if (Mth::Abs(Mth::DotProduct(para, old_para)) < arp.cos_max_degenerate_rail_angle) continue;
// if the perpendicular distance from the start of the new rail to the start of the old rail is small, it's degenerate
perp = p[a] - pRailNode->m_pos;
perp -= Mth::DotProduct(para, perp) * para;
if (perp.LengthSqr() < (arp.farthest_degenerate_rail * arp.farthest_degenerate_rail)) break;
// if the perpendicular distance from the end of the new rail to the start of the old rail is small, it's degenerate
perp = p[b] - pRailNode->m_pos;
perp -= Mth::DotProduct(para, perp) * para;
if (perp.LengthSqr() < (arp.farthest_degenerate_rail * arp.farthest_degenerate_rail)) break;
// if the perpendicular distance from the start of the new rail to the end of the old rail is small, it's degenerate
perp = p[a] - pRailNode->m_pNextLink->m_pos;
perp -= Mth::DotProduct(para, perp) * para;
if (perp.LengthSqr() < (arp.farthest_degenerate_rail * arp.farthest_degenerate_rail)) break;
// if the perpendicular distance from the end of the new rail to the end of the old rail is small, it's degenerate
perp = p[b] - pRailNode->m_pNextLink->m_pos;
perp -= Mth::DotProduct(para, perp) * para;
if (perp.LengthSqr() < (arp.farthest_degenerate_rail * arp.farthest_degenerate_rail)) break;
}
// if we broke from the loop, cull the rail due to degeneracy
if (check_node != m_num_nodes) break;
}
// use feelers to test for good rails within this edge
if (!consider_rail(p[a], p[b], edge, para, normal, perp, edge_length, arg, arp))
{
printf("failed: more than %d rails\n", MAX_NUM_RAILS);
goto ABORT;
}
} // END valid rail test block
} // END if (this edge matches)
} // END loop over previous edges looking for match
// if we didn't find a match
if (new_edge)
{
if (num_edges + 1 < MAX_NUM_EDGES)
{
// save the new edge
arg.p_edges[num_edges].p0 = p[a];
arg.p_edges[num_edges].p1 = p[b];
arg.p_edges[num_edges].normal = normal;
arg.p_edges[num_edges].perp = perp;
arg.p_edges[num_edges].matched = false;
arg.p_edges[num_edges].vert = p_coll_obj->GetFaceFlags(face) & mFD_VERT;
num_edges++;
}
else
{
printf("failed: more that %d edges in an object\n", MAX_NUM_EDGES);
goto ABORT; // sorry dijkstra ...
}
} // END if (new_edge)
} // END for (int a=0;a<3;a++) (iterating over 3 edges in a face)
} // END for (int face = 0; face < num_faces; face++) (iterate over faces in a collision sector)
p_sector = p_sector_list->IterateNext();
} // END loop over sectors
printf("building railsets...\n");
// we need to group in the individual rails into continuous rail sets, so we can cull short solo rails and short railsets
// note that the connectivity is not perfect in the sense that it can make loops but not loops with tails
// loop through rail set
for (int r = 0; r < arg.num_rails; r++)
{
SAutoRail& rail = arg.p_rails[r];
// loop over rail's endpoints
for (int endpoint = START; endpoint <= END; endpoint++)
{
// if the endpoint is connected
if (rail.endpoints[endpoint].connection != -1) continue;
// now we'll check for connections
int rail_key = generate_endpoint_key(rail.endpoints[endpoint].p);
// loop over all rails in same endpoint bin as rail's endpoint's bin
// we will check only these rails for connections
for (SAutoRail* next_rail = arg.p_rail_endpoint_list->GetFirstItem(rail_key);
next_rail;
next_rail = arg.p_rail_endpoint_list->GetNextItem(rail_key, next_rail))
{
SAutoRail& match_rail = *next_rail;
// we'll always find ourself
if (&match_rail == &rail) continue;
// check only rails with unconnected endpoints
if (match_rail.endpoints[START].connection != -1 && match_rail.endpoints[END].connection != -1) continue;
// rails connect only if the angle between them is small; this should be adjusted or adjustable
float dot = Mth::DotProduct(rail.para, match_rail.para);
if (Mth::Abs(dot) < arp.cos_max_corner_in_railset) continue;
bool reverse_connection = false;
// see if the start of the match rail is connected to our endpoint
if (match_rail.endpoints[START].connection == -1
&& very_close(rail.endpoints[endpoint].p, match_rail.endpoints[START].p, arp))
{
// rails connect only for obtuse angles
if (endpoint == START)
{
reverse_connection = true;
if (dot > 0.0f) continue;
}
else
{
if (dot < 0.0f) continue;
}
rail.endpoints[endpoint].connection = &match_rail - arg.p_rails;
match_rail.endpoints[START].connection = r;
}
// else see if the end of the match rail is connected to our endpoint
else if (match_rail.endpoints[END].connection == -1
&& very_close(rail.endpoints[endpoint].p, match_rail.endpoints[END].p, arp))
{
// rails connect only for obtuse angles
if (endpoint == END)
{
reverse_connection = true;
if (dot > 0.0f) continue;
}
else
{
if (dot < 0.0f) continue;
}
rail.endpoints[endpoint].connection = &match_rail - arg.p_rails;
match_rail.endpoints[END].connection = r;
} // END ifelse determining connectivity
// if this endpoint of the rail is not found to be connected, continue
if (rail.endpoints[endpoint].connection == -1) continue;
// otherwise, we'll add the rail to a railset
// if both rails are not in railset
if (match_rail.railset == -1 && rail.railset == -1)
{
if (arg.num_railsets == MAX_NUM_RAILSETS)
{
printf("failed: more that %d railsets\n", MAX_NUM_RAILSETS);
goto ABORT;
}
// setup a new railset
rail.railset = arg.num_railsets;
arg.p_railsets[arg.num_railsets].length = rail.length;
arg.num_railsets++;
// add match_rail to rail's new railset
match_rail.railset = rail.railset;
arg.p_railsets[match_rail.railset].length += match_rail.length;
// swap the rail's endpoints if the connection is reverse
if (reverse_connection)
{
SAutoRailEndpoint temp = rail.endpoints[START];
rail.endpoints[START] = rail.endpoints[END];
rail.endpoints[END] = temp;
}
// if only match_rail has a railset
}
else if (match_rail.railset != -1 && rail.railset == -1)
{
// add rail to match_rail's railset
rail.railset = match_rail.railset;
arg.p_railsets[rail.railset].length += rail.length;
// swap the rail's endpoints if the connection is reverse
if (reverse_connection)
{
SAutoRailEndpoint temp = rail.endpoints[START];
rail.endpoints[START] = rail.endpoints[END];
rail.endpoints[END] = temp;
}
// if only rail has a railset
}
else if (match_rail.railset == -1 && rail.railset != -1)
{
// add match_rail to rail's railset
match_rail.railset = rail.railset;
arg.p_railsets[match_rail.railset].length += rail.length;
// swap the match rail's endpoints if the connection is reverse
if (reverse_connection)
{
SAutoRailEndpoint temp = match_rail.endpoints[START];
match_rail.endpoints[START] = match_rail.endpoints[END];
match_rail.endpoints[END] = temp;
}
// if both rails have multrails
}
else
{
// join railsets
// NOTE: this currently waists a railset; more flexible railset data structure would fix this;
// can i use STL? if geometry is ordered in any reasonable fashion, waist should be minimal
// match_rail's railset survives
if (match_rail.railset != rail.railset)
{
match_rail.length += arg.p_railsets[rail.railset].length;
for (int n = arg.num_rails; n--; )
{
if (arg.p_rails[n].railset == rail.railset) {
arg.p_rails[n].railset = match_rail.railset;
}
}
rail.railset = match_rail.railset;
}
// swap all of the rail's railset's endpoints if the connection is reverse
// if we're closing a ring, the connection should never be reversed; so that shouldn't be a worry
if (reverse_connection)
{
// traverse in the opposite direction of the connection
int traversal_direction = endpoint ^ 1;
// traverse the rail's railset
for (int s = r; s != -1; s = arg.p_rails[s].endpoints[traversal_direction].connection)
{
SAutoRailEndpoint temp = arg.p_rails[s].endpoints[START];
arg.p_rails[s].endpoints[START] = arg.p_rails[s].endpoints[END];
arg.p_rails[s].endpoints[END] = temp;
}
}
} // END ifelse block determining rail and match_rail's previous connectivity
} // END loop over potential match rails
} // END loop over endpoints
} // END loop over all rails
printf("culling short rails...\n");
// now we have rails grouped into railsets; we can cull rails based on their length or the length of their railset
for (int r = 0; r < arg.num_rails; r++)
{
SAutoRail& rail = arg.p_rails[r];
// solo rails
if (rail.railset == -1)
{
// cull short solo rails
if (rail.length < arp.min_railset_length)
{
rail.disabled = true;
continue;
}
}
// railsets
else
{
// cull short railsets
if (arg.p_railsets[rail.railset].length < arp.min_railset_length)
{
rail.disabled = true;
continue;
}
}
arg.num_active_rails++;
} // END loop over rails
if (!arp.remove_old_rails && arg.num_rails == 0)
{
printf("failed: no rails found\n");
goto ABORT;
}
printf("creating rail nodes...\n");
// block to scope the final-rail-list generation variables
{
// we need to count the number of nodes required for the new rails
// new_num_nodes = (num rails) + (num rails with no end connection) + (num old nodes)
int new_num_nodes = arg.num_active_rails;
for (int r = arg.num_rails; r--; )
{
if (!arg.p_rails[r].disabled && arg.p_rails[r].endpoints[END].connection == -1)
{
new_num_nodes++;
}
}
if (!arp.remove_old_rails)
{
new_num_nodes += m_num_nodes;
}
// and create the array for the rails
CRailNode* p_new_nodes = (CRailNode*)Mem::Malloc(new_num_nodes * sizeof(CRailNode));
// add the old rails to the new data structure
int next_node = 0;
if (!arp.remove_old_rails)
{
// loop over the old nodes
for (; next_node < m_num_nodes; next_node++)
{
CRailNode* pOldRailNode = &mp_nodes[next_node];
CRailNode* pRailNode = &p_new_nodes[next_node];
pRailNode->m_node = next_node;
// calculate connectivity using pointer offsets
pRailNode->m_pNextLink = (pOldRailNode->m_pNextLink
? pOldRailNode->m_pNextLink - mp_nodes + p_new_nodes
: NULL);
pRailNode->m_pPrevLink = (pOldRailNode->m_pPrevLink
? pOldRailNode->m_pPrevLink - mp_nodes + p_new_nodes
: NULL);
// copy in their state
pRailNode->m_flags = pOldRailNode->m_flags;
pRailNode->m_pos = pOldRailNode->m_pos;
pRailNode->m_terrain_type = pOldRailNode->m_terrain_type; // debug colors for now pOldRailNode->m_terrain_type;
pRailNode->m_BBMin = pOldRailNode->m_BBMin;
pRailNode->m_BBMax = pOldRailNode->m_BBMax;
} // END loop over old nodes
} // END if retaining old rails
// iterate over the nodes and add the new rails to the array; each time we hit a railset, we move to the head, then traverse the set, adding the
// rails; not the most optimal algorithm but simple; we skip previously added rails and we move through the array
for (int r = 0; r < arg.num_rails; r++)
{
// because we add whole railsets at once, we may already have added any given rail
if (arg.p_rails[r].disabled) continue;
// traverse to start of this rail's railset watching for a loop
int s = r;
while (arg.p_rails[s].endpoints[START].connection != -1)
{
s = arg.p_rails[s].endpoints[START].connection;
if (s == r) break;
}
// traverse the railset, adding nodes as we go
int starting_rail = s;
CRailNode* p_starting_node = &p_new_nodes[next_node];
int last_s;
do {
CRailNode* pRailNode = &p_new_nodes[next_node];
pRailNode->m_node = next_node;
if (arg.p_rails[s].endpoints[START].connection != -1)
{
pRailNode->m_pPrevLink = &p_new_nodes[next_node - 1];
}
else
{
pRailNode->m_pPrevLink = NULL;
}
// check for a loop
if (arg.p_rails[s].endpoints[END].connection != starting_rail)
{
pRailNode->m_pNextLink = &p_new_nodes[next_node + 1];
}
else
{
pRailNode->m_pNextLink = p_starting_node;
}
pRailNode->m_flags = 0;
pRailNode->SetActive(true);
pRailNode->m_pos = arg.p_rails[s].endpoints[START].p;
if (arg.p_rails[s].railset == -1)
pRailNode->m_terrain_type = vTERRAIN_CONCSMOOTH; // red
else
pRailNode->m_terrain_type = vTERRAIN_METALSMOOTH; // blue
Rail_ComputeBB(arg.p_rails[s].endpoints[START].p, arg.p_rails[s].endpoints[END].p, pRailNode->m_BBMin, pRailNode->m_BBMax);
// mark as having been added
arg.p_rails[s].disabled = true;
next_node++;
last_s = s;
s = arg.p_rails[s].endpoints[END].connection;
} while (s != -1 && s != starting_rail);
// if not a loop
if (s != starting_rail)
{
// add extra ending node of railset
CRailNode* pRailNode = &p_new_nodes[next_node];
pRailNode->m_node = next_node;
pRailNode->m_pPrevLink = &p_new_nodes[next_node - 1];
pRailNode->m_pNextLink = NULL;
pRailNode->m_pos = arg.p_rails[last_s].endpoints[END].p;
next_node++;
}
} // END final-rail-list generation scope
// first reset the manager (this)
// Note this invalidates mp_node_array (setting it to NULL)
// so I had to patch up a couple of placed in skater.cpp
// that were using the rail manager's node array
Cleanup();
mp_nodes = p_new_nodes;
m_num_nodes = new_num_nodes;
printf("complete.\n");
}
ABORT:
delete arg.p_rail_endpoint_list;
delete [] arg.p_railsets;
delete [] arg.p_edges;
delete [] arg.p_rails;
Mem::Manager::sHandle().PopContext();
printf("-----------------------\n");
#endif
}
}