Ticket #657: waypoints_wip_2012-10-14.patch

File waypoints_wip_2012-10-14.patch, 72.4 KB (added by leper, 12 years ago)

WIP patch

  • binaries/data/mods/public/gui/session/input.js

     
    536536        "autocontinue": true,
    537537        "queued": queued
    538538    });
     539    Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "x": placementSupport.position.x, "z": placementSupport.position.z, "queued": queued});
    539540    Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] });
    540541
    541542    if (!queued)
     
    13031304    case "move":
    13041305        var target = Engine.GetTerrainAtScreenPoint(ev.x, ev.y);
    13051306        Engine.PostNetworkCommand({"type": "walk", "entities": selection, "x": target.x, "z": target.z, "queued": queued});
     1307        Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "x": target.x, "z": target.z, "queued": queued});
    13061308        Engine.GuiInterfaceCall("PlaySound", { "name": "order_walk", "entity": selection[0] });
    13071309        return true;
    13081310
    13091311    case "attack":
    13101312        Engine.PostNetworkCommand({"type": "attack", "entities": selection, "target": action.target, "queued": queued});
     1313        Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "target": action.target, "queued": queued});
    13111314        Engine.GuiInterfaceCall("PlaySound", { "name": "order_attack", "entity": selection[0] });
    13121315        return true;
    13131316
    13141317    case "heal":
    13151318        Engine.PostNetworkCommand({"type": "heal", "entities": selection, "target": action.target, "queued": queued});
     1319        Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "target": action.target, "queued": queued});
    13161320        Engine.GuiInterfaceCall("PlaySound", { "name": "order_heal", "entity": selection[0] });
    13171321        return true;
    13181322
    13191323    case "build": // (same command as repair)
    13201324    case "repair":
    13211325        Engine.PostNetworkCommand({"type": "repair", "entities": selection, "target": action.target, "autocontinue": true, "queued": queued});
     1326        Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "target": action.target, "queued": queued});
    13221327        Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] });
    13231328        return true;
    13241329
    13251330    case "gather":
    13261331        Engine.PostNetworkCommand({"type": "gather", "entities": selection, "target": action.target, "queued": queued});
     1332        Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "target": action.target, "queued": queued});
    13271333        Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
    13281334        return true;
    13291335
    13301336    case "returnresource":
    13311337        Engine.PostNetworkCommand({"type": "returnresource", "entities": selection, "target": action.target, "queued": queued});
     1338        Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "target": action.target, "queued": queued});
    13321339        Engine.GuiInterfaceCall("PlaySound", { "name": "order_gather", "entity": selection[0] });
    13331340        return true;
    13341341
    13351342    case "setup-trade-route":
    13361343        Engine.PostNetworkCommand({"type": "setup-trade-route", "entities": selection, "target": action.target});
     1344        Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "target": action.target, "queued": queued});
    13371345        Engine.GuiInterfaceCall("PlaySound", { "name": "order_trade", "entity": selection[0] });
    13381346        return true;
    13391347
    13401348    case "garrison":
    13411349        Engine.PostNetworkCommand({"type": "garrison", "entities": selection, "target": action.target, "queued": queued});
     1350        Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "target": action.target, "queued": queued});
    13421351        Engine.GuiInterfaceCall("PlaySound", { "name": "order_garrison", "entity": selection[0] });
    13431352        return true;
    13441353
  • binaries/data/mods/public/gui/session/session.js

     
    260260
    261261        // Display rally points for selected buildings
    262262        Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
     263
     264        // Display way points for selected units
     265        Engine.GuiInterfaceCall("DisplayWayPoint", { "entities": g_Selection.toList() });
    263266    }
    264267
    265268    // Run timers
  • binaries/data/mods/public/simulation/components/GuiInterface.js

     
    2020    this.placementEntity = undefined; // = undefined or [templateName, entityID]
    2121    this.placementWallEntities = undefined;
    2222    this.placementWallLastAngle = 0;
    23     this.rallyPoints = undefined;
    2423    this.notifications = [];
    2524    this.renamedEntities = [];
    2625};
     
    710709        if (cmpRallyPointRenderer)
    711710            cmpRallyPointRenderer.SetDisplayed(false);
    712711    }
    713    
     712
    714713    this.entsRallyPointsDisplayed = [];
    715    
     714
    716715    // Show the rally points for the passed entities
    717716    for each (var ent in cmd.entities)
    718717    {
    719718        var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
    720719        if (!cmpRallyPointRenderer)
    721720            continue;
    722        
     721
    723722        // entity must have a rally point component to display a rally point marker
    724723        // (regardless of whether cmd specifies a custom location)
    725724        var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
     
    756755};
    757756
    758757/**
     758 * Displays the way points of a given list of entities (carried in cmd.entities).
     759 *
     760 * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the way point should
     761 * be rendered or a target to retrieve that location, in order to support instantaneously rendering a way point
     762 * marker at a specified location instead of incurring a delay before PostNetworkCommand is processed.
     763 * If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
     764 * WayPoint component.
     765 */
     766GuiInterface.prototype.DisplayWayPoint = function(player, cmd)
     767{
     768    var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
     769    var cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
     770
     771    // If there are some way points already displayed, first hide them
     772    for each (var ent in this.entsWayPointsDisplayed)
     773    {
     774        var cmpWayPointRenderer = Engine.QueryInterface(ent, IID_WayPointRenderer);
     775        if (cmpWayPointRenderer)
     776            cmpWayPointRenderer.SetDisplayed(false);
     777    }
     778
     779    this.entsWayPointsDisplayed = [];
     780
     781    // Show the way points for the passed entities
     782    for each (var ent in cmd.entities)
     783    {
     784        var cmpWayPointRenderer = Engine.QueryInterface(ent, IID_WayPointRenderer);
     785        if (!cmpWayPointRenderer)
     786            continue;
     787
     788        // entity must have a way point component to display a way point marker
     789        // (regardless of whether cmd specifies a custom location)
     790        var cmpWayPoint = Engine.QueryInterface(ent, IID_WayPoint);
     791        if (!cmpWayPoint)
     792            continue;
     793
     794        // Verify the owner
     795        var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership);
     796        if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
     797            if (!cmpOwnership || cmpOwnership.GetOwner() != player)
     798                continue;
     799
     800        // If the command was passed an explicit position or a target, use that and
     801        // override the real way point position; otherwise use the real position
     802        var pos;
     803        if (cmd.x && cmd.z)
     804            pos = cmd;
     805        else if (cmd.target)
     806            pos = Engine.QueryInterface(cmd.target, IID_Position).GetPosition();
     807        else
     808            pos = cmpWayPoint.GetPositions()[0]; // may return undefined if no way point is set
     809
     810        if (pos)
     811        {
     812            // Only update the position if we changed it (cmd.queued is set)
     813            if (cmd.queued == true)
     814                cmpWayPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
     815            else if (cmd.queued == false)
     816                cmpWayPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
     817            cmpWayPointRenderer.SetDisplayed(true);
     818           
     819            // remember which entities have their way points displayed so we can hide them again
     820            this.entsWayPointsDisplayed.push(ent);
     821        }
     822    }
     823};
     824
     825/**
    759826 * Display the building placement preview.
    760827 * cmd.template is the name of the entity template, or "" to disable the preview.
    761828 * cmd.x, cmd.z, cmd.angle give the location.
     
    16661733    "SetStatusBars": 1,
    16671734    "GetPlayerEntities": 1,
    16681735    "DisplayRallyPoint": 1,
     1736    "DisplayWayPoint": 1,
    16691737    "SetBuildingPlacementPreview": 1,
    16701738    "SetWallPlacementPreview": 1,
    16711739    "GetFoundationSnapData": 1,
  • binaries/data/mods/public/simulation/components/UnitAI.js

     
    21212121    this.orderQueue.shift();
    21222122    this.order = this.orderQueue[0];
    21232123
     2124    // Remove current waypoint
     2125    var cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
     2126    if (cmpWayPoint)
     2127    {
     2128        cmpWayPoint.Shift();
     2129        var cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
     2130        if (cmpWayPointRenderer)
     2131            cmpWayPointRenderer.Shift();
     2132    }
     2133    // TODO: Waypoints for formations
     2134
    21242135    if (this.orderQueue.length)
    21252136    {
    21262137        var ret = UnitFsm.ProcessMessage(this,
     
    21552166    var order = { "type": type, "data": data };
    21562167    this.orderQueue.push(order);
    21572168
     2169    if (!data.force)
     2170        this.AddWayPoint(data);
     2171
    21582172    // If we didn't already have an order, then process this new one
    21592173    if (this.orderQueue.length == 1)
    21602174    {
     
    21862200    {
    21872201        var cheeringOrder = this.orderQueue.shift();
    21882202        this.orderQueue.unshift(cheeringOrder, order);
     2203       
     2204        // TODO AddWayPoint
    21892205    }
    21902206    else
    21912207    {
    21922208        this.orderQueue.unshift(order);
    21932209        this.order = order;
     2210
     2211        this.AddWayPointFront(data);
     2212
    21942213        var ret = UnitFsm.ProcessMessage(this,
    21952214            {"type": "Order."+this.order.type, "data": this.order.data}
    21962215        );
     
    22172236        var order = { "type": type, "data": data };
    22182237        var cheeringOrder = this.orderQueue.shift();
    22192238        this.orderQueue = [ cheeringOrder, order ];
     2239       
     2240        // TODO Waypoints
    22202241    }
    22212242    else
    22222243    {
    22232244        this.orderQueue = [];
    22242245        this.PushOrder(type, data);
     2246
     2247        this.ReplaceWayPoint(data);
     2248    }
     2249};
     2250
     2251UnitAI.prototype.AddWayPointFront = function(data)
     2252{
     2253    var cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
     2254    if (cmpWayPoint)
     2255    {
     2256        var pos;
     2257        var cmpPosition;
     2258        if (data.target && (cmpPosition = Engine.QueryInterface(data.target, IID_Position)))
     2259            pos = cmpPosition.GetPosition();
     2260        else
     2261            pos = {'x': data.x, 'z': data.z};
     2262
     2263        cmpWayPoint.AddPositionFront(pos.x, pos.z);
     2264        var cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
     2265        if (cmpWayPointRenderer)
     2266            cmpWayPointRenderer.AddPositionFront({'x': pos.x, 'y': pos.z}); // AddPositionFront takes a CFixedVector2D which has X/Y components, not X/Z
     2267    }
     2268};
     2269
     2270UnitAI.prototype.AddWayPoint = function(data)
     2271{
     2272    var cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
     2273    if (cmpWayPoint)
     2274    {
     2275        var pos;
     2276        var cmpPosition;
     2277        if (data.target && (cmpPosition = Engine.QueryInterface(data.target, IID_Position)))
     2278            pos = cmpPosition.GetPosition();
     2279        else if (data.x && data.z)
     2280            pos = {'x': data.x, 'z': data.z};
     2281        else
     2282            return;
     2283
     2284        cmpWayPoint.AddPosition(pos.x, pos.z);
     2285        var cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
     2286        if (cmpWayPointRenderer)
     2287            cmpWayPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
     2288    }
     2289};
     2290
     2291UnitAI.prototype.ReplaceWayPoint = function(data)
     2292{
     2293    var cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
     2294    if (cmpWayPoint)
     2295    {
     2296        var pos;
     2297        var cmpPosition;
     2298
     2299        cmpWayPoint.Unset();
     2300        if (data.target && (cmpPosition = Engine.QueryInterface(data.target, IID_Position)))
     2301            pos = cmpPosition.GetPosition();
     2302        else if (data.x && data.z)
     2303            pos = {'x': data.x, 'z': data.z};
     2304        else
     2305            return;
     2306
     2307        cmpWayPoint.AddPosition(pos.x, pos.z);
     2308        var cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
     2309        if (cmpWayPointRenderer)
     2310            cmpWayPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
    22252311    }
    22262312};
    22272313
     
    31473233    if (marketsChanged)
    31483234    {
    31493235        if (cmpTrader.HasBothMarkets())
     3236        {
     3237            // TODO move this to AddOrder?
     3238            this.AddWayPoint({"target": cmpTrader.GetFirstMarket()});
     3239            this.AddWayPoint({"target": cmpTrader.GetSecondMarket()});
    31503240            this.AddOrder("Trade", { "firstMarket": cmpTrader.GetFirstMarket(), "secondMarket": cmpTrader.GetSecondMarket(), "force": false }, queued);
     3241        }
    31513242        else
    31523243            this.WalkToTarget(cmpTrader.GetFirstMarket(), queued);
    31533244    }
     
    31553246
    31563247UnitAI.prototype.MoveToMarket = function(targetMarket)
    31573248{
     3249    var cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
     3250    if (cmpWayPoint)
     3251    {
     3252        cmpWayPoint.Shift();
     3253        var cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
     3254        if (cmpWayPointRenderer)
     3255            cmpWayPointRenderer.Shift();
     3256    }
     3257
    31583258    if (this.MoveToTarget(targetMarket))
    31593259    {
    31603260        // We've started walking to the market
     
    31663266        // Give up.
    31673267        this.StopMoving();
    31683268        this.StopTrading();
     3269        // TODO clear waypoints
    31693270        return false;
    31703271    }
    31713272};
     
    31803281
    31813282    if (this.CheckTargetRange(currentMarket, IID_Trader))
    31823283    {
     3284        this.AddWayPoint({"target": currentMarket});
    31833285        this.PerformTrade();
    31843286        if (this.MoveToMarket(nextMarket))
    31853287        {
  • binaries/data/mods/public/simulation/components/WayPoint.js

     
     1function WayPoint() {}
     2
     3WayPoint.prototype.Schema =
     4    "<a:component/><empty/>";
     5
     6WayPoint.prototype.Init = function()
     7{
     8    this.pos = [];
     9};
     10
     11WayPoint.prototype.AddPosition = function(x, z)
     12{
     13    this.pos.push({
     14        'x': x,
     15        'z': z
     16    });
     17};
     18
     19WayPoint.prototype.AddPositionFront = function(x, z)
     20{
     21    this.pos.unshift({
     22        'x': x,
     23        'z': z
     24    });
     25};
     26
     27WayPoint.prototype.GetPositions = function()
     28{
     29    return this.pos;
     30};
     31
     32WayPoint.prototype.Unset = function()
     33{
     34    this.pos = [];
     35};
     36
     37WayPoint.prototype.Shift = function()
     38{
     39//  warn(uneval(this.pos));
     40    this.pos.shift();
     41};
     42
     43Engine.RegisterComponentType(IID_WayPoint, "WayPoint", WayPoint);
  • binaries/data/mods/public/simulation/components/interfaces/WayPoint.js

     
     1Engine.RegisterInterface("WayPoint");
  • binaries/data/mods/public/simulation/components/tests/test_UnitAI.js

     
    99Engine.LoadComponentScript("interfaces/ResourceSupply.js");
    1010Engine.LoadComponentScript("interfaces/Timer.js");
    1111Engine.LoadComponentScript("interfaces/UnitAI.js");
     12Engine.LoadComponentScript("interfaces/WayPoint.js");
    1213Engine.LoadComponentScript("Formation.js");
    1314Engine.LoadComponentScript("UnitAI.js");
    1415
  • binaries/data/mods/public/simulation/helpers/Commands.js

     
    418418    default:
    419419        error("Invalid command: unknown command type: "+uneval(cmd));
    420420    }
     421
     422    // Set way points
     423    switch(cmd.type)
     424    {
     425    case "walk":            //ok
     426    case "attack":          //ok
     427    case "heal":            //ok
     428    case "repair":          //ok
     429    case "gather":          //ok
     430    case "gather-near-position":    // NOTE: Not working with rallypoints so need to find another way...
     431                    // if no animals/resources of that specific type, no further lines will
     432                    // be displayed (TODO fix RallyPoint or the part that pushes commands
     433                    // for ungarrisoned/trained units)
     434    case "returnresource":      //ok
     435    case "garrison":        //ok
     436    case "setup-trade-route":   //ok
     437        for each (var ent in cmd.entities)
     438        {
     439            var cmpWayPoint = Engine.QueryInterface(ent, IID_WayPoint);
     440            if (cmpWayPoint)
     441            {
     442                if (!cmd.queued)
     443                    cmpWayPoint.Unset();
     444                if (cmd.x && cmd.z)
     445                    cmpWayPoint.AddPosition(cmd.x, cmd.z);
     446                else if (cmd.target)
     447                {
     448                    var cmpPosition = Engine.QueryInterface(cmd.target, IID_Position);
     449                    if (cmpPosition)
     450                    {
     451                        cmpWayPoint.AddPosition(cmpPosition.GetPosition().x, cmpPosition.GetPosition().z);
     452
     453                        // For setup-trade-route call from RallyPointCommands
     454                        if (cmd.source)
     455                        {
     456                            warn("cmd.source "+uneval(cmd));
     457                            var cmpSourcePosition = Engine.QueryInterface(cmd.source, IID_Position);
     458                            if (cmpSourcePosition)
     459                                cmpWayPoint.AddPosition(cmpSourcePosition.GetPosition().x, cmpSourcePosition.GetPosition().z);
     460                        }
     461                    }
     462                }
     463            }
     464        }
     465        break;
     466    }
    421467}
    422468
    423469/**
     
    754800        {
    755801            if (!(i == 0 && piece.template == cmd.wallSet.templates.tower && !cmd.startSnappedEntity))
    756802            {
    757                 error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass, iteration " + i + ")");
    758                 break;
     803                error("[TryConstructWall] Expected last tower control group to be available, none found (1st pass, iteration " + i + ")");
     804                break;
    759805            }
    760806        }
    761807       
     
    783829            if (cmpEndSnappedObstruction)
    784830                constructPieceCmd.obstructionControlGroup2 = cmpEndSnappedObstruction.GetControlGroup();
    785831        }
    786        
     832
    787833        var pieceEntityId = TryConstructBuilding(player, cmpPlayer, controlAllUnits, constructPieceCmd);
     834
     835        // Waypoints
     836        var cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
     837        if (cmpGuiInterface)
     838            cmpGuiInterface.DisplayWayPoint(player, constructPieceCmd);
     839
    788840        if (pieceEntityId)
    789841        {
    790842            // wall piece foundation successfully built, save the entity ID in the piece info object so we can reference it later
  • binaries/data/mods/public/simulation/templates/special/waypoint.xml

     
     1<?xml version="1.0" encoding="utf-8"?>
     2<Entity parent="special/actor">
     3  <VisualActor>
     4    <Actor>props/special/common/waypoint_flag.xml</Actor>
     5  </VisualActor>
     6  <Vision>
     7    <AlwaysVisible>true</AlwaysVisible>
     8  </Vision>
     9</Entity>
  • binaries/data/mods/public/simulation/templates/template_unit.xml

     
    120120    <SilhouetteDisplay>true</SilhouetteDisplay>
    121121    <SilhouetteOccluder>false</SilhouetteOccluder>
    122122  </VisualActor>
     123  <WayPoint/>
     124  <WayPointRenderer>
     125    <MarkerTemplate>special/rallypoint</MarkerTemplate>
     126    <LineTexture>art/textures/misc/rallypoint_line.png</LineTexture>
     127    <LineTextureMask>art/textures/misc/rallypoint_line_mask.png</LineTextureMask>
     128    <LineThickness>0.2</LineThickness>
     129    <LineColour r="35" g="86" b="188"/>
     130    <LineDashColour r="158" g="11" b="15"/>
     131    <LineStartCap>square</LineStartCap>
     132    <LineEndCap>round</LineEndCap>
     133    <LineCostClass>default</LineCostClass>
     134    <LinePassabilityClass>default</LinePassabilityClass>
     135  </WayPointRenderer>
    123136</Entity>
  • binaries/data/mods/public/simulation/templates/units/cart_support_healer_b.xml

     
    55    <SelectionGroupName>units/cart_support_healer_b</SelectionGroupName>
    66    <SpecificName>Kehinit</SpecificName>
    77    <History>Tanit (also spelled TINITH, TINNIT, or TINT), chief goddess of Carthage, equivalent of Astarte. Although she seems to have had some connection with the heavens, she was also a mother goddess, and fertility symbols often accompany representations of her. She was probably the consort of Baal Hammon (or Amon), the chief god of Carthage, and was often given the attribute "face of Baal." Although Tanit did not appear at Carthage before the 5th century BC, she soon eclipsed the more established cult of Baal Hammon and, in the Carthaginian area at least, was frequently listed before him on the monuments. In the worship of Tanit and Baal Hammon, children, probably firstborn, were sacrificed. Ample evidence of the practice has been found west of Carthage in the precinct of Tanit, where a tofet (a sanctuary for the sacrifice of children) was discovered. Tanit was also worshipped on Malta, Sardinia, and in Spain. There is no other reason for giving the Carthaginians a priestess instead of a priest in 0 A.D., although Tanit was the most popular of their two main gods with the people. </History>
    8     <Tooltip>Heal units within her aura. (Not implemented yet)</Tooltip>
    98    <Icon>units/cart_support_healer.png</Icon>
    109    <Rank>Basic</Rank>
    1110  </Identity>
  • source/renderer/TimeManager.h

     
    4040};
    4141
    4242
    43 #endif // INCLUDED_TIMEMANAGER
    44  No newline at end of file
     43#endif // INCLUDED_TIMEMANAGER
  • source/simulation2/TypeList.h

     
    161161
    162162INTERFACE(WaterManager)
    163163COMPONENT(WaterManager)
     164
     165INTERFACE(WayPointRenderer)
     166COMPONENT(WayPointRenderer)
  • source/simulation2/components/CCmpRallyPointRenderer.cpp

     
    9292    /// actual positions used in the simulation at any given time. In particular, we need this separate copy to support
    9393    /// instantaneously rendering the rally point markers/lines when the user sets one in-game (instead of waiting until the
    9494    /// network-synchronization code sets it on the RallyPoint component, which might take up to half a second).
    95     std::vector<CFixedVector2D> m_RallyPoints;
     95    std::deque<CFixedVector2D> m_RallyPoints;
    9696    /// Full path to the rally points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
    97     std::vector<std::vector<CVector2D> > m_Path;
     97    std::deque<std::vector<CVector2D> > m_Path;
    9898    /// Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments.
    9999    std::deque<std::deque<SVisibilitySegment> > m_VisibilitySegments;
    100100
    101101    bool m_Displayed; ///< Should we render the rally points and the path lines? (set from JS when e.g. the unit is selected/deselected)
    102102    bool m_SmoothPath; ///< Smooth the path before rendering?
    103103
    104     std::vector<entity_id_t> m_MarkerEntityIds; ///< Entity IDs of the rally point markers.
     104    std::deque<entity_id_t> m_MarkerEntityIds; ///< Entity IDs of the rally point markers.
    105105    size_t m_LastMarkerCount;
    106106    player_id_t m_LastOwner; ///< Last seen owner of this entity (used to keep track of ownership changes).
    107107    std::wstring m_MarkerTemplate;  ///< Template name of the rally point markers.
     
    122122
    123123    /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
    124124    /// dashes for segments that are inside the SoD.
    125     std::vector<std::vector<SOverlayTexturedLine> > m_TexturedOverlayLines;
     125    std::list<std::list<SOverlayTexturedLine> > m_TexturedOverlayLines;
    126126
    127127    /// Draw little overlay circles to indicate where the exact path points are?
    128128    bool m_EnableDebugNodeOverlay;
    129     std::vector<std::vector<SOverlayLine> > m_DebugNodeOverlays;
     129    std::deque<std::deque<SOverlayLine> > m_DebugNodeOverlays;
    130130
    131131public:
    132132
     
    243243            break;
    244244        case MT_Destroy:
    245245            {
    246                 for (std::vector<entity_id_t>::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it)
     246                for (std::deque<entity_id_t>::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it)
    247247                {
    248248                    if (*it != INVALID_ENTITY)
    249249                    {
     
    668668    {
    669669        while (index >= m_DebugNodeOverlays.size())
    670670        {
    671             std::vector<SOverlayLine> tmp;
     671            std::deque<SOverlayLine> tmp;
    672672            m_DebugNodeOverlays.push_back(tmp);
    673673        }
    674674        m_DebugNodeOverlays[index].clear();
     
    718718    // pass (which is only sensible).
    719719    while (index >= m_TexturedOverlayLines.size())
    720720    {
    721         std::vector<SOverlayTexturedLine> tmp;
     721        std::list<SOverlayTexturedLine> tmp;
    722722        m_TexturedOverlayLines.push_back(tmp);
    723723    }
    724     m_TexturedOverlayLines[index].clear();
     724
     725    std::list<std::list<SOverlayTexturedLine> >::iterator iter = m_TexturedOverlayLines.begin();
     726    size_t count = index;
     727    while(count--)
     728        iter++;
     729    (*iter).clear();
    725730
    726731    if (m_Path[index].size() < 2)
    727732        return;
     
    762767                overlayLine.m_Coords.push_back(m_Path[index][j].Y);
    763768            }
    764769
    765             m_TexturedOverlayLines[index].push_back(overlayLine);
     770            (*iter).push_back(overlayLine);
    766771        }
    767772        else
    768773        {
     
    834839                    dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y);
    835840                }
    836841
    837                 m_TexturedOverlayLines[index].push_back(dashOverlay);
     842                (*iter).push_back(dashOverlay);
    838843            }
    839844           
    840845        }
     
    845850    {
    846851        while (index >= m_DebugNodeOverlays.size())
    847852        {
    848             std::vector<SOverlayLine> tmp;
     853            std::deque<SOverlayLine> tmp;
    849854            m_DebugNodeOverlays.push_back(tmp);
    850855        }
    851856        for (size_t j = 0; j < m_Path[index].size(); ++j)
     
    11891194void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector)
    11901195{
    11911196    // we only get here if the rally point is set and should be displayed
    1192     for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i)
     1197    for (std::list<std::list<SOverlayTexturedLine> >::iterator it = m_TexturedOverlayLines.begin();
     1198        it != m_TexturedOverlayLines.end(); ++it)
    11931199    {
    1194         for (size_t j = 0; j < m_TexturedOverlayLines[i].size(); ++j)
     1200        for (std::list<SOverlayTexturedLine>::iterator iter = (*it).begin(); iter != (*it).end(); ++iter)
    11951201        {
    1196             if (!m_TexturedOverlayLines[i][j].m_Coords.empty())
    1197                 collector.Submit(&m_TexturedOverlayLines[i][j]);
     1202            if (!(*iter).m_Coords.empty())
     1203                collector.Submit(&(*iter));
    11981204        }
    11991205    }
    12001206
  • source/simulation2/components/CCmpWayPointRenderer.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#include "ICmpWayPointRenderer.h"
     20
     21#include "simulation2/MessageTypes.h"
     22#include "simulation2/components/ICmpFootprint.h"
     23#include "simulation2/components/ICmpObstructionManager.h"
     24#include "simulation2/components/ICmpOwnership.h"
     25#include "simulation2/components/ICmpPathfinder.h"
     26#include "simulation2/components/ICmpPlayer.h"
     27#include "simulation2/components/ICmpPlayerManager.h"
     28#include "simulation2/components/ICmpPosition.h"
     29#include "simulation2/components/ICmpRangeManager.h"
     30#include "simulation2/components/ICmpTerrain.h"
     31#include "simulation2/components/ICmpVisual.h"
     32#include "simulation2/components/ICmpWaterManager.h"
     33#include "simulation2/helpers/Render.h"
     34#include "simulation2/helpers/Geometry.h"
     35#include "simulation2/system/Component.h"
     36
     37#include "ps/CLogger.h"
     38#include "graphics/Overlay.h"
     39#include "graphics/TextureManager.h"
     40#include "renderer/Renderer.h"
     41
     42// TODO refactor
     43// Maybe create a common ancestor for this and RallyPointRenderer FlagPointRenderer
     44struct SVisibilitySegment
     45{
     46    bool m_Visible;
     47    size_t m_StartIndex;
     48    size_t m_EndIndex; // inclusive
     49
     50    SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
     51        : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex)
     52    {}
     53
     54    bool operator==(const SVisibilitySegment& other) const
     55    {
     56        return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex);
     57    }
     58
     59    bool operator!=(const SVisibilitySegment& other) const
     60    {
     61        return !(*this == other);
     62    }
     63
     64    bool IsSinglePoint()
     65    {
     66        return (m_StartIndex == m_EndIndex);
     67    }
     68};
     69
     70class CCmpWayPointRenderer : public ICmpWayPointRenderer
     71{
     72    // import some types for less verbosity
     73    typedef ICmpRangeManager::CLosQuerier CLosQuerier;
     74    typedef SOverlayTexturedLine::LineCapType LineCapType;
     75
     76public:
     77    static void ClassInit(CComponentManager& componentManager)
     78    {
     79        componentManager.SubscribeToMessageType(MT_RenderSubmit);
     80        componentManager.SubscribeToMessageType(MT_OwnershipChanged);
     81        componentManager.SubscribeToMessageType(MT_TurnStart);
     82        componentManager.SubscribeToMessageType(MT_Destroy);
     83        componentManager.SubscribeToMessageType(MT_PositionChanged);
     84    }
     85
     86    DEFAULT_COMPONENT_ALLOCATOR(WayPointRenderer)
     87
     88protected:
     89
     90    /// Display position of the way points. Note that this are merely the display positions; they not necessarily the same as the
     91    /// actual positions used in the simulation at any given time. In particular, we need this separate copy to support
     92    /// instantaneously rendering the way point markers/lines when the user sets one in-game (instead of waiting until the
     93    /// network-synchronization code sets it on the WayPoint component, which might take up to half a second).
     94    std::deque<CFixedVector2D> m_WayPoints;
     95// TODO are we using the pathfinder? / should we be using it that much? TODO wait for the pathfinder rewrite and use hopefully exported data
     96// from that to make a curved path that uses the same data as the moving unit.
     97    /// Full path to the way points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
     98    std::deque<std::deque<CVector2D> > m_Path;
     99
     100    /// Visibility segments of the way point paths; splits the path into SoD/non-SoD segments.
     101    std::deque<std::deque<SVisibilitySegment> > m_VisibilitySegments;
     102
     103    bool m_Displayed; ///< Should we render the way points and the path lines? (set from JS when e.g. the unit is selected/deselected)
     104
     105    std::deque<entity_id_t> m_MarkerEntityIds; ///< Entity IDs of the way point markers.
     106    size_t m_LastMarkerCount;
     107    player_id_t m_LastOwner; ///< Last seen owner of this entity (used to keep track of ownership changes).
     108    std::wstring m_MarkerTemplate;  ///< Template name of the way point markers.
     109
     110    /// Marker connector line settings (loaded from XML)
     111    float m_LineThickness;
     112    CColor m_LineColor;
     113    CColor m_LineDashColor;
     114    LineCapType m_LineStartCapType;
     115    LineCapType m_LineEndCapType;
     116    std::wstring m_LineTexturePath;
     117    std::wstring m_LineTextureMaskPath;
     118    std::string m_LinePassabilityClass; ///< Pathfinder passability class to use for computing the (long-range) marker line path.
     119    std::string m_LineCostClass; ///< Pathfinder cost class to use for computing the (long-range) marker line path.
     120
     121    CTexturePtr m_Texture;
     122    CTexturePtr m_TextureMask;
     123
     124    /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
     125    /// dashes for segments that are inside the SoD.
     126    std::list<std::list<SOverlayTexturedLine> > m_TexturedOverlayLines;
     127
     128public:
     129
     130    static std::string GetSchema()
     131    {
     132        return
     133            "<a:help>Displays a way point marker where units will go to when tasked to move</a:help>"
     134            "<a:example>"
     135                "<MarkerTemplate>special/WayPoint</MarkerTemplate>"
     136                "<LineThickness>0.75</LineThickness>"
     137                "<LineStartCap>round</LineStartCap>"
     138                "<LineEndCap>square</LineEndCap>"
     139                "<LineColour r='20' g='128' b='240'></LineColour>"
     140                "<LineDashColour r='158' g='11' b='15'></LineDashColour>"
     141                "<LineCostClass>default</LineCostClass>"
     142                "<LinePassabilityClass>default</LinePassabilityClass>"
     143            "</a:example>"
     144            "<element name='MarkerTemplate' a:help='Template name for the way point marker entity (typically a waypoint flag actor)'>"
     145                "<text/>"
     146            "</element>"
     147            "<element name='LineTexture' a:help='Texture file to use for the way point line'>"
     148                "<text />"
     149            "</element>"
     150            "<element name='LineTextureMask' a:help='Texture mask to indicate where overlay colors are to be applied (see LineColour and LineDashColour)'>"
     151                "<text />"
     152            "</element>"
     153            "<element name='LineThickness' a:help='Thickness of the marker line connecting the entity to the way point marker'>"
     154                "<data type='decimal'/>"
     155            "</element>"
     156            "<element name='LineColour'>"
     157                "<attribute name='r'>"
     158                    "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     159                "</attribute>"
     160                "<attribute name='g'>"
     161                    "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     162                "</attribute>"
     163                "<attribute name='b'>"
     164                    "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     165                "</attribute>"
     166            "</element>"
     167            "<element name='LineDashColour'>"
     168                "<attribute name='r'>"
     169                    "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     170                "</attribute>"
     171                "<attribute name='g'>"
     172                    "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     173                "</attribute>"
     174                "<attribute name='b'>"
     175                    "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     176                "</attribute>"
     177            "</element>"
     178            "<element name='LineStartCap'>"
     179                "<choice>"
     180                    "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>"
     181                    "<value a:help='Semi-circular line end cap'>round</value>"
     182                    "<value a:help='Sharp, pointy line end cap'>sharp</value>"
     183                    "<value a:help='Square line end cap'>square</value>"
     184                "</choice>"
     185            "</element>"
     186            "<element name='LineEndCap'>"
     187                "<choice>"
     188                    "<value a:help='Abrupt line ending; line endings are not closed'>flat</value>"
     189                    "<value a:help='Semi-circular line end cap'>round</value>"
     190                    "<value a:help='Sharp, pointy line end cap'>sharp</value>"
     191                    "<value a:help='Square line end cap'>square</value>"
     192                "</choice>"
     193            "</element>"
     194            "<element name='LinePassabilityClass' a:help='The pathfinder passability class to use for computing the way point marker line path'>"
     195                "<text />"
     196            "</element>"
     197            "<element name='LineCostClass' a:help='The pathfinder cost class to use for computing the way point marker line path'>"
     198                "<text />"
     199            "</element>";
     200    }
     201
     202    virtual void Init(const CParamNode& paramNode);
     203
     204    virtual void Deinit()
     205    {
     206    }
     207
     208    virtual void Serialize(ISerializer& UNUSED(serialize))
     209    {
     210        // do NOT serialize anything; this is a rendering-only component, it does not and should not affect simulation state
     211    }
     212
     213    virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
     214    {
     215        Init(paramNode);
     216    }
     217
     218    virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
     219    {
     220        switch (msg.GetType())
     221        {
     222        case MT_RenderSubmit:
     223            {
     224                if (m_Displayed && IsSet())
     225                {
     226                    const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (msg);
     227                    RenderSubmit(msgData.collector);
     228                }
     229            }
     230            break;
     231        case MT_OwnershipChanged:
     232            {
     233                UpdateMarkers(); // update marker variation to new player's civilization
     234            }
     235            break;
     236        case MT_TurnStart:
     237            {
     238                UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
     239            }
     240            break;
     241        case MT_Destroy:
     242            {
     243                for (std::deque<entity_id_t>::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it)
     244                {
     245                    if (*it != INVALID_ENTITY)
     246                    {
     247                        GetSimContext().GetComponentManager().DestroyComponentsSoon(*it);
     248                        *it = INVALID_ENTITY;
     249                    }
     250                }
     251            }
     252            break;
     253        case MT_PositionChanged:
     254            {
     255                RecomputeWayPointPath_wrapper(0);
     256            }
     257            break;
     258        }
     259    }
     260
     261    virtual void AddPosition_wrapper(CFixedVector2D pos)
     262    {
     263        AddPosition(pos, false);
     264    }
     265
     266    // Rename to Unshift???
     267    virtual void AddPositionFront(CFixedVector2D pos)
     268    {
     269        m_WayPoints.push_front(pos);
     270        UpdateMarkers();
     271
     272        // TODO don't recompute everything (maybe just shift everything one back? and recompute first only
     273        RecomputeAllWayPointPaths();
     274    }
     275
     276    virtual void SetPosition(CFixedVector2D pos)
     277    {
     278        if (!(m_WayPoints.size() == 1 && m_WayPoints.front() == pos))
     279        {
     280            m_WayPoints.clear();
     281            AddPosition(pos, true);
     282        }
     283    }
     284
     285    virtual void SetDisplayed(bool displayed)
     286    {
     287        if (m_Displayed != displayed)
     288        {
     289            m_Displayed = displayed;
     290
     291            // move the markers out of oblivion and back into the real world, or vice-versa
     292            UpdateMarkers();
     293           
     294            // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method
     295            // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
     296            // while this way point was not being displayed.
     297            UpdateOverlayLines();
     298        }
     299    }
     300
     301    virtual void Shift()
     302    {
     303        LOGERROR(L"m_WayPoints %d \tm_Path %d \tm_VisibilitySegments %d \tm_TexturedOverlayLines %d\n",m_WayPoints.size(),m_Path.size(),m_VisibilitySegments.size(),m_TexturedOverlayLines.size());
     304
     305        // Still not sure why size is sometimes <= 0
     306        if (m_WayPoints.size() > 0)
     307        {
     308            m_WayPoints.pop_front();
     309
     310            m_Path.pop_front();
     311            m_VisibilitySegments.pop_front();
     312
     313            m_TexturedOverlayLines.pop_front();
     314
     315            // TODO should we skip this as this is already done on MT_PositionChanged?
     316            RecomputeWayPointPath_wrapper(0);
     317
     318            UpdateMarkers();
     319        }
     320    }
     321
     322private:
     323
     324    /**
     325     * Helper function for AddPosition_wrapper and SetPosition.
     326     */
     327    void AddPosition(CFixedVector2D pos, bool recompute)
     328    {
     329        m_WayPoints.push_back(pos);
     330        UpdateMarkers();
     331
     332        if (recompute)
     333            RecomputeAllWayPointPaths();
     334        else
     335            RecomputeWayPointPath_wrapper(m_WayPoints.size()-1);
     336    }
     337
     338    /**
     339     * Returns true iff at least one display way point is set; i.e., if we have a point to render our marker/line at.
     340     */
     341    bool IsSet()
     342    {
     343        return !m_WayPoints.empty();
     344    }
     345
     346    /**
     347     * Repositions the way point markers; moves them outside of the world (ie. hides them), or positions them at the currently
     348     * set way points. Also updates the actor's variation according to the entity's current owning player's civilization.
     349     *
     350     * Should be called whenever either the position of a way point changes (including whether it is set or not), or the display
     351     * flag changes, or the ownership of the entity changes.
     352     */
     353    void UpdateMarkers();
     354
     355    /**
     356     * Recomputes all the full paths from this entity to the way point and from the way point to the next, and does all the necessary
     357     * post-processing to make them prettier.
     358     *
     359     * Should be called whenever all way points' position changes.
     360     */
     361    void RecomputeAllWayPointPaths();
     362
     363    /**
     364     * Recomputes the full path for m_Path[ @p index], and does all the necessary post-processing to make it prettier.
     365     *
     366     * Should be called whenever either the starting position or the way point's position changes.
     367     */
     368    void RecomputeWayPointPath_wrapper(size_t index);
     369
     370    /**
     371     * Recomputes the full path from this entity/the previous way point to the next way point, and does all the necessary
     372     * post-processing to make it prettier. This doesn't check if we have a valid position or if a way point is set.
     373     *
     374     * You shouldn't need to call this method directly.
     375     */
     376    void RecomputeWayPointPath(size_t index, CmpPtr<ICmpPosition>& cmpPosition, CmpPtr<ICmpFootprint>& cmpFootprint, CmpPtr<ICmpPathfinder> cmpPathfinder);
     377
     378    /**
     379     * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to
     380     * match if necessary. Does nothing if the way point lines are not currently set to be displayed, or if no way point is set.
     381     */
     382    void UpdateOverlayLines();
     383
     384    /**
     385     * Sets up all overlay lines for rendering according to the current full path and visibility segments. Splits the line into solid
     386     * and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently set, this method
     387     * does nothing.
     388     */
     389    void ConstructAllOverlayLines();
     390
     391    /**
     392     * Sets up the overlay lines for rendering according to the full path and visibility segments at @p index. Splits the line into
     393     * solid and dashed pieces (for the SoD). Should be called whenever the SoD of the path at @p index has changed.
     394     */
     395    void ConstructOverlayLines(size_t index);
     396
     397    /**
     398     * Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility changes, ordered from
     399     * building/previous way point to way point. Used to construct the overlay line segments and track changes to the SoD.
     400     */
     401    void GetVisibilitySegments(std::deque<SVisibilitySegment>& out, size_t index);
     402
     403    /**
     404     * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily
     405     * intended to reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which
     406     * will happily curve and bend to reduce costs. Visually, it doesn't make sense for a way point path to curve and bend when it
     407     * could just as well have gone in a straight line; that's why we have this, to make it look more natural.
     408     *
     409     * @p coords array of path coordinates to simplify
     410     * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be joined into a
     411     *                    single link. If this value is set to e.g. 1, then no reductions will be performed. A value of 3 means that
     412     *                    at most 3 consecutive node links will be joined into a single link.
     413     * @p floating whether to consider nodes who are under the water level as floating on top of the water
     414     */
     415    void ReduceSegmentsByVisibility(std::deque<CVector2D>& coords, unsigned maxSegmentLinks = 0, bool floating = true);
     416
     417    /**
     418     * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring
     419     * segments. You should not have to call this method directly.
     420     */
     421    static void MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments);
     422
     423    void RenderSubmit(SceneCollector& collector);
     424};
     425
     426REGISTER_COMPONENT_TYPE(WayPointRenderer)
     427
     428void CCmpWayPointRenderer::Init(const CParamNode& paramNode)
     429{
     430    m_Displayed = false;
     431    m_LastOwner = INVALID_PLAYER;
     432    m_LastMarkerCount = 0;
     433
     434    // ---------------------------------------------------------------------------------------------
     435    // load some XML configuration data (schema guarantees that all these nodes are valid)
     436
     437    m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString();
     438
     439    const CParamNode& lineColor = paramNode.GetChild("LineColour");
     440    m_LineColor = CColor(
     441        lineColor.GetChild("@r").ToInt()/255.f,
     442        lineColor.GetChild("@g").ToInt()/255.f,
     443        lineColor.GetChild("@b").ToInt()/255.f,
     444        1.f
     445    );
     446
     447    const CParamNode& lineDashColor = paramNode.GetChild("LineDashColour");
     448    m_LineDashColor = CColor(
     449        lineDashColor.GetChild("@r").ToInt()/255.f,
     450        lineDashColor.GetChild("@g").ToInt()/255.f,
     451        lineDashColor.GetChild("@b").ToInt()/255.f,
     452        1.f
     453    );
     454
     455    m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat();
     456    m_LineTexturePath = paramNode.GetChild("LineTexture").ToString();
     457    m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString();
     458    m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString());
     459    m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString());
     460    m_LineCostClass = paramNode.GetChild("LineCostClass").ToUTF8();
     461    m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8();
     462
     463    // ---------------------------------------------------------------------------------------------
     464    // load some textures
     465
     466    if (CRenderer::IsInitialised())
     467    {
     468        CTextureProperties texturePropsBase(m_LineTexturePath);
     469        texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
     470        texturePropsBase.SetMaxAnisotropy(4.f);
     471        m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
     472
     473        CTextureProperties texturePropsMask(m_LineTextureMaskPath);
     474        texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
     475        texturePropsMask.SetMaxAnisotropy(4.f);
     476        m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
     477    }
     478}
     479
     480void CCmpWayPointRenderer::UpdateMarkers()
     481{
     482    player_id_t previousOwner = m_LastOwner;
     483    for (size_t i = 0; i < m_WayPoints.size(); ++i)
     484    {
     485        if (i >= m_MarkerEntityIds.size())
     486            m_MarkerEntityIds.push_back(INVALID_ENTITY);
     487
     488        if (m_MarkerEntityIds[i] == INVALID_ENTITY)
     489        {
     490            // no marker exists yet, create one first
     491            CComponentManager& componentMgr = GetSimContext().GetComponentManager();
     492
     493            // allocate a new entity for the marker
     494            if (!m_MarkerTemplate.empty())
     495            {
     496                m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity();
     497                if (m_MarkerEntityIds[i] != INVALID_ENTITY)
     498                    m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]);
     499            }
     500        }
     501
     502        // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it
     503        if (m_MarkerEntityIds[i] == INVALID_ENTITY)
     504            LOGERROR(L"Failed to create way point marker entity");
     505
     506        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
     507        if (cmpPosition)
     508        {
     509            if (m_Displayed && IsSet())
     510            {
     511                cmpPosition->JumpTo(m_WayPoints[i].X, m_WayPoints[i].Y);
     512            }
     513            else
     514            {
     515                cmpPosition->MoveOutOfWorld(); // hide it
     516            }
     517        }
     518
     519        // set way point flag selection based on player civilization
     520        CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), GetEntityId());
     521        if (cmpOwnership)
     522        {
     523            player_id_t ownerId = cmpOwnership->GetOwner();
     524            if (ownerId != INVALID_PLAYER && (ownerId != previousOwner || m_LastMarkerCount <= i))
     525            {
     526                m_LastOwner = ownerId;
     527                CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
     528                // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time
     529                // (we can't rely on component initialization order in Init())
     530                if (cmpPlayerManager)
     531                {
     532                    CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId));
     533                    if (cmpPlayer)
     534                    {
     535                        CmpPtr<ICmpVisual> cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]);
     536                        if (cmpVisualActor)
     537                        {
     538                            cmpVisualActor->SetUnitEntitySelection(CStrW(cmpPlayer->GetCiv()).ToUTF8());
     539                        }
     540                    }
     541                }
     542            }
     543        }
     544    }
     545
     546    // Hide currently unused markers
     547    for (size_t i = m_WayPoints.size(); i < m_LastMarkerCount; ++i)
     548    {
     549        CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
     550        if (cmpPosition)
     551            cmpPosition->MoveOutOfWorld();
     552    }
     553    m_LastMarkerCount = m_WayPoints.size();
     554}
     555
     556void CCmpWayPointRenderer::RecomputeAllWayPointPaths()
     557{
     558    m_Path.clear();
     559    m_VisibilitySegments.clear();
     560    m_TexturedOverlayLines.clear();
     561
     562    if (!IsSet())
     563        return; // no use computing a path if the way point isn't set
     564
     565    CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
     566    if (!cmpPosition || !cmpPosition->IsInWorld())
     567        return; // no point going on if this entity doesn't have a position or is outside of the world
     568
     569    // Not used
     570    CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId());
     571    CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
     572
     573    for (size_t i = 0; i < m_WayPoints.size(); ++i)
     574    {
     575        RecomputeWayPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder);
     576    }
     577}
     578
     579void CCmpWayPointRenderer::RecomputeWayPointPath_wrapper(size_t index)
     580{
     581    if (!IsSet())
     582        return; // no use computing a path if the wayw point isn't set
     583
     584    CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
     585    if (!cmpPosition || !cmpPosition->IsInWorld())
     586        return; // no point going on if this entity doesn't have a position or is outside of the world
     587
     588    // Not used
     589    CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId());
     590    CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
     591
     592    RecomputeWayPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder);
     593}
     594
     595void CCmpWayPointRenderer::RecomputeWayPointPath(size_t index, CmpPtr<ICmpPosition>& cmpPosition, CmpPtr<ICmpFootprint>& UNUSED(cmpFootprint), CmpPtr<ICmpPathfinder> UNUSED(cmpPathfinder))
     596{
     597    while (index >= m_Path.size())
     598    {
     599        std::deque<CVector2D> tmp;
     600        m_Path.push_back(tmp);
     601    }
     602    m_Path[index].clear();
     603
     604    while (index >= m_VisibilitySegments.size())
     605    {
     606        std::deque<SVisibilitySegment> tmp;
     607        m_VisibilitySegments.push_back(tmp);
     608    }
     609    m_VisibilitySegments[index].clear();
     610
     611    entity_pos_t pathStartX;
     612    entity_pos_t pathStartY;
     613
     614    if (index == 0)
     615    {
     616        pathStartX = cmpPosition->GetPosition2D().X;
     617        pathStartY = cmpPosition->GetPosition2D().Y;
     618    }
     619    else
     620    {
     621        pathStartX = m_WayPoints[index-1].X;
     622        pathStartY = m_WayPoints[index-1].Y;
     623    }
     624
     625    // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
     626    // list of waypoints (i.e. a Path) from the building/previous way point to the goal, where each
     627    // waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth.
     628    // TODO update comment
     629
     630    m_Path[index].push_back(CVector2D(m_WayPoints[index].X.ToFloat(), m_WayPoints[index].Y.ToFloat()));
     631    m_Path[index].push_back(CVector2D(pathStartX.ToFloat(), pathStartY.ToFloat()));
     632
     633    // find which point is the last visible point before going into the SoD, so we have a point to compare to on the next turn
     634    GetVisibilitySegments(m_VisibilitySegments[index], index);
     635
     636    // build overlay lines for the new path
     637    ConstructOverlayLines(index);
     638}
     639
     640void CCmpWayPointRenderer::ConstructAllOverlayLines()
     641{
     642    m_TexturedOverlayLines.clear();
     643
     644    for (size_t i = 0; i < m_Path.size(); ++i)
     645        ConstructOverlayLines(i);
     646}
     647
     648void CCmpWayPointRenderer::ConstructOverlayLines(size_t index)
     649{
     650    // We need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the
     651    // renderer, because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every
     652    // pass (which is only sensible).
     653    while (index >= m_TexturedOverlayLines.size())
     654    {
     655        std::list<SOverlayTexturedLine> tmp;
     656        m_TexturedOverlayLines.push_back(tmp);
     657    }
     658
     659    std::list<std::list<SOverlayTexturedLine> >::iterator iter = m_TexturedOverlayLines.begin();
     660    size_t count = index;
     661    while(count--)
     662        iter++;
     663    (*iter).clear();
     664
     665    if (m_Path[index].size() < 2)
     666        return;
     667
     668    CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     669    LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
     670
     671    for (std::deque<SVisibilitySegment>::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it)
     672    {
     673        const SVisibilitySegment& segment = (*it);
     674
     675        if (segment.m_Visible)
     676        {
     677            // does this segment border on the building or way point flag on either side?
     678            bool bordersBuilding = (segment.m_EndIndex == m_Path[index].size() - 1);
     679            bool bordersFlag = (segment.m_StartIndex == 0);
     680
     681            // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
     682            SOverlayTexturedLine overlayLine;
     683            overlayLine.m_Thickness = m_LineThickness;
     684            overlayLine.m_SimContext = &GetSimContext();
     685            overlayLine.m_TextureBase = m_Texture;
     686            overlayLine.m_TextureMask = m_TextureMask;
     687            overlayLine.m_Color = m_LineColor;
     688            overlayLine.m_Closed = false;
     689            // we should take care to only use m_LineXCap for the actual end points at the building and the way point; any intermediate
     690            // end points (i.e., that border a dashed segment) should have the dashed cap
     691            // the path line is actually in reverse order as well, so let's swap out the start and end caps
     692            overlayLine.m_StartCapType = (bordersFlag ? m_LineEndCapType : dashesLineCapType);
     693            overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType);
     694            overlayLine.m_AlwaysVisible = true;
     695
     696            // push overlay line coordinates
     697            ENSURE(segment.m_EndIndex > segment.m_StartIndex);
     698            for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here
     699            {
     700                overlayLine.m_Coords.push_back(m_Path[index][j].X);
     701                overlayLine.m_Coords.push_back(m_Path[index][j].Y);
     702            }
     703
     704            (*iter).push_back(overlayLine);
     705        }
     706        else
     707        {
     708            // construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list
     709            std::vector<CVector2D> straightLine;
     710            straightLine.push_back(m_Path[index][segment.m_StartIndex]);
     711            straightLine.push_back(m_Path[index][segment.m_EndIndex]);
     712
     713            // We always want to the dashed line to end at either point with a full dash (i.e. not a cleared space), so that the dashed
     714            // area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them
     715            // to fit exactly.
     716
     717            float maxDashSize = 3.f;
     718            float maxClearSize = 3.f;
     719           
     720            float dashSize = maxDashSize;
     721            float clearSize = maxClearSize;
     722            float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length
     723
     724            float distance = (m_Path[index][segment.m_StartIndex] - m_Path[index][segment.m_EndIndex]).Length(); // straight-line distance between the points
     725
     726            // See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly
     727            // a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is.
     728            int numFitUnmodified = floor(distance/(dashSize + clearSize));
     729            float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize));
     730
     731            // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly.
     732            // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make
     733            // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified
     734            // (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's
     735            // length and the clear's length.
     736
     737            // we always want to have at least one dash/clear pair (i.e., "|===|   |===|"); also, we need to avoid division by zero below.
     738            numFitUnmodified = std::max(1, numFitUnmodified);
     739
     740            float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified; // can be either positive or negative
     741            dashSize += pairDashRatio * pairwiseLengthDifference;
     742            clearSize += (1 - pairDashRatio) * pairwiseLengthDifference;
     743
     744            // ------------------------------------------------------------------------------------------------
     745
     746            SDashedLine dashedLine;
     747            SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize);
     748
     749            // build overlay lines for dashes
     750            size_t numDashes = dashedLine.m_StartIndices.size();
     751            for (size_t i=0; i < numDashes; i++)
     752            {
     753                SOverlayTexturedLine dashOverlay;
     754
     755                dashOverlay.m_Thickness = m_LineThickness;
     756                dashOverlay.m_SimContext = &GetSimContext();
     757                dashOverlay.m_TextureBase = m_Texture;
     758                dashOverlay.m_TextureMask = m_TextureMask;
     759                dashOverlay.m_Color = m_LineDashColor;
     760                dashOverlay.m_Closed = false;
     761                dashOverlay.m_StartCapType = dashesLineCapType;
     762                dashOverlay.m_EndCapType = dashesLineCapType;
     763                dashOverlay.m_AlwaysVisible = true;
     764                // TODO: maybe adjust the elevation of the dashes to be a little lower, so that it slides underneath the actual path
     765
     766                size_t dashStartIndex = dashedLine.m_StartIndices[i];
     767                size_t dashEndIndex = dashedLine.GetEndIndex(i);
     768                ENSURE(dashEndIndex > dashStartIndex);
     769
     770                for (size_t n = dashStartIndex; n < dashEndIndex; n++)
     771                {
     772                    dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].X);
     773                    dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y);
     774                }
     775
     776                (*iter).push_back(dashOverlay);
     777            }
     778           
     779        }
     780    }
     781}
     782
     783void CCmpWayPointRenderer::UpdateOverlayLines()
     784{
     785    // We should only do this if the way point is currently being displayed and set inside the world, otherwise it's a massive
     786    // waste of time to calculate all this stuff (this method is called every turn)
     787    if (!m_Displayed || !IsSet())
     788        return;
     789
     790    // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
     791    std::deque<std::deque<SVisibilitySegment> > newVisibilitySegments;
     792    for (size_t i = 0; i < m_Path.size(); ++i)
     793    {
     794        std::deque<SVisibilitySegment> tmp;
     795        newVisibilitySegments.push_back(tmp);
     796        GetVisibilitySegments(newVisibilitySegments[i], i);
     797    }
     798
     799    // Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that.
     800    if (m_VisibilitySegments.size() != newVisibilitySegments.size())
     801    {
     802        m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time
     803        ConstructAllOverlayLines();
     804    }
     805    else
     806    {
     807        for (size_t i = 0; i < m_VisibilitySegments.size(); ++i)
     808        {
     809            if (m_VisibilitySegments[i] != newVisibilitySegments[i])
     810            {
     811                // The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't
     812                // change, only the overlay lines we construct from it.
     813                m_VisibilitySegments[i] = newVisibilitySegments[i]; // save the new visibility segments to compare against next time
     814                ConstructOverlayLines(i);
     815            }
     816        }
     817    }
     818}
     819
     820// TODO remove pathfinder?; or remove this as a whole?
     821void CCmpWayPointRenderer::ReduceSegmentsByVisibility(std::deque<CVector2D>& coords, unsigned maxSegmentLinks, bool floating)
     822{
     823    CmpPtr<ICmpPathfinder> cmpPathFinder(GetSimContext(), SYSTEM_ENTITY);
     824    CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     825    CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
     826    ENSURE(cmpPathFinder && cmpTerrain && cmpWaterManager);
     827
     828    if (coords.size() < 3)
     829        return;
     830
     831    // The basic idea is this: starting from a base node, keep checking each individual point along the path to see if there's a visible
     832    // line between it and the base point. If so, keep going, otherwise, make the last visible point the new base node and start the same
     833    // process from there on until the entire line is checked. The output is the array of base nodes.
     834
     835    std::deque<CVector2D> newCoords;
     836    StationaryOnlyObstructionFilter obstructionFilter;
     837    entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness);
     838    ICmpPathfinder::pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass);
     839
     840    newCoords.push_back(coords[0]); // save the first base node
     841
     842    size_t baseNodeIdx = 0;
     843    size_t curNodeIdx = 1;
     844   
     845    float baseNodeY;
     846    entity_pos_t baseNodeX;
     847    entity_pos_t baseNodeZ;
     848
     849    // set initial base node coords
     850    baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
     851    baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
     852    baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
     853    if (floating)
     854        baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
     855
     856    while (curNodeIdx < coords.size())
     857    {
     858        ENSURE(curNodeIdx > baseNodeIdx); // this needs to be true at all times, otherwise we're checking visibility between a point and itself
     859
     860        entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X);
     861        entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y);
     862        float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y);
     863        if (floating)
     864            curNodeY = std::max(curNodeY, cmpWaterManager->GetExactWaterLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y));
     865
     866        // find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!)
     867        bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass);
     868
     869        // since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at
     870        // roughly the same terrain elevation
     871        curNodeVisible = curNodeVisible && (fabsf(curNodeY - baseNodeY) < 3.f); // TODO: this could probably use some tuning
     872        if (maxSegmentLinks > 0)
     873            // max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx)
     874            curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks);
     875
     876        if (!curNodeVisible)
     877        {
     878            // current node is not visible from the base node, so the previous one was the last visible point from baseNode and should
     879            // hence become the new base node for further iterations.
     880
     881            // if curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then
     882            // we should take care not to stay stuck at the current base node
     883            if (curNodeIdx > baseNodeIdx + 1)
     884            {
     885                baseNodeIdx = curNodeIdx - 1;
     886            }
     887            else
     888            {
     889                // curNodeIdx == baseNodeIdx + 1
     890                baseNodeIdx = curNodeIdx;
     891                curNodeIdx++; // move the next candidate node one forward so that we don't test a point against itself in the next iteration
     892            }
     893
     894            newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list
     895
     896            // update base node coordinates
     897            baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
     898            baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
     899            baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
     900            if (floating)
     901                baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
     902        }
     903
     904        curNodeIdx++;
     905    }
     906
     907    // we always need to add the last point back to the array; if e.g. all the points up to the last one are all visible from the current
     908    // base node, then the loop above just ends and no endpoint is ever added to the list.
     909    ENSURE(curNodeIdx == coords.size());
     910    newCoords.push_back(coords[coords.size() - 1]);
     911
     912    coords.swap(newCoords);
     913}
     914
     915void CCmpWayPointRenderer::GetVisibilitySegments(std::deque<SVisibilitySegment>& out, size_t index)
     916{
     917    out.clear();
     918
     919    if (m_Path[index].size() < 2)
     920        return;
     921
     922    CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY);
     923
     924    player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
     925    CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
     926
     927    // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
     928    // a new one at the next point.
     929
     930    bool lastVisible = losQuerier.IsExplored(
     931        (fixed::FromFloat(m_Path[index][0].X) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest(),
     932        (fixed::FromFloat(m_Path[index][0].Y) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest()
     933    );
     934    size_t curSegmentStartIndex = 0; // starting node index of the current segment
     935
     936    for (size_t k = 1; k < m_Path[index].size(); ++k)
     937    {
     938        // grab tile indices for this coord
     939        int i = (fixed::FromFloat(m_Path[index][k].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
     940        int j = (fixed::FromFloat(m_Path[index][k].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
     941
     942        bool nodeVisible = losQuerier.IsExplored(i, j);
     943        if (nodeVisible != lastVisible)
     944        {
     945            // visibility changed; write out the segment that was just completed and get ready for the new one
     946            out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
     947
     948            //curSegmentStartIndex = k; // new segment starts here
     949            curSegmentStartIndex = k - 1;
     950            lastVisible = nodeVisible;
     951        }
     952
     953    }
     954
     955    // terminate the last segment
     956    out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1));
     957
     958    MergeVisibilitySegments(out);
     959}
     960
     961void CCmpWayPointRenderer::MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments)
     962{
     963    // Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding segments.
     964    // If they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't
     965    // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first).
     966
     967    // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
     968    size_t numSegments = segments.size();
     969
     970    // WARNING: FOR LOOP TRICKERY AHEAD!
     971    for (size_t i = 1; i < numSegments - 1;)
     972    {
     973        SVisibilitySegment& segment = segments[i];
     974        if (segment.IsSinglePoint())
     975        {
     976            // since the segments' visibility alternates, the surrounding ones should have the same visibility
     977            ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible);
     978
     979            segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; // make previous segment span all the way across to the next
     980            segments.erase(segments.begin() + i); // erase this segment ...
     981            segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i])
     982            numSegments -= 2; // we removed 2 segments, so update the loop condition
     983            // in the next iteration, i should still point to the segment right after the one that got expanded, which is now
     984            // at position i; so don't increment i here
     985        }
     986        else
     987        {
     988            ++i;
     989        }
     990    }
     991
     992    ENSURE(numSegments == segments.size());
     993
     994    // check to see if the first segment needs to be merged with its neighbour
     995    if (segments.size() >= 2 && segments[0].IsSinglePoint())
     996    {
     997        int firstSegmentStartIndex = segments.front().m_StartIndex;
     998        ENSURE(firstSegmentStartIndex == 0);
     999        ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment
     1000       
     1001        segments.erase(segments.begin());
     1002        segments.front().m_StartIndex = firstSegmentStartIndex;
     1003
     1004    }
     1005
     1006    // check to see if the last segment needs to be merged with its neighbour
     1007    if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint())
     1008    {
     1009        int lastSegmentEndIndex = segments.back().m_EndIndex;
     1010        ENSURE(!segments[segments.size()-2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment
     1011
     1012        segments.erase(segments.end());
     1013        segments.back().m_EndIndex = lastSegmentEndIndex;
     1014    }
     1015
     1016    // --------------------------------------------------------------------------------------------------------
     1017    // at this point, every segment should have at least 2 points
     1018    for (size_t i = 0; i < segments.size(); ++i)
     1019    {
     1020        ENSURE(!segments[i].IsSinglePoint());
     1021        ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex);
     1022    }
     1023}
     1024
     1025void CCmpWayPointRenderer::RenderSubmit(SceneCollector& collector)
     1026{
     1027    // we only get here if the way point is set and should be displayed
     1028    for (std::list<std::list<SOverlayTexturedLine> >::iterator it = m_TexturedOverlayLines.begin();
     1029        it != m_TexturedOverlayLines.end(); ++it)
     1030    {
     1031        for (std::list<SOverlayTexturedLine>::iterator iter = (*it).begin(); iter != (*it).end(); ++iter)
     1032        {
     1033            if (!(*iter).m_Coords.empty())
     1034                collector.Submit(&(*iter));
     1035        }
     1036    }
     1037}
  • source/simulation2/components/ICmpRallyPointRenderer.h

     
    2424
    2525/**
    2626 * Rally Point.
    27  * Holds the position of a unit's rally points, and renders them to screen.
     27 * Holds the position(s) of a unit's rally points, and renders them to screen.
    2828 */
    2929class ICmpRallyPointRenderer : public IComponent
    3030{
  • source/simulation2/components/ICmpWayPointRenderer.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 "ICmpWayPointRenderer.h"
     21#include "simulation2/system/InterfaceScripted.h"
     22
     23class CFixedVector2D;
     24
     25BEGIN_INTERFACE_WRAPPER(WayPointRenderer)
     26DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpWayPointRenderer, SetDisplayed, bool)
     27DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpWayPointRenderer, SetPosition, CFixedVector2D)
     28DEFINE_INTERFACE_METHOD_1("AddPosition", void, ICmpWayPointRenderer, AddPosition_wrapper, CFixedVector2D)
     29DEFINE_INTERFACE_METHOD_1("AddPositionFront", void, ICmpWayPointRenderer, AddPositionFront, CFixedVector2D)
     30DEFINE_INTERFACE_METHOD_0("Shift", void, ICmpWayPointRenderer, Shift)
     31END_INTERFACE_WRAPPER(WayPointRenderer)
  • source/simulation2/components/ICmpWayPointRenderer.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_ICMPWAYPOINT
     19#define INCLUDED_ICMPWAYPOINT
     20
     21#include "maths/FixedVector2D.h"
     22#include "simulation2/helpers/Position.h"
     23#include "simulation2/system/Interface.h"
     24
     25/**
     26 * Way Point.
     27 * Holds the position(s) of a unit's way points, and renders them to screen.
     28 */
     29class ICmpWayPointRenderer : public IComponent
     30{
     31public:
     32
     33    /// Sets whether the way point marker and line should be displayed.
     34    virtual void SetDisplayed(bool displayed) = 0;
     35
     36    /// Sets the position at which the way point marker should be displayed.
     37    /// Discards all previous positions
     38    virtual void SetPosition(CFixedVector2D position) = 0;
     39
     40    /// Add another position at which a marker should be displayed, connected
     41    /// to the previous one.
     42    virtual void AddPosition_wrapper(CFixedVector2D position) = 0;
     43
     44    /// At a position at which a marker should be displayed, connected to
     45    /// the previous first.
     46    virtual void AddPositionFront(CFixedVector2D position) = 0;
     47
     48    /// Remove the first way point (as we have reached it)
     49    virtual void Shift() = 0;
     50
     51    DECLARE_INTERFACE_TYPE(WayPointRenderer)
     52};
     53
     54#endif // INCLUDED_ICMPWAYPOINT