1
0
forked from 0ad/0ad

Pretty unit selection overlay rings. Fixes #824.

This was SVN commit r11623.
This commit is contained in:
vts 2012-04-22 04:04:02 +00:00
parent 60944a1439
commit 34fc883317
58 changed files with 1718 additions and 268 deletions

BIN
binaries/data/mods/public/art/textures/selection/actor.png (Stored with Git LFS) Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -1,9 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<program type="arb">
<!-- This shader is used for rendering overlay lines (e.g. territory boundaries), and also for
rendering the unit selection ring quad overlays, since they are almost identical. The only
difference is that in unit selection ring mode, the uniform objectColor is ignored and instead
replaced by a color stream from the vertices. The USE_OBJECTCOLOR define is used to switch
between either mode. -->
<vertex file="arb/overlayline.vp">
<stream name="pos"/>
<stream name="uv0"/>
<stream name="color" if="!USE_OBJECTCOLOR"/>
<uniform name="losTransform" loc="0" type="vec2"/>
</vertex>
@ -11,7 +18,7 @@
<uniform name="baseTex" loc="0" type="sampler2D"/>
<uniform name="maskTex" loc="1" type="sampler2D"/>
<uniform name="losTex" loc="2" type="sampler2D"/>
<uniform name="objectColor" loc="0" type="vec3"/>
<uniform name="objectColor" loc="0" type="vec3" if="USE_OBJECTCOLOR"/>
</fragment>
</program>

View File

@ -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)

View File

@ -23,6 +23,12 @@
</Position>
<Selectable>
<EditorOnly/>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
<TerritoryInfluence>
<OverrideCost>64</OverrideCost>

View File

@ -23,6 +23,12 @@
</Position>
<Selectable>
<EditorOnly disable=""/>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
<TerritoryInfluence>
<OverrideCost>0</OverrideCost>

View File

@ -18,6 +18,12 @@
</ResourceSupply>
<Selectable>
<EditorOnly disable=""/>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
<Sound>
<SoundGroups>

View File

@ -17,6 +17,12 @@
</ResourceSupply>
<Selectable>
<EditorOnly disable=""/>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
<Sound>
<SoundGroups>

View File

@ -17,6 +17,13 @@
</ResourceSupply>
<Selectable>
<EditorOnly disable=""/>
<Overlay>
<Outline>
<LineTexture>outline_border.png</LineTexture>
<LineTextureMask>outline_border_mask.png</LineTextureMask>
<LineThickness>0.2</LineThickness>
</Outline>
</Overlay>
</Selectable>
<Sound>
<SoundGroups>

View File

@ -17,6 +17,12 @@
</ResourceSupply>
<Selectable>
<EditorOnly disable=""/>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
<Sound>
<SoundGroups>

View File

@ -24,6 +24,12 @@
</ResourceSupply>
<Selectable>
<EditorOnly disable=""/>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
<Sound>
<SoundGroups>

View File

@ -29,5 +29,11 @@
</Sound>
<Selectable>
<EditorOnly disable=""/>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
</Entity>

View File

@ -66,6 +66,15 @@
<LineCostClass>default</LineCostClass>
<LinePassabilityClass>default</LinePassabilityClass>
</RallyPointRenderer>
<Selectable>
<Overlay>
<Outline>
<LineTexture>outline_border.png</LineTexture>
<LineTextureMask>outline_border_mask.png</LineTextureMask>
<LineThickness>0.4</LineThickness>
</Outline>
</Overlay>
</Selectable>
<Sound>
<SoundGroups>
<select>interface/select/building/sel_universal.xml</select>

View File

@ -80,6 +80,14 @@
<metal>20</metal>
</Capacities>
</ResourceGatherer>
<Selectable>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
<StatusBars>
<BarWidth>2.0</BarWidth>
<BarHeight>0.333</BarHeight>

View File

@ -70,6 +70,14 @@
<metal.ore>1</metal.ore>
</Rates>
</ResourceGatherer>
<Selectable>
<Overlay>
<Texture>
<MainTexture>circle/128x128.png</MainTexture>
<MainTextureMask>circle/128x128_mask.png</MainTextureMask>
</Texture>
</Overlay>
</Selectable>
<Sound>
<SoundGroups>
<select>voice/hellenes/civ/civ_male_select.xml</select>

View File

@ -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

View File

@ -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<float> 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<CRenderData> 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<CVector2D>& 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

View File

@ -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;
}

View File

@ -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;

View File

@ -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);

