Ticket #524: rallypoints_06dec11.patch

File rallypoints_06dec11.patch, 126.7 KB (added by vts, 12 years ago)
  • .gitignore

    diff --git a/.gitignore b/.gitignore
    index e82cae3..8ade2ca 100644
    a b  
    44docs/
    55patches/
    66binaries/data/mods/public/art/textures/misc/*.psd
     7binaries/system/ActorEditor.exe
    78binaries/system/*.pdb
    89binaries/system/*.ilk
    910binaries/system/*.exp
  • 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..4ac1a0c 100644
    a b  
    88    </variant>
    99  </group>
    1010  <group>
    11     <variant name="waypoint_hellenes">
     11    <variant name="hele">
    1212      <texture>props/banner_greek.png</texture>
    1313    </variant>
     14    <variant name="pers">
     15      <texture>props/banner_persian.png</texture>
     16    </variant>
     17    <variant name="celt">
     18      <texture>props/banner_celt.png</texture>
     19    </variant>
     20    <variant name="cart">
     21      <texture>props/banner_carthage.png</texture>
     22    </variant>
     23    <variant name="iber">
     24      <texture>props/banner_iberians.png</texture>
     25    </variant>
     26    <variant name="rome">
     27      <texture>props/banner_romans.png</texture>
     28    </variant>
    1429  </group>
    1530</actor>
  • binaries/data/mods/public/gui/session/input.js

    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 0000000..55c1100
    Binary files /dev/null and b/binaries/data/mods/public/art/textures/misc/rallypoint_line.png differ
    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 0000000..b1824fc
    Binary files /dev/null and b/binaries/data/mods/public/art/textures/misc/rallypoint_line_mask.png differ
    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 0000000..31a341a
    Binary files /dev/null and b/binaries/data/mods/public/art/textures/ui/session/icons/single/focus-rally.png differ
    diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
    index 9a7068f..7ece473 100644
    a b function getActionInfo(action, target)  
    135135        return entState && entState.rallyPoint;
    136136    });
    137137
    138 
    139138    if (!target)
    140139    {
    141140        if (action == "set-rallypoint" && haveRallyPoints)
    function performCommand(entity, commandName)  
    11151114            case "unload-all":
    11161115                unloadAll(entity);
    11171116                break;
     1117            case "focus-rally":
     1118                // if the selected building has a rally point set, move the camera to it; otherwise, move to the building itself
     1119                // (since that's where units will spawn without a rally point)
     1120                var focusTarget = null;
     1121                if (entState.rallyPoint && entState.rallyPoint.position)
     1122                {
     1123                    focusTarget = entState.rallyPoint.position;
     1124                }
     1125                else
     1126                {
     1127                    if (entState.position)
     1128                        focusTarget = entState.position;
     1129                }
     1130               
     1131                if (focusTarget !== null)
     1132                    Engine.CameraMoveTo(focusTarget.x, focusTarget.z);
     1133               
     1134                break;
    11181135            default:
    11191136                break;
    11201137            }
  • 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 18f10f6..52c7771 100644
    a b function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    270270                break;
    271271
    272272            case COMMAND:
    273                 if (item == "unload-all")
     273                // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single)
     274                if (item.name == "unload-all")
    274275                {
    275276                    var count = unitEntState.garrisonHolder.entities.length;
    276277                    getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 0 ? count : "");
    function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    280281                    getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = "";
    281282                }
    282283
    283                 tooltip = toTitleCase(item);
     284                tooltip = (item.tooltip ? item.tooltip : toTitleCase(item.name));
    284285                break;
    285286
    286287            default:
    function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    334335        {
    335336            //icon.cell_id = i;
    336337            //icon.cell_id = getCommandCellId(item);
    337             icon.sprite = "stretched:session/icons/single/" + getCommandImage(item);
     338            icon.sprite = "stretched:session/icons/single/" + item.icon;
    338339
    339340        }
    340341        else if (template.icon)
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    477478        var commands = getEntityCommandsList(entState);
    478479        if (commands.length)
    479480            setupUnitPanel("Command", usedPanels, entState, commands,
    480                 function (item) { performCommand(entState.id, item); } );
     481                function (item) { performCommand(entState.id, item.name); } );
    481482
    482483        if (entState.garrisonHolder)
    483484        {
  • 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 1cb89da..f723baa 100644
    a b function getFormationCellId(formationName)  
    189189    }
    190190}
    191191
    192 function getCommandImage(commandName)
    193 {
    194     switch (commandName)
    195     {
    196     case "delete":
    197         return "kill_small.png";
    198     case "unload-all":
    199         return "garrison-out.png";
    200     case "garrison":
    201         return "garrison.png";
    202     case "repair":
    203         return "repair.png";
    204     default:
    205         return "";
    206     }
    207 }
    208 
    209192function getEntityFormationsList(entState)
    210193{
    211194    var civ = g_Players[entState.player].civ;
    function getEntityCommandsList(entState)  
    234217{
    235218    var commands = [];
    236219    if (entState.garrisonHolder)
    237         commands.push("unload-all");
     220    {
     221        commands.push({
     222            "name": "unload-all",
     223            "tooltip": "Unload All",
     224            "icon": "garrison-out.png"
     225        });
     226    }
     227   
     228    commands.push({
     229        "name": "delete",
     230        "tooltip": "Delete",
     231        "icon": "kill_small.png"
     232    });
     233   
    238234    if (isUnit(entState))
    239         commands.push("garrison");
     235    {
     236        commands.push({
     237            "name": "garrison",
     238            "tooltip": "Garrison",
     239            "icon": "garrison.png"
     240        });
     241    }
     242   
    240243    if (entState.buildEntities)
    241         commands.push("repair");
    242     commands.push("delete");
     244    {
     245        commands.push({
     246            "name": "repair",
     247            "tooltip": "Repair",
     248            "icon": "repair.png"
     249        });
     250    }
     251   
     252    if (entState.rallyPoint)
     253    {
     254        commands.push({
     255            "name": "focus-rally",
     256            "tooltip": "Focus on Rally Point",
     257            "icon": "focus-rally.png"
     258        });
     259    }
     260   
    243261    return commands;
    244262}
    245263
  • 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..fd78472 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/GuiInterface.js

    diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js
    index 8565f82..7e8440f 100644
    a b GuiInterface.prototype.Init = function()  
    2626/*
    2727 * All of the functions defined below are called via Engine.GuiInterfaceCall(name, arg)
    2828 * from GUI scripts, and executed here with arguments (player, arg).
     29 *
     30 * CAUTION: The input to the functions in this module is not network-synchronised, so it
     31 * mustn't affect the simulation state (i.e. the data that is serialised and can affect
     32 * the behaviour of the rest of the simulation) else it'll cause out-of-sync errors.
    2933 */
    3034
    3135/**
    GuiInterface.prototype.GetEntityState = function(player, ent)  
    223227    var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
    224228    if (cmpRallyPoint)
    225229    {
    226         ret.rallyPoint = { };
     230        ret.rallyPoint = {'position': cmpRallyPoint.GetPosition()}; // undefined or {x,z} object
    227231    }
    228232
    229233    var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
    GuiInterface.prototype.SetStatusBars = function(player, cmd)  
    418422};
    419423
    420424/**
    421  * Displays the rally point of a building
     425 * Displays the rally point of a given list of entities (carried in cmd.entities).
     426 *
     427 * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
     428 * be rendered, in order to support instantaneously rendering a rally point marker at a specified location
     429 * instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
     430 * If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
     431 * RallyPoint component.
    422432 */
    423433GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
    424434{
    425     // If there are rally points already displayed, destroy them
    426     for each (var ent in this.rallyPoints)
     435    // If there are some rally points already displayed, first hide them
     436    for each (var ent in this.entsRallyPointsDisplayed)
    427437    {
    428         // Hide it first (the destruction won't be instantaneous)
    429         var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    430         cmpPosition.MoveOutOfWorld();
    431 
    432         Engine.DestroyEntity(ent);
     438        var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
     439        if (cmpRallyPointRenderer)
     440            cmpRallyPointRenderer.SetDisplayed(false);
    433441    }
    434442
    435     this.rallyPoints = [];
    436 
    437     var positions = [];
    438     // DisplayRallyPoints is called passing a list of entities for which
    439     // rally points must be displayed
     443    // Show the rally points for the passed entities
    440444    for each (var ent in cmd.entities)
    441445    {
     446        var cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
     447        if (!cmpRallyPointRenderer)
     448            continue;
     449       
     450        // entity must have a rally point component to display a rally point marker
     451        // (regardless of whether cmd specifies a custom location)
    442452        var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
    443453        if (!cmpRallyPoint)
    444454            continue;
    GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)  
    452462        // override the real rally point position; otherwise use the real position
    453463        var pos;
    454464        if (cmd.x && cmd.z)
    455             pos = {"x": cmd.x, "z": cmd.z};
     465            pos = cmd;
    456466        else
    457             pos = cmpRallyPoint.GetPosition();
     467            pos = cmpRallyPoint.GetPosition(); // may return undefined
    458468
    459469        if (pos)
    460         {
    461             // TODO: it'd probably be nice if we could draw some kind of line
    462             // between the building and pos, to make the marker easy to find even
    463             // if it's a long way from the building
     470            cmpRallyPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
    464471
    465             positions.push(pos);
    466         }
     472        cmpRallyPointRenderer.SetDisplayed(true);
    467473    }
    468474
    469     // Add rally point entity for each building
    470     for each (var pos in positions)
    471     {
    472         var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml");
    473         var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position);
    474         cmpPosition.JumpTo(pos.x, pos.z);
    475         this.rallyPoints.push(rallyPoint);
    476     }   
     475    // Remember which entities have their rally points displayed so we can hide them again
     476    this.entsRallyPointsDisplayed = cmd.entities;
    477477};
    478478
    479479/**
  • 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
    index 3c3816a..80f8ffe 100644
    a b RallyPoint.prototype.SetPosition = function(x, z)  
    1313    this.pos = {
    1414        "x": x,
    1515        "z": z
    16     }
     16    };
    1717};
    1818
    1919RallyPoint.prototype.Unset = function()
  • 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/structures/cart_super_dock.xml

    diff --git a/binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml b/binaries/data/mods/public/simulation/templates/structures/cart_super_dock.xml
    index 71568df..c41969f 100644
    a b  
    3434  <Position>
    3535    <Floating>true</Floating>
    3636  </Position>
     37  <RallyPointRenderer>
     38    <LinePassabilityClass>ship</LinePassabilityClass>
     39  </RallyPointRenderer>
    3740  <Sound>
    3841    <SoundGroups>
    3942      <select>interface/select/building/sel_dock.xml</select>
  • 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..a4733d4 100644
    a b  
    5353    <DisableBlockPathfinding>false</DisableBlockPathfinding>
    5454  </Obstruction>
    5555  <OverlayRenderer/>
    56   <RallyPoint/>
     56  <RallyPoint />
     57  <RallyPointRenderer>
     58    <MarkerTemplate>special/rallypoint</MarkerTemplate>
     59    <LineTexture>art/textures/misc/rallypoint_line.png</LineTexture>
     60    <LineTextureMask>art/textures/misc/rallypoint_line_mask.png</LineTextureMask>
     61    <LineThickness>0.2</LineThickness>
     62    <LineColour r="35" g="86" b="188" />
     63    <LineDashColour r="158" g="11" b="15" />
     64    <LineStartCap>square</LineStartCap>
     65    <LineEndCap>round</LineEndCap>
     66    <LineCostClass>default</LineCostClass>
     67    <LinePassabilityClass>default</LinePassabilityClass>
     68  </RallyPointRenderer>
    5769  <Sound>
    5870    <SoundGroups>
    5971      <select>interface/select/building/sel_universal.xml</select>
  • binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml

    diff --git a/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml b/binaries/data/mods/public/simulation/templates/template_structure_military_dock.xml
    index 74ab503..abda9bf 100644
    a b  
    3636  <Position>
    3737    <Floating>true</Floating>
    3838  </Position>
     39  <RallyPointRenderer>
     40    <LinePassabilityClass>ship</LinePassabilityClass>
     41  </RallyPointRenderer>
    3942  <ResourceDropsite>
    4043    <Types>food wood stone metal</Types>
    4144  </ResourceDropsite>
  • source/graphics/GameView.cpp

    diff --git a/binaries/system/ActorEditor.exe b/binaries/system/ActorEditor.exe
    deleted file mode 100644
    index 15cdfc5..0000000
    Binary files a/binaries/system/ActorEditor.exe and /dev/null differ
    diff --git a/source/graphics/GameView.cpp b/source/graphics/GameView.cpp
    index 6e0c2f5..f17d2b0 100644
    a b void CGameView::Update(float DeltaTime)  
    899899    m->ViewCamera.UpdateFrustum();
    900900}
    901901
    902 void CGameView::MoveCameraTarget(const CVector3D& target, bool minimap)
     902void CGameView::MoveCameraTarget(const CVector3D& target)
    903903{
    904904    // Maintain the same orientation and level of zoom, if we can
    905905    // (do this by working out the point the camera is looking at, saving
    void CGameView::MoveCameraTarget(const CVector3D& target, bool minimap)  
    912912    CVector3D pivot = targetCam.GetFocus();
    913913    CVector3D delta = target - pivot;
    914914   
    915     //If minimap movement, maintain previous zoom level by not changing Y position
    916     //  - this prevents strange behavior when moving across changes in terrain height
    917     if (!minimap)
    918         m->PosY.SetValueSmoothly(delta.Y + m->PosY.GetValue());
    919 
    920915    m->PosX.SetValueSmoothly(delta.X + m->PosX.GetValue());
    921916    m->PosZ.SetValueSmoothly(delta.Z + m->PosZ.GetValue());
    922917
  • source/graphics/GameView.h

    diff --git a/source/graphics/GameView.h b/source/graphics/GameView.h
    index 0811cda..f7bb96c 100644
    a b public:  
    7979
    8080    InReaction HandleEvent(const SDL_Event_* ev);
    8181
    82     void MoveCameraTarget(const CVector3D& target, bool minimap = false);
     82    void MoveCameraTarget(const CVector3D& target);
    8383    void ResetCameraTarget(const CVector3D& target);
    8484    void ResetCameraAngleZoom();
    8585    void CameraFollow(entity_id_t entity, bool firstPerson);
  • new file source/graphics/Overlay.cpp

    diff --git a/source/graphics/Overlay.cpp b/source/graphics/Overlay.cpp
    new file mode 100644
    index 0000000..7e52063
    - +  
     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 "ps/CStr.h"
     21#include "Overlay.h"
     22
     23SOverlayTexturedLine::LineCapType SOverlayTexturedLine::StrToLineCapType(const std::wstring& str)
     24{
     25    if (str == L"round")
     26        return LINECAP_ROUND;
     27    else if (str == L"sharp")
     28        return LINECAP_SHARP;
     29    else if (str == L"square")
     30        return LINECAP_SQUARE;
     31    else if (str == L"flat")
     32        return LINECAP_FLAT;
     33    else {
     34        debug_warn(L"[Overlay] Unrecognized line cap type identifier");
     35        return LINECAP_FLAT;
     36    }
     37}
  • source/graphics/Overlay.h

    diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h
    index da47d19..0e0dc29 100644
    a b struct SOverlayLine  
    4949 */
    5050struct SOverlayTexturedLine
    5151{
    52     SOverlayTexturedLine() : m_Terrain(NULL), m_Thickness(1.0f) { }
     52
     53    enum LineCapType
     54    {
     55        LINECAP_FLAT, ///< no line ending; abrupt stop of the line (aka. butt ending)
     56
     57        /**
     58         * Semi-circular line ending. The texture is mapped by curving the left vertical edge around the semi-circle's rim. That is,
     59         * 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
     60         * from 0 to 1 as the rim is traversed.
     61         */
     62        LINECAP_ROUND,
     63        LINECAP_SHARP, ///< sharp point ending
     64        LINECAP_SQUARE, ///< square end that extends half the line width beyond the line end
     65    };
     66
     67    SOverlayTexturedLine()
     68        : m_Terrain(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), m_StartCapType(LINECAP_FLAT), m_EndCapType(LINECAP_FLAT)
     69    {}
    5370
    5471    CTerrain* m_Terrain;
    5572    CTexturePtr m_TextureBase;
    5673    CTexturePtr m_TextureMask;
    57     CColor m_Color;
    58     std::vector<float> m_Coords; // (x, z) vertex coordinate pairs; y is computed automatically; shape is automatically closed
    59     float m_Thickness; // world-space units
     74    CColor m_Color; ///< Color to apply to the line texture
     75    std::vector<float> m_Coords; ///< (x, z) vertex coordinate pairs; y is computed automatically
     76    float m_Thickness; ///< Half-width of the line, in world-space units
     77
     78    bool m_Closed; ///< Should this line be treated as a closed loop? (if set, the end cap settings are ignored)
     79    bool m_AlwaysVisible; ///< Should this line be rendered even under the SoD?
     80    LineCapType m_StartCapType; ///< LineCapType to be used at the start of the line
     81    LineCapType m_EndCapType; ///< LineCapType to be used at the end of the line
     82
     83    shared_ptr<CRenderData> m_RenderData; ///< Cached renderer data (shared_ptr so that copies/deletes are automatic)
     84
     85    /**
     86     * Converts a string line cap type into its corresponding LineCap enum value, and returns the resulting value.
     87     * If the input string is unrecognized, a warning is issued and a default value is returned.
     88     */
     89    static LineCapType StrToLineCapType(const std::wstring& str);
    6090
    61     shared_ptr<CRenderData> m_RenderData; // cached renderer data (shared_ptr so that copies/deletes are automatic)
    6291};
    6392
    6493/**
  • source/graphics/ShaderManager.cpp

    diff --git a/source/graphics/ShaderManager.cpp b/source/graphics/ShaderManager.cpp
    index ba270f0..599bac9 100644
    a b bool CShaderManager::NewProgram(const char* name, const std::map<CStr, CStr>& ba  
    8080
    8181    if (strncmp(name, "fixed:", 6) == 0)
    8282    {
    83         program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6));
     83        program = CShaderProgramPtr(CShaderProgram::ConstructFFP(name+6, baseDefines));
    8484        if (!program)
    8585            return false;
    8686        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..f2caf26 100644
    a b class CShaderProgramFFP_OverlayLine : public CShaderProgramFFP  
    100100        ID_objectColor
    101101    };
    102102
     103    bool m_IgnoreLos;
     104
    103105public:
    104     CShaderProgramFFP_OverlayLine() :
     106    CShaderProgramFFP_OverlayLine(const std::map<CStr, CStr>& defines) :
    105107        CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1)
    106108    {
    107109        m_UniformIndexes["losTransform"] = ID_losTransform;
    public:  
    111113        m_UniformIndexes["baseTex"] = 0;
    112114        m_UniformIndexes["maskTex"] = 1;
    113115        m_UniformIndexes["losTex"] = 2;
     116
     117        m_IgnoreLos = (defines.find(CStr("IGNORE_LOS")) != defines.end());
     118    }
     119
     120    bool IsIgnoreLos()
     121    {
     122        return m_IgnoreLos;
    114123    }
    115124
    116125    virtual void Uniform(Binding id, float v0, float v1, float v2, float v3)
    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
    191         glEnable(GL_TEXTURE_GEN_S);
    192         glEnable(GL_TEXTURE_GEN_T);
    193         glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    194         glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
    195         // Uniform() sets GL_OBJECT_PLANE values
    196 
    197         glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
    198         glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
    199         glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
    200         glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
    201         glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_ALPHA);
     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
     215            glEnable(GL_TEXTURE_GEN_S);
     216            glEnable(GL_TEXTURE_GEN_T);
     217            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
     218            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
     219            // Uniform() sets GL_OBJECT_PLANE values
     220           
     221            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE);
     222            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS);
     223            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR);
     224            glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_TEXTURE);
     225            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:  
    221248    }
    222249};
    223250
    224 /*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id)
     251/*static*/ CShaderProgram* CShaderProgram::ConstructFFP(const std::string& id, const std::map<CStr, CStr>& defines)
    225252{
    226253    if (id == "overlayline")
    227         return new CShaderProgramFFP_OverlayLine();
     254        return new CShaderProgramFFP_OverlayLine(defines);
    228255
    229256    LOGERROR(L"CShaderProgram::ConstructFFP: Invalid id '%hs'", id.c_str());
    230257    return NULL;
  • source/gui/MiniMap.cpp

    diff --git a/source/gui/MiniMap.cpp b/source/gui/MiniMap.cpp
    index 05e234e..97f05cc 100644
    a b void CMiniMap::SetCameraPos()  
    160160    CVector3D target;
    161161    GetMouseWorldCoordinates(target.X, target.Z);
    162162    target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
    163     g_Game->GetView()->MoveCameraTarget(target, true);
     163    g_Game->GetView()->MoveCameraTarget(target);
    164164}
    165165
    166166float CMiniMap::GetAngle()
  • source/gui/scripting/ScriptFunctions.cpp

    diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
    index 0fc6ddb..5e1afcb 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)  
    400401        g_Game->GetView()->CameraFollow(entityid, true);
    401402}
    402403
     404/// Move camera to a 2D location
     405void CameraMoveTo(void* UNUSED(cbdata), entity_pos_t x, entity_pos_t z)
     406{
     407    // called from JS; must not fail
     408    if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
     409        return;
     410
     411    CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
     412
     413    CVector3D target;
     414    target.X = x.ToFloat();
     415    target.Z = z.ToFloat();
     416    target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
     417
     418    g_Game->GetView()->MoveCameraTarget(target);
     419}
     420
    403421entity_id_t GetFollowedEntity(void* UNUSED(cbdata))
    404422{
    405423    if (g_Game && g_Game->GetView())
    void QuickLoad(void* UNUSED(cbdata))  
    519537{
    520538    g_Game->GetTurnManager()->QuickLoad();
    521539}
     540
    522541void SetBoundingBoxDebugOverlay(void* UNUSED(cbdata), bool enabled)
    523542{
    524543    ICmpSelectable::ms_EnableDebugOverlays = enabled;
    void GuiScriptingInit(ScriptInterface& scriptInterface)  
    574593    scriptInterface.RegisterFunction<CScriptVal, &GetMapSettings>("GetMapSettings");
    575594    scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollow>("CameraFollow");
    576595    scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollowFPS>("CameraFollowFPS");
     596    scriptInterface.RegisterFunction<void, entity_pos_t, entity_pos_t, &CameraMoveTo>("CameraMoveTo");
    577597    scriptInterface.RegisterFunction<entity_id_t, &GetFollowedEntity>("GetFollowedEntity");
    578598    scriptInterface.RegisterFunction<bool, std::string, &HotkeyIsPressed_>("HotkeyIsPressed");
    579599    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 4e49103..34a818c 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"
     23#include "maths/Quaternion.h"
     24#include "maths/Vector2D.h"
    2225#include "graphics/LOSTexture.h"
    2326#include "graphics/Overlay.h"
    24 #include "graphics/ShaderManager.h"
    2527#include "graphics/Terrain.h"
    2628#include "graphics/TextureManager.h"
    2729#include "lib/ogl.h"
    class CTexturedLineRData : public CRenderData  
    4446{
    4547public:
    4648    CTexturedLineRData(SOverlayTexturedLine* line) :
    47         m_Line(line), m_VB(NULL), m_VBIndices(NULL)
    48     {
    49     }
     49        m_Line(line), m_VB(NULL), m_VBIndices(NULL), m_Raise(.2f)
     50    { }
    5051
    5152    ~CTexturedLineRData()
    5253    {
    public:  
    5859
    5960    struct SVertex
    6061    {
    61         SVertex(CVector3D pos, short u, short v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
     62        SVertex(CVector3D pos, float u, float v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
    6263        CVector3D m_Position;
    63         GLshort m_UVs[2];
     64        GLfloat m_UVs[2];
     65        float _padding[3]; // 5 floats up till now, so pad with another 3 floats to get a power of 2
    6466    };
    65     cassert(sizeof(SVertex) == 16);
     67    cassert(sizeof(SVertex) == 32);
    6668
    6769    void Update();
    6870
    69     SOverlayTexturedLine* m_Line;
     71    /**
     72     * Creates a line cap of the specified type @p endCapType at the end of the segment going in direction @p normal, and appends
     73     * the vertices to @p verticesOut in GL_TRIANGLES order.
     74     *
     75     * @param corner1 One of the two butt-end corner points of the line to which the cap should be attached.
     76     * @param corner2 One of the two butt-end corner points of the line to which the cap should be attached.
     77     * @param normal Normal vector indicating the direction of the segment to which the cap should be attached.
     78     * @param endCapType The type of end cap to produce.
     79     * @param verticesOut Output vector of vertices for passing to the renderer.
     80     * @param indicesOut Output vector of vertex indices for passing to the renderer.
     81     */
     82    void CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal,
     83                       SOverlayTexturedLine::LineCapType endCapType, std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut);
     84
     85    /// Small utility function; grabs the centroid of the positions of two vertices
     86    inline CVector3D Centroid(const SVertex& v1, const SVertex& v2)
     87    {
     88        return (v1.m_Position + v2.m_Position) * 0.5;
     89    }
    7090
     91    SOverlayTexturedLine* m_Line;
    7192    CVertexBuffer::VBChunk* m_VB;
    7293    CVertexBuffer::VBChunk* m_VBIndices;
     94
     95    float m_Raise; // small vertical offset of line from terrain to prevent visual glitches
    7396};
    7497
    7598OverlayRenderer::OverlayRenderer()
    void OverlayRenderer::RenderOverlaysAfterWater()  
    180203        else
    181204            shaderName = "fixed:overlayline";
    182205
    183         CShaderManager& shaderManager = g_Renderer.GetShaderManager();
    184         CShaderProgramPtr shaderTexLine(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>()));
    185 
    186         shaderTexLine->Bind();
    187 
    188         int streamflags = shaderTexLine->GetStreamFlags();
    189 
    190         if (streamflags & STREAM_POS)
    191             glEnableClientState(GL_VERTEX_ARRAY);
    192 
    193         if (streamflags & STREAM_UV0)
    194         {
    195             pglClientActiveTextureARB(GL_TEXTURE0);
    196             glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    197         }
    198 
    199         if (streamflags & STREAM_UV1)
    200         {
    201             pglClientActiveTextureARB(GL_TEXTURE1);
    202             glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    203         }
     206        std::map<CStr, CStr> defAlwaysVisible;
     207        defAlwaysVisible.insert(std::make_pair(CStr("IGNORE_LOS"), CStr("1")));
    204208
    205209        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);
    208210
    209         for (size_t i = 0; i < m->texlines.size(); ++i)
    210         {
    211             SOverlayTexturedLine* line = m->texlines[i];
    212             if (!line->m_RenderData)
    213                 continue;
     211        CShaderManager& shaderManager = g_Renderer.GetShaderManager();
     212        CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, std::map<CStr, CStr>()));
     213        CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, defAlwaysVisible));
    214214
    215             shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle());
    216             shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle());
    217             shaderTexLine->Uniform("objectColor", line->m_Color);
     215        // ----------------------------------------------------------------------------------------
    218216
    219             CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get());
     217        shaderTexLineNormal->Bind();
     218        shaderTexLineNormal->BindTexture("losTex", los.GetTexture());
     219        shaderTexLineNormal->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
    220220
    221             GLsizei stride = sizeof(CTexturedLineRData::SVertex);
    222             CTexturedLineRData::SVertex* base = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());
     221        // batch render only the non-always-visible overlay lines using the normal shader
     222        RenderTexturedOverlayLines(shaderTexLineNormal, false);
    223223
    224             if (streamflags & STREAM_POS)
    225                 glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]);
     224        shaderTexLineNormal->Unbind();
    226225
    227             if (streamflags & STREAM_UV0)
    228             {
    229                 pglClientActiveTextureARB(GL_TEXTURE0);
    230                 glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);
    231             }
     226        // ----------------------------------------------------------------------------------------
    232227
    233             if (streamflags & STREAM_UV1)
    234             {
    235                 pglClientActiveTextureARB(GL_TEXTURE1);
    236                 glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);
    237             }
    238 
    239             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);
     228        shaderTexLineAlwaysVisible->Bind();
     229        // TODO: losTex and losTransform are unused in the always visible shader, but I'm not sure if it's worthwhile messing
     230        // with it just to remove these calls
     231        shaderTexLineAlwaysVisible->BindTexture("losTex", los.GetTexture());
     232        shaderTexLineAlwaysVisible->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
    241233
    242             g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count - 2;
    243         }
     234        // batch render only the always-visible overlay lines using the LoS-ignored shader
     235        RenderTexturedOverlayLines(shaderTexLineAlwaysVisible, true);
    244236
    245         shaderTexLine->Unbind();
     237        shaderTexLineAlwaysVisible->Unbind();
    246238
    247239        // TODO: the shader should probably be responsible for unbinding its textures
    248240        g_Renderer.BindTexture(1, 0);
    void OverlayRenderer::RenderOverlaysAfterWater()  
    260252    }
    261253}
    262254
     255void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shaderTexLine, bool alwaysVisible)
     256{
     257    int streamflags = shaderTexLine->GetStreamFlags();
     258
     259    if (streamflags & STREAM_POS)
     260        glEnableClientState(GL_VERTEX_ARRAY);
     261
     262    if (streamflags & STREAM_UV0)
     263    {
     264        pglClientActiveTextureARB(GL_TEXTURE0);
     265        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
     266    }
     267
     268    if (streamflags & STREAM_UV1)
     269    {
     270        pglClientActiveTextureARB(GL_TEXTURE1);
     271        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
     272    }
     273
     274    for (size_t i = 0; i < m->texlines.size(); ++i)
     275    {
     276        SOverlayTexturedLine* line = m->texlines[i];
     277
     278        // render only those lines matching the requested alwaysVisible status
     279        if (!line->m_RenderData || line->m_AlwaysVisible != alwaysVisible)
     280            continue;
     281
     282        shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle());
     283        shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle());
     284        shaderTexLine->Uniform("objectColor", line->m_Color);
     285
     286        CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get());
     287        if (!rdata->m_VB || !rdata->m_VBIndices)
     288            continue; // might have failed to allocate
     289
     290        // -- render main line quad strip ----------------------
     291
     292        GLsizei stride = sizeof(CTexturedLineRData::SVertex);
     293        CTexturedLineRData::SVertex* vertexBase = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());
     294
     295        if (streamflags & STREAM_POS)
     296            glVertexPointer(3, GL_FLOAT, stride, &vertexBase->m_Position[0]);
     297
     298        if (streamflags & STREAM_UV0)
     299        {
     300            pglClientActiveTextureARB(GL_TEXTURE0);
     301            glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]);
     302        }
     303
     304        if (streamflags & STREAM_UV1)
     305        {
     306            pglClientActiveTextureARB(GL_TEXTURE1);
     307            glTexCoordPointer(2, GL_FLOAT, stride, &vertexBase->m_UVs[0]);
     308        }
     309
     310        u8* indexBase = rdata->m_VBIndices->m_Owner->Bind();
     311        glDrawElements(GL_TRIANGLES, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index);
     312        g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count/3;
     313
     314    }
     315
     316}
     317
    263318void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)
    264319{
    265320    PROFILE3_GPU("overlays (fg)");
    void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera)  
    293348        };
    294349
    295350        glVertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X);
    296 
    297351        glDrawArrays(GL_QUADS, 0, (GLsizei)4);
    298352    }
    299353
    void CTexturedLineRData::Update()  
    312366        g_VBMan.Release(m_VB);
    313367        m_VB = NULL;
    314368    }
    315 
    316369    if (m_VBIndices)
    317370    {
    318371        g_VBMan.Release(m_VBIndices);
    319372        m_VBIndices = NULL;
    320373    }
    321374
     375    CTerrain* terrain = m_Line->m_Terrain;
    322376    CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
    323377
     378    float v = 0.f;
    324379    std::vector<SVertex> vertices;
    325380    std::vector<u16> indices;
    326381
    327     short v = 0;
    328 
    329     size_t n = m_Line->m_Coords.size() / 2;
    330     ENSURE(n >= 1);
    331 
    332     CTerrain* terrain = m_Line->m_Terrain;
     382    size_t n = m_Line->m_Coords.size() / 2; // number of line points
     383    bool closed = m_Line->m_Closed;
    333384
    334     // TODO: this assumes paths are closed loops; probably should extend this to
    335     // handle non-closed paths too
     385    ENSURE(n >= 2); // minimum needed to avoid errors (also minimum value to make sense, can't draw a line between 1 point)
    336386
    337387    // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1.
    338388    // To avoid slightly expensive terrain computations we cycle these around and
    339389    // 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]);
     390
     391    CVector3D p0;
     392    CVector3D p1(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]);
     393    CVector3D p2(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]);
     394
     395    if (closed)
     396        // grab the ending point so as to close the loop
     397        p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]);
     398    else
     399        // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that
     400        // extends the p2 -> p1 direction, and use that point instead
     401        p0 = p1 + (p1 - p2);
     402
    343403    bool p1floating = false;
    344404    bool p2floating = false;
    345405
    void CTexturedLineRData::Update()  
    386446        if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero
    387447            b *= m_Line->m_Thickness / l;
    388448
    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));
    393         indices.push_back(vertices.size() - 1);
     449        // Push vertices and indices in GL_TRIANGLES order
     450        //
     451        // NOTE: in order for OpenGL to successfully render these, the winding order needs to be correct. Basically, it means
     452        // that every pair of triangles sharing a side must specify the vertices of that side in the opposite order from the
     453        // other triangle.
     454        // (see http://www.opengl.org/resources/code/samples/sig99/advanced99/notes/node16.html for an illustration)
     455        //
     456        // What the code below does is push the indices for a quad composed of two triangles in each iteration. The two triangles
     457        // of each quad are indexed using the winding orders (BR, BL, TR) and (TR, BL, TR) (where BR is bottom-right of this
     458        // iteration's quad, TR top-right etc).
     459        SVertex vertex1(p1 + b + norm*m_Raise, 0.f, v);
     460        SVertex vertex2(p1 - b + norm*m_Raise, 1.f, v);
     461        vertices.push_back(vertex1);
     462        vertices.push_back(vertex2);
     463
     464        u16 index1 = vertices.size() - 2; // index of vertex1 in this iteration (TR of this quad)
     465        u16 index2 = vertices.size() - 1; // index of the vertex2 in this iteration (TL of this quad)
     466
     467        if (i == 0)
     468        {
     469            // initial two vertices to continue building triangles from (n must be >= 2 for this to work)
     470            indices.push_back(index1);
     471            indices.push_back(index2);
     472        }
     473        else
     474        {
     475            u16 index1Prev = vertices.size() - 4; // index of the vertex1 in the previous iteration (BR of this quad)
     476            u16 index2Prev = vertices.size() - 3; // index of the vertex2 in the previous iteration (BL of this quad)
     477            ENSURE(index1Prev >= 0 && index1Prev < vertices.size());
     478            ENSURE(index2Prev >= 0 && index2Prev < vertices.size());
     479            // Add two corner points from last iteration and join with one of our own corners to create triangle 1
     480            // (don't need to do this if i == 1 because i == 0 are the first two ones, they don't need to be copied)
     481            if (i > 1)
     482            {
     483                indices.push_back(index1Prev);
     484                indices.push_back(index2Prev);
     485            }
     486            indices.push_back(index1); // complete triangle 1
    394487
    395         vertices.push_back(SVertex(p1 - b + norm*raised, 1, v));
    396         indices.push_back(vertices.size() - 1);
     488            // create triangle 2, specifying the adjacent side's vertices in the opposite order from triangle 1
     489            indices.push_back(index1);
     490            indices.push_back(index2Prev);
     491            indices.push_back(index2);
     492        }
    397493
    398         // Alternate V coordinate for debugging
     494        // alternate V coordinate for debugging
    399495        v = 1 - v;
    400496
    401         // Cycle the p's and compute the new p2
     497        // cycle the p's and compute the new p2
    402498        p0 = p1;
    403499        p1 = p2;
    404500        p1floating = p2floating;
    405         p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]);
     501
     502        // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly
     503        if (!closed && i == n-2)
     504            // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction
     505            p2 = p1 + (p1 - p0);
     506        else
     507            p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]);
     508
    406509        p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z);
    407510        if (p2.Y < w)
    408511        {
    void CTexturedLineRData::Update()  
    413516            p2floating = false;
    414517    }
    415518
    416     // Close the path
    417     indices.push_back(0);
    418     indices.push_back(1);
     519    if (!closed)
     520    {
     521        // Create start and end caps. On either end, this is done by taking the centroid between the last and second-to-last pair of
     522        // vertices that was generated along the path (i.e. the vertex1's and vertex2's from above), taking a directional vector
     523        // between them, and drawing the line cap in the plane given by the two butt-end corner points plus said vector.
     524        std::vector<u16> capIndices;
     525        std::vector<SVertex> capVertices;
     526
     527        // create end cap
     528        CreateLineCap(
     529            // the order of these vertices is important here, swapping them produces caps at the wrong side
     530            vertices[vertices.size()-2].m_Position, // top-right vertex of last quad
     531            vertices[vertices.size()-1].m_Position, // top-left vertex of last quad
     532            // directional vector between centroids of last vertex pair and second-to-last vertex pair
     533            (Centroid(vertices[vertices.size()-2], vertices[vertices.size()-1]) - Centroid(vertices[vertices.size()-4], vertices[vertices.size()-3])).Normalized(),
     534            m_Line->m_EndCapType,
     535            capVertices,
     536            capIndices
     537        );
     538
     539        for (unsigned i = 0; i < capIndices.size(); i++)
     540            capIndices[i] += vertices.size();
     541        vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
     542        indices.insert(indices.end(), capIndices.begin(), capIndices.end());
     543
     544        capIndices.clear();
     545        capVertices.clear();
     546
     547        // create start cap
     548        CreateLineCap(
     549            // the order of these vertices is important here, swapping them produces caps at the wrong side
     550            vertices[1].m_Position,
     551            vertices[0].m_Position,
     552            // directional vector between centroids of first vertex pair and second vertex pair
     553            (Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(),
     554            m_Line->m_StartCapType,
     555            capVertices,
     556            capIndices
     557        );
     558
     559        for (unsigned i = 0; i < capIndices.size(); i++)
     560            capIndices[i] += vertices.size();
     561        vertices.insert(vertices.end(), capVertices.begin(), capVertices.end());
     562        indices.insert(indices.end(), capIndices.begin(), capIndices.end());
     563    }
     564
     565    ENSURE(indices.size() % 3 == 0); // GL_TRIANGLES indices, so must be multiple of 3
    419566
    420567    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]);
     568    if (m_VB)
     569    {
     570        // allocation might fail (e.g. due to too many vertices)
     571        m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]); // copy data into VBO
     572
     573        for (size_t k = 0; k < indices.size(); ++k)
     574            indices[k] += m_VB->m_Index;
     575
     576        m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
     577        if (m_VBIndices)
     578            m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]);
     579    }
    422580
    423     // Update the indices to include the base offset of the vertex data
    424     for (size_t k = 0; k < indices.size(); ++k)
    425         indices[k] += m_VB->m_Index;
     581}
     582
     583void CTexturedLineRData::CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& lineDirectionNormal,
     584                                       SOverlayTexturedLine::LineCapType endCapType, std::vector<SVertex>& verticesOut,
     585                                       std::vector<u16>& indicesOut)
     586{
     587    if (endCapType == SOverlayTexturedLine::LINECAP_FLAT)
     588        return; // no action needed, this is the default
     589
     590    // When not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the
     591    // direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular
     592    // butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction.
     593    // That is to say, when viewed from the top, we will have something like
     594    //                                                 .
     595    //  this:                     and not like this:  /|
     596    //         ____.                                 / |
     597    //             |                                /  .
     598    //             |                                  /
     599    //         ____.                                 /
     600    //
     601
     602    int roundCapPoints = 8; // amount of points to sample along the semicircle for rounded caps (including corner points)
     603    float radius = m_Line->m_Thickness;
     604
     605    CVector3D centerPoint = (corner1 + corner2) * 0.5f;
     606    SVertex centerVertex(centerPoint, 0.5f, 0.5f);
     607    u16 indexOffset = verticesOut.size(); // index offset in verticesOut from where we start adding our vertices
     608
     609    switch (endCapType)
     610    {
     611    case SOverlayTexturedLine::LINECAP_SHARP:
     612        {
     613            roundCapPoints = 3; // creates only one point directly ahead
     614            radius *= 1.5f; // make it a bit sharper (note that we don't use the radius for the butt-end corner points so it should be ok)
     615            centerVertex.m_UVs[0] = 0.480f; // slight visual correction to make the texture match up better at the corner points
     616        }
     617        // fall-through
     618    case SOverlayTexturedLine::LINECAP_ROUND:
     619        {
     620            // Draw a rounded line cap in the 3D plane of the line specified by the two corner points and the normal vector of the
     621            // line's direction. The terrain normal at the centroid between the two corner points is perpendicular to this plane.
     622            // The way this works is by taking a vector from the corner points' centroid to one of the corner points (which is then
     623            // of radius length), and rotate it around the terrain normal vector in that centroid. This will rotate the vector in
     624            // the line's plane, producing the desired rounded cap.
     625
     626            // To please OpenGL's winding order, this angle needs to be negated depending on whether we start rotating from
     627            // the (center -> corner1) or (center -> corner2) vector. For the (center -> corner2) vector, we apparently need to use
     628            // the negated angle.
     629            float stepAngle = -(float)(M_PI/(roundCapPoints-1));
     630
     631            // Push the vertices in triangle fan order (easy to generate GL_TRIANGLES indices for afterwards)
     632            // Note that we're manually adding the corner vertices instead of having them be generated by the rotating vector.
     633            // This is because we want to support an overly large radius to make the sharp line ending look sharper.
     634            verticesOut.push_back(centerVertex);
     635            verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
     636
     637            // Get the base vector that we will incrementally rotate in the cap plane to produce the radial sample points.
     638            // Normally corner2 - centerPoint would suffice for this since it is of radius length, but we want to support custom
     639            // radii to support tuning the 'sharpness' of sharp end caps (see above)
     640            CVector3D rotationBaseVector = (corner2 - centerPoint).Normalized() * radius;
     641            // Calculate the normal vector of the plane in which we're going to be drawing the line cap. This is the vector that
     642            // is perpendicular to both baseVector and the 'lineDirectionNormal' vector indicating the direction of the line.
     643            // Note that we shouldn't use terrain->CalcExactNormal() here because if the line is being rendered on top of water,
     644            // then CalcExactNormal will return the normal vector of the terrain that's underwater (which can be quite funky).
     645            CVector3D capPlaneNormal = lineDirectionNormal.Cross(rotationBaseVector).Normalized();
     646
     647            for (int i = 1; i < roundCapPoints - 1; ++i)
     648            {
     649                // Rotate the centerPoint -> corner vector by i*stepAngle radians around the cap plane normal at the center point.
     650                CQuaternion quatRotation;
     651                quatRotation.FromAxisAngle(capPlaneNormal, i * stepAngle);
     652                CVector3D worldPos3D = centerPoint + quatRotation.Rotate(rotationBaseVector);
     653
     654                // 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
     655                // of the texture around the edge of the semicircle)
     656                float u = 0.f;
     657                float v = clamp((i/(float)(roundCapPoints-1)), 0.f, 1.f); // pos, u, v
     658                verticesOut.push_back(SVertex(worldPos3D, u, v));
     659            }
     660
     661            // connect back to the other butt-end corner point to complete the semicircle
     662            verticesOut.push_back(SVertex(corner1, 0.f, 1.f));
     663
     664            // now push indices in GL_TRIANGLES order; vertices[indexOffset] is the center vertex, vertices[indexOffset + 1] is the
     665            // first corner point, then a bunch of radial samples, and then at the end we have the other corner point again. So:
     666            for (int i=1; i < roundCapPoints; ++i)
     667            {
     668                indicesOut.push_back(indexOffset); // center vertex
     669                indicesOut.push_back(indexOffset + i);
     670                indicesOut.push_back(indexOffset + i + 1);
     671            }
     672        }
     673        break;
     674
     675    case SOverlayTexturedLine::LINECAP_SQUARE:
     676        {
     677            // Extend the (corner1 -> corner2) vector along the direction normal and draw a square line ending consisting of
     678            // three triangles (sort of like a triangle fan)
     679            // NOTE: The order in which the vertices are pushed out determines the visibility, as they
     680            // are rendered only one-sided; the wrong order of vertices will make the cap visible only from the bottom.
     681            verticesOut.push_back(centerVertex);
     682            verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
     683            verticesOut.push_back(SVertex(corner2 + (lineDirectionNormal * (m_Line->m_Thickness)), 0.f, 0.33333f)); // extend butt corner point 2 along the normal vector
     684            verticesOut.push_back(SVertex(corner1 + (lineDirectionNormal * (m_Line->m_Thickness)), 0.f, 0.66666f)); // extend butt corner point 1 along the normal vector
     685            verticesOut.push_back(SVertex(corner1, 0.f, 1.0f)); // push butt corner point 1
     686
     687            for (int i=1; i < 4; ++i)
     688            {
     689                indicesOut.push_back(indexOffset); // center point
     690                indicesOut.push_back(indexOffset + i);
     691                indicesOut.push_back(indexOffset + i + 1);
     692            }
     693        }
     694        break;
     695
     696    default:
     697        break;
     698    }
    426699
    427     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]);
    429700}
  • source/renderer/OverlayRenderer.h

    diff --git a/source/renderer/OverlayRenderer.h b/source/renderer/OverlayRenderer.h
    index a48ae0a..66de988 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 those overlay lines currently registered in the internals (i.e. in m->texlines) for which the
     92     * always visible flag equals @alwaysVisible. Used for batch rendering the overlay lines by their alwaysVisible status,
     93     * because this requires a separate shader to be used.
     94     */
     95    void RenderTexturedOverlayLines(CShaderProgramPtr shader, bool alwaysVisible);
     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..a7291c2 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:  
    135135
    136136
    137137/**
    138  * Class RenderModifierRenderer: Interface to a model renderer that can render
    139  * its models via a RenderModifier that sets up fragment stages.
    140  */
    141 class RenderModifierRenderer : public ModelRenderer
    142 {
    143 public:
    144     RenderModifierRenderer() { }
    145     virtual ~RenderModifierRenderer();
    146 };
    147 
    148 
    149 /**
    150138 * Class PlainRenderModifier: RenderModifier that simply uses opaque textures
    151139 * modulated by primary color. It is used for normal, no-frills models.
    152140 */
  • source/scriptinterface/ScriptInterface.cpp

    diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp
    index 46910bd..4d1d1f4 100644
    a b ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh  
    503503    m_nativeScope = JS_DefineObject(m_cx, m_glob, nativeScopeName, NULL, NULL, JSPROP_ENUMERATE | JSPROP_READONLY
    504504            | JSPROP_PERMANENT);
    505505
    506     JS_DefineFunction(m_cx, m_glob, "print", ::print,  0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    507     JS_DefineFunction(m_cx, m_glob, "log",   ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    508     JS_DefineFunction(m_cx, m_glob, "warn",  ::warn,   1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    509     JS_DefineFunction(m_cx, m_glob, "error", ::error,  1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    510     JS_DefineFunction(m_cx, m_glob, "deepcopy", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     506    JS_DefineFunction(m_cx, m_glob, "print", ::print,        0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     507    JS_DefineFunction(m_cx, m_glob, "log",   ::logmsg,       1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     508    JS_DefineFunction(m_cx, m_glob, "warn",  ::warn,         1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     509    JS_DefineFunction(m_cx, m_glob, "error", ::error,        1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     510    JS_DefineFunction(m_cx, m_glob, "deepcopy", ::deepcopy,  1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    511511
    512512    Register("ProfileStart", ::ProfileStart, 1);
    513513    Register("ProfileStop", ::ProfileStop, 0);
  • source/simulation2/TypeList.h

    diff --git a/source/simulation2/TypeList.h b/source/simulation2/TypeList.h
    index 60d1f54..0157b0f 100644
    a b COMPONENT(Position) // must be before VisualActor  
    118118INTERFACE(ProjectileManager)
    119119COMPONENT(ProjectileManager)
    120120
     121INTERFACE(RallyPointRenderer)
     122COMPONENT(RallyPointRenderer)
     123
    121124INTERFACE(RangeManager)
    122125COMPONENT(RangeManager)
    123126
  • new file source/simulation2/components/CCmpRallyPointRenderer.cpp

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

    diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp
    index 2a87eba..28893db 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                const int numHighlightNodes = 7; // highlight the X last nodes on either end to see where they meet (if closed)
     807                SOverlayLine overlayNode;
     808                if (j > boundaries[i].points.size() - 1 - numHighlightNodes)
     809                    overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
     810                else if (j < numHighlightNodes)
     811                    overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
     812                else
     813                    overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     814
     815                overlayNode.m_Thickness = 1;
     816                SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true);
     817                m_DebugBoundaryLineNodes.push_back(overlayNode);
     818            }
    799819        }
     820
    800821    }
    801822}
    802823
    void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)  
    825846{
    826847    for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
    827848        collector.Submit(&m_BoundaryLines[i].overlay);
     849   
     850    for (size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i)
     851        collector.Submit(&m_DebugBoundaryLineNodes[i]);
     852
    828853}
    829854
    830855player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
  • source/simulation2/components/CCmpVisualActor.cpp

    diff --git a/source/simulation2/components/CCmpVisualActor.cpp b/source/simulation2/components/CCmpVisualActor.cpp
    index 30b8b52..b677a33 100644
    a b public:  
    356356        }
    357357    }
    358358
     359    virtual void SetUnitEntitySelection(const CStr& selection)
     360    {
     361        if (m_Unit)
     362        {
     363            m_Unit->SetEntitySelection(selection);
     364        }
     365    }
     366
    359367    virtual void SelectMovementAnimation(fixed runThreshold)
    360368    {
    361369        m_AnimRunThreshold = runThreshold;
  • source/simulation2/components/ICmpPlayer.cpp

    diff --git a/source/simulation2/components/ICmpPlayer.cpp b/source/simulation2/components/ICmpPlayer.cpp
    index ad2a56e..49f16a4 100644
    a b public:  
    5353        return m_Script.Call<CColor>("GetColour");
    5454    }
    5555
     56    virtual std::wstring GetCiv()
     57    {
     58        return m_Script.Call<std::wstring>("GetCiv");
     59    }
     60
    5661    virtual CFixedVector3D GetStartingCameraPos()
    5762    {
    5863        return m_Script.Call<CFixedVector3D>("GetStartingCameraPos");
  • source/simulation2/components/ICmpPlayer.h

    diff --git a/source/simulation2/components/ICmpPlayer.h b/source/simulation2/components/ICmpPlayer.h
    index 531689b..4418639 100644
    a b public:  
    3636    virtual void SetColour(u8 r, u8 g, u8 b) = 0;
    3737
    3838    virtual CColor GetColour() = 0;
     39    virtual std::wstring GetCiv() = 0;
    3940    virtual CFixedVector3D GetStartingCameraPos() = 0;
    4041    virtual CFixedVector3D GetStartingCameraRot() = 0;
    4142
  • new file source/simulation2/components/ICmpRallyPointRenderer.cpp

    diff --git a/source/simulation2/components/ICmpRallyPointRenderer.cpp b/source/simulation2/components/ICmpRallyPointRenderer.cpp
    new file mode 100644
    index 0000000..8a3a64f
    - +  
     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 "ICmpRallyPointRenderer.h"
     21#include "simulation2/system/InterfaceScripted.h"
     22
     23class CFixedVector2D;
     24
     25BEGIN_INTERFACE_WRAPPER(RallyPointRenderer)
     26DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpRallyPointRenderer, SetDisplayed, bool)
     27DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpRallyPointRenderer, SetPosition, CFixedVector2D)
     28END_INTERFACE_WRAPPER(RallyPointRenderer)
  • new file source/simulation2/components/ICmpRallyPointRenderer.h

    diff --git a/source/simulation2/components/ICmpRallyPointRenderer.h b/source/simulation2/components/ICmpRallyPointRenderer.h
    new file mode 100644
    index 0000000..71f315a
    - +  
     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 "maths/FixedVector2D.h"
     22#include "simulation2/helpers/Position.h"
     23#include "simulation2/system/Interface.h"
     24
     25/**
     26 * Rally Point.
     27 * Holds the position of a unit's rally point, and renders it to screen.
     28 */
     29class ICmpRallyPointRenderer : public IComponent
     30{
     31public:
     32
     33    /// Sets whether the rally point marker and line should be displayed.
     34    virtual void SetDisplayed(bool displayed) = 0;
     35
     36    /// Sets the position at which the rally point marker should be displayed.
     37    virtual void SetPosition(CFixedVector2D position) = 0;
     38
     39    DECLARE_INTERFACE_TYPE(RallyPointRenderer)
     40};
     41
     42#endif // INCLUDED_ICMPRALLYPOINT
  • source/simulation2/components/ICmpVisual.h

    diff --git a/source/simulation2/components/ICmpVisual.h b/source/simulation2/components/ICmpVisual.h
    index 86a5183..e02b4b5 100644
    a b  
    2020
    2121#include "simulation2/system/Interface.h"
    2222
     23#include "ps/CStr.h"
    2324#include "maths/BoundingBoxOriented.h"
    2425#include "maths/BoundingBoxAligned.h"
    2526#include "maths/Fixed.h"
    public:  
    8283    virtual void SelectAnimation(std::string name, bool once, fixed speed, std::wstring soundgroup) = 0;
    8384
    8485    /**
     86     * Sets the specified entity selection on the underlying unit.
     87     */
     88    virtual void SetUnitEntitySelection(const CStr& selection) = 0;
     89
     90    /**
    8591     * Start playing the walk/run animations, scaled to the unit's movement speed.
    8692     * @param runThreshold movement speed at which to switch to the run animation
    8793     */
  • 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 7c83778..dd8a906 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  
    319319    return p + CVector2D(dp.Y*-offset, dp.X*offset);
    320320}
    321321
    322 void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset)
     322void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples)
    323323{
    324324    PROFILE("InterpolatePointsRNS");
     325    ENSURE(segmentSamples > 0);
    325326
    326327    std::vector<CVector2D> newPoints;
    327328
    void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed  
    333334    // curve with fewer points
    334335
    335336    size_t n = points.size();
    336     if (n < 1)
    337         return; // can't do anything unless we have two points
    338337
    339     size_t imax = closed ? n : n-1; // TODO: we probably need to do a bit more to handle non-closed paths
     338    if (closed)
     339    {
     340        if (n < 1)
     341            return; // we need at least a single point to not crash
     342    }
     343    else
     344    {
     345        if (n < 2)
     346            return; // in non-closed mode, we need at least n=2 to not crash
     347    }
    340348
    341     newPoints.reserve(imax*4);
     349    size_t imax = closed ? n : n-1;
     350    newPoints.reserve(imax*segmentSamples);
     351
     352    // these are primarily used inside the loop, but for open paths we need them outside the loop once to compute the last point
     353    CVector2D a0;
     354    CVector2D a1;
     355    CVector2D a2;
     356    CVector2D a3;
    342357
    343358    for (size_t i = 0; i < imax; ++i)
    344359    {
    345         // Get the relevant points for this spline segment
    346         CVector2D p0 = points[(i-1+n)%n];
     360
     361        // Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points
     362        // before p1 and after p2, respectively; they're needed to compute tangents and whatnot.
     363        CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below
    347364        CVector2D p1 = points[i];
    348365        CVector2D p2 = points[(i+1)%n];
    349         CVector2D p3 = points[(i+2)%n];
     366        CVector2D p3; // normally points[(i+2)%n], but it's a bit more complicated due to open/closed paths -- see below
     367
     368        if (!closed && (i == 0))
     369            // 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
     370            // that extends p1 -> p0 (i.e. the first segment's direction)
     371            p0 = points[0] + (points[0] - points[1]);
     372        else
     373            // standard wrap-around case
     374            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)
     375
     376
     377        if (!closed && (i == n-2))
     378            // 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)
     379            // (note that p2's index should not be out of bounds, because in non-closed mode imax is reduced by 1)
     380            p3 = points[n-1] + (points[n-1] - points[n-2]);
     381        else
     382            // standard wrap-around case
     383            p3 = points[(i+2)%n];
     384
    350385
    351386        // Do the RNS computation (based on GPG4 "Nonuniform Splines")
    352387        float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1)
    void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed  
    357392        CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1
    358393
    359394        // Compute standard cubic spline parameters
    360         CVector2D a0 = p1*2 + p2*-2 + v1 + v2;
    361         CVector2D a1 = p1*-3 + p2*3 + v1*-2 + v2*-1;
    362         CVector2D a2 = v1;
    363         CVector2D a3 = p1;
    364 
    365         // Interpolate at various points
    366         newPoints.push_back(EvaluateSpline(0.f, a0, a1, a2, a3, offset));
    367         newPoints.push_back(EvaluateSpline(1.f/4.f, a0, a1, a2, a3, offset));
    368         newPoints.push_back(EvaluateSpline(2.f/4.f, a0, a1, a2, a3, offset));
    369         newPoints.push_back(EvaluateSpline(3.f/4.f, a0, a1, a2, a3, offset));
     395        a0 = p1*2 + p2*-2 + v1 + v2;
     396        a1 = p1*-3 + p2*3 + v1*-2 + v2*-1;
     397        a2 = v1;
     398        a3 = p1;
     399
     400        // Interpolate at regular points across the interval
     401        for (int sample = 0; sample < segmentSamples; sample++)
     402            newPoints.push_back(EvaluateSpline(sample/((float) segmentSamples), a0, a1, a2, a3, offset));
     403
    370404    }
    371405
     406    if (!closed)
     407        // if the path is open, we should take care to include the last control point
     408        // NOTE: we can't just do push_back(points[n-1]) here because that ignores the offset
     409        newPoints.push_back(EvaluateSpline(1.f, a0, a1, a2, a3, offset));
     410
    372411    points.swap(newPoints);
    373412}
     413
     414void SimRender::ConstructDashedLine(const std::vector<CVector2D>& keyPoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength)
     415{
     416    // sanity checks
     417    if (dashLength <= 0)
     418        return;
     419
     420    if (blankLength <= 0)
     421        return;
     422
     423    if (keyPoints.size() < 2)
     424        return;
     425
     426    dashedLineOut.m_Points.clear();
     427    dashedLineOut.m_StartIndices.clear();
     428
     429    // walk the line, counting the total length so far at each node point. When the length exceeds dashLength, cut the last segment at the
     430    // required length and continue for blankLength along the line to start a new dash segment.
     431
     432    // TODO: we should probably extend this function to also allow for closed lines. I was thinking of slightly scaling the dash/blank length
     433    // 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
     434    // (O(n) and lots of square roots).
     435
     436    bool buildingDash = true; // true if we're building a dash, false if a blank
     437    float curDashLength = 0; // builds up the current dash/blank's length as we walk through the line nodes
     438    CVector2D dashLastPoint = keyPoints[0]; // last point of the current dash/blank being built.
     439
     440    // register the first starting node of the first dash
     441    dashedLineOut.m_Points.push_back(keyPoints[0]);
     442    dashedLineOut.m_StartIndices.push_back(0);
     443
     444    // 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
     445    // properly take a direction vector along the path.
     446    size_t i = 0;
     447
     448    while(i < keyPoints.size() - 1)
     449    {
     450        // get length of this segment
     451        CVector2D segmentVector = keyPoints[i + 1] - dashLastPoint; // vector from our current point along the path to nextNode
     452        float segmentLength = segmentVector.Length();
     453
     454        float targetLength = (buildingDash ? dashLength : blankLength);
     455        if (curDashLength + segmentLength > targetLength)
     456        {
     457            // 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
     458            // line that is of just the required length to complete the dash, then make it the base point for the next dash/blank.
     459            float cutLength = targetLength - curDashLength;
     460            CVector2D cutPoint = dashLastPoint + (segmentVector.Normalized() * cutLength);
     461
     462            // start a new dash or blank in the next iteration
     463            curDashLength = 0;
     464            buildingDash = !buildingDash; // flip from dash to blank and vice-versa
     465            dashLastPoint = cutPoint;
     466
     467            // don't increment i, we haven't fully traversed this segment yet so we still need to use the same point to take the
     468            // direction vector with in the next iteration
     469
     470            // 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
     471            // in the points array.
     472            dashedLineOut.m_Points.push_back(cutPoint);
     473
     474            if (buildingDash)
     475            {
     476                // 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
     477                // index as a start index of a dash.
     478                dashedLineOut.m_StartIndices.push_back(dashedLineOut.m_Points.size() - 1);
     479            }
     480
     481        }
     482        else
     483        {
     484            // the segment from lastDashPoint to keyPoints[i+1] doesn't suffice to complete the dash, so we need to add keyPoints[i+1]
     485            // to this dash's points and continue from there
     486
     487            if (buildingDash)
     488                // still building the dash, add it to the output (we don't need to store the blanks)
     489                dashedLineOut.m_Points.push_back(keyPoints[i+1]);
     490
     491            curDashLength += segmentLength;
     492            dashLastPoint = keyPoints[i+1];
     493            i++;
     494
     495        }
     496
     497    }
     498
     499}
  • source/simulation2/helpers/Render.h

    diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h
    index bf66828..5b2f251 100644
    a b  
    2323 * Helper functions related to rendering
    2424 */
    2525
     26#include "maths/Vector2D.h"
     27
    2628class CSimContext;
    27 class CVector2D;
    2829class CVector3D;
    2930class CBoundingBoxAligned;
    3031class CBoundingBoxOriented;
    3132struct SOverlayLine;
    3233
     34
     35
     36struct SDashedLine
     37{
     38    std::vector<CVector2D> m_Points; ///< Packed array of consecutive dashes' points. Use m_StartIndices to navigate it.
     39
     40    /**
     41     * Start indices in m_Points of each dash. Dash n starts at point m_StartIndices[n] and ends at the point with index
     42     * m_StartIndices[n+1] - 1, or at the end of the m_Points vector. Use the GetEndIndex(n) convenience method to abstract away the
     43     * difference and get the (exclusive) end index of dash n.
     44     */
     45    std::vector<size_t> m_StartIndices;
     46
     47    /// Returns the (exclusive) end point index (i.e. index within m_Points) of dash n.
     48    size_t GetEndIndex(size_t i)
     49    {
     50        // for the last dash, there is no next starting index, so we need to use the end index of the m_Points array instead
     51        return (i < m_StartIndices.size() - 1 ? m_StartIndices[i+1] : m_Points.size());
     52    }
     53};
     54
    3355namespace SimRender
    3456{
    3557
    void SmoothPointsAverage(std::vector<CVector2D>& points, bool closed);  
    88110 * the direction of the curve.
    89111 * If @p closed then the points are treated as a closed path (the last is connected
    90112 * to the first).
     113 * @param segmentSamples Amount of intermediate points to sample between every two control points.
     114 */
     115void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples = 4);
     116
     117/**
     118 * Creates a dashed line from the line specified by @points so that each dash is of length
     119 * @p dashLength, and each blank inbetween is of length @p blankLength. The dashed line returned as a list of smaller lines
     120 * in @p dashedLineOut.
     121 *
     122 * @param dashLength Length of a single dash. Must be strictly positive.
     123 * @param blankLength Length of a single blank between dashes. Must be strictly positive.
    91124 */
    92 void InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset);
     125void ConstructDashedLine(const std::vector<CVector2D>& linePoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength);
    93126
    94127} // namespace
    95128