Ticket #2430: newSubdivision.patch
File newSubdivision.patch, 20.9 KB (added by , 10 years ago) |
---|
-
source/simulation2/helpers/Spatial_2.h
1 /* Copyright (C) 2010 Wildfire Games. 2 * This file is part of 0 A.D. 3 * 4 * 0 A.D. is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 2 of the License, or 7 * (at your option) any later version. 8 * 9 * 0 A.D. is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with 0 A.D. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 #ifndef INCLUDED_SPATIAL2 19 #define INCLUDED_SPATIAL2 20 21 #include "simulation2/system/Component.h" 22 #include "simulation2/serialization/SerializeTemplates.h" 23 #include "ps/CLogger.h" 24 25 /** 26 * A basic subdivision scheme for finding entities in range 27 * Items are stored in vectors in fixed-size divisions. 28 * Items have a size (min/max values of their axis-aligned bounding box) 29 * If that size is higher than a subdivision's size, they're stored in the "general" vector 30 * This means that if too many objects have a size that's big, it'll end up being slow 31 * We want subdivisions to be as small as possible yet contain as many items as possible. 32 * I have currently set it to "30", which is bigger than most buildings and entities. 33 * 34 * When adding, deleting or moving, you need to supply the position 35 * the item was added at, otherwise it might check the wrong subdivision and fail. 36 * 37 * If a unit size were to change, it would need to be updated, but that doesn't happen for now. 38 * 39 * Using a template because why not. 40 */ 41 template<typename T> 42 class SpatialTree 43 { 44 std::vector<T> m_OverSizedData; 45 // fixed size array of subdivisions. 46 std::vector<T>* m_SpatialDivisionsData; 47 // number of columns in the above array 48 size_t m_ArrayWidth; 49 // size of subdivisions, used to deduce m_ArrayWidth 50 int m_SubdivisionSize; 51 52 public: 53 SpatialTree() : m_SubdivisionSize(30) 54 { 55 } 56 ~SpatialTree() 57 { 58 } 59 // copy constructor 60 SpatialTree(const SpatialTree& other) : m_SubdivisionSize(30) 61 { 62 Create(other.m_ArrayWidth); 63 std::copy(other.m_SpatialDivisionsData, other.m_SpatialDivisionsData+m_ArrayWidth*m_ArrayWidth, m_SpatialDivisionsData); 64 } 65 SpatialTree& operator=(const SpatialTree& other) 66 { 67 if (this != &other) 68 { 69 Reset(other.m_ArrayWidth); 70 std::copy(other.m_SpatialDivisionsData, other.m_SpatialDivisionsData+m_ArrayWidth*m_ArrayWidth, m_SpatialDivisionsData); 71 } 72 return *this; 73 } 74 75 // Create a spatial tree of the map. 76 void Create(size_t arrayWidth) 77 { 78 if (m_SpatialDivisionsData) delete[] m_SpatialDivisionsData; 79 // figure the number of divisions we want 80 m_ArrayWidth = arrayWidth; 81 m_SpatialDivisionsData = new std::vector<T>[m_ArrayWidth*m_ArrayWidth]; 82 m_OverSizedData.clear(); 83 } 84 85 void Reset(size_t x, size_t y) 86 { 87 if (y > x) 88 Create(y/m_SubdivisionSize); 89 else 90 Create(x/m_SubdivisionSize); 91 } 92 93 /** 94 * Equivalence test (ignoring order of items within each subdivision) 95 */ 96 bool operator==(const SpatialTree& other) 97 { 98 if (m_ArrayWidth != other.m_ArrayWidth) 99 return false; 100 if (m_OverSizedData != other.m_OverSizedData) 101 return false; 102 for (size_t idx = 0; idx < m_ArrayWidth*m_ArrayWidth; ++idx) 103 if (m_SpatialDivisionsData[idx] != other.m_SpatialDivisionsData[idx]) 104 return false; 105 return true; 106 } 107 108 inline bool operator!=(const SpatialTree& rhs) 109 { 110 return !(*this == rhs); 111 } 112 113 /** 114 * Add an item. 115 * The item must not already be present. 116 */ 117 void Add(const T item, CFixedVector2D position, u16 size) 118 { 119 if (size > m_SubdivisionSize) 120 { 121 if (std::find(m_OverSizedData.begin(),m_OverSizedData.end(), item) == m_OverSizedData.end()) 122 m_OverSizedData.push_back(item); 123 } else { 124 // find the proper subdivision. 125 int X = (position.X / m_SubdivisionSize).ToInt_RoundToZero(); 126 int Y = (position.Y / m_SubdivisionSize).ToInt_RoundToZero(); 127 std::vector<T>& subdivision = m_SpatialDivisionsData[X+Y*m_ArrayWidth]; 128 if (std::find(subdivision.begin(),subdivision.end(), item) == subdivision.end()) 129 { 130 // add it since it's unique. 131 subdivision.push_back(item); 132 } 133 } 134 } 135 136 /** 137 * Remove an item. 138 * Position must be where we expect to find it, or we won't find it. 139 */ 140 void Remove(const T item, CFixedVector2D position, u16 size) 141 { 142 if (size > m_SubdivisionSize) 143 { 144 typename std::vector<T>::iterator itr = std::find(m_OverSizedData.begin(),m_OverSizedData.end(), item); 145 if (itr != m_OverSizedData.end()) 146 m_OverSizedData.erase(itr); 147 } else { 148 // find the proper subdivision. 149 size_t X = (position.X / m_SubdivisionSize).ToInt_RoundToZero(); 150 size_t Y = (position.Y / m_SubdivisionSize).ToInt_RoundToZero(); 151 std::vector<T>& subdivision = m_SpatialDivisionsData[X+Y*m_ArrayWidth]; 152 typename std::vector<T>::iterator itr = std::find(subdivision.begin(),subdivision.end(), item); 153 if (itr != subdivision.end()) 154 { 155 subdivision.erase(itr); 156 } 157 } 158 } 159 160 /** 161 * Equivalent to Remove() then Add(), but slightly faster. 162 * In particular for big objects nothing needs to be done. 163 */ 164 void Move(const T item, CFixedVector2D oldPosition, CFixedVector2D newPosition, u16 size) 165 { 166 if (size <= m_SubdivisionSize) 167 { 168 // find the proper subdivision. 169 size_t oldX = (oldPosition.X / m_SubdivisionSize).ToInt_RoundToZero(); 170 size_t oldY = (oldPosition.Y / m_SubdivisionSize).ToInt_RoundToZero(); 171 size_t newX = (newPosition.X / m_SubdivisionSize).ToInt_RoundToZero(); 172 size_t newY = (newPosition.Y / m_SubdivisionSize).ToInt_RoundToZero(); 173 std::vector<T>& subdivision = m_SpatialDivisionsData[oldX+oldY*m_ArrayWidth]; 174 typename std::vector<T>::iterator itr = std::find(subdivision.begin(),subdivision.end(), item); 175 if (itr != subdivision.end()) 176 { 177 subdivision.erase(itr); 178 // since we've found it, we'll assume the query is OK and add it directly. 179 m_SpatialDivisionsData[newX+newY*m_ArrayWidth].push_back(item); 180 } 181 } 182 } 183 184 /** 185 * Returns a list of item that are either in the square 186 * or really quite close to it. 187 * The data is garanteed to be unique but is unsorted. 188 * It's the responsibility of the querier to do proper distance checking. 189 */ 190 void GetInRange(std::vector<T>& out, CFixedVector2D posMin, CFixedVector2D posMax) 191 { 192 size_t minX = (posMin.X / m_SubdivisionSize).ToInt_RoundToZero(); 193 size_t maxX = (posMax.X / m_SubdivisionSize).ToInt_RoundToZero(); 194 size_t minY = (posMin.Y / m_SubdivisionSize).ToInt_RoundToZero(); 195 size_t maxY = (posMax.Y / m_SubdivisionSize).ToInt_RoundToZero(); 196 // Now expand the subdivisions by One so we make sure that we've got all elements potentially in range. 197 // also abuse ternary operators because I like to pretend I'm a good C++ developer. 198 minX > 0? --minX : 0; 199 minX < m_ArrayWidth? ++maxX : 0; 200 minY > 0? --minY : 0; 201 minY < m_ArrayWidth? ++maxY : 0; 202 // assume out is clean 203 for (size_t Y = minY; Y < maxY; ++Y) 204 for (size_t X = minX; X < maxX; ++X) 205 { 206 std::vector<T>& subdivision = m_SpatialDivisionsData[X+Y*m_ArrayWidth]; 207 if (subdivision.size() != 0) 208 out.insert(out.end(), subdivision.begin(), subdivision.end()); 209 } 210 } 211 212 /** 213 * Returns a sorted list of unique items that includes all items 214 * within the given circular distance of the given point and a few more. 215 */ 216 void GetNear(std::vector<T>& out, CFixedVector2D pos, entity_pos_t range) 217 { 218 // TODO: be cleverer and return a circular pattern of divisions, 219 // not this square over-approximation 220 CFixedVector2D r(range, range); 221 GetInRange(out, pos - r, pos + r); 222 } 223 224 int GetDivisionSize() { return m_SubdivisionSize; }; 225 int GetWidth() { return m_ArrayWidth; }; 226 }; 227 228 #endif // INCLUDED_SPATIAL2 -
source/simulation2/components/CCmpRangeManager.cpp
Property changes on: source/simulation2/helpers/Spatial_2.h ___________________________________________________________________ Added: svn:eol-style ## -0,0 +1 ## +native \ No newline at end of property
28 28 #include "simulation2/components/ICmpVision.h" 29 29 #include "simulation2/components/ICmpWaterManager.h" 30 30 #include "simulation2/helpers/Render.h" 31 #include "simulation2/helpers/Spatial.h"32 31 33 32 #include "graphics/Overlay.h" 34 33 #include "graphics/Terrain.h" … … 131 130 static std::map<entity_id_t, EntityParabolicRangeOutline> ParabolicRangesOutlines; 132 131 133 132 /** 134 * Representation of an entity, with the data needed for queries.135 */136 struct EntityData137 {138 EntityData() : retainInFog(0), owner(-1), inWorld(0), flags(1) { }139 entity_pos_t x, z;140 entity_pos_t visionRange;141 u8 retainInFog; // boolean142 i8 owner;143 u8 inWorld; // boolean144 u8 flags; // See GetEntityFlagMask145 };146 147 cassert(sizeof(EntityData) == 16);148 149 /**150 133 * Serialization helper template for Query 151 134 */ 152 135 struct SerializeQuery … … 193 176 template<typename S> 194 177 void operator()(S& serialize, const char* UNUSED(name), EntityData& value) 195 178 { 179 serialize.NumberU32_Unbounded("id", value.id); 196 180 serialize.NumberFixed_Unbounded("x", value.x); 197 181 serialize.NumberFixed_Unbounded("z", value.z); 198 182 serialize.NumberFixed_Unbounded("vision", value.visionRange); 183 serialize.NumberU16_Unbounded("size", value.size); 199 184 serialize.NumberU8("retain in fog", value.retainInFog, 0, 1); 200 185 serialize.NumberI8_Unbounded("owner", value.owner); 201 186 serialize.NumberU8("in world", value.inWorld, 0, 1); … … 274 259 std::map<tag_t, Query> m_Queries; 275 260 EntityMap<EntityData> m_EntityData; 276 261 277 SpatialSubdivision m_Subdivision; // spatial index of m_EntityData 262 SpatialTree<const EntityData*> m_Subdivision; // spatial index of m_EntityData 263 std::vector<const EntityData*> queryResultVector; 278 264 279 265 // LOS state: 280 266 static const player_id_t MAX_LOS_PLAYER_ID = 16; … … 328 314 m_LosRevealAll.resize(MAX_LOS_PLAYER_ID+2,false); 329 315 m_SharedLosMasks.resize(MAX_LOS_PLAYER_ID+2,0); 330 316 317 queryResultVector.reserve(12000); 318 331 319 m_LosCircular = false; 332 320 m_TerrainVerticesPerSide = 0; 333 321 … … 395 383 if (!cmpPosition) 396 384 break; 397 385 398 // The newly-created entity will have owner -1 and position out-of-world 399 // (any initialisation of those values will happen later), so we can just 400 // use the default-constructed EntityData here 401 EntityData entdata; 402 386 // initialization of position-related values happens later. 387 EntityData entdata(ent); 388 403 389 // Store the LOS data, if any 404 390 CmpPtr<ICmpVision> cmpVision(GetSimContext(), ent); 405 391 if (cmpVision) … … 407 393 entdata.visionRange = cmpVision->GetRange(); 408 394 entdata.retainInFog = (cmpVision->GetRetainInFog() ? 1 : 0); 409 395 } 396 397 // Store the size data, if any 398 CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), ent); 399 if (cmpObstruction) 400 { 401 ICmpObstructionManager::ObstructionSquare out; 402 cmpObstruction->GetObstructionSquare(out); 403 size_t size = (out.hh.Square() + out.hw.Square()).Sqrt().ToInt_RoundToInfinity(); 404 // avoid overflow, it doesn't really matter as long as this is above subdivision size. 405 if (size > 65530) 406 size = 65530; 407 entdata.size = (u16)size; 408 } 410 409 411 410 // Remember this entity 412 411 m_EntityData.insert(ent, entdata); … … 429 428 { 430 429 CFixedVector2D from(it->second.x, it->second.z); 431 430 CFixedVector2D to(msgData.x, msgData.z); 432 m_Subdivision.Move( ent, from, to);431 m_Subdivision.Move(&it->second, from, to, it->second.size); 433 432 LosMove(it->second.owner, it->second.visionRange, from, to); 434 433 } 435 434 else 436 435 { 437 436 CFixedVector2D to(msgData.x, msgData.z); 438 m_Subdivision.Add( ent, to);437 m_Subdivision.Add(&it->second, to, it->second.size); 439 438 LosAdd(it->second.owner, it->second.visionRange, to); 440 439 } 441 440 … … 448 447 if (it->second.inWorld) 449 448 { 450 449 CFixedVector2D from(it->second.x, it->second.z); 451 m_Subdivision.Remove( ent, from);450 m_Subdivision.Remove(&it->second, from, it->second.size); 452 451 LosRemove(it->second.owner, it->second.visionRange, from); 453 452 } 454 453 … … 494 493 break; 495 494 496 495 if (it->second.inWorld) 497 m_Subdivision.Remove( ent, CFixedVector2D(it->second.x, it->second.z));496 m_Subdivision.Remove(&it->second, CFixedVector2D(it->second.x, it->second.z), it->second.size); 498 497 499 498 // This will be called after Ownership's OnDestroy, so ownership will be set 500 499 // to -1 already and we don't have to do a LosRemove here … … 573 572 574 573 std::vector<std::vector<u16> > oldPlayerCounts = m_LosPlayerCounts; 575 574 std::vector<u32> oldStateRevealed = m_LosStateRevealed; 576 Spatial SubdivisionoldSubdivision = m_Subdivision;575 SpatialTree<const EntityData*> oldSubdivision = m_Subdivision; 577 576 578 577 ResetDerivedData(true); 579 578 … … 601 600 debug_warn(L"inconsistent subdivs"); 602 601 } 603 602 604 Spatial Subdivision* GetSubdivision()603 SpatialTree<const EntityData*>* GetSubdivision() 605 604 { 606 return & 605 return &m_Subdivision; 607 606 } 608 607 609 608 // Reinitialise subdivisions and LOS data, based on entity data … … 661 660 662 661 void ResetSubdivisions(entity_pos_t x1, entity_pos_t z1) 663 662 { 664 // Use 8x8 tile subdivisions 665 // (TODO: find the optimal number instead of blindly guessing) 666 m_Subdivision.Reset(x1, z1, entity_pos_t::FromInt(8*TERRAIN_TILE_SIZE)); 663 m_Subdivision.Reset(x1.ToInt_RoundToInfinity(), z1.ToInt_RoundToInfinity()); 667 664 668 665 for (EntityMap<EntityData>::const_iterator it = m_EntityData.begin(); it != m_EntityData.end(); ++it) 669 666 { 670 667 if (it->second.inWorld) 671 m_Subdivision.Add( it->first, CFixedVector2D(it->second.x, it->second.z));668 m_Subdivision.Add(&it->second, CFixedVector2D(it->second.x, it->second.z), it->second.size); 672 669 } 673 670 } 674 671 … … 939 936 CFixedVector3D pos3d = cmpSourcePosition->GetPosition()+ 940 937 CFixedVector3D(entity_pos_t::Zero(), q.elevationBonus, entity_pos_t::Zero()) ; 941 938 // Get a quick list of entities that are potentially in range, with a cutoff of 2*maxRange 942 SpatialQueryArray ents;943 m_Subdivision.GetNear( ents, pos, q.maxRange*2);939 queryResultVector.clear(); 940 m_Subdivision.GetNear(queryResultVector, pos, q.maxRange*2); 944 941 945 for ( int i = 0; i < ents.size(); ++i)942 for (size_t i = 0; i < queryResultVector.size(); ++i) 946 943 { 947 EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]);948 ENSURE(it != m_EntityData.end());944 //EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]); 945 //ENSURE(it != m_EntityData.end()); 949 946 950 if (!TestEntityQuery(q, it->first, it->second))947 if (!TestEntityQuery(q, queryResultVector[i]->id, *queryResultVector[i])) 951 948 continue; 952 949 953 CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), ents[i]);950 CmpPtr<ICmpPosition> cmpSecondPosition(GetSimContext(), queryResultVector[i]->id); 954 951 if (!cmpSecondPosition || !cmpSecondPosition->IsInWorld()) 955 952 continue; 956 953 CFixedVector3D secondPosition = cmpSecondPosition->GetPosition(); 957 954 958 955 // Restrict based on precise distance 959 956 if (!InParabolicRange( 960 CFixedVector3D( it->second.x, secondPosition.Y, it->second.z)957 CFixedVector3D(queryResultVector[i]->x, secondPosition.Y, queryResultVector[i]->z) 961 958 - pos3d, 962 959 q.maxRange)) 963 960 continue; 964 961 965 962 if (!q.minRange.IsZero()) 966 963 { 967 int distVsMin = (CFixedVector2D( it->second.x, it->second.z) - pos).CompareLength(q.minRange);964 int distVsMin = (CFixedVector2D(queryResultVector[i]->x, queryResultVector[i]->z) - pos).CompareLength(q.minRange); 968 965 if (distVsMin < 0) 969 966 continue; 970 967 } 971 968 972 r.push_back( it->first);969 r.push_back(queryResultVector[i]->id); 973 970 } 974 971 } 975 972 // check a regular range (i.e. not the entire world, and not parabolic) 976 973 else 977 974 { 978 975 // Get a quick list of entities that are potentially in range 979 SpatialQueryArray ents;980 m_Subdivision.GetNear( ents, pos, q.maxRange);976 queryResultVector.clear(); 977 m_Subdivision.GetNear(queryResultVector, pos, q.maxRange); 981 978 982 for ( int i = 0; i < ents.size(); ++i)979 for (size_t i = 0; i < queryResultVector.size(); ++i) 983 980 { 984 EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]);985 ENSURE(it != m_EntityData.end());981 //EntityMap<EntityData>::const_iterator it = m_EntityData.find(ents[i]); 982 //ENSURE(it != m_EntityData.end()); 986 983 987 if (!TestEntityQuery(q, it->first, it->second))984 if (!TestEntityQuery(q, queryResultVector[i]->id, *queryResultVector[i])) 988 985 continue; 989 986 990 987 // Restrict based on precise distance 991 int distVsMax = (CFixedVector2D( it->second.x, it->second.z) - pos).CompareLength(q.maxRange);988 int distVsMax = (CFixedVector2D(queryResultVector[i]->x, queryResultVector[i]->z) - pos).CompareLength(q.maxRange); 992 989 if (distVsMax > 0) 993 990 continue; 994 991 995 992 if (!q.minRange.IsZero()) 996 993 { 997 int distVsMin = (CFixedVector2D( it->second.x, it->second.z) - pos).CompareLength(q.minRange);994 int distVsMin = (CFixedVector2D(queryResultVector[i]->x, queryResultVector[i]->z) - pos).CompareLength(q.minRange); 998 995 if (distVsMin < 0) 999 996 continue; 1000 997 } 1001 998 1002 r.push_back( it->first);999 r.push_back(queryResultVector[i]->id); 1003 1000 } 1004 1001 } 1005 1002 } … … 1264 1261 } 1265 1262 1266 1263 // render subdivision grid 1267 float divSize = m_Subdivision.GetDivisionSize().ToFloat();1264 int divSize = m_Subdivision.GetDivisionSize(); 1268 1265 int width = m_Subdivision.GetWidth(); 1269 int height = m_Subdivision.GetHeight();1270 1266 for (int x = 0; x < width; ++x) 1271 1267 { 1272 for (int y = 0; y < height; ++y)1268 for (int y = 0; y < width; ++y) 1273 1269 { 1274 1270 m_DebugOverlayLines.push_back(SOverlayLine()); 1275 1271 m_DebugOverlayLines.back().m_Color = subdivColour; -
source/simulation2/components/ICmpRangeManager.h
24 24 #include "simulation2/system/Interface.h" 25 25 #include "simulation2/helpers/Position.h" 26 26 #include "simulation2/helpers/Player.h" 27 #include "simulation2/helpers/Spatial .h"27 #include "simulation2/helpers/Spatial_2.h" 28 28 29 29 #include "graphics/Terrain.h" // for TERRAIN_TILE_SIZE 30 30 … … 65 65 * sent to the entity once per turn if anybody has entered or left the range since the last RangeUpdate. 66 66 * Queries can be disabled, in which case no message will be sent. 67 67 */ 68 69 /** 70 * Representation of an entity, with the data needed for queries. 71 */ 72 struct EntityData 73 { 74 EntityData() : retainInFog(0), owner(-1), inWorld(0), flags(1) { } 75 EntityData(entity_id_t ID) : retainInFog(0), owner(-1), inWorld(0), flags(1), id(ID) { } 76 entity_id_t id; 77 entity_pos_t x, z; 78 entity_pos_t visionRange; 79 u16 size; 80 u8 retainInFog; // boolean 81 i8 owner; 82 u8 inWorld; // boolean 83 u8 flags; // See GetEntityFlagMask 84 }; 85 86 cassert(sizeof(EntityData) == 24); 87 68 88 class ICmpRangeManager : public IComponent 69 89 { 70 90 public: … … 77 97 * Access the spatial subdivision kept by the range manager. 78 98 * @return pointer to spatial subdivision structure. 79 99 */ 80 virtual Spatial Subdivision* GetSubdivision() = 0;100 virtual SpatialTree<const EntityData*>* GetSubdivision() = 0; 81 101 82 102 /** 83 103 * Set the bounds of the world. -
source/simulation2/helpers/Selection.cpp
27 27 #include "simulation2/components/ICmpTemplateManager.h" 28 28 #include "simulation2/components/ICmpSelectable.h" 29 29 #include "simulation2/components/ICmpVisual.h" 30 #include "simulation2/helpers/Spatial.h"31 30 #include "ps/CLogger.h" 32 31 #include "ps/Profiler2.h" 33 32 … … 49 48 CFixedVector2D pos(fixed::FromFloat(pos3d.X), fixed::FromFloat(pos3d.Z)); 50 49 51 50 // Get a rough group of entities using our approximated origin. 52 SpatialQueryArrayents;51 std::vector<const EntityData*> ents; 53 52 cmpRangeManager->GetSubdivision()->GetNear(ents, pos, entity_pos_t::FromInt(range)); 54 53 55 54 // Filter for relevent entities and calculate precise distances. 56 55 std::vector<std::pair<float, entity_id_t> > hits; // (dist^2, entity) pairs 57 for ( int i = 0; i < ents.size(); ++i)56 for (size_t i = 0; i < ents.size(); ++i) 58 57 { 59 CmpPtr<ICmpSelectable> cmpSelectable(simulation, ents[i] );58 CmpPtr<ICmpSelectable> cmpSelectable(simulation, ents[i]->id); 60 59 if (!cmpSelectable) 61 60 continue; 62 61 … … 107 106 CVector3D closest = origin + dir * (center - origin).Dot(dir); 108 107 dist2 = (closest - center).LengthSquared(); 109 108 110 hits.push_back(std::make_pair(dist2, ents[i] ));109 hits.push_back(std::make_pair(dist2, ents[i]->id)); 111 110 } 112 111 113 112 // Sort hits by distance