Index: binaries/data/mods/public/gui/session/input.js
===================================================================
--- binaries/data/mods/public/gui/session/input.js
+++ binaries/data/mods/public/gui/session/input.js
@@ -350,6 +350,7 @@
"autocontinue": true,
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {"entities": selection, "x": placementSupport.position.x, "z": placementSupport.position.z, "queued": queued});
Engine.GuiInterfaceCall("PlaySound", { "name": "order_repair", "entity": selection[0] });
if (!queued)
Index: binaries/data/mods/public/gui/session/session.js
===================================================================
--- binaries/data/mods/public/gui/session/session.js
+++ binaries/data/mods/public/gui/session/session.js
@@ -757,7 +757,10 @@
// Display rally points for selected buildings
if (Engine.GetPlayerID() != -1)
+ {
Engine.GuiInterfaceCall("DisplayRallyPoint", { "entities": g_Selection.toList() });
+ Engine.GuiInterfaceCall("DisplayWayPoint", { "entities": g_Selection.toList() });
+ }
}
else if (g_ShowAllStatusBars && now % g_StatusBarUpdate <= tickLength)
recalculateStatusBarDisplay();
Index: binaries/data/mods/public/gui/session/unit_actions.js
===================================================================
--- binaries/data/mods/public/gui/session/unit_actions.js
+++ binaries/data/mods/public/gui/session/unit_actions.js
@@ -45,6 +45,13 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "x": target.x,
+ "z": target.z,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_walk",
"entity": selection[0]
@@ -85,6 +92,13 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "x": target.x,
+ "z": target.z,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_walk",
"entity": selection[0]
@@ -123,6 +137,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_attack",
"entity": selection[0]
@@ -169,6 +189,14 @@
"allowCapture": false
});
+ Engine.GuiInterfaceCall("DisplayWayPoint",
+ {
+ "entities": selection,
+ "x": target.x,
+ "z": target.z,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_attack",
"entity": selection[0]
@@ -229,6 +257,14 @@
"queued": queued,
"allowCapture": false
});
+
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "x": target.x,
+ "z": target.z,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", { "name": "order_patrol", "entity": selection[0] });
return true;
},
@@ -248,7 +284,8 @@
return {
"type": "patrol",
"cursor": "action-patrol",
- "target": target
+ "x": target.x,
+ "z": target.z,
};
},
"preSelectedActionCheck" : function(target)
@@ -275,6 +312,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_heal",
"entity": selection[0]
@@ -326,6 +369,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_repair",
"entity": selection[0]
@@ -367,6 +416,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_repair",
"entity": selection[0]
@@ -438,6 +493,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_gather",
"entity": selection[0]
@@ -486,6 +547,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_gather",
"entity": selection[0]
@@ -547,6 +614,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_trade",
"entity": selection[0]
@@ -638,6 +711,12 @@
"queued": queued
});
+ Engine.GuiInterfaceCall("DisplayWayPoint", {
+ "entities": selection,
+ "target": action.target,
+ "queued": queued
+ });
+
Engine.GuiInterfaceCall("PlaySound", {
"name": "order_garrison",
"entity": selection[0]
Index: binaries/data/mods/public/simulation/components/GuiInterface.js
===================================================================
--- binaries/data/mods/public/simulation/components/GuiInterface.js
+++ binaries/data/mods/public/simulation/components/GuiInterface.js
@@ -32,6 +32,7 @@
this.timeNotificationID = 1;
this.timeNotifications = [];
this.entsRallyPointsDisplayed = [];
+ this.entsWayPointsDisplayed = [];
this.entsWithAuraAndStatusBars = new Set();
this.enabledVisualRangeOverlayTypes = {};
};
@@ -964,43 +965,29 @@
GuiInterface.prototype.GetNonGaiaEntities = function()
{
- return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetNonGaiaEntities();
+ return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetNonGaiaEntities();
};
-/**
- * Displays the rally points of a given list of entities (carried in cmd.entities).
- *
- * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
- * be rendered, in order to support instantaneously rendering a rally point marker at a specified location
- * instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
- * If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
- * RallyPoint component.
- */
-GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
+
+GuiInterface.prototype.DisplayItineraryPoint = function (cmpPlayer, player, cmd, itineraryPointsDisplayed, Renderer_IID, ent_IID)
{
- let cmpPlayer = QueryPlayerIDInterface(player);
-
- // If there are some rally points already displayed, first hide them
- for (let ent of this.entsRallyPointsDisplayed)
+ for (let ent of itineraryPointsDisplayed)
{
- let cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
- if (cmpRallyPointRenderer)
- cmpRallyPointRenderer.SetDisplayed(false);
+ let cmpItineraryPointRenderer = Engine.QueryInterface(ent, Renderer_IID);
+ if (cmpItineraryPointRenderer)
+ cmpItineraryPointRenderer.SetDisplayed(false);
}
- this.entsRallyPointsDisplayed = [];
+ itineraryPointsDisplayed = [];
- // Show the rally points for the passed entities
for (let ent of cmd.entities)
{
- let cmpRallyPointRenderer = Engine.QueryInterface(ent, IID_RallyPointRenderer);
- if (!cmpRallyPointRenderer)
+ let cmpItineraryPointRenderer = Engine.QueryInterface(ent, Renderer_IID);
+ if (!cmpItineraryPointRenderer)
continue;
- // entity must have a rally point component to display a rally point marker
- // (regardless of whether cmd specifies a custom location)
- let cmpRallyPoint = Engine.QueryInterface(ent, IID_RallyPoint);
- if (!cmpRallyPoint)
+ let cmpItineraryPoint = Engine.QueryInterface(ent, ent_IID);
+ if (!cmpItineraryPoint)
continue;
// Verify the owner
@@ -1008,38 +995,70 @@
if (!(cmpPlayer && cmpPlayer.CanControlAllUnits()))
if (!cmpOwnership || cmpOwnership.GetOwner() != player)
continue;
-
+
+
// If the command was passed an explicit position, use that and
// override the real rally point position; otherwise use the real position
let pos;
if (cmd.x && cmd.z)
pos = cmd;
+ else if (cmd.target)
+ pos = Engine.QueryInterface(cmd.target, IID_Position).GetPosition();
else
- pos = cmpRallyPoint.GetPositions()[0]; // may return undefined if no rally point is set
+ pos = cmpItineraryPoint.GetPositions()[0]; // may return undefined if no rally point is set
- if (pos)
- {
- // Only update the position if we changed it (cmd.queued is set)
- if ("queued" in cmd)
- if (cmd.queued == true)
- cmpRallyPointRenderer.AddPosition({ 'x': pos.x, 'y': pos.z }); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
- else
- cmpRallyPointRenderer.SetPosition({ 'x': pos.x, 'y': pos.z }); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
+ if (!pos)
+ continue;
+
+ if ("queued" in cmd)
+ if (cmd.queued == true)
+ cmpItineraryPointRenderer.AddPosition({ 'x': pos.x, 'y': pos.z }); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
+ else
+ cmpItineraryPointRenderer.SetPosition({ 'x': pos.x, 'y': pos.z }); // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
- // rebuild the renderer when not set (when reading saved game or in case of building update)
- else if (!cmpRallyPointRenderer.IsSet())
- for (let posi of cmpRallyPoint.GetPositions())
- cmpRallyPointRenderer.AddPosition({ 'x': posi.x, 'y': posi.z });
+ else if (!cmpItineraryPointRenderer.IsSet())
+ for (let posi of cmpRallyPoint.GetPositions())
+ cmpItineraryPointRenderer.AddPosition({ 'x': posi.x, 'y': posi.z });
- cmpRallyPointRenderer.SetDisplayed(true);
+ cmpItineraryPointRenderer.SetDisplayed(true);
+ itineraryPointsDisplayed.push(ent);
+ }
- // remember which entities have their rally points displayed so we can hide them again
- this.entsRallyPointsDisplayed.push(ent);
- }
- }
+ return itineraryPointsDisplayed;
+}
+
+/**
+ * Displays the rally points of a given list of entities (carried in cmd.entities).
+ *
+ * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the rally point should
+ * be rendered, in order to support instantaneously rendering a rally point marker at a specified location
+ * instead of incurring a delay while PostNetworkCommand processes the set-rallypoint command (see input.js).
+ * If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
+ * RallyPoint component.
+ */
+GuiInterface.prototype.DisplayRallyPoint = function(player, cmd)
+{
+ let cmpPlayer = QueryPlayerIDInterface(player);
+ this.entsRallyPointsDisplayed = this.DisplayItineraryPoint(cmpPlayer, player, cmd, this.entsRallyPointsDisplayed, IID_RallyPointRenderer, IID_RallyPoint)
};
/**
+ * Displays the way points of a given list of entities (carried in cmd.entities).
+ *
+ * The 'cmd' object may carry its own x/z coordinate pair indicating the location where the way point should
+ * be rendered or a target to retrieve that location, in order to support instantaneously rendering a way point
+ * marker at a specified location instead of incurring a delay before PostNetworkCommand is processed.
+ * If cmd doesn't carry a custom location, then the position to render the marker at will be read from the
+ * WayPoint component.
+ */
+GuiInterface.prototype.DisplayWayPoint = function(player, cmd)
+{
+ let cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager);
+ let cmpPlayer = Engine.QueryInterface(cmpPlayerMan.GetPlayerByID(player), IID_Player);
+ this.entsWayPointsDisplayed = this.DisplayItineraryPoint(cmpPlayer, player, cmd, this.entsWayPointsDisplayed, IID_WayPointRenderer, IID_WayPoint)
+};
+
+/**
* Display the building placement preview.
* cmd.template is the name of the entity template, or "" to disable the preview.
* cmd.x, cmd.z, cmd.angle give the location.
@@ -1990,7 +2009,7 @@
"SetStatusBars": 1,
"GetPlayerEntities": 1,
"GetNonGaiaEntities": 1,
- "DisplayRallyPoint": 1,
+ "DisplayWayPoint": 1,
"SetBuildingPlacementPreview": 1,
"SetWallPlacementPreview": 1,
"GetFoundationSnapData": 1,
@@ -2001,6 +2020,8 @@
"GetTradingDetails": 1,
"CanAttack": 1,
"GetBatchTime": 1,
+ "DisplayRallyPoint": 1,
+ "DisplayItineraryPoint": 1,
"IsMapRevealed": 1,
"SetPathfinderDebugOverlay": 1,
Index: binaries/data/mods/public/simulation/components/UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/UnitAI.js
+++ binaries/data/mods/public/simulation/components/UnitAI.js
@@ -3687,6 +3687,16 @@
this.orderQueue.shift();
this.order = this.orderQueue[0];
+ // Remove current waypoint
+ let cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+ if (cmpWayPoint)
+ {
+ cmpWayPoint.Shift();
+ let cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.Shift();
+ }
+ // TODO: Waypoints for formations
if (this.orderQueue.length)
{
@@ -3738,12 +3748,15 @@
var order = { "type": type, "data": data };
this.orderQueue.push(order);
+ if (!order.data.force)
+ this.AddWayPoint(data);
+
// If we didn't already have an order, then process this new one
if (this.orderQueue.length == 1)
{
this.order = order;
let ret = this.UnitFsm.ProcessMessage(this,
- {"type": "Order."+this.order.type, "data": this.order.data}
+ { "type": "Order." + this.order.type, "data": this.order.data }
);
// If the order was rejected then immediately take it off
@@ -3768,6 +3781,7 @@
{
var cheeringOrder = this.orderQueue.shift();
this.orderQueue.unshift(cheeringOrder, order);
+ // TODO: AddWayPoint
}
else if (this.order && this.IsPacking())
{
@@ -3778,6 +3792,9 @@
{
this.orderQueue.unshift(order);
this.order = order;
+
+ this.StackWayPoint(data);
+
let ret = this.UnitFsm.ProcessMessage(this,
{"type": "Order."+this.order.type, "data": this.order.data}
);
@@ -3791,6 +3808,8 @@
this.orderQueue.shift();
this.order = this.orderQueue[0];
}
+
+ // TODO: WayPoints
}
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() });
@@ -3849,15 +3868,88 @@
var order = { "type": type, "data": data };
var packingOrder = this.orderQueue.shift();
this.orderQueue = [packingOrder, order];
+
+ // TODO Waypoints
}
else
{
this.orderQueue = [];
this.PushOrder(type, data);
+
+ this.ReplaceWayPoint(data);
}
Engine.PostMessage(this.entity, MT_UnitAIOrderDataChanged, { "to": this.GetOrderData() });
};
+UnitAI.prototype.StackWayPoint = function(data)
+{
+ let cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+
+ if(!cmpWayPoint)
+ return;
+
+ let pos;
+ let cmpPosition = Engine.QueryInterface(data.target, IID_Position);
+
+ if (data.target && cmpPosition)
+ pos = cmpPosition.GetPosition();
+ else
+ pos = {'x': data.x, 'z': data.z };
+
+ cmpWayPoint.AddPositionFront(pos.x, pos.z);
+
+ let cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+
+ // AddPositionFront takes a CFixedVector2D which has X/Y components, not X/Z
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.Stack({'x': pos.x, 'y': pos.z});
+};
+
+UnitAI.prototype.AddWayPoint = function(data)
+{
+ var cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+ if (cmpWayPoint)
+ {
+ var pos;
+ var cmpPosition;
+ if (data.target && (cmpPosition = Engine.QueryInterface(data.target, IID_Position)))
+ pos = cmpPosition.GetPosition();
+ else if (data.x && data.z)
+ pos = {'x': data.x, 'z': data.z};
+ else
+ return;
+ cmpWayPoint.AddPosition(pos.x, pos.z);
+ var cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.AddPosition({'x': pos.x, 'y': pos.z}); // AddPosition takes a CFixedVector2D which has X/Y components, not X/Z
+ }
+};
+
+UnitAI.prototype.ReplaceWayPoint = function(data)
+{
+ let cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+ if (!cmpWayPoint)
+ return;
+
+ let pos;
+ let cmpPosition;
+
+ cmpWayPoint.Unset();
+
+ if (data.target && (cmpPosition = Engine.QueryInterface(data.target, IID_Position)))
+ pos = cmpPosition.GetPosition();
+ else if (data.x && data.z)
+ pos = {'x': data.x, 'z': data.z};
+ else
+ return;
+
+ cmpWayPoint.AddPosition(pos.x, pos.z);
+ let cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+
+ // SetPosition takes a CFixedVector2D which has X/Y components, not X/Z
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.SetPosition({'x': pos.x, 'y': pos.z}); }
+
UnitAI.prototype.GetOrders = function()
{
return this.orderQueue.slice();
@@ -5292,6 +5384,10 @@
var cmpTrader = Engine.QueryInterface(this.entity, IID_Trader);
if (cmpTrader.HasBothMarkets())
{
+
+ this.AddWayPoint({"target": cmpTrader.GetFirstMarket()});
+ this.AddWayPoint({"target": cmpTrader.GetSecondMarket()});
+
let data = {
"target": cmpTrader.GetFirstMarket(),
"route": route,
@@ -5346,6 +5442,14 @@
UnitAI.prototype.MoveToMarket = function(targetMarket)
{
+ let cmpWayPoint = Engine.QueryInterface(this.entity, IID_WayPoint);
+ if (cmpWayPoint)
+ {
+ cmpWayPoint.Shift();
+ let cmpWayPointRenderer = Engine.QueryInterface(this.entity, IID_WayPointRenderer);
+ if (cmpWayPointRenderer)
+ cmpWayPointRenderer.Shift();
+ }
if (this.waypoints && this.waypoints.length > 1)
{
let point = this.waypoints.pop();
@@ -5361,11 +5465,13 @@
if (!this.CanTrade(currentMarket))
{
this.StopTrading();
+ // TODO clear waypoints
return;
}
if (!this.CheckTargetRange(currentMarket, IID_Trader))
{
+ this.AddWayPoint({"target": currentMarket});
if (!this.MoveToMarket(currentMarket)) // If the current market is not reached try again
this.StopTrading();
return;
Index: binaries/data/mods/public/simulation/components/WayPoint.js
===================================================================
--- binaries/data/mods/public/simulation/components/WayPoint.js
+++ binaries/data/mods/public/simulation/components/WayPoint.js
@@ -0,0 +1,41 @@
+function WayPoint() {}
+
+WayPoint.prototype.Schema = "";
+
+WayPoint.prototype.Init = function()
+{
+ this.positions = [];
+};
+
+WayPoint.prototype.AddPosition = function(x, z)
+{
+ this.positions.push({
+ 'x': x,
+ 'z': z
+ });
+};
+
+WayPoint.prototype.AddPositionFront = function(x, z)
+{
+ this.positions.push({
+ 'x': x,
+ 'z': z
+ });
+};
+
+WayPoint.prototype.GetPositions = function()
+{
+ return this.positions;
+};
+
+WayPoint.prototype.Unset = function()
+{
+ this.positions = [];
+};
+
+WayPoint.prototype.Shift = function()
+{
+ this.positions.shift();
+};
+
+Engine.RegisterComponentType(IID_WayPoint, "WayPoint", WayPoint);
Index: binaries/data/mods/public/simulation/components/interfaces/WayPoint.js
===================================================================
--- binaries/data/mods/public/simulation/components/interfaces/WayPoint.js
+++ binaries/data/mods/public/simulation/components/interfaces/WayPoint.js
@@ -0,0 +1 @@
+Engine.RegisterInterface("WayPoint");
Index: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
+++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js
@@ -13,6 +13,7 @@
Engine.LoadComponentScript("interfaces/ResourceSupply.js");
Engine.LoadComponentScript("interfaces/Timer.js");
Engine.LoadComponentScript("interfaces/UnitAI.js");
+Engine.LoadComponentScript("interfaces/WayPoint.js");
Engine.LoadComponentScript("Formation.js");
Engine.LoadComponentScript("UnitAI.js");
Index: binaries/data/mods/public/simulation/components/tests/test_WayPoint.js
===================================================================
--- binaries/data/mods/public/simulation/components/tests/test_WayPoint.js
+++ binaries/data/mods/public/simulation/components/tests/test_WayPoint.js
@@ -0,0 +1,15 @@
+Engine.LoadComponentScript("interfaces/WayPoint.js");
+Engine.LoadComponentScript("WayPoint.js");
+
+let entId = 1;
+let cmpWayPoint = ConstructComponent(entId, "WayPoint", {});
+
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions(), []);
+cmpWayPoint.AddPosition(10, 5);
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions(), [{ 'x': 10, 'z': 5 }]);
+cmpWayPoint.AddPosition(6, 8);
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions(), [{ 'x': 10, 'z': 5 }, { 'x': 6, 'z': 8 }]);
+cmpWayPoint.Shift();
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions(), [{ 'x': 6, 'z': 8 }]);
+cmpWayPoint.Unset();
+TS_ASSERT_UNEVAL_EQUALS(cmpWayPoint.GetPositions(), []);
Index: binaries/data/mods/public/simulation/helpers/Commands.js
===================================================================
--- binaries/data/mods/public/simulation/helpers/Commands.js
+++ binaries/data/mods/public/simulation/helpers/Commands.js
@@ -1291,6 +1291,11 @@
constructPieceCmd.obstructionControlGroup2 = cmpEndSnappedObstruction.GetControlGroup();
}
+ // Waypoints
+ let cmpGuiInterface = Engine.QueryInterface(SYSTEM_ENTITY, IID_GuiInterface);
+ if (cmpGuiInterface)
+ cmpGuiInterface.DisplayWayPoint(player, constructPieceCmd);
+
var pieceEntityId = TryConstructBuilding(player, cmpPlayer, controlAllUnits, constructPieceCmd);
if (pieceEntityId)
{
Index: binaries/data/mods/public/simulation/templates/template_unit.xml
===================================================================
--- binaries/data/mods/public/simulation/templates/template_unit.xml
+++ binaries/data/mods/public/simulation/templates/template_unit.xml
@@ -135,4 +135,17 @@
false
false
+
+
+ special/rallypoint
+ art/textures/misc/rallypoint_line.png
+ art/textures/misc/rallypoint_line_mask.png
+ 0.2
+
+
+ square
+ round
+ default
+ default
+
Index: source/simulation2/TypeList.h
===================================================================
--- source/simulation2/TypeList.h
+++ source/simulation2/TypeList.h
@@ -193,3 +193,6 @@
INTERFACE(WaterManager)
COMPONENT(WaterManager)
+
+INTERFACE(WayPointRenderer)
+COMPONENT(WayPointRenderer)
Index: source/simulation2/components/CCmpItineraryPointRenderer.h
===================================================================
--- source/simulation2/components/CCmpItineraryPointRenderer.h
+++ source/simulation2/components/CCmpItineraryPointRenderer.h
@@ -0,0 +1,269 @@
+/* Copyright (C) 2017 Wildfire Games.
+* This file is part of 0 A.D.
+*
+* 0 A.D. is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* 0 A.D. is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with 0 A.D. If not, see .
+*/
+#ifndef INCLUDED_CCMPITINERARYPOINTRENDERER
+#define INCLUDED_CCMPITINERARYPOINTRENDERER
+
+#include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpFootprint.h"
+#include "simulation2/components/ICmpObstructionManager.h"
+#include "simulation2/components/ICmpOwnership.h"
+#include "simulation2/components/ICmpPathfinder.h"
+#include "simulation2/components/ICmpPlayer.h"
+#include "simulation2/components/ICmpPlayerManager.h"
+#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRangeManager.h"
+#include "simulation2/components/ICmpTerrain.h"
+#include "simulation2/components/ICmpVisual.h"
+#include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/helpers/Render.h"
+#include "simulation2/helpers/Geometry.h"
+#include "simulation2/system/Component.h"
+
+#include "graphics/Overlay.h"
+#include "graphics/TextureManager.h"
+#include "ps/CLogger.h"
+#include "ps/Profile.h"
+#include "renderer/Renderer.h"
+
+struct SVisibilitySegment
+{
+ bool m_Visible;
+
+ size_t m_StartIndex;
+
+ size_t m_EndIndex; // inclusive
+
+ SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
+ : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex)
+ {}
+
+ bool operator==(const SVisibilitySegment& other) const
+ {
+ return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex);
+ }
+
+ bool operator!=(const SVisibilitySegment& other) const
+ {
+ return !(*this == other);
+ }
+
+ bool IsSinglePoint() const
+ {
+ return (m_StartIndex == m_EndIndex);
+ }
+};
+
+class CCmpItineraryPointRenderer
+{
+public:
+ static void ClassInit(CComponentManager& componentManager);
+
+ static void MergeVisibilitySegments(std::vector& segments);
+
+ virtual void Init(const CParamNode& paramNode);
+
+ virtual void Deinit();
+
+ virtual void Serialize(ISerializer& UNUSED(serialize));
+
+ virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize));
+
+ virtual void AddPosition_wrapper(const CFixedVector2D&) = 0;
+
+ virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) = 0;
+ /**
+ * Returns true if at least one display rally point is set; i.e., if we have a point to render our marker/line at.
+ */
+ virtual bool IsSet() const = 0;
+
+ virtual void SetDisplayed(bool displayed) = 0;
+
+ virtual void SetPosition(const CFixedVector2D& pos) = 0;
+
+ virtual ~CCmpItineraryPointRenderer() = 0;
+
+protected:
+
+ /// Import some types for less verbosity
+ typedef WaypointPath Path;
+ typedef PathGoal Goal;
+ typedef ICmpRangeManager::CLosQuerier CLosQuerier;
+ typedef SOverlayTexturedLine::LineCapType LineCapType;
+
+ ///< Should we render the itinerary points and the path lines? (set from JS when e.g. the unit is selected/deselected)
+ bool m_Displayed;
+
+ ///< Smooth the path before rendering?
+ bool m_SmoothPath;
+
+ /// Draw little overlay circles to indicate where the exact path points are?
+ bool m_EnableDebugNodeOverlay;
+
+ size_t m_LastMarkerCount;
+
+ ///< Last seen owner of this entity (used to keep track of ownership changes).
+ player_id_t m_LastOwner;
+
+ CColor m_LineColor;
+
+ CColor m_LineDashColor;
+
+ CTexturePtr m_Texture;
+
+ CTexturePtr m_TextureMask;
+
+ /// Marker connector line settings (loaded from XML)
+ float m_LineThickness;
+
+ LineCapType m_LineStartCapType;
+
+ LineCapType m_LineEndCapType;
+
+ /// Display position of the points. Note that this are merely the display positions; they not necessarily the same as the
+ /// actual positions used in the simulation at any given time. In particular, we need this separate copy to support
+ /// instantaneously rendering the way point markers/lines when the user sets one in-game (instead of waiting until the
+ /// network-synchronization code sets it on the WayPoint component, which might take up to half a second).
+ std::vector m_ItineraryPoints;
+
+ /// Full path to the itinerary points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
+ std::vector > m_Path;
+
+ /// Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments.
+ std::vector > m_VisibilitySegments;
+
+ /// Entity IDs of the rally point markers.
+ std::vector m_MarkerEntityIds;
+
+ std::vector > m_DebugNodeOverlays;
+
+ /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
+ /// dashes for segments that are inside the SoD.
+ std::vector > m_TexturedOverlayLines;
+
+ /// Pathfinder passability class to use for computing the (long-range) marker line path.
+ std::string m_LinePassabilityClass;
+
+ /// Template name of the rally point markers.
+ std::wstring m_MarkerTemplate;
+
+ std::wstring m_LineTexturePath;
+
+ std::wstring m_LineTextureMaskPath;
+
+ ///< Pathfinder cost class to use for computing the (long-range) marker line path.
+ std::string m_LineCostClass;
+
+ /**
+ * Helper function for AddPosition_wrapper and SetPosition.
+ */
+ virtual void AddPosition(CFixedVector2D pos, bool recompute) = 0;
+
+ /**
+
+ * Sets up all overlay lines for rendering according to the current full path and visibility segments. Splits the line into solid
+ * and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently set, this method
+ * does nothing.
+ */
+ virtual void ConstructAllOverlayLines() = 0;
+
+ /**
+ * Sets up the overlay lines for rendering according to the full path and visibility segments at @p index. Splits the line into
+ * solid and dashed pieces (for the SoD). Should be called whenever the SoD of the path at @p index has changed.
+ */
+ virtual void ConstructOverlayLines(size_t index) = 0;
+
+ /**
+ * Removes points from @p coords that are obstructed by the originating building's footprint, and links up the last point
+ * nicely to the edge of the building's footprint. Only needed if the pathfinder can possibly return obstructed tile waypoints,
+ * i.e. when pathfinding is started from an obstructed tile.
+ */
+ virtual void FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const = 0;
+
+ /**
+ * Get the point on the footprint edge that's as close from "start" as possible.
+ */
+ virtual void GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr cmpPosition, CmpPtr cmpFootprint) const = 0;
+
+ /**
+ * Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility changes, ordered from
+ * building/previous rally point to rally point. Used to construct the overlay line segments and track changes to the SoD.
+ */
+ virtual void GetVisibilitySegments(std::vector& out, size_t index) const = 0;
+
+ /**
+ * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring
+ * segments. You should not have to call this method directly.
+ */
+ virtual void RecomputeAllItineraryPointPaths() = 0;
+
+ /**
+ * Recomputes the full path from this entity/the previous rally point to the next rally point, and does all the necessary
+ * post-processing to make it prettier. This doesn't check if we have a valid position or if a rally point is set.
+ *
+ * You shouldn't need to call this method directly.
+ */
+ virtual void RecomputeItineraryPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder) = 0;
+
+ /**
+ * Recomputes the full path for m_Path[ @p index], and does all the necessary post-processing to make it prettier.
+ *
+ * Should be called whenever either the starting position or the rally point's position changes.
+ */
+ virtual void RecomputeItineraryPointPath_wrapper(size_t index) = 0;
+
+ /**
+ * Repositions the way point markers; moves them outside of the world (ie. hides them), or positions them at the currently
+ * set way points. Also updates the actor's variation according to the entity's current owning player's civilization.
+ *
+ * Should be called whenever either the position of a way point changes (including whether it is set or not), or the display
+ * flag changes, or the ownership of the entity changes.
+ */
+ virtual void UpdateMarkers() = 0;
+
+ /*
+ * Must be called whenever m_Displayed or the size of m_RallyPoints change,
+ * to determine whether we need to respond to render messages.
+ */
+ virtual void UpdateMessageSubscriptions() = 0;
+
+ /**
+ * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to
+ * match if necessary. Does nothing if the rally point lines are not currently set to be displayed, or if no rally point is set.
+ */
+ virtual void UpdateOverlayLines() = 0;
+
+ /**
+ * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily
+ * intended to reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which
+ * will happily curve and bend to reduce costs. Visually, it doesn't make sense for a rally point path to curve and bend when it
+ * could just as well have gone in a straight line; that's why we have this, to make it look more natural.
+ *
+ * @p coords array of path coordinates to simplify
+ * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be joined into a
+ * single 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 consecutive node links will be joined into a single link.
+ * @p floating whether to consider nodes who are under the water level as floating on top of the water
+ */
+ virtual void ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks = 0, bool floating = true) const = 0;
+
+ /**
+ * Displays the rally point, and if m_EnableDebugNodeOverlay is set the debug overlay
+ */
+ virtual void RenderSubmit(SceneCollector& collector) = 0;
+};
+
+#endif // INCLUDED_CCMPITINERARYPOINTRENDERER
\ No newline at end of file
Index: source/simulation2/components/CCmpItineraryPointRenderer.cpp
===================================================================
--- source/simulation2/components/CCmpItineraryPointRenderer.cpp
+++ source/simulation2/components/CCmpItineraryPointRenderer.cpp
@@ -0,0 +1,294 @@
+/* Copyright (C) 2017 Wildfire Games.
+* This file is part of 0 A.D.
+*
+* 0 A.D. is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* 0 A.D. is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with 0 A.D. If not, see .
+*/
+
+#include "precompiled.h"
+#include "CCmpItineraryPointRenderer.h"
+
+void CCmpItineraryPointRenderer::ClassInit(CComponentManager& componentManager)
+{
+ componentManager.SubscribeToMessageType(MT_OwnershipChanged);
+ componentManager.SubscribeToMessageType(MT_TurnStart);
+ componentManager.SubscribeToMessageType(MT_Destroy);
+ componentManager.SubscribeToMessageType(MT_PositionChanged);
+}
+
+void CCmpItineraryPointRenderer::MergeVisibilitySegments(std::vector& segments)
+{
+ // Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding segments.
+ // If they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't
+ // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first).
+
+ // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
+ size_t numSegments = segments.size();
+
+ // WARNING: FOR LOOP TRICKERY AHEAD!
+ for (size_t i = 1; i < numSegments - 1;)
+ {
+ SVisibilitySegment& segment = segments[i];
+ if (segment.IsSinglePoint())
+ {
+ // since the segments' visibility alternates, the surrounding ones should have the same visibility
+ ENSURE(segments[i - 1].m_Visible == segments[i + 1].m_Visible);
+
+ segments[i - 1].m_EndIndex = segments[i + 1].m_EndIndex; // make previous segment span all the way across to the next
+ segments.erase(segments.begin() + i); // erase this segment ...
+ segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i])
+ numSegments -= 2; // we removed 2 segments, so update the loop condition
+ // in the next iteration, i should still point to the segment right after the one that got expanded, which is now
+ // at position i; so don't increment i here
+ }
+ else
+ {
+ ++i;
+ }
+ }
+
+ ENSURE(numSegments == segments.size());
+
+ // check to see if the first segment needs to be merged with its neighbour
+ if (segments.size() >= 2 && segments[0].IsSinglePoint())
+ {
+ int firstSegmentStartIndex = segments.front().m_StartIndex;
+ ENSURE(firstSegmentStartIndex == 0);
+ ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment
+
+ segments.erase(segments.begin());
+ segments.front().m_StartIndex = firstSegmentStartIndex;
+ }
+
+ // check to see if the last segment needs to be merged with its neighbour
+ if (segments.size() >= 2 && segments[segments.size() - 1].IsSinglePoint())
+ {
+ int lastSegmentEndIndex = segments.back().m_EndIndex;
+ ENSURE(!segments[segments.size() - 2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment
+
+ segments.erase(segments.end());
+ segments.back().m_EndIndex = lastSegmentEndIndex;
+ }
+
+ // --------------------------------------------------------------------------------------------------------
+ // at this point, every segment should have at least 2 points
+ for (size_t i = 0; i < segments.size(); ++i)
+ {
+ ENSURE(!segments[i].IsSinglePoint());
+ ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex);
+ }
+}
+
+void CCmpItineraryPointRenderer::Init(const CParamNode& paramNode)
+{
+ m_Displayed = false;
+ m_SmoothPath = true;
+ m_LastOwner = INVALID_PLAYER;
+ m_LastMarkerCount = 0;
+ m_EnableDebugNodeOverlay = false;
+ // ---------------------------------------------------------------------------------------------
+ // load some XML configuration data (schema guarantees that all these nodes are valid)
+
+ m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString();
+
+ const CParamNode& lineColor = paramNode.GetChild("LineColour");
+ m_LineColor = CColor(
+ lineColor.GetChild("@r").ToInt() / 255.f,
+ lineColor.GetChild("@g").ToInt() / 255.f,
+ lineColor.GetChild("@b").ToInt() / 255.f,
+ 1.f
+ );
+
+ const CParamNode& lineDashColor = paramNode.GetChild("LineDashColour");
+ m_LineDashColor = CColor(
+ lineDashColor.GetChild("@r").ToInt() / 255.f,
+ lineDashColor.GetChild("@g").ToInt() / 255.f,
+ lineDashColor.GetChild("@b").ToInt() / 255.f,
+ 1.f
+ );
+
+ m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat();
+ m_LineTexturePath = paramNode.GetChild("LineTexture").ToString();
+ m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString();
+ m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString());
+ m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString());
+ m_LineCostClass = paramNode.GetChild("LineCostClass").ToUTF8();
+ m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8();
+
+ // ---------------------------------------------------------------------------------------------
+ // load some textures
+ if (CRenderer::IsInitialised())
+ {
+ CTextureProperties texturePropsBase(m_LineTexturePath);
+ texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
+ texturePropsBase.SetMaxAnisotropy(4.f);
+ m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
+
+ CTextureProperties texturePropsMask(m_LineTextureMaskPath);
+ texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
+ texturePropsMask.SetMaxAnisotropy(4.f);
+ m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
+ }
+}
+
+void CCmpItineraryPointRenderer::Deinit() {}
+
+void CCmpItineraryPointRenderer::Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
+{
+ Init(paramNode);
+}
+
+void CCmpItineraryPointRenderer::Serialize(ISerializer& UNUSED(serialize))
+{
+ // Do not serialize anything; this is a rendering-only component, it does not and should not affect simulation state
+}
+
+void CCmpItineraryPointRenderer::AddPosition(CFixedVector2D pos, bool recompute)
+{
+ m_ItineraryPoints.push_back(pos);
+ UpdateMarkers();
+
+ if (recompute)
+ RecomputeAllItineraryPointPaths();
+ else
+ RecomputeItineraryPointPath_wrapper(m_ItineraryPoints.size() - 1);
+
+ UpdateMessageSubscriptions();
+}
+
+void CCmpItineraryPointRenderer::AddPosition_wrapper(const CFixedVector2D& pos)
+{
+ AddPosition(pos, false);
+}
+
+void CCmpItineraryPointRenderer::ConstructAllOverlayLines()
+{
+ m_TexturedOverlayLines.clear();
+
+ for (size_t i = 0; i < m_Path.size(); ++i)
+ ConstructOverlayLines(i);
+}
+
+bool CCmpItineraryPointRenderer::IsSet() const
+{
+ return !m_ItineraryPoints.empty();
+}
+
+void CCmpItineraryPointRenderer::RenderSubmit(SceneCollector& collector)
+{
+ // we only get here if the rally point is set and should be displayed
+ for (std::vector >::iterator it = m_TexturedOverlayLines.begin();
+ it != m_TexturedOverlayLines.end(); ++it)
+ {
+ for (std::vector::iterator iter = (*it).begin(); iter != (*it).end(); ++iter)
+ {
+ if (!(*iter).m_Coords.empty())
+ collector.Submit(&(*iter));
+ }
+ }
+
+ if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty())
+ {
+ for (size_t i = 0; i < m_DebugNodeOverlays.size(); ++i)
+ for (size_t j = 0; j < m_DebugNodeOverlays[i].size(); ++j)
+ collector.Submit(&m_DebugNodeOverlays[i][j]);
+ }
+}
+
+void CCmpItineraryPointRenderer::SetDisplayed(bool displayed)
+{
+ if (m_Displayed != displayed)
+ {
+ m_Displayed = displayed;
+
+ // move the markers out of oblivion and back into the real world, or vice-versa
+ UpdateMarkers();
+
+ // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method
+ // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
+ // while this rally point was not being displayed.
+ UpdateOverlayLines();
+
+ UpdateMessageSubscriptions();
+ }
+}
+
+void CCmpItineraryPointRenderer::SetPosition(const CFixedVector2D& pos)
+{
+ if (!(m_ItineraryPoints.size() == 1 && m_ItineraryPoints.front() == pos))
+ {
+ m_ItineraryPoints.clear();
+ AddPosition(pos, true);
+ // Don't need to UpdateMessageSubscriptions here since AddPosition already calls it
+ }
+}
+
+void CCmpItineraryPointRenderer::UpdateOverlayLines()
+{
+ // We should only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive
+ // waste of time to calculate all this stuff (this method is called every turn)
+ if (!m_Displayed || !CCmpItineraryPointRenderer::IsSet())
+ return;
+
+ // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
+ std::vector > newVisibilitySegments;
+ for (size_t i = 0; i < m_Path.size(); ++i)
+ {
+ std::vector tmp;
+ newVisibilitySegments.push_back(tmp);
+ GetVisibilitySegments(newVisibilitySegments[i], i);
+ }
+
+ // Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that.
+ if (m_VisibilitySegments.size() != newVisibilitySegments.size())
+ {
+ m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time
+ ConstructAllOverlayLines();
+ }
+ else
+ {
+ for (size_t i = 0; i < m_VisibilitySegments.size(); ++i)
+ {
+ if (m_VisibilitySegments[i] != newVisibilitySegments[i])
+ {
+ // The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't
+ // change, only the overlay lines we construct from it.
+ m_VisibilitySegments[i] = newVisibilitySegments[i]; // save the new visibility segments to compare against next time
+ ConstructOverlayLines(i);
+ }
+ }
+ }
+}
+
+CCmpItineraryPointRenderer::~CCmpItineraryPointRenderer()
+{
+
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Index: source/simulation2/components/CCmpRallyPointRenderer.h
===================================================================
--- source/simulation2/components/CCmpRallyPointRenderer.h
+++ source/simulation2/components/CCmpRallyPointRenderer.h
@@ -0,0 +1,87 @@
+/* Copyright (C) 2017 Wildfire Games.
+* This file is part of 0 A.D.
+*
+* 0 A.D. is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* 0 A.D. is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with 0 A.D. If not, see .
+*/
+
+#ifndef INCLUDED_CCMPRALLYPOINTRENDERER
+#define INCLUDED_CCMPRALLYPOINTRENDERER
+
+#include "CCmpItineraryPointRenderer.h"
+#include "ICmpRallyPointRenderer.h"
+
+#include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpFootprint.h"
+#include "simulation2/components/ICmpObstructionManager.h"
+#include "simulation2/components/ICmpOwnership.h"
+#include "simulation2/components/ICmpPathfinder.h"
+#include "simulation2/components/ICmpPlayer.h"
+#include "simulation2/components/ICmpPlayerManager.h"
+#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRangeManager.h"
+#include "simulation2/components/ICmpTerrain.h"
+#include "simulation2/components/ICmpVisual.h"
+#include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/helpers/Render.h"
+#include "simulation2/helpers/Geometry.h"
+#include "simulation2/system/Component.h"
+
+#include "graphics/Overlay.h"
+#include "graphics/TextureManager.h"
+#include "ps/CLogger.h"
+#include "ps/Profile.h"
+#include "renderer/Renderer.h"
+
+class CCmpRallyPointRenderer : public CCmpItineraryPointRenderer, public ICmpRallyPointRenderer
+{
+
+public:
+ DEFAULT_COMPONENT_ALLOCATOR(RallyPointRenderer)
+
+ static std::string GetSchema();
+
+ virtual void Init(const CParamNode& paramNode);
+ virtual void Deinit();
+ virtual void Serialize(ISerializer& serialize);
+ virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize);
+
+ virtual void AddPosition_wrapper(const CFixedVector2D& pos);
+ virtual void HandleMessage(const CMessage& msg, bool UNUSED(global));
+ virtual bool IsSet() const;
+ virtual void Reset();
+ virtual void SetDisplayed(bool displayed);
+ virtual void SetPosition(const CFixedVector2D& pos);
+ virtual void UpdateMessageSubscriptions();
+ virtual void UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos);
+
+protected:
+
+ virtual void AddPosition(CFixedVector2D pos, bool recompute);
+ virtual void ConstructAllOverlayLines();
+ virtual void ConstructOverlayLines(size_t index);
+ virtual void FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
+ virtual void GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
+ virtual void GetVisibilitySegments(std::vector& out, size_t index) const;
+ virtual void RecomputeAllItineraryPointPaths();
+ virtual void RecomputeItineraryPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder);
+ virtual void RecomputeItineraryPointPath_wrapper(size_t index);
+ virtual void ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks = 0, bool floating = true) const;
+ virtual void RenderSubmit(SceneCollector& collector);
+ virtual void UpdateMarkers();
+ virtual void UpdateOverlayLines();
+};
+
+REGISTER_COMPONENT_TYPE(RallyPointRenderer)
+
+#endif // INCLUDED_CCMPRALLYPOINTRENDERER
Index: source/simulation2/components/CCmpRallyPointRenderer.cpp
===================================================================
--- source/simulation2/components/CCmpRallyPointRenderer.cpp
+++ source/simulation2/components/CCmpRallyPointRenderer.cpp
@@ -16,725 +16,105 @@
*/
#include "precompiled.h"
-#include "ICmpRallyPointRenderer.h"
+#include "CCmpRallyPointRenderer.h"
-#include "simulation2/MessageTypes.h"
-#include "simulation2/components/ICmpFootprint.h"
-#include "simulation2/components/ICmpObstructionManager.h"
-#include "simulation2/components/ICmpOwnership.h"
-#include "simulation2/components/ICmpPathfinder.h"
-#include "simulation2/components/ICmpPlayer.h"
-#include "simulation2/components/ICmpPlayerManager.h"
-#include "simulation2/components/ICmpPosition.h"
-#include "simulation2/components/ICmpRangeManager.h"
-#include "simulation2/components/ICmpTerrain.h"
-#include "simulation2/components/ICmpVisual.h"
-#include "simulation2/components/ICmpWaterManager.h"
-#include "simulation2/helpers/Render.h"
-#include "simulation2/helpers/Geometry.h"
-#include "simulation2/system/Component.h"
-
-#include "graphics/Overlay.h"
-#include "graphics/TextureManager.h"
-#include "ps/CLogger.h"
-#include "ps/Profile.h"
-#include "renderer/Renderer.h"
-
-struct SVisibilitySegment
+std::string CCmpRallyPointRenderer::GetSchema()
{
- bool m_Visible;
- size_t m_StartIndex;
- size_t m_EndIndex; // inclusive
+ return
+ "Displays a rally point marker where created units will gather when spawned"
+ ""
+ "special/rallypoint"
+ "0.75"
+ "round"
+ "square"
+ ""
+ ""
+ "default"
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ ""
+ ""
+ "flat"
+ "round"
+ "sharp"
+ "square"
+ ""
+ ""
+ ""
+ ""
+ "flat"
+ "round"
+ "sharp"
+ "square"
+ ""
+ ""
+ ""
+ ""
+ "";
+}
- SVisibilitySegment(bool visible, size_t startIndex, size_t endIndex)
- : m_Visible(visible), m_StartIndex(startIndex), m_EndIndex(endIndex)
- {}
-
- bool operator==(const SVisibilitySegment& other) const
- {
- return (m_Visible == other.m_Visible && m_StartIndex == other.m_StartIndex && m_EndIndex == other.m_EndIndex);
- }
-
- bool operator!=(const SVisibilitySegment& other) const
- {
- return !(*this == other);
- }
-
- bool IsSinglePoint() const
- {
- return (m_StartIndex == m_EndIndex);
- }
-};
-
-class CCmpRallyPointRenderer : public ICmpRallyPointRenderer
-{
- // import some types for less verbosity
- typedef WaypointPath Path;
- typedef PathGoal Goal;
- typedef ICmpRangeManager::CLosQuerier CLosQuerier;
- typedef SOverlayTexturedLine::LineCapType LineCapType;
-
-public:
- static void ClassInit(CComponentManager& componentManager)
- {
- componentManager.SubscribeToMessageType(MT_OwnershipChanged);
- componentManager.SubscribeToMessageType(MT_TurnStart);
- componentManager.SubscribeToMessageType(MT_Destroy);
- componentManager.SubscribeToMessageType(MT_PositionChanged);
- }
-
- DEFAULT_COMPONENT_ALLOCATOR(RallyPointRenderer)
-
-protected:
-
- /// Display position of the rally points. Note that this are merely the display positions; they not necessarily the same as the
- /// actual positions used in the simulation at any given time. In particular, we need this separate copy to support
- /// instantaneously rendering the rally point markers/lines when the user sets one in-game (instead of waiting until the
- /// network-synchronization code sets it on the RallyPoint component, which might take up to half a second).
- std::vector m_RallyPoints;
- /// Full path to the rally points as returned by the pathfinder, with some post-processing applied to reduce zig/zagging.
- std::vector > m_Path;
- /// Visibility segments of the rally point paths; splits the path into SoD/non-SoD segments.
- std::deque > m_VisibilitySegments;
-
- bool m_Displayed; ///< Should we render the rally points and the path lines? (set from JS when e.g. the unit is selected/deselected)
- bool m_SmoothPath; ///< Smooth the path before rendering?
-
- std::vector m_MarkerEntityIds; ///< Entity IDs of the rally point markers.
- size_t m_LastMarkerCount;
- player_id_t m_LastOwner; ///< Last seen owner of this entity (used to keep track of ownership changes).
- std::wstring m_MarkerTemplate; ///< Template name of the rally point markers.
-
- /// Marker connector line settings (loaded from XML)
- float m_LineThickness;
- CColor m_LineColor;
- CColor m_LineDashColor;
- LineCapType m_LineStartCapType;
- LineCapType m_LineEndCapType;
- std::wstring m_LineTexturePath;
- std::wstring m_LineTextureMaskPath;
- std::string m_LinePassabilityClass; ///< Pathfinder passability class to use for computing the (long-range) marker line path.
-
- CTexturePtr m_Texture;
- CTexturePtr m_TextureMask;
-
- /// Textured overlay lines to be used for rendering the marker line. There can be multiple because we may need to render
- /// dashes for segments that are inside the SoD.
- std::vector > m_TexturedOverlayLines;
-
- /// Draw little overlay circles to indicate where the exact path points are?
- bool m_EnableDebugNodeOverlay;
- std::vector > m_DebugNodeOverlays;
-
-public:
-
- static std::string GetSchema()
- {
- return
- "Displays a rally point marker where created units will gather when spawned"
- ""
- "special/rallypoint"
- "0.75"
- "round"
- "square"
- ""
- ""
- "default"
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- ""
- "0255"
- ""
- ""
- "0255"
- ""
- ""
- "0255"
- ""
- ""
- ""
- ""
- "0255"
- ""
- ""
- "0255"
- ""
- ""
- "0255"
- ""
- ""
- ""
- ""
- "flat"
- "round"
- "sharp"
- "square"
- ""
- ""
- ""
- ""
- "flat"
- "round"
- "sharp"
- "square"
- ""
- ""
- ""
- ""
- "";
- }
-
- virtual void Init(const CParamNode& paramNode);
-
- virtual void Deinit()
- {
- }
-
- virtual void Serialize(ISerializer& UNUSED(serialize))
- {
- // do NOT serialize anything; this is a rendering-only component, it does not and should not affect simulation state
- }
-
- virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize))
- {
- Init(paramNode);
- }
-
- virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
- {
- switch (msg.GetType())
- {
- case MT_RenderSubmit:
- {
- PROFILE("RallyPoint::RenderSubmit");
- if (m_Displayed && IsSet())
- {
- const CMessageRenderSubmit& msgData = static_cast (msg);
- RenderSubmit(msgData.collector);
- }
- }
- break;
- case MT_OwnershipChanged:
- {
- UpdateMarkers(); // update marker variation to new player's civilization
- }
- break;
- case MT_TurnStart:
- {
- UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
- }
- break;
- case MT_Destroy:
- {
- for (std::vector::iterator it = m_MarkerEntityIds.begin(); it < m_MarkerEntityIds.end(); ++it)
- {
- if (*it != INVALID_ENTITY)
- {
- GetSimContext().GetComponentManager().DestroyComponentsSoon(*it);
- *it = INVALID_ENTITY;
- }
- }
- }
- break;
- case MT_PositionChanged:
- {
- // Unlikely to happen in-game, but can occur in atlas
- // Just recompute the path from the entity to the first rally point
- RecomputeRallyPointPath_wrapper(0);
- }
- break;
- }
- }
-
- /*
- * Must be called whenever m_Displayed or the size of m_RallyPoints change,
- * to determine whether we need to respond to render messages.
- */
- void UpdateMessageSubscriptions()
- {
- bool needRender = m_Displayed && IsSet();
- GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
- }
-
- virtual void AddPosition_wrapper(const CFixedVector2D& pos)
- {
- AddPosition(pos, false);
- }
-
- virtual void SetPosition(const CFixedVector2D& pos)
- {
- if (!(m_RallyPoints.size() == 1 && m_RallyPoints.front() == pos))
- {
- m_RallyPoints.clear();
- AddPosition(pos, true);
- // Don't need to UpdateMessageSubscriptions here since AddPosition already calls it
- }
- }
-
- virtual void UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos)
- {
- if (rallyPointId >= m_RallyPoints.size())
- return;
-
- m_RallyPoints[rallyPointId] = pos;
-
- UpdateMarkers();
-
- // Compute a new path for the current, and if existing the next rally point
- RecomputeRallyPointPath_wrapper(rallyPointId);
- if (rallyPointId+1 < m_RallyPoints.size())
- RecomputeRallyPointPath_wrapper(rallyPointId+1);
- }
-
- virtual void SetDisplayed(bool displayed)
- {
- if (m_Displayed != displayed)
- {
- m_Displayed = displayed;
-
- // move the markers out of oblivion and back into the real world, or vice-versa
- UpdateMarkers();
-
- // Check for changes to the SoD and update the overlay lines accordingly. We need to do this here because this method
- // only takes effect when the display flag is active; we need to pick up changes to the SoD that might have occurred
- // while this rally point was not being displayed.
- UpdateOverlayLines();
-
- UpdateMessageSubscriptions();
- }
- }
-
- virtual void Reset()
- {
- m_RallyPoints.clear();
- RecomputeAllRallyPointPaths();
- UpdateMessageSubscriptions();
- }
-
- /**
- * Returns true if at least one display rally point is set; i.e., if we have a point to render our marker/line at.
- */
- bool IsSet() const
- {
- return !m_RallyPoints.empty();
- }
-
-private:
-
- /**
- * Helper function for AddPosition_wrapper and SetPosition.
- */
- void AddPosition(CFixedVector2D pos, bool recompute)
- {
- m_RallyPoints.push_back(pos);
- UpdateMarkers();
-
- if (recompute)
- RecomputeAllRallyPointPaths();
- else
- RecomputeRallyPointPath_wrapper(m_RallyPoints.size()-1);
-
- UpdateMessageSubscriptions();
- }
-
- /**
- * Repositions the rally point markers; moves them outside of the world (ie. hides them), or positions them at the currently
- * set rally points. Also updates the actor's variation according to the entity's current owning player's civilization.
- *
- * Should be called whenever either the position of a rally point changes (including whether it is set or not), or the display
- * flag changes, or the ownership of the entity changes.
- */
- void UpdateMarkers();
-
- /**
- * Recomputes all the full paths from this entity to the rally point and from the rally point to the next, and does all the necessary
- * post-processing to make them prettier.
- *
- * Should be called whenever all rally points' position changes.
- */
- void RecomputeAllRallyPointPaths();
-
- /**
- * Recomputes the full path for m_Path[ @p index], and does all the necessary post-processing to make it prettier.
- *
- * Should be called whenever either the starting position or the rally point's position changes.
- */
- void RecomputeRallyPointPath_wrapper(size_t index);
-
- /**
- * Recomputes the full path from this entity/the previous rally point to the next rally point, and does all the necessary
- * post-processing to make it prettier. This doesn't check if we have a valid position or if a rally point is set.
- *
- * You shouldn't need to call this method directly.
- */
- void RecomputeRallyPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder);
-
- /**
- * Checks for changes to the SoD to the previously saved state, and reconstructs the visibility segments and overlay lines to
- * match if necessary. Does nothing if the rally point lines are not currently set to be displayed, or if no rally point is set.
- */
- void UpdateOverlayLines();
-
- /**
- * Sets up all overlay lines for rendering according to the current full path and visibility segments. Splits the line into solid
- * and dashed pieces (for the SoD). Should be called whenever the SoD has changed. If no full path is currently set, this method
- * does nothing.
- */
- void ConstructAllOverlayLines();
-
- /**
- * Sets up the overlay lines for rendering according to the full path and visibility segments at @p index. Splits the line into
- * solid and dashed pieces (for the SoD). Should be called whenever the SoD of the path at @p index has changed.
- */
- void ConstructOverlayLines(size_t index);
-
- /**
- * Removes points from @p coords that are obstructed by the originating building's footprint, and links up the last point
- * nicely to the edge of the building's footprint. Only needed if the pathfinder can possibly return obstructed tile waypoints,
- * i.e. when pathfinding is started from an obstructed tile.
- */
- void FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
-
- /**
- * Get the point on the footprint edge that's as close from "start" as possible.
- */
- void GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
-
- /**
- * Returns a list of indices of waypoints in the current path (m_Path[index]) where the LOS visibility changes, ordered from
- * building/previous rally point to rally point. Used to construct the overlay line segments and track changes to the SoD.
- */
- void GetVisibilitySegments(std::deque& out, size_t index) const;
-
- /**
- * Simplifies the path by removing waypoints that lie between two points that are visible from one another. This is primarily
- * intended to reduce some unnecessary curviness of the path; the pathfinder returns a mathematically (near-)optimal path, which
- * will happily curve and bend to reduce costs. Visually, it doesn't make sense for a rally point path to curve and bend when it
- * could just as well have gone in a straight line; that's why we have this, to make it look more natural.
- *
- * @p coords array of path coordinates to simplify
- * @p maxSegmentLinks if non-zero, indicates the maximum amount of consecutive node-to-node links that can be joined into a
- * single 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 consecutive node links will be joined into a single link.
- * @p floating whether to consider nodes who are under the water level as floating on top of the water
- */
- void ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks = 0, bool floating = true) const;
-
- /**
- * Helper function to GetVisibilitySegments, factored out for testing. Merges single-point segments with its neighbouring
- * segments. You should not have to call this method directly.
- */
- static void MergeVisibilitySegments(std::deque& segments);
-
- void RenderSubmit(SceneCollector& collector);
-};
-
-REGISTER_COMPONENT_TYPE(RallyPointRenderer)
-
void CCmpRallyPointRenderer::Init(const CParamNode& paramNode)
{
- m_Displayed = false;
- m_SmoothPath = true;
- m_LastOwner = INVALID_PLAYER;
- m_LastMarkerCount = 0;
- m_EnableDebugNodeOverlay = false;
-
- // ---------------------------------------------------------------------------------------------
- // load some XML configuration data (schema guarantees that all these nodes are valid)
-
- m_MarkerTemplate = paramNode.GetChild("MarkerTemplate").ToString();
-
- const CParamNode& lineColor = paramNode.GetChild("LineColor");
- m_LineColor = CColor(
- lineColor.GetChild("@r").ToInt()/255.f,
- lineColor.GetChild("@g").ToInt()/255.f,
- lineColor.GetChild("@b").ToInt()/255.f,
- 1.f
- );
-
- const CParamNode& lineDashColor = paramNode.GetChild("LineDashColor");
- m_LineDashColor = CColor(
- lineDashColor.GetChild("@r").ToInt()/255.f,
- lineDashColor.GetChild("@g").ToInt()/255.f,
- lineDashColor.GetChild("@b").ToInt()/255.f,
- 1.f
- );
-
- m_LineThickness = paramNode.GetChild("LineThickness").ToFixed().ToFloat();
- m_LineTexturePath = paramNode.GetChild("LineTexture").ToString();
- m_LineTextureMaskPath = paramNode.GetChild("LineTextureMask").ToString();
- m_LineStartCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineStartCap").ToString());
- m_LineEndCapType = SOverlayTexturedLine::StrToLineCapType(paramNode.GetChild("LineEndCap").ToString());
- m_LinePassabilityClass = paramNode.GetChild("LinePassabilityClass").ToUTF8();
-
- // ---------------------------------------------------------------------------------------------
- // load some textures
-
- if (CRenderer::IsInitialised())
- {
- CTextureProperties texturePropsBase(m_LineTexturePath);
- texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
- texturePropsBase.SetMaxAnisotropy(4.f);
- m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase);
-
- CTextureProperties texturePropsMask(m_LineTextureMaskPath);
- texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE);
- texturePropsMask.SetMaxAnisotropy(4.f);
- m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
- }
+ CCmpItineraryPointRenderer::Init(paramNode);
}
-
-void CCmpRallyPointRenderer::UpdateMarkers()
+void CCmpRallyPointRenderer::Deinit()
{
- player_id_t previousOwner = m_LastOwner;
- for (size_t i = 0; i < m_RallyPoints.size(); ++i)
- {
- if (i >= m_MarkerEntityIds.size())
- m_MarkerEntityIds.push_back(INVALID_ENTITY);
-
- if (m_MarkerEntityIds[i] == INVALID_ENTITY)
- {
- // no marker exists yet, create one first
- CComponentManager& componentMgr = GetSimContext().GetComponentManager();
-
- // allocate a new entity for the marker
- if (!m_MarkerTemplate.empty())
- {
- m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity();
- if (m_MarkerEntityIds[i] != INVALID_ENTITY)
- m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]);
- }
- }
-
- // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it
- if (m_MarkerEntityIds[i] == INVALID_ENTITY)
- LOGERROR("Failed to create rally point marker entity");
-
- CmpPtr markerCmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
- if (markerCmpPosition)
- {
- if (m_Displayed && IsSet())
- {
- markerCmpPosition->MoveTo(m_RallyPoints[i].X, m_RallyPoints[i].Y);
- }
- else
- {
- markerCmpPosition->MoveOutOfWorld(); // hide it
- }
- }
-
- // set rally point flag selection based on player civilization
- CmpPtr cmpOwnership(GetEntityHandle());
- if (!cmpOwnership)
- continue;
-
- player_id_t ownerId = cmpOwnership->GetOwner();
- if (ownerId == INVALID_PLAYER || (ownerId == previousOwner && m_LastMarkerCount >= i))
- continue;
-
- m_LastOwner = ownerId;
- CmpPtr cmpPlayerManager(GetSystemEntity());
- // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time
- // (we can't rely on component initialization order in Init())
- if (!cmpPlayerManager)
- continue;
-
- CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId));
- if (!cmpPlayer)
- continue;
-
- CmpPtr cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]);
- if (cmpVisualActor)
- cmpVisualActor->SetVariant("civ", CStrW(cmpPlayer->GetCiv()).ToUTF8());
- }
- m_LastMarkerCount = m_RallyPoints.size() - 1;
+ CCmpItineraryPointRenderer::Deinit();
}
-
-void CCmpRallyPointRenderer::RecomputeAllRallyPointPaths()
+void CCmpRallyPointRenderer::Serialize(ISerializer& serialize)
{
- m_Path.clear();
- m_VisibilitySegments.clear();
- m_TexturedOverlayLines.clear();
-
- //// ///////////////////////////////////////////////
- if (m_EnableDebugNodeOverlay)
- m_DebugNodeOverlays.clear();
- //// //////////////////////////////////////////////
-
- if (!IsSet())
- return; // no use computing a path if the rally point isn't set
-
- CmpPtr cmpPosition(GetEntityHandle());
- if (!cmpPosition || !cmpPosition->IsInWorld())
- return; // no point going on if this entity doesn't have a position or is outside of the world
-
- CmpPtr cmpFootprint(GetEntityHandle());
- CmpPtr cmpPathfinder(GetSystemEntity());
-
- for (size_t i = 0; i < m_RallyPoints.size(); ++i)
- {
- RecomputeRallyPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder);
- }
+ CCmpItineraryPointRenderer::Serialize(serialize);
}
-
-void CCmpRallyPointRenderer::RecomputeRallyPointPath_wrapper(size_t index)
+void CCmpRallyPointRenderer::Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
- if (!IsSet())
- return; // no use computing a path if the rally point isn't set
-
- CmpPtr cmpPosition(GetEntityHandle());
- if (!cmpPosition || !cmpPosition->IsInWorld())
- return; // no point going on if this entity doesn't have a position or is outside of the world
-
- CmpPtr cmpFootprint(GetEntityHandle());
- CmpPtr cmpPathfinder(GetSystemEntity());
-
- RecomputeRallyPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder);
+ CCmpItineraryPointRenderer::Deserialize(paramNode, deserialize);
}
-void CCmpRallyPointRenderer::RecomputeRallyPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder)
+void CCmpRallyPointRenderer::AddPosition(CFixedVector2D pos, bool recompute)
{
- while (index >= m_Path.size())
- {
- std::vector tmp;
- m_Path.push_back(tmp);
- }
- m_Path[index].clear();
-
- while (index >= m_VisibilitySegments.size())
- {
- std::deque tmp;
- m_VisibilitySegments.push_back(tmp);
- }
- m_VisibilitySegments[index].clear();
-
- // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
- // list of waypoints (i.e. a Path) from the goal to the foundation/previous rally point, where each
- // waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth.
- Path path;
- std::vector& waypoints = path.m_Waypoints;
-
- CFixedVector2D start(cmpPosition->GetPosition2D());
- Goal goal = { Goal::POINT, m_RallyPoints[index].X, m_RallyPoints[index].Y };
-
- if (index == 0)
- GetClosestsEdgePointFrom(start,m_RallyPoints[index], cmpPosition, cmpFootprint);
- else
- {
- start.X = m_RallyPoints[index-1].X;
- start.Y = m_RallyPoints[index-1].Y;
- }
- cmpPathfinder->ComputePath(start.X, start.Y, goal, cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass), path);
-
- // Check if we got a path back; if not we probably have two markers less than one tile apart.
- if (path.m_Waypoints.size() < 2)
- {
- m_Path[index].emplace_back(start.X.ToFloat(), start.Y.ToFloat());
- m_Path[index].emplace_back(m_RallyPoints[index].X.ToFloat(), m_RallyPoints[index].Y.ToFloat());
- return;
- }
- else if (index == 0)
- {
- // sometimes this ends up not being optimal if you asked for a long path, so improve.
- CFixedVector2D newend(waypoints[waypoints.size()-2].x,waypoints[waypoints.size()-2].z);
- GetClosestsEdgePointFrom(newend,newend, cmpPosition, cmpFootprint);
- waypoints.back().x = newend.X;
- waypoints.back().z = newend.Y;
- }
- else
- {
- // make sure we actually start at the rallypoint because the pathfinder moves us to a usable tile.
- waypoints.back().x = m_RallyPoints[index-1].X;
- waypoints.back().z = m_RallyPoints[index-1].Y;
- }
-
- // pathfinder makes us go to the nearest passable cell which isn't always what we want
- waypoints[0].x = m_RallyPoints[index].X;
- waypoints[0].z = m_RallyPoints[index].Y;
-
- // From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth
- // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot.
- // Since we'll only be further using these points for rendering purposes, using floats should be fine.
-
- for (Waypoint& waypoint : waypoints)
- m_Path[index].emplace_back(waypoint.x.ToFloat(), waypoint.z.ToFloat());
-
- // add the start position
- // m_Path[index].emplace_back(m_RallyPoints[index].X.ToFloat(), m_RallyPoints[index].Y.ToFloat());
-
- // Post-processing
-
- // Linearize the path;
- // Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path
- // goes from the marker to this entity/the previous flag and we want to keep the point at the marker's exact position,
- // loop backwards through the waypoints so that the marker waypoint is maintained.
- // TODO: see if we can do this at the same time as the waypoint -> coord conversion above
- for(size_t i = m_Path[index].size() - 2; i > 0; --i)
- m_Path[index][i] = (m_Path[index][i] + m_Path[index][i-1]) / 2.0f;
-
- // if there's a footprint and this path starts from this entity, remove any points returned by the pathfinder that may be on obstructed footprint tiles
- //if (index == 0 && cmpFootprint)
- // FixFootprintWaypoints(m_Path[index], cmpPosition, cmpFootprint);
-
- // Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles
- // (prevents segments that are too long to properly stick to the terrain)
- ReduceSegmentsByVisibility(m_Path[index], 6);
-
- // Debug overlays
- if (m_EnableDebugNodeOverlay)
- {
- while (index >= m_DebugNodeOverlays.size())
- m_DebugNodeOverlays.emplace_back();
-
- m_DebugNodeOverlays[index].clear();
- }
- if (m_EnableDebugNodeOverlay && m_SmoothPath)
- {
- // Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the
- // terrain so we can still see them after the interpolated points are added)
- for (CVector2D& point : m_Path[index])
- {
- SOverlayLine overlayLine;
- overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f);
- overlayLine.m_Thickness = 2;
- SimRender::ConstructSquareOnGround(GetSimContext(), point.X, point.Y, 0.2f, 0.2f, 1.0f, overlayLine, true);
- m_DebugNodeOverlays[index].push_back(overlayLine);
- }
- }
-
- if (m_SmoothPath)
- // The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together
- // by the visibility reduction. The more node links that can be joined together, the more interpolated points you need to
- // generate to be able to deal with local terrain height changes.
- SimRender::InterpolatePointsRNS(m_Path[index], false, 0, 4); // no offset, keep line at its exact path
-
- // 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
- GetVisibilitySegments(m_VisibilitySegments[index], index);
-
- // build overlay lines for the new path
- ConstructOverlayLines(index);
+ CCmpItineraryPointRenderer::AddPosition(pos, recompute);
}
-
+void CCmpRallyPointRenderer::AddPosition_wrapper(const CFixedVector2D& pos)
+{
+ CCmpItineraryPointRenderer::AddPosition_wrapper(pos);
+}
void CCmpRallyPointRenderer::ConstructAllOverlayLines()
{
- m_TexturedOverlayLines.clear();
-
- for (size_t i = 0; i < m_Path.size(); ++i)
- ConstructOverlayLines(i);
+ CCmpItineraryPointRenderer::ConstructAllOverlayLines();
}
-
void CCmpRallyPointRenderer::ConstructOverlayLines(size_t index)
{
// We need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the
@@ -745,14 +125,19 @@
std::vector tmp;
m_TexturedOverlayLines.push_back(tmp);
}
- m_TexturedOverlayLines[index].clear();
+ std::vector >::iterator iter = m_TexturedOverlayLines.begin();
+ size_t count = index;
+ while (count--)
+ iter++;
+ (*iter).clear();
+
if (m_Path[index].size() < 2)
return;
LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
- for (std::deque::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it)
+ for (std::vector::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it)
{
const SVisibilitySegment& segment = (*it);
@@ -785,7 +170,7 @@
overlayLine.m_Coords.push_back(m_Path[index][j].Y);
}
- m_TexturedOverlayLines[index].push_back(overlayLine);
+ (*iter).push_back(overlayLine);
}
else
{
@@ -809,7 +194,7 @@
// See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly
// a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is.
- int numFitUnmodified = floor(distance/(dashSize + clearSize));
+ int numFitUnmodified = floor(distance / (dashSize + clearSize));
float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize));
// Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly.
@@ -821,7 +206,7 @@
// we always want to have at least one dash/clear pair (i.e., "|===| |===|"); also, we need to avoid division by zero below.
numFitUnmodified = std::max(1, numFitUnmodified);
- float pairwiseLengthDifference = (remainderDistance - maxDashSize)/numFitUnmodified; // can be either positive or negative
+ float pairwiseLengthDifference = (remainderDistance - maxDashSize) / numFitUnmodified; // can be either positive or negative
dashSize += pairDashRatio * pairwiseLengthDifference;
clearSize += (1 - pairDashRatio) * pairwiseLengthDifference;
@@ -832,7 +217,7 @@
// build overlay lines for dashes
size_t numDashes = dashedLine.m_StartIndices.size();
- for (size_t i=0; i < numDashes; i++)
+ for (size_t i = 0; i < numDashes; i++)
{
SOverlayTexturedLine dashOverlay;
@@ -857,12 +242,11 @@
dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y);
}
- m_TexturedOverlayLines[index].push_back(dashOverlay);
+ (*iter).push_back(dashOverlay);
}
}
}
-
//// //////////////////////////////////////////////
if (m_EnableDebugNodeOverlay)
{
@@ -882,44 +266,90 @@
}
//// //////////////////////////////////////////////
}
-
-void CCmpRallyPointRenderer::UpdateOverlayLines()
+void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const
{
- // We should only do this if the rally point is currently being displayed and set inside the world, otherwise it's a massive
- // waste of time to calculate all this stuff (this method is called every turn)
- if (!m_Displayed || !IsSet())
- return;
+ ENSURE(cmpPosition);
+ ENSURE(cmpFootprint);
- // see if there have been any changes to the SoD by grabbing the visibility edge points and comparing them to the previous ones
- std::deque > newVisibilitySegments;
- for (size_t i = 0; i < m_Path.size(); ++i)
+ // -----------------------------------------------------------------------------------------------------
+ // TODO: nasty fixed/float conversions everywhere
+
+ // grab the shape and dimensions of the footprint
+ entity_pos_t footprintSize0, footprintSize1, footprintHeight;
+ ICmpFootprint::EShape footprintShape;
+ cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
+
+ // grab the center of the footprint
+ CFixedVector2D center = cmpPosition->GetPosition2D();
+
+ // -----------------------------------------------------------------------------------------------------
+
+ switch (footprintShape)
{
- std::deque tmp;
- newVisibilitySegments.push_back(tmp);
- GetVisibilitySegments(newVisibilitySegments[i], i);
- }
+ case ICmpFootprint::SQUARE:
+ {
+ // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
- // Check if the full path changed, then reconstruct all overlay lines, otherwise check if a segment changed and update that.
- if (m_VisibilitySegments.size() != newVisibilitySegments.size())
- {
- m_VisibilitySegments = newVisibilitySegments; // save the new visibility segments to compare against next time
- ConstructAllOverlayLines();
+ // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
+ // and the rotated unit vectors in the X/Z plane of the shape's footprint
+ // (the Footprint itself holds only the outline, the Position holds the orientation)
+
+ fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
+ fixed a = cmpPosition->GetRotation().Y;
+ sincos_approx(a, s, c);
+ CFixedVector2D u(c, -s); // unit vector along the rotated X axis
+ CFixedVector2D v(s, c); // unit vector along the rotated Z axis
+ CFixedVector2D halfSize(footprintSize0 / 2, footprintSize1 / 2);
+
+ // starting from the start position, check if any points are within the footprint of the building
+ // (this is possible if the pathfinder was started from a point located within the footprint)
+ for (int i = (int)(coords.size() - 1); i >= 0; i--)
+ {
+ const CVector2D& wp = coords[i];
+ if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center, u, v, halfSize))
+ {
+ coords.erase(coords.begin() + i);
+ }
+ else
+ {
+ break; // point no longer inside footprint, from this point on neither will any of the following be
+ }
+ }
+
+ // 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
+ CFixedVector2D lastWaypoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y));
+ CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(lastWaypoint - center, u, v, halfSize); // relative to the shape origin (center)
+ CVector2D footprintEdge((center.X + footprintEdgePoint.X).ToFloat(), (center.Y + footprintEdgePoint.Y).ToFloat());
+ coords.push_back(footprintEdge);
+
}
- else
+ break;
+ case ICmpFootprint::CIRCLE:
{
- for (size_t i = 0; i < m_VisibilitySegments.size(); ++i)
+ // in this case, both footprintSize0 and 1 indicate the circle's radius
+
+ for (int i = (int)(coords.size() - 1); i >= 0; i--)
{
- if (m_VisibilitySegments[i] != newVisibilitySegments[i])
+ const CVector2D& wp = coords[i];
+ fixed pointDistance = (CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center).Length();
+ if (pointDistance <= footprintSize0)
{
- // The visibility segments have changed, reconstruct the overlay lines to match. NOTE: The path itself doesn't
- // change, only the overlay lines we construct from it.
- m_VisibilitySegments[i] = newVisibilitySegments[i]; // save the new visibility segments to compare against next time
- ConstructOverlayLines(i);
+ coords.erase(coords.begin() + i);
}
+ else
+ {
+ break; // point no longer inside footprint, from this point on neither will any of the following be
+ }
}
+
+ // add a point right on the edge of the footprint so that it links up nicely with the rest of the path
+ CVector2D centerVec2D(center.X.ToFloat(), center.Y.ToFloat());
+ CVector2D centerToLast(coords.back() - centerVec2D);
+ coords.push_back(centerVec2D + (centerToLast.Normalized() * footprintSize0.ToFloat()));
}
+ break;
+ }
}
-
void CCmpRallyPointRenderer::GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr cmpPosition, CmpPtr cmpFootprint) const
{
ENSURE(cmpPosition);
@@ -935,123 +365,288 @@
switch (footprintShape)
{
- case ICmpFootprint::SQUARE:
+ case ICmpFootprint::SQUARE:
+ {
+ // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
+
+ // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
+ // and the rotated unit vectors in the X/Z plane of the shape's footprint
+ // (the Footprint itself holds only the outline, the Position holds the orientation)
+
+ fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
+ fixed a = cmpPosition->GetRotation().Y;
+ sincos_approx(a, s, c);
+ CFixedVector2D u(c, -s); // unit vector along the rotated X axis
+ CFixedVector2D v(s, c); // unit vector along the rotated Z axis
+ CFixedVector2D halfSize(footprintSize0 / 2, footprintSize1 / 2);
+
+ CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(start - center, u, v, halfSize);
+ result = center + footprintEdgePoint;
+ break;
+ }
+ case ICmpFootprint::CIRCLE:
+ {
+ // in this case, both footprintSize0 and 1 indicate the circle's radius
+ // Transform target to the point nearest on the edge.
+ CFixedVector2D centerVec2D(center.X, center.Y);
+ CFixedVector2D centerToLast(start - centerVec2D);
+ centerToLast.Normalize();
+ result = centerVec2D + (centerToLast.Multiply(footprintSize0));
+ break;
+ }
+ }
+}
+void CCmpRallyPointRenderer::GetVisibilitySegments(std::vector& out, size_t index) const
+{
+ out.clear();
+
+ if (m_Path[index].size() < 2)
+ return;
+
+ CmpPtr cmpRangeMgr(GetSystemEntity());
+
+ player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
+ CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
+
+ // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
+ // a new one at the next point.
+
+ bool lastVisible = losQuerier.IsExplored(
+ (fixed::FromFloat(m_Path[index][0].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(),
+ (fixed::FromFloat(m_Path[index][0].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest()
+ );
+ size_t curSegmentStartIndex = 0; // starting node index of the current segment
+
+ for (size_t k = 1; k < m_Path[index].size(); ++k)
+ {
+ // grab tile indices for this coord
+ int i = (fixed::FromFloat(m_Path[index][k].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
+ int j = (fixed::FromFloat(m_Path[index][k].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
+
+ bool nodeVisible = losQuerier.IsExplored(i, j);
+ if (nodeVisible != lastVisible)
{
- // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
+ // visibility changed; write out the segment that was just completed and get ready for the new one
+ out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
- // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
- // and the rotated unit vectors in the X/Z plane of the shape's footprint
- // (the Footprint itself holds only the outline, the Position holds the orientation)
+ //curSegmentStartIndex = k; // new segment starts here
+ curSegmentStartIndex = k - 1;
+ lastVisible = nodeVisible;
+ }
- fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
- fixed a = cmpPosition->GetRotation().Y;
- sincos_approx(a, s, c);
- CFixedVector2D u(c, -s); // unit vector along the rotated X axis
- CFixedVector2D v(s, c); // unit vector along the rotated Z axis
- CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2);
+ }
- CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(start - center, u, v, halfSize);
- result = center + footprintEdgePoint;
- break;
+ // terminate the last segment
+ out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1));
+ MergeVisibilitySegments(out);
+}
+void CCmpRallyPointRenderer::HandleMessage(const CMessage& msg, bool UNUSED(global))
+{
+ switch (msg.GetType())
+ {
+ case MT_RenderSubmit:
+ {
+ PROFILE("RallyPoint::RenderSubmit");
+ if (m_Displayed && CCmpItineraryPointRenderer::IsSet())
+ {
+ const CMessageRenderSubmit& msgData = static_cast (msg);
+ RenderSubmit(msgData.collector);
}
- case ICmpFootprint::CIRCLE:
+ }
+ break;
+ case MT_OwnershipChanged:
+ {
+ UpdateMarkers(); // update marker variation to new player's civilization
+ }
+ break;
+ case MT_TurnStart:
+ {
+ UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
+ }
+ break;
+ case MT_Destroy:
+ {
+ for (std::vector::iterator it = m_MarkerEntityIds.begin(); it != m_MarkerEntityIds.end(); ++it)
{
- // in this case, both footprintSize0 and 1 indicate the circle's radius
- // Transform target to the point nearest on the edge.
- CFixedVector2D centerVec2D(center.X, center.Y);
- CFixedVector2D centerToLast(start - centerVec2D);
- centerToLast.Normalize();
- result = centerVec2D + (centerToLast.Multiply(footprintSize0));
- break;
+ if (*it != INVALID_ENTITY)
+ {
+ GetSimContext().GetComponentManager().DestroyComponentsSoon(*it);
+ *it = INVALID_ENTITY;
+ }
}
}
+ break;
+ case MT_PositionChanged:
+ {
+ // Unlikely to happen in-game, but can occur in atlas
+ // Just recompute the path from the entity to the first rally point
+ RecomputeItineraryPointPath_wrapper(0);
+ }
+ break;
+ }
}
+bool CCmpRallyPointRenderer::IsSet() const
+{
+ return CCmpItineraryPointRenderer::IsSet();
+}
+void CCmpRallyPointRenderer::RecomputeAllItineraryPointPaths()
+{
+ m_Path.clear();
+ m_VisibilitySegments.clear();
+ m_TexturedOverlayLines.clear();
-void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const
+ if (!IsSet())
+ return; // no use computing a path if the way point isn't set
+
+ CmpPtr cmpPosition(GetSimContext(), GetEntityId());
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ return; // no point going on if this entity doesn't have a position or is outside of the world
+ // Not used
+
+ CmpPtr cmpFootprint(GetSimContext(), GetEntityId());
+ CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
+
+ for (size_t i = 0; i < m_ItineraryPoints.size(); ++i)
+ {
+ RecomputeItineraryPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder);
+ }
+}
+void CCmpRallyPointRenderer::RecomputeItineraryPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder)
{
- ENSURE(cmpPosition);
- ENSURE(cmpFootprint);
+ while (index >= m_Path.size())
+ {
+ std::vector tmp;
+ m_Path.push_back(tmp);
+ }
+ m_Path[index].clear();
- // -----------------------------------------------------------------------------------------------------
- // TODO: nasty fixed/float conversions everywhere
+ while (index >= m_VisibilitySegments.size())
+ {
+ std::vector tmp;
+ m_VisibilitySegments.push_back(tmp);
+ }
+ m_VisibilitySegments[index].clear();
- // grab the shape and dimensions of the footprint
- entity_pos_t footprintSize0, footprintSize1, footprintHeight;
- ICmpFootprint::EShape footprintShape;
- cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
+ // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
+ // list of waypoints (i.e. a Path) from the goal to the foundation/previous rally point, where each
+ // waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth.
+ Path path;
+ std::vector& waypoints = path.m_Waypoints;
- // grab the center of the footprint
- CFixedVector2D center = cmpPosition->GetPosition2D();
+ CFixedVector2D start(cmpPosition->GetPosition2D());
+ Goal goal = { Goal::POINT, m_ItineraryPoints[index].X, m_ItineraryPoints[index].Y };
- // -----------------------------------------------------------------------------------------------------
+ if (index == 0)
+ GetClosestsEdgePointFrom(start, m_ItineraryPoints[index], cmpPosition, cmpFootprint);
+ else
+ {
+ start.X = m_ItineraryPoints[index - 1].X;
+ start.Y = m_ItineraryPoints[index - 1].Y;
+ }
+ cmpPathfinder->ComputePath(start.X, start.Y, goal, cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass), path);
- switch (footprintShape)
+ // Check if we got a path back; if not we probably have two markers less than one tile apart.
+ if (path.m_Waypoints.size() < 2)
{
- case ICmpFootprint::SQUARE:
- {
- // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
+ m_Path[index].emplace_back(start.X.ToFloat(), start.Y.ToFloat());
+ m_Path[index].emplace_back(m_ItineraryPoints[index].X.ToFloat(), m_ItineraryPoints[index].Y.ToFloat());
+ return;
+ }
+ else if (index == 0)
+ {
+ // sometimes this ends up not being optimal if you asked for a long path, so improve.
+ CFixedVector2D newend(waypoints[waypoints.size() - 2].x, waypoints[waypoints.size() - 2].z);
+ GetClosestsEdgePointFrom(newend, newend, cmpPosition, cmpFootprint);
+ waypoints.back().x = newend.X;
+ waypoints.back().z = newend.Y;
+ }
+ else
+ {
+ // make sure we actually start at the rallypoint because the pathfinder moves us to a usable tile.
+ waypoints.back().x = m_ItineraryPoints[index - 1].X;
+ waypoints.back().z = m_ItineraryPoints[index - 1].Y;
+ }
- // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
- // and the rotated unit vectors in the X/Z plane of the shape's footprint
- // (the Footprint itself holds only the outline, the Position holds the orientation)
+ // pathfinder makes us go to the nearest passable cell which isn't always what we want
+ waypoints[0].x = m_ItineraryPoints[index].X;
+ waypoints[0].z = m_ItineraryPoints[index].Y;
- fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
- fixed a = cmpPosition->GetRotation().Y;
- sincos_approx(a, s, c);
- CFixedVector2D u(c, -s); // unit vector along the rotated X axis
- CFixedVector2D v(s, c); // unit vector along the rotated Z axis
- CFixedVector2D halfSize(footprintSize0/2, footprintSize1/2);
+ // From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth
+ // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot.
+ // Since we'll only be further using these points for rendering purposes, using floats should be fine.
- // starting from the start position, check if any points are within the footprint of the building
- // (this is possible if the pathfinder was started from a point located within the footprint)
- for(int i = (int)(coords.size() - 1); i >= 0; i--)
- {
- const CVector2D& wp = coords[i];
- if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center, u, v, halfSize))
- {
- coords.erase(coords.begin() + i);
- }
- else
- {
- break; // point no longer inside footprint, from this point on neither will any of the following be
- }
- }
+ for (Waypoint& waypoint : waypoints)
+ m_Path[index].emplace_back(waypoint.x.ToFloat(), waypoint.z.ToFloat());
- // 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
- CFixedVector2D lastWaypoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y));
- CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(lastWaypoint - center, u, v, halfSize); // relative to the shape origin (center)
- CVector2D footprintEdge((center.X + footprintEdgePoint.X).ToFloat(), (center.Y + footprintEdgePoint.Y).ToFloat());
- coords.push_back(footprintEdge);
+ // add the start position
+ // m_Path[index].emplace_back(m_ItineraryPoints[index].X.ToFloat(), m_ItineraryPoints[index].Y.ToFloat());
- }
- break;
- case ICmpFootprint::CIRCLE:
- {
- // in this case, both footprintSize0 and 1 indicate the circle's radius
+ // Post-processing
- for(int i = (int)(coords.size() - 1); i >= 0; i--)
- {
- const CVector2D& wp = coords[i];
- fixed pointDistance = (CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center).Length();
- if (pointDistance <= footprintSize0)
- {
- coords.erase(coords.begin() + i);
- }
- else
- {
- break; // point no longer inside footprint, from this point on neither will any of the following be
- }
- }
+ // Linearize the path;
+ // Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path
+ // goes from the marker to this entity/the previous flag and we want to keep the point at the marker's exact position,
+ // loop backwards through the waypoints so that the marker waypoint is maintained.
+ // TODO: see if we can do this at the same time as the waypoint -> coord conversion above
+ for (size_t i = m_Path[index].size() - 2; i > 0; --i)
+ m_Path[index][i] = (m_Path[index][i] + m_Path[index][i - 1]) / 2.0f;
- // add a point right on the edge of the footprint so that it links up nicely with the rest of the path
- CVector2D centerVec2D(center.X.ToFloat(), center.Y.ToFloat());
- CVector2D centerToLast(coords.back() - centerVec2D);
- coords.push_back(centerVec2D + (centerToLast.Normalized() * footprintSize0.ToFloat()));
+ // if there's a footprint and this path starts from this entity, remove any points returned by the pathfinder that may be on obstructed footprint tiles
+ //if (index == 0 && cmpFootprint)
+ // FixFootprintWaypoints(m_Path[index], cmpPosition, cmpFootprint);
+
+ // Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles
+ // (prevents segments that are too long to properly stick to the terrain)
+ ReduceSegmentsByVisibility(m_Path[index], 6);
+
+ // Debug overlays
+ if (m_EnableDebugNodeOverlay)
+ {
+ while (index >= m_DebugNodeOverlays.size())
+ m_DebugNodeOverlays.emplace_back();
+
+ m_DebugNodeOverlays[index].clear();
+ }
+ if (m_EnableDebugNodeOverlay && m_SmoothPath)
+ {
+ // Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the
+ // terrain so we can still see them after the interpolated points are added)
+ for (CVector2D& point : m_Path[index])
+ {
+ SOverlayLine overlayLine;
+ overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f);
+ overlayLine.m_Thickness = 2;
+ SimRender::ConstructSquareOnGround(GetSimContext(), point.X, point.Y, 0.2f, 0.2f, 1.0f, overlayLine, true);
+ m_DebugNodeOverlays[index].push_back(overlayLine);
}
- break;
}
+
+ if (m_SmoothPath)
+ // The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together
+ // by the visibility reduction. The more node links that can be joined together, the more interpolated points you need to
+ // generate to be able to deal with local terrain height changes.
+ SimRender::InterpolatePointsRNS(m_Path[index], false, 0, 4); // no offset, keep line at its exact path
+
+ // 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
+ GetVisibilitySegments(m_VisibilitySegments[index], index);
+
+ // build overlay lines for the new path
+ ConstructOverlayLines(index);
}
+void CCmpRallyPointRenderer::RecomputeItineraryPointPath_wrapper(size_t index)
+{
+ if (!CCmpItineraryPointRenderer::IsSet())
+ return;
+ CmpPtr cmpPosition(GetEntityHandle());
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ return;
+
+ CmpPtr cmpFootprint(GetEntityHandle());
+ CmpPtr cmpPathfinder(GetSystemEntity());
+
+ RecomputeItineraryPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder);
+}
void CCmpRallyPointRenderer::ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks, bool floating) const
{
CmpPtr cmpPathFinder(GetSystemEntity());
@@ -1127,7 +722,7 @@
newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list
- // update base node coordinates
+ // update base node coordinates
baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
@@ -1145,132 +740,109 @@
coords.swap(newCoords);
}
-
-void CCmpRallyPointRenderer::GetVisibilitySegments(std::deque& out, size_t index) const
+void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector)
{
- out.clear();
-
- if (m_Path[index].size() < 2)
- return;
-
- CmpPtr cmpRangeMgr(GetSystemEntity());
-
- player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
- CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
-
- // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
- // a new one at the next point.
-
- bool lastVisible = losQuerier.IsExplored(
- (fixed::FromFloat(m_Path[index][0].X) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest(),
- (fixed::FromFloat(m_Path[index][0].Y) / (int) TERRAIN_TILE_SIZE).ToInt_RoundToNearest()
- );
- size_t curSegmentStartIndex = 0; // starting node index of the current segment
-
- for (size_t k = 1; k < m_Path[index].size(); ++k)
+ CCmpItineraryPointRenderer::RenderSubmit(collector);
+}
+void CCmpRallyPointRenderer::Reset()
+{
+ m_ItineraryPoints.clear();
+ RecomputeAllItineraryPointPaths();
+ UpdateMessageSubscriptions();
+}
+void CCmpRallyPointRenderer::SetDisplayed(bool displayed)
+{
+ CCmpItineraryPointRenderer::SetDisplayed(displayed);
+}
+void CCmpRallyPointRenderer::SetPosition(const CFixedVector2D& pos)
+{
+ CCmpItineraryPointRenderer::SetPosition(pos);
+}
+void CCmpRallyPointRenderer::UpdateMarkers()
+{
+ player_id_t previousOwner = m_LastOwner;
+ for (size_t i = 0; i < m_ItineraryPoints.size(); ++i)
{
- // grab tile indices for this coord
- int i = (fixed::FromFloat(m_Path[index][k].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
- int j = (fixed::FromFloat(m_Path[index][k].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
+ if (i >= m_MarkerEntityIds.size())
+ m_MarkerEntityIds.push_back(INVALID_ENTITY);
- bool nodeVisible = losQuerier.IsExplored(i, j);
- if (nodeVisible != lastVisible)
+ if (m_MarkerEntityIds[i] == INVALID_ENTITY)
{
- // visibility changed; write out the segment that was just completed and get ready for the new one
- out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
+ // no marker exists yet, create one first
+ CComponentManager& componentMgr = GetSimContext().GetComponentManager();
- //curSegmentStartIndex = k; // new segment starts here
- curSegmentStartIndex = k - 1;
- lastVisible = nodeVisible;
+ // allocate a new entity for the marker
+ if (!m_MarkerTemplate.empty())
+ {
+ m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity();
+ if (m_MarkerEntityIds[i] != INVALID_ENTITY)
+ m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]);
+ }
}
- }
+ // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it
+ if (m_MarkerEntityIds[i] == INVALID_ENTITY)
+ LOGERROR("Failed to create rally point marker entity");
- // terminate the last segment
- out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1));
-
- MergeVisibilitySegments(out);
-}
-
-void CCmpRallyPointRenderer::MergeVisibilitySegments(std::deque& segments)
-{
- // Scan for single-point segments; if they are inbetween two other segments, delete them and merge the surrounding segments.
- // If they're at either end of the path, include them in their bordering segment (but only if those bordering segments aren't
- // themselves single-point segments, because then we would want those to get absorbed by its surrounding ones first).
-
- // first scan for absorptions of single-point surrounded segments (i.e. excluding edge segments)
- size_t numSegments = segments.size();
-
- // WARNING: FOR LOOP TRICKERY AHEAD!
- for (size_t i = 1; i < numSegments - 1;)
- {
- SVisibilitySegment& segment = segments[i];
- if (segment.IsSinglePoint())
+ CmpPtr markerCmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
+ if (markerCmpPosition)
{
- // since the segments' visibility alternates, the surrounding ones should have the same visibility
- ENSURE(segments[i-1].m_Visible == segments[i+1].m_Visible);
-
- segments[i-1].m_EndIndex = segments[i+1].m_EndIndex; // make previous segment span all the way across to the next
- segments.erase(segments.begin() + i); // erase this segment ...
- segments.erase(segments.begin() + i); // and the next (we removed [i], so [i+1] is now at position [i])
- numSegments -= 2; // we removed 2 segments, so update the loop condition
- // in the next iteration, i should still point to the segment right after the one that got expanded, which is now
- // at position i; so don't increment i here
+ if (m_Displayed && CCmpItineraryPointRenderer::IsSet())
+ {
+ markerCmpPosition->MoveTo(m_ItineraryPoints[i].X, m_ItineraryPoints[i].Y);
+ }
+ else
+ {
+ markerCmpPosition->MoveOutOfWorld(); // hide it
+ }
}
- else
- {
- ++i;
- }
- }
- ENSURE(numSegments == segments.size());
+ // set rally point flag selection based on player civilization
+ CmpPtr cmpOwnership(GetEntityHandle());
+ if (!cmpOwnership)
+ continue;
- // check to see if the first segment needs to be merged with its neighbour
- if (segments.size() >= 2 && segments[0].IsSinglePoint())
- {
- int firstSegmentStartIndex = segments.front().m_StartIndex;
- ENSURE(firstSegmentStartIndex == 0);
- ENSURE(!segments[1].IsSinglePoint()); // at this point, the second segment should never be a single-point segment
+ player_id_t ownerId = cmpOwnership->GetOwner();
+ if (ownerId == INVALID_PLAYER || (ownerId == previousOwner && m_LastMarkerCount >= i))
+ continue;
- segments.erase(segments.begin());
- segments.front().m_StartIndex = firstSegmentStartIndex;
- }
+ m_LastOwner = ownerId;
+ CmpPtr cmpPlayerManager(GetSystemEntity());
+ // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time
+ // (we can't rely on component initialization order in Init())
+ if (!cmpPlayerManager)
+ continue;
- // check to see if the last segment needs to be merged with its neighbour
- if (segments.size() >= 2 && segments[segments.size()-1].IsSinglePoint())
- {
- int lastSegmentEndIndex = segments.back().m_EndIndex;
- ENSURE(!segments[segments.size()-2].IsSinglePoint()); // at this point, the second-to-last segment should never be a single-point segment
+ CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId));
+ if (!cmpPlayer)
+ continue;
- segments.erase(segments.end());
- segments.back().m_EndIndex = lastSegmentEndIndex;
+ CmpPtr cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]);
+ if (cmpVisualActor)
+ cmpVisualActor->SetVariant("civ", CStrW(cmpPlayer->GetCiv()).ToUTF8());
}
-
- // --------------------------------------------------------------------------------------------------------
- // at this point, every segment should have at least 2 points
- for (size_t i = 0; i < segments.size(); ++i)
- {
- ENSURE(!segments[i].IsSinglePoint());
- ENSURE(segments[i].m_EndIndex > segments[i].m_StartIndex);
- }
+ m_LastMarkerCount = m_ItineraryPoints.size() - 1;
}
-
-void CCmpRallyPointRenderer::RenderSubmit(SceneCollector& collector)
+void CCmpRallyPointRenderer::UpdateMessageSubscriptions()
{
- // we only get here if the rally point is set and should be displayed
- for (size_t i = 0; i < m_TexturedOverlayLines.size(); ++i)
- {
- for (size_t j = 0; j < m_TexturedOverlayLines[i].size(); ++j)
- {
- if (!m_TexturedOverlayLines[i][j].m_Coords.empty())
- collector.Submit(&m_TexturedOverlayLines[i][j]);
- }
- }
+ bool needRender = m_Displayed && CCmpItineraryPointRenderer::IsSet();
+ GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
+}
+void CCmpRallyPointRenderer::UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos)
+{
+ if (rallyPointId >= m_ItineraryPoints.size())
+ return;
- if (m_EnableDebugNodeOverlay && !m_DebugNodeOverlays.empty())
- {
- for (size_t i = 0; i < m_DebugNodeOverlays.size(); ++i)
- for (size_t j = 0; j < m_DebugNodeOverlays[i].size(); ++j)
- collector.Submit(&m_DebugNodeOverlays[i][j]);
- }
+ m_ItineraryPoints[rallyPointId] = pos;
+
+ UpdateMarkers();
+
+ // Compute a new path for the current, and if existing the next rally point
+ RecomputeItineraryPointPath_wrapper(rallyPointId);
+ if (rallyPointId + 1 < m_ItineraryPoints.size())
+ RecomputeItineraryPointPath_wrapper(rallyPointId + 1);
}
+void CCmpRallyPointRenderer::UpdateOverlayLines()
+{
+ CCmpItineraryPointRenderer::UpdateOverlayLines();
+}
Index: source/simulation2/components/CCmpWayPointRenderer.h
===================================================================
--- source/simulation2/components/CCmpWayPointRenderer.h
+++ source/simulation2/components/CCmpWayPointRenderer.h
@@ -0,0 +1,82 @@
+/* Copyright (C) 2017 Wildfire Games.
+* This file is part of 0 A.D.
+*
+* 0 A.D. is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 of the License, or
+* (at your option) any later version.
+*
+* 0 A.D. is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with 0 A.D. If not, see .
+*/
+
+#ifndef INCLUDED_CCMPWAYPOINTRENDERER
+#define INCLUDED_CCMPWAYPOINTRENDERER
+
+#include "CCmpItineraryPointRenderer.h"
+#include "ICmpWayPointRenderer.h"
+
+#include "simulation2/MessageTypes.h"
+#include "simulation2/components/ICmpFootprint.h"
+#include "simulation2/components/ICmpObstructionManager.h"
+#include "simulation2/components/ICmpOwnership.h"
+#include "simulation2/components/ICmpPathfinder.h"
+#include "simulation2/components/ICmpPlayer.h"
+#include "simulation2/components/ICmpPlayerManager.h"
+#include "simulation2/components/ICmpPosition.h"
+#include "simulation2/components/ICmpRangeManager.h"
+#include "simulation2/components/ICmpTerrain.h"
+#include "simulation2/components/ICmpVisual.h"
+#include "simulation2/components/ICmpWaterManager.h"
+#include "simulation2/helpers/Render.h"
+#include "simulation2/helpers/Geometry.h"
+#include "simulation2/system/Component.h"
+
+#include "graphics/Overlay.h"
+#include "graphics/TextureManager.h"
+#include "ps/CLogger.h"
+#include "ps/Profile.h"
+#include "renderer/Renderer.h"
+
+class CCmpWayPointRenderer : public CCmpItineraryPointRenderer, public ICmpWayPointRenderer
+{
+public:
+ DEFAULT_COMPONENT_ALLOCATOR(WayPointRenderer)
+ static std::string GetSchema();
+
+ virtual void Init(const CParamNode& paramNode);
+ virtual void Deinit();
+ virtual void Serialize(ISerializer& UNUSED(serialize));
+ virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize);
+
+ virtual void AddPosition_wrapper(const CFixedVector2D& pos);
+ virtual void HandleMessage(const CMessage& msg, bool UNUSED(global));
+ virtual void UpdateMessageSubscriptions();
+ virtual void Stack(const CFixedVector2D& pos);
+ virtual void SetDisplayed(bool displayed);
+ virtual void SetPosition(const CFixedVector2D& pos);
+ virtual void Shift();
+
+private:
+ virtual void AddPosition(CFixedVector2D pos, bool recompute);
+ virtual void ConstructAllOverlayLines();
+ virtual void ConstructOverlayLines(size_t index);
+ virtual void GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
+ virtual void GetVisibilitySegments(std::vector& out, size_t index) const;
+ virtual void FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const;
+ virtual bool IsSet() const;
+ virtual void RenderSubmit(SceneCollector& collector);
+ virtual void RecomputeAllItineraryPointPaths();
+ virtual void RecomputeItineraryPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder);
+ virtual void RecomputeItineraryPointPath_wrapper(size_t index);
+ virtual void ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks = 0, bool floating = true) const;
+ virtual void UpdateOverlayLines();
+ virtual void UpdateMarkers();
+};
+REGISTER_COMPONENT_TYPE(WayPointRenderer)
+#endif //INCLUDED_CCMPWAYPOINTRENDERER
Index: source/simulation2/components/CCmpWayPointRenderer.cpp
===================================================================
--- source/simulation2/components/CCmpWayPointRenderer.cpp
+++ source/simulation2/components/CCmpWayPointRenderer.cpp
@@ -0,0 +1,861 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+#include "CCmpWayPointRenderer.h"
+
+std::string CCmpWayPointRenderer::GetSchema()
+{
+ return
+ "Displays a way point marker where units will go to when tasked to move"
+ ""
+ "special/WayPoint"
+ "0.75"
+ "round"
+ "square"
+ ""
+ ""
+ "default"
+ "default"
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ "0255"
+ ""
+ ""
+ ""
+ ""
+ "flat"
+ "round"
+ "sharp"
+ "square"
+ ""
+ ""
+ ""
+ ""
+ "flat"
+ "round"
+ "sharp"
+ "square"
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ ""
+ "";
+}
+
+void CCmpWayPointRenderer::Init(const CParamNode& paramNode)
+{
+ CCmpItineraryPointRenderer::Init(paramNode);
+}
+void CCmpWayPointRenderer::Deinit()
+{
+ CCmpItineraryPointRenderer::Deinit();
+}
+void CCmpWayPointRenderer::Serialize(ISerializer& UNUSED(serialize))
+{
+}
+void CCmpWayPointRenderer::Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
+{
+ CCmpItineraryPointRenderer::Deserialize(paramNode, deserialize);
+}
+
+void CCmpWayPointRenderer::AddPosition(CFixedVector2D pos, bool recompute)
+{
+ CCmpItineraryPointRenderer::AddPosition(pos, recompute);
+}
+void CCmpWayPointRenderer::AddPosition_wrapper(const CFixedVector2D& pos)
+{
+ CCmpItineraryPointRenderer::AddPosition_wrapper(pos);
+}
+void CCmpWayPointRenderer::ConstructAllOverlayLines()
+{
+ CCmpItineraryPointRenderer::ConstructAllOverlayLines();
+}
+void CCmpWayPointRenderer::ConstructOverlayLines(size_t index)
+{
+ // We need to create a new SOverlayTexturedLine every time we want to change the coordinates after having passed it to the
+ // renderer, because it does some fancy vertex buffering thing and caches them internally instead of recomputing them on every
+ // pass (which is only sensible).
+ while (index >= m_TexturedOverlayLines.size())
+ {
+ std::vector tmp;
+ m_TexturedOverlayLines.push_back(tmp);
+ }
+
+ std::vector >::iterator iter = m_TexturedOverlayLines.begin();
+ size_t count = index;
+ while (count--)
+ iter++;
+ (*iter).clear();
+
+ if (m_Path[index].size() < 2)
+ return;
+
+ LineCapType dashesLineCapType = SOverlayTexturedLine::LINECAP_ROUND; // line caps to use for the dashed segments (and any other segment's edges that border it)
+
+ for (std::vector::const_iterator it = m_VisibilitySegments[index].begin(); it != m_VisibilitySegments[index].end(); ++it)
+ {
+ const SVisibilitySegment& segment = (*it);
+
+ if (segment.m_Visible)
+ {
+ // does this segment border on the building or rally point flag on either side?
+ bool bordersBuilding = (segment.m_EndIndex == m_Path[index].size() - 1);
+ bool bordersFlag = (segment.m_StartIndex == 0);
+
+ // construct solid textured overlay line along a subset of the full path points from startPointIdx to endPointIdx
+ SOverlayTexturedLine overlayLine;
+ overlayLine.m_Thickness = m_LineThickness;
+ overlayLine.m_SimContext = &GetSimContext();
+ overlayLine.m_TextureBase = m_Texture;
+ overlayLine.m_TextureMask = m_TextureMask;
+ overlayLine.m_Color = m_LineColor;
+ overlayLine.m_Closed = false;
+ // we should take care to only use m_LineXCap for the actual end points at the building and the rally point; any intermediate
+ // end points (i.e., that border a dashed segment) should have the dashed cap
+ // the path line is actually in reverse order as well, so let's swap out the start and end caps
+ overlayLine.m_StartCapType = (bordersFlag ? m_LineEndCapType : dashesLineCapType);
+ overlayLine.m_EndCapType = (bordersBuilding ? m_LineStartCapType : dashesLineCapType);
+ overlayLine.m_AlwaysVisible = true;
+
+ // push overlay line coordinates
+ ENSURE(segment.m_EndIndex > segment.m_StartIndex);
+ for (size_t j = segment.m_StartIndex; j <= segment.m_EndIndex; ++j) // end index is inclusive here
+ {
+ overlayLine.m_Coords.push_back(m_Path[index][j].X);
+ overlayLine.m_Coords.push_back(m_Path[index][j].Y);
+ }
+
+ (*iter).push_back(overlayLine);
+ }
+ else
+ {
+ // construct dashed line from startPointIdx to endPointIdx; add textured overlay lines for it to the render list
+ std::vector straightLine;
+ straightLine.push_back(m_Path[index][segment.m_StartIndex]);
+ straightLine.push_back(m_Path[index][segment.m_EndIndex]);
+
+ // We always want to the dashed line to end at either point with a full dash (i.e. not a cleared space), so that the dashed
+ // area is visually obvious. This requires some calculations to see what size we should make the dashes and clears for them
+ // to fit exactly.
+
+ float maxDashSize = 3.f;
+ float maxClearSize = 3.f;
+
+ float dashSize = maxDashSize;
+ float clearSize = maxClearSize;
+ float pairDashRatio = (dashSize / (dashSize + clearSize)); // ratio of the dash's length to a (dash + clear) pair's length
+
+ float distance = (m_Path[index][segment.m_StartIndex] - m_Path[index][segment.m_EndIndex]).Length(); // straight-line distance between the points
+
+ // See how many pairs (dash + clear) of unmodified size can fit into the distance. Then check the remaining distance; if it's not exactly
+ // a dash size's worth (which it probably won't be), then adjust the dash/clear sizes slightly so that it is.
+ int numFitUnmodified = floor(distance / (dashSize + clearSize));
+ float remainderDistance = distance - (numFitUnmodified * (dashSize + clearSize));
+
+ // Now we want to make remainderDistance equal exactly one dash size (i.e. maxDashSize) by scaling dashSize and clearSize slightly.
+ // We have (remainderDistance - maxDashSize) of space to distribute over numFitUnmodified instances of (dashSize + clearSize) to make
+ // it fit, so each (dashSize + clearSize) pair needs to adjust its length by (remainderDistance - maxDashSize)/numFitUnmodified
+ // (which will be positive or negative accordingly). This number can then be distributed further proportionally among the dash's
+ // length and the clear's length.
+
+ // we always want to have at least one dash/clear pair (i.e., "|===| |===|"); also, we need to avoid division by zero below.
+ numFitUnmodified = std::max(1, numFitUnmodified);
+
+ float pairwiseLengthDifference = (remainderDistance - maxDashSize) / numFitUnmodified; // can be either positive or negative
+ dashSize += pairDashRatio * pairwiseLengthDifference;
+ clearSize += (1 - pairDashRatio) * pairwiseLengthDifference;
+
+ // ------------------------------------------------------------------------------------------------
+
+ SDashedLine dashedLine;
+ SimRender::ConstructDashedLine(straightLine, dashedLine, dashSize, clearSize);
+
+ // build overlay lines for dashes
+ size_t numDashes = dashedLine.m_StartIndices.size();
+ for (size_t i = 0; i < numDashes; i++)
+ {
+ SOverlayTexturedLine dashOverlay;
+
+ dashOverlay.m_Thickness = m_LineThickness;
+ dashOverlay.m_SimContext = &GetSimContext();
+ dashOverlay.m_TextureBase = m_Texture;
+ dashOverlay.m_TextureMask = m_TextureMask;
+ dashOverlay.m_Color = m_LineDashColor;
+ dashOverlay.m_Closed = false;
+ dashOverlay.m_StartCapType = dashesLineCapType;
+ dashOverlay.m_EndCapType = dashesLineCapType;
+ dashOverlay.m_AlwaysVisible = true;
+ // TODO: maybe adjust the elevation of the dashes to be a little lower, so that it slides underneath the actual path
+
+ size_t dashStartIndex = dashedLine.m_StartIndices[i];
+ size_t dashEndIndex = dashedLine.GetEndIndex(i);
+ ENSURE(dashEndIndex > dashStartIndex);
+
+ for (size_t n = dashStartIndex; n < dashEndIndex; n++)
+ {
+ dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].X);
+ dashOverlay.m_Coords.push_back(dashedLine.m_Points[n].Y);
+ }
+
+ (*iter).push_back(dashOverlay);
+ }
+
+ }
+ }
+ //// //////////////////////////////////////////////
+ if (m_EnableDebugNodeOverlay)
+ {
+ while (index >= m_DebugNodeOverlays.size())
+ {
+ std::vector tmp;
+ m_DebugNodeOverlays.push_back(tmp);
+ }
+ for (size_t j = 0; j < m_Path[index].size(); ++j)
+ {
+ SOverlayLine overlayLine;
+ overlayLine.m_Color = CColor(1.0f, 1.0f, 1.0f, 1.0f);
+ overlayLine.m_Thickness = 1;
+ SimRender::ConstructCircleOnGround(GetSimContext(), m_Path[index][j].X, m_Path[index][j].Y, 0.075f, overlayLine, true);
+ m_DebugNodeOverlays[index].push_back(overlayLine);
+ }
+ }
+ //// //////////////////////////////////////////////
+}
+void CCmpWayPointRenderer::FixFootprintWaypoints(std::vector& coords, CmpPtr cmpPosition, CmpPtr cmpFootprint) const
+{
+ ENSURE(cmpPosition);
+ ENSURE(cmpFootprint);
+
+ // -----------------------------------------------------------------------------------------------------
+ // TODO: nasty fixed/float conversions everywhere
+
+ // grab the shape and dimensions of the footprint
+ entity_pos_t footprintSize0, footprintSize1, footprintHeight;
+ ICmpFootprint::EShape footprintShape;
+ cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
+
+ // grab the center of the footprint
+ CFixedVector2D center = cmpPosition->GetPosition2D();
+
+ // -----------------------------------------------------------------------------------------------------
+
+ switch (footprintShape)
+ {
+ case ICmpFootprint::SQUARE:
+ {
+ // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
+
+ // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
+ // and the rotated unit vectors in the X/Z plane of the shape's footprint
+ // (the Footprint itself holds only the outline, the Position holds the orientation)
+
+ fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
+ fixed a = cmpPosition->GetRotation().Y;
+ sincos_approx(a, s, c);
+ CFixedVector2D u(c, -s); // unit vector along the rotated X axis
+ CFixedVector2D v(s, c); // unit vector along the rotated Z axis
+ CFixedVector2D halfSize(footprintSize0 / 2, footprintSize1 / 2);
+
+ // starting from the start position, check if any points are within the footprint of the building
+ // (this is possible if the pathfinder was started from a point located within the footprint)
+ for (int i = (int)(coords.size() - 1); i >= 0; i--)
+ {
+ const CVector2D& wp = coords[i];
+ if (Geometry::PointIsInSquare(CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center, u, v, halfSize))
+ {
+ coords.erase(coords.begin() + i);
+ }
+ else
+ {
+ break; // point no longer inside footprint, from this point on neither will any of the following be
+ }
+ }
+
+ // 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
+ CFixedVector2D lastWaypoint(fixed::FromFloat(coords.back().X), fixed::FromFloat(coords.back().Y));
+ CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(lastWaypoint - center, u, v, halfSize); // relative to the shape origin (center)
+ CVector2D footprintEdge((center.X + footprintEdgePoint.X).ToFloat(), (center.Y + footprintEdgePoint.Y).ToFloat());
+ coords.push_back(footprintEdge);
+
+ }
+ break;
+ case ICmpFootprint::CIRCLE:
+ {
+ // in this case, both footprintSize0 and 1 indicate the circle's radius
+
+ for (int i = (int)(coords.size() - 1); i >= 0; i--)
+ {
+ const CVector2D& wp = coords[i];
+ fixed pointDistance = (CFixedVector2D(fixed::FromFloat(wp.X), fixed::FromFloat(wp.Y)) - center).Length();
+ if (pointDistance <= footprintSize0)
+ {
+ coords.erase(coords.begin() + i);
+ }
+ else
+ {
+ break; // point no longer inside footprint, from this point on neither will any of the following be
+ }
+ }
+
+ // add a point right on the edge of the footprint so that it links up nicely with the rest of the path
+ CVector2D centerVec2D(center.X.ToFloat(), center.Y.ToFloat());
+ CVector2D centerToLast(coords.back() - centerVec2D);
+ coords.push_back(centerVec2D + (centerToLast.Normalized() * footprintSize0.ToFloat()));
+ }
+ break;
+ }
+}
+void CCmpWayPointRenderer::GetClosestsEdgePointFrom(CFixedVector2D& result, CFixedVector2D& start, CmpPtr cmpPosition, CmpPtr cmpFootprint) const
+{
+ ENSURE(cmpPosition);
+ ENSURE(cmpFootprint);
+
+ // grab the shape and dimensions of the footprint
+ entity_pos_t footprintSize0, footprintSize1, footprintHeight;
+ ICmpFootprint::EShape footprintShape;
+ cmpFootprint->GetShape(footprintShape, footprintSize0, footprintSize1, footprintHeight);
+
+ // grab the center of the footprint
+ CFixedVector2D center = cmpPosition->GetPosition2D();
+
+ switch (footprintShape)
+ {
+ case ICmpFootprint::SQUARE:
+ {
+ // in this case, footprintSize0 and 1 indicate the size along the X and Z axes, respectively.
+
+ // the building's footprint could be rotated any which way, so let's get the rotation around the Y axis
+ // and the rotated unit vectors in the X/Z plane of the shape's footprint
+ // (the Footprint itself holds only the outline, the Position holds the orientation)
+
+ fixed s, c; // sine and cosine of the Y axis rotation angle (aka the yaw)
+ fixed a = cmpPosition->GetRotation().Y;
+ sincos_approx(a, s, c);
+ CFixedVector2D u(c, -s); // unit vector along the rotated X axis
+ CFixedVector2D v(s, c); // unit vector along the rotated Z axis
+ CFixedVector2D halfSize(footprintSize0 / 2, footprintSize1 / 2);
+
+ CFixedVector2D footprintEdgePoint = Geometry::NearestPointOnSquare(start - center, u, v, halfSize);
+ result = center + footprintEdgePoint;
+ break;
+ }
+ case ICmpFootprint::CIRCLE:
+ {
+ // in this case, both footprintSize0 and 1 indicate the circle's radius
+ // Transform target to the point nearest on the edge.
+ CFixedVector2D centerVec2D(center.X, center.Y);
+ CFixedVector2D centerToLast(start - centerVec2D);
+ centerToLast.Normalize();
+ result = centerVec2D + (centerToLast.Multiply(footprintSize0));
+ break;
+ }
+ }
+}
+void CCmpWayPointRenderer::GetVisibilitySegments(std::vector& out, size_t index) const
+{
+ out.clear();
+
+ if (m_Path[index].size() < 2)
+ return;
+
+ CmpPtr cmpRangeMgr(GetSystemEntity());
+
+ player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
+ CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
+
+ // go through the path node list, comparing each node's visibility with the previous one. If it changes, end the current segment and start
+ // a new one at the next point.
+
+ bool lastVisible = losQuerier.IsExplored(
+ (fixed::FromFloat(m_Path[index][0].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(),
+ (fixed::FromFloat(m_Path[index][0].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest()
+ );
+ size_t curSegmentStartIndex = 0; // starting node index of the current segment
+
+ for (size_t k = 1; k < m_Path[index].size(); ++k)
+ {
+ // grab tile indices for this coord
+ int i = (fixed::FromFloat(m_Path[index][k].X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
+ int j = (fixed::FromFloat(m_Path[index][k].Y) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();
+
+ bool nodeVisible = losQuerier.IsExplored(i, j);
+ if (nodeVisible != lastVisible)
+ {
+ // visibility changed; write out the segment that was just completed and get ready for the new one
+ out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, k - 1));
+
+ //curSegmentStartIndex = k; // new segment starts here
+ curSegmentStartIndex = k - 1;
+ lastVisible = nodeVisible;
+ }
+
+ }
+
+ // terminate the last segment
+ out.push_back(SVisibilitySegment(lastVisible, curSegmentStartIndex, m_Path[index].size() - 1));
+ MergeVisibilitySegments(out);
+}
+void CCmpWayPointRenderer::HandleMessage(const CMessage& msg, bool UNUSED(global))
+{
+ switch (msg.GetType())
+ {
+ case MT_RenderSubmit:
+ {
+ PROFILE("RallyPoint::RenderSubmit");
+ if (m_Displayed && IsSet())
+ {
+ const CMessageRenderSubmit& msgData = static_cast (msg);
+ RenderSubmit(msgData.collector);
+ }
+ }
+ break;
+ case MT_OwnershipChanged:
+ {
+ UpdateMarkers(); // update marker variation to new player's civilization
+ }
+ break;
+ case MT_TurnStart:
+ {
+ UpdateOverlayLines(); // check for changes to the SoD and update the overlay lines accordingly
+ }
+ break;
+ case MT_Destroy:
+ {
+ for (std::vector::iterator it = m_MarkerEntityIds.begin(); it != m_MarkerEntityIds.end(); ++it)
+ {
+ if (*it != INVALID_ENTITY)
+ {
+ GetSimContext().GetComponentManager().DestroyComponentsSoon(*it);
+ *it = INVALID_ENTITY;
+ }
+ }
+ }
+ break;
+ case MT_PositionChanged:
+ {
+ // Unlikely to happen in-game, but can occur in atlas
+ // Just recompute the path from the entity to the first rally point
+ RecomputeItineraryPointPath_wrapper(0);
+ }
+ break;
+ }
+}
+bool CCmpWayPointRenderer::IsSet() const
+{
+ return CCmpItineraryPointRenderer::IsSet();
+}
+void CCmpWayPointRenderer::RecomputeAllItineraryPointPaths()
+{
+ m_Path.clear();
+ m_VisibilitySegments.clear();
+ m_TexturedOverlayLines.clear();
+
+ if (!IsSet())
+ return; // no use computing a path if the way point isn't set
+
+ CmpPtr cmpPosition(GetSimContext(), GetEntityId());
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ return; // no point going on if this entity doesn't have a position or is outside of the world
+ // Not used
+
+ CmpPtr cmpFootprint(GetSimContext(), GetEntityId());
+ CmpPtr cmpPathfinder(GetSimContext(), SYSTEM_ENTITY);
+
+ for (size_t i = 0; i < m_ItineraryPoints.size(); ++i)
+ {
+ RecomputeItineraryPointPath(i, cmpPosition, cmpFootprint, cmpPathfinder);
+ }
+}
+void CCmpWayPointRenderer::RecomputeItineraryPointPath(size_t index, CmpPtr& cmpPosition, CmpPtr& cmpFootprint, CmpPtr cmpPathfinder)
+{
+ while (index >= m_Path.size())
+ {
+ std::vector tmp;
+ m_Path.push_back(tmp);
+ }
+ m_Path[index].clear();
+
+ while (index >= m_VisibilitySegments.size())
+ {
+ std::vector tmp;
+ m_VisibilitySegments.push_back(tmp);
+ }
+ m_VisibilitySegments[index].clear();
+
+ // Find a long path to the goal point -- this uses the tile-based pathfinder, which will return a
+ // list of waypoints (i.e. a Path) from the goal to the foundation/previous rally point, where each
+ // waypoint is centered at a tile. We'll have to do some post-processing on the path to get it smooth.
+ Path path;
+ std::vector& waypoints = path.m_Waypoints;
+
+ CFixedVector2D start(cmpPosition->GetPosition2D());
+ Goal goal = { Goal::POINT, m_ItineraryPoints[index].X, m_ItineraryPoints[index].Y };
+
+ if (index == 0)
+ GetClosestsEdgePointFrom(start, m_ItineraryPoints[index], cmpPosition, cmpFootprint);
+ else
+ {
+ start.X = m_ItineraryPoints[index - 1].X;
+ start.Y = m_ItineraryPoints[index - 1].Y;
+ }
+ cmpPathfinder->ComputePath(start.X, start.Y, goal, cmpPathfinder->GetPassabilityClass(m_LinePassabilityClass), path);
+
+ // Check if we got a path back; if not we probably have two markers less than one tile apart.
+ if (path.m_Waypoints.size() < 2)
+ {
+ m_Path[index].emplace_back(start.X.ToFloat(), start.Y.ToFloat());
+ m_Path[index].emplace_back(m_ItineraryPoints[index].X.ToFloat(), m_ItineraryPoints[index].Y.ToFloat());
+ return;
+ }
+ else if (index == 0)
+ {
+ // sometimes this ends up not being optimal if you asked for a long path, so improve.
+ CFixedVector2D newend(waypoints[waypoints.size() - 2].x, waypoints[waypoints.size() - 2].z);
+ GetClosestsEdgePointFrom(newend, newend, cmpPosition, cmpFootprint);
+ waypoints.back().x = newend.X;
+ waypoints.back().z = newend.Y;
+ }
+ else
+ {
+ // make sure we actually start at the rallypoint because the pathfinder moves us to a usable tile.
+ waypoints.back().x = m_ItineraryPoints[index - 1].X;
+ waypoints.back().z = m_ItineraryPoints[index - 1].Y;
+ }
+
+ // pathfinder makes us go to the nearest passable cell which isn't always what we want
+ waypoints[0].x = m_ItineraryPoints[index].X;
+ waypoints[0].z = m_ItineraryPoints[index].Y;
+
+ // From here on, we choose to represent the waypoints as CVector2D floats to avoid to have to convert back and forth
+ // between fixed-point Waypoint/CFixedVector2D and various other float-based formats used by interpolation and whatnot.
+ // Since we'll only be further using these points for rendering purposes, using floats should be fine.
+
+ for (Waypoint& waypoint : waypoints)
+ m_Path[index].emplace_back(waypoint.x.ToFloat(), waypoint.z.ToFloat());
+
+ // add the start position
+ // m_Path[index].emplace_back(m_ItineraryPoints[index].X.ToFloat(), m_ItineraryPoints[index].Y.ToFloat());
+
+ // Post-processing
+
+ // Linearize the path;
+ // Pass through the waypoints, averaging each waypoint with its next one except the last one. Because the path
+ // goes from the marker to this entity/the previous flag and we want to keep the point at the marker's exact position,
+ // loop backwards through the waypoints so that the marker waypoint is maintained.
+ // TODO: see if we can do this at the same time as the waypoint -> coord conversion above
+ for (size_t i = m_Path[index].size() - 2; i > 0; --i)
+ m_Path[index][i] = (m_Path[index][i] + m_Path[index][i - 1]) / 2.0f;
+
+ // if there's a footprint and this path starts from this entity, remove any points returned by the pathfinder that may be on obstructed footprint tiles
+ //if (index == 0 && cmpFootprint)
+ // FixFootprintWaypoints(m_Path[index], cmpPosition, cmpFootprint);
+
+ // Eliminate some consecutive waypoints that are visible from eachother. Reduce across a maximum distance of approx. 6 tiles
+ // (prevents segments that are too long to properly stick to the terrain)
+ ReduceSegmentsByVisibility(m_Path[index], 6);
+
+ // Debug overlays
+ if (m_EnableDebugNodeOverlay)
+ {
+ while (index >= m_DebugNodeOverlays.size())
+ m_DebugNodeOverlays.emplace_back();
+
+ m_DebugNodeOverlays[index].clear();
+ }
+ if (m_EnableDebugNodeOverlay && m_SmoothPath)
+ {
+ // Create separate control point overlays so we can differentiate when using smoothing (offset them a little higher from the
+ // terrain so we can still see them after the interpolated points are added)
+ for (CVector2D& point : m_Path[index])
+ {
+ SOverlayLine overlayLine;
+ overlayLine.m_Color = CColor(1.0f, 0.0f, 0.0f, 1.0f);
+ overlayLine.m_Thickness = 2;
+ SimRender::ConstructSquareOnGround(GetSimContext(), point.X, point.Y, 0.2f, 0.2f, 1.0f, overlayLine, true);
+ m_DebugNodeOverlays[index].push_back(overlayLine);
+ }
+ }
+
+ if (m_SmoothPath)
+ // The number of points to interpolate goes hand in hand with the maximum amount of node links allowed to be joined together
+ // by the visibility reduction. The more node links that can be joined together, the more interpolated points you need to
+ // generate to be able to deal with local terrain height changes.
+ SimRender::InterpolatePointsRNS(m_Path[index], false, 0, 4); // no offset, keep line at its exact path
+
+ // 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
+ GetVisibilitySegments(m_VisibilitySegments[index], index);
+
+ // build overlay lines for the new path
+ ConstructOverlayLines(index);
+}
+void CCmpWayPointRenderer::RecomputeItineraryPointPath_wrapper(size_t index)
+{
+ if (!IsSet())
+ return;
+
+ CmpPtr cmpPosition(GetEntityHandle());
+ if (!cmpPosition || !cmpPosition->IsInWorld())
+ return;
+
+ CmpPtr cmpFootprint(GetEntityHandle());
+ CmpPtr cmpPathfinder(GetSystemEntity());
+
+ RecomputeItineraryPointPath(index, cmpPosition, cmpFootprint, cmpPathfinder);
+}
+void CCmpWayPointRenderer::ReduceSegmentsByVisibility(std::vector& coords, unsigned maxSegmentLinks, bool floating) const
+{
+ CmpPtr cmpPathFinder(GetSystemEntity());
+ CmpPtr cmpTerrain(GetSystemEntity());
+ CmpPtr cmpWaterManager(GetSystemEntity());
+ ENSURE(cmpPathFinder && cmpTerrain && cmpWaterManager);
+
+ if (coords.size() < 3)
+ return;
+
+ // 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
+ // 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
+ // process from there on until the entire line is checked. The output is the array of base nodes.
+
+ std::vector newCoords;
+ StationaryOnlyObstructionFilter obstructionFilter;
+ entity_pos_t lineRadius = fixed::FromFloat(m_LineThickness);
+ pass_class_t passabilityClass = cmpPathFinder->GetPassabilityClass(m_LinePassabilityClass);
+
+ newCoords.push_back(coords[0]); // save the first base node
+
+ size_t baseNodeIdx = 0;
+ size_t curNodeIdx = 1;
+
+ float baseNodeY;
+ entity_pos_t baseNodeX;
+ entity_pos_t baseNodeZ;
+
+ // set initial base node coords
+ baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
+ baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
+ baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
+ if (floating)
+ baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
+
+ while (curNodeIdx < coords.size())
+ {
+ ENSURE(curNodeIdx > baseNodeIdx); // this needs to be true at all times, otherwise we're checking visibility between a point and itself
+
+ entity_pos_t curNodeX = fixed::FromFloat(coords[curNodeIdx].X);
+ entity_pos_t curNodeZ = fixed::FromFloat(coords[curNodeIdx].Y);
+ float curNodeY = cmpTerrain->GetExactGroundLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y);
+ if (floating)
+ curNodeY = std::max(curNodeY, cmpWaterManager->GetExactWaterLevel(coords[curNodeIdx].X, coords[curNodeIdx].Y));
+
+ // find out whether curNode is visible from baseNode (careful; this is in 2D only; terrain height differences are ignored!)
+ bool curNodeVisible = cmpPathFinder->CheckMovement(obstructionFilter, baseNodeX, baseNodeZ, curNodeX, curNodeZ, lineRadius, passabilityClass);
+
+ // 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
+ curNodeVisible = curNodeVisible && (fabsf(curNodeY - baseNodeY) < 3.f); // TODO: this could probably use some tuning
+ if (maxSegmentLinks > 0)
+ // max. amount of node-to-node links to be eliminated (unsigned subtraction is valid because curNodeIdx is always > baseNodeIdx)
+ curNodeVisible = curNodeVisible && ((curNodeIdx - baseNodeIdx) <= maxSegmentLinks);
+
+ if (!curNodeVisible)
+ {
+ // current node is not visible from the base node, so the previous one was the last visible point from baseNode and should
+ // hence become the new base node for further iterations.
+
+ // if curNodeIdx is adjacent to the current baseNode (which is possible due to steep height differences, e.g. hills), then
+ // we should take care not to stay stuck at the current base node
+ if (curNodeIdx > baseNodeIdx + 1)
+ {
+ baseNodeIdx = curNodeIdx - 1;
+ }
+ else
+ {
+ // curNodeIdx == baseNodeIdx + 1
+ baseNodeIdx = curNodeIdx;
+ curNodeIdx++; // move the next candidate node one forward so that we don't test a point against itself in the next iteration
+ }
+
+ newCoords.push_back(coords[baseNodeIdx]); // add new base node to output list
+
+ // update base node coordinates
+ baseNodeX = fixed::FromFloat(coords[baseNodeIdx].X);
+ baseNodeZ = fixed::FromFloat(coords[baseNodeIdx].Y);
+ baseNodeY = cmpTerrain->GetExactGroundLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y);
+ if (floating)
+ baseNodeY = std::max(baseNodeY, cmpWaterManager->GetExactWaterLevel(coords[baseNodeIdx].X, coords[baseNodeIdx].Y));
+ }
+
+ curNodeIdx++;
+ }
+
+ // 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
+ // base node, then the loop above just ends and no endpoint is ever added to the list.
+ ENSURE(curNodeIdx == coords.size());
+ newCoords.push_back(coords[coords.size() - 1]);
+
+ coords.swap(newCoords);
+}
+void CCmpWayPointRenderer::RenderSubmit(SceneCollector& collector)
+{
+ CCmpItineraryPointRenderer::RenderSubmit(collector);
+}
+void CCmpWayPointRenderer::SetDisplayed(bool displayed)
+{
+ CCmpItineraryPointRenderer::SetDisplayed(displayed);
+}
+void CCmpWayPointRenderer::SetPosition(const CFixedVector2D& pos)
+{
+ CCmpItineraryPointRenderer::SetPosition(pos);
+}
+void CCmpWayPointRenderer::Shift()
+{
+ // Still not sure why size is sometimes <= 0
+ if (!m_ItineraryPoints.empty())
+ m_ItineraryPoints.erase(m_ItineraryPoints.begin());
+
+ if (!m_MarkerEntityIds.empty())
+ {
+ CmpPtr markerCmpPosition(GetSimContext(), m_MarkerEntityIds[0]);
+ if (markerCmpPosition)
+ markerCmpPosition->MoveOutOfWorld();
+
+ m_MarkerEntityIds.erase(m_MarkerEntityIds.begin());
+ }
+
+ if (!m_VisibilitySegments.empty())
+ m_VisibilitySegments.erase(m_VisibilitySegments.begin());
+
+ if (!m_TexturedOverlayLines.empty())
+ m_TexturedOverlayLines.erase(m_TexturedOverlayLines.begin());
+
+ if (!m_Path.empty())
+ m_Path.erase(m_Path.begin());
+
+ UpdateMarkers();
+}
+void CCmpWayPointRenderer::Stack(const CFixedVector2D& pos)
+{
+ m_ItineraryPoints.insert(m_ItineraryPoints.begin(), pos);
+ UpdateMarkers();
+ // TODO don't recompute everything (maybe just shift everything one back? and recompute first only
+ RecomputeAllItineraryPointPaths();
+}
+void CCmpWayPointRenderer::UpdateMarkers()
+{
+ player_id_t previousOwner = m_LastOwner;
+ for (size_t i = 0; i < m_ItineraryPoints.size(); ++i)
+ {
+ if (i >= m_MarkerEntityIds.size())
+ m_MarkerEntityIds.push_back(INVALID_ENTITY);
+
+ if (m_MarkerEntityIds[i] == INVALID_ENTITY)
+ {
+ // no marker exists yet, create one first
+ CComponentManager& componentMgr = GetSimContext().GetComponentManager();
+
+ // allocate a new entity for the marker
+ if (!m_MarkerTemplate.empty())
+ {
+ m_MarkerEntityIds[i] = componentMgr.AllocateNewLocalEntity();
+ if (m_MarkerEntityIds[i] != INVALID_ENTITY)
+ m_MarkerEntityIds[i] = componentMgr.AddEntity(m_MarkerTemplate, m_MarkerEntityIds[i]);
+ }
+ }
+
+ // the marker entity should be valid at this point, otherwise something went wrong trying to allocate it
+ if (m_MarkerEntityIds[i] == INVALID_ENTITY)
+ LOGERROR("Failed to create way point marker entity");
+
+ CmpPtr markerCmpPosition(GetSimContext(), m_MarkerEntityIds[i]);
+ if (markerCmpPosition)
+ {
+ if (m_Displayed && IsSet())
+ markerCmpPosition->MoveTo(m_ItineraryPoints[i].X, m_ItineraryPoints[i].Y);
+ else
+ markerCmpPosition->MoveOutOfWorld();
+ }
+
+ // Set rally point flag selection based on player civilization
+ CmpPtr cmpOwnership(GetEntityHandle());
+ if (!cmpOwnership)
+ continue;
+
+ player_id_t ownerId = cmpOwnership->GetOwner();
+ if (ownerId == INVALID_PLAYER || (ownerId == previousOwner && m_LastMarkerCount > i))
+ continue;
+
+ m_LastOwner = ownerId;
+ CmpPtr cmpPlayerManager(GetSystemEntity());
+ // cmpPlayerManager should not be null as long as this method is called on-demand instead of at Init() time
+ // (we can't rely on component initialization order in Init())
+ if (!cmpPlayerManager)
+ continue;
+
+ CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(ownerId));
+ if (!cmpPlayer)
+ continue;
+
+ CmpPtr cmpVisualActor(GetSimContext(), m_MarkerEntityIds[i]);
+ if (cmpVisualActor)
+ cmpVisualActor->SetVariant("civ", CStrW(cmpPlayer->GetCiv()).ToUTF8());
+ }
+ m_LastMarkerCount = m_ItineraryPoints.size();
+}
+
+void CCmpWayPointRenderer::UpdateMessageSubscriptions()
+{
+ bool needRender = m_Displayed && IsSet();
+ GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRender);
+}
+void CCmpWayPointRenderer::UpdateOverlayLines()
+{
+ CCmpItineraryPointRenderer::UpdateOverlayLines();
+}
Index: source/simulation2/components/ICmpRallyPointRenderer.h
===================================================================
--- source/simulation2/components/ICmpRallyPointRenderer.h
+++ source/simulation2/components/ICmpRallyPointRenderer.h
@@ -24,26 +24,20 @@
/**
* Rally Point.
- * Holds the position of a unit's rally points, and renders them to screen.
+ * Holds the position(s) of a unit's rally points, and renders them to screen.
*/
class ICmpRallyPointRenderer : public IComponent
{
public:
-
- /// Sets whether the rally point marker and line should be displayed.
virtual void SetDisplayed(bool displayed) = 0;
- /// Sets the position at which the rally point marker should be displayed.
- /// Discards all previous positions
- virtual void SetPosition(const CFixedVector2D& position) = 0;
+ virtual void SetPosition(const CFixedVector2D& pos) = 0;
+ virtual void AddPosition_wrapper(const CFixedVector2D& pos) = 0;
+
/// Updates the position of one given rally point marker.
- virtual void UpdatePosition(u32 rallyPointId, const CFixedVector2D& position) = 0;
+ virtual void UpdatePosition(u32 rallyPointId, const CFixedVector2D& pos) = 0;
- /// Add another position at which a marker should be displayed, connected
- /// to the previous one.
- virtual void AddPosition_wrapper(const CFixedVector2D& position) = 0;
-
/// Reset the positions of this rally point marker
virtual void Reset() = 0;
Index: source/simulation2/components/ICmpWayPointRenderer.h
===================================================================
--- source/simulation2/components/ICmpWayPointRenderer.h
+++ source/simulation2/components/ICmpWayPointRenderer.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_ICMPWAYPOINT
+#define INCLUDED_ICMPWAYPOINT
+
+#include "maths/FixedVector2D.h"
+#include "simulation2/helpers/Position.h"
+#include "simulation2/system/Interface.h"
+
+/**
+ * Way Point.
+ * Holds the position(s) of a unit's way points, and renders them to screen.
+ */
+class ICmpWayPointRenderer : public IComponent
+{
+public:
+ virtual void SetDisplayed(bool displayed) = 0;
+
+ virtual void SetPosition(const CFixedVector2D& pos) = 0;
+
+ virtual void AddPosition_wrapper(const CFixedVector2D& pos) = 0;
+
+ /// At a position at which a marker should be displayed, connected to
+ /// the previous first.
+ virtual void Stack(const CFixedVector2D& pos) = 0;
+
+ /// Remove the first way point (as we have reached it)
+ virtual void Shift() = 0;
+
+ /// Returns true if at least one display rally point is set
+ virtual bool IsSet() const = 0;
+
+ DECLARE_INTERFACE_TYPE(WayPointRenderer)
+};
+#endif // INCLUDED_ICMPWAYPOINT
Index: source/simulation2/components/ICmpWayPointRenderer.cpp
===================================================================
--- source/simulation2/components/ICmpWayPointRenderer.cpp
+++ source/simulation2/components/ICmpWayPointRenderer.cpp
@@ -0,0 +1,32 @@
+/* Copyright (C) 2017 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "ICmpWayPointRenderer.h"
+#include "simulation2/system/InterfaceScripted.h"
+
+class CFixedVector2D;
+
+BEGIN_INTERFACE_WRAPPER(WayPointRenderer)
+DEFINE_INTERFACE_METHOD_1("SetDisplayed", void, ICmpWayPointRenderer, SetDisplayed, bool)
+DEFINE_INTERFACE_METHOD_1("SetPosition", void, ICmpWayPointRenderer, SetPosition, CFixedVector2D)
+DEFINE_INTERFACE_METHOD_1("AddPosition", void, ICmpWayPointRenderer, AddPosition_wrapper, CFixedVector2D)
+DEFINE_INTERFACE_METHOD_1("Stack", void, ICmpWayPointRenderer, Stack, CFixedVector2D)
+DEFINE_INTERFACE_METHOD_0("Shift", void, ICmpWayPointRenderer, Shift)
+DEFINE_INTERFACE_METHOD_CONST_0("IsSet", bool, ICmpWayPointRenderer, IsSet)
+END_INTERFACE_WRAPPER(WayPointRenderer)