Ticket #1756: perfpath6-quantumstate.diff

File perfpath6-quantumstate.diff, 264.3 KB (added by Jonathan Waller, 11 years ago)

Rough merge with svn 13111

  • source/tools/atlas/GameInterface/View.cpp

     
    254254        {
    255255            cmpPathfinder->SetDebugOverlay(true);
    256256            // Kind of a hack to make it update the terrain grid
    257             ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, fixed::Zero(), fixed::Zero() };
     257            PathGoal goal = { PathGoal::POINT, fixed::Zero(), fixed::Zero() };
    258258            ICmpPathfinder::pass_class_t passClass = cmpPathfinder->GetPassabilityClass(m_DisplayPassability);
    259             ICmpPathfinder::cost_class_t costClass = cmpPathfinder->GetCostClass("default");
    260             cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass, costClass);
     259            cmpPathfinder->SetDebugPath(fixed::Zero(), fixed::Zero(), goal, passClass);
    261260        }
    262261    }
    263262
  • source/simulation2/serialization/SerializeTemplates.h

     
    191191struct SerializeGoal
    192192{
    193193    template<typename S>
    194     void operator()(S& serialize, const char* UNUSED(name), ICmpPathfinder::Goal& value)
     194    void operator()(S& serialize, const char* UNUSED(name), PathGoal& value)
    195195    {
    196         SerializeU8_Enum<ICmpPathfinder::Goal::Type, ICmpPathfinder::Goal::SQUARE>()(serialize, "type", value.type);
     196        SerializeU8_Enum<PathGoal::Type, PathGoal::SQUARE>()(serialize, "type", value.type);
    197197        serialize.NumberFixed_Unbounded("goal x", value.x);
    198198        serialize.NumberFixed_Unbounded("goal z", value.z);
    199199        serialize.NumberFixed_Unbounded("goal u x", value.u.X);
  • source/simulation2/helpers/Render.h

     
    6464 * @param[in] floating If true, the line conforms to water as well.
    6565 * @param[in] heightOffset Height above terrain to offset the line.
    6666 */
    67 void ConstructLineOnGround(const CSimContext& context, const std::vector<float>& xz,
     67void ConstructLineOnGround(
     68        const CSimContext& context, const std::vector<float>& xz,
    6869        SOverlayLine& overlay,
    6970        bool floating, float heightOffset = 0.25f);
    7071
     
    7879 * @param[in] heightOffset Height above terrain to offset the circle.
    7980 * @param heightOffset The vertical offset to apply to points, to raise the line off the terrain a bit.
    8081 */
    81 void ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius,
     82void ConstructCircleOnGround(
     83        const CSimContext& context, float x, float z, float radius,
    8284        SOverlayLine& overlay,
    8385        bool floating, float heightOffset = 0.25f);
    8486
    8587/**
     88 * Constructs overlay line as an outlined circle sector (an arc with straight lines between the
     89 * endpoints and the circle's center), conforming to terrain.
     90 */
     91void ConstructClosedArcOnGround(
     92        const CSimContext& context, float x, float z, float radius,
     93        float start, float end,
     94        SOverlayLine& overlay,
     95        bool floating, float heightOffset = 0.25f);
     96
     97/**
    8698 * Constructs overlay line as rectangle with given center and dimensions, conforming to terrain.
    8799 *
    88100 * @param[in] x,z Coordinates of center of rectangle.
     
    92104 * @param[in] floating If true, the rectangle conforms to water as well.
    93105 * @param[in] heightOffset Height above terrain to offset the rectangle.
    94106 */
    95 void ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a,
     107void ConstructSquareOnGround(
     108        const CSimContext& context, float x, float z, float w, float h, float a,
    96109        SOverlayLine& overlay,
    97110        bool floating, float heightOffset = 0.25f);
    98111
  • source/simulation2/helpers/Render.cpp

     
    6767    }
    6868}
    6969
    70 void SimRender::ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius,
    71         SOverlayLine& overlay, bool floating, float heightOffset)
     70static void ConstructCircleOrClosedArc(
     71    const CSimContext& context, float x, float z, float radius,
     72    bool isCircle,
     73    float start, float end,
     74    SOverlayLine& overlay, bool floating, float heightOffset)
    7275{
    7376    overlay.m_Coords.clear();
    7477
     
    7982    float water = 0.f;
    8083    if (floating)
    8184    {
    82         CmpPtr<ICmpWaterManager> cmpWaterManager(context, SYSTEM_ENTITY);
    83         if (cmpWaterManager)
    84             water = cmpWaterManager->GetExactWaterLevel(x, z);
     85        CmpPtr<ICmpWaterManager> cmpWaterMan(context, SYSTEM_ENTITY);
     86        if (cmpWaterMan)
     87            water = cmpWaterMan->GetExactWaterLevel(x, z);
    8588    }
    8689
    8790    // Adapt the circle resolution to look reasonable for small and largeish radiuses
    88     size_t numPoints = clamp((size_t)(radius*4.0f), (size_t)12, (size_t)48);
     91    size_t numPoints = clamp((size_t)(radius*(end-start)), (size_t)12, (size_t)48);
    8992
    90     overlay.m_Coords.reserve((numPoints + 1) * 3);
     93    if (isCircle)
     94        overlay.m_Coords.reserve((numPoints + 1 + 2) * 3);
     95    else
     96        overlay.m_Coords.reserve((numPoints + 1) * 3);
    9197
     98    float cy;
     99    if (!isCircle)
     100    {
     101        // Start at the center point
     102        cy = std::max(water, cmpTerrain->GetExactGroundLevel(x, z)) + heightOffset;
     103        overlay.m_Coords.push_back(x);
     104        overlay.m_Coords.push_back(cy);
     105        overlay.m_Coords.push_back(z);
     106    }
     107
    92108    for (size_t i = 0; i <= numPoints; ++i) // use '<=' so it's a closed loop
    93109    {
    94         float a = (float)i * 2 * (float)M_PI / (float)numPoints;
    95         float px = x + radius * sinf(a);
    96         float pz = z + radius * cosf(a);
     110        float a = start + (float)i * (end - start) / (float)numPoints;
     111        float px = x + radius * cosf(a);
     112        float pz = z + radius * sinf(a);
    97113        float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset;
    98114        overlay.m_Coords.push_back(px);
    99115        overlay.m_Coords.push_back(py);
    100116        overlay.m_Coords.push_back(pz);
    101117    }
     118
     119    if (!isCircle)
     120    {
     121        // Return to the center point
     122        overlay.m_Coords.push_back(x);
     123        overlay.m_Coords.push_back(cy);
     124        overlay.m_Coords.push_back(z);
     125    }
    102126}
    103127
     128void SimRender::ConstructCircleOnGround(
     129    const CSimContext& context, float x, float z, float radius,
     130    SOverlayLine& overlay, bool floating, float heightOffset)
     131{
     132    ConstructCircleOrClosedArc(context, x, z, radius, true, 0.0f, 2.0f*(float)M_PI, overlay, floating, heightOffset);
     133}
     134
     135void SimRender::ConstructClosedArcOnGround(
     136    const CSimContext& context, float x, float z, float radius,
     137    float start, float end,
     138    SOverlayLine& overlay, bool floating, float heightOffset)
     139{
     140    ConstructCircleOrClosedArc(context, x, z, radius, false, start, end, overlay, floating, heightOffset);
     141}
     142
    104143// This method splits up a straight line into a number of line segments each having a length ~= TERRAIN_TILE_SIZE
    105144static void SplitLine(std::vector<std::pair<float, float> >& coords, float x1, float y1, float x2, float y2)
    106145{
  • source/simulation2/helpers/Rasterize.h

     
     1/* Copyright (C) 2012 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_HELPER_RASTERIZE
     19#define INCLUDED_HELPER_RASTERIZE
     20
     21/**
     22 * @file
     23 * Helper functions related to rasterizing geometric shapes to grids.
     24 */
     25
     26#include "simulation2/components/ICmpObstructionManager.h"
     27
     28namespace SimRasterize
     29{
     30
     31/**
     32 * Represents the set of cells (i,j) where i0 <= i < i1
     33 */
     34struct Span
     35{
     36    i16 i0;
     37    i16 i1;
     38    i16 j;
     39};
     40
     41typedef std::vector<Span> Spans;
     42
     43/**
     44 * Converts an ObstructionSquare @p shape (a rotated rectangle),
     45 * expanded by the given @p clearance,
     46 * into a list of spans of cells that are strictly inside the shape.
     47 */
     48void RasterizeRectWithClearance(Spans& spans,
     49    const ICmpObstructionManager::ObstructionSquare& shape,
     50    entity_pos_t clearance, entity_pos_t cellSize);
     51
     52}
     53
     54#endif // INCLUDED_HELPER_RASTERIZE
  • source/simulation2/helpers/Rasterize.cpp

     
     1/* Copyright (C) 2012 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#include "precompiled.h"
     19
     20#include "Rasterize.h"
     21
     22#include "simulation2/helpers/Geometry.h"
     23
     24void SimRasterize::RasterizeRectWithClearance(Spans& spans,
     25    const ICmpObstructionManager::ObstructionSquare& shape,
     26    entity_pos_t clearance, entity_pos_t cellSize)
     27{
     28    // Get the bounds of cells that might possibly be within the shape
     29    // (We'll then test each of those cells more precisely)
     30    CFixedVector2D halfSize(shape.hw + clearance, shape.hh + clearance);
     31    CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize);
     32    i16 i0 = ((shape.x - halfBound.X) / cellSize).ToInt_RoundToNegInfinity();
     33    i16 j0 = ((shape.z - halfBound.Y) / cellSize).ToInt_RoundToNegInfinity();
     34    i16 i1 = ((shape.x + halfBound.X) / cellSize).ToInt_RoundToInfinity();
     35    i16 j1 = ((shape.z + halfBound.Y) / cellSize).ToInt_RoundToInfinity();
     36
     37    if (j1 <= j0)
     38        return; // empty bounds - this shouldn't happen
     39
     40    spans.reserve(j1 - j0);
     41
     42    for (i16 j = j0; j < j1; ++j)
     43    {
     44        // Find the min/max range of cells that are strictly inside the square+clearance.
     45        // (Since the square+clearance is a convex shape, we can just test each
     46        // corner of each cell is inside the shape.)
     47        // (TODO: This potentially does a lot of redundant work.)
     48        i16 spanI0 = std::numeric_limits<i16>::max();
     49        i16 spanI1 = std::numeric_limits<i16>::min();
     50        for (i16 i = i0; i < i1; ++i)
     51        {
     52            if (Geometry::DistanceToSquare(
     53                CFixedVector2D(cellSize*i, cellSize*j) - CFixedVector2D(shape.x, shape.z),
     54                shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh), true) > clearance)
     55            {
     56                continue;
     57            }
     58
     59            if (Geometry::DistanceToSquare(
     60                CFixedVector2D(cellSize*(i+1), cellSize*j) - CFixedVector2D(shape.x, shape.z),
     61                shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh), true) > clearance)
     62            {
     63                continue;
     64            }
     65
     66            if (Geometry::DistanceToSquare(
     67                CFixedVector2D(cellSize*i, cellSize*(j+1)) - CFixedVector2D(shape.x, shape.z),
     68                shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh), true) > clearance)
     69            {
     70                continue;
     71            }
     72
     73            if (Geometry::DistanceToSquare(
     74                CFixedVector2D(cellSize*(i+1), cellSize*(j+1)) - CFixedVector2D(shape.x, shape.z),
     75                shape.u, shape.v, CFixedVector2D(shape.hw, shape.hh), true) > clearance)
     76            {
     77                continue;
     78            }
     79
     80            spanI0 = std::min(spanI0, i);
     81            spanI1 = std::max(spanI1, (i16)(i+1));
     82        }
     83
     84        // Add non-empty spans onto the list
     85        if (spanI0 < spanI1)
     86        {
     87            Span span = { spanI0, spanI1, j };
     88            spans.push_back(span);
     89        }
     90    }
     91}
  • source/simulation2/helpers/PriorityQueue.h

     
    3232template <typename Item, typename CMP>
    3333struct QueueItemPriority
    3434{
    35     bool operator()(const Item& a, const Item& b)
     35    bool operator()(const Item& a, const Item& b) const
    3636    {
    3737        if (CMP()(b.rank, a.rank)) // higher costs are lower priority
    3838            return true;
    3939        if (CMP()(a.rank, b.rank))
    4040            return false;
    4141        // Need to tie-break to get a consistent ordering
    42         // TODO: Should probably tie-break on g or h or something, but don't bother for now
    4342        if (a.id < b.id)
    4443            return true;
    4544        if (b.id < a.id)
     
    7372        push_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority<Item, CMP>());
    7473    }
    7574
    76     Item* find(ID id)
     75    void promote(ID id, R UNUSED(oldrank), R newrank)
    7776    {
    78         for (size_t n = 0; n < m_Heap.size(); ++n)
     77        // Loop backwards since it seems a little faster in practice
     78        for (ssize_t n = m_Heap.size() - 1; n >= 0; --n)
    7979        {
    8080            if (m_Heap[n].id == id)
    81                 return &m_Heap[n];
    82         }
    83         return NULL;
    84     }
    85 
    86     void promote(ID id, R newrank)
    87     {
    88         for (size_t n = 0; n < m_Heap.size(); ++n)
    89         {
    90             if (m_Heap[n].id == id)
    9181            {
    9282#if PRIORITYQUEUE_DEBUG
    9383                ENSURE(m_Heap[n].rank > newrank);
     
    10494#if PRIORITYQUEUE_DEBUG
    10595        ENSURE(m_Heap.size());
    10696#endif
    107         Item r = m_Heap.front();
     97        Item r = m_Heap[0];
    10898        pop_heap(m_Heap.begin(), m_Heap.end(), QueueItemPriority<Item, CMP>());
    10999        m_Heap.pop_back();
    110100        return r;
     
    155145        return NULL;
    156146    }
    157147
    158     void promote(ID id, R newrank)
     148    void promote(ID id, R UNUSED(oldrank), R newrank)
    159149    {
    160150        find(id)->rank = newrank;
    161151    }
  • source/simulation2/helpers/PathGoal.h

     
     1/* Copyright (C) 2012 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_PATHGOAL
     19#define INCLUDED_PATHGOAL
     20
     21#include "maths/FixedVector2D.h"
     22#include "simulation2/helpers/Position.h"
     23
     24/**
     25 * Pathfinder goal.
     26 * The goal can be either a point, a circle, or a square (rectangle).
     27 * For circles/squares, any point inside the shape is considered to be
     28 * part of the goal.
     29 * Also, it can be an 'inverted' circle/square, where any point outside
     30 * the shape is part of the goal.
     31 */
     32class PathGoal
     33{
     34public:
     35    enum Type {
     36        POINT,           // single point
     37        CIRCLE,          // the area inside a circle
     38        INVERTED_CIRCLE, // the area outside a circle
     39        SQUARE,          // the area inside a square
     40        INVERTED_SQUARE  // the area outside a square
     41    } type;
     42
     43    entity_pos_t x, z; // position of center
     44
     45    CFixedVector2D u, v; // if [INVERTED_]SQUARE, then orthogonal unit axes
     46
     47    entity_pos_t hw, hh; // if [INVERTED_]SQUARE, then half width & height; if [INVERTED_]CIRCLE, then hw is radius
     48
     49    /**
     50     * Returns true if the given navcell contains a part of the goal area.
     51     */
     52    bool NavcellContainsGoal(int i, int j) const;
     53
     54    /**
     55     * Returns true if any navcell (i, j) where
     56     * min(i0,i1) <= i <= max(i0,i1)
     57     * min(j0,j1) <= j <= max(j0,j1),
     58     * contains a part of the goal area.
     59     * If so, arguments i and j (if not NULL) are set to the goal navcell nearest
     60     * to (i0, j0), assuming the rect has either width or height = 1.
     61     */
     62    bool NavcellRectContainsGoal(int i0, int j0, int i1, int j1, int* i, int* j) const;
     63
     64    /**
     65     * Returns true if the rectangle defined by (x0,z0)-(x1,z1) (inclusive)
     66     * contains a part of the goal area.
     67     */
     68    bool RectContainsGoal(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) const;
     69
     70    /**
     71     * Returns the minimum distance from the point with the given @p pos
     72     * to any point on the outline of the goal shape.
     73     */
     74    fixed DistanceToPoint(CFixedVector2D pos) const;
     75};
     76
     77#endif // INCLUDED_PATHGOAL
  • source/simulation2/helpers/PathGoal.cpp

     
     1/* Copyright (C) 2012 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#include "precompiled.h"
     19
     20#include "PathGoal.h"
     21
     22#include "graphics/Terrain.h"
     23#include "simulation2/components/ICmpObstructionManager.h"
     24#include "simulation2/helpers/Geometry.h"
     25
     26static bool NavcellContainsCircle(int i, int j, fixed x, fixed z, fixed r, bool inside)
     27{
     28    // Accept any navcell (i,j) that contains a point which is inside[/outside]
     29    // (or on the edge of) the circle
     30
     31    // Get world-space bounds of navcell
     32    entity_pos_t x0 = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     33    entity_pos_t z0 = entity_pos_t::FromInt(j).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     34    entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE;
     35    entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE;
     36
     37    if (inside)
     38    {
     39        // Get the point inside the navcell closest to (x,z)
     40        entity_pos_t nx = Clamp(x, x0, x1);
     41        entity_pos_t nz = Clamp(z, z0, z1);
     42        // Check if that point is inside the circle
     43        return (CFixedVector2D(nx, nz) - CFixedVector2D(x, z)).CompareLength(r) <= 0;
     44    }
     45    else
     46    {
     47        // If any corner of the navcell is outside the circle, return true.
     48        // Otherwise, since the circle is convex, there cannot be any other point
     49        // in the navcell that is outside the circle.
     50        return (
     51            (CFixedVector2D(x0, z0) - CFixedVector2D(x, z)).CompareLength(r) >= 0
     52         || (CFixedVector2D(x1, z0) - CFixedVector2D(x, z)).CompareLength(r) >= 0
     53         || (CFixedVector2D(x0, z1) - CFixedVector2D(x, z)).CompareLength(r) >= 0
     54         || (CFixedVector2D(x1, z1) - CFixedVector2D(x, z)).CompareLength(r) >= 0
     55        );
     56    }
     57}
     58
     59static bool NavcellContainsSquare(int i, int j,
     60    fixed x, fixed z, CFixedVector2D u, CFixedVector2D v, fixed hw, fixed hh,
     61    bool inside)
     62{
     63    // Accept any navcell (i,j) that contains a point which is inside[/outside]
     64    // (or on the edge of) the square
     65
     66    // Get world-space bounds of navcell
     67    entity_pos_t x0 = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     68    entity_pos_t z0 = entity_pos_t::FromInt(j).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     69    entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE;
     70    entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE;
     71
     72    if (inside)
     73    {
     74        // Get the point inside the navcell closest to (x,z)
     75        entity_pos_t nx = Clamp(x, x0, x1);
     76        entity_pos_t nz = Clamp(z, z0, z1);
     77        // Check if that point is inside the circle
     78        return Geometry::PointIsInSquare(CFixedVector2D(nx - x, nz - z), u, v, CFixedVector2D(hw, hh));
     79    }
     80    else
     81    {
     82        // If any corner of the navcell is outside the square, return true.
     83        // Otherwise, since the square is convex, there cannot be any other point
     84        // in the navcell that is outside the square.
     85        return (
     86            Geometry::PointIsInSquare(CFixedVector2D(x0 - x, z0 - z), u, v, CFixedVector2D(hw, hh))
     87         || Geometry::PointIsInSquare(CFixedVector2D(x1 - x, z0 - z), u, v, CFixedVector2D(hw, hh))
     88         || Geometry::PointIsInSquare(CFixedVector2D(x0 - x, z1 - z), u, v, CFixedVector2D(hw, hh))
     89         || Geometry::PointIsInSquare(CFixedVector2D(x1 - x, z1 - z), u, v, CFixedVector2D(hw, hh))
     90        );
     91    }
     92}
     93
     94bool PathGoal::NavcellContainsGoal(int i, int j) const
     95{
     96    switch (type)
     97    {
     98    case POINT:
     99    {
     100        // Only accept a single navcell
     101        int gi = (x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero();
     102        int gj = (z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero();
     103        return gi == i && gj == j;
     104    }
     105    case CIRCLE:
     106        return NavcellContainsCircle(i, j, x, z, hw, true);
     107    case INVERTED_CIRCLE:
     108        return NavcellContainsCircle(i, j, x, z, hw, false);
     109    case SQUARE:
     110        return NavcellContainsSquare(i, j, x, z, u, v, hw, hh, true);
     111    case INVERTED_SQUARE:
     112        return NavcellContainsSquare(i, j, x, z, u, v, hw, hh, false);
     113    default:
     114    {
     115        debug_warn(L"invalid type");
     116        return false;
     117    }
     118    }
     119}
     120
     121bool PathGoal::NavcellRectContainsGoal(int i0, int j0, int i1, int j1, int* gi, int* gj) const
     122{
     123    // Get min/max to simplify range checks
     124    int imin = std::min(i0, i1);
     125    int imax = std::max(i0, i1);
     126    int jmin = std::min(j0, j1);
     127    int jmax = std::max(j0, j1);
     128
     129    // Direction to iterate from ij0 towards ij1
     130    int di = i1 < i0 ? -1 : +1;
     131    int dj = j1 < j0 ? -1 : +1;
     132
     133    switch (type)
     134    {
     135    case POINT:
     136    {
     137        // Calculate the navcell that contains the point goal
     138        int i = (x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero();
     139        int j = (z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero();
     140        // If that goal navcell is in the given range, return it
     141        if (imin <= i && i <= imax && jmin <= j && j <= jmax)
     142        {
     143            if (gi) *gi = i;
     144            if (gj) *gj = j;
     145            return true;
     146        }
     147        return false;
     148    }
     149    case CIRCLE:
     150    {
     151        // Loop over all navcells in the given range (starting at (i0,j0) since
     152        // this function is meant to find the goal navcell nearest to there
     153        // assuming jmin==jmax || imin==imax),
     154        // and check whether any point in each navcell is within the goal circle.
     155        // (TODO: this is pretty inefficient.)
     156        for (int j = j0; jmin <= j && j <= jmax; j += dj)
     157        {
     158            for (int i = i0; imin <= i && i <= imax; i += di)
     159            {
     160                entity_pos_t x0 = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     161                entity_pos_t z0 = entity_pos_t::FromInt(j).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     162                entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE;
     163                entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE;
     164                entity_pos_t nx = Clamp(x, x0, x1);
     165                entity_pos_t nz = Clamp(z, z0, z1);
     166                if ((CFixedVector2D(nx, nz) - CFixedVector2D(x, z)).CompareLength(hw) <= 0)
     167                {
     168                    if (gi) *gi = i;
     169                    if (gj) *gj = j;
     170                    return true;
     171                }
     172            }
     173        }
     174        return false;
     175    }
     176    case SQUARE:
     177    {
     178        // Loop over all navcells in the given range (starting at (i0,j0) since
     179        // this function is meant to find the goal navcell nearest to there
     180        // assuming jmin==jmax || imin==imax),
     181        // and check whether any point in each navcell is within the goal square.
     182        // (TODO: this is pretty inefficient.)
     183        for (int j = j0; jmin <= j && j <= jmax; j += dj)
     184        {
     185            for (int i = i0; imin <= i && i <= imax; i += di)
     186            {
     187                entity_pos_t x0 = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     188                entity_pos_t z0 = entity_pos_t::FromInt(j).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     189                entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE;
     190                entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE;
     191                entity_pos_t nx = Clamp(x, x0, x1);
     192                entity_pos_t nz = Clamp(z, z0, z1);
     193                if (Geometry::PointIsInSquare(CFixedVector2D(nx - x, nz - z), u, v, CFixedVector2D(hw, hh)))
     194                {
     195                    if (gi) *gi = i;
     196                    if (gj) *gj = j;
     197                    return true;
     198                }
     199            }
     200        }
     201        return false;
     202    }
     203    case INVERTED_CIRCLE:
     204    case INVERTED_SQUARE:
     205        {
     206            // Haven't bothered implementing these, since they're not needed by the
     207            // current pathfinder design
     208            debug_warn(L"PathGoal::NavcellRectContainsGoal doesn't support inverted shapes");
     209            return false;
     210        }
     211    default:
     212    {
     213        debug_warn(L"invalid type");
     214        return false;
     215    }
     216    }
     217}
     218
     219bool PathGoal::RectContainsGoal(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) const
     220{
     221    switch (type)
     222    {
     223    case POINT:
     224    {
     225        return x0 <= x && x <= x1 && z0 <= z && z <= z1;
     226    }
     227    case CIRCLE:
     228    {
     229        entity_pos_t nx = Clamp(x, x0, x1);
     230        entity_pos_t nz = Clamp(z, z0, z1);
     231        return (CFixedVector2D(nx, nz) - CFixedVector2D(x, z)).CompareLength(hw) <= 0;
     232    }
     233    case SQUARE:
     234    {
     235        entity_pos_t nx = Clamp(x, x0, x1);
     236        entity_pos_t nz = Clamp(z, z0, z1);
     237        return Geometry::PointIsInSquare(CFixedVector2D(nx - x, nz - z), u, v, CFixedVector2D(hw, hh));
     238    }
     239    case INVERTED_CIRCLE:
     240    case INVERTED_SQUARE:
     241    {
     242        // Haven't bothered implementing these, since they're not needed by the
     243        // current pathfinder design
     244        debug_warn(L"PathGoal::RectContainsGoal doesn't support inverted shapes");
     245        return false;
     246    }
     247    default:
     248    {
     249        debug_warn(L"invalid type");
     250        return false;
     251    }
     252    }
     253}
     254
     255fixed PathGoal::DistanceToPoint(CFixedVector2D pos) const
     256{
     257    switch (type)
     258    {
     259    case PathGoal::POINT:
     260        return (pos - CFixedVector2D(x, z)).Length();
     261
     262    case PathGoal::CIRCLE:
     263    case PathGoal::INVERTED_CIRCLE:
     264        return ((pos - CFixedVector2D(x, z)).Length() - hw).Absolute();
     265
     266    case PathGoal::SQUARE:
     267    case PathGoal::INVERTED_SQUARE:
     268    {
     269        CFixedVector2D halfSize(hw, hh);
     270        CFixedVector2D d(pos.X - x, pos.Y - z);
     271        return Geometry::DistanceToSquare(d, u, v, halfSize);
     272    }
     273
     274    default:
     275        debug_warn(L"invalid type");
     276        return fixed::Zero();
     277    }
     278}
  • source/simulation2/helpers/Grid.h

     
    4848
    4949    Grid(const Grid& g)
    5050    {
     51        m_Data = NULL;
    5152        *this = g;
    5253    }
    5354
     
    5859            m_W = g.m_W;
    5960            m_H = g.m_H;
    6061            m_DirtyID = g.m_DirtyID;
     62            delete[] m_Data;
    6163            if (g.m_Data)
    6264            {
    6365                m_Data = new T[m_W * m_H];
  • source/simulation2/helpers/Geometry.h

     
    3232{
    3333
    3434/**
    35  * Checks if a point is inside the given rotated square or rectangle.
     35 * Checks if a point is inside the given rotated rectangle.
     36 * Points precisely on an edge are considered to be inside.
    3637 *
    37  * @note Currently assumes the @p u and @p v vectors are perpendicular.
    38  * @param point point vector of the point that is to be tested relative to the origin (center) of the shape.
    39  * @param u rotated X axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated,
    40  *          this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta), as
    41  *          the absolute Z axis points down in the unit circle.
    42  * @param v rotated Z axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated,
    43  *          this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta), as
    44  *          the absolute Z axis points down in the unit circle.
    45  * @param halfSize Holds half the dimensions of the shape along the u and v vectors, respectively.
     38 * The rectangle is defined by the four vertexes
     39 * (+/-u*halfSize.X +/-v*halfSize.Y).
    4640 *
    47  * @return true if @p point is inside the square with rotated X axis unit vector @p u and rotated Z axis unit vector @p v,
    48  * and half dimensions specified by @p halfSizes.
     41 * The @p u and @p v vectors must be perpendicular.
    4942 */
    50 bool PointIsInSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
     43bool PointIsInSquare(CFixedVector2D point,
     44    CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    5145
     46/**
     47 * Returns a vector (bx,by) such that every point inside
     48 * the given rotated rectangle has coordinates
     49 * (x,y) with -bx <= x <= bx, -by <= y < by.
     50 *
     51 * The rectangle is defined by the four vertexes
     52 * (+/-u*halfSize.X +/-v*halfSize.Y).
     53 */
    5254CFixedVector2D GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    5355
    54 fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    55 
    5656/**
    57  * Given a circle of radius @p radius, and a chord of length @p chordLength on this circle, computes the central angle formed by
    58  * connecting the chord's endpoints to the center of the circle.
    59  *
    60  * @param radius Radius of the circle; must be strictly positive.
     57 * Returns the minimum Euclidean distance from the given point to
     58 * any point on the boundary of the given rotated rectangle.
     59 *
     60 * If @p countInsideAsZero is true, and the point is inside the rectangle,
     61 * it will return 0.
     62 * If @p countInsideAsZero is false, the (positive) distance to the boundary
     63 * will be returned regardless of where the point is.
     64 *
     65 * The rectangle is defined by the four vertexes
     66 * (+/-u*halfSize.X +/-v*halfSize.Y).
     67 *
     68 * The @p u and @p v vectors must be perpendicular and unit length.
    6169 */
    62 float ChordToCentralAngle(const float chordLength, const float radius);
     70fixed DistanceToSquare(CFixedVector2D point,
     71    CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize,
     72    bool countInsideAsZero = false);
    6373
    6474/**
    65  * Find point closest to the given point on the edge of the given square or rectangle.
     75 * Returns a point on the boundary of the given rotated rectangle
     76 * that is closest (or equally closest) to the given point
     77 * in Euclidean distance.
    6678 *
    67  * @note Currently assumes the @p u and @p v vectors are perpendicular.
    68  * @param point point vector of the point we want to get the nearest edge point for, relative to the origin (center) of the shape.
    69  * @param u rotated X axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated,
    70  *          this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta).
    71  * @param v rotated Z axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated,
    72  *          this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta).
    73  * @param halfSize Holds half the dimensions of the shape along the u and v vectors, respectively.
     79 * The rectangle is defined by the four vertexes
     80 * (+/-u*halfSize.X +/-v*halfSize.Y).
    7481 *
    75  * @return point that is closest to @p point on the edge of the square specified by orientation unit vectors @p u and @p v and half
    76  *  dimensions @p halfSize, relative to the center of the square
     82 * The @p u and @p v vectors must be perpendicular and unit length.
    7783 */
    78 CFixedVector2D NearestPointOnSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
     84CFixedVector2D NearestPointOnSquare(CFixedVector2D point,
     85    CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    7986
     87/**
     88 * Given a circle of radius @p radius, and a chord of length @p chordLength
     89 * on this circle, computes the central angle formed by
     90 * connecting the chord's endpoints to the center of the circle.
     91 *
     92 * @param radius Radius of the circle; must be strictly positive.
     93 */
     94float ChordToCentralAngle(const float chordLength, const float radius);
     95
    8096bool TestRaySquare(CFixedVector2D a, CFixedVector2D b, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    8197
    8298bool TestRayAASquare(CFixedVector2D a, CFixedVector2D b, CFixedVector2D halfSize);
  • source/simulation2/helpers/Geometry.cpp

     
    5050    return acosf(1.f - SQR(chordLength)/(2.f*SQR(radius))); // cfr. law of cosines
    5151}
    5252
    53 fixed Geometry::DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize)
     53fixed Geometry::DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize, bool countInsideAsZero)
    5454{
    5555    /*
    5656     * Relative to its own coordinate system, we have a square like:
     
    9292        fixed closest = (dv.Absolute() - hh).Absolute(); // horizontal edges
    9393
    9494        if (-hh < dv && dv < hh) // region I
    95             closest = std::min(closest, (du.Absolute() - hw).Absolute()); // vertical edges
     95        {
     96            if (countInsideAsZero)
     97                closest = fixed::Zero();
     98            else
     99                closest = std::min(closest, (du.Absolute() - hw).Absolute()); // vertical edges
     100        }
    96101
    97102        return closest;
    98103    }
  • source/simulation2/components/tests/test_Pathfinder.h

     
    2626#include "lib/timer.h"
    2727#include "lib/tex/tex.h"
    2828#include "ps/Loader.h"
     29#include "ps/Pyrogenesis.h"
    2930#include "simulation2/Simulation2.h"
    3031
    3132class TestCmpPathfinder : public CxxTest::TestSuite
     
    8586        ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, x1, z1 };
    8687
    8788        ICmpPathfinder::Path path;
    88         cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), cmp->GetCostClass("default"), path);
     89        cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), path);
    8990        for (size_t i = 0; i < path.m_Waypoints.size(); ++i)
    9091            printf("%d: %f %f\n", (int)i, path.m_Waypoints[i].x.ToDouble(), path.m_Waypoints[i].z.ToDouble());
    9192#endif
     
    99100            entity_pos_t z0 = entity_pos_t::FromInt(rand() % 512);
    100101            entity_pos_t x1 = x0 + entity_pos_t::FromInt(rand() % 64);
    101102            entity_pos_t z1 = z0 + entity_pos_t::FromInt(rand() % 64);
    102             ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, x1, z1 };
     103            PathGoal goal = { PathGoal::POINT, x1, z1 };
    103104
    104105            ICmpPathfinder::Path path;
    105             cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), cmp->GetCostClass("default"), path);
     106            cmp->ComputePath(x0, z0, goal, cmp->GetPassabilityClass("default"), path);
    106107        }
    107108
    108109        t = timer_Time() - t;
     
    133134        }
    134135
    135136        NullObstructionFilter filter;
    136         ICmpPathfinder::Goal goal = { ICmpPathfinder::Goal::POINT, range, range };
     137        PathGoal goal = { PathGoal::POINT, range, range };
    137138        ICmpPathfinder::Path path;
    138139        cmpPathfinder->ComputeShortPath(filter, range/3, range/3, fixed::FromInt(2), range, goal, 0, path);
    139140        for (size_t i = 0; i < path.m_Waypoints.size(); ++i)
    140141            printf("# %d: %f %f\n", (int)i, path.m_Waypoints[i].x.ToFloat(), path.m_Waypoints[i].z.ToFloat());
    141142    }
     143
     144    template<typename T>
     145    void DumpGrid(std::ostream& stream, const Grid<T>& grid, int mask)
     146    {
     147        for (u16 j = 0; j < grid.m_H; ++j)
     148        {
     149            for (u16 i = 0; i < grid.m_W; )
     150            {
     151                if (!(grid.get(i, j) & mask))
     152                {
     153                    i++;
     154                    continue;
     155                }
     156
     157                u16 i0 = i;
     158                for (i = i0+1; ; ++i)
     159                {
     160                    if (i >= grid.m_W || !(grid.get(i, j) & mask))
     161                    {
     162                        stream << "  <rect x='" << i0 << "' y='" << j << "' width='" << (i-i0) << "' height='1'/>\n";
     163                        break;
     164                    }
     165                }
     166            }
     167        }
     168    }
     169
     170#define USE_JPS 1
     171
     172    void test_perf2()
     173    {
     174        CTerrain terrain;
     175
     176        CSimulation2 sim2(NULL, &terrain);
     177        sim2.LoadDefaultScripts();
     178        sim2.ResetState();
     179
     180        CMapReader* mapReader = new CMapReader(); // it'll call "delete this" itself
     181
     182        LDR_BeginRegistering();
     183        mapReader->LoadMap(L"maps/scenarios/Peloponnese.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL,
     184            &sim2, &sim2.GetSimContext(), -1, false);
     185        LDR_EndRegistering();
     186        TS_ASSERT_OK(LDR_NonprogressiveLoad());
     187
     188        sim2.Update(0);
     189
     190        std::ofstream stream(OsString("perf2.html").c_str(), std::ofstream::out | std::ofstream::trunc);
     191
     192        CmpPtr<ICmpObstructionManager> cmpObstructionManager(sim2, SYSTEM_ENTITY);
     193        CmpPtr<ICmpPathfinder> cmpPathfinder(sim2, SYSTEM_ENTITY);
     194
     195        ICmpPathfinder::pass_class_t obstructionsMask = cmpPathfinder->GetPassabilityClass("default") | ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING;
     196        const Grid<u16>& obstructions = cmpPathfinder->GetPassabilityGrid();
     197
     198        int scale = 1;
     199        stream << "<!DOCTYPE html>\n";
     200        stream << "<style>\n";
     201        stream << ".passability rect { fill: black; }\n";
     202        stream << ".astar-open rect { fill: rgba(255,255,0,0.75); }\n";
     203        stream << ".astar-closed rect { fill: rgba(255,0,0,0.75); }\n";
     204//      stream << ".astar-closed rect { fill: rgba(0,255,0,0.75); }\n";
     205        stream << ".path { stroke: rgba(0,0,255,0.75); stroke-width: 1; fill: none; }\n";
     206        stream << "</style>\n";
     207        stream << "<svg width='" << obstructions.m_W*scale << "' height='" << obstructions.m_H*scale << "'>\n";
     208        stream << "<g transform='translate(0 " << obstructions.m_H*scale << ") scale(" << scale << " -" << scale << ")'>\n";
     209
     210        stream << " <g class='passability'>\n";
     211        DumpGrid(stream, obstructions, obstructionsMask);
     212        stream << " </g>\n";
     213
     214        DumpPath(stream, 128*4, 256*4, 128*4, 384*4, cmpPathfinder);
     215//      RepeatPath(500, 128*4, 256*4, 128*4, 384*4, cmpPathfinder);
     216// //       RepeatPath(100, 128*4, 256*4, 128*4, 384*4, cmpPathfinder);
     217//
     218//      DumpPath(stream, 128*4, 204*4, 192*4, 204*4, cmpPathfinder);
     219//
     220//      DumpPath(stream, 128*4, 230*4, 32*4, 230*4, cmpPathfinder);
     221
     222        stream << "</g>\n";
     223        stream << "</svg>\n";
     224    }
     225   
     226    void test_perf3()
     227    {
     228        CTerrain terrain;
     229
     230        CSimulation2 sim2(NULL, &terrain);
     231        sim2.LoadDefaultScripts();
     232        sim2.ResetState();
     233
     234        CMapReader* mapReader = new CMapReader(); // it'll call "delete this" itself
     235
     236        LDR_BeginRegistering();
     237        mapReader->LoadMap(L"maps/scenarios/pathfindtest7.pmp", &terrain, NULL, NULL, NULL, NULL, NULL, NULL,
     238            &sim2, &sim2.GetSimContext(), -1, false);
     239        LDR_EndRegistering();
     240        TS_ASSERT_OK(LDR_NonprogressiveLoad());
     241
     242        sim2.Update(0);
     243
     244        std::ofstream stream(OsString("perf3.html").c_str(), std::ofstream::out | std::ofstream::trunc);
     245
     246        CmpPtr<ICmpObstructionManager> cmpObstructionManager(sim2, SYSTEM_ENTITY);
     247        CmpPtr<ICmpPathfinder> cmpPathfinder(sim2, SYSTEM_ENTITY);
     248
     249        ICmpPathfinder::pass_class_t obstructionsMask = cmpPathfinder->GetPassabilityClass("default") | ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING;
     250        const Grid<u16>& obstructions = cmpPathfinder->GetPassabilityGrid();
     251
     252        int scale = 31;
     253        stream << "<!DOCTYPE html>\n";
     254        stream << "<style>\n";
     255        stream << ".passability rect { fill: black; }\n";
     256        stream << ".astar-open rect { fill: rgba(255,255,0,0.75); }\n";
     257        stream << ".astar-closed rect { fill: rgba(255,0,0,0.75); }\n";
     258        stream << ".path { stroke: rgba(0,0,255,0.75); stroke-width: " << (1.0f / scale) << "; fill: none; }\n";
     259        stream << "</style>\n";
     260        stream << "<svg width='" << obstructions.m_W*scale << "' height='" << obstructions.m_H*scale << "'>\n";
     261        stream << "<defs><pattern id='grid' width='1' height='1' patternUnits='userSpaceOnUse'>\n";
     262        stream << "<rect fill='none' stroke='#888' stroke-width='" << (1.0f / scale) << "' width='" << scale << "' height='" << scale << "'/>\n";
     263        stream << "</pattern></defs>\n";
     264        stream << "<g transform='translate(0 " << obstructions.m_H*scale << ") scale(" << scale << " -" << scale << ")'>\n";
     265
     266        stream << " <g class='passability'>\n";
     267        DumpGrid(stream, obstructions, obstructionsMask);
     268        stream << " </g>\n";
     269
     270        for (int j = 160; j < 190; ++j)
     271            for (int i = 220; i < 290; ++i)
     272                DumpPath(stream, 230, 178, i, j, cmpPathfinder);
     273
     274        stream << "<rect fill='url(#grid)' width='100%' height='100%'/>\n";
     275        stream << "</g>\n";
     276        stream << "</svg>\n";
     277    }
     278
     279    void DumpPath(std::ostream& stream, int i0, int j0, int i1, int j1, CmpPtr<ICmpPathfinder>& cmpPathfinder)
     280    {
     281        entity_pos_t x0 = entity_pos_t::FromInt(i0);
     282        entity_pos_t z0 = entity_pos_t::FromInt(j0);
     283        entity_pos_t x1 = entity_pos_t::FromInt(i1);
     284        entity_pos_t z1 = entity_pos_t::FromInt(j1);
     285
     286        PathGoal goal = { PathGoal::POINT, x1, z1 };
     287
     288        ICmpPathfinder::Path path;
     289#if USE_JPS
     290        cmpPathfinder->ComputePathJPS(x0, z0, goal, cmpPathfinder->GetPassabilityClass("default"), path);
     291#else
     292        cmpPathfinder->ComputePath(x0, z0, goal, cmpPathfinder->GetPassabilityClass("default"), path);
     293#endif
     294
     295        u32 debugSteps;
     296        double debugTime;
     297        Grid<u8> debugGrid;
     298#if USE_JPS
     299        cmpPathfinder->GetDebugDataJPS(debugSteps, debugTime, debugGrid);
     300#else
     301        cmpPathfinder->GetDebugData(debugSteps, debugTime, debugGrid);
     302#endif
     303//      stream << " <g style='visibility:hidden'>\n";
     304
     305        stream << " <g>\n";
     306//      stream << " <g class='astar-open'>\n";
     307//      DumpGrid(stream, debugGrid, 1);
     308//      stream << " </g>\n";
     309//      stream << " <g class='astar-closed'>\n";
     310//      DumpGrid(stream, debugGrid, 2);
     311//      stream << " </g>\n";
     312//      stream << " <g class='astar-closed'>\n";
     313//      DumpGrid(stream, debugGrid, 3);
     314//      stream << " </g>\n";
     315        stream << " </g>\n";
     316
     317        stream << " <polyline";
     318        stream << " onmouseover='this.previousElementSibling.style.visibility=\"visible\"' onmouseout='this.previousElementSibling.style.visibility=\"hidden\"'";
     319        double length = 0;
     320        for (ssize_t i = 0; i < (ssize_t)path.m_Waypoints.size()-1; ++i)
     321        {
     322            double dx = (path.m_Waypoints[i+1].x - path.m_Waypoints[i].x).ToDouble();
     323            double dz = (path.m_Waypoints[i+1].z - path.m_Waypoints[i].z).ToDouble();
     324            ENSURE(dx == 0 || dz == 0 || abs(dx) == abs(dz));
     325            length += sqrt(dx*dx + dz*dz);
     326        }
     327        stream << " title='length: " << length << "; tiles explored: " << debugSteps << "; time: " << debugTime*1000 << " msec'";
     328        stream << " class='path' points='";
     329        for (size_t i = 0; i < path.m_Waypoints.size(); ++i)
     330            stream << path.m_Waypoints[i].x.ToDouble()*ICmpObstructionManager::NAVCELLS_PER_TILE/TERRAIN_TILE_SIZE << "," << path.m_Waypoints[i].z.ToDouble()*ICmpObstructionManager::NAVCELLS_PER_TILE/TERRAIN_TILE_SIZE << " ";
     331        stream << "'/>\n";
     332    }
     333
     334    void RepeatPath(int n, int i0, int j0, int i1, int j1, CmpPtr<ICmpPathfinder>& cmpPathfinder)
     335    {
     336        entity_pos_t x0 = entity_pos_t::FromInt(i0);
     337        entity_pos_t z0 = entity_pos_t::FromInt(j0);
     338        entity_pos_t x1 = entity_pos_t::FromInt(i1);
     339        entity_pos_t z1 = entity_pos_t::FromInt(j1);
     340
     341        PathGoal goal = { PathGoal::POINT, x1, z1 };
     342
     343        double t = timer_Time();
     344        for (int i = 0; i < n; ++i)
     345        {
     346            ICmpPathfinder::Path path;
     347#if USE_JPS
     348            cmpPathfinder->ComputePathJPS(x0, z0, goal, cmpPathfinder->GetPassabilityClass("default"), path);
     349#else
     350            cmpPathfinder->ComputePath(x0, z0, goal, cmpPathfinder->GetPassabilityClass("default"), path);
     351#endif
     352        }
     353        t = timer_Time() - t;
     354        debug_printf(L"### RepeatPath %fms each (%fs total)\n", 1000*t / n, t);
     355    }
     356
    142357};
  • source/simulation2/components/ICmpUnitMotion.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    3838
    3939    /**
    4040     * Attempt to walk into range of a to a given point, or as close as possible.
     41     * The range is measured from the center of the unit.
    4142     * If the unit is already in range, or cannot move anywhere at all, or if there is
    4243     * some other error, then returns false.
    4344     * Otherwise, returns true and sends a MotionChanged message after starting to move,
     
    6061
    6162    /**
    6263     * Attempt to walk into range of a given target entity, or as close as possible.
     64     * The range is measured between approximately the edges of the unit and the target, so that
     65     * maxRange=0 is not unreachably close to the target.
    6366     * If the unit is already in range, or cannot move anywhere at all, or if there is
    6467     * some other error, then returns false.
    6568     * Otherwise, returns true and sends a MotionChanged message after starting to move,
  • source/simulation2/components/ICmpTerritoryManager.h

     
    2929public:
    3030    virtual bool NeedUpdate(size_t* dirtyID) = 0;
    3131
     32    /**
     33     * Number of pathfinder navcells per territory tile.
     34     * Passability data is stored per navcell, but we probably don't need that much
     35     * resolution, and a lower resolution can make the boundary lines look prettier
     36     * and will take less memory, so we downsample the passability data.
     37     */
     38    static const int NAVCELLS_PER_TERRITORY_TILE = 8;
     39
    3240    static const int TERRITORY_PLAYER_MASK = 0x3F;
    3341    static const int TERRITORY_CONNECTED_MASK = 0x40;
    3442    static const int TERRITORY_PROCESSED_MASK = 0x80; //< For internal use; marks a tile as processed.
  • source/simulation2/components/ICmpPathfinder.h

     
    1 /* Copyright (C) 2011 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    2020
    2121#include "simulation2/system/Interface.h"
    2222
     23#include "simulation2/helpers/PathGoal.h"
    2324#include "simulation2/helpers/Position.h"
    2425
    2526#include "maths/FixedVector2D.h"
     
    3839 * accounts for terrain costs but ignore units, and a 'short' vertex-based pathfinder that
    3940 * provides precise paths and avoids other units.
    4041 *
    41  * Both use the same concept of a Goal: either a point, circle or square.
     42 * Both use the same concept of a PathGoal: either a point, circle or square.
    4243 * (If the starting point is inside the goal shape then the path will move outwards
    4344 * to reach the shape's outline.)
    4445 *
     
    4849{
    4950public:
    5051    typedef u16 pass_class_t;
    51     typedef u8 cost_class_t;
    5252
    53     struct Goal
    54     {
    55         enum Type {
    56             POINT,
    57             CIRCLE,
    58             SQUARE
    59         } type;
    60         entity_pos_t x, z; // position of center
    61         CFixedVector2D u, v; // if SQUARE, then orthogonal unit axes
    62         entity_pos_t hw, hh; // if SQUARE, then half width & height; if CIRCLE, then hw is radius
    63     };
    64 
    6553    struct Waypoint
    6654    {
    6755        entity_pos_t x, z;
     
    8775     */
    8876    virtual pass_class_t GetPassabilityClass(const std::string& name) = 0;
    8977
    90     /**
    91      * Get the tag for a given movement cost class name.
    92      * Logs an error and returns something acceptable if the name is unrecognised.
    93      */
    94     virtual cost_class_t GetCostClass(const std::string& name) = 0;
    95 
    9678    virtual const Grid<u16>& GetPassabilityGrid() = 0;
    9779
    9880    /**
     
    10082     * The waypoints correspond to the centers of horizontally/vertically adjacent tiles
    10183     * along the path.
    10284     */
    103     virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& ret) = 0;
     85    virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, Path& ret) = 0;
    10486
    10587    /**
     88     * Equivalent to ComputePath, but using the JPS pathfinder.
     89     */
     90    virtual void ComputePathJPS(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, Path& ret) = 0;
     91
     92    /**
    10693     * Asynchronous version of ComputePath.
    10794     * The result will be sent as CMessagePathResult to 'notify'.
    10895     * Returns a unique non-zero number, which will match the 'ticket' in the result,
    10996     * so callers can recognise each individual request they make.
    11097     */
    111     virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify) = 0;
     98    virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify) = 0;
    11299
    113100    /**
    114101     * If the debug overlay is enabled, render the path that will computed by ComputePath.
    115102     */
    116     virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass) = 0;
     103    virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass) = 0;
    117104
    118105    /**
    119106     * Compute a precise path from the given point to the goal, and return the set of waypoints.
     
    121108     * a unit of radius 'r' will be able to follow the path with no collisions.
    122109     * The path is restricted to a box of radius 'range' from the starting point.
    123110     */
    124     virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& ret) = 0;
     111    virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, Path& ret) = 0;
    125112
    126113    /**
    127114     * Asynchronous version of ComputeShortPath (using ControlGroupObstructionFilter).
     
    129116     * Returns a unique non-zero number, which will match the 'ticket' in the result,
    130117     * so callers can recognise each individual request they make.
    131118     */
    132     virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify) = 0;
     119    virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify) = 0;
    133120
    134121    /**
    135      * Find the speed factor (typically around 1.0) for a unit of the given cost class
    136      * at the given position.
    137      */
    138     virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, cost_class_t costClass) = 0;
    139 
    140     /**
    141122     * Returns the coordinates of the point on the goal that is closest to pos in a straight line.
    142123     */
    143     virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal) = 0;
     124    virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const PathGoal& goal) = 0;
    144125
    145126    /**
    146127     * Check whether the given movement line is valid and doesn't hit any obstructions
     
    178159     */
    179160    virtual void ProcessSameTurnMoves() = 0;
    180161
     162    /**
     163     * Returns some stats about the last ComputePath.
     164     */
     165    virtual void GetDebugData(u32& steps, double& time, Grid<u8>& grid) = 0;
     166
     167    /**
     168     * Returns some stats about the last ComputePathJPS.
     169     */
     170    virtual void GetDebugDataJPS(u32& steps, double& time, Grid<u8>& grid) = 0;
     171
    181172    DECLARE_INTERFACE_TYPE(Pathfinder)
    182173};
    183174
  • source/simulation2/components/ICmpObstructionManager.h

     
    4848 * Units can be marked as either moving or stationary, which simply determines whether
    4949 * certain filters include or exclude them.
    5050 *
    51  * The @c Rasterise function approximates the current set of shapes onto a 2D grid,
     51 * The @c Rasterize function approximates the current set of shapes onto a 2D grid,
    5252 * for use with tile-based pathfinding.
    5353 */
    5454class ICmpObstructionManager : public IComponent
    5555{
    5656public:
    5757    /**
     58     * The pathfinders operate primarily over a navigation grid (a uniform-cost
     59     * 2D passability grid, with horizontal/vertical (not diagonal) connectivity).
     60     * This is based on the terrain tile passability, plus the rasterized shapes of
     61     * obstructions, all expanded outwards by the radius of the units.
     62     * Since units are much smaller than terrain tiles, the nav grid should be
     63     * higher resolution than the tiles.
     64     * We therefore split each terrain tile into NxN "nav cells" (for some integer N,
     65     * preferably a power of two).
     66     */
     67    static const int NAVCELLS_PER_TILE = 4;
     68
     69    /**
     70     * Size of a navcell in metres ( = TERRAIN_TILE_SIZE / NAVCELLS_PER_TILE)
     71     */
     72    static const fixed NAVCELL_SIZE;
     73
     74    /**
    5875     * External identifiers for shapes.
    5976     * (This is a struct rather than a raw u32 for type-safety.)
    6077     */
     
    205222        std::vector<entity_id_t>* out) = 0;
    206223
    207224    /**
    208      * Bit-flags for Rasterise.
     225     * Bit-flags for Rasterize.
    209226     */
    210227    enum TileObstruction
    211228    {
     
    215232    };
    216233
    217234    /**
    218      * Convert the current set of shapes onto a grid.
    219      * Tiles that are intersected by a pathfind-blocking shape will have TILE_OBSTRUCTED_PATHFINDING set;
    220      * tiles that are intersected by a foundation-blocking shape will also have TILE_OBSTRUCTED_FOUNDATION;
    221      * tiles that are outside the world bounds will also have TILE_OUTOFBOUNDS;
    222      * others will be set to 0.
    223      * This is very cheap if the grid has been rasterised before and the set of shapes has not changed.
    224      * @param grid the grid to be updated
    225      * @return true if any changes were made to the grid, false if it was already up-to-date
     235     * Convert the current set of shapes onto a navcell grid.
     236     * Shapes are expanded by the clearance radius @p expand.
     237     * Only shapes with at least one of the flags from @p requireMask will be considered.
     238     * @p setMask will be ORed onto the @p grid value for all navcells
     239     * that are wholly enclosed by an expanded shape.
    226240     */
    227     virtual bool Rasterise(Grid<u8>& grid) = 0;
     241    virtual void Rasterize(Grid<u16>& grid, entity_pos_t expand, ICmpObstructionManager::flags_t requireMask, u16 setMask) = 0;
    228242
    229243    /**
     244     * Returns whether obstructions have changed such that Rasterize will
     245     * return different data, since the last call to NeedUpdate with the same
     246     * @p dirtyID handle.
     247     * This should be first called with @p dirtyID pointing to 0; the function
     248     * will return true and update the pointed-at value to indicate the current
     249     * state. Pass the same pointer to subsequent calls, and the function either
     250     * will return false (if nothing relevant has changed), or will update the
     251     * value and return true (in which case you should call Rasterize again).
     252     */
     253    virtual bool NeedUpdate(size_t* dirtyID) = 0;
     254
     255    /**
    230256     * Standard representation for all types of shapes, for use with geometry processing code.
    231257     */
    232258    struct ObstructionSquare
     
    248274    virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares) = 0;
    249275
    250276    /**
    251      * Find a single obstruction that blocks a unit at the given point with the given radius.
    252      * Static obstructions (buildings) are more important than unit obstructions, and
    253      * obstructions that cover the given point are more important than those that only cover
    254      * the point expanded by the radius.
     277     * Returns the entity IDs of all unit shapes that intersect the given
     278     * obstruction square.
     279     * (TODO: This currently ignores the clearance/radius values of units,
     280     * so it just uses the unexpanded obstruction shapes, which is not ideal.)
    255281     */
    256     virtual bool FindMostImportantObstruction(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, ObstructionSquare& square) = 0;
     282    virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out) = 0;
    257283
    258284    /**
    259285     * Get the obstruction square representing the given shape.
     
    344370    {
    345371        if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group))
    346372            return false;
     373
     374        // If an obstruction already blocks tile-based pathfinding,
     375        // it will be handled as part of the terrain passability handling
     376        // and doesn't need to be matched by this filter
     377        if (flags & ICmpObstructionManager::FLAG_BLOCK_PATHFINDING)
     378            return false;
     379
    347380        if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT))
    348381            return false;
     382
    349383        if ((flags & ICmpObstructionManager::FLAG_MOVING) && !m_AvoidMoving)
    350384            return false;
     385
    351386        return true;
    352387    }
    353388};
  • source/simulation2/components/ICmpObstructionManager.cpp

     
    2121
    2222#include "simulation2/system/InterfaceScripted.h"
    2323
     24#include "graphics/Terrain.h"
     25
     26const fixed ICmpObstructionManager::NAVCELL_SIZE = fixed::FromInt(TERRAIN_TILE_SIZE) / NAVCELLS_PER_TILE;
     27
    2428BEGIN_INTERFACE_WRAPPER(ObstructionManager)
    2529DEFINE_INTERFACE_METHOD_1("SetPassabilityCircular", void, ICmpObstructionManager, SetPassabilityCircular, bool)
    2630DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpObstructionManager, SetDebugOverlay, bool)
  • source/simulation2/components/CCmpUnitMotion.cpp

     
    3838#include "ps/Profile.h"
    3939#include "renderer/Scene.h"
    4040
     41// For debugging; units will start going straight to the target
     42// instead of calling the pathfinder
     43#define DISABLE_PATHFINDER 0
     44
    4145/**
    4246 * When advancing along the long path, and picking a new waypoint to move
    4347 * towards, we'll pick one that's up to this far from the unit's current
     
    98102static const CColor OVERLAY_COLOUR_LONG_PATH(1, 1, 1, 1);
    99103static const CColor OVERLAY_COLOUR_SHORT_PATH(1, 0, 0, 1);
    100104
    101 static const entity_pos_t g_GoalDelta = entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/4; // for extending the goal outwards/inwards a little bit
     105static const entity_pos_t g_GoalDelta = ICmpObstructionManager::NAVCELL_SIZE; // for extending the goal outwards/inwards a little bit
    102106
    103107class CCmpUnitMotion : public ICmpUnitMotion
    104108{
     
    123127    fixed m_WalkSpeed; // in metres per second
    124128    fixed m_RunSpeed;
    125129    ICmpPathfinder::pass_class_t m_PassClass;
    126     ICmpPathfinder::cost_class_t m_CostClass;
    127130
    128131    // Dynamic state:
    129132
     
    236239    ICmpPathfinder::Path m_LongPath;
    237240    ICmpPathfinder::Path m_ShortPath;
    238241
    239     ICmpPathfinder::Goal m_FinalGoal;
     242    PathGoal m_FinalGoal;
    240243
    241244    static std::string GetSchema()
    242245    {
     
    245248            "<a:example>"
    246249                "<WalkSpeed>7.0</WalkSpeed>"
    247250                "<PassabilityClass>default</PassabilityClass>"
    248                 "<CostClass>infantry</CostClass>"
    249251            "</a:example>"
    250252            "<element name='FormationController'>"
    251253                "<data type='boolean'/>"
     
    266268            "</optional>"
    267269            "<element name='PassabilityClass' a:help='Identifies the terrain passability class (values are defined in special/pathfinder.xml)'>"
    268270                "<text/>"
    269             "</element>"
    270             "<element name='CostClass' a:help='Identifies the movement speed/cost class (values are defined in special/pathfinder.xml)'>"
    271                 "<text/>"
    272271            "</element>";
    273272    }
    274273
     
    298297        if (cmpPathfinder)
    299298        {
    300299            m_PassClass = cmpPathfinder->GetPassabilityClass(paramNode.GetChild("PassabilityClass").ToUTF8());
    301             m_CostClass = cmpPathfinder->GetCostClass(paramNode.GetChild("CostClass").ToUTF8());
    302300        }
    303301
    304302        CmpPtr<ICmpObstruction> cmpObstruction(GetSimContext(), GetEntityId());
     
    312310
    313311        m_TargetEntity = INVALID_ENTITY;
    314312
    315         m_FinalGoal.type = ICmpPathfinder::Goal::POINT;
     313        m_FinalGoal.type = PathGoal::POINT;
    316314
    317315        m_DebugOverlayEnabled = false;
    318316    }
     
    568566     * Might go in a straight line immediately, or might start an asynchronous
    569567     * path request.
    570568     */
    571     void BeginPathing(CFixedVector2D from, const ICmpPathfinder::Goal& goal);
     569    void BeginPathing(CFixedVector2D from, const PathGoal& goal);
    572570
    573571    /**
    574572     * Start an asynchronous long path query.
    575573     */
    576     void RequestLongPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal);
     574    void RequestLongPath(CFixedVector2D from, const PathGoal& goal);
    577575
    578576    /**
    579577     * Start an asynchronous short path query.
    580578     */
    581     void RequestShortPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal, bool avoidMovingUnits);
     579    void RequestShortPath(CFixedVector2D from, const PathGoal& goal, bool avoidMovingUnits);
    582580
    583581    /**
    584582     * Select a next long waypoint, given the current unit position.
     
    824822        // Find the speed factor of the underlying terrain
    825823        // (We only care about the tile we start on - it doesn't matter if we're moving
    826824        // partially onto a much slower/faster tile)
    827         fixed terrainSpeed = cmpPathfinder->GetMovementSpeed(pos.X, pos.Y, m_CostClass);
     825        // TODO: Terrain-dependent speeds are not currently supported
     826        fixed terrainSpeed = fixed::FromInt(1);
    828827
    829828        fixed maxSpeed = basicSpeed.Multiply(terrainSpeed);
    830829
     
    10251024        return false;
    10261025
    10271026    // Move the goal to match the target entity's new position
    1028     ICmpPathfinder::Goal goal = m_FinalGoal;
     1027    PathGoal goal = m_FinalGoal;
    10291028    goal.x = targetPos.X;
    10301029    goal.z = targetPos.Y;
    10311030    // (we ignore changes to the target's rotation, since only buildings are
     
    11471146
    11481147
    11491148
    1150 void CCmpUnitMotion::BeginPathing(CFixedVector2D from, const ICmpPathfinder::Goal& goal)
     1149void CCmpUnitMotion::BeginPathing(CFixedVector2D from, const PathGoal& goal)
    11511150{
    11521151    // Cancel any pending path requests
    11531152    m_ExpectedPathTicket = 0;
     
    11601159    if (cmpObstruction)
    11611160        cmpObstruction->SetMovingFlag(true);
    11621161
     1162#if DISABLE_PATHFINDER
     1163    {
     1164        CmpPtr<ICmpPathfinder> cmpPathfinder (GetSimContext(), SYSTEM_ENTITY);
     1165        CFixedVector2D goalPos = cmpPathfinder->GetNearestPointOnGoal(from, m_FinalGoal);
     1166        m_LongPath.m_Waypoints.clear();
     1167        m_ShortPath.m_Waypoints.clear();
     1168        ICmpPathfinder::Waypoint wp = { goalPos.X, goalPos.Y };
     1169        m_ShortPath.m_Waypoints.push_back(wp);
     1170        m_PathState = PATHSTATE_FOLLOWING;
     1171        return;
     1172    }
     1173#endif
     1174
    11631175    // If we're aiming at a target entity and it's close and we can reach
    11641176    // it in a straight line, then we'll just go along the straight line
    11651177    // instead of computing a path.
     
    11811193    RequestLongPath(from, goal);
    11821194}
    11831195
    1184 void CCmpUnitMotion::RequestLongPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal)
     1196void CCmpUnitMotion::RequestLongPath(CFixedVector2D from, const PathGoal& goal)
    11851197{
    11861198    CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
    11871199    if (!cmpPathfinder)
    11881200        return;
    11891201
    1190     cmpPathfinder->SetDebugPath(from.X, from.Y, goal, m_PassClass, m_CostClass);
     1202    cmpPathfinder->SetDebugPath(from.X, from.Y, goal, m_PassClass);
    11911203
    1192     m_ExpectedPathTicket = cmpPathfinder->ComputePathAsync(from.X, from.Y, goal, m_PassClass, m_CostClass, GetEntityId());
     1204    m_ExpectedPathTicket = cmpPathfinder->ComputePathAsync(from.X, from.Y, goal, m_PassClass, GetEntityId());
    11931205}
    11941206
    1195 void CCmpUnitMotion::RequestShortPath(CFixedVector2D from, const ICmpPathfinder::Goal& goal, bool avoidMovingUnits)
     1207void CCmpUnitMotion::RequestShortPath(CFixedVector2D from, const PathGoal& goal, bool avoidMovingUnits)
    11961208{
    11971209    CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
    11981210    if (!cmpPathfinder)
     
    12271239
    12281240    // Now we need to recompute a short path to the waypoint
    12291241
    1230     ICmpPathfinder::Goal goal;
     1242    PathGoal goal;
    12311243    if (m_LongPath.m_Waypoints.empty())
    12321244    {
    12331245        // This was the last waypoint - head for the exact goal
     
    12361248    else
    12371249    {
    12381250        // Head for somewhere near the waypoint (but allow some leeway in case it's obstructed)
    1239         goal.type = ICmpPathfinder::Goal::CIRCLE;
     1251        goal.type = PathGoal::CIRCLE;
    12401252        goal.hw = SHORT_PATH_GOAL_RADIUS;
    12411253        goal.x = targetX;
    12421254        goal.z = targetZ;
     
    12621274
    12631275    CFixedVector2D pos = cmpPosition->GetPosition2D();
    12641276
    1265     ICmpPathfinder::Goal goal;
     1277    PathGoal goal;
     1278    goal.x = x;
     1279    goal.z = z;
    12661280
    12671281    if (minRange.IsZero() && maxRange.IsZero())
    12681282    {
    1269         // Handle the non-ranged mode:
     1283        // Non-ranged movement:
    12701284
    1271         // Check whether this point is in an obstruction
    1272 
    1273         CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
    1274         if (!cmpObstructionManager)
    1275             return false;
    1276 
    1277         ICmpObstructionManager::ObstructionSquare obstruction;
    1278         if (cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(true), x, z, m_Radius, obstruction))
    1279         {
    1280             // If we're aiming inside a building, then aim for the outline of the building instead
    1281             // TODO: if we're aiming at a unit then maybe a circle would look nicer?
    1282 
    1283             goal.type = ICmpPathfinder::Goal::SQUARE;
    1284             goal.x = obstruction.x;
    1285             goal.z = obstruction.z;
    1286             goal.u = obstruction.u;
    1287             goal.v = obstruction.v;
    1288             goal.hw = obstruction.hw + m_Radius + g_GoalDelta; // nudge the goal outwards so it doesn't intersect the building itself
    1289             goal.hh = obstruction.hh + m_Radius + g_GoalDelta;
    1290         }
    1291         else
    1292         {
    1293             // Unobstructed - head directly for the goal
    1294             goal.type = ICmpPathfinder::Goal::POINT;
    1295             goal.x = x;
    1296             goal.z = z;
    1297         }
     1285        // Head directly for the goal
     1286        goal.type = PathGoal::POINT;
    12981287    }
    12991288    else
    13001289    {
     1290        // Ranged movement:
     1291
    13011292        entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length();
    13021293
    1303         entity_pos_t goalDistance;
    13041294        if (distance < minRange)
    13051295        {
    1306             goalDistance = minRange + g_GoalDelta;
     1296            // Too close to target - move outwards to a circle
     1297            // that's slightly larger than the min range
     1298            goal.type = PathGoal::INVERTED_CIRCLE;
     1299            goal.hw = minRange + g_GoalDelta;
    13071300        }
    13081301        else if (maxRange >= entity_pos_t::Zero() && distance > maxRange)
    13091302        {
    1310             goalDistance = maxRange - g_GoalDelta;
     1303            // Too far from target - move outwards to a circle
     1304            // that's slightly smaller than the max range
     1305            goal.type = PathGoal::CIRCLE;
     1306            goal.hw = maxRange - g_GoalDelta;
     1307
     1308            // If maxRange was abnormally small,
     1309            // collapse the circle into a point
     1310            if (goal.hw <= entity_pos_t::Zero())
     1311                goal.type = PathGoal::POINT;
    13111312        }
    13121313        else
    13131314        {
     
    13151316            FaceTowardsPointFromPos(pos, x, z);
    13161317            return false;
    13171318        }
    1318 
    1319         // TODO: what happens if goalDistance < 0? (i.e. we probably can never get close enough to the target)
    1320 
    1321         goal.type = ICmpPathfinder::Goal::CIRCLE;
    1322         goal.x = x;
    1323         goal.z = z;
    1324 
    1325         // Formerly added m_Radius, but it seems better to go by the mid-point.
    1326         goal.hw = goalDistance;
    13271319    }
    13281320
    13291321    m_State = STATE_INDIVIDUAL_PATH;
     
    13491341    bool hasObstruction = false;
    13501342    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
    13511343    ICmpObstructionManager::ObstructionSquare obstruction;
    1352     if (cmpObstructionManager)
    1353         hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(true), x, z, m_Radius, obstruction);
     1344//TODO  if (cmpObstructionManager)
     1345//      hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(true), x, z, m_Radius, obstruction);
    13541346
    13551347    if (minRange.IsZero() && maxRange.IsZero() && hasObstruction)
    13561348    {
     
    14221414    if (cmpObstruction)
    14231415        hasObstruction = cmpObstruction->GetObstructionSquare(obstruction);
    14241416
     1417    if (!hasObstruction)
     1418    {
     1419        // The target didn't have an obstruction or obstruction shape, so treat it as a point instead
     1420
     1421        CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
     1422        if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
     1423            return false;
     1424
     1425        CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
     1426
     1427        return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange);
     1428    }
     1429
    14251430    /*
    14261431     * If we're starting outside the maxRange, we need to move closer in.
    14271432     * If we're starting inside the minRange, we need to move further out.
     
    14471452     * (Those units should set minRange to 0 so they'll never be considered *too* close.)
    14481453     */
    14491454
    1450     if (hasObstruction)
     1455    CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
     1456    PathGoal goal;
     1457    goal.x = obstruction.x;
     1458    goal.z = obstruction.z;
     1459
     1460    entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
     1461
     1462    if (distance < minRange)
    14511463    {
    1452         CFixedVector2D halfSize(obstruction.hw, obstruction.hh);
    1453         ICmpPathfinder::Goal goal;
    1454         goal.x = obstruction.x;
    1455         goal.z = obstruction.z;
     1464        // Too close to the square - need to move away
    14561465
    1457         entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize);
     1466        // TODO: maybe we should do the ShouldTreatTargetAsCircle thing here?
    14581467
    1459         if (distance < minRange)
    1460         {
    1461             // Too close to the square - need to move away
     1468        entity_pos_t goalDistance = minRange + g_GoalDelta;
    14621469
    1463             // TODO: maybe we should do the ShouldTreatTargetAsCircle thing here?
     1470        goal.type = PathGoal::SQUARE;
     1471        goal.u = obstruction.u;
     1472        goal.v = obstruction.v;
     1473        entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself
     1474        goal.hw = obstruction.hw + delta;
     1475        goal.hh = obstruction.hh + delta;
     1476    }
     1477    else if (maxRange < entity_pos_t::Zero() || distance < maxRange)
     1478    {
     1479        // We're already in range - no need to move anywhere
     1480        FaceTowardsPointFromPos(pos, goal.x, goal.z);
     1481        return false;
     1482    }
     1483    else
     1484    {
     1485        // We might need to move closer:
    14641486
    1465             entity_pos_t goalDistance = minRange + g_GoalDelta;
     1487        // Circumscribe the square
     1488        entity_pos_t circleRadius = halfSize.Length();
    14661489
    1467             goal.type = ICmpPathfinder::Goal::SQUARE;
    1468             goal.u = obstruction.u;
    1469             goal.v = obstruction.v;
    1470             entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself
    1471             goal.hw = obstruction.hw + delta;
    1472             goal.hh = obstruction.hh + delta;
    1473         }
    1474         else if (maxRange < entity_pos_t::Zero() || distance < maxRange)
     1490        if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius))
    14751491        {
    1476             // We're already in range - no need to move anywhere
    1477             FaceTowardsPointFromPos(pos, goal.x, goal.z);
    1478             return false;
    1479         }
    1480         else
    1481         {
    1482             // We might need to move closer:
     1492            // The target is small relative to our range, so pretend it's a circle
    14831493
    1484             // Circumscribe the square
    1485             entity_pos_t circleRadius = halfSize.Length();
     1494            // Note that the distance to the circle will always be less than
     1495            // the distance to the square, so the previous "distance < maxRange"
     1496            // check is still valid (though not sufficient)
     1497            entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
    14861498
    1487             if (ShouldTreatTargetAsCircle(maxRange, obstruction.hw, obstruction.hh, circleRadius))
     1499            if (circleDistance < maxRange)
    14881500            {
    1489                 // The target is small relative to our range, so pretend it's a circle
    1490 
    1491                 // Note that the distance to the circle will always be less than
    1492                 // the distance to the square, so the previous "distance < maxRange"
    1493                 // check is still valid (though not sufficient)
    1494                 entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius;
    1495 
    1496                 if (circleDistance < maxRange)
    1497                 {
    1498                     // We're already in range - no need to move anywhere
    1499                     FaceTowardsPointFromPos(pos, goal.x, goal.z);
    1500                     return false;
    1501                 }
    1502 
    1503                 entity_pos_t goalDistance = maxRange - g_GoalDelta;
    1504 
    1505                 goal.type = ICmpPathfinder::Goal::CIRCLE;
    1506                 goal.hw = circleRadius + goalDistance;
     1501                // We're already in range - no need to move anywhere
     1502                FaceTowardsPointFromPos(pos, goal.x, goal.z);
     1503                return false;
    15071504            }
    1508             else
    1509             {
    1510                 // The target is large relative to our range, so treat it as a square and
    1511                 // get close enough that the diagonals come within range
    15121505
    1513                 entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
     1506            entity_pos_t goalDistance = maxRange - g_GoalDelta;
    15141507
    1515                 goal.type = ICmpPathfinder::Goal::SQUARE;
    1516                 goal.u = obstruction.u;
    1517                 goal.v = obstruction.v;
    1518                 entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself
    1519                 goal.hw = obstruction.hw + delta;
    1520                 goal.hh = obstruction.hh + delta;
    1521             }
     1508            goal.type = PathGoal::CIRCLE;
     1509            goal.hw = circleRadius + goalDistance;
    15221510        }
     1511        else
     1512        {
     1513            // The target is large relative to our range, so treat it as a square and
     1514            // get close enough that the diagonals come within range
    15231515
    1524         m_State = STATE_INDIVIDUAL_PATH;
    1525         m_TargetEntity = target;
    1526         m_TargetOffset = CFixedVector2D();
    1527         m_TargetMinRange = minRange;
    1528         m_TargetMaxRange = maxRange;
    1529         m_FinalGoal = goal;
     1516            entity_pos_t goalDistance = (maxRange - g_GoalDelta)*2 / 3; // multiply by slightly less than 1/sqrt(2)
    15301517
    1531         BeginPathing(pos, goal);
    1532 
    1533         return true;
     1518            goal.type = PathGoal::SQUARE;
     1519            goal.u = obstruction.u;
     1520            goal.v = obstruction.v;
     1521            entity_pos_t delta = std::max(goalDistance, m_Radius + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16); // ensure it's far enough to not intersect the building itself
     1522            goal.hw = obstruction.hw + delta;
     1523            goal.hh = obstruction.hh + delta;
     1524        }
    15341525    }
    1535     else
    1536     {
    1537         // The target didn't have an obstruction or obstruction shape, so treat it as a point instead
    15381526
    1539         CmpPtr<ICmpPosition> cmpTargetPosition(GetSimContext(), target);
    1540         if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld())
    1541             return false;
     1527    m_State = STATE_INDIVIDUAL_PATH;
     1528    m_TargetEntity = target;
     1529    m_TargetOffset = CFixedVector2D();
     1530    m_TargetMinRange = minRange;
     1531    m_TargetMaxRange = maxRange;
     1532    m_FinalGoal = goal;
    15421533
    1543         CFixedVector2D targetPos = cmpTargetPosition->GetPosition2D();
     1534    BeginPathing(pos, goal);
    15441535
    1545         return MoveToPointRange(targetPos.X, targetPos.Y, minRange, maxRange);
    1546     }
     1536    return true;
    15471537}
    15481538
    15491539bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange)
     
    16201610
    16211611    CFixedVector2D pos = cmpPosition->GetPosition2D();
    16221612
    1623     ICmpPathfinder::Goal goal;
    1624     goal.type = ICmpPathfinder::Goal::POINT;
     1613    PathGoal goal;
     1614    goal.type = PathGoal::POINT;
    16251615    goal.x = pos.X;
    16261616    goal.z = pos.Y;
    16271617
  • source/simulation2/components/CCmpTerritoryManager.cpp

     
    4848
    4949class CCmpTerritoryManager;
    5050
    51 class TerritoryOverlay : public TerrainOverlay
     51class TerritoryOverlay : public TerrainTextureOverlay
    5252{
    5353    NONCOPYABLE(TerritoryOverlay);
    5454public:
    5555    CCmpTerritoryManager& m_TerritoryManager;
    5656
    5757    TerritoryOverlay(CCmpTerritoryManager& manager);
    58     virtual void StartRender();
    59     virtual void ProcessTile(ssize_t i, ssize_t j);
     58    virtual void BuildTextureRGBA(u8* data, size_t w, size_t h);
    6059};
    6160
    6261class CCmpTerritoryManager : public ICmpTerritoryManager
     
    269268
    270269REGISTER_COMPONENT_TYPE(TerritoryManager)
    271270
     271/**
     272 * Compute the tile indexes on the grid nearest to a given point
     273 */
     274static void NearestTerritoryTile(entity_pos_t x, entity_pos_t z, int& i, int& j, int w, int h)
     275{
     276    i = clamp((x / (ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE)).ToInt_RoundToNegInfinity(), 0, w-1);
     277    j = clamp((z / (ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE)).ToInt_RoundToNegInfinity(), 0, h-1);
     278}
     279
     280/**
     281 * Returns the position of the center of the given tile
     282 */
     283static void TerritoryTileCenter(int i, int j, entity_pos_t& x, entity_pos_t& z)
     284{
     285    x = entity_pos_t::FromInt(i*2 + 1).Multiply(ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / 2);
     286    z = entity_pos_t::FromInt(j*2 + 1).Multiply(ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / 2);
     287}
     288
    272289/*
    273290We compute the territory influence of an entity with a kind of best-first search,
    274291storing an 'open' list of tiles that have not yet been processed,
     
    279296
    280297typedef PriorityQueueHeap<std::pair<u16, u16>, u32, std::greater<u32> > OpenQueue;
    281298
    282 static void ProcessNeighbour(u32 falloff, u16 i, u16 j, u32 pg, bool diagonal,
     299static void ProcessNeighbour(u32 falloff, int i, int j, u32 pg, bool diagonal,
    283300        Grid<u32>& grid, OpenQueue& queue, const Grid<u8>& costGrid)
    284301{
    285302    u32 dg = falloff * costGrid.get(i, j);
     
    294311    u32 g = pg - dg; // cost to this tile = cost to predecessor - falloff from predecessor
    295312
    296313    grid.set(i, j, g);
    297     OpenQueue::Item tile = { std::make_pair(i, j), g };
     314    OpenQueue::Item tile = { std::make_pair((u16)i, (u16)j), g };
    298315    queue.push(tile);
    299316}
    300317
    301318static void FloodFill(Grid<u32>& grid, Grid<u8>& costGrid, OpenQueue& openTiles, u32 falloff)
    302319{
    303     u16 tilesW = grid.m_W;
    304     u16 tilesH = grid.m_H;
     320    int tilesW = grid.m_W;
     321    int tilesH = grid.m_H;
    305322
    306323    while (!openTiles.empty())
    307324    {
    308325        OpenQueue::Item tile = openTiles.pop();
    309326
    310327        // Process neighbours (if they're not off the edge of the map)
    311         u16 x = tile.id.first;
    312         u16 z = tile.id.second;
     328        int x = tile.id.first;
     329        int z = tile.id.second;
    313330        if (x > 0)
    314             ProcessNeighbour(falloff, (u16)(x-1), z, tile.rank, false, grid, openTiles, costGrid);
     331            ProcessNeighbour(falloff, x-1, z, tile.rank, false, grid, openTiles, costGrid);
    315332        if (x < tilesW-1)
    316             ProcessNeighbour(falloff, (u16)(x+1), z, tile.rank, false, grid, openTiles, costGrid);
     333            ProcessNeighbour(falloff, x+1, z, tile.rank, false, grid, openTiles, costGrid);
    317334        if (z > 0)
    318             ProcessNeighbour(falloff, x, (u16)(z-1), tile.rank, false, grid, openTiles, costGrid);
     335            ProcessNeighbour(falloff, x, z-1, tile.rank, false, grid, openTiles, costGrid);
    319336        if (z < tilesH-1)
    320             ProcessNeighbour(falloff, x, (u16)(z+1), tile.rank, false, grid, openTiles, costGrid);
     337            ProcessNeighbour(falloff, x, z+1, tile.rank, false, grid, openTiles, costGrid);
    321338        if (x > 0 && z > 0)
    322             ProcessNeighbour(falloff, (u16)(x-1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);
     339            ProcessNeighbour(falloff, x-1, z-1, tile.rank, true, grid, openTiles, costGrid);
    323340        if (x > 0 && z < tilesH-1)
    324             ProcessNeighbour(falloff, (u16)(x-1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);
     341            ProcessNeighbour(falloff, x-1, z+1, tile.rank, true, grid, openTiles, costGrid);
    325342        if (x < tilesW-1 && z > 0)
    326             ProcessNeighbour(falloff, (u16)(x+1), (u16)(z-1), tile.rank, true, grid, openTiles, costGrid);
     343            ProcessNeighbour(falloff, x+1, z-1, tile.rank, true, grid, openTiles, costGrid);
    327344        if (x < tilesW-1 && z < tilesH-1)
    328             ProcessNeighbour(falloff, (u16)(x+1), (u16)(z+1), tile.rank, true, grid, openTiles, costGrid);
     345            ProcessNeighbour(falloff, x+1, z+1, tile.rank, true, grid, openTiles, costGrid);
    329346    }
    330347}
    331348
     
    343360    if (!cmpTerrain->IsLoaded())
    344361        return;
    345362
    346     u16 tilesW = cmpTerrain->GetTilesPerSide();
    347     u16 tilesH = cmpTerrain->GetTilesPerSide();
     363    CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
     364    ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("territory");
     365    ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
    348366
     367    const Grid<u16>& passGrid = cmpPathfinder->GetPassabilityGrid();
     368
     369    // Downsample the passability data to count the number of impassable
     370    // navcells per territory tile
     371    // (TODO: do we really want to average the passability per territory tile,
     372    // instead of doing min/max/etc?)
     373
     374    int tilesW = passGrid.m_W / NAVCELLS_PER_TERRITORY_TILE;
     375    int tilesH = passGrid.m_H / NAVCELLS_PER_TERRITORY_TILE;
     376
    349377    m_Territories = new Grid<u8>(tilesW, tilesH);
    350378
    351     // Compute terrain-passability-dependent costs per tile
    352     Grid<u8> influenceGrid(tilesW, tilesH);
     379    // Downsample passability grid horizontally first
     380    Grid<u16> tempPassGrid(passGrid.m_W / NAVCELLS_PER_TERRITORY_TILE, passGrid.m_H);
    353381
    354     CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
    355     ICmpPathfinder::pass_class_t passClassDefault = cmpPathfinder->GetPassabilityClass("default");
    356     ICmpPathfinder::pass_class_t passClassUnrestricted = cmpPathfinder->GetPassabilityClass("unrestricted");
     382    cassert(NAVCELLS_PER_TERRITORY_TILE < 16); // else we might overflow the counters
    357383
    358     const Grid<u16>& passGrid = cmpPathfinder->GetPassabilityGrid();
    359     for (u16 j = 0; j < tilesH; ++j)
     384    for (int j = 0; j < tempPassGrid.m_H; ++j)
    360385    {
    361         for (u16 i = 0; i < tilesW; ++i)
     386        for (int i = 0; i < tempPassGrid.m_W; ++i)
    362387        {
    363             u16 g = passGrid.get(i, j);
     388            u32 count = 0;
     389            for (u16 di = 0; di < NAVCELLS_PER_TERRITORY_TILE; ++di)
     390            {
     391                u16 g = passGrid.get(i*NAVCELLS_PER_TERRITORY_TILE + di, j);
     392                if (g & passClassUnrestricted)
     393                    count += 65536; // off the world; force maximum cost
     394                else if (g & passClassDefault)
     395                    count += 1;
     396            }
     397            tempPassGrid.set(i, j, std::min((u32)65535, count)); // clamp to avoid overflow
     398        }
     399    }
     400
     401    // Compute terrain-passability-dependent costs per tile
     402
     403    Grid<u8> influenceGrid(tilesW, tilesH);
     404    for (int j = 0; j < tilesH; ++j)
     405    {
     406        for (int i = 0; i < tilesW; ++i)
     407        {
     408            // Downsample vertically
     409            u32 count = 0;
     410            for (int dj = 0; dj < NAVCELLS_PER_TERRITORY_TILE; ++dj)
     411                count += tempPassGrid.get(i, j*NAVCELLS_PER_TERRITORY_TILE + dj);
     412
    364413            u8 cost;
    365             if (g & passClassUnrestricted)
    366                 cost = 255; // off the world; use maximum cost
    367             else if (g & passClassDefault)
    368                 cost = m_ImpassableCost;
     414            if (count >= 65535)
     415            {
     416                cost = 255; // at least partially off the world; use maximum cost
     417            }
    369418            else
    370                 cost = 1;
     419            {
     420                // Compute average cost of the cells within this tile
     421                u32 totalCells = NAVCELLS_PER_TERRITORY_TILE*NAVCELLS_PER_TERRITORY_TILE;
     422                cost = (m_ImpassableCost*count + 1*(totalCells-count)) / totalCells;
     423            }
    371424            influenceGrid.set(i, j, cost);
    372425        }
    373426    }
     
    430483
    431484            CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *eit);
    432485            CFixedVector2D pos = cmpPosition->GetPosition2D();
    433             u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
    434             u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);
     486            int i, j;
     487            NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH);
    435488
    436489            CmpPtr<ICmpTerritoryInfluence> cmpTerritoryInfluence(GetSimContext(), *eit);
    437490            u32 weight = cmpTerritoryInfluence->GetWeight();
    438             u32 radius = cmpTerritoryInfluence->GetRadius() / TERRAIN_TILE_SIZE;
     491            u32 radius = (fixed::FromInt(cmpTerritoryInfluence->GetRadius()) / (ICmpObstructionManager::NAVCELL_SIZE * NAVCELLS_PER_TERRITORY_TILE)).ToInt_RoundToNegInfinity();
    439492            u32 falloff = weight / radius; // earlier check for GetRadius() == 0 prevents divide-by-zero
    440493
    441494            // TODO: we should have some maximum value on weight, to avoid overflow
     
    451504            FloodFill(entityGrid, influenceGrid, openTiles, falloff);
    452505
    453506            // TODO: we should do a sparse grid and only add the non-zero regions, for performance
    454             for (u16 j = 0; j < entityGrid.m_H; ++j)
    455                 for (u16 i = 0; i < entityGrid.m_W; ++i)
     507            for (int j = 0; j < entityGrid.m_H; ++j)
     508                for (int i = 0; i < entityGrid.m_W; ++i)
    456509                    playerGrid.set(i, j, playerGrid.get(i, j) + entityGrid.get(i, j));
    457510        }
    458511
     
    460513    }
    461514
    462515    // Set m_Territories to the player ID with the highest influence for each tile
    463     for (u16 j = 0; j < tilesH; ++j)
     516    for (int j = 0; j < tilesH; ++j)
    464517    {
    465         for (u16 i = 0; i < tilesW; ++i)
     518        for (int i = 0; i < tilesW; ++i)
    466519        {
    467520            u32 bestWeight = 0;
    468521            for (size_t k = 0; k < playerGrids.size(); ++k)
     
    487540        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), *it);
    488541
    489542        CFixedVector2D pos = cmpPosition->GetPosition2D();
    490         u16 i = (u16)clamp((pos.X / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesW-1);
    491         u16 j = (u16)clamp((pos.Y / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNegInfinity(), 0, tilesH-1);
     543        int i, j;
     544        NearestTerritoryTile(pos.X, pos.Y, i, j, tilesW, tilesH);
    492545
    493546        u8 owner = (u8)cmpOwnership->GetOwner();
    494547
     
    500553
    501554        Grid<u8>& grid = *m_Territories;
    502555
    503         u16 maxi = (u16)(grid.m_W-1);
    504         u16 maxj = (u16)(grid.m_H-1);
     556        int maxi = grid.m_W-1;
     557        int maxj = grid.m_H-1;
    505558
    506559        std::vector<std::pair<u16, u16> > tileStack;
    507560
    508 #define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, owner | TERRITORY_CONNECTED_MASK); tileStack.push_back(std::make_pair(i, j)); )
     561#define MARK_AND_PUSH(i, j) STMT(grid.set(i, j, owner | TERRITORY_CONNECTED_MASK); tileStack.push_back(std::make_pair((u16)(i), (u16)(j))); )
    509562
    510563        MARK_AND_PUSH(i, j);
    511564        while (!tileStack.empty())
     
    537590    }
    538591}
    539592
    540 /**
    541  * Compute the tile indexes on the grid nearest to a given point
    542  */
    543 static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
    544 {
    545     i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1);
    546     j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1);
    547 }
    548 
    549 /**
    550  * Returns the position of the center of the given tile
    551  */
    552 static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
    553 {
    554     x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
    555     z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
    556 }
    557 
    558 // TODO: would be nice not to duplicate those two functions from CCmpObstructionManager.cpp
    559 
    560 
    561593void CCmpTerritoryManager::RasteriseInfluences(CComponentManager::InterfaceList& infls, Grid<u8>& grid)
    562594{
    563595    for (CComponentManager::InterfaceList::iterator it = infls.begin(); it != infls.end(); ++it)
     
    579611        CFixedVector2D halfSize(square.hw, square.hh);
    580612        CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize);
    581613
    582         u16 i0, j0, i1, j1;
    583         NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
    584         NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
    585         for (u16 j = j0; j <= j1; ++j)
     614        int i0, j0, i1, j1;
     615        NearestTerritoryTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
     616        NearestTerritoryTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
     617        for (int j = j0; j <= j1; ++j)
    586618        {
    587             for (u16 i = i0; i <= i1; ++i)
     619            for (int i = i0; i <= i1; ++i)
    588620            {
    589621                entity_pos_t x, z;
    590                 TileCenter(i, j, x, z);
     622                TerritoryTileCenter(i, j, x, z);
    591623                if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize))
    592624                    grid.set(i, j, (u8)cost);
    593625            }
     
    714746
    715747player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
    716748{
    717     u16 i, j;
    718749    CalculateTerritories();
    719750    if (!m_Territories)
    720751        return 0;
    721752
    722     NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     753    int i, j;
     754    NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
    723755    return m_Territories->get(i, j) & TERRITORY_PLAYER_MASK;
    724756}
    725757
    726758bool CCmpTerritoryManager::IsConnected(entity_pos_t x, entity_pos_t z)
    727759{
    728     u16 i, j;
    729760    CalculateTerritories();
    730761    if (!m_Territories)
    731762        return false;
    732763
    733     NearestTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
     764    int i, j;
     765    NearestTerritoryTile(x, z, i, j, m_Territories->m_W, m_Territories->m_H);
    734766    return (m_Territories->get(i, j) & TERRITORY_CONNECTED_MASK) != 0;
    735767}
    736768
    737 TerritoryOverlay::TerritoryOverlay(CCmpTerritoryManager& manager)
    738     : TerrainOverlay(manager.GetSimContext()), m_TerritoryManager(manager)
    739 { }
    740 
    741 void TerritoryOverlay::StartRender()
     769TerritoryOverlay::TerritoryOverlay(CCmpTerritoryManager& manager) :
     770    TerrainTextureOverlay((float)ICmpObstructionManager::NAVCELLS_PER_TILE / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE),
     771    m_TerritoryManager(manager)
    742772{
    743     m_TerritoryManager.CalculateTerritories();
    744773}
    745774
    746 void TerritoryOverlay::ProcessTile(ssize_t i, ssize_t j)
     775void TerritoryOverlay::BuildTextureRGBA(u8* data, size_t w, size_t h)
    747776{
    748     if (!m_TerritoryManager.m_Territories)
    749         return;
     777    m_TerritoryManager.CalculateTerritories();
    750778
    751     u8 id = (m_TerritoryManager.m_Territories->get((int) i, (int) j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK);
     779    for (size_t j = 0; j < h; ++j)
     780    {
     781        for (size_t i = 0; i < w; ++i)
     782        {
     783            SColor4ub color;
    752784
    753     float a = 0.2f;
    754     switch (id)
    755     {
    756     case 0:  break;
    757     case 1:  RenderTile(CColor(1, 0, 0, a), false); break;
    758     case 2:  RenderTile(CColor(0, 1, 0, a), false); break;
    759     case 3:  RenderTile(CColor(0, 0, 1, a), false); break;
    760     case 4:  RenderTile(CColor(1, 1, 0, a), false); break;
    761     case 5:  RenderTile(CColor(0, 1, 1, a), false); break;
    762     case 6:  RenderTile(CColor(1, 0, 1, a), false); break;
    763     default: RenderTile(CColor(1, 1, 1, a), false); break;
     785            u8 id = (m_TerritoryManager.m_Territories->get((int)i, (int)j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK);
     786            color = GetColor(id, 64);
     787
     788            *data++ = color.R;
     789            *data++ = color.G;
     790            *data++ = color.B;
     791            *data++ = color.A;
     792        }
    764793    }
    765794}
  • source/simulation2/components/CCmpRangeManager.cpp

     
    2222
    2323#include "simulation2/MessageTypes.h"
    2424#include "simulation2/components/ICmpPosition.h"
     25#include "simulation2/components/ICmpObstructionManager.h"
    2526#include "simulation2/components/ICmpTerritoryManager.h"
    2627#include "simulation2/components/ICmpVision.h"
    2728#include "simulation2/helpers/Render.h"
     
    10721073        if (!cmpTerritoryManager || !cmpTerritoryManager->NeedUpdate(&m_TerritoriesDirtyID))
    10731074            return;
    10741075
     1076        PROFILE3("UpdateTerritoriesLos");
     1077
    10751078        const Grid<u8>& grid = cmpTerritoryManager->GetTerritoryGrid();
    1076         ENSURE(grid.m_W == m_TerrainVerticesPerSide-1 && grid.m_H == m_TerrainVerticesPerSide-1);
    10771079
    1078         // For each tile, if it is owned by a valid player then update the LOS
    1079         // for every vertex around that tile, to mark them as explored
     1080        // Territory data is stored per territory-tile (typically a multiple of terrain-tiles).
     1081        // LOS data is stored per terrain-tile vertex.
    10801082
     1083        // For each territory-tile, if it is owned by a valid player then update the LOS
     1084        // for every vertex inside/around that tile, to mark them as explored.
     1085
     1086        // Currently this code doesn't support territory-tiles smaller than terrain-tiles
     1087        // (it will get scale==0 and break), or a non-integer multiple, so check that first
     1088        cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE >= ICmpObstructionManager::NAVCELLS_PER_TILE);
     1089        cassert(ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE % ICmpObstructionManager::NAVCELLS_PER_TILE == 0);
     1090
     1091        int scale = ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE / ICmpObstructionManager::NAVCELLS_PER_TILE;
     1092
     1093        ENSURE(grid.m_W*scale == m_TerrainVerticesPerSide-1 && grid.m_H*scale == m_TerrainVerticesPerSide-1);
     1094
    10811095        for (u16 j = 0; j < grid.m_H; ++j)
    10821096        {
    10831097            for (u16 i = 0; i < grid.m_W; ++i)
     
    10851099                u8 p = grid.get(i, j) & ICmpTerritoryManager::TERRITORY_PLAYER_MASK;
    10861100                if (p > 0 && p <= MAX_LOS_PLAYER_ID)
    10871101                {
    1088                     m_LosState[i + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
    1089                     m_LosState[i+1 + j*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
    1090                     m_LosState[i + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
    1091                     m_LosState[i+1 + (j+1)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
     1102                    for (int dj = 0; dj <= scale; ++dj)
     1103                        for (int di = 0; di <= scale; ++di)
     1104                            m_LosState[(i*scale+di) + (j*scale+dj)*m_TerrainVerticesPerSide] |= (LOS_EXPLORED << (2*(p-1)));
    10921105                }
    10931106            }
    10941107        }
  • source/simulation2/components/CCmpRallyPointRenderer.cpp

     
    6969{
    7070    // import some types for less verbosity
    7171    typedef ICmpPathfinder::Path Path;
    72     typedef ICmpPathfinder::Goal Goal;
     72    typedef PathGoal Goal;
    7373    typedef ICmpPathfinder::Waypoint Waypoint;
    7474    typedef ICmpRangeManager::CLosQuerier CLosQuerier;
    7575    typedef SOverlayTexturedLine::LineCapType LineCapType;
     
    115115    std::wstring m_LineTexturePath;
    116116    std::wstring m_LineTextureMaskPath;
    117117    std::string m_LinePassabilityClass; ///< Pathfinder passability class to use for computing the (long-range) marker line path.
    118     std::string m_LineCostClass; ///< Pathfinder cost class to use for computing the (long-range) marker line path.
    119118
    120119    CTexturePtr m_Texture;
    121120    CTexturePtr m_TextureMask;
     
    141140                "<LineEndCap>square</LineEndCap>"
    142141                "<LineColour r='20' g='128' b='240'></LineColour>"
    143142                "<LineDashColour r='158' g='11' b='15'></LineDashColour>"
    144                 "<LineCostClass>default</LineCostClass>"
    145143                "<LinePassabilityClass>default</LinePassabilityClass>"
    146144            "</a:example>"
    147145            "<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor)'>"
     
    196194            "</element>"
    197195            "<element name='LinePassabilityClass' a:help='The pathfinder passability class to use for computing the rally point marker line path'>"
    198196                "<text />"
    199             "</element>"
    200             "<element name='LineCostClass' a:help='The pathfinder cost class to use for computing the rally point marker line path'>"
    201                 "<text />"
    202197            "</element>";
    203198    }
    204199
     
    440435    m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString();
    441436    m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString());
    442437    m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString());
    443     m_LineCostClass = paramNode.GetChild("LineCostClass").ToUTF8();
    444438    m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8();
    445439
    446440    // ---------------------------------------------------------------------------------------------
     
    612606        pathStartY,
    613607        goal,
    614608        cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass),
    615         cmpPathfinder->GetCostClass(m_LineCostClass),
    616609        path
    617610    );
    618611
  • source/simulation2/components/CCmpPathfinder_Vertex.cpp

     
    101101};
    102102
    103103// Obstruction edges (paths will not cross any of these).
    104 // When used in the 'edges' list, defines the two points of the edge.
    105 // When used in the 'edgesAA' list, defines the opposing corners of an axis-aligned square
    106 // (from which four individual edges can be trivially computed), requiring p0 <= p1
     104// Defines the two points of the edge.
    107105struct Edge
    108106{
    109107    CFixedVector2D p0, p1;
    110108};
    111109
     110// Axis-aligned obstruction squares (paths will not cross any of these).
     111// Defines the opposing corners of an axis-aligned square
     112// (from which four individual edges can be trivially computed), requiring p0 <= p1
     113struct Square
     114{
     115    CFixedVector2D p0, p1;
     116};
     117
    112118// Axis-aligned obstruction edges.
    113119// p0 defines one end; c1 is either the X or Y coordinate of the other end,
    114120// depending on the context in which this is used.
     
    284290}
    285291
    286292
    287 static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal)
     293static CFixedVector2D NearestPointOnGoal(CFixedVector2D pos, const PathGoal& goal)
    288294{
    289295    CFixedVector2D g(goal.x, goal.z);
    290296
    291297    switch (goal.type)
    292298    {
    293     case CCmpPathfinder::Goal::POINT:
     299    case PathGoal::POINT:
    294300    {
    295301        return g;
    296302    }
    297303
    298     case CCmpPathfinder::Goal::CIRCLE:
     304    case PathGoal::CIRCLE:
     305    case PathGoal::INVERTED_CIRCLE:
    299306    {
    300307        CFixedVector2D d = pos - g;
    301308        if (d.IsZero())
     
    304311        return g + d;
    305312    }
    306313
    307     case CCmpPathfinder::Goal::SQUARE:
     314    case PathGoal::SQUARE:
     315    case PathGoal::INVERTED_SQUARE:
    308316    {
    309317        CFixedVector2D halfSize(goal.hw, goal.hh);
    310318        CFixedVector2D d = pos - g;
     
    317325    }
    318326}
    319327
    320 CFixedVector2D CCmpPathfinder::GetNearestPointOnGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal)
     328CFixedVector2D CCmpPathfinder::GetNearestPointOnGoal(CFixedVector2D pos, const PathGoal& goal)
    321329{
    322330    return NearestPointOnGoal(pos, goal);
    323331    // (It's intentional that we don't put the implementation inside this
     
    327335
    328336typedef PriorityQueueHeap<u16, fixed> PriorityQueue;
    329337
    330 struct TileEdge
     338/**
     339 * Add edges and vertexes to represent the boundaries between passable and impassable
     340 * navcells (for impassable terrain and for static obstruction shapes).
     341 * Navcells i0 <= i <= i1, j0 <= j <= j1 will be considered.
     342 */
     343static void AddTerrainEdges(std::vector<Edge>& edges, std::vector<Vertex>& vertexes,
     344    int i0, int j0, int i1, int j1,
     345    ICmpPathfinder::pass_class_t passClass, const Grid<NavcellData>& grid)
    331346{
    332     u16 i, j;
    333     enum { TOP, BOTTOM, LEFT, RIGHT } dir;
    334 };
    335 
    336 static void AddTerrainEdges(std::vector<Edge>& edgesAA, std::vector<Vertex>& vertexes,
    337     u16 i0, u16 j0, u16 i1, u16 j1, fixed r,
    338     ICmpPathfinder::pass_class_t passClass, const Grid<TerrainTile>& terrain)
    339 {
    340347    PROFILE("AddTerrainEdges");
    341348
    342     std::vector<TileEdge> tileEdges;
     349    // Clamp the coordinates so we won't attempt to sample outside of the grid.
     350    // (This assumes the outermost ring of navcells (which are always impassable)
     351    // won't have a boundary with any passable navcells. TODO: is that definitely
     352    // safe enough?)
    343353
    344     // Find all edges between tiles of differently passability statuses
    345     for (u16 j = j0; j <= j1; ++j)
     354    i0 = clamp(i0, 1, grid.m_W-2);
     355    j0 = clamp(j0, 1, grid.m_H-2);
     356    i1 = clamp(i1, 1, grid.m_W-2);
     357    j1 = clamp(j1, 1, grid.m_H-2);
     358
     359    for (int j = j0; j <= j1; ++j)
    346360    {
    347         for (u16 i = i0; i <= i1; ++i)
     361        for (int i = i0; i <= i1; ++i)
    348362        {
    349             if (!IS_TERRAIN_PASSABLE(terrain.get(i, j), passClass))
     363            if (IS_PASSABLE(grid.get(i, j), passClass))
     364                continue;
     365
     366            if (IS_PASSABLE(grid.get(i+1, j), passClass) && IS_PASSABLE(grid.get(i, j+1), passClass) && IS_PASSABLE(grid.get(i+1, j+1), passClass))
    350367            {
    351                 bool any = false; // whether we're adding any edges of this tile
     368                Vertex vert;
     369                vert.status = Vertex::UNEXPLORED;
     370                vert.quadOutward = QUADRANT_ALL;
     371                vert.quadInward = QUADRANT_BL;
     372                vert.p = CFixedVector2D(fixed::FromInt(i+1)+EDGE_EXPAND_DELTA, fixed::FromInt(j+1)+EDGE_EXPAND_DELTA).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     373                vertexes.push_back(vert);
     374            }
    352375
    353                 if (j > 0 && IS_TERRAIN_PASSABLE(terrain.get(i, j-1), passClass))
    354                 {
    355                     TileEdge e = { i, j, TileEdge::BOTTOM };
    356                     tileEdges.push_back(e);
    357                     any = true;
    358                 }
     376            if (IS_PASSABLE(grid.get(i-1, j), passClass) && IS_PASSABLE(grid.get(i, j+1), passClass) && IS_PASSABLE(grid.get(i-1, j+1), passClass))
     377            {
     378                Vertex vert;
     379                vert.status = Vertex::UNEXPLORED;
     380                vert.quadOutward = QUADRANT_ALL;
     381                vert.quadInward = QUADRANT_BR;
     382                vert.p = CFixedVector2D(fixed::FromInt(i)-EDGE_EXPAND_DELTA, fixed::FromInt(j+1)+EDGE_EXPAND_DELTA).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     383                vertexes.push_back(vert);
     384            }
    359385
    360                 if (j < terrain.m_H-1 && IS_TERRAIN_PASSABLE(terrain.get(i, j+1), passClass))
    361                 {
    362                     TileEdge e = { i, j, TileEdge::TOP };
    363                     tileEdges.push_back(e);
    364                     any = true;
    365                 }
     386            if (IS_PASSABLE(grid.get(i+1, j), passClass) && IS_PASSABLE(grid.get(i, j-1), passClass) && IS_PASSABLE(grid.get(i+1, j-1), passClass))
     387            {
     388                Vertex vert;
     389                vert.status = Vertex::UNEXPLORED;
     390                vert.quadOutward = QUADRANT_ALL;
     391                vert.quadInward = QUADRANT_TL;
     392                vert.p = CFixedVector2D(fixed::FromInt(i+1)+EDGE_EXPAND_DELTA, fixed::FromInt(j)-EDGE_EXPAND_DELTA).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     393                vertexes.push_back(vert);
     394            }
    366395
    367                 if (i > 0 && IS_TERRAIN_PASSABLE(terrain.get(i-1, j), passClass))
     396            if (IS_PASSABLE(grid.get(i-1, j), passClass) && IS_PASSABLE(grid.get(i, j-1), passClass) && IS_PASSABLE(grid.get(i-1, j-1), passClass))
     397            {
     398                Vertex vert;
     399                vert.status = Vertex::UNEXPLORED;
     400                vert.quadOutward = QUADRANT_ALL;
     401                vert.quadInward = QUADRANT_TR;
     402                vert.p = CFixedVector2D(fixed::FromInt(i)-EDGE_EXPAND_DELTA, fixed::FromInt(j)-EDGE_EXPAND_DELTA).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     403                vertexes.push_back(vert);
     404            }
     405        }
     406    }
     407
     408    // XXX rewrite this stuff
     409
     410    for (int j = j0; j < j1; ++j)
     411    {
     412        std::vector<u16> segmentsR;
     413        std::vector<u16> segmentsL;
     414
     415        for (int i = i0; i <= i1; ++i)
     416        {
     417            bool a = IS_PASSABLE(grid.get(i, j+1), passClass);
     418            bool b = IS_PASSABLE(grid.get(i, j), passClass);
     419            if (a && !b)
     420                segmentsL.push_back(i);
     421            if (b && !a)
     422                segmentsR.push_back(i);
     423        }
     424
     425        if (!segmentsR.empty())
     426        {
     427            segmentsR.push_back(0); // sentinel value to simplify the loop
     428            u16 ia = segmentsR[0];
     429            u16 ib = ia + 1;
     430            for (size_t n = 1; n < segmentsR.size(); ++n)
     431            {
     432                if (segmentsR[n] == ib)
     433                    ++ib;
     434                else
    368435                {
    369                     TileEdge e = { i, j, TileEdge::LEFT };
    370                     tileEdges.push_back(e);
    371                     any = true;
    372                 }
     436                    CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(ia), fixed::FromInt(j+1)).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     437                    CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(ib), fixed::FromInt(j+1)).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     438                    Edge e = { v0, v1 };
     439                    edges.push_back(e);
    373440
    374                 if (i < terrain.m_W-1 && IS_TERRAIN_PASSABLE(terrain.get(i+1, j), passClass))
    375                 {
    376                     TileEdge e = { i, j, TileEdge::RIGHT };
    377                     tileEdges.push_back(e);
    378                     any = true;
     441                    ia = segmentsR[n];
     442                    ib = ia + 1;
    379443                }
     444            }
     445        }
    380446
    381                 // If we want to add any edge, then add the whole square to the axis-aligned-edges list.
    382                 // (The inner edges are redundant but it's easier than trying to split the squares apart.)
    383                 if (any)
     447        if (!segmentsL.empty())
     448        {
     449            segmentsL.push_back(0); // sentinel value to simplify the loop
     450            u16 ia = segmentsL[0];
     451            u16 ib = ia + 1;
     452            for (size_t n = 1; n < segmentsL.size(); ++n)
     453            {
     454                if (segmentsL[n] == ib)
     455                    ++ib;
     456                else
    384457                {
    385                     CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
    386                     CFixedVector2D v1 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
     458                    CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(ib), fixed::FromInt(j+1)).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     459                    CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(ia), fixed::FromInt(j+1)).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
    387460                    Edge e = { v0, v1 };
    388                     edgesAA.push_back(e);
     461                    edges.push_back(e);
     462
     463                    ia = segmentsL[n];
     464                    ib = ia + 1;
    389465                }
    390466            }
    391467        }
    392468    }
    393469
    394     // TODO: maybe we should precompute these terrain edges since they'll rarely change?
    395 
    396     // TODO: for efficiency (minimising the A* search space), we should coalesce adjoining edges
    397 
    398     // Add all the tile outer edges to the search vertex lists
    399     for (size_t n = 0; n < tileEdges.size(); ++n)
     470    for (int i = i0; i < i1; ++i)
    400471    {
    401         u16 i = tileEdges[n].i;
    402         u16 j = tileEdges[n].j;
    403         CFixedVector2D v0, v1;
    404         Vertex vert;
    405         vert.status = Vertex::UNEXPLORED;
    406         vert.quadOutward = QUADRANT_ALL;
     472        std::vector<u16> segmentsU;
     473        std::vector<u16> segmentsD;
    407474
    408         switch (tileEdges[n].dir)
     475        for (int j = j0; j <= j1; ++j)
    409476        {
    410         case TileEdge::BOTTOM:
    411         {
    412             v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
    413             v1 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
    414             vert.p.X = v0.X - EDGE_EXPAND_DELTA; vert.p.Y = v0.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TR; vertexes.push_back(vert);
    415             vert.p.X = v1.X + EDGE_EXPAND_DELTA; vert.p.Y = v1.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TL; vertexes.push_back(vert);
    416             break;
     477            bool a = IS_PASSABLE(grid.get(i+1, j), passClass);
     478            bool b = IS_PASSABLE(grid.get(i, j), passClass);
     479            if (a && !b)
     480                segmentsU.push_back(j);
     481            if (b && !a)
     482                segmentsD.push_back(j);
    417483        }
    418         case TileEdge::TOP:
     484
     485        if (!segmentsU.empty())
    419486        {
    420             v0 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
    421             v1 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
    422             vert.p.X = v0.X + EDGE_EXPAND_DELTA; vert.p.Y = v0.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BL; vertexes.push_back(vert);
    423             vert.p.X = v1.X - EDGE_EXPAND_DELTA; vert.p.Y = v1.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BR; vertexes.push_back(vert);
    424             break;
     487            segmentsU.push_back(0); // sentinel value to simplify the loop
     488            u16 ja = segmentsU[0];
     489            u16 jb = ja + 1;
     490            for (size_t n = 1; n < segmentsU.size(); ++n)
     491            {
     492                if (segmentsU[n] == jb)
     493                    ++jb;
     494                else
     495                {
     496                    CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(ja)).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     497                    CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(jb)).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     498                    Edge e = { v0, v1 };
     499                    edges.push_back(e);
     500
     501                    ja = segmentsU[n];
     502                    jb = ja + 1;
     503                }
     504            }
    425505        }
    426         case TileEdge::LEFT:
     506
     507        if (!segmentsD.empty())
    427508        {
    428             v0 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
    429             v1 = CFixedVector2D(fixed::FromInt(i * (int)TERRAIN_TILE_SIZE) - r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
    430             vert.p.X = v0.X - EDGE_EXPAND_DELTA; vert.p.Y = v0.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BR; vertexes.push_back(vert);
    431             vert.p.X = v1.X - EDGE_EXPAND_DELTA; vert.p.Y = v1.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TR; vertexes.push_back(vert);
    432             break;
     509            segmentsD.push_back(0); // sentinel value to simplify the loop
     510            u16 ja = segmentsD[0];
     511            u16 jb = ja + 1;
     512            for (size_t n = 1; n < segmentsD.size(); ++n)
     513            {
     514                if (segmentsD[n] == jb)
     515                    ++jb;
     516                else
     517                {
     518                    CFixedVector2D v0 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(jb)).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     519                    CFixedVector2D v1 = CFixedVector2D(fixed::FromInt(i+1), fixed::FromInt(ja)).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     520                    Edge e = { v0, v1 };
     521                    edges.push_back(e);
     522
     523                    ja = segmentsD[n];
     524                    jb = ja + 1;
     525                }
     526            }
    433527        }
    434         case TileEdge::RIGHT:
    435         {
    436             v0 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt(j * (int)TERRAIN_TILE_SIZE) - r);
    437             v1 = CFixedVector2D(fixed::FromInt((i+1) * (int)TERRAIN_TILE_SIZE) + r, fixed::FromInt((j+1) * (int)TERRAIN_TILE_SIZE) + r);
    438             vert.p.X = v0.X + EDGE_EXPAND_DELTA; vert.p.Y = v0.Y - EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_TL; vertexes.push_back(vert);
    439             vert.p.X = v1.X + EDGE_EXPAND_DELTA; vert.p.Y = v1.Y + EDGE_EXPAND_DELTA; vert.quadInward = QUADRANT_BL; vertexes.push_back(vert);
    440             break;
    441         }
    442         }
    443528    }
    444529}
    445530
    446531static void SplitAAEdges(CFixedVector2D a,
    447         const std::vector<Edge>& edgesAA,
     532        const std::vector<Edge>& edges,
     533        const std::vector<Square>& squares,
     534        std::vector<Edge>& edgesUnaligned,
    448535        std::vector<EdgeAA>& edgesLeft, std::vector<EdgeAA>& edgesRight,
    449536        std::vector<EdgeAA>& edgesBottom, std::vector<EdgeAA>& edgesTop)
    450537{
    451     edgesLeft.reserve(edgesAA.size());
    452     edgesRight.reserve(edgesAA.size());
    453     edgesBottom.reserve(edgesAA.size());
    454     edgesTop.reserve(edgesAA.size());
     538    edgesLeft.reserve(squares.size());
     539    edgesRight.reserve(squares.size());
     540    edgesBottom.reserve(squares.size());
     541    edgesTop.reserve(squares.size());
    455542
    456     for (size_t i = 0; i < edgesAA.size(); ++i)
     543    for (size_t i = 0; i < squares.size(); ++i)
    457544    {
    458         if (a.X <= edgesAA[i].p0.X)
     545        if (a.X <= squares[i].p0.X)
    459546        {
    460             EdgeAA e = { edgesAA[i].p0, edgesAA[i].p1.Y };
     547            EdgeAA e = { squares[i].p0, squares[i].p1.Y };
    461548            edgesLeft.push_back(e);
    462549        }
    463         if (a.X >= edgesAA[i].p1.X)
     550        if (a.X >= squares[i].p1.X)
    464551        {
    465             EdgeAA e = { edgesAA[i].p1, edgesAA[i].p0.Y };
     552            EdgeAA e = { squares[i].p1, squares[i].p0.Y };
    466553            edgesRight.push_back(e);
    467554        }
    468         if (a.Y <= edgesAA[i].p0.Y)
     555        if (a.Y <= squares[i].p0.Y)
    469556        {
    470             EdgeAA e = { edgesAA[i].p0, edgesAA[i].p1.X };
     557            EdgeAA e = { squares[i].p0, squares[i].p1.X };
    471558            edgesBottom.push_back(e);
    472559        }
    473         if (a.Y >= edgesAA[i].p1.Y)
     560        if (a.Y >= squares[i].p1.Y)
    474561        {
    475             EdgeAA e = { edgesAA[i].p1, edgesAA[i].p0.X };
     562            EdgeAA e = { squares[i].p1, squares[i].p0.X };
    476563            edgesTop.push_back(e);
    477564        }
    478565    }
     566
     567    for (size_t i = 0; i < edges.size(); ++i)
     568    {
     569        if (edges[i].p0.X == edges[i].p1.X)
     570        {
     571            if (edges[i].p1.Y < edges[i].p0.Y)
     572            {
     573                if (!(a.X <= edges[i].p0.X))
     574                    continue;
     575                EdgeAA e = { edges[i].p1, edges[i].p0.Y };
     576                edgesLeft.push_back(e);
     577            }
     578            else
     579            {
     580                if (!(a.X >= edges[i].p0.X))
     581                    continue;
     582                EdgeAA e = { edges[i].p1, edges[i].p0.Y };
     583                edgesRight.push_back(e);
     584            }
     585        }
     586        else if (edges[i].p0.Y == edges[i].p1.Y)
     587        {
     588            if (edges[i].p0.X < edges[i].p1.X)
     589            {
     590                if (!(a.Y <= edges[i].p0.Y))
     591                    continue;
     592                EdgeAA e = { edges[i].p0, edges[i].p1.X };
     593                edgesBottom.push_back(e);
     594            }
     595            else
     596            {
     597                if (!(a.Y >= edges[i].p0.Y))
     598                    continue;
     599                EdgeAA e = { edges[i].p0, edges[i].p1.X };
     600                edgesTop.push_back(e);
     601            }
     602        }
     603        else
     604        {
     605            edgesUnaligned.push_back(edges[i]);
     606        }
     607    }
    479608}
    480609
    481610/**
    482  * Functor for sorting edges by approximate proximity to a fixed point.
     611 * Functor for sorting edge-squares by approximate proximity to a fixed point.
    483612 */
    484 struct EdgeSort
     613struct SquareSort
    485614{
    486615    CFixedVector2D src;
    487     EdgeSort(CFixedVector2D src) : src(src) { }
    488     bool operator()(const Edge& a, const Edge& b)
     616    SquareSort(CFixedVector2D src) : src(src) { }
     617    bool operator()(const Square& a, const Square& b)
    489618    {
    490619        if ((a.p0 - src).CompareLength(b.p0 - src) < 0)
    491620            return true;
     
    495624
    496625void CCmpPathfinder::ComputeShortPath(const IObstructionTestFilter& filter,
    497626    entity_pos_t x0, entity_pos_t z0, entity_pos_t r,
    498     entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& path)
     627    entity_pos_t range, const PathGoal& goal, pass_class_t passClass, Path& path)
    499628{
    500629    UpdateGrid(); // TODO: only need to bother updating if the terrain changed
    501630
    502631    PROFILE3("ComputeShortPath");
    503 //  ScopeTimer UID__(L"ComputeShortPath");
     632    TIMER(L"ComputeShortPath");
    504633
    505634    m_DebugOverlayShortPathLines.clear();
    506635
     
    511640        m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 0, 0, 1);
    512641        switch (goal.type)
    513642        {
    514         case CCmpPathfinder::Goal::POINT:
     643        case PathGoal::POINT:
    515644        {
    516645            SimRender::ConstructCircleOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), 0.2f, m_DebugOverlayShortPathLines.back(), true);
    517646            break;
    518647        }
    519         case CCmpPathfinder::Goal::CIRCLE:
     648        case PathGoal::CIRCLE:
     649        case PathGoal::INVERTED_CIRCLE:
    520650        {
    521651            SimRender::ConstructCircleOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat(), m_DebugOverlayShortPathLines.back(), true);
    522652            break;
    523653        }
    524         case CCmpPathfinder::Goal::SQUARE:
     654        case PathGoal::SQUARE:
     655        case PathGoal::INVERTED_SQUARE:
    525656        {
    526657            float a = atan2f(goal.v.X.ToFloat(), goal.v.Y.ToFloat());
    527658            SimRender::ConstructSquareOnGround(GetSimContext(), goal.x.ToFloat(), goal.z.ToFloat(), goal.hw.ToFloat()*2, goal.hh.ToFloat()*2, a, m_DebugOverlayShortPathLines.back(), true);
     
    533664    // List of collision edges - paths must never cross these.
    534665    // (Edges are one-sided so intersections are fine in one direction, but not the other direction.)
    535666    std::vector<Edge> edges;
    536     std::vector<Edge> edgesAA; // axis-aligned squares
     667    std::vector<Square> edgeSquares; // axis-aligned squares; equivalent to 4 edges
    537668
    538669    // Create impassable edges at the max-range boundary, so we can't escape the region
    539670    // where we're meant to be searching
     
    574705    // Add terrain obstructions
    575706    {
    576707        u16 i0, j0, i1, j1;
    577         NearestTile(rangeXMin, rangeZMin, i0, j0);
    578         NearestTile(rangeXMax, rangeZMax, i1, j1);
    579         AddTerrainEdges(edgesAA, vertexes, i0, j0, i1, j1, r, passClass, *m_Grid);
     708        NearestNavcell(rangeXMin, rangeZMin, i0, j0);
     709        NearestNavcell(rangeXMax, rangeZMax, i1, j1);
     710        AddTerrainEdges(edges, vertexes, i0, j0, i1, j1, passClass, *m_Grid);
    580711    }
    581712
    582713    // Find all the obstruction squares that might affect us
     
    586717
    587718    // Resize arrays to reduce reallocations
    588719    vertexes.reserve(vertexes.size() + squares.size()*4);
    589     edgesAA.reserve(edgesAA.size() + squares.size()); // (assume most squares are AA)
     720    edgeSquares.reserve(edgeSquares.size() + squares.size()); // (assume most squares are AA)
    590721
    591722    // Convert each obstruction square into collision edges and search graph vertexes
    592723    for (size_t i = 0; i < squares.size(); ++i)
     
    615746
    616747        // Add the edges:
    617748
    618         CFixedVector2D h0(squares[i].hw + r, squares[i].hh + r);
     749        CFixedVector2D h0(squares[i].hw + r,   squares[i].hh + r);
    619750        CFixedVector2D h1(squares[i].hw + r, -(squares[i].hh + r));
    620751
    621752        CFixedVector2D ev0(center.X - h0.Dot(u), center.Y + h0.Dot(v));
     
    624755        CFixedVector2D ev3(center.X + h1.Dot(u), center.Y - h1.Dot(v));
    625756        if (aa)
    626757        {
    627             Edge e = { ev1, ev3 };
    628             edgesAA.push_back(e);
     758            Square e = { ev1, ev3 };
     759            edgeSquares.push_back(e);
    629760        }
    630761        else
    631762        {
     
    645776
    646777    ENSURE(vertexes.size() < 65536); // we store array indexes as u16
    647778
     779    // Render the debug overlay
    648780    if (m_DebugOverlay)
    649781    {
    650         // Render the obstruction edges
     782#define PUSH_POINT(p) xz.push_back(p.X.ToFloat()); xz.push_back(p.Y.ToFloat());
     783        // Render the vertexes as little Pac-Man shapes to indicate quadrant direction
     784        for (size_t i = 0; i < vertexes.size(); ++i)
     785        {
     786            m_DebugOverlayShortPathLines.push_back(SOverlayLine());
     787            m_DebugOverlayShortPathLines.back().m_Color = CColor(1, 1, 0, 1);
     788
     789            float x = vertexes[i].p.X.ToFloat();
     790            float z = vertexes[i].p.Y.ToFloat();
     791
     792            float a0 = 0, a1 = 0;
     793            // Get arc start/end angles depending on quadrant (if any)
     794            if      (vertexes[i].quadInward == QUADRANT_BL) { a0 = -0.25f; a1 = 0.50f; }
     795            else if (vertexes[i].quadInward == QUADRANT_TR) { a0 =  0.25f; a1 = 1.00f; }
     796            else if (vertexes[i].quadInward == QUADRANT_TL) { a0 = -0.50f; a1 = 0.25f; }
     797            else if (vertexes[i].quadInward == QUADRANT_BR) { a0 =  0.00f; a1 = 0.75f; }
     798
     799            if (a0 == a1)
     800                SimRender::ConstructCircleOnGround(GetSimContext(), x, z, 0.5f,
     801                    m_DebugOverlayShortPathLines.back(), true);
     802            else
     803                SimRender::ConstructClosedArcOnGround(GetSimContext(), x, z, 0.5f,
     804                    a0 * ((float)M_PI*2.0f), a1 * ((float)M_PI*2.0f),
     805                    m_DebugOverlayShortPathLines.back(), true);
     806        }
     807
     808        // Render the edges
    651809        for (size_t i = 0; i < edges.size(); ++i)
    652810        {
    653811            m_DebugOverlayShortPathLines.push_back(SOverlayLine());
    654812            m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1);
    655813            std::vector<float> xz;
    656             xz.push_back(edges[i].p0.X.ToFloat());
    657             xz.push_back(edges[i].p0.Y.ToFloat());
    658             xz.push_back(edges[i].p1.X.ToFloat());
    659             xz.push_back(edges[i].p1.Y.ToFloat());
     814            PUSH_POINT(edges[i].p0);
     815            PUSH_POINT(edges[i].p1);
     816
     817            // Add an arrowhead to indicate the direction
     818            CFixedVector2D d = edges[i].p1 - edges[i].p0;
     819            d.Normalize(fixed::FromInt(1)/8);
     820            CFixedVector2D p2 = edges[i].p1 - d*2;
     821            CFixedVector2D p3 = p2 + d.Perpendicular();
     822            CFixedVector2D p4 = p2 - d.Perpendicular();
     823            PUSH_POINT(p3);
     824            PUSH_POINT(p4);
     825            PUSH_POINT(edges[i].p1);
     826
    660827            SimRender::ConstructLineOnGround(GetSimContext(), xz, m_DebugOverlayShortPathLines.back(), true);
    661828        }
     829#undef PUSH_POINT
    662830
    663         for (size_t i = 0; i < edgesAA.size(); ++i)
     831        // Render the axis-aligned squares
     832        for (size_t i = 0; i < edgeSquares.size(); ++i)
    664833        {
    665834            m_DebugOverlayShortPathLines.push_back(SOverlayLine());
    666835            m_DebugOverlayShortPathLines.back().m_Color = CColor(0, 1, 1, 1);
    667836            std::vector<float> xz;
    668             xz.push_back(edgesAA[i].p0.X.ToFloat());
    669             xz.push_back(edgesAA[i].p0.Y.ToFloat());
    670             xz.push_back(edgesAA[i].p0.X.ToFloat());
    671             xz.push_back(edgesAA[i].p1.Y.ToFloat());
    672             xz.push_back(edgesAA[i].p1.X.ToFloat());
    673             xz.push_back(edgesAA[i].p1.Y.ToFloat());
    674             xz.push_back(edgesAA[i].p1.X.ToFloat());
    675             xz.push_back(edgesAA[i].p0.Y.ToFloat());
    676             xz.push_back(edgesAA[i].p0.X.ToFloat());
    677             xz.push_back(edgesAA[i].p0.Y.ToFloat());
     837            Square s = edgeSquares[i];
     838            xz.push_back(s.p0.X.ToFloat());
     839            xz.push_back(s.p0.Y.ToFloat());
     840            xz.push_back(s.p0.X.ToFloat());
     841            xz.push_back(s.p1.Y.ToFloat());
     842            xz.push_back(s.p1.X.ToFloat());
     843            xz.push_back(s.p1.Y.ToFloat());
     844            xz.push_back(s.p1.X.ToFloat());
     845            xz.push_back(s.p0.Y.ToFloat());
     846            xz.push_back(s.p0.X.ToFloat());
     847            xz.push_back(s.p0.Y.ToFloat());
    678848            SimRender::ConstructLineOnGround(GetSimContext(), xz, m_DebugOverlayShortPathLines.back(), true);
    679849        }
    680850    }
     
    714884
    715885        // Sort the edges so ones nearer this vertex are checked first by CheckVisibility,
    716886        // since they're more likely to block the rays
    717         std::sort(edgesAA.begin(), edgesAA.end(), EdgeSort(vertexes[curr.id].p));
     887        std::sort(edgeSquares.begin(), edgeSquares.end(), SquareSort(vertexes[curr.id].p));
    718888
     889        std::vector<Edge> edgesUnaligned;
    719890        std::vector<EdgeAA> edgesLeft;
    720891        std::vector<EdgeAA> edgesRight;
    721892        std::vector<EdgeAA> edgesBottom;
    722893        std::vector<EdgeAA> edgesTop;
    723         SplitAAEdges(vertexes[curr.id].p, edgesAA, edgesLeft, edgesRight, edgesBottom, edgesTop);
     894        SplitAAEdges(vertexes[curr.id].p, edges, edgeSquares, edgesUnaligned, edgesLeft, edgesRight, edgesBottom, edgesTop);
     895        //debug_printf(L"edges: e=%d aa=%d; u=%d l=%d r=%d b=%d t=%d\n", edges.size(), edgeSquares.size(), edgesUnaligned.size(), edgesLeft.size(), edgesRight.size(), edgesBottom.size(), edgesTop.size());
    724896
    725897        // Check the lines to every other vertex
    726898        for (size_t n = 0; n < vertexes.size(); ++n)
     
    768940                CheckVisibilityRight(vertexes[curr.id].p, npos, edgesRight) &&
    769941                CheckVisibilityBottom(vertexes[curr.id].p, npos, edgesBottom) &&
    770942                CheckVisibilityTop(vertexes[curr.id].p, npos, edgesTop) &&
    771                 CheckVisibility(vertexes[curr.id].p, npos, edges);
     943                CheckVisibility(vertexes[curr.id].p, npos, edgesUnaligned);
    772944
    773945            /*
    774946            // Render the edges that we examine
     
    792964                    // Add it to the open list:
    793965                    vertexes[n].status = Vertex::OPEN;
    794966                    vertexes[n].g = g;
    795                     vertexes[n].h = DistanceToGoal(npos, goal);
     967                    vertexes[n].h = goal.DistanceToPoint(npos);
    796968                    vertexes[n].pred = curr.id;
    797969
    798970                    // If this is an axis-aligned shape, the path must continue in the same quadrant
     
    823995                        continue;
    824996
    825997                    // Otherwise, we have a better path, so replace the old one with the new cost/parent
     998                    fixed gprev = vertexes[n].g;
    826999                    vertexes[n].g = g;
    8271000                    vertexes[n].pred = curr.id;
    8281001
     
    8341007                    if (n == GOAL_VERTEX_ID)
    8351008                        vertexes[n].p = npos; // remember the new best goal position
    8361009
    837                     open.promote((u16)n, g + vertexes[n].h);
     1010                    open.promote((u16)n, gprev + vertexes[n].h, g + vertexes[n].h);
    8381011                }
    8391012            }
    8401013        }
     
    8541027    entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r,
    8551028    pass_class_t passClass)
    8561029{
     1030    // Test against dynamic obstructions first
     1031
    8571032    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
    8581033    if (!cmpObstructionManager)
    8591034        return false;
     
    8611036    if (cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r))
    8621037        return false;
    8631038
    864     // Test against terrain:
     1039    // Test against the passability grid.
     1040    // This should ignore r, and just check that the line (x0,z0)-(x1,z1)
     1041    // does not intersect any impassable navcells.
     1042    // We shouldn't allow lines between diagonally-adjacent navcells.
     1043    // It doesn't matter whether we allow lines precisely along the edge
     1044    // of an impassable navcell.
    8651045
    866     // (TODO: this could probably be a tiny bit faster by not reusing all the vertex computation code)
     1046    // To rasterise the line:
     1047    // If the line is (e.g.) aiming up-right, then we start at the navcell
     1048    // containing the start point and the line must either end in that navcell
     1049    // or else exit along the top edge or the right edge (or through the top-right corner,
     1050    // which we'll arbitrary treat as the horizontal edge).
     1051    // So we jump into the adjacent navcell across that edge, and continue.
    8671052
     1053    // To handle the special case of units that are stuck on impassable cells,
     1054    // we allow them to move from an impassable to a passable cell (but not
     1055    // vice versa).
     1056
    8681057    UpdateGrid();
    8691058
    870     std::vector<Edge> edgesAA;
    871     std::vector<Vertex> vertexes;
    872 
    8731059    u16 i0, j0, i1, j1;
    874     NearestTile(std::min(x0, x1) - r, std::min(z0, z1) - r, i0, j0);
    875     NearestTile(std::max(x0, x1) + r, std::max(z0, z1) + r, i1, j1);
    876     AddTerrainEdges(edgesAA, vertexes, i0, j0, i1, j1, r, passClass, *m_Grid);
     1060    NearestNavcell(x0, z0, i0, j0);
     1061    NearestNavcell(x1, z1, i1, j1);
    8771062
    878     CFixedVector2D a(x0, z0);
    879     CFixedVector2D b(x1, z1);
     1063    // Find which direction the line heads in
     1064    int di = (i0 < i1 ? +1 : i1 < i0 ? -1 : 0);
     1065    int dj = (j0 < j1 ? +1 : j1 < j0 ? -1 : 0);
    8801066
    881     std::vector<EdgeAA> edgesLeft;
    882     std::vector<EdgeAA> edgesRight;
    883     std::vector<EdgeAA> edgesBottom;
    884     std::vector<EdgeAA> edgesTop;
    885     SplitAAEdges(a, edgesAA, edgesLeft, edgesRight, edgesBottom, edgesTop);
     1067    u16 i = i0;
     1068    u16 j = j0;
    8861069
    887     bool visible =
    888         CheckVisibilityLeft(a, b, edgesLeft) &&
    889         CheckVisibilityRight(a, b, edgesRight) &&
    890         CheckVisibilityBottom(a, b, edgesBottom) &&
    891         CheckVisibilityTop(a, b, edgesTop);
     1070//  debug_printf(L"(%f,%f)..(%f,%f) [%d,%d]..[%d,%d]\n", x0.ToFloat(), z0.ToFloat(), x1.ToFloat(), z1.ToFloat(), i0, j0, i1, j1);
    8921071
    893     return visible;
     1072    bool currentlyOnImpassable = !IS_PASSABLE(m_Grid->get(i0, j0), passClass);
     1073
     1074    while (true)
     1075    {
     1076        // Fail if we're moving onto an impassable navcell
     1077        bool passable = IS_PASSABLE(m_Grid->get(i, j), passClass);
     1078        if (passable)
     1079            currentlyOnImpassable = false;
     1080        else if (!currentlyOnImpassable)
     1081            return false;
     1082
     1083        // Succeed if we're at the target
     1084        if (i == i1 && j == j1)
     1085            return true;
     1086
     1087        // If we can only move horizontally/vertically, then just move in that direction
     1088        if (di == 0)
     1089        {
     1090            j += dj;
     1091            continue;
     1092        }
     1093        else if (dj == 0)
     1094        {
     1095            i += di;
     1096            continue;
     1097        }
     1098
     1099        // Otherwise we need to check which cell to move into:
     1100
     1101        // Check whether the line intersects the horizontal (top/bottom) edge of
     1102        // the current navcell.
     1103        // Horizontal edge is (i, j + (dj>0?1:0)) .. (i + 1, j + (dj>0?1:0))
     1104        // Since we already know the line is moving from this navcell into a different
     1105        // navcell, we simply need to test that the edge's endpoints are not both on the
     1106        // same side of the line.
     1107
     1108        entity_pos_t xia = entity_pos_t::FromInt(i).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     1109        entity_pos_t xib = entity_pos_t::FromInt(i+1).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     1110        entity_pos_t zj = entity_pos_t::FromInt(j + (dj+1)/2).Multiply(ICmpObstructionManager::NAVCELL_SIZE);
     1111
     1112        CFixedVector2D perp = CFixedVector2D(x1 - x0, z1 - z0).Perpendicular();
     1113        entity_pos_t dota = (CFixedVector2D(xia, zj) - CFixedVector2D(x0, z0)).Dot(perp);
     1114        entity_pos_t dotb = (CFixedVector2D(xib, zj) - CFixedVector2D(x0, z0)).Dot(perp);
     1115
     1116//      debug_printf(L"(%f,%f)-(%f,%f) :: %f %f\n", xia.ToFloat(), zj.ToFloat(), xib.ToFloat(), zj.ToFloat(), dota.ToFloat(), dotb.ToFloat());
     1117
     1118        if ((dota < entity_pos_t::Zero() && dotb < entity_pos_t::Zero()) ||
     1119            (dota > entity_pos_t::Zero() && dotb > entity_pos_t::Zero()))
     1120        {
     1121            // Horizontal edge is fully on one side of the line, so the line doesn't
     1122            // intersect it, so we should move across the vertical edge instead
     1123            i += di;
     1124        }
     1125        else
     1126        {
     1127            j += dj;
     1128        }
     1129    }
    8941130}
  • source/simulation2/components/CCmpPathfinder_Tile.cpp

     
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2012 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    3030#include "renderer/TerrainOverlay.h"
    3131#include "simulation2/helpers/PriorityQueue.h"
    3232
    33 typedef PriorityQueueHeap<std::pair<u16, u16>, u32> PriorityQueue;
     33#define PATHFIND_STATS 1
    3434
    35 #define PATHFIND_STATS 0
     35typedef PriorityQueueHeap<TileID, PathCost> PriorityQueue;
    3636
    37 #define USE_DIAGONAL_MOVEMENT 1
    38 
    39 // Heuristic cost to move between adjacent tiles.
    40 // This should be similar to DEFAULT_MOVE_COST.
    41 const u32 g_CostPerTile = 256;
    42 
    4337/**
    4438 * Tile data for A* computation.
    45  * (We store an array of one of these per terrain tile, so it ought to be optimised for size)
     39 * (We store an array of one of these per terrain navcell, so it ought to be optimized for size)
    4640 */
    4741struct PathfindTile
    4842{
     
    6357    u16 GetPredI(u16 i) { return (u16)(i + dpi); }
    6458    u16 GetPredJ(u16 j) { return (u16)(j + dpj); }
    6559    // Set the pi,pj coords of predecessor, given i,j coords of this tile
    66     void SetPred(u16 pi_, u16 pj_, u16 i, u16 j)
     60    void SetPred(u16 pi, u16 pj, u16 i, u16 j)
    6761    {
    68         dpi = (i8)((int)pi_ - (int)i);
    69         dpj = (i8)((int)pj_ - (int)j);
    7062#if PATHFIND_DEBUG
    7163        // predecessor must be adjacent
    72         ENSURE(pi_-i == -1 || pi_-i == 0 || pi_-i == 1);
    73         ENSURE(pj_-j == -1 || pj_-j == 0 || pj_-j == 1);
     64        ENSURE(pi-i == -1 || pi-i == 0 || pi-i == 1);
     65        ENSURE(pj-j == -1 || pj-j == 0 || pj-j == 1);
    7466#endif
     67        dpi = (i8)((int)pi - (int)i);
     68        dpj = (i8)((int)pj - (int)j);
    7569    }
    7670
     71    PathCost GetCost() const { return g; }
     72    void SetCost(PathCost cost) { g = cost; }
     73
    7774private:
     75    i8 dpi, dpj; // values are in {-1, 0, 1}, pointing to an adjacent tile
    7876    u8 status; // this only needs 2 bits
    79     i8 dpi, dpj; // these only really need 2 bits in total
     77    PathCost g; // cost to reach this tile
     78
    8079public:
    81     u32 cost; // g (cost to this tile)
    82     u32 h; // h (heuristic cost to goal) (TODO: is it really better for performance to store this instead of recomputing?)
    83 
    8480#if PATHFIND_DEBUG
    8581    u32 GetStep() { return step; }
    8682    void SetStep(u32 s) { step = s; }
     
    9793 * Terrain overlay for pathfinder debugging.
    9894 * Renders a representation of the most recent pathfinding operation.
    9995 */
    100 class PathfinderOverlay : public TerrainOverlay
     96class PathfinderOverlay : public TerrainTextureOverlay
    10197{
    102     NONCOPYABLE(PathfinderOverlay);
    10398public:
    10499    CCmpPathfinder& m_Pathfinder;
    105100
    106     PathfinderOverlay(CCmpPathfinder& pathfinder)
    107         : TerrainOverlay(pathfinder.GetSimContext()), m_Pathfinder(pathfinder)
     101    PathfinderOverlay(CCmpPathfinder& pathfinder) :
     102        TerrainTextureOverlay(ICmpObstructionManager::NAVCELLS_PER_TILE), m_Pathfinder(pathfinder)
    108103    {
    109104    }
    110105
    111     virtual void StartRender()
     106    virtual void BuildTextureRGBA(u8* data, size_t w, size_t h)
    112107    {
     108        // Ensure m_Pathfinder.m_Grid is up-to-date
    113109        m_Pathfinder.UpdateGrid();
    114     }
    115110
    116     virtual void ProcessTile(ssize_t i, ssize_t j)
    117     {
    118         if (m_Pathfinder.m_Grid && !IS_PASSABLE(m_Pathfinder.m_Grid->get((int)i, (int)j), m_Pathfinder.m_DebugPassClass))
    119             RenderTile(CColor(1, 0, 0, 0.6f), false);
     111        // Grab the debug data for the most recently generated path
     112        u32 steps;
     113        double time;
     114        Grid<u8> debugGrid;
     115        if (m_Pathfinder.m_DebugGridJPS)
     116            m_Pathfinder.GetDebugDataJPS(steps, time, debugGrid);
     117        else if (m_Pathfinder.m_DebugGrid)
     118            m_Pathfinder.GetDebugData(steps, time, debugGrid);
    120119
    121         if (m_Pathfinder.m_DebugGrid)
     120        // Render navcell passability
     121        u8* p = data;
     122        for (size_t j = 0; j < h; ++j)
    122123        {
    123             PathfindTile& n = m_Pathfinder.m_DebugGrid->get((int)i, (int)j);
     124            for (size_t i = 0; i < w; ++i)
     125            {
     126                SColor4ub color(0, 0, 0, 0);
     127                if (!IS_PASSABLE(m_Pathfinder.m_Grid->get((int)i, (int)j), m_Pathfinder.m_DebugPassClass))
     128                    color = SColor4ub(255, 0, 0, 127);
    124129
    125             float c = clamp((float)n.GetStep() / (float)m_Pathfinder.m_DebugSteps, 0.f, 1.f);
     130                if (debugGrid.m_W && debugGrid.m_H)
     131                {
     132                    u8 n = debugGrid.get((int)i, (int)j);
    126133
    127             if (n.IsOpen())
    128                 RenderTile(CColor(1, 1, c, 0.6f), false);
    129             else if (n.IsClosed())
    130                 RenderTile(CColor(0, 1, c, 0.6f), false);
     134                    if (n == 1)
     135                        color = SColor4ub(255, 255, 0, 127);
     136                    else if (n == 2)
     137                        color = SColor4ub(0, 255, 0, 127);
     138
     139                    if (m_Pathfinder.m_DebugGoal.NavcellContainsGoal(i, j))
     140                        color = SColor4ub(0, 0, 255, 127);
     141                }
     142
     143                *p++ = color.R;
     144                *p++ = color.G;
     145                *p++ = color.B;
     146                *p++ = color.A;
     147            }
    131148        }
    132     }
    133149
    134     virtual void EndRender()
    135     {
    136         if (m_Pathfinder.m_DebugPath)
     150        // Render the most recently generated path
     151        if (m_Pathfinder.m_DebugPath && !m_Pathfinder.m_DebugPath->m_Waypoints.empty())
    137152        {
    138             std::vector<ICmpPathfinder::Waypoint>& wp = m_Pathfinder.m_DebugPath->m_Waypoints;
    139             for (size_t n = 0; n < wp.size(); ++n)
     153            std::vector<ICmpPathfinder::Waypoint>& waypoints = m_Pathfinder.m_DebugPath->m_Waypoints;
     154            u16 ip = 0, jp = 0;
     155            for (size_t k = 0; k < waypoints.size(); ++k)
    140156            {
    141157                u16 i, j;
    142                 m_Pathfinder.NearestTile(wp[n].x, wp[n].z, i, j);
    143                 RenderTileOutline(CColor(1, 1, 1, 1), 2, false, i, j);
     158                m_Pathfinder.NearestNavcell(waypoints[k].x, waypoints[k].z, i, j);
     159                if (k == 0)
     160                {
     161                    ip = i;
     162                    jp = j;
     163                }
     164                else
     165                {
     166                    bool firstCell = true;
     167                    do
     168                    {
     169                        if (data[(jp*w + ip)*4+3] == 0)
     170                        {
     171                            data[(jp*w + ip)*4+0] = 0xFF;
     172                            data[(jp*w + ip)*4+1] = 0xFF;
     173                            data[(jp*w + ip)*4+2] = 0xFF;
     174                            data[(jp*w + ip)*4+3] = firstCell ? 0xA0 : 0x60;
     175                        }
     176                        ip = ip < i ? ip+1 : ip > i ? ip-1 : ip;
     177                        jp = jp < j ? jp+1 : jp > j ? jp-1 : jp;
     178                        firstCell = false;
     179                    }
     180                    while (ip != i || jp != j);
     181                }
    144182            }
    145183        }
    146184    }
     
    159197    }
    160198}
    161199
    162 void CCmpPathfinder::SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass)
     200void CCmpPathfinder::SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass)
    163201{
    164202    if (!m_DebugOverlay)
    165203        return;
    166204
    167     delete m_DebugGrid;
    168     m_DebugGrid = NULL;
     205    SAFE_DELETE(m_DebugGrid);
    169206    delete m_DebugPath;
    170207    m_DebugPath = new Path();
    171     ComputePath(x0, z0, goal, passClass, costClass, *m_DebugPath);
     208#if PATHFIND_USE_JPS
     209    ComputePathJPS(x0, z0, goal, passClass, *m_DebugPath);
     210#else
     211    ComputePath(x0, z0, goal, passClass, *m_DebugPath);
     212#endif
    172213    m_DebugPassClass = passClass;
    173214}
    174215
    175216void CCmpPathfinder::ResetDebugPath()
    176217{
    177     delete m_DebugGrid;
    178     m_DebugGrid = NULL;
    179     delete m_DebugPath;
    180     m_DebugPath = NULL;
     218    SAFE_DELETE(m_DebugGrid);
     219    SAFE_DELETE(m_DebugPath);
    181220}
    182221
    183222
    184223//////////////////////////////////////////////////////////
    185224
    186 
    187225struct PathfinderState
    188226{
    189227    u32 steps; // number of algorithm iterations
    190228
    191229    u16 iGoal, jGoal; // goal tile
    192     u16 rGoal; // radius of goal (around tile center)
    193230
    194231    ICmpPathfinder::pass_class_t passClass;
    195     std::vector<u32> moveCosts;
    196232
    197233    PriorityQueue open;
    198234    // (there's no explicit closed list; it's encoded in PathfindTile)
    199235
    200236    PathfindTileGrid* tiles;
    201     Grid<TerrainTile>* terrain;
     237    Grid<NavcellData>* terrain;
    202238
    203     bool ignoreImpassable; // allows us to escape if stuck in patches of impassability
    204 
    205     u32 hBest; // heuristic of closest discovered tile to goal
     239    PathCost hBest; // heuristic of closest discovered tile to goal
    206240    u16 iBest, jBest; // closest tile
    207241
    208242#if PATHFIND_STATS
     
    215249#endif
    216250};
    217251
    218 static bool AtGoal(u16 i, u16 j, const ICmpPathfinder::Goal& goal)
    219 {
    220     // Allow tiles slightly more than sqrt(2) from the actual goal,
    221     // i.e. adjacent diagonally to the target tile
    222     fixed tolerance = entity_pos_t::FromInt(TERRAIN_TILE_SIZE*3/2);
    223 
    224     entity_pos_t x, z;
    225     CCmpPathfinder::TileCenter(i, j, x, z);
    226     fixed dist = CCmpPathfinder::DistanceToGoal(CFixedVector2D(x, z), goal);
    227     return (dist < tolerance);
    228 }
    229 
    230 // Calculate heuristic cost from tile i,j to destination
     252// Calculate heuristic cost from tile i,j to goal
    231253// (This ought to be an underestimate for correctness)
    232 static u32 CalculateHeuristic(u16 i, u16 j, u16 iGoal, u16 jGoal, u16 rGoal)
     254static PathCost CalculateHeuristic(int i, int j, int iGoal, int jGoal)
    233255{
    234 #if USE_DIAGONAL_MOVEMENT
    235     CFixedVector2D pos (fixed::FromInt(i), fixed::FromInt(j));
    236     CFixedVector2D goal (fixed::FromInt(iGoal), fixed::FromInt(jGoal));
    237     fixed dist = (pos - goal).Length();
    238     // TODO: the heuristic could match the costs better - it's not really Euclidean movement
    239 
    240     fixed rdist = dist - fixed::FromInt(rGoal);
    241     rdist = rdist.Absolute();
    242 
    243     // To avoid overflows on large distances we have to convert to int before multiplying
    244     // by the full tile cost, which means we lose some accuracy over short distances,
    245     // so do a partial multiplication here.
    246     // (This will overflow if sqrt(2)*tilesPerSide*premul >= 32768, so
    247     // premul=32 means max tilesPerSide=724)
    248     const int premul = 32;
    249     cassert(g_CostPerTile % premul == 0);
    250     return (rdist * premul).ToInt_RoundToZero() * (g_CostPerTile / premul);
    251 
    252 #else
    253     return (abs((int)i - (int)iGoal) + abs((int)j - (int)jGoal)) * g_CostPerTile;
    254 #endif
     256    int di = abs(i - iGoal);
     257    int dj = abs(j - jGoal);
     258    int diag = std::min(di, dj);
     259    return PathCost(di-diag + dj-diag, diag);
    255260}
    256261
    257 // Calculate movement cost from predecessor tile pi,pj to tile i,j
    258 static u32 CalculateCostDelta(u16 pi, u16 pj, u16 i, u16 j, PathfindTileGrid* tempGrid, u32 tileCost)
    259 {
    260     u32 dg = tileCost;
    261 
    262 #if USE_DIAGONAL_MOVEMENT
    263     // XXX: Probably a terrible hack:
    264     // For simplicity, we only consider horizontally/vertically adjacent neighbours, but
    265     // units can move along arbitrary lines. That results in ugly square paths, so we want
    266     // to prefer diagonal paths.
    267     // Instead of solving this nicely, I'll just special-case 45-degree and 30-degree lines
    268     // by checking the three predecessor tiles (which'll be in the closed set and therefore
    269     // likely to be reasonably stable) and reducing the cost, and use a Euclidean heuristic.
    270     // At least this makes paths look a bit nicer for now...
    271 
    272     PathfindTile& p = tempGrid->get(pi, pj);
    273     u16 ppi = p.GetPredI(pi);
    274     u16 ppj = p.GetPredJ(pj);
    275     if (ppi != i && ppj != j)
    276         dg = (dg << 16) / 92682; // dg*sqrt(2)/2
    277     else
    278     {
    279         PathfindTile& pp = tempGrid->get(ppi, ppj);
    280         int di = abs(i - pp.GetPredI(ppi));
    281         int dj = abs(j - pp.GetPredJ(ppj));
    282         if ((di == 1 && dj == 2) || (di == 2 && dj == 1))
    283             dg = (dg << 16) / 79742; // dg*(sqrt(5)-sqrt(2))
    284     }
    285 #endif
    286 
    287     return dg;
    288 }
    289 
    290262// Do the A* processing for a neighbour tile i,j.
    291 static void ProcessNeighbour(u16 pi, u16 pj, u16 i, u16 j, u32 pg, PathfinderState& state)
     263static void ProcessNeighbour(int pi, int pj, int i, int j, PathCost pg, PathfinderState& state)
    292264{
    293265#if PATHFIND_STATS
    294266    state.numProcessed++;
    295267#endif
    296268
     269    PathfindTile& n = state.tiles->get(i, j);
     270
     271    if (n.IsClosed())
     272        return;
     273
    297274    // Reject impassable tiles
    298     TerrainTile tileTag = state.terrain->get(i, j);
    299     if (!IS_PASSABLE(tileTag, state.passClass) && !state.ignoreImpassable)
     275    if (!IS_PASSABLE(state.terrain->get(i, j), state.passClass))
    300276        return;
     277    // Also, diagonal moves are only allowed if the adjacent tiles
     278    // are also unobstructed
     279    if (pi != i && pj != j)
     280    {
     281        if (!IS_PASSABLE(state.terrain->get(pi, j), state.passClass))
     282            return;
     283        if (!IS_PASSABLE(state.terrain->get(i, pj), state.passClass))
     284            return;
     285    }
    301286
    302     u32 dg = CalculateCostDelta(pi, pj, i, j, state.tiles, state.moveCosts.at(GET_COST_CLASS(tileTag)));
     287    PathCost dg;
     288    if (pi == i || pj == j)
     289        dg = PathCost::horizvert(1);
     290    else
     291        dg = PathCost::diag(1);
    303292
    304     u32 g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
     293    PathCost g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
    305294
    306     PathfindTile& n = state.tiles->get(i, j);
     295    PathCost h = CalculateHeuristic(i, j, state.iGoal, state.jGoal);
    307296
    308297    // If this is a new tile, compute the heuristic distance
    309298    if (n.IsUnexplored())
    310299    {
    311         n.h = CalculateHeuristic(i, j, state.iGoal, state.jGoal, state.rGoal);
    312300        // Remember the best tile we've seen so far, in case we never actually reach the target
    313         if (n.h < state.hBest)
     301        if (h < state.hBest)
    314302        {
    315             state.hBest = n.h;
     303            state.hBest = h;
    316304            state.iBest = i;
    317305            state.jBest = j;
    318306        }
     
    321309    {
    322310        // If we've already seen this tile, and the new path to this tile does not have a
    323311        // better cost, then stop now
    324         if (g >= n.cost)
     312        if (g >= n.GetCost())
    325313            return;
    326314
    327315        // Otherwise, we have a better path.
     
    330318        if (n.IsOpen())
    331319        {
    332320            // This is a better path, so replace the old one with the new cost/parent
    333             n.cost = g;
     321            PathCost gprev = n.GetCost();
     322            n.SetCost(g);
    334323            n.SetPred(pi, pj, i, j);
    335324            n.SetStep(state.steps);
    336             state.open.promote(std::make_pair(i, j), g + n.h);
     325            state.open.promote(TileID(i, j), gprev + h, g + h);
    337326#if PATHFIND_STATS
    338327            state.numImproveOpen++;
    339328#endif
    340329            return;
    341330        }
    342331
     332#if PATHFIND_STATS
    343333        // If we've already found the 'best' path to this tile:
    344334        if (n.IsClosed())
    345335        {
    346336            // This is a better path (possible when we use inadmissible heuristics), so reopen it
    347 #if PATHFIND_STATS
     337            // by falling through
    348338            state.numImproveClosed++;
    349 #endif
    350             // (fall through)
    351339        }
    352340    }
     341#endif
    353342
    354343    // Add it to the open list:
    355344    n.SetStatusOpen();
    356     n.cost = g;
     345    n.SetCost(g);
    357346    n.SetPred(pi, pj, i, j);
    358347    n.SetStep(state.steps);
    359     PriorityQueue::Item t = { std::make_pair(i, j), g + n.h };
     348    PriorityQueue::Item t = { TileID(i, j), g + h };
    360349    state.open.push(t);
    361350#if PATHFIND_STATS
    362351    state.numAddToOpen++;
    363352#endif
    364353}
    365354
    366 void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& path)
     355void CCmpPathfinder::ComputePath(entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal, pass_class_t passClass, Path& path)
    367356{
    368357    UpdateGrid();
    369358
    370359    PROFILE3("ComputePath");
     360    TIMER(L"ComputePath");
     361    double time = timer_Time();
    371362
    372363    PathfinderState state = { 0 };
    373364
    374365    // Convert the start/end coordinates to tile indexes
    375366    u16 i0, j0;
    376     NearestTile(x0, z0, i0, j0);
    377     NearestTile(goal.x, goal.z, state.iGoal, state.jGoal);
     367    NearestNavcell(x0, z0, i0, j0);
    378368
     369    // To be consistent with the JPS pathfinder (which requires passable source navcell),
     370    // and to let us guarantee the goal is reachable from the source, we switch to
     371    // the escape-from-impassability mode if currently on an impassable navcell
     372    if (!IS_PASSABLE(m_Grid->get(i0, j0), passClass))
     373    {
     374        ComputePathOffImpassable(i0, j0, passClass, path);
     375        return;
     376    }
     377
     378    // Adjust the goal so that it's reachable from the source navcell
     379    PathGoal goal = origGoal;
     380    PathfinderHierMakeGoalReachable(i0, j0, goal, passClass);
     381
    379382    // If we're already at the goal tile, then move directly to the exact goal coordinates
    380     if (AtGoal(i0, j0, goal))
     383    // XXX: this seems bogus for non-point goals, it should be the point on the current cell nearest the goal
     384    if (goal.NavcellContainsGoal(i0, j0))
    381385    {
    382386        Waypoint w = { goal.x, goal.z };
    383387        path.m_Waypoints.push_back(w);
    384388        return;
    385389    }
    386390
    387     // If the target is a circle, we want to aim for the edge of it (so e.g. if we're inside
    388     // a large circle then the heuristics will aim us directly outwards);
    389     // otherwise just aim at the center point. (We'll never try moving outwards to a square shape.)
    390     if (goal.type == Goal::CIRCLE)
    391         state.rGoal = (u16)(goal.hw / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero();
    392     else
    393         state.rGoal = 0;
     391    // Store the navcell at the goal center, for A* heuristics
     392    NearestNavcell(goal.x, goal.z, state.iGoal, state.jGoal);
    394393
    395394    state.passClass = passClass;
    396     state.moveCosts = m_MoveCosts.at(costClass);
    397395
    398396    state.steps = 0;
    399397
    400     state.tiles = new PathfindTileGrid(m_MapSize, m_MapSize);
     398    state.tiles = new PathfindTileGrid(m_Grid->m_W, m_Grid->m_H);
    401399    state.terrain = m_Grid;
    402400
    403401    state.iBest = i0;
    404402    state.jBest = j0;
    405     state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal, state.rGoal);
     403    state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal);
    406404
    407     PriorityQueue::Item start = { std::make_pair(i0, j0), 0 };
     405    PriorityQueue::Item start = { TileID(i0, j0), PathCost() };
    408406    state.open.push(start);
    409407    state.tiles->get(i0, j0).SetStatusOpen();
    410408    state.tiles->get(i0, j0).SetPred(i0, j0, i0, j0);
    411     state.tiles->get(i0, j0).cost = 0;
     409    state.tiles->get(i0, j0).SetCost(PathCost());
    412410
    413     // To prevent units getting very stuck, if they start on an impassable tile
    414     // surrounded entirely by impassable tiles, we ignore the impassability
    415     state.ignoreImpassable = !IS_PASSABLE(state.terrain->get(i0, j0), state.passClass);
    416 
    417411    while (1)
    418412    {
    419413        ++state.steps;
    420414
    421         // Hack to avoid spending ages computing giant paths, particularly when
    422         // the destination is unreachable
    423         if (state.steps > 40000)
    424             break;
    425 
    426415        // If we ran out of tiles to examine, give up
    427416        if (state.open.empty())
    428417            break;
     
    433422
    434423        // Move best tile from open to closed
    435424        PriorityQueue::Item curr = state.open.pop();
    436         u16 i = curr.id.first;
    437         u16 j = curr.id.second;
     425        u16 i = curr.id.i();
     426        u16 j = curr.id.j();
    438427        state.tiles->get(i, j).SetStatusClosed();
    439428
    440429        // If we've reached the destination, stop
    441         if (AtGoal(i, j, goal))
     430        if (goal.NavcellContainsGoal(i, j))
    442431        {
    443432            state.iBest = i;
    444433            state.jBest = j;
    445             state.hBest = 0;
     434            state.hBest = PathCost();
    446435            break;
    447436        }
    448437
    449         // As soon as we find an escape route from the impassable terrain,
    450         // take it and forbid any further use of impassable tiles
    451         if (state.ignoreImpassable)
    452         {
    453             if (i > 0 && IS_PASSABLE(state.terrain->get(i-1, j), state.passClass))
    454                 state.ignoreImpassable = false;
    455             else if (i < m_MapSize-1 && IS_PASSABLE(state.terrain->get(i+1, j), state.passClass))
    456                 state.ignoreImpassable = false;
    457             else if (j > 0 && IS_PASSABLE(state.terrain->get(i, j-1), state.passClass))
    458                 state.ignoreImpassable = false;
    459             else if (j < m_MapSize-1 && IS_PASSABLE(state.terrain->get(i, j+1), state.passClass))
    460                 state.ignoreImpassable = false;
    461         }
     438        PathCost g = state.tiles->get(i, j).GetCost();
    462439
    463         u32 g = state.tiles->get(i, j).cost;
    464         if (i > 0)
    465             ProcessNeighbour(i, j, (u16)(i-1), j, g, state);
    466         if (i < m_MapSize-1)
    467             ProcessNeighbour(i, j, (u16)(i+1), j, g, state);
    468         if (j > 0)
    469             ProcessNeighbour(i, j, i, (u16)(j-1), g, state);
    470         if (j < m_MapSize-1)
    471             ProcessNeighbour(i, j, i, (u16)(j+1), g, state);
     440        // Try all 8 neighbors
     441        ProcessNeighbour(i, j, i-1, j-1, g, state);
     442        ProcessNeighbour(i, j, i+1, j-1, g, state);
     443        ProcessNeighbour(i, j, i-1, j+1, g, state);
     444        ProcessNeighbour(i, j, i+1, j+1, g, state);
     445        ProcessNeighbour(i, j, i-1, j, g, state);
     446        ProcessNeighbour(i, j, i+1, j, g, state);
     447        ProcessNeighbour(i, j, i, j-1, g, state);
     448        ProcessNeighbour(i, j, i, j+1, g, state);
    472449    }
    473450
    474451    // Reconstruct the path (in reverse)
     
    477454    {
    478455        PathfindTile& n = state.tiles->get(ip, jp);
    479456        entity_pos_t x, z;
    480         TileCenter(ip, jp, x, z);
     457        NavcellCenter(ip, jp, x, z);
    481458        Waypoint w = { x, z };
    482459        path.m_Waypoints.push_back(w);
    483460
     
    486463        jp = n.GetPredJ(jp);
    487464    }
    488465
     466    NormalizePathWaypoints(path);
     467
    489468    // Save this grid for debug display
     469    m_DebugTime = timer_Time() - time;
    490470    delete m_DebugGrid;
    491471    m_DebugGrid = state.tiles;
    492472    m_DebugSteps = state.steps;
     473    m_DebugGoal = goal;
    493474
    494475    PROFILE2_ATTR("from: (%d, %d)", i0, j0);
    495476    PROFILE2_ATTR("to: (%d, %d)", state.iGoal, state.jGoal);
     
    497478    PROFILE2_ATTR("steps: %u", state.steps);
    498479
    499480#if PATHFIND_STATS
    500     printf("PATHFINDER: steps=%d avgo=%d proc=%d impc=%d impo=%d addo=%d\n", state.steps, state.sumOpenSize/state.steps, state.numProcessed, state.numImproveClosed, state.numImproveOpen, state.numAddToOpen);
     481    debug_printf(L"PATHFINDER: steps=%d avgo=%d proc=%d impc=%d impo=%d addo=%d\n", state.steps, state.sumOpenSize/state.steps, state.numProcessed, state.numImproveClosed, state.numImproveOpen, state.numAddToOpen);
    501482#endif
    502483}
     484
     485void CCmpPathfinder::GetDebugData(u32& steps, double& time, Grid<u8>& grid)
     486{
     487    steps = m_DebugSteps;
     488    time = m_DebugTime;
     489
     490    if (!m_DebugGrid)
     491        return;
     492
     493    grid = Grid<u8>(m_DebugGrid->m_W, m_DebugGrid->m_H);
     494    for (u16 j = 0; j < grid.m_H; ++j)
     495    {
     496        for (u16 i = 0; i < grid.m_W; ++i)
     497        {
     498            PathfindTile t = m_DebugGrid->get(i, j);
     499            grid.set(i, j, (t.IsOpen() ? 1 : 0) | (t.IsClosed() ? 2 : 0));
     500        }
     501    }
     502}
  • source/simulation2/components/CCmpPathfinder_JPS.cpp

     
     1/* Copyright (C) 2012 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#include "precompiled.h"
     19
     20#include "CCmpPathfinder_Common.h"
     21
     22#include "lib/bits.h"
     23#include "lib/sysdep/rtl.h"
     24#include "ps/Profile.h"
     25#include "renderer/TerrainOverlay.h"
     26#include "simulation2/helpers/PriorityQueue.h"
     27
     28#define PATHFIND_STATS 1
     29
     30#define USE_JUMPPOINT_CACHE 1
     31
     32#define ACCEPT_DIAGONAL_GAPS 0
     33
     34typedef PriorityQueueHeap<TileID, PathCost> PriorityQueue;
     35
     36/**
     37 * Jump point cache.
     38 *
     39 * The JPS algorithm wants to efficiently either find the first jump point
     40 * in some direction from some cell (not counting the cell itself),
     41 * if it is reachable without crossing any impassable cells;
     42 * or know that there is no such reachable jump point.
     43 * The jump point is always on a passable cell.
     44 * We cache that data to allow fast lookups, which helps performance
     45 * significantly (especially on sparse maps).
     46 * Recalculation might be expensive but the underlying passability data
     47 * changes relatively rarely.
     48 *
     49 * To allow the algorithm to detect goal cells, we want to treat them as
     50 * jump points too. (That means the algorithm will push those cells onto
     51 * its open queue, and will eventually pop a goal cell and realise it's done.)
     52 * (Goals might be circles/squares/etc, not just a single cell.)
     53 * But the goal generally changes for every path request, so we can't cache
     54 * it like the normal jump points.
     55 * Instead, if there's no jump point from some cell then we'll cache the
     56 * first impassable cell as an 'obstruction jump point'
     57 * (with a flag to distinguish from a real jump point), and then the caller
     58 * can test whether the goal includes a cell that's closer than the first
     59 * (obstruction or real) jump point,
     60 * and treat the goal cell as a jump point in that case.
     61 *
     62 * We only ever need to find the jump point relative to a passable cell;
     63 * the cache is allowed to return bogus values for impassable cells.
     64 */
     65class JumpPointCache
     66{
     67    /**
     68     * Simple space-inefficient row storage.
     69     */
     70    struct RowRaw
     71    {
     72        std::vector<u16> data;
     73
     74        size_t GetMemoryUsage() const
     75        {
     76            return data.capacity() * sizeof(u16);
     77        }
     78
     79        RowRaw(int length)
     80        {
     81            data.resize(length);
     82        }
     83
     84        /**
     85         * Set cells x0 <= x < x1 to have jump point x1.
     86         */
     87        void SetRange(int x0, int x1, bool obstruction)
     88        {
     89            ENSURE(0 <= x0 && x0 <= x1 && x1 < (int)data.size());
     90            for (int x = x0; x < x1; ++x)
     91                data[x] = (x1 << 1) | (obstruction ? 1 : 0);
     92        }
     93
     94        /**
     95         * Returns the coordinate of the next jump point xp (where x < xp),
     96         * and whether it's an obstruction point or jump point.
     97         */
     98        void Get(int x, int& xp, bool& obstruction)
     99        {
     100            ENSURE(0 <= x && x < (int)data.size());
     101            xp = data[x] >> 1;
     102            obstruction = data[x] & 1;
     103        }
     104
     105        void Finish() { }
     106    };
     107
     108    struct RowTree
     109    {
     110        /**
     111         * Represents an interval [u15 x0, u16 x1)
     112         * with a boolean obstruction flag,
     113         * packed into a single u32.
     114         */
     115        struct Interval
     116        {
     117            Interval() : data(0) { }
     118
     119            Interval(int x0, int x1, bool obstruction)
     120            {
     121                ENSURE(0 <= x0 && x0 < 0x8000);
     122                ENSURE(0 <= x1 && x1 < 0x10000);
     123                data = ((u32)x0 << 17) | (u32)(obstruction ? 0x10000 : 0) | (u32)x1;
     124            }
     125
     126            int x0() { return data >> 17; }
     127            int x1() { return data & 0xFFFF; }
     128            bool obstruction() { return (data & 0x10000) != 0; }
     129
     130            u32 data;
     131        };
     132
     133        std::vector<Interval> data;
     134
     135        size_t GetMemoryUsage() const
     136        {
     137            return data.capacity() * sizeof(Interval);
     138        }
     139
     140        RowTree(int UNUSED(length))
     141        {
     142        }
     143
     144        void SetRange(int x0, int x1, bool obstruction)
     145        {
     146            ENSURE(0 <= x0 && x0 <= x1);
     147            Interval iv(x0, x1, obstruction);
     148            data.push_back(iv);
     149        }
     150
     151        /**
     152         * Recursive helper function for Finish().
     153         * Given two ranges [x0, pivot) and [pivot, x1) in the sorted array 'data',
     154         * the pivot element is added onto the binary tree (stored flattened in an
     155         * array), and then each range is split into two sub-ranges with a pivot in
     156         * the middle (to ensure the tree remains balanced) and ConstructTree recurses.
     157         */
     158        void ConstructTree(std::vector<Interval>& tree, size_t x0, size_t pivot, size_t x1, size_t idx_tree)
     159        {
     160            ENSURE(x0 < data.size());
     161            ENSURE(x1 <= data.size());
     162            ENSURE(x0 <= pivot);
     163            ENSURE(pivot < x1);
     164            ENSURE(idx_tree < tree.size());
     165
     166            tree[idx_tree] = data[pivot];
     167
     168            if (x0 < pivot)
     169                ConstructTree(tree, x0, (x0 + pivot) / 2, pivot, (idx_tree << 1) + 1);
     170            if (pivot+1 < x1)
     171                ConstructTree(tree, pivot+1, (pivot + x1) / 2, x1, (idx_tree << 1) + 2);
     172        }
     173
     174        void Finish()
     175        {
     176            // Convert the sorted interval list into a balanced binary tree
     177
     178            std::vector<Interval> tree;
     179
     180            if (!data.empty())
     181            {
     182                size_t depth = ceil_log2(data.size() + 1);
     183                tree.resize((1 << depth) - 1);
     184                ConstructTree(tree, 0, data.size() / 2, data.size(), 0);
     185            }
     186
     187            data.swap(tree);
     188        }
     189
     190        void Get(int x, int& xp, bool& obstruction)
     191        {
     192            // Search the binary tree for an interval which contains x
     193            int i = 0;
     194            while (true)
     195            {
     196                ENSURE(i < (int)data.size());
     197                Interval interval = data[i];
     198                if (x < interval.x0())
     199                    i = (i << 1) + 1;
     200                else if (x >= interval.x1())
     201                    i = (i << 1) + 2;
     202                else
     203                {
     204                    ENSURE(interval.x0() <= x && x < interval.x1());
     205                    xp = interval.x1();
     206                    obstruction = interval.obstruction();
     207                    return;
     208                }
     209            }
     210        }
     211    };
     212
     213    // Pick one of the row implementations
     214    typedef RowRaw Row;
     215
     216public:
     217    int m_Width;
     218    int m_Height;
     219    std::vector<Row> m_JumpPointsRight;
     220    std::vector<Row> m_JumpPointsLeft;
     221    std::vector<Row> m_JumpPointsUp;
     222    std::vector<Row> m_JumpPointsDown;
     223
     224    /**
     225     * Compute the cached obstruction/jump points for each cell,
     226     * in a single direction. By default the code assumes the rightwards
     227     * (+i) direction; set 'transpose' to switch to upwards (+j),
     228     * and/or set 'mirror' to reverse the direction.
     229     */
     230    void ComputeRows(std::vector<Row>& rows,
     231        const Grid<NavcellData>& terrain, ICmpPathfinder::pass_class_t passClass,
     232        bool transpose, bool mirror)
     233    {
     234        int w = terrain.m_W;
     235        int h = terrain.m_H;
     236
     237        if (transpose)
     238            std::swap(w, h);
     239
     240        // Check the terrain passability, adjusted for transpose/mirror
     241#define TERRAIN_IS_PASSABLE(i, j) \
     242    IS_PASSABLE( \
     243        mirror \
     244        ? (transpose ? terrain.get((j), w-1-(i)) : terrain.get(w-1-(i), (j))) \
     245        : (transpose ? terrain.get((j), (i)) : terrain.get((i), (j))) \
     246    , passClass)
     247
     248        rows.reserve(h);
     249        for (int j = 0; j < h; ++j)
     250            rows.push_back(Row(w));
     251
     252        for (int j = 1; j < h - 1; ++j)
     253        {
     254            // Find the first passable cell.
     255            // Then, find the next jump/obstruction point after that cell,
     256            // and store that point for the passable range up to that cell,
     257            // then repeat.
     258
     259            int i = 0;
     260            while (i < w)
     261            {
     262                // Restart the 'while' loop until we reach a passable cell
     263                if (!TERRAIN_IS_PASSABLE(i, j))
     264                {
     265                    ++i;
     266                    continue;
     267                }
     268
     269                // i is now a passable cell; find the next jump/obstruction point.
     270                // (We assume the map is surrounded by impassable cells, so we don't
     271                // need to explicitly check for world bounds here.)
     272
     273                int i0 = i;
     274                while (true)
     275                {
     276                    ++i;
     277
     278                    // Check if we hit an obstructed tile
     279                    if (!TERRAIN_IS_PASSABLE(i, j))
     280                    {
     281                        rows[j].SetRange(i0, i, true);
     282                        break;
     283                    }
     284
     285                    // Check if we reached a jump point
     286#if ACCEPT_DIAGONAL_GAPS
     287                    if ((!TERRAIN_IS_PASSABLE(i, j-1) && TERRAIN_IS_PASSABLE(i+1, j-1)) ||
     288                        (!TERRAIN_IS_PASSABLE(i, j+1) && TERRAIN_IS_PASSABLE(i+1, j+1)))
     289#else
     290                    if ((!TERRAIN_IS_PASSABLE(i-1, j-1) && TERRAIN_IS_PASSABLE(i, j-1)) ||
     291                        (!TERRAIN_IS_PASSABLE(i-1, j+1) && TERRAIN_IS_PASSABLE(i, j+1)))
     292#endif
     293                    {
     294                        rows[j].SetRange(i0, i, false);
     295                        break;
     296                    }
     297                }
     298            }
     299
     300            rows[j].Finish();
     301        }
     302    }
     303
     304    void reset(const Grid<NavcellData>* terrain, ICmpPathfinder::pass_class_t passClass)
     305    {
     306        PROFILE3("JumpPointCache reset");
     307        TIMER(L"JumpPointCache reset");
     308
     309        m_Width = terrain->m_W;
     310        m_Height = terrain->m_H;
     311
     312        ComputeRows(m_JumpPointsRight, *terrain, passClass, false, false);
     313        ComputeRows(m_JumpPointsLeft, *terrain, passClass, false, true);
     314        ComputeRows(m_JumpPointsUp, *terrain, passClass, true, false);
     315        ComputeRows(m_JumpPointsDown, *terrain, passClass, true, true);
     316    }
     317
     318    size_t GetMemoryUsage() const
     319    {
     320        size_t bytes = 0;
     321        for (int i = 0; i < m_Width; ++i)
     322        {
     323            bytes += m_JumpPointsUp[i].GetMemoryUsage();
     324            bytes += m_JumpPointsDown[i].GetMemoryUsage();
     325        }
     326        for (int j = 0; j < m_Height; ++j)
     327        {
     328            bytes += m_JumpPointsRight[j].GetMemoryUsage();
     329            bytes += m_JumpPointsLeft[j].GetMemoryUsage();
     330        }
     331        return bytes;
     332    }
     333
     334    /**
     335     * Returns the next jump point (or goal point) to explore,
     336     * at (ip, j) where i < ip.
     337     * Returns i if there is no such point.
     338     */
     339    int GetJumpPointRight(int i, int j, const PathGoal& goal)
     340    {
     341        int ip;
     342        bool obstruction;
     343        m_JumpPointsRight[j].Get(i, ip, obstruction);
     344        // Adjust ip to be a goal cell, if there is one closer than the jump point;
     345        // and then return the new ip if there is a goal,
     346        // or the old ip if there is a (non-obstruction) jump point
     347        if (goal.NavcellRectContainsGoal(i+1, j, ip-1, j, &ip, NULL) || !obstruction)
     348            return ip;
     349        return i;
     350    }
     351
     352    int GetJumpPointLeft(int i, int j, const PathGoal& goal)
     353    {
     354        int mip; // mirrored value, because m_JumpPointsLeft is generated from a mirrored map
     355        bool obstruction;
     356        m_JumpPointsLeft[j].Get(m_Width-1 - i, mip, obstruction);
     357        int ip = m_Width-1 - mip;
     358        if (goal.NavcellRectContainsGoal(i-1, j, ip+1, j, &ip, NULL) || !obstruction)
     359            return ip;
     360        return i;
     361    }
     362
     363    int GetJumpPointUp(int i, int j, const PathGoal& goal)
     364    {
     365        int jp;
     366        bool obstruction;
     367        m_JumpPointsUp[i].Get(j, jp, obstruction);
     368        if (goal.NavcellRectContainsGoal(i, j+1, i, jp-1, NULL, &jp) || !obstruction)
     369            return jp;
     370        return j;
     371    }
     372
     373    int GetJumpPointDown(int i, int j, const PathGoal& goal)
     374    {
     375        int mjp; // mirrored value
     376        bool obstruction;
     377        m_JumpPointsDown[i].Get(m_Height-1 - j, mjp, obstruction);
     378        int jp = m_Height-1 - mjp;
     379        if (goal.NavcellRectContainsGoal(i, j-1, i, jp+1, NULL, &jp) || !obstruction)
     380            return jp;
     381        return j;
     382    }
     383};
     384
     385/**
     386 * Tile data for A* computation.
     387 * (We store an array of one of these per terrain tile, so it ought to be optimised for size)
     388 */
     389struct PathfindTileJPS
     390{
     391public:
     392    enum {
     393        STATUS_UNEXPLORED = 0,
     394        STATUS_OPEN = 1,
     395        STATUS_CLOSED = 2
     396    };
     397
     398    bool IsUnexplored() { return GetStatus() == STATUS_UNEXPLORED; }
     399    bool IsOpen() { return GetStatus() == STATUS_OPEN; }
     400    bool IsClosed() { return GetStatus() == STATUS_CLOSED; }
     401    void SetStatusOpen() { SetStatus(STATUS_OPEN); }
     402    void SetStatusClosed() { SetStatus(STATUS_CLOSED); }
     403
     404    // Get pi,pj coords of predecessor to this tile on best path, given i,j coords of this tile
     405    int GetPredI(int i) { return i + GetPredDI(); }
     406    int GetPredJ(int j) { return j + GetPredDJ(); }
     407
     408    PathCost GetCost() const { return g; }
     409    void SetCost(PathCost cost) { g = cost; }
     410
     411private:
     412    PathCost g; // cost to reach this tile
     413    u32 data; // 2-bit status; 15-bit PredI; 15-bit PredJ; packed for storage efficiency
     414
     415public:
     416    u8 GetStatus() const
     417    {
     418        return data & 3;
     419    }
     420
     421    void SetStatus(u8 s)
     422    {
     423        ASSERT(s < 4);
     424        data &= ~3;
     425        data |= (s & 3);
     426    }
     427
     428    int GetPredDI() const
     429    {
     430        return (i32)data >> 17;
     431    }
     432
     433    int GetPredDJ() const
     434    {
     435        return ((i32)data << 15) >> 17;
     436    }
     437
     438    // Set the pi,pj coords of predecessor, given i,j coords of this tile
     439    void SetPred(int pi, int pj, int i, int j)
     440    {
     441        int di = pi - i;
     442        int dj = pj - j;
     443        ASSERT(-16384 <= di && di < 16384);
     444        ASSERT(-16384 <= dj && dj < 16384);
     445        data &= 3;
     446        data |= (((u32)di & 0x7FFF) << 17) | (((u32)dj & 0x7FFF) << 2);
     447    }
     448};
     449
     450//////////////////////////////////////////////////////////
     451
     452struct PathfinderStateJPS
     453{
     454    u32 steps; // number of algorithm iterations
     455
     456    PathGoal goal;
     457
     458    u16 iGoal, jGoal; // goal tile
     459
     460    ICmpPathfinder::pass_class_t passClass;
     461
     462    PriorityQueue open;
     463    // (there's no explicit closed list; it's encoded in PathfindTile)
     464
     465    PathfindTileJPSGrid* tiles;
     466    Grid<NavcellData>* terrain;
     467
     468    PathCost hBest; // heuristic of closest discovered tile to goal
     469    u16 iBest, jBest; // closest tile
     470
     471    JumpPointCache* jpc;
     472
     473#if PATHFIND_STATS
     474    // Performance debug counters
     475    size_t numProcessed;
     476    size_t numImproveOpen;
     477    size_t numAddToOpen;
     478    size_t sumOpenSize;
     479#endif
     480};
     481
     482// Calculate heuristic cost from tile i,j to goal
     483// (This ought to be an underestimate for correctness)
     484static PathCost CalculateHeuristic(int i, int j, int iGoal, int jGoal)
     485{
     486    int di = abs(i - iGoal);
     487    int dj = abs(j - jGoal);
     488    int diag = std::min(di, dj);
     489    return PathCost(di-diag + dj-diag, diag);
     490}
     491
     492
     493// Do the A* processing for a neighbour tile i,j.
     494// TODO: it'd be nice to not duplicate so much code with CCmpPathfinder_Tile.cpp
     495static void ProcessNeighbour(int pi, int pj, int i, int j, PathCost pg, PathfinderStateJPS& state)
     496{
     497#if PATHFIND_STATS
     498    state.numProcessed++;
     499#endif
     500
     501    // Reject impassable tiles
     502    if (!IS_PASSABLE(state.terrain->get(i, j), state.passClass))
     503        return;
     504
     505    PathfindTileJPS& n = state.tiles->get(i, j);
     506
     507    if (n.IsClosed())
     508        return;
     509
     510    PathCost dg;
     511    if (pi == i)
     512        dg = PathCost::horizvert(abs(pj - j));
     513    else if (pj == j)
     514        dg = PathCost::horizvert(abs(pi - i));
     515    else
     516    {
     517        ASSERT(abs((int)pi - (int)i) == abs((int)pj - (int)j)); // must be 45 degrees
     518        dg = PathCost::diag(abs((int)pi - (int)i));
     519    }
     520
     521    PathCost g = pg + dg; // cost to this tile = cost to predecessor + delta from predecessor
     522
     523    PathCost h = CalculateHeuristic(i, j, state.iGoal, state.jGoal);
     524
     525    // If this is a new tile, compute the heuristic distance
     526    if (n.IsUnexplored())
     527    {
     528        // Remember the best tile we've seen so far, in case we never actually reach the target
     529        if (h < state.hBest)
     530        {
     531            state.hBest = h;
     532            state.iBest = i;
     533            state.jBest = j;
     534        }
     535    }
     536    else
     537    {
     538        // If we've already seen this tile, and the new path to this tile does not have a
     539        // better cost, then stop now
     540        if (g >= n.GetCost())
     541            return;
     542
     543        // Otherwise, we have a better path.
     544
     545        // If we've already added this tile to the open list:
     546        if (n.IsOpen())
     547        {
     548            // This is a better path, so replace the old one with the new cost/parent
     549            PathCost gprev = n.GetCost();
     550            n.SetCost(g);
     551            n.SetPred(pi, pj, i, j);
     552            state.open.promote(TileID(i, j), gprev + h, g + h);
     553#if PATHFIND_STATS
     554            state.numImproveOpen++;
     555#endif
     556            return;
     557        }
     558    }
     559
     560    // Add it to the open list:
     561    n.SetStatusOpen();
     562    n.SetCost(g);
     563    n.SetPred(pi, pj, i, j);
     564    PriorityQueue::Item t = { TileID(i, j), g + h };
     565    state.open.push(t);
     566#if PATHFIND_STATS
     567    state.numAddToOpen++;
     568#endif
     569}
     570
     571#define PASSABLE(i, j) IS_PASSABLE(state.terrain->get(i, j), state.passClass)
     572
     573/*
     574 * In the JPS algorithm, after a tile is taken off the open queue,
     575 * we don't process every adjacent neighbour (as in standard A*).
     576 * Instead we only move in a subset of directions (depending on the
     577 * direction from the predecessor); and instead of moving by a single
     578 * cell, we move up to the next jump point in that direction.
     579 * The AddJumped... functions do this by calling ProcessNeighbour
     580 * on the jump point (if any) in a certain direction.
     581 * The HasJumped... functions return whether there is any jump point
     582 * in that direction.
     583 */
     584
     585#if USE_JUMPPOINT_CACHE
     586
     587// Use the jump-point cache to find the jump points:
     588
     589static void AddJumpedHoriz(int i, int j, int di, PathCost g, PathfinderStateJPS& state)
     590{
     591    int jump;
     592    if (di > 0)
     593        jump = state.jpc->GetJumpPointRight(i, j, state.goal);
     594    else
     595        jump = state.jpc->GetJumpPointLeft(i, j, state.goal);
     596
     597    if (jump != i)
     598        ProcessNeighbour(i, j, jump, j, g, state);
     599}
     600
     601static bool HasJumpedHoriz(int i, int j, int di, PathfinderStateJPS& state)
     602{
     603    int jump;
     604    if (di > 0)
     605        jump = state.jpc->GetJumpPointRight(i, j, state.goal);
     606    else
     607        jump = state.jpc->GetJumpPointLeft(i, j, state.goal);
     608
     609    return (jump != i);
     610}
     611
     612static void AddJumpedVert(int i, int j, int dj, PathCost g, PathfinderStateJPS& state)
     613{
     614    int jump;
     615    if (dj > 0)
     616        jump = state.jpc->GetJumpPointUp(i, j, state.goal);
     617    else
     618        jump = state.jpc->GetJumpPointDown(i, j, state.goal);
     619
     620    if (jump != j)
     621        ProcessNeighbour(i, j, i, jump, g, state);
     622}
     623
     624static bool HasJumpedVert(int i, int j, int dj, PathfinderStateJPS& state)
     625{
     626    int jump;
     627    if (dj > 0)
     628        jump = state.jpc->GetJumpPointUp(i, j, state.goal);
     629    else
     630        jump = state.jpc->GetJumpPointDown(i, j, state.goal);
     631
     632    return (jump != j);
     633}
     634
     635#else // USE_JUMPPOINT_CACHE
     636
     637// Find the jump points by scanning along the map:
     638
     639static void AddJumpedHoriz(int i, int j, int di, PathCost g, PathfinderStateJPS& state)
     640{
     641    ASSERT(di == 1 || di == -1);
     642    int ni = i + di;
     643    while (true)
     644    {
     645        if (!PASSABLE(ni, j))
     646            break;
     647
     648        if ((ni == state.iGoal && j == state.jGoal) || // XXX
     649#if ACCEPT_DIAGONAL_GAPS
     650            (!PASSABLE(ni, j-1) && PASSABLE(ni+di, j-1)) ||
     651            (!PASSABLE(ni, j+1) && PASSABLE(ni+di, j+1)))
     652#else
     653            (!PASSABLE(ni-di, j-1) && PASSABLE(ni, j-1)) ||
     654            (!PASSABLE(ni-di, j+1) && PASSABLE(ni, j+1)))
     655#endif
     656        {
     657            ProcessNeighbour(i, j, ni, j, g, state);
     658            break;
     659        }
     660
     661        ni += di;
     662    }
     663}
     664
     665static bool HasJumpedHoriz(int i, int j, int di, PathfinderStateJPS& state)
     666{
     667    ASSERT(di == 1 || di == -1);
     668    int ni = i + di;
     669    while (true)
     670    {
     671        if (!PASSABLE(ni, j))
     672            return false;
     673
     674        if ((ni == state.iGoal && j == state.jGoal) || // XXX
     675#if ACCEPT_DIAGONAL_GAPS
     676            (!PASSABLE(ni, j-1) && PASSABLE(ni+di, j-1)) ||
     677            (!PASSABLE(ni, j+1) && PASSABLE(ni+di, j+1)))
     678#else
     679            (!PASSABLE(ni-di, j-1) && PASSABLE(ni, j-1)) ||
     680            (!PASSABLE(ni-di, j+1) && PASSABLE(ni, j+1)))
     681#endif
     682        {
     683            return true;
     684        }
     685
     686        ni += di;
     687    }
     688}
     689
     690static void AddJumpedVert(int i, int j, int dj, PathCost g, PathfinderStateJPS& state)
     691{
     692    ASSERT(dj == 1 || dj == -1);
     693    int nj = j + dj;
     694    while (true)
     695    {
     696        if (!PASSABLE(i, nj))
     697            break;
     698
     699        if ((i == state.iGoal && nj == state.jGoal) ||
     700#if ACCEPT_DIAGONAL_GAPS
     701            (!PASSABLE(i-1, nj) && PASSABLE(i-1, nj+dj)) ||
     702            (!PASSABLE(i+1, nj) && PASSABLE(i+1, nj+dj)))
     703#else
     704            (!PASSABLE(i-1, nj-dj) && PASSABLE(i-1, nj)) ||
     705            (!PASSABLE(i+1, nj-dj) && PASSABLE(i+1, nj)))
     706#endif
     707        {
     708            ProcessNeighbour(i, j, i, nj, g, state);
     709            break;
     710        }
     711
     712        nj += dj;
     713    }
     714}
     715
     716static bool HasJumpedVert(int i, int j, int dj, PathfinderStateJPS& state)
     717{
     718    ASSERT(dj == 1 || dj == -1);
     719    int nj = j + dj;
     720    while (true)
     721    {
     722        if (!PASSABLE(i, nj))
     723            return false;
     724
     725        if ((i == state.iGoal && nj == state.jGoal) ||
     726#if ACCEPT_DIAGONAL_GAPS
     727            (!PASSABLE(i-1, nj) && PASSABLE(i-1, nj+dj)) ||
     728            (!PASSABLE(i+1, nj) && PASSABLE(i+1, nj+dj)))
     729#else
     730            (!PASSABLE(i-1, nj-dj) && PASSABLE(i-1, nj)) ||
     731            (!PASSABLE(i+1, nj-dj) && PASSABLE(i+1, nj)))
     732#endif
     733        {
     734            return true;
     735        }
     736
     737        nj += dj;
     738    }
     739}
     740
     741#endif // USE_JUMPPOINT_CACHE
     742
     743
     744/*
     745 * We don't cache diagonal jump points - they're usually so frequent that
     746 * a linear search is about as cheap and avoids the setup cost and memory cost.
     747 */
     748static void AddJumpedDiag(int i, int j, int di, int dj, PathCost g, PathfinderStateJPS& state)
     749{
     750//  ProcessNeighbour(i, j, i + di, j + dj, g, state);
     751//  return;
     752
     753    ASSERT(di == 1 || di == -1);
     754    ASSERT(dj == 1 || dj == -1);
     755
     756    int ni = i + di;
     757    int nj = j + dj;
     758    while (true)
     759    {
     760        // Stop if we hit an obstructed cell
     761        if (!PASSABLE(ni, nj))
     762            return;
     763
     764        // Stop if moving onto this cell caused us to
     765        // touch the corner of an obstructed cell
     766#if !ACCEPT_DIAGONAL_GAPS
     767        if (!PASSABLE(ni - di, nj) || !PASSABLE(ni, nj - dj))
     768            return;
     769#endif
     770
     771        // Process this cell if it's at the goal
     772        if (state.goal.NavcellContainsGoal(ni, nj))
     773        {
     774            ProcessNeighbour(i, j, ni, nj, g, state);
     775            return;
     776        }
     777
     778#if ACCEPT_DIAGONAL_GAPS
     779        if ((!PASSABLE(ni - di, nj) && PASSABLE(ni - di, nj + dj)) ||
     780            (!PASSABLE(ni, nj - dj) && PASSABLE(ni + di, nj - dj)))
     781        {
     782            ProcessNeighbour(i, j, ni, nj, g, state);
     783            return;
     784        }
     785#endif
     786
     787        if (HasJumpedHoriz(ni, nj, di, state) || HasJumpedVert(ni, nj, dj, state))
     788        {
     789            ProcessNeighbour(i, j, ni, nj, g, state);
     790            return;
     791        }
     792
     793        ni += di;
     794        nj += dj;
     795    }
     796}
     797
     798
     799void CCmpPathfinder::PathfinderJPSMakeDirty()
     800{
     801    m_JumpPointCache.clear();
     802}
     803
     804void CCmpPathfinder::ComputePathJPS(entity_pos_t x0, entity_pos_t z0, const PathGoal& origGoal, pass_class_t passClass, Path& path)
     805{
     806    UpdateGrid();
     807
     808    PathfinderStateJPS state = { 0 };
     809
     810    state.jpc = m_JumpPointCache[passClass].get();
     811#if USE_JUMPPOINT_CACHE
     812    if (!state.jpc)
     813    {
     814        state.jpc = new JumpPointCache;
     815        state.jpc->reset(m_Grid, passClass);
     816        debug_printf(L"PATHFINDER: JPC memory: %d kB\n", (int)state.jpc->GetMemoryUsage() / 1024);
     817        m_JumpPointCache[passClass] = shared_ptr<JumpPointCache>(state.jpc);
     818    }
     819#endif
     820
     821    PROFILE3("ComputePathJPS");
     822    TIMER(L"ComputePathJPS");
     823    double time = timer_Time();
     824
     825    // Convert the start coordinates to tile indexes
     826    u16 i0, j0;
     827    NearestNavcell(x0, z0, i0, j0);
     828
     829    if (!IS_PASSABLE(m_Grid->get(i0, j0), passClass))
     830    {
     831        // The JPS pathfinder requires units to be on passable tiles
     832        // (otherwise it might crash), so handle the supposedly-invalid
     833        // state specially
     834        ComputePathOffImpassable(i0, j0, passClass, path);
     835        return;
     836    }
     837
     838    state.goal = origGoal;
     839    PathfinderHierMakeGoalReachable(i0, j0, state.goal, passClass);
     840
     841    // If we're already at the goal tile, then move directly to the exact goal coordinates
     842    // XXX: this seems bogus for non-point goals, it should be the point on the current cell nearest the goal
     843    if (state.goal.NavcellContainsGoal(i0, j0))
     844    {
     845        Waypoint w = { state.goal.x, state.goal.z };
     846        path.m_Waypoints.push_back(w);
     847        return;
     848    }
     849
     850    NearestNavcell(state.goal.x, state.goal.z, state.iGoal, state.jGoal);
     851
     852    state.passClass = passClass;
     853
     854    state.steps = 0;
     855
     856    state.tiles = new PathfindTileJPSGrid(m_Grid->m_W, m_Grid->m_H);
     857    state.terrain = m_Grid;
     858
     859    state.iBest = i0;
     860    state.jBest = j0;
     861    state.hBest = CalculateHeuristic(i0, j0, state.iGoal, state.jGoal);
     862
     863    PriorityQueue::Item start = { TileID(i0, j0), PathCost() };
     864    state.open.push(start);
     865    state.tiles->get(i0, j0).SetStatusOpen();
     866    state.tiles->get(i0, j0).SetPred(i0, j0, i0, j0);
     867    state.tiles->get(i0, j0).SetCost(PathCost());
     868
     869    while (1)
     870    {
     871        ++state.steps;
     872
     873        // If we ran out of tiles to examine, give up
     874        if (state.open.empty())
     875            break;
     876
     877#if PATHFIND_STATS
     878        state.sumOpenSize += state.open.size();
     879#endif
     880
     881        // Move best tile from open to closed
     882        PriorityQueue::Item curr = state.open.pop();
     883        u16 i = curr.id.i();
     884        u16 j = curr.id.j();
     885        state.tiles->get(i, j).SetStatusClosed();
     886
     887        // If we've reached the destination, stop
     888        if (state.goal.NavcellContainsGoal(i, j))
     889        {
     890            state.iBest = i;
     891            state.jBest = j;
     892            state.hBest = PathCost();
     893            break;
     894        }
     895
     896        PathfindTileJPS tile = state.tiles->get(i, j);
     897        PathCost g = tile.GetCost();
     898
     899        // Get the direction of the predecessor tile from this tile
     900        int dpi = tile.GetPredDI();
     901        int dpj = tile.GetPredDJ();
     902        dpi = (dpi < 0 ? -1 : dpi > 0 ? 1 : 0);
     903        dpj = (dpj < 0 ? -1 : dpj > 0 ? 1 : 0);
     904
     905        if (dpi != 0 && dpj == 0)
     906        {
     907            // Moving horizontally from predecessor
     908#if ACCEPT_DIAGONAL_GAPS
     909            if (!IS_PASSABLE(state.terrain->get(i, j-1), state.passClass))
     910                AddJumpedDiag(i, j, -dpi, -1, g, state);
     911            if (!IS_PASSABLE(state.terrain->get(i, j+1), state.passClass))
     912                AddJumpedDiag(i, j, -dpi, +1, g, state);
     913#else
     914            if (!IS_PASSABLE(state.terrain->get(i + dpi, j-1), state.passClass))
     915            {
     916                AddJumpedDiag(i, j, -dpi, -1, g, state);
     917                AddJumpedVert(i, j, -1, g, state);
     918            }
     919            if (!IS_PASSABLE(state.terrain->get(i + dpi, j+1), state.passClass))
     920            {
     921                AddJumpedDiag(i, j, -dpi, +1, g, state);
     922                AddJumpedVert(i, j, +1, g, state);
     923            }
     924#endif
     925            AddJumpedHoriz(i, j, -dpi, g, state);
     926        }
     927        else if (dpi == 0 && dpj != 0)
     928        {
     929            // Moving vertically from predecessor
     930#if ACCEPT_DIAGONAL_GAPS
     931            if (!IS_PASSABLE(state.terrain->get(i-1, j), state.passClass))
     932                AddJumpedDiag(i, j, -1, -dpj, g, state);
     933            if (!IS_PASSABLE(state.terrain->get(i+1, j), state.passClass))
     934                AddJumpedDiag(i, j, +1, -dpj, g, state);
     935#else
     936            if (!IS_PASSABLE(state.terrain->get(i-1, j + dpj), state.passClass))
     937            {
     938                AddJumpedDiag(i, j, -1, -dpj, g, state);
     939                AddJumpedHoriz(i, j, -1, g, state);
     940            }
     941            if (!IS_PASSABLE(state.terrain->get(i+1, j + dpj), state.passClass))
     942            {
     943                AddJumpedDiag(i, j, +1, -dpj, g, state);
     944                AddJumpedHoriz(i, j, +1, g, state);
     945            }
     946#endif
     947            AddJumpedVert(i, j, -dpj, g, state);
     948        }
     949        else if (dpi != 0 && dpj != 0)
     950        {
     951            // Moving diagonally from predecessor
     952#if ACCEPT_DIAGONAL_GAPS
     953            if (!IS_PASSABLE(state.terrain->get(i + dpi, j), state.passClass))
     954                AddJumpedDiag(i, j, dpi, -dpj, g, state);
     955            if (!IS_PASSABLE(state.terrain->get(i, j + dpj), state.passClass))
     956                AddJumpedDiag(i, j, -dpi, dpj, g, state);
     957#endif
     958            AddJumpedHoriz(i, j, -dpi, g, state);
     959            AddJumpedVert(i, j, -dpj, g, state);
     960            AddJumpedDiag(i, j, -dpi, -dpj, g, state);
     961        }
     962        else
     963        {
     964            // No predecessor, i.e. the start tile
     965            // Start searching in every direction
     966
     967            // XXX - check passability?
     968
     969            ProcessNeighbour(i, j, (u16)(i-1), (u16)(j-1), g, state);
     970            ProcessNeighbour(i, j, (u16)(i+1), (u16)(j-1), g, state);
     971            ProcessNeighbour(i, j, (u16)(i-1), (u16)(j+1), g, state);
     972            ProcessNeighbour(i, j, (u16)(i+1), (u16)(j+1), g, state);
     973            ProcessNeighbour(i, j, (u16)(i-1), j, g, state);
     974            ProcessNeighbour(i, j, (u16)(i+1), j, g, state);
     975            ProcessNeighbour(i, j, i, (u16)(j-1), g, state);
     976            ProcessNeighbour(i, j, i, (u16)(j+1), g, state);
     977        }
     978    }
     979
     980    // Reconstruct the path (in reverse)
     981    u16 ip = state.iBest, jp = state.jBest;
     982    while (ip != i0 || jp != j0)
     983    {
     984        PathfindTileJPS& n = state.tiles->get(ip, jp);
     985        entity_pos_t x, z;
     986        NavcellCenter(ip, jp, x, z);
     987        Waypoint w = { x, z };
     988        path.m_Waypoints.push_back(w);
     989
     990        // Follow the predecessor link
     991        ip = n.GetPredI(ip);
     992        jp = n.GetPredJ(jp);
     993    }
     994
     995    NormalizePathWaypoints(path);
     996
     997    // Save this grid for debug display
     998    m_DebugTime = timer_Time() - time;
     999    delete m_DebugGridJPS;
     1000    m_DebugGridJPS = state.tiles;
     1001    m_DebugSteps = state.steps;
     1002    m_DebugGoal = state.goal;
     1003
     1004    PROFILE2_ATTR("from: (%d, %d)", i0, j0);
     1005    PROFILE2_ATTR("to: (%d, %d)", state.iGoal, state.jGoal);
     1006    PROFILE2_ATTR("reached: (%d, %d)", state.iBest, state.jBest);
     1007    PROFILE2_ATTR("steps: %d", state.steps);
     1008
     1009#if PATHFIND_STATS
     1010    debug_printf(L"PATHFINDER: steps=%d avgo=%d proc=%d impo=%d addo=%d\n", state.steps, state.sumOpenSize/state.steps, state.numProcessed, state.numImproveOpen, state.numAddToOpen);
     1011#endif
     1012}
     1013
     1014void CCmpPathfinder::GetDebugDataJPS(u32& steps, double& time, Grid<u8>& grid)
     1015{
     1016    steps = m_DebugSteps;
     1017    time = m_DebugTime;
     1018
     1019    if (!m_DebugGridJPS)
     1020        return;
     1021
     1022    u16 iGoal, jGoal;
     1023    NearestNavcell(m_DebugGoal.x, m_DebugGoal.z, iGoal, jGoal);
     1024
     1025    grid = Grid<u8>(m_DebugGridJPS->m_W, m_DebugGridJPS->m_H);
     1026    for (u16 j = 0; j < grid.m_H; ++j)
     1027    {
     1028        for (u16 i = 0; i < grid.m_W; ++i)
     1029        {
     1030            if (i == iGoal && j == jGoal)
     1031                continue;
     1032            PathfindTileJPS t = m_DebugGridJPS->get(i, j);
     1033            grid.set(i, j, (t.IsOpen() ? 1 : 0) | (t.IsClosed() ? 2 : 0));
     1034        }
     1035    }
     1036}
  • source/simulation2/components/CCmpPathfinder_Hier.cpp

     
     1/* Copyright (C) 2012 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#include "precompiled.h"
     19
     20#include "CCmpPathfinder_Common.h"
     21
     22#include "renderer/Scene.h"
     23#include "renderer/TerrainOverlay.h"
     24#include "simulation2/helpers/Render.h"
     25
     26class PathfinderHierOverlay;
     27
     28/**
     29 * Hierarchical pathfinder.
     30 *
     31 * Currently this doesn't actually find shortest paths, it just deals with
     32 * connectivity.
     33 *
     34 * The navcell-grid representation of the map is split into fixed-size chunks.
     35 * Within a chunks, each maximal set of adjacently-connected passable navcells
     36 * is defined as a region.
     37 * Each region is a vertex in the hierarchical pathfinder's graph.
     38 * When two regions in adjacent chunks are connected by passable navcells,
     39 * the graph contains an edge between the corresponding two vertexes.
     40 * (There will never be an edge between two regions in the same chunk.)
     41 *
     42 * Since regions are typically fairly large, it is possible to determine
     43 * connectivity between any two navcells by mapping them onto their appropriate
     44 * region and then doing a relatively small graph search.
     45 */
     46class CCmpPathfinder_Hier
     47{
     48    NONCOPYABLE(CCmpPathfinder_Hier);
     49
     50    typedef ICmpPathfinder::pass_class_t pass_class_t;
     51
     52public:
     53    struct RegionID
     54    {
     55        u8 ci, cj; // chunk ID
     56        u16 r; // unique-per-chunk local region ID
     57
     58        RegionID(u8 ci, u8 cj, u16 r) : ci(ci), cj(cj), r(r) { }
     59
     60        bool operator<(RegionID b) const
     61        {
     62            // Sort by chunk ID, then by per-chunk region ID
     63            if (ci < b.ci)
     64                return true;
     65            if (b.ci < ci)
     66                return false;
     67            if (cj < b.cj)
     68                return true;
     69            if (b.cj < cj)
     70                return false;
     71            return r < b.r;
     72        }
     73    };
     74
     75    CCmpPathfinder_Hier(CCmpPathfinder& pathfinder);
     76    ~CCmpPathfinder_Hier();
     77
     78    void Init(const std::vector<PathfinderPassability>& passClasses, Grid<NavcellData>* grid);
     79
     80    RegionID Get(u16 i, u16 j, pass_class_t passClass);
     81
     82    /**
     83     * Updates @p goal so that it's guaranteed to be reachable from the navcell
     84     * @p i0, @p j0 (which is assumed to be on a passable navcell).
     85     * If any part of the goal area is already reachable then
     86     * nothing is changed; otherwise the goal is replaced with a point goal
     87     * at the nearest reachable navcell to the original goal's center.
     88     * Returns true if the goal was replaced.
     89     */
     90    bool MakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass);
     91
     92    /**
     93     * Updates @p i0, @p j0 (which is assumed to be an impassable navcell)
     94     * to the nearest passable navcell.
     95     */
     96    void FindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass);
     97
     98private:
     99    static const u8 CHUNK_SIZE = 64; // number of navcells per side
     100
     101    struct Chunk
     102    {
     103        u8 m_ChunkI, m_ChunkJ; // chunk ID
     104        u16 m_NumRegions; // number of local region IDs (starting from 1)
     105        u16 m_Regions[CHUNK_SIZE][CHUNK_SIZE]; // local region ID per navcell
     106
     107        cassert(CHUNK_SIZE*CHUNK_SIZE/2 < 65536); // otherwise we could overflow m_NumRegions with a checkerboard pattern
     108
     109        void InitRegions(int ci, int cj, Grid<NavcellData>* grid, pass_class_t passClass);
     110
     111        RegionID Get(int i, int j);
     112
     113        void RegionCenter(u16 r, int& i, int& j) const;
     114
     115        bool RegionContainsGoal(u16 r, const PathGoal& goal) const;
     116
     117        void RegionNavcellNearest(u16 r, int iGoal, int jGoal, int& iBest, int& jBest, u32& dist2Best) const;
     118
     119        void FloodFill(int i0, int j0, u16 r);
     120    };
     121
     122    typedef std::map<RegionID, std::set<RegionID> > EdgesMap;
     123
     124    void FindEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges);
     125
     126    void AddDebugEdges(pass_class_t passClass);
     127
     128    void FindReachableRegions(RegionID from, std::set<RegionID>& reachable, pass_class_t passClass);
     129
     130    void FindPassableRegions(std::set<RegionID>& regions, pass_class_t passClass);
     131
     132    /**
     133     * Updates @p iGoal and @p jGoal to the navcell that is the nearest to the
     134     * initial goal coordinates, in one of the given @p regions.
     135     * (Assumes @p regions is non-empty.)
     136     */
     137    void FindNearestNavcellInRegions(const std::set<RegionID>& regions, u16& iGoal, u16& jGoal, pass_class_t passClass);
     138
     139    Chunk& GetChunk(u8 ci, u8 cj, pass_class_t passClass)
     140    {
     141        return m_Chunks[passClass].at(cj * m_ChunksW + ci);
     142    }
     143
     144    PathfinderHierOverlay* m_DebugOverlay;
     145
     146    u16 m_ChunksW, m_ChunksH;
     147    std::map<pass_class_t, std::vector<Chunk> > m_Chunks;
     148   
     149    std::map<pass_class_t, EdgesMap> m_Edges;
     150
     151public:
     152    CCmpPathfinder& m_Pathfinder;
     153    std::vector<SOverlayLine> m_DebugOverlayLines;
     154};
     155
     156class PathfinderHierOverlay : public TerrainTextureOverlay
     157{
     158public:
     159    CCmpPathfinder_Hier& m_PathfinderHier;
     160
     161    PathfinderHierOverlay(CCmpPathfinder_Hier& pathfinderHier) :
     162        TerrainTextureOverlay(ICmpObstructionManager::NAVCELLS_PER_TILE), m_PathfinderHier(pathfinderHier)
     163    {
     164    }
     165
     166    virtual void BuildTextureRGBA(u8* data, size_t w, size_t h)
     167    {
     168        ICmpPathfinder::pass_class_t passClass = m_PathfinderHier.m_Pathfinder.GetPassabilityClass("default");
     169
     170        for (size_t j = 0; j < h; ++j)
     171        {
     172            for (size_t i = 0; i < w; ++i)
     173            {
     174                SColor4ub color;
     175
     176                CCmpPathfinder_Hier::RegionID rid = m_PathfinderHier.Get(i, j, passClass);
     177                if (rid.r == 0)
     178                    color = SColor4ub(0, 0, 0, 0);
     179                else if (rid.r == 0xFFFF)
     180                    color = SColor4ub(255, 0, 255, 255);
     181                else
     182                    color = GetColor(rid.r + rid.ci*5 + rid.cj*7, 127);
     183
     184                *data++ = color.R;
     185                *data++ = color.G;
     186                *data++ = color.B;
     187                *data++ = color.A;
     188            }
     189        }
     190    }
     191};
     192
     193void CCmpPathfinder_Hier::Chunk::InitRegions(int ci, int cj, Grid<NavcellData>* grid, pass_class_t passClass)
     194{
     195    ENSURE(ci < 256 && cj < 256); // avoid overflows
     196    m_ChunkI = ci;
     197    m_ChunkJ = cj;
     198
     199    memset(m_Regions, 0, sizeof(m_Regions));
     200
     201    int i0 = ci * CHUNK_SIZE;
     202    int j0 = cj * CHUNK_SIZE;
     203    int i1 = std::min(i0 + CHUNK_SIZE, (int)grid->m_W);
     204    int j1 = std::min(j0 + CHUNK_SIZE, (int)grid->m_H);
     205
     206    // Start by filling the grid with 0 for blocked,
     207    // and a 0xFFFF placeholder for unblocked
     208    for (int j = j0; j < j1; ++j)
     209    {
     210        for (int i = i0; i < i1; ++i)
     211        {
     212            if (IS_PASSABLE(grid->get(i, j), passClass))
     213                m_Regions[j-j0][i-i0] = 0xFFFF;
     214            else
     215                m_Regions[j-j0][i-i0] = 0;
     216        }
     217    }
     218
     219    // Scan for tiles with the 0xFFFF placeholder, and then floodfill
     220    // the new region this tile belongs to
     221    int r = 0;
     222    for (int j = 0; j < CHUNK_SIZE; ++j)
     223    {
     224        for (int i = 0; i < CHUNK_SIZE; ++i)
     225        {
     226            if (m_Regions[j][i] == 0xFFFF)
     227                FloodFill(i, j, ++r);
     228        }
     229    }
     230
     231    m_NumRegions = r;
     232}
     233
     234/**
     235 * Flood-fill a connected area of navcells that are currently set to
     236 * region 0xFFFF, starting at chunk-local coords (i0,j0),
     237 * and assign them to region r.
     238 */
     239void CCmpPathfinder_Hier::Chunk::FloodFill(int i0, int j0, u16 r)
     240{
     241    std::vector<std::pair<u8, u8> > stack;
     242    stack.push_back(std::make_pair(i0, j0));
     243
     244    while (!stack.empty())
     245    {
     246        int i = stack.back().first;
     247        int j = stack.back().second;
     248        stack.pop_back();
     249        m_Regions[j][i] = r;
     250
     251        if (i > 0 && m_Regions[j][i-1] == 0xFFFF)
     252            stack.push_back(std::make_pair(i-1, j));
     253        if (j > 0 && m_Regions[j-1][i] == 0xFFFF)
     254            stack.push_back(std::make_pair(i, j-1));
     255        if (i < CHUNK_SIZE-1 && m_Regions[j][i+1] == 0xFFFF)
     256            stack.push_back(std::make_pair(i+1, j));
     257        if (j < CHUNK_SIZE-1 && m_Regions[j+1][i] == 0xFFFF)
     258            stack.push_back(std::make_pair(i, j+1));
     259    }
     260}
     261
     262/**
     263 * Returns a RegionID for the given global navcell coords
     264 * (which must be inside this chunk);
     265 */
     266CCmpPathfinder_Hier::RegionID CCmpPathfinder_Hier::Chunk::Get(int i, int j)
     267{
     268    ENSURE(i < CHUNK_SIZE && j < CHUNK_SIZE);
     269    return RegionID(m_ChunkI, m_ChunkJ, m_Regions[j][i]);
     270}
     271
     272/**
     273 * Return the global navcell coords that correspond roughly to the
     274 * center of the given region in this chunk.
     275 * (This is not guaranteed to be actually inside the region.)
     276 */
     277void CCmpPathfinder_Hier::Chunk::RegionCenter(u16 r, int& i_out, int& j_out) const
     278{
     279    // Find the mean of i,j coords of navcells in this region:
     280
     281    u32 si = 0, sj = 0; // sum of i,j coords
     282    u32 n = 0; // number of navcells in region
     283
     284    cassert(CHUNK_SIZE < 256); // conservative limit to ensure si and sj don't overflow
     285
     286    for (int j = 0; j < CHUNK_SIZE; ++j)
     287    {
     288        for (int i = 0; i < CHUNK_SIZE; ++i)
     289        {
     290            if (m_Regions[j][i] == r)
     291            {
     292                si += i;
     293                sj += j;
     294                n += 1;
     295            }
     296        }
     297    }
     298
     299    // Avoid divide-by-zero
     300    if (n == 0)
     301        n = 1;
     302
     303    i_out = m_ChunkI * CHUNK_SIZE + si / n;
     304    j_out = m_ChunkJ * CHUNK_SIZE + sj / n;
     305}
     306
     307/**
     308 * Returns whether any navcell in the given region is inside the goal.
     309 */
     310bool CCmpPathfinder_Hier::Chunk::RegionContainsGoal(u16 r, const PathGoal& goal) const
     311{
     312    // Inefficiently check every single navcell:
     313    for (u16 j = 0; j < CHUNK_SIZE; ++j)
     314    {
     315        for (u16 i = 0; i < CHUNK_SIZE; ++i)
     316        {
     317            if (m_Regions[j][i] == r)
     318            {
     319                if (goal.NavcellContainsGoal(m_ChunkI * CHUNK_SIZE + i, m_ChunkJ * CHUNK_SIZE + j))
     320                    return true;
     321            }
     322        }
     323    }
     324
     325    return false;
     326}
     327
     328/**
     329 * Returns the global navcell coords, and the squared distance to the goal
     330 * navcell, of whichever navcell inside the given region is closest to
     331 * that goal.
     332 */
     333void CCmpPathfinder_Hier::Chunk::RegionNavcellNearest(u16 r, int iGoal, int jGoal, int& iBest, int& jBest, u32& dist2Best) const
     334{
     335    iBest = 0;
     336    jBest = 0;
     337    dist2Best = std::numeric_limits<u32>::max();
     338
     339    for (int j = 0; j < CHUNK_SIZE; ++j)
     340    {
     341        for (int i = 0; i < CHUNK_SIZE; ++i)
     342        {
     343            if (m_Regions[j][i] == r)
     344            {
     345                u32 dist2 = (i + m_ChunkI*CHUNK_SIZE - iGoal)*(i + m_ChunkI*CHUNK_SIZE - iGoal)
     346                          + (j + m_ChunkJ*CHUNK_SIZE - jGoal)*(j + m_ChunkJ*CHUNK_SIZE - jGoal);
     347
     348                if (dist2 < dist2Best)
     349                {
     350                    iBest = i + m_ChunkI*CHUNK_SIZE;
     351                    jBest = j + m_ChunkJ*CHUNK_SIZE;
     352                    dist2Best = dist2;
     353                }
     354            }
     355        }
     356    }
     357}
     358
     359CCmpPathfinder_Hier::CCmpPathfinder_Hier(CCmpPathfinder& pathfinder) :
     360    m_Pathfinder(pathfinder)
     361{
     362    m_DebugOverlay = NULL;
     363//  m_DebugOverlay = new PathfinderHierOverlay(*this);
     364}
     365
     366CCmpPathfinder_Hier::~CCmpPathfinder_Hier()
     367{
     368    SAFE_DELETE(m_DebugOverlay);
     369}
     370
     371void CCmpPathfinder_Hier::Init(const std::vector<PathfinderPassability>& passClasses, Grid<NavcellData>* grid)
     372{
     373    PROFILE3("hier init");
     374
     375    // Divide grid into chunks with round-to-positive-infinity
     376    m_ChunksW = (grid->m_W + CHUNK_SIZE - 1) / CHUNK_SIZE;
     377    m_ChunksH = (grid->m_H + CHUNK_SIZE - 1) / CHUNK_SIZE;
     378
     379    ENSURE(m_ChunksW < 256 && m_ChunksH < 256); // else the u8 Chunk::m_ChunkI will overflow
     380
     381    m_Chunks.clear();
     382    m_Edges.clear();
     383
     384    for (size_t n = 0; n < passClasses.size(); ++n)
     385    {
     386        pass_class_t passClass = passClasses[n].m_Mask;
     387
     388        // Compute the regions within each chunk
     389        m_Chunks[passClass].resize(m_ChunksW*m_ChunksH);
     390        for (int cj = 0; cj < m_ChunksH; ++cj)
     391        {
     392            for (int ci = 0; ci < m_ChunksW; ++ci)
     393            {
     394                m_Chunks[passClass].at(cj*m_ChunksW + ci).InitRegions(ci, cj, grid, passClass);
     395            }
     396        }
     397
     398        // Construct the search graph over the regions
     399       
     400        EdgesMap& edges = m_Edges[passClass];
     401
     402        for (int cj = 0; cj < m_ChunksH; ++cj)
     403        {
     404            for (int ci = 0; ci < m_ChunksW; ++ci)
     405            {
     406                FindEdges(ci, cj, passClass, edges);
     407            }
     408        }
     409    }
     410
     411    if (m_DebugOverlay)
     412    {
     413        PROFILE("debug overlay");
     414        m_DebugOverlayLines.clear();
     415        AddDebugEdges(m_Pathfinder.GetPassabilityClass("default"));
     416    }
     417}
     418
     419/**
     420 * Find edges between regions in this chunk and the adjacent below/left chunks.
     421 */
     422void CCmpPathfinder_Hier::FindEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges)
     423{
     424    std::vector<Chunk>& chunks = m_Chunks[passClass];
     425
     426    Chunk& a = chunks.at(cj*m_ChunksW + ci);
     427
     428    // For each edge between chunks, we loop over every adjacent pair of
     429    // navcells in the two chunks. If they are both in valid regions
     430    // (i.e. are passable navcells) then add a graph edge between those regions.
     431    // (We don't need to test for duplicates since EdgesMap already uses a
     432    // std::set which will drop duplicate entries.)
     433
     434    if (ci > 0)
     435    {
     436        Chunk& b = chunks.at(cj*m_ChunksW + (ci-1));
     437        for (int j = 0; j < CHUNK_SIZE; ++j)
     438        {
     439            RegionID ra = a.Get(0, j);
     440            RegionID rb = b.Get(CHUNK_SIZE-1, j);
     441            if (ra.r && rb.r)
     442            {
     443                edges[ra].insert(rb);
     444                edges[rb].insert(ra);
     445            }
     446        }
     447    }
     448
     449    if (cj > 0)
     450    {
     451        Chunk& b = chunks.at((cj-1)*m_ChunksW + ci);
     452        for (int i = 0; i < CHUNK_SIZE; ++i)
     453        {
     454            RegionID ra = a.Get(i, 0);
     455            RegionID rb = b.Get(i, CHUNK_SIZE-1);
     456            if (ra.r && rb.r)
     457            {
     458                edges[ra].insert(rb);
     459                edges[rb].insert(ra);
     460            }
     461        }
     462    }
     463
     464}
     465
     466/**
     467 * Debug visualisation of graph edges between regions.
     468 */
     469void CCmpPathfinder_Hier::AddDebugEdges(pass_class_t passClass)
     470{
     471    const EdgesMap& edges = m_Edges[passClass];
     472    const std::vector<Chunk>& chunks = m_Chunks[passClass];
     473
     474    for (EdgesMap::const_iterator it = edges.begin(); it != edges.end(); ++it)
     475    {
     476        for (std::set<RegionID>::const_iterator rit = it->second.begin(); rit != it->second.end(); ++rit)
     477        {
     478            // Draw a line between the two regions' centers
     479
     480            int i0, j0, i1, j1;
     481            chunks[it->first.cj * m_ChunksW + it->first.ci].RegionCenter(it->first.r, i0, j0);
     482            chunks[rit->cj * m_ChunksW + rit->ci].RegionCenter(rit->r, i1, j1);
     483
     484            CFixedVector2D a, b;
     485            m_Pathfinder.NavcellCenter(i0, j0, a.X, a.Y);
     486            m_Pathfinder.NavcellCenter(i1, j1, b.X, b.Y);
     487
     488            // Push the endpoints inwards a little to avoid overlaps
     489            CFixedVector2D d = b - a;
     490            d.Normalize(entity_pos_t::FromInt(1));
     491            a += d;
     492            b -= d;
     493
     494            std::vector<float> xz;
     495            xz.push_back(a.X.ToFloat());
     496            xz.push_back(a.Y.ToFloat());
     497            xz.push_back(b.X.ToFloat());
     498            xz.push_back(b.Y.ToFloat());
     499
     500            m_DebugOverlayLines.push_back(SOverlayLine());
     501            m_DebugOverlayLines.back().m_Color = CColor(1.0, 1.0, 1.0, 1.0);
     502            SimRender::ConstructLineOnGround(m_Pathfinder.GetSimContext(), xz, m_DebugOverlayLines.back(), true);
     503        }
     504    }
     505}
     506
     507CCmpPathfinder_Hier::RegionID CCmpPathfinder_Hier::Get(u16 i, u16 j, pass_class_t passClass)
     508{
     509    int ci = i / CHUNK_SIZE;
     510    int cj = j / CHUNK_SIZE;
     511    ENSURE(ci < m_ChunksW && cj < m_ChunksH);
     512    return m_Chunks[passClass][cj*m_ChunksW + ci].Get(i % CHUNK_SIZE, j % CHUNK_SIZE);
     513}
     514
     515bool CCmpPathfinder_Hier::MakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass)
     516{
     517    RegionID source = Get(i0, j0, passClass);
     518
     519    // Find everywhere that's reachable
     520    std::set<RegionID> reachableRegions;
     521    FindReachableRegions(source, reachableRegions, passClass);
     522
     523//  debug_printf(L"\nReachable from (%d,%d): ", i0, j0);
     524//  for (std::set<RegionID>::iterator it = reachableRegions.begin(); it != reachableRegions.end(); ++it)
     525//      debug_printf(L"[%d,%d,%d], ", it->ci, it->cj, it->r);
     526//  debug_printf(L"\n");
     527
     528    // Check whether any reachable region contains the goal
     529    for (std::set<RegionID>::const_iterator it = reachableRegions.begin(); it != reachableRegions.end(); ++it)
     530    {
     531        // Skip region if its chunk doesn't contain the goal area
     532        entity_pos_t x0 = ICmpObstructionManager::NAVCELL_SIZE * (it->ci * CHUNK_SIZE);
     533        entity_pos_t z0 = ICmpObstructionManager::NAVCELL_SIZE * (it->cj * CHUNK_SIZE);
     534        entity_pos_t x1 = x0 + ICmpObstructionManager::NAVCELL_SIZE * CHUNK_SIZE;
     535        entity_pos_t z1 = z0 + ICmpObstructionManager::NAVCELL_SIZE * CHUNK_SIZE;
     536        if (!goal.RectContainsGoal(x0, z0, x1, z1))
     537            continue;
     538
     539        // If the region contains the goal area, the goal is reachable
     540        // and we don't need to move it
     541        if (GetChunk(it->ci, it->cj, passClass).RegionContainsGoal(it->r, goal))
     542            return false;
     543    }
     544
     545    // The goal area wasn't reachable,
     546    // so find the navcell that's nearest to the goal's center
     547
     548    u16 iGoal, jGoal;
     549    m_Pathfinder.NearestNavcell(goal.x, goal.z, iGoal, jGoal);
     550
     551    FindNearestNavcellInRegions(reachableRegions, iGoal, jGoal, passClass);
     552
     553    // Construct a new point goal at the nearest reachable navcell
     554    PathGoal newGoal;
     555    newGoal.type = PathGoal::POINT;
     556    m_Pathfinder.NavcellCenter(iGoal, jGoal, newGoal.x, newGoal.z);
     557    goal = newGoal;
     558
     559    return true;
     560}
     561
     562void CCmpPathfinder_Hier::FindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass)
     563{
     564    std::set<RegionID> regions;
     565    FindPassableRegions(regions, passClass);
     566    FindNearestNavcellInRegions(regions, i, j, passClass);
     567}
     568
     569void CCmpPathfinder_Hier::FindNearestNavcellInRegions(const std::set<RegionID>& regions, u16& iGoal, u16& jGoal, pass_class_t passClass)
     570{
     571    // Find the navcell in the given regions that's nearest to the goal navcell:
     572    // * For each region, record the (squared) minimal distance to the goal point
     573    // * Sort regions by that underestimated distance
     574    // * For each region, find the actual nearest navcell
     575    // * Stop when the underestimated distances are worse than the best real distance
     576
     577    std::vector<std::pair<u32, RegionID> > regionDistEsts; // pair of (distance^2, region)
     578
     579    for (std::set<RegionID>::const_iterator it = regions.begin(); it != regions.end(); ++it)
     580    {
     581        int i0 = it->ci * CHUNK_SIZE;
     582        int j0 = it->cj * CHUNK_SIZE;
     583        int i1 = i0 + CHUNK_SIZE - 1;
     584        int j1 = j0 + CHUNK_SIZE - 1;
     585
     586        // Pick the point in the chunk nearest the goal
     587        int iNear = Clamp((int)iGoal, i0, i1);
     588        int jNear = Clamp((int)jGoal, j0, j1);
     589
     590        int dist2 = (iNear - iGoal)*(iNear - iGoal)
     591                  + (jNear - jGoal)*(jNear - jGoal);
     592
     593        regionDistEsts.push_back(std::make_pair(dist2, *it));
     594    }
     595
     596    // Sort by increasing distance (tie-break on RegionID)
     597    std::sort(regionDistEsts.begin(), regionDistEsts.end());
     598
     599    int iBest = iGoal;
     600    int jBest = jGoal;
     601    u32 dist2Best = std::numeric_limits<u32>::max();
     602
     603    for (size_t n = 0; n < regionDistEsts.size(); ++n)
     604    {
     605        RegionID region = regionDistEsts[n].second;
     606
     607        if (regionDistEsts[n].first >= dist2Best)
     608            break;
     609
     610        int i, j;
     611        u32 dist2;
     612        GetChunk(region.ci, region.cj, passClass).RegionNavcellNearest(region.r, iGoal, jGoal, i, j, dist2);
     613
     614        if (dist2 < dist2Best)
     615        {
     616            iBest = i;
     617            jBest = j;
     618            dist2Best = dist2;
     619        }
     620    }
     621
     622    iGoal = iBest;
     623    jGoal = jBest;
     624}
     625
     626void CCmpPathfinder_Hier::FindReachableRegions(RegionID from, std::set<RegionID>& reachable, pass_class_t passClass)
     627{
     628    // Flood-fill the region graph, starting at 'from',
     629    // collecting all the regions that are reachable via edges
     630
     631    std::vector<RegionID> open;
     632    open.push_back(from);
     633    reachable.insert(from);
     634
     635    while (!open.empty())
     636    {
     637        RegionID curr = open.back();
     638        open.pop_back();
     639
     640        const std::set<RegionID>& neighbours = m_Edges[passClass][curr];
     641        for (std::set<RegionID>::const_iterator it = neighbours.begin(); it != neighbours.end(); ++it)
     642        {
     643            // Add to the reachable set; if this is the first time we added
     644            // it then also add it to the open list
     645            if (reachable.insert(*it).second)
     646                open.push_back(*it);
     647        }
     648    }
     649}
     650
     651void CCmpPathfinder_Hier::FindPassableRegions(std::set<RegionID>& regions, pass_class_t passClass)
     652{
     653    // Construct a set of all regions of all chunks for this pass class
     654
     655    const std::vector<Chunk>& chunks = m_Chunks[passClass];
     656    for (size_t c = 0; c < chunks.size(); ++c)
     657    {
     658        // Skip the impassable region r=0
     659        for (int r = 1; r < chunks[c].m_NumRegions; ++r)
     660            regions.insert(RegionID(chunks[c].m_ChunkI, chunks[c].m_ChunkJ, r));
     661    }
     662}
     663
     664void CCmpPathfinder::PathfinderHierInit()
     665{
     666    m_PathfinderHier = new CCmpPathfinder_Hier(*this);
     667}
     668
     669void CCmpPathfinder::PathfinderHierDeinit()
     670{
     671    SAFE_DELETE(m_PathfinderHier);
     672}
     673
     674void CCmpPathfinder::PathfinderHierReload()
     675{
     676    m_PathfinderHier->Init(m_PassClasses, m_Grid);
     677}
     678
     679void CCmpPathfinder::PathfinderHierRenderSubmit(SceneCollector& collector)
     680{
     681    for (size_t i = 0; i < m_PathfinderHier->m_DebugOverlayLines.size(); ++i)
     682        collector.Submit(&m_PathfinderHier->m_DebugOverlayLines[i]);
     683}
     684
     685bool CCmpPathfinder::PathfinderHierMakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass)
     686{
     687    return m_PathfinderHier->MakeGoalReachable(i0, j0, goal, passClass);
     688}
     689
     690void CCmpPathfinder::PathfinderHierFindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass)
     691{
     692    m_PathfinderHier->FindNearestPassableNavcell(i, j, passClass);
     693}
     694 No newline at end of file
  • source/simulation2/components/CCmpPathfinder_Common.h

     
    3434#include "graphics/Overlay.h"
    3535#include "graphics/Terrain.h"
    3636#include "maths/MathUtil.h"
     37#include "ps/CLogger.h"
     38#include "simulation2/components/ICmpObstructionManager.h"
    3739#include "simulation2/helpers/Geometry.h"
    3840#include "simulation2/helpers/Grid.h"
    3941
    4042class PathfinderOverlay;
    4143class SceneCollector;
    4244struct PathfindTile;
     45struct PathfindTileJPS;
     46class JumpPointCache;
     47class CCmpPathfinder_Hier;
    4348
    4449#ifdef NDEBUG
    4550#define PATHFIND_DEBUG 0
     
    4752#define PATHFIND_DEBUG 1
    4853#endif
    4954
     55#define PATHFIND_USE_JPS 1
     56
    5057/*
    5158 * For efficient pathfinding we want to try hard to minimise the per-tile search cost,
    5259 * so we precompute the tile passability flags and movement costs for the various different
     
    6471 * the class passabilities) so that it can be ignored when doing the accurate short paths.
    6572 * We use another bit to indicate tiles near obstructions that block construction,
    6673 * for the AI to plan safe building spots.
    67  *
    68  * To handle movement costs, we have an arbitrary number of unit cost classes (e.g. "infantry", "camel"),
    69  * and a small number of terrain cost classes (e.g. "grass", "steep grass", "road", "sand"),
    70  * and a cost mapping table between the classes (e.g. camels are fast on sand).
    71  * We need log2(|terrain cost classes|) bits per tile to represent costs.
    72  *
    73  * We could have one passability bitmap per class, and another array for cost classes,
    74  * but instead (for no particular reason) we'll pack them all into a single u16 array.
    75  *
    76  * We handle dynamic updates currently by recomputing the entire array, which is stupid;
    77  * it should only bother updating the region that has changed.
    7874 */
    7975class PathfinderPassability
    8076{
     
    107103        else
    108104            m_MaxShore = std::numeric_limits<fixed>::max();
    109105
     106        if (node.GetChild("Clearance").IsOk())
     107        {
     108            m_HasClearance = true;
     109            m_Clearance = node.GetChild("Clearance").ToFixed();
     110
     111            if (!(m_Clearance % ICmpObstructionManager::NAVCELL_SIZE).IsZero())
     112            {
     113                // If clearance isn't an integer number of navcells then we'll
     114                // probably get weird behaviour when expanding the navcell grid
     115                // by clearance, vs expanding static obstructions by clearance
     116                LOGWARNING(L"Pathfinder passability class has clearance %f, should be multiple of %f",
     117                    m_Clearance.ToFloat(), ICmpObstructionManager::NAVCELL_SIZE.ToFloat());
     118            }
     119        }
     120        else
     121        {
     122            m_HasClearance = false;
     123            m_Clearance = fixed::Zero();
     124        }
    110125    }
    111126
    112127    bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist)
     
    115130    }
    116131
    117132    ICmpPathfinder::pass_class_t m_Mask;
     133
     134    bool m_HasClearance; // whether static obstructions are impassable
     135    fixed m_Clearance; // min distance from static obstructions
     136
    118137private:
    119138    fixed m_MinDepth;
    120139    fixed m_MaxDepth;
     
    123142    fixed m_MaxShore;
    124143};
    125144
    126 typedef u16 TerrainTile;
    127 // 1 bit for pathfinding obstructions,
    128 // 1 bit for construction obstructions (used by AI),
    129 // PASS_CLASS_BITS for terrain passability (allowing PASS_CLASS_BITS classes),
    130 // COST_CLASS_BITS for movement costs (allowing 2^COST_CLASS_BITS classes)
     145typedef u16 NavcellData; // 1 bit per passability class (up to PASS_CLASS_BITS)
     146const int PASS_CLASS_BITS = 16;
     147#define IS_PASSABLE(item, classmask) (((item) & (classmask)) == 0)
     148#define PASS_CLASS_MASK_FROM_INDEX(id) ((pass_class_t)(1u << (id)))
    131149
    132 const int PASS_CLASS_BITS = 10;
    133 const int COST_CLASS_BITS = 16 - (PASS_CLASS_BITS + 2);
    134 #define IS_TERRAIN_PASSABLE(item, classmask) (((item) & (classmask)) == 0)
    135 #define IS_PASSABLE(item, classmask) (((item) & ((classmask) | 1)) == 0)
    136 #define GET_COST_CLASS(item) ((item) >> (PASS_CLASS_BITS + 2))
    137 #define COST_CLASS_MASK(id) ( (TerrainTile) ((id) << (PASS_CLASS_BITS + 2)) )
    138 
    139150typedef SparseGrid<PathfindTile> PathfindTileGrid;
     151typedef SparseGrid<PathfindTileJPS> PathfindTileJPSGrid;
    140152
     153/**
     154 * Represents the 2D coordinates of a tile.
     155 * The i/j components are packed into a single u32, since we usually use these
     156 * objects for equality comparisons and the VC2010 optimizer doesn't seem to automatically
     157 * compare two u16s in a single operation.
     158 */
     159struct TileID
     160{
     161    TileID() { }
     162
     163    TileID(u16 i, u16 j) : data((i << 16) | j) { }
     164
     165    bool operator==(const TileID& b) const
     166    {
     167        return data == b.data;
     168    }
     169
     170    /// Returns lexicographic order over (i,j)
     171    bool operator<(const TileID& b) const
     172    {
     173        return data < b.data;
     174    }
     175
     176    u16 i() const { return data >> 16; }
     177    u16 j() const { return data & 0xFFFF; }
     178
     179private:
     180    u32 data;
     181};
     182
     183/**
     184 * Represents the cost of a path consisting of horizontal/vertical and
     185 * diagonal movements over a uniform-cost grid.
     186 * Maximum path length before overflow is about 45K steps.
     187 */
     188struct PathCost
     189{
     190    PathCost() : data(0) { }
     191
     192    /// Construct from a number of horizontal/vertical and diagonal steps
     193    PathCost(u16 hv, u16 d)
     194        : data(hv*65536 + d*92682) // 2^16 * sqrt(2) == 92681.9
     195    {
     196    }
     197
     198    /// Construct for horizontal/vertical movement of given number of steps
     199    static PathCost horizvert(u16 n)
     200    {
     201        return PathCost(n, 0);
     202    }
     203
     204    /// Construct for diagonal movement of given number of steps
     205    static PathCost diag(u16 n)
     206    {
     207        return PathCost(0, n);
     208    }
     209
     210    PathCost operator+(const PathCost& a) const
     211    {
     212        PathCost c;
     213        c.data = data + a.data;
     214        return c;
     215    }
     216
     217    bool operator<=(const PathCost& b) const { return data <= b.data; }
     218    bool operator< (const PathCost& b) const { return data <  b.data; }
     219    bool operator>=(const PathCost& b) const { return data >= b.data; }
     220    bool operator> (const PathCost& b) const { return data >  b.data; }
     221
     222    u32 ToInt()
     223    {
     224        return data;
     225    }
     226
     227private:
     228    u32 data;
     229};
     230
     231
     232
    141233struct AsyncLongPathRequest
    142234{
    143235    u32 ticket;
    144236    entity_pos_t x0;
    145237    entity_pos_t z0;
    146     ICmpPathfinder::Goal goal;
     238    PathGoal goal;
    147239    ICmpPathfinder::pass_class_t passClass;
    148     ICmpPathfinder::cost_class_t costClass;
    149240    entity_id_t notify;
    150241};
    151242
     
    156247    entity_pos_t z0;
    157248    entity_pos_t r;
    158249    entity_pos_t range;
    159     ICmpPathfinder::Goal goal;
     250    PathGoal goal;
    160251    ICmpPathfinder::pass_class_t passClass;
    161252    bool avoidMovingUnits;
    162253    entity_id_t group;
     
    184275    std::map<std::string, pass_class_t> m_PassClassMasks;
    185276    std::vector<PathfinderPassability> m_PassClasses;
    186277
    187     std::map<std::string, cost_class_t> m_TerrainCostClassTags;
    188     std::map<std::string, cost_class_t> m_UnitCostClassTags;
    189     std::vector<std::vector<u32> > m_MoveCosts; // costs[unitClass][terrainClass]
    190     std::vector<std::vector<fixed> > m_MoveSpeeds; // speeds[unitClass][terrainClass]
    191 
    192278    // Dynamic state:
    193279
    194280    std::vector<AsyncLongPathRequest> m_AsyncLongPathRequests;
     
    199285    // Lazily-constructed dynamic state (not serialized):
    200286
    201287    u16 m_MapSize; // tiles per side
    202     Grid<TerrainTile>* m_Grid; // terrain/passability information
    203     Grid<u8>* m_ObstructionGrid; // cached obstruction information (TODO: we shouldn't bother storing this, it's redundant with LSBs of m_Grid)
     288    Grid<NavcellData>* m_Grid; // terrain/passability information
     289    size_t m_ObstructionGridDirtyID; // dirty ID for ICmpObstructionManager::NeedUpdate
    204290    bool m_TerrainDirty; // indicates if m_Grid has been updated since terrain changed
    205291   
     292    std::map<pass_class_t, shared_ptr<JumpPointCache> > m_JumpPointCache; // for JPS pathfinder
     293
     294    // Interface to the hierarchical pathfinder.
     295    // (This is hidden behind proxy methods to keep the code
     296    // slightly better encapsulated.)
     297    CCmpPathfinder_Hier* m_PathfinderHier;
     298    void PathfinderHierInit();
     299    void PathfinderHierDeinit();
     300    void PathfinderHierReload();
     301    void PathfinderHierRenderSubmit(SceneCollector& collector);
     302    bool PathfinderHierMakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass);
     303    void PathfinderHierFindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass);
     304
     305    void PathfinderJPSMakeDirty();
     306
    206307    // For responsiveness we will process some moves in the same turn they were generated in
    207308   
    208309    u16 m_MaxSameTurnMoves; // max number of moves that can be created and processed in the same turn
     
    210311    // Debugging - output from last pathfind operation:
    211312
    212313    PathfindTileGrid* m_DebugGrid;
     314    PathfindTileJPSGrid* m_DebugGridJPS;
    213315    u32 m_DebugSteps;
     316    double m_DebugTime;
     317    PathGoal m_DebugGoal;
    214318    Path* m_DebugPath;
    215319    PathfinderOverlay* m_DebugOverlay;
    216320    pass_class_t m_DebugPassClass;
     
    236340
    237341    virtual std::map<std::string, pass_class_t> GetPassabilityClasses();
    238342
    239     virtual cost_class_t GetCostClass(const std::string& name);
     343    const PathfinderPassability* GetPassabilityFromMask(pass_class_t passClass);
    240344
    241345    virtual const Grid<u16>& GetPassabilityGrid();
    242346
    243     virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, Path& ret);
     347    virtual void ComputePath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, Path& ret);
    244348
    245     virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify);
     349    virtual void ComputePathJPS(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, Path& ret);
    246350
    247     virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, Path& ret);
     351    /**
     352     * Same kind of interface as ICmpPathfinder::ComputePath, but works when the
     353     * unit is starting on an impassable navcell. Returns a path heading directly
     354     * to the nearest passable navcell.
     355     */
     356    void ComputePathOffImpassable(u16 i0, u16 j0, pass_class_t passClass, Path& ret);
    248357
    249     virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify);
     358    /**
     359     * Given a path with an arbitrary collection of waypoints, updates the
     360     * waypoints to be nicer. (In particular, the current implementation
     361     * ensures a consistent maximum distance between adjacent waypoints.
     362     * Might be nice to improve it to smooth the paths, etc.)
     363     */
     364    void NormalizePathWaypoints(Path& path);
    250365
    251     virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass);
     366    virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify);
    252367
     368    virtual void ComputeShortPath(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, Path& ret);
     369
     370    virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify);
     371
     372    virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass);
     373
    253374    virtual void ResetDebugPath();
    254375
    255376    virtual void SetDebugOverlay(bool enabled);
    256377
    257     virtual fixed GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, cost_class_t costClass);
     378    virtual void GetDebugData(u32& steps, double& time, Grid<u8>& grid);
    258379
    259     virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const Goal& goal);
     380    virtual void GetDebugDataJPS(u32& steps, double& time, Grid<u8>& grid);
    260381
     382    virtual CFixedVector2D GetNearestPointOnGoal(CFixedVector2D pos, const PathGoal& goal);
     383
    261384    virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass);
    262385
    263386    virtual bool CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass);
     
    273396    virtual void ProcessSameTurnMoves();
    274397
    275398    /**
    276      * Returns the tile containing the given position
     399     * Compute the navcell indexes on the grid nearest to a given point
    277400     */
    278     void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j)
     401    void NearestNavcell(entity_pos_t x, entity_pos_t z, u16& i, u16& j)
    279402    {
    280         i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, m_MapSize-1);
    281         j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, m_MapSize-1);
     403        i = (u16)clamp((x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToNegInfinity(), 0, m_MapSize*ICmpObstructionManager::NAVCELLS_PER_TILE - 1);
     404        j = (u16)clamp((z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToNegInfinity(), 0, m_MapSize*ICmpObstructionManager::NAVCELLS_PER_TILE - 1);
    282405    }
    283406
    284407    /**
     
    286409     */
    287410    static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
    288411    {
     412        cassert(TERRAIN_TILE_SIZE % 2 == 0);
    289413        x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
    290414        z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
    291415    }
    292416
    293     static fixed DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal);
     417    static void NavcellCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
     418    {
     419        x = entity_pos_t::FromInt(i*2 + 1).Multiply(ICmpObstructionManager::NAVCELL_SIZE / 2);
     420        z = entity_pos_t::FromInt(j*2 + 1).Multiply(ICmpObstructionManager::NAVCELL_SIZE / 2);
     421    }
    294422
    295423    /**
    296424     * Regenerates the grid based on the current obstruction list, if necessary
    297425     */
    298426    void UpdateGrid();
    299427
     428    Grid<u16> ComputeShoreGrid();
     429
     430    void ComputeTerrainPassabilityGrid(const Grid<u16>& shoreGrid);
     431
    300432    void RenderSubmit(SceneCollector& collector);
    301433};
    302434
  • source/simulation2/components/CCmpPathfinder.cpp

     
    3333#include "simulation2/components/ICmpObstructionManager.h"
    3434#include "simulation2/components/ICmpTerrain.h"
    3535#include "simulation2/components/ICmpWaterManager.h"
     36#include "simulation2/helpers/Rasterize.h"
    3637#include "simulation2/serialization/SerializeTemplates.h"
    3738
    3839// Default cost to move a single tile is a fairly arbitrary number, which should be big
     
    4647{
    4748    m_MapSize = 0;
    4849    m_Grid = NULL;
    49     m_ObstructionGrid = NULL;
     50    m_ObstructionGridDirtyID = 0;
    5051    m_TerrainDirty = true;
    5152    m_NextAsyncTicket = 1;
    5253
    5354    m_DebugOverlay = NULL;
    5455    m_DebugGrid = NULL;
     56    m_DebugGridJPS = NULL;
    5557    m_DebugPath = NULL;
    5658
     59    PathfinderHierInit();
     60
    5761    m_SameTurnMovesCount = 0;
    5862
    5963    // Since this is used as a system component (not loaded from an entity template),
     
    8690    {
    8791        std::string name = it->first;
    8892        ENSURE((int)m_PassClasses.size() <= PASS_CLASS_BITS);
    89         pass_class_t mask = (pass_class_t)(1u << (m_PassClasses.size() + 2));
     93        pass_class_t mask = PASS_CLASS_MASK_FROM_INDEX(m_PassClasses.size());
    9094        m_PassClasses.push_back(PathfinderPassability(mask, it->second));
    9195        m_PassClassMasks[name] = mask;
    9296    }
    93 
    94 
    95     const CParamNode::ChildrenMap& moveClasses = externalParamNode.GetChild("Pathfinder").GetChild("MovementClasses").GetChildren();
    96 
    97     // First find the set of unit classes used by any terrain classes,
    98     // and assign unique tags to terrain classes
    99     std::set<std::string> unitClassNames;
    100     unitClassNames.insert("default"); // must always have costs for default
    101 
    102     {
    103         size_t i = 0;
    104         for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it)
    105         {
    106             std::string terrainClassName = it->first;
    107             m_TerrainCostClassTags[terrainClassName] = (cost_class_t)i;
    108             ++i;
    109 
    110             const CParamNode::ChildrenMap& unitClasses = it->second.GetChild("UnitClasses").GetChildren();
    111             for (CParamNode::ChildrenMap::const_iterator uit = unitClasses.begin(); uit != unitClasses.end(); ++uit)
    112                 unitClassNames.insert(uit->first);
    113         }
    114     }
    115 
    116     // For each terrain class, set the costs for every unit class,
    117     // and assign unique tags to unit classes
    118     {
    119         size_t i = 0;
    120         for (std::set<std::string>::const_iterator nit = unitClassNames.begin(); nit != unitClassNames.end(); ++nit)
    121         {
    122             m_UnitCostClassTags[*nit] = (cost_class_t)i;
    123             ++i;
    124 
    125             std::vector<u32> costs;
    126             std::vector<fixed> speeds;
    127 
    128             for (CParamNode::ChildrenMap::const_iterator it = moveClasses.begin(); it != moveClasses.end(); ++it)
    129             {
    130                 // Default to the general costs for this terrain class
    131                 fixed cost = it->second.GetChild("@Cost").ToFixed();
    132                 fixed speed = it->second.GetChild("@Speed").ToFixed();
    133                 // Check for specific cost overrides for this unit class
    134                 const CParamNode& unitClass = it->second.GetChild("UnitClasses").GetChild(nit->c_str());
    135                 if (unitClass.IsOk())
    136                 {
    137                     cost = unitClass.GetChild("@Cost").ToFixed();
    138                     speed = unitClass.GetChild("@Speed").ToFixed();
    139                 }
    140                 costs.push_back((cost * DEFAULT_MOVE_COST).ToInt_RoundToZero());
    141                 speeds.push_back(speed);
    142             }
    143 
    144             m_MoveCosts.push_back(costs);
    145             m_MoveSpeeds.push_back(speeds);
    146         }
    147     }
    14897}
    14998
    15099void CCmpPathfinder::Deinit()
     
    152101    SetDebugOverlay(false); // cleans up memory
    153102    ResetDebugPath();
    154103
    155     delete m_Grid;
    156     delete m_ObstructionGrid;
     104    PathfinderHierDeinit();
     105
     106    SAFE_DELETE(m_Grid);
    157107}
    158108
    159109struct SerializeLongRequest
     
    166116        serialize.NumberFixed_Unbounded("z0", value.z0);
    167117        SerializeGoal()(serialize, "goal", value.goal);
    168118        serialize.NumberU16_Unbounded("pass class", value.passClass);
    169         serialize.NumberU8_Unbounded("cost class", value.costClass);
    170119        serialize.NumberU32_Unbounded("notify", value.notify);
    171120    }
    172121};
     
    235184{
    236185    for (size_t i = 0; i < m_DebugOverlayShortPathLines.size(); ++i)
    237186        collector.Submit(&m_DebugOverlayShortPathLines[i]);
     187
     188    PathfinderHierRenderSubmit(collector);
    238189}
    239190
    240191
    241 fixed CCmpPathfinder::GetMovementSpeed(entity_pos_t x0, entity_pos_t z0, u8 costClass)
    242 {
    243     UpdateGrid();
    244 
    245     u16 i, j;
    246     NearestTile(x0, z0, i, j);
    247     TerrainTile tileTag = m_Grid->get(i, j);
    248     return m_MoveSpeeds.at(costClass).at(GET_COST_CLASS(tileTag));
    249 }
    250 
    251192ICmpPathfinder::pass_class_t CCmpPathfinder::GetPassabilityClass(const std::string& name)
    252193{
    253194    if (m_PassClassMasks.find(name) == m_PassClassMasks.end())
     
    264205    return m_PassClassMasks;
    265206}
    266207
    267 ICmpPathfinder::cost_class_t CCmpPathfinder::GetCostClass(const std::string& name)
     208const PathfinderPassability* CCmpPathfinder::GetPassabilityFromMask(pass_class_t passClass)
    268209{
    269     if (m_UnitCostClassTags.find(name) == m_UnitCostClassTags.end())
    270     {
    271         LOGERROR(L"Invalid unit cost class name '%hs'", name.c_str());
    272         return m_UnitCostClassTags["default"];
    273     }
    274 
    275     return m_UnitCostClassTags[name];
     210    for (size_t i = 0; i < m_PassClasses.size(); ++i)
     211        if (m_PassClasses[i].m_Mask == passClass)
     212            return &m_PassClasses[i];
     213    return NULL;
    276214}
    277215
    278 fixed CCmpPathfinder::DistanceToGoal(CFixedVector2D pos, const CCmpPathfinder::Goal& goal)
    279 {
    280     switch (goal.type)
    281     {
    282     case CCmpPathfinder::Goal::POINT:
    283         return (pos - CFixedVector2D(goal.x, goal.z)).Length();
    284 
    285     case CCmpPathfinder::Goal::CIRCLE:
    286         return ((pos - CFixedVector2D(goal.x, goal.z)).Length() - goal.hw).Absolute();
    287 
    288     case CCmpPathfinder::Goal::SQUARE:
    289     {
    290         CFixedVector2D halfSize(goal.hw, goal.hh);
    291         CFixedVector2D d(pos.X - goal.x, pos.Y - goal.z);
    292         return Geometry::DistanceToSquare(d, goal.u, goal.v, halfSize);
    293     }
    294 
    295     default:
    296         debug_warn(L"invalid type");
    297         return fixed::Zero();
    298     }
    299 }
    300 
    301216const Grid<u16>& CCmpPathfinder::GetPassabilityGrid()
    302217{
    303218    UpdateGrid();
    304219    return *m_Grid;
    305220}
    306221
    307 void CCmpPathfinder::UpdateGrid()
     222/**
     223 * Given a grid of passable/impassable navcells (based on some passability mask),
     224 * computes a new grid where a navcell is impassable (per that mask) if
     225 * it is <=clearance navcells away from an impassable navcell in the original grid.
     226 * The results are ORed onto the original grid.
     227 *
     228 * This is used for adding clearance onto terrain-based navcell passability.
     229 *
     230 * TODO: might be nicer to get rounded corners by measuring clearances as
     231 * Euclidean distances; currently it effectively does dist=max(dx,dy) instead.
     232 */
     233static void ExpandImpassableCells(Grid<u16>& grid, u16 clearance, ICmpPathfinder::pass_class_t mask)
    308234{
    309     CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
    310     if (!cmpTerrain)
    311         return; // error
     235    PROFILE3("ExpandImpassableCells");
    312236
    313     // If the terrain was resized then delete the old grid data
    314     if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide())
    315     {
    316         SAFE_DELETE(m_Grid);
    317         SAFE_DELETE(m_ObstructionGrid);
    318         m_TerrainDirty = true;
    319     }
     237    u16 w = grid.m_W;
     238    u16 h = grid.m_H;
    320239
    321     // Initialise the terrain data when first needed
    322     if (!m_Grid)
     240    // First expand impassable cells horizontally into a temporary 1-bit grid
     241    Grid<u8> tempGrid(w, h);
     242    for (u16 j = 0; j < h; ++j)
    323243    {
    324         m_MapSize = cmpTerrain->GetTilesPerSide();
    325         m_Grid = new Grid<TerrainTile>(m_MapSize, m_MapSize);
    326         m_ObstructionGrid = new Grid<u8>(m_MapSize, m_MapSize);
    327     }
     244        // New cell (i,j) is blocked if (i',j) blocked for any i-clearance <= i' <= i+clearance
    328245
    329     CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
     246        // Count the number of blocked cells around i=0
     247        u16 numBlocked = 0;
     248        for (u16 i = 0; i <= clearance && i < w; ++i)
     249            if (!IS_PASSABLE(grid.get(i, j), mask))
     250                ++numBlocked;
    330251
    331     bool obstructionsDirty = cmpObstructionManager->Rasterise(*m_ObstructionGrid);
     252        for (u16 i = 0; i < w; ++i)
     253        {
     254            // Store a flag if blocked by at least one nearby cell
     255            if (numBlocked)
     256                tempGrid.set(i, j, 1);
    332257
    333     if (obstructionsDirty && !m_TerrainDirty)
     258            // Slide the numBlocked window along:
     259            // remove the old i-clearance value, add the new (i+1)+clearance
     260            // (avoiding overflowing the grid)
     261            if (i >= clearance && !IS_PASSABLE(grid.get(i-clearance, j), mask))
     262                --numBlocked;
     263            if (i+1+clearance < w && !IS_PASSABLE(grid.get(i+1+clearance, j), mask))
     264                ++numBlocked;
     265        }
     266    }
     267
     268    for (u16 i = 0; i < w; ++i)
    334269    {
    335         PROFILE("UpdateGrid obstructions");
     270        // New cell (i,j) is blocked if (i,j') blocked for any j-clearance <= j' <= j+clearance
    336271
    337         // Obstructions changed - we need to recompute passability
    338         // Since terrain hasn't changed we only need to update the obstruction bits
    339         // and can skip the rest of the data
     272        // Count the number of blocked cells around j=0
     273        u16 numBlocked = 0;
     274        for (u16 j = 0; j <= clearance && j < h; ++j)
     275            if (tempGrid.get(i, j))
     276                ++numBlocked;
    340277
    341         // TODO: if ObstructionManager::SetPassabilityCircular was called at runtime
    342         // (which should probably never happen, but that's not guaranteed),
    343         // then TILE_OUTOFBOUNDS will change and we can't use this fast path, but
    344         // currently it'll just set obstructionsDirty and we won't notice
    345 
    346         for (u16 j = 0; j < m_MapSize; ++j)
     278        for (u16 j = 0; j < h; ++j)
    347279        {
    348             for (u16 i = 0; i < m_MapSize; ++i)
    349             {
    350                 TerrainTile& t = m_Grid->get(i, j);
     280            // Add the mask if blocked by at least one nearby cell
     281            if (numBlocked)
     282                grid.set(i, j, grid.get(i, j) | mask);
    351283
    352                 u8 obstruct = m_ObstructionGrid->get(i, j);
    353 
    354                 if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
    355                     t |= 1;
    356                 else
    357                     t &= (TerrainTile)~1;
    358 
    359                 if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION)
    360                     t |= 2;
    361                 else
    362                     t &= (TerrainTile)~2;
    363             }
     284            // Slide the numBlocked window along:
     285            // remove the old j-clearance value, add the new (j+1)+clearance
     286            // (avoiding overflowing the grid)
     287            if (j >= clearance && tempGrid.get(i, j-clearance))
     288                --numBlocked;
     289            if (j+1+clearance < h && tempGrid.get(i, j+1+clearance))
     290                ++numBlocked;
    364291        }
    365 
    366         ++m_Grid->m_DirtyID;
    367292    }
    368     else if (obstructionsDirty || m_TerrainDirty)
    369     {
    370         PROFILE("UpdateGrid full");
     293}
    371294
    372         // Obstructions or terrain changed - we need to recompute passability
    373         // TODO: only bother recomputing the region that has actually changed
     295Grid<u16> CCmpPathfinder::ComputeShoreGrid()
     296{
     297    PROFILE3("ComputeShoreGrid");
    374298
    375         CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
     299    CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
    376300
    377         // TOOD: these bits should come from ICmpTerrain
    378         CTerrain& terrain = GetSimContext().GetTerrain();
     301    // TOOD: these bits should come from ICmpTerrain
     302    CTerrain& terrain = GetSimContext().GetTerrain();
    379303
    380         // avoid integer overflow in intermediate calculation
    381         const u16 shoreMax = 32767;
    382        
    383         // First pass - find underwater tiles
    384         Grid<bool> waterGrid(m_MapSize, m_MapSize);
    385         for (u16 j = 0; j < m_MapSize; ++j)
     304    // avoid integer overflow in intermediate calculation
     305    const u16 shoreMax = 32767;
     306
     307    // First pass - find underwater tiles
     308    Grid<u8> waterGrid(m_MapSize, m_MapSize);
     309    for (u16 j = 0; j < m_MapSize; ++j)
     310    {
     311        for (u16 i = 0; i < m_MapSize; ++i)
    386312        {
    387             for (u16 i = 0; i < m_MapSize; ++i)
    388             {
    389                 fixed x, z;
    390                 TileCenter(i, j, x, z);
    391                
    392                 bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z));
    393                 waterGrid.set(i, j, underWater);
    394             }
     313            fixed x, z;
     314            TileCenter(i, j, x, z);
     315
     316            bool underWater = cmpWaterManager && (cmpWaterManager->GetWaterLevel(x, z) > terrain.GetExactGroundLevelFixed(x, z));
     317            waterGrid.set(i, j, underWater ? 1 : 0);
    395318        }
    396         // Second pass - find shore tiles
    397         Grid<u16> shoreGrid(m_MapSize, m_MapSize);
    398         for (u16 j = 0; j < m_MapSize; ++j)
     319    }
     320
     321    // Second pass - find shore tiles
     322    Grid<u16> shoreGrid(m_MapSize, m_MapSize);
     323    for (u16 j = 0; j < m_MapSize; ++j)
     324    {
     325        for (u16 i = 0; i < m_MapSize; ++i)
    399326        {
    400             for (u16 i = 0; i < m_MapSize; ++i)
     327            // Find a land tile
     328            if (!waterGrid.get(i, j))
    401329            {
    402                 // Find a land tile
    403                 if (!waterGrid.get(i, j))
     330                if ((i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1))
     331                    || (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1))
     332                    || (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1))
     333                    )
     334                {   // If it's bordered by water, it's a shore tile
     335                    shoreGrid.set(i, j, 0);
     336                }
     337                else
    404338                {
    405                     if ((i > 0 && waterGrid.get(i-1, j)) || (i > 0 && j < m_MapSize-1 && waterGrid.get(i-1, j+1)) || (i > 0 && j > 0 && waterGrid.get(i-1, j-1))
    406                         || (i < m_MapSize-1 && waterGrid.get(i+1, j)) || (i < m_MapSize-1 && j < m_MapSize-1 && waterGrid.get(i+1, j+1)) || (i < m_MapSize-1 && j > 0 && waterGrid.get(i+1, j-1))
    407                         || (j > 0 && waterGrid.get(i, j-1)) || (j < m_MapSize-1 && waterGrid.get(i, j+1))
    408                         )
    409                     {   // If it's bordered by water, it's a shore tile
    410                         shoreGrid.set(i, j, 0);
    411                     }
    412                     else
    413                     {
    414                         shoreGrid.set(i, j, shoreMax);
    415                     }
     339                    shoreGrid.set(i, j, shoreMax);
    416340                }
    417341            }
    418342        }
     343    }
    419344
    420         // Expand influences on land to find shore distance
    421         for (u16 y = 0; y < m_MapSize; ++y)
     345    // Expand influences on land to find shore distance
     346    for (u16 y = 0; y < m_MapSize; ++y)
     347    {
     348        u16 min = shoreMax;
     349        for (u16 x = 0; x < m_MapSize; ++x)
    422350        {
    423             u16 min = shoreMax;
    424             for (u16 x = 0; x < m_MapSize; ++x)
     351            if (!waterGrid.get(x, y))
    425352            {
    426                 if (!waterGrid.get(x, y))
    427                 {
    428                     u16 g = shoreGrid.get(x, y);
    429                     if (g > min)
    430                         shoreGrid.set(x, y, min);
    431                     else if (g < min)
    432                         min = g;
     353                u16 g = shoreGrid.get(x, y);
     354                if (g > min)
     355                    shoreGrid.set(x, y, min);
     356                else if (g < min)
     357                    min = g;
    433358
    434                     ++min;
    435                 }
     359                ++min;
    436360            }
    437             for (u16 x = m_MapSize; x > 0; --x)
     361        }
     362        for (u16 x = m_MapSize; x > 0; --x)
     363        {
     364            if (!waterGrid.get(x-1, y))
    438365            {
    439                 if (!waterGrid.get(x-1, y))
    440                 {
    441                     u16 g = shoreGrid.get(x-1, y);
    442                     if (g > min)
    443                         shoreGrid.set(x-1, y, min);
    444                     else if (g < min)
    445                         min = g;
     366                u16 g = shoreGrid.get(x-1, y);
     367                if (g > min)
     368                    shoreGrid.set(x-1, y, min);
     369                else if (g < min)
     370                    min = g;
    446371
    447                     ++min;
    448                 }
     372                ++min;
    449373            }
    450374        }
    451         for (u16 x = 0; x < m_MapSize; ++x)
     375    }
     376    for (u16 x = 0; x < m_MapSize; ++x)
     377    {
     378        u16 min = shoreMax;
     379        for (u16 y = 0; y < m_MapSize; ++y)
    452380        {
    453             u16 min = shoreMax;
    454             for (u16 y = 0; y < m_MapSize; ++y)
     381            if (!waterGrid.get(x, y))
    455382            {
    456                 if (!waterGrid.get(x, y))
    457                 {
    458                     u16 g = shoreGrid.get(x, y);
    459                     if (g > min)
    460                         shoreGrid.set(x, y, min);
    461                     else if (g < min)
    462                         min = g;
     383                u16 g = shoreGrid.get(x, y);
     384                if (g > min)
     385                    shoreGrid.set(x, y, min);
     386                else if (g < min)
     387                    min = g;
    463388
    464                     ++min;
    465                 }
     389                ++min;
    466390            }
    467             for (u16 y = m_MapSize; y > 0; --y)
     391        }
     392        for (u16 y = m_MapSize; y > 0; --y)
     393        {
     394            if (!waterGrid.get(x, y-1))
    468395            {
    469                 if (!waterGrid.get(x, y-1))
    470                 {
    471                     u16 g = shoreGrid.get(x, y-1);
    472                     if (g > min)
    473                         shoreGrid.set(x, y-1, min);
    474                     else if (g < min)
    475                         min = g;
     396                u16 g = shoreGrid.get(x, y-1);
     397                if (g > min)
     398                    shoreGrid.set(x, y-1, min);
     399                else if (g < min)
     400                    min = g;
    476401
    477                     ++min;
    478                 }
     402                ++min;
    479403            }
    480404        }
     405    }
    481406
    482         // Apply passability classes to terrain
    483         for (u16 j = 0; j < m_MapSize; ++j)
     407    return shoreGrid;
     408}
     409
     410void CCmpPathfinder::ComputeTerrainPassabilityGrid(const Grid<u16>& shoreGrid)
     411{
     412    PROFILE3("terrain passability");
     413
     414    CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
     415
     416    CTerrain& terrain = GetSimContext().GetTerrain();
     417
     418    // Compute initial terrain-dependent passability
     419    for (int j = 0; j < m_MapSize * ICmpObstructionManager::NAVCELLS_PER_TILE; ++j)
     420    {
     421        for (int i = 0; i < m_MapSize * ICmpObstructionManager::NAVCELLS_PER_TILE; ++i)
    484422        {
    485             for (u16 i = 0; i < m_MapSize; ++i)
     423            // World-space coordinates for this navcell
     424            fixed x, z;
     425            NavcellCenter(i, j, x, z);
     426
     427            // Terrain-tile coordinates for this navcell
     428            int itile = i / ICmpObstructionManager::NAVCELLS_PER_TILE;
     429            int jtile = j / ICmpObstructionManager::NAVCELLS_PER_TILE;
     430
     431            // Gather all the data potentially needed to determine passability:
     432
     433            fixed height = terrain.GetExactGroundLevelFixed(x, z);
     434
     435            fixed water;
     436            if (cmpWaterManager)
     437                water = cmpWaterManager->GetWaterLevel(x, z);
     438
     439            fixed depth = water - height;
     440
     441            //fixed slope = terrain.GetExactSlopeFixed(x, z);
     442            // Exact slopes give kind of weird output, so just use rough tile-based slopes
     443            fixed slope = terrain.GetSlopeFixed(itile, jtile);
     444
     445            // Get world-space coordinates from shoreGrid (which uses terrain tiles)
     446            fixed shoredist = fixed::FromInt(shoreGrid.get(itile, jtile)) * (int)TERRAIN_TILE_SIZE;
     447
     448            // Compute the passability for every class for this cell:
     449
     450            NavcellData t = 0;
     451            for (size_t n = 0; n < m_PassClasses.size(); ++n)
    486452            {
    487                 fixed x, z;
    488                 TileCenter(i, j, x, z);
     453                if (!m_PassClasses[n].IsPassable(depth, slope, shoredist))
     454                    t |= m_PassClasses[n].m_Mask;
     455            }
    489456
    490                 TerrainTile t = 0;
     457            m_Grid->set(i, j, t);
     458        }
     459    }
     460}
    491461
    492                 u8 obstruct = m_ObstructionGrid->get(i, j);
     462void CCmpPathfinder::UpdateGrid()
     463{
     464    CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     465    if (!cmpTerrain)
     466        return; // error
    493467
    494                 fixed height = terrain.GetExactGroundLevelFixed(x, z);
     468    // If the terrain was resized then delete the old grid data
     469    if (m_Grid && m_MapSize != cmpTerrain->GetTilesPerSide())
     470    {
     471        SAFE_DELETE(m_Grid);
     472        m_TerrainDirty = true;
     473    }
    495474
    496                 fixed water;
    497                 if (cmpWaterManager)
    498                     water = cmpWaterManager->GetWaterLevel(x, z);
     475    // Initialise the terrain data when first needed
     476    if (!m_Grid)
     477    {
     478        m_MapSize = cmpTerrain->GetTilesPerSide();
     479        m_Grid = new Grid<NavcellData>(m_MapSize * ICmpObstructionManager::NAVCELLS_PER_TILE, m_MapSize * ICmpObstructionManager::NAVCELLS_PER_TILE);
     480        m_ObstructionGridDirtyID = 0;
     481    }
    499482
    500                 fixed depth = water - height;
     483    CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
    501484
    502                 fixed slope = terrain.GetSlopeFixed(i, j);
     485    bool obstructionsDirty = cmpObstructionManager->NeedUpdate(&m_ObstructionGridDirtyID);
    503486
    504                 fixed shoredist = fixed::FromInt(shoreGrid.get(i, j));
     487    // TODO: for performance, it'd be nice if we could get away with not
     488    // recomputing all the terrain passability when only an obstruction has
     489    // changed. But that's not supported yet, so recompute everything after
     490    // every change:
     491    if (obstructionsDirty || m_TerrainDirty)
     492    {
     493        PROFILE3("UpdateGrid full");
    505494
    506                 if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_PATHFINDING)
    507                     t |= 1;
     495        // Obstructions or terrain changed - we need to recompute passability
     496        // TODO: only bother recomputing the region that has actually changed
    508497
    509                 if (obstruct & ICmpObstructionManager::TILE_OBSTRUCTED_FOUNDATION)
    510                     t |= 2;
     498        Grid<u16> shoreGrid = ComputeShoreGrid();
    511499
    512                 if (obstruct & ICmpObstructionManager::TILE_OUTOFBOUNDS)
     500        ComputeTerrainPassabilityGrid(shoreGrid);
     501
     502        if (1) // XXX: if circular
     503        {
     504            PROFILE3("off-world passability");
     505
     506            // WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this
     507            const int edgeSize = 3 * ICmpObstructionManager::NAVCELLS_PER_TILE; // number of tiles around the edge that will be off-world
     508
     509            NavcellData edgeMask = 0;
     510            for (size_t n = 0; n < m_PassClasses.size(); ++n)
     511                edgeMask |= m_PassClasses[n].m_Mask;
     512
     513            int w = m_Grid->m_W;
     514            int h = m_Grid->m_H;
     515            for (int j = 0; j < h; ++j)
     516            {
     517                for (int i = 0; i < w; ++i)
    513518                {
    514                     // If out of bounds, nobody is allowed to pass
    515                     for (size_t n = 0; n < m_PassClasses.size(); ++n)
    516                         t |= m_PassClasses[n].m_Mask;
    517                 }
    518                 else
    519                 {
    520                     for (size_t n = 0; n < m_PassClasses.size(); ++n)
    521                     {
    522                         if (!m_PassClasses[n].IsPassable(depth, slope, shoredist))
    523                             t |= m_PassClasses[n].m_Mask;
    524                     }
    525                 }
     519                    // Based on CCmpRangeManager::LosIsOffWorld
     520                    // but tweaked since it's tile-based instead.
     521                    // (We double all the values so we can handle half-tile coordinates.)
     522                    // This needs to be slightly tighter than the LOS circle,
     523                    // else units might get themselves lost in the SoD around the edge.
    526524
    527                 std::string moveClass = terrain.GetMovementClass(i, j);
    528                 if (m_TerrainCostClassTags.find(moveClass) != m_TerrainCostClassTags.end())
    529                     t |= COST_CLASS_MASK(m_TerrainCostClassTags[moveClass]);
     525                    int dist2 = (i*2 + 1 - w)*(i*2 + 1 - w)
     526                        + (j*2 + 1 - h)*(j*2 + 1 - h);
    530527
    531                 m_Grid->set(i, j, t);
     528                    if (dist2 >= (w - 2*edgeSize) * (h - 2*edgeSize))
     529                        m_Grid->set(i, j, m_Grid->get(i, j) | edgeMask);
     530                }
    532531            }
    533532        }
    534533
     534        // Expand the impassability grid, for any class with non-zero clearance,
     535        // so that we can stop units getting too close to impassable navcells
     536        for (size_t n = 0; n < m_PassClasses.size(); ++n)
     537        {
     538            if (m_PassClasses[n].m_HasClearance)
     539            {
     540                // TODO: if multiple classes have the same clearance, we should
     541                // only bother doing this once for them all
     542                int clearance = (m_PassClasses[n].m_Clearance / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToInfinity();
     543                if (clearance > 0)
     544                    ExpandImpassableCells(*m_Grid, clearance, m_PassClasses[n].m_Mask);
     545            }
     546        }
     547
     548        // Add obstructions onto the grid, for any class with (possibly zero) clearance
     549        for (size_t n = 0; n < m_PassClasses.size(); ++n)
     550        {
     551            // TODO: if multiple classes have the same clearance, we should
     552            // only bother running Rasterize once for them all
     553            if (m_PassClasses[n].m_HasClearance)
     554                cmpObstructionManager->Rasterize(*m_Grid, m_PassClasses[n].m_Clearance, ICmpObstructionManager::FLAG_BLOCK_PATHFINDING, m_PassClasses[n].m_Mask);
     555        }
     556
    535557        m_TerrainDirty = false;
    536558
    537559        ++m_Grid->m_DirtyID;
     560
     561        PathfinderHierReload();
     562
     563        PathfinderJPSMakeDirty();
    538564    }
    539565}
    540566
     
    542568
    543569// Async path requests:
    544570
    545 u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const Goal& goal, pass_class_t passClass, cost_class_t costClass, entity_id_t notify)
     571u32 CCmpPathfinder::ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify)
    546572{
    547     AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, costClass, notify };
     573    AsyncLongPathRequest req = { m_NextAsyncTicket++, x0, z0, goal, passClass, notify };
    548574    m_AsyncLongPathRequests.push_back(req);
    549575    return req.ticket;
    550576}
    551577
    552 u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const Goal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify)
     578u32 CCmpPathfinder::ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t r, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t group, entity_id_t notify)
    553579{
    554580    AsyncShortPathRequest req = { m_NextAsyncTicket++, x0, z0, r, range, goal, passClass, avoidMovingUnits, group, notify };
    555581    m_AsyncShortPathRequests.push_back(req);
     
    580606    {
    581607        const AsyncLongPathRequest& req = longRequests[i];
    582608        Path path;
    583         ComputePath(req.x0, req.z0, req.goal, req.passClass, req.costClass, path);
     609#if PATHFIND_USE_JPS
     610        ComputePathJPS(req.x0, req.z0, req.goal, req.passClass, path);
     611#else
     612        ComputePath(req.x0, req.z0, req.goal, req.passClass, path);
     613#endif
    584614        CMessagePathResult msg(req.ticket, path);
    585615        GetSimContext().GetComponentManager().PostMessage(req.notify, msg);
    586616    }
     
    656686    }
    657687}
    658688
     689//////////////////////////////////////////////////////////
     690
    659691bool CCmpPathfinder::CheckUnitPlacement(const IObstructionTestFilter& filter,
    660692    entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass)
    661693{
     
    667699    if (cmpObstructionManager->TestUnitShape(filter, x, z, r, NULL))
    668700        return false;
    669701
    670     // Test against terrain:
     702    // Test against terrain and static obstructions:
    671703
    672704    UpdateGrid();
    673705
    674     u16 i0, j0, i1, j1;
    675     NearestTile(x - r, z - r, i0, j0);
    676     NearestTile(x + r, z + r, i1, j1);
    677     for (u16 j = j0; j <= j1; ++j)
    678     {
    679         for (u16 i = i0; i <= i1; ++i)
    680         {
    681             if (!IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
    682             {
    683                 return false;
    684             }
    685         }
    686     }
     706    u16 i, j;
     707    NearestNavcell(x, z, i, j);
     708    if (!IS_PASSABLE(m_Grid->get(i, j), passClass))
     709        return false;
     710
     711    // (Static obstructions will be redundantly tested against in both the
     712    // obstruction-shape test and navcell-passability test, which is slightly
     713    // inefficient but shouldn't affect behaviour)
     714
    687715    return true;
    688716}
    689717
     
    711739    if (!cmpObstruction->GetObstructionSquare(square))
    712740        return false;
    713741
    714     // Expand bounds by 1/sqrt(2) tile (multiply by TERRAIN_TILE_SIZE since we want world coordinates)
    715     entity_pos_t expand = entity_pos_t::FromInt(2).Sqrt().Multiply(entity_pos_t::FromInt(TERRAIN_TILE_SIZE / 2));
    716     CFixedVector2D halfSize(square.hw + expand, square.hh + expand);
    717     CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(square.u, square.v, halfSize);
     742    entity_pos_t expand;
     743    const PathfinderPassability* passability = GetPassabilityFromMask(passClass);
     744    if (passability && passability->m_HasClearance)
     745        expand = passability->m_Clearance;
    718746
    719     u16 i0, j0, i1, j1;
    720     NearestTile(square.x - halfBound.X, square.z - halfBound.Y, i0, j0);
    721     NearestTile(square.x + halfBound.X, square.z + halfBound.Y, i1, j1);
    722     for (u16 j = j0; j <= j1; ++j)
     747    SimRasterize::Spans spans;
     748    SimRasterize::RasterizeRectWithClearance(spans, square, expand, ICmpObstructionManager::NAVCELL_SIZE);
     749    for (size_t k = 0; k < spans.size(); ++k)
    723750    {
    724         for (u16 i = i0; i <= i1; ++i)
     751        i16 i0 = spans[k].i0;
     752        i16 i1 = spans[k].i1;
     753        i16 j = spans[k].j;
     754
     755        // Fail if any span extends outside the grid
     756        if (i0 < 0 || i1 > m_Grid->m_W || j < 0 || j > m_Grid->m_H)
     757            return false;
     758
     759        // Fail if any span includes an impassable tile
     760        for (i16 i = i0; i < i1; ++i)
     761            if (!IS_PASSABLE(m_Grid->get(i, j), passClass))
     762                return false;
     763    }
     764
     765    return true;
     766}
     767
     768//////////////////////////////////////////////////////////
     769
     770void CCmpPathfinder::ComputePathOffImpassable(u16 i0, u16 j0, pass_class_t passClass, Path& path)
     771{
     772    u16 iGoal = i0;
     773    u16 jGoal = j0;
     774    this->PathfinderHierFindNearestPassableNavcell(iGoal, jGoal, passClass);
     775
     776    int ip = iGoal;
     777    int jp = jGoal;
     778
     779    // Reconstruct the path (in reverse)
     780    while (ip != i0 || jp != j0)
     781    {
     782        entity_pos_t x, z;
     783        NavcellCenter(ip, jp, x, z);
     784        Waypoint w = { x, z };
     785        path.m_Waypoints.push_back(w);
     786
     787        // Move diagonally/horizontally/vertically towards the start navcell
     788
     789        if (ip > i0)
     790            ip--;
     791        else if (ip < i0)
     792            ip++;
     793
     794        if (jp > j0)
     795            jp--;
     796        else if (jp < j0)
     797            jp++;
     798    }
     799}
     800
     801void CCmpPathfinder::NormalizePathWaypoints(Path& path)
     802{
     803    if (path.m_Waypoints.empty())
     804        return;
     805
     806    // Given the current list of waypoints, add intermediate waypoints
     807    // in a straight line between them, so that the maximum gap between
     808    // waypoints is within the (fairly arbitrary) limit
     809    const fixed MAX_WAYPOINT_SEPARATION = ICmpObstructionManager::NAVCELL_SIZE * 4;
     810
     811    std::vector<Waypoint>& waypoints = path.m_Waypoints;
     812    std::vector<Waypoint> newWaypoints;
     813
     814    newWaypoints.push_back(waypoints.front());
     815    for (size_t k = 1; k < waypoints.size(); ++k)
     816    {
     817        CFixedVector2D prev(waypoints[k-1].x, waypoints[k-1].z);
     818        CFixedVector2D curr(waypoints[k].x, waypoints[k].z);
     819        fixed dist = (curr - prev).Length();
     820        if (dist > MAX_WAYPOINT_SEPARATION)
    725821        {
    726             entity_pos_t x, z;
    727             TileCenter(i, j, x, z);
    728             if (Geometry::PointIsInSquare(CFixedVector2D(x - square.x, z - square.z), square.u, square.v, halfSize)
    729                 && !IS_TERRAIN_PASSABLE(m_Grid->get(i,j), passClass))
     822            int segments = (dist / MAX_WAYPOINT_SEPARATION).ToInt_RoundToInfinity();
     823            for (int i = 1; i < segments; ++i)
    730824            {
    731                 return false;
     825                CFixedVector2D p = prev + ((curr - prev)*i) / segments;
     826                Waypoint wp = { p.X, p.Y };
     827                newWaypoints.push_back(wp);
    732828            }
    733829        }
     830        newWaypoints.push_back(waypoints[k]);
    734831    }
    735832
    736     return true;
     833    path.m_Waypoints.swap(newWaypoints);
    737834}
  • source/simulation2/components/CCmpObstructionManager.cpp

     
    2222
    2323#include "simulation2/MessageTypes.h"
    2424#include "simulation2/helpers/Geometry.h"
     25#include "simulation2/helpers/Rasterize.h"
    2526#include "simulation2/helpers/Render.h"
    2627#include "simulation2/helpers/Spatial.h"
    2728#include "simulation2/serialization/SerializeTemplates.h"
     
    435436    virtual bool TestStaticShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, std::vector<entity_id_t>* out);
    436437    virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, std::vector<entity_id_t>* out);
    437438
    438     virtual bool Rasterise(Grid<u8>& grid);
     439    virtual void Rasterize(Grid<u16>& grid, entity_pos_t expand, ICmpObstructionManager::flags_t requireMask, u16 setMask);
    439440    virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares);
    440     virtual bool FindMostImportantObstruction(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, ObstructionSquare& square);
     441    virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out);
    441442
    442443    virtual void SetPassabilityCircular(bool enabled)
    443444    {
     
    505506        m_DebugOverlayDirty = true;
    506507    }
    507508
    508     /**
    509      * Test whether a Rasterise()d grid is dirty and needs updating
    510      */
    511     template<typename T>
    512     bool IsDirty(const Grid<T>& grid)
     509    virtual bool NeedUpdate(size_t* dirtyID)
    513510    {
    514         return grid.m_DirtyID < m_DirtyID;
     511        ENSURE(*dirtyID <= m_DirtyID);
     512        if (*dirtyID != m_DirtyID)
     513        {
     514            *dirtyID = m_DirtyID;
     515            return true;
     516        }
     517        return false;
    515518    }
    516519
    517520    /**
     
    703706}
    704707
    705708/**
    706  * Compute the tile indexes on the grid nearest to a given point
     709 * Compute the navcell indexes on the grid nearest to a given point
    707710 */
    708 static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
     711static void NearestNavcell(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h)
    709712{
    710     i = (u16)clamp((x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, w-1);
    711     j = (u16)clamp((z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), 0, h-1);
     713    i = (u16)clamp((x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero(), 0, w-1);
     714    j = (u16)clamp((z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToZero(), 0, h-1);
    712715}
    713716
    714 /**
    715  * Returns the position of the center of the given tile
    716  */
    717 static void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z)
     717void CCmpObstructionManager::Rasterize(Grid<u16>& grid, entity_pos_t expand, ICmpObstructionManager::flags_t requireMask, u16 setMask)
    718718{
    719     x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
    720     z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE/2);
    721 }
     719    PROFILE3("Rasterize");
    722720
    723 bool CCmpObstructionManager::Rasterise(Grid<u8>& grid)
    724 {
    725     if (!IsDirty(grid))
    726         return false;
     721    // Since m_DirtyID is only updated for pathfinding/foundation blocking shapes,
     722    // NeedUpdate+Rasterise will only be accurate for that subset of shapes.
     723    // (If we ever want to support rasterizing more shapes, we need to improve
     724    // the dirty-detection system too.)
     725    ENSURE(!(requireMask & ~(FLAG_BLOCK_PATHFINDING|FLAG_BLOCK_FOUNDATION)));
    727726
    728     PROFILE("Rasterise");
     727    // Cells are only marked as blocked if the whole cell is strictly inside the shape.
     728    // (That ensures the shape's geometric border is always reachable.)
    729729
    730     grid.m_DirtyID = m_DirtyID;
     730    // TODO: it might be nice to rasterize with rounded corners for large 'expand' values.
    731731
    732     // TODO: this is all hopelessly inefficient
    733     // What we should perhaps do is have some kind of quadtree storing Shapes so it's
    734     // quick to invalidate and update small numbers of tiles
     732    // (This could be implemented much more efficiently.)
    735733
    736     grid.reset();
    737 
    738     // For tile-based pathfinding:
    739     // Since we only count tiles whose centers are inside the square,
    740     // we maybe want to expand the square a bit so we're less likely to think there's
    741     // free space between buildings when there isn't. But this is just a random guess
    742     // and needs to be tweaked until everything works nicely.
    743     //entity_pos_t expandPathfinding = entity_pos_t::FromInt(TERRAIN_TILE_SIZE / 2);
    744     // Actually that's bad because units get stuck when the A* pathfinder thinks they're
    745     // blocked on all sides, so it's better to underestimate
    746     entity_pos_t expandPathfinding = entity_pos_t::FromInt(0);
    747 
    748     // For AI building foundation planning, we want to definitely block all
    749     // potentially-obstructed tiles (so we don't blindly build on top of an obstruction),
    750     // so we need to expand by at least 1/sqrt(2) of a tile
    751     entity_pos_t expandFoundation = (entity_pos_t::FromInt(TERRAIN_TILE_SIZE) * 3) / 4;
    752 
    753734    for (std::map<u32, StaticShape>::iterator it = m_StaticShapes.begin(); it != m_StaticShapes.end(); ++it)
    754735    {
    755         CFixedVector2D center(it->second.x, it->second.z);
    756 
    757         if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     736        const StaticShape& shape = it->second;
     737        if (shape.flags & requireMask)
    758738        {
    759             CFixedVector2D halfSize(it->second.hw + expandPathfinding, it->second.hh + expandPathfinding);
    760             CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(it->second.u, it->second.v, halfSize);
    761 
    762             u16 i0, j0, i1, j1;
    763             NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
    764             NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
    765             for (u16 j = j0; j <= j1; ++j)
     739            ObstructionSquare square = { shape.x, shape.z, shape.u, shape.v, shape.hw, shape.hh };
     740            SimRasterize::Spans spans;
     741            SimRasterize::RasterizeRectWithClearance(spans, square, expand, ICmpObstructionManager::NAVCELL_SIZE);
     742            for (size_t k = 0; k < spans.size(); ++k)
    766743            {
    767                 for (u16 i = i0; i <= i1; ++i)
     744                i16 j = spans[k].j;
     745                if (j >= 0 && j <= grid.m_H)
    768746                {
    769                     entity_pos_t x, z;
    770                     TileCenter(i, j, x, z);
    771                     if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, it->second.u, it->second.v, halfSize))
    772                         grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_PATHFINDING);
     747                    i16 i0 = std::max(spans[k].i0, (i16)0);
     748                    i16 i1 = std::min(spans[k].i1, (i16)grid.m_W);
     749                    for (i16 i = i0; i < i1; ++i)
     750                        grid.set(i, j, grid.get(i, j) | setMask);
    773751                }
    774752            }
    775753        }
    776 
    777         if (it->second.flags & FLAG_BLOCK_FOUNDATION)
    778         {
    779             CFixedVector2D halfSize(it->second.hw + expandFoundation, it->second.hh + expandFoundation);
    780             CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(it->second.u, it->second.v, halfSize);
    781 
    782             u16 i0, j0, i1, j1;
    783             NearestTile(center.X - halfBound.X, center.Y - halfBound.Y, i0, j0, grid.m_W, grid.m_H);
    784             NearestTile(center.X + halfBound.X, center.Y + halfBound.Y, i1, j1, grid.m_W, grid.m_H);
    785             for (u16 j = j0; j <= j1; ++j)
    786             {
    787                 for (u16 i = i0; i <= i1; ++i)
    788                 {
    789                     entity_pos_t x, z;
    790                     TileCenter(i, j, x, z);
    791                     if (Geometry::PointIsInSquare(CFixedVector2D(x, z) - center, it->second.u, it->second.v, halfSize))
    792                         grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_FOUNDATION);
    793                 }
    794             }
    795         }
    796754    }
    797755
    798756    for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it)
    799757    {
    800758        CFixedVector2D center(it->second.x, it->second.z);
    801759
    802         if (it->second.flags & FLAG_BLOCK_PATHFINDING)
     760        if (it->second.flags & requireMask)
    803761        {
    804             entity_pos_t r = it->second.r + expandPathfinding;
     762            entity_pos_t r = it->second.r + expand;
    805763
    806764            u16 i0, j0, i1, j1;
    807             NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
    808             NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
    809             for (u16 j = j0; j <= j1; ++j)
    810                 for (u16 i = i0; i <= i1; ++i)
    811                     grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_PATHFINDING);
     765            NearestNavcell(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
     766            NearestNavcell(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
     767            for (u16 j = j0+1; j < j1; ++j)
     768                for (u16 i = i0+1; i < i1; ++i)
     769                    grid.set(i, j, grid.get(i, j) | setMask);
    812770        }
    813 
    814         if (it->second.flags & FLAG_BLOCK_FOUNDATION)
    815         {
    816             entity_pos_t r = it->second.r + expandFoundation;
    817 
    818             u16 i0, j0, i1, j1;
    819             NearestTile(center.X - r, center.Y - r, i0, j0, grid.m_W, grid.m_H);
    820             NearestTile(center.X + r, center.Y + r, i1, j1, grid.m_W, grid.m_H);
    821             for (u16 j = j0; j <= j1; ++j)
    822                 for (u16 i = i0; i <= i1; ++i)
    823                     grid.set(i, j, grid.get(i, j) | TILE_OBSTRUCTED_FOUNDATION);
    824         }
    825771    }
    826 
    827     // Any tiles outside or very near the edge of the map are impassable
    828 
    829     // WARNING: CCmpRangeManager::LosIsOffWorld needs to be kept in sync with this
    830     const u16 edgeSize = 3; // number of tiles around the edge that will be off-world
    831 
    832     u8 edgeFlags = TILE_OBSTRUCTED_PATHFINDING | TILE_OBSTRUCTED_FOUNDATION | TILE_OUTOFBOUNDS;
    833 
    834     if (m_PassabilityCircular)
    835     {
    836         for (u16 j = 0; j < grid.m_H; ++j)
    837         {
    838             for (u16 i = 0; i < grid.m_W; ++i)
    839             {
    840                 // Based on CCmpRangeManager::LosIsOffWorld
    841                 // but tweaked since it's tile-based instead.
    842                 // (We double all the values so we can handle half-tile coordinates.)
    843                 // This needs to be slightly tighter than the LOS circle,
    844                 // else units might get themselves lost in the SoD around the edge.
    845 
    846                 ssize_t dist2 = (i*2 + 1 - grid.m_W)*(i*2 + 1 - grid.m_W)
    847                         + (j*2 + 1 - grid.m_H)*(j*2 + 1 - grid.m_H);
    848 
    849                 if (dist2 >= (grid.m_W - 2*edgeSize) * (grid.m_H - 2*edgeSize))
    850                     grid.set(i, j, edgeFlags);
    851             }
    852         }
    853     }
    854     else
    855     {
    856         u16 i0, j0, i1, j1;
    857         NearestTile(m_WorldX0, m_WorldZ0, i0, j0, grid.m_W, grid.m_H);
    858         NearestTile(m_WorldX1, m_WorldZ1, i1, j1, grid.m_W, grid.m_H);
    859 
    860         for (u16 j = 0; j < grid.m_H; ++j)
    861             for (u16 i = 0; i < i0+edgeSize; ++i)
    862                 grid.set(i, j, edgeFlags);
    863         for (u16 j = 0; j < grid.m_H; ++j)
    864             for (u16 i = (u16)(i1-edgeSize+1); i < grid.m_W; ++i)
    865                 grid.set(i, j, edgeFlags);
    866         for (u16 j = 0; j < j0+edgeSize; ++j)
    867             for (u16 i = (u16)(i0+edgeSize); i < i1-edgeSize+1; ++i)
    868                 grid.set(i, j, edgeFlags);
    869         for (u16 j = (u16)(j1-edgeSize+1); j < grid.m_H; ++j)
    870             for (u16 i = (u16)(i0+edgeSize); i < i1-edgeSize+1; ++i)
    871                 grid.set(i, j, edgeFlags);
    872     }
    873 
    874     return true;
    875772}
    876773
    877774void CCmpObstructionManager::GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector<ObstructionSquare>& squares)
     
    923820    }
    924821}
    925822
    926 bool CCmpObstructionManager::FindMostImportantObstruction(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, ObstructionSquare& square)
     823void CCmpObstructionManager::GetUnitsOnObstruction(const ObstructionSquare& square, std::vector<entity_id_t>& out)
    927824{
    928     std::vector<ObstructionSquare> squares;
     825    PROFILE3("GetUnitsOnObstruction");
    929826
    930     CFixedVector2D center(x, z);
     827    // Ideally we'd want to find all units s.t. the RasterizeRectWithClearance
     828    // of the building's shape with the unit's clearance covers the navcell
     829    // the unit is on.
     830    // But ObstructionManager doesn't actually know the clearance values
     831    // (only the radiuses), so we can't easily do that properly.
     832    // Instead, cheat and just ignore the clearance entirely;
     833    // this might allow players to build buildings so a unit ends up on
     834    // an impassable tile, which is slightly bad and might let players cheat,
     835    // so TODO: fix this properly.
    931836
    932     // First look for obstructions that are covering the exact target point
    933     GetObstructionsInRange(filter, x, z, x, z, squares);
    934     // Building squares are more important but returned last, so check backwards
    935     for (std::vector<ObstructionSquare>::reverse_iterator it = squares.rbegin(); it != squares.rend(); ++it)
    936     {
    937         CFixedVector2D halfSize(it->hw, it->hh);
    938         if (Geometry::PointIsInSquare(CFixedVector2D(it->x, it->z) - center, it->u, it->v, halfSize))
    939         {
    940             square = *it;
    941             return true;
    942         }
    943     }
     837    SimRasterize::Spans spans;
     838    SimRasterize::RasterizeRectWithClearance(spans, square, fixed::Zero(), ICmpObstructionManager::NAVCELL_SIZE);
     839 
     840    for (std::map<u32, UnitShape>::iterator it = m_UnitShapes.begin(); it != m_UnitShapes.end(); ++it)
     841    {
     842        // Check whether the unit's center is on a navcell that's in
     843        // any of the spans
    944844
    945     // Then look for obstructions that cover the target point when expanded by r
    946     // (i.e. if the target is not inside an object but closer than we can get to it)
    947    
    948     GetObstructionsInRange(filter, x-r, z-r, x+r, z+r, squares);
    949     // Building squares are more important but returned last, so check backwards
    950     for (std::vector<ObstructionSquare>::reverse_iterator it = squares.rbegin(); it != squares.rend(); ++it)
    951     {
    952         CFixedVector2D halfSize(it->hw + r, it->hh + r);
    953         if (Geometry::PointIsInSquare(CFixedVector2D(it->x, it->z) - center, it->u, it->v, halfSize))
    954         {
    955             square = *it;
    956             return true;
    957         }
    958     }
     845        u16 i = (it->second.x / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToNegInfinity();
     846        u16 j = (it->second.z / ICmpObstructionManager::NAVCELL_SIZE).ToInt_RoundToNegInfinity();
    959847
    960     return false;
     848        for (size_t k = 0; k < spans.size(); ++k)
     849        {
     850            if (j == spans[k].j && spans[k].i0 <= i && i < spans[k].i1)
     851                out.push_back(it->second.entity);
     852        }
     853    }
     854    //TODO Should we expand by r here?
    961855}
    962856
    963857void CCmpObstructionManager::RenderSubmit(SceneCollector& collector)
  • source/simulation2/components/CCmpObstruction.cpp

     
    553553    {
    554554        std::vector<entity_id_t> ret;
    555555
    556         CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
    557         if (!cmpPosition)
    558             return ret; // error
    559 
    560         if (!cmpPosition->IsInWorld())
    561             return ret; // no obstruction
    562 
    563         CFixedVector2D pos = cmpPosition->GetPosition2D();
    564 
    565556        CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY);
    566557        if (!cmpObstructionManager)
    567558            return ret; // error
    568559
    569         // required precondition to use SkipControlGroupsRequireFlagObstructionFilter
    570         if (m_ControlGroup == INVALID_ENTITY)
    571         {
    572             LOGERROR(L"[CmpObstruction] Cannot test for unit or structure obstructions; primary control group must be valid");
    573             return ret;
    574         }
    575 
    576560        flags_t flags = 0;
    577561        bool invertMatch = false;
    578562
     
    595579            flags = ICmpObstructionManager::FLAG_BLOCK_FOUNDATION | ICmpObstructionManager::FLAG_BLOCK_PATHFINDING;
    596580        }
    597581
    598         // Ignore collisions within the same control group, or with other shapes that don't match the filter.
    599         // Note that, since the control group for each entity defaults to the entity's ID, this is typically
    600         // equivalent to only ignoring the entity's own shape and other shapes that don't match the filter.
    601         SkipControlGroupsRequireFlagObstructionFilter filter(invertMatch,
    602                 m_ControlGroup, m_ControlGroup2, flags);
    603 
    604         if (m_Type == STATIC)
    605             cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret);
    606         else if (m_Type == UNIT)
    607             cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, &ret);
    608         else
    609             cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret);
     582        ICmpObstructionManager::ObstructionSquare square;
     583        if (!GetObstructionSquare(square))
     584            return ret; // error
     585           
     586        cmpObstructionManager->GetUnitsOnObstruction(square, ret);
     587        //if (m_Type == STATIC)
     588        //  cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret);
     589        //else if (m_Type == UNIT)
     590        //  cmpObstructionManager->TestUnitShape(filter, pos.X, pos.Y, m_Size0, &ret);
     591        //else
     592        //  cmpObstructionManager->TestStaticShape(filter, pos.X, pos.Y, cmpPosition->GetRotation().Y, m_Size0, m_Size1, &ret);
    610593   
    611594        return ret;
    612595    }
  • source/renderer/TerrainOverlay.h

     
    2626#include "lib/ogl.h"
    2727
    2828struct CColor;
     29struct SColor4ub;
    2930class CTerrain;
    3031class CSimContext;
    3132
     
    186187     */
    187188    virtual void BuildTextureRGBA(u8* data, size_t w, size_t h) = 0;
    188189
     190    /**
     191     * Returns an arbitrary color, for subclasses that want to distinguish
     192     * different integers visually.
     193     */
     194    SColor4ub GetColor(size_t idx, u8 alpha) const;
     195
    189196private:
    190197    void RenderAfterWater();
    191198
  • source/renderer/TerrainOverlay.cpp

     
    331331
    332332    g_Renderer.GetTerrainRenderer().RenderTerrainOverlayTexture(matrix);
    333333}
     334
     335SColor4ub TerrainTextureOverlay::GetColor(size_t idx, u8 alpha) const
     336{
     337    static u8 colors[][3] = {
     338        { 255, 0, 0 },
     339        { 0, 255, 0 },
     340        { 0, 0, 255 },
     341        { 255, 255, 0 },
     342        { 255, 0, 255 },
     343        { 0, 255, 255 },
     344        { 255, 255, 255 },
     345
     346        { 127, 0, 0 },
     347        { 0, 127, 0 },
     348        { 0, 0, 127 },
     349        { 127, 127, 0 },
     350        { 127, 0, 127 },
     351        { 0, 127, 127 },
     352        { 127, 127, 127},
     353
     354        { 255, 127, 0 },
     355        { 127, 255, 0 },
     356        { 255, 0, 127 },
     357        { 127, 0, 255},
     358        { 0, 255, 127 },
     359        { 0, 127, 255},
     360        { 255, 127, 127},
     361        { 127, 255, 127},
     362        { 127, 127, 255},
     363
     364        { 127, 255, 255 },
     365        { 255, 127, 255 },
     366        { 255, 255, 127 },
     367    };
     368
     369    size_t c = idx % ARRAY_SIZE(colors);
     370    return SColor4ub(colors[c][0], colors[c][1], colors[c][2], alpha);
     371}
  • source/maths/FixedVector2D.h

     
    7979        return CFixedVector2D(X*n, Y*n);
    8080    }
    8181
     82    /// Scalar division by an integer. Must not have n == 0.
     83    CFixedVector2D operator/(int n) const
     84    {
     85        return CFixedVector2D(X/n, Y/n);
     86    }
     87
    8288    /**
    8389     * Multiply by a CFixed. Likely to overflow if both numbers are large,
    8490     * so we use an ugly name instead of operator* to make it obvious.
  • source/maths/Brush.cpp

     
    379379
    380380    ENSURE(prev == &result);
    381381}
     382
    382383std::vector<CVector3D> CBrush::GetVertices() const
    383384{
    384385    return m_Vertices;
  • source/gui/GUIManager.h

     
    145145        CStrW name;
    146146        boost::unordered_set<VfsPath> inputs; // for hotloading
    147147
    148         JSContext* cx;
    149148        CScriptValRooted initData; // data to be passed to the init() function
    150149
    151150        shared_ptr<CGUI> gui; // the actual GUI page
  • source/graphics/TerritoryTexture.cpp

     
    2525#include "ps/Profile.h"
    2626#include "renderer/Renderer.h"
    2727#include "simulation2/Simulation2.h"
     28#include "simulation2/components/ICmpObstructionManager.h"
    2829#include "simulation2/components/ICmpPlayer.h"
    2930#include "simulation2/components/ICmpPlayerManager.h"
    3031#include "simulation2/components/ICmpTerrain.h"
     
    9293    if (!cmpTerrain)
    9394        return;
    9495
    95     m_MapSize = cmpTerrain->GetVerticesPerSide() - 1;
     96    // Convert size from terrain tiles to territory tiles
     97    m_MapSize = cmpTerrain->GetTilesPerSide() * ICmpObstructionManager::NAVCELLS_PER_TILE / ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE;
    9698
    9799    m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize);
    98100
  • source/graphics/TerritoryBoundary.cpp

     
    2121#include <algorithm> // for reverse
    2222
    2323#include "graphics/Terrain.h"
     24#include "simulation2/components/ICmpObstructionManager.h"
    2425#include "simulation2/components/ICmpTerritoryManager.h"
    2526
    2627std::vector<STerritoryBoundary> CTerritoryBoundaryCalculator::ComputeBoundaries(const Grid<u8>* territory)
     
    123124            u16 maxi = (u16)(grid.m_W-1);
    124125            u16 maxj = (u16)(grid.m_H-1);
    125126
     127            // Size of a territory tile in metres
     128            float territoryTileSize = (ICmpObstructionManager::NAVCELL_SIZE * ICmpTerritoryManager::NAVCELLS_PER_TERRITORY_TILE).ToFloat();
     129
    126130            while (true)
    127131            {
    128                 points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * TERRAIN_TILE_SIZE);
     132                points.push_back((CVector2D(ci, cj) + edgeOffsets[cdir]) * territoryTileSize);
    129133
    130134                // Given that we're on an edge on a continuous boundary and aiming anticlockwise,
    131135                // we can either carry on straight or turn left or turn right, so examine each
  • source/graphics/Terrain.h

     
    8787    fixed GetExactGroundLevelFixed(fixed x, fixed z) const;
    8888    float GetFilteredGroundLevel(float x, float z, float radius) const;
    8989
    90     // get the approximate slope (0 = horizontal, 0.5 = 30 degrees, 1.0 = 45 degrees, etc)
     90    // get the approximate slope of a tile
     91    // (0 = horizontal, 0.5 = 30 degrees, 1.0 = 45 degrees, etc)
    9192    fixed GetSlopeFixed(ssize_t i, ssize_t j) const;
    9293
     94    // get the precise slope of a point, accounting for triangulation direction
     95    fixed GetExactSlopeFixed(fixed x, fixed z) const;
     96
    9397    // Returns true if the triangulation diagonal for tile (i, j)
    9498    // should be in the direction (1,-1); false if it should be (1,1)
    9599    bool GetTriangulationDir(ssize_t i, ssize_t j) const;
  • source/graphics/Terrain.cpp

     
    338338    return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
    339339}
    340340
     341fixed CTerrain::GetExactSlopeFixed(fixed x, fixed z) const
     342{
     343    // Clamp to size-2 so we can use the tiles (xi,zi)-(xi+1,zi+1)
     344    const ssize_t xi = clamp((ssize_t)(x / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), (ssize_t)0, m_MapSize-2);
     345    const ssize_t zi = clamp((ssize_t)(z / (int)TERRAIN_TILE_SIZE).ToInt_RoundToZero(), (ssize_t)0, m_MapSize-2);
     346
     347    const fixed one = fixed::FromInt(1);
     348
     349    const fixed xf = clamp((x / (int)TERRAIN_TILE_SIZE) - fixed::FromInt(xi), fixed::Zero(), one);
     350    const fixed zf = clamp((z / (int)TERRAIN_TILE_SIZE) - fixed::FromInt(zi), fixed::Zero(), one);
     351
     352    u16 h00 = m_Heightmap[zi*m_MapSize + xi];
     353    u16 h01 = m_Heightmap[(zi+1)*m_MapSize + xi];
     354    u16 h10 = m_Heightmap[zi*m_MapSize + (xi+1)];
     355    u16 h11 = m_Heightmap[(zi+1)*m_MapSize + (xi+1)];
     356
     357    u16 delta;
     358    if (GetTriangulationDir(xi, zi))
     359    {
     360        if (xf + zf <= one)
     361        {
     362            // Lower-left triangle (don't use h11)
     363            delta = std::max(std::max(h00, h01), h10) -
     364                    std::min(std::min(h00, h01), h10);
     365        }
     366        else
     367        {
     368            // Upper-right triangle (don't use h00)
     369            delta = std::max(std::max(h01, h10), h11) -
     370                    std::min(std::min(h01, h10), h11);
     371        }
     372    }
     373    else
     374    {
     375        if (xf <= zf)
     376        {
     377            // Upper-left triangle (don't use h10)
     378            delta = std::max(std::max(h00, h01), h11) -
     379                    std::min(std::min(h00, h01), h11);
     380        }
     381        else
     382        {
     383            // Lower-right triangle (don't use h01)
     384            delta = std::max(std::max(h00, h10), h11) -
     385                    std::min(std::min(h00, h10), h11);
     386        }
     387    }
     388
     389    // Compute fractional slope (being careful to avoid intermediate overflows)
     390    return fixed::FromInt(delta / TERRAIN_TILE_SIZE) / (int)HEIGHT_UNITS_PER_METRE;
     391}
     392
    341393float CTerrain::GetFilteredGroundLevel(float x, float z, float radius) const
    342394{
    343395    // convert to [0,1] interval
  • binaries/data/mods/public/simulation/templates/units/hele_mechanical_siege_tower.xml

     
    88    <History>When Demetrius Poliorcetes besieged Salamis, in Cyprus, he instructed that a machine be constructed, which he called "the taker of cities." Its form was that of a square tower, each side 90 cubits high and 45 wide. It rested on four wheels, each eight cubits high. It was divided into nine stories, the lower of which contained machines for throwing great stones, the middle large catapults for throwing spears, and the highest, other machines for throwing smaller stones, together with smaller catapults. It was manned with 200 soldiers, besides those that moved it by pushing the parallel beams at the bottom.</History>
    99    <Icon>units/hele_mechanical_siege_tower.png</Icon>
    1010  </Identity>
     11  <Obstruction>
     12    <Unit radius="3.75"/>
     13  </Obstruction>
     14  <UnitMotion>
     15    <PassabilityClass>siege-large</PassabilityClass>
     16  </UnitMotion>
    1117  <Vision>
    1218    <Range>88</Range>
    1319  </Vision>
  • binaries/data/mods/public/simulation/templates/template_unit_mechanical_ship_fishing.xml

     
    4343    </Rates>
    4444  </ResourceGatherer>
    4545  <UnitMotion>
     46    <PassabilityClass>ship-small</PassabilityClass>
    4647    <WalkSpeed>8.5</WalkSpeed>
    4748  </UnitMotion>
    4849  <Vision>
  • binaries/data/mods/public/simulation/templates/template_unit_infantry.xml

     
    118118    <Run>
    119119      <Speed>18.75</Speed>
    120120    </Run>
    121     <CostClass>infantry</CostClass>
    122121  </UnitMotion>
    123122  <Vision>
    124123    <Range>60</Range>
  • binaries/data/mods/public/simulation/templates/template_unit.xml

     
    109109      <DecayTime>0.2</DecayTime>
    110110    </Run>
    111111    <PassabilityClass>default</PassabilityClass>
    112     <CostClass>default</CostClass>
    113112  </UnitMotion>
    114113  <Vision>
    115114    <Range>10</Range>
  • binaries/data/mods/public/simulation/templates/template_structure.xml

     
    6868    <LineDashColour r="158" g="11" b="15"/>
    6969    <LineStartCap>square</LineStartCap>
    7070    <LineEndCap>round</LineEndCap>
    71     <LineCostClass>default</LineCostClass>
    7271    <LinePassabilityClass>default</LinePassabilityClass>
    7372  </RallyPointRenderer>
    7473  <Selectable>
  • binaries/data/mods/public/simulation/templates/special/formation.xml

     
    2222    <FormationController>true</FormationController>
    2323    <WalkSpeed>1.0</WalkSpeed>
    2424    <PassabilityClass>default</PassabilityClass>
    25     <CostClass>default</CostClass>
    2625  </UnitMotion>
    2726</Entity>
  • binaries/data/mods/public/simulation/data/pathfinder.xml

     
    66 
    77  <PassabilityClasses>
    88
     9    <unrestricted/>
     10
    911    <!-- Unit pathfinding classes: -->
    10     <unrestricted/>
    1112    <default>
    1213      <MaxWaterDepth>2</MaxWaterDepth>
    1314      <MaxTerrainSlope>1.0</MaxTerrainSlope>
     15      <Clearance>1.0</Clearance>
    1416    </default>
     17    <siege-large>
     18      <MaxWaterDepth>2</MaxWaterDepth>
     19      <MaxTerrainSlope>1.0</MaxTerrainSlope>
     20      <Clearance>4.0</Clearance>
     21    </siege-large>
     22    <default-no-clearance>
     23      <MaxWaterDepth>2</MaxWaterDepth>
     24      <MaxTerrainSlope>1.0</MaxTerrainSlope>
     25      <Clearance>0.0</Clearance>
     26    </default-no-clearance>
    1527    <ship>
    1628      <MinWaterDepth>1</MinWaterDepth>
     29      <Clearance>12.0</Clearance>
    1730    </ship>
     31    <ship-small>
     32      <MinWaterDepth>1</MinWaterDepth>
     33      <Clearance>4.0</Clearance>
     34    </ship-small>
     35
    1836    <!-- Building construction classes:
    1937    * Land is used for most buildings, which must be away
    2038        from water and not on cliffs or mountains.
    2139    * Shore is used for docks, which must be near water and
    2240        land, yet shallow enough for builders to approach.
     41
     42      (These should not use <Clearance>, because the foundation placement
     43      checker already does precise obstruction-shape-based checking.)
    2344    -->
    2445    <building-land>
    2546      <MaxWaterDepth>0</MaxWaterDepth>
    26       <MinShoreDistance>1.0</MinShoreDistance>
     47      <MinShoreDistance>4.0</MinShoreDistance>
    2748      <MaxTerrainSlope>1.0</MaxTerrainSlope>
    2849    </building-land>
    2950    <building-shore>
    30       <MaxShoreDistance>2.0</MaxShoreDistance>
     51      <MaxShoreDistance>8.0</MaxShoreDistance>
    3152      <MaxTerrainSlope>1.25</MaxTerrainSlope>
    3253    </building-shore>
    3354
     55    <!-- Territory growth influences: -->
     56    <territory>
     57      <MaxWaterDepth>2</MaxWaterDepth>
     58      <MaxTerrainSlope>1.0</MaxTerrainSlope>
     59    </territory>
     60
    3461  </PassabilityClasses>
    35  
    36   <!--
    37     Warning: Movement costs are a subtle tradeoff between
    38     pathfinding accuracy and computation cost. Be extremely
    39     careful if you change them.
    40     (Speeds are safer to change, but ought to be kept roughly
    41     in sync with costs.)
    42   -->
    43   <MovementClasses>
    44     <default Speed="1.0" Cost="1.08"/>
    45     <city Speed="1.0" Cost="1.0">
    46       <UnitClasses>
    47         <infantry Speed="1.4" Cost="0.6"/>
    48       </UnitClasses>
    49     </city>
    50   </MovementClasses>
    5162</Pathfinder>
  • binaries/data/mods/public/art/textures/terrain/types/special/textures.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<Textures>
     3    <File pattern="grid_subdiv.png" format="rgba"/>
     4</Textures>
  • binaries/data/mods/public/art/textures/terrain/types/special/grid_subdiv.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2
     3<Terrains>
     4    <Terrain angle="0.0" size="4.0"/>
     5</Terrains>
  • binaries/data/mods/public/art/textures/terrain/types/special/grid_subdiv.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2
     3<Terrains>
     4    <Terrain angle="0.0" size="4.0"/>
     5</Terrains>
  • binaries/data/mods/public/art/textures/terrain/types/special/textures.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<Textures>
     3    <File pattern="grid_subdiv.png" format="rgba"/>
     4</Textures>