148
source/maths/Ease.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <math.h>
/**
* 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

View File

@ -27,7 +27,6 @@ public:
fixed X, Y;
CFixedVector2D() { }
CFixedVector2D(fixed X, fixed Y) : X(X), Y(Y) { }
/// Vector equality

View File

@ -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);

View File

@ -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 <boost/unordered_map.hpp>
#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<SOverlayQuad*> 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<QuadBatchKey, QuadBatchData> QuadBatchMap;
OverlayRendererInternals();
~OverlayRendererInternals(){ }
std::vector<SOverlayLine*> lines;
std::vector<SOverlayTexturedLine*> texlines;
std::vector<SOverlaySprite*> sprites;
std::vector<SOverlayQuad*> 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<u16> 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<CVector3D> vertexPos = m->quadAttributePos.GetIterator<CVector3D>();
VertexArrayIterator<CVector4D> vertexColor = m->quadAttributeColor.GetIterator<CVector4D>();
VertexArrayIterator<short[2]> vertexUV = m->quadAttributeUV.GetIterator<short[2]>();
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<ICmpWaterManager> 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<ICmpWaterManager> cmpWaterManager(*m_Line->m_SimContext, SYSTEM_ENTITY);
float v = 0.f;
std::vector<SVertex> 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)

View File

@ -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;
};

View File

@ -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);

View File

@ -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);

View File

@ -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.
*/

View File

@ -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<u16> VertexArray::Attribute::GetIterator<u16>() const
return vertexArray->MakeIterator<u16>(this);
}
template<>
VertexArrayIterator<u16[2]> VertexArray::Attribute::GetIterator<u16[2]>() const
{
ENSURE(vertexArray);
ENSURE(type == GL_UNSIGNED_SHORT);
ENSURE(elems >= 2);
return vertexArray->MakeIterator<u16[2]>(this);
}
template<>
VertexArrayIterator<u8> VertexArray::Attribute::GetIterator<u8>() const
{
@ -166,7 +180,25 @@ VertexArrayIterator<u8[4]> VertexArray::Attribute::GetIterator<u8[4]>() const
return vertexArray->MakeIterator<u8[4]>(this);
}
template<>
VertexArrayIterator<short> VertexArray::Attribute::GetIterator<short>() const
{
ENSURE(vertexArray);
ENSURE(type == GL_SHORT);
ENSURE(elems >= 1);
return vertexArray->MakeIterator<short>(this);
}
template<>
VertexArrayIterator<short[2]> VertexArray::Attribute::GetIterator<short[2]>() const
{
ENSURE(vertexArray);
ENSURE(type == GL_SHORT);
ENSURE(elems >= 2);
return vertexArray->MakeIterator<short[2]>(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;

View File

@ -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<typename T>
@ -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<u16> GetIterator() const;
private:

View File

@ -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<VBChunk*>::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)
{

View File

@ -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<VBChunk*> 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;
};

View File

@ -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<CVertexBuffer*>& 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<CVertexBuffer*> m_Buffers;
};

View File

@ -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<CVector2D> 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<CVector2D>& 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<CVector2D>& coord
player_id_t currentPlayer = GetSimContext().GetCurrentDisplayedPlayer();
CLosQuerier losQuerier(cmpRangeMgr->GetLosQuerier(currentPlayer));
//for (std::vector<Waypoint>::iterator it = waypoints.begin(); it != waypoints.end();)
for(std::vector<CVector2D>::iterator it = coords.begin(); it != coords.end();)
{
int i = (fixed::FromFloat(it->X) / (int)TERRAIN_TILE_SIZE).ToInt_RoundToNearest();

View File

@ -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:
"<element name='EditorOnly' a:help='If this element is present, the entity is only selectable in Atlas'>"
"<empty/>"
"</element>"
"</optional>";
"</optional>"
"<element name='Overlay' a:help='Specifies the type of overlay to be displayed when this entity is selected'>"
"<choice>"
"<element name='Texture' a:help='Displays a texture underneath the entity.'>"
"<element name='MainTexture' a:help='Texture to display underneath the entity. Filepath relative to art/textures/selection/.'><text/></element>"
"<element name='MainTextureMask' a:help='Mask texture that controls where to apply player color. Filepath relative to art/textures/selection/.'><text/></element>"
"</element>"
"<element name='Outline' a:help='Traces the outline of the entity with a line texture.'>"
"<element name='LineTexture' a:help='Texture to apply to the line. Filepath relative to art/textures/selection/.'><text/></element>"
"<element name='LineTextureMask' a:help='Texture that controls where to apply player color. Filepath relative to art/textures/selection/.'><text/></element>"
"<element name='LineThickness' a:help='Thickness of the line, in world units.'><ref name='positiveDecimal'/></element>"
"</element>"
"</choice>"
"</element>";
}
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<const CMessageInterpolate&> (msg).offset;
ConstructShape(offset);
}
break;
}
case MT_RenderSubmit:
{
if (m_Overlay.m_Color.a > 0)
{
const CMessageRenderSubmit& msgData = static_cast<const CMessageRenderSubmit&> (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<float> 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<const CMessageInterpolate&> (msg);
UpdateDynamicOverlay(msgData.offset);
}
break;
}
case MT_OwnershipChanged:
{
const CMessageOwnershipChanged& msgData = static_cast<const CMessageOwnershipChanged&> (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<ICmpPlayerManager> cmpPlayerManager(GetSimContext(), SYSTEM_ENTITY);
if (!cmpPlayerManager)
break;
CmpPtr<ICmpPlayer> 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<const CMessageRenderSubmit&> (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<ICmpPosition> cmpPosition(GetSimContext(), entityId);
CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), entityId);
if (!cmpFootprint || !cmpPosition || !cmpPosition->IsInWorld())
return;
CmpPtr<ICmpTerrain> 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<CVector2D> 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<ICmpPosition> cmpPosition(GetSimContext(), entityId);
CmpPtr<ICmpFootprint> cmpFootprint(GetSimContext(), entityId);
if (!cmpFootprint || !cmpPosition || !cmpPosition->IsInWorld())
return;
float rotY;
CVector2D position;
cmpPosition->GetInterpolatedPosition2D(frameOffset, position.X, position.Y, rotY);
CmpPtr<ICmpWaterManager> cmpWaterManager(GetSimContext(), SYSTEM_ENTITY);
CmpPtr<ICmpTerrain> 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<ICmpPosition> 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<ICmpFootprint> 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<CVector2D> 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)

View File

@ -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 = "<Entity><VisualActor><Actor>" + name + "</Actor></VisualActor><Selectable><EditorOnly/></Selectable></Entity>";
std::string xml = "<Entity>"
"<VisualActor><Actor>" + name + "</Actor></VisualActor>"
"<Selectable>"
"<EditorOnly/>"
"<Overlay><Texture><MainTexture>actor.png</MainTexture><MainTextureMask>actor_mask.png</MainTextureMask></Texture></Overlay>"
"</Selectable>"
"</Entity>";
CParamNode::LoadXMLString(out, xml.c_str());
}

