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