Index: ps/trunk/source/graphics/LOSTexture.cpp
===================================================================
--- ps/trunk/source/graphics/LOSTexture.cpp (revision 22442)
+++ ps/trunk/source/graphics/LOSTexture.cpp (revision 22443)
@@ -1,440 +1,440 @@
-/* Copyright (C) 2014 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 .
*/
#include "precompiled.h"
#include "LOSTexture.h"
#include "graphics/ShaderManager.h"
#include "graphics/Terrain.h"
#include "lib/bits.h"
#include "lib/config2.h"
#include "ps/CLogger.h"
#include "ps/Game.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"
#include "renderer/TimeManager.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "simulation2/components/ICmpTerrain.h"
/*
The LOS bitmap is computed with one value per map vertex, based on
CCmpRangeManager's visibility information.
The bitmap is then blurred using an NxN filter (in particular a
7-tap Binomial filter as an efficient integral approximation of a Gaussian).
To implement the blur efficiently without using extra memory for a second copy
of the bitmap, we generate the bitmap with (N-1)/2 pixels of padding on each side,
then the blur shifts the image back into the corner.
The blurred bitmap is then uploaded into a GL texture for use by the renderer.
*/
// Blur with a NxN filter, where N = g_BlurSize must be an odd number.
static const size_t g_BlurSize = 7;
// Alignment (in bytes) of the pixel data passed into glTexSubImage2D.
// This must be a multiple of GL_UNPACK_ALIGNMENT, which ought to be 1 (since
// that's what we set it to) but in some weird cases appears to have a different
// value. (See Trac #2594). Multiples of 4 are possibly good for performance anyway.
static const size_t g_SubTextureAlignment = 4;
CLOSTexture::CLOSTexture(CSimulation2& simulation)
: m_Simulation(simulation), m_Dirty(true), m_ShaderInitialized(false),
m_Texture(0), m_TextureSmooth1(0), m_TextureSmooth2(0), m_smoothFbo(0),
m_MapSize(0), m_TextureSize(0), whichTex(true)
{
if (CRenderer::IsInitialised() && g_Renderer.m_Options.m_SmoothLOS)
CreateShader();
}
CLOSTexture::~CLOSTexture()
{
if (m_Texture)
DeleteTexture();
}
// Create the LOS texture engine. Should be ran only once.
bool CLOSTexture::CreateShader()
{
m_smoothShader = g_Renderer.GetShaderManager().LoadEffect(str_los_interp);
CShaderProgramPtr shader = m_smoothShader->GetShader();
m_ShaderInitialized = m_smoothShader && shader;
if (!m_ShaderInitialized)
{
LOGERROR("Failed to load SmoothLOS shader, disabling.");
g_Renderer.m_Options.m_SmoothLOS = false;
return false;
}
pglGenFramebuffersEXT(1, &m_smoothFbo);
return true;
}
void CLOSTexture::DeleteTexture()
{
glDeleteTextures(1, &m_Texture);
if (m_TextureSmooth1)
glDeleteTextures(1, &m_TextureSmooth1);
if (m_TextureSmooth2)
glDeleteTextures(1, &m_TextureSmooth2);
m_Texture = 0;
m_TextureSmooth1 = 0;
m_TextureSmooth2 = 0;
}
void CLOSTexture::MakeDirty()
{
m_Dirty = true;
}
void CLOSTexture::BindTexture(int unit)
{
if (m_Dirty)
{
RecomputeTexture(unit);
m_Dirty = false;
}
g_Renderer.BindTexture(unit, m_Texture);
}
GLuint CLOSTexture::GetTextureSmooth()
{
if (CRenderer::IsInitialised() && !g_Renderer.m_Options.m_SmoothLOS)
return GetTexture();
else
return whichTex ? m_TextureSmooth1 : m_TextureSmooth2;
}
void CLOSTexture::InterpolateLOS()
{
if (CRenderer::IsInitialised() && !g_Renderer.m_Options.m_SmoothLOS)
return;
if (!m_ShaderInitialized)
{
if (!CreateShader())
return;
// RecomputeTexture(0) will not cause the ConstructTexture to run.
// Force the textures to be created.
DeleteTexture();
ConstructTexture(0);
m_Dirty = true;
}
if (m_Dirty)
{
RecomputeTexture(0);
m_Dirty = false;
}
GLint originalFBO;
glGetIntegerv(GL_FRAMEBUFFER_BINDING_EXT, &originalFBO);
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_smoothFbo);
pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D,
whichTex ? m_TextureSmooth2 : m_TextureSmooth1, 0);
GLenum status = pglCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
{
LOGWARNING("LOS framebuffer object incomplete: 0x%04X", status);
}
m_smoothShader->BeginPass();
CShaderProgramPtr shader = m_smoothShader->GetShader();
glDisable(GL_BLEND);
shader->Bind();
shader->BindTexture(str_losTex1, m_Texture);
shader->BindTexture(str_losTex2, whichTex ? m_TextureSmooth1 : m_TextureSmooth2);
shader->Uniform(str_delta, (float)g_Renderer.GetTimeManager().GetFrameDelta() * 4.0f, 0.0f, 0.0f, 0.0f);
const SViewPort oldVp = g_Renderer.GetViewport();
const SViewPort vp = { 0, 0, m_TextureSize, m_TextureSize };
g_Renderer.SetViewport(vp);
float quadVerts[] = {
1.0f, 1.0f,
-1.0f, 1.0f,
-1.0f, -1.0f,
-1.0f, -1.0f,
1.0f, -1.0f,
1.0f, 1.0f
};
float quadTex[] = {
1.0f, 1.0f,
0.0f, 1.0f,
0.0f, 0.0f,
0.0f, 0.0f,
1.0f, 0.0f,
1.0f, 1.0f
};
shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex);
shader->VertexPointer(2, GL_FLOAT, 0, quadVerts);
shader->AssertPointersBound();
glDrawArrays(GL_TRIANGLES, 0, 6);
g_Renderer.SetViewport(oldVp);
shader->Unbind();
m_smoothShader->EndPass();
pglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, 0, 0);
pglBindFramebufferEXT(GL_FRAMEBUFFER_EXT, originalFBO);
whichTex = !whichTex;
}
GLuint CLOSTexture::GetTexture()
{
if (m_Dirty)
{
RecomputeTexture(0);
m_Dirty = false;
}
return m_Texture;
}
const CMatrix3D& CLOSTexture::GetTextureMatrix()
{
ENSURE(!m_Dirty);
return m_TextureMatrix;
}
const CMatrix3D* CLOSTexture::GetMinimapTextureMatrix()
{
ENSURE(!m_Dirty);
return &m_MinimapTextureMatrix;
}
void CLOSTexture::ConstructTexture(int unit)
{
CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (!cmpTerrain)
return;
m_MapSize = cmpTerrain->GetVerticesPerSide();
m_TextureSize = (GLsizei)round_up_to_pow2(round_up((size_t)m_MapSize + g_BlurSize - 1, g_SubTextureAlignment));
glGenTextures(1, &m_Texture);
// Initialise texture with SoD color, for the areas we don't
// overwrite with glTexSubImage2D later
u8* texData = new u8[m_TextureSize * m_TextureSize * 4];
memset(texData, 0x00, m_TextureSize * m_TextureSize * 4);
if (CRenderer::IsInitialised() && g_Renderer.m_Options.m_SmoothLOS)
{
glGenTextures(1, &m_TextureSmooth1);
glGenTextures(1, &m_TextureSmooth2);
g_Renderer.BindTexture(unit, m_TextureSmooth1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData);
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);
g_Renderer.BindTexture(unit, m_TextureSmooth2);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData);
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);
}
g_Renderer.BindTexture(unit, m_Texture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, m_TextureSize, m_TextureSize, 0, GL_ALPHA, GL_UNSIGNED_BYTE, texData);
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);
delete[] texData;
{
// Texture matrix: We want to map
// world pos (0, y, 0) (i.e. first vertex)
// onto texcoord (0.5/texsize, 0.5/texsize) (i.e. middle of first texel);
// world pos ((mapsize-1)*cellsize, y, (mapsize-1)*cellsize) (i.e. last vertex)
// onto texcoord ((mapsize-0.5) / texsize, (mapsize-0.5) / texsize) (i.e. middle of last texel)
float s = (m_MapSize-1) / (float)(m_TextureSize * (m_MapSize-1) * TERRAIN_TILE_SIZE);
float t = 0.5f / m_TextureSize;
m_TextureMatrix.SetZero();
m_TextureMatrix._11 = s;
m_TextureMatrix._23 = s;
m_TextureMatrix._14 = t;
m_TextureMatrix._24 = t;
m_TextureMatrix._44 = 1;
}
{
// Minimap matrix: We want to map UV (0,0)-(1,1) onto (0,0)-(mapsize/texsize, mapsize/texsize)
float s = m_MapSize / (float)m_TextureSize;
m_MinimapTextureMatrix.SetZero();
m_MinimapTextureMatrix._11 = s;
m_MinimapTextureMatrix._22 = s;
m_MinimapTextureMatrix._44 = 1;
}
}
void CLOSTexture::RecomputeTexture(int unit)
{
// If the map was resized, delete and regenerate the texture
if (m_Texture)
{
CmpPtr cmpTerrain(m_Simulation, SYSTEM_ENTITY);
if (cmpTerrain && m_MapSize != (ssize_t)cmpTerrain->GetVerticesPerSide())
DeleteTexture();
}
bool recreated = false;
if (!m_Texture)
{
ConstructTexture(unit);
recreated = true;
}
PROFILE("recompute LOS texture");
std::vector losData;
size_t pitch;
losData.resize(GetBitmapSize(m_MapSize, m_MapSize, &pitch));
CmpPtr cmpRangeManager(m_Simulation, SYSTEM_ENTITY);
if (!cmpRangeManager)
return;
ICmpRangeManager::CLosQuerier los(cmpRangeManager->GetLosQuerier(g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer()));
GenerateBitmap(los, &losData[0], m_MapSize, m_MapSize, pitch);
if (CRenderer::IsInitialised() && g_Renderer.m_Options.m_SmoothLOS && recreated)
{
g_Renderer.BindTexture(unit, m_TextureSmooth1);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]);
g_Renderer.BindTexture(unit, m_TextureSmooth2);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]);
}
g_Renderer.BindTexture(unit, m_Texture);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, pitch, m_MapSize, GL_ALPHA, GL_UNSIGNED_BYTE, &losData[0]);
}
size_t CLOSTexture::GetBitmapSize(size_t w, size_t h, size_t* pitch)
{
*pitch = round_up(w + g_BlurSize - 1, g_SubTextureAlignment);
return *pitch * (h + g_BlurSize - 1);
}
-void CLOSTexture::GenerateBitmap(ICmpRangeManager::CLosQuerier los, u8* losData, size_t w, size_t h, size_t pitch)
+void CLOSTexture::GenerateBitmap(const ICmpRangeManager::CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch)
{
u8 *dataPtr = losData;
// Initialise the top padding
for (size_t j = 0; j < g_BlurSize/2; ++j)
for (size_t i = 0; i < pitch; ++i)
*dataPtr++ = 0;
for (size_t j = 0; j < h; ++j)
{
// Initialise the left padding
for (size_t i = 0; i < g_BlurSize/2; ++i)
*dataPtr++ = 0;
// Fill in the visibility data
for (size_t i = 0; i < w; ++i)
{
if (los.IsVisible_UncheckedRange(i, j))
*dataPtr++ = 255;
else if (los.IsExplored_UncheckedRange(i, j))
*dataPtr++ = 127;
else
*dataPtr++ = 0;
}
// Initialise the right padding
for (size_t i = 0; i < pitch - w - g_BlurSize/2; ++i)
*dataPtr++ = 0;
}
// Initialise the bottom padding
for (size_t j = 0; j < g_BlurSize/2; ++j)
for (size_t i = 0; i < pitch; ++i)
*dataPtr++ = 0;
// Horizontal blur:
for (size_t j = g_BlurSize/2; j < h + g_BlurSize/2; ++j)
{
for (size_t i = 0; i < w; ++i)
{
u8* d = &losData[i+j*pitch];
*d = (
1*d[0] +
6*d[1] +
15*d[2] +
20*d[3] +
15*d[4] +
6*d[5] +
1*d[6]
) / 64;
}
}
// Vertical blur:
for (size_t j = 0; j < h; ++j)
{
for (size_t i = 0; i < w; ++i)
{
u8* d = &losData[i+j*pitch];
*d = (
1*d[0*pitch] +
6*d[1*pitch] +
15*d[2*pitch] +
20*d[3*pitch] +
15*d[4*pitch] +
6*d[5*pitch] +
1*d[6*pitch]
) / 64;
}
}
}
Index: ps/trunk/source/graphics/LOSTexture.h
===================================================================
--- ps/trunk/source/graphics/LOSTexture.h (revision 22442)
+++ ps/trunk/source/graphics/LOSTexture.h (revision 22443)
@@ -1,107 +1,107 @@
-/* Copyright (C) 2014 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 .
*/
#include "lib/ogl.h"
#include "maths/Matrix3D.h"
#include "simulation2/components/ICmpRangeManager.h"
#include "graphics/ShaderManager.h"
class CSimulation2;
/**
* Maintains the LOS (fog-of-war / shroud-of-darkness) texture, used for
* rendering and for the minimap.
*/
class CLOSTexture
{
NONCOPYABLE(CLOSTexture);
friend class TestLOSTexture;
public:
CLOSTexture(CSimulation2& simulation);
~CLOSTexture();
/**
* Marks the LOS texture as needing recomputation. Call this after each
* simulation update, to ensure responsive updates.
*/
void MakeDirty();
/**
* Recomputes the LOS texture if necessary, and binds it to the requested
* texture unit.
* Also switches the current active texture unit, and enables texturing on it.
* The texture is in 8-bit ALPHA format.
*/
void BindTexture(int unit);
/**
* Recomputes the LOS texture if necessary, and returns the texture handle.
* Also potentially switches the current active texture unit, and enables texturing on it.
* The texture is in 8-bit ALPHA format.
*/
GLuint GetTexture();
void InterpolateLOS();
GLuint GetTextureSmooth();
/**
* Returns a matrix to map (x,y,z) world coordinates onto (u,v) LOS texture
* coordinates, in the form expected by glLoadMatrixf.
* This must only be called after BindTexture.
*/
const CMatrix3D& GetTextureMatrix();
/**
* Returns a matrix to map (0,0)-(1,1) texture coordinates onto LOS texture
* coordinates, in the form expected by glLoadMatrixf.
* This must only be called after BindTexture.
*/
const CMatrix3D* GetMinimapTextureMatrix();
private:
void DeleteTexture();
bool CreateShader();
void ConstructTexture(int unit);
void RecomputeTexture(int unit);
size_t GetBitmapSize(size_t w, size_t h, size_t* pitch);
- void GenerateBitmap(ICmpRangeManager::CLosQuerier los, u8* losData, size_t w, size_t h, size_t pitch);
+ void GenerateBitmap(const ICmpRangeManager::CLosQuerier& los, u8* losData, size_t w, size_t h, size_t pitch);
CSimulation2& m_Simulation;
bool m_Dirty;
bool m_ShaderInitialized;
GLuint m_Texture;
GLuint m_TextureSmooth1, m_TextureSmooth2;
bool whichTex;
GLuint m_smoothFbo;
CShaderTechniquePtr m_smoothShader;
ssize_t m_MapSize; // vertexes per side
GLsizei m_TextureSize; // texels per side
CMatrix3D m_TextureMatrix;
CMatrix3D m_MinimapTextureMatrix;
};
Index: ps/trunk/source/gui/CGUISprite.cpp
===================================================================
--- ps/trunk/source/gui/CGUISprite.cpp (revision 22442)
+++ ps/trunk/source/gui/CGUISprite.cpp (revision 22443)
@@ -1,81 +1,86 @@
-/* Copyright (C) 2015 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 .
*/
#include "precompiled.h"
#include "CGUISprite.h"
CGUISprite::~CGUISprite()
{
for (SGUIImage* const& img : m_Images)
delete img;
}
void CGUISprite::AddImage(SGUIImage* image)
{
m_Images.push_back(image);
}
void CGUISpriteInstance::Draw(CRect Size, int CellID, std::map& Sprites, float Z) const
{
if (m_CachedSize != Size || m_CachedCellID != CellID)
{
GUIRenderer::UpdateDrawCallCache(m_DrawCallCache, m_SpriteName, Size, CellID, Sprites);
m_CachedSize = Size;
m_CachedCellID = CellID;
}
GUIRenderer::Draw(m_DrawCallCache, Z);
}
void CGUISpriteInstance::Invalidate()
{
m_CachedSize = CRect();
m_CachedCellID = -1;
}
bool CGUISpriteInstance::IsEmpty() const
{
return m_SpriteName.empty();
}
// Plus a load of constructors / assignment operators, which don't copy the
// DrawCall cache (to avoid losing track of who has allocated various bits
// of data):
CGUISpriteInstance::CGUISpriteInstance()
: m_CachedCellID(-1)
{
}
CGUISpriteInstance::CGUISpriteInstance(const CStr& SpriteName)
: m_SpriteName(SpriteName), m_CachedCellID(-1)
{
}
CGUISpriteInstance::CGUISpriteInstance(const CGUISpriteInstance& Sprite)
: m_SpriteName(Sprite.m_SpriteName), m_CachedCellID(-1)
{
}
+CGUISpriteInstance& CGUISpriteInstance::operator=(const CGUISpriteInstance& Sprite)
+{
+ return this->operator=(Sprite.m_SpriteName);
+}
+
CGUISpriteInstance& CGUISpriteInstance::operator=(const CStr& SpriteName)
{
m_SpriteName = SpriteName;
m_DrawCallCache.clear();
Invalidate();
return *this;
}
Index: ps/trunk/source/gui/CGUISprite.h
===================================================================
--- ps/trunk/source/gui/CGUISprite.h (revision 22442)
+++ ps/trunk/source/gui/CGUISprite.h (revision 22443)
@@ -1,181 +1,182 @@
-/* Copyright (C) 2015 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 .
*/
/*
A GUI Sprite
--Overview--
A GUI Sprite, which is actually a collage of several
sprites.
--Usage--
Used internally and declared in XML files, read documentations
on how.
--More info--
Check GUI.h
*/
#ifndef INCLUDED_CGUISPRITE
#define INCLUDED_CGUISPRITE
#include "GUIbase.h"
#include "lib/res/graphics/ogl_tex.h"
struct SGUIImageEffects
{
SGUIImageEffects() : m_Greyscale(false) {}
CColor m_AddColor;
CColor m_SolidColor;
bool m_Greyscale;
};
/**
* A CGUISprite is actually a collage of several real
* sprites, this struct represents is such real sprite.
*/
struct SGUIImage
{
NONCOPYABLE(SGUIImage);
public:
SGUIImage() :
m_FixedHAspectRatio(0.f), m_RoundCoordinates(true), m_WrapMode(GL_REPEAT),
m_Effects(NULL), m_Border(false), m_DeltaZ(0.f)
{
}
~SGUIImage()
{
delete m_Effects;
}
// Filename of the texture
VfsPath m_TextureName;
// Image placement (relative to object)
CClientArea m_Size;
// Texture placement (relative to image placement)
CClientArea m_TextureSize;
// Because OpenGL wants textures in squares with a power of 2 (64x64, 256x256)
// it's sometimes tedious to adjust this. So this value simulates which area
// is the real texture
CRect m_TexturePlacementInFile;
// For textures that contain a collection of icons (e.g. unit portraits), this
// will be set to the size of one icon. An object's cell-id will determine
// which part of the texture is used.
// Equal to CSize(0,0) for non-celled textures.
CSize m_CellSize;
/**
* If non-zero, then the image's width will be adjusted when rendering so that
* the width:height ratio equals this value.
*/
float m_FixedHAspectRatio;
/**
* If true, the image's coordinates will be rounded to integer pixels when
* rendering, to avoid blurry filtering.
*/
bool m_RoundCoordinates;
/**
* Texture wrapping mode (GL_REPEAT, GL_CLAMP_TO_EDGE, etc)
*/
GLint m_WrapMode;
// Visual effects (e.g. color modulation)
SGUIImageEffects* m_Effects;
// Color
CColor m_BackColor;
CColor m_BorderColor;
// 0 or 1 pixel border is the only option
bool m_Border;
/**
* Z value modification of the image.
* Inputted in XML as x-level, although it just an easier and safer
* way of declaring delta-z.
*/
float m_DeltaZ;
};
/**
* The GUI sprite, is actually several real sprites (images)
* like a collage. View the section \ in the GUI
* TDD for more information.
*
* Drawing routine is located in CGUI
*
* @see CGUI#DrawSprite
*/
class CGUISprite
{
NONCOPYABLE(CGUISprite);
public:
CGUISprite() {}
virtual ~CGUISprite();
/**
* Adds an image to the sprite collage.
*
* @param image Adds this image to the sprite collage.
*/
void AddImage(SGUIImage*);
/// List of images
std::vector m_Images;
};
#include "GUIRenderer.h"
// An instance of a sprite, usually stored in IGUIObjects - basically a string
// giving the sprite's name, but with some extra data to cache rendering
// calculations between draw calls.
class CGUISpriteInstance
{
public:
CGUISpriteInstance();
CGUISpriteInstance(const CStr& SpriteName);
CGUISpriteInstance(const CGUISpriteInstance& Sprite);
+ CGUISpriteInstance& operator=(const CGUISpriteInstance&);
CGUISpriteInstance& operator=(const CStr& SpriteName);
void Draw(CRect Size, int CellID, std::map& Sprites, float Z) const;
void Invalidate();
bool IsEmpty() const;
const CStr& GetName() { return m_SpriteName; }
private:
CStr m_SpriteName;
// Stored drawing calls, for more efficient rendering
mutable GUIRenderer::DrawCalls m_DrawCallCache;
// Relevant details of previously rendered sprite; the cache is invalidated
// whenever any of these values changes.
mutable CRect m_CachedSize;
mutable int m_CachedCellID;
};
#endif // INCLUDED_CGUISPRITE
Index: ps/trunk/source/lib/path.h
===================================================================
--- ps/trunk/source/lib/path.h (revision 22442)
+++ ps/trunk/source/lib/path.h (revision 22443)
@@ -1,321 +1,327 @@
-/* Copyright (C) 2017 Wildfire Games.
+/* Copyright (C) 2019 Wildfire Games.
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* Path string class, similar to boost::filesystem::basic_path.
*/
// notes:
// - this module is independent of lib/file so that it can be used from
// other code without pulling in the entire file manager.
// - there is no restriction on buffer lengths except the underlying OS.
// input buffers must not exceed PATH_MAX chars, while outputs
// must hold at least that much.
// - unless otherwise mentioned, all functions are intended to work with
// native and VFS paths.
// when reading, both '/' and SYS_DIR_SEP are accepted; '/' is written.
#ifndef INCLUDED_PATH
#define INCLUDED_PATH
#if CONFIG_ENABLE_BOOST
# include "boost/functional/hash.hpp"
#endif
#include "lib/utf8.h"
#include
namespace ERR
{
const Status PATH_CHARACTER_ILLEGAL = -100300;
const Status PATH_CHARACTER_UNSAFE = -100301;
const Status PATH_NOT_FOUND = -100302;
const Status PATH_MIXED_SEPARATORS = -100303;
}
/**
* is s2 a subpath of s1, or vice versa? (equal counts as subpath)
*
* @param s1, s2 comparand strings
* @return bool
**/
LIB_API bool path_is_subpath(const wchar_t* s1, const wchar_t* s2);
/**
* Get the path component of a path.
* Skips over all characters up to the last dir separator, if any.
*
* @param path Input path.
* @return pointer to path component within \.
**/
LIB_API const wchar_t* path_name_only(const wchar_t* path);
// NB: there is a need for 'generic' paths (e.g. for Trace entry / archive pathnames).
// converting between specialized variants via c_str would be inefficient, and the
// Os/VfsPath typedefs are hopefully sufficient to avoid errors.
class Path
{
public:
typedef std::wstring String;
Path()
{
DetectSeparator();
}
+ Path(const Path& p)
+ : path(p.path)
+ {
+ DetectSeparator();
+ }
+
Path(const char* p)
: path((const unsigned char*)p, (const unsigned char*)p+strlen(p))
// interpret bytes as unsigned; makes no difference for ASCII,
// and ensures OsPath on Unix will only contain values 0 <= c < 0x100
{
DetectSeparator();
}
Path(const wchar_t* p)
: path(p, p+wcslen(p))
{
DetectSeparator();
}
Path(const std::string& s)
: path((const unsigned char*)s.c_str(), (const unsigned char*)s.c_str()+s.length())
{
DetectSeparator();
}
Path(const std::wstring& s)
: path(s)
{
DetectSeparator();
}
Path& operator=(const Path& rhs)
{
path = rhs.path;
DetectSeparator(); // (warns if separators differ)
return *this;
}
bool empty() const
{
return path.empty();
}
const String& string() const
{
return path;
}
/**
* Return a UTF-8 version of the path, in a human-readable but potentially
* lossy form. It is *not* safe to take this string and construct a new
* Path object from it (it may fail for some non-ASCII paths) - it should
* only be used for displaying paths to users.
*/
std::string string8() const
{
Status err;
#if !OS_WIN
// On Unix, assume paths consisting of 8-bit charactes saved in this wide string.
std::string spath(path.begin(), path.end());
// Return it if it's valid UTF-8
wstring_from_utf8(spath, &err);
if(err == INFO::OK)
return spath;
// Otherwise assume ISO-8859-1 and let utf8_from_wstring treat each character as a Unicode code point.
#endif
// On Windows, paths are UTF-16 strings. We don't support non-BMP characters so we can assume it's simply a wstring.
return utf8_from_wstring(path, &err);
}
bool operator<(const Path& rhs) const
{
return path < rhs.path;
}
bool operator==(const Path& rhs) const
{
return path == rhs.path;
}
bool operator!=(const Path& rhs) const
{
return !operator==(rhs);
}
bool IsDirectory() const
{
if(empty()) // (ensure length()-1 is safe)
return true; // (the VFS root directory is represented as an empty string)
return path[path.length()-1] == separator;
}
Path Parent() const
{
const size_t idxSlash = path.find_last_of(separator);
if(idxSlash == String::npos)
return L"";
return path.substr(0, idxSlash);
}
Path Filename() const
{
const size_t idxSlash = path.find_last_of(separator);
if(idxSlash == String::npos)
return path;
return path.substr(idxSlash+1);
}
Path Basename() const
{
const Path filename = Filename();
const size_t idxDot = filename.string().find_last_of('.');
if(idxDot == String::npos)
return filename;
return filename.string().substr(0, idxDot);
}
// (Path return type allows callers to use our operator==)
Path Extension() const
{
const Path filename = Filename();
const size_t idxDot = filename.string().find_last_of('.');
if(idxDot == String::npos)
return Path();
return filename.string().substr(idxDot);
}
Path ChangeExtension(Path extension) const
{
return Parent() / Path(Basename().string() + extension.string());
}
Path operator/(Path rhs) const
{
Path ret = *this;
if(ret.path.empty()) // (empty paths assume '/')
ret.separator = rhs.separator;
if(!ret.IsDirectory())
ret.path += ret.separator;
if(rhs.path.find((ret.separator == '/')? '\\' : '/') != String::npos)
{
PrintToDebugOutput();
rhs.PrintToDebugOutput();
DEBUG_WARN_ERR(ERR::PATH_MIXED_SEPARATORS);
}
ret.path += rhs.path;
return ret;
}
/**
* Return the path before the common part of both paths
* @param other Indicates the start of the path which should be removed
* @note other should be a VfsPath, while this should be an OsPath
*/
Path BeforeCommon(Path other) const
{
Path ret = *this;
if(ret.empty() || other.empty())
return L"";
// Convert the separator to allow for string comparison
if(other.separator != ret.separator)
replace(other.path.begin(), other.path.end(), other.separator, ret.separator);
const size_t idx = ret.path.rfind(other.path);
if(idx == String::npos)
return L"";
return path.substr(0, idx);
}
static Status Validate(String::value_type c);
private:
void PrintToDebugOutput() const
{
debug_printf("Path %s, separator %c\n", string8().c_str(), (char)separator);
}
void DetectSeparator()
{
const size_t idxBackslash = path.find('\\');
if(path.find('/') != String::npos && idxBackslash != String::npos)
{
PrintToDebugOutput();
DEBUG_WARN_ERR(ERR::PATH_MIXED_SEPARATORS);
}
// (default to '/' for empty strings)
separator = (idxBackslash == String::npos)? '/' : '\\';
}
String path;
// note: ideally, path strings would only contain '/' or even SYS_DIR_SEP.
// however, Windows-specific code (e.g. the sound driver detection)
// uses these routines with '\\' strings. the boost::filesystem approach of
// converting them all to '/' and then back via external_file_string is
// annoying and inefficient. we allow either type of separators,
// appending whichever was first encountered. when modifying the path,
// we ensure the same separator is used.
wchar_t separator;
};
static inline std::wostream& operator<<(std::wostream& s, const Path& path)
{
s << path.string();
return s;
}
static inline std::wistream& operator>>(std::wistream& s, Path& path)
{
Path::String string;
s >> string;
path = Path(string);
return s;
}
#if CONFIG_ENABLE_BOOST
namespace boost {
template<>
struct hash : std::unary_function
{
std::size_t operator()(const Path& path) const
{
return hash_value(path.string());
}
};
}
#endif // #if CONFIG_ENABLE_BOOST
#endif // #ifndef INCLUDED_PATH
Index: ps/trunk/source/ps/Shapes.cpp
===================================================================
--- ps/trunk/source/ps/Shapes.cpp (revision 22442)
+++ ps/trunk/source/ps/Shapes.cpp (revision 22443)
@@ -1,387 +1,400 @@
/* 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 "Shapes.h"
CRect::CRect() :
left(0.f), top(0.f), right(0.f), bottom(0.f)
{
}
+CRect::CRect(const CRect& rect) :
+ left(rect.left), top(rect.top), right(rect.right), bottom(rect.bottom)
+{
+}
+
CRect::CRect(const CPos &pos) :
left(pos.x), top(pos.y), right(pos.x), bottom(pos.y)
{
}
CRect::CRect(const CSize& size) :
left(0.f), top(0.f), right(size.cx), bottom(size.cy)
{
}
CRect::CRect(const CPos& upperleft, const CPos& bottomright) :
left(upperleft.x), top(upperleft.y), right(bottomright.x), bottom(bottomright.y)
{
}
CRect::CRect(const CPos& pos, const CSize& size) :
left(pos.x), top(pos.y), right(pos.x + size.cx), bottom(pos.y + size.cy)
{
}
CRect::CRect(const float l, const float t, const float r, const float b) :
left(l), top(t), right(r), bottom(b)
{
}
CRect& CRect::operator=(const CRect& a)
{
left = a.left;
top = a.top;
right = a.right;
bottom = a.bottom;
return *this;
}
bool CRect::operator==(const CRect &a) const
{
return (left == a.left &&
top == a.top &&
right == a.right &&
bottom == a.bottom);
}
bool CRect::operator!=(const CRect& a) const
{
return !(*this == a);
}
CRect CRect::operator-() const
{
return CRect(-left, -top, -right, -bottom);
}
CRect CRect::operator+() const
{
return *this;
}
CRect CRect::operator+(const CRect& a) const
{
return CRect(left + a.left, top + a.top, right + a.right, bottom + a.bottom);
}
CRect CRect::operator+(const CPos& a) const
{
return CRect(left + a.x, top + a.y, right + a.x, bottom + a.y);
}
CRect CRect::operator+(const CSize& a) const
{
return CRect(left + a.cx, top + a.cy, right + a.cx, bottom + a.cy);
}
CRect CRect::operator-(const CRect& a) const
{
return CRect(left - a.left, top - a.top, right - a.right, bottom - a.bottom);
}
CRect CRect::operator-(const CPos& a) const
{
return CRect(left - a.x, top - a.y, right - a.x, bottom - a.y);
}
CRect CRect::operator-(const CSize& a) const
{
return CRect(left - a.cx, top - a.cy, right - a.cx, bottom - a.cy);
}
void CRect::operator+=(const CRect& a)
{
left += a.left;
top += a.top;
right += a.right;
bottom += a.bottom;
}
void CRect::operator+=(const CPos& a)
{
left += a.x;
top += a.y;
right += a.x;
bottom += a.y;
}
void CRect::operator+=(const CSize& a)
{
left += a.cx;
top += a.cy;
right += a.cx;
bottom += a.cy;
}
void CRect::operator-=(const CRect& a)
{
left -= a.left;
top -= a.top;
right -= a.right;
bottom -= a.bottom;
}
void CRect::operator-=(const CPos& a)
{
left -= a.x;
top -= a.y;
right -= a.x;
bottom -= a.y;
}
void CRect::operator-=(const CSize& a)
{
left -= a.cx;
top -= a.cy;
right -= a.cx;
bottom -= a.cy;
}
float CRect::GetWidth() const
{
return right-left;
}
float CRect::GetHeight() const
{
return bottom-top;
}
CSize CRect::GetSize() const
{
return CSize(right - left, bottom - top);
}
CPos CRect::TopLeft() const
{
return CPos(left, top);
}
CPos CRect::TopRight() const
{
return CPos(right, top);
}
CPos CRect::BottomLeft() const
{
return CPos(left, bottom);
}
CPos CRect::BottomRight() const
{
return CPos(right, bottom);
}
CPos CRect::CenterPoint() const
{
return CPos((left + right) / 2.f, (top + bottom) / 2.f);
}
bool CRect::PointInside(const CPos &point) const
{
return (point.x >= left &&
point.x <= right &&
point.y >= top &&
point.y <= bottom);
}
CRect CRect::Scale(float x, float y) const
{
return CRect(left * x, top * y, right * x, bottom * y);
}
/*************************************************************************/
CPos::CPos() : x(0.f), y(0.f)
{
}
+CPos::CPos(const CPos& pos) : x(pos.x), y(pos.y)
+{
+}
+
CPos::CPos(const CSize& s) : x(s.cx), y(s.cy)
{
}
CPos::CPos(const float px, const float py) : x(px), y(py)
{
}
CPos& CPos::operator=(const CPos& a)
{
x = a.x;
y = a.y;
return *this;
}
bool CPos::operator==(const CPos &a) const
{
return x == a.x && y == a.y;
}
bool CPos::operator!=(const CPos& a) const
{
return !(*this == a);
}
CPos CPos::operator-() const
{
return CPos(-x, -y);
}
CPos CPos::operator+() const
{
return *this;
}
CPos CPos::operator+(const CPos& a) const
{
return CPos(x + a.x, y + a.y);
}
CPos CPos::operator+(const CSize& a) const
{
return CPos(x + a.cx, y + a.cy);
}
CPos CPos::operator-(const CPos& a) const
{
return CPos(x - a.x, y - a.y);
}
CPos CPos::operator-(const CSize& a) const
{
return CPos(x - a.cx, y - a.cy);
}
void CPos::operator+=(const CPos& a)
{
x += a.x;
y += a.y;
}
void CPos::operator+=(const CSize& a)
{
x += a.cx;
y += a.cy;
}
void CPos::operator-=(const CPos& a)
{
x -= a.x;
y -= a.y;
}
void CPos::operator-=(const CSize& a)
{
x -= a.cx;
y -= a.cy;
}
/*************************************************************************/
CSize::CSize() : cx(0.f), cy(0.f)
{
}
+CSize::CSize(const CSize& size) : cx(size.cx), cy(size.cy)
+{
+}
+
CSize::CSize(const CRect &rect) : cx(rect.GetWidth()), cy(rect.GetHeight())
{
}
CSize::CSize(const CPos &pos) : cx(pos.x), cy(pos.y)
{
}
CSize::CSize(const float sx, const float sy) : cx(sx), cy(sy)
{
}
CSize& CSize::operator=(const CSize& a)
{
cx = a.cx;
cy = a.cy;
return *this;
}
bool CSize::operator==(const CSize &a) const
{
return cx == a.cx && cy == a.cy;
}
bool CSize::operator!=(const CSize& a) const
{
return !(*this == a);
}
CSize CSize::operator-() const
{
return CSize(-cx, -cy);
}
CSize CSize::operator+() const
{
return *this;
}
CSize CSize::operator+(const CSize& a) const
{
return CSize(cx + a.cx, cy + a.cy);
}
CSize CSize::operator-(const CSize& a) const
{
return CSize(cx - a.cx, cy - a.cy);
}
CSize CSize::operator/(const float a) const
{
return CSize(cx / a, cy / a);
}
CSize CSize::operator*(const float a) const
{
return CSize(cx * a, cy * a);
}
void CSize::operator+=(const CSize& a)
{
cx += a.cx;
cy += a.cy;
}
void CSize::operator-=(const CSize& a)
{
cx -= a.cx;
cy -= a.cy;
}
void CSize::operator/=(const float a)
{
cx /= a;
cy /= a;
}
void CSize::operator*=(const float a)
{
cx *= a;
cy *= a;
}
Index: ps/trunk/source/ps/Shapes.h
===================================================================
--- ps/trunk/source/ps/Shapes.h (revision 22442)
+++ ps/trunk/source/ps/Shapes.h (revision 22443)
@@ -1,201 +1,204 @@
/* 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 .
*/
/*
--Overview--
Classes mostly useful for representing 2D screen overlays;
includes functionality for overlay position, color, texture and borders.
*/
#ifndef INCLUDED_SHAPES
#define INCLUDED_SHAPES
class CPos;
class CSize;
/**
* Rectangle class used for screen rectangles. It's very similar to the MS
* CRect, but with FLOATS because it's meant to be used with OpenGL which
* takes float values.
*/
class CRect
{
public:
CRect();
CRect(const CPos &pos);
CRect(const CSize &size);
CRect(const CPos &upperleft, const CPos &bottomright);
CRect(const CPos &pos, const CSize &size);
CRect(const float l, const float t, const float r, const float b);
+ CRect(const CRect&);
CRect& operator=(const CRect& a);
bool operator==(const CRect& a) const;
bool operator!=(const CRect& a) const;
CRect operator-() const;
CRect operator+() const;
CRect operator+(const CRect& a) const;
CRect operator+(const CPos& a) const;
CRect operator+(const CSize& a) const;
CRect operator-(const CRect& a) const;
CRect operator-(const CPos& a) const;
CRect operator-(const CSize& a) const;
void operator+=(const CRect& a);
void operator+=(const CPos& a);
void operator+=(const CSize& a);
void operator-=(const CRect& a);
void operator-=(const CPos& a);
void operator-=(const CSize& a);
/**
* @return Width of Rectangle
*/
float GetWidth() const;
/**
* @return Height of Rectangle
*/
float GetHeight() const;
/**
* Get Size
*/
CSize GetSize() const;
/**
* Get Position equivalent to top/left corner
*/
CPos TopLeft() const;
/**
* Get Position equivalent to top/right corner
*/
CPos TopRight() const;
/**
* Get Position equivalent to bottom/left corner
*/
CPos BottomLeft() const;
/**
* Get Position equivalent to bottom/right corner
*/
CPos BottomRight() const;
/**
* Get Position equivalent to the center of the rectangle
*/
CPos CenterPoint() const;
/**
* Evalutates if point is within the rectangle
* @param point CPos representing point
* @return true if inside.
*/
bool PointInside(const CPos &point) const;
CRect Scale(float x, float y) const;
/**
* Returning CPos representing each corner.
*/
public:
/**
* Dimensions
*/
float left, top, right, bottom;
};
/**
* Made to represent screen positions and delta values.
* @see CRect
* @see CSize
*/
class CPos
{
public:
CPos();
+ CPos(const CPos& pos);
CPos(const CSize &pos);
CPos(const float px, const float py);
CPos& operator=(const CPos& a);
bool operator==(const CPos& a) const;
bool operator!=(const CPos& a) const;
CPos operator-() const;
CPos operator+() const;
CPos operator+(const CPos& a) const;
CPos operator+(const CSize& a) const;
CPos operator-(const CPos& a) const;
CPos operator-(const CSize& a) const;
void operator+=(const CPos& a);
void operator+=(const CSize& a);
void operator-=(const CPos& a);
void operator-=(const CSize& a);
public:
/**
* Position
*/
float x, y;
};
/**
* Made to represent a screen size, should in philosophy
* be made of unsigned ints, but for the sake of compatibility
* with CRect and CPos it's not.
* @see CRect
* @see CPos
*/
class CSize
{
public:
CSize();
CSize(const CRect &rect);
CSize(const CPos &pos);
+ CSize(const CSize& size);
CSize(const float sx, const float sy);
CSize& operator=(const CSize& a);
bool operator==(const CSize& a) const;
bool operator!=(const CSize& a) const;
CSize operator-() const;
CSize operator+() const;
CSize operator+(const CSize& a) const;
CSize operator-(const CSize& a) const;
CSize operator/(const float a) const;
CSize operator*(const float a) const;
void operator+=(const CSize& a);
void operator-=(const CSize& a);
void operator/=(const float a);
void operator*=(const float a);
public:
/**
* Size
*/
float cx, cy;
};
#endif // INCLUDED_SHAPES
Index: ps/trunk/source/simulation2/components/CCmpProjectileManager.cpp
===================================================================
--- ps/trunk/source/simulation2/components/CCmpProjectileManager.cpp (revision 22442)
+++ ps/trunk/source/simulation2/components/CCmpProjectileManager.cpp (revision 22443)
@@ -1,396 +1,396 @@
-/* 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 .
*/
#include "precompiled.h"
#include "simulation2/system/Component.h"
#include "ICmpProjectileManager.h"
#include "ICmpObstruction.h"
#include "ICmpObstructionManager.h"
#include "ICmpPosition.h"
#include "ICmpRangeManager.h"
#include "ICmpTerrain.h"
#include "simulation2/MessageTypes.h"
#include "graphics/Frustum.h"
#include "graphics/Model.h"
#include "graphics/Unit.h"
#include "graphics/UnitManager.h"
#include "maths/Matrix3D.h"
#include "maths/Quaternion.h"
#include "maths/Vector3D.h"
#include "ps/CLogger.h"
#include "renderer/Scene.h"
// Time (in seconds) before projectiles that stuck in the ground are destroyed
const static float PROJECTILE_DECAY_TIME = 30.f;
class CCmpProjectileManager : public ICmpProjectileManager
{
public:
static void ClassInit(CComponentManager& componentManager)
{
componentManager.SubscribeToMessageType(MT_Interpolate);
componentManager.SubscribeToMessageType(MT_RenderSubmit);
}
DEFAULT_COMPONENT_ALLOCATOR(ProjectileManager)
static std::string GetSchema()
{
return "";
}
virtual void Init(const CParamNode& UNUSED(paramNode))
{
m_ActorSeed = 0;
m_NextId = 1;
}
virtual void Deinit()
{
for (size_t i = 0; i < m_Projectiles.size(); ++i)
GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles[i].unit);
m_Projectiles.clear();
}
virtual void Serialize(ISerializer& serialize)
{
// Because this is just graphical effects, and because it's all non-deterministic floating point,
// we don't do much serialization here.
// (That means projectiles will vanish if you save/load - is that okay?)
// The attack code stores the id so that the projectile gets deleted when it hits the target
serialize.NumberU32_Unbounded("next id", m_NextId);
}
virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize)
{
Init(paramNode);
// The attack code stores the id so that the projectile gets deleted when it hits the target
deserialize.NumberU32_Unbounded("next id", m_NextId);
}
virtual void HandleMessage(const CMessage& msg, bool UNUSED(global))
{
switch (msg.GetType())
{
case MT_Interpolate:
{
const CMessageInterpolate& msgData = static_cast (msg);
Interpolate(msgData.deltaSimTime);
break;
}
case MT_RenderSubmit:
{
const CMessageRenderSubmit& msgData = static_cast (msg);
RenderSubmit(msgData.collector, msgData.frustum, msgData.culling);
break;
}
}
}
virtual uint32_t LaunchProjectileAtPoint(const CFixedVector3D& launchPoint, const CFixedVector3D& target, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime)
{
return LaunchProjectile(launchPoint, target, speed, gravity, actorName, impactActorName, impactAnimationLifetime);
}
virtual void RemoveProjectile(uint32_t);
void RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector, const CFrustum& frustum, bool culling,
- ICmpRangeManager::CLosQuerier los, bool losRevealAll) const;
+ const ICmpRangeManager::CLosQuerier& los, bool losRevealAll) const;
private:
struct Projectile
{
CUnit* unit;
CVector3D origin;
CVector3D pos;
CVector3D v;
float time;
float timeHit;
float gravity;
float impactAnimationLifetime;
uint32_t id;
std::wstring impactActorName;
bool isImpactAnimationCreated;
bool stopped;
CVector3D position(float t)
{
float t2 = t;
if (t2 > timeHit)
t2 = timeHit + logf(1.f + t2 - timeHit);
CVector3D ret(origin);
ret.X += v.X * t2;
ret.Z += v.Z * t2;
ret.Y += v.Y * t2 - 0.5f * gravity * t * t;
return ret;
}
};
struct ProjectileImpactAnimation
{
CUnit* unit;
CVector3D pos;
float time;
};
std::vector m_Projectiles;
std::vector m_ProjectileImpactAnimations;
uint32_t m_ActorSeed;
uint32_t m_NextId;
uint32_t LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity,
const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime);
void AdvanceProjectile(Projectile& projectile, float dt) const;
void Interpolate(float frameTime);
void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) const;
};
REGISTER_COMPONENT_TYPE(ProjectileManager)
uint32_t CCmpProjectileManager::LaunchProjectile(CFixedVector3D launchPoint, CFixedVector3D targetPoint, fixed speed, fixed gravity, const std::wstring& actorName, const std::wstring& impactActorName, fixed impactAnimationLifetime)
{
// This is network synced so don't use GUI checks before incrementing or it breaks any non GUI simulations
uint32_t currentId = m_NextId++;
if (!GetSimContext().HasUnitManager() || actorName.empty())
return currentId; // do nothing if graphics are disabled
Projectile projectile;
projectile.id = currentId;
projectile.time = 0.f;
projectile.stopped = false;
projectile.gravity = gravity.ToFloat();
projectile.isImpactAnimationCreated = false;
if (!impactActorName.empty())
{
projectile.impactActorName = impactActorName;
projectile.impactAnimationLifetime = impactAnimationLifetime.ToFloat();
}
else
{
projectile.impactActorName = L"";
projectile.impactAnimationLifetime = 0.0f;
}
projectile.origin = launchPoint;
std::set selections;
projectile.unit = GetSimContext().GetUnitManager().CreateUnit(actorName, m_ActorSeed++, selections);
if (!projectile.unit) // The error will have already been logged
return currentId;
projectile.pos = projectile.origin;
CVector3D offset(targetPoint);
offset -= projectile.pos;
float horizDistance = sqrtf(offset.X*offset.X + offset.Z*offset.Z);
projectile.timeHit = horizDistance / speed.ToFloat();
projectile.v = offset * (1.f / projectile.timeHit);
projectile.v.Y = offset.Y / projectile.timeHit + 0.5f * projectile.gravity * projectile.timeHit;
m_Projectiles.push_back(projectile);
return projectile.id;
}
void CCmpProjectileManager::AdvanceProjectile(Projectile& projectile, float dt) const
{
projectile.time += dt;
if (projectile.stopped)
return;
CVector3D delta;
if (dt < 0.1f)
delta = projectile.pos;
else // For big dt delta is unprecise
delta = projectile.position(projectile.time - 0.1f);
projectile.pos = projectile.position(projectile.time);
delta = projectile.pos - delta;
// If we've passed the target position and haven't stopped yet,
// carry on until we reach solid land
if (projectile.time >= projectile.timeHit)
{
CmpPtr cmpTerrain(GetSystemEntity());
if (cmpTerrain)
{
float h = cmpTerrain->GetExactGroundLevel(projectile.pos.X, projectile.pos.Z);
if (projectile.pos.Y < h)
{
projectile.pos.Y = h; // stick precisely to the terrain
projectile.stopped = true;
}
}
}
// Construct a rotation matrix so that (0,1,0) is in the direction of 'delta'
CVector3D up(0, 1, 0);
delta.Normalize();
CVector3D axis = up.Cross(delta);
if (axis.LengthSquared() < 0.0001f)
axis = CVector3D(1, 0, 0); // if up & delta are almost collinear, rotate around some other arbitrary axis
else
axis.Normalize();
float angle = acosf(up.Dot(delta));
CMatrix3D transform;
CQuaternion quat;
quat.FromAxisAngle(axis, angle);
quat.ToMatrix(transform);
// Then apply the translation
transform.Translate(projectile.pos);
// Move the model
projectile.unit->GetModel().SetTransform(transform);
}
void CCmpProjectileManager::Interpolate(float frameTime)
{
for (size_t i = 0; i < m_Projectiles.size(); ++i)
{
AdvanceProjectile(m_Projectiles[i], frameTime);
}
// Remove the ones that have reached their target
for (size_t i = 0; i < m_Projectiles.size(); )
{
if (!m_Projectiles[i].stopped)
{
++i;
continue;
}
if (!m_Projectiles[i].impactActorName.empty() && !m_Projectiles[i].isImpactAnimationCreated)
{
m_Projectiles[i].isImpactAnimationCreated = true;
CMatrix3D transform;
CQuaternion quat;
quat.ToMatrix(transform);
transform.Translate(m_Projectiles[i].pos);
std::set selections;
CUnit* unit = GetSimContext().GetUnitManager().CreateUnit(m_Projectiles[i].impactActorName, m_ActorSeed++, selections);
unit->GetModel().SetTransform(transform);
ProjectileImpactAnimation projectileImpactAnimation;
projectileImpactAnimation.unit = unit;
projectileImpactAnimation.time = m_Projectiles[i].impactAnimationLifetime;
projectileImpactAnimation.pos = m_Projectiles[i].pos;
m_ProjectileImpactAnimations.push_back(projectileImpactAnimation);
}
// Projectiles hitting targets get removed immediately.
// Those hitting the ground stay for a while, because it looks pretty.
if (m_Projectiles[i].time - m_Projectiles[i].timeHit > PROJECTILE_DECAY_TIME)
{
// Delete in-place by swapping with the last in the list
std::swap(m_Projectiles[i], m_Projectiles.back());
GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
m_Projectiles.pop_back();
continue;
}
++i;
}
for (size_t i = 0; i < m_ProjectileImpactAnimations.size();)
{
if (m_ProjectileImpactAnimations[i].time > 0)
{
m_ProjectileImpactAnimations[i].time -= frameTime;
++i;
}
else
{
std::swap(m_ProjectileImpactAnimations[i], m_ProjectileImpactAnimations.back());
GetSimContext().GetUnitManager().DeleteUnit(m_ProjectileImpactAnimations.back().unit);
m_ProjectileImpactAnimations.pop_back();
}
}
}
void CCmpProjectileManager::RemoveProjectile(uint32_t id)
{
// Scan through the projectile list looking for one with the correct id to remove
for (size_t i = 0; i < m_Projectiles.size(); i++)
{
if (m_Projectiles[i].id == id)
{
// Delete in-place by swapping with the last in the list
std::swap(m_Projectiles[i], m_Projectiles.back());
GetSimContext().GetUnitManager().DeleteUnit(m_Projectiles.back().unit);
m_Projectiles.pop_back();
return;
}
}
}
void CCmpProjectileManager::RenderModel(CModelAbstract& model, const CVector3D& position, SceneCollector& collector,
- const CFrustum& frustum, bool culling, ICmpRangeManager::CLosQuerier los, bool losRevealAll) const
+ const CFrustum& frustum, bool culling, const ICmpRangeManager::CLosQuerier& los, bool losRevealAll) const
{
// Don't display objects outside the visible area
ssize_t posi = (ssize_t)(0.5f + position.X / TERRAIN_TILE_SIZE);
ssize_t posj = (ssize_t)(0.5f + position.Z / TERRAIN_TILE_SIZE);
if (!losRevealAll && !los.IsVisible(posi, posj))
return;
model.ValidatePosition();
if (culling && !frustum.IsBoxVisible(model.GetWorldBoundsRec()))
return;
// TODO: do something about LOS (copy from CCmpVisualActor)
collector.SubmitRecursive(&model);
}
void CCmpProjectileManager::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) const
{
CmpPtr cmpRangeManager(GetSystemEntity());
int player = GetSimContext().GetCurrentDisplayedPlayer();
ICmpRangeManager::CLosQuerier los(cmpRangeManager->GetLosQuerier(player));
bool losRevealAll = cmpRangeManager->GetLosRevealAll(player);
for (const Projectile& projectile : m_Projectiles)
{
RenderModel(projectile.unit->GetModel(), projectile.pos, collector, frustum, culling, los, losRevealAll);
}
for (const ProjectileImpactAnimation& projectileImpactAnimation : m_ProjectileImpactAnimations)
{
RenderModel(projectileImpactAnimation.unit->GetModel(), projectileImpactAnimation.pos,
collector, frustum, culling, los, losRevealAll);
}
}
Index: ps/trunk/source/simulation2/tests/test_Serializer.h
===================================================================
--- ps/trunk/source/simulation2/tests/test_Serializer.h (revision 22442)
+++ ps/trunk/source/simulation2/tests/test_Serializer.h (revision 22443)
@@ -1,886 +1,886 @@
-/* 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 .
*/
#include "lib/self_test.h"
#include "simulation2/serialization/DebugSerializer.h"
#include "simulation2/serialization/HashSerializer.h"
#include "simulation2/serialization/StdSerializer.h"
#include "simulation2/serialization/StdDeserializer.h"
#include "scriptinterface/ScriptInterface.h"
#include "graphics/MapReader.h"
#include "graphics/Terrain.h"
#include "graphics/TerrainTextureManager.h"
#include "lib/timer.h"
#include "ps/CLogger.h"
#include "ps/Filesystem.h"
#include "ps/Loader.h"
#include "ps/XML/Xeromyces.h"
#include "simulation2/Simulation2.h"
#include "callgrind.h"
#include
#define TS_ASSERT_STREAM(stream, len, buffer) \
TS_ASSERT_EQUALS(stream.str().length(), (size_t)len); \
TS_ASSERT_SAME_DATA(stream.str().data(), buffer, len)
#define TSM_ASSERT_STREAM(m, stream, len, buffer) \
TSM_ASSERT_EQUALS(m, stream.str().length(), (size_t)len); \
TSM_ASSERT_SAME_DATA(m, stream.str().data(), buffer, len)
class TestSerializer : public CxxTest::TestSuite
{
public:
void serialize_types(ISerializer& serialize)
{
serialize.NumberI8_Unbounded("i8", (signed char)-123);
serialize.NumberU8_Unbounded("u8", (unsigned char)255);
serialize.NumberI16_Unbounded("i16", -12345);
serialize.NumberU16_Unbounded("u16", 56789);
serialize.NumberI32_Unbounded("i32", -123);
serialize.NumberU32_Unbounded("u32", (unsigned)-123);
serialize.NumberFloat_Unbounded("float", 1e+30f);
serialize.NumberDouble_Unbounded("double", 1e+300);
serialize.NumberFixed_Unbounded("fixed", fixed::FromFloat(1234.5f));
serialize.Bool("t", true);
serialize.Bool("f", false);
serialize.StringASCII("string", "example", 0, 255);
serialize.StringASCII("string 2", "example\"\\\"", 0, 255);
serialize.StringASCII("string 3", "example\n\ntest", 0, 255);
wchar_t testw[] = { 't', 0xEA, 's', 't', 0 };
serialize.String("string 4", testw, 0, 255);
serialize.RawBytes("raw bytes", (const u8*)"\0\1\2\3\x0f\x10", 6);
}
void test_Debug_basic()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
std::stringstream stream;
CDebugSerializer serialize(script, stream);
serialize.NumberI32_Unbounded("x", -123);
serialize.NumberU32_Unbounded("y", 1234);
serialize.NumberI32("z", 12345, 0, 65535);
TS_ASSERT_STR_EQUALS(stream.str(), "x: -123\ny: 1234\nz: 12345\n");
}
void test_Debug_floats()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
std::stringstream stream;
CDebugSerializer serialize(script, stream);
serialize.NumberFloat_Unbounded("x", 1e4f);
serialize.NumberFloat_Unbounded("x", 1e-4f);
serialize.NumberFloat_Unbounded("x", 1e5f);
serialize.NumberFloat_Unbounded("x", 1e-5f);
serialize.NumberFloat_Unbounded("x", 1e6f);
serialize.NumberFloat_Unbounded("x", 1e-6f);
serialize.NumberFloat_Unbounded("x", 1e10f);
serialize.NumberFloat_Unbounded("x", 1e-10f);
serialize.NumberDouble_Unbounded("x", 1e4);
serialize.NumberDouble_Unbounded("x", 1e-4);
serialize.NumberDouble_Unbounded("x", 1e5);
serialize.NumberDouble_Unbounded("x", 1e-5);
serialize.NumberDouble_Unbounded("x", 1e6);
serialize.NumberDouble_Unbounded("x", 1e-6);
serialize.NumberDouble_Unbounded("x", 1e10);
serialize.NumberDouble_Unbounded("x", 1e-10);
serialize.NumberDouble_Unbounded("x", 1e100);
serialize.NumberDouble_Unbounded("x", 1e-100);
serialize.NumberFixed_Unbounded("x", fixed::FromDouble(1e4));
TS_ASSERT_STR_EQUALS(stream.str(),
"x: 10000\nx: 9.9999997e-05\nx: 100000\nx: 9.9999997e-06\nx: 1000000\nx: 1e-06\nx: 1e+10\nx: 1e-10\n"
"x: 10000\nx: 0.0001\nx: 100000\nx: 1.0000000000000001e-05\nx: 1000000\nx: 9.9999999999999995e-07\nx: 10000000000\nx: 1e-10\nx: 1e+100\nx: 1e-100\n"
"x: 10000\n"
);
}
void test_Debug_types()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
std::stringstream stream;
CDebugSerializer serialize(script, stream);
serialize.Comment("comment");
serialize_types(serialize);
TS_ASSERT_STR_EQUALS(stream.str(),
"# comment\n"
"i8: -123\n"
"u8: 255\n"
"i16: -12345\n"
"u16: 56789\n"
"i32: -123\n"
"u32: 4294967173\n"
"float: 1e+30\n"
"double: 1.0000000000000001e+300\n"
"fixed: 1234.5\n"
"t: true\n"
"f: false\n"
"string: \"example\"\n"
"string 2: \"example\\\"\\\\\\\"\"\n" // C-escaped form of: "example\"\\\""
"string 3: \"example\\n\\ntest\"\n"
"string 4: \"t\xC3\xAAst\"\n"
"raw bytes: (6 bytes) 00 01 02 03 0f 10\n"
);
}
void test_Std_basic()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
std::stringstream stream;
CStdSerializer serialize(script, stream);
serialize.NumberI32_Unbounded("x", -123);
serialize.NumberU32_Unbounded("y", 1234);
serialize.NumberI32("z", 12345, 0, 65535);
TS_ASSERT_STREAM(stream, 12, "\x85\xff\xff\xff" "\xd2\x04\x00\x00" "\x39\x30\x00\x00");
CStdDeserializer deserialize(script, stream);
int32_t n;
deserialize.NumberI32_Unbounded("x", n);
TS_ASSERT_EQUALS(n, -123);
deserialize.NumberI32_Unbounded("y", n);
TS_ASSERT_EQUALS(n, 1234);
deserialize.NumberI32("z", n, 0, 65535);
TS_ASSERT_EQUALS(n, 12345);
// NOTE: Don't use good() here - it fails due to a bug in older libc++ versions
TS_ASSERT(!stream.bad() && !stream.fail());
TS_ASSERT_EQUALS(stream.peek(), EOF);
}
void test_Std_types()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
std::stringstream stream;
CStdSerializer serialize(script, stream);
serialize_types(serialize);
CStdDeserializer deserialize(script, stream);
int8_t i8v;
uint8_t u8v;
int16_t i16v;
uint16_t u16v;
int32_t i32v;
uint32_t u32v;
float flt;
double dbl;
fixed fxd;
bool bl;
std::string str;
std::wstring wstr;
u8 cbuf[256];
deserialize.NumberI8_Unbounded("i8", i8v);
TS_ASSERT_EQUALS(i8v, -123);
deserialize.NumberU8_Unbounded("u8", u8v);
TS_ASSERT_EQUALS(u8v, 255);
deserialize.NumberI16_Unbounded("i16", i16v);
TS_ASSERT_EQUALS(i16v, -12345);
deserialize.NumberU16_Unbounded("u16", u16v);
TS_ASSERT_EQUALS(u16v, 56789);
deserialize.NumberI32_Unbounded("i32", i32v);
TS_ASSERT_EQUALS(i32v, -123);
deserialize.NumberU32_Unbounded("u32", u32v);
TS_ASSERT_EQUALS(u32v, 4294967173u);
deserialize.NumberFloat_Unbounded("float", flt);
TS_ASSERT_EQUALS(flt, 1e+30f);
deserialize.NumberDouble_Unbounded("double", dbl);
TS_ASSERT_EQUALS(dbl, 1e+300);
deserialize.NumberFixed_Unbounded("fixed", fxd);
TS_ASSERT_EQUALS(fxd.ToDouble(), 1234.5);
deserialize.Bool("t", bl);
TS_ASSERT_EQUALS(bl, true);
deserialize.Bool("f", bl);
TS_ASSERT_EQUALS(bl, false);
deserialize.StringASCII("string", str, 0, 255);
TS_ASSERT_STR_EQUALS(str, "example");
deserialize.StringASCII("string 2", str, 0, 255);
TS_ASSERT_STR_EQUALS(str, "example\"\\\"");
deserialize.StringASCII("string 3", str, 0, 255);
TS_ASSERT_STR_EQUALS(str, "example\n\ntest");
wchar_t testw[] = { 't', 0xEA, 's', 't', 0 };
deserialize.String("string 4", wstr, 0, 255);
TS_ASSERT_WSTR_EQUALS(wstr, testw);
cbuf[6] = 0x42; // sentinel
deserialize.RawBytes("raw bytes", cbuf, 6);
TS_ASSERT_SAME_DATA(cbuf, (const u8*)"\0\1\2\3\x0f\x10\x42", 7);
// NOTE: Don't use good() here - it fails due to a bug in older libc++ versions
TS_ASSERT(!stream.bad() && !stream.fail());
TS_ASSERT_EQUALS(stream.peek(), EOF);
}
void test_Hash_basic()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
CHashSerializer serialize(script);
serialize.NumberI32_Unbounded("x", -123);
serialize.NumberU32_Unbounded("y", 1234);
serialize.NumberI32("z", 12345, 0, 65535);
TS_ASSERT_EQUALS(serialize.GetHashLength(), (size_t)16);
TS_ASSERT_SAME_DATA(serialize.ComputeHash(), "\xa0\x3a\xe5\x3e\x9b\xd7\xfb\x11\x88\x35\xc6\xfb\xb9\x94\xa9\x72", 16);
// echo -en "\x85\xff\xff\xff\xd2\x04\x00\x00\x39\x30\x00\x00" | openssl md5 -binary | xxd -p | perl -pe 's/(..)/\\x$1/g'
}
void test_Hash_stream()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
CHashSerializer hashSerialize(script);
hashSerialize.NumberI32_Unbounded("x", -123);
hashSerialize.NumberU32_Unbounded("y", 1234);
hashSerialize.NumberI32("z", 12345, 0, 65535);
ISerializer& serialize = hashSerialize;
{
CStdSerializer streamSerialize(script, serialize.GetStream());
streamSerialize.NumberI32_Unbounded("x2", -456);
streamSerialize.NumberU32_Unbounded("y2", 5678);
streamSerialize.NumberI32("z2", 45678, 0, 65535);
}
TS_ASSERT_EQUALS(hashSerialize.GetHashLength(), (size_t)16);
TS_ASSERT_SAME_DATA(hashSerialize.ComputeHash(), "\x5c\xff\x33\xd1\x72\xdd\x6d\x77\xa8\xd4\xa1\xf6\x84\xcc\xaa\x10", 16);
// echo -en "\x85\xff\xff\xff\xd2\x04\x00\x00\x39\x30\x00\x00\x38\xfe\xff\xff\x2e\x16\x00\x00\x6e\xb2\x00\x00" | openssl md5 -binary | xxd -p | perl -pe 's/(..)/\\x$1/g'
}
void test_bounds()
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
std::stringstream stream;
CDebugSerializer serialize(script, stream);
serialize.NumberI32("x", 16, -16, 16);
serialize.NumberI32("x", -16, -16, 16);
- TS_ASSERT_THROWS(serialize.NumberI32("x", 17, -16, 16), PSERROR_Serialize_OutOfBounds);
- TS_ASSERT_THROWS(serialize.NumberI32("x", -17, -16, 16), PSERROR_Serialize_OutOfBounds);
+ TS_ASSERT_THROWS(serialize.NumberI32("x", 99, -16, 16), const PSERROR_Serialize_OutOfBounds&);
+ TS_ASSERT_THROWS(serialize.NumberI32("x", -17, -16, 16), const PSERROR_Serialize_OutOfBounds&);
}
// TODO: test exceptions more thoroughly
void helper_script_roundtrip(const char* msg, const char* input, const char* expected, size_t expstreamlen = 0, const char* expstream = NULL, const char* debug = NULL)
{
ScriptInterface script("Test", "Test", g_ScriptRuntime);
JSContext* cx = script.GetContext();
JSAutoRequest rq(cx);
JS::RootedValue obj(cx);
TSM_ASSERT(msg, script.Eval(input, &obj));
if (debug)
{
std::stringstream dbgstream;
CDebugSerializer serialize(script, dbgstream);
serialize.ScriptVal("script", &obj);
TS_ASSERT_STR_EQUALS(dbgstream.str(), debug);
}
std::stringstream stream;
CStdSerializer serialize(script, stream);
serialize.ScriptVal("script", &obj);
if (expstream)
{
TSM_ASSERT_STREAM(msg, stream, expstreamlen, expstream);
}
CStdDeserializer deserialize(script, stream);
JS::RootedValue newobj(cx);
deserialize.ScriptVal("script", &newobj);
// NOTE: Don't use good() here - it fails due to a bug in older libc++ versions
TSM_ASSERT(msg, !stream.bad() && !stream.fail());
TSM_ASSERT_EQUALS(msg, stream.peek(), EOF);
std::string source;
TSM_ASSERT(msg, script.CallFunction(newobj, "toSource", source));
TS_ASSERT_STR_EQUALS(source, expected);
}
void test_script_basic()
{
helper_script_roundtrip("Object",
"({'x': 123, 'y': [1, 1.5, '2', 'test', undefined, null, true, false]})",
/* expected: */
"({x:123, y:[1, 1.5, \"2\", \"test\", (void 0), null, true, false]})",
/* expected stream: */
116,
"\x03" // SCRIPT_TYPE_OBJECT
"\x02\0\0\0" // num props
"\x01\x01\0\0\0" "x" // "x"
"\x05" // SCRIPT_TYPE_INT
"\x7b\0\0\0" // 123
"\x01\x01\0\0\0" "y" // "y"
"\x02" // SCRIPT_TYPE_ARRAY
"\x08\0\0\0" // array length
"\x08\0\0\0" // num props
"\x01\x01\0\0\0" "0" // "0"
"\x05" "\x01\0\0\0" // SCRIPT_TYPE_INT 1
"\x01\x01\0\0\0" "1" // "1"
"\x06" "\0\0\0\0\0\0\xf8\x3f" // SCRIPT_TYPE_DOUBLE 1.5
"\x01\x01\0\0\0" "2" // "2"
"\x04" "\x01\x01\0\0\0" "2" // SCRIPT_TYPE_STRING "2"
"\x01\x01\0\0\0" "3" // "3"
"\x04" "\x01\x04\0\0\0" "test" // SCRIPT_TYPE_STRING "test"
"\x01\x01\0\0\0" "4" // "4"
"\x00" // SCRIPT_TYPE_VOID
"\x01\x01\0\0\0" "5" // "5"
"\x01" // SCRIPT_TYPE_NULL
"\x01\x01\0\0\0" "6" // "6"
"\x07" "\x01" // SCRIPT_TYPE_BOOLEAN true
"\x01\x01\0\0\0" "7" // "7"
"\x07" "\x00", // SCRIPT_TYPE_BOOLEAN false
/* expected debug: */
"script: {\n"
" \"x\": 123,\n"
" \"y\": [\n"
" 1,\n"
" 1.5,\n"
" \"2\",\n"
" \"test\",\n"
" null,\n"
" null,\n"
" true,\n"
" false\n"
" ]\n"
"}\n"
);
}
void test_script_unicode()
{
helper_script_roundtrip("unicode", "({"
"'x': \"\\x01\\x80\\xff\\u0100\\ud7ff\", "
"'y': \"\\ue000\\ufffd\""
"})",
/* expected: */
"({"
"x:\"\\x01\\x80\\xFF\\u0100\\uD7FF\", "
"y:\"\\uE000\\uFFFD\""
"})");
// Disabled since we no longer do the UTF-8 conversion that rejects invalid characters
// TS_ASSERT_THROWS(helper_script_roundtrip("invalid chars 1", "(\"\\ud7ff\\ud800\")", "..."), PSERROR_Serialize_InvalidCharInString);
// TS_ASSERT_THROWS(helper_script_roundtrip("invalid chars 2", "(\"\\udfff\")", "..."), PSERROR_Serialize_InvalidCharInString);
// TS_ASSERT_THROWS(helper_script_roundtrip("invalid chars 3", "(\"\\uffff\")", "..."), PSERROR_Serialize_InvalidCharInString);
// TS_ASSERT_THROWS(helper_script_roundtrip("invalid chars 4", "(\"\\ud800\\udc00\")" /* U+10000 */, "..."), PSERROR_Serialize_InvalidCharInString);
helper_script_roundtrip("unicode", "\"\\ud800\\uffff\"", "(new String(\"\\uD800\\uFFFF\"))");
}
void test_script_objects()
{
helper_script_roundtrip("Number", "[1, new Number('2.0'), 3]", "[1, (new Number(2)), 3]");
helper_script_roundtrip("Number with props", "var n=new Number('2.0'); n.foo='bar'; n", "(new Number(2))");
helper_script_roundtrip("String", "['test1', new String('test2'), 'test3']", "[\"test1\", (new String(\"test2\")), \"test3\"]");
helper_script_roundtrip("String with props", "var s=new String('test'); s.foo='bar'; s", "(new String(\"test\"))");
helper_script_roundtrip("Boolean", "[new Boolean('true'), false]", "[(new Boolean(true)), false]");
helper_script_roundtrip("Boolean with props", "var b=new Boolean('true'); b.foo='bar'; b", "(new Boolean(true))");
}
void test_script_objects_properties()
{
helper_script_roundtrip("Object with null in prop name", "({\"foo\\0bar\":1})", "({\'foo\\x00bar\':1})");
}
void test_script_typed_arrays_simple()
{
helper_script_roundtrip("Int8Array",
"var arr=new Int8Array(8);"
"for(var i=0; iMount(L"", DataDir()/"mods"/"public", VFS_MOUNT_MUST_EXIST));
TS_ASSERT_OK(g_VFS->Mount(L"cache", DataDir()/"_testcache"));
// Need some stuff for terrain movement costs:
// (TODO: this ought to be independent of any graphics code)
new CTerrainTextureManager;
g_TexMan.LoadTerrainTextures();
CTerrain terrain;
CSimulation2 sim2(NULL, g_ScriptRuntime, &terrain);
sim2.LoadDefaultScripts();
sim2.ResetState();
std::unique_ptr mapReader(new CMapReader());
LDR_BeginRegistering();
mapReader->LoadMap(L"maps/skirmishes/Greek Acropolis (2).pmp",
sim2.GetScriptInterface().GetJSRuntime(), JS::UndefinedHandleValue,
&terrain, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
&sim2, &sim2.GetSimContext(), -1, false);
LDR_EndRegistering();
TS_ASSERT_OK(LDR_NonprogressiveLoad());
sim2.Update(0);
{
std::stringstream str;
std::string hash;
sim2.SerializeState(str);
sim2.ComputeStateHash(hash, false);
debug_printf("\n");
debug_printf("# size = %d\n", (int)str.str().length());
debug_printf("# hash = ");
for (size_t i = 0; i < hash.size(); ++i)
debug_printf("%02x", (unsigned int)(u8)hash[i]);
debug_printf("\n");
}
double t = timer_Time();
CALLGRIND_START_INSTRUMENTATION;
size_t reps = 128;
for (size_t i = 0; i < reps; ++i)
{
std::string hash;
sim2.ComputeStateHash(hash, false);
}
CALLGRIND_STOP_INSTRUMENTATION;
t = timer_Time() - t;
debug_printf("# time = %f (%f/%d)\n", t/reps, t, (int)reps);
// Shut down the world
delete &g_TexMan;
g_VFS.reset();
DeleteDirectory(DataDir()/"_testcache");
CXeromyces::Terminate();
}
};
Index: ps/trunk/source/tools/atlas/AtlasObject/AtlasObject.h
===================================================================
--- ps/trunk/source/tools/atlas/AtlasObject/AtlasObject.h (revision 22442)
+++ ps/trunk/source/tools/atlas/AtlasObject/AtlasObject.h (revision 22443)
@@ -1,194 +1,199 @@
/* 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 .
*/
// Public interface to almost all of AtlasObject.
// (See AtlasObjectText for the rest of it).
//
// Tries to include as few headers as possible, to minimise its impact
// on compile times.
#ifndef INCLUDED_ATLASOBJECT
#define INCLUDED_ATLASOBJECT
#if defined(_WIN32)
# define _CRT_SECURE_NO_WARNINGS // Disable deprecation warning in VS2008
#endif
#include
class wxString;
//////////////////////////////////////////////////////////////////////////
// Mostly-private bits:
// Helper class to let us define a conversion operator only for AtSmartPtr
template struct ConstCastHelper { operator ConstSmPtr (); };
template struct ConstCastHelper { };
// Simple reference-counted pointer. class T must contain a reference count,
// initialised to 0. An external implementation (in AtlasObjectImpl.cpp)
// provides the inc_ref and dec_ref methods, so that this header file doesn't
// need to know their implementations.
template class AtSmartPtr : public ConstCastHelper, T>
{
friend struct ConstCastHelper, T>;
private:
void inc_ref();
void dec_ref();
T* ptr;
public:
// Constructors
AtSmartPtr() : ptr(NULL) {}
explicit AtSmartPtr(T* p) : ptr(p) { inc_ref(); }
// Copy constructor
AtSmartPtr(const AtSmartPtr& r) : ptr(r.ptr) { inc_ref(); }
// Assignment operators
AtSmartPtr& operator=(T* p) { dec_ref(); ptr = p; inc_ref(); return *this; }
AtSmartPtr& operator=(const AtSmartPtr& r) { if (&r != this) { dec_ref(); ptr = r.ptr; inc_ref(); } return *this; }
// Destructor
~AtSmartPtr() { dec_ref(); }
// Allow conversion from non-const T* to const T*
//operator AtSmartPtr () { return AtSmartPtr(ptr); } // (actually provided by ConstCastHelper)
// Override ->
T* operator->() const { return ptr; }
// Test whether the pointer is pointing to anything
explicit operator bool() const { return ptr != nullptr; }
};
template
ConstCastHelper::operator ConstSmPtr ()
{
return ConstSmPtr(static_cast*>(this)->ptr);
}
// A few required declarations
class AtObj;
class AtNode;
class AtIterImpl;
//////////////////////////////////////////////////////////////////////////
// Public bits:
// AtIter is an iterator over AtObjs - use it like:
//
// for (AtIter thing = whatever["thing"]; thing.defined(); ++thing)
// DoStuff(thing);
//
// to handle XML data like:
//
//
// Stuff 1
// Stuff 2
//
class AtIter
{
public:
// Increment the iterator; or make it undefined, if there weren't any
// AtObjs left to iterate over
AtIter& operator++ ();
// Return whether this iterator has an AtObj to point to
bool defined() const;
// Return whether this iterator is pointing to a non-contentless AtObj
bool hasContent() const;
// Return the number of AtObjs that will be iterated over (including the current one)
size_t count() const;
// Return an iterator to the children matching 'key'. (That is, children
// of the AtObj currently pointed to by this iterator)
const AtIter operator[] (const char* key) const;
// Return the AtObj currently pointed to by this iterator
const AtObj operator* () const;
// Return the string value of the AtObj currently pointed to by this iterator
operator const char* () const;
// Private implementation. (But not 'private:', because it's a waste of time
// adding loads of friend functions)
AtSmartPtr m_Impl;
};
class AtObj
{
public:
AtObj() {}
AtObj(const AtObj& r) : m_Node(r.m_Node) {}
+ AtObj& operator=(const AtObj& r)
+ {
+ m_Node = r.m_Node;
+ return *this;
+ }
// Return an iterator to the children matching 'key'
const AtIter operator[] (const char* key) const;
// Return the string value of this object
operator const char* () const;
// Return the floating point value of this object
double getDouble() const;
// Return the integer value of this object
int getInt() const;
long getLong() const;
// Check whether the object contains anything (even if those things are empty)
bool defined() const { return static_cast(m_Node); }
// Check recursively whether there's actually any non-empty data in the object
bool hasContent() const;
// Add or set a child. The char* version creates a new AtObj with
// the appropriate string value, then uses that as the child.
//
// These alter the AtObj's internal pointer, and the pointed-to data is
// never actually altered. Copies of this AtObj (including copies stored
// inside other AtObjs) will not be affected.
void add(const char* key, const char* value);
void add(const char* key, AtObj& data);
void set(const char* key, const char* value);
void set(const char* key, AtObj& data);
void setBool(const char* key, bool value);
void setDouble(const char* key, double value);
void setInt(const char* key, int value);
void setString(const char* value);
void addOverlay(AtObj& data);
AtSmartPtr m_Node;
};
// Miscellaneous utility functions:
namespace AtlasObject
{
// Returns AtObj() on failure - test with AtObj::defined()
AtObj LoadFromXML(const std::string& xml);
// Returns AtObj() on failure - test with AtObj::defined()
AtObj LoadFromJSON(const std::string& json);
// Returns UTF-8-encoded XML document string.
// Returns empty string on failure.
std::string SaveToXML(AtObj& obj);
// Returns UTF-8-encoded JSON string.
// Returns empty string on failure.
std::string SaveToJSON(AtObj& obj);
AtObj TrimEmptyChildren(AtObj& obj);
}
#endif // INCLUDED_ATLASOBJECT
Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp (revision 22442)
+++ ps/trunk/source/tools/atlas/GameInterface/Handlers/TerrainHandlers.cpp (revision 22443)
@@ -1,548 +1,549 @@
-/* Copyright (C) 2012 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 .
*/
#include "precompiled.h"
#include "MessageHandler.h"
#include "../CommandProc.h"
#include "graphics/Patch.h"
#include "graphics/TerrainTextureManager.h"
#include "graphics/TerrainTextureEntry.h"
#include "graphics/Terrain.h"
#include "ps/Game.h"
#include "ps/World.h"
#include "lib/ogl.h"
#include "lib/res/graphics/ogl_tex.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpPathfinder.h"
#include "simulation2/components/ICmpTerrain.h"
#include "simulation2/helpers/Grid.h"
#include "../Brushes.h"
#include "../DeltaArray.h"
#include "../View.h"
#include
namespace AtlasMessage {
QUERYHANDLER(GetTerrainGroups)
{
const CTerrainTextureManager::TerrainGroupMap &groups = g_TexMan.GetGroups();
std::vector groupNames;
for (CTerrainTextureManager::TerrainGroupMap::const_iterator it = groups.begin(); it != groups.end(); ++it)
groupNames.push_back(it->first.FromUTF8());
msg->groupNames = groupNames;
}
static bool CompareTerrain(const sTerrainTexturePreview& a, const sTerrainTexturePreview& b)
{
return (wcscmp(a.name.c_str(), b.name.c_str()) < 0);
}
static sTerrainTexturePreview GetPreview(CTerrainTextureEntry* tex, int width, int height)
{
sTerrainTexturePreview preview;
preview.name = tex->GetTag().FromUTF8();
std::vector buf (width*height*3);
#if !CONFIG2_GLES
// It's not good to shrink the entire texture to fit the small preview
// window, since it's the fine details in the texture that are
// interesting; so just go down one mipmap level, then crop a chunk
// out of the middle.
// Read the size of the texture. (Usually loads the texture from
// disk, which is slow.)
tex->GetTexture()->Bind();
int level = 1; // level 0 is the original size
int w = std::max(1, (int)tex->GetTexture()->GetWidth() >> level);
int h = std::max(1, (int)tex->GetTexture()->GetHeight() >> level);
if (w >= width && h >= height)
{
// Read the whole texture into a new buffer
unsigned char* texdata = new unsigned char[w*h*3];
glGetTexImage(GL_TEXTURE_2D, level, GL_RGB, GL_UNSIGNED_BYTE, texdata);
// Extract the middle section (as a representative preview),
// and copy into buf
unsigned char* texdata_ptr = texdata + (w*(h - height)/2 + (w - width)/2) * 3;
unsigned char* buf_ptr = &buf[0];
for (ssize_t y = 0; y < height; ++y)
{
memcpy(buf_ptr, texdata_ptr, width*3);
buf_ptr += width*3;
texdata_ptr += w*3;
}
delete[] texdata;
}
else
#endif
{
// Too small to preview, or glGetTexImage not supported (on GLES)
// Just use a flat color instead
u32 c = tex->GetBaseColor();
for (ssize_t i = 0; i < width*height; ++i)
{
buf[i*3+0] = (c>>16) & 0xff;
buf[i*3+1] = (c>>8) & 0xff;
buf[i*3+2] = (c>>0) & 0xff;
}
}
preview.loaded = tex->GetTexture()->IsLoaded();
preview.imageWidth = width;
preview.imageHeight = height;
preview.imageData = buf;
return preview;
}
QUERYHANDLER(GetTerrainGroupPreviews)
{
std::vector previews;
CTerrainGroup* group = g_TexMan.FindGroup(CStrW(*msg->groupName).ToUTF8());
for (std::vector::const_iterator it = group->GetTerrains().begin(); it != group->GetTerrains().end(); ++it)
{
previews.push_back(GetPreview(*it, msg->imageWidth, msg->imageHeight));
}
// Sort the list alphabetically by name
std::sort(previews.begin(), previews.end(), CompareTerrain);
msg->previews = previews;
}
QUERYHANDLER(GetTerrainPassabilityClasses)
{
CmpPtr cmpPathfinder(*AtlasView::GetView_Game()->GetSimulation2(), SYSTEM_ENTITY);
if (cmpPathfinder)
{
std::map nonPathfindingClasses, pathfindingClasses;
cmpPathfinder->GetPassabilityClasses(nonPathfindingClasses, pathfindingClasses);
std::vector classNames;
for (std::map::iterator it = nonPathfindingClasses.begin(); it != nonPathfindingClasses.end(); ++it)
classNames.push_back(CStr(it->first).FromUTF8());
msg->classNames = classNames;
}
}
QUERYHANDLER(GetTerrainTexture)
{
ssize_t x, y;
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
g_CurrentBrush.GetCentre(x, y);
CTerrain* terrain = g_Game->GetWorld()->GetTerrain();
CMiniPatch* tile = terrain->GetTile(x, y);
if (tile)
{
CTerrainTextureEntry* tex = tile->GetTextureEntry();
msg->texture = tex->GetTag().FromUTF8();
}
else
{
msg->texture = std::wstring();
}
}
QUERYHANDLER(GetTerrainTexturePreview)
{
CTerrainTextureEntry* tex = g_TexMan.FindTexture(CStrW(*msg->name).ToUTF8());
if (tex)
{
msg->preview = GetPreview(tex, msg->imageWidth, msg->imageHeight);
}
else
{
- sTerrainTexturePreview noPreview;
+ sTerrainTexturePreview noPreview{};
noPreview.name = std::wstring();
+ noPreview.loaded = false;
noPreview.imageHeight = 0;
noPreview.imageWidth = 0;
msg->preview = noPreview;
}
}
//////////////////////////////////////////////////////////////////////////
namespace {
struct TerrainTile
{
TerrainTile(CTerrainTextureEntry* t, ssize_t p) : tex(t), priority(p) {}
CTerrainTextureEntry* tex;
ssize_t priority;
};
class TerrainArray : public DeltaArray2D
{
public:
void Init()
{
m_Terrain = g_Game->GetWorld()->GetTerrain();
m_VertsPerSide = g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide();
}
void UpdatePriority(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priorityScale, ssize_t& priority)
{
CMiniPatch* tile = m_Terrain->GetTile(x, y);
if (!tile)
return; // tile was out-of-bounds
// If this tile matches the current texture, we just want to match its
// priority; otherwise we want to exceed its priority
if (tile->GetTextureEntry() == tex)
priority = std::max(priority, tile->GetPriority()*priorityScale);
else
priority = std::max(priority, tile->GetPriority()*priorityScale + 1);
}
CTerrainTextureEntry* GetTexEntry(ssize_t x, ssize_t y)
{
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
return NULL;
return get(x, y).tex;
}
ssize_t GetPriority(ssize_t x, ssize_t y)
{
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
return 0;
return get(x, y).priority;
}
void PaintTile(ssize_t x, ssize_t y, CTerrainTextureEntry* tex, ssize_t priority)
{
// Ignore out-of-bounds tiles
if (size_t(x) >= size_t(m_VertsPerSide-1) || size_t(y) >= size_t(m_VertsPerSide-1))
return;
set(x,y, TerrainTile(tex, priority));
}
ssize_t GetTilesPerSide()
{
return m_VertsPerSide-1;
}
protected:
TerrainTile getOld(ssize_t x, ssize_t y)
{
CMiniPatch* mp = m_Terrain->GetTile(x, y);
ENSURE(mp);
return TerrainTile(mp->Tex, mp->Priority);
}
void setNew(ssize_t x, ssize_t y, const TerrainTile& val)
{
CMiniPatch* mp = m_Terrain->GetTile(x, y);
ENSURE(mp);
mp->Tex = val.tex;
mp->Priority = val.priority;
}
CTerrain* m_Terrain;
ssize_t m_VertsPerSide;
};
}
BEGIN_COMMAND(PaintTerrain)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cPaintTerrain()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
}
void Do()
{
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
if (! texentry)
{
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
return;
}
// Priority system: If the new tile should have a high priority,
// set it to one plus the maximum priority of all surrounding tiles
// that aren't included in the brush (so that it's definitely the highest).
// Similar for low priority.
ssize_t priorityScale = (msg->priority == ePaintTerrainPriority::HIGH ? +1 : -1);
ssize_t priority = 0;
for (ssize_t dy = -1; dy < g_CurrentBrush.m_H+1; ++dy)
{
for (ssize_t dx = -1; dx < g_CurrentBrush.m_W+1; ++dx)
{
if (!(g_CurrentBrush.Get(dx, dy) > 0.5f)) // ignore tiles that will be painted over
m_TerrainDelta.UpdatePriority(x0+dx, y0+dy, texentry, priorityScale, priority);
}
}
for (ssize_t dy = 0; dy < g_CurrentBrush.m_H; ++dy)
{
for (ssize_t dx = 0; dx < g_CurrentBrush.m_W; ++dx)
{
if (g_CurrentBrush.Get(dx, dy) > 0.5f) // TODO: proper solid brushes
m_TerrainDelta.PaintTile(x0+dx, y0+dy, texentry, priority*priorityScale);
}
}
m_i0 = x0 - 1;
m_j0 = y0 - 1;
m_i1 = x0 + g_CurrentBrush.m_W + 1;
m_j1 = y0 + g_CurrentBrush.m_H + 1;
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
void MergeIntoPrevious(cPaintTerrain* prev)
{
prev->m_TerrainDelta.OverlayWith(m_TerrainDelta);
prev->m_i0 = std::min(prev->m_i0, m_i0);
prev->m_j0 = std::min(prev->m_j0, m_j0);
prev->m_i1 = std::max(prev->m_i1, m_i1);
prev->m_j1 = std::max(prev->m_j1, m_j1);
}
};
END_COMMAND(PaintTerrain)
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(ReplaceTerrain)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cReplaceTerrain()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
m_i0 = m_i1 = x0;
m_j0 = m_j1 = y0;
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
if (! texentry)
{
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
return;
}
CTerrainTextureEntry* replacedTex = m_TerrainDelta.GetTexEntry(x0, y0);
// Don't bother if we're not making a change
if (texentry == replacedTex)
{
return;
}
ssize_t tiles = m_TerrainDelta.GetTilesPerSide();
for (ssize_t j = 0; j < tiles; ++j)
{
for (ssize_t i = 0; i < tiles; ++i)
{
if (m_TerrainDelta.GetTexEntry(i, j) == replacedTex)
{
m_i0 = std::min(m_i0, i-1);
m_j0 = std::min(m_j0, j-1);
m_i1 = std::max(m_i1, i+2);
m_j1 = std::max(m_j1, j+2);
m_TerrainDelta.PaintTile(i, j, texentry, m_TerrainDelta.GetPriority(i, j));
}
}
}
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
};
END_COMMAND(ReplaceTerrain)
//////////////////////////////////////////////////////////////////////////
BEGIN_COMMAND(FillTerrain)
{
TerrainArray m_TerrainDelta;
ssize_t m_i0, m_j0, m_i1, m_j1; // dirtied tiles (inclusive lower bound, exclusive upper)
cFillTerrain()
{
m_TerrainDelta.Init();
}
void MakeDirty()
{
g_Game->GetWorld()->GetTerrain()->MakeDirty(m_i0, m_j0, m_i1, m_j1, RENDERDATA_UPDATE_INDICES);
CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY);
if (cmpTerrain)
cmpTerrain->MakeDirty(m_i0, m_j0, m_i1, m_j1);
}
void Do()
{
g_CurrentBrush.m_Centre = msg->pos->GetWorldSpace();
ssize_t x0, y0;
g_CurrentBrush.GetBottomLeft(x0, y0);
m_i0 = m_i1 = x0;
m_j0 = m_j1 = y0;
CTerrainTextureEntry* texentry = g_TexMan.FindTexture(CStrW(*msg->texture).ToUTF8());
if (! texentry)
{
debug_warn(L"Can't find texentry"); // TODO: nicer error handling
return;
}
CTerrainTextureEntry* replacedTex = m_TerrainDelta.GetTexEntry(x0, y0);
// Don't bother if we're not making a change
if (texentry == replacedTex)
{
return;
}
ssize_t tiles = m_TerrainDelta.GetTilesPerSide();
// Simple 4-way flood fill algorithm using queue and a grid to keep track of visited tiles,
// almost as fast as loop for filling whole map, much faster for small patches
SparseGrid visited(tiles, tiles);
std::queue > queue;
// Initial tile
queue.push(std::make_pair((u16)x0, (u16)y0));
visited.set(x0, y0, true);
while(!queue.empty())
{
// Check front of queue
std::pair t = queue.front();
queue.pop();
u16 i = t.first;
u16 j = t.second;
if (m_TerrainDelta.GetTexEntry(i, j) == replacedTex)
{
// Found a tile to replace: adjust bounds and paint it
m_i0 = std::min(m_i0, (ssize_t)i-1);
m_j0 = std::min(m_j0, (ssize_t)j-1);
m_i1 = std::max(m_i1, (ssize_t)i+2);
m_j1 = std::max(m_j1, (ssize_t)j+2);
m_TerrainDelta.PaintTile(i, j, texentry, m_TerrainDelta.GetPriority(i, j));
// Visit 4 adjacent tiles (could visit 8 if we want to count diagonal adjacency)
if (i > 0 && !visited.get(i-1, j))
{
visited.set(i-1, j, true);
queue.push(std::make_pair(i-1, j));
}
if (i < (tiles-1) && !visited.get(i+1, j))
{
visited.set(i+1, j, true);
queue.push(std::make_pair(i+1, j));
}
if (j > 0 && !visited.get(i, j-1))
{
visited.set(i, j-1, true);
queue.push(std::make_pair(i, j-1));
}
if (j < (tiles-1) && !visited.get(i, j+1))
{
visited.set(i, j+1, true);
queue.push(std::make_pair(i, j+1));
}
}
}
MakeDirty();
}
void Undo()
{
m_TerrainDelta.Undo();
MakeDirty();
}
void Redo()
{
m_TerrainDelta.Redo();
MakeDirty();
}
};
END_COMMAND(FillTerrain)
}
Index: ps/trunk/source/tools/atlas/GameInterface/MessagesSetup.h
===================================================================
--- ps/trunk/source/tools/atlas/GameInterface/MessagesSetup.h (revision 22442)
+++ ps/trunk/source/tools/atlas/GameInterface/MessagesSetup.h (revision 22443)
@@ -1,213 +1,213 @@
/* 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 .
*/
// Used by Messages.h, so that file stays relatively clean.
#ifndef MESSAGESSETUP_NOTFIRST
#define MESSAGESSETUP_NOTFIRST
#include "MessagePasser.h"
#include "SharedTypes.h"
#include "Shareable.h"
// Structures in this file are passed over the DLL boundary, so some
// carefulness and/or luck is required...
namespace AtlasMessage
{
struct IMessage
{
virtual const char* GetName() const = 0;
virtual ~IMessage() {}
enum Type { Message, Query };
virtual Type GetType() const = 0;
};
#define MESSAGESTRUCT(t) \
struct m##t : public IMessage { \
virtual const char* GetName() const { return #t; } \
virtual Type GetType() const { return IMessage::Message; } \
private: \
const m##t& operator=(const m##t&); \
public:
// Messages for doing/undoing/etc world-altering commands
MESSAGESTRUCT(WorldCommand)
mWorldCommand() {}
virtual void* CloneData() const = 0;
virtual bool IsMergeable() const = 0;
};
MESSAGESTRUCT(DoCommand)
mDoCommand(mWorldCommand* c) : name(c->GetName()), data(c->CloneData()) {}
const Shareable name;
const Shareable data;
// 'data' gets deallocated by ~cWhatever in the game thread
};
MESSAGESTRUCT(UndoCommand) };
MESSAGESTRUCT(RedoCommand) };
MESSAGESTRUCT(MergeCommand) };
struct QueryMessage : public IMessage
{
Type GetType() const { return IMessage::Query; }
void Post(); // defined in ScenarioEditor.cpp
void* m_Semaphore; // for use by MessagePasser implementations (yay encapsulation)
};
#define QUERYSTRUCT(t) \
struct q##t : public QueryMessage { \
const char* GetName() const { return #t; } \
private: \
const q##t& operator=(const q##t&); \
public:
const bool MERGE = true;
const bool NOMERGE = false;
#define COMMANDDATASTRUCT(t) \
struct d##t { \
private: \
- const d##t& operator=(const d##t&); \
+ d##t& operator=(const d##t&) = delete; \
public:
#define COMMANDSTRUCT(t, merge) \
struct m##t : public mWorldCommand, public d##t { \
m##t(const d##t& d) : d##t(d) {} \
const char* GetName() const { return #t; } \
virtual bool IsMergeable() const { return merge; } \
void* CloneData() const { return SHAREABLE_NEW(d##t, (*this)); } \
private: \
const m##t& operator=(const m##t&);\
}
#include
#include
#include
#include
#define B_TYPE(elem) BOOST_PP_TUPLE_ELEM(2, 0, elem)
#define B_NAME(elem) BOOST_PP_TUPLE_ELEM(2, 1, elem)
#define B_CONSTRUCTORARGS(r, data, n, elem) BOOST_PP_COMMA_IF(n) B_TYPE(elem) BOOST_PP_CAT(B_NAME(elem),_)
#define B_CONSTRUCTORTYPES(r, data, n, elem) BOOST_PP_COMMA_IF(n) B_TYPE(elem)
#define B_CONSTRUCTORINIT(r, data, n, elem) BOOST_PP_COMMA_IF(n) B_NAME(elem)(BOOST_PP_CAT(B_NAME(elem),_))
#define B_CONSTMEMBERS(r, data, n, elem) const Shareable< B_TYPE(elem) > B_NAME(elem);
#define B_MEMBERS(r, data, n, elem) Shareable< B_TYPE(elem) > B_NAME(elem);
/* For each message type, generate something roughly like:
struct mBlah : public IMessage {
const char* GetName() const { return "Blah"; }
mBlah(int in0_, bool in1_) : in0(in0_), in1(in1_) {}
static mBlah* CtorType (int, bool) { return NULL; } // This doesn't do anything useful - it's just to make template-writing easier
const Shareable in0;
const Shareable in1;
}
*/
#define MESSAGE_WITH_INPUTS(name, vals) \
MESSAGESTRUCT(name) \
m##name( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORARGS, ~, vals) ) \
: BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORINIT, ~, vals) {} \
static m##name* CtorType( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORTYPES, ~, vals) ) { return NULL; } \
BOOST_PP_SEQ_FOR_EACH_I(B_CONSTMEMBERS, ~, vals) \
}
#define MESSAGE_WITHOUT_INPUTS(name, vals) \
MESSAGESTRUCT(name) \
m##name() {} \
static m##name* CtorType() { return NULL; } \
}
#define MESSAGE(name, vals) \
BOOST_PP_IIF( \
BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE((~)vals), 1), \
MESSAGE_WITHOUT_INPUTS, \
MESSAGE_WITH_INPUTS) \
(name, vals)
#define COMMAND(name, merge, vals) \
COMMANDDATASTRUCT(name) \
d##name( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORARGS, ~, vals) ) \
: BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORINIT, ~, vals) {} \
BOOST_PP_SEQ_FOR_EACH_I(B_CONSTMEMBERS, ~, vals) \
}; \
COMMANDSTRUCT(name, merge)
// Need different syntax depending on whether there are some input values in the query:
#define QUERY_WITHOUT_INPUTS(name, in_vals, out_vals) \
QUERYSTRUCT(name) \
q##name() {} \
static q##name* CtorType() { return NULL; } \
BOOST_PP_SEQ_FOR_EACH_I(B_MEMBERS, ~, out_vals) /* other members */ \
}
#define QUERY_WITH_INPUTS(name, in_vals, out_vals) \
QUERYSTRUCT(name) \
q##name( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORARGS, ~, in_vals) ) \
: BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORINIT, ~, in_vals) {} \
static q##name* CtorType( BOOST_PP_SEQ_FOR_EACH_I(B_CONSTRUCTORTYPES, ~, in_vals) ) { return NULL; } \
BOOST_PP_SEQ_FOR_EACH_I(B_CONSTMEMBERS, ~, in_vals) \
BOOST_PP_SEQ_FOR_EACH_I(B_MEMBERS, ~, out_vals) \
}
#define QUERY(name, in_vals, out_vals) \
BOOST_PP_IIF( \
BOOST_PP_EQUAL(BOOST_PP_SEQ_SIZE((~)in_vals), 1), \
QUERY_WITHOUT_INPUTS, \
QUERY_WITH_INPUTS) \
(name, in_vals, out_vals)
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
#else // MESSAGESSETUP_NOTFIRST => clean up the mess
#undef MESSAGESTRUCT
#undef QUERYSTRUCT
#undef COMMANDDATASTRUCT
#undef COMMANDSTRUCT
#undef B_TYPE
#undef B_NAME
#undef B_CONSTRUCTORARGS
#undef B_CONSTRUCTORTYPES
#undef B_CONSTRUCTORINIT
#undef B_CONSTMEMBERS
#undef B_MEMBERS
#undef MESSAGE
#undef COMMAND
#undef QUERY_WITHOUT_INPUTS
#undef QUERY_WITH_INPUTS
#undef QUERY
}
#endif