[10165] New collission system (vmap) implementation

Important:
* You have to re-extract and assemble vmaps
* Update your config file, new option 'vmap.enableIndoorCheck' added

New features:
* Include WMO+DBC area information for correct subarea identification and indoor check
* Support for WMO liquid (fishing/swimming in cities, instances and oterh WMO based environments)

Technical changes:
* New Bounding Interval Hierarchy (BIH) data structure for better performance
* Referenced model data for reduced memory usage,
  needs more files, but reduces overall file size from ~1.9GB to ~550MB

Additional Authors:
arrai (DBC handling and indoor detection)
faramir118 (windows support and bug investigation)
And of course thanks Vladimir for a lot of patience and support!
This commit is contained in:
Lynx3d 2010-07-08 23:17:18 +02:00
parent c2bcfd0f18
commit 5e89098a61
57 changed files with 3472 additions and 5694 deletions

View file

@ -1075,249 +1075,106 @@ float Map::GetHeight(float x, float y, float z, bool pUseVmaps) const
}
}
uint16 Map::GetAreaFlag(float x, float y, float z) const
inline bool IsOutdoorWMO(uint32 mogpFlags, int32 adtId, int32 rootId, int32 groupId,
WMOAreaTableEntry const* wmoEntry, AreaTableEntry const* atEntry)
{
uint16 areaflag;
if(GridMap *gmap = const_cast<Map*>(this)->GetGrid(x, y))
areaflag = gmap->getArea(x, y);
// this used while not all *.map files generated (instances)
else
areaflag = GetAreaFlagByMapId(i_id);
bool outdoor = true;
//FIXME: some hacks for areas above or underground for ground area
// required for area specific spells/etc, until map/vmap data
// not provided correct areaflag with this hacks
switch(areaflag)
if(wmoEntry && atEntry)
{
case 1146: // Blade's Edge Mountains
case 1409: // Forge Camp: Wrath (Blade's Edge Mountains)
if (x > 3025.0f && x < 3207.0f && y > 6987.0f && y < 7165.0f && z < 183.0f)
areaflag = 1404; // Blackwing Coven (Blade's Edge Mountains)
break;
// Acherus: The Ebon Hold (Plaguelands: The Scarlet Enclave)
case 1984: // Plaguelands: The Scarlet Enclave
case 2076: // Death's Breach (Plaguelands: The Scarlet Enclave)
case 2745: // The Noxious Pass (Plaguelands: The Scarlet Enclave)
if(z > 350.0f) areaflag = 2048; break;
// Acherus: The Ebon Hold (Eastern Plaguelands)
case 856: // The Noxious Glade (Eastern Plaguelands)
case 2456: // Death's Breach (Eastern Plaguelands)
if(z > 350.0f) areaflag = 1950; break;
// Winterfin Caverns
case 1652: // Coldarra
case 1653: // The Westrift
case 1661: // Winterfin Village
if (x > 3823.0f && x < 4141.5f && y > 6247.0f && y < 64890.0f && z < 42.5f)
areaflag = 1723;
break;
// Moonrest Gardens
case 1787:
if (x > 3315.3f && x < 3361.6f && y > 2469.4f && y < 2565.8f && z > 197.0f)
areaflag = 1786; // Surge Needle (cords not entirely correct, will need round circle if this is really needed(see spell 47097 eff 77))
break;
// Dalaran
case 2492: // Forlorn Woods (Crystalsong Forest)
case 2371: // Valley of Echoes (Icecrown Glacier)
if (x > 5568.0f && x < 6116.0f && y > 282.0f && y < 982.0f && z > 563.0f)
{
// Krasus' Landing (Dalaran), fast check
if (x > 5758.77f && x < 5869.03f && y < 510.46f)
{
// Krasus' Landing (Dalaran), with open east side
if (y < 449.33f || (x-5813.9f)*(x-5813.9f)+(y-449.33f)*(y-449.33f) < 1864.0f)
{
areaflag = 2531; // Note: also 2633, possible one flight allowed and other not allowed case
break;
}
}
// Sunreaver's Sanctuary (Dalaran) (Enter,Left,Right-border lines)
if ((y-548.11f)/(568.80f-548.11f) <= (x-5849.30f)/(5866.57f-5849.30f) &&
(y-582.76f)/(622.97f-582.76f) <= (x-5961.64f)/(5886.09f-5961.64f) &&
(y-446.79f)/(508.22f-446.79f) >= (x-5867.66f)/(5846.12f-5867.66f))
{
// need exclude 2 shop rooms
if (((x-5862.26f)*(x-5862.26f)+(y-554.76f)*(y-554.76f) > 335.85f/*310.64f*/ || z > 671.12f) &&
((y-580.51f)/(608.37f-580.51f) <= (x-5896.23f)/(5859.79f-5896.23f) ||
(y-580.51f)/(610.49f-580.51f) <= (x-5896.23f)/(5919.15f-5896.23f) || z > 669.10f))
{
areaflag = 2687;
break;
}
}
// The Silver Enclave (Dalaran) (Enter,Left,Right-border lines)
if ((y-693.19f)/(737.41f-693.19f) >= (x-5732.80f)/(5769.38f-5732.80f) &&
(y-693.19f)/(787.00f-693.19f) >= (x-5732.80f)/(5624.17f-5732.80f) &&
(y-737.41f)/(831.30f-737.41f) <= (x-5769.38f)/(5671.15f-5769.38f))
{
// need exclude ground floor shop room
if ((x-5758.07f)*(x-5758.07f)+(y-738.18f)*(y-738.18f) > 83.30f || z > 650.00f)
{
areaflag = 3007;
break;
}
}
// Dalaran
areaflag = 2153;
}
break;
// The Violet Citadel (Dalaran) or Dalaran
case 2484: // The Twilight Rivulet (Crystalsong Forest)
case 1593: // Crystalsong Forest
// Dalaran
if (x > 5568.0f && x < 6116.0f && y > 282.0f && y < 982.0f && z > 563.0f)
{
// The Violet Citadel (Dalaran), fast check
if (x > 5721.1f && x < 5884.66f && y > 764.4f && y < 948.0f)
{
// The Violet Citadel (Dalaran)
if ((x-5803.0f)*(x-5803.0f)+(y-846.18f)*(y-846.18f) < 6690.0f)
{
areaflag = 2696;
break;
}
}
// Sunreaver's Sanctuary (Dalaran) (Enter,Left,Right-border lines)
if ((y-581.27f)/(596.73f-581.27f) <= (x-5858.57f)/(5871.87f-5858.57f) &&
(y-582.76f)/(622.97f-582.76f) <= (x-5961.64f)/(5886.09f-5961.64f) &&
(y-446.79f)/(508.22f-446.79f) >= (x-5867.66f)/(5846.12f-5867.66f))
{
areaflag = 2687;
break;
}
// The Silver Enclave (Dalaran) (Enter,Left,Right-border lines)
if ((y-693.19f)/(737.41f-693.19f) >= (x-5732.80f)/(5769.38f-5732.80f) &&
(y-693.19f)/(787.00f-693.19f) >= (x-5732.80f)/(5624.17f-5732.80f) &&
(y-737.41f)/(831.30f-737.41f) <= (x-5769.38f)/(5671.15f-5769.38f))
{
// need exclude ground floor shop room
if ((x-5758.07f)*(x-5758.07f)+(y-738.18f)*(y-738.18f) > 64.30f || z > 650.00f)
{
areaflag = 3007;
break;
}
}
// Dalaran
areaflag = 2153;
}
break;
// Vargoth's Retreat (Dalaran) or The Violet Citadel (Dalaran) or Dalaran
case 2504: // Violet Stand (Crystalsong Forest)
// Dalaran
if (x > 5568.0f && x < 6116.0f && y > 282.0f && y < 982.0f && z > 563.0f)
{
// The Violet Citadel (Dalaran), fast check
if (x > 5721.1f && x < 5884.66f && y > 764.4f && y < 948.0f)
{
// Vargoth's Retreat (Dalaran), nice slow circle with upper limit
if (z < 898.0f && (x-5765.0f)*(x-5765.0f)+(y-862.4f)*(y-862.4f) < 262.0f)
{
areaflag = 2748;
break;
}
// The Violet Citadel (Dalaran)
if ((x-5803.0f)*(x-5803.0f)+(y-846.18f)*(y-846.18f) < 6690.0f)
{
areaflag = 2696;
break;
}
}
// Dalaran
areaflag = 2153;
}
break;
// Maw of Neltharion (cave)
case 164: // Dragonblight
case 1797: // Obsidian Dragonshrine (Dragonblight)
case 1827: // Wintergrasp
case 2591: // The Cauldron of Flames (Wintergrasp)
if (x > 4364.0f && x < 4632.0f && y > 1545.0f && y < 1886.0f && z < 200.0f) areaflag = 1853; break;
// Undercity (sewers enter and path)
case 179: // Tirisfal Glades
if (x > 1595.0f && x < 1699.0f && y > 535.0f && y < 643.5f && z < 30.5f) areaflag = 685; break;
// Undercity (Royal Quarter)
case 210: // Silverpine Forest
case 316: // The Shining Strand (Silverpine Forest)
case 438: // Lordamere Lake (Silverpine Forest)
if (x > 1237.0f && x < 1401.0f && y > 284.0f && y < 440.0f && z < -40.0f) areaflag = 685; break;
// Undercity (cave and ground zone, part of royal quarter)
case 607: // Ruins of Lordaeron (Tirisfal Glades)
// ground and near to ground (by city walls)
if(z > 0.0f)
{
if (x > 1510.0f && x < 1839.0f && y > 29.77f && y < 433.0f) areaflag = 685;
}
// more wide underground, part of royal quarter
else
{
if (x > 1299.0f && x < 1839.0f && y > 10.0f && y < 440.0f) areaflag = 685;
}
break;
// The Makers' Perch (ground) and Makers' Overlook (ground and cave)
case 1335: // Sholazar Basin
// The Makers' Perch ground (fast box)
if (x > 6100.0f && x < 6250.0f && y > 5650.0f && y < 5800.0f)
{
// nice slow circle
if ((x-6183.0f)*(x-6183.0f)+(y-5717.0f)*(y-5717.0f) < 2500.0f)
areaflag = 2189;
}
// Makers' Overlook (ground and cave)
else if (x > 5634.48f && x < 5774.53f && y < 3475.0f && z > 300.0f)
{
if (y > 3380.26f || (y > 3265.0f && z < 360.0f))
areaflag = 2187;
}
break;
// The Makers' Perch (underground)
case 2147: // The Stormwright's Shelf (Sholazar Basin)
if (x > 6199.0f && x < 6283.0f && y > 5705.0f && y < 5817.0f && z < 38.0f) areaflag = 2189; break;
// Makers' Overlook (deep cave)
case 267: // Icecrown
if (x > 5684.0f && x < 5798.0f && y > 3035.0f && y < 3367.0f && z < 358.0f) areaflag = 2187; break;
// Wyrmrest Temple (Dragonblight)
case 1814: // Path of the Titans (Dragonblight)
case 1897: // The Dragon Wastes (Dragonblight)
// fast box
if (x > 3400.0f && x < 3700.0f && y > 130.0f && y < 420.0f)
{
// nice slow circle
if ((x-3546.87f)*(x-3546.87f)+(y-272.71f)*(y-272.71f) < 19600.0f) areaflag = 1791;
}
break;
// The Forlorn Mine (The Storm Peaks)
case 166: // The Storm Peaks
case 2207: // Brunnhildar Village (The Storm Peaks)
case 2209: // Sifreldar Village (The Storm Peaks)
case 2227: // The Foot Steppes (The Storm Peaks)
// fast big box
if (x > 6812.0f && x < 7049.5f && y > -1474.5f && y < -1162.5f && z < 866.15f)
{
// east, avoid ground east-south corner wrong detection
if (x > 6925.0f && y > -1474.5f && y < -1290.0f)
areaflag = 2213;
// east middle, wide part
else if (x > 6812.0f && y > -1400.0f && y < -1290.0f)
areaflag = 2213;
// west middle, avoid ground west-south corner wrong detection
else if (x > 6833.0f && y > -1474.5f && y < -1233.0f)
areaflag = 2213;
// west, avoid ground west-south corner wrong detection
else if (x > 6885.0f && y > -1474.5f && y < -1162.5f)
areaflag = 2213;
}
break;
default:
break;
if(atEntry->flags & AREA_FLAG_OUTSIDE)
return true;
if(atEntry->flags & AREA_FLAG_INSIDE)
return false;
}
outdoor = mogpFlags&0x8;
if(wmoEntry)
{
if(wmoEntry->Flags & 4)
return true;
if((wmoEntry->Flags & 2)!=0)
outdoor = false;
}
return outdoor;
}
bool Map::IsOutdoors(float x, float y, float z) const
{
uint32 mogpFlags;
int32 adtId, rootId, groupId;
// no wmo found? -> outside by default
if(!GetAreaInfo(x, y, z, mogpFlags, adtId, rootId, groupId))
return true;
AreaTableEntry const* atEntry = 0;
WMOAreaTableEntry const* wmoEntry= GetWMOAreaTableEntryByTripple(rootId, adtId, groupId);
if(wmoEntry)
{
DEBUG_LOG("Got WMOAreaTableEntry! flag %u, areaid %u", wmoEntry->Flags, wmoEntry->areaId);
atEntry = GetAreaEntryByAreaID(wmoEntry->areaId);
}
return IsOutdoorWMO(mogpFlags, adtId, rootId, groupId, wmoEntry, atEntry);
}
bool Map::GetAreaInfo(float x, float y, float z, uint32 &flags, int32 &adtId, int32 &rootId, int32 &groupId) const
{
float vmap_z = z;
VMAP::IVMapManager* vmgr = VMAP::VMapFactory::createOrGetVMapManager();
if (vmgr->getAreaInfo(GetId(), x, y, vmap_z, flags, adtId, rootId, groupId))
{
// check if there's terrain between player height and object height
if(GridMap *gmap = const_cast<Map*>(this)->GetGrid(x, y))
{
float _mapheight = gmap->getHeight(x,y);
// z + 2.0f condition taken from GetHeight(), not sure if it's such a great choice...
if(z + 2.0f > _mapheight && _mapheight > vmap_z)
return false;
}
return true;
}
return false;
}
uint16 Map::GetAreaFlag(float x, float y, float z, bool *isOutdoors) const
{
uint32 mogpFlags;
int32 adtId, rootId, groupId;
WMOAreaTableEntry const* wmoEntry = 0;
AreaTableEntry const* atEntry = 0;
bool haveAreaInfo = false;
if(GetAreaInfo(x, y, z, mogpFlags, adtId, rootId, groupId))
{
haveAreaInfo = true;
if(wmoEntry = GetWMOAreaTableEntryByTripple(rootId, adtId, groupId))
atEntry = GetAreaEntryByAreaID(wmoEntry->areaId);
}
uint16 areaflag;
if (atEntry)
areaflag = atEntry->exploreFlag;
else
{
if(GridMap *gmap = const_cast<Map*>(this)->GetGrid(x, y))
areaflag = gmap->getArea(x, y);
// this used while not all *.map files generated (instances)
else
areaflag = GetAreaFlagByMapId(i_id);
}
if (isOutdoors)
{
if (haveAreaInfo)
*isOutdoors = IsOutdoorWMO(mogpFlags, adtId, rootId, groupId, wmoEntry, atEntry);
else
*isOutdoors = true;
}
return areaflag;
}
@ -1331,10 +1188,50 @@ uint8 Map::GetTerrainType(float x, float y ) const
GridMapLiquidStatus Map::getLiquidStatus(float x, float y, float z, uint8 ReqLiquidType, GridMapLiquidData *data) const
{
GridMapLiquidStatus result = LIQUID_MAP_NO_WATER;
VMAP::IVMapManager* vmgr = VMAP::VMapFactory::createOrGetVMapManager();
float liquid_level, ground_level = INVALID_HEIGHT;
uint32 liquid_type;
if (vmgr->GetLiquidLevel(GetId(), x, y, z, ReqLiquidType, liquid_level, ground_level, liquid_type))
{
DEBUG_LOG("getLiquidStatus(): vmap liquid level: %f ground: %f type: %u", liquid_level, ground_level, liquid_type);
// Check water level and ground level
if (liquid_level > ground_level && z > ground_level - 2)
{
// All ok in water -> store data
if (data)
{
data->type = liquid_type;
data->level = liquid_level;
data->depth_level = ground_level;
}
// For speed check as int values
int delta = int((liquid_level - z) * 10);
// Get position delta
if (delta > 20) // Under water
return LIQUID_MAP_UNDER_WATER;
if (delta > 0 ) // In water
return LIQUID_MAP_IN_WATER;
if (delta > -1) // Walk on water
return LIQUID_MAP_WATER_WALK;
result = LIQUID_MAP_ABOVE_WATER;
}
}
if(GridMap* gmap = const_cast<Map*>(this)->GetGrid(x, y))
return gmap->getLiquidStatus(x, y, z, ReqLiquidType, data);
else
return LIQUID_MAP_NO_WATER;
{
GridMapLiquidData map_data;
GridMapLiquidStatus map_result = gmap->getLiquidStatus(x, y, z, ReqLiquidType, &map_data);
// Not override LIQUID_MAP_ABOVE_WATER with LIQUID_MAP_NO_WATER:
if (map_result != LIQUID_MAP_NO_WATER && (map_data.level > ground_level))
{
if (data)
*data = map_data;
return map_result;
}
}
return result;
}
float Map::GetWaterLevel(float x, float y ) const
@ -1373,15 +1270,16 @@ void Map::GetZoneAndAreaIdByAreaFlag(uint32& zoneid, uint32& areaid, uint16 area
zoneid = entry ? (( entry->zone != 0 ) ? entry->zone : entry->ID) : 0;
}
bool Map::IsInWater(float x, float y, float pZ) const
bool Map::IsInWater(float x, float y, float pZ, GridMapLiquidData *data) const
{
// Check surface in x, y point for liquid
if (const_cast<Map*>(this)->GetGrid(x, y))
{
GridMapLiquidData liquid_status;
if (getLiquidStatus(x, y, pZ, MAP_ALL_LIQUIDS, &liquid_status))
GridMapLiquidData *liquid_ptr = data ? data : &liquid_status;
if (getLiquidStatus(x, y, pZ, MAP_ALL_LIQUIDS, liquid_ptr))
{
if (liquid_status.level - liquid_status.depth_level > 2)
//if (liquid_prt->level - liquid_prt->depth_level > 2) //???
return true;
}
}