Ticket #524: rallypoints_wip_25aug11.patch

File rallypoints_wip_25aug11.patch, 136.5 KB (added by vts, 13 years ago)

Rally point marker lines/focus command WIP patch

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

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

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

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

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

    literal 0
    HcmV?d00001
    
    diff --git a/binaries/data/mods/public/gui/session/input.js b/binaries/data/mods/public/gui/session/input.js
    index f082daf..7dc3ddc 100644
    a b function performCommand(entity, commandName)  
    11091109            case "unload-all":
    11101110                unloadAll(entity);
    11111111                break;
     1112            case "focus-rally":
     1113                // make the camera move to the building's rally point
     1114               
     1115                // grab the rally point position (if any)
     1116                var entState = Engine.GuiInterfaceCall("GetEntityState", entity);
     1117                if (entState.rallyPoint)
     1118                {
     1119                    // the rally point isn't necessarily set
     1120                    if (entState.rallyPoint.position)
     1121                    {
     1122                        Engine.CameraMoveTo(entState.rallyPoint.position.x, entState.rallyPoint.position.z);
     1123                    }
     1124                    else
     1125                    {
     1126                        // no rally point set, jump to the building itself instead (since units will now spawn right next to the
     1127                        // building)
     1128                        if (entState.position)
     1129                            Engine.CameraMoveTo(entState.position.x, entState.position.z);
     1130                    }
     1131                }
     1132               
     1133                break;
    11121134            default:
    11131135                break;
    11141136            }
  • 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 d546de9..ebb1575 100644
    a b function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    225225            break;
    226226
    227227        case COMMAND:
    228             if (item == "unload-all")
     228            // here, "item" is an object with properties .name (command name), .tooltip and .icon (relative to session/icons/single)
     229            if (item.name == "unload-all")
    229230            {
    230231                var count = unitEntState.garrisonHolder.entities.length;
    231232                getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = (count > 0 ? count : "");
    function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    235236                getGUIObjectByName("unit"+guiName+"Count["+i+"]").caption = "";
    236237            }
    237238
    238             tooltip = toTitleCase(item);
     239            tooltip = (item.tooltip ? item.tooltip : toTitleCase(item.name)); // use item.tooltip if available, otherwise default to title-cased command name
    239240            break;
    240241
    241242        default:
    function setupUnitPanel(guiName, usedPanels, unitEntState, items, callback)  
    289290        {
    290291            //icon.cell_id = i;
    291292            //icon.cell_id = getCommandCellId(item);
    292             icon.sprite = "stretched:session/icons/single/" + getCommandImage(item);
     293            icon.sprite = "stretched:session/icons/single/" + item.icon;
    293294           
    294295        }
    295296        else if (template.icon)
    function updateUnitCommands(entState, supplementalDetailsPanel, commandsPanel, s  
    373374        var commands = getEntityCommandsList(entState);
    374375        if (commands.length)
    375376            setupUnitPanel("Command", usedPanels, entState, commands,
    376                 function (item) { performCommand(entState.id, item); } );
     377                function (item) { performCommand(entState.id, item.name); } );
    377378
    378379        if (entState.garrisonHolder)
    379380        {
  • 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 a25ee94..a86b1fe 100644
    a b function getFormationCellId(formationName)  
    253253    }
    254254}
    255255
    256 function getCommandImage(commandName)
    257 {
    258     switch (commandName)
    259     {
    260     case "delete":
    261         return "kill_small.png";
    262     case "unload-all":
    263         return "garrison-out.png";
    264     case "garrison":
    265         return "garrison.png";
    266     case "repair":
    267         return "repair.png";
    268     default:
    269         return "";
    270     }
    271 }
    272 
    273256function getEntityFormationsList(entState)
    274257{
    275258    var civ = g_Players[entState.player].civ;
    function getEntityCommandsList(entState)  
    298281{
    299282    var commands = [];
    300283    if (entState.garrisonHolder)
    301         commands.push("unload-all");
    302     commands.push("delete");
     284    {
     285        commands.push({
     286            "name": "unload-all",
     287            "tooltip": "Unload All",
     288            "icon": "garrison-out.png"
     289        });
     290    }
     291   
     292    commands.push({
     293        "name": "delete",
     294        "tooltip": "Delete",
     295        "icon": "kill_small.png"
     296    });
     297   
    303298    if (isUnit(entState))
    304         commands.push("garrison");
     299    {
     300        commands.push({
     301            "name": "garrison",
     302            "tooltip": "Garrison",
     303            "icon": "garrison.png"
     304        });
     305    }
     306   
    305307    if (entState.buildEntities)
    306         commands.push("repair");
     308    {
     309        commands.push({
     310            "name": "repair",
     311            "tooltip": "Repair",
     312            "icon": "repair.png"
     313        });
     314    }
     315   
     316    if(entState.rallyPoint)
     317    {
     318        commands.push({
     319            "name": "focus-rally",
     320            "tooltip": "Focus on Rally Point",
     321            "icon": "focus-rally.png"
     322        });
     323    }
     324   
    307325    return commands;
    308326}
    309327
  • 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..3d78109 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#ifdef IGNORE_LOS
     14  MOV result.color.rgb, color;
     15  MOV result.color.a, 1; // full-bright
     16#else
     17  TEMP los;
    1318// Multiply by LOS texture
    1419TEX los, fragment.texcoord[1], texture[2], 2D;
    1520MUL result.color.rgb, color, los.a;
    1621
    1722// Use alpha from base texture
     23  //MOV result.color.a, base.a;
    1824MUL result.color.a, objectColor.a, base.a;
     25#endif
     26
    1927
    2028END
  • 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 ac55d7c..03caee8 100644
    a b GuiInterface.prototype.GetEntityState = function(player, ent)  
    219219    if (cmpRallyPoint)
    220220    {
    221221        ret.rallyPoint = { };
     222        if (cmpRallyPoint.IsSet()) // can't return a rally point position if none is set ...
     223            ret.rallyPoint.position = cmpRallyPoint.GetPosition();
    222224    }
    223225
    224226    var cmpGarrisonHolder = Engine.QueryInterface(ent, IID_GarrisonHolder);
    GuiInterface.prototype.SetStatusBars = function(player, cmd)  
    376378 */
    377379GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
    378380{
    379     // If there are rally points already displayed, destroy them
    380     for each (var ent in this.rallyPoints)
     381    // If there are some rally points already displayed, first hide them
     382    for each (var ent in this.entsRallyPointsDisplayed)
    381383    {
    382         // Hide it first (the destruction won't be instantaneous)
    383         var cmpPosition = Engine.QueryInterface(ent, IID_Position);
    384         cmpPosition.MoveOutOfWorld();
     384        var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
     385        if (!cmpRallyPoint)
     386            continue;
    385387
    386         Engine.DestroyEntity(ent);
     388        cmpRallyPoint.SetDisplayed(false);
    387389    }
    388390
    389     this.rallyPoints = [];
    390 
    391     var positions = [];
    392     // DisplayRallyPoints is called passing a list of entities for which
    393     // rally points must be displayed
     391    // Show the rally points for the passed entities
    394392    for each (var ent in cmd.entities)
    395393    {
    396394        var cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
    GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)  
    402400        if (!cmpOwnership || cmpOwnership.GetOwner() != player)
    403401            continue;
    404402
    405         // If the command was passed an explicit position, use that and
    406         // override the real rally point position; otherwise use the real position
    407         var pos;
     403        // If the command was passed an explicit position, first set it
    408404        if (cmd.x && cmd.z)
    409             pos = {"x": cmd.x, "z": cmd.z};
    410         else
    411             pos = cmpRallyPoint.GetPosition();
     405            cmpRallyPoint.SetPosition(cmd);
    412406
    413         if (pos)
    414         {
    415             // TODO: it'd probably be nice if we could draw some kind of line
    416             // between the building and pos, to make the marker easy to find even
    417             // if it's a long way from the building
     407        cmpRallyPoint.SetDisplayed(true);
    418408
    419             positions.push(pos);
    420         }
    421409    }
    422410
    423     // Add rally point entity for each building
    424     for each (var pos in positions)
    425     {
    426         var rallyPoint = Engine.AddLocalEntity("actor|props/special/common/waypoint_flag.xml");
    427         var cmpPosition = Engine.QueryInterface(rallyPoint, IID_Position);
    428         cmpPosition.JumpTo(pos.x, pos.z);
    429         this.rallyPoints.push(rallyPoint);
    430     }   
     411    // Remember which entities have their rally points displayed so we can hide them again
     412    this.entsRallyPointsDisplayed = cmd.entities;
     413   
    431414};
    432415
    433416/**
  • deleted file binaries/data/mods/public/simulation/components/RallyPoint.js

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

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

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

    diff --git a/binaries/data/mods/public/simulation/templates/special/rallypoint.xml b/binaries/data/mods/public/simulation/templates/special/rallypoint.xml
    new file mode 100644
    index 0000000..c73b5d8
    - +  
     1<?xml version="1.0" encoding="utf-8"?>
     2<Entity parent="special/actor">
     3  <VisualActor>
     4    <Actor>props/special/common/waypoint_flag.xml</Actor>
     5  </VisualActor>
     6  <Vision>
     7    <AlwaysVisible>true</AlwaysVisible>
     8  </Vision>
     9</Entity>
  • binaries/data/mods/public/simulation/templates/template_structure.xml

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

    diff --git a/source/graphics/Overlay.cpp b/source/graphics/Overlay.cpp
    new file mode 100644
    index 0000000..1acf8f7
    - +  
     1/* Copyright (C) 2011 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "Overlay.h"
     21
     22bool SOverlayTexturedLine::StrToLineCap(const std::wstring& str, SOverlayTexturedLine::LineCap& out)
     23{
     24    if (str.compare(L"flat") == 0)
     25    {
     26        out = LINECAP_FLAT;
     27        return true;
     28    }
     29    else if (str.compare(L"round") == 0)
     30    {
     31        out = LINECAP_ROUND;
     32        return true;
     33    }
     34    else if (str.compare(L"sharp") == 0)
     35    {
     36        out = LINECAP_SHARP;
     37        return true;
     38    }
     39    else if (str.compare(L"square") == 0)
     40    {
     41        out = LINECAP_SQUARE;
     42        return true;
     43    }
     44    else
     45        return false;
     46}
  • source/graphics/Overlay.h

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

    diff --git a/source/gui/scripting/ScriptFunctions.cpp b/source/gui/scripting/ScriptFunctions.cpp
    index 0054246..e6c0dcc 100644
    a b  
    3434#include "ps/CConsole.h"
    3535#include "ps/Errors.h"
    3636#include "ps/Game.h"
     37#include "ps/World.h"
    3738#include "ps/Hotkey.h"
    3839#include "ps/Overlay.h"
    3940#include "ps/ProfileViewer.h"
    void CameraFollowFPS(void* UNUSED(cbdata), entity_id_t entityid)  
    346347        g_Game->GetView()->CameraFollow(entityid, true);
    347348}
    348349
     350void MoveCameraTarget2D(void* UNUSED(cbdata), entity_pos_t x, entity_pos_t z)
     351{
     352    // this must not fail
     353    if(!(g_Game && g_Game->GetWorld() && g_Game->GetView() && g_Game->GetWorld()->GetTerrain()))
     354        return;
     355
     356    CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
     357
     358    CVector3D target;
     359    target.X = x.ToFloat();
     360    target.Z = z.ToFloat();
     361    target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
     362    g_Game->GetView()->MoveCameraTarget(target, true); // perform this in minimap mode since this is an "instant" camera movement similar to what you would get for clicking the minimap (also prevents height glitches)
     363}
     364
    349365entity_id_t GetFollowedEntity(void* UNUSED(cbdata))
    350366{
    351367    if (g_Game && g_Game->GetView())
    void GuiScriptingInit(ScriptInterface& scriptInterface)  
    499515    scriptInterface.RegisterFunction<CScriptVal, &GetMapSettings>("GetMapSettings");
    500516    scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollow>("CameraFollow");
    501517    scriptInterface.RegisterFunction<void, entity_id_t, &CameraFollowFPS>("CameraFollowFPS");
     518    scriptInterface.RegisterFunction<void, entity_pos_t, entity_pos_t, &MoveCameraTarget2D>("CameraMoveTo"); // renamed slightly to better match the rest of the CameraXxx functions
    502519    scriptInterface.RegisterFunction<entity_id_t, &GetFollowedEntity>("GetFollowedEntity");
    503520    scriptInterface.RegisterFunction<bool, std::string, &HotkeyIsPressed_>("HotkeyIsPressed");
    504521    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..dfab3d4 100644
    a b public:  
    127127        return CVector2D(X / mag, Y / mag);
    128128    }
    129129
     130    /**
     131     * Returns a version of this vector rotated by @p angle radians counterclockwise.
     132     */
     133    CVector2D Rotated(float angle)
     134    {
     135        float c = cos(angle);
     136        float s = sin(angle);
     137        return CVector2D(
     138            c*X - s*Y,
     139            s*X + c*Y
     140        );
     141    }
     142
     143    /**
     144     * Rotates this vector by @p angle radians counterclockwise.
     145     */
     146    void Rotate(float angle)
     147    {
     148        float c = cos(angle);
     149        float s = sin(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 d5eb0cc..ddb108f 100644
    a b  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2011 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
     
    1919
    2020#include "OverlayRenderer.h"
    2121
     22#include "maths/MathUtil.h"
    2223#include "graphics/LOSTexture.h"
    2324#include "graphics/Overlay.h"
    24 #include "graphics/ShaderManager.h"
    2525#include "graphics/Terrain.h"
    2626#include "graphics/TextureManager.h"
    2727#include "lib/ogl.h"
     28#include "maths/Vector2D.h"
    2829#include "ps/Game.h"
    2930#include "ps/Profile.h"
     31#include "ps/CLogger.h"
    3032#include "renderer/Renderer.h"
    3133#include "renderer/VertexBuffer.h"
    3234#include "renderer/VertexBufferManager.h"
    class CTexturedLineRData : public CRenderData  
    4446{
    4547public:
    4648    CTexturedLineRData(SOverlayTexturedLine* line) :
    47         m_Line(line), m_VB(NULL), m_VBIndices(NULL)
     49        m_Line(line),
     50        m_VB(NULL),
     51        m_VBIndices(NULL),
     52        m_VBStartCap(NULL),
     53        m_VBStartCapIndices(NULL),
     54        m_VBEndCap(NULL),
     55        m_VBEndCapIndices(NULL),
     56        m_Raise(.2f)
    4857    {
    4958    }
    5059
    public:  
    5463            g_VBMan.Release(m_VB);
    5564        if (m_VBIndices)
    5665            g_VBMan.Release(m_VBIndices);
     66        if (m_VBStartCap)
     67            g_VBMan.Release(m_VBStartCap);
     68        if (m_VBStartCapIndices)
     69            g_VBMan.Release(m_VBStartCapIndices);
     70        if (m_VBEndCap)
     71            g_VBMan.Release(m_VBEndCap);
     72        if (m_VBEndCapIndices)
     73            g_VBMan.Release(m_VBEndCapIndices);
    5774    }
    5875
    5976    struct SVertex
    6077    {
    61         SVertex(CVector3D pos, short u, short v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
     78        SVertex(CVector3D pos, float u, float v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; }
    6279        CVector3D m_Position;
    63         GLshort m_UVs[2];
     80        //GLshort m_UVs[2];
     81        GLfloat m_UVs[2];
    6482    };
    65     cassert(sizeof(SVertex) == 16);
     83    //cassert(sizeof(SVertex) == 16);
    6684
    6785    void Update();
    6886
     87    /**
     88     * Creates a line cap of the specified type @p endCapType at the end of the segment going in direction @p normal, and write the vertices and indices
     89     * to @p verticesOut and @p indicesOut, respectively. The drawing mode to use for the resulting vertices/indices array (quad strip,
     90     * triangle strip, ...) is written to @p drawModeOut.
     91     *
     92     * @param corner1 One of the two butt-end corner points of the line to which the cap should be attached.
     93     * @param corner2 One of the two butt-end corner points of the line to which the cap should be attached.
     94     * @param normal Normal vector indicating the direction of the segment to which the cap should be attached.
     95     * @param endCapType The type of end cap to produce.
     96     * @param verticesOut Output vector of vertices to draw using @p drawModeOut. These will be entered into a vertex buffer and handed to the
     97     *                    renderer along with the indices array.
     98     * @param indicesOut Output vector of vertex indices to draw using @p drawModeOut. These will be entered into a vertex index buffer and
     99     *                   handed to the rendered along with the vertex array.
     100     * @param drawModeOut The rendering mode to be used by the renderer to draw the end cap, e.g. GL_QUAD_STRIP, GL_TRIANGLE_STRIP, etc.
     101     */
     102    void CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal, SOverlayTexturedLine::LineCap endCapType,
     103                       std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut, GLenum& drawModeOut);
     104
     105    /// Small utility function; grabs the centroid of the positions of two vertices
     106    inline CVector3D Centroid(const SVertex& v1, const SVertex& v2)
     107    {
     108        return (v1.m_Position + v2.m_Position) * 0.5;
     109    }
     110
    69111    SOverlayTexturedLine* m_Line;
    70112
    71113    CVertexBuffer::VBChunk* m_VB;
    72114    CVertexBuffer::VBChunk* m_VBIndices;
     115
     116    // vertex buffers for custom line end caps (at both the start and end of the line)
     117    CVertexBuffer::VBChunk* m_VBStartCap;
     118    CVertexBuffer::VBChunk* m_VBStartCapIndices;
     119    GLenum m_StartCapDrawMode; // quad strip, triangle fan, ...
     120
     121    CVertexBuffer::VBChunk* m_VBEndCap;
     122    CVertexBuffer::VBChunk* m_VBEndCapIndices;
     123    GLenum m_EndCapDrawMode;
     124
     125    float m_Raise; // small vertical offset of line from terrain to prevent visual glitches
     126
    73127};
    74128
    75129OverlayRenderer::OverlayRenderer()
    void OverlayRenderer::RenderOverlaysBeforeWater()  
    164218    glDisable(GL_BLEND);
    165219}
    166220
     221void OverlayRenderer::RenderTexturedOverlayLines(const std::vector<size_t>& indices, CShaderProgramPtr shaderTexLine)
     222{
     223
     224    //for (size_t i = 0; i < m->texlines.size(); ++i)
     225    for (std::vector<size_t>::const_iterator idxIt = indices.begin(); idxIt != indices.end(); idxIt++)
     226    {
     227        SOverlayTexturedLine* line = m->texlines[(*idxIt)];
     228        if (!line->m_RenderData)
     229            continue;
     230
     231        shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle());
     232        shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle());
     233        shaderTexLine->Uniform("objectColor", line->m_Color);
     234
     235        CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get());
     236
     237        // -- render main line quad strip ----------------------
     238
     239        GLsizei stride = sizeof(CTexturedLineRData::SVertex);
     240        CTexturedLineRData::SVertex* base = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());
     241
     242        glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]);
     243        glTexCoordPointer(2, GL_FLOAT, stride, &base->m_UVs[0]);
     244
     245        u8* indexBase = rdata->m_VBIndices->m_Owner->Bind();
     246        glDrawElements(GL_QUAD_STRIP, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index);
     247
     248        g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count - 2;
     249
     250        // -- render end cap (if any) --------------------------
     251
     252        if (rdata->m_VBEndCap && rdata->m_VBEndCapIndices) // only draw end cap if we've created vertex/index buffers for it
     253        {
     254            CTexturedLineRData::SVertex* endCapBase = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VBEndCap->m_Owner->Bind());
     255
     256            glVertexPointer(3, GL_FLOAT, stride, &endCapBase->m_Position[0]);
     257            glTexCoordPointer(2, GL_FLOAT, stride, &endCapBase->m_UVs[0]);
     258
     259            u8* endCapIndexBase = rdata->m_VBEndCapIndices->m_Owner->Bind();
     260            glDrawElements(rdata->m_EndCapDrawMode, rdata->m_VBEndCapIndices->m_Count, GL_UNSIGNED_SHORT, endCapIndexBase + sizeof(u16)*rdata->m_VBEndCapIndices->m_Index);
     261
     262            g_Renderer.GetStats().m_OverlayTris += rdata->m_VBEndCapIndices->m_Count - 2; // valid for both quad strip and triangle strip
     263
     264        }
     265
     266        // -- render start cap (if any) ------------------------
     267
     268        if (rdata->m_VBStartCap && rdata->m_VBStartCapIndices) // only draw start cap if we've created vertex/index buffers for it
     269        {
     270            CTexturedLineRData::SVertex* startCapBase = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VBStartCap->m_Owner->Bind());
     271
     272            glVertexPointer(3, GL_FLOAT, stride, &startCapBase->m_Position[0]);
     273            glTexCoordPointer(2, GL_FLOAT, stride, &startCapBase->m_UVs[0]);
     274
     275            u8* startCapIndexBase = rdata->m_VBStartCapIndices->m_Owner->Bind();
     276            glDrawElements(rdata->m_StartCapDrawMode, rdata->m_VBStartCapIndices->m_Count, GL_UNSIGNED_SHORT, startCapIndexBase + sizeof(u16)*rdata->m_VBStartCapIndices->m_Index);
     277
     278            g_Renderer.GetStats().m_OverlayTris += rdata->m_VBStartCapIndices->m_Count - 2; // valid for both quad strip and triangle strip
     279        }
     280
     281    }
     282
     283}
     284
    167285void OverlayRenderer::RenderOverlaysAfterWater()
    168286{
    169287    PROFILE("render overlays (after water)");
    void OverlayRenderer::RenderOverlaysAfterWater()  
    182300        glEnableClientState(GL_VERTEX_ARRAY);
    183301        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
    184302
    185         CShaderManager& shaderManager = g_Renderer.GetShaderManager();
    186         CShaderProgramPtr shaderTexLine(shaderManager.LoadProgram("overlayline", std::map<CStr, CStr>()));
     303        CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
    187304
    188         shaderTexLine->Bind();
     305        std::map<CStr, CStr> defAlwaysVisible;
     306        defAlwaysVisible.insert(std::make_pair(CStr("IGNORE_LOS"), CStr("1")));
    189307
    190         CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture();
    191         shaderTexLine->BindTexture("losTex", los.GetTexture());
    192         shaderTexLine->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
     308        CShaderManager& shaderManager = g_Renderer.GetShaderManager();
     309        CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram("overlayline", std::map<CStr, CStr>()));
     310        CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram("overlayline", defAlwaysVisible));
    193311
    194         for (size_t i = 0; i < m->texlines.size(); ++i)
     312        // ----------------------------------------------------------------------------------------
     313        // group the overlay lines by whether they need to be always visible
     314        std::vector<size_t> alwaysVisibleTexLineIndices;
     315        std::vector<size_t> normalTexLineIndices;
     316
     317        for (size_t i = 0; i < m->texlines.size(); i++)
    195318        {
    196             SOverlayTexturedLine* line = m->texlines[i];
    197             if (!line->m_RenderData)
    198                 continue;
     319            if (m->texlines[i]->m_AlwaysVisible)
     320            {
     321                alwaysVisibleTexLineIndices.push_back(i);
     322            }
     323            else
     324            {
     325                normalTexLineIndices.push_back(i);
     326            }
     327        }
    199328
    200             shaderTexLine->BindTexture("baseTex", line->m_TextureBase->GetHandle());
    201             shaderTexLine->BindTexture("maskTex", line->m_TextureMask->GetHandle());
    202             shaderTexLine->Uniform("objectColor", line->m_Color);
     329        // render go go
    203330
    204             CTexturedLineRData* rdata = static_cast<CTexturedLineRData*>(line->m_RenderData.get());
     331        shaderTexLineNormal->Bind();
     332        shaderTexLineNormal->BindTexture("losTex", los.GetTexture());
     333        shaderTexLineNormal->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
    205334
    206             GLsizei stride = sizeof(CTexturedLineRData::SVertex);
    207             CTexturedLineRData::SVertex* base = reinterpret_cast<CTexturedLineRData::SVertex*>(rdata->m_VB->m_Owner->Bind());
     335        RenderTexturedOverlayLines(normalTexLineIndices, shaderTexLineNormal);
    208336
    209             glVertexPointer(3, GL_FLOAT, stride, &base->m_Position[0]);
    210             glTexCoordPointer(2, GL_SHORT, stride, &base->m_UVs[0]);
     337        shaderTexLineNormal->Unbind();
    211338
    212             u8* indexBase = rdata->m_VBIndices->m_Owner->Bind();
    213             glDrawElements(GL_QUAD_STRIP, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index);
     339        // ----------------------------------------------------------------------------------------
    214340
    215             g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count - 2;
    216         }
     341        shaderTexLineAlwaysVisible->Bind();
     342        // TODO: losTex and losTransform are unused in the always visible shader, but I'm not sure if it's worthwhile messing
     343        // with it just to remove these calls
     344        shaderTexLineAlwaysVisible->BindTexture("losTex", los.GetTexture());
     345        shaderTexLineAlwaysVisible->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f);
     346
     347        RenderTexturedOverlayLines(alwaysVisibleTexLineIndices, shaderTexLineAlwaysVisible);
     348
     349        shaderTexLineAlwaysVisible->Unbind();
    217350
    218         shaderTexLine->Unbind();
     351        // ----------------------------------------------------------------------------------------
    219352
    220353        // TODO: the shader should probably be responsible for unbinding its textures
    221354        g_Renderer.BindTexture(1, 0);
    void CTexturedLineRData::Update()  
    289422        m_VBIndices = NULL;
    290423    }
    291424
     425    if (m_VBStartCap)
     426    {
     427        g_VBMan.Release(m_VBStartCap);
     428        m_VBStartCap = NULL;
     429    }
     430
     431    if (m_VBStartCapIndices)
     432    {
     433        g_VBMan.Release(m_VBStartCapIndices);
     434        m_VBStartCapIndices = NULL;
     435    }
     436
     437    if (m_VBEndCap)
     438    {
     439        g_VBMan.Release(m_VBEndCap);
     440        m_VBEndCap = NULL;
     441    }
     442
     443    if (m_VBEndCapIndices)
     444    {
     445        g_VBMan.Release(m_VBEndCapIndices);
     446        m_VBEndCapIndices = NULL;
     447    }
     448
    292449    CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
    293450
    294451    std::vector<SVertex> vertices;
    295452    std::vector<u16> indices;
    296453
    297     short v = 0;
     454    float v = 0.f;
    298455
    299     size_t n = m_Line->m_Coords.size() / 2;
    300     ENSURE(n >= 1);
     456    size_t n = m_Line->m_Coords.size() / 2; // number of line points
     457    bool closed = m_Line->m_Closed;
    301458
    302     CTerrain* terrain = m_Line->m_Terrain;
     459    if (closed)
     460        ENSURE(n >= 1); // granted, it's kind of hard to draw a closed loop through 1 point, but this is the minimum to avoid errors
     461    else
     462        ENSURE(n >= 2);
    303463
    304     // TODO: this assumes paths are closed loops; probably should extend this to
    305     // handle non-closed paths too
     464    CTerrain* terrain = m_Line->m_Terrain;
    306465
    307466    // In each iteration, p1 is the position of vertex i, p0 is i-1, p2 is i+1.
    308467    // To avoid slightly expensive terrain computations we cycle these around and
    309468    // recompute p2 at the end of each iteration.
    310     CVector3D p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]);
    311     CVector3D p1 = CVector3D(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]);
    312     CVector3D p2 = CVector3D(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]);
     469
     470    CVector3D p0;
     471    CVector3D p1(m_Line->m_Coords[0], 0, m_Line->m_Coords[1]);
     472    CVector3D p2(m_Line->m_Coords[(1 % n)*2], 0, m_Line->m_Coords[(1 % n)*2+1]);
     473
     474    if (closed)
     475        // grab the ending point so as to close the loop
     476        p0 = CVector3D(m_Line->m_Coords[(n-1)*2], 0, m_Line->m_Coords[(n-1)*2+1]);
     477    else
     478        // we don't want to loop around and use the direction towards the other end of the line, so create an artificial p0 that
     479        // extends the p2 -> p1 direction, and use that point instead
     480        p0 = p1 + (p1 - p2);
     481
    313482    bool p1floating = false;
    314483    bool p2floating = false;
    315484
    void CTexturedLineRData::Update()  
    341510    {
    342511        // For vertex i, compute bisector of lines (i-1)..(i) and (i)..(i+1)
    343512        // perpendicular to terrain normal
     513        // Push vertices in GL_QUAD_STRIP order
    344514
    345515        // Normal is vertical if on water, else computed from terrain
    346516        CVector3D norm;
    void CTexturedLineRData::Update()  
    356526        if (fabs(l) > 0.000001f) // avoid unlikely divide-by-zero
    357527            b *= m_Line->m_Thickness / l;
    358528
    359         // Raise off the terrain a little bit
    360         const float raised = 0.2f;
    361 
    362         vertices.push_back(SVertex(p1 + b + norm*raised, 0, v));
     529        vertices.push_back(SVertex(p1 + b + norm*m_Raise, 0.f, v));
    363530        indices.push_back(vertices.size() - 1);
    364531
    365         vertices.push_back(SVertex(p1 - b + norm*raised, 1, v));
     532        vertices.push_back(SVertex(p1 - b + norm*m_Raise, 1.f, v));
    366533        indices.push_back(vertices.size() - 1);
    367534
    368535        // Alternate V coordinate for debugging
    void CTexturedLineRData::Update()  
    372539        p0 = p1;
    373540        p1 = p2;
    374541        p1floating = p2floating;
     542
     543        // if in closed mode, wrap around the coordinate array for p2 -- otherwise, extend linearly
     544        if (!closed && i == n-2)
     545            // next iteration is the last point of the line, so create an artificial p2 that extends the p0 -> p1 direction
     546            p2 = p1 + (p1 - p0);
     547        else
    375548        p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]);
     549
    376550        p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z);
    377551        if (p2.Y < w)
    378552        {
    void CTexturedLineRData::Update()  
    383557            p2floating = false;
    384558    }
    385559
     560   
     561    if (closed)
     562    {
    386563    // Close the path
    387564    indices.push_back(0);
    388565    indices.push_back(1);
     566    }
    389567
    390568    m_VB = g_VBMan.Allocate(sizeof(SVertex), vertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER);
    391569    m_VB->m_Owner->UpdateChunkVertices(m_VB, &vertices[0]);
    void CTexturedLineRData::Update()  
    396574
    397575    m_VBIndices = g_VBMan.Allocate(sizeof(u16), indices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
    398576    m_VBIndices->m_Owner->UpdateChunkVertices(m_VBIndices, &indices[0]);
     577
     578    if (!closed)
     579    {
     580        // -------------------------------------------------------------------
     581        // create start cap
     582
     583        std::vector<u16> startCapIndices;
     584        std::vector<SVertex> startCapVertices;
     585
     586        CreateLineCap(
     587            vertices[1].m_Position, // the order of these vertices is actually important here, swapping them produces caps at the wrong side
     588            vertices[0].m_Position,
     589            (Centroid(vertices[1], vertices[0]) - Centroid(vertices[3], vertices[2])).Normalized(),
     590            m_Line->m_StartCap,
     591            startCapVertices,
     592            startCapIndices,
     593            m_StartCapDrawMode
     594        );
     595
     596        if (!startCapVertices.empty() && !startCapIndices.empty())
     597        {
     598            m_VBStartCap = g_VBMan.Allocate(sizeof(SVertex), startCapVertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER);
     599            m_VBStartCap->m_Owner->UpdateChunkVertices(m_VBStartCap, &startCapVertices[0]);
     600
     601            // Update the indices to include the base offset of the vertex data
     602            for (size_t k = 0; k < startCapVertices.size(); ++k)
     603                startCapIndices[k] += m_VBStartCap->m_Index;
     604
     605            m_VBStartCapIndices = g_VBMan.Allocate(sizeof(u16), startCapIndices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
     606            m_VBStartCapIndices->m_Owner->UpdateChunkVertices(m_VBStartCapIndices, &startCapIndices[0]);
     607
     608        }
     609
     610        // -------------------------------------------------------------------
     611        // create end cap
     612
     613        std::vector<u16> endcapIndices;
     614        std::vector<SVertex> endcapVertices;
     615
     616        CreateLineCap(
     617            vertices[2*n-2].m_Position, // second-to-last line vertex
     618            vertices[2*n-1].m_Position, // last line vertex
     619            (Centroid(vertices[2*n-2], vertices[2*n-1]) - Centroid(vertices[2*n-4], vertices[2*n-3])).Normalized(),
     620            m_Line->m_EndCap,
     621            endcapVertices,
     622            endcapIndices,
     623            m_EndCapDrawMode
     624        );
     625
     626        if (!endcapVertices.empty() && !endcapIndices.empty())
     627        {
     628            m_VBEndCap = g_VBMan.Allocate(sizeof(SVertex), endcapVertices.size(), GL_STATIC_DRAW, GL_ARRAY_BUFFER);
     629            m_VBEndCap->m_Owner->UpdateChunkVertices(m_VBEndCap, &endcapVertices[0]);
     630
     631            // Update the indices to include the base offset of the vertex data
     632            for (size_t k = 0; k < endcapVertices.size(); ++k)
     633                endcapIndices[k] += m_VBEndCap->m_Index;
     634
     635            m_VBEndCapIndices = g_VBMan.Allocate(sizeof(u16), endcapIndices.size(), GL_STATIC_DRAW, GL_ELEMENT_ARRAY_BUFFER);
     636            m_VBEndCapIndices->m_Owner->UpdateChunkVertices(m_VBEndCapIndices, &endcapIndices[0]);
     637
     638        }
     639
     640    }
     641}
     642
     643void CTexturedLineRData::CreateLineCap(const CVector3D& corner1, const CVector3D& corner2, const CVector3D& normal, SOverlayTexturedLine::LineCap endCapType,
     644                                       std::vector<SVertex>& verticesOut, std::vector<u16>& indicesOut, GLenum& drawModeOut)
     645{
     646
     647    if (endCapType == SOverlayTexturedLine::LINECAP_FLAT)
     648        return; // no action needed, this is the default
     649
     650    CTerrain* terrain = m_Line->m_Terrain;
     651    CmpPtr<ICmpWaterManager> cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
     652
     653    // when not in closed mode, we've created artificial points for the start- and endpoints that extend the line in the
     654    // direction of the first and the last segment, respectively. Thus, we know both the start and endpoints have perpendicular
     655    // butt endings, i.e. the end corner vertices on either side of the line extend perpendicularly from the segment direction.
     656    // That is to say, we will have something like
     657    //                                                 .
     658    //  this:                     and not like this:  /|
     659    //         ____.                                 / |
     660    //             |                                /  .
     661    //             |                                  /
     662    //         ____.                                 /
     663    //
     664
     665    int roundCapPoints = 8; // = how many points to sample along the semicircle for rounded caps (including corner points)
     666    float radius = m_Line->m_Thickness;
     667    float texCenterU = 0.5f; // U coordinate of butt end centroid
     668    float texCenterV = 0.5f; // V coordinate of butt end centroid
     669
     670    CVector3D centerPoint = (corner1 + corner2) * 0.5f; // middle between last two vertices (i.e. "butt" corner points)
     671    /*centerPoint.X = (corner1.X + corner2.X)/2.f;
     672    centerPoint.Y = (corner1.Y + corner2.Y)/2.f;
     673    centerPoint.Z = (corner1.Z + corner2.Z)/2.f;*/
     674
     675    switch (endCapType)
     676    {
     677
     678    case SOverlayTexturedLine::LINECAP_SHARP:
     679        {
     680            roundCapPoints = 3; // creates only one point directly ahead
     681            radius *= 1.2f; // make it a bit sharper (note that we don't use this for the butt corner points so it should be ok)
     682            texCenterU = 0.485f; // slight visual correction to make the texture edges match up better (they're slightly too thin with only 3 points and a U coord of 0.5f)
     683        }
     684    case SOverlayTexturedLine::LINECAP_ROUND:
     685        {
     686           
     687            // TODO: this currently uses a complicated method where it grabs the points across the arc of the rounded cap, and then
     688            // fetches the height of the map at that point. The idea was to make it "stick" to the terrain better, but it turns out
     689            // to look bad on funky terrain. It'd probably be better to adopt the same method the square line caps use, and just
     690            // extend the line plane. However, that requires rotating the (centerPoint -> cornerPoint) vector along an arbitrary 3D axis,
     691            // and I'm not sure how to do that yet. (--vts)
     692
     693            drawModeOut = GL_TRIANGLE_FAN;
     694
     695            // depending on whether we start rotating from the (center -> corner1) or (center -> corner2) vector,
     696            // this angle needs to be inverted. For the (center -> corner1) vector, apparently we need to use the positive angle;
     697            // there's probably some reason for why that one is the positive one and not vice-versa, but I don't want to be bothered
     698            // to find out why as it probably has to do with right-hand-rules and whatnot.
     699            float stepAngle = (float)(M_PI/(roundCapPoints-1));
     700
     701            // add center point vertex
     702            CVector2D centerPoint2D(centerPoint.X, centerPoint.Z);
     703
     704            verticesOut.push_back(SVertex(centerPoint, texCenterU, texCenterV));
     705            indicesOut.push_back(verticesOut.size() - 1);
     706
     707            // rotate a 2D unit vector from center to one of the butt corner points stepwise 180 degrees to sample semicircular points, then
     708            // multiply by the radius. For each resulting point, grab the actual terrain height. To prevent visual glitches, we reuse the two
     709            // butt corner vertices directly instead of recomputing them as part of the semicircle.
     710            CVector2D baseVector((corner2 - centerPoint).X, (corner2 - centerPoint).Z);
     711            baseVector.Normalize();
     712
     713            // connect to first butt corner point
     714            verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
     715            indicesOut.push_back(verticesOut.size() - 1);
     716
     717            //for (int i=0; i<roundCapPoints; i++)
     718            for (int i=1; i<roundCapPoints-1; i++) // note: excludes the first and last iteration, since we're adding those points manually
     719            {
     720                CVector2D currentVector = baseVector.Rotated(i * stepAngle);
     721
     722                // 2D sample point location in absolute world space
     723                CVector2D worldPos2D = centerPoint2D + (currentVector * radius);
     724
     725                // now grab the 3D location
     726                CVector3D norm;
     727                float groundLevel = terrain->GetExactGroundLevel(worldPos2D.X, worldPos2D.Y);
     728                float w = cmpWaterManager->GetExactWaterLevel(worldPos2D.X, worldPos2D.Y);
     729
     730                // we want this to float on water, so if the actual terrain ground level is under water, then we artificially raise it
     731                // to the water level
     732                if (groundLevel < w)
     733                {
     734                    norm = CVector3D(0, 1, 0);
     735                    groundLevel = w;
     736                }
     737                else
     738                    norm = m_Line->m_Terrain->CalcExactNormal(worldPos2D.X, worldPos2D.Y);
     739
     740                CVector3D worldPos3D(worldPos2D.X, groundLevel, worldPos2D.Y);
     741                worldPos3D += norm * m_Raise;
     742
     743                // let v range from 0 to 1 as we move along the semi-circle, keep u fixed at 0 (i.e. curve the left vertical edge of the texture
     744                // around the edge of the semicircle)
     745                float u = 0.f;
     746                float v = clamp((i/(float)(roundCapPoints-1)), 0.f, 1.f);
     747                verticesOut.push_back(SVertex(worldPos3D, u, v)); // pos, u, v
     748                indicesOut.push_back(verticesOut.size() - 1);
     749            }
     750
     751            // connect back to the other butt corner point to complete the semicircle
     752            verticesOut.push_back(SVertex(corner1, 0.f, 1.f));
     753            indicesOut.push_back(verticesOut.size() - 1);
     754
     755        }
     756        break;
     757
     758    case SOverlayTexturedLine::LINECAP_SQUARE:
     759        {
     760            // extend the (corner1 -> corner2) vector along the direction normal and draw a triangle fan with 3 triangles
     761            // NOTE: the order in which the vertices are pushed out determines the visibility of the rendered fan, as they
     762            // are rendered only one-sided; the wrong order of vertices will only make the fan visible from the bottom.
     763
     764            drawModeOut = GL_TRIANGLE_FAN;
     765
     766            // push center point
     767            verticesOut.push_back(SVertex(centerPoint, texCenterU, texCenterV));
     768            indicesOut.push_back(verticesOut.size() - 1);
     769
     770            // push butt corner point 2
     771            verticesOut.push_back(SVertex(corner2, 0.f, 0.f));
     772            indicesOut.push_back(verticesOut.size() - 1);
     773
     774            // extend butt corner point 2 along the normal vector
     775            verticesOut.push_back(SVertex(corner2 + (normal * (m_Line->m_Thickness)), 0.f, 0.333f));
     776            indicesOut.push_back(verticesOut.size() - 1);
     777
     778            // extend butt corner point 1 along the normal vector
     779            verticesOut.push_back(SVertex(corner1 + (normal * (m_Line->m_Thickness)), 0.f, 0.666f));
     780            indicesOut.push_back(verticesOut.size() - 1);
     781
     782            // push butt corner point 1
     783            verticesOut.push_back(SVertex(corner1, 0.f, 1.0f));
     784            indicesOut.push_back(verticesOut.size() - 1);
     785
     786        }
     787        break;
     788
     789    default:
     790        break;
     791
     792    }
     793
    399794}
  • source/renderer/OverlayRenderer.h

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

    diff --git a/source/renderer/RenderModifiers.h b/source/renderer/RenderModifiers.h
    index 01bc887..ecf8930 100644
    a b  
    1 /* Copyright (C) 2009 Wildfire Games.
     1/* Copyright (C) 2011 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    private:  
    137137/**
    138138 * Class RenderModifierRenderer: Interface to a model renderer that can render
    139139 * its models via a RenderModifier that sets up fragment stages.
     140 * TODO: this appears to be currently unused, should be considered for deletion
    140141 */
    141142class RenderModifierRenderer : public ModelRenderer
    142143{
  • source/scriptinterface/ScriptInterface.cpp

    diff --git a/source/scriptinterface/ScriptInterface.cpp b/source/scriptinterface/ScriptInterface.cpp
    index c175890..c2daa21 100644
    a b JSBool logmsg(JSContext* cx, uintN argc, jsval* vp)  
    326326    return JS_TRUE;
    327327}
    328328
     329JSBool logmsgrender(JSContext* cx, uintN argc, jsval* vp)
     330{
     331    if (argc < 1)
     332    {
     333        JS_SET_RVAL(cx, vp, JSVAL_VOID);
     334        return JS_TRUE;
     335    }
     336
     337    std::wstring str;
     338    if (!ScriptInterface::FromJSVal(cx, JS_ARGV(cx, vp)[0], str))
     339        return JS_FALSE;
     340    LOGMESSAGERENDER(L"%ls", str.c_str());
     341    JS_SET_RVAL(cx, vp, JSVAL_VOID);
     342    return JS_TRUE;
     343}
     344
    329345JSBool warn(JSContext* cx, uintN argc, jsval* vp)
    330346{
    331347    if (argc < 1)
    ScriptInterface_impl::ScriptInterface_impl(const char* nativeScopeName, const sh  
    505521
    506522    JS_DefineFunction(m_cx, m_glob, "print", ::print,  0, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    507523    JS_DefineFunction(m_cx, m_glob, "log",   ::logmsg, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
     524    JS_DefineFunction(m_cx, m_glob, "logr",  ::logmsgrender, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    508525    JS_DefineFunction(m_cx, m_glob, "warn",  ::warn,   1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    509526    JS_DefineFunction(m_cx, m_glob, "error", ::error,  1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
    510527    JS_DefineFunction(m_cx, m_glob, "deepcopy", ::deepcopy, 1, JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT);
  • source/simulation2/TypeList.h

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

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

    diff --git a/source/simulation2/components/CCmpSelectable.cpp b/source/simulation2/components/CCmpSelectable.cpp
    index e9aa3d2..27bb2a6 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
     
    2222
    2323#include "ICmpPosition.h"
    2424#include "ICmpFootprint.h"
     25#include "ICmpVisual.h"
    2526#include "simulation2/MessageTypes.h"
    2627#include "simulation2/helpers/Render.h"
    2728
    public:  
    155156        // (This is only called if a > 0)
    156157
    157158        collector.Submit(&m_Overlay);
     159
     160        // TODO: use this to draw debug overlays for the bounding boxes
     161        /*CmpPtr<ICmpVisual> cmpVisual(GetSimContext(), GetEntityId());
     162        if (!cmpVisual.null())
     163        {
     164            CBound bound = cmpVisual->GetBounds();
     165            CVector3D tmin = bound[0];
     166            CVector3D tmax = bound[1];
     167
     168            static SOverlayLine* overlayLine = new SOverlayLine();
     169            overlayLine->m_Thickness = 2;
     170            overlayLine->m_Color = CColor(1.0, 0.0, 0.0, 1.0);
     171            overlayLine->m_Coords.clear();
     172            // floor square
     173            overlayLine->m_Coords.push_back(tmin.X); overlayLine->m_Coords.push_back(tmin.Y); overlayLine->m_Coords.push_back(tmin.Z);
     174            overlayLine->m_Coords.push_back(tmax.X); overlayLine->m_Coords.push_back(tmin.Y); overlayLine->m_Coords.push_back(tmin.Z);
     175            overlayLine->m_Coords.push_back(tmax.X); overlayLine->m_Coords.push_back(tmin.Y); overlayLine->m_Coords.push_back(tmax.Z);
     176            overlayLine->m_Coords.push_back(tmin.X); overlayLine->m_Coords.push_back(tmin.Y); overlayLine->m_Coords.push_back(tmax.Z);
     177            overlayLine->m_Coords.push_back(tmin.X); overlayLine->m_Coords.push_back(tmin.Y); overlayLine->m_Coords.push_back(tmin.Z);
     178            // roof square
     179            overlayLine->m_Coords.push_back(tmin.X); overlayLine->m_Coords.push_back(tmax.Y); overlayLine->m_Coords.push_back(tmin.Z);
     180            overlayLine->m_Coords.push_back(tmax.X); overlayLine->m_Coords.push_back(tmax.Y); overlayLine->m_Coords.push_back(tmin.Z);
     181            overlayLine->m_Coords.push_back(tmax.X); overlayLine->m_Coords.push_back(tmax.Y); overlayLine->m_Coords.push_back(tmax.Z);
     182            overlayLine->m_Coords.push_back(tmin.X); overlayLine->m_Coords.push_back(tmax.Y); overlayLine->m_Coords.push_back(tmax.Z);
     183            overlayLine->m_Coords.push_back(tmin.X); overlayLine->m_Coords.push_back(tmax.Y); overlayLine->m_Coords.push_back(tmin.Z);
     184            // supports
     185            collector.Submit(overlayLine);
     186        }*/
     187
    158188    }
    159189};
    160190
  • source/simulation2/components/CCmpTerritoryManager.cpp

    diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp
    index e000f34..8e131f2 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    std::vector<CCmpTerritoryManager::TerritoryBoundary> boundaries = ComputeBoundaries();
    747751
    void CCmpTerritoryManager::UpdateBoundaryLines()  
    777781        m_BoundaryLines.push_back(SBoundaryLine());
    778782        m_BoundaryLines.back().connected = boundaries[i].connected;
    779783        m_BoundaryLines.back().color = color;
    780 
    781784        m_BoundaryLines.back().overlay.m_Terrain = terrain;
    782785        m_BoundaryLines.back().overlay.m_TextureBase = textureBase;
    783786        m_BoundaryLines.back().overlay.m_TextureMask = textureMask;
    784787        m_BoundaryLines.back().overlay.m_Color = color;
    785788        m_BoundaryLines.back().overlay.m_Thickness = m_BorderThickness;
     789        m_BoundaryLines.back().overlay.m_Closed = true;
    786790
    787         SimRender::SmoothPointsAverage(boundaries[i].points, true);
     791        SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed);
    788792
    789         SimRender::InterpolatePointsRNS(boundaries[i].points, true, m_BorderSeparation);
     793        SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation);
    790794
    791795        std::vector<float>& points = m_BoundaryLines.back().overlay.m_Coords;
    792796        for (size_t j = 0; j < boundaries[i].points.size(); ++j)
    793797        {
    794798            points.push_back(boundaries[i].points[j].X);
    795799            points.push_back(boundaries[i].points[j].Y);
     800
     801            if (m_EnableLineDebugOverlays)
     802            {
     803                SOverlayLine overlayNode;
     804                if (j > boundaries[i].points.size() - 1 - 7)
     805                    overlayNode.m_Color = CColor(1.f, 0.f, 0.f, 1.f);
     806                else if (j < 7)
     807                    overlayNode.m_Color = CColor(0.f, 1.f, 0.f, 1.f);
     808                else
     809                    overlayNode.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
     810
     811                overlayNode.m_Thickness = 1;
     812                SimRender::ConstructCircleOnGround(GetSimContext(), boundaries[i].points[j].X, boundaries[i].points[j].Y, 0.1f, overlayNode, true);
     813                m_DebugBoundaryLineNodes.push_back(overlayNode);
     814            }
    796815        }
     816
    797817    }
    798818}
    799819
    void CCmpTerritoryManager::RenderSubmit(SceneCollector& collector)  
    822842{
    823843    for (size_t i = 0; i < m_BoundaryLines.size(); ++i)
    824844        collector.Submit(&m_BoundaryLines[i].overlay);
     845   
     846    for( size_t i = 0; i < m_DebugBoundaryLineNodes.size(); ++i)
     847        collector.Submit(&m_DebugBoundaryLineNodes[i]);
     848
    825849}
    826850
    827851player_id_t CCmpTerritoryManager::GetOwner(entity_pos_t x, entity_pos_t z)
  • new file source/simulation2/components/ICmpRallyPoint.cpp

    diff --git a/source/simulation2/components/ICmpRallyPoint.cpp b/source/simulation2/components/ICmpRallyPoint.cpp
    new file mode 100644
    index 0000000..3d0fd4d
    - +  
     1/* Copyright (C) 2011 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#include "precompiled.h"
     19
     20#include "ICmpRallyPoint.h"
     21#include "simulation2/system/InterfaceScripted.h"
     22
     23BEGIN_INTERFACE_WRAPPER(RallyPoint)
     24DEFINE_INTERFACE_METHOD_0("GetPosition", RallyPointPosition, ICmpRallyPoint, GetPosition)
     25DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpRallyPoint, SetPosition, RallyPointPosition)
     26DEFINE_INTERFACE_METHOD_0("IsSet", bool, ICmpRallyPoint, IsSet)
     27DEFINE_INTERFACE_METHOD_0("Unset", void, ICmpRallyPoint, Unset)
     28DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpRallyPoint, SetDisplayed, bool)
     29//DEFINE_INTERFACE_METHOD_0("GetMarkerEntityId", entity_id_t, ICmpRallyPoint, GetMarkerEntityId)
     30END_INTERFACE_WRAPPER(RallyPoint)
  • new file source/simulation2/components/ICmpRallyPoint.h

    diff --git a/source/simulation2/components/ICmpRallyPoint.h b/source/simulation2/components/ICmpRallyPoint.h
    new file mode 100644
    index 0000000..0e9fa9c
    - +  
     1/* Copyright (C) 2010 Wildfire Games.
     2 * This file is part of 0 A.D.
     3 *
     4 * 0 A.D. is free software: you can redistribute it and/or modify
     5 * it under the terms of the GNU General Public License as published by
     6 * the Free Software Foundation, either version 2 of the License, or
     7 * (at your option) any later version.
     8 *
     9 * 0 A.D. is distributed in the hope that it will be useful,
     10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
     11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     12 * GNU General Public License for more details.
     13 *
     14 * You should have received a copy of the GNU General Public License
     15 * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
     16 */
     17
     18#ifndef INCLUDED_ICMPRALLYPOINT
     19#define INCLUDED_ICMPRALLYPOINT
     20
     21#include "simulation2/system/Interface.h"
     22#include "simulation2/helpers/Position.h"
     23
     24#include "maths/FixedVector2D.h"
     25
     26// trivial position subclass for JS conversions (so we can use x and z instead of x and y)
     27class RallyPointPosition : public CFixedVector2D {};
     28
     29/**
     30 * Rally Point.
     31 * Holds the position of a unit's rally point, and renders it to screen.
     32 */
     33class ICmpRallyPoint : public IComponent
     34{
     35public:
     36
     37    virtual RallyPointPosition GetPosition() = 0;
     38    virtual void SetPosition(RallyPointPosition pos) = 0;
     39    virtual void SetDisplayed(bool displayed) = 0;
     40    //virtual entity_id_t GetMarkerEntityId() = 0;
     41    virtual void Unset() = 0;
     42    virtual bool IsSet() = 0;
     43
     44    DECLARE_INTERFACE_TYPE(RallyPoint)
     45};
     46
     47#endif // INCLUDED_ICMPRALLYPOINT
  • source/simulation2/helpers/Geometry.h

    diff --git a/source/simulation2/helpers/Geometry.h b/source/simulation2/helpers/Geometry.h
    index 0fc046c..4e5ede1 100644
    a b  
    1 /* Copyright (C) 2010 Wildfire Games.
     1/* Copyright (C) 2011 Wildfire Games.
    22 * This file is part of 0 A.D.
    33 *
    44 * 0 A.D. is free software: you can redistribute it and/or modify
    class CFixedVector2D;  
    3030namespace Geometry
    3131{
    3232
     33/**
     34 * Returns true if @p point is inside the square with rotated X axis unit vector @p u and rotated Z axis unit vector @p v,
     35 * and half dimensions specified by @p halfSizes. Currently assumes the @p u and @p v vectors are perpendicular. Can also
     36 * be used for rectangles.
     37 *
     38 * @param point point vector of the point that is to be tested relative to the origin (center) of the shape.
     39 * @param u rotated X axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated,
     40 *          this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta), as
     41 *          the absolute Z axis points down in the unit circle.
     42 * @param v rotated Z axis unit vector relative to the absolute XZ plane. Indicates the orientation of the rectangle. If not rotated,
     43 *          this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta), as
     44 *          the absolute Z axis points down in the unit circle.
     45 * @param halfSizes Holds half the dimensions of the shape along the u and v vectors, respectively.
     46 */
    3347bool PointIsInSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    3448
    3549CFixedVector2D GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    3650
    3751fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    3852
     53/**
     54 * Returns the point that is closest to @p point on the edge of the square specified by orientation unit vectors @p u and @p v and half
     55 * dimensions @p halfSize, relative to the center of the square. Currently assumes the @p u and @p v vectors are perpendicular.
     56 * Can also be used for rectangles.
     57 *
     58 * @param point point vector of the point we want to get the nearest edge point for, relative to the origin (center) of the shape.
     59 * @param u rotated X axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated,
     60 *          this value is the absolute X axis unit vector (1,0). If rotated by angle theta, this should be (cos theta, -sin theta).
     61 * @param v rotated Z axis unit vector, relative to the absolute XZ plane. Indicates the orientation of the shape. If not rotated,
     62 *          this value is the absolute Z axis unit vector (0,1). If rotated by angle theta, this should be (sin theta, cos theta).
     63 * @param halfSizes Holds half the dimensions of the shape along the u and v vectors, respectively.
     64 */
    3965CFixedVector2D NearestPointOnSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
    4066
    4167bool TestRaySquare(CFixedVector2D a, CFixedVector2D b, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize);
  • source/simulation2/helpers/Render.cpp

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

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

    diff --git a/source/simulation2/scripting/EngineScriptConversions.cpp b/source/simulation2/scripting/EngineScriptConversions.cpp
    index b7cc512..3399416 100644
    a b  
    2929#include "simulation2/helpers/Grid.h"
    3030#include "simulation2/system/IComponent.h"
    3131#include "simulation2/system/ParamNode.h"
     32#include "simulation2/components/ICmpRallyPoint.h"
    3233
    3334#define FAIL(msg) STMT(JS_ReportError(cx, msg); return false)
    3435
    template<> jsval ScriptInterface::ToJSVal<CFixedVector2D>(JSContext* cx, const C  
    210211    return OBJECT_TO_JSVAL(obj);
    211212}
    212213
     214// rally point conversion; a RallyPointPosition is merely a simple CFixedVector2D with a custom name so that we
     215// can create this convertor for it. This is the same thing as the CFixedVector2D conversion, except that it
     216// renames the y component to z in javascript to prevent breaking any code that relies on it (and vice-versa for
     217// FromJSVal below)
     218template<> jsval ScriptInterface::ToJSVal<RallyPointPosition>(JSContext* cx, const RallyPointPosition& val)
     219{
     220    JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);
     221    if (!obj)
     222        return JSVAL_VOID;
     223
     224    jsval x = ToJSVal(cx, val.X);
     225    jsval z = ToJSVal(cx, val.Y);
     226
     227    JS_SetProperty(cx, obj, "x", &x);
     228    JS_SetProperty(cx, obj, "z", &z); // rename "y" to "z" for JavaScript
     229
     230    return OBJECT_TO_JSVAL(obj);
     231}
     232
     233template<> bool ScriptInterface::FromJSVal<RallyPointPosition>(JSContext* cx, jsval v, RallyPointPosition& out)
     234{
     235    if (!JSVAL_IS_OBJECT(v))
     236        return false; // TODO: report type error
     237    JSObject* obj = JSVAL_TO_OBJECT(v);
     238
     239    jsval p;
     240
     241    if (!JS_GetProperty(cx, obj, "x", &p)) return false; // TODO: report type errors
     242    if (!FromJSVal(cx, p, out.X)) return false;
     243
     244    if (!JS_GetProperty(cx, obj, "z", &p)) return false; // rename "z" to "y" from javascript
     245    if (!FromJSVal(cx, p, out.Y)) return false;
     246
     247    return true;
     248}
     249
    213250template<> jsval ScriptInterface::ToJSVal<Grid<u16> >(JSContext* cx, const Grid<u16>& val)
    214251{
    215252    JSObject* obj = JS_NewObject(cx, NULL, NULL, NULL);