public InclineMeshData BuildMeshData(MapMetaData mapMetaData) { DateTime startTime = System.DateTime.Now; Debug.Log("started making incline mesh at " + startTime); int numInclineNodesX = Mathf.CeilToInt(mapMetaData.mapTerrainDataCells.GetLength(0) / (float)downsampleFactor); int numInclineNodesZ = Mathf.CeilToInt(mapMetaData.mapTerrainDataCells.GetLength(1) / (float)downsampleFactor); InclineMeshData meshData = new InclineMeshData(numInclineNodesX, numInclineNodesZ); meshData.downsampleFactor = downsampleFactor; meshData.mapMetaData = mapMetaData; for (int x = 0; x < numInclineNodesX; ++x) { for (int z = 0; z < numInclineNodesZ; ++z) { InclineIndexPoint inclinePoint = new InclineIndexPoint(x, z); processNode(meshData, mapMetaData, inclinePoint); } } DateTime endTime = System.DateTime.Now; TimeSpan elapsed = endTime - startTime; Debug.Log("finished making incline mesh at " + endTime); Debug.Log("Elapsed Time: " + elapsed); return(meshData); }
/// <summary> /// Finds a path from any of start to goal, such that the path has no links that are steeper up than maxIncline /// nor steeper down than maxDecline. /// </summary> /// <returns>The path.</returns> /// <param name="startPoint">PointWithDistance of point to start from</param> /// <param name="goalPoint">map indices of point to go to</param> List <PointWithDistance> FindPath(PointWithDistance startPoint, InclineIndexPoint goalPoint, float maxIncline, float maxDecline) { List <PointWithDistance> startList = new List <PointWithDistance>(); startList.Add(startPoint); return(FindPath(startList, goalPoint, maxIncline, maxDecline)); }
float getDistanceBetweenTwoInclinePoints(InclineMeshData meshData, MapMetaData mapMetaData) { InclineIndexPoint inclinePoint1 = new InclineIndexPoint(1, 1); InclineIndexPoint inclinePoint2 = new InclineIndexPoint(1, 2); Point mapPoint1 = meshData.InclineIndicesToMapIndices(inclinePoint1); Point mapPoint2 = meshData.InclineIndicesToMapIndices(inclinePoint2); Vector3 worldVec1 = mapMetaData.getWorldPos(mapPoint1); Vector3 worldVec2 = mapMetaData.getWorldPos(mapPoint2); return((worldVec2 - worldVec1).magnitude); }
public Vector3 GetDestination(List <PointWithDistance> startPointList, Vector3 goal, float movementBudget, float maxSlope, AbstractActor unit, bool shouldSprint, List <AbstractActor> lanceUnits, PathNodeGrid pathGrid, out Vector3 lookAtPoint) { if (shouldSprint && unit.CanSprint) { unit.Pathing.SetSprinting(); } else { unit.Pathing.SetWalking(); } InclineIndexPoint goalPoint = WorldPointToInclineIndices(goal); List <PointWithDistance> pathLatticePoints = FindPath(startPointList, goalPoint, maxSlope, maxSlope); if ((pathLatticePoints == null) || (pathLatticePoints.Count == 0)) { // can't find a path(!) // set the lookAtPoint (out) variable to a point further out in the direction of start->goal lookAtPoint = goal * 2.0f - unit.CurrentPosition; return(goal); } List <PathNode> pathNodes = new List <PathNode>(); PathNode walkNode = pathLatticePoints[0].pathNode; while (walkNode != null) { pathNodes.Insert(0, walkNode); walkNode = walkNode.Parent; } List <Vector3> longRangePathWorldPoints = pathLatticePoints.ConvertAll(x => InclineIndicesToWorldPoint(x.point)); List <Vector3> pathWorldPoints = pathNodes.ConvertAll(x => x.Position); pathWorldPoints.AddRange(longRangePathWorldPoints); Vector3 destination; if (longRangePathWorldPoints.Count == 1) { destination = longRangePathWorldPoints[0]; if ((goal - destination).magnitude < 1.0f) { lookAtPoint = destination * 2.0f - unit.CurrentPosition; } else { lookAtPoint = goal; } return(destination); } // Debug Draw Path float scale = 1.0f; for (int pathIndex = 0; pathIndex < pathWorldPoints.Count - 1; ++pathIndex) { int nextIndex = pathIndex + 1; Vector3 p0 = pathWorldPoints[pathIndex]; Vector3 p1 = pathWorldPoints[nextIndex]; scale = (p1 - p0).magnitude; for (int dx = -1; dx <= 1; ++dx) { for (int dz = -1; dz <= 1; ++dz) { Vector3 offset = new Vector3(dx * scale * 0.1f, 0, dz * scale * 0.1f); Debug.DrawLine(p0 + offset, p1 + offset, Color.red, 30.0f); } } } drawCircle(unit.CurrentPosition, scale * 0.4f, Color.cyan, 30.0f); drawCircle(goal, scale * 0.4f, Color.magenta, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.3f, Color.red, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.35f, Color.white, 30.0f); drawCircle(pathWorldPoints[0], scale * 0.4f, Color.blue, 30.0f); // TODO/dlecompte push this up to filter our selection of path nodes, above. float spread = unit.BehaviorTree.GetBehaviorVariableValue( unit.Combat.TurnDirector.IsInterleaved ? BehaviorVariableName.Float_InterleavedLanceSpreadDistance : BehaviorVariableName.Float_NonInterleavedLanceSpreadDistance).FloatVal; drawCircle(unit.CurrentPosition, spread, Color.green, 30.0f); Debug.Assert(pathWorldPoints.Count >= 2); // already tested this, above. float accumDistance = 0.0f; // if we can get to the goal, look at a point further away in the direction of start -> goal lookAtPoint = 2 * goal - unit.CurrentPosition; Vector3 clipPoint = goal; // Now we walk along the pathWorldPoints, snapping them to grid points. // We want to take the point furthest along the path that doesn't alias to an earlier point. // Also, we want to make sure that it's the furthest point within our movement budget and within our lance spread. // MUST BE : within movement budget, not an alias to an earlier point // IF any points exist inside lance spread, pick last point inside lance spread, else last point. List <Vector3> dedupedSnappedPointsList = new List <Vector3>(); List <Vector3> snappedPointsInOrder = new List <Vector3>(); List <Vector3> nextPointsInOrder = new List <Vector3>(); List <bool> pointsInSpreadRangeList = new List <bool>(); List <bool> isNewGroundList = new List <bool>(); float ROUNDING_RADIUS = 1.0f; bool wasEverInside = false; for (int pointIndex = 0; (pointIndex < pathWorldPoints.Count) && (accumDistance <= movementBudget); ++pointIndex) { Vector3 thisPoint = pathWorldPoints[pointIndex]; Vector3 nextPoint = goal; if (pointIndex + 1 < pathWorldPoints.Count) { nextPoint = pathWorldPoints[pointIndex + 1]; } Vector3 thisSnappedPoint = unit.Combat.HexGrid.GetClosestPointOnGrid(thisPoint); snappedPointsInOrder.Add(thisSnappedPoint); nextPointsInOrder.Add(nextPoint); bool pointIsInsideSpread = AIUtil.IsPositionWithinLanceSpread(unit, lanceUnits, thisSnappedPoint); wasEverInside |= pointIsInsideSpread; pointsInSpreadRangeList.Add(pointIsInsideSpread); bool alreadyVisited = isPointInList(thisSnappedPoint, dedupedSnappedPointsList, ROUNDING_RADIUS); isNewGroundList.Add(!alreadyVisited); if (!alreadyVisited) { dedupedSnappedPointsList.Add(thisSnappedPoint); } if (pointIndex + 1 < pathWorldPoints.Count) { accumDistance += (nextPoint - thisPoint).magnitude; } } if (wasEverInside) { // find the last point of our list that is "new ground" and inside for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { if (isNewGroundList[i] && pointsInSpreadRangeList[i]) { clipPoint = snappedPointsInOrder[i]; lookAtPoint = nextPointsInOrder[i]; break; } } } if ((!wasEverInside) || ((clipPoint - unit.CurrentPosition).magnitude < 1.0f)) { // find the last point of our list that is "new ground" for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { if (isNewGroundList[i]) { clipPoint = snappedPointsInOrder[i]; lookAtPoint = nextPointsInOrder[i]; break; } } } for (int i = snappedPointsInOrder.Count - 1; i >= 0; --i) { drawCircle(snappedPointsInOrder[i], scale * 0.2f, new Color(0.5f, 0.5f, 0.0f), 30.0f); if (pointsInSpreadRangeList[i]) { drawCircle(snappedPointsInOrder[i], scale * 0.25f, new Color(0.0f, 1.0f, 0.0f), 30.0f); } } //float lookAngle = PathingUtil.GetAngle(lookAtPoint - clipPoint); //Vector3 resultPos = clipPoint; drawCircle(clipPoint, scale * 0.4f, new Color(1.0f, 0.5f, 0.0f), 30.0f); return(clipPoint); }
/// <summary> /// Finds a path from any of the points in startPointList to goal, such that the path has no links that are steeper up than maxIncline /// nor steeper down than maxDecline. /// </summary> /// <returns>The path.</returns> /// <param name="startPointList">PointWithDistance structs for starting area</param> /// <param name="goalPoint">map indices of point to go to</param> List <PointWithDistance> FindPath(List <PointWithDistance> startPointList, InclineIndexPoint goalPoint, float maxIncline, float maxDecline) { List <PointWithDistance> path = new List <PointWithDistance>(); HeapQueue <PointWithDistance> openHeap = new HeapQueue <PointWithDistance>(); Dictionary <InclineIndexPoint, float> bestDistanceDict = new Dictionary <InclineIndexPoint, float>(); Dictionary <InclineIndexPoint, PointWithDistance> bestPrevPoint = new Dictionary <InclineIndexPoint, PointWithDistance>(); Point goalMapPoint = InclineIndicesToMapIndices(goalPoint); Vector3 worldGoalPoint = mapMetaData.getWorldPos(goalMapPoint); for (int startIndex = 0; startIndex < startPointList.Count; ++startIndex) { PointWithDistance pwd = startPointList[startIndex]; openHeap.Push(pwd); bestDistanceDict[pwd.point] = pwd.distance; bestPrevPoint[pwd.point] = null; } float bestPathLength = -1; PointWithDistance bestGoalPoint = new PointWithDistance(new InclineIndexPoint(-1024, -1024), float.MaxValue, float.MaxValue); while (!openHeap.IsEmpty()) { PointWithDistance ptWithDist = openHeap.PopMinimum(); if ((bestPathLength > 0) && ((ptWithDist.estimatedTotalDistance > bestPathLength) || TAKE_FIRST_PATH)) { break; } int[] xOffsets = { 1, 0, -1, 0 }; int[] zOffsets = { 0, 1, 0, -1 }; InclineMeshNode node = nodes[ptWithDist.point.X, ptWithDist.point.Z]; Vector3 worldNodePoint = InclineIndicesToWorldPoint(ptWithDist.point); for (int direction = 0; direction < 4; ++direction) { int dx = xOffsets[direction]; int dz = zOffsets[direction]; int nx = ptWithDist.point.X + dx; int nz = ptWithDist.point.Z + dz; InclineIndexPoint neighborPoint = new InclineIndexPoint(nx, nz); Point mapNeighborPoint = InclineIndicesToMapIndices(neighborPoint); if ((!mapMetaData.IsWithinBounds(mapNeighborPoint)) || (node.NeighborLinks[direction] == null)) { continue; } Vector3 worldNeighborPoint = InclineIndicesToWorldPoint(neighborPoint); for (int linkIndex = 0; linkIndex < node.NeighborLinks[direction].Count; ++linkIndex) { Debug.DrawLine(worldNodePoint, worldNeighborPoint, Color.yellow, 15.0f); InclineLinkData link = node.NeighborLinks[direction][linkIndex]; if ((link.declineAsFloat() > maxDecline) || (link.inclineAsFloat() > maxIncline)) { continue; } float linkDistance = (worldNeighborPoint - worldNodePoint).magnitude; float totalDistance = ptWithDist.distance + linkDistance; if ((bestPathLength >= 0) && (totalDistance >= bestPathLength)) { continue; } if ((!bestDistanceDict.ContainsKey(neighborPoint)) || (totalDistance < bestDistanceDict[neighborPoint])) { bestDistanceDict[neighborPoint] = totalDistance; bestPrevPoint[neighborPoint] = ptWithDist; float distanceToGoal = (worldNeighborPoint - worldGoalPoint).magnitude; if (neighborPoint.Equals(goalPoint)) { if ((bestPathLength < 0) || (totalDistance < bestPathLength)) { bestPathLength = totalDistance; bestGoalPoint = new PointWithDistance(neighborPoint, totalDistance, 0.0f); } } else { openHeap.Push(new PointWithDistance(neighborPoint, totalDistance, totalDistance + distanceToGoal)); } } break; } } } if (bestPathLength >= 0) { PointWithDistance p = bestGoalPoint; path.Add(p); while (bestPrevPoint.ContainsKey(p.point)) { PointWithDistance prevPoint = bestPrevPoint[p.point]; if ((prevPoint == null) || (path.Contains(prevPoint))) { break; } path.Insert(0, prevPoint); p = prevPoint; } } return(path); }
public PointWithDistance(InclineIndexPoint point, float distance, float estimatedTotalDistance) { this.point = point; this.distance = distance; this.estimatedTotalDistance = estimatedTotalDistance; }
public Vector3 InclineIndicesToWorldPoint(InclineIndexPoint inclineIndices) { Point mapIndices = InclineIndicesToMapIndices(inclineIndices); return(mapMetaData.getWorldPos(mapIndices)); }
public Point InclineIndicesToMapIndices(InclineIndexPoint inclineIndices) { return(new Point(inclineIndices.X * downsampleFactor, inclineIndices.Z * downsampleFactor)); }
void processNode(InclineMeshData meshData, MapMetaData mapMetaData, InclineIndexPoint inclinePoint) { Point startPointMapIndices = meshData.InclineIndicesToMapIndices(inclinePoint); if (!mapMetaData.IsWithinBounds(startPointMapIndices)) { return; } Dictionary <Point, List <InclineLinkData> > bestPaths = new Dictionary <Point, List <InclineLinkData> >(); List <InclineLinkData> starterList = new List <InclineLinkData>(); starterList.Add(new InclineLinkData()); bestPaths[startPointMapIndices] = starterList; List <Point> openPoints = new List <Point>(); openPoints.Add(startPointMapIndices); float maximumPathDistance = overshootMultiplier * getDistanceBetweenTwoInclinePoints(meshData, mapMetaData); while (openPoints.Count > 0) { Point p = openPoints[0]; openPoints.RemoveAt(0); //Debug.LogFormat("dequeueing {0} {1}", p.X, p.Z); for (int d = 0; d < 4; ++d) { int dx = 0; int dz = 0; switch (d) { case 0: dx = 1; dz = 0; break; case 1: dx = 0; dz = 1; break; case 2: dx = -1; dz = 0; break; case 3: dx = 0; dz = -1; break; default: Debug.LogError("invalid direction: " + d); continue; } Point newPoint = new Point(p.X + dx, p.Z + dz); if ((!mapMetaData.IsWithinBounds(newPoint)) || (!mapMetaData.IsWithinBounds(p))) { continue; } float incline, decline, distance; getIncrementalInclines(mapMetaData, p, newPoint, out incline, out decline, out distance); bool pointIsDirty = false; for (int pathIndex = 0; pathIndex < bestPaths[p].Count; pathIndex++) { InclineLinkData pathSoFar = bestPaths[p][pathIndex]; float oldFloatIncline = pathSoFar.inclineAsFloat(); float oldFloatDecline = pathSoFar.declineAsFloat(); float oldFloatDistance = pathSoFar.distanceAsFloat(); float newFloatIncline = Mathf.Max(incline, oldFloatIncline); float newFloatDecline = Mathf.Max(decline, oldFloatDecline); float newFloatDistance = oldFloatDistance + distance; // if the distance is too far, also stop recursing. if (newFloatDistance > maximumPathDistance) { continue; } InclineLinkData newPathLinkData = new InclineLinkData(newFloatIncline, newFloatDecline, newFloatDistance); bool foundAnyBetter = false; int iAmBetterThanThisIndex = -1; if (bestPaths.ContainsKey(newPoint)) { for (int existingPathIndex = 0; existingPathIndex < bestPaths[newPoint].Count; ++existingPathIndex) { InclineLinkData existingPath = bestPaths[newPoint][existingPathIndex]; // If an existing path is as good or better than us, then stop recursing. if (existingPath.Equals(newPathLinkData) || existingPath.Dominates(newPathLinkData)) { foundAnyBetter = true; break; } if (newPathLinkData.Dominates(existingPath)) { iAmBetterThanThisIndex = existingPathIndex; break; } } } else { bestPaths[newPoint] = new List <InclineLinkData>(); } if (iAmBetterThanThisIndex >= 0) { // found a better path than an existing one, remove the old one and recompute. bestPaths[newPoint][iAmBetterThanThisIndex] = newPathLinkData; pointIsDirty = true; } else if (!foundAnyBetter) { // Otherwise, add this path to the list of paths, and recurse. bestPaths[newPoint].Add(newPathLinkData); pointIsDirty = true; } } if (pointIsDirty) { openPoints.Add(newPoint); } } } meshData.nodes[inclinePoint.X, inclinePoint.Z] = new InclineMeshNode(); // now, grab the links from our neighbors for (int d = 0; d < 4; ++d) { int dx = 0; int dz = 0; switch (d) { case 0: dx = 1; dz = 0; break; case 1: dx = 0; dz = 1; break; case 2: dx = -1; dz = 0; break; case 3: dx = 0; dz = -1; break; default: Debug.LogError("invalid direction: " + d); continue; } InclineIndexPoint neighborInclinePoint = new InclineIndexPoint(inclinePoint.X + dx, inclinePoint.Z + dz); Point neighborMapIndices = meshData.InclineIndicesToMapIndices(neighborInclinePoint); if (!mapMetaData.IsWithinBounds(neighborMapIndices)) { continue; } if (bestPaths.ContainsKey(neighborMapIndices)) { /* * foreach (InclineLinkData ild in bestPaths[neighborWorldPoint]) * { * //Debug.LogFormat("from {0} {1} in dir {2}: {3}/{4}/{5}", inclinePoint.X, inclinePoint.Z, d, ild.incline, ild.decline, ild.distance); * } */ meshData.nodes[inclinePoint.X, inclinePoint.Z].NeighborLinks[d] = bestPaths[neighborMapIndices]; // draw debug //Debug.DrawLine(meshData.InclineIndicesToWorldPoint(inclinePoint), meshData.mapMetaData.getWorldPos(neighborMapIndices), Color.white, 25.0f); } } }