//===========================================================================// // File: Map.cpp // // Project: MUNGA Brick: Interest Manager // // Contents: Interface specifications for interest manager // //---------------------------------------------------------------------------// // Date Who Modification // // -------- --- -----------------------------------------------------------// // 11/04/94 ECH Initial coding. // // 11/29/94 JMA Changed Identities to IDs // // 02/10/95 CPB Added GaugeInterestType // // 08/25/97 ECH Infrastructure changes. // //---------------------------------------------------------------------------// // Copyright (C) 1994-1995, Virtual World Entertainment, Inc. // // All Rights reserved worldwide // // This unpublished sourcecode is PROPRIETARY and CONFIDENTIAL // //===========================================================================// #include "AdeptHeaders.hpp" #include "Tile.hpp" #include #include "Zone.hpp" #include "Map.hpp" #include "CollisionVolume.hpp" #include "LightManager.hpp" #include #include #include #include "Application.hpp" #if defined(BSP_BUG) bool Adept::debug_bsp=true; #endif bool TerrainBSP::TestLine( CollisionQuery *query, Scalar enter, Scalar leave ) { Check_Object(this); Check_Object(query); // //----------------------------------------- // Set up the recursion avoidance variables //----------------------------------------- // Check_Object(query->m_line); Line3D *line = query->m_line; Check_Object(query->m_zone); Zone *zone = query->m_zone; Check_Object(query->m_tile); Tile *tile = query->m_tile; TerrainBSP *bsp = this; // //------------------------------------------------------------------------- // Here is the real start of the loop. We first want to find which side of // the plane we are on and which direction we are headed //------------------------------------------------------------------------- // Start_Over: Check_Object(bsp); Verify(enter >= 0.0f && enter <= leave); Verify(bsp->m_planeIndex < zone->m_planeCount); const Plane *plane = &zone->m_planes[bsp->m_planeIndex]; Scalar dot = plane->normal * line->m_direction; Scalar separation = plane->GetDistanceTo(line->m_origin); // //---------------------------------------------------------- // We are heading into the plane, so see when we will hit it //---------------------------------------------------------- // #if defined(BSP_BUG) if (debug_bsp) { SPEW(( BSP_BUG, "### %sBSP %d", (plane->m_normal.y > SMALL) ? "" : "pseudo ", bsp-tile->m_BSP )); SPEW(( BSP_BUG, " enter: %f, leave: %f, dot: %f, sep: %f", enter, leave, dot, separation )); } #endif if (dot < -SMALL) { Scalar distance = -separation/dot; #if defined(BSP_BUG) if (debug_bsp) SPEW(( BSP_BUG, " distance into plane = %f", distance )); #endif // //------------------------------------------------------------------ // See if we are still on the outside of the plane when we take into // account the distance already moved along the line. If we are, // look to see if the line will strike the plane. If not, and there // is no tree on the outer halfspace of the plane, we won't hit // anything so just return //------------------------------------------------------------------ // if (distance > enter) { if (distance > leave) { if (!bsp->m_outerIndex) { #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " won't reach plane and outside is empty")); #endif return false; } // //------------------------------------------------------------- // There is a tree to check on the outside of our plane, so set // it as the current tree and start over //------------------------------------------------------------- // #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " won't reach plane, jumping to outer halfspace")); #endif Verify(bsp->m_outerIndex < tile->m_BSPCount); bsp = &tile->m_BSP[bsp->m_outerIndex]; goto Start_Over; } // //--------------------------------------------------------------- // We will cross into the plane space, so first check the outer // half via recursion, as we will need to continue into the inner // space. We will shorten the line for his test to make sure // that any plane hits happen only within our outer halfspace. // If our child hits something, stop the traversal. Note that if // there is no tree on the outer space, it is empty and can just // be ignored //--------------------------------------------------------------- // if (bsp->m_outerIndex) { Verify(bsp->m_outerIndex < tile->m_BSPCount); Verify(enter <= distance); Verify(distance <= leave); Scalar new_leave = leave; Max_Clamp(new_leave, distance); if (tile->m_BSP[bsp->m_outerIndex].TestLine(query, enter, new_leave)) { // // I'm commenting out this line because I have never beaten the case where // rounding error causes the line to exit one tile and enter the next tile // under the terrain... // // Verify(!line->m_length || query->m_normal->y > SMALL); return true; } #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, "### returning to BSP %d", bsp-tile->m_BSP)); #endif } // //----------------------------------------------------------------- // We have struck the plane, so this is the normal of impact unless // we go through another plane. Bump the enter to be on our plane // surface to make sure that nothing tries to check against the // outer space of the plane //----------------------------------------------------------------- // #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " crossing <%f,%f,%f>", plane->m_normal.x, plane->m_normal.y, plane->m_normal.z)); #endif *query->m_normal = plane->normal; enter = distance; } // //---------------------------------------------------------------------- // At this point we need only concern ourself with the inner space of // the plane. If there is no tree there, then it is solid space. We // set the line length to the amount already traveled and then stop with // a successful hit //---------------------------------------------------------------------- // if (!bsp->m_innerIndex) { #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " inside is solid")); #endif line->m_length = enter; // // I'm commenting out this line because I have never beaten the case where // rounding error causes the line to exit one tile and enter the next tile // under the terrain... // // Verify(!line->m_length || query->m_normal->y > SMALL); return true; } // //------------------------------------------------------------ // Set our inner space tree as the current tree and start over //------------------------------------------------------------ // #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " jumping to inner halfspace")); #endif Verify(bsp->m_innerIndex < tile->m_BSPCount); bsp = &tile->m_BSP[bsp->m_innerIndex]; goto Start_Over; } // //--------------------------------------------------------------- // If we are heading out of the plane, see when we will strike it //--------------------------------------------------------------- // else if (dot > SMALL) { Scalar distance = -separation/dot; #if defined(BSP_BUG) if (debug_bsp) SPEW(( BSP_BUG, " distance out of plane = %f", distance )); #endif // //---------------------------------------------------------------- // If we are still inside the plane when we take into account the // distance already travelled, see if the inside is solid. If so, // set the line length to that already travelled and stop the // traversal with a successful hit //---------------------------------------------------------------- // if (distance > enter) { if (!bsp->m_innerIndex) { #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " inside is solid")); #endif line->m_length = enter; // // I'm commenting out this line because I have never beaten the case where // rounding error causes the line to exit one tile and enter the next tile // under the terrain... // // Verify(!line->m_length || query->m_normal->y > SMALL); return true; } // //--------------------------------------------------------------- // We now know there is a tree inside our plane halfspace. If we // won't hit the surface of the plane, set it as the current tree // and start over //--------------------------------------------------------------- // if (distance > leave) { #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " jumping into inner halfspace")); #endif Verify(bsp->m_innerIndex < tile->m_BSPCount); bsp = &tile->m_BSP[bsp->m_innerIndex]; goto Start_Over; } // //--------------------------------------------------------------- // We will cross into the plane space, so first check the inner // half via recursion, as we will need to continue into the outer // space. We will shorten the line for his test to make sure // that any plane hits happen only within our inner halfspace. // If our child hits something, stop the traversal //--------------------------------------------------------------- // Verify(bsp->m_innerIndex < tile->m_BSPCount); Verify(enter <= distance); Verify(distance <= leave); Scalar new_leave = leave; Max_Clamp(new_leave, distance); if (tile->m_BSP[bsp->m_innerIndex].TestLine(query, enter, new_leave)) { // // I'm commenting out this line because I have never beaten the case where // rounding error causes the line to exit one tile and enter the next tile // under the terrain... // // Verify(!line->m_length || query->m_normal->y > SMALL); return true; } #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, "### returning to BSP %d", bsp-tile->m_BSP)); #endif enter = distance; } // //--------------------------------------------------------------------- // If we get here, we only need concern ourselves with the outer space. // If it is empty, stop the traversal with a miss //--------------------------------------------------------------------- // if (!bsp->m_outerIndex) { #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " outside is empty")); #endif return false; } // //------------------------------------------------------------ // Set the outer space tree as the current tree and start over //------------------------------------------------------------ // #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " jumping to outer halfspace")); #endif Verify(bsp->m_outerIndex < tile->m_BSPCount); bsp = &tile->m_BSP[bsp->m_outerIndex]; goto Start_Over; } // //---------------------------------------------------------------------- // We are moving parallel to the plane, so check only the inside of the // plane if that's where we are //---------------------------------------------------------------------- // else if (separation < SMALL) { #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " Inside plane")); #endif if (!bsp->m_innerIndex) { #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " inside is solid")); #endif line->m_length = enter; // // I'm commenting out this line because I have never beaten the case where // rounding error causes the line to exit one tile and enter the next tile // under the terrain... // // Verify(!line->m_length || query->m_normal->y > SMALL); return true; } #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " jumping to inner halfspace")); #endif Verify(bsp->m_innerIndex < tile->m_BSPCount); bsp = &tile->m_BSP[bsp->m_innerIndex]; goto Start_Over; } // //------------------------ // Check outside the plane //------------------------ // #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " Outside plane")); #endif if (!bsp->m_outerIndex) { #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " outside is empty")); #endif return false; } #if defined(BSP_BUG) if (debug_bsp) SPEW((BSP_BUG, " jumping to outer halfspace")); #endif Verify(bsp->m_outerIndex < tile->m_BSPCount); bsp = &tile->m_BSP[bsp->m_outerIndex]; goto Start_Over; } //############################################################################# //########################### Zone ################################# //############################################################################# Tile::ClassData *Tile::DefaultData = NULL; const Stuff::RGBAColor *Tile::s_GroundColor = NULL; DECLARE_TIMER(static, Ray_Vs_BSP); DECLARE_TIMER(static, Ray_Vs_OBB); static bool Hide_Ground=false; static bool __stdcall CheckHideGround() {return Hide_Ground;} static void __stdcall EnableHideGround() {Hide_Ground = !Hide_Ground;} static bool Hide_TileEntities=false; static bool __stdcall CheckHideTileEntities() {return Hide_TileEntities;} static void __stdcall EnableHideTileEntities() {Hide_TileEntities = !Hide_TileEntities;} //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Tile::InitializeClass() { Verify(!DefaultData); DefaultData = new ClassData( TileClassID, "Adept::Tile", ElementRenderer::GroupElement::DefaultData, reinterpret_cast(&Make) ); Register_Object(DefaultData); s_GroundColor = NULL; Initialize_Timer(Ray_Vs_BSP, "Ray vs BSP"); Initialize_Timer(Ray_Vs_OBB, "Ray vs OBB"); AddDebuggerMenuItem("Libraries\\Adept\\Hide Ground", CheckHideGround, EnableHideGround, 0 ); AddDebuggerMenuItem("Libraries\\Adept\\Hide Tile Entities", CheckHideTileEntities, EnableHideTileEntities, 0 ); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Tile::TerminateClass() { Unregister_Object(DefaultData); delete DefaultData; DefaultData = NULL; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Tile::TestInstance() { Verify(IsDerivedFrom(DefaultData)); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Tile::Tile( Zone *zone, BYTE row, BYTE column ): GroupElement(DefaultData), m_collidees(NULL), m_colliders(NULL), m_lights(NULL), damagableEntityList(NULL) { Check_Pointer(this); m_draw = static_cast(&Tile::Draw); AdoptStateChange(new ElementRenderer::StateChange); m_row = row; m_column = column; m_zone = zone; m_ground = NULL; m_BSP = NULL; m_BSPCount = 0; m_lightsChanged = false; m_DamageBitStart = -1; m_damagableEntityCount = 0; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Tile::~Tile() { Check_Object(this); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Tile::Save(MemoryStream *stream) { Check_Object(this); Check_Object(stream); // //-------------------------- // Write stuff to the stream //-------------------------- // *stream << static_cast(ElementRenderer::GroupElementClassID) << m_state; if (m_stateChange) { Check_Object(m_stateChange); Verify(m_state & PropertyChangeFlag); m_stateChange->Save(stream); } if (!IsLocalToParentIdentity()) *stream << m_localToParent; // //-------------------------- // Write the bounding volume //-------------------------- // if (IsBoundedByOBB()) { *stream << m_localOBB.localToParent << m_localOBB.axisExtents << m_localOBB.sphereRadius; } else { Point3D center(m_localOBB.localToParent); *stream << center; *stream << m_localOBB.sphereRadius; } // //----------------------------------------------------------- // Write out the child count and then the children themselves //----------------------------------------------------------- // ChainIteratorOf elements(&m_group); ElementRenderer::Element *element; *stream << elements.GetSize(); while ((element = elements.ReadAndNext()) != NULL) { Check_Object(element); element->Save(stream); } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void* Tile::TestTile( ElementRenderer::GridElement *grid, WORD cell, ElementRenderer::CollisionQuery *query, Scalar enter, Scalar leave ) { Check_Object(grid); Check_Object(query); Verify(query->m_line->m_length >= 0.0f); Entity::CollisionQuery *masked_query = Cast_Pointer(Entity::CollisionQuery*, query); // //------------------------------- // Figure out what tile we are in //------------------------------- // Tile *tile = Cast_Object(Tile*, grid->GetIndexedElement(cell)); Entity *result = NULL; // //----------------------------------------------- // Find the zone - it should be the grid's parent //----------------------------------------------- // if (masked_query->m_collisionMask&Entity::WalkerMask) { Zone *zone = tile->m_zone; Check_Object(zone); Normal3D normal; Check_Object(masked_query->m_line); normal.Vector3D::Negate(masked_query->m_line->m_direction); TerrainBSP::CollisionQuery bsp_query(masked_query->m_line, &normal, zone, tile); // //------------------------- // Try out our new BSP code //------------------------- // bool hit; Check_Object(tile->m_BSP); { RAYCAST_LOGIC("vs. BSPs"); Start_Timer(Ray_Vs_BSP); hit = tile->m_BSP->TestLine(&bsp_query, enter, leave); Stop_Timer(Ray_Vs_BSP); } if (hit) { *masked_query->m_normal = normal; // //------------------------------------------------------------------------ // This is turned off for now because I couldn't figure out the case where // a ray exits one tile and enters another tile underneath the terrain - I // suspect it has something to do with roundoff errors //------------------------------------------------------------------------ // Verify(*query->m_normal * query->m_line->m_direction < -SMALL); Point3D impact; query->m_line->FindEnd(&impact); query->m_material = zone->GetMaterial(impact); result = Map::GetInstance(); } } // //--------------------------- // Check each collidee entity //--------------------------- // ChainIteratorOf collidees(&tile->m_collidees); Entity *entity; while ((entity = collidees.ReadAndNext()) != NULL) { Check_Object(entity); Verify(!entity->IsACollider()); Verify(entity->GetInterestLevel() != Entity::DormantInterestLevel); // //--------------------------------------------------------------------- // If the ray is coming from us, don't check. Also don't bother if the // masks don't match //--------------------------------------------------------------------- // if ( entity == masked_query->m_raySource || !entity->CouldCollideWith(masked_query->m_collisionMask) ) continue; // //----------------------------------------------------------------------- // If there is a hit on this element, set the return value and figure out // the relative position of the hit to the target entity //----------------------------------------------------------------------- // CollisionVolume *bounds = entity->GetHierarchicalVolume(); if (!bounds) bounds = entity->GetSolidVolume(); Check_Object(bounds); { RAYCAST_LOGIC("vs. Collidees"); Start_Timer(Ray_Vs_OBB); Verify(query->m_line->m_length >= 0.0f); bounds = bounds->CastRay(masked_query, entity, entity->GetLocalToWorld()); Stop_Timer(Ray_Vs_OBB); } if (bounds) { result = bounds->m_entity; query->m_data = NULL; } } // //--------------------------- // Check each collider entity //--------------------------- // ChainIteratorOf colliders(&tile->m_colliders); while ((entity = colliders.ReadAndNext()) != NULL) { Check_Object(entity); Verify(entity->IsACollider()); Verify(entity->GetInterestLevel() != Entity::DormantInterestLevel); // //--------------------------------------------------------------------- // If the ray is coming from us, don't check. Also don't bother if the // masks don't match //--------------------------------------------------------------------- // if ( entity == masked_query->m_raySource || !entity->CouldCollideWith(masked_query->m_collisionMask) ) continue; // //----------------------------------------------------------------------- // If there is a hit on this element, set the return value and figure out // the relative position of the hit to the target entity //----------------------------------------------------------------------- // CollisionVolume *bounds = entity->GetHierarchicalVolume(); if (!bounds) bounds = entity->GetSolidVolume(); Check_Object(bounds); { RAYCAST_LOGIC("vs. Colliders"); Start_Timer(Ray_Vs_OBB); bounds = bounds->CastRay(masked_query, entity, entity->GetLocalToWorld()); Stop_Timer(Ray_Vs_OBB); } if (bounds) { result = bounds->m_entity; query->m_data = NULL; } } return result; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Tile::SetDrawState() { Check_Object(this); m_draw = static_cast(&Tile::Draw); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Tile::Draw( ElementRenderer::CameraElement *camera, const ElementRenderer::StateChange *inherited_state, int culling_state ) { Check_Object(this); Check_Object(inherited_state); Check_Object(camera); Verify(culling_state != -1); // //-------------------------- // Construct our light array //-------------------------- // if (m_lightsChanged) UpdateLights(); // //------------------- // Mix the properties //------------------- // Check_Object(m_stateChange); ElementRenderer::StateChange mixed; mixed.Mix(*inherited_state, *m_stateChange); MixLights( mixed.m_lights, inherited_state->m_lights, m_stateChange->m_lights ); // //-------------------------------------- // Draw our bounds if we are supposed to //-------------------------------------- // #if defined(LAB_ONLY) Callback *callback = GetCallbackSet(); unsigned index = GetCallbackIndex(); if (callback && callback[index]) { Check_Pointer(callback[index]); (*callback[index])(camera, inherited_state, culling_state, this); } #endif // //------------------ // Draw our children //------------------ // ChainIteratorOf children(&m_group); ElementRenderer::Element *child; while ((child = children.ReadAndNext()) != NULL) { Check_Object(child); // //--------------------- // Deal with the ground //--------------------- // if (child==m_ground) { #if defined(LAB_ONLY) if (Hide_Ground) continue; #endif if (s_GroundColor) { RGBAColor old(Element::FadeColor); Element::FadeColor = *s_GroundColor; camera->DrawElement(child, &mixed); Element::FadeColor = old; } else camera->DrawElement(child, &mixed); } // //------------------------ // Draw our other children //------------------------ // else { #if defined(LAB_ONLY) if (Hide_TileEntities) continue; #endif camera->DrawElement(child, &mixed); } } } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Tile::UpdateLights() { Check_Object(this); Verify(m_lightsChanged); ChainIteratorOf light_chain(&m_lights); gosFX::Light *light; Check_Object(m_stateChange); int i=0; while ( (light = light_chain.ReadAndNext()) != NULL && im_light->GetIntensity()>Stuff::SMALL) { m_stateChange->m_lights[i++] = light->m_light; } } if (i < MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive) m_stateChange->m_lights[i] = NULL; Verify(i <= MidLevelRenderer::Limits::Max_Number_Of_Lights_Per_Primitive); m_lightsChanged = false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // void Tile::AddEntityToDamagableList(Entity* entity) { if (!entity->IsDestroyable()) return; if (Application::GetInstance()->GetApplicationState() != ApplicationStateEngine::LoadingGameState) { return; } if (m_damagableEntityCount >= damagableEntityList.GetLength()) { damagableEntityList.SetLength(m_damagableEntityCount + 10); } damagableEntityList[m_damagableEntityCount] = entity; ++m_damagableEntityCount; }