Ticket #524: rallypoints_wip_11sep11.patch

File rallypoints_wip_11sep11.patch, 131.6 KB (added by vts, 13 years ago)

Small bugfix in the focus-rally button

  • binaries/data/mods/public/art/actors/props/special/common/waypoint_flag.xml

    diff --git a/binaries/data/mods/public/art/actors/props/special/common/waypoint_flag.xml b/binaries/data/mods/public/art/actors/props/special/common/waypoint_flag.xml
    index fcc167a..1ee5bdc 100644
    a b  
    1111    <variant name="waypoint_hellenes">
    1212      <texture>props/banner_greek.png</texture>
    1313    </variant>
     14    <variant name="waypoint_persians">
     15      <texture>props/banner_persian.png</texture>
     16    </variant>
     17    <variant name="waypoint_celts">
     18      <texture>props/banner_celt.png</texture>
     19    </variant>
     20    <variant name="waypoint_carthaginians">
     21      <texture>props/banner_carthage.png</texture>
     22    </variant>
     23    <variant name="waypoint_iberians">
     24      <texture>props/banner_iberians.png</texture>
     25    </variant>
     26    <variant name="waypoint_romans">
     27      <texture>props/banner_romans.png</texture>
     28    </variant>
    1429  </group>
    1530</actor>
  • new file inaries/data/mods/public/art/textures/misc/rallypoint_line.png

    diff --git a/binaries/data/mods/public/art/textures/misc/rallypoint_line.png b/binaries/data/mods/public/art/textures/misc/rallypoint_line.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..55c1100618b0fa701fe2ee22c16430987a80cc89
    GIT binary patch
    literal 2849
    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
  • new file 0

    literal 0
    HcmV?d00001
    
    diff --git a/binaries/data/mods/public/art/textures/misc/rallypoint_line_mask.png b/binaries/data/mods/public/art/textures/misc/rallypoint_line_mask.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..b1824fc318c54ca2d9b31a1e4f9acf1d3d5696db
    GIT binary patch
    literal 2826
    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
  • new file 0

    literal 0
    HcmV?d00001
    
    diff --git a/binaries/data/mods/public/art/textures/ui/session/icons/single/focus-rally.png b/binaries/data/mods/public/art/textures/ui/session/icons/single/focus-rally.png
    new file mode 100644
    index 0000000000000000000000000000000000000000..31a341a52d386d3176e14fdd96ead1ac209f2ad3
    GIT binary patch
    literal 3816
    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
  • binaries/data/mods/public/gui/session/input.js

    literal 0
    HcmV?d00001
    
    diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
    index b8c9395..b34eb18 100644
    a b function performCommand(entity, commandName)  
    11081108            case "unload-all":
    11091109                unloadAll(entity);
    11101110                break;
     1111            case "focus-rally":
     1112                // make the camera move to the building's rally point
     1113               
     1114                // grab the rally point position (if any)
     1115                var entState = Engine.GuiInterfaceCall("GetEntityState", entity);
     1116                if (entState.rallyPoint)
     1117                {
     1118                    // the rally point isn't necessarily set
     1119                    if (entState.rallyPoint.position)
     1120                    {
     1121                        Engine.CameraMoveTo(entState.rallyPoint.position.x, entState.rallyPoint.position.y);
     1122                    }
     1123                    else
     1124                    {
     1125                        // no rally point set, jump to the building itself instead (since units will now spawn right next to the
     1126                        // building)
     1127                        if (entState.position)
     1128                            Engine.CameraMoveTo(entState.position.x, entState.position.z);
     1129                    }
     1130                }
     1131               
     1132                break;
    11111133            default:
    11121134                break;
    11131135            }
  • binaries/data/mods/public/gui/session/unit_commands.js

    diff --git a/binaries/data/mods/public/gui/session/unit_commands.js b/binaries/data/mods/public/gui/session/unit_commands.js
    index eaaf6d6..9817b00 100644
    a b function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    236236                break;
    237237
    238238            case COMMAND:
    239                 if (item == "unload-all")
     239                // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single)
     240                if (item.name == "unload-all")
    240241                {
    241242                    var count = unitEntState.garrisonHolder.entities.length;
    242243                    getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 0 ? count : "");
    function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    246247                    getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = "";
    247248                }
    248249
    249                 tooltip = toTitleCase(item);
     250                tooltip = (item.tooltip ? item.tooltip : toTitleCase(item.name)); // use item.tooltip if available, otherwise default to title-cased command name
    250251                break;
    251252
    252253            default:
    function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    300301        {
    301302            //icon.cell_id = i;
    302303            //icon.cell_id = getCommandCellId(item);
    303             icon.sprite = "stretched:session/icons/single/" + getCommandImage(item);
     304            icon.sprite = "stretched:session/icons/single/" + item.icon;
    304305
    305306        }
    306307        else if (template.icon)
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    384385        var commands = getEntityCommandsList(entState);
    385386        if (commands.length)
    386387            setupUnitPanel("Command", usedPanels, entState, commands,
    387                 function (item) { performCommand(entState.id, item); } );
     388                function (item) { performCommand(entState.id, item.name); } );
    388389
    389390        if (entState.garrisonHolder)
    390391        {
  • binaries/data/mods/public/gui/session/utility_functions.js

    diff --git a/binaries/data/mods/public/gui/session/utility_functions.js b/binaries/data/mods/public/gui/session/utility_functions.js
    index 313e141..3c67a1a 100644
    a b function getFormationCellId(formationName)  
    188188    }
    189189}
    190190
    191 function getCommandImage(commandName)
    192 {
    193     switch (commandName)
    194     {
    195     case "delete":
    196         return "kill_small.png";
    197     case "unload-all":
    198         return "garrison-out.png";
    199     case "garrison":
    200         return "garrison.png";
    201     case "repair":
    202         return "repair.png";
    203     default:
    204         return "";
    205     }
    206 }
    207 
    208191function getEntityFormationsList(entState)
    209192{
    210193    var civ = g_Players[entState.player].civ;
    function getEntityCommandsList(entState)  
    233216{
    234217    var commands = [];
    235218    if (entState.garrisonHolder)
    236         commands.push("unload-all");
     219    {
     220        commands.push({
     221            "name": "unload-all",
     222            "tooltip": "Unload All",
     223            "icon": "garrison-out.png"
     224        });
     225    }
     226   
     227    commands.push({
     228        "name": "delete",
     229        "tooltip": "Delete",
     230        "icon": "kill_small.png"
     231    });
     232   
    237233    if (isUnit(entState))
    238         commands.push("garrison");
     234    {
     235        commands.push({
     236            "name": "garrison",
     237            "tooltip": "Garrison",
     238            "icon": "garrison.png"
     239        });
     240    }
     241   
    239242    if (entState.buildEntities)
    240         commands.push("repair");
    241     commands.push("delete");
     243    {
     244        commands.push({
     245            "name": "repair",
     246            "tooltip": "Repair",
     247            "icon": "repair.png"
     248        });
     249    }
     250   
     251    if(entState.rallyPoint)
     252    {
     253        commands.push({
     254            "name": "focus-rally",
     255            "tooltip": "Focus on Rally Point",
     256            "icon": "focus-rally.png"
     257        });
     258    }
     259   
    242260    return commands;
    243261}
    244262
  • binaries/data/mods/public/shaders/overlayline.fp

    diff --git a/binaries/data/mods/public/shaders/overlayline.fp b/binaries/data/mods/public/shaders/overlayline.fp
    index 368ce42..e9c123d 100644
    a b PARAM objectColor = program.local[0];  
    33TEMP base;
    44TEMP mask;
    55TEMP color;
    6 TEMP los;
     6
    77
    88// Combine base texture and color, using mask texture
    99TEX base, fragment.texcoord[0], texture[0], 2D;
    1010TEX mask, fragment.texcoord[0], texture[1], 2D;
    1111LRP color.rgb, mask, objectColor, base;
    1212
    13 // Multiply by LOS texture
    14 TEX los, fragment.texcoord[1], texture[2], 2D;
    15 MUL result.color.rgb, color, los.a;
     13#ifdef IGNORE_LOS
     14  MOV result.color.rgb, color;
     15#else
     16  // Multiply RGB by LOS texture (alpha channel)
     17  TEMP los;
     18  TEX los, fragment.texcoord[1], texture[2], 2D;
     19  MUL result.color.rgb, color, los.a;
     20#endif
    1621
    17 // Use alpha from base texture
     22// use alpha from base texture, combined with the object color alpha
     23// the latter is usually 1, so this basically comes down to base.a
    1824MUL result.color.a, objectColor.a, base.a;
    1925
     26
    2027END
  • binaries/data/mods/public/simulation/components/GarrisonHolder.js

    diff --git a/binaries/data/mods/public/simulation/components/GarrisonHolder.js b/binaries/data/mods/public/simulation/components/GarrisonHolder.js
    index b128529..bba6725 100644
    a b GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities)  
    183183{
    184184    var cmpOwnership = Engine.QueryInterface(this.entity, IID_Ownership);
    185185    var cmpRallyPoint = Engine.QueryInterface(this.entity, IID_RallyPoint);
    186     if (cmpRallyPoint)
     186    if (cmpRallyPoint && cmpRallyPoint.IsSet())
    187187    {
    188188        var rallyPos = cmpRallyPoint.GetPosition();
    189189        if (rallyPos)
    GarrisonHolder.prototype.OrderWalkToRallyPoint = function(entities)  
    192192                "type": "walk",
    193193                "entities": entities,
    194194                "x": rallyPos.x,
    195                 "z": rallyPos.z,
     195                "z": rallyPos.y,
    196196                "queued": false
    197197            });
    198198        }
  • binaries/data/mods/public/simulation/components/GuiInterface.js

    diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
    index abd7e1e..dc32d2b 100644
    a b GuiInterface.prototype.GetEntityState = function(player, ent)  
    222222    if (cmpRallyPoint)
    223223    {
    224224        ret.rallyPoint = { };
     225        if (cmpRallyPoint.IsSet()) // can't return a rally point position if none is set ...
     226            ret.rallyPoint.position = cmpRallyPoint.GetPosition();
    225227    }
    226228
    227229    var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
    GuiInterface.prototype.SetStatusBars = function(player, cmd)  
    379381 */
    380382GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
    381383{
    382     // If there are rally points already displayed, destroy them
    383     for each (var ent in this.rallyPoints)
     384    // If there are some rally points already displayed, first hide them
     385    for each (var ent in this.entsRallyPointsDisplayed)
    384386    {
    385         // Hide it first (the destruction won't be instantaneous)
    386         var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    387         cmpPosition.MoveOutOfWorld();
     387        var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
     388        if (!cmpRallyPoint)
     389            continue;
    388390
    389         Engine.DestroyEntity(ent);
     391        cmpRallyPoint.SetDisplayed(false);
    390392    }
    391393
    392     this.rallyPoints = [];
    393 
    394     var positions = [];
    395     // DisplayRallyPoints is called passing a list of entities for which
    396     // rally points must be displayed
     394    // Show the rally points for the passed entities
    397395    for each (var ent in cmd.entities)
    398396    {
    399397        var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
    GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)  
    405403        if (!cmpOwnership || cmpOwnership.GetOwner() != player)
    406404            continue;
    407405
    408         // If the command was passed an explicit position, use that and
    409         // override the real rally point position; otherwise use the real position
    410         var pos;
     406        // If the command was passed an explicit position, first set it
    411407        if (cmd.x && cmd.z)
    412             pos = {"x": cmd.x, "z": cmd.z};
    413         else
    414             pos = cmpRallyPoint.GetPosition();
     408            cmpRallyPoint.SetPosition({x: cmd.x, y:cmd.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
    415409
    416         if (pos)
    417         {
    418             // TODO: it'd probably be nice if we could draw some kind of line
    419             // between the building and pos, to make the marker easy to find even
    420             // if it's a long way from the building
     410        cmpRallyPoint.SetDisplayed(true);
    421411
    422             positions.push(pos);
    423         }
    424412    }
    425413
    426     // Add rally point entity for each building
    427     for each (var pos in positions)
    428     {
    429         var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml");
    430         var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position);
    431         cmpPosition.JumpTo(pos.x, pos.z);
    432         this.rallyPoints.push(rallyPoint);
    433     }   
     414    // Remember which entities have their rally points displayed so we can hide them again
     415    this.entsRallyPointsDisplayed = cmd.entities;
     416   
    434417};
    435418
    436419/**
  • deleted file binaries/data/mods/public/simulation/components/RallyPoint.js

    diff --git a/binaries/data/mods/public/simulation/components/RallyPoint.js b/binaries/data/mods/public/simulation/components/RallyPoint.js
    deleted file mode 100644
    index 3c3816a..0000000
    + -  
    1 function RallyPoint() {}
    2 
    3 RallyPoint.prototype.Schema =
    4     "<a:component/><empty/>";
    5 
    6 RallyPoint.prototype.Init = function()
    7 {
    8     this.pos = undefined;
    9 };
    10 
    11 RallyPoint.prototype.SetPosition = function(x, z)
    12 {
    13     this.pos = {
    14         "x": x,
    15         "z": z
    16     }
    17 };
    18 
    19 RallyPoint.prototype.Unset = function()
    20 {
    21     this.pos = undefined;
    22 };
    23 
    24 RallyPoint.prototype.GetPosition = function()
    25 {
    26     return this.pos;
    27 };
    28 
    29 Engine.RegisterComponentType(IID_RallyPoint, "RallyPoint", RallyPoint);
  • binaries/data/mods/public/simulation/components/TrainingQueue.js

    diff --git a/binaries/data/mods/public/simulation/components/TrainingQueue.js b/binaries/data/mods/public/simulation/components/TrainingQueue.js
    index 87f74a9..3bbad11 100644
    a b TrainingQueue.prototype.SpawnUnits = function(templateName, count, metadata)  
    281281    if (spawnedEnts.length > 0)
    282282    {
    283283        // If a rally point is set, walk towards it (in formation)
    284         if (cmpRallyPoint)
     284        if (cmpRallyPoint && cmpRallyPoint.IsSet())
    285285        {
    286286            var rallyPos = cmpRallyPoint.GetPosition();
    287287            if (rallyPos)
    TrainingQueue.prototype.SpawnUnits = function(templateName, count, metadata)  
    290290                    "type": "walk",
    291291                    "entities": spawnedEnts,
    292292                    "x": rallyPos.x,
    293                     "z": rallyPos.z,
     293                    "z": rallyPos.y,
    294294                    "queued": false
    295295                });
    296296            }
  • deleted file binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js

    diff --git a/binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js b/binaries/data/mods/public/simulation/components/interfaces/RallyPoint.js
    deleted file mode 100644
    index ac0f079..0000000
    + -  
    1 Engine.RegisterInterface("RallyPoint");
  • binaries/data/mods/public/simulation/helpers/Commands.js

    diff --git a/binaries/data/mods/public/simulation/helpers/Commands.js b/binaries/data/mods/public/simulation/helpers/Commands.js
    index 57dc2b9..a344461 100644
    a b function ProcessCommand(player, cmd)  
    229229        {
    230230            var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
    231231            if (cmpRallyPoint)
    232                 cmpRallyPoint.SetPosition(cmd.x, cmd.z);
     232                cmpRallyPoint.SetPosition({x:cmd.x, y:cmd.z}); // SetPosition takes a CFixedVector2D which has X/Y components instead of X/Z
    233233        }
    234234        break;
    235235
  • new file inaries/data/mods/public/simulation/templates/special/rallypoint.xml

    diff --git a/binaries/data/mods/public/simulation/templates/special/rallypoint.xml b/binaries/data/mods/public/simulation/templates/special/rallypoint.xml
    new file mode 100644
    index 0000000..c73b5d8
    - +  
     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_structure.xml

    diff --git a/binaries/data/mods/public/simulation/templates/template_structure.xml b/binaries/data/mods/public/simulation/templates/template_structure.xml
    index a346fd9..8de5ade 100644
    a b  
    5353    <DisableBlockPathfinding>false</DisableBlockPathfinding>
    5454  </Obstruction>
    5555  <OverlayRenderer/>
    56   <RallyPoint/>
     56  <RallyPoint>
     57    <MarkerTemplate>special/rallypoint</MarkerTemplate>
     58    <LineThickness>0.2</LineThickness>
     59    <LineColour r="35" g="86" b="188"></LineColour>
     60    <LineDashColour r="158" g="11" b="15"></LineDashColour>
     61    <LineStartCap>square</LineStartCap>
     62    <LineEndCap>round</LineEndCap>
     63  </RallyPoint>
    5764  <Sound>
    5865    <SoundGroups>
    5966      <select>interface/select/building/sel_universal.xml</select>
  • new file source/graphics/Overlay.cpp

    diff --git a/source/graphics/Overlay.cpp b/source/graphics/Overlay.cpp
    new file mode 100644
    index 0000000..7db8ab6
    - +  
     1/* Copyright (C) 2011 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 "Overlay.h"
     21
     22bool SOverlayTexturedLine::StrToLineCap(const std::wstring& str, SOverlayTexturedLine::LineCap& out)
     23{
     24    if (str == L"flat")
     25    {
     26        out = LINECAP_FLAT;
     27        return true;
     28    }
     29    else if (str == L"round")
     30    {
     31        out = LINECAP_ROUND;
     32        return true;
     33    }
     34    else if (str == L"sharp")
     35    {
     36        out = LINECAP_SHARP;
     37        return true;
     38    }
     39    else if (str == L"square")
     40    {
     41        out = LINECAP_SQUARE;
     42        return true;
     43    }
     44    else
     45        return false;
     46}
  • source/graphics/Overlay.h

    diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h
    index e07b13b..871f7a6 100644
    a b struct SOverlayLine  
    4444 */
    4545struct SOverlayTexturedLine
    4646{
    47     SOverlayTexturedLine() : m_Terrain(NULL), m_Thickness(1.0f) { }
     47
     48    enum LineCap
     49    {
     50        LINECAP_FLAT, ///< no line ending; abrupt stop of the line (aka. butt ending)
     51
     52        /**
     53         * Semi-circular line ending. The texture is mapped by curving the left vertical edge around the semi-circle's rim. That is,
     54         * the center point has UV coordinates (0.5;0.5), and the rim vertices all have U coordinate 0 and a V coordinate that ranges
     55         * from 0 to 1 as the rim is traversed.
     56         */
     57        LINECAP_ROUND,
     58        LINECAP_SHARP, ///< sharp point ending
     59        LINECAP_SQUARE, ///< square end that extends half the line width beyond the line end
     60    };
     61
     62    SOverlayTexturedLine()
     63        : m_Terrain(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), m_StartCap(LINECAP_FLAT), m_EndCap(LINECAP_FLAT)
     64    {}
    4865
    4966    CTerrain* m_Terrain;
    5067    CTexturePtr m_TextureBase;
    5168    CTexturePtr m_TextureMask;
    52     CColor m_Color;
    53     std::vector<float> m_Coords; // (x, z) vertex coordinate pairs; y is computed automatically; shape is automatically closed
    54     float m_Thickness; // world-space units
     69    CColor m_Color; // color to apply to the line texture
     70    std::vector<float> m_Coords; // (x, z) vertex coordinate pairs; y is computed automatically
     71    float m_Thickness; // half-width of the line, in world-space units
     72
     73    bool m_Closed; // should this line be treated as a closed loop? (if set, the end cap settings are ignored)
     74    bool m_AlwaysVisible; // should this line be rendered even under the SoD?
     75    LineCap m_StartCap; // LineEndCap to be used at the start of the line
     76    LineCap m_EndCap; // LineEndCap to be used at the end of the line
    5577
    5678    shared_ptr<CRenderData> m_RenderData; // cached renderer data (shared_ptr so that copies/deletes are automatic)
     79
     80    /**
     81     * Interprets @p str as a LineCap value, and writes the matching value to @p out, if any. Returns true if a value was written to @p out,
     82     * false otherwise.
     83     */
     84    static bool StrToLineCap(const std::wstring& str, LineCap& out);
     85
    5786};
    5887
    5988/**
  • source/graphics/ShaderManager.cpp

    diff --git a/source/graphics/ShaderManager.cpp b/source/graphics/ShaderManager.cpp
    index ad3080c..942cd91 100644
    a b bool CShaderManager::NewProgram(const char* name, const std::map<CStr, CStr>& ba  
    7676{
    7777    if (strncmp(name, "fixed:", 6) == 0)
    7878    {
    79         program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6));
     79        program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6, baseDefines));
    8080        if (!program)
    8181            return false;
    8282        program->Reload();
  • source/graphics/ShaderProgram.h

    diff --git a/source/graphics/ShaderProgram.h b/source/graphics/ShaderProgram.h
    index 773096f..4d4516c 100644
    a b public:  
    7171    /**
    7272     * Construct an instance of a pre-defined fixed-function pipeline setup.
    7373     */
    74     static CShaderProgram* ConstructFFP(const std::string& id);
     74    static CShaderProgram* ConstructFFP(const std::string& id, const std::map<CStr, CStr>& defines);
    7575
    7676    typedef const char* attrib_id_t;
    7777    typedef const char* texture_id_t;
  • source/graphics/ShaderProgramFFP.cpp

    diff --git a/source/graphics/ShaderProgramFFP.cpp b/source/graphics/ShaderProgramFFP.cpp
    index b7d0ae3..6a5cb66 100644
    a b class CShaderProgramFFP_OverlayLine : public CShaderProgramFFP  
    100100        ID_objectColor
    101101    };
    102102
     103    std::map<CStr, CStr> m_Defines;
     104
     105    bool IsIgnoreLos()
     106    {
     107        std::map<CStr, CStr>::const_iterator define = m_Defines.find(CStr("IGNORE_LOS"));
     108        return (define != m_Defines.end() && define->second == CStr("1"));
     109    }
     110
    103111public:
    104     CShaderProgramFFP_OverlayLine() :
    105         CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1)
     112    CShaderProgramFFP_OverlayLine(const std::map<CStr, CStr>& defines) :
     113        CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1),
     114        m_Defines(defines)
    106115    {
    107116        m_UniformIndexes["losTransform"] = ID_losTransform;
    108117        m_UniformIndexes["objectColor"] = ID_objectColor;
    public:  
    145154        // RGB channels:
    146155        //   Unit 0: Load base texture
    147156        //   Unit 1: Load mask texture; interpolate with objectColor & base
    148         //   Unit 2: Load LOS texture; multiply
     157        //   Unit 2: (Load LOS texture; multiply) if not #IGNORE_LOS, pass through otherwise
    149158        // Alpha channel:
    150159        //   Unit 0: Load base texture
    151160        //   Unit 1: Multiply by objectColor
    public:  
    163172        glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE);
    164173        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
    165174
     175        // -----------------------------------------------------------------------------
     176
    166177        pglActiveTextureARB(GL_TEXTURE1);
    167178        glEnable(GL_TEXTURE_2D);
    168 
    169179        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
    170180        // Uniform() sets GL_TEXTURE_ENV_COLOR
    171181
     182        // load mask texture; interpolate with objectColor and base; GL_INTERPOLATE takes 3 arguments:
     183        // a0 * a2 + a1 * (1 - a2)
    172184        glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE);
    173185        glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT);
    174186        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
    public:  
    183195        glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS);
    184196        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA);
    185197
     198        // -----------------------------------------------------------------------------
     199
    186200        pglActiveTextureARB(GL_TEXTURE2);
    187201        glEnable(GL_TEXTURE_2D);
    188 
    189202        glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
    190203
     204        bool ignoreLos = IsIgnoreLos();
     205        if (ignoreLos)
     206        {
     207            // RGB pass through
     208            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE);
     209            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
     210            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
     211        }
     212        else
     213        {
     214            // multiply RGB with LoS texture alpha channel
    191215        glEnable(GL_TEXTURE_GEN_S);
    192216        glEnable(GL_TEXTURE_GEN_T);
    193217        glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    public:  
    199223        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
    200224        glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
    201225        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA);
     226        }
    202227
     228        // alpha pass through
    203229        glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE);
    204230        glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_PREVIOUS);
    205231        glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA);
     232       
    206233    }
    207234
    208235    virtual void Unbind()
    public:  
    210237        pglActiveTextureARB(GL_TEXTURE2);
    211238        glDisable(GL_TEXTURE_2D);
    212239
     240        // technically these aren't enabled if ignore_los is true, but the previous behaviour before ignore_los was added
     241        // was to always disable it, so not doing the if-check for ignore_los here shouldn't break anything
    213242        glDisable(GL_TEXTURE_GEN_S);
    214243        glDisable(GL_TEXTURE_GEN_T);
    215244
    public:  
    221250    }
    222251};
    223252
    224 /*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id)
     253/*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id, const std::map<CStr, CStr>& defines)
    225254{
    226255    if (id == "overlayline")
    227         return new CShaderProgramFFP_OverlayLine();
     256        return new CShaderProgramFFP_OverlayLine(defines);
    228257
    229258    LOGERROR(L"CShaderProgram::ConstructFFP: Invalid id '%hs'", id.c_str());
    230259    return NULL;
  • source/gui/scripting/ScriptFunctions.cpp

    diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
    index 0054246..6dc6d82 100644
    a b  
    3434#include "ps/CConsole.h"
    3535#include "ps/Errors.h"
    3636#include "ps/Game.h"
     37#include "ps/World.h"
    3738#include "ps/Hotkey.h"
    3839#include "ps/Overlay.h"
    3940#include "ps/ProfileViewer.h"
    void CameraFollowFPS(void* UNUSED(cbdata), entity_id_t entityid)  
    346347        g_Game->GetView()->CameraFollow(entityid, true);
    347348}
    348349
     350/// Move camera to a 2D location
     351void CameraMoveTo(void* UNUSED(cbdata), entity_pos_t x, entity_pos_t z)
     352{
     353    // this must not fail
     354    if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
     355        return;
     356
     357    CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
     358
     359    CVector3D target;
     360    target.X = x.ToFloat();
     361    target.Z = z.ToFloat();
     362    target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
     363
     364    // move the camera in minimap mode since this is an "instant" camera movement similar to what
     365    // you would get for clicking the minimap (also prevents height glitches)
     366    g_Game->GetView()->MoveCameraTarget(target, true);
     367}
     368
    349369entity_id_t GetFollowedEntity(void* UNUSED(cbdata))
    350370{
    351371    if (g_Game && g_Game->GetView())
    void GuiScriptingInit(ScriptInterface& scriptInterface)  
    499519    scriptInterface.RegisterFunction<CScriptVal, &GetMapSettings>("GetMapSettings");
    500520    scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollow>("CameraFollow");
    501521    scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollowFPS>("CameraFollowFPS");
     522    scriptInterface.RegisterFunction<void, entity_pos_t, entity_pos_t, &CameraMoveTo>("CameraMoveTo");
    502523    scriptInterface.RegisterFunction<entity_id_t, &GetFollowedEntity>("GetFollowedEntity");
    503524    scriptInterface.RegisterFunction<bool, std::string, &HotkeyIsPressed_>("HotkeyIsPressed");
    504525    scriptInterface.RegisterFunction<void, std::wstring, &DisplayErrorDialog>("DisplayErrorDialog");
  • source/maths/Vector2D.h

    diff --git a/source/maths/Vector2D.h b/source/maths/Vector2D.h
    index c61d31b..38a26a4 100644
    a b public:  
    127127        return CVector2D(X / mag, Y / mag);
    128128    }
    129129
     130    /**
     131     * Returns a version of this vector rotated counterclockwise by @p angle radians.
     132     */
     133    CVector2D Rotated(float angle)
     134    {
     135        float c = cosf(angle);
     136        float s = sinf(angle);
     137        return CVector2D(
     138            c*X - s*Y,
     139            s*X + c*Y
     140        );
     141    }
     142
     143    /**
     144     * Rotates this vector counterclockwise by @p angle radians.
     145     */
     146    void Rotate(float angle)
     147    {
     148        float c = cosf(angle);
     149        float s = sinf(angle);
     150        float newX = c*X - s*Y;
     151        float newY = s*X + c*Y;
     152        X = newX;
     153        Y = newY;
     154    }
     155
    130156public:
    131157    float X, Y;
    132158};
  • source/renderer/OverlayRenderer.cpp

    diff --git a/source/renderer/OverlayRenderer.cpp b/source/renderer/OverlayRenderer.cpp
    index a8cd900..0a44e32 100644
    a b  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2011 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
     
    1919
    2020#include "OverlayRenderer.h"
    2121
     22#include "maths/MathUtil.h"
    2223#include "graphics/LOSTexture.h"
    2324#include "graphics/Overlay.h"
    24 #include "graphics/ShaderManager.h"
    2525#include "graphics/Terrain.h"
    2626#include "graphics/TextureManager.h"
    2727#include "lib/ogl.h"
     28#include "maths/Vector2D.h"
     29#include "maths/Quaternion.h"
    2830#include "ps/Game.h"
    2931#include "ps/Profile.h"
    3032#include "renderer/Renderer.h"
    class CTexturedLineRData : public CRenderData  
    4446{
    4547public:
    4648    CTexturedLineRData(SOverlayTexturedLine* line) :
    47         m_Line(line), m_VB(NULL), m_VBIndices(NULL)
     49        m_Line(line),
     50        m_VB(NULL),
     51        m_VBIndices(NULL),
     52        m_LineIndexCount(0),
     53        m_StartCapIndexCount(0),
     54        m_StartCapIndexOffset(0),
     55        m_EndCapIndexCount(0),
     56        m_EndCapIndexOffset(0),
     57        m_Raise(.2f)
    4858    {
    4959    }
    5060
    public:  
    5868
    5969    struct SVertex
    6070    {
    61         SVertex(CVector3D pos, short u, short v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
     71        SVertex(CVector3D pos, float u, float v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
    6272        CVector3D m_Position;
    63         GLshort m_UVs[2];
     73        GLfloat m_UVs[2];
     74        float _padding[3]; // 5 floats up till now, so pad with another 3 floats to get a power of 2
    6475    };
    65     cassert(sizeof(SVertex) == 16);
     76    cassert(sizeof(SVertex) == 32);
    6677
    6778    void Update();
    6879
     80    /**
     81     * Creates a line cap of the specified type @p endCapType at the end of the segment going in direction @p normal, and write the vertices and indices
     82     * to @p verticesOut and @p indicesOut, respectively. The drawing mode to use for the resulting vertices/indices array (quad strip,
     83     * triangle strip, ...) is written to @p drawModeOut.
     84     *
     85     * @param corner1 One of the two butt-end corner points of the line to which the cap should be attached.
     86     * @param corner2 One of the two butt-end corner points of the line to which the cap should be attached.
     87     * @param normal Normal vector indicating the direction of the segment to which the cap should be attached.
     88     * @param endCapType The type of end cap to produce.
     89     * @param verticesOut Output vector of vertices to draw using @p drawModeOut. These will be entered into a vertex buffer and handed to the
     90     *                    renderer along with the indices array.
     91     * @param indicesOut Output vector of vertex indices to draw using @p drawModeOut. These will be entered into a vertex index buffer and
     92     *                   handed to the rendered along with the vertex array.
     93     */
     94    void CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal, SOverlayTexturedLine::LineCap endCapType,
     95                       std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut);
     96
     97    /// Small utility function; grabs the centroid of the positions of two vertices
     98    inline CVector3D Centroid(const SVertex& v1, const SVertex& v2)
     99    {
     100        return (v1.m_Position + v2.m_Position) * 0.5;
     101    }
     102
    69103    SOverlayTexturedLine* m_Line;
    70104
    71105    CVertexBuffer::VBChunk* m_VB;
    72106    CVertexBuffer::VBChunk* m_VBIndices;
     107    unsigned int m_LineIndexCount;
     108    unsigned int m_StartCapIndexOffset; // offset of start cap indices in the VB; valid iff > 0
     109    unsigned int m_StartCapIndexCount; // number of start cap indices in the VB; valid iff > 0
     110    unsigned int m_EndCapIndexOffset; // offset of end cap indices in the VB; valid iff > 0
     111    unsigned int m_EndCapIndexCount; // number of end cap indices in the VB; valid iff > 0
     112
     113    float m_Raise; // small vertical offset of line from terrain to prevent visual glitches
     114
    73115};
    74116
    75117OverlayRenderer::OverlayRenderer()
    void OverlayRenderer::RenderOverlaysBeforeWater()  
    164206    glDisable(GL_BLEND);
    165207}
    166208
    167 void OverlayRenderer::RenderOverlaysAfterWater()
     209void OverlayRenderer::RenderTexturedOverlayLines(const std::vector<size_t>& indices, CShaderProgramPtr shaderTexLine)
    168210{
    169     PROFILE("render overlays (after water)");
    170 
    171     if (!m->texlines.empty())
    172     {
    173         glEnable(GL_TEXTURE_2D);
    174         glEnable(GL_BLEND);
    175         glDepthMask(0);
    176 
    177         const char* shaderName;
    178         if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
    179             shaderName = "overlayline";
    180         else
    181             shaderName = "fixed:overlayline";
    182 
    183         CShaderManager& shaderManager = g_Renderer.GetShaderManager();
    184         CShaderProgramPtr shaderTexLine(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>()));
    185 
    186         shaderTexLine->Bind();
    187 
    188211        int streamflags = shaderTexLine->GetStreamFlags();
    189212
    190213        if (streamflags & STREAM_POS)
    void OverlayRenderer::RenderOverlaysAfterWater()  
    202225            glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    203226        }
    204227
    205         CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
    206         shaderTexLine->BindTexture("losTex", los.GetTexture());
    207         shaderTexLine->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
    208 
    209         for (size_t i = 0; i < m->texlines.size(); ++i)
     228    //for (size_t i = 0; i < m->texlines.size(); ++i)
     229    for (std::vector<size_t>::const_iterator idxIt = indices.begin(); idxIt != indices.end(); idxIt++)
    210230        {
    211             SOverlayTexturedLine* line = m->texlines[i];
     231        SOverlayTexturedLine* line = m->texlines[(*idxIt)];
    212232            if (!line->m_RenderData)
    213233                continue;
    214234
    void OverlayRenderer::RenderOverlaysAfterWater()  
    218238
    219239            CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get());
    220240
     241        // -- render main line quad strip ----------------------
     242
    221243            GLsizei stride = sizeof(CTexturedLineRData::SVertex);
    222             CTexturedLineRData::SVertex* base = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());
     244        CTexturedLineRData::SVertex* vertexBase = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());
    223245
    224246            if (streamflags & STREAM_POS)
    225                 glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]);
     247            glVertexPointer(3, GL_FLOAT, stride, &vertexBase->m_Position[0]);
    226248
    227249            if (streamflags & STREAM_UV0)
    228250            {
    229251                pglClientActiveTextureARB(GL_TEXTURE0);
    230                 glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);
     252            glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]);
    231253            }
    232254
    233255            if (streamflags & STREAM_UV1)
    234256            {
    235257                pglClientActiveTextureARB(GL_TEXTURE1);
    236                 glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);
     258            glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]);
    237259            }
    238260
     261
    239262            u8* indexBase = rdata->m_VBIndices->m_Owner->Bind();
    240             glDrawElements(GL_QUAD_STRIP, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index);
     263        glDrawElements(GL_QUAD_STRIP, rdata->m_LineIndexCount, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index);
     264
     265        g_Renderer.GetStats().m_OverlayTris += rdata->m_LineIndexCount - 2;
    241266
    242             g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count - 2;
     267        // -- render start cap (if any) ------------------------
     268
     269        if (rdata->m_StartCapIndexCount > 0)
     270        {
     271            glDrawElements(GL_TRIANGLES, rdata->m_StartCapIndexCount, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(rdata->m_VBIndices->m_Index + rdata->m_StartCapIndexOffset));
     272            g_Renderer.GetStats().m_OverlayTris += rdata->m_StartCapIndexCount/3;
    243273        }
    244274
    245         shaderTexLine->Unbind();
     275        // -- render end cap (if any) --------------------------
     276
     277        if (rdata->m_EndCapIndexCount > 0)
     278        {
     279            glDrawElements(GL_TRIANGLES, rdata->m_EndCapIndexCount, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*(rdata->m_VBIndices->m_Index + rdata->m_EndCapIndexOffset));
     280            g_Renderer.GetStats().m_OverlayTris += rdata->m_EndCapIndexCount/3;
     281        }
     282
     283    }
     284
     285}
     286
     287void OverlayRenderer::RenderOverlaysAfterWater()
     288{
     289    PROFILE("render overlays (after water)");
     290
     291    if (!m->texlines.empty())
     292    {
     293        glEnable(GL_TEXTURE_2D);
     294        glEnable(GL_BLEND);
     295        glDepthMask(0);
     296
     297        const char* shaderName;
     298        if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
     299            shaderName = "overlayline";
     300        else
     301            shaderName = "fixed:overlayline";
     302
     303        std::map<CStr, CStr> defAlwaysVisible;
     304        defAlwaysVisible.insert(std::make_pair(CStr("IGNORE_LOS"), CStr("1")));
     305
     306        CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
     307
     308        CShaderManager& shaderManager = g_Renderer.GetShaderManager();
     309        CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>()));
     310        CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, defAlwaysVisible));
     311
     312        // ----------------------------------------------------------------------------------------
     313        // group the overlay lines by whether they need to be always visible
     314        std::vector<size_t> alwaysVisibleTexLineIndices;
     315        std::vector<size_t> normalTexLineIndices;
     316
     317        for (size_t i = 0; i < m->texlines.size(); i++)
     318        {
     319            if (m->texlines[i]->m_AlwaysVisible)
     320                alwaysVisibleTexLineIndices.push_back(i);
     321            else
     322                normalTexLineIndices.push_back(i);
     323        }
     324
     325        // render go go
     326        shaderTexLineNormal->Bind();
     327        shaderTexLineNormal->BindTexture("losTex", los.GetTexture());
     328        shaderTexLineNormal->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
     329
     330        RenderTexturedOverlayLines(normalTexLineIndices, shaderTexLineNormal);
     331
     332        shaderTexLineNormal->Unbind();
     333
     334        // ----------------------------------------------------------------------------------------
     335
     336        shaderTexLineAlwaysVisible->Bind();
     337        // TODO: losTex and losTransform are unused in the always visible shader, but I'm not sure if it's worthwhile messing
     338        // with it just to remove these calls
     339        shaderTexLineAlwaysVisible->BindTexture("losTex", los.GetTexture());
     340        shaderTexLineAlwaysVisible->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
     341
     342        RenderTexturedOverlayLines(alwaysVisibleTexLineIndices, shaderTexLineAlwaysVisible);
     343
     344        shaderTexLineAlwaysVisible->Unbind();
    246345
    247346        // TODO: the shader should probably be responsible for unbinding its textures
    248347        g_Renderer.BindTexture(1, 0);
    void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)  
    293392        };
    294393
    295394        glVertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X);
    296 
    297395        glDrawArrays(GL_QUADS, 0, (GLsizei)4);
    298396    }
    299397
    void CTexturedLineRData::Update()  
    324422    std::vector<SVertex> vertices;
    325423    std::vector<u16> indices;
    326424
    327     short v = 0;
     425    float v = 0.f;
    328426
    329     size_t n = m_Line->m_Coords.size() / 2;
    330     ENSURE(n >= 1);
     427    size_t n = m_Line->m_Coords.size() / 2; // number of line points
     428    bool closed = m_Line->m_Closed;
    331429
    332     CTerrain* terrain = m_Line->m_Terrain;
     430    if (closed)
     431        ENSURE(n >= 1); // granted, it's kind of hard to draw a closed loop through 1 point, but this is the minimum to avoid errors
     432    else
     433        ENSURE(n >= 2);
    333434
    334     // TODO: this assumes paths are closed loops; probably should extend this to
    335     // handle non-closed paths too
     435    CTerrain* terrain = m_Line->m_Terrain;
    336436
    337437    // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1.
    338438    // To avoid slightly expensive terrain computations we cycle these around and
    339439    // recompute p2 at the end of each iteration.
    340     CVector3D p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]);
    341     CVector3D p1 = CVector3D(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]);
    342     CVector3D p2 = CVector3D(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]);
     440
     441    CVector3D p0;
     442    CVector3D p1(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]);
     443    CVector3D p2(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]);
     444
     445    if (closed)
     446        // grab the ending point so as to close the loop
     447        p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]);
     448    else
     449        // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that
     450        // extends the p2 -> p1 direction, and use that point instead
     451        p0 = p1 + (p1 - p2);
     452
    343453    bool p1floating = false;
    344454    bool p2floating = false;
    345455
    void CTexturedLineRData::Update()  
    371481    {
    372482        // For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1)
    373483        // perpendicular to terrain normal
     484        // Push vertices in GL_QUAD_STRIP order
    374485
    375486        // Normal is vertical if on water, else computed from terrain
    376487        CVector3D norm;
    void CTexturedLineRData::Update()  
    386497        if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero
    387498            b *= m_Line->m_Thickness / l;
    388499
    389         // Raise off the terrain a little bit
    390         const float raised = 0.2f;
    391 
    392         vertices.push_back(SVertex(p1 + b + norm*raised, 0, v));
     500        vertices.push_back(SVertex(p1 + b + norm*m_Raise, 0.f, v));
    393501        indices.push_back(vertices.size() - 1);
    394502
    395         vertices.push_back(SVertex(p1 - b + norm*raised, 1, v));
     503        vertices.push_back(SVertex(p1 - b + norm*m_Raise, 1.f, v));
    396504        indices.push_back(vertices.size() - 1);
    397505
    398506        // Alternate V coordinate for debugging
    void CTexturedLineRData::Update()  
    402510        p0 = p1;
    403511        p1 = p2;
    404512        p1floating = p2floating;
     513
     514        // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly
     515        if (!closed && i == n-2)
     516            // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction
     517            p2 = p1 + (p1 - p0);
     518        else
    405519        p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]);
     520
    406521        p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z);
    407522        if (p2.Y < w)
    408523        {
    void CTexturedLineRData::Update()  
    413528            p2floating = false;
    414529    }
    415530
     531   
     532    if (closed)
     533    {
    416534    // Close the path
    417535    indices.push_back(0);
    418536    indices.push_back(1);
     537    }
     538
     539    m_LineIndexCount = indices.size();
     540
     541    if (!closed)
     542    {
     543        // -------------------------------------------------------------------
     544        // create start cap
     545
     546        std::vector<u16> startCapIndices;
     547        std::vector<SVertex> startCapVertices;
     548
     549        CreateLineCap(
     550            vertices[1].m_Position, // the order of these vertices is actually important here, swapping them produces caps at the wrong side
     551            vertices[0].m_Position,
     552            (Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(),
     553            m_Line->m_StartCap,
     554            startCapVertices,
     555            startCapIndices
     556        );
     557
     558        if (!startCapVertices.empty() && !startCapIndices.empty())
     559        {
     560            ENSURE(startCapIndices.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3
     561
     562            m_StartCapIndexOffset = indices.size(); // start cap indices start where the line indices end
     563            m_StartCapIndexCount = startCapIndices.size();
     564
     565            // adjust the indices to take the existing line vertices into account (right now they're relative to their own vertices array)
     566            for (size_t k = 0; k < m_StartCapIndexCount; ++k)
     567                startCapIndices[k] += vertices.size();
     568
     569            indices.insert(indices.end(), startCapIndices.begin(), startCapIndices.end()); // append start cap indices to VBO data
     570            vertices.insert(vertices.end(), startCapVertices.begin(), startCapVertices.end()); // append start cap vertices to VBO data
     571        }
     572
     573        // -------------------------------------------------------------------
     574        // create end cap
     575
     576        std::vector<u16> endcapIndices;
     577        std::vector<SVertex> endcapVertices;
     578
     579        CreateLineCap(
     580            vertices[2*n-2].m_Position, // second-to-last line vertex
     581            vertices[2*n-1].m_Position, // last line vertex
     582            (Centroid(vertices[2*n-2], vertices[2*n-1]) - Centroid(vertices[2*n-4], vertices[2*n-3])).Normalized(),
     583            m_Line->m_EndCap,
     584            endcapVertices,
     585            endcapIndices
     586        );
     587
     588        if (!endcapVertices.empty() && !endcapIndices.empty())
     589        {
     590            ENSURE(endcapIndices.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3
     591
     592            m_EndCapIndexOffset = indices.size(); // end cap indices start where the line + start indices end
     593            m_EndCapIndexCount = endcapIndices.size();
     594
     595            // adjust the indices to take the existing line + start vertices into account (right now they're relative to their own vertices array)
     596            for (size_t k = 0; k < m_EndCapIndexCount; ++k)
     597                endcapIndices[k] += vertices.size();
     598
     599            indices.insert(indices.end(), endcapIndices.begin(), endcapIndices.end()); // append end cap indices to VBO data
     600            vertices.insert(vertices.end(), endcapVertices.begin(), endcapVertices.end()); // append end cap vertices to VBO data
     601        }
     602
     603    }
    419604
    420605    m_VB = g_VBMan.Allocate(sizeof(SVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER);
    421     m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]);
     606    m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]); // copy data into VBO
    422607
    423608    // Update the indices to include the base offset of the vertex data
    424609    for (size_t k = 0; k < indices.size(); ++k)
    425610        indices[k] += m_VB->m_Index;
    426611
    427612    m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
    428     m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]);
     613    m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]); // copy data into VBO
     614}
     615
     616void CTexturedLineRData::CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal, SOverlayTexturedLine::LineCap endCapType,
     617                                       std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut)
     618{
     619
     620    if (endCapType == SOverlayTexturedLine::LINECAP_FLAT)
     621        return; // no action needed, this is the default
     622
     623    CTerrain* terrain = m_Line->m_Terrain;
     624    CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
     625
     626    // when not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the
     627    // direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular
     628    // butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction.
     629    // That is to say, we will have something like
     630    //                                                 .
     631    //  this:                     and not like this:  /|
     632    //         ____.                                 / |
     633    //             |                                /  .
     634    //             |                                  /
     635    //         ____.                                 /
     636    //
     637
     638    int roundCapPoints = 8; // = how many points to sample along the semicircle for rounded caps (including corner points)
     639    float texCenterU = 0.5f; // U coordinate of butt end centroid
     640    float texCenterV = 0.5f; // V coordinate of butt end centroid
     641
     642    float radius = m_Line->m_Thickness;
     643    CVector3D centerPoint = (corner1 + corner2) * 0.5f; // middle between last two vertices (i.e. "butt" corner points)
     644
     645    switch (endCapType)
     646    {
     647
     648    case SOverlayTexturedLine::LINECAP_SHARP:
     649        {
     650            roundCapPoints = 3; // creates only one point directly ahead
     651            radius *= 1.2f; // make it a bit sharper (note that we don't use this for the butt corner points so it should be ok)
     652            texCenterU = 0.485f; // slight visual correction to make the texture edges match up better (they're slightly too thin with only 3 points and a U coord of 0.5f)
     653        }
     654    case SOverlayTexturedLine::LINECAP_ROUND:
     655        {
     656           
     657            // Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the
     658            // line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane.
     659            // The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then of
     660            // radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in the line's
     661            // plane, producing the desired rounded cap.
     662
     663            // To please OpenGL's winding order thing, this angle needs to be negated depending on whether we start rotating from the
     664            // (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, apparently we need to use the
     665            // negated angle. There's probable some reason for it you can look up, but trial and error is a much more effective way of
     666            // finding out.
     667            float stepAngle = -(float)(M_PI/(roundCapPoints-1));
     668
     669            // first push the vertices, then figure out the GL_TRIANGLES indices
     670            verticesOut.push_back(SVertex(centerPoint, texCenterU, texCenterV));
     671            verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
     672
     673            // usually corner2 - centerPoint suffices for baseVector below since it is of radius length, but we want to support custom
     674            // radii to support tuning the sharp caps (see above)
     675            CVector3D baseVector = (corner2 - centerPoint).Normalized() * radius;
     676            CVector3D centerPointNormalVector = terrain->CalcExactNormal(centerPoint.X, centerPoint.Z); // always normalized
     677
     678            for (int i = 1; i < roundCapPoints - 1; ++i)
     679            {
     680                // set up a quaternion rotation of the centerPoint -> corner vector by i*stepAngle
     681                // radians around the terrain normal at centerPoint
     682                CQuaternion quatRotation;
     683                quatRotation.FromAxisAngle(centerPointNormalVector, i * stepAngle);
     684                // at this point, quatRotation is always normalized because centerPointNormalVector is normalized. Note that we don't need
     685                // to scale the rotated baseVector to the radius, as baseVector was already of radius length and rotation maintains vector length.
     686                CVector3D worldPos3D = centerPoint + quatRotation.Rotate(baseVector);
     687
     688                // let v range from 0 to 1 as we move along the semi-circle, keep u fixed at 0 (i.e. curve the left vertical edge of the texture
     689                // around the edge of the semicircle)
     690                float u = 0.f;
     691                float v = clamp((i/(float)(roundCapPoints-1)), 0.f, 1.f);
     692                verticesOut.push_back(SVertex(worldPos3D, u, v)); // pos, u, v
     693            }
     694
     695            // connect back to the other butt corner point to complete the semicircle
     696            verticesOut.push_back(SVertex(corner1, 0.f, 1.f));
     697
     698            // now push indices for GL_TRIANGLES; vertices[0] is the center, vertices[1] is the first corner point, then a bunch of
     699            // radial samples, and then at the end we have the other corner point again. So:
     700            for (int i=1; i < roundCapPoints; ++i)
     701            {
     702                indicesOut.push_back(0); // center point
     703                indicesOut.push_back(i);
     704                indicesOut.push_back(i+1);
     705            }
     706
     707        }
     708        break;
     709
     710    case SOverlayTexturedLine::LINECAP_SQUARE:
     711        {
     712            // extend the (corner1 -> corner2) vector along the direction normal and draw a triangle fan with 3 triangles (although actually
     713            // drawn as GL_TRIANGLES)
     714            // NOTE: the order in which the vertices are pushed out determines the visibility, as they
     715            // are rendered only one-sided; the wrong order of vertices will only make the cap visible from the bottom.
     716           
     717            verticesOut.push_back(SVertex(centerPoint, texCenterU, texCenterV)); // push center point
     718            verticesOut.push_back(SVertex(corner2, 0.f, 0.f)); // push butt corner point 2
     719            verticesOut.push_back(SVertex(corner2 + (normal * (m_Line->m_Thickness)), 0.f, 0.33333f)); // extend butt corner point 2 along the normal vector
     720            verticesOut.push_back(SVertex(corner1 + (normal * (m_Line->m_Thickness)), 0.f, 0.66666f)); // extend butt corner point 1 along the normal vector
     721            verticesOut.push_back(SVertex(corner1, 0.f, 1.0f)); // push butt corner point 1
     722
     723            for (int i=1; i < 4; ++i)
     724            {
     725                indicesOut.push_back(0); // center point
     726                indicesOut.push_back(i);
     727                indicesOut.push_back(i+1);
     728            }
     729
     730        }
     731        break;
     732
     733    default:
     734        break;
     735
     736    }
     737
    429738}
  • source/renderer/OverlayRenderer.h

    diff --git a/source/renderer/OverlayRenderer.h b/source/renderer/OverlayRenderer.h
    index a48ae0a..7845cb4 100644
    a b  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2011 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
     
    1818#ifndef INCLUDED_OVERLAYRENDERER
    1919#define INCLUDED_OVERLAYRENDERER
    2020
     21#include "graphics/ShaderManager.h"
     22
    2123struct SOverlayLine;
    2224struct SOverlayTexturedLine;
    2325struct SOverlaySprite;
    public:  
    8486    void RenderForegroundOverlays(const CCamera& viewCamera);
    8587
    8688private:
     89   
     90    /**
     91     * Helper method; renders the overlay lines currently registered in the internals (i.e. in m->texlines) in the order
     92     * specified by @p indices. Used for splitting the textured overlay lines by their visibility status, and then batch
     93     * rendering them with the right shader by passing the correct indices to this method.
     94     */
     95    void RenderTexturedOverlayLines(const std::vector<size_t>& indices, CShaderProgramPtr shader);
     96
     97private:
    8798    OverlayRendererInternals* m;
    8899};
    89100
  • source/renderer/RenderModifiers.h

    diff --git a/source/renderer/RenderModifiers.h b/source/renderer/RenderModifiers.h
    index 01bc887..ecf8930 100644
    a b  
    1 /* Copyright (C) 2009 Wildfire Games.
     1/* Copyright (C) 2011 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
    private:  
    137137/**
    138138 * Class RenderModifierRenderer: Interface to a model renderer that can render
    139139 * its models via a RenderModifier that sets up fragment stages.
     140 * TODO: this appears to be currently unused, should be considered for deletion
    140141 */
    141142class RenderModifierRenderer : public ModelRenderer
    142143{
  • source/simulation2/TypeList.h

    diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
    index a7ac94b..795a586 100644
    a b COMPONENT(Position) // must be before VisualActor  
    114114INTERFACE(ProjectileManager)
    115115COMPONENT(ProjectileManager)
    116116
     117INTERFACE(RallyPoint)
     118COMPONENT(RallyPoint)
     119
    117120INTERFACE(RangeManager)
    118121COMPONENT(RangeManager)
    119122
  • new file source/simulation2/components/CCmpRallyPoint.cpp

    diff --git a/source/simulation2/components/CCmpRallyPoint.cpp b/source/simulation2/components/CCmpRallyPoint.cpp
    new file mode 100644
    index 0000000..56baf25
    - +  
     1/* Copyright (C) 2011 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 "simulation2/system/Component.h"
     21#include "ICmpRallyPoint.h"
     22
     23#include "simulation2/MessageTypes.h"
     24#include "simulation2/helpers/Render.h"
     25#include "simulation2/helpers/Geometry.h"
     26
     27#include "simulation2/components/ICmpPosition.h"
     28#include "simulation2/components/ICmpPathFinder.h"
     29#include "simulation2/components/ICmpFootprint.h"
     30#include "simulation2/components/ICmpRangeManager.h"
     31#include "simulation2/components/ICmpPlayer.h"
     32#include "simulation2/components/ICmpOwnership.h"
     33#include "simulation2/components/ICmpTerrain.h"
     34#include "simulation2/components/ICmpPlayerManager.h"
     35#include "simulation2/components/ICmpPlayer.h"
     36#include "simulation2/components/ICmpObstructionManager.h"
     37
     38#include "ps/CLogger.h"
     39#include "graphics/Overlay.h"
     40#include "graphics/TextureManager.h"
     41#include "renderer/Renderer.h"
     42
     43struct SVisibilitySegment
     44{
     45    bool m_Visible;
     46    size_t m_StartIndex;
     47    size_t m_EndIndex; // inclusive
     48
     49    SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
     50        : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex)
     51    {}
     52
     53    bool operator==(const SVisibilitySegment& other) const
     54    {
     55        return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex);
     56    }
     57
     58    bool operator!=(const SVisibilitySegment& other) const
     59    {
     60        return !(*this == other);
     61    }
     62
     63    bool IsSinglePoint()
     64    {
     65        return (m_StartIndex == m_EndIndex);
     66    }
     67};
     68
     69class CCmpRallyPoint : public ICmpRallyPoint
     70{
     71    // import some types for less verbosity
     72    typedef ICmpPathfinder::Waypoint Waypoint;
     73    typedef ICmpPathfinder::Path Path;
     74    typedef ICmpPathfinder::Goal Goal;
     75    typedef ICmpRangeManager::CLosQuerier CLosQuerier;
     76    typedef SOverlayTexturedLine::LineCap LineCap;
     77
     78public:
     79    static void ClassInit(CComponentManager& componentManager)
     80    {
     81        componentManager.SubscribeToMessageType(MT_RenderSubmit);
     82        componentManager.SubscribeToMessageType(MT_OwnershipChanged);
     83        componentManager.SubscribeToMessageType(MT_TurnStart);
     84        // TODO: should probably also listen to movement messages
     85    }
     86
     87    DEFAULT_COMPONENT_ALLOCATOR(RallyPoint)
     88
     89protected:
     90
     91    /// Actual position of the rally point (in fixed-point world coordinates for stability)
     92    CFixedVector2D m_RallyPoint;
     93    /// Full path to the rally point as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
     94    std::vector<CVector2D> m_FullPath;
     95    std::deque<SVisibilitySegment> m_VisibilitySegments;
     96
     97    bool m_Displayed; ///< Should we render the rally point and its path line? (set from JS when e.g. the unit is selected/deselected)
     98    bool m_SmoothPath; ///< Smooth the path before rendering?
     99
     100    entity_id_t m_MarkerEntity; ///< Entity ID of the rally point marker. Allocated when first displayed.
     101    std::wstring m_MarkerTemplate;  ///< Template name of the rally point marker.
     102
     103    /// Marker connector line settings (loaded from XML)
     104    float m_LineThickness;
     105    CColor m_LineColor;
     106    CColor m_LineDashColor;
     107    LineCap m_LineStartCap;
     108    LineCap m_LineEndCap;
     109    std::wstring m_LineTexturePath;
     110    std::wstring m_LineTextureMaskPath;
     111
     112    CTexturePtr m_Texture;
     113    CTexturePtr m_TextureMask;
     114
     115    ///< Textured overlay lines for the marker line. There can be multiple because we need to do dashes inside the SoD.
     116    std::vector<SOverlayTexturedLine> m_TexturedOverlayLines;
     117
     118    /// Draw little overlay circles to indicate where the exact path points are?
     119    bool m_EnableDebugNodeOverlay;
     120    std::vector<SOverlayLine> m_DebugNodeOverlays;
     121
     122public:
     123
     124    CCmpRallyPoint()
     125    {
     126        m_Displayed = false;
     127        m_SmoothPath = true;
     128        m_MarkerEntity = INVALID_ENTITY;
     129        m_EnableDebugNodeOverlay = false;
     130    }
     131
     132    ~CCmpRallyPoint(){}
     133
     134    static std::string GetSchema()
     135    {
     136        return
     137            "<a:component />"
     138            "<a:help>Displays a rally point where created units will gather when spawned.</a:help>"
     139            "<a:example>"
     140                "<MarkerTemplate>special/rallypoint</MarkerTemplate>"
     141                "<LineThickness>0.75</LineThickness>"
     142                "<LineStartCap>round</LineStartCap>"
     143                "<LineEndCap>square</LineEndCap>"
     144                "<LineColour r='20' g='128' b='240'></LineColour>"
     145            "</a:example>"
     146            "<element name='MarkerTemplate' a:help='Template name for the rally point marker entity (typically a waypoint flag actor).'>"
     147                "<text/>"
     148            "</element>"
     149            "<optional>"
     150                "<element name='LineColour'>"
     151                    "<attribute name='r'>"
     152                        "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     153                    "</attribute>"
     154                    "<attribute name='g'>"
     155                        "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     156                    "</attribute>"
     157                    "<attribute name='b'>"
     158                        "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     159                    "</attribute>"
     160                "</element>"
     161            "</optional>"
     162            "<optional>"
     163                "<element name='LineStartCap'>"
     164                    "<choice>"
     165                        "<value a:help='Abrupt line ending; line endings are not closed.'>flat</value>"
     166                        "<value a:help='Semi-circular line end cap.'>round</value>"
     167                        "<value a:help='Sharp, pointy line end cap.'>sharp</value>"
     168                        "<value a:help='Square line end cap.'>square</value>"
     169                    "</choice>"
     170                "</element>"
     171            "</optional>"
     172            "<optional>"
     173                "<element name='LineEndCap'>"
     174                    "<choice>"
     175                    "<value a:help='Abrupt line ending; line endings are not closed.'>flat</value>"
     176                    "<value a:help='Semi-circular line end cap.'>round</value>"
     177                    "<value a:help='Sharp, pointy line end cap.'>sharp</value>"
     178                    "<value a:help='Square line end cap.'>square</value>"
     179                    "</choice>"
     180                "</element>"
     181            "</optional>"
     182            "<optional>"
     183                "<element name='LineThickness' a:help='Thickness of the marker line connecting the entity to the rally point marker.'>"
     184                    "<data type='decimal'/>"
     185                "</element>"
     186            "</optional>"
     187            "<optional>"
     188                "<element name='LineDashColour'>"
     189                    "<attribute name='r'>"
     190                        "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     191                    "</attribute>"
     192                    "<attribute name='g'>"
     193                        "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     194                    "</attribute>"
     195                    "<attribute name='b'>"
     196                        "<data type='integer'><param name='minInclusive'>0</param><param name='maxInclusive'>255</param></data>"
     197                    "</attribute>"
     198                "</element>"
     199            "</optional>";
     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        // TODO should probably serialize the rally point location here
     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            break;
     230        }
     231        case MT_OwnershipChanged:
     232        {
     233            CreateMarker();
     234            RepositionMarker(); // the new marker doesn't have a position yet, so let's make sure to update it
     235        }
     236        case MT_TurnStart:
     237        {
     238            UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
     239        }
     240        }
     241    }
     242
     243    virtual CFixedVector2D GetPosition()
     244    {
     245        return m_RallyPoint;
     246    }
     247
     248    virtual void SetPosition(CFixedVector2D pos)
     249    {
     250        bool posChanged = (pos.X != m_RallyPoint.X || pos.Y != m_RallyPoint.Y);
     251        m_RallyPoint = pos;
     252
     253        if(posChanged)
     254        {
     255            RecomputeRallyPointPath();
     256            RepositionMarker();
     257        }
     258    }
     259
     260    virtual void Unset()
     261    {
     262        SetPosition(CFixedVector2D()); // reset to zero
     263    }
     264
     265    bool IsSet()
     266    {
     267        return !m_RallyPoint.IsZero();
     268    }
     269
     270    virtual void SetDisplayed(bool displayed)
     271    {
     272        bool displayChanged = (m_Displayed != displayed);
     273        m_Displayed = displayed;
     274
     275        if(displayChanged)
     276        {
     277            RepositionMarker(); // move the marker out of oblivion and back into the real world, or vice-versa
     278            UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
     279        }
     280    }
     281
     282private:
     283
     284    /**
     285     * (Re)creates the rally point marker entity. Called upon initialization and whenever the ownership of this entity changes, as the marker
     286     * depends on the owner's civilization. If a marker entity already exists, it will be destroyed first.
     287     */
     288    void CreateMarker();
     289
     290    /**
     291     * Repositions the rally point marker; moves it outside of the world (ie. hides it), or positions it at the rally point. Executed
     292     * whenever either the position of the rally point changes (including whether it is set or not), or the display flag changes.
     293     *
     294     * TODO: this is executed from JS-executed methods, which must not fail under any circumstances. We'd probably have to change this to
     295     * detect the changes in the rendering code rather than execute them directly here.
     296     **/
     297    void RepositionMarker();
     298
     299    /**
     300     * Recomputes the full path from this entity to the rally point, and does all the necessary post-processing to make it prettier.
     301     * Called whenever the rally point position changes.
     302     */
     303    void RecomputeRallyPointPath();
     304
     305    /**
     306     * Sets up the overlay lines for rendering according to the current full path and visibility segments. Does all the necessary splitting
     307     * of the line into solid and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently
     308     * set, this method does nothing.
     309     */
     310    void ConstructOverlayLines();
     311
     312    /**
     313     * Checks for changes to the SoD to the previously saved state, and reconstructs the overlay lines to match if necessary. Does nothing
     314     * if the rally point line is not currently set to be displayed, so that we don't waste compute cycles trying to update rally point
     315     * overlays for every building all the time if they're not even gonna be visible anyway.
     316     */
     317    void UpdateOverlayLines();
     318
     319    /**
     320     * Removes waypoints from m_rallyPointPath that are obstructed by the originating building's footprint, and links up the rally point path
     321     * nicely to the edge of the building's footprint. Only needed if the pathfinder can possibly return obstructed tile waypoints, i.e. when
     322     * pathfinding is started from an obstructed tile.
     323     */
     324    void FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint);
     325
     326    /**
     327     * Removes waypoints that are inside the shroud of darkness, i.e. where the player shouldn't be able to get any information about the
     328     * positions of various buildings and whatnot from the rally point path.
     329     */
     330    void FixInvisibleWaypoints(std::vector<CVector2D>& coords);
     331
     332    /**
     333     * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily intended to
     334     * reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which will happily curve
     335     * and bend to reduce costs. Visually, it doesn't make sense for a rally point path to curve and bend when it could just as well have gone
     336     * in a straight line, so that's why we have this.
     337     *
     338     * @p coords array of path coordinates to simplify
     339     * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be eliminated to form a single
     340     *                    link. If this value is set to e.g. 1, then no reductions will be performed. A value of 3 means that at most 3
     341     *                    consecutive node links will be joined into a single link.
     342     */
     343    void ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks = 0);
     344
     345    /**
     346     * Returns a list of indices of waypoints in the current path (m_FullPath) where the LOS visibility changes, ordered from building to rally
     347     * point. Used to construct the overlay line segments and track changes to the shroud of darkness.
     348     */
     349    void GetVisibilitySegments(std::deque<SVisibilitySegment>& out);
     350
     351    /**
     352     * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring segments.
     353     * You should not have to call this method directly.
     354     */
     355    static void MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments);
     356
     357    void RenderSubmit(SceneCollector& collector);
     358
     359};
     360
     361REGISTER_COMPONENT_TYPE(RallyPoint)
     362
     363void CCmpRallyPoint::Init(const CParamNode& paramNode)
     364{
     365    // set some defaults
     366    m_LineThickness = .3f;
     367    m_LineStartCap = SOverlayTexturedLine::LINECAP_FLAT;
     368    m_LineEndCap = SOverlayTexturedLine::LINECAP_FLAT;
     369    m_LineTexturePath = std::wstring(L"art/textures/misc/rallypoint_line.png");
     370    m_LineTextureMaskPath = std::wstring(L"art/textures/misc/rallypoint_line_mask.png");
     371    // implicit constructor defaults for m_LineColor and m_MarkerTemplate
     372
     373    // ---------------------------------------------------------------------------------------------
     374    // load some XML configuration data
     375
     376    if (paramNode.GetChild("MarkerTemplate").IsOk())
     377        m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString();
     378
     379    if (paramNode.GetChild("LineThickness").IsOk())
     380        m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat();
     381
     382    const CParamNode& lineColor = paramNode.GetChild("LineColour");
     383    if (lineColor.IsOk() && lineColor.GetChild("@r").IsOk() && lineColor.GetChild("@g").IsOk() && lineColor.GetChild("@b").IsOk())
     384    {
     385        m_LineColor = CColor(
     386            lineColor.GetChild("@r").ToInt()/255.f,
     387            lineColor.GetChild("@g").ToInt()/255.f,
     388            lineColor.GetChild("@b").ToInt()/255.f,
     389            1.f
     390        );
     391    }
     392
     393    const CParamNode& lineDashColor = paramNode.GetChild("LineDashColour");
     394    if (lineDashColor.IsOk() && lineDashColor.GetChild("@r").IsOk() && lineDashColor.GetChild("@g").IsOk() && lineDashColor.GetChild("@b").IsOk())
     395    {
     396        m_LineDashColor = CColor(
     397            lineDashColor.GetChild("@r").ToInt()/255.f,
     398            lineDashColor.GetChild("@g").ToInt()/255.f,
     399            lineDashColor.GetChild("@b").ToInt()/255.f,
     400            1.f
     401        );
     402    }
     403    else
     404    {
     405        m_LineDashColor = m_LineColor; // if not specified, use the same colour as the regular line segments
     406    }
     407
     408    if (paramNode.GetChild("LineTexture").IsOk())
     409        m_LineTexturePath = paramNode.GetChild("LineTexture").ToString();
     410
     411    if (paramNode.GetChild("LineMaskTexture").IsOk())
     412        m_LineTextureMaskPath = paramNode.GetChild("LineMaskTexture").ToString();
     413
     414    if (paramNode.GetChild("LineStartCap").IsOk())
     415    {
     416        const std::wstring& xmlLineStartCap = paramNode.GetChild("LineStartCap").ToString();
     417        if (!SOverlayTexturedLine::StrToLineCap(xmlLineStartCap, m_LineStartCap))
     418            LOGERROR(L"Unrecognized LineStartCap value \"%ls\"", xmlLineStartCap); // this shouldn't happen by virtue of the XML validation
     419    }
     420
     421    if (paramNode.GetChild("LineEndCap").IsOk())
     422    {
     423        const std::wstring& xmlLineEndCap = paramNode.GetChild("LineEndCap").ToString();
     424        if (!SOverlayTexturedLine::StrToLineCap(xmlLineEndCap, m_LineEndCap))
     425            LOGERROR(L"Unrecognized LineEndCap value \"%ls\"", xmlLineEndCap); // this shouldn't happen by virtue of the XML validation
     426    }
     427
     428    // ---------------------------------------------------------------------------------------------
     429    // load some textures
     430
     431    CTextureProperties texturePropsBase(m_LineTexturePath);
     432    texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
     433    texturePropsBase.SetMaxAnisotropy(8.f);
     434    m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
     435
     436    CTextureProperties texturePropsMask(m_LineTextureMaskPath);
     437    texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
     438    texturePropsMask.SetMaxAnisotropy(8.f);
     439    m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
     440
     441    // ---------------------------------------------------------------------------------------------
     442
     443    CreateMarker(); // TODO: evaluate how much load this puts on the entity IDs, if it's too high then we can do this on-demand)
     444
     445}
     446
     447void CCmpRallyPoint::CreateMarker()
     448{
     449
     450    CComponentManager& componentMgr = GetSimContext().GetComponentManager();
     451
     452    // if a marker entity already exists, kill it first
     453    if (m_MarkerEntity != INVALID_ENTITY)
     454    {
     455        CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntity);
     456        if (!markerCmpPosition.null())
     457            markerCmpPosition->MoveOutOfWorld();
     458
     459        componentMgr.DestroyComponentsSoon(m_MarkerEntity); // queue entity for destruction
     460        m_MarkerEntity = INVALID_ENTITY; // make sure any code below doesn't try to access the soon-to-be-destroyed entity anymore
     461    }
     462
     463    // allocate a new entity for the marker
     464    if (!m_MarkerTemplate.empty())
     465    {
     466        m_MarkerEntity = componentMgr.AllocateNewLocalEntity();
     467        if (m_MarkerEntity != INVALID_ENTITY)
     468            m_MarkerEntity = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntity);
     469    }
     470   
     471    CmpPtr<ICmpOwnership> cmpOwnership(GetSimContext(), GetEntityId());
     472    if (!cmpOwnership.null())
     473    {
     474        player_id_t ownerId = cmpOwnership->GetOwner();
     475        if (ownerId != INVALID_PLAYER)
     476        {
     477            CmpPtr<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
     478            ENSURE(!cmpPlayerManager.null());
     479
     480            CmpPtr<ICmpPlayer> cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId));
     481            if (!cmpPlayer.null())
     482            {
     483                // TODO: in the future, when variants are working properly for actors, we should set the variant corresponding to the
     484                // owning player's civilization here.
     485            }
     486        }
     487    }
     488
     489}
     490
     491void CCmpRallyPoint::RepositionMarker()
     492{
     493    if (m_MarkerEntity == INVALID_ENTITY)
     494        return; // there is no valid marker entity, no use trying to position it
     495
     496    CmpPtr<ICmpPosition> markerCmpPosition(GetSimContext(), m_MarkerEntity);
     497    if (!markerCmpPosition.null())
     498    {
     499        if(m_Displayed && IsSet())
     500        {
     501            markerCmpPosition->JumpTo(m_RallyPoint.X, m_RallyPoint.Y);
     502        }
     503        else
     504        {
     505            markerCmpPosition->MoveOutOfWorld(); // hide it
     506        }
     507    }
     508}
     509
     510void CCmpRallyPoint::RecomputeRallyPointPath()
     511{
     512   
     513    m_FullPath.clear();
     514    m_VisibilitySegments.clear();
     515
     516    if (!IsSet())
     517        return; // no use computing a path if the rally point isn't set
     518
     519    CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId());
     520    if (cmpPosition.null() || !cmpPosition->IsInWorld())
     521        return; // no point going on if this entity doesn't have a position or is outside of the world
     522
     523    CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), GetEntityId());
     524    CmpPtr<ICmpPathfinder> cmpPathFinder(GetSimContext(), SYSTEM_ENTITY);
     525    //CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     526
     527
     528    // -------------------------------------------------------------------------------------------------
     529
     530    entity_pos_t pathStartX = cmpPosition->GetPosition2D().X;
     531    entity_pos_t pathStartY = cmpPosition->GetPosition2D().Y;
     532
     533    // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
     534    // list of waypoints (i.e. a Path) from the building to the goal, where each waypoint is centered
     535    // at a tile. We'll have to do some post-processing on the path to get it smooth.
     536    Path path;
     537    std::vector<Waypoint>& waypoints = path.m_Waypoints;
     538
     539    Goal goal = { Goal::POINT, m_RallyPoint.X, m_RallyPoint.Y };
     540    cmpPathFinder->ComputePath(
     541        pathStartX,
     542        pathStartY,
     543        goal,
     544        cmpPathFinder->GetPassabilityClass("default"),
     545        cmpPathFinder->GetCostClass("default"),
     546        path
     547    );
     548
     549    if (path.m_Waypoints.size() < 2)
     550        return; // not likely to happen, but can't hurt to check
     551
     552    // from here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth
     553    // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot.
     554    // Since we'll only be further using these points for rendering purposes, using floats should be fine.
     555
     556    // make sure to add the actual goal point as the last point (the long pathfinder only finds paths to
     557    // the tile closest to the goal, so we need to complete the last bit from the closest tile to the rally
     558    // point itself)
     559    // NOTE: the points are returned in reverse order (from the goal to the start point), so we need to
     560    // actually insert it at the front of the coordinate list. Hence, we'll do this first before appending the rest of the
     561    // fixed waypoints as CVector2Ds.
     562
     563    Waypoint& lastWaypoint = waypoints.back();
     564    if (lastWaypoint.x != goal.x || lastWaypoint.z != goal.z)
     565        m_FullPath.push_back(CVector2D(goal.x.ToFloat(), goal.z.ToFloat()));
     566
     567    // add the rest of the waypoints
     568    for (size_t i = 0; i < waypoints.size(); ++i)
     569        m_FullPath.push_back(CVector2D(waypoints[i].x.ToFloat(), waypoints[i].z.ToFloat()));
     570
     571    // -------------------------------------------------------------------------------------------
     572    // postprocessing
     573
     574    // linearize the path;
     575    // pass through the waypoints , averaging each waypoint with its next one except the last one. Because the path
     576    // goes from the marker to this entity and we want to keep the point at the marker's exact position, loop backwards through the
     577    // waypoints so that the marker waypoint is maintained.
     578    // TODO: see if we can do this at the same time as the waypoint -> coord conversion above
     579    for(size_t i = m_FullPath.size() - 1; i > 0; --i)
     580        m_FullPath[i] = (m_FullPath[i] + m_FullPath[i-1]) / 2.0f;
     581
     582    // if there's a footprint, remove any points returned by the pathfinder that may be on obstructed footprint tiles
     583    if (!cmpFootprint.null())
     584        FixFootprintWaypoints(m_FullPath, cmpPosition, cmpFootprint);
     585
     586    // eliminate some consecutive waypoints that are visible from eachother.
     587    ReduceSegmentsByVisibility(m_FullPath, 6); // reduce across a maximum distance of approx. 6 tiles (prevents segments that are too long to properly stick to the terrain)
     588
     589    //// <DEBUG> ///////////////////////////////////////////////
     590    if (m_EnableDebugNodeOverlay)
     591        m_DebugNodeOverlays.clear();
     592
     593    if (m_EnableDebugNodeOverlay && m_SmoothPath)
     594    {
     595        // create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the
     596        // terrain so we can still see them after the interpolated points are added)
     597        for (size_t j = 0; j < m_FullPath.size(); ++j)
     598        {
     599            SOverlayLine overlayLine;
     600            overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f);
     601            overlayLine.m_Thickness = 2;
     602            SimRender::ConstructSquareOnGround(GetSimContext(), m_FullPath[j].X, m_FullPath[j].Y, 0.2f, 0.2f, 1.0f, overlayLine, true);
     603            m_DebugNodeOverlays.push_back(overlayLine);
     604        }
     605    }
     606    //// </DEBUG> //////////////////////////////////////////////
     607
     608    if (m_SmoothPath)
     609        // the number of points to interpolate goes together with the maximum amount of node links allowed to be joined together by
     610        // the visibility reduction. The more node links that can be joined together, the more interpolated points you need to generate
     611        // to be able to deal with local terrain height changes
     612        SimRender::InterpolatePointsRNS(m_FullPath, false, 0, 8); // no offset, keep line at its exact path
     613
     614    // -------------------------------------------------------------------------------------------
     615   
     616    // 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
     617    GetVisibilitySegments(m_VisibilitySegments);
     618
     619    // build overlay lines for the new path
     620    ConstructOverlayLines();
     621
     622}
     623
     624void CCmpRallyPoint::ConstructOverlayLines()
     625{
     626   
     627    // we need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the renderer,
     628    // because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every pass (which is
     629    // only sensible)
     630
     631    m_TexturedOverlayLines.clear();
     632
     633    if (m_FullPath.size() < 2)
     634        return;
     635
     636    CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     637    SOverlayTexturedLine::LineCap dashesLineCap = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
     638
     639    for (std::deque<SVisibilitySegment>::const_iterator it = m_VisibilitySegments.begin(); it != m_VisibilitySegments.end(); ++it)
     640    {
     641        const SVisibilitySegment& segment = (*it);
     642
     643        if (segment.m_Visible)
     644        {
     645            // does this segment border on the building or rally point flag on either side?
     646            bool bordersBuilding = (segment.m_EndIndex == m_FullPath.size() - 1);
     647            bool bordersFlag = (segment.m_StartIndex == 0);
     648
     649            // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
     650            SOverlayTexturedLine overlayLine;
     651            overlayLine.m_Thickness = m_LineThickness;
     652            overlayLine.m_Terrain = cmpTerrain->GetCTerrain();
     653            overlayLine.m_TextureBase = m_Texture;
     654            overlayLine.m_TextureMask = m_TextureMask;
     655            overlayLine.m_Color = m_LineColor;
     656            overlayLine.m_Closed = false;
     657            // we should take care to only use m_LineXCap for the actual end points at the building and the rally point; any intermediate
     658            // end points (i.e., that border a dashed segment) should have the dashed cap
     659            // the path line is actually in reverse order as well, so let's swap out the start and end caps
     660            overlayLine.m_StartCap = (bordersFlag ? m_LineEndCap : dashesLineCap);
     661            overlayLine.m_EndCap = (bordersBuilding ? m_LineStartCap : dashesLineCap);
     662            overlayLine.m_AlwaysVisible = true;
     663
     664            // push overlay line coordinates
     665            ENSURE(segment.m_EndIndex > segment.m_StartIndex);
     666            for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here
     667            {
     668                overlayLine.m_Coords.push_back(m_FullPath[j].X);
     669                overlayLine.m_Coords.push_back(m_FullPath[j].Y);
     670            }
     671
     672            m_TexturedOverlayLines.push_back(overlayLine);
     673        }
     674        else
     675        {
     676            // construct dashed line from startPointIdx to endPointIdx, add textured overlay lines for it to the render list
     677            std::vector<CVector2D> straightLine;
     678            straightLine.push_back(m_FullPath[segment.m_StartIndex]);
     679            straightLine.push_back(m_FullPath[segment.m_EndIndex]);
     680
     681            // We always want to have the dashed line end at either point with a full dash (i.e. not a cleared space), so that the dashed
     682            // area is visually obvious. That implies that we want at least So, let's do some calculations to see what size we should make
     683            // the dashes and clears.
     684
     685            float maxDashSize = 3.f;
     686            float maxClearSize = 3.f;
     687           
     688            float dashSize = maxDashSize;
     689            float clearSize = maxClearSize;
     690            float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length
     691
     692            float distance = (m_FullPath[segment.m_StartIndex] - m_FullPath[segment.m_EndIndex]).Length(); // straight-line distance between the points
     693
     694            // see how many pairs (dash + clear) can fit into the distance unmodified. Then check the remaining distance; if it's not exactly
     695            // a dash size's worth (and it likely won't be), then adjust the dash/clear sizes slightly so that it is.
     696            int numFitUnmodified = floor(distance/(dashSize + clearSize));
     697            float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize));
     698
     699            // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly.
     700            // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make
     701            // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified
     702            // (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's
     703            // length and the clear's length.
     704
     705            // we always want to have at least one dash/clear pair (i.e., "|===|   |===|"); also, we need to avoid division by zero below.
     706            numFitUnmodified = std::max(1, numFitUnmodified);
     707
     708            float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified; // can be either positive or negative
     709            dashSize += pairDashRatio * pairwiseLengthDifference;
     710            clearSize += (1 - pairDashRatio) * pairwiseLengthDifference;
     711
     712            // ------------------------------------------------------------------------------------------------
     713
     714            SDashedLine dashedLine;
     715            SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize);
     716
     717            // build overlay lines for dashes
     718            size_t numDashes = dashedLine.m_StartIndices.size();
     719            for (size_t i=0; i < numDashes; i++)
     720            {
     721                SOverlayTexturedLine dashOverlay;
     722
     723                dashOverlay.m_Thickness = m_LineThickness;
     724                dashOverlay.m_Terrain = cmpTerrain->GetCTerrain();
     725                dashOverlay.m_TextureBase = m_Texture;
     726                dashOverlay.m_TextureMask = m_TextureMask;
     727                dashOverlay.m_Color = m_LineDashColor;
     728                dashOverlay.m_Closed = false;
     729                dashOverlay.m_StartCap = dashesLineCap;
     730                dashOverlay.m_EndCap = dashesLineCap;
     731                dashOverlay.m_AlwaysVisible = true;
     732                // TODO: maybe adjust the elevation of the dashes to be a little lower, so that it slides underneath the actual path
     733
     734                size_t dashStartIndex = dashedLine.m_StartIndices[i];
     735                size_t dashEndIndex = dashedLine.GetEndIndex(i);
     736                ENSURE(dashEndIndex > dashStartIndex);
     737
     738                for (size_t n = dashStartIndex; n < dashEndIndex; n++)
     739                {
     740                    dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].X);
     741                    dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y);
     742                }
     743
     744                m_TexturedOverlayLines.push_back(dashOverlay);
     745            }
     746           
     747        }
     748    }
     749
     750    //// <DEBUG> //////////////////////////////////////////////
     751    if (m_EnableDebugNodeOverlay)
     752    {
     753        for (size_t j = 0; j < m_FullPath.size(); ++j)
     754        {
     755            SOverlayLine overlayLine;
     756            overlayLine.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     757            overlayLine.m_Thickness = 1;
     758            SimRender::ConstructCircleOnGround(GetSimContext(), m_FullPath[j].X, m_FullPath[j].Y, 0.075f, overlayLine, true);
     759            m_DebugNodeOverlays.push_back(overlayLine);
     760        }
     761    }
     762    //// </DEBUG> //////////////////////////////////////////////
     763}
     764
     765void CCmpRallyPoint::UpdateOverlayLines()
     766{
     767    // we should really only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive
     768    // waste of time to calculate all this stuff every turn if there's nothing to show for it anyway
     769
     770    if (!m_Displayed || !IsSet())
     771        return;
     772
     773    // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
     774    std::deque<SVisibilitySegment> newVisibilitySegments;
     775    GetVisibilitySegments(newVisibilitySegments);
     776
     777    // compare the two indices vectors; as soon as an element is different (and provided the full path hasn't changed), then the SoD
     778    // has changed and we should recreate the overlay lines
     779    bool same = (m_VisibilitySegments == newVisibilitySegments);
     780    if (!same)
     781    {
     782        // the visibility segments have changed, so we want to reconstruct the overlay lines to match. Note that the path itself doesn't
     783        // change, only the overlay lines we construct from them.
     784        m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time
     785        ConstructOverlayLines();
     786    }
     787
     788}
     789
     790void CCmpRallyPoint::FixFootprintWaypoints(std::vector<CVector2D>& coords, CmpPtr<ICmpPosition> cmpPosition, CmpPtr<ICmpFootprint> cmpFootprint)
     791{
     792    ENSURE(!cmpPosition.null());
     793    ENSURE(!cmpFootprint.null());
     794
     795    // -----------------------------------------------------------------------------------------------------
     796    // TODO: nasty fixed/float conversions everywhere
     797
     798    // grab the shape and dimensions of the footprint
     799    entity_pos_t footprintSize0, footprintSize1, footprintHeight;
     800    ICmpFootprint::EShape footprintShape;
     801    cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
     802
     803    // grab the center of the footprint
     804    CFixedVector2D center = cmpPosition->GetPosition2D();
     805
     806    // -----------------------------------------------------------------------------------------------------
     807
     808    switch (footprintShape)
     809    {
     810    case ICmpFootprint::SQUARE:
     811        {
     812            // in this case, footprintSize0 and 1 respectively indicate the (unrotated) size along the X and Z axes
     813
     814            // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
     815            // and the rotated unit vectors in the X/Z plane of the shape's footprint
     816            // (the Footprint itself holds only the outline, the Position holds the orientation)
     817
     818            fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
     819            fixed a = cmpPosition->GetRotation().Y;
     820            sincos_approx(a, s, c);
     821            CFixedVector2D u(c, -s); // unit vector along the rotated X axis
     822            CFixedVector2D v(s, c); // unit vector along the rotated Z axis
     823            CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2);
     824
     825            // starting from the start position, check if any points are within the footprint of the building
     826            // (this is possible if the pathfinder was started from a point located within the footprint)
     827            for(int i = (int)(coords.size() - 1); i >= 0; i--)
     828            {
     829                const CVector2D& wp = coords[i];
     830                if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center, u, v, halfSize))
     831                {
     832                    coords.erase(coords.begin() + i);
     833                }
     834                else
     835                {
     836                    break; // point no longer inside footprint, from this point on neither will any of the following be
     837                }
     838            }
     839
     840            // add a point right on the edge of the footprint (nearest to the last waypoint) so that it links up nicely with the rest of the path
     841            CFixedVector2D lastWaypoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y));
     842            CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(lastWaypoint - center, u, v, halfSize); // relative to the shape origin (center)
     843            CVector2D footprintEdge((center.X + footprintEdgePoint.X).ToFloat(), (center.Y + footprintEdgePoint.Y).ToFloat());
     844            coords.push_back(footprintEdge);
     845
     846        }
     847        break;
     848    case ICmpFootprint::CIRCLE:
     849        {
     850            // in this case, both footprintSize0 and 1 indicate the circle's radius
     851
     852            for(int i = (int)(coords.size() - 1); i >= 0; i--)
     853            {
     854                const CVector2D& wp = coords[i];
     855                fixed pointDistance = (CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center).Length();
     856                if (pointDistance <= footprintSize0)
     857                {
     858                    coords.erase(coords.begin() + i);
     859                }
     860                else
     861                {
     862                    break; // point no longer inside footprint, from this point on neither will any of the following be
     863                }
     864            }
     865
     866            // add a point right on the edge of the footprint so that it links up nicely with the rest of the path
     867            CFixedVector2D radiusEdgePoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y));
     868            radiusEdgePoint.Normalize(footprintSize1);
     869            CVector2D footprintEdge((center.X + radiusEdgePoint.X).ToFloat(), (center.Y + radiusEdgePoint.Y).ToFloat());
     870            coords.push_back(footprintEdge);
     871
     872        }
     873        break;
     874    }
     875
     876}
     877
     878void CCmpRallyPoint::FixInvisibleWaypoints(std::vector<CVector2D>& coords)
     879{
     880    CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY);
     881
     882    player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
     883    CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
     884
     885    //for (std::vector<Waypoint>::iterator it = waypoints.begin(); it != waypoints.end();)
     886    for(std::vector<CVector2D>::iterator it = coords.begin(); it != coords.end();)
     887    {
     888        int i = (fixed::FromFloat(it->X) / (int)CELL_SIZE).ToInt_RoundToNearest();
     889        int j = (fixed::FromFloat(it->Y) / (int)CELL_SIZE).ToInt_RoundToNearest();
     890
     891        bool explored = losQuerier.IsExplored(i, j);
     892        if (!explored)
     893        {
     894            it = coords.erase(it);
     895        }
     896        else
     897        {
     898            it++;
     899        }
     900    }
     901
     902}
     903
     904void CCmpRallyPoint::ReduceSegmentsByVisibility(std::vector<CVector2D>& coords, unsigned maxSegmentLinks)
     905{
     906
     907    CmpPtr<ICmpPathfinder> cmpPathFinder(GetSimContext(), SYSTEM_ENTITY);
     908    CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
     909
     910    if (coords.size() < 3)
     911        return;
     912
     913    // 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
     914    // 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
     915    // process from there on until the entire line is checked. The output is the array of base nodes.
     916
     917    std::vector<CVector2D> newCoords;
     918    StationaryObstructionFilter obstructionFilter; // TODO: is this the right one to use?
     919    entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness);
     920    ICmpPathfinder::pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass("default");
     921
     922    newCoords.push_back(coords[0]); // save the first base node
     923
     924    size_t baseNodeIdx = 0;
     925    size_t curNodeIdx = 1;
     926
     927// kind of ugly, but prevents code duplication below
     928#define UPDATE_BASENODE_XYZ() STMT(baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);\
     929                                   baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);\
     930                                   baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);)
     931   
     932    float baseNodeY;
     933    entity_pos_t baseNodeX;
     934    entity_pos_t baseNodeZ;
     935
     936    UPDATE_BASENODE_XYZ(); // set initial base node coords
     937
     938    while (curNodeIdx < coords.size())
     939    {
     940        ENSURE(curNodeIdx > baseNodeIdx); // this needs to be true at all times, otherwise we're checking visibility between a point and itself
     941
     942        entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X);
     943        entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y);
     944        float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y);
     945
     946        // find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!)
     947        bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass);
     948
     949        // since height differences are ignored by CheckMovement, let's call two points visible from one another only if they're at roughly the same terrain elevation
     950        curNodeVisible = curNodeVisible && fabsf(curNodeY - baseNodeY) < 3.f; // TODO: this could probably use some tuning
     951        if (maxSegmentLinks > 0)
     952            curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks); // max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx)
     953
     954        if (!curNodeVisible)
     955        {
     956            // current node is not visible from the base node, so the previous one was the last visible point from baseNode and should
     957            // hence become the new base node for further iterations.
     958
     959            // if curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then
     960            // we should take care not to stay stuck at the current base node
     961            if (curNodeIdx > baseNodeIdx + 1)
     962            {
     963                baseNodeIdx = curNodeIdx - 1;
     964            }
     965            else
     966            {
     967                // curNodeIdx == baseNodeIdx + 1
     968                baseNodeIdx = curNodeIdx;
     969                curNodeIdx++; // move the next candidate node one forward so that we don't test a point against itself in the next iteration
     970            }
     971
     972            newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list
     973
     974            UPDATE_BASENODE_XYZ();
     975
     976        }
     977
     978        curNodeIdx++;
     979
     980    }
     981
     982#undef UPDATE_BASENODE_XYZ
     983
     984    // 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
     985    // base node, then the loop above just ends and no endpoint is ever added to the list.
     986    ENSURE(curNodeIdx == coords.size());
     987    newCoords.push_back(coords[coords.size() - 1]);
     988
     989    coords.swap(newCoords);
     990
     991}
     992
     993void CCmpRallyPoint::GetVisibilitySegments(std::deque<SVisibilitySegment>& out)
     994{
     995   
     996    out.clear();
     997
     998    if (m_FullPath.size() < 2)
     999        return;
     1000
     1001    CmpPtr<ICmpRangeManager> cmpRangeMgr(GetSimContext(), SYSTEM_ENTITY);
     1002
     1003    player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
     1004    CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
     1005
     1006    // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
     1007    // a new one at the next point.
     1008
     1009    bool lastVisible = losQuerier.IsExplored(
     1010        (fixed::FromFloat(m_FullPath[0].X) / (int) CELL_SIZE).ToInt_RoundToNearest(),
     1011        (fixed::FromFloat(m_FullPath[0].Y) / (int) CELL_SIZE).ToInt_RoundToNearest()
     1012    );
     1013    size_t curSegmentStartIndex = 0; // starting node index of the current segment
     1014
     1015    for (size_t k = 1; k < m_FullPath.size(); ++k)
     1016    {
     1017        // grab tile indices for this coord
     1018        int i = (fixed::FromFloat(m_FullPath[k].X) / (int)CELL_SIZE).ToInt_RoundToNearest();
     1019        int j = (fixed::FromFloat(m_FullPath[k].Y) / (int)CELL_SIZE).ToInt_RoundToNearest();
     1020
     1021        bool nodeVisible = losQuerier.IsExplored(i, j);
     1022        if (nodeVisible != lastVisible)
     1023        {
     1024            // visibility changed; write out the segment that was just completed and get ready for the new one
     1025            out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
     1026
     1027            //curSegmentStartIndex = k; // new segment starts here
     1028            curSegmentStartIndex = k - 1;
     1029            lastVisible = nodeVisible;
     1030        }
     1031
     1032    }
     1033
     1034    // terminate the last segment
     1035    out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_FullPath.size() - 1));
     1036
     1037    // ---------------------------------------------------------------------------------------------------
     1038    MergeVisibilitySegments(out);
     1039
     1040}
     1041
     1042void CCmpRallyPoint::MergeVisibilitySegments(std::deque<SVisibilitySegment>& segments)
     1043{
     1044    // scan for single-point segments; if they are inbetween two other segments, delete it and merge the surrounding segments;
     1045    // if they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't
     1046    // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first).
     1047
     1048    // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
     1049    size_t numSegments = segments.size();
     1050
     1051    // YE BE WARNED: HERE BE FOR LOOP TRICKERY
     1052    for (size_t i = 1; i < numSegments - 1;)
     1053    {
     1054        SVisibilitySegment& segment = segments[i];
     1055        if (segment.IsSinglePoint())
     1056        {
     1057            ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible); // since the segments' visibility alternates, the surrounding ones should have the same visibility
     1058            segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; // make previous segment span all the way across to the next
     1059            segments.erase(segments.begin() + i); // erase this segment ...
     1060            segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i])
     1061            numSegments -= 2; // we removed 2 segments, so update the loop condition
     1062            // in the next iteration, i should still point to the segment right after the one that got expanded, which is now
     1063            // at position i; so don't increment i here
     1064        }
     1065        else
     1066        {
     1067            ++i;
     1068        }
     1069    }
     1070
     1071    ENSURE(numSegments == segments.size());
     1072
     1073    // check to see if the first segment needs to be merged with its neighbour
     1074    if (segments.size() >= 2 && segments[0].IsSinglePoint())
     1075    {
     1076        int firstSegmentStartIndex = segments.front().m_StartIndex;
     1077        ENSURE(firstSegmentStartIndex == 0);
     1078        ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment
     1079       
     1080        segments.erase(segments.begin());
     1081        segments.front().m_StartIndex = firstSegmentStartIndex;
     1082
     1083    }
     1084
     1085    // check to see if the last segment needs to be merged with its neighbour
     1086    if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint())
     1087    {
     1088        int lastSegmentEndIndex = segments.back().m_EndIndex;
     1089        ENSURE(!segments[segments.size()-2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment
     1090
     1091        segments.erase(segments.end());
     1092        segments.back().m_EndIndex = lastSegmentEndIndex;
     1093    }
     1094
     1095    // --------------------------------------------------------------------------------------------------------
     1096    // at this point, every segment should have at least 2 points
     1097    for (size_t i = 0; i < segments.size(); ++i)
     1098    {
     1099        ENSURE(!segments[i].IsSinglePoint());
     1100        ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex);
     1101    }
     1102
     1103}
     1104
     1105void CCmpRallyPoint::RenderSubmit(SceneCollector& collector)
     1106{
     1107    // we only get here if the rally point is set and should be displayed
     1108    for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i)
     1109    {
     1110        if (!m_TexturedOverlayLines[i].m_Coords.empty())
     1111            collector.Submit(&m_TexturedOverlayLines[i]);
     1112    }
     1113
     1114    if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty())
     1115    {
     1116        for (size_t i = 0; i < m_DebugNodeOverlays.size(); i++)
     1117            collector.Submit(&m_DebugNodeOverlays[i]);
     1118    }
     1119}
  • source/simulation2/components/CCmpTerritoryManager.cpp

    diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp
    index 2a87eba..8f06d5a 100644
    a b public:  
    103103
    104104    TerritoryOverlay* m_DebugOverlay;
    105105
     106    bool m_EnableLineDebugOverlays; ///< Enable node debugging overlays for boundary lines?
     107    std::vector<SOverlayLine> m_DebugBoundaryLineNodes;
     108
    106109    virtual void Init(const CParamNode& UNUSED(paramNode))
    107110    {
    108111        m_Territories = NULL;
    public:  
    110113//      m_DebugOverlay = new TerritoryOverlay(*this);
    111114        m_BoundaryLinesDirty = true;
    112115        m_TriggerEvent = true;
    113 
     116        m_EnableLineDebugOverlays = false;
    114117        m_DirtyID = 1;
    115118
    116119        m_AnimTime = 0.0;
    void CCmpTerritoryManager::UpdateBoundaryLines()  
    742745    PROFILE("update boundary lines");
    743746
    744747    m_BoundaryLines.clear();
     748    m_DebugBoundaryLineNodes.clear();
    745749
    746750    if (!CRenderer::IsInitialised())
    747751        return;
    void CCmpTerritoryManager::UpdateBoundaryLines()  
    780784        m_BoundaryLines.push_back(SBoundaryLine());
    781785        m_BoundaryLines.back().connected = boundaries[i].connected;
    782786        m_BoundaryLines.back().color = color;
    783 
    784787        m_BoundaryLines.back().overlay.m_Terrain = terrain;
    785788        m_BoundaryLines.back().overlay.m_TextureBase = textureBase;
    786789        m_BoundaryLines.back().overlay.m_TextureMask = textureMask;
    787790        m_BoundaryLines.back().overlay.m_Color = color;
    788791        m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness;
     792        m_BoundaryLines.back().overlay.m_Closed = true;
    789793
    790         SimRender::SmoothPointsAverage(boundaries[i].points, true);
     794        SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed);
    791795
    792         SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation);
     796        SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation);
    793797
    794798        std::vector<float>& points = m_BoundaryLines.back().overlay.m_Coords;
    795799        for (size_t j = 0; j < boundaries[i].points.size(); ++j)
    796800        {
    797801            points.push_back(boundaries[i].points[j].X);
    798802            points.push_back(boundaries[i].points[j].Y);
     803
     804            if (m_EnableLineDebugOverlays)
     805            {
     806                SOverlayLine overlayNode;
     807                if (j > boundaries[i].points.size() - 1 - 7)
     808                    overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
     809                else if (j < 7)
     810                    overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
     811                else
     812                    overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     813
     814                overlayNode.m_Thickness = 1;
     815                SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true);
     816                m_DebugBoundaryLineNodes.push_back(overlayNode);
     817            }
    799818        }
     819
    800820    }
    801821}
    802822
    void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)  
    825845{
    826846    for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
    827847        collector.Submit(&m_BoundaryLines[i].overlay);
     848   
     849    for( size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i)
     850        collector.Submit(&m_DebugBoundaryLineNodes[i]);
     851
    828852}
    829853
    830854player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
  • new file source/simulation2/components/ICmpRallyPoint.cpp

    diff --git a/source/simulation2/components/ICmpRallyPoint.cpp b/source/simulation2/components/ICmpRallyPoint.cpp
    new file mode 100644
    index 0000000..dee3f27
    - +  
     1/* Copyright (C) 2011 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 "ICmpRallyPoint.h"
     21#include "simulation2/system/InterfaceScripted.h"
     22
     23BEGIN_INTERFACE_WRAPPER(RallyPoint)
     24DEFINE_INTERFACE_METHOD_0("GetPosition", CFixedVector2D, ICmpRallyPoint, GetPosition)
     25DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpRallyPoint, SetPosition, CFixedVector2D)
     26DEFINE_INTERFACE_METHOD_0("IsSet", bool, ICmpRallyPoint, IsSet)
     27DEFINE_INTERFACE_METHOD_0("Unset", void, ICmpRallyPoint, Unset)
     28DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpRallyPoint, SetDisplayed, bool)
     29//DEFINE_INTERFACE_METHOD_0("GetMarkerEntityId", entity_id_t, ICmpRallyPoint, GetMarkerEntityId)
     30END_INTERFACE_WRAPPER(RallyPoint)
  • new file source/simulation2/components/ICmpRallyPoint.h

    diff --git a/source/simulation2/components/ICmpRallyPoint.h b/source/simulation2/components/ICmpRallyPoint.h
    new file mode 100644
    index 0000000..3e019c6
    - +  
     1/* Copyright (C) 2011 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_ICMPRALLYPOINT
     19#define INCLUDED_ICMPRALLYPOINT
     20
     21#include "simulation2/system/Interface.h"
     22#include "simulation2/helpers/Position.h"
     23
     24#include "maths/FixedVector2D.h"
     25
     26/**
     27 * Rally Point.
     28 * Holds the position of a unit's rally point, and renders it to screen.
     29 */
     30class ICmpRallyPoint : public IComponent
     31{
     32public:
     33
     34    virtual CFixedVector2D GetPosition() = 0; ///< Returns the position of the rally point
     35    virtual void SetPosition(CFixedVector2D pos) = 0; ///< Set the position of the rally point
     36    virtual void SetDisplayed(bool displayed) = 0; ///< Set whether the rally point marker and line should be displayed
     37    virtual void Unset() = 0; ///< Unsets the current rally point position
     38    virtual bool IsSet() = 0; ///< Is the rally point position currently set?
     39
     40    DECLARE_INTERFACE_TYPE(RallyPoint)
     41};
     42
     43#endif // INCLUDED_ICMPRALLYPOINT
  • source/simulation2/helpers/Geometry.h

    diff --git a/source/simulation2/helpers/Geometry.h b/source/simulation2/helpers/Geometry.h
    index 0fc046c..4e5ede1 100644
    a b  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2011 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
    class CFixedVector2D;  
    3030namespace Geometry
    3131{
    3232
     33/**
     34 * Returns true if @p point is inside the square with rotated X axis unit vector @p u and rotated Z axis unit vector @p v,
     35 * and half dimensions specified by @p halfSizes. Currently assumes the @p u and @p v vectors are perpendicular. Can also
     36 * be used for rectangles.
     37 *
     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 halfSizes Holds half the dimensions of the shape along the u and v vectors, respectively.
     46 */
    3347bool PointIsInSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    3448
    3549CFixedVector2D GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    3650
    3751fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    3852
     53/**
     54 * Returns the 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
     55 * dimensions @p halfSize, relative to the center of the square. Currently assumes the @p u and @p v vectors are perpendicular.
     56 * Can also be used for rectangles.
     57 *
     58 * @param point point vector of the point we want to get the nearest edge point for, relative to the origin (center) of the shape.
     59 * @param u rotated X axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated,
     60 *          this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta).
     61 * @param v rotated Z axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated,
     62 *          this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta).
     63 * @param halfSizes Holds half the dimensions of the shape along the u and v vectors, respectively.
     64 */
    3965CFixedVector2D NearestPointOnSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    4066
    4167bool TestRaySquare(CFixedVector2D a, CFixedVector2D b, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
  • source/simulation2/helpers/Render.cpp

    diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp
    index db63115..44675e8 100644
    a b  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2011 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
    static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a  
    201201    return p + CVector2D(dp.Y*-offset, dp.X*offset);
    202202}
    203203
    204 void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset)
     204void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples)
    205205{
    206206    PROFILE("InterpolatePointsRNS");
     207    ENSURE(segmentSamples > 0);
    207208
    208209    std::vector<CVector2D> newPoints;
     210    const float fSegmentSamples = (float) segmentSamples;
    209211
    210212    // (This does some redundant computations for adjacent vertices,
    211213    // but it's fairly fast (<1ms typically) so we don't worry about it yet)
    void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed  
    215217    // curve with fewer points
    216218
    217219    size_t n = points.size();
     220
     221    if (closed)
     222    {
    218223    if (n < 1)
    219         return; // can't do anything unless we have two points
     224            return; // we need at least a single point to not crash
     225    }
     226    else
     227    {
     228        if (n < 2)
     229            return; // in non-closed mode, we need at least n=2 to not crash
     230    }
    220231
    221     size_t imax = closed ? n : n-1; // TODO: we probably need to do a bit more to handle non-closed paths
     232    size_t imax = closed ? n : n-1;
     233    newPoints.reserve(imax*segmentSamples);
    222234
    223     newPoints.reserve(imax*4);
     235    // these are primarily used inside the loop, but for open paths we need them outside the loop once to compute the last point
     236    CVector2D a0;
     237    CVector2D a1;
     238    CVector2D a2;
     239    CVector2D a3;
    224240
    225241    for (size_t i = 0; i < imax; ++i)
    226242    {
    227         // Get the relevant points for this spline segment
    228         CVector2D p0 = points[(i-1+n)%n];
     243
     244        // Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points
     245        // before p1 and after p2, respectively; they're needed to compute tangents and whatnot.
     246        CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below
    229247        CVector2D p1 = points[i];
    230248        CVector2D p2 = points[(i+1)%n];
    231         CVector2D p3 = points[(i+2)%n];
     249        CVector2D p3; // normally points[(i+2)%n], but it's a bit more complicated due to open/closed paths -- see below
     250
     251        if (!closed && (i == 0))
     252            // p0's point index is out of bounds, and we can't wrap around because we're in non-closed mode -- create an artificial point
     253            // that extends p1 -> p0 (i.e. the first segment's direction)
     254            p0 = points[0] + (points[0] - points[1]);
     255        else
     256            // standard wrap-around case
     257            p0 = points[(i-1+n)%n]; // careful; don't use (i-1)%n here, as the result is machine-dependent for negative operands (e.g. if i==0, the result could be either -1 or n-1)
     258
     259
     260        if (!closed && (i == n-2))
     261            // p3's point index is out of bounds; create an artificial point that extends p_(n-2) -> p_(n-1) (i.e. the last segment's direction)
     262            // (note that p2's index should not be out of bounds, because in non-closed mode imax is reduced by 1)
     263            p3 = points[n-1] + (points[n-1] - points[n-2]);
     264        else
     265            // standard wrap-around case
     266            p3 = points[(i+2)%n];
     267
    232268
    233269        // Do the RNS computation (based on GPG4 "Nonuniform Splines")
    234270        float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1)
    void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed  
    239275        CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1
    240276
    241277        // Compute standard cubic spline parameters
    242         CVector2D a0 = p1*2 + p2*-2 + v1 + v2;
    243         CVector2D a1 = p1*-3 + p2*3 + v1*-2 + v2*-1;
    244         CVector2D a2 = v1;
    245         CVector2D a3 = p1;
     278        a0 = p1*2 + p2*-2 + v1 + v2;
     279        a1 = p1*-3 + p2*3 + v1*-2 + v2*-1;
     280        a2 = v1;
     281        a3 = p1;
     282
     283        // Interpolate at regular points across the interval
     284        for (int sample = 0; sample < segmentSamples; sample++)
     285            newPoints.push_back(EvaluateSpline(sample/fSegmentSamples, a0, a1, a2, a3, offset));
    246286
    247         // Interpolate at various points
    248         newPoints.push_back(EvaluateSpline(0.f, a0, a1, a2, a3, offset));
    249         newPoints.push_back(EvaluateSpline(1.f/4.f, a0, a1, a2, a3, offset));
    250         newPoints.push_back(EvaluateSpline(2.f/4.f, a0, a1, a2, a3, offset));
    251         newPoints.push_back(EvaluateSpline(3.f/4.f, a0, a1, a2, a3, offset));
    252287    }
    253288
     289    if (!closed)
     290        // if the path is open, we should take care to include the last control point
     291        // NOTE: we can't just do push_back(points[n-1]) here because that ignores the offset
     292        newPoints.push_back(EvaluateSpline(1.f, a0, a1, a2, a3, offset));
     293
    254294    points.swap(newPoints);
    255295}
     296
     297void SimRender::ConstructDashedLine(const std::vector<CVector2D>& keyPoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength)
     298{
     299    // sanity checks
     300    if (dashLength <= 0)
     301        return;
     302
     303    if (blankLength <= 0)
     304        return;
     305
     306    if (keyPoints.size() < 2)
     307        return;
     308
     309    dashedLineOut.m_Points.clear();
     310    dashedLineOut.m_StartIndices.clear();
     311
     312    // walk the line, counting the total length so far at each node point. When the length exceeds dashLength, cut the last segment at the
     313    // required length and continue for blankLength along the line to start a new dash segment.
     314
     315    // TODO: we should probably extend this function to also allow for closed lines. I was thinking of slightly scaling the dash/blank length
     316    // so that it fits the length of the curve, but that requires knowing the length of the curve upfront which is sort of expensive to compute
     317    // (O(n) and lots of square roots).
     318
     319    bool buildingDash = true; // true if we're building a dash, false if a blank
     320    float curDashLength = 0; // builds up the current dash/blank's length as we walk through the line nodes
     321    CVector2D dashLastPoint = keyPoints[0]; // last point of the current dash/blank being built.
     322
     323    // register the first starting node of the first dash
     324    dashedLineOut.m_Points.push_back(keyPoints[0]);
     325    dashedLineOut.m_StartIndices.push_back(0);
     326
     327    // index of the next key point on the path. Must always point to a node that is further along the path than dashLastPoint, so we can
     328    // properly take a direction vector along the path.
     329    size_t i = 0;
     330
     331    while(i < keyPoints.size() - 1)
     332    {
     333        // get length of this segment
     334        CVector2D segmentVector = keyPoints[i + 1] - dashLastPoint; // vector from our current point along the path to nextNode
     335        float segmentLength = segmentVector.Length();
     336
     337        float targetLength = (buildingDash ? dashLength : blankLength);
     338        if (curDashLength + segmentLength > targetLength)
     339        {
     340            // segment is longer than the dash length we still have to go, so we'll need to cut it; create a cut point along the segment
     341            // line that is of just the required length to complete the dash, then make it the base point for the next dash/blank.
     342            float cutLength = targetLength - curDashLength;
     343            CVector2D cutPoint = dashLastPoint + (segmentVector.Normalized() * cutLength);
     344
     345            // start a new dash or blank in the next iteration
     346            curDashLength = 0;
     347            buildingDash = !buildingDash; // flip from dash to blank and vice-versa
     348            dashLastPoint = cutPoint;
     349
     350            // don't increment i, we haven't fully traversed this segment yet so we still need to use the same point to take the
     351            // direction vector with in the next iteration
     352
     353            // this cut point is either the end of the current dash or the beginning of a new dash; either way, we're gonna need it
     354            // in the points array.
     355            dashedLineOut.m_Points.push_back(cutPoint);
     356
     357            if (buildingDash)
     358            {
     359                // if we're gonna be building a new dash, then cutPoint is now the base point of that new dash, so let's register its
     360                // index as a start index of a dash.
     361                dashedLineOut.m_StartIndices.push_back(dashedLineOut.m_Points.size() - 1);
     362            }
     363
     364        }
     365        else
     366        {
     367            // the segment from lastDashPoint to keyPoints[i+1] doesn't suffice to complete the dash, so we need to add keyPoints[i+1]
     368            // to this dash's points and continue from there
     369
     370            if (buildingDash)
     371                // still building the dash, add it to the output (we don't need to store the blanks)
     372                dashedLineOut.m_Points.push_back(keyPoints[i+1]);
     373
     374            curDashLength += segmentLength;
     375            dashLastPoint = keyPoints[i+1];
     376            i++;
     377
     378        }
     379
     380    }
     381
     382}
  • source/simulation2/helpers/Render.h

    diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h
    index 0129b01..b991ed9 100644
    a b  
    2323 * Helper functions related to rendering
    2424 */
    2525
     26#include "maths/Vector2D.h"
     27
    2628class CSimContext;
    27 class CVector2D;
    2829struct SOverlayLine;
    2930
     31
     32
     33struct SDashedLine
     34{
     35    std::vector<CVector2D> m_Points; ///< Packed array of consecutive dashes' points. Use m_StartIndices to navigate it.
     36
     37    /**
     38     * Start indices in m_Points of each dash. Dash n starts at point m_StartIndices[n] and ends at the point with index
     39     * m_StartIndices[n+1] - 1, or at the end of the m_Points vector. Use the GetEndIndex(n) convenience method to abstract away the
     40     * difference and get the (exclusive) end index of dash n.
     41     */
     42    std::vector<size_t> m_StartIndices;
     43
     44    /// Returns the (exclusive) end point index (i.e. index within m_Points) of dash n.
     45    size_t GetEndIndex(size_t i)
     46    {
     47        // for the last dash, there is no next starting index, so we need to use the end index of the m_Points array instead
     48        return (i < m_StartIndices.size() - 1 ? m_StartIndices[i+1] : m_Points.size());
     49    }
     50};
     51
    3052namespace SimRender
    3153{
    3254
    void SmoothPointsAverage(std::vector<CVector2D>& points, bool closed);  
    6890 * the direction of the curve.
    6991 * If @p closed then the points are treated as a closed path (the last is connected
    7092 * to the first).
     93 * @param segmentSamples Amount of intermediate points to sample between every two control points.
     94 */
     95void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples = 4);
     96
     97/**
     98 * Creates a dashed line from the line specified by @points so that each dash is of length
     99 * @p dashLength, and each blank inbetween is of length @p blankLength. The dashed line returned as a list of smaller lines
     100 * in @p dashedLineOut.
     101 *
     102 * @param dashLength Length of a single dash. Must be strictly positive.
     103 * @param blankLength Length of a single blank between dashes. Must be strictly positive.
    71104 */
    72 void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset);
     105void ConstructDashedLine(const std::vector<CVector2D>& linePoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength);
    73106
    74107} // namespace
    75108