View File

@ -620,11 +620,6 @@ void CCmpTerritoryManager::UpdateBoundaryLines()
texturePropsMask.SetMaxAnisotropy(2.f);
CTexturePtr textureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask);
CmpPtr<ICmpTerrain> cmpTerrain(GetSimContext(), SYSTEM_ENTITY);
if (!cmpTerrain)
return;
CTerrain* terrain = cmpTerrain->GetCTerrain();
CmpPtr<ICmpPlayerManager> 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<float>& points = m_BoundaryLines.back().overlay.m_Coords;

View File

@ -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.
*/

View File

@ -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)
{
/*

View File

@ -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.
*

View File

@ -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<float>& 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<CVector2D>& points, bool closed, float offset, int segmentSamples)
void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& points, bool closed, float offset, int segmentSamples /* = 4 */)
{
PROFILE("InterpolatePointsRNS");
ENSURE(segmentSamples > 0);
@ -381,7 +382,6 @@ void SimRender::InterpolatePointsRNS(std::vector<CVector2D>& 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<CVector2D>& 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<CVector2D>& 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<CVector2D> 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);
}

View File

@ -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<float>&
* @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<CVector2D>& 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<CVector2D>& linePoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength);
void ConstructDashedLine(const std::vector<CVector2D>& 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<CVector2D>& points, float maxSegmentLength, bool closed);
} // namespace

View File

@ -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")

View File

@ -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)
*/

View File

@ -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"<Selectable><EditorOnly></EditorOnly></Selectable><VisualActor><Actor>example1</Actor><SilhouetteDisplay>false</SilhouetteDisplay><SilhouetteOccluder>false</SilhouetteOccluder></VisualActor>");
L"<Selectable><EditorOnly></EditorOnly><Overlay><Texture><MainTexture>actor.png</MainTexture><MainTextureMask>actor_mask.png</MainTextureMask></Texture></Overlay></Selectable>"
L"<VisualActor><Actor>example1</Actor><SilhouetteDisplay>false</SilhouetteDisplay><SilhouetteOccluder>false</SilhouetteOccluder></VisualActor>");
const CParamNode* preview = tempMan->LoadTemplate(ent2, "preview|unit", -1);
TS_ASSERT(preview != NULL);

View File

@ -111,6 +111,7 @@ MESSAGEHANDLER(SetSelectionPreview)
{
CmpPtr<ICmpSelectable> cmpSelectable(sim, g_Selection[i]);
if (cmpSelectable)
// TODO: change only alpha component
cmpSelectable->SetSelectionHighlight(CColor(1, 1, 1, 0));
}

View File

@ -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 <Footprint> nodes that are a child of <Entity> 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:
# <Circle radius="xx.x" />
# <Square width="xx.x" depth="xx.x"/>
# There's also a <Height> 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)),
)