/// returns the normal vector StaticVector<ctype,3> normal(int tri) const { const StaticVector<ctype,3> a = vertices(triangles(tri).vertices[1]) - vertices(triangles(tri).vertices[0]); const StaticVector<ctype,3> b = vertices(triangles(tri).vertices[2]) - vertices(triangles(tri).vertices[0]); StaticVector<ctype,3> n = a.cross(b); n.normalize(); return n; }
TEST(StaticVectorTest, push_get) { StaticVector<int> sVect; auto ref1 = sVect.push_back(23); auto ref2 = sVect.push_back(24); auto ref3 = sVect.push_back(25); ASSERT_EQ(23, ref1.get()); ASSERT_EQ(24, ref2.get()); ASSERT_EQ(25, ref3.get()); ASSERT_EQ(size_t(3), sVect.activeSize()); sVect.remove(ref2); ASSERT_EQ(size_t(2), sVect.activeSize()); auto ref4 = sVect.push_back(45); ASSERT_EQ(45, ref4.get()); ASSERT_EQ(size_t(3), sVect.activeSize()); auto findInsert = [] ( int const& it ) -> bool {return it > 30;}; auto ref5 = sVect.push(55, findInsert); ASSERT_EQ(55, ref5.get()); // we expect it at positon 2 ASSERT_EQ(55, sVect.get(2)); ASSERT_EQ(45, sVect.get(3)); }
void ContactMapping<2,ctype>::computeDiscreteDomainDirections(const DirectionFunction<2,ctype>* direction, std::vector<StaticVector<ctype,2> >& normals) { size_t nElements = psurface_.domainSegments.size(); if (!direction) { // ////////////////////////////////////////////////////////// // The contact directions are given as the vertex normals // ////////////////////////////////////////////////////////// normals.resize(psurface_.domainVertices.size()); for (size_t i=0; i<psurface_.domainVertices.size(); i++) normals[i] = StaticVector<ctype,2>(0); for (size_t i=0; i<nElements; i++) { // Compute segment normal int v0 = psurface_.domainSegments[i].points[0]; int v1 = psurface_.domainSegments[i].points[1]; StaticVector<ctype,2> segment; segment[0] = psurface_.domainVertices[v1][0] - psurface_.domainVertices[v0][0]; segment[1] = psurface_.domainVertices[v1][1] - psurface_.domainVertices[v0][1]; StaticVector<ctype,2> segmentNormal; segmentNormal[0] = segment[1]; segmentNormal[1] = -segment[0]; segmentNormal /= segmentNormal.length(); normals[psurface_.domainSegments[i].points[0]] += segmentNormal; normals[psurface_.domainSegments[i].points[1]] += segmentNormal; } for (size_t i=0; i<normals.size(); i++) normals[i] /= normals[i].length(); } else { // Sample the provided analytical contact direction field normals.resize(psurface_.domainVertices.size()); for (size_t i=0; i<psurface_.domainVertices.size(); i++) { if (dynamic_cast<const AnalyticDirectionFunction<2,ctype>*>(direction)) normals[i] = (*dynamic_cast<const AnalyticDirectionFunction<2,ctype>*>(direction))(psurface_.domainVertices[i]); else if (dynamic_cast<const DiscreteDirectionFunction<2,ctype>*>(direction)) normals[i] = (*dynamic_cast<const DiscreteDirectionFunction<2,ctype>*>(direction))(i); else throw(std::runtime_error("Domain direction function not properly set!")); } } }
void ContactMapping<2,ctype>::computeDiscreteTargetDirections(const std::vector<std::array<int,2> >& elements, const DirectionFunction<2,ctype>* direction, std::vector<StaticVector<ctype,2> >& normals) { // Build the normal field normals.resize(psurface_.targetVertices.size()); for (size_t i=0; i<psurface_.targetVertices.size(); i++) normals[i] = StaticVector<ctype,2>(0); if (!direction) { for (int i=0; i<elements.size(); i++) { // Compute segment normal int v0 = elements[i][0]; int v1 = elements[i][1]; StaticVector<ctype,2> segment; segment[0] = psurface_.targetVertices[v1][0] - psurface_.targetVertices[v0][0]; segment[1] = psurface_.targetVertices[v1][1] - psurface_.targetVertices[v0][1]; StaticVector<ctype,2> segmentNormal; segmentNormal[0] = segment[1]; segmentNormal[1] = -segment[0]; segmentNormal /= segmentNormal.length(); normals[elements[i][0]] += segmentNormal; normals[elements[i][1]] += segmentNormal; } for (size_t i=0; i<normals.size(); i++) normals[i] /= normals[i].length(); } else { // Sample the provided analytical contact direction field normals.resize(psurface_.targetVertices.size()); for (size_t i=0; i<psurface_.targetVertices.size(); i++) { if (dynamic_cast<const AnalyticDirectionFunction<2,ctype>*>(direction)) normals[i] = (*dynamic_cast<const AnalyticDirectionFunction<2,ctype>*>(direction))(psurface_.targetVertices[i]); else if (dynamic_cast<const DiscreteDirectionFunction<2,ctype>*>(direction)) normals[i] = (*dynamic_cast<const DiscreteDirectionFunction<2,ctype>*>(direction))(i); else throw(std::runtime_error("Target direction function not properly set!")); } } }
typename SurfaceBase<VertexType,EdgeType,TriangleType>::ctype SurfaceBase<VertexType,EdgeType,TriangleType>::minInteriorAngle(int n) const { ctype minAngle = 2*M_PI; const std::array<int, 3>& p = triangles(n).vertices; for (int i=0; i<3; i++){ StaticVector<ctype,3> a = vertices(p[(i+1)%3]) - vertices(p[i]); StaticVector<ctype,3> b = vertices(p[(i+2)%3]) - vertices(p[i]); ctype angle = acosf(a.dot(b) / (a.length() * b.length())); if (angle<minAngle) minAngle = angle; } return minAngle; }
void AI_Shutdown( void ) { hubAreas.clear(); AI_UnloadLevel(); AiShutdownHooksHolder::Instance()->InvokeHooks(); }
void completeGame(GoState& s, StaticVector<GoMove, MAX_GAME_LENGTH>& move_seq, RNG &rng) { bool game_over = false; unsigned int moves = 0; while (!game_over) { moves++; GoMove move = selectMove(s, rng); assert(s.isValidMove(move)); if (move.isPass() && s.getPreviousMoveWasPass()) { game_over = true; } s.makeMove(move); move_seq.push_back(move); } }
void ContactMapping<2,ctype>::build(const std::vector<std::array<ctype,2> >& coords1, ///< The vertex coordinates of the first surface const std::vector<std::array<int,2> >& tri1, ///< The triangles of the first surface const std::vector<std::array<ctype,2> >& coords2, ///< The vertices of the second surface const std::vector<std::array<int,2> >& tri2, const DirectionFunction<2,ctype>* domainDirection, const DirectionFunction<2,ctype>* targetDirection ) { int numVertices1 = coords1.size(); int numVertices2 = coords2.size(); int nTri1 = tri1.size(); int nTri2 = tri2.size(); #if 0 printf("----- 1 -----\n"); for (int i=0; i<nTri1; i++) printf("-- %d %d\n", tri1[2*i], tri1[2*i+1]); printf("----- 2 -----\n"); for (int i=0; i<nTri2; i++) printf("-- %d %d\n", tri2[2*i], tri2[2*i+1]); #endif // ////////////////////////////////////////////////// // Build domain surface and its normal field // ////////////////////////////////////////////////// psurface_.domainVertices.resize(numVertices1); for (int i=0; i<numVertices1; i++) for (int j=0; j<2; j++) psurface_.domainVertices[i][j] = coords1[i][j]; // Build the domain segments psurface_.domainSegments.clear(); // may contain old stuff from previous runs psurface_.domainSegments.resize(nTri1); for (int i=0; i<nTri1; i++) { psurface_.domainSegments[i].points[0] = tri1[i][0]; psurface_.domainSegments[i].points[1] = tri1[i][1]; } // /////////////////////////////// // Build the domain normal field // /////////////////////////////// std::vector<StaticVector<ctype, 2> > domainNormals; std::vector<StaticVector<ctype, 2> > targetNormals; computeDiscreteDomainDirections(domainDirection, domainNormals); // ////////////////////////////////////////////////// // Build range surface and its normal field // ////////////////////////////////////////////////// // first mark the vertices that are actually used psurface_.targetVertices.resize(numVertices2); for (int i=0; i<numVertices2; i++) for (int j=0; j<2; j++) psurface_.targetVertices[i][j] = coords2[i][j]; // ///////////////////////////////////////////////////// // Build the segments-per-vertex arrays // ///////////////////////////////////////////////////// std::vector<std::array<int, 2> > segPerVertex1(psurface_.domainVertices.size()); for (size_t i=0; i<segPerVertex1.size(); i++) segPerVertex1[i][0] = segPerVertex1[i][1] = -1; for (int i=0; i<nTri1; i++) { //printf("segment %d: %d %d -- %d %d\n", i, tri2[2*i], tri2[2*i+1], used2[tri2[2*i]],used2[tri2[2*i+1]]); for (int j=0; j<2; j++) { int p = tri1[i][j]; if (segPerVertex1[p][0]==-1) segPerVertex1[p][0] = i; else segPerVertex1[p][1] = i; } } // use this to construct the neighbor relationships between segments for (size_t i=0; i<psurface_.domainSegments.size(); i++) { int vertex0 = psurface_.domainSegments[i].points[0]; int other0 = (segPerVertex1[vertex0][0] == i) ? segPerVertex1[vertex0][1] : segPerVertex1[vertex0][0]; psurface_.domainSegments[i].neighbor[0] = other0; int vertex1 = psurface_.domainSegments[i].points[1]; int other1 = (segPerVertex1[vertex1][0] == i) ? segPerVertex1[vertex1][1] : segPerVertex1[vertex1][0]; psurface_.domainSegments[i].neighbor[1] = other1; //printf("Segment %d neighbors: %d %d\n", i, other0, other1); } // Build the segments-per-vertex arrays for the target vertices std::vector<std::array<int, 2> > segPerVertex2(psurface_.targetVertices.size()); for (size_t i=0; i<segPerVertex2.size(); i++) segPerVertex2[i][0] = segPerVertex2[i][1] = -1; for (int i=0; i<nTri2; i++) { //printf("segment %d: %d %d -- %d %d\n", i, tri2[2*i], tri2[2*i+1], tri2[2*i], tri2[2*i+1]); for (int j=0; j<2; j++) { int p = tri2[i][j]; if (segPerVertex2[p][0]==-1) segPerVertex2[p][0] = i; else segPerVertex2[p][1] = i; } } computeDiscreteTargetDirections(tri2, targetDirection, targetNormals); // /////////////////////////////////////////////////////////////////////// // Project the vertices of the target surface onto the domain surface // /////////////////////////////////////////////////////////////////////// const ctype eps = 1e-10; for (size_t i=0; i<psurface_.targetVertices.size(); i++) { ctype bestLocalPos = std::numeric_limits<ctype>::max(); // init to something int bestSegment = -1; ctype bestDist = std::numeric_limits<ctype>::max(); for (int j=0; j<psurface_.domainSegments.size(); j++) { const StaticVector<ctype,2>& p0 = psurface_.domainVertices[psurface_.domainSegments[j].points[0]]; const StaticVector<ctype,2>& p1 = psurface_.domainVertices[psurface_.domainSegments[j].points[1]]; const StaticVector<ctype,2>& n0 = domainNormals[psurface_.domainSegments[j].points[0]]; const StaticVector<ctype,2>& n1 = domainNormals[psurface_.domainSegments[j].points[1]]; ctype local; // the unknown... if (NormalProjector<ctype>::computeInverseNormalProjection(p0, p1, n0, n1, psurface_.targetVertices[i], local)) { // We want that the line from the domain surface to its projection // approaches the target surface from the front side, i.e., it should // not pass through the body represented by the target surface. // We do a simplified test by comparing the connecting segment // with the normal at the target surface and the normal at the // domain surface /** \todo Rewrite this once we have expression templates */ StaticVector<ctype,2> base; StaticVector<ctype, 2> baseNormal; StaticVector<ctype, 2> segment; for (int k=0; k<2; k++) { base[k] = (1-local)*p0[k] + local*p1[k]; baseNormal[k] = (1-local)*n0[k] + local*n1[k]; segment[k] = psurface_.targetVertices[i][k] - base[k]; } ctype distance = segment.length2(); if (segment.dot(targetNormals[i]) > -0.0001 && segment.dot(baseNormal) > -0.0001 && distance > 1e-8) { //printf("aborting %g %g %g\n", segment * targetNormals[i], segment * baseNormal, distance); continue; } // There may be several inverse orthogonal projections. // We want the shortest one. if (distance < bestDist) { bestDist = distance; bestLocalPos = local; bestSegment = j; } } } // ///////////////////////////////////////////// // We have found a valid projection // ///////////////////////////////////////////// if (bestSegment != -1) { typename PSurface<1,ctype>::DomainSegment& bS = psurface_.domainSegments[bestSegment]; if (bestLocalPos < eps) { // Insert as new first element bS.nodes.insert(bS.nodes.begin(), typename PSurface<1,ctype>::Node(0, 1, true, true, segPerVertex2[i][0], segPerVertex2[i][1])); // Look for left neighbor segment if (psurface_.domainSegments[bestSegment].neighbor[0] != -1) { psurface_.domainSegments[psurface_.domainSegments[bestSegment].neighbor[0]].nodes.push_back( typename PSurface<1,ctype>::Node(1, 0, true, true, segPerVertex2[i][0], segPerVertex2[i][1]) ); } } else if (bestLocalPos > 1-eps) { typename PSurface<1,ctype>::Node newNode(1, 0, true, true, segPerVertex2[i][0], segPerVertex2[i][1]); bS.nodes.push_back(newNode); // Look for right neighbor segment if (psurface_.domainSegments[bestSegment].neighbor[1] != -1) { typename PSurface<1,ctype>::DomainSegment& rightNeighborSegment = psurface_.domainSegments[psurface_.domainSegments[bestSegment].neighbor[1]]; rightNeighborSegment.nodes.insert(rightNeighborSegment.nodes.begin(), typename PSurface<1,ctype>::Node(0, 1, true, true, segPerVertex2[i][0], segPerVertex2[i][1])); } } else { int nNodes = bS.nodes.size(); bS.nodes.resize(nNodes+1); int j=nNodes-1; for (; j>=0; j--) { if (bS.nodes[j].domainLocalPosition > bestLocalPos) bS.nodes[j+1] = bS.nodes[j]; else break; } bS.nodes[j+1] = typename PSurface<1,ctype>::Node(bestLocalPos, 0, false, true, segPerVertex2[i][0], segPerVertex2[i][1]); } } } // ////////////////////////////////////////////////////////////////////// // Insert missing nodes that belong to vertices of the domain segment // ////////////////////////////////////////////////////////////////////// for (int i=0; i<psurface_.domainSegments.size(); i++) { typename PSurface<1,ctype>::DomainSegment& cS = psurface_.domainSegments[i]; // Insert node belonging to domain vertex to the segment to the left of the vertex if (cS.nodes.size()==0 || !cS.nodes[0].isNodeOnVertex || (cS.nodes.size()==1 && cS.nodes[0].isNodeOnVertex && cS.nodes[0].domainLocalPosition > 1-eps)) { ctype rangeLocalPosition; int rangeSegment; if (NormalProjector<ctype>::normalProjection(psurface_.domainVertices[cS.points[0]], domainNormals[cS.points[0]], rangeSegment, rangeLocalPosition, tri2, coords2)) { typename PSurface<1,ctype>::Node newNode(0, rangeLocalPosition, true, false, rangeSegment, rangeSegment); cS.nodes.insert(cS.nodes.begin(), newNode); } } // Insert node belonging to domain vertex to the segment to the right of the vertex if (cS.nodes.size()==0 || !cS.nodes.back().isNodeOnVertex || (cS.nodes.size()==1 && cS.nodes[0].isNodeOnVertex && cS.nodes[0].domainLocalPosition < eps)) { ctype rangeLocalPosition; int rangeSegment; if (NormalProjector<ctype>::normalProjection(psurface_.domainVertices[cS.points[1]], domainNormals[cS.points[1]], rangeSegment, rangeLocalPosition, tri2, coords2)) { typename PSurface<1,ctype>::Node newNode(1, rangeLocalPosition, true, false, rangeSegment, rangeSegment); cS.nodes.push_back(newNode); } } } #if 0 for (int i=0; i<psurface_.domainSegments.size(); i++) { printf(" --- segment %d --- (%d --> %d)\n", i, psurface_.domainSegments[i].points[0],psurface_.domainSegments[i].points[1]); for (int j=0; j<psurface_.domainSegments[i].nodes.size(); j++) std::cout << psurface_.domainSegments[i].nodes[j]; std::cout << std::endl; } #endif // ///////////////////////////////////////////////////// // Insert edges // ///////////////////////////////////////////////////// /** \todo Only works if the relevant domain is a single connected component */ for (int i=0; i<psurface_.domainSegments.size(); i++) { std::vector<typename PSurface<1,ctype>::Node>& nodes = psurface_.domainSegments[i].nodes; //////////////////////////////// for (int j=0; j<int(nodes.size())-1; j++) { if (nodes[j].rangeSegments[0] == nodes[j+1].rangeSegments[0]) nodes[j].rightRangeSegment = nodes[j].rangeSegments[0]; else if (nodes[j].rangeSegments[0] == nodes[j+1].rangeSegments[1]) nodes[j].rightRangeSegment = nodes[j].rangeSegments[0]; else if (nodes[j].rangeSegments[1] == nodes[j+1].rangeSegments[0]) nodes[j].rightRangeSegment = nodes[j].rangeSegments[1]; else if (nodes[j].rangeSegments[1] == nodes[j+1].rangeSegments[1]) nodes[j].rightRangeSegment = nodes[j].rangeSegments[1]; else throw(std::runtime_error("Segment of the PSurface<1> data structure is inconsistent!")); if (nodes[j].rightRangeSegment == -1) throw(std::runtime_error("Segment of the PSurface<1> data structure is inconsistent!")); } } }
ctype CircularPatch<ctype>::distanceTo(const StaticVector<ctype,3> &p) const { int i, j; ctype bestDist = std::numeric_limits<ctype>::max(); // check point against triangles for (j=0; j<size(); j++){ const DomainTriangle<ctype>& cT = par->triangles(triangles[j]); StaticVector<ctype,3> triPoints[3]; triPoints[0] = par->vertices(cT.vertices[0]); triPoints[1] = par->vertices(cT.vertices[1]); triPoints[2] = par->vertices(cT.vertices[2]); // local base StaticVector<ctype,3> a = triPoints[1] - triPoints[0]; StaticVector<ctype,3> b = triPoints[2] - triPoints[0]; StaticVector<ctype,3> c = a.cross(b); c.normalize(); StaticVector<ctype,3> x = p - triPoints[0]; // write x in the new base (Cramer's rule) StaticMatrix<ctype,3> numerator(a, b, c); StaticMatrix<ctype,3> alphaMat(x, b, c); StaticMatrix<ctype,3> betaMat(a, x, c); StaticMatrix<ctype,3> gammaMat(a, b, x); ctype alpha = alphaMat.det()/numerator.det(); ctype beta = betaMat.det()/numerator.det(); ctype gamma = gammaMat.det()/numerator.det(); // check whether orthogonal projection onto the ab plane is in triangle bool isIn = alpha>=0 && beta>=0 && (1-alpha-beta)>=0; if (isIn && fabs(gamma)<bestDist){ // printf("a(%1.2f %1.2f %1.2f) b(%1.2f %1.2f %1.2f) c(%1.2f %1.2f %1.2f) x(%1.2f %1.2f %1.2f)\n", // a.x, a.y, a.z, b.x, b.y, b.z, c.x, c.y, c.z, x.x, x.y, x.z); // printf("tri: %d, alpha = %f, beta = %f, gamma = %f\n", j, alpha, beta, gamma); bestDist = fabs(gamma); } } // check point against edges for (i=0; i<size(); i++){ for (j=0; j<3; j++){ const DomainTriangle<ctype>& cT = par->triangles(triangles[i]); StaticVector<ctype,3> from = par->vertices(cT.vertices[j]); StaticVector<ctype,3> to = par->vertices(cT.vertices[(j+1)%3]); StaticVector<ctype,3> edge = to - from; ctype projectLength = edge.dot(p - from)/edge.length(); StaticVector<ctype,3> projection = edge/edge.length() * projectLength; ctype orthoDist = ((p-from) - projection).length(); if (projectLength>=0 && projectLength<=edge.length() && orthoDist<bestDist) bestDist = orthoDist; } } // check point against vertices for (i=0; i<size(); i++){ for (j=0; j<3; j++){ ctype dist = (p - par->vertices(par->triangles(triangles[i]).vertices[j])).length(); if (dist < bestDist){ bestDist = dist; } } } return bestDist; }
static void FindHubAreas() { if (!hubAreas.empty()) return; AiAasWorld *aasWorld = AiAasWorld::Instance(); if (!aasWorld->IsLoaded()) return; // Select not more than hubAreas.capacity() grounded areas that have highest connectivity to other areas. struct AreaAndReachCount { int area, reachCount; AreaAndReachCount(int area_, int reachCount_): area(area_), reachCount(reachCount_) {} // Ensure that area with lowest reachCount will be evicted in pop_heap(), so use > bool operator<(const AreaAndReachCount &that) const { return reachCount > that.reachCount; } }; StaticVector<AreaAndReachCount, hubAreas.capacity() + 1> bestAreasHeap; for (int i = 1; i < aasWorld->NumAreas(); ++i) { const auto &areaSettings = aasWorld->AreaSettings()[i]; if (!(areaSettings.areaflags & AREA_GROUNDED)) continue; if (areaSettings.areaflags & AREA_DISABLED) continue; if (areaSettings.contents & (AREACONTENTS_DONOTENTER|AREACONTENTS_LAVA|AREACONTENTS_SLIME|AREACONTENTS_WATER)) continue; // Reject degenerate areas, pass only relatively large areas const auto &area = aasWorld->Areas()[i]; if (area.maxs[0] - area.mins[0] < 128.0f) continue; if (area.maxs[1] - area.mins[1] < 128.0f) continue; // Count as useful only several kinds of reachabilities int usefulReachCount = 0; int reachNum = areaSettings.firstreachablearea; int lastReachNum = areaSettings.firstreachablearea + areaSettings.numreachableareas - 1; while (reachNum <= lastReachNum) { const auto &reach = aasWorld->Reachabilities()[reachNum]; if (reach.traveltype == TRAVEL_WALK || reach.traveltype == TRAVEL_WALKOFFLEDGE) usefulReachCount++; ++reachNum; } // Reject early to avoid more expensive call to push_heap() if (!usefulReachCount) continue; bestAreasHeap.push_back(AreaAndReachCount(i, usefulReachCount)); std::push_heap(bestAreasHeap.begin(), bestAreasHeap.end()); // bestAreasHeap size should be always less than its capacity: // 1) to ensure that there is a free room for next area; // 2) to ensure that hubAreas capacity will not be exceeded. if (bestAreasHeap.size() == bestAreasHeap.capacity()) { std::pop_heap(bestAreasHeap.begin(), bestAreasHeap.end()); bestAreasHeap.pop_back(); } } static_assert(bestAreasHeap.capacity() == hubAreas.capacity() + 1, ""); for (const auto &areaAndReachCount: bestAreasHeap) hubAreas.push_back(areaAndReachCount.area); }
/// gives the surface area ctype area(int tri) const { StaticVector<ctype,3> a = vertices(triangles(tri).vertices[1]) - vertices(triangles(tri).vertices[0]); StaticVector<ctype,3> b = vertices(triangles(tri).vertices[2]) - vertices(triangles(tri).vertices[0]); return fabs(0.5 * (a.cross(b)).length()); }
void Bot::RegisterVisibleEnemies() { if(G_ISGHOSTING(self) || GS_MatchState() == MATCH_STATE_COUNTDOWN || GS_ShootingDisabled()) return; CheckIsInThinkFrame(__FUNCTION__); // Compute look dir before loop vec3_t lookDir; AngleVectors(self->s.angles, lookDir, nullptr, nullptr); float fov = 110.0f + 69.0f * Skill(); float dotFactor = cosf((float)DEG2RAD(fov / 2)); struct EntAndDistance { int entNum; float distance; EntAndDistance(int entNum_, float distance_): entNum(entNum_), distance(distance_) {} bool operator<(const EntAndDistance &that) const { return distance < that.distance; } }; // Do not call inPVS() and G_Visible() for potential targets inside a loop for all clients. // In worst case when all bots may see each other we get N^2 traces and PVS tests // First, select all candidate targets along with distance to a bot. // Then choose not more than BotBrain::maxTrackedEnemies nearest enemies for calling OnEnemyViewed() // It may cause data loss (far enemies may have higher logical priority), // but in a common good case (when there are few visible enemies) it preserves data, // and in the worst case mentioned above it does not act weird from player POV and prevents server hang up. // Note: non-client entities also may be candidate targets. StaticVector<EntAndDistance, MAX_EDICTS> candidateTargets; for (int i = 1; i < game.numentities; ++i) { edict_t *ent = game.edicts + i; if (botBrain.MayNotBeFeasibleEnemy(ent)) continue; // Reject targets quickly by fov Vec3 toTarget(ent->s.origin); toTarget -= self->s.origin; float squareDistance = toTarget.SquaredLength(); if (squareDistance < 1) continue; float invDistance = Q_RSqrt(squareDistance); toTarget *= invDistance; if (toTarget.Dot(lookDir) < dotFactor) continue; // It seams to be more instruction cache-friendly to just add an entity to a plain array // and sort it once after the loop instead of pushing an entity in a heap on each iteration candidateTargets.emplace_back(EntAndDistance(ENTNUM(ent), 1.0f / invDistance)); } std::sort(candidateTargets.begin(), candidateTargets.end()); // Select inPVS/visible targets first to aid instruction cache, do not call callbacks in loop StaticVector<edict_t *, MAX_CLIENTS> targetsInPVS; StaticVector<edict_t *, MAX_CLIENTS> visibleTargets; static_assert(AiBaseEnemyPool::MAX_TRACKED_ENEMIES <= MAX_CLIENTS, "targetsInPVS capacity may be exceeded"); for (int i = 0, end = std::min(candidateTargets.size(), botBrain.MaxTrackedEnemies()); i < end; ++i) { edict_t *ent = game.edicts + candidateTargets[i].entNum; if (trap_inPVS(self->s.origin, ent->s.origin)) targetsInPVS.push_back(ent); } for (auto ent: targetsInPVS) if (G_Visible(self, ent)) visibleTargets.push_back(ent); // Call bot brain callbacks on visible targets for (auto ent: visibleTargets) botBrain.OnEnemyViewed(ent); botBrain.AfterAllEnemiesViewed(); CheckAlertSpots(visibleTargets); }
SelectedNavEntity BotItemsSelector::SuggestGoalNavEntity( const SelectedNavEntity &currSelectedNavEntity ) { UpdateInternalItemAndGoalWeights(); StaticVector<NavEntityAndWeight, MAX_NAVENTS> rawWeightCandidates; const auto levelTime = level.time; auto *navEntitiesRegistry = NavEntitiesRegistry::Instance(); for( auto it = navEntitiesRegistry->begin(), end = navEntitiesRegistry->end(); it != end; ++it ) { const NavEntity *navEnt = *it; if( navEnt->IsDisabled() ) { continue; } // We cannot just set a zero internal weight for a temporarily disabled nav entity // (it might be overridden by an external weight, and we should not modify external weights // as script users expect them remaining the same unless explicitly changed via script API) if( disabledForSelectionUntil[navEnt->Id()] >= levelTime ) { continue; } // Since movable goals have been introduced (and clients qualify as movable goals), prevent picking itself as a goal. if( navEnt->Id() == ENTNUM( self ) ) { continue; } if( navEnt->Item() && !G_Gametype_CanPickUpItem( navEnt->Item() ) ) { continue; } // Reject an entity quickly if it looks like blocked by an enemy that is close to the entity. // Note than passing this test does not guarantee that entire path to the entity is not blocked by enemies. if( self->ai->botRef->routeCache->AreaDisabled( navEnt->AasAreaNum() ) ) { continue; } // This is a coarse and cheap test, helps to reject recently picked armors and powerups unsigned spawnTime = navEnt->SpawnTime(); // A feasible spawn time (non-zero) always >= level.time. if( !spawnTime || level.time - spawnTime > 15000 ) { continue; } float weight = GetEntityWeight( navEnt->Id() ); if( weight > 0 ) { rawWeightCandidates.push_back( NavEntityAndWeight( navEnt, weight ) ); } } // Sort all pre-selected candidates by their raw weights std::sort( rawWeightCandidates.begin(), rawWeightCandidates.end() ); // Try checking whether the bot is in some floor cluster to give a greater weight for items in the same cluster int currFloorClusterNum = 0; const auto &entityPhysicsState = self->ai->botRef->EntityPhysicsState(); const auto *aasFloorClusterNums = AiAasWorld::Instance()->AreaFloorClusterNums(); if( aasFloorClusterNums[entityPhysicsState->CurrAasAreaNum()] ) { currFloorClusterNum = aasFloorClusterNums[entityPhysicsState->CurrAasAreaNum()]; } else if( aasFloorClusterNums[entityPhysicsState->DroppedToFloorAasAreaNum()] ) { currFloorClusterNum = aasFloorClusterNums[entityPhysicsState->DroppedToFloorAasAreaNum()]; } const NavEntity *currGoalNavEntity = currSelectedNavEntity.navEntity; float currGoalEntWeight = 0.0f; float currGoalEntCost = 0.0f; const NavEntity *bestNavEnt = nullptr; float bestWeight = 0.000001f; float bestNavEntCost = 0.0f; // Test not more than 16 best pre-selected by raw weight candidates. // (We try to avoid too many expensive FindTravelTimeToGoalArea() calls, // thats why we start from the best item to avoid wasting these calls for low-priority items) for( unsigned i = 0, end = std::min( rawWeightCandidates.size(), 16U ); i < end; ++i ) { const NavEntity *navEnt = rawWeightCandidates[i].goal; float weight = rawWeightCandidates[i].weight; unsigned moveDuration = 1; unsigned waitDuration = 1; if( self->ai->botRef->CurrAreaNum() != navEnt->AasAreaNum() ) { // We ignore cost of traveling in goal area, since: // 1) to estimate it we have to retrieve reachability to goal area from last area before the goal area // 2) it is relative low compared to overall travel cost, and movement in areas is cheap anyway moveDuration = self->ai->botRef->botBrain.FindTravelTimeToGoalArea( navEnt->AasAreaNum() ) * 10U; // AAS functions return 0 as a "none" value, 1 as a lowest feasible value if( !moveDuration ) { continue; } if( navEnt->IsDroppedEntity() ) { // Do not pick an entity that is likely to dispose before it may be reached if( navEnt->Timeout() <= level.time + moveDuration ) { continue; } } } unsigned spawnTime = navEnt->SpawnTime(); // The entity is not spawned and respawn time is unknown if( !spawnTime ) { continue; } // Entity origin may be reached at this time unsigned reachTime = level.time + moveDuration; if( reachTime < spawnTime ) { waitDuration = spawnTime - reachTime; } if( waitDuration > navEnt->MaxWaitDuration() ) { continue; } float moveCost = MOVE_TIME_WEIGHT * moveDuration * navEnt->CostInfluence(); float cost = 0.0001f + moveCost + WAIT_TIME_WEIGHT * waitDuration * navEnt->CostInfluence(); weight = ( 1000 * weight ) / cost; // If the bot is inside a floor cluster if( currFloorClusterNum ) { // Greatly increase weight for items in the same floor cluster if( currFloorClusterNum == aasFloorClusterNums[navEnt->AasAreaNum()] ) { weight *= 4.0f; } } // Store current weight of the current goal entity if( currGoalNavEntity == navEnt ) { currGoalEntWeight = weight; // Waiting time is handled by the planner for wait actions separately. currGoalEntCost = moveCost; } if( weight > bestWeight ) { bestNavEnt = navEnt; bestWeight = weight; // Waiting time is handled by the planner for wait actions separately. bestNavEntCost = moveCost; } } if( !bestNavEnt ) { Debug( "Can't find a feasible long-term goal nav. entity\n" ); return SelectedNavEntity( nullptr, std::numeric_limits<float>::max(), 0.0f, level.time + 200 ); } // If it is time to pick a new goal (not just re-evaluate current one), do not be too sticky to the current goal const float currToBestWeightThreshold = currGoalNavEntity != nullptr ? 0.6f : 0.8f; if( currGoalNavEntity && currGoalNavEntity == bestNavEnt ) { constexpr const char *format = "current goal entity %s is kept as still having best weight %.3f\n"; Debug( format, currGoalNavEntity->Name(), bestWeight ); return SelectedNavEntity( bestNavEnt, bestNavEntCost, GetGoalWeight( bestNavEnt->Id() ), level.time + 4000 ); } else if( currGoalEntWeight > 0 && currGoalEntWeight / bestWeight > currToBestWeightThreshold ) { constexpr const char *format = "current goal entity %s is kept as having weight %.3f good enough to not consider picking another one\n"; // If currGoalEntWeight > 0, currLongTermGoalEnt is guaranteed to be non-null Debug( format, currGoalNavEntity->Name(), currGoalEntWeight ); return SelectedNavEntity( currGoalNavEntity, currGoalEntCost, GetGoalWeight( bestNavEnt->Id() ), level.time + 2500 ); } else { if( currGoalNavEntity ) { const char *format = "suggested %s weighted %.3f as a long-term goal instead of %s weighted now as %.3f\n"; Debug( format, bestNavEnt->Name(), bestWeight, currGoalNavEntity->Name(), currGoalEntWeight ); } else { Debug( "suggested %s weighted %.3f as a new long-term goal\n", bestNavEnt->Name(), bestWeight ); } return SelectedNavEntity( bestNavEnt, bestNavEntCost, GetGoalWeight( bestNavEnt->Id() ), level.time + 2500 ); } }