diff --git a/binaries/data/mods/public/art/textures/selection/actor.png b/binaries/data/mods/public/art/textures/selection/actor.png new file mode 100644 index 0000000000..2e5155bb62 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/actor.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:e637fde91b7d2fe5df3693e75cf5606b4a13f9186fe399ac7978f2290dbcfd07 +size 14097 diff --git a/binaries/data/mods/public/art/textures/selection/actor_mask.png b/binaries/data/mods/public/art/textures/selection/actor_mask.png new file mode 100644 index 0000000000..62f166ca58 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/actor_mask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:dd306a1bc00ffd1be2a37e70fefdd14084f729f524583398813eb8d0dc11eb36 +size 7906 diff --git a/binaries/data/mods/public/art/textures/selection/circle/128x128.png b/binaries/data/mods/public/art/textures/selection/circle/128x128.png new file mode 100644 index 0000000000..0b99ae665c --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/circle/128x128.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3c9b5ad47361501990ee49d74fecc55dc59bdd590a53db71075d8978b676ac95 +size 6338 diff --git a/binaries/data/mods/public/art/textures/selection/circle/128x128_mask.png b/binaries/data/mods/public/art/textures/selection/circle/128x128_mask.png new file mode 100644 index 0000000000..bc3c20dc98 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/circle/128x128_mask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1077a64d841e8315794e4345443692616b14e43be88807c654b74af9c6f5f2ef +size 3546 diff --git a/binaries/data/mods/public/art/textures/selection/circle/256x256.png b/binaries/data/mods/public/art/textures/selection/circle/256x256.png new file mode 100644 index 0000000000..8e8041fab7 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/circle/256x256.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:35f4ecde8d11f157c834bfd6053e84ead85a853f60bfc71dcf4463e8b5802f24 +size 14083 diff --git a/binaries/data/mods/public/art/textures/selection/circle/256x256_mask.png b/binaries/data/mods/public/art/textures/selection/circle/256x256_mask.png new file mode 100644 index 0000000000..f2fe9b840f --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/circle/256x256_mask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:21c690fe57fe019a5a047acd875be93db67b66148fcf6e8b5cadbb26fe99c413 +size 7828 diff --git a/binaries/data/mods/public/art/textures/selection/circle/32x32.png b/binaries/data/mods/public/art/textures/selection/circle/32x32.png new file mode 100644 index 0000000000..2799a40209 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/circle/32x32.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f84577986ca2e24a78fb13ad4be172df74f51aaf32a7c18ec391b72cd9832827 +size 1266 diff --git a/binaries/data/mods/public/art/textures/selection/circle/32x32_mask.png b/binaries/data/mods/public/art/textures/selection/circle/32x32_mask.png new file mode 100644 index 0000000000..57603207f1 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/circle/32x32_mask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c94703921cabeb80d83b2300624128ce3b30a3f002a605b0ba8ce937e6b0cf6b +size 834 diff --git a/binaries/data/mods/public/art/textures/selection/circle/64x64.png b/binaries/data/mods/public/art/textures/selection/circle/64x64.png new file mode 100644 index 0000000000..730eaf888d --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/circle/64x64.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ca1250e3ccffa99d0bcad93638d772b52bffb9ab9247e65a3763944c00dc1145 +size 2874 diff --git a/binaries/data/mods/public/art/textures/selection/circle/64x64_mask.png b/binaries/data/mods/public/art/textures/selection/circle/64x64_mask.png new file mode 100644 index 0000000000..43c06b2716 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/circle/64x64_mask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f770f95e68fad2f1863b3a5238932d6c9052875cf158eb24aeb1e29da5081dc3 +size 1802 diff --git a/binaries/data/mods/public/art/textures/selection/outline_border.png b/binaries/data/mods/public/art/textures/selection/outline_border.png new file mode 100644 index 0000000000..358ff21891 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/outline_border.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:16be8ef5f28cb91f905095506b8184bf8ff04731da66c1f93d316b6b164d3118 +size 2849 diff --git a/binaries/data/mods/public/art/textures/selection/outline_border_mask.png b/binaries/data/mods/public/art/textures/selection/outline_border_mask.png new file mode 100644 index 0000000000..f94b4110d5 --- /dev/null +++ b/binaries/data/mods/public/art/textures/selection/outline_border_mask.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:99e23d7caf55d7181c5f74041178951b2565bbe201b08edff6568be2688e1c0f +size 2826 diff --git a/binaries/data/mods/public/shaders/arb/overlayline.fp b/binaries/data/mods/public/shaders/arb/overlayline.fp index fd7847294a..bde918c33d 100644 --- a/binaries/data/mods/public/shaders/arb/overlayline.fp +++ b/binaries/data/mods/public/shaders/arb/overlayline.fp @@ -4,13 +4,16 @@ TEMP base; TEMP mask; TEMP color; - // Combine base texture and color, using mask texture TEX base, fragment.texcoord[0], texture[0], 2D; TEX mask, fragment.texcoord[0], texture[1], 2D; -LRP color.rgb, mask, objectColor, base; +#if USE_OBJECTCOLOR + LRP color.rgb, mask, objectColor, base; +#else + LRP color.rgb, mask, fragment.color, base; +#endif -#ifdef IGNORE_LOS +#if IGNORE_LOS MOV result.color.rgb, color; #else // Multiply RGB by LOS texture (alpha channel) @@ -19,9 +22,11 @@ LRP color.rgb, mask, objectColor, base; MUL result.color.rgb, color, los.a; #endif -// Use alpha from base texture, combined with the object color alpha. -// The latter is usually 1, so this basically comes down to base.a -MUL result.color.a, objectColor.a, base.a; - +// Use alpha from base texture, combined with the object color/fragment alpha. +#if USE_OBJECTCOLOR + MUL result.color.a, objectColor.a, base.a; +#else + MUL result.color.a, fragment.color.a, base.a; +#endif END diff --git a/binaries/data/mods/public/shaders/arb/overlayline.xml b/binaries/data/mods/public/shaders/arb/overlayline.xml index 07383bc018..c0b46f1563 100644 --- a/binaries/data/mods/public/shaders/arb/overlayline.xml +++ b/binaries/data/mods/public/shaders/arb/overlayline.xml @@ -1,9 +1,16 @@ + + + @@ -11,7 +18,7 @@ - + diff --git a/binaries/data/mods/public/simulation/components/GuiInterface.js b/binaries/data/mods/public/simulation/components/GuiInterface.js index 4be58ad7cd..10a15c7844 100644 --- a/binaries/data/mods/public/simulation/components/GuiInterface.js +++ b/binaries/data/mods/public/simulation/components/GuiInterface.js @@ -495,7 +495,6 @@ GuiInterface.prototype.IsStanceSelected = function(player, data) GuiInterface.prototype.SetSelectionHighlight = function(player, cmd) { var cmpPlayerMan = Engine.QueryInterface(SYSTEM_ENTITY, IID_PlayerManager); - var playerColours = {}; // cache of owner -> colour map for each (var ent in cmd.entities) @@ -504,14 +503,7 @@ GuiInterface.prototype.SetSelectionHighlight = function(player, cmd) if (!cmpSelectable) continue; - if (cmd.alpha == 0) - { - cmpSelectable.SetSelectionHighlight({"r":0, "g":0, "b":0, "a":0}); - continue; - } - // Find the entity's owner's colour: - var owner = -1; var cmpOwnership = Engine.QueryInterface(ent, IID_Ownership); if (cmpOwnership) diff --git a/binaries/data/mods/public/simulation/templates/special/territory_block.xml b/binaries/data/mods/public/simulation/templates/special/territory_block.xml index 7774fb6dce..0ee7160a23 100644 --- a/binaries/data/mods/public/simulation/templates/special/territory_block.xml +++ b/binaries/data/mods/public/simulation/templates/special/territory_block.xml @@ -23,6 +23,12 @@ + + + circle/128x128.png + circle/128x128_mask.png + + 64 diff --git a/binaries/data/mods/public/simulation/templates/special/territory_pull.xml b/binaries/data/mods/public/simulation/templates/special/territory_pull.xml index bd8c4acafe..2c2be00441 100644 --- a/binaries/data/mods/public/simulation/templates/special/territory_pull.xml +++ b/binaries/data/mods/public/simulation/templates/special/territory_pull.xml @@ -23,6 +23,12 @@ + + + circle/128x128.png + circle/128x128_mask.png + + 0 diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_flora_bush_berry.xml b/binaries/data/mods/public/simulation/templates/template_gaia_flora_bush_berry.xml index e979c8ed9b..65fdcafe83 100644 --- a/binaries/data/mods/public/simulation/templates/template_gaia_flora_bush_berry.xml +++ b/binaries/data/mods/public/simulation/templates/template_gaia_flora_bush_berry.xml @@ -18,6 +18,12 @@ + + + circle/128x128.png + circle/128x128_mask.png + + diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml b/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml index ca3e80f1e4..096c4e7a15 100644 --- a/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml +++ b/binaries/data/mods/public/simulation/templates/template_gaia_flora_tree.xml @@ -17,6 +17,12 @@ + + + circle/128x128.png + circle/128x128_mask.png + + diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_geo_mineral.xml b/binaries/data/mods/public/simulation/templates/template_gaia_geo_mineral.xml index f3a147ae96..0f17d2c656 100644 --- a/binaries/data/mods/public/simulation/templates/template_gaia_geo_mineral.xml +++ b/binaries/data/mods/public/simulation/templates/template_gaia_geo_mineral.xml @@ -17,6 +17,13 @@ + + + outline_border.png + outline_border_mask.png + 0.2 + + diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_geo_rock.xml b/binaries/data/mods/public/simulation/templates/template_gaia_geo_rock.xml index a9542935e5..a247c15830 100644 --- a/binaries/data/mods/public/simulation/templates/template_gaia_geo_rock.xml +++ b/binaries/data/mods/public/simulation/templates/template_gaia_geo_rock.xml @@ -17,6 +17,12 @@ + + + circle/128x128.png + circle/128x128_mask.png + + diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_ruins.xml b/binaries/data/mods/public/simulation/templates/template_gaia_ruins.xml index f9ee70a705..408ccb65ca 100644 --- a/binaries/data/mods/public/simulation/templates/template_gaia_ruins.xml +++ b/binaries/data/mods/public/simulation/templates/template_gaia_ruins.xml @@ -24,6 +24,12 @@ + + + circle/128x128.png + circle/128x128_mask.png + + diff --git a/binaries/data/mods/public/simulation/templates/template_gaia_treasure.xml b/binaries/data/mods/public/simulation/templates/template_gaia_treasure.xml index 15aea8a4a0..4f11e3774d 100644 --- a/binaries/data/mods/public/simulation/templates/template_gaia_treasure.xml +++ b/binaries/data/mods/public/simulation/templates/template_gaia_treasure.xml @@ -29,5 +29,11 @@ + + + circle/128x128.png + circle/128x128_mask.png + + diff --git a/binaries/data/mods/public/simulation/templates/template_structure.xml b/binaries/data/mods/public/simulation/templates/template_structure.xml index d27bdd5714..c7fdbd5b1b 100644 --- a/binaries/data/mods/public/simulation/templates/template_structure.xml +++ b/binaries/data/mods/public/simulation/templates/template_structure.xml @@ -66,6 +66,15 @@ default default + + + + outline_border.png + outline_border_mask.png + 0.4 + + + diff --git a/binaries/data/mods/public/simulation/templates/template_unit.xml b/binaries/data/mods/public/simulation/templates/template_unit.xml index d539d87364..8afd0fced0 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit.xml @@ -80,6 +80,14 @@ 20 + + + + circle/128x128.png + circle/128x128_mask.png + + + 2.0 0.333 diff --git a/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml b/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml index c9d5e0d600..d1c58bb705 100644 --- a/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml +++ b/binaries/data/mods/public/simulation/templates/template_unit_infantry.xml @@ -70,6 +70,14 @@ 1 + + + + circle/128x128.png + circle/128x128_mask.png + + + diff --git a/source/graphics/ModelAbstract.h b/source/graphics/ModelAbstract.h index 1686847e31..60baec3a8d 100644 --- a/source/graphics/ModelAbstract.h +++ b/source/graphics/ModelAbstract.h @@ -89,7 +89,7 @@ public: virtual void SetDirtyRec(int dirtyflags) = 0; /// Returns world space bounds of this object and all child objects. - virtual const CBoundingBoxAligned GetWorldBoundsRec() { return GetWorldBounds(); } + virtual const CBoundingBoxAligned GetWorldBoundsRec() { return GetWorldBounds(); } // default implementation /** * Returns the world-space selection box of this model. Used primarily for hittesting against against a selection ray. The diff --git a/source/graphics/Overlay.h b/source/graphics/Overlay.h index ce6f7c4383..556c5bd1e5 100644 --- a/source/graphics/Overlay.h +++ b/source/graphics/Overlay.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -20,8 +20,10 @@ #include "graphics/RenderableObject.h" #include "graphics/Texture.h" +#include "maths/Vector2D.h" #include "maths/Vector3D.h" #include "ps/Overlay.h" // CColor (TODO: that file has nothing to do with overlays, it should be renamed) +#include "simulation2/components/ICmpFootprint.h" class CTerrain; @@ -35,11 +37,9 @@ struct SOverlayLine CColor m_Color; std::vector m_Coords; // (x, y, z) vertex coordinate triples; shape is not automatically closed - u8 m_Thickness; // pixels + u8 m_Thickness; // in pixels - /// Utility function; pushes three vertex coordinates at once onto the coordinates array void PushCoords(const float x, const float y, const float z) { m_Coords.push_back(x); m_Coords.push_back(y); m_Coords.push_back(z); } - /// Utility function; pushes a vertex location onto the coordinates array void PushCoords(const CVector3D& v) { PushCoords(v.X, v.Y, v.Z); } }; @@ -64,10 +64,10 @@ struct SOverlayTexturedLine }; SOverlayTexturedLine() - : m_Terrain(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), m_StartCapType(LINECAP_FLAT), m_EndCapType(LINECAP_FLAT) - {} + : m_SimContext(NULL), m_Thickness(1.0f), m_Closed(false), m_AlwaysVisible(false), + m_StartCapType(LINECAP_FLAT), m_EndCapType(LINECAP_FLAT) + { } - CTerrain* m_Terrain; CTexturePtr m_TextureBase; CTexturePtr m_TextureMask; CColor m_Color; ///< Color to apply to the line texture @@ -79,6 +79,7 @@ struct SOverlayTexturedLine LineCapType m_StartCapType; ///< LineCapType to be used at the start of the line LineCapType m_EndCapType; ///< LineCapType to be used at the end of the line + const CSimContext* m_SimContext; /// Simulation context applicable for this overlay line; used to obtain terrain information shared_ptr m_RenderData; ///< Cached renderer data (shared_ptr so that copies/deletes are automatic) /** @@ -86,6 +87,14 @@ struct SOverlayTexturedLine * If the input string is unrecognized, a warning is issued and a default value is returned. */ static LineCapType StrToLineCapType(const std::wstring& str); + + void PushCoords(const float x, const float z) { m_Coords.push_back(x); m_Coords.push_back(z); } + void PushCoords(const CVector2D& v) { PushCoords(v.X, v.Y); } + void PushCoords(const std::vector& points) + { + for (size_t i = 0; i < points.size(); ++i) + PushCoords(points[i]); + } }; /** @@ -99,6 +108,19 @@ struct SOverlaySprite float m_X0, m_Y0, m_X1, m_Y1; // billboard corner coordinates, relative to base position }; +/** + * Rectangular single-quad terrain overlay, with world space coordinates. The vertices of the quad + * are not required to be coplanar; the quad is arbitrarily triangulated with no effort being made to + * find a best fit to the underlying terrain. + */ +struct SOverlayQuad +{ + CTexturePtr m_Texture; + CTexturePtr m_TextureMask; + CVector3D m_Corners[4]; + CColor m_Color; +}; + // TODO: OverlayText #endif // INCLUDED_GRAPHICS_OVERLAY diff --git a/source/graphics/RenderableObject.h b/source/graphics/RenderableObject.h index c3784fd5e6..cdc702a6d5 100644 --- a/source/graphics/RenderableObject.h +++ b/source/graphics/RenderableObject.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -57,14 +57,16 @@ class CRenderableObject public: // constructor - CRenderableObject() : m_RenderData(0), m_BoundsValid(false) { + CRenderableObject() : m_RenderData(0), m_BoundsValid(false) + { m_Transform.SetIdentity(); } // destructor virtual ~CRenderableObject() { delete m_RenderData; } // set object transform - virtual void SetTransform(const CMatrix3D& transform) { + virtual void SetTransform(const CMatrix3D& transform) + { if (m_Transform == transform) return; // store transform, calculate inverse @@ -82,8 +84,10 @@ public: // mark some part of the renderdata as dirty, and requiring // an update on next render - void SetDirty(u32 dirtyflags) { - if (m_RenderData) m_RenderData->m_UpdateFlags|=dirtyflags; + void SetDirty(u32 dirtyflags) + { + if (m_RenderData) + m_RenderData->m_UpdateFlags |= dirtyflags; } /** @@ -98,7 +102,8 @@ public: virtual void CalcBounds() = 0; /// Returns the world-space axis-aligned bounds of this object. - const CBoundingBoxAligned& GetWorldBounds() { + const CBoundingBoxAligned& GetWorldBounds() + { RecalculateBoundsIfNecessary(); return m_WorldBounds; } @@ -111,7 +116,8 @@ public: virtual void InvalidateBounds() { m_BoundsValid = false; } // Set the object renderdata and free previous renderdata, if any. - void SetRenderData(CRenderData* renderdata) { + void SetRenderData(CRenderData* renderdata) + { delete m_RenderData; m_RenderData = renderdata; } diff --git a/source/graphics/ShaderProgram.h b/source/graphics/ShaderProgram.h index 4f94da176d..c024299f62 100644 --- a/source/graphics/ShaderProgram.h +++ b/source/graphics/ShaderProgram.h @@ -136,7 +136,7 @@ public: /** * Returns bitset of STREAM_* value, indicating what vertex data streams the - * vertex shader needs. + * vertex shader needs (e.g. position, color, UV, ...). */ int GetStreamFlags() const; diff --git a/source/graphics/ShaderProgramFFP.cpp b/source/graphics/ShaderProgramFFP.cpp index 7eb565c296..de4f8d8f72 100644 --- a/source/graphics/ShaderProgramFFP.cpp +++ b/source/graphics/ShaderProgramFFP.cpp @@ -168,10 +168,11 @@ class CShaderProgramFFP_OverlayLine : public CShaderProgramFFP }; bool m_IgnoreLos; + bool m_UseObjectColor; public: CShaderProgramFFP_OverlayLine(const CShaderDefines& defines) : - CShaderProgramFFP(STREAM_POS | STREAM_UV0 | STREAM_UV1) + CShaderProgramFFP(0) // will be set manually in initializer below { SetUniformIndex("losTransform", ID_losTransform); SetUniformIndex("objectColor", ID_objectColor); @@ -182,6 +183,11 @@ public: SetUniformIndex("losTex", 2); m_IgnoreLos = (defines.GetInt("IGNORE_LOS") != 0); + m_UseObjectColor = (defines.GetInt("USE_OBJECTCOLOR") != 0); + + m_StreamFlags = STREAM_POS | STREAM_UV0 | STREAM_UV1; + if (!m_UseObjectColor) + m_StreamFlags |= STREAM_COLOR; } bool IsIgnoreLos() @@ -219,11 +225,11 @@ public: virtual void Bind() { // RGB channels: - // Unit 0: Load base texture - // Unit 1: Load mask texture; interpolate with objectColor & base + // Unit 0: Sample base texture + // Unit 1: Sample mask texture; interpolate with [objectColor or vertexColor] and base, depending on USE_OBJECTCOLOR // Unit 2: (Load LOS texture; multiply) if not #IGNORE_LOS, pass through otherwise // Alpha channel: - // Unit 0: Load base texture + // Unit 0: Sample base texture // Unit 1: Multiply by objectColor // Unit 2: Pass through @@ -231,10 +237,12 @@ public: glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); + // Sample base texture RGB glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); + // Sample base texture Alpha glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_REPLACE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); @@ -244,20 +252,22 @@ public: pglActiveTextureARB(GL_TEXTURE1); glEnable(GL_TEXTURE_2D); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE); - // Uniform() sets GL_TEXTURE_ENV_COLOR - // load mask texture; interpolate with objectColor and base; GL_INTERPOLATE takes 3 arguments: - // a0 * a2 + a1 * (1 - a2) + // RGB: interpolate component-wise between [objectColor or vertexColor] and base using mask: + // a0 * a2 + a1 * (1 - a2) + // Overridden implementation of Uniform() sets GL_TEXTURE_ENV_COLOR to objectColor, which + // is referenced as GL_CONSTANT (see spec) glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_INTERPOLATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_CONSTANT); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, m_UseObjectColor ? GL_CONSTANT : GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB_ARB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_RGB_ARB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB_ARB, GL_SRC_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE2_RGB_ARB, GL_TEXTURE); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB_ARB, GL_SRC_COLOR); + // ALPHA: Multiply base alpha with [objectColor or vertexColor] alpha glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA_ARB, GL_MODULATE); - glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, GL_CONSTANT); + glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_ALPHA_ARB, m_UseObjectColor ? GL_CONSTANT : GL_PRIMARY_COLOR); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA_ARB, GL_SRC_ALPHA); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE1_ALPHA_ARB, GL_PREVIOUS); glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA_ARB, GL_SRC_ALPHA); @@ -278,12 +288,12 @@ public: } else { - // multiply RGB with LoS texture alpha channel + // Multiply RGB result up till now with LoS texture alpha channel glEnable(GL_TEXTURE_GEN_S); glEnable(GL_TEXTURE_GEN_T); glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR); - // Uniform() sets GL_OBJECT_PLANE values + // Overridden implementation of Uniform() sets GL_OBJECT_PLANE values glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB_ARB, GL_MODULATE); glTexEnvi(GL_TEXTURE_ENV, GL_SOURCE0_RGB_ARB, GL_PREVIOUS); diff --git a/source/maths/Ease.h b/source/maths/Ease.h new file mode 100644 index 0000000000..4b3e18be21 --- /dev/null +++ b/source/maths/Ease.h @@ -0,0 +1,148 @@ +/* Copyright (C) 2012 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_EASE +#define INCLUDED_EASE + +/* + * Straightforward C++ port of Robert Penner's easing equations + * http://www.robertpenner.com/easing/ + * + * Copyright (c) 2001 Robert Penner + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted + * provided that the following conditions are met: + * - Redistributions of source code must retain the above copyright notice, this list of + * conditions and the following disclaimer. + * - Redistributions in binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other materials provided + * with the distribution. + * - Neither the name of the author nor the names of contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND + * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER + * IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include + +/** + * Generic easing functions. In each function, the parameters are: + * + * @param t Current time in seconds, as a float between 0 and d (inclusive). + * @param d Total duration of the ease, in seconds. Must be strictly positive. + * @param b Baseline value (at t = 0). + * @param c Delta from baseline value to reach the target value (at t = d). I.e., target = b + c. + * + * Each function outputs the eased value between 'b' and 'b+c' at time 't'. + */ +class Ease +{ +public: + static float QuadIn(float t, const float b, const float c, const float d) + { + t /= d; + return c*t*t + b; + } + + static float QuadOut(float t, const float b, const float c, const float d) + { + t /= d; + return -c * t*(t-2) + b; + } + + static float QuadInOut(float t, const float b, const float c, const float d) + { + t /= d/2; + if (t < 1) + return c/2*t*t + b; + --t; + return -c/2 * (t*(t-2) - 1) + b; + } + + static float CubicIn(float t, const float b, const float c, const float d) + { + t /= d; + return c*t*t*t + b; + } + + static float CubicOut(float t, const float b, const float c, const float d) + { + t = t/d - 1; + return c*(t*t*t + 1) + b; + } + + static float CubicInOut(float t, const float b, const float c, const float d) + { + t /= d/2; + if (t < 1) + return c/2*t*t*t + b; + t -= 2; + return c/2*(t*t*t + 2) + b; + } + + static float QuartIn(float t, const float b, const float c, const float d) + { + t /= d; + return c*t*t*t*t + b; + } + + static float QuartOut(float t, const float b, const float c, const float d) + { + t = t/d - 1; + return -c*(t*t*t*t - 1) + b; + } + + static float QuartInOut(float t, const float b, const float c, const float d) + { + t /= d/2; + if (t < 1) + return c/2*t*t*t*t + b; + t -= 2; + return -c/2 * (t*t*t*t - 2) + b; + } + + static float QuintIn(float t, const float b, const float c, const float d) + { + t /= d; + return c*t*t*t*t*t + b; + } + + static float QuintOut(float t, const float b, const float c, const float d) + { + t = t/d - 1; + return c*(t*t*t*t*t + 1) + b; + } + + static float QuintInOut(float t, const float b, const float c, const float d) + { + t /= d/2; + if (t < 1) + return c/2*t*t*t*t*t + b; + t -= 2; + return c/2*(t*t*t*t*t + 2) + b; + } +}; + +#endif // INCLUDED_EASE diff --git a/source/maths/FixedVector2D.h b/source/maths/FixedVector2D.h index c58f66f189..739469edbf 100644 --- a/source/maths/FixedVector2D.h +++ b/source/maths/FixedVector2D.h @@ -27,7 +27,6 @@ public: fixed X, Y; CFixedVector2D() { } - CFixedVector2D(fixed X, fixed Y) : X(X), Y(Y) { } /// Vector equality diff --git a/source/maths/Vector2D.h b/source/maths/Vector2D.h index 977584c84c..55787d81f6 100644 --- a/source/maths/Vector2D.h +++ b/source/maths/Vector2D.h @@ -121,7 +121,7 @@ public: Y /= mag; } - CVector2D Normalized() + CVector2D Normalized() const { float mag = Length(); return CVector2D(X / mag, Y / mag); @@ -130,7 +130,7 @@ public: /** * Returns a version of this vector rotated counterclockwise by @p angle radians. */ - CVector2D Rotated(float angle) + CVector2D Rotated(float angle) const { float c = cosf(angle); float s = sinf(angle); diff --git a/source/renderer/OverlayRenderer.cpp b/source/renderer/OverlayRenderer.cpp index 0a30e7ad14..940f50711f 100644 --- a/source/renderer/OverlayRenderer.cpp +++ b/source/renderer/OverlayRenderer.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -19,34 +19,154 @@ #include "OverlayRenderer.h" -#include "maths/MathUtil.h" -#include "maths/Quaternion.h" -#include "maths/Vector2D.h" +#include #include "graphics/LOSTexture.h" #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "lib/ogl.h" +#include "maths/MathUtil.h" +#include "maths/Quaternion.h" #include "ps/Game.h" #include "ps/Profile.h" #include "renderer/Renderer.h" +#include "renderer/VertexArray.h" #include "renderer/VertexBuffer.h" #include "renderer/VertexBufferManager.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpWaterManager.h" +#include "simulation2/system/SimContext.h" + +/** + * As a general TODO, some of the code here still uses g_VBMan manually. + * For consistency with other parts of the engine, it'd be nice to switch + * over to the cleaner and more readable VertexArray API. + */ + +/** + * Key used to group quads into batches for more efficient rendering. Currently groups by the combination + * of the main texture and the texture mask, to minimize texture swapping during rendering. + */ +struct QuadBatchKey +{ + QuadBatchKey (const CTexturePtr& texture, const CTexturePtr& textureMask) + : m_Texture(texture), m_TextureMask(textureMask) + { } + + bool operator==(const QuadBatchKey& other) const + { + return (m_Texture == other.m_Texture && m_TextureMask == other.m_TextureMask); + } + + CTexturePtr m_Texture; + CTexturePtr m_TextureMask; +}; + +/** + * Holds information about a single quad rendering batch. + */ +class QuadBatchData : public CRenderData +{ +public: + QuadBatchData() : m_IndicesBase(0) { } + + /// Holds the quad overlay structures to be rendered during this batch. + /// Must be cleared after each frame. + std::vector m_Quads; + /// Start index of this batch into the dedicated quad indices VertexArray (see OverlayInternals). + size_t m_IndicesBase; +}; struct OverlayRendererInternals { + typedef boost::unordered_map QuadBatchMap; + + OverlayRendererInternals(); + ~OverlayRendererInternals(){ } + std::vector lines; std::vector texlines; std::vector sprites; + std::vector quads; + + QuadBatchMap quadBatchMap; + + // Dedicated vertex/index buffers for rendering all quads (to within the limits set by + // MAX_QUAD_OVERLAYS). + VertexArray quadVertices; + VertexArray::Attribute quadAttributePos; + VertexArray::Attribute quadAttributeColor; + VertexArray::Attribute quadAttributeUV; + VertexIndexArray quadIndices; + + /// Maximum amount of quad overlays we support for rendering. This limit is set to be able to + /// render all quads from a single dedicated VB without having to reallocate it, which is much + /// faster in the typical case of rendering only a handful of quads. When modifying this value, + /// you must take care for the new amount of quads to fit in a single VBO (which is not likely + /// to be a problem). + static const size_t MAX_QUAD_OVERLAYS = 1024; + + // Sets of commonly-(re)used shader defines. + CShaderDefines defsOverlayLineNormal; + CShaderDefines defsOverlayLineAlwaysVisible; + CShaderDefines defsQuadOverlay; + + /// Small vertical offset of overlays from terrain to prevent visual glitches + static const float OVERLAY_VOFFSET; }; +const float OverlayRendererInternals::OVERLAY_VOFFSET = 0.2f; + +OverlayRendererInternals::OverlayRendererInternals() + : quadVertices(GL_DYNAMIC_DRAW), quadIndices(GL_DYNAMIC_DRAW) +{ + quadAttributePos.elems = 3; + quadAttributePos.type = GL_FLOAT; + quadVertices.AddAttribute(&quadAttributePos); + + quadAttributeColor.elems = 4; + quadAttributeColor.type = GL_FLOAT; + quadVertices.AddAttribute(&quadAttributeColor); + + quadAttributeUV.elems = 2; + quadAttributeUV.type = GL_SHORT; // don't use GL_UNSIGNED_SHORT here, TexCoordPointer won't accept it + quadVertices.AddAttribute(&quadAttributeUV); + + quadVertices.SetNumVertices(MAX_QUAD_OVERLAYS * 4); + quadVertices.Layout(); // allocate backing store + + quadIndices.SetNumVertices(MAX_QUAD_OVERLAYS * 6); + quadIndices.Layout(); // allocate backing store + + // Since the quads in the vertex array are independent and always consist of exactly 4 vertices per quad, the + // indices are always the same; we can therefore fill in all the indices once and pretty much forget about + // them. We then also no longer need its backing store, since we never change any indices afterwards. + VertexArrayIterator index = quadIndices.GetIterator(); + for (size_t i = 0; i < MAX_QUAD_OVERLAYS; ++i) + { + *index++ = i*4 + 0; + *index++ = i*4 + 1; + *index++ = i*4 + 2; + *index++ = i*4 + 2; + *index++ = i*4 + 3; + *index++ = i*4 + 0; + } + quadIndices.Upload(); + quadIndices.FreeBackingStore(); + + // Note that we're reusing the textured overlay line shader for the quad overlay rendering. This + // is because their code is almost identical; the only difference is that for the quad overlays + // we want to use a vertex color stream as opposed to an objectColor uniform. To this end, the + // shader has been set up to switch between the two behaviours based on the USE_OBJECTCOLOR define. + defsOverlayLineNormal.Add("USE_OBJECTCOLOR", "1"); + defsOverlayLineAlwaysVisible.Add("USE_OBJECTCOLOR", "1"); + defsOverlayLineAlwaysVisible.Add("IGNORE_LOS", "1"); +} + class CTexturedLineRData : public CRenderData { public: - CTexturedLineRData(SOverlayTexturedLine* line) : - m_Line(line), m_VB(NULL), m_VBIndices(NULL), m_Raise(.2f) + CTexturedLineRData(SOverlayTexturedLine* line) : m_Line(line), m_VB(NULL), m_VBIndices(NULL) { } ~CTexturedLineRData() @@ -62,7 +182,7 @@ public: SVertex(CVector3D pos, float u, float v) : m_Position(pos) { m_UVs[0] = u; m_UVs[1] = v; } CVector3D m_Position; GLfloat m_UVs[2]; - float _padding[3]; // 5 floats up till now, so pad with another 3 floats to get a power of 2 + float _padding[3]; // get a pow2 struct size }; cassert(sizeof(SVertex) == 32); @@ -91,10 +211,16 @@ public: SOverlayTexturedLine* m_Line; CVertexBuffer::VBChunk* m_VB; CVertexBuffer::VBChunk* m_VBIndices; - - float m_Raise; // small vertical offset of line from terrain to prevent visual glitches }; +static size_t hash_value(const QuadBatchKey& d) +{ + size_t seed = 0; + boost::hash_combine(seed, d.m_Texture); + boost::hash_combine(seed, d.m_TextureMask); + return seed; +} + OverlayRenderer::OverlayRenderer() { m = new OverlayRendererInternals(); @@ -128,13 +254,25 @@ void OverlayRenderer::Submit(SOverlaySprite* overlay) m->sprites.push_back(overlay); } +void OverlayRenderer::Submit(SOverlayQuad* overlay) +{ + m->quads.push_back(overlay); +} + void OverlayRenderer::EndFrame() { m->lines.clear(); m->texlines.clear(); m->sprites.clear(); + m->quads.clear(); // this should leave the capacity unchanged, which is okay since it // won't be very large or very variable + + // Empty the batch rendering data structures, but keep their key mappings around for the next frames + for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); it++) + { + it->second.m_Quads.clear(); + } } void OverlayRenderer::PrepareForRendering() @@ -157,6 +295,75 @@ void OverlayRenderer::PrepareForRendering() // any of the parameters after first submitting the line. } } + + // Group quad overlays by their texture/mask combination for efficient rendering + for (size_t i = 0; i < m->quads.size(); ++i) + { + SOverlayQuad* const quad = m->quads[i]; + + QuadBatchKey textures(quad->m_Texture, quad->m_TextureMask); + QuadBatchData& batchRenderData = m->quadBatchMap[textures]; // will create entry if it doesn't already exist + + // add overlay to list of quads + batchRenderData.m_Quads.push_back(quad); + } + + const CVector3D vOffset(0, OverlayRendererInternals::OVERLAY_VOFFSET, 0); + + // Write quad overlay vertices/indices to VA backing store + VertexArrayIterator vertexPos = m->quadAttributePos.GetIterator(); + VertexArrayIterator vertexColor = m->quadAttributeColor.GetIterator(); + VertexArrayIterator vertexUV = m->quadAttributeUV.GetIterator(); + + size_t indicesIdx = 0; + + for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); ++it) + { + QuadBatchData& batchRenderData = (it->second); + if (batchRenderData.m_Quads.empty()) + continue; + + // Remember our current index into the (entire) indices array as our base offset for this batch + batchRenderData.m_IndicesBase = indicesIdx; + + // points to the index where each iteration's vertices will be appended + for (size_t i = 0; i < batchRenderData.m_Quads.size(); i++) + { + const SOverlayQuad* quad = batchRenderData.m_Quads[i]; + + // TODO: this is kind of ugly, the iterator should use a type that can have quad->m_Color assigned + // to it directly + const CVector4D quadColor(quad->m_Color.r, quad->m_Color.g, quad->m_Color.b, quad->m_Color.a); + + *vertexPos++ = quad->m_Corners[0] + vOffset; + *vertexPos++ = quad->m_Corners[1] + vOffset; + *vertexPos++ = quad->m_Corners[2] + vOffset; + *vertexPos++ = quad->m_Corners[3] + vOffset; + + (*vertexUV)[0] = 0; + (*vertexUV)[1] = 0; + ++vertexUV; + (*vertexUV)[0] = 0; + (*vertexUV)[1] = 1; + ++vertexUV; + (*vertexUV)[0] = 1; + (*vertexUV)[1] = 1; + ++vertexUV; + (*vertexUV)[0] = 1; + (*vertexUV)[1] = 0; + ++vertexUV; + + *vertexColor++ = quadColor; + *vertexColor++ = quadColor; + *vertexColor++ = quadColor; + *vertexColor++ = quadColor; + + indicesIdx += 6; + } + } + + m->quadVertices.Upload(); + // don't free the backing store! we'll overwrite it on the next frame to save a reallocation. } void OverlayRenderer::RenderOverlaysBeforeWater() @@ -196,36 +403,41 @@ void OverlayRenderer::RenderOverlaysAfterWater() { PROFILE3_GPU("overlays (after)"); + RenderTexturedOverlayLines(); + RenderQuadOverlays(); +} + +void OverlayRenderer::RenderTexturedOverlayLines() +{ #if CONFIG2_GLES -#warning TODO: implement OverlayRenderer::RenderOverlaysAfterWater for GLES +#warning TODO: implement OverlayRenderer::RenderTexturedOverlayLines for GLES return; #endif + if (m->texlines.empty()) + return; ogl_WarnIfError(); - if (!m->texlines.empty()) + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(0); + + const char* shaderName; + if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) + shaderName = "arb/overlayline"; + else + shaderName = "fixed:overlayline"; + + CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); + + CShaderManager& shaderManager = g_Renderer.GetShaderManager(); + CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, m->defsOverlayLineNormal)); + CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, m->defsOverlayLineAlwaysVisible)); + + // ---------------------------------------------------------------------------------------- + + if (shaderTexLineNormal) { - glEnable(GL_TEXTURE_2D); - glEnable(GL_BLEND); - glDepthMask(0); - - const char* shaderName; - if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) - shaderName = "arb/overlayline"; - else - shaderName = "fixed:overlayline"; - - CShaderDefines defAlwaysVisible; - defAlwaysVisible.Add("IGNORE_LOS", "1"); - - CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); - - CShaderManager& shaderManager = g_Renderer.GetShaderManager(); - CShaderProgramPtr shaderTexLineNormal(shaderManager.LoadProgram(shaderName, CShaderDefines())); - CShaderProgramPtr shaderTexLineAlwaysVisible(shaderManager.LoadProgram(shaderName, defAlwaysVisible)); - - // ---------------------------------------------------------------------------------------- - shaderTexLineNormal->Bind(); shaderTexLineNormal->BindTexture("losTex", los.GetTexture()); shaderTexLineNormal->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); @@ -234,12 +446,14 @@ void OverlayRenderer::RenderOverlaysAfterWater() RenderTexturedOverlayLines(shaderTexLineNormal, false); shaderTexLineNormal->Unbind(); + } - // ---------------------------------------------------------------------------------------- + // ---------------------------------------------------------------------------------------- + if (shaderTexLineAlwaysVisible) + { shaderTexLineAlwaysVisible->Bind(); - // TODO: losTex and losTransform are unused in the always visible shader, but I'm not sure if it's worthwhile messing - // with it just to remove these calls + // TODO: losTex and losTransform are unused in the always visible shader; see if these can be safely omitted shaderTexLineAlwaysVisible->BindTexture("losTex", los.GetTexture()); shaderTexLineAlwaysVisible->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); @@ -247,16 +461,18 @@ void OverlayRenderer::RenderOverlaysAfterWater() RenderTexturedOverlayLines(shaderTexLineAlwaysVisible, true); shaderTexLineAlwaysVisible->Unbind(); - - // TODO: the shader should probably be responsible for unbinding its textures - g_Renderer.BindTexture(1, 0); - g_Renderer.BindTexture(0, 0); - - CVertexBuffer::Unbind(); - - glDepthMask(1); - glDisable(GL_BLEND); } + + // ---------------------------------------------------------------------------------------- + + // TODO: the shaders should probably be responsible for unbinding their textures + g_Renderer.BindTexture(1, 0); + g_Renderer.BindTexture(0, 0); + + CVertexBuffer::Unbind(); + + glDepthMask(1); + glDisable(GL_BLEND); } void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shaderTexLine, bool alwaysVisible) @@ -298,10 +514,98 @@ void OverlayRenderer::RenderTexturedOverlayLines(CShaderProgramPtr shaderTexLine shaderTexLine->AssertPointersBound(); glDrawElements(GL_TRIANGLES, rdata->m_VBIndices->m_Count, GL_UNSIGNED_SHORT, indexBase + sizeof(u16)*rdata->m_VBIndices->m_Index); + g_Renderer.GetStats().m_DrawCalls++; g_Renderer.GetStats().m_OverlayTris += rdata->m_VBIndices->m_Count/3; } } +void OverlayRenderer::RenderQuadOverlays() +{ + if (m->quadBatchMap.empty()) + return; + + ogl_WarnIfError(); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + glDepthMask(0); + + const char* shaderName; + if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER) + shaderName = "arb/overlayline"; + else + shaderName = "fixed:overlayline"; + + CLOSTexture& los = g_Renderer.GetScene().GetLOSTexture(); + + CShaderManager& shaderManager = g_Renderer.GetShaderManager(); + CShaderProgramPtr shader(shaderManager.LoadProgram(shaderName, m->defsQuadOverlay)); + + // ---------------------------------------------------------------------------------------- + + if (shader) + { + shader->Bind(); + shader->BindTexture("losTex", los.GetTexture()); + shader->Uniform("losTransform", los.GetTextureMatrix()[0], los.GetTextureMatrix()[12], 0.f, 0.f); + + // Base offsets (in bytes) of the two backing stores relative to their owner VBO + u8* indexBase = m->quadIndices.Bind(); + u8* vertexBase = m->quadVertices.Bind(); + GLsizei indexStride = m->quadIndices.GetStride(); + GLsizei vertexStride = m->quadVertices.GetStride(); + + for (OverlayRendererInternals::QuadBatchMap::iterator it = m->quadBatchMap.begin(); it != m->quadBatchMap.end(); it++) + { + QuadBatchData& batchRenderData = it->second; + const size_t batchNumQuads = batchRenderData.m_Quads.size(); + + // Careful; some drivers don't like drawing calls with 0 stuff to draw. + // Also needed to ensure that batchRenderData.m_IndicesBase is valid (see PrepareForRendering). + if (batchNumQuads == 0) + continue; + + const QuadBatchKey& maskPair = it->first; + + shader->BindTexture("baseTex", maskPair.m_Texture->GetHandle()); + shader->BindTexture("maskTex", maskPair.m_TextureMask->GetHandle()); + + int streamflags = shader->GetStreamFlags(); ogl_WarnIfError(); + + if (streamflags & STREAM_POS) + shader->VertexPointer(m->quadAttributePos.elems, m->quadAttributePos.type, vertexStride, vertexBase + m->quadAttributePos.offset); + + if (streamflags & STREAM_UV0) + shader->TexCoordPointer(GL_TEXTURE0, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset); + + if (streamflags & STREAM_UV1) + shader->TexCoordPointer(GL_TEXTURE1, m->quadAttributeUV.elems, m->quadAttributeUV.type, vertexStride, vertexBase + m->quadAttributeUV.offset); + + if (streamflags & STREAM_COLOR) + shader->ColorPointer(m->quadAttributeColor.elems, m->quadAttributeColor.type, vertexStride, vertexBase + m->quadAttributeColor.offset); + + shader->AssertPointersBound(); + glDrawElements(GL_TRIANGLES, (GLsizei)(batchNumQuads * 6), GL_UNSIGNED_SHORT, indexBase + indexStride * batchRenderData.m_IndicesBase); + + g_Renderer.GetStats().m_DrawCalls++; + g_Renderer.GetStats().m_OverlayTris += batchNumQuads*2; + } + + shader->Unbind(); + } + + // ---------------------------------------------------------------------------------------- + + // TODO: the shader should probably be responsible for unbinding its textures + g_Renderer.BindTexture(1, 0); + g_Renderer.BindTexture(0, 0); + + CVertexBuffer::Unbind(); + + glDepthMask(1); + glDisable(GL_BLEND); +} + void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) { PROFILE3_GPU("overlays (fg)"); @@ -339,6 +643,9 @@ void OverlayRenderer::RenderForegroundOverlays(const CCamera& viewCamera) glVertexPointer(3, GL_FLOAT, sizeof(float)*3, &pos[0].X); glDrawArrays(GL_QUADS, 0, (GLsizei)4); + + g_Renderer.GetStats().m_DrawCalls++; + g_Renderer.GetStats().m_OverlayTris += 2; } glDisableClientState(GL_VERTEX_ARRAY); @@ -363,8 +670,14 @@ void CTexturedLineRData::Update() m_VBIndices = NULL; } - CTerrain* terrain = m_Line->m_Terrain; - CmpPtr cmpWaterManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + if (!m_Line->m_SimContext) + { + debug_warn(L"[OverlayRenderer] No SimContext set for textured overlay line, cannot render (no terrain data)"); + return; + } + + const CTerrain& terrain = m_Line->m_SimContext->GetTerrain(); + CmpPtr cmpWaterManager(*m_Line->m_SimContext, SYSTEM_ENTITY); float v = 0.f; std::vector vertices; @@ -400,18 +713,18 @@ void CTexturedLineRData::Update() // TODO: if we ever support more than one water level per map, recompute this per point float w = cmpWaterManager->GetExactWaterLevel(p0.X, p0.Z); - p0.Y = terrain->GetExactGroundLevel(p0.X, p0.Z); + p0.Y = terrain.GetExactGroundLevel(p0.X, p0.Z); if (p0.Y < w) p0.Y = w; - p1.Y = terrain->GetExactGroundLevel(p1.X, p1.Z); + p1.Y = terrain.GetExactGroundLevel(p1.X, p1.Z); if (p1.Y < w) { p1.Y = w; p1floating = true; } - p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z); + p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z); if (p2.Y < w) { p2.Y = w; @@ -428,7 +741,7 @@ void CTexturedLineRData::Update() if (p1floating) norm = CVector3D(0, 1, 0); else - norm = m_Line->m_Terrain->CalcExactNormal(p1.X, p1.Z); + norm = terrain.CalcExactNormal(p1.X, p1.Z); CVector3D b = ((p1 - p0).Normalized() + (p2 - p1).Normalized()).Cross(norm); @@ -447,8 +760,8 @@ void CTexturedLineRData::Update() // What the code below does is push the indices for a quad composed of two triangles in each iteration. The two triangles // of each quad are indexed using the winding orders (BR, BL, TR) and (TR, BL, TR) (where BR is bottom-right of this // iteration's quad, TR top-right etc). - SVertex vertex1(p1 + b + norm*m_Raise, 0.f, v); - SVertex vertex2(p1 - b + norm*m_Raise, 1.f, v); + SVertex vertex1(p1 + b + norm*OverlayRendererInternals::OVERLAY_VOFFSET, 0.f, v); + SVertex vertex2(p1 - b + norm*OverlayRendererInternals::OVERLAY_VOFFSET, 1.f, v); vertices.push_back(vertex1); vertices.push_back(vertex2); @@ -497,7 +810,7 @@ void CTexturedLineRData::Update() else p2 = CVector3D(m_Line->m_Coords[((i+2) % n)*2], 0, m_Line->m_Coords[((i+2) % n)*2+1]); - p2.Y = terrain->GetExactGroundLevel(p2.X, p2.Z); + p2.Y = terrain.GetExactGroundLevel(p2.X, p2.Z); if (p2.Y < w) { p2.Y = w; @@ -540,6 +853,7 @@ void CTexturedLineRData::Update() for (unsigned i = 0; i < capIndices.size(); i++) capIndices[i] += vertices.size(); + vertices.insert(vertices.end(), capVertices.begin(), capVertices.end()); indices.insert(indices.end(), capIndices.begin(), capIndices.end()); @@ -560,6 +874,7 @@ void CTexturedLineRData::Update() for (unsigned i = 0; i < capIndices.size(); i++) capIndices[i] += vertices.size(); + vertices.insert(vertices.end(), capVertices.begin(), capVertices.end()); indices.insert(indices.end(), capIndices.begin(), capIndices.end()); } @@ -595,10 +910,10 @@ void CTexturedLineRData::CreateLineCap(const CVector3D& corner1, const CVector3D // That is to say, when viewed from the top, we will have something like // . // this: and not like this: /| - // ____. / | + // ----+ / | // | / . // | / - // ____. / + // ----+ / // int roundCapPoints = 8; // amount of points to sample along the semicircle for rounded caps (including corner points) diff --git a/source/renderer/OverlayRenderer.h b/source/renderer/OverlayRenderer.h index 6c1dfdcf42..c28d6d5921 100644 --- a/source/renderer/OverlayRenderer.h +++ b/source/renderer/OverlayRenderer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -23,6 +23,7 @@ struct SOverlayLine; struct SOverlayTexturedLine; struct SOverlaySprite; +struct SOverlayQuad; class CCamera; struct OverlayRendererInternals; @@ -39,19 +40,32 @@ public: /** * Add a line overlay for rendering in this frame. + * @param overlay Must be non-null. The pointed-to object must remain valid at least + * until the end of the frame. */ void Submit(SOverlayLine* overlay); /** * Add a textured line overlay for rendering in this frame. + * @param overlay Must be non-null. The pointed-to object must remain valid at least + * until the end of the frame. */ void Submit(SOverlayTexturedLine* overlay); /** * Add a sprite overlay for rendering in this frame. + * @param overlay Must be non-null. The pointed-to object must remain valid at least + * until the end of the frame. */ void Submit(SOverlaySprite* overlay); + /** + * Add a textured quad overlay for rendering in this frame. + * @param overlay Must be non-null. The pointed-to object must remain valid at least + * until the end of the frame. + */ + void Submit(SOverlayQuad* overlay); + /** * Prepare internal data structures for rendering. * Must be called after all Submit calls for a frame, and before @@ -88,12 +102,25 @@ public: private: /** - * Helper method; renders those overlay lines currently registered in the internals (i.e. in m->texlines) for which the - * always visible flag equals @alwaysVisible. Used for batch rendering the overlay lines by their alwaysVisible status, - * because this requires a separate shader to be used. + * Helper method; renders all overlay lines currently registered in the internals. Batch- + * renders textured overlay lines batched according to their visibility status by delegating + * to RenderTexturedOverlayLines(CShaderProgramPtr, bool). + */ + void RenderTexturedOverlayLines(); + + /** + * Helper method; renders those overlay lines currently registered in the internals (i.e. + * in m->texlines) for which the 'always visible' flag equals @p alwaysVisible. Used for + * batch rendering the overlay lines according to their alwaysVisible status, as this + * requires a separate shader to be used. */ void RenderTexturedOverlayLines(CShaderProgramPtr shader, bool alwaysVisible); + /** + * Helper method; batch-renders all registered quad overlays, batched by their texture for effiency. + */ + void RenderQuadOverlays(); + private: OverlayRendererInternals* m; }; diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index d743ef20d3..e66c1fa8a5 100644 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -1569,6 +1569,11 @@ void CRenderer::Submit(SOverlaySprite* overlay) m->overlayRenderer.Submit(overlay); } +void CRenderer::Submit(SOverlayQuad* overlay) +{ + m->overlayRenderer.Submit(overlay); +} + void CRenderer::Submit(CModelDecal* decal) { m->terrainRenderer.Submit(decal); diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index a6d1b2be2c..df8257959e 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -316,6 +316,7 @@ protected: void Submit(SOverlayLine* overlay); void Submit(SOverlayTexturedLine* overlay); void Submit(SOverlaySprite* overlay); + void Submit(SOverlayQuad* overlay); void Submit(CModelDecal* decal); void Submit(CParticleEmitter* emitter); void SubmitNonRecursive(CModel* model); diff --git a/source/renderer/Scene.h b/source/renderer/Scene.h index 2018135d83..99c99f7a07 100644 --- a/source/renderer/Scene.h +++ b/source/renderer/Scene.h @@ -39,6 +39,7 @@ class CTerritoryTexture; struct SOverlayLine; struct SOverlayTexturedLine; struct SOverlaySprite; +struct SOverlayQuad; class SceneCollector; @@ -103,6 +104,11 @@ public: */ virtual void Submit(SOverlaySprite* overlay) = 0; + /** + * Submit a textured quad overlay. + */ + virtual void Submit(SOverlayQuad* overlay) = 0; + /** * Submit a terrain decal. */ diff --git a/source/renderer/VertexArray.cpp b/source/renderer/VertexArray.cpp index 6bf1feda5d..42d769b40e 100644 --- a/source/renderer/VertexArray.cpp +++ b/source/renderer/VertexArray.cpp @@ -22,6 +22,7 @@ #include "lib/sysdep/rtl.h" #include "maths/Vector3D.h" #include "maths/Vector4D.h" +#include "graphics/Color.h" #include "graphics/SColor.h" #include "renderer/VertexArray.h" #include "renderer/VertexBuffer.h" @@ -73,7 +74,10 @@ void VertexArray::SetNumVertices(size_t num) // Add vertex attributes like Position, Normal, UV void VertexArray::AddAttribute(Attribute* attr) { - ENSURE((attr->type == GL_FLOAT || attr->type == GL_UNSIGNED_SHORT || attr->type == GL_UNSIGNED_BYTE) && "Unsupported attribute type"); + ENSURE( + (attr->type == GL_FLOAT || attr->type == GL_SHORT || attr->type == GL_UNSIGNED_SHORT || attr->type == GL_UNSIGNED_BYTE) + && "Unsupported attribute type" + ); ENSURE(attr->elems >= 1 && attr->elems <= 4); attr->vertexArray = this; @@ -146,6 +150,16 @@ VertexArrayIterator VertexArray::Attribute::GetIterator() const return vertexArray->MakeIterator(this); } +template<> +VertexArrayIterator VertexArray::Attribute::GetIterator() const +{ + ENSURE(vertexArray); + ENSURE(type == GL_UNSIGNED_SHORT); + ENSURE(elems >= 2); + + return vertexArray->MakeIterator(this); +} + template<> VertexArrayIterator VertexArray::Attribute::GetIterator() const { @@ -166,7 +180,25 @@ VertexArrayIterator VertexArray::Attribute::GetIterator() const return vertexArray->MakeIterator(this); } +template<> +VertexArrayIterator VertexArray::Attribute::GetIterator() const +{ + ENSURE(vertexArray); + ENSURE(type == GL_SHORT); + ENSURE(elems >= 1); + return vertexArray->MakeIterator(this); +} + +template<> +VertexArrayIterator VertexArray::Attribute::GetIterator() const +{ + ENSURE(vertexArray); + ENSURE(type == GL_SHORT); + ENSURE(elems >= 2); + + return vertexArray->MakeIterator(this); +} static size_t RoundStride(size_t stride) { @@ -206,6 +238,9 @@ void VertexArray::Layout() case GL_UNSIGNED_BYTE: attrSize = sizeof(GLubyte); break; + case GL_SHORT: + attrSize = sizeof(GLshort); + break; case GL_UNSIGNED_SHORT: attrSize = sizeof(GLushort); break; diff --git a/source/renderer/VertexArray.h b/source/renderer/VertexArray.h index 7cf6cfaadd..5a945f17a9 100644 --- a/source/renderer/VertexArray.h +++ b/source/renderer/VertexArray.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -134,7 +134,7 @@ class VertexArray public: struct Attribute { - // Data type. Currently supported: GL_FLOAT, GL_UNSIGNED_BYTE + // Data type. Currently supported: GL_FLOAT, GL_SHORT, GL_UNSIGNED_SHORT, GL_UNSIGNED_BYTE. GLenum type; // How many elements per vertex (e.g. 3 for RGB, 2 for UV) GLuint elems; @@ -146,8 +146,10 @@ public: Attribute() : type(0), elems(0), offset(0), vertexArray(0) { } - // Get an iterator for the given attribute that initially points at the first vertex. - // Supported types T: CVector3D, CVector4D, float[2], SColor3ub, SColor4ub + // Get an iterator over the backing store for the given attribute that + // initially points at the first vertex. + // Supported types T: CVector3D, CVector4D, float[2], SColor3ub, SColor4ub, + // u16, u16[2], u8, u8[4], short, short[2]. // This function verifies at runtime that the requested type T matches // the attribute definition passed to AddAttribute(). template @@ -171,7 +173,8 @@ public: // attributes. // All vertex data is lost when a vertex array is re-layouted. void Layout(); - // (Re-)Upload the attributes of the vertex array. + // (Re-)Upload the attributes of the vertex array from the backing store to + // the underlying VBO object. void Upload(); // Bind this array, returns the base address for calls to glVertexPointer etc. u8* Bind(); @@ -204,13 +207,14 @@ private: * A VertexArray that is specialised to handle 16-bit array indices. * Call Bind() and pass the return value to the indices parameter of * glDrawElements/glDrawRangeElements/glMultiDrawElements. - * Use CVertexBuffer::Unbind() to unbind the array. + * Use CVertexBuffer::Unbind() to unbind the array when done. */ class VertexIndexArray : public VertexArray { public: VertexIndexArray(GLenum usage); + /// Gets the iterator over the (only) attribute in this array, i.e. a u16. VertexArrayIterator GetIterator() const; private: diff --git a/source/renderer/VertexBuffer.cpp b/source/renderer/VertexBuffer.cpp index bcbfebe42f..514d6eb2a1 100644 --- a/source/renderer/VertexBuffer.cpp +++ b/source/renderer/VertexBuffer.cpp @@ -33,10 +33,11 @@ CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target) { size_t size = MAX_VB_SIZE_BYTES; - if (target == GL_ARRAY_BUFFER) + if (target == GL_ARRAY_BUFFER) // vertex data buffer { // We want to store 16-bit indices to any vertex in a buffer, so the - // buffer must never be bigger than vertexSize*64K bytes + // buffer must never be bigger than vertexSize*64K bytes since we can + // address at most 64K of them with 16-bit indices size = std::min(size, vertexSize*65536); } @@ -54,13 +55,13 @@ CVertexBuffer::CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target) } // store max/free vertex counts - m_MaxVertices=m_FreeVertices=size/vertexSize; + m_MaxVertices = m_FreeVertices = size/vertexSize; // create sole free chunk - VBChunk* chunk=new VBChunk; - chunk->m_Owner=this; - chunk->m_Count=m_FreeVertices; - chunk->m_Index=0; + VBChunk* chunk = new VBChunk; + chunk->m_Owner = this; + chunk->m_Count = m_FreeVertices; + chunk->m_Index = 0; m_FreeList.push_front(chunk); } @@ -100,11 +101,11 @@ CVertexBuffer::VBChunk* CVertexBuffer::Allocate(size_t vertexSize, size_t numVer return 0; // trawl free list looking for first free chunk with enough space - VBChunk* chunk=0; + VBChunk* chunk = 0; typedef std::list::iterator Iter; - for (Iter iter=m_FreeList.begin();iter!=m_FreeList.end();++iter) { - if (numVertices<=(*iter)->m_Count) { - chunk=*iter; + for (Iter iter = m_FreeList.begin(); iter != m_FreeList.end(); ++iter) { + if (numVertices <= (*iter)->m_Count) { + chunk = *iter; // remove this chunk from the free list m_FreeList.erase(iter); m_FreeVertices -= chunk->m_Count; @@ -173,7 +174,7 @@ void CVertexBuffer::Release(VBChunk* chunk) /////////////////////////////////////////////////////////////////////////////// // UpdateChunkVertices: update vertex data for given chunk -void CVertexBuffer::UpdateChunkVertices(VBChunk* chunk,void* data) +void CVertexBuffer::UpdateChunkVertices(VBChunk* chunk, void* data) { if (g_Renderer.m_Caps.m_VBO) { diff --git a/source/renderer/VertexBuffer.h b/source/renderer/VertexBuffer.h index 816d83f25f..c4268681d9 100644 --- a/source/renderer/VertexBuffer.h +++ b/source/renderer/VertexBuffer.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -33,20 +33,22 @@ // TODO: measure what influence this has on performance #define MAX_VB_SIZE_BYTES (2*1024*1024) -/////////////////////////////////////////////////////////////////////////////// -// CVertexBuffer: encapsulation of ARB_vertex_buffer_object, also supplying -// some additional functionality for sharing buffers between multiple objects +/** + * CVertexBuffer: encapsulation of ARB_vertex_buffer_object, also supplying + * some additional functionality for sharing buffers between multiple objects + */ class CVertexBuffer { public: - // VBChunk: describes a portion of this vertex buffer + + /// VBChunk: describes a portion of this vertex buffer struct VBChunk { - // owning buffer + /// Owning (parent) vertex buffer CVertexBuffer* m_Owner; - // start index of this chunk in owner + /// Start index of this chunk in owner size_t m_Index; - // number of vertices used by chunk + /// Number of vertices used by chunk size_t m_Count; private: @@ -62,24 +64,24 @@ public: CVertexBuffer(size_t vertexSize, GLenum usage, GLenum target); ~CVertexBuffer(); - // bind to this buffer; return pointer to address required as parameter - // to glVertexPointer ( + etc) calls + /// Bind to this buffer; return pointer to address required as parameter + /// to glVertexPointer ( + etc) calls u8* Bind(); - // get the address that Bind() will return, without actually binding + /// Get the address that Bind() will return, without actually binding u8* GetBindAddress(); - // unbind any currently-bound buffer, so glVertexPointer etc calls will not attempt to use it + /// Unbind any currently-bound buffer, so glVertexPointer etc calls will not attempt to use it static void Unbind(); - // update vertex data for given chunk + /// Update vertex data for given chunk. Transfers the provided data to the actual OpenGL vertex buffer. void UpdateChunkVertices(VBChunk* chunk, void* data); size_t GetVertexSize() const { return m_VertexSize; } - size_t GetBytesReserved() const; size_t GetBytesAllocated() const; + /// Returns true if this vertex buffer is compatible with the specified vertex type and intended usage. bool CompatibleVertexType(size_t vertexSize, GLenum usage, GLenum target); void DumpStatus(); @@ -87,29 +89,29 @@ public: protected: friend class CVertexBufferManager; // allow allocate only via CVertexBufferManager - // try to allocate a buffer of given number of vertices (each of given size), - // and with the given type - return null if no free chunks available + /// Try to allocate a buffer of given number of vertices (each of given size), + /// and with the given type - return null if no free chunks available VBChunk* Allocate(size_t vertexSize, size_t numVertices, GLenum usage, GLenum target); - // return given chunk to this buffer + /// Return given chunk to this buffer void Release(VBChunk* chunk); private: - // vertex size of this vertex buffer + /// Vertex size of this vertex buffer size_t m_VertexSize; - // number of vertices of above size in this buffer + /// Number of vertices of above size in this buffer size_t m_MaxVertices; - // list of free chunks in this buffer + /// List of free chunks in this buffer std::list m_FreeList; - // available free vertices - total of all free vertices in the free list + /// Available free vertices - total of all free vertices in the free list size_t m_FreeVertices; - // handle to the actual GL vertex buffer object + /// Handle to the actual GL vertex buffer object GLuint m_Handle; - // raw system memory for systems not supporting VBOs + /// Raw system memory for systems not supporting VBOs u8* m_SysMem; - // usage type of the buffer (GL_STATIC_DRAW etc) + /// Usage type of the buffer (GL_STATIC_DRAW etc) GLenum m_Usage; - // buffer target (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER) + /// Buffer target (GL_ARRAY_BUFFER, GL_ELEMENT_ARRAY_BUFFER) GLenum m_Target; }; diff --git a/source/renderer/VertexBufferManager.h b/source/renderer/VertexBufferManager.h index b97c68d152..4ccb02f59e 100644 --- a/source/renderer/VertexBufferManager.h +++ b/source/renderer/VertexBufferManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -30,8 +30,6 @@ class CVertexBufferManager { public: - // Explicit shutdown of the vertex buffer subsystem - void Shutdown(); /** * Try to allocate a vertex buffer of the given size and type. @@ -44,17 +42,23 @@ public: */ CVertexBuffer::VBChunk* Allocate(size_t vertexSize, size_t numVertices, GLenum usage, GLenum target); - // return given chunk to its owner + /// Returns the given @p chunk to its owning buffer void Release(CVertexBuffer::VBChunk* chunk); - // return list of all buffers + /// Returns a list of all buffers const std::list& GetBufferList() const { return m_Buffers; } size_t GetBytesReserved(); size_t GetBytesAllocated(); + /// Returns the maximum possible size of a single vertex buffer + size_t GetMaxBufferSize() const { return MAX_VB_SIZE_BYTES; } + + /// Explicit shutdown of the vertex buffer subsystem; releases all currently-allocated buffers. + void Shutdown(); + private: - // list of all known vertex buffers + /// List of all known vertex buffers std::list m_Buffers; }; diff --git a/source/simulation2/components/CCmpRallyPointRenderer.cpp b/source/simulation2/components/CCmpRallyPointRenderer.cpp index dc16628c64..0a52b6f23d 100644 --- a/source/simulation2/components/CCmpRallyPointRenderer.cpp +++ b/source/simulation2/components/CCmpRallyPointRenderer.cpp @@ -610,7 +610,7 @@ void CCmpRallyPointRenderer::ConstructOverlayLines() // 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_Terrain = cmpTerrain->GetCTerrain(); + overlayLine.m_SimContext = &GetSimContext(); overlayLine.m_TextureBase = m_Texture; overlayLine.m_TextureMask = m_TextureMask; overlayLine.m_Color = m_LineColor; @@ -634,14 +634,14 @@ void CCmpRallyPointRenderer::ConstructOverlayLines() } else { - // construct dashed line from startPointIdx to endPointIdx, add textured overlay lines for it to the render list + // 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[segment.m_StartIndex]); straightLine.push_back(m_Path[segment.m_EndIndex]); - // We always want to have the dashed line end at either point with a full dash (i.e. not a cleared space), so that the dashed - // area is visually obvious. That implies that we want at least So, let's do some calculations to see what size we should make - // the dashes and clears. + // 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; @@ -652,8 +652,8 @@ void CCmpRallyPointRenderer::ConstructOverlayLines() float distance = (m_Path[segment.m_StartIndex] - m_Path[segment.m_EndIndex]).Length(); // straight-line distance between the points - // see how many pairs (dash + clear) can fit into the distance unmodified. Then check the remaining distance; if it's not exactly - // a dash size's worth (and it likely won't be), then adjust the dash/clear sizes slightly so that it is. + // 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)); @@ -682,7 +682,7 @@ void CCmpRallyPointRenderer::ConstructOverlayLines() SOverlayTexturedLine dashOverlay; dashOverlay.m_Thickness = m_LineThickness; - dashOverlay.m_Terrain = cmpTerrain->GetCTerrain(); + dashOverlay.m_SimContext = &GetSimContext(); dashOverlay.m_TextureBase = m_Texture; dashOverlay.m_TextureMask = m_TextureMask; dashOverlay.m_Color = m_LineDashColor; @@ -767,7 +767,7 @@ void CCmpRallyPointRenderer::FixFootprintWaypoints(std::vector& coord { case ICmpFootprint::SQUARE: { - // in this case, footprintSize0 and 1 respectively indicate the (unrotated) size along the X and Z axes + // 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 @@ -837,7 +837,6 @@ void CCmpRallyPointRenderer::FixInvisibleWaypoints(std::vector& coord player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer(); CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer)); - //for (std::vector::iterator it = waypoints.begin(); it != waypoints.end();) for(std::vector::iterator it = coords.begin(); it != coords.end();) { int i = (fixed::FromFloat(it->X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest(); diff --git a/source/simulation2/components/CCmpSelectable.cpp b/source/simulation2/components/CCmpSelectable.cpp index d23916f310..f4e8fa05ae 100644 --- a/source/simulation2/components/CCmpSelectable.cpp +++ b/source/simulation2/components/CCmpSelectable.cpp @@ -20,17 +20,26 @@ #include "simulation2/system/Component.h" #include "ICmpSelectable.h" -#include "ICmpPosition.h" -#include "ICmpFootprint.h" -#include "ICmpVisual.h" -#include "simulation2/MessageTypes.h" -#include "simulation2/helpers/Render.h" - #include "graphics/Overlay.h" +#include "graphics/Terrain.h" +#include "graphics/TextureManager.h" +#include "maths/Ease.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" +#include "maths/Vector2D.h" #include "renderer/Scene.h" +#include "renderer/Renderer.h" +#include "simulation2/MessageTypes.h" +#include "simulation2/components/ICmpPosition.h" +#include "simulation2/components/ICmpFootprint.h" +#include "simulation2/components/ICmpVisual.h" +#include "simulation2/components/ICmpTerrain.h" +#include "simulation2/components/ICmpOwnership.h" +#include "simulation2/components/ICmpPlayer.h" +#include "simulation2/components/ICmpPlayerManager.h" +#include "simulation2/components/ICmpWaterManager.h" +#include "simulation2/helpers/Render.h" class CCmpSelectable : public ICmpSelectable { @@ -39,27 +48,29 @@ public: { componentManager.SubscribeToMessageType(MT_Interpolate); componentManager.SubscribeToMessageType(MT_RenderSubmit); + componentManager.SubscribeToMessageType(MT_OwnershipChanged); + componentManager.SubscribeToMessageType(MT_PositionChanged); // TODO: it'd be nice if we didn't get these messages except in the rare // cases where we're actually drawing a selection highlight } DEFAULT_COMPONENT_ALLOCATOR(Selectable) - SOverlayLine m_Overlay; - SOverlayLine* m_DebugBoundingBoxOverlay; - SOverlayLine* m_DebugSelectionBoxOverlay; - bool m_EditorOnly; - CCmpSelectable() - : m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL) + : m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL), + m_BuildingOverlay(NULL), m_UnitOverlay(NULL), + m_FadeBaselineAlpha(0.f), m_FadeDeltaAlpha(0.f), m_FadeProgress(0.f) { - m_Overlay.m_Thickness = 2; - m_Overlay.m_Color = CColor(0, 0, 0, 0); + m_Color = CColor(0, 0, 0, m_FadeBaselineAlpha); + m_LastRealTime = 0; } - ~CCmpSelectable(){ + ~CCmpSelectable() + { delete m_DebugBoundingBoxOverlay; delete m_DebugSelectionBoxOverlay; + delete m_BuildingOverlay; + delete m_UnitOverlay; } static std::string GetSchema() @@ -71,17 +82,51 @@ public: "" "" "" - ""; + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + ""; } virtual void Init(const CParamNode& paramNode) { m_EditorOnly = paramNode.GetChild("EditorOnly").IsOk(); + + 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()); + } + 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_LineThickness = outlineNode.GetChild("LineThickness").ToFloat(); + } } - virtual void Deinit() - { - } + virtual void Deinit() { } virtual void Serialize(ISerializer& UNUSED(serialize)) { @@ -95,29 +140,18 @@ public: Init(paramNode); } - virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)) + virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)); + + virtual void SetSelectionHighlight(CColor color) { - switch (msg.GetType()) - { - case MT_Interpolate: - { - if (m_Overlay.m_Color.a > 0) - { - float offset = static_cast (msg).offset; - ConstructShape(offset); - } - break; - } - case MT_RenderSubmit: - { - if (m_Overlay.m_Color.a > 0) - { - const CMessageRenderSubmit& msgData = static_cast (msg); - RenderSubmit(msgData.collector); - } - break; - } - } + m_Color.r = color.r; + m_Color.g = color.g; + m_Color.b = color.b; + + // set up fading from the current value (as the baseline) to the target value + m_FadeBaselineAlpha = m_Color.a; + m_FadeDeltaAlpha = color.a - m_FadeBaselineAlpha; + m_FadeProgress = 0.f; } virtual bool IsEditorOnly() @@ -125,58 +159,349 @@ public: return m_EditorOnly; } - virtual void SetSelectionHighlight(CColor color) + 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). + */ + void UpdateStaticOverlay(); + + /** + * Called from the interpolation handler; responsible for ensuring the dynamic overlay (provided we're + * using one) is up-to-date and ready to be submitted to the next rendering run. + */ + void UpdateDynamicOverlay(float frameOffset); + + /// Explicitly invalidates the static overlay. + void InvalidateStaticOverlay(); + +private: + SOverlayDescriptor m_OverlayDescriptor; + SOverlayTexturedLine* m_BuildingOverlay; + SOverlayQuad* m_UnitOverlay; + + SOverlayLine* m_DebugBoundingBoxOverlay; + SOverlayLine* m_DebugSelectionBoxOverlay; + + bool m_EditorOnly; + + /// Current selection overlay color. Alpha component is subject to fading. + CColor m_Color; + /// Baseline alpha value to start fading from. Constant during a single fade. + float m_FadeBaselineAlpha; + /// Delta between target and baseline alpha. Constant during a single fade. Can be positive or negative. + float m_FadeDeltaAlpha; + /// Linear time progress of the fade, between 0 and m_FadeDuration. + float m_FadeProgress; + /// 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; + double m_LastRealTime; // temporary member, only here to support the TODO case in HandleMessage. +}; + +const double CCmpSelectable::FADE_DURATION = 0.3; + +void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global)) +{ + switch (msg.GetType()) { - m_Overlay.m_Color = color; - - if (color.a == 0 && !m_Overlay.m_Coords.empty()) + case MT_Interpolate: { - // Delete the overlay data to save memory (we don't want hundreds of bytes - // times thousands of units when the selections are not being rendered any more) - std::vector empty; - m_Overlay.m_Coords.swap(empty); - ENSURE(m_Overlay.m_Coords.capacity() == 0); - } + // TODO: temporary solution using real elapsed time instead of simulation time to prevent + // the overlay fades from not happening in atlas while the simulation is paused. As a cleaner + // solution, we should add a field to CMessageInterpolate that holds an elapsed real time delta. + //static double lastRealTime = 0; + const double currentRealTime = timer_Time(); + float deltaRealTime = (float)(currentRealTime - m_LastRealTime); + m_LastRealTime = currentRealTime; - // TODO: it'd be nice to fade smoothly (but quickly) from transparent to solid + if (m_FadeDeltaAlpha != 0.f) + { + m_FadeProgress += deltaRealTime; + if (m_FadeProgress >= FADE_DURATION) + { + const float targetAlpha = m_FadeBaselineAlpha + m_FadeDeltaAlpha; + + // stop the fade + m_Color.a = targetAlpha; + m_FadeBaselineAlpha = targetAlpha; + m_FadeDeltaAlpha = 0.f; + m_FadeProgress = FADE_DURATION; // will need to be reset to start the next fade again + } + else + { + m_Color.a = Ease::QuartOut(m_FadeProgress, m_FadeBaselineAlpha, m_FadeDeltaAlpha, FADE_DURATION); + } + } + + // update dynamic overlay only when visible + if (m_Color.a > 0) + { + const CMessageInterpolate& msgData = static_cast (msg); + UpdateDynamicOverlay(msgData.offset); + } + + break; + } + case MT_OwnershipChanged: + { + const CMessageOwnershipChanged& msgData = static_cast (msg); + + // don't update color if there's no new owner (e.g. the unit died) + if (msgData.to == INVALID_PLAYER) + break; + + // update the selection highlight color + CmpPtr cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY); + if (!cmpPlayerManager) + break; + + CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(msgData.to)); + if (!cmpPlayer) + break; + + // Update the highlight color, while keeping the current alpha target value intact + // (i.e. baseline + delta), so that any ongoing fades simply continue with the new color. + CColor color = cmpPlayer->GetColour(); + SetSelectionHighlight(CColor(color.r, color.g, color.b, m_FadeBaselineAlpha + m_FadeDeltaAlpha)); + } + // fall-through + case MT_PositionChanged: + { + InvalidateStaticOverlay(); + break; + } + case MT_RenderSubmit: + { + const CMessageRenderSubmit& msgData = static_cast (msg); + RenderSubmit(msgData.collector); + + break; + } + } +} + +void CCmpSelectable::InvalidateStaticOverlay() +{ + SAFE_DELETE(m_BuildingOverlay); +} + +void CCmpSelectable::UpdateStaticOverlay() +{ + // 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; + + entity_id_t entityId = GetEntityId(); + CmpPtr cmpPosition(GetSimContext(), entityId); + CmpPtr cmpFootprint(GetSimContext(), entityId); + if (!cmpFootprint || !cmpPosition || !cmpPosition->IsInWorld()) + return; + + CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY); + 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()); + texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); + texturePropsBase.SetMaxAnisotropy(4.f); + + CTextureProperties texturePropsMask(m_OverlayDescriptor.m_LineTextureMask.c_str()); + texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); + texturePropsMask.SetMaxAnisotropy(4.f); + + // ------------------------------------------------------------------------------------- + + 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) + { + 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 + 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); + } + 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; } - void ConstructShape(float frameOffset) + ENSURE(m_BuildingOverlay); +} + +void CCmpSelectable::UpdateDynamicOverlay(float frameOffset) +{ + // Dynamic overlay lines are allocated once and never deleted. Since they are expected to change frequently, + // they are assumed dirty on every call to this function, and we should therefore use this function more + // thoughtfully than calling it right before every frame render. + + if (m_OverlayDescriptor.m_Type != DYNAMIC_QUAD) + return; + + if (!CRenderer::IsInitialised()) + return; + + entity_id_t entityId = GetEntityId(); + CmpPtr cmpPosition(GetSimContext(), entityId); + CmpPtr cmpFootprint(GetSimContext(), entityId); + if (!cmpFootprint || !cmpPosition || !cmpPosition->IsInWorld()) + return; + + float rotY; + CVector2D position; + cmpPosition->GetInterpolatedPosition2D(frameOffset, position.X, position.Y, rotY); + + CmpPtr cmpWaterManager(GetSimContext(), SYSTEM_ENTITY); + CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY); + ENSURE(cmpWaterManager && cmpTerrain); + + CTerrain* terrain = cmpTerrain->GetCTerrain(); + ENSURE(terrain); + + ICmpFootprint::EShape fpShape; + entity_pos_t fpSize0_fixed, fpSize1_fixed, fpHeight_fixed; + cmpFootprint->GetShape(fpShape, fpSize0_fixed, fpSize1_fixed, fpHeight_fixed); + + // --------------------------------------------------------------------------------- + + if (!m_UnitOverlay) { - CmpPtr cmpPosition(GetSimContext(), GetEntityId()); - if (!cmpPosition) - return; + m_UnitOverlay = new SOverlayQuad; - if (!cmpPosition->IsInWorld()) - return; + // Assuming we don't need the capability of swapping textures on-demand. + CTextureProperties texturePropsBase(m_OverlayDescriptor.m_QuadTexture.c_str()); + texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); + texturePropsBase.SetMaxAnisotropy(4.f); - float x, z, rotY; - cmpPosition->GetInterpolatedPosition2D(frameOffset, x, z, rotY); + CTextureProperties texturePropsMask(m_OverlayDescriptor.m_QuadTextureMask.c_str()); + texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); + texturePropsMask.SetMaxAnisotropy(4.f); - CmpPtr cmpFootprint(GetSimContext(), GetEntityId()); - if (!cmpFootprint) + m_UnitOverlay->m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); + m_UnitOverlay->m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); + } + + m_UnitOverlay->m_Color = m_Color; + + // TODO: some code duplication here :< would be nice to factor out getting the corner points of an + // entity based on its footprint sizes (and regardless of whether it's a circle or a square) + + float s = sinf(-rotY); + float c = cosf(-rotY); + CVector2D unitX(c, s); + CVector2D unitZ(-s, c); + + float halfSizeX = fpSize0_fixed.ToFloat(); + float halfSizeZ = fpSize1_fixed.ToFloat(); + if (fpShape == ICmpFootprint::SQUARE) + { + halfSizeX /= 2.0f; + halfSizeZ /= 2.0f; + } + + std::vector points; + points.push_back(CVector2D(position + unitX *(-halfSizeX) + unitZ * halfSizeZ)); // top left + points.push_back(CVector2D(position + unitX *(-halfSizeX) + unitZ *(-halfSizeZ))); // bottom left + points.push_back(CVector2D(position + unitX * halfSizeX + unitZ *(-halfSizeZ))); // bottom right + points.push_back(CVector2D(position + unitX * halfSizeX + unitZ * halfSizeZ)); // top right + + for (int i=0; i < 4; i++) + { + float quadY = std::max( + terrain->GetExactGroundLevel(points[i].X, points[i].Y), + cmpWaterManager->GetExactWaterLevel(points[i].X, points[i].Y) + ); + + m_UnitOverlay->m_Corners[i] = CVector3D(points[i].X, quadY, points[i].Y); + } +} + +void CCmpSelectable::RenderSubmit(SceneCollector& collector) +{ + // don't render selection overlay if it's not gonna be visible + if (m_Color.a > 0) + { + switch (m_OverlayDescriptor.m_Type) { - // Default (this probably shouldn't happen) - just render an arbitrary-sized circle - SimRender::ConstructCircleOnGround(GetSimContext(), x, z, 2.f, m_Overlay, cmpPosition->IsFloating()); - } - else - { - ICmpFootprint::EShape shape; - entity_pos_t size0, size1, height; - cmpFootprint->GetShape(shape, size0, size1, height); - - if (shape == ICmpFootprint::SQUARE) - SimRender::ConstructSquareOnGround(GetSimContext(), x, z, size0.ToFloat(), size1.ToFloat(), rotY, m_Overlay, cmpPosition->IsFloating()); - else - SimRender::ConstructCircleOnGround(GetSimContext(), x, z, size0.ToFloat(), m_Overlay, cmpPosition->IsFloating()); + case STATIC_OUTLINE: + { + UpdateStaticOverlay(); + m_BuildingOverlay->m_Color = m_Color; // done separately so alpha changes don't require a full update call + collector.Submit(m_BuildingOverlay); + } + break; + case DYNAMIC_QUAD: + { + if (m_UnitOverlay) + collector.Submit(m_UnitOverlay); + } + break; + default: + break; } } - void RenderSubmit(SceneCollector& collector) + // Render bounding box debug overlays if we have a positive target alpha value. This ensures + // that the debug overlays respond immediately to deselection without delay from fading out. + if (m_FadeBaselineAlpha + m_FadeDeltaAlpha > 0) { - // (This is only called if a > 0) - collector.Submit(&m_Overlay); - if (ICmpSelectable::ms_EnableDebugOverlays) { // allocate debug overlays on-demand @@ -205,6 +530,6 @@ public: if (m_DebugSelectionBoxOverlay) SAFE_DELETE(m_DebugSelectionBoxOverlay); } } -}; +} REGISTER_COMPONENT_TYPE(Selectable) diff --git a/source/simulation2/components/CCmpTemplateManager.cpp b/source/simulation2/components/CCmpTemplateManager.cpp index 9061c5fe8a..7b24af752d 100644 --- a/source/simulation2/components/CCmpTemplateManager.cpp +++ b/source/simulation2/components/CCmpTemplateManager.cpp @@ -377,7 +377,14 @@ void CCmpTemplateManager::ConstructTemplateActor(const std::string& actorName, C // Initialise the actor's name and make it an Atlas selectable entity. std::string name = utf8_from_wstring(CParamNode::EscapeXMLString(wstring_from_utf8(actorName))); - std::string xml = "" + name + ""; + std::string xml = "" + "" + name + "" + "" + "" + "actor.pngactor_mask.png" + "" + ""; + CParamNode::LoadXMLString(out, xml.c_str()); } diff --git a/source/simulation2/components/CCmpTerritoryManager.cpp b/source/simulation2/components/CCmpTerritoryManager.cpp index 4aa01bfc25..03c4fff0b1 100644 --- a/source/simulation2/components/CCmpTerritoryManager.cpp +++ b/source/simulation2/components/CCmpTerritoryManager.cpp @@ -620,11 +620,6 @@ void CCmpTerritoryManager::UpdateBoundaryLines() texturePropsMask.SetMaxAnisotropy(2.f); CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); - CmpPtr cmpTerrain(GetSimContext(), SYSTEM_ENTITY); - if (!cmpTerrain) - return; - CTerrain* terrain = cmpTerrain->GetCTerrain(); - CmpPtr cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpPlayerManager) return; @@ -642,7 +637,7 @@ void CCmpTerritoryManager::UpdateBoundaryLines() m_BoundaryLines.push_back(SBoundaryLine()); m_BoundaryLines.back().connected = boundaries[i].connected; m_BoundaryLines.back().color = color; - m_BoundaryLines.back().overlay.m_Terrain = terrain; + m_BoundaryLines.back().overlay.m_SimContext = &GetSimContext(); m_BoundaryLines.back().overlay.m_TextureBase = textureBase; m_BoundaryLines.back().overlay.m_TextureMask = textureMask; m_BoundaryLines.back().overlay.m_Color = color; @@ -650,7 +645,6 @@ void CCmpTerritoryManager::UpdateBoundaryLines() m_BoundaryLines.back().overlay.m_Closed = true; SimRender::SmoothPointsAverage(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed); - SimRender::InterpolatePointsRNS(boundaries[i].points, m_BoundaryLines.back().overlay.m_Closed, m_BorderSeparation); std::vector& points = m_BoundaryLines.back().overlay.m_Coords; diff --git a/source/simulation2/components/ICmpSelectable.h b/source/simulation2/components/ICmpSelectable.h index c7c8ec2436..42f0026600 100644 --- a/source/simulation2/components/ICmpSelectable.h +++ b/source/simulation2/components/ICmpSelectable.h @@ -18,6 +18,7 @@ #ifndef INCLUDED_ICMPSELECTABLE #define INCLUDED_ICMPSELECTABLE +#include "ps/CStrIntern.h" #include "simulation2/system/Interface.h" struct CColor; @@ -25,6 +26,27 @@ struct CColor; class ICmpSelectable : public IComponent { public: + + enum EOverlayType { + /// A single textured quad overlay, intended for entities that move around much, like units (e.g. foot soldiers, etc). + DYNAMIC_QUAD, + /// A more complex textured line overlay, composed of several textured line segments. Intended for entities that do not + /// move often, such as buildings (structures). + STATIC_OUTLINE, + }; + + struct SOverlayDescriptor + { + EOverlayType m_Type; + CStrIntern m_QuadTexture; + CStrIntern m_QuadTextureMask; + CStrIntern m_LineTexture; + CStrIntern m_LineTextureMask; + float m_LineThickness; + + SOverlayDescriptor() : m_LineThickness(0) { } + }; + /** * Returns true if the entity is only selectable in Atlas editor, e.g. a decorative visual actor. */ diff --git a/source/simulation2/helpers/Geometry.cpp b/source/simulation2/helpers/Geometry.cpp index b357561d57..5b713f03da 100644 --- a/source/simulation2/helpers/Geometry.cpp +++ b/source/simulation2/helpers/Geometry.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -45,6 +45,11 @@ CFixedVector2D Geometry::GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, ); } +float Geometry::ChordToCentralAngle(const float chordLength, const float radius) +{ + return acosf(1.f - SQR(chordLength)/(2.f*SQR(radius))); // cfr. law of cosines +} + fixed Geometry::DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize) { /* diff --git a/source/simulation2/helpers/Geometry.h b/source/simulation2/helpers/Geometry.h index cb925b760e..e8af00ea21 100644 --- a/source/simulation2/helpers/Geometry.h +++ b/source/simulation2/helpers/Geometry.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -24,6 +24,7 @@ */ #include "maths/Fixed.h" +#include "maths/MathUtil.h" class CFixedVector2D; @@ -52,6 +53,14 @@ CFixedVector2D GetHalfBoundingBox(CFixedVector2D u, CFixedVector2D v, CFixedVect fixed DistanceToSquare(CFixedVector2D point, CFixedVector2D u, CFixedVector2D v, CFixedVector2D halfSize); +/** + * Given a circle of radius @p radius, and a chord of length @p chordLength on this circle, computes the central angle formed by + * connecting the chord's endpoints to the center of the circle. + * + * @param radius Radius of the circle; must be strictly positive. + */ +float ChordToCentralAngle(const float chordLength, const float radius); + /** * Find point closest to the given point on the edge of the given square or rectangle. * diff --git a/source/simulation2/helpers/Render.cpp b/source/simulation2/helpers/Render.cpp index 450a28d00f..712a6d40a4 100644 --- a/source/simulation2/helpers/Render.cpp +++ b/source/simulation2/helpers/Render.cpp @@ -19,17 +19,18 @@ #include "Render.h" -#include "simulation2/Simulation2.h" -#include "simulation2/components/ICmpTerrain.h" -#include "simulation2/components/ICmpWaterManager.h" #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "maths/BoundingBoxAligned.h" #include "maths/BoundingBoxOriented.h" #include "maths/MathUtil.h" +#include "maths/Quaternion.h" #include "maths/Vector2D.h" #include "ps/Profile.h" -#include "maths/Quaternion.h" +#include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpTerrain.h" +#include "simulation2/components/ICmpWaterManager.h" +#include "simulation2/helpers/Geometry.h" void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector& xz, SOverlayLine& overlay, bool floating, float heightOffset) @@ -343,7 +344,7 @@ static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a return p + CVector2D(dp.Y*-offset, dp.X*offset); } -void SimRender::InterpolatePointsRNS(std::vector& points, bool closed, float offset, int segmentSamples) +void SimRender::InterpolatePointsRNS(std::vector& points, bool closed, float offset, int segmentSamples /* = 4 */) { PROFILE("InterpolatePointsRNS"); ENSURE(segmentSamples > 0); @@ -381,7 +382,6 @@ void SimRender::InterpolatePointsRNS(std::vector& points, bool closed for (size_t i = 0; i < imax; ++i) { - // Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points // before p1 and after p2, respectively; they're needed to compute tangents and whatnot. CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below @@ -521,3 +521,50 @@ void SimRender::ConstructDashedLine(const std::vector& keyPoints, SDa } } + +void SimRender::AngularStepFromChordLen(const float maxChordLength, const float radius, float& out_stepAngle, unsigned& out_numSteps) +{ + float maxAngle = Geometry::ChordToCentralAngle(maxChordLength, radius); + out_numSteps = ceilf(float(2*M_PI)/maxAngle); + out_stepAngle = float(2*M_PI)/out_numSteps; +} + +// TODO: this serves a similar purpose to SplitLine above, but is more general. Also, SplitLine seems to be implemented more +// efficiently, might be nice to take some cues from it +void SimRender::SubdividePoints(std::vector& points, float maxSegmentLength, bool closed) +{ + size_t numControlPoints = points.size(); + if (numControlPoints < 2) + return; + + ENSURE(maxSegmentLength > 0); + + size_t endIndex = numControlPoints; + if (!closed && numControlPoints > 2) + endIndex--; + + std::vector newPoints; + + for (size_t i = 0; i < endIndex; i++) + { + const CVector2D& curPoint = points[i]; + const CVector2D& nextPoint = points[(i+1) % numControlPoints]; + const CVector2D line(nextPoint - curPoint); + CVector2D lineDirection = line.Normalized(); + + // include control point i + a list of intermediate points between i and i + 1 (excluding i+1 itself) + newPoints.push_back(curPoint); + + // calculate how many intermediate points are needed so that each segment is of length <= maxSegmentLength + float lineLength = line.Length(); + size_t numSegments = (size_t) ceilf(lineLength / maxSegmentLength); + float segmentLength = lineLength / numSegments; + + for (size_t s = 1; s < numSegments; ++s) // start at one, we already included curPoint + { + newPoints.push_back(curPoint + lineDirection * (s * segmentLength)); + } + } + + points.swap(newPoints); +} \ No newline at end of file diff --git a/source/simulation2/helpers/Render.h b/source/simulation2/helpers/Render.h index 93e812d22a..6ed8be28ec 100644 --- a/source/simulation2/helpers/Render.h +++ b/source/simulation2/helpers/Render.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -76,6 +76,7 @@ void ConstructLineOnGround(const CSimContext& context, const std::vector& * @param[in,out] overlay Updated overlay line representing this circle. * @param[in] floating If true, the circle conforms to water as well. * @param[in] heightOffset Height above terrain to offset the circle. + * @param heightOffset The vertical offset to apply to points, to raise the line off the terrain a bit. */ void ConstructCircleOnGround(const CSimContext& context, float x, float z, float radius, SOverlayLine& overlay, @@ -96,7 +97,7 @@ void ConstructSquareOnGround(const CSimContext& context, float x, float z, float bool floating, float heightOffset = 0.25f); /** - * Constructs a solid outline of an arbitrarily-aligned box. + * Constructs a solid outline of an arbitrarily-aligned bounding @p box. * * @param[in] box * @param[in,out] overlayLine Updated overlay line representing the oriented box. @@ -104,12 +105,12 @@ void ConstructSquareOnGround(const CSimContext& context, float x, float z, float void ConstructBoxOutline(const CBoundingBoxOriented& box, SOverlayLine& overlayLine); /** - * Constructs a solid outline of an axis-aligned bounding box. + * Constructs a solid outline of an axis-aligned bounding @p box. * * @param[in] bound * @param[in,out] overlayLine Updated overlay line representing the AABB. */ -void ConstructBoxOutline(const CBoundingBoxAligned& bound, SOverlayLine& overlayLine); +void ConstructBoxOutline(const CBoundingBoxAligned& box, SOverlayLine& overlayLine); /** * Constructs a simple gimbal outline with the given radius and center. @@ -124,7 +125,7 @@ void ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, s /** * Constructs 3D axis marker overlay lines for the given coordinate system. - * The overlay lines are colored RGB for the XYZ axes, respectively. + * The XYZ axes are colored RGB, respectively. * * @param[in] coordSystem Specifies the coordinate system. * @param[out] outX,outY,outZ Constructed overlay lines for each axes. @@ -162,7 +163,33 @@ void InterpolatePointsRNS(std::vector& points, bool closed, float off * @param[in] dashLength Length of a single dash. Must be strictly positive. * @param[in] blankLength Length of a single blank between dashes. Must be strictly positive. */ -void ConstructDashedLine(const std::vector& linePoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength); +void ConstructDashedLine(const std::vector& linePoints, SDashedLine& dashedLineOut, + const float dashLength, const float blankLength); + +/** + * Computes angular step parameters @p out_stepAngle and @p out_numSteps, given a @p maxChordLength on a circle of radius @p radius. + * The resulting values satisfy @p out_numSteps * @p out_stepAngle = 2*PI. + * + * This function is used to find the angular step parameters when drawing a circle outline approximated by several connected chords; + * it returns the step angle and number of steps such that the length of each resulting chord is less than or equal to @p maxChordLength. + * By stating that each chord cannot be longer than a particular length, a certain level of visual smoothness of the resulting circle + * outline can be guaranteed independently of the radius of the outline. + * + * @param radius Radius of the circle. Must be strictly positive. + * @param maxChordLength Desired maximum length of individual chords. Must be strictly positive. + */ +void AngularStepFromChordLen(const float maxChordLength, const float radius, float& out_stepAngle, unsigned& out_numSteps); + +/** + * Subdivides a list of @p points into segments of maximum length @p maxSegmentLength that are of equal size between every two + * control points. The resulting subdivided list of points is written back to @p points. + * + * @param points The list of intermediate points to subdivide. + * @param maxSegmentLength The maximum length of a single segment after subdivision. Must be strictly positive. + * @param closed Should the provided list of points be treated as a closed shape? If true, the resulting list of points will include + * extra subdivided points between the last and the first point. + */ +void SubdividePoints(std::vector& points, float maxSegmentLength, bool closed); } // namespace diff --git a/source/simulation2/system/ParamNode.cpp b/source/simulation2/system/ParamNode.cpp index 4226a61532..7cbf1bd7e0 100644 --- a/source/simulation2/system/ParamNode.cpp +++ b/source/simulation2/system/ParamNode.cpp @@ -205,6 +205,11 @@ const std::string CParamNode::ToUTF8() const return utf8_from_wstring(m_Value); } +const CStrIntern CParamNode::ToUTF8Intern() const +{ + return CStrIntern(utf8_from_wstring(m_Value)); +} + int CParamNode::ToInt() const { int ret = 0; @@ -219,6 +224,15 @@ fixed CParamNode::ToFixed() const return fixed::FromString(CStrW(m_Value)); } +float CParamNode::ToFloat() const +{ + float ret = 0; + std::wstringstream strm; + strm << m_Value; + strm >> ret; + return ret; +} + bool CParamNode::ToBool() const { if (m_Value == L"true") diff --git a/source/simulation2/system/ParamNode.h b/source/simulation2/system/ParamNode.h index 16e9b40289..7caffbd784 100644 --- a/source/simulation2/system/ParamNode.h +++ b/source/simulation2/system/ParamNode.h @@ -20,6 +20,7 @@ #include "lib/file/vfs/vfs_path.h" #include "maths/Fixed.h" +#include "ps/CStrIntern.h" #include "ps/Errors.h" #include "scriptinterface/ScriptVal.h" @@ -168,6 +169,12 @@ public: */ const std::string ToUTF8() const; + /** + * Returns the content of this node as an internalized 8-bit string. Should only be used for + * predictably small and frequently-used strings. + */ + const CStrIntern ToUTF8Intern() const; + /** * Parses the content of this node as an integer */ @@ -178,6 +185,11 @@ public: */ fixed ToFixed() const; + /** + * Parses the content of this node as a floating-point number + */ + float ToFloat() const; + /** * Parses the content of this node as a boolean ("true" == true, anything else == false) */ diff --git a/source/simulation2/tests/test_CmpTemplateManager.h b/source/simulation2/tests/test_CmpTemplateManager.h index d087929828..0c3a0e5079 100644 --- a/source/simulation2/tests/test_CmpTemplateManager.h +++ b/source/simulation2/tests/test_CmpTemplateManager.h @@ -78,7 +78,8 @@ public: const CParamNode* actor = tempMan->LoadTemplate(ent2, "actor|example1", -1); TS_ASSERT(actor != NULL); TS_ASSERT_WSTR_EQUALS(actor->ToXML(), - L"example1falsefalse"); + L"actor.pngactor_mask.png" + L"example1falsefalse"); const CParamNode* preview = tempMan->LoadTemplate(ent2, "preview|unit", -1); TS_ASSERT(preview != NULL); diff --git a/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp b/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp index 456756429e..52a5b3cbcc 100644 --- a/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp +++ b/source/tools/atlas/GameInterface/Handlers/ObjectHandlers.cpp @@ -111,6 +111,7 @@ MESSAGEHANDLER(SetSelectionPreview) { CmpPtr cmpSelectable(sim, g_Selection[i]); if (cmpSelectable) + // TODO: change only alpha component cmpSelectable->SetSelectionHighlight(CColor(1, 1, 1, 0)); } diff --git a/source/tools/selectiontexgen/selectiontexgen.py b/source/tools/selectiontexgen/selectiontexgen.py new file mode 100644 index 0000000000..038ba47e72 --- /dev/null +++ b/source/tools/selectiontexgen/selectiontexgen.py @@ -0,0 +1,288 @@ +""" +Generates basic square and circle selection overlay textures by parsing all the entity XML files and reading +their Footprint components. + +For usage, invoke this script with --help. +""" + +# This script uses PyCairo for plotting, since PIL (Python Imaging Library) is absolutely horrible. On Linux, +# this should be merely a matter of installing a package (e.g. 'python-cairo' for Debian/Ubuntu), but on Windows +# it's kind of tricky and requires some Google-fu. Fortunately, I have saved the working instructions below: +# +# Grab a Win32 binary from http://ftp.gnome.org/pub/GNOME/binaries/win32/pycairo/1.8/ and install PyCairo using +# the installer. The installer extracts the necessary files into Lib\site-packages\cairo within the folder where +# Python is installed. There are some extra DLLs which are required to make Cairo work, so we have to get these +# as well. +# +# Head to http://ftp.gnome.org/pub/gnome/binaries/win32/dependencies/ and get the binary versions of Cairo +# (cairo_1.8.10-3_win32.zip at the time of writing), Fontconfig (fontconfig_2.8.0-2_win32.zip), Freetype +# (freetype_2.4.4-1_win32.zip), Expat (expat_2.0.1-1_win32.zip), libpng (libpng_1.4.3-1_win32.zip) and zlib +# (zlib_1.2.5-2_win32.zip). Version numbers may vary, so be adaptive! Each ZIP file will contain a bin subfolder +# with a DLL file in it. Put the following DLLs in Lib\site-packages\cairo within your Python installation: +# +# freetype6.dll (from freetype_2.4.4-1_win32.zip) +# libcairo-2.dll (from cairo_1.8.10-3_win32.zip) +# libexpat-1.dll (from expat_2.0.1-1_win32.zip) +# libfontconfig-1.dll (from fontconfig_2.8.0-2_win32.zip) +# libpng14-14.dll (from libpng_1.4.3-1_win32.zip) +# zlib1.dll (from zlib_1.2.5-2_win32.zip). +# +# Should be all set now. + +import optparse +import sys, os +import math +import operator +import cairo # Requires PyCairo (see notes above) +from os.path import * +from xml.dom import minidom + +def geqPow2(x): + """Returns the smallest power of two that's equal to or greater than x""" + return int(2**math.ceil(math.log(x, 2))) + +def generateSelectionTexture(shape, textureW, textureH, outerStrokeW, innerStrokeW, outputDir): + + outputBasename = "%dx%d" % (textureW, textureH) + + # size of the image canvas containing the texture (may be larger to ensure power-of-two dimensions) + canvasW = geqPow2(textureW) + canvasH = geqPow2(textureH) + + # draw texture + texture = cairo.ImageSurface(cairo.FORMAT_ARGB32, canvasW, canvasH) + textureMask = cairo.ImageSurface(cairo.FORMAT_RGB24, canvasW, canvasH) + + ctxTexture = cairo.Context(texture) + ctxTextureMask = cairo.Context(textureMask) + + # fill entire image with transparent pixels + ctxTexture.set_source_rgba(1.0, 1.0, 1.0, 0.0) # transparent + ctxTexture.rectangle(0, 0, textureW, textureH) + ctxTexture.fill() # fill current path + + ctxTextureMask.set_source_rgb(0.0, 0.0, 0.0) # black + ctxTextureMask.rectangle(0, 0, canvasW, canvasH) # (!) + ctxTextureMask.fill() + + pasteX = (canvasW - textureW)//2 # integer division, floored result + pasteY = (canvasH - textureH)//2 # integer division, floored result + ctxTexture.translate(pasteX, pasteY) # translate all drawing so that the result is centered + ctxTextureMask.translate(pasteX, pasteY) + + # outer stroke width should always be >= inner stroke width, but let's play it safe + maxStrokeW = max(outerStrokeW, innerStrokeW) + + if shape == "square": + + rectW = textureW + rectH = textureH + + # draw texture (4px white outline, then overlay a 2px black outline) + ctxTexture.rectangle(maxStrokeW/2, maxStrokeW/2, rectW - maxStrokeW, rectH - maxStrokeW) + ctxTexture.set_line_width(outerStrokeW) + ctxTexture.set_source_rgba(1.0, 1.0, 1.0, 1.0) # white + ctxTexture.stroke_preserve() # stroke and maintain path + ctxTexture.set_line_width(innerStrokeW) + ctxTexture.set_source_rgba(0.0, 0.0, 0.0, 1.0) # black + ctxTexture.stroke() # stroke and clear path + + # draw mask (2px white) + ctxTextureMask.rectangle(maxStrokeW/2, maxStrokeW/2, rectW - maxStrokeW, rectH - maxStrokeW) + ctxTextureMask.set_line_width(innerStrokeW) + ctxTextureMask.set_source_rgb(1.0, 1.0, 1.0) + ctxTextureMask.stroke() + + elif shape == "circle": + + centerX = textureW//2 + centerY = textureH//2 + radius = textureW//2 - maxStrokeW/2 # allow for the strokes to fit + + # draw texture + ctxTexture.arc(centerX, centerY, radius, 0, 2*math.pi) + ctxTexture.set_line_width(outerStrokeW) + ctxTexture.set_source_rgba(1.0, 1.0, 1.0, 1.0) # white + ctxTexture.stroke_preserve() # stroke and maintain path + ctxTexture.set_line_width(innerStrokeW) + ctxTexture.set_source_rgba(0.0, 0.0, 0.0, 1.0) # black + ctxTexture.stroke() + + # draw mask + ctxTextureMask.arc(centerX, centerY, radius, 0, 2*math.pi) + ctxTextureMask.set_line_width(innerStrokeW) + ctxTextureMask.set_source_rgb(1.0, 1.0, 1.0) + ctxTextureMask.stroke() + + finalOutputDir = outputDir + "/" + shape + if not isdir(finalOutputDir): + os.makedirs(finalOutputDir) + + print "Generating " + os.path.normcase(finalOutputDir + "/" + outputBasename + ".png") + + texture.write_to_png(finalOutputDir + "/" + outputBasename + ".png") + textureMask.write_to_png(finalOutputDir + "/" + outputBasename + "_mask.png") + + +def generateSelectionTextures(xmlTemplateDir, outputDir, outerStrokeScale, innerStrokeScale, snapSizes = False): + + # recursively list XML files + xmlFiles = [] + + for dir, subdirs, basenames in os.walk(xmlTemplateDir): + for basename in basenames: + filename = join(dir, basename) + if filename[-4:] == ".xml": + xmlFiles.append(filename) + + textureTypesRaw = set() # set of (type, w, h) tuples (so we can eliminate duplicates) + + # parse the XML files, and look for nodes that are a child of and + # that do not have the disable attribute defined + for xmlFile in xmlFiles: + xmlDoc = minidom.parse(xmlFile) + rootNode = xmlDoc.childNodes[0] + + # we're only interested in entity templates + if not rootNode.nodeName == "Entity": + continue + + # check if this entity has a footprint definition + rootChildNodes = [n for n in rootNode.childNodes if n.localName is not None] # remove whitespace text nodes + footprintNodes = filter(lambda x: x.localName == "Footprint", rootChildNodes) + if not len(footprintNodes) == 1: + continue + + footprintNode = footprintNodes[0] + if footprintNode.hasAttribute("disable"): + continue + + # parse the footprint declaration + # Footprints can either have either one of these children: + # + # + # There's also a node, but we don't care about it here. + + squareNodes = footprintNode.getElementsByTagName("Square") + circleNodes = footprintNode.getElementsByTagName("Circle") + + numSquareNodes = len(squareNodes) + numCircleNodes = len(circleNodes) + + if not (numSquareNodes + numCircleNodes == 1): + print "Invalid Footprint definition: insufficient or too many Square and/or Circle definitions in %s" % xmlFile + + texShape = None + texW = None # in world-space units + texH = None # in world-space units + + if numSquareNodes == 1: + texShape = "square" + texW = float(squareNodes[0].getAttribute("width")) + texH = float(squareNodes[0].getAttribute("depth")) + + elif numCircleNodes == 1: + texShape = "circle" + texW = float(circleNodes[0].getAttribute("radius")) + texH = texW + + textureTypesRaw.add((texShape, texW, texH)) + + # endfor xmlFiles + + print "Found: %d footprints (%d square, %d circle)" % ( + len(textureTypesRaw), + len([x for x in textureTypesRaw if x[0] == "square"]), + len([x for x in textureTypesRaw if x[0] == "circle"]) + ) + + textureTypes = set() + + for type, w, h in textureTypesRaw: + if snapSizes: + # "snap" texture sizes to close-enough neighbours that will still look good enough so we can get away with fewer + # actual textures than there are unique footprint outlines + w = 1*math.ceil(w/1) # round up to the nearest world-space unit + h = 1*math.ceil(h/1) # round up to the nearest world-space unit + + textureTypes.add((type, w, h)) + + if snapSizes: + print "Reduced: %d footprints (%d square, %d circle)" % ( + len(textureTypes), + len([x for x in textureTypes if x[0] == "square"]), + len([x for x in textureTypes if x[0] == "circle"]) + ) + + # create list from texture types set (so we can sort and have prettier output) + textureTypes = sorted(list(textureTypes), key=operator.itemgetter(0,1,2)) # sort by the first tuple element, then by the second, then the third + + # ------------------------------------------------------------------------------------ + # compute the size of the actual texture we want to generate (in px) + + scale = 8 # world-space-units-to-pixels scale + for type, w, h in textureTypes: + + # if we have a circle, update the w and h so that they're the full width and height of the texture + # and not just the radius + if type == "circle": + assert w == h + w *= 2 + h *= 2 + + w = int(math.ceil(w*scale)) + h = int(math.ceil(h*scale)) + + # apply a minimum size for really small textures + w = max(24, w) + h = max(24, h) + + generateSelectionTexture(type, w, h, w/outerStrokeScale, innerStrokeScale * (w/outerStrokeScale), outputDir) + + +if __name__ == "__main__": + + parser = optparse.OptionParser(usage="Usage: %prog [filenames]") + + parser.add_option("--template-dir", type="str", default=None, help="Path to simulation template XML definition folder. Will be searched recursively for templates containing Footprint definitions. If not specified and this script is run from its directory, it will be automatically determined.") + parser.add_option("--output-dir", type="str", default=".", help="Output directory. Will be created if it does not already exist. Defaults to the current directory.") + parser.add_option("--oss", "--outer-stroke-scale", type="float", default=12.0, dest="outer_stroke_scale", metavar="SCALE", help="Width of the outer (white) stroke, as a divisor of each generated texture's width. Defaults to 12. Larger values produce thinner overall outlines.") + parser.add_option("--iss", "--inner-stroke-scale", type="float", default=0.5, dest="inner_stroke_scale", metavar="PERCENTAGE", help="Width of the inner (black) stroke, as a percentage of the outer stroke's calculated width. Must be between 0 and 1. Higher values produce thinner black/player color strokes inside the surrounding outer white stroke. Defaults to 0.5.") + + (options, args) = parser.parse_args() + + templateDir = options.template_dir + if templateDir is None: + + scriptDir = dirname(abspath(__file__)) + + # 'autodetect' location if run from its own dir + if normcase(scriptDir).replace('\\', '/').endswith("source/tools/selectiontexgen"): + templateDir = "../../../binaries/data/mods/public/simulation/templates" + else: + print "No template dir specified; use the --template-dir command line argument." + sys.exit() + + # check if the template dir exists + templateDir = abspath(templateDir) + if not isdir(templateDir): + print "No such template directory: %s" % templateDir + sys.exit() + + # check if the output dir exists, create it if needed + outputDir = abspath(options.output_dir) + print outputDir + if not isdir(outputDir): + print "Creating output directory: %s" % outputDir + os.makedirs(outputDir) + + print "Template directory:\t%s" % templateDir + print "Output directory: \t%s" % outputDir + print "------------------------------------------------" + + generateSelectionTextures( + templateDir, + outputDir, + max(0.0, options.outer_stroke_scale), + min(1.0, max(0.0, options.inner_stroke_scale)), + ) \ No newline at end of file