Index: ps/trunk/source/gui/CChart.cpp
===================================================================
--- ps/trunk/source/gui/CChart.cpp (revision 22604)
+++ ps/trunk/source/gui/CChart.cpp (revision 22605)
@@ -1,332 +1,333 @@
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "CChart.h"
#include "gui/CGUIColor.h"
+#include "gui/GUIMatrix.h"
#include "graphics/ShaderManager.h"
#include "i18n/L10n.h"
#include "lib/ogl.h"
#include "ps/CLogger.h"
#include "renderer/Renderer.h"
#include "third_party/cppformat/format.h"
#include
CChart::CChart(CGUI* pGUI)
: IGUIObject(pGUI), IGUITextOwner(pGUI)
{
AddSetting("axis_color");
AddSetting("axis_width");
AddSetting("buffer_zone");
AddSetting("font");
AddSetting("format_x");
AddSetting("format_y");
AddSetting("series_color");
AddSetting("series");
AddSetting("text_align");
GUI::GetSetting(this, "axis_width", m_AxisWidth);
GUI::GetSetting(this, "format_x", m_FormatX);
GUI::GetSetting(this, "format_y", m_FormatY);
}
CChart::~CChart()
{
}
void CChart::HandleMessage(SGUIMessage& Message)
{
// TODO: implement zoom
switch (Message.type)
{
case GUIM_SETTINGS_UPDATED:
{
GUI::GetSetting(this, "axis_width", m_AxisWidth);
GUI::GetSetting(this, "format_x", m_FormatX);
GUI::GetSetting(this, "format_y", m_FormatY);
UpdateSeries();
break;
}
}
}
void CChart::DrawLine(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const
{
shader->Uniform(str_color, color);
shader->VertexPointer(3, GL_FLOAT, 0, &vertices[0]);
shader->AssertPointersBound();
glEnable(GL_LINE_SMOOTH);
glLineWidth(1.1f);
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_LINE_STRIP, 0, vertices.size() / 3);
glLineWidth(1.0f);
glDisable(GL_LINE_SMOOTH);
}
void CChart::DrawTriangleStrip(const CShaderProgramPtr& shader, const CGUIColor& color, const std::vector& vertices) const
{
shader->Uniform(str_color, color);
shader->VertexPointer(3, GL_FLOAT, 0, &vertices[0]);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_TRIANGLE_STRIP, 0, vertices.size() / 3);
}
void CChart::DrawAxes(const CShaderProgramPtr& shader) const
{
const float bz = GetBufferedZ();
CRect rect = GetChartRect();
std::vector vertices;
vertices.reserve(30);
#define ADD(x, y) vertices.push_back(x); vertices.push_back(y); vertices.push_back(bz + 0.5f);
ADD(m_CachedActualSize.right, m_CachedActualSize.bottom);
ADD(rect.right + m_AxisWidth, rect.bottom);
ADD(m_CachedActualSize.left, m_CachedActualSize.bottom);
ADD(rect.left, rect.bottom);
ADD(m_CachedActualSize.left, m_CachedActualSize.top);
ADD(rect.left, rect.top - m_AxisWidth);
#undef ADD
CGUIColor axis_color(0.5f, 0.5f, 0.5f, 1.f);
GUI::GetSetting(this, "axis_color", axis_color);
DrawTriangleStrip(shader, axis_color, vertices);
}
void CChart::Draw()
{
PROFILE3("render chart");
if (!GetGUI())
return;
if (m_Series.empty())
return;
const float bz = GetBufferedZ();
CRect rect = GetChartRect();
const float width = rect.GetWidth();
const float height = rect.GetHeight();
// Disable depth updates to prevent apparent z-fighting-related issues
// with some drivers causing units to get drawn behind the texture.
glDepthMask(0);
// Setup the render state
CMatrix3D transform = GetDefaultGuiMatrix();
CShaderDefines lineDefines;
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid, g_Renderer.GetSystemShaderDefines(), lineDefines);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, transform);
CVector2D scale(width / (m_RightTop.X - m_LeftBottom.X), height / (m_RightTop.Y - m_LeftBottom.Y));
for (const CChartData& data : m_Series)
{
if (data.m_Points.empty())
continue;
std::vector vertices;
for (const CVector2D& point : data.m_Points)
{
if (fabs(point.X) != std::numeric_limits::infinity() && fabs(point.Y) != std::numeric_limits::infinity())
{
vertices.push_back(rect.left + (point.X - m_LeftBottom.X) * scale.X);
vertices.push_back(rect.bottom - (point.Y - m_LeftBottom.Y) * scale.Y);
vertices.push_back(bz + 0.5f);
}
else
{
DrawLine(shader, data.m_Color, vertices);
vertices.clear();
}
}
if (!vertices.empty())
DrawLine(shader, data.m_Color, vertices);
}
if (m_AxisWidth > 0)
DrawAxes(shader);
tech->EndPass();
// Reset depth mask
glDepthMask(1);
for (size_t i = 0; i < m_TextPositions.size(); ++i)
DrawText(i, CGUIColor(1.f, 1.f, 1.f, 1.f), m_TextPositions[i], bz + 0.5f);
}
CRect CChart::GetChartRect() const
{
return CRect(
m_CachedActualSize.TopLeft() + CPos(m_AxisWidth, m_AxisWidth),
m_CachedActualSize.BottomRight() - CPos(m_AxisWidth, m_AxisWidth)
);
}
void CChart::UpdateSeries()
{
CGUISeries* pSeries;
GUI::GetSettingPointer(this, "series", pSeries);
CGUIList* pSeriesColor;
GUI::GetSettingPointer(this, "series_color", pSeriesColor);
m_Series.clear();
m_Series.resize(pSeries->m_Series.size());
for (size_t i = 0; i < pSeries->m_Series.size(); ++i)
{
CChartData& data = m_Series[i];
if (i < pSeriesColor->m_Items.size() && !GUI::ParseColor(pSeriesColor->m_Items[i].GetOriginalString(), data.m_Color, 0))
LOGWARNING("GUI: Error parsing 'series_color' (\"%s\")", utf8_from_wstring(pSeriesColor->m_Items[i].GetOriginalString()));
data.m_Points = pSeries->m_Series[i];
}
UpdateBounds();
SetupText();
}
void CChart::SetupText()
{
if (!GetGUI())
return;
for (SGUIText* t : m_GeneratedTexts)
delete t;
m_GeneratedTexts.clear();
m_TextPositions.clear();
if (m_Series.empty())
return;
CStrW font;
if (GUI::GetSetting(this, "font", font) != PSRETURN_OK || font.empty())
font = L"default";
float buffer_zone = 0.f;
GUI::GetSetting(this, "buffer_zone", buffer_zone);
// Add Y-axis
GUI::GetSetting(this, "format_y", m_FormatY);
const float height = GetChartRect().GetHeight();
// TODO: split values depend on the format;
if (m_EqualY)
{
// We don't need to generate many items for equal values
AddFormattedValue(m_FormatY, m_RightTop.Y, font, buffer_zone);
m_TextPositions.emplace_back(GetChartRect().TopLeft());
}
else
for (int i = 0; i < 3; ++i)
{
AddFormattedValue(m_FormatY, m_RightTop.Y - (m_RightTop.Y - m_LeftBottom.Y) / 3.f * i, font, buffer_zone);
m_TextPositions.emplace_back(GetChartRect().TopLeft() + CPos(0.f, height / 3.f * i));
}
// Add X-axis
GUI::GetSetting(this, "format_x", m_FormatX);
const float width = GetChartRect().GetWidth();
if (m_EqualX)
{
CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X, font, buffer_zone);
m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size);
}
else
for (int i = 0; i < 3; ++i)
{
CSize text_size = AddFormattedValue(m_FormatX, m_RightTop.X - (m_RightTop.X - m_LeftBottom.X) / 3 * i, font, buffer_zone);
m_TextPositions.emplace_back(GetChartRect().BottomRight() - text_size - CPos(width / 3 * i, 0.f));
}
}
CSize CChart::AddFormattedValue(const CStrW& format, const float value, const CStrW& font, const float buffer_zone)
{
// TODO: we need to catch cases with equal formatted values.
CGUIString gui_str;
if (format == L"DECIMAL2")
{
wchar_t buffer[64];
swprintf(buffer, 64, L"%.2f", value);
gui_str.SetValue(buffer);
}
else if (format == L"INTEGER")
{
wchar_t buffer[64];
swprintf(buffer, 64, L"%d", std::lround(value));
gui_str.SetValue(buffer);
}
else if (format == L"DURATION_SHORT")
{
const int seconds = value;
wchar_t buffer[64];
swprintf(buffer, 64, L"%d:%02d", seconds / 60, seconds % 60);
gui_str.SetValue(buffer);
}
else if (format == L"PERCENTAGE")
{
wchar_t buffer[64];
swprintf(buffer, 64, L"%d%%", std::lround(value));
gui_str.SetValue(buffer);
}
else
{
LOGERROR("Unsupported chart format: " + format.EscapeToPrintableASCII());
return CSize();
}
SGUIText* text = new SGUIText();
*text = GetGUI()->GenerateText(gui_str, font, 0, buffer_zone, this);
AddText(text);
return text->m_Size;
}
void CChart::UpdateBounds()
{
if (m_Series.empty() || m_Series[0].m_Points.empty())
{
m_LeftBottom = m_RightTop = CVector2D(0.f, 0.f);
return;
}
m_LeftBottom = m_RightTop = m_Series[0].m_Points[0];
for (const CChartData& data : m_Series)
for (const CVector2D& point : data.m_Points)
{
if (fabs(point.X) != std::numeric_limits::infinity() && point.X < m_LeftBottom.X)
m_LeftBottom.X = point.X;
if (fabs(point.Y) != std::numeric_limits::infinity() && point.Y < m_LeftBottom.Y)
m_LeftBottom.Y = point.Y;
if (fabs(point.X) != std::numeric_limits::infinity() && point.X > m_RightTop.X)
m_RightTop.X = point.X;
if (fabs(point.Y) != std::numeric_limits::infinity() && point.Y > m_RightTop.Y)
m_RightTop.Y = point.Y;
}
m_EqualY = m_RightTop.Y == m_LeftBottom.Y;
if (m_EqualY)
m_RightTop.Y += 1;
m_EqualX = m_RightTop.X == m_LeftBottom.X;
if (m_EqualX)
m_RightTop.X += 1;
}
Index: ps/trunk/source/gui/GUIMatrix.cpp
===================================================================
--- ps/trunk/source/gui/GUIMatrix.cpp (nonexistent)
+++ ps/trunk/source/gui/GUIMatrix.cpp (revision 22605)
@@ -0,0 +1,42 @@
+/* Copyright (C) 2019 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#include "precompiled.h"
+
+#include "GUIMatrix.h"
+
+#include "maths/Matrix3D.h"
+
+extern int g_xres, g_yres;
+extern float g_GuiScale;
+
+CMatrix3D GetDefaultGuiMatrix()
+{
+ float xres = g_xres / g_GuiScale;
+ float yres = g_yres / g_GuiScale;
+
+ CMatrix3D m;
+ m.SetIdentity();
+ m.Scale(1.0f, -1.f, 1.0f);
+ m.Translate(0.0f, yres, -1000.0f);
+
+ CMatrix3D proj;
+ proj.SetOrtho(0.f, xres, 0.f, yres, -1.f, 1000.f);
+ m = proj * m;
+
+ return m;
+}
Property changes on: ps/trunk/source/gui/GUIMatrix.cpp
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/gui/GUIMatrix.h
===================================================================
--- ps/trunk/source/gui/GUIMatrix.h (nonexistent)
+++ ps/trunk/source/gui/GUIMatrix.h (revision 22605)
@@ -0,0 +1,28 @@
+/* Copyright (C) 2019 Wildfire Games.
+ * This file is part of 0 A.D.
+ *
+ * 0 A.D. is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 0 A.D. is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with 0 A.D. If not, see .
+ */
+
+#ifndef INCLUDED_GUIMATRIX
+#define INCLUDED_GUIMATRIX
+
+class CMatrix3D;
+
+/**
+ * Model-view-projection matrix with (0,0) in top-left of screen
+ */
+CMatrix3D GetDefaultGuiMatrix();
+
+#endif // INCLUDED_GUIMATRIX
Property changes on: ps/trunk/source/gui/GUIMatrix.h
___________________________________________________________________
Added: svn:eol-style
## -0,0 +1 ##
+native
\ No newline at end of property
Index: ps/trunk/source/gui/GUIRenderer.cpp
===================================================================
--- ps/trunk/source/gui/GUIRenderer.cpp (revision 22604)
+++ ps/trunk/source/gui/GUIRenderer.cpp (revision 22605)
@@ -1,467 +1,468 @@
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "GUIRenderer.h"
#include "graphics/ShaderManager.h"
#include "graphics/TextureManager.h"
#include "gui/CGUIColor.h"
#include "gui/GUIutil.h"
+#include "gui/GUIMatrix.h"
#include "i18n/L10n.h"
#include "lib/ogl.h"
#include "lib/utf8.h"
#include "lib/res/h_mgr.h"
#include "lib/tex/tex.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "renderer/Renderer.h"
using namespace GUIRenderer;
DrawCalls::DrawCalls()
{
}
// DrawCalls needs to be copyable, so it can be used in other copyable types.
// But actually copying data is hard, since we'd need to avoid losing track of
// who owns various pointers, so instead we just return an empty list.
// The list should get filled in again (by GUIRenderer::UpdateDrawCallCache)
// before it's used for rendering. (TODO: Is this class actually used safely
// in practice?)
DrawCalls::DrawCalls(const DrawCalls&)
: std::vector()
{
}
DrawCalls& DrawCalls::operator=(const DrawCalls&)
{
return *this;
}
void GUIRenderer::UpdateDrawCallCache(DrawCalls& Calls, const CStr& SpriteName, const CRect& Size, int CellID, std::map& Sprites)
{
// This is called only when something has changed (like the size of the
// sprite), so it doesn't need to be particularly efficient.
// Clean up the old data
Calls.clear();
// If this object has zero size, there's nothing to render. (This happens
// with e.g. tooltips that have zero size before they're first drawn, so
// it isn't necessarily an error.)
if (Size.left == Size.right && Size.top == Size.bottom)
return;
std::map::iterator it(Sprites.find(SpriteName));
if (it == Sprites.end())
{
/*
* Sprite not found. Check whether this a special sprite,
* and if so create a new sprite:
* "stretched:filename.ext" - stretched image
* "stretched:grayscale:filename.ext" - stretched grayscale image.
* "cropped:0.5, 0.25" - stretch this ratio (x,y) of the top left of the image
* "color:r g b a" - solid color
* > "textureAsMask" - when using color, use the (optional) texture alpha channel as mask.
* These can be combined, but they must be separated by a ":"
* so you can have a white overlay over an stretched grayscale image with:
* "grayscale:color:255 255 255 100:stretched:filename.ext"
*/
// Check that this can be a special sprite.
if (SpriteName.ReverseFind(":") == -1 && SpriteName.Find("color(") == -1)
{
LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
return;
}
CGUISprite* Sprite = new CGUISprite;
VfsPath TextureName = VfsPath("art/textures/ui") / wstring_from_utf8(SpriteName.AfterLast(":"));
if (SpriteName.Find("stretched:") != -1)
{
// TODO: Should check (nicely) that this is a valid file?
SGUIImage* Image = new SGUIImage;
Image->m_TextureName = TextureName;
// Allow grayscale images for disabled portraits
if (SpriteName.Find("grayscale:") != -1)
{
Image->m_Effects = new SGUIImageEffects;
Image->m_Effects->m_Greyscale = true;
}
CClientArea ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
Image->m_Size = ca;
Image->m_TextureSize = ca;
Sprite->AddImage(Image);
Sprites[SpriteName] = Sprite;
}
else if (SpriteName.Find("cropped:") != -1)
{
// TODO: Should check (nicely) that this is a valid file?
SGUIImage* Image = new SGUIImage;
CStr info = SpriteName.AfterLast("cropped:").BeforeFirst(":");
double xRatio = info.BeforeFirst(",").ToDouble();
double yRatio = info.AfterLast(",").ToDouble();
Image->m_TextureName = TextureName;
CClientArea ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
CClientArea cb(CRect(0, 0, 0, 0), CRect(0, 0, 100/xRatio, 100/yRatio));
Image->m_Size = ca;
Image->m_TextureSize = cb;
Sprite->AddImage(Image);
Sprites[SpriteName] = Sprite;
}
if (SpriteName.Find("color:") != -1)
{
CStrW value = wstring_from_utf8(SpriteName.AfterLast("color:").BeforeFirst(":"));
CGUIColor color;
// Check color is valid
if (!GUI::ParseString(value, color))
{
LOGERROR("GUI: Error parsing sprite 'color' (\"%s\")", utf8_from_wstring(value));
return;
}
SGUIImage* Image = new SGUIImage;
// If we are using a mask, this is an effect.
// Otherwise we can fallback to the "back color" attribute
// TODO: we are assuming there is a filename here.
if (SpriteName.Find("textureAsMask:") != -1)
{
Image->m_TextureName = TextureName;
Image->m_Effects = new SGUIImageEffects;
Image->m_Effects->m_SolidColor = color;
}
else
Image->m_BackColor = color;
CClientArea ca(CRect(0, 0, 0, 0), CRect(0, 0, 100, 100));
Image->m_Size = ca;
Image->m_TextureSize = ca;
Sprite->AddImage(Image);
Sprites[SpriteName] = Sprite;
}
it = Sprites.find(SpriteName);
// Otherwise, just complain and give up:
if (it == Sprites.end())
{
SAFE_DELETE(Sprite);
LOGERROR("Trying to use a sprite that doesn't exist (\"%s\").", SpriteName.c_str());
return;
}
}
Calls.reserve(it->second->m_Images.size());
// Iterate through all the sprite's images, loading the texture and
// calculating the texture coordinates
std::vector::const_iterator cit;
for (cit = it->second->m_Images.begin(); cit != it->second->m_Images.end(); ++cit)
{
SDrawCall Call(*cit); // pointers are safe since we never modify sprites/images after startup
CRect ObjectSize = (*cit)->m_Size.GetClientArea(Size);
if (ObjectSize.GetWidth() == 0.0 || ObjectSize.GetHeight() == 0.0)
{
// Zero sized object. Don't report as an error, since it's common for e.g. hitpoint bars.
continue; // i.e. don't continue with this image
}
Call.m_Vertices = ObjectSize;
if ((*cit)->m_RoundCoordinates)
{
// Round the vertex coordinates to integers, to avoid ugly filtering artifacts
Call.m_Vertices.left = (int)(Call.m_Vertices.left + 0.5f);
Call.m_Vertices.right = (int)(Call.m_Vertices.right + 0.5f);
Call.m_Vertices.top = (int)(Call.m_Vertices.top + 0.5f);
Call.m_Vertices.bottom = (int)(Call.m_Vertices.bottom + 0.5f);
}
if (!(*cit)->m_TextureName.empty())
{
CTextureProperties textureProps(g_L10n.LocalizePath((*cit)->m_TextureName));
textureProps.SetWrap((*cit)->m_WrapMode);
CTexturePtr texture = g_Renderer.GetTextureManager().CreateTexture(textureProps);
texture->Prefetch();
Call.m_HasTexture = true;
Call.m_Texture = texture;
Call.m_EnableBlending = false; // will be overridden if the texture has an alpha channel
Call.m_ObjectSize = ObjectSize;
Call.m_CellID = CellID;
}
else
{
Call.m_HasTexture = false;
// Enable blending if it's transparent (allowing a little error in the calculations)
Call.m_EnableBlending = !(fabs((*cit)->m_BackColor.a - 1.0f) < 0.0000001f);
}
Call.m_BackColor = (*cit)->m_BackColor;
Call.m_BorderColor = (*cit)->m_Border ? (*cit)->m_BorderColor : CGUIColor();
Call.m_DeltaZ = (*cit)->m_DeltaZ;
if (!Call.m_HasTexture)
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
}
else if ((*cit)->m_Effects)
{
if ((*cit)->m_Effects->m_AddColor != CGUIColor())
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_add);
Call.m_ShaderColorParameter = (*cit)->m_Effects->m_AddColor;
// Always enable blending if something's being subtracted from
// the alpha channel
if ((*cit)->m_Effects->m_AddColor.a < 0.f)
Call.m_EnableBlending = true;
}
else if ((*cit)->m_Effects->m_Greyscale)
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_grayscale);
}
else if ((*cit)->m_Effects->m_SolidColor != CGUIColor())
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid_mask);
Call.m_ShaderColorParameter = (*cit)->m_Effects->m_SolidColor;
Call.m_EnableBlending = !(fabs((*cit)->m_Effects->m_SolidColor.a - 1.0f) < 0.0000001f);
}
else /* Slight confusion - why no effects? */
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_basic);
}
}
else
{
Call.m_Shader = g_Renderer.GetShaderManager().LoadEffect(str_gui_basic);
}
Calls.push_back(Call);
}
}
CRect SDrawCall::ComputeTexCoords() const
{
float TexWidth = m_Texture->GetWidth();
float TexHeight = m_Texture->GetHeight();
if (!TexWidth || !TexHeight)
return CRect(0, 0, 1, 1);
// Textures are positioned by defining a rectangular block of the
// texture (usually the whole texture), and a rectangular block on
// the screen. The texture is positioned to make those blocks line up.
// Get the screen's position/size for the block
CRect BlockScreen = m_Image->m_TextureSize.GetClientArea(m_ObjectSize);
if (m_Image->m_FixedHAspectRatio)
BlockScreen.right = BlockScreen.left + BlockScreen.GetHeight() * m_Image->m_FixedHAspectRatio;
// Get the texture's position/size for the block:
CRect BlockTex;
// "real_texture_placement" overrides everything
if (m_Image->m_TexturePlacementInFile != CRect())
{
BlockTex = m_Image->m_TexturePlacementInFile;
}
// Check whether this sprite has "cell_size" set (and non-zero)
else if ((int)m_Image->m_CellSize.cx)
{
int cols = (int)TexWidth / (int)m_Image->m_CellSize.cx;
if (cols == 0)
cols = 1; // avoid divide-by-zero
int col = m_CellID % cols;
int row = m_CellID / cols;
BlockTex = CRect(m_Image->m_CellSize.cx*col, m_Image->m_CellSize.cy*row,
m_Image->m_CellSize.cx*(col+1), m_Image->m_CellSize.cy*(row+1));
}
// Use the whole texture
else
BlockTex = CRect(0, 0, TexWidth, TexHeight);
// When rendering, BlockTex will be transformed onto BlockScreen.
// Also, TexCoords will be transformed onto ObjectSize (giving the
// UV coords at each vertex of the object). We know everything
// except for TexCoords, so calculate it:
CPos translation (BlockTex.TopLeft()-BlockScreen.TopLeft());
float ScaleW = BlockTex.GetWidth()/BlockScreen.GetWidth();
float ScaleH = BlockTex.GetHeight()/BlockScreen.GetHeight();
CRect TexCoords (
// Resize (translating to/from the origin, so the
// topleft corner stays in the same place)
(m_ObjectSize-m_ObjectSize.TopLeft())
.Scale(ScaleW, ScaleH)
+ m_ObjectSize.TopLeft()
// Translate from BlockTex to BlockScreen
+ translation
);
// The tex coords need to be scaled so that (texwidth,texheight) is
// mapped onto (1,1)
TexCoords.left /= TexWidth;
TexCoords.right /= TexWidth;
TexCoords.top /= TexHeight;
TexCoords.bottom /= TexHeight;
return TexCoords;
}
void GUIRenderer::Draw(DrawCalls& Calls, float Z)
{
// Called every frame, to draw the object (based on cached calculations)
// TODO: batching by shader/texture/etc would be nice
CMatrix3D matrix = GetDefaultGuiMatrix();
glDisable(GL_BLEND);
// Set LOD bias so mipmapped textures are prettier
#if CONFIG2_GLES
#warning TODO: implement GUI LOD bias for GLES
#else
glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, -1.f);
#endif
// Iterate through each DrawCall, and execute whatever drawing code is being called
for (DrawCalls::const_iterator cit = Calls.begin(); cit != Calls.end(); ++cit)
{
cit->m_Shader->BeginPass();
CShaderProgramPtr shader = cit->m_Shader->GetShader();
shader->Uniform(str_transform, matrix);
if (cit->m_HasTexture)
{
shader->Uniform(str_color, cit->m_ShaderColorParameter);
shader->BindTexture(str_tex, cit->m_Texture);
if (cit->m_EnableBlending || cit->m_Texture->HasAlpha()) // (shouldn't call HasAlpha before BindTexture)
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
}
CRect TexCoords = cit->ComputeTexCoords();
// Ensure the quad has the correct winding order, and update texcoords to match
CRect Verts = cit->m_Vertices;
if (Verts.right < Verts.left)
{
std::swap(Verts.right, Verts.left);
std::swap(TexCoords.right, TexCoords.left);
}
if (Verts.bottom < Verts.top)
{
std::swap(Verts.bottom, Verts.top);
std::swap(TexCoords.bottom, TexCoords.top);
}
std::vector data;
#define ADD(u, v, x, y, z) STMT(data.push_back(u); data.push_back(v); data.push_back(x); data.push_back(y); data.push_back(z))
ADD(TexCoords.left, TexCoords.bottom, Verts.left, Verts.bottom, Z + cit->m_DeltaZ);
ADD(TexCoords.right, TexCoords.bottom, Verts.right, Verts.bottom, Z + cit->m_DeltaZ);
ADD(TexCoords.right, TexCoords.top, Verts.right, Verts.top, Z + cit->m_DeltaZ);
ADD(TexCoords.right, TexCoords.top, Verts.right, Verts.top, Z + cit->m_DeltaZ);
ADD(TexCoords.left, TexCoords.top, Verts.left, Verts.top, Z + cit->m_DeltaZ);
ADD(TexCoords.left, TexCoords.bottom, Verts.left, Verts.bottom, Z + cit->m_DeltaZ);
#undef ADD
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 5*sizeof(float), &data[0]);
shader->VertexPointer(3, GL_FLOAT, 5*sizeof(float), &data[2]);
glDrawArrays(GL_TRIANGLES, 0, 6);
}
else
{
shader->Uniform(str_color, cit->m_BackColor);
if (cit->m_EnableBlending)
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
}
// Ensure the quad has the correct winding order
CRect Verts = cit->m_Vertices;
if (Verts.right < Verts.left)
std::swap(Verts.right, Verts.left);
if (Verts.bottom < Verts.top)
std::swap(Verts.bottom, Verts.top);
std::vector data;
#define ADD(x, y, z) STMT(data.push_back(x); data.push_back(y); data.push_back(z))
ADD(Verts.left, Verts.bottom, Z + cit->m_DeltaZ);
ADD(Verts.right, Verts.bottom, Z + cit->m_DeltaZ);
ADD(Verts.right, Verts.top, Z + cit->m_DeltaZ);
ADD(Verts.right, Verts.top, Z + cit->m_DeltaZ);
ADD(Verts.left, Verts.top, Z + cit->m_DeltaZ);
ADD(Verts.left, Verts.bottom, Z + cit->m_DeltaZ);
shader->VertexPointer(3, GL_FLOAT, 3*sizeof(float), &data[0]);
glDrawArrays(GL_TRIANGLES, 0, 6);
if (cit->m_BorderColor != CGUIColor())
{
shader->Uniform(str_color, cit->m_BorderColor);
data.clear();
ADD(Verts.left + 0.5f, Verts.top + 0.5f, Z + cit->m_DeltaZ);
ADD(Verts.right - 0.5f, Verts.top + 0.5f, Z + cit->m_DeltaZ);
ADD(Verts.right - 0.5f, Verts.bottom - 0.5f, Z + cit->m_DeltaZ);
ADD(Verts.left + 0.5f, Verts.bottom - 0.5f, Z + cit->m_DeltaZ);
shader->VertexPointer(3, GL_FLOAT, 3*sizeof(float), &data[0]);
glDrawArrays(GL_LINE_LOOP, 0, 4);
}
#undef ADD
}
cit->m_Shader->EndPass();
glDisable(GL_BLEND);
}
#if CONFIG2_GLES
#warning TODO: implement GUI LOD bias for GLES
#else
glTexEnvf(GL_TEXTURE_FILTER_CONTROL, GL_TEXTURE_LOD_BIAS, 0.f);
#endif
}
Index: ps/trunk/source/gui/GUIutil.cpp
===================================================================
--- ps/trunk/source/gui/GUIutil.cpp (revision 22604)
+++ ps/trunk/source/gui/GUIutil.cpp (revision 22605)
@@ -1,430 +1,409 @@
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include "GUIutil.h"
#include "gui/GUI.h"
#include "gui/GUIManager.h"
-#include "maths/Matrix3D.h"
#include "ps/CLogger.h"
-#include "ps/GameSetup/Config.h"
-
-extern int g_xres, g_yres;
template
CGUISetting::CGUISetting(IGUIObject& pObject, const CStr& Name)
: m_pSetting(T()), m_Name(Name), m_pObject(pObject)
{
}
template
bool CGUISetting::FromString(const CStrW& Value, const bool& SkipMessage)
{
T settingValue;
if (!GUI::ParseString(Value, settingValue))
return false;
GUI::SetSetting(&m_pObject, m_Name, settingValue, SkipMessage);
return true;
};
template
bool CGUISetting::FromJSVal(JSContext* cx, JS::HandleValue Value)
{
T settingValue;
if (!ScriptInterface::FromJSVal(cx, Value, settingValue))
return false;
GUI::SetSetting(&m_pObject, m_Name, settingValue);
return true;
};
template
void CGUISetting::ToJSVal(JSContext* cx, JS::MutableHandleValue Value)
{
ScriptInterface::ToJSVal(cx, Value, m_pSetting);
};
template <>
bool __ParseString(const CStrW& Value, bool& Output)
{
if (Value == L"true")
Output = true;
else if (Value == L"false")
Output = false;
else
return false;
return true;
}
template <>
bool __ParseString(const CStrW& Value, int& Output)
{
Output = Value.ToInt();
return true;
}
template <>
bool __ParseString(const CStrW& Value, u32& Output)
{
Output = Value.ToUInt();
return true;
}
template <>
bool __ParseString(const CStrW& Value, float& Output)
{
Output = Value.ToFloat();
return true;
}
template <>
bool __ParseString(const CStrW& Value, CRect& Output)
{
const unsigned int NUM_COORDS = 4;
float coords[NUM_COORDS];
std::wstringstream stream;
stream.str(Value);
// Parse each coordinate
for (unsigned int i = 0; i < NUM_COORDS; ++i)
{
if (stream.eof())
{
LOGWARNING("Too few CRect parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
stream >> coords[i];
if ((stream.rdstate() & std::wstringstream::failbit) != 0)
{
LOGWARNING("Unable to parse CRect parameters. Your input: '%s'", Value.ToUTF8().c_str());
return false;
}
}
if (!stream.eof())
{
LOGWARNING("Too many CRect parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
// Finally the rectangle values
Output = CRect(coords[0], coords[1], coords[2], coords[3]);
return true;
}
template <>
bool __ParseString(const CStrW& Value, CClientArea& Output)
{
return Output.SetClientArea(Value.ToUTF8());
}
template <>
bool GUI::ParseColor(const CStrW& Value, CGUIColor& Output, int DefaultAlpha)
{
return Output.ParseString(Value.ToUTF8(), DefaultAlpha);
}
template <>
bool __ParseString(const CStrW& Value, CGUIColor& Output)
{
return Output.ParseString(Value.ToUTF8());
}
template <>
bool __ParseString(const CStrW& Value, CSize& Output)
{
const unsigned int NUM_COORDS = 2;
float coords[NUM_COORDS];
std::wstringstream stream;
stream.str(Value);
// Parse each coordinate
for (unsigned int i = 0; i < NUM_COORDS; ++i)
{
if (stream.eof())
{
LOGWARNING("Too few CSize parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
stream >> coords[i];
if ((stream.rdstate() & std::wstringstream::failbit) != 0)
{
LOGWARNING("Unable to parse CSize parameters. Your input: '%s'", Value.ToUTF8().c_str());
return false;
}
}
Output.cx = coords[0];
Output.cy = coords[1];
if (!stream.eof())
{
LOGWARNING("Too many CSize parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
return true;
}
template <>
bool __ParseString(const CStrW& Value, CPos& Output)
{
const unsigned int NUM_COORDS = 2;
float coords[NUM_COORDS];
std::wstringstream stream;
stream.str(Value);
// Parse each coordinate
for (unsigned int i = 0; i < NUM_COORDS; ++i)
{
if (stream.eof())
{
LOGWARNING("Too few CPos parameters (min %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
stream >> coords[i];
if ((stream.rdstate() & std::wstringstream::failbit) != 0)
{
LOGWARNING("Unable to parse CPos parameters. Your input: '%s'", Value.ToUTF8().c_str());
return false;
}
}
Output.x = coords[0];
Output.y = coords[1];
if (!stream.eof())
{
LOGWARNING("Too many CPos parameters (max %i). Your input: '%s'", NUM_COORDS, Value.ToUTF8().c_str());
return false;
}
return true;
}
template <>
bool __ParseString(const CStrW& Value, EAlign& Output)
{
if (Value == L"left")
Output = EAlign_Left;
else if (Value == L"center")
Output = EAlign_Center;
else if (Value == L"right")
Output = EAlign_Right;
else
return false;
return true;
}
template <>
bool __ParseString(const CStrW& Value, EVAlign& Output)
{
if (Value == L"top")
Output = EVAlign_Top;
else if (Value == L"center")
Output = EVAlign_Center;
else if (Value == L"bottom")
Output = EVAlign_Bottom;
else
return false;
return true;
}
template <>
bool __ParseString(const CStrW& Value, CGUIString& Output)
{
Output.SetValue(Value);
return true;
}
template <>
bool __ParseString(const CStrW& Value, CStr& Output)
{
// Do very little.
Output = Value.ToUTF8();
return true;
}
template <>
bool __ParseString(const CStrW& Value, CStrW& Output)
{
Output = Value;
return true;
}
template <>
bool __ParseString(const CStrW& Value, CGUISpriteInstance& Output)
{
Output = CGUISpriteInstance(Value.ToUTF8());
return true;
}
template <>
bool __ParseString(const CStrW& UNUSED(Value), CGUIList& UNUSED(Output))
{
return false;
}
template <>
bool __ParseString(const CStrW& UNUSED(Value), CGUISeries& UNUSED(Output))
{
return false;
}
-CMatrix3D GetDefaultGuiMatrix()
-{
- float xres = g_xres / g_GuiScale;
- float yres = g_yres / g_GuiScale;
-
- CMatrix3D m;
- m.SetIdentity();
- m.Scale(1.0f, -1.f, 1.0f);
- m.Translate(0.0f, yres, -1000.0f);
-
- CMatrix3D proj;
- proj.SetOrtho(0.f, xres, 0.f, yres, -1.f, 1000.f);
- m = proj * m;
-
- return m;
-}
-
template
PSRETURN GUI::GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, T*& Value)
{
ENSURE(pObject != NULL);
std::map::const_iterator it = pObject->m_Settings.find(Setting);
if (it == pObject->m_Settings.end())
{
LOGWARNING("setting %s was not found on object %s",
Setting.c_str(),
pObject->GetPresentableName().c_str());
return PSRETURN_GUI_InvalidSetting;
}
if (it->second == nullptr)
return PSRETURN_GUI_InvalidSetting;
// Get value
Value = &(static_cast* >(it->second)->m_pSetting);
return PSRETURN_OK;
}
template
PSRETURN GUI::GetSetting(const IGUIObject* pObject, const CStr& Setting, T& Value)
{
T* v = NULL;
PSRETURN ret = GetSettingPointer(pObject, Setting, v);
if (ret == PSRETURN_OK)
Value = *v;
return ret;
}
template
PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, T& Value, const bool& SkipMessage)
{
return SetSettingWrap(pObject, Setting, Value, SkipMessage,
[&pObject, &Setting, &Value]() {
static_cast* >(pObject->m_Settings[Setting])->m_pSetting = std::move(Value);
});
}
template
PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage)
{
return SetSettingWrap(pObject, Setting, Value, SkipMessage,
[&pObject, &Setting, &Value]() {
static_cast* >(pObject->m_Settings[Setting])->m_pSetting = Value;
});
}
// Helper function for SetSetting
template
bool IsBoolTrue(const T&)
{
return false;
}
template <>
bool IsBoolTrue(const bool& v)
{
return v;
}
template
PSRETURN GUI::SetSettingWrap(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage, const std::function& valueSet)
{
ENSURE(pObject != NULL);
if (!pObject->SettingExists(Setting))
{
LOGWARNING("setting %s was not found on object %s",
Setting.c_str(),
pObject->GetPresentableName().c_str());
return PSRETURN_GUI_InvalidSetting;
}
valueSet();
// Some settings needs special attention at change
// If setting was "size", we need to re-cache itself and all children
if (Setting == "size")
{
RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize);
}
else if (Setting == "hidden")
{
// Hiding an object requires us to reset it and all children
if (IsBoolTrue(Value))
RecurseObject(0, pObject, &IGUIObject::ResetStates);
}
if (!SkipMessage)
{
SGUIMessage msg(GUIM_SETTINGS_UPDATED, Setting);
pObject->HandleMessage(msg);
}
return PSRETURN_OK;
}
// Instantiate templated functions:
#define TYPE(T) \
template PSRETURN GUI::GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, T*& Value); \
template PSRETURN GUI::GetSetting(const IGUIObject* pObject, const CStr& Setting, T& Value); \
template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, T& Value, const bool& SkipMessage); \
template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage); \
template class CGUISetting; \
#define GUITYPE_IGNORE_CGUISpriteInstance
#include "GUItypes.h"
#undef GUITYPE_IGNORE_CGUISpriteInstance
#undef TYPE
// Don't instantiate GetSetting - this will cause linker errors if
// you attempt to retrieve a sprite using GetSetting, since that copies the sprite
// and will mess up the caching performed by DrawSprite. You have to use GetSettingPointer
// instead. (This is mainly useful to stop me accidentally using the wrong function.)
template PSRETURN GUI::GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, CGUISpriteInstance*& Value);
template PSRETURN GUI::SetSetting(IGUIObject* pObject, const CStr& Setting, CGUISpriteInstance& Value, const bool& SkipMessage);
template class CGUISetting;
Index: ps/trunk/source/gui/GUIutil.h
===================================================================
--- ps/trunk/source/gui/GUIutil.h (revision 22604)
+++ ps/trunk/source/gui/GUIutil.h (revision 22605)
@@ -1,361 +1,357 @@
/* Copyright (C) 2019 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 .
*/
/*
GUI util
--Overview--
Contains help class GUI<>, which gives us templated
parameter to all functions within GUI.
--More info--
Check GUI.h
*/
#ifndef INCLUDED_GUIUTIL
#define INCLUDED_GUIUTIL
#include "gui/CGUI.h"
#include "gui/CGUISprite.h"
#include "gui/GUIbase.h"
#include "gui/IGUIObject.h"
#include
class CClientArea;
class CGUIString;
-class CMatrix3D;
template class GUI;
class IGUISetting
{
public:
virtual ~IGUISetting() {};
/**
* Parses the given string and assigns to the setting value. Used for parsing XML attributes.
*/
virtual bool FromString(const CStrW& Value, const bool& SkipMessage) = 0;
/**
* Parses the given JS::Value using ScriptInterface::FromJSVal and assigns it to the setting data.
*/
virtual bool FromJSVal(JSContext* cx, JS::HandleValue Value) = 0;
/**
* Converts the setting data to a JS::Value using ScriptInterface::ToJSVal.
*/
virtual void ToJSVal(JSContext* cx, JS::MutableHandleValue Value) = 0;
};
template
class CGUISetting : public IGUISetting
{
friend class GUI;
public:
CGUISetting(IGUIObject& pObject, const CStr& Name);
/**
* Parses the given string and assigns to the setting value. Used for parsing XML attributes.
*/
bool FromString(const CStrW& Value, const bool& SkipMessage) override;
/**
* Parses the given JS::Value using ScriptInterface::FromJSVal and assigns it to the setting data.
*/
bool FromJSVal(JSContext* cx, JS::HandleValue Value) override;
/**
* Converts the setting data to a JS::Value using ScriptInterface::ToJSVal.
*/
void ToJSVal(JSContext* cx, JS::MutableHandleValue Value) override;
private:
/**
* The object that stores this setting.
*/
IGUIObject& m_pObject;
/**
* Property name identifying the setting.
*/
const CStr m_Name;
/**
* Holds the value of the setting..
*/
T m_pSetting;
};
template
bool __ParseString(const CStrW& Value, T& tOutput);
-// Model-view-projection matrix with (0,0) in top-left of screen
-CMatrix3D GetDefaultGuiMatrix();
-
struct SGUIMessage;
/**
* Includes static functions that needs one template
* argument.
*
* int is only to please functions that doesn't even use T
* and are only within this class because it's convenient
*/
template
class GUI
{
// Private functions further ahead
friend class CGUI;
friend class IGUIObject;
public:
// Like GetSetting (below), but doesn't make a copy of the value
// (so it can be modified later)
static PSRETURN GetSettingPointer(const IGUIObject* pObject, const CStr& Setting, T*& Value);
/**
* Retrieves a setting by name from object pointer
*
* @param pObject Object pointer
* @param Setting Setting by name
* @param Value Stores value here, note type T!
*/
static PSRETURN GetSetting(const IGUIObject* pObject, const CStr& Setting, T& Value);
/**
* Sets a value by name using a real datatype as input.
*
* This is the official way of setting a setting, no other
* way should only cautiously be used!
*
* This variant will use the move-assignment.
*
* @param pObject Object pointer
* @param Setting Setting by name
* @param Value Sets value to this, note type T!
* @param SkipMessage Does not send a GUIM_SETTINGS_UPDATED if true
*/
static PSRETURN SetSetting(IGUIObject* pObject, const CStr& Setting, T& Value, const bool& SkipMessage = false);
/**
* This variant will copy the value.
*/
static PSRETURN SetSetting(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage = false);
/**
* This will return the value of the first sprite if it's not null,
* if it is null, it will return the value of the second sprite, if
* that one is null, then null it is.
*
* @param prim Primary sprite that should be used
* @param sec Secondary sprite if Primary should fail
* @return Resulting string
*/
static const CGUISpriteInstance& FallBackSprite(const CGUISpriteInstance& prim, const CGUISpriteInstance& sec)
{
return (prim.IsEmpty() ? sec : prim);
}
/**
* Same principle as FallBackSprite
*
* @param prim Primary color that should be used
* @param sec Secondary color if Primary should fail
* @return Resulting color
* @see FallBackSprite
*/
static CGUIColor FallBackColor(const CGUIColor& prim, const CGUIColor& sec)
{
// CGUIColor() == null.
return ((prim!=CGUIColor())?(prim):(sec));
}
/**
* Sets a value by setting and object name using a real
* datatype as input.
*
* This is just a wrapper for __ParseString() which really
* works the magic.
*
* @param Value The value in string form, like "0 0 100% 100%"
* @param tOutput Parsed value of type T
* @return True at success.
*
* @see __ParseString()
*/
static bool ParseString(const CStrW& Value, T& tOutput)
{
return __ParseString(Value, tOutput);
}
static bool ParseColor(const CStrW& Value, CGUIColor& tOutput, int DefaultAlpha);
private:
/**
* Changes the value of the setting by calling the valueSet functon that performs either a copy or move assignment.
* Updates some internal data depending on the setting changed.
*/
static PSRETURN SetSettingWrap(IGUIObject* pObject, const CStr& Setting, const T& Value, const bool& SkipMessage, const std::function& valueSet);
// templated typedef of function pointer
typedef void (IGUIObject::*void_Object_pFunction_argT)(const T& arg);
typedef void (IGUIObject::*void_Object_pFunction_argRefT)(T& arg);
typedef void (IGUIObject::*void_Object_pFunction)();
typedef void (IGUIObject::*void_Object_pFunction_argTJS)(const T& arg, JS::HandleValueArray paramData);
/**
* If you want to call a IGUIObject-function
* on not just an object, but also on ALL of their children
* you want to use this recursion system.
* It recurses an object calling a function on itself
* and all children (and so forth).
*
* Restrictions:\n
* You can also set restrictions, so that if the recursion
* reaches an objects with certain setup, it just doesn't
* call the function on the object, nor it's children for
* that matter. i.e. it cuts that object off from the
* recursion tree. What setups that can cause restrictions
* are hardcoded and specific. Check out the defines
* GUIRR_* for all different setups.
*
* Error reports are either logged or thrown out of RecurseObject.
* Always use it with try/catch!
*
* @param RR Recurse Restrictions, set to 0 if no restrictions
* @param pObject Top object, this is where the iteration starts
* @param pFunc Function to recurse
* @param Argument Argument for pFunc of type T
* @throws PSERROR Depends on what pFunc might throw. PSERROR is standard.
* Itself doesn't throw anything.
*/
static void RecurseObject(int RR, IGUIObject* pObject, void_Object_pFunction_argT pFunc, const T& Argument)
{
// TODO Gee: Don't run this for the base object.
if (CheckIfRestricted(RR, pObject))
return;
(pObject->*pFunc)(Argument);
// Iterate children
for (IGUIObject* const& obj : *pObject)
RecurseObject(RR, obj, pFunc, Argument);
}
/**
* Argument is reference.
*
* @see RecurseObject()
*/
static void RecurseObject(int RR, IGUIObject* pObject, void_Object_pFunction_argRefT pFunc, T& Argument)
{
if (CheckIfRestricted(RR, pObject))
return;
(pObject->*pFunc)(Argument);
// Iterate children
for (IGUIObject* const& obj : *pObject)
RecurseObject(RR, obj, pFunc, Argument);
}
static void RecurseObject(int RR, IGUIObject* pObject, void_Object_pFunction_argTJS pFunc, const T& Argument, JS::HandleValueArray paramData)
{
if (CheckIfRestricted(RR, pObject))
return;
(pObject->*pFunc)(Argument, paramData);
// Iterate children
for (IGUIObject* const& obj : *pObject)
RecurseObject(RR, obj, pFunc, Argument, paramData);
}
/**
* With no argument.
*
* @see RecurseObject()
*/
static void RecurseObject(int RR, IGUIObject* pObject, void_Object_pFunction pFunc)
{
if (CheckIfRestricted(RR, pObject))
return;
(pObject->*pFunc)();
// Iterate children
for (IGUIObject* const& obj : *pObject)
RecurseObject(RR, obj, pFunc);
}
/**
* Checks restrictions for the iteration, for instance if
* you tell the recursor to avoid all hidden objects, it
* will, and this function checks a certain object's
* restriction values.
*
* @param RR What kind of restriction, for instance hidden or disabled
* @param pObject Object
* @return true if restricted
*/
static bool CheckIfRestricted(int RR, IGUIObject* pObject)
{
// Statically initialise some strings, so we don't have to do
// lots of allocation every time this function is called
static const CStr strHidden("hidden");
static const CStr strEnabled("enabled");
static const CStr strGhost("ghost");
if (RR & GUIRR_HIDDEN)
{
bool hidden = true;
GUI::GetSetting(pObject, strHidden, hidden);
if (hidden)
return true;
}
if (RR & GUIRR_DISABLED)
{
bool enabled = false;
GUI::GetSetting(pObject, strEnabled, enabled);
if (!enabled)
return true;
}
if (RR & GUIRR_GHOST)
{
bool ghost = true;
GUI::GetSetting(pObject, strGhost, ghost);
if (ghost)
return true;
}
// false means not restricted
return false;
}
};
#endif // INCLUDED_GUIUTIL
Index: ps/trunk/source/gui/MiniMap.cpp
===================================================================
--- ps/trunk/source/gui/MiniMap.cpp (revision 22604)
+++ ps/trunk/source/gui/MiniMap.cpp (revision 22605)
@@ -1,709 +1,710 @@
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#include "precompiled.h"
#include
#include "MiniMap.h"
#include "graphics/GameView.h"
#include "graphics/LOSTexture.h"
#include "graphics/MiniPatch.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerritoryTexture.h"
#include "gui/GUI.h"
#include "gui/GUIManager.h"
+#include "gui/GUIMatrix.h"
#include "lib/bits.h"
#include "lib/external_libraries/libsdl.h"
#include "lib/ogl.h"
#include "lib/timer.h"
#include "ps/ConfigDB.h"
#include "ps/Filesystem.h"
#include "ps/Game.h"
#include "ps/GameSetup/Config.h"
#include "ps/Profile.h"
#include "ps/World.h"
#include "ps/XML/Xeromyces.h"
#include "renderer/Renderer.h"
#include "renderer/WaterManager.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpMinimap.h"
#include "simulation2/system/ParamNode.h"
extern bool g_GameRestarted;
// Set max drawn entities to UINT16_MAX for now, which is more than enough
// TODO: we should be cleverer about drawing them to reduce clutter
const u16 MAX_ENTITIES_DRAWN = 65535;
static unsigned int ScaleColor(unsigned int color, float x)
{
unsigned int r = unsigned(float(color & 0xff) * x);
unsigned int g = unsigned(float((color>>8) & 0xff) * x);
unsigned int b = unsigned(float((color>>16) & 0xff) * x);
return (0xff000000 | b | g<<8 | r<<16);
}
CMiniMap::CMiniMap(CGUI* pGUI) :
IGUIObject(pGUI),
m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f),
m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW),
m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0)
{
AddSetting("tooltip");
AddSetting("tooltip_style");
m_Clicking = false;
m_MouseHovering = false;
// Register Relax NG validator
CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng");
// Get the maximum height for unit passage in water.
CParamNode externalParamNode;
CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder");
const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses");
if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk())
m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat();
else
m_ShallowPassageHeight = 0.0f;
m_AttributePos.type = GL_FLOAT;
m_AttributePos.elems = 2;
m_VertexArray.AddAttribute(&m_AttributePos);
m_AttributeColor.type = GL_UNSIGNED_BYTE;
m_AttributeColor.elems = 4;
m_VertexArray.AddAttribute(&m_AttributeColor);
m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_VertexArray.Layout();
m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN);
m_IndexArray.Layout();
VertexArrayIterator index = m_IndexArray.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
*index++ = i;
m_IndexArray.Upload();
m_IndexArray.FreeBackingStore();
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i)
{
(*attrColor)[0] = 0;
(*attrColor)[1] = 0;
(*attrColor)[2] = 0;
(*attrColor)[3] = 0;
++attrColor;
(*attrPos)[0] = -10000.0f;
(*attrPos)[1] = -10000.0f;
++attrPos;
}
m_VertexArray.Upload();
double blinkDuration = 1.0;
// Tests won't have config initialised
if (CConfigDB::IsInitialised())
{
CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration);
CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration);
}
m_HalfBlinkDuration = blinkDuration/2;
}
CMiniMap::~CMiniMap()
{
Destroy();
}
void CMiniMap::HandleMessage(SGUIMessage& Message)
{
switch (Message.type)
{
case GUIM_MOUSE_PRESS_LEFT:
if (m_MouseHovering)
{
SetCameraPos();
m_Clicking = true;
}
break;
case GUIM_MOUSE_RELEASE_LEFT:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
m_Clicking = false;
break;
case GUIM_MOUSE_DBLCLICK_LEFT:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
m_Clicking = false;
break;
case GUIM_MOUSE_ENTER:
m_MouseHovering = true;
break;
case GUIM_MOUSE_LEAVE:
m_Clicking = false;
m_MouseHovering = false;
break;
case GUIM_MOUSE_RELEASE_RIGHT:
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1);
break;
case GUIM_MOUSE_DBLCLICK_RIGHT:
CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2);
break;
case GUIM_MOUSE_MOTION:
if (m_MouseHovering && m_Clicking)
SetCameraPos();
break;
case GUIM_MOUSE_WHEEL_DOWN:
case GUIM_MOUSE_WHEEL_UP:
Message.Skip();
break;
default:
break;
}
}
bool CMiniMap::MouseOver()
{
// Get the mouse position.
CPos mousePos = GetMousePos();
// Get the position of the center of the minimap.
CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0);
// Take the magnitude of the difference of the mouse position and minimap center.
double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2));
// If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap.
if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0)
return true;
else
return false;
}
void CMiniMap::GetMouseWorldCoordinates(float& x, float& z)
{
// Determine X and Z according to proportion of mouse position and minimap
CPos mousePos = GetMousePos();
float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth();
float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight();
float angle = GetAngle();
// Scale world coordinates for shrunken square map
x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5);
z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5);
}
void CMiniMap::SetCameraPos()
{
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CVector3D target;
GetMouseWorldCoordinates(target.X, target.Z);
target.Y = terrain->GetExactGroundLevel(target.X, target.Z);
g_Game->GetView()->MoveCameraTarget(target);
}
float CMiniMap::GetAngle()
{
CVector3D cameraIn = m_Camera->m_Orientation.GetIn();
return -atan2(cameraIn.X, cameraIn.Z);
}
void CMiniMap::FireWorldClickEvent(int UNUSED(button), int UNUSED(clicks))
{
JSContext* cx = g_GUI->GetActiveGUI()->GetScriptInterface()->GetContext();
JSAutoRequest rq(cx);
float x, z;
GetMouseWorldCoordinates(x, z);
JS::RootedValue coords(cx);
g_GUI->GetActiveGUI()->GetScriptInterface()->CreateObject(&coords, "x", x, "z", z);
JS::AutoValueVector paramData(cx);
paramData.append(coords);
ScriptEvent("worldclick", paramData);
}
// This sets up and draws the rectangle on the minimap
// which represents the view of the camera in the world.
void CMiniMap::DrawViewRect(CMatrix3D transform)
{
// Compute the camera frustum intersected with a fixed-height plane.
// Use the water height as a fixed base height, which should be the lowest we can go
float h = g_Renderer.GetWaterManager()->m_WaterHeight;
const float width = m_CachedActualSize.GetWidth();
const float height = m_CachedActualSize.GetHeight();
const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize);
CVector3D hitPt[4];
hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h);
hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h);
hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h);
hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h);
float ViewRect[4][2];
for (int i = 0; i < 4; ++i)
{
// convert to minimap space
ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize);
ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize);
}
float viewVerts[] = {
ViewRect[0][0], -ViewRect[0][1],
ViewRect[1][0], -ViewRect[1][1],
ViewRect[2][0], -ViewRect[2][1],
ViewRect[3][0], -ViewRect[3][1]
};
// Enable Scissoring to restrict the rectangle to only the minimap.
glScissor(
m_CachedActualSize.left * g_GuiScale,
g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale,
width * g_GuiScale,
height * g_GuiScale);
glEnable(GL_SCISSOR_TEST);
glLineWidth(2.0f);
CShaderDefines lineDefines;
lineDefines.Add(str_MINIMAP_LINE, str_1);
CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines);
tech->BeginPass();
CShaderProgramPtr shader = tech->GetShader();
shader->Uniform(str_transform, transform);
shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f);
shader->VertexPointer(2, GL_FLOAT, 0, viewVerts);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_LINE_LOOP, 0, 4);
tech->EndPass();
glLineWidth(1.0f);
glDisable(GL_SCISSOR_TEST);
}
struct MinimapUnitVertex
{
u8 r, g, b, a;
float x, y;
};
// Adds a vertex to the passed VertexArray
static void inline addVertex(const MinimapUnitVertex& v,
VertexArrayIterator& attrColor,
VertexArrayIterator& attrPos)
{
(*attrColor)[0] = v.r;
(*attrColor)[1] = v.g;
(*attrColor)[2] = v.b;
(*attrColor)[3] = v.a;
++attrColor;
(*attrPos)[0] = v.x;
(*attrPos)[1] = v.y;
++attrPos;
}
void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z)
{
// Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m)
// Scale square maps to fit in circular minimap area
const float s = sin(angle) * m_MapScale;
const float c = cos(angle) * m_MapScale;
const float m = coordMax / 2.f;
float quadTex[] = {
m*(-c + s + 1.f), m*(-c + -s + 1.f),
m*(c + s + 1.f), m*(-c + s + 1.f),
m*(c + -s + 1.f), m*(c + s + 1.f),
m*(c + -s + 1.f), m*(c + s + 1.f),
m*(-c + -s + 1.f), m*(c + -s + 1.f),
m*(-c + s + 1.f), m*(-c + -s + 1.f)
};
float quadVerts[] = {
x, y, z,
x2, y, z,
x2, y2, z,
x2, y2, z,
x, y2, z,
x, y, z
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(3, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawArrays(GL_TRIANGLES, 0, 6);
}
// TODO: render the minimap in a framebuffer and just draw the frambuffer texture
// most of the time, updating the framebuffer twice a frame.
// Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling
// (those operations cause a gpu sync, which slows down the way gpu works)
void CMiniMap::Draw()
{
PROFILE3("render minimap");
// The terrain isn't actually initialized until the map is loaded, which
// happens when the game is started, so abort until then.
if (!(GetGUI() && g_Game && g_Game->IsGameStarted()))
return;
CSimulation2* sim = g_Game->GetSimulation2();
CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY);
ENSURE(cmpRangeManager);
// Set our globals in case they hadn't been set before
m_Camera = g_Game->GetView()->GetCamera();
m_Terrain = g_Game->GetWorld()->GetTerrain();
m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left);
m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top);
m_MapSize = m_Terrain->GetVerticesPerSide();
m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize);
m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f);
if (!m_TerrainTexture || g_GameRestarted)
CreateTextures();
// only update 2x / second
// (note: since units only move a few pixels per second on the minimap,
// we can get away with infrequent updates; this is slow)
// TODO: Update all but camera at same speed as simulation
static double last_time;
const double cur_time = timer_Time();
const bool doUpdate = cur_time - last_time > 0.5;
if (doUpdate)
{
last_time = cur_time;
if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight)
RebuildTerrainTexture();
}
const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom;
const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top;
const float z = GetBufferedZ();
const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize;
const float angle = GetAngle();
const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f);
// Disable depth updates to prevent apparent z-fighting-related issues
// with some drivers causing units to get drawn behind the texture.
glDepthMask(0);
CShaderProgramPtr shader;
CShaderTechniquePtr tech;
CShaderDefines baseDefines;
baseDefines.Add(str_MINIMAP_BASE, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines);
tech->BeginPass();
shader = tech->GetShader();
// Draw the main textured quad
shader->BindTexture(str_baseTex, m_TerrainTexture);
const CMatrix3D baseTransform = GetDefaultGuiMatrix();
CMatrix3D baseTextureTransform;
baseTextureTransform.SetIdentity();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, baseTextureTransform);
DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z);
// Draw territory boundaries
glEnable(GL_BLEND);
CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture();
shader->BindTexture(str_baseTex, territoryTexture.GetTexture());
const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, *territoryTransform);
DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
tech->EndPass();
// Draw the LOS quad in black, using alpha values from the LOS texture
CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture();
CShaderDefines losDefines;
losDefines.Add(str_MINIMAP_LOS, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->BindTexture(str_baseTex, losTexture.GetTexture());
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_textureTransform, *losTransform);
DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z);
tech->EndPass();
glDisable(GL_BLEND);
PROFILE_START("minimap units");
CShaderDefines pointDefines;
pointDefines.Add(str_MINIMAP_POINT, str_1);
tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines);
tech->BeginPass();
shader = tech->GetShader();
shader->Uniform(str_transform, baseTransform);
shader->Uniform(str_pointSize, 3.f);
CMatrix3D unitMatrix;
unitMatrix.SetIdentity();
// Center the minimap on the origin of the axis of rotation.
unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f);
// Rotate the map.
unitMatrix.RotateZ(angle);
// Scale square maps to fit.
unitMatrix.Scale(unitScale, unitScale, 1.f);
// Move the minimap back to it's starting position.
unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f);
// Move the minimap to it's final location.
unitMatrix.Translate(x, y, z);
// Apply the gui matrix.
unitMatrix *= GetDefaultGuiMatrix();
// Load the transform into the shader.
shader->Uniform(str_transform, unitMatrix);
const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE);
CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap);
if (doUpdate)
{
VertexArrayIterator attrPos = m_AttributePos.GetIterator();
VertexArrayIterator attrColor = m_AttributeColor.GetIterator();
m_EntitiesDrawn = 0;
MinimapUnitVertex v;
std::vector pingingVertices;
pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2);
if (cur_time > m_NextBlinkTime)
{
m_BlinkState = !m_BlinkState;
m_NextBlinkTime = cur_time + m_HalfBlinkDuration;
}
entity_pos_t posX, posZ;
for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it)
{
ICmpMinimap* cmpMinimap = static_cast(it->second);
if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ))
{
ICmpRangeManager::ELosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer());
if (vis != ICmpRangeManager::VIS_HIDDEN)
{
v.a = 255;
v.x = posX.ToFloat() * sx;
v.y = -posZ.ToFloat() * sy;
// Check minimap pinging to indicate something
if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration))
{
v.r = 255; // ping color is white
v.g = 255;
v.b = 255;
pingingVertices.push_back(v);
}
else
{
addVertex(v, attrColor, attrPos);
++m_EntitiesDrawn;
}
}
}
}
// Add the pinged vertices at the end, so they are drawn on top
for (size_t v = 0; v < pingingVertices.size(); ++v)
{
addVertex(pingingVertices[v], attrColor, attrPos);
++m_EntitiesDrawn;
}
ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN);
m_VertexArray.Upload();
}
m_VertexArray.PrepareForRendering();
if (m_EntitiesDrawn > 0)
{
#if !CONFIG2_GLES
if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
glEnable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
u8* indexBase = m_IndexArray.Bind();
u8* base = m_VertexArray.Bind();
const GLsizei stride = (GLsizei)m_VertexArray.GetStride();
shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset);
shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset);
shader->AssertPointersBound();
if (!g_Renderer.m_SkipSubmit)
glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase);
g_Renderer.GetStats().m_DrawCalls++;
CVertexBuffer::Unbind();
#if !CONFIG2_GLES
if (g_Renderer.GetRenderPath() == CRenderer::RP_SHADER)
glDisable(GL_VERTEX_PROGRAM_POINT_SIZE);
#endif
}
tech->EndPass();
DrawViewRect(unitMatrix);
PROFILE_END("minimap units");
// Reset depth mask
glDepthMask(1);
}
void CMiniMap::CreateTextures()
{
Destroy();
// Create terrain texture
glGenTextures(1, &m_TerrainTexture);
g_Renderer.BindTexture(0, m_TerrainTexture);
// Initialise texture with solid black, for the areas we don't
// overwrite with glTexSubImage2D later
u32* texData = new u32[m_TextureSize * m_TextureSize];
for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i)
texData[i] = 0xFF000000;
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData);
delete[] texData;
m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)];
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
// Rebuild and upload both of them
RebuildTerrainTexture();
}
void CMiniMap::RebuildTerrainTexture()
{
u32 x = 0;
u32 y = 0;
u32 w = m_MapSize - 1;
u32 h = m_MapSize - 1;
m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight;
m_TerrainDirty = false;
for (u32 j = 0; j < h; ++j)
{
u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x;
for (u32 i = 0; i < w; ++i)
{
float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j)
+ m_Terrain->GetVertexGroundLevel((int)i+1, (int)j)
+ m_Terrain->GetVertexGroundLevel((int)i, (int)j+1)
+ m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1)
) / 4.0f;
if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight)
{
// shallow water
*dataPtr++ = 0xffc09870;
}
else if (avgHeight < m_WaterHeight)
{
// Set water as constant color for consistency on different maps
*dataPtr++ = 0xffa07850;
}
else
{
int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8;
int val = (hmap / 3) + 170;
u32 color = 0xFFFFFFFF;
CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j);
if (mp)
{
CTerrainTextureEntry* tex = mp->GetTextureEntry();
if (tex)
{
// If the texture can't be loaded yet, set the dirty flags
// so we'll try regenerating the terrain texture again soon
if(!tex->GetTexture()->TryLoad())
m_TerrainDirty = true;
color = tex->GetBaseColor();
}
}
*dataPtr++ = ScaleColor(color, float(val) / 255.0f);
}
}
}
// Upload the texture
g_Renderer.BindTexture(0, m_TerrainTexture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData);
}
void CMiniMap::Destroy()
{
if (m_TerrainTexture)
{
glDeleteTextures(1, &m_TerrainTexture);
m_TerrainTexture = 0;
}
SAFE_ARRAY_DELETE(m_TerrainData);
}
Index: ps/trunk/source/gui/MiniMap.h
===================================================================
--- ps/trunk/source/gui/MiniMap.h (revision 22604)
+++ ps/trunk/source/gui/MiniMap.h (revision 22605)
@@ -1,114 +1,114 @@
/* Copyright (C) 2019 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 of the License, or
* (at your option) any later version.
*
* 0 A.D. is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with 0 A.D. If not, see .
*/
#ifndef INCLUDED_MINIMAP
#define INCLUDED_MINIMAP
#include "gui/GUI.h"
#include "renderer/VertexArray.h"
-
class CCamera;
+class CMatrix3D;
class CTerrain;
class CMiniMap : public IGUIObject
{
GUI_OBJECT(CMiniMap)
public:
CMiniMap(CGUI* pGUI);
virtual ~CMiniMap();
protected:
virtual void Draw();
/**
* @see IGUIObject#HandleMessage()
*/
virtual void HandleMessage(SGUIMessage& Message);
/**
* @see IGUIObject#MouseOver()
*/
virtual bool MouseOver();
// create the minimap textures
void CreateTextures();
// rebuild the terrain texture map
void RebuildTerrainTexture();
// destroy and free any memory and textures
void Destroy();
void SetCameraPos();
void FireWorldClickEvent(int button, int clicks);
// the terrain we are mini-mapping
const CTerrain* m_Terrain;
const CCamera* m_Camera;
//Whether or not the mouse is currently down
bool m_Clicking;
// minimap texture handles
GLuint m_TerrainTexture;
// texture data
u32* m_TerrainData;
// whether we need to regenerate the terrain texture
bool m_TerrainDirty;
ssize_t m_Width, m_Height;
// map size
ssize_t m_MapSize;
// texture size
GLsizei m_TextureSize;
// 1.f if map is circular or 1.414f if square (to shrink it inside the circle)
float m_MapScale;
// maximal water height to allow the passage of a unit (for underwater shallows).
float m_ShallowPassageHeight;
float m_WaterHeight;
void DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z);
void DrawViewRect(CMatrix3D transform);
void GetMouseWorldCoordinates(float& x, float& z);
float GetAngle();
VertexIndexArray m_IndexArray;
VertexArray m_VertexArray;
VertexArray::Attribute m_AttributePos;
VertexArray::Attribute m_AttributeColor;
size_t m_EntitiesDrawn;
double m_PingDuration;
double m_HalfBlinkDuration;
double m_NextBlinkTime;
bool m_BlinkState;
};
#endif // INCLUDED_MINIMAP
Index: ps/trunk/source/ps/CConsole.cpp
===================================================================
--- ps/trunk/source/ps/CConsole.cpp (revision 22604)
+++ ps/trunk/source/ps/CConsole.cpp (revision 22605)
@@ -1,695 +1,696 @@
/* Copyright (C) 2019 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 .
*/
/*
* Implements the in-game console with scripting support.
*/
#include "precompiled.h"
#include
#include "CConsole.h"
#include "graphics/FontMetrics.h"
#include "graphics/ShaderManager.h"
#include "graphics/TextRenderer.h"
-#include "gui/GUIutil.h"
+#include "gui/CGUI.h"
#include "gui/GUIManager.h"
+#include "gui/GUIMatrix.h"
#include "lib/ogl.h"
#include "lib/sysdep/clipboard.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "maths/MathUtil.h"
#include "network/NetClient.h"
#include "network/NetServer.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/GameSetup/Config.h"
#include "ps/Globals.h"
#include "ps/Hotkey.h"
#include "ps/Profile.h"
#include "ps/Pyrogenesis.h"
#include "renderer/Renderer.h"
#include "scriptinterface/ScriptInterface.h"
CConsole* g_Console = 0;
CConsole::CConsole()
{
m_bToggle = false;
m_bVisible = false;
m_fVisibleFrac = 0.0f;
m_szBuffer = new wchar_t[CONSOLE_BUFFER_SIZE];
FlushBuffer();
m_iMsgHistPos = 1;
m_charsPerPage = 0;
m_prevTime = 0.0;
m_bCursorVisState = true;
m_cursorBlinkRate = 0.5;
InsertMessage("[ 0 A.D. Console v0.14 ]");
InsertMessage("");
}
CConsole::~CConsole()
{
delete[] m_szBuffer;
}
void CConsole::SetSize(float X, float Y, float W, float H)
{
m_fX = X;
m_fY = Y;
m_fWidth = W;
m_fHeight = H;
}
void CConsole::UpdateScreenSize(int w, int h)
{
float height = h * 0.6f;
SetSize(0, 0, w / g_GuiScale, height / g_GuiScale);
}
void CConsole::ToggleVisible()
{
m_bToggle = true;
m_bVisible = !m_bVisible;
// TODO: this should be based on input focus, not visibility
if (m_bVisible)
SDL_StartTextInput();
else
SDL_StopTextInput();
}
void CConsole::SetVisible(bool visible)
{
if (visible != m_bVisible)
m_bToggle = true;
m_bVisible = visible;
if (visible)
{
m_prevTime = 0.0;
m_bCursorVisState = false;
}
}
void CConsole::SetCursorBlinkRate(double rate)
{
m_cursorBlinkRate = rate;
}
void CConsole::FlushBuffer()
{
// Clear the buffer and set the cursor and length to 0
memset(m_szBuffer, '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE);
m_iBufferPos = m_iBufferLength = 0;
}
void CConsole::Update(const float deltaRealTime)
{
if(m_bToggle)
{
const float AnimateTime = .30f;
const float Delta = deltaRealTime / AnimateTime;
if(m_bVisible)
{
m_fVisibleFrac += Delta;
if(m_fVisibleFrac > 1.0f)
{
m_fVisibleFrac = 1.0f;
m_bToggle = false;
}
}
else
{
m_fVisibleFrac -= Delta;
if(m_fVisibleFrac < 0.0f)
{
m_fVisibleFrac = 0.0f;
m_bToggle = false;
}
}
}
}
//Render Manager.
void CConsole::Render()
{
if (! (m_bVisible || m_bToggle) ) return;
PROFILE3_GPU("console");
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
solidTech->BeginPass();
CShaderProgramPtr solidShader = solidTech->GetShader();
CMatrix3D transform = GetDefaultGuiMatrix();
// animation: slide in from top of screen
const float DeltaY = (1.0f - m_fVisibleFrac) * m_fHeight;
transform.PostTranslate(m_fX, m_fY - DeltaY, 0.0f); // move to window position
solidShader->Uniform(str_transform, transform);
DrawWindow(solidShader);
solidTech->EndPass();
CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
textTech->BeginPass();
CTextRenderer textRenderer(textTech->GetShader());
textRenderer.Font(CStrIntern(CONSOLE_FONT));
textRenderer.SetTransform(transform);
DrawHistory(textRenderer);
DrawBuffer(textRenderer);
textRenderer.Render();
textTech->EndPass();
glDisable(GL_BLEND);
}
void CConsole::DrawWindow(CShaderProgramPtr& shader)
{
float boxVerts[] = {
m_fWidth, 0.0f,
1.0f, 0.0f,
1.0f, m_fHeight-1.0f,
m_fWidth, m_fHeight-1.0f
};
shader->VertexPointer(2, GL_FLOAT, 0, boxVerts);
// Draw Background
// Set the color to a translucent blue
shader->Uniform(str_color, 0.0f, 0.0f, 0.5f, 0.6f);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
// Draw Border
// Set the color to a translucent yellow
shader->Uniform(str_color, 0.5f, 0.5f, 0.0f, 0.6f);
shader->AssertPointersBound();
glDrawArrays(GL_LINE_LOOP, 0, 4);
if (m_fHeight > m_iFontHeight + 4)
{
float lineVerts[] = {
0.0f, m_fHeight - (float)m_iFontHeight - 4.0f,
m_fWidth, m_fHeight - (float)m_iFontHeight - 4.0f
};
shader->VertexPointer(2, GL_FLOAT, 0, lineVerts);
shader->AssertPointersBound();
glDrawArrays(GL_LINES, 0, 2);
}
}
void CConsole::DrawHistory(CTextRenderer& textRenderer)
{
int i = 1;
std::deque::iterator Iter; //History iterator
std::lock_guard lock(m_Mutex); // needed for safe access to m_deqMsgHistory
textRenderer.Color(1.0f, 1.0f, 1.0f);
for (Iter = m_deqMsgHistory.begin();
Iter != m_deqMsgHistory.end()
&& (((i - m_iMsgHistPos + 1) * m_iFontHeight) < m_fHeight);
++Iter)
{
if (i >= m_iMsgHistPos)
textRenderer.Put(9.0f, m_fHeight - (float)m_iFontOffset - (float)m_iFontHeight * (i - m_iMsgHistPos + 1), Iter->c_str());
i++;
}
}
// Renders the buffer to the screen.
void CConsole::DrawBuffer(CTextRenderer& textRenderer)
{
if (m_fHeight < m_iFontHeight)
return;
CMatrix3D savedTransform = textRenderer.GetTransform();
textRenderer.Translate(2.0f, m_fHeight - (float)m_iFontOffset + 1.0f, 0.0f);
textRenderer.Color(1.0f, 1.0f, 0.0f);
textRenderer.PutAdvance(L"]");
textRenderer.Color(1.0f, 1.0f, 1.0f);
if (m_iBufferPos == 0)
DrawCursor(textRenderer);
for (int i = 0; i < m_iBufferLength; i++)
{
textRenderer.PrintfAdvance(L"%lc", m_szBuffer[i]);
if (m_iBufferPos-1 == i)
DrawCursor(textRenderer);
}
textRenderer.SetTransform(savedTransform);
}
void CConsole::DrawCursor(CTextRenderer& textRenderer)
{
if (m_cursorBlinkRate > 0.0)
{
// check if the cursor visibility state needs to be changed
double currTime = timer_Time();
if ((currTime - m_prevTime) >= m_cursorBlinkRate)
{
m_bCursorVisState = !m_bCursorVisState;
m_prevTime = currTime;
}
}
else
{
// Should always be visible
m_bCursorVisState = true;
}
if(m_bCursorVisState)
{
// Slightly translucent yellow
textRenderer.Color(1.0f, 1.0f, 0.0f, 0.8f);
// Cursor character is chosen to be an underscore
textRenderer.Put(0.0f, 0.0f, L"_");
// Revert to the standard text color
textRenderer.Color(1.0f, 1.0f, 1.0f);
}
}
//Inserts a character into the buffer.
void CConsole::InsertChar(const int szChar, const wchar_t cooked)
{
static int iHistoryPos = -1;
if (!m_bVisible) return;
switch (szChar)
{
case SDLK_RETURN:
iHistoryPos = -1;
m_iMsgHistPos = 1;
ProcessBuffer(m_szBuffer);
FlushBuffer();
return;
case SDLK_TAB:
// Auto Complete
return;
case SDLK_BACKSPACE:
if (IsEmpty() || IsBOB()) return;
if (m_iBufferPos == m_iBufferLength)
m_szBuffer[m_iBufferPos - 1] = '\0';
else
{
for (int j = m_iBufferPos-1; j < m_iBufferLength-1; j++)
m_szBuffer[j] = m_szBuffer[j+1]; // move chars to left
m_szBuffer[m_iBufferLength-1] = '\0';
}
m_iBufferPos--;
m_iBufferLength--;
return;
case SDLK_DELETE:
if (IsEmpty() || IsEOB()) return;
if (m_iBufferPos == m_iBufferLength-1)
{
m_szBuffer[m_iBufferPos] = '\0';
m_iBufferLength--;
}
else
{
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
{
// Make Ctrl-Delete delete up to end of line
m_szBuffer[m_iBufferPos] = '\0';
m_iBufferLength = m_iBufferPos;
}
else
{
// Delete just one char and move the others left
for(int j=m_iBufferPos; j lock(m_Mutex); // needed for safe access to m_deqMsgHistory
int linesShown = (int)m_fHeight/m_iFontHeight - 4;
m_iMsgHistPos = clamp((int)m_deqMsgHistory.size() - linesShown, 1, (int)m_deqMsgHistory.size());
}
else
{
m_iBufferPos = 0;
}
return;
case SDLK_END:
if (g_keys[SDLK_RCTRL] || g_keys[SDLK_LCTRL])
{
m_iMsgHistPos = 1;
}
else
{
m_iBufferPos = m_iBufferLength;
}
return;
case SDLK_LEFT:
if (m_iBufferPos) m_iBufferPos--;
return;
case SDLK_RIGHT:
if (m_iBufferPos != m_iBufferLength) m_iBufferPos++;
return;
// BEGIN: Buffer History Lookup
case SDLK_UP:
if (m_deqBufHistory.size() && iHistoryPos != (int)m_deqBufHistory.size() - 1)
{
iHistoryPos++;
SetBuffer(m_deqBufHistory.at(iHistoryPos).c_str());
m_iBufferPos = m_iBufferLength;
}
return;
case SDLK_DOWN:
if (m_deqBufHistory.size())
{
if (iHistoryPos > 0)
{
iHistoryPos--;
SetBuffer(m_deqBufHistory.at(iHistoryPos).c_str());
m_iBufferPos = m_iBufferLength;
}
else if (iHistoryPos == 0)
{
iHistoryPos--;
FlushBuffer();
}
}
return;
// END: Buffer History Lookup
// BEGIN: Message History Lookup
case SDLK_PAGEUP:
{
std::lock_guard lock(m_Mutex); // needed for safe access to m_deqMsgHistory
if (m_iMsgHistPos != (int)m_deqMsgHistory.size()) m_iMsgHistPos++;
return;
}
case SDLK_PAGEDOWN:
if (m_iMsgHistPos != 1) m_iMsgHistPos--;
return;
// END: Message History Lookup
default: //Insert a character
if (IsFull()) return;
if (cooked == 0) return;
if (IsEOB()) //are we at the end of the buffer?
m_szBuffer[m_iBufferPos] = cooked; //cat char onto end
else
{ //we need to insert
int i;
for(i=m_iBufferLength; i>m_iBufferPos; i--)
m_szBuffer[i] = m_szBuffer[i-1]; // move chars to right
m_szBuffer[i] = cooked;
}
m_iBufferPos++;
m_iBufferLength++;
return;
}
}
void CConsole::InsertMessage(const std::string& message)
{
// (TODO: this text-wrapping is rubbish since we now use variable-width fonts)
//Insert newlines to wraparound text where needed
std::wstring wrapAround = wstring_from_utf8(message.c_str());
std::wstring newline(L"\n");
size_t oldNewline=0;
size_t distance;
//make sure everything has been initialized
if ( m_charsPerPage != 0 )
{
while ( oldNewline+m_charsPerPage < wrapAround.length() )
{
distance = wrapAround.find(newline, oldNewline) - oldNewline;
if ( distance > m_charsPerPage )
{
oldNewline += m_charsPerPage;
wrapAround.insert( oldNewline++, newline );
}
else
oldNewline += distance+1;
}
}
// Split into lines and add each one individually
oldNewline = 0;
{
std::lock_guard lock(m_Mutex); // needed for safe access to m_deqMsgHistory
while ( (distance = wrapAround.find(newline, oldNewline)) != wrapAround.npos)
{
distance -= oldNewline;
m_deqMsgHistory.push_front(wrapAround.substr(oldNewline, distance));
oldNewline += distance+1;
}
m_deqMsgHistory.push_front(wrapAround.substr(oldNewline));
}
}
const wchar_t* CConsole::GetBuffer()
{
m_szBuffer[m_iBufferLength] = 0;
return( m_szBuffer );
}
void CConsole::SetBuffer(const wchar_t* szMessage)
{
int oldBufferPos = m_iBufferPos; // remember since FlushBuffer will set it to 0
FlushBuffer();
wcsncpy(m_szBuffer, szMessage, CONSOLE_BUFFER_SIZE);
m_szBuffer[CONSOLE_BUFFER_SIZE-1] = 0;
m_iBufferLength = (int)wcslen(m_szBuffer);
m_iBufferPos = std::min(oldBufferPos, m_iBufferLength);
}
void CConsole::UseHistoryFile(const VfsPath& filename, int max_history_lines)
{
m_MaxHistoryLines = max_history_lines;
m_sHistoryFile = filename;
LoadHistory();
}
void CConsole::ProcessBuffer(const wchar_t* szLine)
{
shared_ptr pScriptInterface = g_GUI->GetActiveGUI()->GetScriptInterface();
JSContext* cx = pScriptInterface->GetContext();
JSAutoRequest rq(cx);
if (szLine == NULL) return;
if (wcslen(szLine) <= 0) return;
ENSURE(wcslen(szLine) < CONSOLE_BUFFER_SIZE);
m_deqBufHistory.push_front(szLine);
SaveHistory(); // Do this each line for the moment; if a script causes
// a crash it's a useful record.
// Process it as JavaScript
JS::RootedValue rval(cx);
pScriptInterface->Eval(szLine, &rval);
if (!rval.isUndefined())
InsertMessage(pScriptInterface->ToString(&rval));
}
void CConsole::LoadHistory()
{
// note: we don't care if this file doesn't exist or can't be read;
// just don't load anything in that case.
// do this before LoadFile to avoid an error message if file not found.
if (!VfsFileExists(m_sHistoryFile))
return;
shared_ptr buf; size_t buflen;
if (g_VFS->LoadFile(m_sHistoryFile, buf, buflen) < 0)
return;
CStr bytes ((char*)buf.get(), buflen);
CStrW str (bytes.FromUTF8());
size_t pos = 0;
while (pos != CStrW::npos)
{
pos = str.find('\n');
if (pos != CStrW::npos)
{
if (pos > 0)
m_deqBufHistory.push_front(str.Left(str[pos-1] == '\r' ? pos - 1 : pos));
str = str.substr(pos + 1);
}
else if (str.length() > 0)
m_deqBufHistory.push_front(str);
}
}
void CConsole::SaveHistory()
{
WriteBuffer buffer;
const int linesToSkip = (int)m_deqBufHistory.size() - m_MaxHistoryLines;
std::deque::reverse_iterator it = m_deqBufHistory.rbegin();
if(linesToSkip > 0)
std::advance(it, linesToSkip);
for (; it != m_deqBufHistory.rend(); ++it)
{
CStr8 line = CStrW(*it).ToUTF8();
buffer.Append(line.data(), line.length());
static const char newline = '\n';
buffer.Append(&newline, 1);
}
g_VFS->CreateFile(m_sHistoryFile, buffer.Data(), buffer.Size());
}
static bool isUnprintableChar(SDL_Keysym key)
{
switch (key.sym)
{
// We want to allow some, which are handled specially
case SDLK_RETURN: case SDLK_TAB:
case SDLK_BACKSPACE: case SDLK_DELETE:
case SDLK_HOME: case SDLK_END:
case SDLK_LEFT: case SDLK_RIGHT:
case SDLK_UP: case SDLK_DOWN:
case SDLK_PAGEUP: case SDLK_PAGEDOWN:
return false;
// Ignore the others
default:
return true;
}
}
InReaction conInputHandler(const SDL_Event_* ev)
{
if ((int)ev->ev.type == SDL_HOTKEYDOWN)
{
std::string hotkey = static_cast(ev->ev.user.data1);
if (hotkey == "console.toggle")
{
g_Console->ToggleVisible();
return IN_HANDLED;
}
else if (g_Console->IsActive() && hotkey == "copy")
{
sys_clipboard_set(g_Console->GetBuffer());
return IN_HANDLED;
}
else if (g_Console->IsActive() && hotkey == "paste")
{
wchar_t* text = sys_clipboard_get();
if (text)
{
for (wchar_t* c = text; *c; c++)
g_Console->InsertChar(0, *c);
sys_clipboard_free(text);
}
return IN_HANDLED;
}
}
if (!g_Console->IsActive())
return IN_PASS;
// In SDL2, we no longer get Unicode wchars via SDL_Keysym
// we use text input events instead and they provide UTF-8 chars
if (ev->ev.type == SDL_TEXTINPUT && !HotkeyIsPressed("console.toggle"))
{
// TODO: this could be more efficient with an interface to insert UTF-8 strings directly
std::wstring wstr = wstring_from_utf8(ev->ev.text.text);
for (size_t i = 0; i < wstr.length(); ++i)
g_Console->InsertChar(0, wstr[i]);
return IN_HANDLED;
}
// TODO: text editing events for IME support
if (ev->ev.type != SDL_KEYDOWN)
return IN_PASS;
int sym = ev->ev.key.keysym.sym;
// Stop unprintable characters (ctrl+, alt+ and escape),
// also prevent ` and/or ~ appearing in console every time it's toggled.
if (!isUnprintableChar(ev->ev.key.keysym) &&
!HotkeyIsPressed("console.toggle"))
{
g_Console->InsertChar(sym, 0);
return IN_HANDLED;
}
return IN_PASS;
}
Index: ps/trunk/source/ps/ProfileViewer.cpp
===================================================================
--- ps/trunk/source/ps/ProfileViewer.cpp (revision 22604)
+++ ps/trunk/source/ps/ProfileViewer.cpp (revision 22605)
@@ -1,623 +1,623 @@
/* Copyright (C) 2019 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 .
*/
/*
* Implementation of profile display (containing only display routines,
* the data model(s) are implemented elsewhere).
*/
#include "precompiled.h"
-#include
-#include
-
#include "ProfileViewer.h"
#include "graphics/FontMetrics.h"
-#include "gui/GUIutil.h"
#include "graphics/ShaderManager.h"
#include "graphics/TextRenderer.h"
+#include "gui/GUIMatrix.h"
+#include "lib/external_libraries/libsdl.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Hotkey.h"
#include "ps/Profile.h"
-#include "lib/external_libraries/libsdl.h"
#include "renderer/Renderer.h"
#include "scriptinterface/ScriptInterface.h"
+#include
+#include
+
extern int g_xres, g_yres;
struct CProfileViewerInternals
{
NONCOPYABLE(CProfileViewerInternals); // because of the ofstream
public:
CProfileViewerInternals() {}
/// Whether the profiling display is currently visible
bool profileVisible;
/// List of root tables
std::vector rootTables;
/// Path from a root table (path[0]) to the currently visible table (path[size-1])
std::vector path;
/// Helper functions
void TableIsDeleted(AbstractProfileTable* table);
void NavigateTree(int id);
/// File for saved profile output (reset when the game is restarted)
std::ofstream outputStream;
};
///////////////////////////////////////////////////////////////////////////////////////////////
// AbstractProfileTable implementation
AbstractProfileTable::~AbstractProfileTable()
{
if (CProfileViewer::IsInitialised())
{
g_ProfileViewer.m->TableIsDeleted(this);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////
// CProfileViewer implementation
// AbstractProfileTable got deleted, make sure we have no dangling pointers
void CProfileViewerInternals::TableIsDeleted(AbstractProfileTable* table)
{
for(int idx = (int)rootTables.size()-1; idx >= 0; --idx)
{
if (rootTables[idx] == table)
rootTables.erase(rootTables.begin() + idx);
}
for(size_t idx = 0; idx < path.size(); ++idx)
{
if (path[idx] != table)
continue;
path.erase(path.begin() + idx, path.end());
if (path.size() == 0)
profileVisible = false;
}
}
// Move into child tables or return to parent tables based on the given number
void CProfileViewerInternals::NavigateTree(int id)
{
if (id == 0)
{
if (path.size() > 1)
path.pop_back();
}
else
{
AbstractProfileTable* table = path[path.size() - 1];
size_t numrows = table->GetNumberRows();
for(size_t row = 0; row < numrows; ++row)
{
AbstractProfileTable* child = table->GetChild(row);
if (!child)
continue;
--id;
if (id == 0)
{
path.push_back(child);
break;
}
}
}
}
// Construction/Destruction
CProfileViewer::CProfileViewer()
{
m = new CProfileViewerInternals;
m->profileVisible = false;
}
CProfileViewer::~CProfileViewer()
{
delete m;
}
// Render
void CProfileViewer::RenderProfile()
{
if (!m->profileVisible)
return;
if (!m->path.size())
{
m->profileVisible = false;
return;
}
PROFILE3_GPU("profile viewer");
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
AbstractProfileTable* table = m->path[m->path.size() - 1];
const std::vector& columns = table->GetColumns();
size_t numrows = table->GetNumberRows();
CStrIntern font_name("mono-stroke-10");
CFontMetrics font(font_name);
int lineSpacing = font.GetLineSpacing();
// Render background
GLint estimate_height;
GLint estimate_width;
estimate_width = 50;
for(size_t i = 0; i < columns.size(); ++i)
estimate_width += (GLint)columns[i].width;
estimate_height = 3 + (GLint)numrows;
if (m->path.size() > 1)
estimate_height += 2;
estimate_height = lineSpacing*estimate_height;
CShaderTechniquePtr solidTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
solidTech->BeginPass();
CShaderProgramPtr solidShader = solidTech->GetShader();
solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.5f);
CMatrix3D transform = GetDefaultGuiMatrix();
solidShader->Uniform(str_transform, transform);
float backgroundVerts[] = {
(float)estimate_width, 0.0f,
0.0f, 0.0f,
0.0f, (float)estimate_height,
0.0f, (float)estimate_height,
(float)estimate_width, (float)estimate_height,
(float)estimate_width, 0.0f
};
solidShader->VertexPointer(2, GL_FLOAT, 0, backgroundVerts);
solidShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
transform.PostTranslate(22.0f, lineSpacing*3.0f, 0.0f);
solidShader->Uniform(str_transform, transform);
// Draw row backgrounds
for (size_t row = 0; row < numrows; ++row)
{
if (row % 2)
solidShader->Uniform(str_color, 1.0f, 1.0f, 1.0f, 0.1f);
else
solidShader->Uniform(str_color, 0.0f, 0.0f, 0.0f, 0.1f);
float rowVerts[] = {
-22.f, 2.f,
estimate_width-22.f, 2.f,
estimate_width-22.f, 2.f-lineSpacing,
estimate_width-22.f, 2.f-lineSpacing,
-22.f, 2.f-lineSpacing,
-22.f, 2.f
};
solidShader->VertexPointer(2, GL_FLOAT, 0, rowVerts);
solidShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
transform.PostTranslate(0.0f, lineSpacing, 0.0f);
solidShader->Uniform(str_transform, transform);
}
solidTech->EndPass();
// Print table and column titles
CShaderTechniquePtr textTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_text);
textTech->BeginPass();
CTextRenderer textRenderer(textTech->GetShader());
textRenderer.Font(font_name);
textRenderer.Color(1.0f, 1.0f, 1.0f);
textRenderer.PrintfAt(2.0f, lineSpacing, L"%hs", table->GetTitle().c_str());
textRenderer.Translate(22.0f, lineSpacing*2.0f, 0.0f);
float colX = 0.0f;
for (size_t col = 0; col < columns.size(); ++col)
{
CStrW text = columns[col].title.FromUTF8();
int w, h;
font.CalculateStringSize(text.c_str(), w, h);
float x = colX;
if (col > 0) // right-align all but the first column
x += columns[col].width - w;
textRenderer.Put(x, 0.0f, text.c_str());
colX += columns[col].width;
}
textRenderer.Translate(0.0f, lineSpacing, 0.0f);
// Print rows
int currentExpandId = 1;
for (size_t row = 0; row < numrows; ++row)
{
if (table->IsHighlightRow(row))
textRenderer.Color(1.0f, 0.5f, 0.5f);
else
textRenderer.Color(1.0f, 1.0f, 1.0f);
if (table->GetChild(row))
{
textRenderer.PrintfAt(-15.0f, 0.0f, L"%d", currentExpandId);
currentExpandId++;
}
float colX = 0.0f;
for (size_t col = 0; col < columns.size(); ++col)
{
CStrW text = table->GetCellText(row, col).FromUTF8();
int w, h;
font.CalculateStringSize(text.c_str(), w, h);
float x = colX;
if (col > 0) // right-align all but the first column
x += columns[col].width - w;
textRenderer.Put(x, 0.0f, text.c_str());
colX += columns[col].width;
}
textRenderer.Translate(0.0f, lineSpacing, 0.0f);
}
textRenderer.Color(1.0f, 1.0f, 1.0f);
if (m->path.size() > 1)
{
textRenderer.Translate(0.0f, lineSpacing, 0.0f);
textRenderer.Put(-15.0f, 0.0f, L"0");
textRenderer.Put(0.0f, 0.0f, L"back to parent");
}
textRenderer.Render();
textTech->EndPass();
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
}
// Handle input
InReaction CProfileViewer::Input(const SDL_Event_* ev)
{
switch(ev->ev.type)
{
case SDL_KEYDOWN:
{
if (!m->profileVisible)
break;
int k = ev->ev.key.keysym.sym;
if (k >= SDLK_0 && k <= SDLK_9)
{
m->NavigateTree(k - SDLK_0);
return IN_HANDLED;
}
break;
}
case SDL_HOTKEYDOWN:
std::string hotkey = static_cast(ev->ev.user.data1);
if( hotkey == "profile.toggle" )
{
if (!m->profileVisible)
{
if (m->rootTables.size())
{
m->profileVisible = true;
m->path.push_back(m->rootTables[0]);
}
}
else
{
size_t i;
for(i = 0; i < m->rootTables.size(); ++i)
{
if (m->rootTables[i] == m->path[0])
break;
}
i++;
m->path.clear();
if (i < m->rootTables.size())
{
m->path.push_back(m->rootTables[i]);
}
else
{
m->profileVisible = false;
}
}
return( IN_HANDLED );
}
else if( hotkey == "profile.save" )
{
SaveToFile();
return( IN_HANDLED );
}
break;
}
return( IN_PASS );
}
InReaction CProfileViewer::InputThunk(const SDL_Event_* ev)
{
if (CProfileViewer::IsInitialised())
return g_ProfileViewer.Input(ev);
return IN_PASS;
}
// Add a table to the list of roots
void CProfileViewer::AddRootTable(AbstractProfileTable* table, bool front)
{
if (front)
m->rootTables.insert(m->rootTables.begin(), table);
else
m->rootTables.push_back(table);
}
namespace
{
struct WriteTable
{
std::ofstream& f;
WriteTable(std::ofstream& f) : f(f) {}
void operator() (AbstractProfileTable* table)
{
std::vector data; // 2d array of (rows+head)*columns elements
const std::vector& columns = table->GetColumns();
// Add column headers to 'data'
for (std::vector::const_iterator col_it = columns.begin();
col_it != columns.end(); ++col_it)
data.push_back(col_it->title);
// Recursively add all profile data to 'data'
WriteRows(1, table, data);
// Calculate the width of each column ( = the maximum width of
// any value in that column)
std::vector columnWidths;
size_t cols = columns.size();
for (size_t c = 0; c < cols; ++c)
{
size_t max = 0;
for (size_t i = c; i < data.size(); i += cols)
max = std::max(max, data[i].length());
columnWidths.push_back(max);
}
// Output data as a formatted table:
f << "\n\n" << table->GetTitle() << "\n";
if (cols == 0) // avoid divide-by-zero
return;
for (size_t r = 0; r < data.size()/cols; ++r)
{
for (size_t c = 0; c < cols; ++c)
f << (c ? " | " : "\n")
<< data[r*cols + c].Pad(PS_TRIM_RIGHT, columnWidths[c]);
// Add dividers under some rows. (Currently only the first, since
// that contains the column headers.)
if (r == 0)
for (size_t c = 0; c < cols; ++c)
f << (c ? "-|-" : "\n")
<< CStr::Repeat("-", columnWidths[c]);
}
}
void WriteRows(int indent, AbstractProfileTable* table, std::vector& data)
{
const std::vector& columns = table->GetColumns();
for (size_t r = 0; r < table->GetNumberRows(); ++r)
{
// Do pretty tree-structure indenting
CStr indentation = CStr::Repeat("| ", indent-1);
if (r+1 == table->GetNumberRows())
indentation += "'-";
else
indentation += "|-";
for (size_t c = 0; c < columns.size(); ++c)
if (c == 0)
data.push_back(indentation + table->GetCellText(r, c));
else
data.push_back(table->GetCellText(r, c));
if (table->GetChild(r))
WriteRows(indent+1, table->GetChild(r), data);
}
}
private:
const WriteTable& operator=(const WriteTable&);
};
struct DumpTable
{
const ScriptInterface& m_ScriptInterface;
JS::PersistentRooted m_Root;
DumpTable(const ScriptInterface& scriptInterface, JS::HandleValue root) :
m_ScriptInterface(scriptInterface), m_Root(scriptInterface.GetJSRuntime(), root)
{
}
// std::for_each requires a move constructor and the use of JS::PersistentRooted apparently breaks a requirement for an
// automatic move constructor
DumpTable(DumpTable && original) :
m_ScriptInterface(original.m_ScriptInterface),
m_Root(original.m_ScriptInterface.GetJSRuntime(), original.m_Root.get())
{
}
void operator() (AbstractProfileTable* table)
{
JSContext* cx = m_ScriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue t(cx);
JS::RootedValue rows(cx, DumpRows(table));
m_ScriptInterface.Eval(L"({})", &t);
m_ScriptInterface.SetProperty(t, "cols", DumpCols(table));
m_ScriptInterface.SetProperty(t, "data", rows);
m_ScriptInterface.SetProperty(m_Root, table->GetTitle().c_str(), t);
}
std::vector DumpCols(AbstractProfileTable* table)
{
std::vector titles;
const std::vector& columns = table->GetColumns();
for (size_t c = 0; c < columns.size(); ++c)
titles.push_back(columns[c].title);
return titles;
}
JS::Value DumpRows(AbstractProfileTable* table)
{
JSContext* cx = m_ScriptInterface.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue data(cx);
m_ScriptInterface.CreateObject(&data);
const std::vector& columns = table->GetColumns();
for (size_t r = 0; r < table->GetNumberRows(); ++r)
{
JS::RootedValue row(cx);
m_ScriptInterface.CreateArray(&row);
m_ScriptInterface.SetProperty(data, table->GetCellText(r, 0).c_str(), row);
if (table->GetChild(r))
{
JS::RootedValue childRows(cx, DumpRows(table->GetChild(r)));
m_ScriptInterface.SetPropertyInt(row, 0, childRows);
}
for (size_t c = 1; c < columns.size(); ++c)
m_ScriptInterface.SetPropertyInt(row, c, table->GetCellText(r, c));
}
return data;
}
private:
const DumpTable& operator=(const DumpTable&);
};
bool SortByName(AbstractProfileTable* a, AbstractProfileTable* b)
{
return (a->GetName() < b->GetName());
}
}
void CProfileViewer::SaveToFile()
{
// Open the file, if necessary. If this method is called several times,
// the profile results will be appended to the previous ones from the same
// run.
if (! m->outputStream.is_open())
{
// Open the file. (It will be closed when the CProfileViewer
// destructor is called.)
OsPath path = psLogDir()/"profile.txt";
m->outputStream.open(OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc);
if (m->outputStream.fail())
{
LOGERROR("Failed to open profile log file");
return;
}
else
{
LOGMESSAGERENDER("Profiler snapshot saved to '%s'", path.string8());
}
}
time_t t;
time(&t);
m->outputStream << "================================================================\n\n";
m->outputStream << "PS profiler snapshot - " << asctime(localtime(&t));
std::vector tables = m->rootTables;
sort(tables.begin(), tables.end(), SortByName);
for_each(tables.begin(), tables.end(), WriteTable(m->outputStream));
m->outputStream << "\n\n================================================================\n";
m->outputStream.flush();
}
void CProfileViewer::ShowTable(const CStr& table)
{
m->path.clear();
if (table.length() > 0)
{
for (size_t i = 0; i < m->rootTables.size(); ++i)
{
if (m->rootTables[i]->GetName() == table)
{
m->path.push_back(m->rootTables[i]);
m->profileVisible = true;
return;
}
}
}
// No matching table found, so don't display anything
m->profileVisible = false;
}
Index: ps/trunk/source/renderer/ShadowMap.cpp
===================================================================
--- ps/trunk/source/renderer/ShadowMap.cpp (revision 22604)
+++ ps/trunk/source/renderer/ShadowMap.cpp (revision 22605)
@@ -1,788 +1,785 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2019 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 .
*/
/*
* Shadow mapping related texture and matrix management
*/
#include "precompiled.h"
-#include "gui/GUIutil.h"
-#include "lib/bits.h"
-#include "lib/ogl.h"
-#include "ps/CLogger.h"
-#include "ps/ConfigDB.h"
-#include "ps/Profile.h"
+#include "ShadowMap.h"
#include "graphics/LightEnv.h"
#include "graphics/ShaderManager.h"
-
+#include "gui/GUIMatrix.h"
+#include "lib/bits.h"
+#include "lib/ogl.h"
#include "maths/BoundingBoxAligned.h"
#include "maths/Brush.h"
#include "maths/MathUtil.h"
#include "maths/Matrix3D.h"
-
+#include "ps/CLogger.h"
+#include "ps/ConfigDB.h"
+#include "ps/Profile.h"
#include "renderer/Renderer.h"
-#include "renderer/ShadowMap.h"
-
///////////////////////////////////////////////////////////////////////////////////////////////////
// ShadowMap implementation
/**
* Struct ShadowMapInternals: Internal data for the ShadowMap implementation
*/
struct ShadowMapInternals
{
// bit depth for the depth texture
int DepthTextureBits;
// the EXT_framebuffer_object framebuffer
GLuint Framebuffer;
// handle of shadow map
GLuint Texture;
// width, height of shadow map
int Width, Height;
// Shadow map quality (-2 - Very Low, -1 - Low, 0 - Medium, 1 - High, 2 - Very High)
int QualityLevel;
// used width, height of shadow map
int EffectiveWidth, EffectiveHeight;
// transform light space into projected light space
// in projected light space, the shadowbound box occupies the [-1..1] cube
// calculated on BeginRender, after the final shadow bounds are known
CMatrix3D LightProjection;
// Transform world space into light space; calculated on SetupFrame
CMatrix3D LightTransform;
// Transform world space into texture space of the shadow map;
// calculated on BeginRender, after the final shadow bounds are known
CMatrix3D TextureMatrix;
// transform light space into world space
CMatrix3D InvLightTransform;
// bounding box of shadowed objects in light space
CBoundingBoxAligned ShadowCasterBound;
CBoundingBoxAligned ShadowReceiverBound;
CBoundingBoxAligned ShadowRenderBound;
// Camera transformed into light space
CCamera LightspaceCamera;
// Some drivers (at least some Intel Mesa ones) appear to handle alpha testing
// incorrectly when the FBO has only a depth attachment.
// When m_ShadowAlphaFix is true, we use DummyTexture to store a useless
// alpha texture which is attached to the FBO as a workaround.
GLuint DummyTexture;
// Copy of renderer's standard view camera, saved between
// BeginRender and EndRender while we replace it with the shadow camera
CCamera SavedViewCamera;
// Save the caller's FBO so it can be restored
GLint SavedViewFBO;
// Helper functions
void CalcShadowMatrices();
void CreateTexture();
};
///////////////////////////////////////////////////////////////////////////////////////////////////
// Construction/Destruction
ShadowMap::ShadowMap()
{
m = new ShadowMapInternals;
m->Framebuffer = 0;
m->Texture = 0;
m->DummyTexture = 0;
m->Width = 0;
m->Height = 0;
m->QualityLevel = 0;
m->EffectiveWidth = 0;
m->EffectiveHeight = 0;
m->DepthTextureBits = 0;
// DepthTextureBits: 24/32 are very much faster than 16, on GeForce 4 and FX;
// but they're very much slower on Radeon 9800.
// In both cases, the default (no specified depth) is fast, so we just use
// that by default and hope it's alright. (Otherwise, we'd probably need to
// do some kind of hardware detection to work out what to use.)
// Avoid using uninitialised values in AddShadowedBound if SetupFrame wasn't called first
m->LightTransform.SetIdentity();
}
ShadowMap::~ShadowMap()
{
if (m->Texture)
glDeleteTextures(1, &m->Texture);
if (m->DummyTexture)
glDeleteTextures(1, &m->DummyTexture);
if (m->Framebuffer)
pglDeleteFramebuffersEXT(1, &m->Framebuffer);
delete m;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Force the texture/buffer/etc to be recreated, particularly when the renderer's
// size has changed
void ShadowMap::RecreateTexture()
{
if (m->Texture)
glDeleteTextures(1, &m->Texture);
if (m->DummyTexture)
glDeleteTextures(1, &m->DummyTexture);
if (m->Framebuffer)
pglDeleteFramebuffersEXT(1, &m->Framebuffer);
m->Texture = 0;
m->DummyTexture = 0;
m->Framebuffer = 0;
// (Texture will be constructed in next SetupFrame)
}
//////////////////////////////////////////////////////////////////////////////
// SetupFrame: camera and light direction for this frame
void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
{
if (!m->Texture)
m->CreateTexture();
CVector3D z = lightdir;
CVector3D y;
CVector3D x = camera.m_Orientation.GetIn();
CVector3D eyepos = camera.m_Orientation.GetTranslation();
z.Normalize();
x -= z * z.Dot(x);
if (x.Length() < 0.001)
{
// this is invoked if the camera and light directions almost coincide
// assumption: light direction has a significant Z component
x = CVector3D(1.0, 0.0, 0.0);
x -= z * z.Dot(x);
}
x.Normalize();
y = z.Cross(x);
// X axis perpendicular to light direction, flowing along with view direction
m->LightTransform._11 = x.X;
m->LightTransform._12 = x.Y;
m->LightTransform._13 = x.Z;
// Y axis perpendicular to light and view direction
m->LightTransform._21 = y.X;
m->LightTransform._22 = y.Y;
m->LightTransform._23 = y.Z;
// Z axis is in direction of light
m->LightTransform._31 = z.X;
m->LightTransform._32 = z.Y;
m->LightTransform._33 = z.Z;
// eye is at the origin of the coordinate system
m->LightTransform._14 = -x.Dot(eyepos);
m->LightTransform._24 = -y.Dot(eyepos);
m->LightTransform._34 = -z.Dot(eyepos);
m->LightTransform._41 = 0.0;
m->LightTransform._42 = 0.0;
m->LightTransform._43 = 0.0;
m->LightTransform._44 = 1.0;
m->LightTransform.GetInverse(m->InvLightTransform);
m->ShadowCasterBound.SetEmpty();
m->ShadowReceiverBound.SetEmpty();
//
m->LightspaceCamera = camera;
m->LightspaceCamera.m_Orientation = m->LightTransform * camera.m_Orientation;
m->LightspaceCamera.UpdateFrustum();
}
//////////////////////////////////////////////////////////////////////////////
// AddShadowedBound: add a world-space bounding box to the bounds of shadowed
// objects
void ShadowMap::AddShadowCasterBound(const CBoundingBoxAligned& bounds)
{
CBoundingBoxAligned lightspacebounds;
bounds.Transform(m->LightTransform, lightspacebounds);
m->ShadowCasterBound += lightspacebounds;
}
void ShadowMap::AddShadowReceiverBound(const CBoundingBoxAligned& bounds)
{
CBoundingBoxAligned lightspacebounds;
bounds.Transform(m->LightTransform, lightspacebounds);
m->ShadowReceiverBound += lightspacebounds;
}
CFrustum ShadowMap::GetShadowCasterCullFrustum()
{
// Get the bounds of all objects that can receive shadows
CBoundingBoxAligned bound = m->ShadowReceiverBound;
// Intersect with the camera frustum, so the shadow map doesn't have to get
// stretched to cover the off-screen parts of large models
bound.IntersectFrustumConservative(m->LightspaceCamera.GetFrustum());
// ShadowBound might have been empty to begin with, producing an empty result
if (bound.IsEmpty())
{
// CFrustum can't easily represent nothingness, so approximate it with
// a single point which won't match many objects
bound += CVector3D(0.0f, 0.0f, 0.0f);
return bound.ToFrustum();
}
// Extend the bounds a long way towards the light source, to encompass
// all objects that might cast visible shadows.
// (The exact constant was picked entirely arbitrarily.)
bound[0].Z -= 1000.f;
CFrustum frustum = bound.ToFrustum();
frustum.Transform(m->InvLightTransform);
return frustum;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// CalcShadowMatrices: calculate required matrices for shadow map generation - the light's
// projection and transformation matrices
void ShadowMapInternals::CalcShadowMatrices()
{
// Start building the shadow map to cover all objects that will receive shadows
CBoundingBoxAligned receiverBound = ShadowReceiverBound;
// Intersect with the camera frustum, so the shadow map doesn't have to get
// stretched to cover the off-screen parts of large models
receiverBound.IntersectFrustumConservative(LightspaceCamera.GetFrustum());
// Intersect with the shadow caster bounds, because there's no point
// wasting space around the edges of the shadow map that we're not going
// to draw into
ShadowRenderBound[0].X = std::max(receiverBound[0].X, ShadowCasterBound[0].X);
ShadowRenderBound[0].Y = std::max(receiverBound[0].Y, ShadowCasterBound[0].Y);
ShadowRenderBound[1].X = std::min(receiverBound[1].X, ShadowCasterBound[1].X);
ShadowRenderBound[1].Y = std::min(receiverBound[1].Y, ShadowCasterBound[1].Y);
// Set the near and far planes to include just the shadow casters,
// so we make full use of the depth texture's range. Add a bit of a
// delta so we don't accidentally clip objects that are directly on
// the planes.
ShadowRenderBound[0].Z = ShadowCasterBound[0].Z - 2.f;
ShadowRenderBound[1].Z = ShadowCasterBound[1].Z + 2.f;
// ShadowBound might have been empty to begin with, producing an empty result
if (ShadowRenderBound.IsEmpty())
{
// no-op
LightProjection.SetIdentity();
TextureMatrix = LightTransform;
return;
}
// round off the shadow boundaries to sane increments to help reduce swim effect
float boundInc = 16.0f;
ShadowRenderBound[0].X = floor(ShadowRenderBound[0].X / boundInc) * boundInc;
ShadowRenderBound[0].Y = floor(ShadowRenderBound[0].Y / boundInc) * boundInc;
ShadowRenderBound[1].X = ceil(ShadowRenderBound[1].X / boundInc) * boundInc;
ShadowRenderBound[1].Y = ceil(ShadowRenderBound[1].Y / boundInc) * boundInc;
// Setup orthogonal projection (lightspace -> clip space) for shadowmap rendering
CVector3D scale = ShadowRenderBound[1] - ShadowRenderBound[0];
CVector3D shift = (ShadowRenderBound[1] + ShadowRenderBound[0]) * -0.5;
if (scale.X < 1.0)
scale.X = 1.0;
if (scale.Y < 1.0)
scale.Y = 1.0;
if (scale.Z < 1.0)
scale.Z = 1.0;
scale.X = 2.0 / scale.X;
scale.Y = 2.0 / scale.Y;
scale.Z = 2.0 / scale.Z;
// make sure a given world position falls on a consistent shadowmap texel fractional offset
float offsetX = fmod(ShadowRenderBound[0].X - LightTransform._14, 2.0f/(scale.X*EffectiveWidth));
float offsetY = fmod(ShadowRenderBound[0].Y - LightTransform._24, 2.0f/(scale.Y*EffectiveHeight));
LightProjection.SetZero();
LightProjection._11 = scale.X;
LightProjection._14 = (shift.X + offsetX) * scale.X;
LightProjection._22 = scale.Y;
LightProjection._24 = (shift.Y + offsetY) * scale.Y;
LightProjection._33 = scale.Z;
LightProjection._34 = shift.Z * scale.Z;
LightProjection._44 = 1.0;
// Calculate texture matrix by creating the clip space to texture coordinate matrix
// and then concatenating all matrices that have been calculated so far
float texscalex = scale.X * 0.5f * (float)EffectiveWidth / (float)Width;
float texscaley = scale.Y * 0.5f * (float)EffectiveHeight / (float)Height;
float texscalez = scale.Z * 0.5f;
CMatrix3D lightToTex;
lightToTex.SetZero();
lightToTex._11 = texscalex;
lightToTex._14 = (offsetX - ShadowRenderBound[0].X) * texscalex;
lightToTex._22 = texscaley;
lightToTex._24 = (offsetY - ShadowRenderBound[0].Y) * texscaley;
lightToTex._33 = texscalez;
lightToTex._34 = -ShadowRenderBound[0].Z * texscalez;
lightToTex._44 = 1.0;
TextureMatrix = lightToTex * LightTransform;
}
//////////////////////////////////////////////////////////////////////////
// Create the shadow map
void ShadowMapInternals::CreateTexture()
{
// Cleanup
if (Texture)
{
glDeleteTextures(1, &Texture);
Texture = 0;
}
if (DummyTexture)
{
glDeleteTextures(1, &DummyTexture);
DummyTexture = 0;
}
if (Framebuffer)
{
pglDeleteFramebuffersEXT(1, &Framebuffer);
Framebuffer = 0;
}
// save the caller's FBO
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &SavedViewFBO);
pglGenFramebuffersEXT(1, &Framebuffer);
if (g_Renderer.m_ShadowMapSize != 0)
{
// non-default option to override the size
Width = Height = g_Renderer.m_ShadowMapSize;
}
else
{
CFG_GET_VAL("shadowquality", QualityLevel);
// get shadow map size as next power of two up from view width/height
int shadow_map_size = (int)round_up_to_pow2((unsigned)std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight()));
switch (QualityLevel)
{
// Very Low
case -2:
shadow_map_size /= 4;
break;
// Low
case -1:
shadow_map_size /= 2;
break;
// High
case 1:
shadow_map_size *= 2;
break;
// Ultra
case 2:
shadow_map_size *= 4;
break;
// Medium as is
default:
break;
}
Width = Height = shadow_map_size;
}
// Clamp to the maximum texture size
Width = std::min(Width, (int)ogl_max_tex_size);
Height = std::min(Height, (int)ogl_max_tex_size);
// Since we're using a framebuffer object, the whole texture is available
EffectiveWidth = Width;
EffectiveHeight = Height;
const char* formatname;
switch(DepthTextureBits)
{
case 16: formatname = "DEPTH_COMPONENT16"; break;
case 24: formatname = "DEPTH_COMPONENT24"; break;
case 32: formatname = "DEPTH_COMPONENT32"; break;
default: formatname = "DEPTH_COMPONENT"; break;
}
LOGMESSAGE("Creating shadow texture (size %dx%d) (format = %s)",
Width, Height, formatname);
if (g_Renderer.m_Options.m_ShadowAlphaFix)
{
glGenTextures(1, &DummyTexture);
g_Renderer.BindTexture(0, DummyTexture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, Width, Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
}
glGenTextures(1, &Texture);
g_Renderer.BindTexture(0, Texture);
GLenum format;
#if CONFIG2_GLES
format = GL_DEPTH_COMPONENT;
#else
switch (DepthTextureBits)
{
case 16: format = GL_DEPTH_COMPONENT16; break;
case 24: format = GL_DEPTH_COMPONENT24; break;
case 32: format = GL_DEPTH_COMPONENT32; break;
default: format = GL_DEPTH_COMPONENT; break;
}
#endif
glTexImage2D(GL_TEXTURE_2D, 0, format, Width, Height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_SHORT, NULL);
// GLES requires type == UNSIGNED_SHORT or UNSIGNED_INT
// set texture parameters
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#if CONFIG2_GLES
// GLES doesn't do depth comparisons, so treat it as a
// basic unfiltered depth texture
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
#else
// Enable automatic depth comparisons
glTexParameteri(GL_TEXTURE_2D, GL_DEPTH_TEXTURE_MODE, GL_INTENSITY);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC, GL_LEQUAL);
// Use GL_LINEAR to trigger automatic PCF on some devices
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
#endif
ogl_WarnIfError();
// bind to framebuffer object
glBindTexture(GL_TEXTURE_2D, 0);
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, Framebuffer);
pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_TEXTURE_2D, Texture, 0);
if (g_Renderer.m_Options.m_ShadowAlphaFix)
{
pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, DummyTexture, 0);
}
else
{
#if CONFIG2_GLES
#warning TODO: figure out whether the glDrawBuffer/glReadBuffer stuff is needed, since it is not supported by GLES
#else
glDrawBuffer(GL_NONE);
#endif
}
#if !CONFIG2_GLES
glReadBuffer(GL_NONE);
#endif
ogl_WarnIfError();
GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, SavedViewFBO);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("Framebuffer object incomplete: 0x%04X", status);
// Disable shadow rendering (but let the user try again if they want)
g_Renderer.m_Options.m_Shadows = false;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Set up to render into shadow map texture
void ShadowMap::BeginRender()
{
// HACK HACK: this depends in non-obvious ways on the behaviour of the caller
// save caller's FBO
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &m->SavedViewFBO);
// Calc remaining shadow matrices
m->CalcShadowMatrices();
{
PROFILE("bind framebuffer");
glBindTexture(GL_TEXTURE_2D, 0);
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m->Framebuffer);
}
// clear buffers
{
PROFILE("clear depth texture");
// In case we used m_ShadowAlphaFix, we ought to clear the unused
// color buffer too, else Mali 400 drivers get confused.
// Might as well clear stencil too for completeness.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
glColorMask(0,0,0,0);
}
// setup viewport
const SViewPort vp = { 0, 0, m->EffectiveWidth, m->EffectiveHeight };
g_Renderer.SetViewport(vp);
m->SavedViewCamera = g_Renderer.GetViewCamera();
CCamera c = m->SavedViewCamera;
c.SetProjection(m->LightProjection);
c.GetOrientation() = m->InvLightTransform;
g_Renderer.SetViewCamera(c);
#if !CONFIG2_GLES
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(&m->LightProjection._11);
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(&m->LightTransform._11);
#endif
glEnable(GL_SCISSOR_TEST);
glScissor(1,1, m->EffectiveWidth-2, m->EffectiveHeight-2);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Finish rendering into shadow map texture
void ShadowMap::EndRender()
{
glDisable(GL_SCISSOR_TEST);
g_Renderer.SetViewCamera(m->SavedViewCamera);
{
PROFILE("unbind framebuffer");
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m->SavedViewFBO);
}
const SViewPort vp = { 0, 0, g_Renderer.GetWidth(), g_Renderer.GetHeight() };
g_Renderer.SetViewport(vp);
glColorMask(1,1,1,1);
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Retrieve the texture handle and texture matrix for shadowing
GLuint ShadowMap::GetTexture() const
{
return m->Texture;
}
const CMatrix3D& ShadowMap::GetTextureMatrix() const
{
return m->TextureMatrix;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Depth texture bits
int ShadowMap::GetDepthTextureBits() const
{
return m->DepthTextureBits;
}
void ShadowMap::SetDepthTextureBits(int bits)
{
if (bits != m->DepthTextureBits)
{
if (m->Texture)
{
glDeleteTextures(1, &m->Texture);
m->Texture = 0;
}
m->Width = m->Height = 0;
m->DepthTextureBits = bits;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
// Depth texture size
int ShadowMap::GetWidth() const
{
return m->Width;
}
int ShadowMap::GetHeight() const
{
return m->Height;
}
//////////////////////////////////////////////////////////////////////////////
void ShadowMap::RenderDebugBounds()
{
CShaderTechniquePtr shaderTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_solid);
shaderTech->BeginPass();
CShaderProgramPtr shader = shaderTech->GetShader();
glDepthMask(0);
glDisable(GL_CULL_FACE);
// Render various shadow bounds:
// Yellow = bounds of objects in view frustum that receive shadows
// Red = culling frustum used to find potential shadow casters
// Green = bounds of objects in culling frustum that cast shadows
// Blue = frustum used for rendering the shadow map
shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection() * m->InvLightTransform);
shader->Uniform(str_color, 1.0f, 1.0f, 0.0f, 1.0f);
m->ShadowReceiverBound.RenderOutline(shader);
shader->Uniform(str_color, 0.0f, 1.0f, 0.0f, 1.0f);
m->ShadowCasterBound.RenderOutline(shader);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
shader->Uniform(str_color, 0.0f, 0.0f, 1.0f, 0.25f);
m->ShadowRenderBound.Render(shader);
glDisable(GL_BLEND);
shader->Uniform(str_color, 0.0f, 0.0f, 1.0f, 1.0f);
m->ShadowRenderBound.RenderOutline(shader);
// Render light frustum
shader->Uniform(str_transform, g_Renderer.GetViewCamera().GetViewProjection());
CFrustum frustum = GetShadowCasterCullFrustum();
// We don't have a function to create a brush directly from a frustum, so use
// the ugly approach of creating a large cube and then intersecting with the frustum
CBoundingBoxAligned dummy(CVector3D(-1e4, -1e4, -1e4), CVector3D(1e4, 1e4, 1e4));
CBrush brush(dummy);
CBrush frustumBrush;
brush.Intersect(frustum, frustumBrush);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
shader->Uniform(str_color, 1.0f, 0.0f, 0.0f, 0.25f);
frustumBrush.Render(shader);
glDisable(GL_BLEND);
shader->Uniform(str_color, 1.0f, 0.0f, 0.0f, 1.0f);
frustumBrush.RenderOutline(shader);
shaderTech->EndPass();
#if 0
CMatrix3D InvTexTransform;
m->TextureMatrix.GetInverse(InvTexTransform);
// Render representative texture rectangle
glPushMatrix();
glMultMatrixf(&InvTexTransform._11);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glColor4ub(255,0,0,64);
glBegin(GL_QUADS);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(1.0, 0.0, 0.0);
glVertex3f(1.0, 1.0, 0.0);
glVertex3f(0.0, 1.0, 0.0);
glEnd();
glDisable(GL_BLEND);
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
glColor3ub(255,0,0);
glBegin(GL_QUADS);
glVertex3f(0.0, 0.0, 0.0);
glVertex3f(1.0, 0.0, 0.0);
glVertex3f(1.0, 1.0, 0.0);
glVertex3f(0.0, 1.0, 0.0);
glEnd();
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
glPopMatrix();
#endif
glEnable(GL_CULL_FACE);
glDepthMask(1);
}
void ShadowMap::RenderDebugTexture()
{
glDepthMask(0);
glDisable(GL_DEPTH_TEST);
#if !CONFIG2_GLES
g_Renderer.BindTexture(0, m->Texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_NONE);
#endif
CShaderTechniquePtr texTech = g_Renderer.GetShaderManager().LoadEffect(str_gui_basic);
texTech->BeginPass();
CShaderProgramPtr texShader = texTech->GetShader();
texShader->Uniform(str_transform, GetDefaultGuiMatrix());
texShader->BindTexture(str_tex, m->Texture);
float s = 256.f;
float boxVerts[] = {
0,0, 0,s, s,0,
s,0, 0,s, s,s
};
float boxUV[] = {
0,0, 0,1, 1,0,
1,0, 0,1, 1,1
};
texShader->VertexPointer(2, GL_FLOAT, 0, boxVerts);
texShader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, boxUV);
texShader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
texTech->EndPass();
#if !CONFIG2_GLES
g_Renderer.BindTexture(0, m->Texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE, GL_COMPARE_R_TO_TEXTURE);
#endif
glEnable(GL_DEPTH_TEST);
glDepthMask(1);
}