From ee5bb1fd61a48c5018ba0e90cfbb326343a8b0f5 Mon Sep 17 00:00:00 2001 From: elexis Date: Sat, 6 May 2017 00:47:21 +0000 Subject: [PATCH] RangeVisualization component and use it to visualize Aura ranges. Differential Revision: https://code.wildfiregames.com/D238 Fixes #4349 Patch By: Sandarac This was SVN commit r19519. --- binaries/data/config/default.cfg | 2 + .../data/mods/public/gui/manual/intro.txt | 1 + .../data/mods/public/gui/options/options.json | 6 + .../mods/public/gui/session/hotkeys/misc.xml | 4 + .../data/mods/public/gui/session/session.js | 31 +++ .../public/simulation/components/Auras.js | 23 +++ .../simulation/components/GuiInterface.js | 24 +++ .../components/RangeVisualization.js | 78 +++++++ .../interfaces/RangeVisualization.js | 1 + .../data/auras/units/female_inspiration.json | 7 +- .../templates/template_structure.xml | 1 + .../simulation/templates/template_unit.xml | 1 + .../simulation2/components/CCmpSelectable.cpp | 194 ++++++++++-------- .../simulation2/components/ICmpSelectable.cpp | 4 +- .../simulation2/components/ICmpSelectable.h | 11 + 15 files changed, 305 insertions(+), 83 deletions(-) create mode 100644 binaries/data/mods/public/simulation/components/RangeVisualization.js create mode 100644 binaries/data/mods/public/simulation/components/interfaces/RangeVisualization.js diff --git a/binaries/data/config/default.cfg b/binaries/data/config/default.cfg index 4d2f84cde9..b6a82bb299 100644 --- a/binaries/data/config/default.cfg +++ b/binaries/data/config/default.cfg @@ -164,6 +164,7 @@ timeelapsedcounter.toggle = "F12" ; Toggle time elapsed counter session.showstatusbars = Tab ; Toggle display of status bars session.highlightguarding = PgDn ; Toggle highlight of guarding units session.highlightguarded = PgUp ; Toggle highlight of guarded units +session.toggleaurarange = "Alt+V" ; Toggle rendering of aura range overlays of selected units and structures ; > HOTKEYS ONLY chat = Return ; Toggle chat window @@ -336,6 +337,7 @@ enabletips = true ; Enable/Disable tips during gamesetup (for ne camerajump.threshold = 40 ; How close do we have to be to the actual location in order to jump back to the previous one? timeelapsedcounter = false ; Show the game duration in the top right corner batchtrainingsize = 5 ; Number of units to be trained per batch (when pressing the hotkey) +aurarange = true [gui.session.minimap] blinkduration = 1.7 ; The blink duration while pinging diff --git a/binaries/data/mods/public/gui/manual/intro.txt b/binaries/data/mods/public/gui/manual/intro.txt index 84211233ba..9fd2a43bba 100644 --- a/binaries/data/mods/public/gui/manual/intro.txt +++ b/binaries/data/mods/public/gui/manual/intro.txt @@ -108,6 +108,7 @@ Alt + D: Show/hide developer overlay (with developer options) Alt + W: Toggle wireframe mode (press once to get wireframes overlaid over the textured models, twice to get just the wireframes colored by the textures, thrice to get back to normal textured mode) Alt + S: Toggle unit silhouettes (might give a small performance boost) Alt + Z: Toggle sky +Alt + V: Toggle aura range visualizations of selected units and structures [font="sans-bold-14"]Camera manipulation [font="sans-14"]W or \[up]: Pan screen up diff --git a/binaries/data/mods/public/gui/options/options.json b/binaries/data/mods/public/gui/options/options.json index 8d890d5fb8..dd9f4d9e6f 100644 --- a/binaries/data/mods/public/gui/options/options.json +++ b/binaries/data/mods/public/gui/options/options.json @@ -215,6 +215,12 @@ "label": "FPS throttling in games", "tooltip": "To save CPU workload, throttle render frequency in running games. Set to maximum to disable throttling.", "parameters": { "config": "adaptivefps.session", "min": 20, "max": 100 } + }, + { + "type": "boolean", + "label": "Aura Range Visualization", + "tooltip": "Display the range of auras of selected units and structures (can also be toggled in-game with the hotkey).", + "parameters": { "config": "gui.session.aurarange" } } ], "soundSetting": diff --git a/binaries/data/mods/public/gui/session/hotkeys/misc.xml b/binaries/data/mods/public/gui/session/hotkeys/misc.xml index a81d2e6b81..0926f91561 100644 --- a/binaries/data/mods/public/gui/session/hotkeys/misc.xml +++ b/binaries/data/mods/public/gui/session/hotkeys/misc.xml @@ -96,4 +96,8 @@ clearSelection(); + + toggleRangeOverlay("Aura"); + + diff --git a/binaries/data/mods/public/gui/session/session.js b/binaries/data/mods/public/gui/session/session.js index a8db6fc7fa..e3b95fb0c3 100644 --- a/binaries/data/mods/public/gui/session/session.js +++ b/binaries/data/mods/public/gui/session/session.js @@ -792,6 +792,11 @@ function onSimulationUpdate() handleNotifications(); updateGUIObjects(); + Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", { + "type": "Aura", + "enabled": Engine.ConfigDB_GetValue("user", "gui.session.aurarange") == "true" + }); + if (g_ConfirmExit) confirmExit(); } @@ -1250,6 +1255,32 @@ function recalculateStatusBarDisplay(remove = false) }); } +/** + * Toggles the display of range overlays of selected entities for the given range type. + * @param {string} type - for example "Aura" + */ +function toggleRangeOverlay(type, currentValue) +{ + let configString = "gui.session." + type.toLowerCase() + "range"; + let enabled = Engine.ConfigDB_GetValue("user", configString) != "true"; + Engine.ConfigDB_CreateValue("user", configString, String(enabled)); + Engine.ConfigDB_WriteValueToFile("user", configString, String(enabled), "config/user.cfg"); + + Engine.GuiInterfaceCall("EnableVisualRangeOverlayType", { + "type": type, + "enabled": enabled + }); + + let selected = g_Selection.toList(); + for (let ent in g_Selection.highlighted) + selected.push(g_Selection.highlighted[ent]); + + Engine.GuiInterfaceCall("SetRangeOverlays", { + "entities": selected, + "enabled": enabled + }); +} + // Update the additional list of entities to be highlighted. function updateAdditionalHighlight() { diff --git a/binaries/data/mods/public/simulation/components/Auras.js b/binaries/data/mods/public/simulation/components/Auras.js index 701b05a3fb..728b258749 100644 --- a/binaries/data/mods/public/simulation/components/Auras.js +++ b/binaries/data/mods/public/simulation/components/Auras.js @@ -63,6 +63,29 @@ Auras.prototype.GetRange = function(name) return undefined; }; +/** + * Return the names of any range auras - used to render their ranges. + */ +Auras.prototype.GetVisualAuraRangeNames = function() +{ + return this.GetAuraNames().filter(auraName => this.IsRangeAura(auraName)); +}; + +Auras.prototype.GetLineTexture = function(name) +{ + return this.auras[name].rangeOverlay ? this.auras[name].rangeOverlay.lineTexture : "outline_border.png"; +}; + +Auras.prototype.GetLineTextureMask = function(name) +{ + return this.auras[name].rangeOverlay ? this.auras[name].rangeOverlay.lineTextureMask : "outline_border_mask.png"; +}; + +Auras.prototype.GetLineThickness = function(name) +{ + return this.auras[name].rangeOverlay ? this.auras[name].rangeOverlay.lineThickness : 0.2; +}; + Auras.prototype.GetClasses = function(name) { return this.auras[name].affects; diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 3630542cc9..caf8089bd2 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -33,6 +33,7 @@ GuiInterface.prototype.Init = function() this.timeNotifications = []; this.entsRallyPointsDisplayed = []; this.entsWithAuraAndStatusBars = new Set(); + this.enabledVisualRangeOverlayTypes = {}; }; /* @@ -895,9 +896,20 @@ GuiInterface.prototype.SetSelectionHighlight = function(player, cmd) } cmpSelectable.SetSelectionHighlight({ "r": color.r, "g": color.g, "b": color.b, "a": cmd.alpha }, cmd.selected); + + let cmpRangeVisualization = Engine.QueryInterface(ent, IID_RangeVisualization); + if (!cmpRangeVisualization || player != owner && player != -1) + continue; + + cmpRangeVisualization.SetEnabled(cmd.selected, this.enabledVisualRangeOverlayTypes); } }; +GuiInterface.prototype.EnableVisualRangeOverlayType = function(player, data) +{ + this.enabledVisualRangeOverlayTypes[data.type] = data.enabled; +}; + GuiInterface.prototype.GetEntitiesWithStatusBars = function() { return [...this.entsWithAuraAndStatusBars]; @@ -938,6 +950,16 @@ GuiInterface.prototype.SetStatusBars = function(player, cmd) } }; +GuiInterface.prototype.SetRangeOverlays = function(player, cmd) +{ + for (let ent of cmd.entities) + { + let cmpRangeVisualization = Engine.QueryInterface(ent, IID_RangeVisualization); + if (cmpRangeVisualization) + cmpRangeVisualization.SetEnabled(cmd.enabled, this.enabledVisualRangeOverlayTypes); + } +}; + GuiInterface.prototype.GetPlayerEntities = function(player) { return Engine.QueryInterface(SYSTEM_ENTITY, IID_RangeManager).GetEntitiesByPlayer(player); @@ -2017,6 +2039,8 @@ let exposedFunctions = { "SetObstructionDebugOverlay": 1, "SetMotionDebugOverlay": 1, "SetRangeDebugOverlay": 1, + "EnableVisualRangeOverlayType": 1, + "SetRangeOverlays": 1, "GetTraderNumber": 1, "GetTradingGoods": 1, diff --git a/binaries/data/mods/public/simulation/components/RangeVisualization.js b/binaries/data/mods/public/simulation/components/RangeVisualization.js new file mode 100644 index 0000000000..59a4a40ee3 --- /dev/null +++ b/binaries/data/mods/public/simulation/components/RangeVisualization.js @@ -0,0 +1,78 @@ +function RangeVisualization() {} + +RangeVisualization.prototype.Schema = ""; + +RangeVisualization.prototype.Init = function() +{ + this.enabled = false; + this.enabledRangeTypes = { + "Aura": false + }; + + this.rangeVisualizations = new Map(); + for (let type in this.enabledRangeTypes) + this["GetVisual" + type + "Ranges"](type); +}; + +// The GUI enables visualizations +RangeVisualization.prototype.Serialize = null; + +RangeVisualization.prototype.Deserialize = function(data) +{ + this.Init(); +}; + +RangeVisualization.prototype.GetVisualAuraRanges = function(type) +{ + let cmpAuras = Engine.QueryInterface(this.entity, IID_Auras); + if (!cmpAuras) + return; + + this.rangeVisualizations.set(type, []); + + for (let auraName of cmpAuras.GetVisualAuraRangeNames()) + this.rangeVisualizations.get(type).push({ + "radius": cmpAuras.GetRange(auraName), + "texture": cmpAuras.GetLineTexture(auraName), + "textureMask": cmpAuras.GetLineTextureMask(auraName), + "thickness": cmpAuras.GetLineThickness(auraName), + }); +}; + +RangeVisualization.prototype.SetEnabled = function(enabled, enabledRangeTypes) +{ + this.enabled = enabled; + this.enabledRangeTypes = enabledRangeTypes; + + this.RegenerateRangeVisualizations(); +}; + +RangeVisualization.prototype.RegenerateRangeVisualizations = function() +{ + let cmpSelectable = Engine.QueryInterface(this.entity, IID_Selectable); + if (!cmpSelectable) + return; + + cmpSelectable.ResetRangeOverlays(); + + if (!this.enabled) + return; + + // Only render individual range types that have been enabled + for (let rangeOverlayType of this.rangeVisualizations.keys()) + if (this.enabledRangeTypes[rangeOverlayType]) + for (let rangeOverlay of this.rangeVisualizations.get(rangeOverlayType)) + cmpSelectable.AddRangeOverlay( + rangeOverlay.radius, + rangeOverlay.texture, + rangeOverlay.textureMask, + rangeOverlay.thickness); +}; + +RangeVisualization.prototype.OnOwnershipChanged = function(msg) +{ + if (this.enabled && msg.to != -1) + this.RegenerateRangeVisualizations(); +}; + +Engine.RegisterComponentType(IID_RangeVisualization, "RangeVisualization", RangeVisualization); diff --git a/binaries/data/mods/public/simulation/components/interfaces/RangeVisualization.js b/binaries/data/mods/public/simulation/components/interfaces/RangeVisualization.js new file mode 100644 index 0000000000..ea36ce68da --- /dev/null +++ b/binaries/data/mods/public/simulation/components/interfaces/RangeVisualization.js @@ -0,0 +1 @@ +Engine.RegisterInterface("RangeVisualization"); diff --git a/binaries/data/mods/public/simulation/data/auras/units/female_inspiration.json b/binaries/data/mods/public/simulation/data/auras/units/female_inspiration.json index 15377e5ae3..91b3a66890 100644 --- a/binaries/data/mods/public/simulation/data/auras/units/female_inspiration.json +++ b/binaries/data/mods/public/simulation/data/auras/units/female_inspiration.json @@ -8,5 +8,10 @@ ], "auraName": "Inspiration Aura", "auraDescription": "Nearby citizen soldiers work 10% faster.", - "overlayIcon": "art/textures/ui/session/auras/buildgather_bonus.png" + "overlayIcon": "art/textures/ui/session/auras/buildgather_bonus.png", + "rangeOverlay" : { + "lineTexture": "outline_border.png", + "lineTextureMask": "outline_border_mask.png", + "lineThickness": 0.075 + } } diff --git a/binaries/data/mods/public/simulation/templates/template_structure.xml b/binaries/data/mods/public/simulation/templates/template_structure.xml index 877e86f190..5e65547ddf 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure.xml @@ -101,6 +101,7 @@ round default + 2.0 diff --git a/binaries/data/mods/public/simulation/templates/template_unit.xml b/binaries/data/mods/public/simulation/templates/template_unit.xml index 281af58b18..168e2a07de 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit.xml @@ -65,6 +65,7 @@ false + 2.0 1.0 diff --git a/source/simulation2/components/CCmpSelectable.cpp b/source/simulation2/components/CCmpSelectable.cpp index 9de41fdc2a..2fb13240bc 100644 --- a/source/simulation2/components/CCmpSelectable.cpp +++ b/source/simulation2/components/CCmpSelectable.cpp @@ -64,7 +64,7 @@ public: CCmpSelectable() : m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL), - m_BuildingOverlay(NULL), m_UnitOverlay(NULL), + m_BuildingOverlay(NULL), m_UnitOverlay(NULL), m_RangeOverlayData(), m_FadeBaselineAlpha(0.f), m_FadeDeltaAlpha(0.f), m_FadeProgress(0.f), m_Selected(false), m_Cached(false), m_Visible(false) { @@ -77,6 +77,8 @@ public: delete m_DebugSelectionBoxOverlay; delete m_BuildingOverlay; delete m_UnitOverlay; + for (RangeOverlayData& rangeOverlay : m_RangeOverlayData) + delete rangeOverlay.second; } static std::string GetSchema() @@ -126,23 +128,21 @@ public: const CParamNode& textureNode = paramNode.GetChild("Overlay").GetChild("Texture"); const CParamNode& outlineNode = paramNode.GetChild("Overlay").GetChild("Outline"); - const char* textureBasePath = "art/textures/selection/"; - // Save some memory by using interned file paths in these descriptors (almost all actors and // entities have this component, and many use the same textures). if (textureNode.IsOk()) { // textured quad mode (dynamic, for units) m_OverlayDescriptor.m_Type = ICmpSelectable::DYNAMIC_QUAD; - m_OverlayDescriptor.m_QuadTexture = CStrIntern(textureBasePath + textureNode.GetChild("MainTexture").ToUTF8()); - m_OverlayDescriptor.m_QuadTextureMask = CStrIntern(textureBasePath + textureNode.GetChild("MainTextureMask").ToUTF8()); + m_OverlayDescriptor.m_QuadTexture = CStrIntern(TEXTUREBASEPATH + textureNode.GetChild("MainTexture").ToUTF8()); + m_OverlayDescriptor.m_QuadTextureMask = CStrIntern(TEXTUREBASEPATH + textureNode.GetChild("MainTextureMask").ToUTF8()); } else if (outlineNode.IsOk()) { // textured outline mode (static, for buildings) m_OverlayDescriptor.m_Type = ICmpSelectable::STATIC_OUTLINE; - m_OverlayDescriptor.m_LineTexture = CStrIntern(textureBasePath + outlineNode.GetChild("LineTexture").ToUTF8()); - m_OverlayDescriptor.m_LineTextureMask = CStrIntern(textureBasePath + outlineNode.GetChild("LineTextureMask").ToUTF8()); + m_OverlayDescriptor.m_LineTexture = CStrIntern(TEXTUREBASEPATH + outlineNode.GetChild("LineTexture").ToUTF8()); + m_OverlayDescriptor.m_LineTextureMask = CStrIntern(TEXTUREBASEPATH + outlineNode.GetChild("LineTextureMask").ToUTF8()); m_OverlayDescriptor.m_LineThickness = outlineNode.GetChild("LineThickness").ToFloat(); } @@ -193,6 +193,22 @@ public: SetSelectionHighlightAlpha(color.a); } + virtual void AddRangeOverlay(float radius, const std::string& texture, const std::string& textureMask, float thickness) + { + if (!CRenderer::IsInitialised()) + return; + + SOverlayDescriptor rangeOverlayDescriptor; + SOverlayTexturedLine* rangeOverlay = nullptr; + + rangeOverlayDescriptor.m_Radius = radius; + rangeOverlayDescriptor.m_LineTexture = CStrIntern(TEXTUREBASEPATH + texture); + rangeOverlayDescriptor.m_LineTextureMask = CStrIntern(TEXTUREBASEPATH + textureMask); + rangeOverlayDescriptor.m_LineThickness = thickness; + + m_RangeOverlayData.push_back({rangeOverlayDescriptor, rangeOverlay}); + } + virtual void SetSelectionHighlightAlpha(float alpha) { alpha = std::max(m_AlphaMin, alpha); @@ -219,11 +235,9 @@ public: void RenderSubmit(SceneCollector& collector); /** - * Called from RenderSubmit if using a static outline; responsible for ensuring that the static overlay - * is up-to-date before it is rendered. Has no effect unless the static overlay is explicitly marked as - * invalid first (see InvalidateStaticOverlay). + * Draw a textured line overlay. The selection overlays for structures are based solely on footprint shape. */ - void UpdateStaticOverlay(); + void UpdateTexturedLineOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset, bool buildingOverlay); /** * Called from the interpolation handler; responsible for ensuring the dynamic overlay (provided we're @@ -244,11 +258,20 @@ public: */ void UpdateMessageSubscriptions(); + /** + * Delete all range overlays. + */ + void ResetRangeOverlays(); + private: SOverlayDescriptor m_OverlayDescriptor; SOverlayTexturedLine* m_BuildingOverlay; SOverlayQuad* m_UnitOverlay; + // Holds the data for all range overlays + typedef std::pair RangeOverlayData; + std::vector m_RangeOverlayData; + SOverlayLine* m_DebugBoundingBoxOverlay; SOverlayLine* m_DebugSelectionBoxOverlay; @@ -279,9 +302,11 @@ private: /// Total duration of a single fade, in seconds. Assumed constant for now; feel free to change this into /// a member variable if you need to adjust it per component. static const double FADE_DURATION; + static const char* TEXTUREBASEPATH; }; const double CCmpSelectable::FADE_DURATION = 0.3; +const char* CCmpSelectable::TEXTUREBASEPATH = "art/textures/selection/"; void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global)) { @@ -314,8 +339,17 @@ void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global)) // update dynamic overlay only when visible if (m_Color.a > 0) + { UpdateDynamicOverlay(msgData.offset); + for (RangeOverlayData& rangeOverlay : m_RangeOverlayData) + { + delete rangeOverlay.second; + rangeOverlay.second = new SOverlayTexturedLine; + UpdateTexturedLineOverlay(&rangeOverlay.first, *rangeOverlay.second, msgData.offset, false); + } + } + UpdateMessageSubscriptions(); break; @@ -373,6 +407,15 @@ void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global)) } } +void CCmpSelectable::ResetRangeOverlays() +{ + for (RangeOverlayData& rangeOverlay : m_RangeOverlayData) + delete rangeOverlay.second; + m_RangeOverlayData.clear(); + + UpdateMessageSubscriptions(); +} + void CCmpSelectable::UpdateMessageSubscriptions() { bool needInterpolate = false; @@ -402,15 +445,8 @@ void CCmpSelectable::InvalidateStaticOverlay() SAFE_DELETE(m_BuildingOverlay); } -void CCmpSelectable::UpdateStaticOverlay() +void CCmpSelectable::UpdateTexturedLineOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset, bool buildingOverlay) { - // Static overlays are allocated once and not updated until they are explicitly deleted again - // (see InvalidateStaticOverlay). Since they are expected to change rarely (if ever) during - // normal gameplay, this saves us doing all the work below on each frame. - - if (m_BuildingOverlay || m_OverlayDescriptor.m_Type != STATIC_OUTLINE) - return; - if (!CRenderer::IsInitialised()) return; @@ -419,84 +455,69 @@ void CCmpSelectable::UpdateStaticOverlay() if (!cmpFootprint || !cmpPosition || !cmpPosition->IsInWorld()) return; - CmpPtr cmpTerrain(GetSystemEntity()); - if (!cmpTerrain) - return; // should never happen - - // grab position/footprint data - CFixedVector2D position = cmpPosition->GetPosition2D(); - CFixedVector3D rotation = cmpPosition->GetRotation(); - ICmpFootprint::EShape fpShape; entity_pos_t fpSize0_fixed, fpSize1_fixed, fpHeight_fixed; cmpFootprint->GetShape(fpShape, fpSize0_fixed, fpSize1_fixed, fpHeight_fixed); - CTextureProperties texturePropsBase(m_OverlayDescriptor.m_LineTexture.c_str()); + float rotY; + CVector2D origin; + cmpPosition->GetInterpolatedPosition2D(frameOffset, origin.X, origin.Y, rotY); + CFixedVector3D rotation = cmpPosition->GetRotation(); + + CTextureProperties texturePropsBase(overlayDescriptor->m_LineTexture.c_str()); texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); texturePropsBase.SetMaxAnisotropy(4.f); - CTextureProperties texturePropsMask(m_OverlayDescriptor.m_LineTextureMask.c_str()); + CTextureProperties texturePropsMask(overlayDescriptor->m_LineTextureMask.c_str()); texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); texturePropsMask.SetMaxAnisotropy(4.f); - // ------------------------------------------------------------------------------------- + overlay.m_AlwaysVisible = false; + overlay.m_Closed = true; + overlay.m_SimContext = &GetSimContext(); + overlay.m_Thickness = overlayDescriptor->m_LineThickness; + overlay.m_TextureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); + overlay.m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); + overlay.m_Color = m_Color; - m_BuildingOverlay = new SOverlayTexturedLine; - m_BuildingOverlay->m_AlwaysVisible = false; - m_BuildingOverlay->m_Closed = true; - m_BuildingOverlay->m_SimContext = &GetSimContext(); - m_BuildingOverlay->m_Thickness = m_OverlayDescriptor.m_LineThickness; - m_BuildingOverlay->m_TextureBase = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); - m_BuildingOverlay->m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); - - CVector2D origin(position.X.ToFloat(), position.Y.ToFloat()); - - switch (fpShape) + if (buildingOverlay && fpShape == ICmpFootprint::SQUARE) { - case ICmpFootprint::SQUARE: + float s = sinf(-rotation.Y.ToFloat()); + float c = cosf(-rotation.Y.ToFloat()); + CVector2D unitX(c, s); + CVector2D unitZ(-s, c); + + // Add half the line thickness to the radius so that we get an 'outside' stroke of the footprint shape + const float halfSizeX = fpSize0_fixed.ToFloat() / 2.f + overlay.m_Thickness / 2.f; + const float halfSizeZ = fpSize1_fixed.ToFloat() / 2.f + overlay.m_Thickness / 2.f; + + std::vector points; + points.push_back(CVector2D(origin + unitX * halfSizeX + unitZ * (-halfSizeZ))); + points.push_back(CVector2D(origin + unitX * (-halfSizeX) + unitZ * (-halfSizeZ))); + points.push_back(CVector2D(origin + unitX * (-halfSizeX) + unitZ * halfSizeZ)); + points.push_back(CVector2D(origin + unitX * halfSizeX + unitZ * halfSizeZ)); + + SimRender::SubdividePoints(points, TERRAIN_TILE_SIZE / 3.f, overlay.m_Closed); + overlay.PushCoords(points); + } + else + { + const float radius = (buildingOverlay ? fpSize0_fixed.ToFloat() : overlayDescriptor->m_Radius) + overlay.m_Thickness / 3.f; + float stepAngle; + unsigned numSteps; + SimRender::AngularStepFromChordLen(TERRAIN_TILE_SIZE / 3.f, radius, stepAngle, numSteps); + + for (unsigned i = 0; i < numSteps; ++i) { - float s = sinf(-rotation.Y.ToFloat()); - float c = cosf(-rotation.Y.ToFloat()); - CVector2D unitX(c, s); - CVector2D unitZ(-s, c); + float angle = i * stepAngle; + float px = origin.X + radius * sinf(angle); + float pz = origin.Y + radius * cosf(angle); - // add half the line thickness to the radius so that we get an 'outside' stroke of the footprint shape - const float halfSizeX = fpSize0_fixed.ToFloat()/2.f + m_BuildingOverlay->m_Thickness/2.f; - const float halfSizeZ = fpSize1_fixed.ToFloat()/2.f + m_BuildingOverlay->m_Thickness/2.f; - - std::vector points; - points.push_back(CVector2D(origin + unitX * halfSizeX + unitZ *(-halfSizeZ))); - points.push_back(CVector2D(origin + unitX *(-halfSizeX) + unitZ *(-halfSizeZ))); - points.push_back(CVector2D(origin + unitX *(-halfSizeX) + unitZ * halfSizeZ)); - points.push_back(CVector2D(origin + unitX * halfSizeX + unitZ * halfSizeZ)); - - SimRender::SubdividePoints(points, TERRAIN_TILE_SIZE/3.f, m_BuildingOverlay->m_Closed); - m_BuildingOverlay->PushCoords(points); + overlay.PushCoords(px, pz); } - break; - case ICmpFootprint::CIRCLE: - { - const float radius = fpSize0_fixed.ToFloat() + m_BuildingOverlay->m_Thickness/3.f; - if (radius > 0) // prevent catastrophic failure - { - float stepAngle; - unsigned numSteps; - SimRender::AngularStepFromChordLen(TERRAIN_TILE_SIZE/3.f, radius, stepAngle, numSteps); - - for (unsigned i = 0; i < numSteps; i++) // '<' is sufficient because the line is closed automatically - { - float angle = i * stepAngle; - float px = origin.X + radius * sinf(angle); - float pz = origin.Y + radius * cosf(angle); - - m_BuildingOverlay->PushCoords(px, pz); - } - } - } - break; } - ENSURE(m_BuildingOverlay); + ENSURE(overlay.m_TextureBase); } void CCmpSelectable::UpdateDynamicOverlay(float frameOffset) @@ -625,7 +646,14 @@ void CCmpSelectable::RenderSubmit(SceneCollector& collector) { case STATIC_OUTLINE: { - UpdateStaticOverlay(); + if (!m_BuildingOverlay) + { + // Static overlays are allocated once and not updated until they are explicitly deleted again + // (see InvalidateStaticOverlay). Since they are expected to change rarely (if ever) during + // normal gameplay, this saves us doing all the work below on each frame. + m_BuildingOverlay = new SOverlayTexturedLine; + UpdateTexturedLineOverlay(&m_OverlayDescriptor, *m_BuildingOverlay, 0, true); + } m_BuildingOverlay->m_Color = m_Color; // done separately so alpha changes don't require a full update call collector.Submit(m_BuildingOverlay); } @@ -639,6 +667,10 @@ void CCmpSelectable::RenderSubmit(SceneCollector& collector) default: break; } + + for (const RangeOverlayData& rangeOverlay : m_RangeOverlayData) + if (rangeOverlay.second) + collector.Submit(rangeOverlay.second); } // Render bounding box debug overlays if we have a positive target alpha value. This ensures diff --git a/source/simulation2/components/ICmpSelectable.cpp b/source/simulation2/components/ICmpSelectable.cpp index a4caea4989..6ec9156e88 100644 --- a/source/simulation2/components/ICmpSelectable.cpp +++ b/source/simulation2/components/ICmpSelectable.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2012 Wildfire Games. +/* 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 @@ -25,6 +25,8 @@ BEGIN_INTERFACE_WRAPPER(Selectable) DEFINE_INTERFACE_METHOD_2("SetSelectionHighlight", void, ICmpSelectable, SetSelectionHighlight, CColor, bool) +DEFINE_INTERFACE_METHOD_4("AddRangeOverlay", void, ICmpSelectable, AddRangeOverlay, float, std::string, std::string, float) +DEFINE_INTERFACE_METHOD_0("ResetRangeOverlays", void, ICmpSelectable, ResetRangeOverlays) END_INTERFACE_WRAPPER(Selectable) bool ICmpSelectable::ms_EnableDebugOverlays = false; diff --git a/source/simulation2/components/ICmpSelectable.h b/source/simulation2/components/ICmpSelectable.h index d10f55034e..5af6208d5d 100644 --- a/source/simulation2/components/ICmpSelectable.h +++ b/source/simulation2/components/ICmpSelectable.h @@ -43,6 +43,7 @@ public: CStrIntern m_LineTexture; CStrIntern m_LineTextureMask; float m_LineThickness; + int m_Radius; SOverlayDescriptor() : m_LineThickness(0) { } }; @@ -60,6 +61,16 @@ public: */ virtual void SetSelectionHighlight(const CColor& color, bool selected) = 0; + /** + * Add a range overlay to this entity, for example for an aura or attack. + */ + virtual void AddRangeOverlay(float radius, const std::string& texture, const std::string& textureMask, float thickness) = 0; + + /** + * Delete all range overlays. + */ + virtual void ResetRangeOverlays() = 0; + /** * Enables or disables rendering of an entity's selectable. * @param visible Whether the selectable should be visible.