/////////////////////////////////////////////////////// // rail.cpp #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // REMOVE #include #ifdef __PLAT_NGPS__ #include // for camera #include #include #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;im_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;im_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;im_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;im_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;nodem_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_nodeIsActive()) { 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) 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_nodem_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_nodem_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_nodem_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_nodeGetNextLink() == 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_nodem_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_nodeGetFlag(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_nodem_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_nodeGetActive()) { 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; iGetSize(); ++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; iGetSize(); ++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;iGetNode()] = p_node; // p_node = p_node->m_pNext; } // now go through all the node // p_node = mp_first_node; for (int node=0;nodem_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((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(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* 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 } }