Index: build/premake/premake5.lua =================================================================== --- build/premake/premake5.lua +++ build/premake/premake5.lua @@ -730,7 +730,8 @@ "graphics/scripting", "renderer", "renderer/scripting", - "third_party/mikktspace" + "third_party/mikktspace", + "third_party/ogre3d_preprocessor" } extern_libs = { "opengl", @@ -1118,6 +1119,7 @@ "CustomControls/FileHistory", "CustomControls/HighResTimer", "CustomControls/MapDialog", + "CustomControls/MapResizeDialog", "CustomControls/SnapSplitterWindow", "CustomControls/VirtualDirTreeCtrl", "CustomControls/Windows", Index: source/graphics/MaterialManager.cpp =================================================================== --- source/graphics/MaterialManager.cpp +++ source/graphics/MaterialManager.cpp @@ -19,13 +19,13 @@ #include "MaterialManager.h" +#include "graphics/PreprocessorWrapper.h" #include "lib/ogl.h" #include "maths/MathUtil.h" #include "maths/Vector4D.h" #include "ps/CLogger.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" -#include "ps/PreprocessorWrapper.h" #include "ps/XML/Xeromyces.h" #include "renderer/RenderingOptions.h" Index: source/graphics/PreprocessorWrapper.h =================================================================== --- source/graphics/PreprocessorWrapper.h +++ source/graphics/PreprocessorWrapper.h @@ -1,4 +1,4 @@ -/* 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 @@ -18,8 +18,8 @@ #ifndef INCLUDED_PREPROCESSORWRAPPER #define INCLUDED_PREPROCESSORWRAPPER -#include "ps/Preprocessor.h" #include "ps/CStr.h" +#include "third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.h" class CShaderDefines; @@ -29,6 +29,8 @@ class CPreprocessorWrapper { public: + CPreprocessorWrapper(); + void AddDefine(const char* name, const char* value); void AddDefines(const CShaderDefines& defines); @@ -37,6 +39,8 @@ CStr Preprocess(const CStr& input); + static void PyrogenesisShaderError(void* UNUSED(iData), int iLine, const char* iError, const char* iToken, size_t iTokenLen); + private: CPreprocessor m_Preprocessor; }; Index: source/graphics/PreprocessorWrapper.cpp =================================================================== --- source/graphics/PreprocessorWrapper.cpp +++ source/graphics/PreprocessorWrapper.cpp @@ -1,4 +1,4 @@ -/* 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 @@ -22,6 +22,19 @@ #include "graphics/ShaderDefines.h" #include "ps/CLogger.h" +void CPreprocessorWrapper::PyrogenesisShaderError(void* UNUSED(iData), int iLine, const char* iError, const char* iToken, size_t iTokenLen) +{ + if (iToken) + LOGERROR("Preprocessor error: line %d: %s: '%s'\n", iLine, iError, std::string(iToken, iTokenLen).c_str()); + else + LOGERROR("Preprocessor error: line %d: %s\n", iLine, iError); +} + +CPreprocessorWrapper::CPreprocessorWrapper() +{ + CPreprocessor::ErrorHandler = CPreprocessorWrapper::PyrogenesisShaderError; +} + void CPreprocessorWrapper::AddDefine(const char* name, const char* value) { m_Preprocessor.Define(name, value); Index: source/graphics/ShaderManager.cpp =================================================================== --- source/graphics/ShaderManager.cpp +++ source/graphics/ShaderManager.cpp @@ -19,6 +19,7 @@ #include "ShaderManager.h" +#include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderTechnique.h" #include "lib/config2.h" #include "lib/hash.h" @@ -27,7 +28,6 @@ #include "ps/CLogger.h" #include "ps/CStrIntern.h" #include "ps/Filesystem.h" -#include "ps/PreprocessorWrapper.h" #include "ps/Profile.h" #if USE_SHADER_XML_VALIDATION # include "ps/XML/RelaxNG.h" Index: source/graphics/ShaderProgram.cpp =================================================================== --- source/graphics/ShaderProgram.cpp +++ source/graphics/ShaderProgram.cpp @@ -20,6 +20,7 @@ #include "ShaderProgram.h" #include "graphics/Color.h" +#include "graphics/PreprocessorWrapper.h" #include "graphics/ShaderManager.h" #include "graphics/TextureManager.h" #include "lib/res/graphics/ogl_tex.h" @@ -27,7 +28,6 @@ #include "maths/Vector3D.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" -#include "ps/PreprocessorWrapper.h" #if !CONFIG2_GLES Index: source/graphics/Terrain.h =================================================================== --- source/graphics/Terrain.h +++ source/graphics/Terrain.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 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 @@ -98,8 +98,9 @@ // should be in the direction (1,-1); false if it should be (1,1) bool GetTriangulationDir(ssize_t i, ssize_t j) const; - // resize this terrain such that each side has given number of patches - void Resize(ssize_t size); + // Resize this terrain such that each side has given number of patches, + // with the center offset in patches from the larger of the two sizes. + void ResizeRecenter(ssize_t size, int horizontalOffset = 0, int verticalOffset = 0); // set up a new heightmap from 16 bit data; assumes heightmap matches current terrain size void SetHeightMap(u16* heightmap); Index: source/graphics/Terrain.cpp =================================================================== --- source/graphics/Terrain.cpp +++ source/graphics/Terrain.cpp @@ -74,11 +74,11 @@ ReleaseData(); // store terrain size - m_MapSize = patchesPerSide*PATCH_SIZE+1; + m_MapSize = patchesPerSide * PATCH_SIZE + 1; m_MapSizePatches = patchesPerSide; // allocate data for new terrain - m_Heightmap = new u16[m_MapSize*m_MapSize]; - m_Patches = new CPatch[m_MapSizePatches*m_MapSizePatches]; + m_Heightmap = new u16[m_MapSize * m_MapSize]; + m_Patches = new CPatch[m_MapSizePatches * m_MapSizePatches]; // given a heightmap? if (data) @@ -496,98 +496,136 @@ return (mid1 < mid2); } -/////////////////////////////////////////////////////////////////////////////// -// Resize: resize this terrain to the given size (in patches per side) -void CTerrain::Resize(ssize_t size) +// Resize/Recenter: resize this terrain to the given size (in patches per side). +// The offset is in patches from the center of the source. +void CTerrain::ResizeRecenter(ssize_t size, i32 horizontalOffset, i32 verticalOffset) { - if (size==m_MapSizePatches) { - // inexplicable request to resize terrain to the same size .. ignore it + if (size == m_MapSizePatches && horizontalOffset == 0 && verticalOffset == 0) + { + // Inexplicable request to resize terrain to the same size .. ignore it return; } - if (!m_Heightmap) { - // not yet created a terrain; build a default terrain of the given size now - Initialize(size,0); + if (!m_Heightmap || + std::abs(horizontalOffset) > std::max(size, m_MapSizePatches) / 2 || + std::abs(verticalOffset) > std::max(size, m_MapSizePatches) / 2) + { + // We have not yet created a terrain, or we are offsetting outside the current source. + // Let's build a default terrain of the given size now. + Initialize(size, 0); return; } - // allocate data for new terrain - ssize_t newMapSize=size*PATCH_SIZE+1; - u16* newHeightmap=new u16[newMapSize*newMapSize]; - CPatch* newPatches=new CPatch[size*size]; - - if (size>m_MapSizePatches) { - // new map is bigger than old one - zero the heightmap so we don't get uninitialised - // height data along the expanded edges - memset(newHeightmap,0,newMapSize*newMapSize*sizeof(u16)); - } + // Allocate data for new terrain + ssize_t newMapSize = size * PATCH_SIZE + 1; + u16* newHeightmap = new u16[newMapSize * newMapSize]; + CPatch* newPatches = new CPatch[size * size]; - // now copy over rows of data - u16* src=m_Heightmap; - u16* dst=newHeightmap; - ssize_t copysize=std::min(newMapSize, m_MapSize); - for (ssize_t j=0;jm_MapSize) { - // extend the last height to the end of the row - for (size_t i=0;i(0), (size - m_MapSizePatches) / 2 + horizontalOffset); + const ssize_t upperLeftZ = std::max(static_cast(0), (size - m_MapSizePatches) / 2 + verticalOffset); + const ssize_t width = std::min(size, (size - m_MapSizePatches) / 2 + horizontalOffset + m_MapSizePatches) - upperLeftX; + const ssize_t height = std::min(size, (size - m_MapSizePatches) / 2 + verticalOffset + m_MapSizePatches) - upperLeftZ; + const ssize_t upperLeftXSource = std::max(static_cast(0), (m_MapSizePatches - size) / 2 - horizontalOffset); + const ssize_t upperLeftZSource = std::max(static_cast(0), (m_MapSizePatches - size) / 2 - verticalOffset); - if (newMapSize>m_MapSize) { - // copy over heights of the last row to any remaining rows - src=newHeightmap+((m_MapSize-1)*newMapSize); - dst=src+newMapSize; - for (ssize_t i=0;im_MapSizePatches) { - // copy over the last tile from each column - for (ssize_t n=0;nm_MapSizePatches) { - // copy over the last tile from each column - CPatch* srcpatch=&newPatches[(m_MapSizePatches-1)*size]; - CPatch* dstpatch=srcpatch+size; - for (ssize_t p=0;p<(ssize_t)size-m_MapSizePatches;p++) { - for (ssize_t n=0;n<(ssize_t)size;n++) { - for (ssize_t m=0;mm_MiniPatches[15][k]; - CMiniPatch& dst=dstpatch->m_MiniPatches[m][k]; - dst = src; - } + for (ssize_t j = 0; j < upperLeftZ; ++j) + { + CPatch* srcpatch = newPatches + (upperLeftZ * size); + CPatch* dstpatch = newPatches + j * size; + for (ssize_t i = 0; i < size; ++i) + { + for (ssize_t patch_j = 0; patch_j < PATCH_SIZE; ++patch_j) + { + for (ssize_t patch_i = 0; patch_i < PATCH_SIZE; ++patch_i) + { + CMiniPatch& src = srcpatch->m_MiniPatches[0][patch_i]; + CMiniPatch& dst = dstpatch->m_MiniPatches[patch_j][patch_i]; + dst = src; + } + } + ++srcpatch; + ++dstpatch; + } + } + for (ssize_t j = upperLeftZ + height; j < size; ++j) + { + CPatch* srcpatch = newPatches + ((upperLeftZ + height - 1) * size); + CPatch* dstpatch = newPatches + j * size; + for (ssize_t i = 0; i < size; ++i) + { + for (ssize_t patch_j = 0; patch_j < PATCH_SIZE; ++patch_j) + { + for (ssize_t patch_i = 0; patch_i < PATCH_SIZE; ++patch_i) + { + CMiniPatch& src = srcpatch->m_MiniPatches[PATCH_SIZE - 1][patch_i]; + CMiniPatch& dst = dstpatch->m_MiniPatches[patch_j][patch_i]; + dst = src; } - srcpatch++; - dstpatch++; } + ++srcpatch; + ++dstpatch; } } @@ -596,16 +634,16 @@ ReleaseData(); // store new data - m_Heightmap=newHeightmap; - m_Patches=newPatches; - m_MapSize=(ssize_t)newMapSize; - m_MapSizePatches=(ssize_t)size; + m_Heightmap = newHeightmap; + m_Patches = newPatches; + m_MapSize = newMapSize; + m_MapSizePatches = size; // initialise all the new patches InitialisePatches(); // initialise mipmap - m_HeightMipmap.Initialize(m_MapSize,m_Heightmap); + m_HeightMipmap.Initialize(m_MapSize, m_Heightmap); } /////////////////////////////////////////////////////////////////////////////// Index: source/gui/ObjectTypes/CMiniMap.h =================================================================== --- source/gui/ObjectTypes/CMiniMap.h +++ source/gui/ObjectTypes/CMiniMap.h @@ -32,6 +32,11 @@ public: CMiniMap(CGUI& pGUI); virtual ~CMiniMap(); + /** + * Gets the maximum height for unit passage in water. + */ + static float GetShallowPassageHeight(); + protected: virtual void Draw(); Index: source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- source/gui/ObjectTypes/CMiniMap.cpp +++ source/gui/ObjectTypes/CMiniMap.cpp @@ -48,7 +48,7 @@ #include "simulation2/Simulation2.h" #include "simulation2/system/ParamNode.h" -#include +#include extern bool g_GameRestarted; @@ -76,14 +76,7 @@ // Register Relax NG validator CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); - // Get the maximum height for unit passage in water. - CParamNode externalParamNode; - CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); - const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); - if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) - m_ShallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); - else - m_ShallowPassageHeight = 0.0f; + m_ShallowPassageHeight = GetShallowPassageHeight(); m_AttributePos.type = GL_FLOAT; m_AttributePos.elems = 2; @@ -709,3 +702,14 @@ SAFE_ARRAY_DELETE(m_TerrainData); } + +float CMiniMap::GetShallowPassageHeight() +{ + float shallowPassageHeight = 0.0f; + CParamNode externalParamNode; + CParamNode::LoadXML(externalParamNode, L"simulation/data/pathfinder.xml", "pathfinder"); + const CParamNode pathingSettings = externalParamNode.GetChild("Pathfinder").GetChild("PassabilityClasses"); + if (pathingSettings.GetChild("default").IsOk() && pathingSettings.GetChild("default").GetChild("MaxWaterDepth").IsOk()) + shallowPassageHeight = pathingSettings.GetChild("default").GetChild("MaxWaterDepth").ToFloat(); + return shallowPassageHeight; +} Index: source/ps/Preprocessor.h =================================================================== --- /dev/null +++ source/ps/Preprocessor.h @@ -1,541 +0,0 @@ -/* - * This source file originally came from OGRE v1.7.2 - http://www.ogre3d.org/ - * with some tweaks as part of 0 A.D. - * All changes are released under the original license, as follows: - */ - -/* ------------------------------------------------------------------------------ -This source file is part of OGRE - (Object-oriented Graphics Rendering Engine) -For the latest info, see http://www.ogre3d.org/ - -Copyright (c) 2000-2009 Torus Knot Software Ltd - -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. ------------------------------------------------------------------------------ -*/ - -#ifndef INCLUDED_CPREPROCESSOR -#define INCLUDED_CPREPROCESSOR - -/** - * This is a simplistic C/C++-like preprocessor. - * It takes an non-zero-terminated string on input and outputs a - * non-zero-terminated string buffer. - * - * This preprocessor was designed specifically for GLSL shaders, so - * if you want to use it for other purposes you might want to check - * if the feature set it provides is enough for you. - * - * Here's a list of supported features: - *
    - *
  • Fast memory allocation-less operation (mostly).
  • - *
  • Line continuation (backslash-newline) is swallowed.
  • - *
  • Line numeration is fully preserved by inserting empty lines where - * required. This is crucial if, say, GLSL compiler reports you an error - * with a line number.
  • - *
  • \#define: Parametrized and non-parametrized macros. Invoking a macro with - * less arguments than it takes assignes empty values to missing arguments.
  • - *
  • \#undef: Forget defined macros
  • - *
  • \#ifdef/\#ifndef/\#else/\#endif: Conditional suppression of parts of code.
  • - *
  • \#if: Supports numeric expression of any complexity, also supports the - * defined() pseudo-function.
  • - *
- */ -class CPreprocessor -{ - /** - * A input token. - * - * For performance reasons most tokens will point to portions of the - * input stream, so no unneeded memory allocation is done. However, - * in some cases we must allocate different memory for token storage, - * in this case this is signalled by setting the Allocated member - * to non-zero in which case the destructor will know that it must - * free memory on object destruction. - * - * Again for performance reasons we use malloc/realloc/free here because - * C++-style new[] lacks the realloc() counterpart. - */ - class Token - { - public: - enum Kind - { - TK_EOS, // End of input stream - TK_ERROR, // An error has been encountered - TK_WHITESPACE, // A whitespace span (but not newline) - TK_NEWLINE, // A single newline (CR & LF) - TK_LINECONT, // Line continuation ('\' followed by LF) - TK_NUMBER, // A number - TK_KEYWORD, // A keyword - TK_PUNCTUATION, // A punctuation character - TK_DIRECTIVE, // A preprocessor directive - TK_STRING, // A string - TK_COMMENT, // A block comment - TK_LINECOMMENT, // A line comment - TK_TEXT, // An unparsed text (cannot be returned from GetToken()) - }; - - /// Token type - Kind Type; - /// True if string was allocated (and must be freed) - mutable size_t Allocated; - union - { - /// A pointer somewhere into the input buffer - const char *String; - /// A memory-allocated string - char *Buffer; - }; - /// Token length in bytes - size_t Length; - - Token () : Type (TK_ERROR), Allocated (0), String (NULL), Length (0) - { } - - Token (Kind iType) : Type (iType), Allocated (0), String (NULL), Length (0) - { } - - Token (Kind iType, const char *iString, size_t iLength) : - Type (iType), Allocated (0), String (iString), Length (iLength) - { } - - Token (const Token &iOther) - { - Type = iOther.Type; - Allocated = iOther.Allocated; - iOther.Allocated = 0; // !!! not quite correct but effective - String = iOther.String; - Length = iOther.Length; - } - - ~Token () - { if (Allocated) free (Buffer); } - - /// Assignment operator - Token &operator = (const Token &iOther) - { - if (Allocated) free (Buffer); - Type = iOther.Type; - Allocated = iOther.Allocated; - iOther.Allocated = 0; // !!! not quite correct but effective - String = iOther.String; - Length = iOther.Length; - return *this; - } - - /// Append a string to this token - void Append (const char *iString, size_t iLength); - - /// Append a token to this token - void Append (const Token &iOther); - - /// Append given number of newlines to this token - void AppendNL (int iCount); - - /// Count number of newlines in this token - int CountNL (); - - /// Get the numeric value of the token - bool GetValue (long &oValue) const; - - /// Set the numeric value of the token - void SetValue (long iValue); - - /// Test two tokens for equality - bool operator == (const Token &iOther) - { - if (iOther.Length != Length) - return false; - return (memcmp (String, iOther.String, Length) == 0); - } - }; - - /// A macro definition - class Macro - { - public: - /// Macro name - Token Name; - /// Number of arguments - int NumArgs; - /// The names of the arguments - Token *Args; - /// The macro value - Token Value; - /// Unparsed macro body (keeps the whole raw unparsed macro body) - Token Body; - /// Next macro in chained list - Macro *Next; - /// A pointer to function implementation (if macro is really a func) - Token (*ExpandFunc) (CPreprocessor *iParent, int iNumArgs, Token *iArgs); - /// true if macro expansion is in progress - bool Expanding; - - Macro (const Token &iName) : - Name (iName), NumArgs (0), Args (NULL), Next (NULL), - ExpandFunc (NULL), Expanding (false) - { } - - ~Macro () - { delete [] Args; delete Next; } - - /// Expand the macro value (will not work for functions) - Token Expand (int iNumArgs, Token *iArgs, Macro *iMacros); - }; - - friend class CPreprocessor::Macro; - - /// The current source text input - const char *Source; - /// The end of the source text - const char *SourceEnd; - /// Current line number - int Line; - /// True if we are at beginning of line - bool BOL; - /// A stack of 32 booleans packed into one value :) - unsigned EnableOutput; - /// The list of macros defined so far - Macro *MacroList; - - /** - * Private constructor to re-parse a single token. - */ - CPreprocessor (const Token &iToken, int iLine); - - /** - * Stateless tokenizer: Parse the input text and return the next token. - * @param iExpand - * If true, macros will be expanded to their values - * @return - * The next token from the input stream - */ - Token GetToken (bool iExpand); - - /** - * Handle a preprocessor directive. - * @param iToken - * The whole preprocessor directive line (until EOL) - * @param iLine - * The line where the directive begins (for error reports) - * @return - * The last input token that was not proceeded. - */ - Token HandleDirective (Token &iToken, int iLine); - - /** - * Handle a \#define directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleDefine (Token &iBody, int iLine); - - /** - * Undefine a previously defined macro - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleUnDef (Token &iBody, int iLine); - - /** - * Handle an \#ifdef directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleIfDef (Token &iBody, int iLine); - - /** - * Handle an \#if directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleIf (Token &iBody, int iLine); - - /** - * Handle an \#else directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleElse (Token &iBody, int iLine); - - /** - * Handle an \#endif directive. - * @param iBody - * The body of the directive (everything after the directive - * until end of line). - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if everything went ok, false if not - */ - bool HandleEndIf (Token &iBody, int iLine); - - /** - * Get a single function argument until next ',' or ')'. - * @param oArg - * The argument is returned in this variable. - * @param iExpand - * If false, parameters are not expanded and no expressions are - * allowed; only a single keyword is expected per argument. - * @return - * The first unhandled token after argument. - */ - Token GetArgument (Token &oArg, bool iExpand); - - /** - * Get all the arguments of a macro: '(' arg1 { ',' arg2 { ',' ... }} ')' - * @param oNumArgs - * Number of parsed arguments is stored into this variable. - * @param oArgs - * This is set to a pointer to an array of parsed arguments. - * @param iExpand - * If false, parameters are not expanded and no expressions are - * allowed; only a single keyword is expected per argument. - */ - Token GetArguments (int &oNumArgs, Token *&oArgs, bool iExpand); - - /** - * Parse an expression, compute it and return the result. - * @param oResult - * A token containing the result of expression - * @param iLine - * The line at which the expression starts (for error reports) - * @param iOpPriority - * Operator priority (at which operator we will stop if - * proceeding recursively -- used internally. Parser stops - * when it encounters an operator with higher or equal priority). - * @return - * The last unhandled token after the expression - */ - Token GetExpression (Token &oResult, int iLine, int iOpPriority = 0); - - /** - * Get the numeric value of a token. - * If the token was produced by expanding a macro, we will get - * an TEXT token which can contain a whole expression; in this - * case we will call GetExpression to parse it. Otherwise we - * just call the token's GetValue() method. - * @param iToken - * The token to get the numeric value of - * @param oValue - * The variable to put the value into - * @param iLine - * The line where the directive begins (for error reports) - * @return - * true if ok, false if not - */ - bool GetValue (const Token &iToken, long &oValue, int iLine); - - /** - * Expand the given macro, if it exists. - * If macro has arguments, they are collected from source stream. - * @param iToken - * A KEYWORD token containing the (possible) macro name. - * @return - * The expanded token or iToken if it is not a macro - */ - Token ExpandMacro (const Token &iToken); - - /** - * Check if a macro is defined, and if so, return it - * @param iToken - * Macro name - * @return - * The macro object or NULL if a macro with this name does not exist - */ - Macro *IsDefined (const Token &iToken); - - /** - * The implementation of the defined() preprocessor function - * @param iParent - * The parent preprocessor object - * @param iNumArgs - * Number of arguments - * @param iArgs - * The arguments themselves - * @return - * The return value encapsulated in a token - */ - static Token ExpandDefined (CPreprocessor *iParent, int iNumArgs, Token *iArgs); - - /** - * Parse the input string and return a token containing the whole output. - * @param iSource - * The source text enclosed in a token - * @return - * The output text enclosed in a token - */ - Token Parse (const Token &iSource); - - /** - * Call the error handler - * @param iLine - * The line at which the error happened. - * @param iError - * The error string. - * @param iToken - * If not NULL contains the erroneous token - */ - void Error (int iLine, const char *iError, const Token *iToken = NULL); - -public: - /// Create an empty preprocessor object - CPreprocessor () : MacroList (NULL) - { } - - /// Destroy the preprocessor object - virtual ~CPreprocessor (); - - /** - * Define a macro without parameters. - * @param iMacroName - * The name of the defined macro - * @param iMacroNameLen - * The length of the name of the defined macro - * @param iMacroValue - * The value of the defined macro - * @param iMacroValueLen - * The length of the value of the defined macro - */ - void Define (const char *iMacroName, size_t iMacroNameLen, - const char *iMacroValue, size_t iMacroValueLen); - - /** - * Define a numerical macro. - * @param iMacroName - * The name of the defined macro - * @param iMacroNameLen - * The length of the name of the defined macro - * @param iMacroValue - * The value of the defined macro - */ - void Define (const char *iMacroName, size_t iMacroNameLen, long iMacroValue); - - /** - * Define a macro without parameters. - * @param iMacroName - * The name of the defined macro - * @param iMacroValue - * The value of the defined macro - */ - void Define (const char *iMacroName, const char *iMacroValue); - - /** - * Define a numerical macro. - * @param iMacroName - * The name of the defined macro - * @param iMacroValue - * The value of the defined macro - */ - void Define (const char *iMacroName, long iMacroValue); - - /** - * Undefine a macro. - * @param iMacroName - * The name of the macro to undefine - * @param iMacroNameLen - * The length of the name of the macro to undefine - * @return - * true if the macro has been undefined, false if macro doesn't exist - */ - bool Undef (const char *iMacroName, size_t iMacroNameLen); - - /** - * Parse the input string and return a newly-allocated output string. - * @note - * The returned preprocessed string is NOT zero-terminated - * (just like the input string). - * @param iSource - * The source text - * @param iLength - * The length of the source text in characters - * @param oLength - * The length of the output string. - * @return - * The output from preprocessor, allocated with malloc(). - * The parser can actually allocate more than needed for performance - * reasons, but this should not be a problem unless you will want - * to store the returned pointer for long time in which case you - * might want to realloc() it. - * If an error has been encountered, the function returns NULL. - * In some cases the function may return an unallocated address - * that's *inside* the source buffer. You must free() the result - * string only if the returned address is not inside the source text. - */ - char *Parse (const char *iSource, size_t iLength, size_t &oLength); - - /** - * An error handler function type. - * The default implementation just drops a note to stderr and - * then the parser ends, returning NULL. - * @param iData - * User-specific pointer from the corresponding CPreprocessor object. - * @param iLine - * The line at which the error happened. - * @param iError - * The error string. - * @param iToken - * If not NULL contains the erroneous token - * @param iTokenLen - * The length of iToken. iToken is never zero-terminated! - */ - typedef void (*ErrorHandlerFunc) ( - void *iData, int iLine, const char *iError, - const char *iToken, size_t iTokenLen); - - /** - * A pointer to the preprocessor's error handler. - * You can assign the address of your own function to this variable - * and implement your own error handling (e.g. throwing an exception etc). - */ - static ErrorHandlerFunc ErrorHandler; - - /// User-specific storage, passed to Error() - void *ErrorData; -}; - -#endif // INCLUDED_CPREPROCESSOR Index: source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp =================================================================== --- source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp +++ source/third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.cpp @@ -34,9 +34,7 @@ #include "precompiled.h" -#include "Preprocessor.h" - -#include "ps/CLogger.h" +#include "OgreGLSLPreprocessor.h" // Limit max number of macro arguments to this #define MAX_MACRO_ARGS 16 @@ -230,11 +228,12 @@ const char *iToken, size_t iTokenLen) { (void)iData; + char line [1000]; if (iToken) - LOGERROR("Preprocessor error: line %d: %s: '%s'\n", - iLine, iError, std::string (iToken, iTokenLen)); + snprintf (line, sizeof (line), "line %d: %s: `%.*s'\n", + iLine, iError, int (iTokenLen), iToken); else - LOGERROR("Preprocessor error: line %d: %s\n", iLine, iError); + snprintf (line, sizeof (line), "line %d: %s\n", iLine, iError); } //---------------------------------------------------------------------------// Index: source/third_party/ogre3d_preprocessor/tests/test_Preprocessor.h =================================================================== --- source/third_party/ogre3d_preprocessor/tests/test_Preprocessor.h +++ source/third_party/ogre3d_preprocessor/tests/test_Preprocessor.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2016 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 @@ -17,11 +17,17 @@ #include "lib/self_test.h" -#include "ps/Preprocessor.h" +#include "graphics/PreprocessorWrapper.h" +#include "third_party/ogre3d_preprocessor/OgreGLSLPreprocessor.h" class TestPreprocessor : public CxxTest::TestSuite { public: + void setUp() + { + CPreprocessor::ErrorHandler = CPreprocessorWrapper::PyrogenesisShaderError; + } + void test_basic() { CPreprocessor preproc; Index: source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.h =================================================================== --- /dev/null +++ source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.h @@ -0,0 +1,49 @@ +/* Copyright (C) 2019 Wildfire Games. +* This file is part of 0 A.D. +* +* 0 A.D. is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 of the License, or +* (at your option) any later version. +* +* 0 A.D. is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with 0 A.D. If not, see . +*/ + +#ifndef INCLUDED_MAPRESIZEDIALOG +#define INCLUDED_MAPRESIZEDIALOG + +#include "PseudoMiniMapPanel.h" +#include + +class MapResizeDialog : public wxDialog +{ +public: + MapResizeDialog(wxWindow* parent); + + /** + * Returns selected new size. + */ + ssize_t GetNewSize() const; + /** + * Returns the offset from center. + */ + wxPoint GetOffset() const; + +private: + void OnCancel(wxCommandEvent& evt); + void OnOK(wxCommandEvent& evt); + void OnListBox(wxCommandEvent& evt); + + ssize_t m_NewSize; + PseudoMiniMapPanel* m_MiniMap; + + DECLARE_EVENT_TABLE(); +}; + +#endif // INCLUDED_MAPRESIZEDIALOG Index: source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.cpp =================================================================== --- /dev/null +++ source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/MapResizeDialog.cpp @@ -0,0 +1,118 @@ +/* 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 "MapResizeDialog.h" +#include "ScenarioEditor/ScenarioEditor.h" + +#include "GameInterface/MessagePasser.h" +#include "GameInterface/Messages.h" + +#include + +MapResizeDialog::MapResizeDialog(wxWindow* parent) + : wxDialog(parent, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxCAPTION | wxRESIZE_BORDER) +{ + Freeze(); + + AtlasMessage::qGetCurrentMapSize qrySize; + qrySize.Post(); + m_NewSize = qrySize.size; + + SetTitle(_("Resize/Recenter map")); + wxSizer* sizer = new wxBoxSizer(wxVERTICAL); + wxStaticText* label = new wxStaticText(this, wxID_ANY, _("Select new map size and center."), wxDefaultPosition, wxDefaultSize); + sizer->Add(label, wxSizerFlags().Align(wxALIGN_CENTER_HORIZONTAL).Border(wxALL, 10)); + + wxBoxSizer* listAndMap = new wxBoxSizer(wxHORIZONTAL); + + wxListBox* listBox = new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, 0, nullptr, wxLB_SINGLE | wxLB_HSCROLL); + // Load the map sizes list + AtlasMessage::qGetMapSizes qrySizes; + qrySizes.Post(); + AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes); + for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s) + { + wxString size(s["Tiles"]); + listBox->Append(wxString(s["Name"]), new wxStringClientData(size)); + if (m_NewSize == static_cast(wxAtoi(size))) + listBox->SetSelection(listBox->GetCount() - 1); + } + listAndMap->Add(listBox, wxSizerFlags().Align(wxALIGN_LEFT).Proportion(1).Expand()); + listAndMap->AddSpacer(10); + + m_MiniMap = new PseudoMiniMapPanel(this, m_NewSize); + listBox->Bind(wxEVT_LISTBOX, &PseudoMiniMapPanel::OnNewSize, m_MiniMap); + + listAndMap->Add(m_MiniMap, wxSizerFlags()); + sizer->Add(listAndMap, wxSizerFlags().Proportion(1).Expand().Border(wxLEFT | wxRIGHT, 10)); + + sizer->AddSpacer(5); + sizer->Add(new wxStaticLine(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxLI_HORIZONTAL), wxSizerFlags().Expand().Border(wxRIGHT | wxLEFT, 7)); + sizer->AddSpacer(5); + + wxSizer* buttonSizer = new wxBoxSizer(wxHORIZONTAL); + buttonSizer->Add(new wxButton(this, wxID_OK, _("OK"))); + buttonSizer->AddSpacer(5); + buttonSizer->Add(new wxButton(this, wxID_CANCEL, _("Cancel"))); + + sizer->Add(buttonSizer, wxSizerFlags().Align(wxALIGN_RIGHT).Border(wxRIGHT | wxBOTTOM, 10)); + + SetSizerAndFit(sizer); + Layout(); + Thaw(); +} + +ssize_t MapResizeDialog::GetNewSize() const +{ + return m_NewSize; +} + +wxPoint MapResizeDialog::GetOffset() const +{ + return m_MiniMap->GetOffset(); +} + +void MapResizeDialog::OnListBox(wxCommandEvent& evt) +{ + if (!evt.IsSelection()) + return; + + m_NewSize = wxAtoi(static_cast(evt.GetClientObject())->GetData()); + if (evt.GetEventType() == wxEVT_COMMAND_LISTBOX_DOUBLECLICKED) + { + EndModal(wxID_OK); + } +} + +void MapResizeDialog::OnCancel(wxCommandEvent& WXUNUSED(evt)) +{ + EndModal(wxID_CANCEL); +} + +void MapResizeDialog::OnOK(wxCommandEvent& WXUNUSED(evt)) +{ + EndModal(wxID_OK); +} + +BEGIN_EVENT_TABLE(MapResizeDialog, wxDialog) + EVT_BUTTON(wxID_CANCEL, MapResizeDialog::OnCancel) + EVT_BUTTON(wxID_OK, MapResizeDialog::OnOK) + EVT_LISTBOX(wxID_ANY, MapResizeDialog::OnListBox) + EVT_LISTBOX_DCLICK(wxID_ANY, MapResizeDialog::OnListBox) +END_EVENT_TABLE() Index: source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.h =================================================================== --- /dev/null +++ source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.h @@ -0,0 +1,56 @@ +/* Copyright (C) 2019 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + + +#ifndef INCLUDED_PSEUDOMINIMAPPANEL +#define INCLUDED_PSEUDOMINIMAPPANEL + +#include + +class PseudoMiniMapPanel : public wxPanel +{ +public: + PseudoMiniMapPanel(wxWindow* parent, int currentSize); + + void PaintEvent(wxPaintEvent& evt); + void EraseBackground(wxEraseEvent& evt); + + void OnNewSize(wxCommandEvent& evt); + + wxPoint GetOffset() const; +private: + void OnMouseDown(wxMouseEvent& evt); + void OnMouseUp(wxMouseEvent& evt); + void OnMouseMove(wxMouseEvent& evt); + void OnMouseLeave(wxMouseEvent& evt); + + const int m_CurrentSize; + wxImage m_Background; + std::map m_ScreenTones; + std::map m_Backgrounds; + + wxPoint m_LastMousePos; + bool m_Dragging; + wxPoint m_SelectionCenter; + int m_SelectionRadius; + bool m_SameOrGrowing; + int m_NewSize; + + DECLARE_EVENT_TABLE(); +}; + +#endif // INCLUDED_PSEUDOMINIMAPPANEL Index: source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.cpp =================================================================== --- /dev/null +++ source/tools/atlas/AtlasUI/CustomControls/MapResizeDialog/PseudoMiniMapPanel.cpp @@ -0,0 +1,232 @@ +/* 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 "PseudoMiniMapPanel.h" + +#include "GameInterface/MessagePasser.h" +#include "GameInterface/Messages.h" +#include "ScenarioEditor/Tools/Common/Tools.h" + +#include +#include +#include +#include +#include + +namespace +{ + const int PanelRadius = 64 + 1; + const wxPoint PanelCenter = wxPoint(PanelRadius + 1, PanelRadius + 1); + const wxPoint ScreenToneOffset(-2 * PanelRadius, -2 * PanelRadius); + const wxPen Rim(*wxBLACK, 3); + const wxPen BackgroundMask(*wxBLACK, 2 * PanelRadius); + + bool Within(const wxPoint& test, const wxPoint& center, int radius) + { + int dx = abs(test.x - center.x); + if (dx > radius) + return false; + int dy = abs(test.y - center.y); + if (dy > radius) + return false; + if (dx + dy <= radius) + return true; + return (dx * dx + dy * dy <= radius * radius); + } +} + +PseudoMiniMapPanel::PseudoMiniMapPanel(wxWindow* parent, int currentSize) + : wxPanel(parent, wxID_ANY, wxDefaultPosition, wxSize(PanelRadius * 2 + 1, PanelRadius * 2 + 1)), + m_CurrentSize(currentSize), m_ScreenTones(), + m_LastMousePos(-1, -1), m_Dragging(false), + m_SelectionRadius(PanelRadius), m_SelectionCenter(PanelCenter), m_SameOrGrowing(true), m_NewSize(currentSize) +{ + + AtlasMessage::qRasterizeMinimap qryBackground; + qryBackground.Post(); + int dim = qryBackground.dimension; + std::vector imageBytes = *qryBackground.imageBytes; + + // Data is destined for a wxImage, which uses free. + uint8_t* data = static_cast(malloc(imageBytes.size())); + std::copy(imageBytes.cbegin(), imageBytes.cend(), data); + m_Background = wxImage(dim, dim, data); + m_Background.Rescale(PanelRadius * 2, PanelRadius * 2, wxIMAGE_QUALITY_BOX_AVERAGE); + m_Backgrounds[PanelRadius] = wxBitmap(m_Background); + + SetBackgroundStyle(wxBG_STYLE_PAINT); +} + +wxPoint PseudoMiniMapPanel::GetOffset() const +{ + // Since offset is from center, amplitude is (at most) half the largest size. + int size = std::max(m_CurrentSize, m_NewSize) / 2; + // If the map is growing, the display is opposite what the actual offset is. + float scalar = (m_SameOrGrowing ? 1.0 : -1.0) / PanelRadius * size; + // Rebase offsets to center. + int hOffset = m_SelectionCenter.x - PanelCenter.x; + int vOffset = m_SelectionCenter.y - PanelCenter.y; + return wxPoint(scalar * hOffset, scalar * vOffset); +} + +void PseudoMiniMapPanel::OnNewSize(wxCommandEvent& evt) +{ + if (!evt.IsSelection()) + return; + + evt.Skip(); + + m_NewSize = wxAtoi(static_cast(evt.GetClientObject())->GetData()); + + m_SameOrGrowing = m_NewSize >= m_CurrentSize; + m_SelectionRadius = std::min(m_NewSize, m_CurrentSize) * PanelRadius / std::max(m_NewSize, m_CurrentSize); + if (!m_SameOrGrowing && m_ScreenTones.find(m_SelectionRadius) == m_ScreenTones.cend()) + { + wxImage overlay = wxImage(PanelRadius * 4, PanelRadius * 4); + overlay.InitAlpha(); + wxGraphicsContext* gc = wxGraphicsContext::Create(overlay); + gc->SetBrush(*wxGREY_BRUSH); + gc->DrawRectangle(0, 0, PanelRadius * 4, PanelRadius * 4); + gc->SetBrush(*wxBLACK_BRUSH); + gc->DrawEllipse(PanelRadius * 2 - m_SelectionRadius, PanelRadius * 2 - m_SelectionRadius, m_SelectionRadius * 2, m_SelectionRadius * 2); + gc->SetPen(*wxWHITE_PEN); + gc->DrawEllipse(PanelRadius * 2 - m_SelectionRadius, PanelRadius * 2 - m_SelectionRadius, m_SelectionRadius * 2, m_SelectionRadius * 2); + delete gc; + // Black -> Converted to transparent. + // White -> converted to black. + overlay.ConvertColourToAlpha(0, 0, 0); + + m_ScreenTones[m_SelectionRadius] = wxBitmap(overlay); + } + else if (m_SameOrGrowing && m_Backgrounds.find(m_SelectionRadius) == m_Backgrounds.cend()) + { + wxImage rescaled = wxImage(m_Background); + rescaled.Rescale(2 * m_SelectionRadius, 2 * m_SelectionRadius, wxIMAGE_QUALITY_BOX_AVERAGE); + m_Backgrounds[m_SelectionRadius] = wxBitmap(rescaled); + } + + Refresh(); +} + +void PseudoMiniMapPanel::OnMouseDown(wxMouseEvent& evt) +{ + // Capture on button-down, so we can respond even when the mouse + // moves off the window + if (!m_Dragging && evt.ButtonDown() && + Within(evt.GetPosition(), PanelCenter, PanelRadius) && + Within(evt.GetPosition(), m_SelectionCenter, m_SelectionRadius)) + { + m_LastMousePos = evt.GetPosition(); + m_Dragging = true; + } +} + +void PseudoMiniMapPanel::OnMouseUp(wxMouseEvent& evt) +{ + if (m_Dragging && + !(evt.ButtonIsDown(wxMOUSE_BTN_LEFT) || evt.ButtonIsDown(wxMOUSE_BTN_MIDDLE) || evt.ButtonIsDown(wxMOUSE_BTN_RIGHT)) + ) + { + m_Dragging = false; + } +} + +void PseudoMiniMapPanel::OnMouseMove(wxMouseEvent& evt) +{ + if (m_Dragging && evt.Dragging()) + { + if (m_LastMousePos == evt.GetPosition()) + return; + + wxPoint delta = evt.GetPosition() - m_LastMousePos; + wxPoint moved = m_SelectionCenter + delta; + + if (!Within(moved, PanelCenter, PanelRadius)) + return; + + m_SelectionCenter = moved; + m_LastMousePos = evt.GetPosition(); + Refresh(); + } +} + +void PseudoMiniMapPanel::OnMouseLeave(wxMouseEvent& WXUNUSED(evt)) +{ + m_Dragging = false; +} + +void PseudoMiniMapPanel::PaintEvent(wxPaintEvent& WXUNUSED(evt)) +{ + wxAutoBufferedPaintDC dca(this); + // Background must be grabbed from paint dc, not gc, or color may be transparent. + wxColor background = dca.GetBackground().GetColour(); + wxGCDC dc(dca); + if (m_SameOrGrowing) + { + dc.DrawBitmap(m_Backgrounds[m_SelectionRadius], m_SelectionCenter - wxSize(m_SelectionRadius, m_SelectionRadius)); + + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetPen(BackgroundMask); + dc.DrawCircle(m_SelectionCenter, PanelRadius + m_SelectionRadius); + + const wxPen BorderPen(*wxWHITE, 2); + dc.SetPen(BorderPen); + dc.DrawCircle(m_SelectionCenter, m_SelectionRadius); + } + else + { + dc.DrawBitmap(m_Backgrounds[PanelRadius], 0, 0); + // "fade out" trimmed areas by drawing a screentone ring ring. + dc.DrawBitmap(m_ScreenTones[m_SelectionRadius], ScreenToneOffset + m_SelectionCenter); + } + + // Centering markers. + dc.SetBrush(*wxBLACK_BRUSH); + dc.SetPen(*wxBLACK_PEN); + dc.DrawCircle(m_SelectionCenter, 2); + dc.SetPen(*wxWHITE_PEN); + dc.DrawLine(PanelRadius - 10, PanelRadius, PanelRadius + 10, PanelRadius); + dc.DrawLine(PanelRadius, PanelRadius + 10, PanelRadius, PanelRadius - 10); + + // Round border. + dc.SetBrush(*wxTRANSPARENT_BRUSH); + dc.SetPen(Rim); + dc.DrawCircle(PanelCenter, PanelRadius - 1); + wxPen mask(background, PanelRadius); + dc.SetPen(mask); + dc.DrawCircle(PanelCenter, PanelRadius + PanelRadius / 2 - 1); +} + +void PseudoMiniMapPanel::EraseBackground(wxEraseEvent& WXUNUSED(evt)) +{ + // Do nothing - don't erase to remove flicker. +} + +BEGIN_EVENT_TABLE(PseudoMiniMapPanel, wxPanel) + EVT_LEAVE_WINDOW(PseudoMiniMapPanel::OnMouseUp) + EVT_LEFT_DOWN(PseudoMiniMapPanel::OnMouseDown) + EVT_LEFT_UP(PseudoMiniMapPanel::OnMouseUp) + EVT_RIGHT_DOWN(PseudoMiniMapPanel::OnMouseDown) + EVT_RIGHT_UP(PseudoMiniMapPanel::OnMouseUp) + EVT_MIDDLE_DOWN(PseudoMiniMapPanel::OnMouseDown) + EVT_MIDDLE_UP(PseudoMiniMapPanel::OnMouseUp) + EVT_MOTION(PseudoMiniMapPanel::OnMouseMove) + EVT_LEAVE_WINDOW(PseudoMiniMapPanel::OnMouseLeave) + EVT_PAINT(PseudoMiniMapPanel::PaintEvent) +END_EVENT_TABLE() Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011 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 @@ -41,6 +41,7 @@ void OnRandomReseed(wxCommandEvent& evt); void OnRandomGenerate(wxCommandEvent& evt); void OnOpenPlayerPanel(wxCommandEvent& evt); + void OnResizeMap(wxCommandEvent& evt); void UpdateSimButtons(); int m_SimState; Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Map/Map.cpp @@ -21,6 +21,7 @@ #include "AtlasObject/AtlasObject.h" #include "GameInterface/Messages.h" +#include "MapResizeDialog/MapResizeDialog.h" #include "ScenarioEditor/ScenarioEditor.h" #include "ScenarioEditor/Tools/Common/Tools.h" @@ -60,7 +61,8 @@ ID_SimSlow, ID_SimPause, ID_SimReset, - ID_OpenPlayerPanel + ID_OpenPlayerPanel, + ID_ResizeMap }; enum @@ -410,6 +412,15 @@ _("Run selected random map script")), wxSizerFlags().Expand()); } + { + + ///////////////////////////////////////////////////////////////////////// + // Misc tools + wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Misc tools")); + sizer->Add(new wxButton(scrolledWindow, ID_ResizeMap, _("Resize/Recenter map")), wxSizerFlags().Expand()); + scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); + } + { ///////////////////////////////////////////////////////////////////////// // Simulation buttons @@ -645,6 +656,16 @@ m_ScenarioEditor.SelectPage(_T("PlayerSidebar")); } +void MapSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt)) +{ + MapResizeDialog dlg(this); + + if (dlg.ShowModal() != wxID_OK) + return; + wxPoint offset = dlg.GetOffset(); + POST_COMMAND(ResizeMap, (dlg.GetNewSize(), offset.x, offset.y)); +} + BEGIN_EVENT_TABLE(MapSidebar, Sidebar) EVT_COLLAPSIBLEPANE_CHANGED(wxID_ANY, MapSidebar::OnCollapse) EVT_BUTTON(ID_SimPlay, MapSidebar::OnSimPlay) @@ -655,4 +676,5 @@ EVT_BUTTON(ID_RandomReseed, MapSidebar::OnRandomReseed) EVT_BUTTON(ID_RandomGenerate, MapSidebar::OnRandomGenerate) EVT_BUTTON(ID_OpenPlayerPanel, MapSidebar::OnOpenPlayerPanel) + EVT_BUTTON(ID_ResizeMap, MapSidebar::OnResizeMap) END_EVENT_TABLE(); Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.h @@ -1,4 +1,4 @@ -/* 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 @@ -30,11 +30,9 @@ private: void OnPassabilityChoice(wxCommandEvent& evt); void OnShowPriorities(wxCommandEvent& evt); - void OnResizeMap(wxCommandEvent& evt); wxChoice* m_PassabilityChoice; TexturePreviewPanel* m_TexturePreview; DECLARE_EVENT_TABLE(); }; - Index: source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp =================================================================== --- source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp +++ source/tools/atlas/AtlasUI/ScenarioEditor/Sections/Terrain/Terrain.cpp @@ -47,8 +47,7 @@ enum { ID_Passability = 1, - ID_ShowPriorities, - ID_ResizeMap + ID_ShowPriorities }; // Helper function for adding tooltips @@ -247,14 +246,6 @@ _("Show terrain texture priorities"))); } - { - ///////////////////////////////////////////////////////////////////////// - // Misc tools - wxSizer* sizer = new wxStaticBoxSizer(wxVERTICAL, scrolledWindow, _("Misc tools")); - sizer->Add(new wxButton(scrolledWindow, ID_ResizeMap, _("Resize map")), wxSizerFlags().Expand()); - scrollSizer->Add(sizer, wxSizerFlags().Expand().Border(wxTOP, 10)); - } - m_BottomBar = new TerrainBottomBar(scenarioEditor, bottomBarContainer); } @@ -283,37 +274,9 @@ POST_MESSAGE(SetViewParamB, (AtlasMessage::eRenderView::GAME, L"priorities", evt.IsChecked())); } -void TerrainSidebar::OnResizeMap(wxCommandEvent& WXUNUSED(evt)) -{ - wxArrayString sizeNames; - std::vector sizeTiles; - - // Load the map sizes list - AtlasMessage::qGetMapSizes qrySizes; - qrySizes.Post(); - AtObj sizes = AtlasObject::LoadFromJSON(*qrySizes.sizes); - for (AtIter s = sizes["Data"]["item"]; s.defined(); ++s) - { - sizeNames.Add(wxString::FromUTF8(s["Name"])); - sizeTiles.push_back((*s["Tiles"]).getLong()); - } - - // TODO: set default based on current map size - - wxSingleChoiceDialog dlg(this, _("Select new map size. WARNING: This probably only works reliably on blank maps."), - _("Resize map"), sizeNames); - - if (dlg.ShowModal() != wxID_OK) - return; - - size_t tiles = sizeTiles.at(dlg.GetSelection()); - POST_COMMAND(ResizeMap, (tiles)); -} - BEGIN_EVENT_TABLE(TerrainSidebar, Sidebar) EVT_CHOICE(ID_Passability, TerrainSidebar::OnPassabilityChoice) EVT_CHECKBOX(ID_ShowPriorities, TerrainSidebar::OnShowPriorities) - EVT_BUTTON(ID_ResizeMap, TerrainSidebar::OnResizeMap) END_EVENT_TABLE(); ////////////////////////////////////////////////////////////////////////// Index: source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp =================================================================== --- source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp +++ source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp @@ -18,8 +18,9 @@ #include "precompiled.h" #include "MessageHandler.h" -#include "../GameLoop.h" #include "../CommandProc.h" +#include "../GameLoop.h" +#include "../MessagePasser.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" @@ -29,6 +30,7 @@ #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" +#include "gui/MiniMap.h" #include "lib/bits.h" #include "lib/file/vfs/vfs_path.h" #include "lib/status.h" @@ -36,16 +38,21 @@ #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Game.h" +#include "ps/GameSetup/GameSetup.h" #include "ps/Loader.h" #include "ps/World.h" #include "renderer/Renderer.h" +#include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" +#include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpRangeManager.h" +#include "simulation2/components/ICmpTemplateManager.h" #include "simulation2/components/ICmpTerrain.h" +#include "simulation2/system/ParamNode.h" namespace { @@ -191,7 +198,7 @@ // Notice that the number of tiles/pixels per side of the heightmap image is // one less than the number of vertices per side of the heightmap. CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); - terrain->Resize((sqrt(heightmap_source.size()) - 1) / PATCH_SIZE); + terrain->ResizeRecenter((sqrt(heightmap_source.size()) - 1) / PATCH_SIZE); // copy heightmap data into map u16* heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap(); @@ -269,17 +276,127 @@ msg->sizes = g_Game->GetSimulation2()->GetMapSizes(); } +QUERYHANDLER(RasterizeMinimap) +{ + const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + const ssize_t dimension = terrain->GetVerticesPerSide() - 1; + const ssize_t bpp = 24; + const ssize_t buf_size = dimension * dimension * (bpp / 8); + + std::vector imageBytes = std::vector(buf_size); + + float shallowPassageHeight = CMiniMap::GetShallowPassageHeight(); + + ssize_t w = dimension; + ssize_t h = dimension; + float waterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; + + for (ssize_t j = 0; j < h; ++j) + { + // Work backwards to vertically flip the image. + ssize_t position = 3 * (h - j - 1) * dimension; + for (ssize_t i = 0; i < w; ++i) + { + float avgHeight = (terrain->GetVertexGroundLevel(i, j) + + terrain->GetVertexGroundLevel(i + 1, j) + + terrain->GetVertexGroundLevel(i, j + 1) + + terrain->GetVertexGroundLevel(i + 1, j + 1) + ) / 4.0f; + + if (avgHeight < waterHeight && avgHeight > waterHeight - shallowPassageHeight) + { + // shallow water + imageBytes[position++] = 0x70; + imageBytes[position++] = 0x98; + imageBytes[position++] = 0xc0; + } + else if (avgHeight < waterHeight) + { + // Set water as constant color for consistency on different maps + imageBytes[position++] = 0x50; + imageBytes[position++] = 0x78; + imageBytes[position++] = 0xa0; + } + else + { + u32 color = std::numeric_limits::max(); + u32 hmap = static_cast(terrain->GetHeightMap()[j * dimension + i]) >> 8; + float scale = hmap / 3.0f + 170.0f / 255.0f; + + CMiniPatch* mp = terrain->GetTile(i, j); + if (mp) + { + CTerrainTextureEntry* tex = mp->GetTextureEntry(); + if (tex) + color = tex->GetBaseColor(); + } + + // Convert + imageBytes[position++] = static_cast(static_cast(color & 0xff) * scale); + imageBytes[position++] = static_cast(static_cast((color >> 8) & 0xff) * scale); + imageBytes[position++] = static_cast(static_cast((color >> 16) & 0xff) * scale); + } + } + } + + msg->imageBytes = imageBytes; + msg->dimension = dimension; +} + QUERYHANDLER(GetRMSData) { msg->data = g_Game->GetSimulation2()->GetRMSData(); } +QUERYHANDLER(GetCurrentMapSize) +{ + msg->size = g_Game->GetWorld()->GetTerrain()->GetTilesPerSide(); +} + BEGIN_COMMAND(ResizeMap) { - int m_OldTiles, m_NewTiles; + bool Within(const CFixedVector3D& pos, const int centerX, const int centerZ, const int radius) + { + int dx = abs(pos.X.ToInt_RoundToZero() - centerX); + if (dx > radius) + return false; + int dz = abs(pos.Z.ToInt_RoundToZero() - centerZ); + if (dz > radius) + return false; + if (dx + dz <= radius) + return true; + return (dx * dx + dz * dz <= radius * radius); + } + + struct DeletedObject + { + entity_id_t entityId; + CStr templateName; + int32_t owner; + CFixedVector3D pos; + CFixedVector3D rot; + }; + + ssize_t m_OldPatches, m_NewPatches; + int m_OffsetX, m_OffsetY; + + u16* m_Heightmap; + CPatch* m_Patches; + + std::vector m_DeletedObjects; + std::vector> m_OldPositions; + std::vector> m_NewPositions; cResizeMap() { + m_Heightmap = nullptr; + m_Patches = nullptr; + } + + ~cResizeMap() + { + delete m_Heightmap; + delete m_Patches; } void MakeDirty() @@ -293,39 +410,186 @@ g_Game->GetView()->GetLOSTexture().MakeDirty(); } - void ResizeTerrain(int tiles) + + void ResizeTerrain(ssize_t patches, int offsetX, int offsetY) { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); + terrain->ResizeRecenter(patches, offsetX, offsetY); + } + + void DeleteObjects(const std::vector& deletedObjects) + { + for (const DeletedObject& deleted : deletedObjects) + { + g_Game->GetSimulation2()->DestroyEntity(deleted.entityId); + } + + g_Game->GetSimulation2()->FlushDestroyedEntities(); + } - terrain->Resize(tiles / PATCH_SIZE); + void RestoreObjects(const std::vector& deletedObjects) + { + CSimulation2& sim = *g_Game->GetSimulation2(); - MakeDirty(); + for (const DeletedObject& deleted : deletedObjects) + { + entity_id_t ent = sim.AddEntity(deleted.templateName.FromUTF8(), deleted.entityId); + if (ent == INVALID_ENTITY) + { + LOGERROR("Failed to load entity template '%s'", deleted.templateName.c_str()); + } + else + { + CmpPtr cmpPosition(sim, deleted.entityId); + if (cmpPosition) + { + cmpPosition->JumpTo(deleted.pos.X, deleted.pos.Z); + cmpPosition->SetXZRotation(deleted.rot.X, deleted.rot.Z); + cmpPosition->SetYRotation(deleted.rot.Y); + } + + CmpPtr cmpOwnership(sim, deleted.entityId); + if (cmpOwnership) + cmpOwnership->SetOwner(deleted.owner); + } + } + } + + void SetMovedEntitiesPosition(const std::vector>& movedObjects) + { + for (std::pair const& kv : movedObjects) + { + entity_id_t id = kv.first; + CFixedVector3D position = kv.second; + CmpPtr cmpPosition(*g_Game->GetSimulation2(), id); + ENSURE(cmpPosition); + cmpPosition->JumpTo(position.X, position.Z); + } } void Do() { - CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); + CSimulation2& sim = *g_Game->GetSimulation2(); + CmpPtr cmpTemplateManager(sim, SYSTEM_ENTITY); + ENSURE(cmpTemplateManager); + + CmpPtr cmpTerrain(sim, SYSTEM_ENTITY); if (!cmpTerrain) { - m_OldTiles = m_NewTiles = 0; + m_OldPatches = m_NewPatches = 0; + m_OffsetX = m_OffsetY = 0; } else { - m_OldTiles = (int)cmpTerrain->GetTilesPerSide(); - m_NewTiles = msg->tiles; + m_OldPatches = static_cast(cmpTerrain->GetTilesPerSide() / PATCH_SIZE); + m_NewPatches = msg->tiles / PATCH_SIZE; + m_OffsetX = msg->offsetX / PATCH_SIZE; + // Need to flip direction of vertical offset, due to screen mapping order. + m_OffsetY = -(msg->offsetY / PATCH_SIZE); + + CTerrain* terrain = cmpTerrain->GetCTerrain(); + m_Heightmap = new u16[(m_OldPatches * PATCH_SIZE + 1) * (m_OldPatches * PATCH_SIZE + 1)]; + std::copy_n(terrain->GetHeightMap(), (m_OldPatches * PATCH_SIZE + 1) * (m_OldPatches * PATCH_SIZE + 1), m_Heightmap); + m_Patches = new CPatch[m_OldPatches * m_OldPatches]; + for (ssize_t j = 0; j < m_OldPatches; ++j) + { + for (ssize_t i = 0; i < m_OldPatches; ++i) + { + CPatch& src = *(terrain->GetPatch(i, j)); + CPatch& dst = m_Patches[j * m_OldPatches + i]; + std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]); + } + } + } + + const int radiusInTerrainUnits = m_NewPatches * PATCH_SIZE * TERRAIN_TILE_SIZE / 2 * (1.f - 1e-6f); + // Opposite direction offset, as we move the destination onto the source, not the source into the destination. + const int mapCenterX = (m_OldPatches / 2 - m_OffsetX) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const int mapCenterZ = (m_OldPatches / 2 - m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE; + // The offset to move units by is opposite the direction the map is moved, and from the corner. + const int offsetX = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetX) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const int offsetZ = ((m_NewPatches - m_OldPatches) / 2 + m_OffsetY) * PATCH_SIZE * TERRAIN_TILE_SIZE; + const CFixedVector3D offset = CFixedVector3D(fixed::FromInt(offsetX), fixed::FromInt(0), fixed::FromInt(offsetZ)); + + const CSimulation2::InterfaceListUnordered& ents = sim.GetEntitiesWithInterfaceUnordered(IID_Selectable); + + for (std::pair const& kv : ents) + { + const entity_id_t entityId = kv.first; + + CmpPtr cmpPosition(sim, entityId); + + if (cmpPosition && cmpPosition->IsInWorld() && Within(cmpPosition->GetPosition(), mapCenterX, mapCenterZ, radiusInTerrainUnits)) + { + CFixedVector3D position = cmpPosition->GetPosition(); + + m_NewPositions.emplace_back(entityId, position + offset); + m_OldPositions.emplace_back(entityId, position); + } + else + { + DeletedObject deleted; + deleted.entityId = entityId; + deleted.templateName = cmpTemplateManager->GetCurrentTemplateName(entityId); + + // If the entity has a position, but the ending position is not valid; + if (cmpPosition) + { + deleted.pos = cmpPosition->GetPosition(); + deleted.rot = cmpPosition->GetRotation(); + } + + CmpPtr cmpOwnership(sim, entityId); + if (cmpOwnership) + deleted.owner = cmpOwnership->GetOwner(); + + m_DeletedObjects.push_back(deleted); + } } - ResizeTerrain(m_NewTiles); + DeleteObjects(m_DeletedObjects); + ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY); + SetMovedEntitiesPosition(m_NewPositions); + MakeDirty(); } + void Undo() { - ResizeTerrain(m_OldTiles); + if (m_Heightmap == nullptr || m_Patches == nullptr) + { + // If there previously was no data, just resize to old (probably not originally valid). + ResizeTerrain(m_OldPatches, -m_OffsetX, -m_OffsetY); + } + else + { + CSimulation2& sim = *g_Game->GetSimulation2(); + CmpPtr cmpTerrain(sim, SYSTEM_ENTITY); + CTerrain* terrain = cmpTerrain->GetCTerrain(); + + terrain->Initialize(m_OldPatches, m_Heightmap); + // Copy terrain data back. + for (ssize_t j = 0; j < m_OldPatches; ++j) + { + for (ssize_t i = 0; i < m_OldPatches; ++i) + { + CPatch& src = m_Patches[j * m_OldPatches + i]; + CPatch& dst = *(terrain->GetPatch(i, j)); + std::copy_n(&src.m_MiniPatches[0][0], PATCH_SIZE * PATCH_SIZE, &dst.m_MiniPatches[0][0]); + } + } + } + RestoreObjects(m_DeletedObjects); + SetMovedEntitiesPosition(m_OldPositions); + MakeDirty(); } void Redo() { - ResizeTerrain(m_NewTiles); + DeleteObjects(m_DeletedObjects); + ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY); + SetMovedEntitiesPosition(m_NewPositions); + MakeDirty(); } }; END_COMMAND(ResizeMap) Index: source/tools/atlas/GameInterface/Messages.h =================================================================== --- source/tools/atlas/GameInterface/Messages.h +++ source/tools/atlas/GameInterface/Messages.h @@ -195,6 +195,17 @@ ((std::string, sizes)) ); +QUERY(GetCurrentMapSize, + , + ((int, size)) + ); + +QUERY(RasterizeMinimap, + , + ((int, dimension)) + ((std::vector, imageBytes)) + ); + QUERY(GetRMSData, , ((std::vector, data)) @@ -202,6 +213,8 @@ COMMAND(ResizeMap, NOMERGE, ((int, tiles)) + ((int, offsetX)) + ((int, offsetY)) ); QUERY(VFSFileExists,