/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. #include "CryLegacy_precompiled.h" #include "Area.h" #include "AreaSolid.h" #include <IRenderAuxGeom.h> #include "Components/IComponentAudio.h" #include <AzFramework/Terrain/TerrainDataRequestBus.h> namespace { static StaticInstance<CArea::TAreaBoxes> s_areaBoxes; } ////////////////////////////////////////////////////////////////////////// CArea::CArea(CAreaManager* pManager) : m_VOrigin(0.0f) , m_VSize(0.0f) , m_PrevFade(-1.0f) , m_fProximity(5.0f) , m_fFadeDistance(-1.0f) , m_fEnvironmentFadeDistance(0.0f) , m_AreaGroupID(INVALID_AREA_GROUP_ID) , m_nPriority(0) , m_AreaID(-1) , m_EntityID(INVALID_ENTITYID) , m_BoxMin(ZERO) , m_BoxMax(ZERO) , m_SphereCenter(0) , m_SphereRadius(0) , m_SphereRadius2(0) , m_bIsActive(false) , m_bObstructRoof(false) , m_bObstructFloor(false) , m_bEntityIdsResolved(false) , m_bAllObstructed(0) , m_nObstructedCount(0) , m_AreaSolid(0) , m_oNULLVec(ZERO) { m_AreaType = ENTITY_AREA_TYPE_SHAPE; m_InvMatrix.SetIdentity(); m_WorldTM.SetIdentity(); m_pAreaManager = pManager; m_bInitialized = false; m_bHasSoundAttached = false; m_bAttachedSoundTested = false; m_mapEntityCachedAreaData.reserve(256); // All sides not obstructed by default memset(&m_abBoxSideObstruction, 0, 6); m_bbox_holder = s_areaBoxes.size(); s_areaBoxes.push_back(SBoxHolder()); s_areaBoxes[m_bbox_holder].area = this; } ////////////////////////////////////////////////////////////////////////// CArea::~CArea(void) { ClearEntities(); m_pAreaManager->Unregister(this); ReleaseAreaData(); size_t last = s_areaBoxes.size() - 1; s_areaBoxes[m_bbox_holder].box = s_areaBoxes[last].box; (s_areaBoxes[m_bbox_holder].area = s_areaBoxes[last].area)->m_bbox_holder = m_bbox_holder; s_areaBoxes.erase(s_areaBoxes.begin() + last, s_areaBoxes.end()); } ////////////////////////////////////////////////////////////////////////// void CArea::Release() { delete this; } ////////////////////////////////////////////////////////////////////////// void CArea::SetSoundObstructionOnAreaFace(int unsigned const nFaceIndex, bool const bObstructs) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); switch (m_AreaType) { case ENTITY_AREA_TYPE_BOX: { m_abBoxSideObstruction[nFaceIndex].bObstructed = bObstructs ? 1 : 0; m_nObstructedCount = 0; for (unsigned int i = 0; i < 6; ++i) { if (m_abBoxSideObstruction[i].bObstructed) { ++m_nObstructedCount; } } m_bAllObstructed = 0; if (m_nObstructedCount == 6) { m_bAllObstructed = 1; } } break; case ENTITY_AREA_TYPE_SHAPE: { unsigned int const nSegmentCount = m_vpSegments.size(); if (nFaceIndex < nSegmentCount) { m_vpSegments[nFaceIndex]->bObstructSound = bObstructs; } else { // We exceed segment count which could mean // that the user wants to set roof and floor sound obstruction if (nFaceIndex == nSegmentCount) { // The user wants to set roof sound obstruction m_bObstructRoof = bObstructs; } else if (nFaceIndex == nSegmentCount + 1) { // The user wants to set floor sound obstruction m_bObstructFloor = bObstructs; } } } break; } } ////////////////////////////////////////////////////////////////////////// void CArea::SetAreaType(EEntityAreaType type) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); m_AreaType = type; // to prevent gravityvolumes being evaluated in the // AreaManager::UpdatePlayer function, that caused problems. if (m_AreaType == ENTITY_AREA_TYPE_GRAVITYVOLUME) { m_pAreaManager->Unregister(this); } } // resets area - clears all segments in area ////////////////////////////////////////////////////////////////////////// void CArea::ClearPoints() { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); a2DSegment* carSegment; for (unsigned int sIdx = 0; sIdx < m_vpSegments.size(); sIdx++) { carSegment = m_vpSegments[sIdx]; delete carSegment; } m_vpSegments.clear(); m_bInitialized = false; } ////////////////////////////////////////////////////////////////////////// unsigned CArea::MemStat() { unsigned memSize = sizeof *this; memSize += m_vpSegments.size() * (sizeof(a2DSegment) + sizeof(a2DSegment*)); return memSize; } //adds segment to area, calculates line parameters y=kx+b, sets horizontal/vertical flags ////////////////////////////////////////////////////////////////////////// void CArea::AddSegment(const a2DPoint& p0, const a2DPoint& p1, bool const bObstructSound) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); a2DSegment* newSegment = new a2DSegment; newSegment->bObstructSound = bObstructSound; //if this is horizontal line set flag. This segment is needed only for distance calculations if (p1.y == p0.y) { newSegment->isHorizontal = true; } else { newSegment->isHorizontal = false; } if (p0.x < p1.x) { newSegment->bbox.min.x = p0.x; newSegment->bbox.max.x = p1.x; } else { newSegment->bbox.min.x = p1.x; newSegment->bbox.max.x = p0.x; } if (p0.y < p1.y) { newSegment->bbox.min.y = p0.y; newSegment->bbox.max.y = p1.y; } else { newSegment->bbox.min.y = p1.y; newSegment->bbox.max.y = p0.y; } if (!newSegment->isHorizontal) { //if this is vertical line - spesial case if (p1.x == p0.x) { newSegment->k = 0; newSegment->b = p0.x; } else { newSegment->k = (p1.y - p0.y) / (p1.x - p0.x); newSegment->b = p0.y - newSegment->k * p0.x; } } else { newSegment->k = 0; newSegment->b = 0; } m_vpSegments.push_back(newSegment); } // calculates min distance from point within area to the border of area // returns fade coefficient: Distance/m_Proximity // [0 - on the very border of area, 1 - inside area, distance to border is more than m_Proximity] ////////////////////////////////////////////////////////////////////////// float CArea::CalcDistToPoint(a2DPoint const& point) const { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (!m_bInitialized) { return -1; } if (m_fProximity == 0.0f) { return 1.0f; } float distMin = m_fProximity * m_fProximity; float curDist; a2DBBox proximityBox; proximityBox.max.x = point.x + m_fProximity; proximityBox.max.y = point.y + m_fProximity; proximityBox.min.x = point.x - m_fProximity; proximityBox.min.y = point.y - m_fProximity; for (unsigned int sIdx = 0; sIdx < m_vpSegments.size(); sIdx++) { a2DSegment* curSg = m_vpSegments[sIdx]; if (!m_vpSegments[sIdx]->bbox.BBoxOutBBox2D(proximityBox)) { if (m_vpSegments[sIdx]->isHorizontal) { if (point.x < m_vpSegments[sIdx]->bbox.min.x) { curDist = m_vpSegments[sIdx]->bbox.min.DistSqr(point); } else if (point.x > m_vpSegments[sIdx]->bbox.max.x) { curDist = m_vpSegments[sIdx]->bbox.max.DistSqr(point); } else { curDist = fabsf(point.y - m_vpSegments[sIdx]->bbox.max.y); } curDist *= curDist; } else { if (m_vpSegments[sIdx]->k == 0.0f) { if (point.y < m_vpSegments[sIdx]->bbox.min.y) { curDist = m_vpSegments[sIdx]->bbox.min.DistSqr(point); } else if (point.y > m_vpSegments[sIdx]->bbox.max.y) { curDist = m_vpSegments[sIdx]->bbox.max.DistSqr(point); } else { curDist = fabsf(point.x - m_vpSegments[sIdx]->b); } curDist *= curDist; } else { a2DPoint intersection; float b2, k2; k2 = -1.0f / m_vpSegments[sIdx]->k; b2 = point.y - k2 * point.x; intersection.x = (b2 - m_vpSegments[sIdx]->b) / (m_vpSegments[sIdx]->k - k2); intersection.y = k2 * intersection.x + b2; if (intersection.x < m_vpSegments[sIdx]->bbox.min.x) { if (m_vpSegments[sIdx]->k < 0) { curDist = point.DistSqr(m_vpSegments[sIdx]->bbox.min.x, m_vpSegments[sIdx]->bbox.max.y); } else { curDist = point.DistSqr(m_vpSegments[sIdx]->bbox.min); } } else if (intersection.x > m_vpSegments[sIdx]->bbox.max.x) { if (m_vpSegments[sIdx]->k < 0) { curDist = point.DistSqr(m_vpSegments[sIdx]->bbox.max.x, m_vpSegments[sIdx]->bbox.min.y); } else { curDist = point.DistSqr(m_vpSegments[sIdx]->bbox.max); } } else { curDist = intersection.DistSqr(point); } } if (curDist < distMin) { distMin = curDist; } } } } return sqrt_tpl(distMin) / m_fProximity; // TODO: Check if "distmin/m_fProximity*m_fProximity" is faster } // check if the point is within the area // first BBox check, then count number of intersections for horizontal ray from point and area segments // if the number is odd - the point is inside ////////////////////////////////////////////////////////////////////////// bool CArea::CalcPointWithin(EntityId const nEntityID, Vec3 const& point3d, bool const bIgnoreHeight /* = false */, bool const bCacheResult /* = true */) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); bool bResult = false; if (m_bInitialized) { SCachedAreaData* pCachedData = NULL; if (nEntityID != INVALID_ENTITYID) { TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { pCachedData = &(Iter->second); } } if (pCachedData != NULL && (pCachedData->eFlags & eCachedAreaData_PointWithinValid) != 0) { bResult = GetCachedPointWithin(nEntityID); } else { switch (m_AreaType) { case ENTITY_AREA_TYPE_SPHERE: { Vec3 oPoint(point3d - m_SphereCenter); if (bIgnoreHeight) { oPoint.z = 0.0f; } bResult = (oPoint.GetLengthSquared() < m_SphereRadius2); break; } case ENTITY_AREA_TYPE_BOX: { Vec3 p3d = m_InvMatrix.TransformPoint(point3d); if (bIgnoreHeight) { p3d.z = m_BoxMax.z; } // And put the result into the data cache if ((p3d.x < m_BoxMin.x) || (p3d.y < m_BoxMin.y) || (p3d.z < m_BoxMin.z) || (p3d.x > m_BoxMax.x) || (p3d.y > m_BoxMax.y) || (p3d.z > m_BoxMax.z)) { bResult = false; } else { bResult = true; } break; } case ENTITY_AREA_TYPE_SOLID: { if (point3d.IsValid()) { Vec3 localPoint3D = m_InvMatrix.TransformPoint(point3d); bResult = m_AreaSolid->IsInside(localPoint3D); } break; } case ENTITY_AREA_TYPE_SHAPE: { bResult = true; if (!bIgnoreHeight) { if (m_VSize > 0.0f) { if (point3d.z < m_VOrigin || point3d.z > m_VOrigin + m_VSize) { bResult = false; } } } if (bResult) { a2DPoint const* const point = (CArea::a2DPoint*)(&point3d); bResult = !m_areaBBox.PointOutBBox2D(*point); if (bResult) { size_t cntr = 0; size_t const nSegmentCount = m_vpSegments.size(); for (size_t sIdx = 0; sIdx < nSegmentCount; ++sIdx) { if (!m_vpSegments[sIdx]->isHorizontal && !m_vpSegments[sIdx]->bbox.PointOutBBox2DVertical(*point)) { if (m_vpSegments[sIdx]->IntersectsXPosVertical(*point) || m_vpSegments[sIdx]->IntersectsXPos(*point)) { ++cntr; } } } bResult = ((cntr & 1) != 0); } } break; } default: { CryFatalError("Unknown area type during CArea::CalcPointWithin"); break; } } // Set the flags and put the result into the data cache. if (pCachedData != NULL && bCacheResult) { if (bIgnoreHeight) { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_PointWithinValid | eCachedAreaData_PointWithinValidHeightIgnored); } else { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_PointWithinValid); } pCachedData->bPointWithin = bResult; } } } return bResult; } // for editor use - if point is within - returns min horizontal distance to border // if point out - returns -1 ////////////////////////////////////////////////////////////////////////// float CArea::CalcPointWithinDist(EntityId const nEntityID, Vec3 const& point3d, bool const bIgnoreSoundObstruction /* = true */, bool const bCacheResult /* = true */) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); float fMinDist = -1.0f; if (m_bInitialized) { float fDistanceWithinSq = 0.0f; SCachedAreaData* pCachedData = NULL; if (nEntityID != INVALID_ENTITYID) { TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { pCachedData = &(Iter->second); } } // Only computes distance if point is inside the area. bool bGoAheadAndCompute = false; if (pCachedData != NULL) { bGoAheadAndCompute = (pCachedData->eFlags & eCachedAreaData_DistWithinSqValid) == 0 && pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE; } else { // Unfortunately we need to compute the position type. bGoAheadAndCompute = CalcPosType(nEntityID, point3d) == AREA_POS_TYPE_2DINSIDE_ZINSIDE; } if (bGoAheadAndCompute) { switch (m_AreaType) { case ENTITY_AREA_TYPE_SPHERE: { Vec3 const sPnt = point3d - m_SphereCenter; float fPointLengthSq = sPnt.GetLengthSquared(); fMinDist = m_SphereRadius - sPnt.GetLength(); fDistanceWithinSq = m_SphereRadius2 - fPointLengthSq; break; } case ENTITY_AREA_TYPE_BOX: { if (!bIgnoreSoundObstruction) { Vec3 v3ClosestPos(ZERO); CalcClosestPointToObstructedBox(v3ClosestPos, fDistanceWithinSq, point3d); fMinDist = (fDistanceWithinSq > 0.0f) ? sqrt_tpl(fDistanceWithinSq) : fMinDist; } else { Vec3 oTemp(ZERO); fDistanceWithinSq = ClosestPointOnHullDistSq(nEntityID, point3d, oTemp, true); fMinDist = (fDistanceWithinSq > 0.0f) ? sqrt_tpl(fDistanceWithinSq) : fMinDist; } break; } case ENTITY_AREA_TYPE_SOLID: { Vec3 const localPoint3D(m_InvMatrix.TransformPoint(point3d)); int queryFlag = bIgnoreSoundObstruction ? CAreaSolid::eSegmentQueryFlag_Obstruction : CAreaSolid::eSegmentQueryFlag_All; Vec3 vOnHull; if (m_AreaSolid->QueryNearest(localPoint3D, queryFlag, vOnHull, fDistanceWithinSq)) { fMinDist = (fDistanceWithinSq > 0.0f) ? sqrt_tpl(fDistanceWithinSq) : fMinDist; } break; } case ENTITY_AREA_TYPE_SHAPE: { if (!bIgnoreSoundObstruction) { Vec3 v3ClosestPos; CalcClosestPointToObstructedShape(nEntityID, v3ClosestPos, fDistanceWithinSq, point3d); fMinDist = (fDistanceWithinSq > 0.0f) ? sqrt_tpl(fDistanceWithinSq) : fMinDist; } else { // check distance to every line segment, remember the closest size_t const nSegmentSize = m_vpSegments.size(); for (size_t sIdx = 0; sIdx < nSegmentSize; sIdx++) { float fT; a2DSegment* curSg = m_vpSegments[sIdx]; Vec3 startSeg(curSg->GetStart().x, curSg->GetStart().y, point3d.z); Vec3 endSeg(curSg->GetEnd().x, curSg->GetEnd().y, point3d.z); Lineseg line(startSeg, endSeg); // Returns distance from a point to a line segment, ignoring the z coordinates float fDist = Distance::Point_Lineseg2D(point3d, line, fT); if (fMinDist == -1) { fMinDist = fDist; } fMinDist = min(fDist, fMinDist); } if (m_VSize > 0.0f) { float fDistToRoof = fMinDist + 1.0f; float fDistToFloor = fMinDist + 1.0f; if (!m_bObstructFloor) { fDistToFloor = point3d.z - m_VOrigin; } if (!m_bObstructRoof) { fDistToRoof = m_VOrigin + m_VSize - point3d.z; } float fZDist = min(fDistToFloor, fDistToRoof); fMinDist = min(fMinDist, fZDist); } fDistanceWithinSq = (fDistanceWithinSq > 0.0f) ? fMinDist * fMinDist : 0.0f; } break; } default: { CryFatalError("Unknown area type during CArea::CalcPointWithinDist"); break; } } } // Set the flags and put the shortest distance into the data cache also when not computing. if (pCachedData != NULL && bCacheResult) { if (bIgnoreSoundObstruction) { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_DistWithinSqValid | eCachedAreaData_DistWithinSqValidNotObstructed); } else { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_DistWithinSqValid); } pCachedData->fDistanceWithinSq = fDistanceWithinSq; } } return fMinDist; } ////////////////////////////////////////////////////////////////////////// float CArea::ClosestPointOnHullDistSq(EntityId const nEntityID, Vec3 const& Point3d, Vec3& OnHull3d, bool const bIgnoreSoundObstruction /* = true */, bool const bCacheResult /* = true */) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); float fClosestDistance = -1.0f; if (m_bInitialized) { SCachedAreaData* pCachedData = NULL; if (nEntityID != INVALID_ENTITYID) { TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { pCachedData = &(Iter->second); } } if (pCachedData != NULL && (pCachedData->eFlags & eCachedAreaData_PosOnHullValid) != 0 && (pCachedData->eFlags & eCachedAreaData_DistWithinSqValid) != 0) { fClosestDistance = GetCachedPointWithinDistSq(nEntityID); OnHull3d = GetCachedPointOnHull(nEntityID); } else { Vec3 Closest3d(ZERO); switch (m_AreaType) { case ENTITY_AREA_TYPE_SOLID: { CalcClosestPointToSolid(Point3d, bIgnoreSoundObstruction, fClosestDistance, &OnHull3d); break; } case ENTITY_AREA_TYPE_SHAPE: { if (!bIgnoreSoundObstruction) { CalcClosestPointToObstructedShape(nEntityID, OnHull3d, fClosestDistance, Point3d); } else { float fDistToRoof = 0.0f; float fDistToFloor = 0.0f; float fZDistTemp = 0.0f; if (m_VSize) { // negative means from inside to hull fDistToRoof = Point3d.z - (m_VOrigin + m_VSize); fDistToFloor = m_VOrigin - Point3d.z; if (fabsf(fDistToFloor) < fabsf(fDistToRoof)) { // below if (m_bObstructFloor) { fDistToFloor = 0.0f; fZDistTemp = fDistToRoof; } else { fDistToRoof = 0.0f; fZDistTemp = fDistToFloor; } } else { // above if (m_bObstructRoof) { fDistToRoof = 0.0f; fZDistTemp = fDistToFloor; } else { fDistToFloor = 0.0f; fZDistTemp = fDistToRoof; } } } float fZDistSq = fZDistTemp * fZDistTemp; float fXDistSq = 0.0f; bool bIsIn2DShape = false; if (pCachedData && (pCachedData->eFlags & eCachedAreaData_PosTypeValid) != 0) { bIsIn2DShape = (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZABOVE) || (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE) || (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZBELOW); } else { bIsIn2DShape = (CalcPosType(nEntityID, Point3d) == AREA_POS_TYPE_2DINSIDE_ZINSIDE); } //// point is not under or above area shape, so approach from the side // Find the line segment that is closest to the 2d point. size_t const nSegmentSize = m_vpSegments.size(); for (size_t sIdx = 0; sIdx < nSegmentSize; sIdx++) { float fT; a2DSegment* curSg = m_vpSegments[sIdx]; Vec3 startSeg(curSg->GetStart().x, curSg->GetStart().y, Point3d.z); Vec3 endSeg(curSg->GetEnd().x, curSg->GetEnd().y, Point3d.z); Lineseg line(startSeg, endSeg); /// Returns distance from a point to a line segment, ignoring the z coordinates fXDistSq = Distance::Point_Lineseg2DSq(Point3d, line, fT); float fThisDistance = 0.0f; if (bIsIn2DShape && fZDistSq) { fThisDistance = min(fXDistSq, fZDistSq); } else { fThisDistance = fXDistSq + fZDistSq; } // is this closer than the previous one? if (fThisDistance < fClosestDistance) { fClosestDistance = fThisDistance; // find closest point if (fZDistSq && fZDistSq < fXDistSq) { Closest3d = Point3d; Closest3d.z = Point3d.z + fDistToFloor - fDistToRoof; } else { Closest3d = (line.GetPoint(fT)); } } } OnHull3d = Closest3d; } break; } case ENTITY_AREA_TYPE_SPHERE: { Vec3 Temp = Point3d - m_SphereCenter; OnHull3d = Temp.normalize() * m_SphereRadius; OnHull3d += m_SphereCenter; fClosestDistance = OnHull3d.GetSquaredDistance(Point3d); break; } case ENTITY_AREA_TYPE_BOX: { if (!bIgnoreSoundObstruction) { CalcClosestPointToObstructedBox(OnHull3d, fClosestDistance, Point3d); } else { Vec3 const p3d = m_InvMatrix.TransformPoint(Point3d); AABB const myAABB(m_BoxMin, m_BoxMax); fClosestDistance = Distance::Point_AABBSq(p3d, myAABB, OnHull3d); if (m_abBoxSideObstruction[4].bObstructed && OnHull3d.z == myAABB.max.z) { // Point is on the roof plane, but may be on the edge already Vec2 vTop(OnHull3d.x, myAABB.max.y); Vec2 vLeft(myAABB.min.x, OnHull3d.y); Vec2 vLow(OnHull3d.x, myAABB.min.y); Vec2 vRight(myAABB.max.x, OnHull3d.y); float fDistanceToTop = p3d.GetSquaredDistance2D(vTop); float fDistanceToLeft = p3d.GetSquaredDistance2D(vLeft); float fDistanceToLow = p3d.GetSquaredDistance2D(vLow); float fDistanceToRight = p3d.GetSquaredDistance2D(vRight); float fTempMinDistance = fDistanceToTop; OnHull3d = vTop; if (fDistanceToLeft < fTempMinDistance) { OnHull3d = vLeft; fTempMinDistance = fDistanceToLeft; } if (fDistanceToLow < fTempMinDistance) { OnHull3d = vLow; fTempMinDistance = fDistanceToLow; } if (fDistanceToRight < fTempMinDistance) { OnHull3d = vRight; fTempMinDistance = fDistanceToRight; } OnHull3d.z = min(myAABB.max.z, p3d.z); fClosestDistance = OnHull3d.GetSquaredDistance(p3d); } if (m_abBoxSideObstruction[5].bObstructed && OnHull3d.z == myAABB.min.z) { // Point is on the roof plane, but may be on the edge already Vec2 vTop(OnHull3d.x, myAABB.max.y); Vec2 vLeft(myAABB.min.x, OnHull3d.y); Vec2 vLow(OnHull3d.x, myAABB.min.y); Vec2 vRight(myAABB.max.x, OnHull3d.y); float fDistanceToTop = p3d.GetSquaredDistance2D(vTop); float fDistanceToLeft = p3d.GetSquaredDistance2D(vLeft); float fDistanceToLow = p3d.GetSquaredDistance2D(vLow); float fDistanceToRight = p3d.GetSquaredDistance2D(vRight); float fTempMinDistance = fDistanceToTop; OnHull3d = vTop; if (fDistanceToLeft < fTempMinDistance) { OnHull3d = vLeft; fTempMinDistance = fDistanceToLeft; } if (fDistanceToLow < fTempMinDistance) { OnHull3d = vLow; fTempMinDistance = fDistanceToLow; } if (fDistanceToRight < fTempMinDistance) { OnHull3d = vRight; fTempMinDistance = fDistanceToRight; } OnHull3d.z = max(myAABB.min.z, p3d.z); fClosestDistance = OnHull3d.GetSquaredDistance(p3d); } OnHull3d = m_WorldTM.TransformPoint(OnHull3d); } // TODO transform point back to world ... (why?) break; } default: { CryFatalError("Unknown area type during CArea::ClosestPointOnHullDistSq"); break; } } // Put the shortest distance and point into the data cache. if (pCachedData != NULL && bCacheResult) { if (bIgnoreSoundObstruction) { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistWithinSqValid | eCachedAreaData_DistWithinSqValidNotObstructed); } else { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistWithinSqValid); } pCachedData->oPos = OnHull3d; pCachedData->fDistanceWithinSq = fClosestDistance; } } } return fClosestDistance; } ////////////////////////////////////////////////////////////////////////// float CArea::CalcPointNearDistSq(EntityId const nEntityID, Vec3 const& Point3d, Vec3& OnHull3d, bool const bIgnoreSoundObstruction /* = true */, bool const bCacheResult /* = true */) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); float fClosestDistance = -1.0f; if (m_bInitialized) { SCachedAreaData* pCachedData = NULL; if (nEntityID != INVALID_ENTITYID) { TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { pCachedData = &(Iter->second); } } if (pCachedData != NULL && (pCachedData->eFlags & eCachedAreaData_PosOnHullValid) != 0 && (pCachedData->eFlags & eCachedAreaData_DistNearSqValid) != 0) { fClosestDistance = GetCachedPointNearDistSq(nEntityID); OnHull3d = GetCachedPointOnHull(nEntityID); } else { Vec3 Closest3d(ZERO); switch (m_AreaType) { case ENTITY_AREA_TYPE_SOLID: { CalcClosestPointToSolid(Point3d, bIgnoreSoundObstruction, fClosestDistance, &OnHull3d); break; } case ENTITY_AREA_TYPE_SHAPE: { if (!bIgnoreSoundObstruction) { CalcClosestPointToObstructedShape(nEntityID, OnHull3d, fClosestDistance, Point3d); } else { float fZDistSq = 0.0f; float fXDistSq = FLT_MAX; // first find the closest edge size_t const nSegmentSize = m_vpSegments.size(); for (size_t sIdx = 0; sIdx < nSegmentSize; sIdx++) { float fT = 0.0f; a2DSegment const* const curSg = m_vpSegments[sIdx]; Vec3 startSeg(curSg->GetStart().x, curSg->GetStart().y, Point3d.z); Vec3 endSeg(curSg->GetEnd().x, curSg->GetEnd().y, Point3d.z); Lineseg line(startSeg, endSeg); // Returns distance from a point to a line segment, ignoring the z coordinates float fThisXDistSq = Distance::Point_Lineseg2DSq(Point3d, line, fT); if (fThisXDistSq < fXDistSq) { // find closest point fXDistSq = fThisXDistSq; Closest3d = (line.GetPoint(fT)); } } // now we have Closest3d being the point on the 2D hull of the shape if (m_VSize) { // negative means from inside to hull float fDistToRoof = Point3d.z - (m_VOrigin + m_VSize); float fDistToFloor = m_VOrigin - Point3d.z; float fZRoofSq = fDistToRoof * fDistToRoof; float fZFloorSq = fDistToFloor * fDistToFloor; bool bIsIn2DShape = false; if (pCachedData != NULL && (pCachedData->eFlags & eCachedAreaData_PosTypeValid) != 0) { bIsIn2DShape = (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZABOVE) || (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE) || (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZBELOW); } else { bIsIn2DShape = (CalcPosType(nEntityID, Point3d) == AREA_POS_TYPE_2DINSIDE_ZINSIDE); } if (bIsIn2DShape) { // point is below, in, or above the area if ((Point3d.z < m_VOrigin + m_VSize && Point3d.z > m_VOrigin)) { // Point is inside z-boundary if (!m_bObstructRoof && (fZRoofSq < fXDistSq) && (fZRoofSq < fZFloorSq)) { // roof is closer than side fZDistSq = fZRoofSq; Closest3d = Point3d; fXDistSq = 0.0f; } if (!m_bObstructFloor && (fZFloorSq < fXDistSq) && (fZFloorSq < fZRoofSq)) { // floor is closer than side fZDistSq = fZFloorSq; Closest3d = Point3d; fXDistSq = 0.0f; } // correcting z-axis value if (fZRoofSq < fZFloorSq) { Closest3d.z = Point3d.z - fDistToRoof; } else { Closest3d.z = Point3d.z - fDistToFloor; } } else { // point is above or below area if (fabsf(fDistToRoof) < fabsf(fDistToFloor)) { // being above if (!m_bObstructRoof) { // perpendicular point to Roof fXDistSq = 0.0f; Closest3d = Point3d; } // correcting z axis value Closest3d.z = m_VOrigin + m_VSize; fZDistSq = fZRoofSq; } else { // being below if (!m_bObstructFloor) { // perpendicular point to Floor fXDistSq = 0.0f; Closest3d = Point3d; } // correcting z axis value Closest3d.z = m_VOrigin; fZDistSq = fZFloorSq; } } } else { // outside of 2D Shape, so diagonal or only to the side if ((Point3d.z > m_VOrigin + m_VSize || Point3d.z < m_VOrigin)) { // Point is outside z-boundary // point is above or below area if (fabsf(fDistToRoof) < fabsf(fDistToFloor)) { // being above fZDistSq = fZRoofSq; Closest3d.z = m_VOrigin + m_VSize; } else { // being below fZDistSq = fZFloorSq; Closest3d.z = m_VOrigin; } } else { // on the side and outside of the area, so point is on the face // correct z-Value Closest3d.z = Point3d.z; } } } else { // infinite high area // Closest is on an edge, ZDistance is 0, so nothing really to do here } fClosestDistance = fXDistSq + fZDistSq; OnHull3d = Closest3d; } break; } case ENTITY_AREA_TYPE_SPHERE: { Vec3 Temp = Point3d - m_SphereCenter; OnHull3d = Temp.normalize() * m_SphereRadius; OnHull3d += m_SphereCenter; fClosestDistance = OnHull3d.GetSquaredDistance(Point3d); break; } case ENTITY_AREA_TYPE_BOX: { if (!bIgnoreSoundObstruction) { CalcClosestPointToObstructedBox(OnHull3d, fClosestDistance, Point3d); } else { Vec3 const p3d = m_InvMatrix.TransformPoint(Point3d); AABB const myAABB(m_BoxMin, m_BoxMax); fClosestDistance = Distance::Point_AABBSq(p3d, myAABB, OnHull3d); if (m_abBoxSideObstruction[4].bObstructed && OnHull3d.z == myAABB.max.z) { // Point is on the roof plane, but may be on the edge already Vec2 vTop(OnHull3d.x, myAABB.max.y); Vec2 vLeft(myAABB.min.x, OnHull3d.y); Vec2 vLow(OnHull3d.x, myAABB.min.y); Vec2 vRight(myAABB.max.x, OnHull3d.y); float fDistanceToTop = p3d.GetSquaredDistance2D(vTop); float fDistanceToLeft = p3d.GetSquaredDistance2D(vLeft); float fDistanceToLow = p3d.GetSquaredDistance2D(vLow); float fDistanceToRight = p3d.GetSquaredDistance2D(vRight); float fTempMinDistance = fDistanceToTop; OnHull3d = vTop; if (fDistanceToLeft < fTempMinDistance) { OnHull3d = vLeft; fTempMinDistance = fDistanceToLeft; } if (fDistanceToLow < fTempMinDistance) { OnHull3d = vLow; fTempMinDistance = fDistanceToLow; } if (fDistanceToRight < fTempMinDistance) { OnHull3d = vRight; fTempMinDistance = fDistanceToRight; } OnHull3d.z = min(myAABB.max.z, p3d.z); fClosestDistance = OnHull3d.GetSquaredDistance(p3d); } if (m_abBoxSideObstruction[5].bObstructed && OnHull3d.z == myAABB.min.z) { // Point is on the roof plane, but may be on the edge already Vec2 vTop(OnHull3d.x, myAABB.max.y); Vec2 vLeft(myAABB.min.x, OnHull3d.y); Vec2 vLow(OnHull3d.x, myAABB.min.y); Vec2 vRight(myAABB.max.x, OnHull3d.y); float fDistanceToTop = p3d.GetSquaredDistance2D(vTop); float fDistanceToLeft = p3d.GetSquaredDistance2D(vLeft); float fDistanceToLow = p3d.GetSquaredDistance2D(vLow); float fDistanceToRight = p3d.GetSquaredDistance2D(vRight); float fTempMinDistance = fDistanceToTop; OnHull3d = vTop; if (fDistanceToLeft < fTempMinDistance) { OnHull3d = vLeft; fTempMinDistance = fDistanceToLeft; } if (fDistanceToLow < fTempMinDistance) { OnHull3d = vLow; fTempMinDistance = fDistanceToLow; } if (fDistanceToRight < fTempMinDistance) { OnHull3d = vRight; fTempMinDistance = fDistanceToRight; } OnHull3d.z = max(myAABB.min.z, p3d.z); fClosestDistance = OnHull3d.GetSquaredDistance(p3d); } OnHull3d = m_WorldTM.TransformPoint(OnHull3d); } break; } default: { CryFatalError("Unknown area type during CArea::CalcPointNearDistSq (1)"); break; } } // Put the shortest distance and point into the data cache. if (pCachedData != NULL && bCacheResult) { if (bIgnoreSoundObstruction) { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed); } else { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_PosOnHullValid | eCachedAreaData_DistNearSqValid); } pCachedData->oPos = OnHull3d; pCachedData->fDistanceNearSq = fClosestDistance; } } } return fClosestDistance; } ////////////////////////////////////////////////////////////////////////// float CArea::CalcPointNearDistSq(EntityId const nEntityID, Vec3 const& Point3d, bool const bIgnoreHeight /* = false */, bool const bIgnoreSoundObstruction /* = true */, bool const bCacheResult /* = true */) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); float fClosestDistance = -1.0f; if (m_bInitialized) { SCachedAreaData* pCachedData = NULL; if (nEntityID != INVALID_ENTITYID) { TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { pCachedData = &(Iter->second); } } if (pCachedData != NULL && (pCachedData->eFlags & eCachedAreaData_DistNearSqValid) != 0) { fClosestDistance = GetCachedPointNearDistSq(nEntityID); } else { switch (m_AreaType) { case ENTITY_AREA_TYPE_SOLID: { CalcClosestPointToSolid(Point3d, bIgnoreSoundObstruction, fClosestDistance, NULL); break; } case ENTITY_AREA_TYPE_SHAPE: { if (!bIgnoreSoundObstruction) { Vec3 oTemp(ZERO); CalcClosestPointToObstructedShape(nEntityID, oTemp, fClosestDistance, Point3d); } else { float fZDistSq = 0.0f; float fXDistSq = FLT_MAX; for (std::vector<a2DSegment*>::const_iterator it = m_vpSegments.begin(), itEnd = m_vpSegments.end(); it != itEnd; ++it) { a2DSegment const* const curSg = *it; a2DPoint const curSgStart = curSg->GetStart(), curSgEnd = curSg->GetEnd(); Lineseg line; line.start.x = curSgStart.x; line.start.y = curSgStart.y; line.end.x = curSgEnd.x; line.end.y = curSgEnd.y; /// Returns distance from a point to a line segment, ignoring the z coordinates float fThisXDistSq = Distance::Point_Lineseg2DSq(Point3d, line); fXDistSq = min(fXDistSq, fThisXDistSq); } if (m_VSize) { // negative means from inside to hull float const fDistToRoof = Point3d.z - (m_VOrigin + m_VSize); float const fDistToFloor = m_VOrigin - Point3d.z; float const fZRoofSq = fDistToRoof * fDistToRoof; float const fZFloorSq = fDistToFloor * fDistToFloor; bool bIsIn2DShape = false; if (pCachedData != NULL && (pCachedData->eFlags & eCachedAreaData_PosTypeValid) != 0) { bIsIn2DShape = (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZABOVE) || (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE) || (pCachedData->ePosType == AREA_POS_TYPE_2DINSIDE_ZBELOW); } else { bIsIn2DShape = (CalcPosType(nEntityID, Point3d) == AREA_POS_TYPE_2DINSIDE_ZINSIDE); } if (bIsIn2DShape) { // point is below, in, or above the area if ((Point3d.z < m_VOrigin + m_VSize && Point3d.z > m_VOrigin)) { // Point is inside z-boundary if (!m_bObstructRoof && (fZRoofSq < fXDistSq) && (fZRoofSq < fZFloorSq)) { // roof is closer than side fZDistSq = fZRoofSq; fXDistSq = 0.0f; } if (!m_bObstructFloor && (fZFloorSq < fXDistSq) && (fZFloorSq < fZRoofSq)) { // floor is closer than side fZDistSq = fZFloorSq; fXDistSq = 0.0f; } } else { // point is above or below area if (fabsf(fDistToRoof) < fabsf(fDistToFloor)) { // being above fZDistSq = fZRoofSq; if (!m_bObstructRoof) { // perpendicular point to Roof fXDistSq = 0.0f; } } else { // being below fZDistSq = fZFloorSq; if (!m_bObstructFloor) { // perpendicular point to Floor fXDistSq = 0.0f; } } } } else { // outside of 2D Shape, so diagonal or only to the side if ((Point3d.z < m_VOrigin + m_VSize && Point3d.z > m_VOrigin)) { // Point is inside z-boundary } else { // point is above or below area if (fabsf(fDistToRoof) < fabsf(fDistToFloor)) { // being above fZDistSq = fZRoofSq; } else { // being below fZDistSq = fZFloorSq; } } } } else { // infinite high area // Closest is on an edge, ZDistance is 0, so nothing really to do here } fClosestDistance = fXDistSq; if (!bIgnoreHeight) { fClosestDistance += fZDistSq; } } break; } case ENTITY_AREA_TYPE_SPHERE: { Vec3 vTemp(Point3d); if (bIgnoreHeight) { vTemp.z = m_SphereCenter.z; } float const fLength = m_SphereCenter.GetDistance(vTemp) - m_SphereRadius; fClosestDistance = fLength * fLength; break; } case ENTITY_AREA_TYPE_BOX: { if (!bIgnoreSoundObstruction) { Vec3 oTemp(ZERO); CalcClosestPointToObstructedBox(oTemp, fClosestDistance, Point3d); } else { Vec3 p3d = m_InvMatrix.TransformPoint(Point3d); Vec3 OnHull3d; if (bIgnoreHeight) { p3d.z = m_BoxMin.z; } AABB myAABB(m_BoxMin, m_BoxMax); fClosestDistance = Distance::Point_AABBSq(p3d, myAABB, OnHull3d); if (m_abBoxSideObstruction[4].bObstructed && OnHull3d.z == myAABB.max.z) { // Point is on the roof plane, but may be on the edge already Vec2 vTop(OnHull3d.x, myAABB.max.y); Vec2 vLeft(myAABB.min.x, OnHull3d.y); Vec2 vLow(OnHull3d.x, myAABB.min.y); Vec2 vRight(myAABB.max.x, OnHull3d.y); float fDistanceToTop = p3d.GetSquaredDistance2D(vTop); float fDistanceToLeft = p3d.GetSquaredDistance2D(vLeft); float fDistanceToLow = p3d.GetSquaredDistance2D(vLow); float fDistanceToRight = p3d.GetSquaredDistance2D(vRight); float fTempMinDistance = fDistanceToTop; OnHull3d = vTop; if (fDistanceToLeft < fTempMinDistance) { OnHull3d = vLeft; fTempMinDistance = fDistanceToLeft; } if (fDistanceToLow < fTempMinDistance) { OnHull3d = vLow; fTempMinDistance = fDistanceToLow; } if (fDistanceToRight < fTempMinDistance) { OnHull3d = vRight; fTempMinDistance = fDistanceToRight; } OnHull3d.z = min(myAABB.max.z, p3d.z); fClosestDistance = OnHull3d.GetSquaredDistance(p3d); } if (m_abBoxSideObstruction[5].bObstructed && OnHull3d.z == myAABB.min.z) { // Point is on the floor plane, but may be on the edge already Vec2 vTop(OnHull3d.x, myAABB.max.y); Vec2 vLeft(myAABB.min.x, OnHull3d.y); Vec2 vLow(OnHull3d.x, myAABB.min.y); Vec2 vRight(myAABB.max.x, OnHull3d.y); float fDistanceToTop = p3d.GetSquaredDistance2D(vTop); float fDistanceToLeft = p3d.GetSquaredDistance2D(vLeft); float fDistanceToLow = p3d.GetSquaredDistance2D(vLow); float fDistanceToRight = p3d.GetSquaredDistance2D(vRight); float fTempMinDistance = fDistanceToTop; OnHull3d = vTop; if (fDistanceToLeft < fTempMinDistance) { OnHull3d = vLeft; fTempMinDistance = fDistanceToLeft; } if (fDistanceToLow < fTempMinDistance) { OnHull3d = vLow; fTempMinDistance = fDistanceToLow; } if (fDistanceToRight < fTempMinDistance) { OnHull3d = vRight; fTempMinDistance = fDistanceToRight; } OnHull3d.z = max(myAABB.min.z, p3d.z); fClosestDistance = OnHull3d.GetSquaredDistance(p3d); } } // TODO transform point back to world ... (why?) break; } default: { CryFatalError("Unknown area type during CArea::CalcPointNearDistSq (2)"); break; } } // Put the shortest distance into the data cache if (pCachedData != NULL && bCacheResult) { if (bIgnoreSoundObstruction) { if (bIgnoreHeight) { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed | eCachedAreaData_DistNearSqValidHeightIgnored); } else { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidNotObstructed); } } else { if (bIgnoreHeight) { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_DistNearSqValid | eCachedAreaData_DistNearSqValidHeightIgnored); } else { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_DistNearSqValid); } } pCachedData->fDistanceNearSq = fClosestDistance; } } } return fClosestDistance; } ////////////////////////////////////////////////////////////////////////// EAreaPosType CArea::CalcPosType(EntityId const nEntityID, Vec3 const& rPos, bool const bCacheResult /* = true */) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); EAreaPosType eTempPosType = AREA_POS_TYPE_COUNT; if (m_bInitialized) { SCachedAreaData* pCachedData = NULL; if (nEntityID != INVALID_ENTITYID) { TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { pCachedData = &(Iter->second); } } if (pCachedData != NULL && (pCachedData->eFlags & eCachedAreaData_PosTypeValid) != 0) { eTempPosType = GetCachedPointPosTypeWithin(nEntityID); } else { bool const bIsIn2DShape = CalcPointWithin(nEntityID, rPos, true, false); switch (m_AreaType) { case ENTITY_AREA_TYPE_SOLID: { Vec3 const ov3PosLocalSpace(m_InvMatrix.TransformPoint(rPos)); AABB const boundbox(m_AreaSolid->GetBoundBox()); if (ov3PosLocalSpace.z < boundbox.min.z) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZBELOW; } else if (ov3PosLocalSpace.z > boundbox.max.z) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZABOVE; } else { if (bIsIn2DShape) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE; } } break; } case ENTITY_AREA_TYPE_SHAPE: { if (m_VSize > 0.0f) { // Negative means from inside to hull float const fDistToRoof = rPos.z - (m_VOrigin + m_VSize); float const fDistToFloor = m_VOrigin - rPos.z; if (bIsIn2DShape) { // Point is below, in, or above the area if ((rPos.z < m_VOrigin + m_VSize && rPos.z > m_VOrigin)) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE; } else { // Point is above or below area if (fabsf(fDistToRoof) < fabsf(fDistToFloor)) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZABOVE; } else { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZBELOW; } } } else { // Outside of 2D Shape, so diagonal or only to the side if ((rPos.z > m_VOrigin + m_VSize || rPos.z < m_VOrigin)) { // Point is outside z-boundary // point is above or below area if (fabsf(fDistToRoof) < fabsf(fDistToFloor)) { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZABOVE; } else { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZBELOW; } } else { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE; } } } else { // No height means infinite Z boundaries (no roof, no floor) if (bIsIn2DShape) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE; } else { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE; } } break; } case ENTITY_AREA_TYPE_BOX: { if (m_BoxMax.z > 0.0f) { Vec3 const ov3PosLocalSpace(m_InvMatrix.TransformPoint(rPos)); if (bIsIn2DShape) { if (ov3PosLocalSpace.z < m_BoxMin.z) // Warning : Fix for boxes not centered around the base of an entity. Please check before back integrating. { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZBELOW; } else if (ov3PosLocalSpace.z > m_BoxMax.z) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZABOVE; } else { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE; } } else { if (ov3PosLocalSpace.z < m_BoxMin.z) // Warning : Fix for boxes not centered around the base of an entity. Please check before back integrating. { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZBELOW; } else if (ov3PosLocalSpace.z > m_BoxMax.z) { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZABOVE; } else { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE; } } } else { // No height means infinite Z boundaries (no roof, no floor) if (bIsIn2DShape) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE; } else { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE; } } break; } case ENTITY_AREA_TYPE_SPHERE: { float const fSphereMostTop = m_SphereCenter.z + m_SphereRadius; float const fSphereMostBottom = m_SphereCenter.z - m_SphereRadius; bool const bAbove = rPos.z > fSphereMostTop; bool const bBelow = rPos.z < fSphereMostBottom; bool const bIsIn3DShape = CalcPointWithin(nEntityID, rPos, false); if (bIsIn3DShape) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZINSIDE; } else { if (bIsIn2DShape) { // We're inside of the sphere 2D silhouette but not inside the sphere itself // now check if we're above its max z or below its min z if (bAbove) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZABOVE; } if (bBelow) { eTempPosType = AREA_POS_TYPE_2DINSIDE_ZBELOW; } } else { // We're outside of the sphere 2D silhouette and outside of the sphere itself // now check if we're above its max z or below its min z eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZINSIDE; if (bAbove) { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZABOVE; } if (bBelow) { eTempPosType = AREA_POS_TYPE_2DOUTSIDE_ZBELOW; } } } break; } default: { CryFatalError("Unknown area type during CArea::CalcPosType"); break; } } if (pCachedData != NULL && bCacheResult) { pCachedData->eFlags = (ECachedAreaData)(pCachedData->eFlags | eCachedAreaData_PosTypeValid); pCachedData->ePosType = eTempPosType; } } } return eTempPosType; } ////////////////////////////////////////////////////////////////////////// void CArea::CalcClosestPointToObstructedShape(EntityId const nEntityID, Vec3& outClosest, float& outClosestDistSq, Vec3 const& sourcePos) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); size_t const nSegmentCount = m_vpSegments.size(); Lineseg oLine; Vec3 closest(ZERO); float closestDistSq(FLT_MAX); // If this area got a height if (m_VSize) { EAreaPosType const ePosType = CalcPosType(nEntityID, sourcePos); float const fRoofWorldPosZ = m_VOrigin + m_VSize; // Find the closest point // First of all check if we're either right above a non-obstructing roof or below a non-obstructing floor // if so, just use this data since we have the shortest distance right there if (ePosType == AREA_POS_TYPE_2DINSIDE_ZABOVE && !m_bObstructRoof) { closestDistSq = (sourcePos.z - fRoofWorldPosZ) * (sourcePos.z - fRoofWorldPosZ); closest.x = sourcePos.x; closest.y = sourcePos.y; closest.z = fRoofWorldPosZ; } else if (ePosType == AREA_POS_TYPE_2DINSIDE_ZBELOW && !m_bObstructFloor) { closestDistSq = (m_VOrigin - sourcePos.z) * (m_VOrigin - sourcePos.z); closest.x = sourcePos.x; closest.y = sourcePos.y; closest.z = m_VOrigin; } else { PrefetchLine(&m_vpSegments[0], 0); for (size_t nIdx = 0; nIdx < nSegmentCount; ++nIdx) { a2DSegment const* const curSg = m_vpSegments[nIdx]; float const fCurrSegStart[2] = {curSg->GetStart().x, curSg->GetStart().y}; float const fCurrSegEnd[2] = {curSg->GetEnd().x, curSg->GetEnd().y}; // If the segment is not obstructed get the closest point to it if (!curSg->bObstructSound) { float fPosZ = sourcePos.z; bool bAdjusted = false; // Adjust Z position if we're outside the boundaries if (fPosZ > fRoofWorldPosZ) { fPosZ = fRoofWorldPosZ; bAdjusted = true; } else if (fPosZ < m_VOrigin) { fPosZ = m_VOrigin; bAdjusted = true; } oLine.start = Vec3(fCurrSegStart[0], fCurrSegStart[1], fPosZ); oLine.end = Vec3(fCurrSegEnd[0], fCurrSegEnd[1], fPosZ); // If we're outside the Z boundaries we need to include Z on our test float fT; float const fTempDistToLineSq = bAdjusted ? Distance::Point_LinesegSq(sourcePos, oLine, fT) : Distance::Point_Lineseg2DSq(sourcePos, oLine, fT); if (fTempDistToLineSq < closestDistSq) { closestDistSq = fTempDistToLineSq; closest = oLine.GetPoint(fT); } } else { // Otherwise we need to check the roof and the floor if we're not inside the area if (ePosType != AREA_POS_TYPE_2DINSIDE_ZINSIDE) { // Roof if (!m_bObstructRoof) { oLine.start = Vec3(fCurrSegStart[0], fCurrSegStart[1], fRoofWorldPosZ); oLine.end = Vec3(fCurrSegEnd[0], fCurrSegEnd[1], fRoofWorldPosZ); float fT; float const fTempDistToLineSq = Distance::Point_LinesegSq(sourcePos, oLine, fT); if (fTempDistToLineSq < closestDistSq) { closestDistSq = fTempDistToLineSq; closest = oLine.GetPoint(fT); } } // Floor if (!m_bObstructFloor) { oLine.start = Vec3(fCurrSegStart[0], fCurrSegStart[1], m_VOrigin); oLine.end = Vec3(fCurrSegEnd[0], fCurrSegEnd[1], m_VOrigin); float fT; float const fTempDistToLineSq = Distance::Point_LinesegSq(sourcePos, oLine, fT); if (fTempDistToLineSq < closestDistSq) { closestDistSq = fTempDistToLineSq; closest = oLine.GetPoint(fT); } } } } } } // If we're inside an area we always need to check a non-obstructing roof and floor // this needs to be done only once right after the loop if (ePosType == AREA_POS_TYPE_2DINSIDE_ZINSIDE) { // Roof if (!m_bObstructRoof) { float const fTempDistToLineSq = (sourcePos.z - fRoofWorldPosZ) * (sourcePos.z - fRoofWorldPosZ); if (fTempDistToLineSq < closestDistSq) { closestDistSq = fTempDistToLineSq; closest.x = sourcePos.x; closest.y = sourcePos.y; closest.z = fRoofWorldPosZ; } } // Floor if (!m_bObstructFloor) { float const fTempDistToLineSq = (m_VOrigin - sourcePos.z) * (m_VOrigin - sourcePos.z); if (fTempDistToLineSq < closestDistSq) { closestDistSq = fTempDistToLineSq; closest.x = sourcePos.x; closest.y = sourcePos.y; closest.z = m_VOrigin; } } } } else // This area has got infinite height { // Find the closest distance to non-obstructing segments, at source pos height for (size_t nIdx = 0; nIdx < nSegmentCount; ++nIdx) { a2DSegment const* const curSg = m_vpSegments[nIdx]; float const fCurrSegStart[2] = {curSg->GetStart().x, curSg->GetStart().y}; float const fCurrSegEnd[2] = {curSg->GetEnd().x, curSg->GetEnd().y}; // If the segment is not obstructed get the closest point and continue if (!curSg->bObstructSound) { oLine.start = Vec3(fCurrSegStart[0], fCurrSegStart[1], sourcePos.z); oLine.end = Vec3(fCurrSegEnd[0], fCurrSegEnd[1], sourcePos.z); float fT; float const fTempDistToLineSq = Distance::Point_Lineseg2DSq(sourcePos, oLine, fT); // Ignore Z if (fTempDistToLineSq < closestDistSq) { closestDistSq = fTempDistToLineSq; closest = oLine.GetPoint(fT); } } } } // If there was no calculation, most likely all sides + roof + floor are obstructed, return the source position if (closest.IsZeroFast() && closestDistSq == FLT_MAX) { closest = sourcePos; } outClosest = closest; outClosestDistSq = closestDistSq; } ////////////////////////////////////////////////////////////////////////// void CArea::CalcClosestPointToObstructedBox(Vec3& outClosest, float& outClosestDistSq, Vec3 const& sourcePos) const { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); Vec3 source(sourcePos); Vec3 closest(sourcePos); outClosestDistSq = FLT_MAX; if (!m_bAllObstructed) { Vec3 const v3PosLocalSpace(m_InvMatrix.TransformPoint(source)), v3BoxMin = m_BoxMin, v3BoxMax = m_BoxMax; // Determine the closest side and check for obstruction int nClosestSideIdx = -1; bool bAllObstructedExceptBackside = false; Vec3 aoBoxBackSideEdges[4]; if (v3PosLocalSpace.x > v3BoxMax.x) { // Side 2 is closest, 4 is backside nClosestSideIdx = 1; if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[3].bObstructed) { // Describe possible snapping positions on back side 4 aoBoxBackSideEdges[0] = Vec3(v3BoxMin.x, v3BoxMin.y, v3PosLocalSpace.z); aoBoxBackSideEdges[1] = Vec3(v3BoxMin.x, v3PosLocalSpace.y, v3BoxMax.z); aoBoxBackSideEdges[2] = Vec3(v3BoxMin.x, v3BoxMax.y, v3PosLocalSpace.z); aoBoxBackSideEdges[3] = Vec3(v3BoxMin.x, v3PosLocalSpace.y, v3BoxMin.z); bAllObstructedExceptBackside = true; } } else if (v3PosLocalSpace.x < v3BoxMin.x) { // Side 4 is closest, 2 is backside nClosestSideIdx = 3; if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[1].bObstructed) { // Describe possible snapping positions on back side 2 aoBoxBackSideEdges[0] = Vec3(v3BoxMax.x, v3BoxMax.y, v3PosLocalSpace.z); aoBoxBackSideEdges[1] = Vec3(v3BoxMax.x, v3PosLocalSpace.y, v3BoxMax.z); aoBoxBackSideEdges[2] = Vec3(v3BoxMax.x, v3BoxMin.y, v3PosLocalSpace.z); aoBoxBackSideEdges[3] = Vec3(v3BoxMax.x, v3PosLocalSpace.y, v3BoxMin.z); bAllObstructedExceptBackside = true; } } else { if (v3PosLocalSpace.y < v3BoxMin.y) { // Side 0 is closest, 2 is backside nClosestSideIdx = 0; if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[2].bObstructed) { // Describe possible snapping positions on back side 2 aoBoxBackSideEdges[0] = Vec3(v3BoxMin.x, v3BoxMax.y, v3PosLocalSpace.z); aoBoxBackSideEdges[1] = Vec3(v3PosLocalSpace.x, v3BoxMax.y, v3BoxMax.z); aoBoxBackSideEdges[2] = Vec3(v3BoxMax.x, v3BoxMax.y, v3PosLocalSpace.z); aoBoxBackSideEdges[3] = Vec3(v3PosLocalSpace.x, v3BoxMax.y, v3BoxMin.z); bAllObstructedExceptBackside = true; } } else if (v3PosLocalSpace.y > v3BoxMax.y) { // Side 2 is closest, 0 is backside nClosestSideIdx = 2; if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[0].bObstructed) { // Describe possible snapping positions on back side 0 aoBoxBackSideEdges[0] = Vec3(v3BoxMax.x, v3BoxMin.y, v3PosLocalSpace.z); aoBoxBackSideEdges[1] = Vec3(v3PosLocalSpace.x, v3BoxMin.y, v3BoxMax.z); aoBoxBackSideEdges[2] = Vec3(v3BoxMin.x, v3BoxMin.y, v3PosLocalSpace.z); aoBoxBackSideEdges[3] = Vec3(v3PosLocalSpace.x, v3BoxMin.y, v3BoxMin.z); bAllObstructedExceptBackside = true; } } else { if (v3PosLocalSpace.z < v3BoxMin.z) { // Side 5 is closest, 4 is backside nClosestSideIdx = 5; if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[4].bObstructed) { // Describe possible snapping positions on back side 4 aoBoxBackSideEdges[0] = Vec3(v3PosLocalSpace.x, v3BoxMin.y, v3BoxMax.z); aoBoxBackSideEdges[1] = Vec3(v3BoxMin.x, v3PosLocalSpace.y, v3BoxMax.z); aoBoxBackSideEdges[2] = Vec3(v3PosLocalSpace.x, v3BoxMax.y, v3BoxMax.z); aoBoxBackSideEdges[3] = Vec3(v3BoxMax.x, v3PosLocalSpace.y, v3BoxMax.z); bAllObstructedExceptBackside = true; } } else if (v3PosLocalSpace.z > v3BoxMax.z) { // Side 4 is closest, 5 is backside nClosestSideIdx = 4; if (m_nObstructedCount == 5 && !m_abBoxSideObstruction[5].bObstructed) { // Describe possible snapping positions on back side 5 aoBoxBackSideEdges[0] = Vec3(v3PosLocalSpace.x, v3BoxMin.y, v3BoxMin.z); aoBoxBackSideEdges[1] = Vec3(v3BoxMin.x, v3PosLocalSpace.y, v3BoxMin.z); aoBoxBackSideEdges[2] = Vec3(v3PosLocalSpace.x, v3BoxMax.y, v3BoxMin.z); aoBoxBackSideEdges[3] = Vec3(v3BoxMax.x, v3PosLocalSpace.y, v3BoxMin.z); bAllObstructedExceptBackside = true; } } else { // We're inside the box float const fDistSide[6] = { abs(m_abBoxSideObstruction[0].bObstructed ? FLT_MAX : v3BoxMin.y - v3PosLocalSpace.y), abs(m_abBoxSideObstruction[1].bObstructed ? FLT_MAX : v3BoxMax.x - v3PosLocalSpace.x), abs(m_abBoxSideObstruction[2].bObstructed ? FLT_MAX : v3BoxMax.y - v3PosLocalSpace.y), abs(m_abBoxSideObstruction[3].bObstructed ? FLT_MAX : v3BoxMin.x - v3PosLocalSpace.x), abs(m_abBoxSideObstruction[4].bObstructed ? FLT_MAX : v3BoxMax.z - v3PosLocalSpace.z), abs(m_abBoxSideObstruction[5].bObstructed ? FLT_MAX : v3BoxMin.z - v3PosLocalSpace.z) }; float const fClosestDist = min(min(min(fDistSide[0], fDistSide[1]), min(fDistSide[2], fDistSide[3])), min(fDistSide[4], fDistSide[5])); for (unsigned int i = 0; i < 6; ++i) { if (fDistSide[i] == fClosestDist) { switch (i) { case 0: closest = Vec3(v3PosLocalSpace.x, v3PosLocalSpace.y - fClosestDist, v3PosLocalSpace.z); break; case 1: closest = Vec3(v3PosLocalSpace.x + fClosestDist, v3PosLocalSpace.y, v3PosLocalSpace.z); break; case 2: closest = Vec3(v3PosLocalSpace.x, v3PosLocalSpace.y + fClosestDist, v3PosLocalSpace.z); break; case 3: closest = Vec3(v3PosLocalSpace.x - fClosestDist, v3PosLocalSpace.y, v3PosLocalSpace.z); break; case 4: closest = Vec3(v3PosLocalSpace.x, v3PosLocalSpace.y, v3PosLocalSpace.z + fClosestDist); break; case 5: closest = Vec3(v3PosLocalSpace.x, v3PosLocalSpace.y, v3PosLocalSpace.z - fClosestDist); break; } // Transform it all back to world coordinates outClosest = m_WorldTM.TransformPoint(closest); outClosestDistSq = Vec3(source - outClosest).GetLengthSquared(); return; } } } } } // We're done determining the us facing side and we're not inside the box, // now check if the side not obstructed and return the closest position on it if (nClosestSideIdx > -1 && !m_abBoxSideObstruction[nClosestSideIdx].bObstructed) { // The shortest side is not obstructed closest.x = min(max(v3PosLocalSpace.x, v3BoxMin.x), v3BoxMax.x); closest.y = min(max(v3PosLocalSpace.y, v3BoxMin.y), v3BoxMax.y); closest.z = min(max(v3PosLocalSpace.z, v3BoxMin.z), v3BoxMax.z); // Transform the result back to world values outClosest = m_WorldTM.TransformPoint(closest); outClosestDistSq = Vec3(source - outClosest).GetLengthSquared(); return; } // If we get here the closest side was obstructed // Now describe the 6 sides by applying min and max coordinate values SBoxSide const aoBoxSides[6] = { SBoxSide(v3BoxMin, Vec3(v3BoxMax.x, v3BoxMin.y, v3BoxMax.z)), SBoxSide(Vec3(v3BoxMax.x, v3BoxMin.y, v3BoxMin.z), v3BoxMax), SBoxSide(Vec3(v3BoxMin.x, v3BoxMax.y, v3BoxMin.z), v3BoxMax), SBoxSide(v3BoxMin, Vec3(v3BoxMin.x, v3BoxMax.y, v3BoxMax.z)), SBoxSide(Vec3(v3BoxMin.x, v3BoxMin.y, v3BoxMax.z), v3BoxMax), SBoxSide(v3BoxMin, Vec3(v3BoxMax.x, v3BoxMax.y, v3BoxMin.z)) }; // Check all non-obstructing sides, get the closest position on them float fClosestDist = FLT_MAX; Vec3 v3ClosestPoint(ZERO); for (unsigned int i = 0; i < 6; ++i) { if (!m_abBoxSideObstruction[i].bObstructed) { if (bAllObstructedExceptBackside) { // At least 2 axis must be within boundaries, which means we're facing directly a side and snapping is necessary bool const bWithinX = v3PosLocalSpace.x > v3BoxMin.x && v3PosLocalSpace.x < v3BoxMax.x; bool const bWithinY = v3PosLocalSpace.y > v3BoxMin.y && v3PosLocalSpace.y < v3BoxMax.y; bool const bWithinZ = v3PosLocalSpace.z > v3BoxMin.z && v3PosLocalSpace.z < v3BoxMax.z; if (bWithinX && (bWithinY || bWithinZ) || bWithinY && (bWithinX || bWithinZ) || bWithinZ && (bWithinX || bWithinY)) { // Get the distance to the edges and choose the shortest one float const fDistToEdges[4] = { Vec3(v3PosLocalSpace - aoBoxBackSideEdges[0]).GetLengthSquared(), Vec3(v3PosLocalSpace - aoBoxBackSideEdges[1]).GetLengthSquared(), Vec3(v3PosLocalSpace - aoBoxBackSideEdges[2]).GetLengthSquared(), Vec3(v3PosLocalSpace - aoBoxBackSideEdges[3]).GetLengthSquared() }; float const fClosestDistToEdges = min(min(fDistToEdges[0], fDistToEdges[1]), min(fDistToEdges[2], fDistToEdges[3])); for (unsigned int j = 0; j < 4; ++j) { if (fDistToEdges[j] == fClosestDistToEdges) { // Snap to it closest = aoBoxBackSideEdges[j]; break; } } break; } } v3ClosestPoint.x = min(max(v3PosLocalSpace.x, aoBoxSides[i].v3MinValues.x), aoBoxSides[i].v3MaxValues.x); v3ClosestPoint.y = min(max(v3PosLocalSpace.y, aoBoxSides[i].v3MinValues.y), aoBoxSides[i].v3MaxValues.y); v3ClosestPoint.z = min(max(v3PosLocalSpace.z, aoBoxSides[i].v3MinValues.z), aoBoxSides[i].v3MaxValues.z); float const fTemp = Vec3(v3PosLocalSpace - v3ClosestPoint).GetLengthSquared(); if (fTemp < fClosestDist) { fClosestDist = fTemp; closest = v3ClosestPoint; } } } // Transform the result back to world values outClosest = m_WorldTM.TransformPoint(closest); outClosestDistSq = Vec3(source - outClosest).GetLengthSquared(); } else { outClosest = sourcePos; } } ////////////////////////////////////////////////////////////////////////// void CArea::CalcClosestPointToSolid(Vec3 const& rv3SourcePos, bool bIgnoreSoundObstruction, float& rfClosestDistSq, Vec3* rv3ClosestPos) const { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); Vec3 localPoint3D = m_InvMatrix.TransformPoint(rv3SourcePos); int queryFlag = bIgnoreSoundObstruction ? CAreaSolid::eSegmentQueryFlag_All : CAreaSolid::eSegmentQueryFlag_Open; if (m_AreaSolid->IsInside(localPoint3D)) { queryFlag |= CAreaSolid::eSegmentQueryFlag_UsingReverseSegment; } Vec3 closestPos(0, 0, 0); m_AreaSolid->QueryNearest(localPoint3D, queryFlag, closestPos, rfClosestDistSq); if (rv3ClosestPos) { *rv3ClosestPos = m_WorldTM.TransformPoint(closestPos); } } ////////////////////////////////////////////////////////////////////////// void CArea::InvalidateCachedAreaData(EntityId const nEntityID) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { SCachedAreaData& rCachedData = Iter->second; rCachedData.eFlags = eCachedAreaData_None; rCachedData.ePosType = AREA_POS_TYPE_COUNT; rCachedData.fDistanceWithinSq = FLT_MAX; rCachedData.fDistanceNearSq = FLT_MAX; rCachedData.bPointWithin = false; rCachedData.oPos.zero(); } } ////////////////////////////////////////////////////////////////////////// void CArea::SetPoints(const Vec3* const vPoints, const bool* const pabSoundObstructionSegments, const int nPointsCount) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); ReleaseAreaData(); m_AreaType = ENTITY_AREA_TYPE_SHAPE; // at least three points needed to create closed shape if (nPointsCount > 2) { m_bInitialized = true; float minZ = 10000000.0f; ////////////////////////////////////////////////////////////////////////// for (int i = 0; i < nPointsCount; ++i) { if (vPoints[i].z < minZ) { minZ = vPoints[i].z; } } m_VOrigin = minZ; int pIdx; for (pIdx = 1; pIdx < nPointsCount; ++pIdx) { AddSegment(*((CArea::a2DPoint*)(vPoints + pIdx - 1)), *((CArea::a2DPoint*)(vPoints + pIdx)), *(pabSoundObstructionSegments + pIdx - 1)); } AddSegment(*((CArea::a2DPoint*)(vPoints + pIdx - 1)), *((CArea::a2DPoint*)(vPoints)), *(pabSoundObstructionSegments + pIdx - 1)); CalcBBox(); } m_pAreaManager->SetAreaDirty(this); } ////////////////////////////////////////////////////////////////////////// void CArea::SetBox(const Vec3& min, const Vec3& max, const Matrix34& tm) { ReleaseAreaData(); m_AreaType = ENTITY_AREA_TYPE_BOX; m_bInitialized = true; m_BoxMin = min; m_BoxMax = max; m_InvMatrix = tm.GetInverted(); /* uint32 nErrorCode=0; uint32 minValid = min.IsValid(); if (minValid==0) nErrorCode|=0x8000; uint32 maxValid = max.IsValid(); if (maxValid==0) nErrorCode|=0x8000; if (max.x < min.x) nErrorCode|=0x0001; if (max.y < min.y) nErrorCode|=0x0001; if (max.z < min.z) nErrorCode|=0x0001; if (min.x < -8000) nErrorCode|=0x0002; if (min.y < -8000) nErrorCode|=0x0004; if (min.z < -8000) nErrorCode|=0x0008; if (max.x > +8000) nErrorCode|=0x0010; if (max.y > +8000) nErrorCode|=0x0020; if (max.z > +8000) nErrorCode|=0x0040; assert(nErrorCode==0); if (nErrorCode) { CryFatalError("Fatal Error: BBox in EntitySystem is out of range"); //AnimWarning("CryAnimation: Invalid BBox (%.3f,%.3f,%.3f)-(%.3f,%.3f,%.3f). ModenPath: '%s' ErrorCode: %08x", m_AABB.min.x, m_AABB.min.y, m_AABB.min.z, m_AABB.max.x, m_AABB.max.y, m_AABB.max.z, m_pInstance->m_pModel->GetFilePathCStr(), nErrorCode); assert(0); } //if (rAnim.m_nEAnimID>=numAnimations) */ m_pAreaManager->SetAreaDirty(this); } ////////////////////////////////////////////////////////////////////////// void CArea::BeginSettingSolid(const Matrix34& worldTM) { ReleaseAreaData(); m_AreaType = ENTITY_AREA_TYPE_SOLID; m_bInitialized = true; m_WorldTM = worldTM; m_InvMatrix = m_WorldTM.GetInverted(); m_AreaSolid = new CAreaSolid; } ////////////////////////////////////////////////////////////////////////// void CArea::AddConvexHullToSolid(const Vec3* verticesOfConvexHull, bool bObstruction, int numberOfVertices) { if (!m_AreaSolid || m_AreaType != ENTITY_AREA_TYPE_SOLID) { return; } m_AreaSolid->AddSegment(verticesOfConvexHull, bObstruction, numberOfVertices); } ////////////////////////////////////////////////////////////////////////// void CArea::EndSettingSolid() { if (!m_AreaSolid || m_AreaType != ENTITY_AREA_TYPE_SOLID) { return; } m_AreaSolid->BuildBSP(); m_pAreaManager->SetAreaDirty(this); } ////////////////////////////////////////////////////////////////////////// void CArea::SetMatrix(const Matrix34& tm) { m_InvMatrix = tm.GetInverted(); m_WorldTM = tm; m_pAreaManager->SetAreaDirty(this); } ////////////////////////////////////////////////////////////////////////// void CArea::GetMatrix(Matrix34& tm) const { tm = m_WorldTM; } ////////////////////////////////////////////////////////////////////////// void CArea::GetWorldBox(Vec3& rMin, Vec3& rMax) const { switch (m_AreaType) { case ENTITY_AREA_TYPE_SHAPE: { rMin.x = m_areaBBox.min.x; rMin.y = m_areaBBox.min.y; rMin.z = m_VOrigin; rMax.x = m_areaBBox.max.x; rMax.y = m_areaBBox.max.y; rMax.z = m_VOrigin + m_VSize; break; } case ENTITY_AREA_TYPE_BOX: { rMin = m_WorldTM.TransformPoint(m_BoxMin); rMax = m_WorldTM.TransformPoint(m_BoxMax); break; } case ENTITY_AREA_TYPE_SPHERE: { rMin = m_SphereCenter - Vec3(m_SphereRadius, m_SphereRadius, m_SphereRadius); rMax = m_SphereCenter + Vec3(m_SphereRadius, m_SphereRadius, m_SphereRadius); break; } case ENTITY_AREA_TYPE_GRAVITYVOLUME: { rMin.zero(); rMax.zero(); break; } case ENTITY_AREA_TYPE_SOLID: { AABB oAABB; GetSolidBoundBox(oAABB); rMin = oAABB.min; rMax = oAABB.max; break; } default: { rMin.zero(); rMax.zero(); break; } } } ////////////////////////////////////////////////////////////////////////// void CArea::SetSphere(const Vec3& center, float fRadius) { ReleaseAreaData(); m_bInitialized = true; m_AreaType = ENTITY_AREA_TYPE_SPHERE; m_SphereCenter = center; m_SphereRadius = fRadius; m_SphereRadius2 = m_SphereRadius * m_SphereRadius; m_pAreaManager->SetAreaDirty(this); } ////////////////////////////////////////////////////////////////////////// void CArea::CalcBBox() { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); a2DBBox& areaBBox = m_areaBBox; areaBBox.min.x = m_vpSegments[0]->bbox.min.x; areaBBox.min.y = m_vpSegments[0]->bbox.min.y; areaBBox.max.x = m_vpSegments[0]->bbox.max.x; areaBBox.max.y = m_vpSegments[0]->bbox.max.y; for (unsigned int sIdx = 1; sIdx < m_vpSegments.size(); sIdx++) { if (areaBBox.min.x > m_vpSegments[sIdx]->bbox.min.x) { areaBBox.min.x = m_vpSegments[sIdx]->bbox.min.x; } if (areaBBox.min.y > m_vpSegments[sIdx]->bbox.min.y) { areaBBox.min.y = m_vpSegments[sIdx]->bbox.min.y; } if (areaBBox.max.x < m_vpSegments[sIdx]->bbox.max.x) { areaBBox.max.x = m_vpSegments[sIdx]->bbox.max.x; } if (areaBBox.max.y < m_vpSegments[sIdx]->bbox.max.y) { areaBBox.max.y = m_vpSegments[sIdx]->bbox.max.y; } } GetBBox() = areaBBox; } ////////////////////////////////////////////////////////////////////////// void CArea::AddEntity(const EntityId entId) { m_vEntityID.push_back(entId); m_bAttachedSoundTested = false; m_fFadeDistance = -1.0f; m_pAreaManager->SetAreaDirty(this); } ////////////////////////////////////////////////////////////////////////// void CArea::AddEntity(const EntityGUID entGuid) { m_vEntityGuid.push_back(entGuid); EntityId entId = GetEntitySystem()->FindEntityByGuid(entGuid); AddEntity(entId); } ////////////////////////////////////////////////////////////////////////// void CArea::ResolveEntityIds() { if (m_bEntityIdsResolved) { return; } for (unsigned int i = 0; i < m_vEntityGuid.size(); i++) { EntityId entId = GetEntitySystem()->FindEntityByGuid(m_vEntityGuid[i]); m_vEntityID[i] = entId; } // Go through all our entity ids and have the entity system update them, to account // for any cloning unsigned int nSize = m_vEntityID.size(); for (unsigned int eIdx = 0; eIdx < nSize; eIdx++) { m_vEntityID[eIdx] = GetEntitySystem()->GetClonedEntityId(m_vEntityID[eIdx], m_EntityID); } m_bEntityIdsResolved = true; } void CArea::ReleaseCachedAreaData() { stl::free_container(m_mapEntityCachedAreaData); } ////////////////////////////////////////////////////////////////////////// float CArea::GetGreatestFadeDistance() { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_fFadeDistance < 0.0f || gEnv->IsEditor()) { m_fFadeDistance = 0.0f; TEntityIDs::const_iterator Iter(m_vEntityID.begin()); TEntityIDs::const_iterator const IterEnd(m_vEntityID.end()); for (; Iter != IterEnd; ++Iter) { IEntity const* const pEntity = GetEntitySystem()->GetEntity((*Iter)); if (pEntity != NULL) { IComponentAudioConstPtr const pAudioComponent = pEntity->GetComponent<IComponentAudio>(); if (pAudioComponent != NULL) { m_fFadeDistance = max(m_fFadeDistance, pAudioComponent->GetFadeDistance()); } } } } return m_fFadeDistance; } ////////////////////////////////////////////////////////////////////////// float CArea::GetGreatestEnvironmentFadeDistance() { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_fEnvironmentFadeDistance < 0.0f || gEnv->IsEditor()) { m_fEnvironmentFadeDistance = 0.0f; TEntityIDs::const_iterator Iter(m_vEntityID.begin()); TEntityIDs::const_iterator const IterEnd(m_vEntityID.end()); for (; Iter != IterEnd; ++Iter) { IEntity const* const pEntity = GetEntitySystem()->GetEntity((*Iter)); if (pEntity != NULL) { IComponentAudioConstPtr const pAudioComponent = pEntity->GetComponent<IComponentAudio>(); if (pAudioComponent != NULL) { m_fEnvironmentFadeDistance = max(m_fEnvironmentFadeDistance, pAudioComponent->GetEnvironmentFadeDistance()); } } } } return m_fEnvironmentFadeDistance; } ////////////////////////////////////////////////////////////////////////// bool CArea::HasSoundAttached() { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_vEntityID.empty()) { return false; } if (!m_bAttachedSoundTested) { for (unsigned int eIdx = 0; eIdx < m_vEntityID.size(); eIdx++) { IEntity* pAreaAttachedEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]); // assert(pEntity); if (pAreaAttachedEntity) { IEntityClass* pEntityClass = pAreaAttachedEntity->GetClass(); string sClassName = pEntityClass->GetName(); if (sClassName == "AmbientVolume" || sClassName == "SoundSpot" || sClassName == "ReverbVolume") { m_bHasSoundAttached = true; } } } m_bAttachedSoundTested = true; } return m_bHasSoundAttached; } ////////////////////////////////////////////////////////////////////////// void CArea::GetBBox(Vec2& vMin, Vec2& vMax) const { // Only valid for shape areas. const a2DBBox& areaBox = GetBBox(); vMin = Vec2(areaBox.min.x, areaBox.min.y); vMax = Vec2(areaBox.max.x, areaBox.max.y); } ////////////////////////////////////////////////////////////////////////// const CArea::a2DBBox& CArea::GetBBox() const { return s_areaBoxes[m_bbox_holder].box; } ////////////////////////////////////////////////////////////////////////// CArea::a2DBBox& CArea::GetBBox() { return s_areaBoxes[m_bbox_holder].box; } ////////////////////////////////////////////////////////////////////////// void CArea::GetSolidBoundBox(AABB& outBoundBox) const { if (!m_AreaSolid) { return; } outBoundBox = m_AreaSolid->GetBoundBox(); } ////////////////////////////////////////////////////////////////////////// void CArea::AddEntites(const std::vector<EntityId>& entIDs) { for (unsigned int i = 0; i < entIDs.size(); i++) { AddEntity(entIDs[i]); } // invalidating effect radius m_fFadeDistance = -1.0f; } ////////////////////////////////////////////////////////////////////////// void CArea::ClearEntities() { // tell all attached entities they have been disconnect to prevent lost entities IEntity* pEntity; if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "DETACH_THIS"); } for (unsigned int eIdx = 0; eIdx < m_vEntityID.size(); eIdx++) { pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]); // assert(pEntity); if (pEntity) { SEntityEvent event; event.event = ENTITY_EVENT_DETACH_THIS; event.nParam[0] = m_EntityID; event.nParam[1] = m_AreaID; event.nParam[2] = 0; pEntity->SendEvent(event); } } m_vEntityID.clear(); m_vEntityGuid.clear(); m_PrevFade = -1.0f; m_bHasSoundAttached = false; // invalidating effect radius m_fFadeDistance = -1.0f; } ////////////////////////////////////////////////////////////////////////// void CArea::AddCachedEvent(const SEntityEvent& event) { m_cachedEvents.push_back(event); } ////////////////////////////////////////////////////////////////////////// void CArea::ClearCachedEventsFor(EntityId const nEntityID) { for (CachedEvents::iterator it = m_cachedEvents.begin(); it != m_cachedEvents.end(); ) { SEntityEvent& event = *it; if (event.nParam[0] == nEntityID) { it = m_cachedEvents.erase(it); continue; } ++it; } } ////////////////////////////////////////////////////////////////////////// void CArea::ClearCachedEvents() { m_cachedEvents.clear(); } ////////////////////////////////////////////////////////////////////////// void CArea::SendCachedEventsFor(EntityId const nEntityID) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); size_t const nCountCachedEvents = m_cachedEvents.size(); if (m_bInitialized && nCountCachedEvents > 0) { for (size_t i = 0; i < nCountCachedEvents; ++i) { SEntityEvent cachedEvent = m_cachedEvents[i]; // copy to avoid invalidation if vector re-allocates if (cachedEvent.nParam[0] == nEntityID) { if (CVar::pDrawAreaDebug->GetIVal() == 2) { string sState; if (cachedEvent.event == ENTITY_EVENT_ENTERNEARAREA) { sState = "ENTERNEAR"; } if (cachedEvent.event == ENTITY_EVENT_MOVENEARAREA) { sState = "MOVENEAR"; } if (cachedEvent.event == ENTITY_EVENT_ENTERAREA) { sState = "ENTER"; } if (cachedEvent.event == ENTITY_EVENT_MOVEINSIDEAREA) { sState = "MOVEINSIDE"; } if (cachedEvent.event == ENTITY_EVENT_LEAVEAREA) { sState = "LEAVE"; } if (cachedEvent.event == ENTITY_EVENT_LEAVENEARAREA) { sState = "LEAVENEAR"; } CryLog("<AreaManager> Area %d Queued Event: %s", m_EntityID, sState.c_str()); } cachedEvent.nParam[1] = m_AreaID; cachedEvent.nParam[2] = m_EntityID; cachedEvent.fParam[0] = m_PrevFade; SendEvent(cachedEvent, false); } } ClearCachedEventsFor(nEntityID); } } ////////////////////////////////////////////////////////////////////////// void CArea::SendEvent(SEntityEvent& newEvent, bool bClearCachedEvents /* = true */) { m_pAreaManager->OnEvent(newEvent.event, (EntityId)newEvent.nParam[0], this); size_t const nCountEntities = m_vEntityID.size(); for (size_t eIdx = 0; eIdx < nCountEntities; ++eIdx) { if (IEntity* pAreaAttachedEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx])) { pAreaAttachedEntity->SendEvent(newEvent); if (bClearCachedEvents) { ClearCachedEventsFor((EntityId)newEvent.nParam[0]); } } } } // do enter area - player was outside, now is inside // calls entity OnEnterArea which calls script OnEnterArea( player, AreaID ) ////////////////////////////////////////////////////////////////////////// void CArea::EnterArea(IEntity const* const __restrict pEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { EntityId const nEntityID = pEntity->GetId(); TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter == m_mapEntityCachedAreaData.end()) { // If we get here it means "OnAddedToAreaCache" did not get called, the entity was probably spawned within this area. m_mapEntityCachedAreaData.insert(std::make_pair(nEntityID, SCachedAreaData())).first; } m_PrevFade = 1.0f; m_bIsActive = true; if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "ENTER"); } SEntityEvent event; event.event = ENTITY_EVENT_ENTERAREA; event.nParam[0] = nEntityID; event.nParam[1] = m_AreaID; event.nParam[2] = m_EntityID; event.fParam[0] = 1.0f; // fading is handled within the near areas.. we've entered the inner area... ensure fade is fully on.. this fixes any time we teleport immediately into a region rather than transfering across the near region SendEvent(event); } } // do leave area - player was inside, now is outside // calls entity OnLeaveArea which calls script OnLeaveArea( player, AreaID ) ////////////////////////////////////////////////////////////////////////// void CArea::LeaveArea(IEntity const* const __restrict pSrcEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "LEAVE"); } SEntityEvent event; event.event = ENTITY_EVENT_LEAVEAREA; event.nParam[0] = pSrcEntity->GetId(); event.nParam[1] = m_AreaID; event.nParam[2] = m_EntityID; SendEvent(event); } } // do enter near area - entity was "far", now is "near" // calls entity OnEnterNearArea which calls script OnEnterNearArea( entity(player), AreaID ) ////////////////////////////////////////////////////////////////////////// void CArea::EnterNearArea(IEntity const* const __restrict pSrcEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { EntityId const nEntityID = pSrcEntity->GetId(); TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter == m_mapEntityCachedAreaData.end()) { // If we get here it means "OnAddedToAreaCache" did not get called, the entity was probably spawned within the near region of this area. m_mapEntityCachedAreaData.insert(std::make_pair(nEntityID, SCachedAreaData())).first; } if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "ENTERNEAR"); } SEntityEvent event; event.event = ENTITY_EVENT_ENTERNEARAREA; event.nParam[0] = nEntityID; event.nParam[1] = m_AreaID; event.nParam[2] = m_EntityID; SendEvent(event); } } // do leave near area - entity was "near", now is "far" // calls entity OnLeaveNearArea which calls script OnLeaveNearArea( entity(player), AreaID ) ////////////////////////////////////////////////////////////////////////// void CArea::LeaveNearArea(IEntity const* const __restrict pSrcEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "LEAVENEAR"); } SEntityEvent event; event.event = ENTITY_EVENT_LEAVENEARAREA; event.nParam[0] = pSrcEntity->GetId(); event.nParam[1] = m_AreaID; event.nParam[2] = m_EntityID; SendEvent(event); // If the entity currently leaving is the last one in the area, set the area as inactive if (m_pAreaManager->GetNumberOfPlayersNearOrInArea(this) <= 1) { m_PrevFade = -1.0f; m_bIsActive = false; } } } ////////////////////////////////////////////////////////////////////////// void CArea::OnAddedToAreaCache(IEntity const* const pEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { EntityId const nEntityID = pEntity->GetId(); TEntityCachedAreaDataMap::iterator Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter == m_mapEntityCachedAreaData.end()) { m_mapEntityCachedAreaData.insert(std::make_pair(nEntityID, SCachedAreaData())).first; } else { // Cannot yet exist, if so figure out why it wasn't removed properly or why this is called more than once! assert(false); } } } ////////////////////////////////////////////////////////////////////////// void CArea::OnRemovedFromAreaCache(IEntity const* const pEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); m_mapEntityCachedAreaData.erase(pEntity->GetId()); } //calculate distance to area - proceed fade. player is inside of the area ////////////////////////////////////////////////////////////////////////// void CArea::UpdateArea(Vec3 const& vPos, IEntity const* const pEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { ProceedFade(pEntity, CalculateFade(vPos)); } } //calculate distance to area - proceed fade. player is inside of the area ////////////////////////////////////////////////////////////////////////// void CArea::UpdateAreaInside(IEntity const* const __restrict pSrcEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVEINSIDE"); } size_t const nCount = m_vEntityID.size(); for (size_t eIdx = 0; eIdx < nCount; ++eIdx) { IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]); if (pEntity != NULL) { SEntityEvent event; event.event = ENTITY_EVENT_MOVEINSIDEAREA; event.nParam[0] = pSrcEntity->GetId(); event.nParam[1] = m_AreaID; event.nParam[2] = m_EntityID; event.fParam[0] = m_PrevFade; pEntity->SendEvent(event); } } } } // pEntity moves in an area, which is controlled by an area with a higher priority ////////////////////////////////////////////////////////////////////////// void CArea::ExclusiveUpdateAreaInside(IEntity const* const __restrict pSrcEntity, EntityId const AreaHighEntityID, float const fadeValue, float const environmentFadeValue) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVEINSIDE"); } size_t const nCount = m_vEntityID.size(); for (size_t eIdx = 0; eIdx < nCount; ++eIdx) { IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]); if (pEntity != NULL) { SEntityEvent event; event.event = ENTITY_EVENT_MOVEINSIDEAREA; event.nParam[0] = pSrcEntity->GetId(); event.nParam[1] = m_AreaID; event.nParam[2] = m_EntityID; // AreaLowEntityID event.nParam[3] = AreaHighEntityID; event.fParam[0] = fadeValue; event.fParam[1] = environmentFadeValue; pEntity->SendEvent(event); } } } } // pEntity moves near an area, which is controlled by an area with a higher priority ////////////////////////////////////////////////////////////////////////// void CArea::ExclusiveUpdateAreaNear(IEntity const* const __restrict pSrcEntity, EntityId const AreaHighEntityID, float const fadeValue) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLogAlways("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVENEAR"); } size_t const nCount = m_vEntityID.size(); for (size_t eIdx = 0; eIdx < nCount; ++eIdx) { IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]); if (pEntity != NULL) { SEntityEvent event; event.event = ENTITY_EVENT_MOVENEARAREA; event.nParam[0] = pSrcEntity->GetId(); event.nParam[1] = m_AreaID; event.nParam[2] = m_EntityID; // AreaLowEntityID event.nParam[3] = AreaHighEntityID; event.fParam[0] = fadeValue; pEntity->SendEvent(event); } } } } //calculate distance to area. player is inside of the area ////////////////////////////////////////////////////////////////////////// float CArea::CalculateFade(const Vec3& pos3D) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); float fadeCoeff = 0.0f; if (m_bInitialized) { a2DPoint const pos = CArea::a2DPoint(pos3D); switch (m_AreaType) { case ENTITY_AREA_TYPE_SOLID: { if (m_fProximity <= 0.0f) { fadeCoeff = 1.0f; break; } Vec3 PosOnHull(ZERO); float squaredDistance; if (!m_AreaSolid->QueryNearest(pos3D, CAreaSolid::eSegmentQueryFlag_All, PosOnHull, squaredDistance)) { break; } fadeCoeff = sqrt_tpl(squaredDistance) / m_fProximity; } break; case ENTITY_AREA_TYPE_SHAPE: fadeCoeff = CalcDistToPoint(pos); break; case ENTITY_AREA_TYPE_SPHERE: { if (m_fProximity <= 0.0f) { fadeCoeff = 1.0f; break; } Vec3 Delta = pos3D - m_SphereCenter; fadeCoeff = (m_SphereRadius - Delta.GetLength()) / m_fProximity; if (fadeCoeff > 1.0f) { fadeCoeff = 1.0f; } break; } case ENTITY_AREA_TYPE_BOX: { if (m_fProximity <= 0.0f) { fadeCoeff = 1.0f; break; } Vec3 p3D = m_InvMatrix.TransformPoint(pos3D); Vec3 MinDelta = p3D - m_BoxMin; Vec3 MaxDelta = m_BoxMax - p3D; Vec3 EdgeDist = (m_BoxMax - m_BoxMin) / 2.0f; if ((!EdgeDist.x) || (!EdgeDist.y) || (!EdgeDist.z)) { fadeCoeff = 1.0f; break; } float fFadeScale = m_fProximity / 100.0f; EdgeDist *= fFadeScale; float fMinFade = MinDelta.x / EdgeDist.x; for (int k = 0; k < 3; k++) { float fFade1 = MinDelta[k] / EdgeDist[k]; float fFade2 = MaxDelta[k] / EdgeDist[k]; fMinFade = min(fMinFade, min(fFade1, fFade2)); } //k fadeCoeff = fMinFade; if (fadeCoeff > 1.0f) { fadeCoeff = 1.0f; } break; } } } return fadeCoeff; } //proceed fade. player is inside of the area ////////////////////////////////////////////////////////////////////////// void CArea::ProceedFade(IEntity const* const __restrict pSrcEntity, float const fadeCoeff) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized) { // no update if fade coefficient hasn't changed if (m_PrevFade != fadeCoeff) { m_PrevFade = fadeCoeff; if (CVar::pDrawAreaDebug->GetIVal() == 2) { CryLog("<AreaManager> Area %d Direct Event: %s", m_EntityID, "MOVEINSIDE"); } size_t const nCount = m_vEntityID.size(); for (size_t eIdx = 0; eIdx < nCount; ++eIdx) { IEntity* const __restrict pEntity = GetEntitySystem()->GetEntity(m_vEntityID[eIdx]); if (pEntity != NULL) { SEntityEvent event; event.event = ENTITY_EVENT_MOVEINSIDEAREA; event.nParam[0] = pSrcEntity->GetId(); event.nParam[1] = m_EntityID; event.fParam[0] = fadeCoeff; pEntity->SendEvent(event); } } } } } ////////////////////////////////////////////////////////////////////////// void CArea::OnAreaCrossing(IEntity const* const __restrict pEntity) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); if (m_bInitialized && pEntity != NULL) { IEntity* __restrict pAreaAttachedEntity = NULL; std::vector<EntityId>::const_iterator const IterEnd(m_vEntityID.end()); for (std::vector<EntityId>::const_iterator Iter(m_vEntityID.begin()); Iter != IterEnd; ++Iter) { pAreaAttachedEntity = GetEntitySystem()->GetEntity(*Iter); if (pAreaAttachedEntity != NULL) { SEntityEvent oEvent; oEvent.event = ENTITY_EVENT_CROSS_AREA; oEvent.nParam[0] = pEntity->GetId(); pAreaAttachedEntity->SendEvent(oEvent); } } } } ////////////////////////////////////////////////////////////////////////// void CArea::Draw(size_t const idx) { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); I3DEngine* p3DEngine = gEnv->p3DEngine; IRenderAuxGeom* pRC = gEnv->pRenderer->GetIRenderAuxGeom(); pRC->SetRenderFlags(e_Def3DPublicRenderflags); ColorB colorsArray[] = { ColorB(255, 0, 0, 255), ColorB(0, 255, 0, 255), ColorB(0, 0, 255, 255), ColorB(255, 255, 0, 255), ColorB(255, 0, 255, 255), ColorB(0, 255, 255, 255), ColorB(255, 255, 255, 255), }; ColorB color = colorsArray[idx % (sizeof(colorsArray) / sizeof(ColorB))]; ColorB color1 = colorsArray[(idx + 1) % (sizeof(colorsArray) / sizeof(ColorB))]; // ColorB color2 = colorsArray[(idx+2)%(sizeof(colorsArray)/sizeof(ColorB))]; switch (m_AreaType) { case ENTITY_AREA_TYPE_SOLID: m_AreaSolid->Draw(m_WorldTM, color, color1); break; case ENTITY_AREA_TYPE_SHAPE: { Vec3 v0, v1; float deltaZ = 0.1f; unsigned int nSize = m_vpSegments.size(); for (unsigned int sIdx = 0; sIdx < nSize; sIdx++) { if (m_vpSegments[sIdx]->k < 0) { v0.x = m_vpSegments[sIdx]->bbox.min.x; v0.y = m_vpSegments[sIdx]->bbox.max.y; v1.x = m_vpSegments[sIdx]->bbox.max.x; v1.y = m_vpSegments[sIdx]->bbox.min.y; } else { v0.x = m_vpSegments[sIdx]->bbox.min.x; v0.y = m_vpSegments[sIdx]->bbox.min.y; v1.x = m_vpSegments[sIdx]->bbox.max.x; v1.y = m_vpSegments[sIdx]->bbox.max.y; } float elevation = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainHeight(); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(elevation , &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats , v0.x, v0.y, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, nullptr); v0.z = max(m_VOrigin, elevation + deltaZ); elevation = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainHeight(); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(elevation , &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats , v1.x, v1.y, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, nullptr); v1.z = max(m_VOrigin, elevation + deltaZ); // draw lower line segments pRC->DrawLine(v0, color, v1, color); // Draw upper line segments and vertical edges if (m_VSize > 0.0f) { Vec3 v0Z = Vec3(v0.x, v0.y, m_VOrigin + m_VSize); Vec3 v1Z = Vec3(v1.x, v1.y, m_VOrigin + m_VSize); pRC->DrawLine(v0, color, v0Z, color); //pRC->DrawLine( v1, color, v1Z, color ); pRC->DrawLine(v0Z, color, v1Z, color); } } break; } case ENTITY_AREA_TYPE_SPHERE: { ColorB color3 = color; color3.a = 64; pRC->SetRenderFlags(e_Def3DPublicRenderflags | e_AlphaBlended); pRC->DrawSphere(m_SphereCenter, m_SphereRadius, color3); break; } case ENTITY_AREA_TYPE_BOX: { float fLength = m_BoxMax.x - m_BoxMin.x; float fWidth = m_BoxMax.y - m_BoxMin.y; float fHeight = m_BoxMax.z - m_BoxMin.z; Vec3 v0 = m_BoxMin; Vec3 v1 = Vec3(m_BoxMin.x + fLength, m_BoxMin.y, m_BoxMin.z); Vec3 v2 = Vec3(m_BoxMin.x + fLength, m_BoxMin.y + fWidth, m_BoxMin.z); Vec3 v3 = Vec3(m_BoxMin.x, m_BoxMin.y + fWidth, m_BoxMin.z); Vec3 v4 = Vec3(m_BoxMin.x, m_BoxMin.y, m_BoxMin.z + fHeight); Vec3 v5 = Vec3(m_BoxMin.x + fLength, m_BoxMin.y, m_BoxMin.z + fHeight); Vec3 v6 = Vec3(m_BoxMin.x + fLength, m_BoxMin.y + fWidth, m_BoxMin.z + fHeight); Vec3 v7 = Vec3(m_BoxMin.x, m_BoxMin.y + fWidth, m_BoxMin.z + fHeight); v0 = m_WorldTM.TransformPoint(v0); v1 = m_WorldTM.TransformPoint(v1); v2 = m_WorldTM.TransformPoint(v2); v3 = m_WorldTM.TransformPoint(v3); v4 = m_WorldTM.TransformPoint(v4); v5 = m_WorldTM.TransformPoint(v5); v6 = m_WorldTM.TransformPoint(v6); v7 = m_WorldTM.TransformPoint(v7); // draw lower half of box pRC->DrawLine(v0, color1, v1, color1); pRC->DrawLine(v1, color1, v2, color1); pRC->DrawLine(v2, color1, v3, color1); pRC->DrawLine(v3, color1, v0, color1); // draw upper half of box pRC->DrawLine(v4, color1, v5, color1); pRC->DrawLine(v5, color1, v6, color1); pRC->DrawLine(v6, color1, v7, color1); pRC->DrawLine(v7, color1, v4, color1); // draw vertical edges pRC->DrawLine(v0, color1, v4, color1); pRC->DrawLine(v1, color1, v5, color1); pRC->DrawLine(v2, color1, v6, color1); pRC->DrawLine(v3, color1, v7, color1); break; } default: break; } } ////////////////////////////////////////////////////////////////////////// void CArea::ReleaseAreaData() { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); ClearPoints(); stl::free_container(m_mapEntityCachedAreaData); } ////////////////////////////////////////////////////////////////////////// void CArea::GetMemoryUsage(ICrySizer* pSizer) const { SIZER_COMPONENT_NAME(pSizer, "CArea"); if (m_AreaSolid) { m_AreaSolid->GetMemoryUsage(pSizer); } for (size_t i = 0, iSementSize(m_vpSegments.size()); i < iSementSize; ++i) { pSizer->AddObject(m_vpSegments[i], sizeof(*m_vpSegments[i])); } pSizer->AddObject(this, sizeof(*this)); } ////////////////////////////////////////////////////////////////////////// float CArea::GetCachedPointWithinDistSq(EntityId const nEntityID) const { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); float fValue = 0.0f; TEntityCachedAreaDataMap::const_iterator const Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { fValue = Iter->second.fDistanceWithinSq; } return fValue; } ////////////////////////////////////////////////////////////////////////// bool CArea::GetCachedPointWithin(EntityId const nEntityID) const { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); bool bValue = false; TEntityCachedAreaDataMap::const_iterator const Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { bValue = Iter->second.bPointWithin; } return bValue; } ////////////////////////////////////////////////////////////////////////// EAreaPosType CArea::GetCachedPointPosTypeWithin(EntityId const nEntityID) const { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); EAreaPosType eValue = AREA_POS_TYPE_COUNT; TEntityCachedAreaDataMap::const_iterator const Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { eValue = Iter->second.ePosType; } return eValue; } ////////////////////////////////////////////////////////////////////////// float CArea::GetCachedPointNearDistSq(EntityId const nEntityID) const { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); float fValue = 0.0f; TEntityCachedAreaDataMap::const_iterator const Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { fValue = Iter->second.fDistanceNearSq; } return fValue; } ////////////////////////////////////////////////////////////////////////// Vec3 const& CArea::GetCachedPointOnHull(EntityId const nEntityID) const { FUNCTION_PROFILER(GetISystem(), PROFILE_ENTITY); TEntityCachedAreaDataMap::const_iterator const Iter(m_mapEntityCachedAreaData.find(nEntityID)); if (Iter != m_mapEntityCachedAreaData.end()) { return Iter->second.oPos; } return m_oNULLVec; } ////////////////////////////////////////////////////////////////////////// const CArea::TAreaBoxes& CArea::GetBoxHolders() { return s_areaBoxes; } #if defined(INCLUDE_ENTITYSYSTEM_PRODUCTION_CODE) ////////////////////////////////////////////////////////////////////////// char const* const CArea::GetAreaEntityName() const { IEntitySystem const* const pIEntitySystem = gEnv->pEntitySystem; if (pIEntitySystem != NULL) { IEntity const* const pIEntity = pIEntitySystem->GetEntity(m_EntityID); if (pIEntity != NULL) { return pIEntity->GetName(); } } return NULL; } #endif // INCLUDE_ENTITYSYSTEM_PRODUCTION_CODE