Index: ps/trunk/source/lib/sysdep/os/osx/osx_stl_fixes.h =================================================================== --- ps/trunk/source/lib/sysdep/os/osx/osx_stl_fixes.h (revision 24226) +++ ps/trunk/source/lib/sysdep/os/osx/osx_stl_fixes.h (nonexistent) @@ -1,121 +0,0 @@ -/* Copyright (C) 2013 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. - */ - -#ifndef OSX_STL_FIXES_H -#define OSX_STL_FIXES_H - -#include -#include -#include // MAC_OS_X_VERSION_* - -/** - * This file adds some explicit template instantiations that are - * declared external on 10.6+ SDKs but are missing from stdc++ on 10.5 - * (this causes a failure to load due to missing symbols on 10.5) - **/ - -#if MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 - -_GLIBCXX_BEGIN_NAMESPACE(std) - - -// ostream_insert.h: -# if _GLIBCXX_EXTERN_TEMPLATE - template ostream& __ostream_insert(ostream&, const char*, streamsize); -# ifdef _GLIBCXX_USE_WCHAR_T - template wostream& __ostream_insert(wostream&, const wchar_t*, - streamsize); -# endif -# endif - - -// istream.tcc: -# if _GLIBCXX_EXTERN_TEMPLATE - template istream& istream::_M_extract(unsigned short&); - template istream& istream::_M_extract(unsigned int&); - template istream& istream::_M_extract(long&); - template istream& istream::_M_extract(unsigned long&); - template istream& istream::_M_extract(bool&); -# ifdef _GLIBCXX_USE_LONG_LONG - template istream& istream::_M_extract(long long&); - template istream& istream::_M_extract(unsigned long long&); -# endif - template istream& istream::_M_extract(float&); - template istream& istream::_M_extract(double&); - template istream& istream::_M_extract(long double&); - template istream& istream::_M_extract(void*&); - - template class basic_iostream; - -# ifdef _GLIBCXX_USE_WCHAR_T - template wistream& wistream::_M_extract(unsigned short&); - template wistream& wistream::_M_extract(unsigned int&); - template wistream& wistream::_M_extract(long&); - template wistream& wistream::_M_extract(unsigned long&); - template wistream& wistream::_M_extract(bool&); -# ifdef _GLIBCXX_USE_LONG_LONG - template wistream& wistream::_M_extract(long long&); - template wistream& wistream::_M_extract(unsigned long long&); -# endif - template wistream& wistream::_M_extract(float&); - template wistream& wistream::_M_extract(double&); - template wistream& wistream::_M_extract(long double&); - template wistream& wistream::_M_extract(void*&); - - template class basic_iostream; -# endif -# endif - - -// ostream.tcc: -# if _GLIBCXX_EXTERN_TEMPLATE - template ostream& ostream::_M_insert(long); - template ostream& ostream::_M_insert(unsigned long); - template ostream& ostream::_M_insert(bool); -# ifdef _GLIBCXX_USE_LONG_LONG - template ostream& ostream::_M_insert(long long); - template ostream& ostream::_M_insert(unsigned long long); -# endif - template ostream& ostream::_M_insert(double); - template ostream& ostream::_M_insert(long double); - template ostream& ostream::_M_insert(const void*); - -# ifdef _GLIBCXX_USE_WCHAR_T - template wostream& wostream::_M_insert(long); - template wostream& wostream::_M_insert(unsigned long); - template wostream& wostream::_M_insert(bool); -# ifdef _GLIBCXX_USE_LONG_LONG - template wostream& wostream::_M_insert(long long); - template wostream& wostream::_M_insert(unsigned long long); -# endif - template wostream& wostream::_M_insert(double); - template wostream& wostream::_M_insert(long double); - template wostream& wostream::_M_insert(const void*); -# endif -# endif - - -_GLIBCXX_END_NAMESPACE - -#endif // MAC_OS_X_VERSION_MAX_ALLOWED >= 1060 && MAC_OS_X_VERSION_MIN_REQUIRED < 1060 - -#endif // OSX_STL_FIXES_H Property changes on: ps/trunk/source/lib/sysdep/os/osx/osx_stl_fixes.h ___________________________________________________________________ Deleted: svn:eol-style ## -1 +0,0 ## -native \ No newline at end of property Index: ps/trunk/source/ps/Profile.h =================================================================== --- ps/trunk/source/ps/Profile.h (revision 24226) +++ ps/trunk/source/ps/Profile.h (revision 24227) @@ -1,194 +1,191 @@ /* 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 . */ /* * GPG3-style hierarchical profiler */ #ifndef INCLUDED_PROFILE #define INCLUDED_PROFILE #include #include "lib/adts/ring_buf.h" #include "lib/posix/posix_pthread.h" #include "ps/Profiler2.h" #include "ps/Singleton.h" #include #include #include #include #define PROFILE_AMORTIZE_FRAMES 30 #define PROFILE_AMORTIZE_TURNS 1 class CProfileManager; class CProfileNodeTable; -class CStr8; -class CStrW; - // To profile scripts usefully, we use a call hook that's called on every enter/exit, // and need to find the function name. But most functions are anonymous so we make do // with filename plus line number instead. // Computing the names is fairly expensive, and we need to return an interned char* // for the profiler to hold a copy of, so we use boost::flyweight to construct interned // strings per call location. // // TODO: Check again how much the overhead for getting filename and line really is and if // it has increased with the new approach after the SpiderMonkey 31 upgrade. // // Flyweight types (with no_locking because the call hooks are only used in the // main thread, and no_tracking because we mustn't delete values the profiler is // using and it's not going to waste much memory) typedef boost::flyweight< std::string, boost::flyweights::no_tracking, boost::flyweights::no_locking > StringFlyweight; class CProfileNode { NONCOPYABLE(CProfileNode); public: typedef std::vector::iterator profile_iterator; typedef std::vector::const_iterator const_profile_iterator; CProfileNode( const char* name, CProfileNode* parent ); ~CProfileNode(); const char* GetName() const { return name; } double GetFrameCalls() const; double GetFrameTime() const; double GetTurnCalls() const; double GetTurnTime() const; double GetFrameMallocs() const; double GetTurnMallocs() const; const CProfileNode* GetChild( const char* name ) const; const CProfileNode* GetScriptChild( const char* name ) const; const std::vector* GetChildren() const { return( &children ); } const std::vector* GetScriptChildren() const { return( &script_children ); } bool CanExpand(); CProfileNode* GetChild( const char* name ); CProfileNode* GetScriptChild( const char* name ); CProfileNode* GetParent() const { return( parent ); } // Resets timing information for this node and all its children void Reset(); // Resets frame timings for this node and all its children void Frame(); // Resets turn timings for this node and all its children void Turn(); // Enters the node void Call(); // Leaves the node. Returns true if the node has actually been left bool Return(); private: friend class CProfileManager; friend class CProfileNodeTable; const char* name; int calls_frame_current; int calls_turn_current; RingBuf calls_per_frame; RingBuf calls_per_turn; double time_frame_current; double time_turn_current; RingBuf time_per_frame; RingBuf time_per_turn; long mallocs_frame_current; long mallocs_turn_current; RingBuf mallocs_per_frame; RingBuf mallocs_per_turn; double start; long start_mallocs; int recursion; CProfileNode* parent; std::vector children; std::vector script_children; CProfileNodeTable* display_table; }; class CProfileManager : public Singleton { public: CProfileManager(); ~CProfileManager(); // Begins timing for a named subsection void Start( const char* name ); void StartScript( const char* name ); // Ends timing for the current subsection void Stop(); // Resets all timing information void Reset(); // Resets frame timing information void Frame(); // Resets turn timing information // (Must not be called before Frame) void Turn(); // Resets absolutely everything, at the end of this frame void StructuralReset(); inline const CProfileNode* GetCurrent() { return( current ); } inline const CProfileNode* GetRoot() { return( root ); } private: CProfileNode* root; CProfileNode* current; bool needs_structural_reset; void PerformStructuralReset(); }; #define g_Profiler CProfileManager::GetSingleton() class CProfileSample { public: CProfileSample(const char* name); ~CProfileSample(); }; // Put a PROFILE("xyz") block at the start of all code to be profiled. // Profile blocks last until the end of the containing scope. #define PROFILE(name) CProfileSample __profile(name) // Cheat a bit to make things slightly easier on the user #define PROFILE_START(name) { CProfileSample __profile(name) #define PROFILE_END(name) } // Do both old and new profilers simultaneously (1+2=3), for convenience. #define PROFILE3(name) PROFILE(name); PROFILE2(name) // Also do GPU #define PROFILE3_GPU(name) PROFILE(name); PROFILE2(name); PROFILE2_GPU(name) #endif // INCLUDED_PROFILE Index: ps/trunk/source/renderer/PostprocManager.h =================================================================== --- ps/trunk/source/renderer/PostprocManager.h (revision 24226) +++ ps/trunk/source/renderer/PostprocManager.h (revision 24227) @@ -1,148 +1,149 @@ /* Copyright (C) 2020 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_POSTPROCMANAGER #define INCLUDED_POSTPROCMANAGER -#include "graphics/ShaderTechnique.h" +#include "graphics/ShaderTechniquePtr.h" +#include "lib/ogl.h" #include "ps/CStr.h" #include class CPostprocManager { public: CPostprocManager(); ~CPostprocManager(); // Create all buffers/textures in GPU memory and set default effect. // @note Must be called before using in the renderer. May be called multiple times. void Initialize(); // Update the size of the screen void Resize(); // Returns a list of xml files found in shaders/effects/postproc. static std::vector GetPostEffects(); // Returns the name of the current effect. const CStrW& GetPostEffect() const { return m_PostProcEffect; } // Sets the current effect. void SetPostEffect(const CStrW& name); // Triggers update of shaders and FBO if needed. void UpdateAntiAliasingTechnique(); void UpdateSharpeningTechnique(); void UpdateSharpnessFactor(); void SetDepthBufferClipPlanes(float nearPlane, float farPlane); // Clears the two color buffers and depth buffer, and redirects all rendering // to our textures instead of directly to the system framebuffer. // @note CPostprocManager must be initialized first void CaptureRenderOutput(); // First renders blur textures, then calls ApplyEffect for each effect pass, // ping-ponging the buffers at each step. // @note CPostprocManager must be initialized first void ApplyPostproc(); // Blits the final postprocessed texture to the system framebuffer. The system framebuffer // is selected as the output buffer. Should be called before silhouette rendering. // @note CPostprocManager must be initialized first void ReleaseRenderOutput(); // Returns true if we render main scene in the MSAA framebuffer. bool IsMultisampleEnabled() const; // Resolves the MSAA framebuffer into the regular one. void ResolveMultisampleFramebuffer(); private: void CreateMultisampleBuffer(); void DestroyMultisampleBuffer(); // Two framebuffers, that we flip between at each shader pass. GLuint m_PingFbo, m_PongFbo; // Unique color textures for the framebuffers. GLuint m_ColorTex1, m_ColorTex2; // The framebuffers share a depth/stencil texture. GLuint m_DepthTex; float m_NearPlane, m_FarPlane; // A framebuffer and textures x2 for each blur level we render. GLuint m_BloomFbo, m_BlurTex2a, m_BlurTex2b, m_BlurTex4a, m_BlurTex4b, m_BlurTex8a, m_BlurTex8b; // Indicates which of the ping-pong buffers is used for reading and which for drawing. bool m_WhichBuffer; // The name and shader technique we are using. "default" name means no technique is used // (i.e. while we do allocate the buffers, no effects are rendered). CStrW m_PostProcEffect; CShaderTechniquePtr m_PostProcTech; CStr m_SharpName; CShaderTechniquePtr m_SharpTech; float m_Sharpness; CStr m_AAName; CShaderTechniquePtr m_AATech; bool m_UsingMultisampleBuffer; GLuint m_MultisampleFBO; GLuint m_MultisampleColorTex, m_MultisampleDepthTex; GLsizei m_MultisampleCount; std::vector m_AllowedSampleCounts; // The current screen dimensions in pixels. int m_Width, m_Height; // Is the postproc manager initialized? Buffers created? Default effect loaded? bool m_IsInitialized; // Creates blur textures at various scales, for bloom, DOF, etc. void ApplyBlur(); // High quality GPU image scaling to half size. outTex must have exactly half the size // of inTex. inWidth and inHeight are the dimensions of inTex in texels. void ApplyBlurDownscale2x(GLuint inTex, GLuint outTex, int inWidth, int inHeight); // GPU-based Gaussian blur in two passes. inOutTex contains the input image and will be filled // with the blurred image. tempTex must have the same size as inOutTex. // inWidth and inHeight are the dimensions of the images in texels. void ApplyBlurGauss(GLuint inOutTex, GLuint tempTex, int inWidth, int inHeight); // Applies a pass of a given effect to the entire current framebuffer. The shader is // provided with a number of general-purpose variables, including the rendered screen so far, // the depth buffer, a number of blur textures, the screen size, the zNear/zFar planes and // some other parameters used by the optional bloom/HDR pass. void ApplyEffect(CShaderTechniquePtr &shaderTech1, int pass); // Delete all allocated buffers/textures from GPU memory. void Cleanup(); // Delete existing buffers/textures and create them again, using a new screen size if needed. // (the textures are also attached to the framebuffers) void RecreateBuffers(); }; #endif // INCLUDED_POSTPROCMANAGER Index: ps/trunk/source/simulation2/components/CCmpSelectable.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpSelectable.cpp (revision 24226) +++ ps/trunk/source/simulation2/components/CCmpSelectable.cpp (revision 24227) @@ -1,692 +1,693 @@ /* Copyright (C) 2020 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 "ICmpSelectable.h" #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "graphics/TextureManager.h" #include "maths/Ease.h" #include "maths/MathUtil.h" #include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "maths/Vector2D.h" +#include "ps/CLogger.h" #include "ps/Profile.h" #include "renderer/Scene.h" #include "renderer/Renderer.h" #include "simulation2/MessageTypes.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpFootprint.h" #include "simulation2/components/ICmpVisual.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpOwnership.h" #include "simulation2/components/ICmpPlayer.h" #include "simulation2/components/ICmpPlayerManager.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/helpers/Render.h" #include "simulation2/system/Component.h" // Minimum alpha value for always visible overlays [0 fully transparent, 1 fully opaque] static const float MIN_ALPHA_ALWAYS_VISIBLE = 0.65f; // Minimum alpha value for other overlays static const float MIN_ALPHA_UNSELECTED = 0.0f; // Desaturation value for unselected, always visible overlays (0.33 = 33% desaturated or 66% of original saturation) static const float RGB_DESATURATION = 0.333333f; class CCmpSelectable : public ICmpSelectable { public: enum EShape { FOOTPRINT, CIRCLE, SQUARE }; static void ClassInit(CComponentManager& componentManager) { componentManager.SubscribeToMessageType(MT_OwnershipChanged); componentManager.SubscribeToMessageType(MT_PlayerColorChanged); componentManager.SubscribeToMessageType(MT_PositionChanged); componentManager.SubscribeToMessageType(MT_TerrainChanged); componentManager.SubscribeToMessageType(MT_WaterChanged); } DEFAULT_COMPONENT_ALLOCATOR(Selectable) CCmpSelectable() : m_DebugBoundingBoxOverlay(NULL), m_DebugSelectionBoxOverlay(NULL), m_BuildingOverlay(NULL), m_UnitOverlay(NULL), m_FadeBaselineAlpha(0.f), m_FadeDeltaAlpha(0.f), m_FadeProgress(0.f), m_Selected(false), m_Cached(false), m_Visible(false) { m_Color = CColor(0, 0, 0, m_FadeBaselineAlpha); } ~CCmpSelectable() { delete m_DebugBoundingBoxOverlay; delete m_DebugSelectionBoxOverlay; delete m_BuildingOverlay; delete m_UnitOverlay; } static std::string GetSchema() { return "Allows this entity to be selected by the player." "" "" "" "" "" "" "" "" "" "" "" "" "" "" "0.0" "" "" "" "" "0.0" "" "" "" "" "" "" "0.0" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" "" ""; } EShape m_Shape; entity_pos_t m_Width; // width/radius entity_pos_t m_Height; // height/radius virtual void Init(const CParamNode& paramNode) { m_EditorOnly = paramNode.GetChild("EditorOnly").IsOk(); // Certain special units always have their selection overlay shown m_AlwaysVisible = paramNode.GetChild("Overlay").GetChild("AlwaysVisible").IsOk(); if (m_AlwaysVisible) { m_AlphaMin = MIN_ALPHA_ALWAYS_VISIBLE; m_Color.a = m_AlphaMin; } else m_AlphaMin = MIN_ALPHA_UNSELECTED; const CParamNode& textureNode = paramNode.GetChild("Overlay").GetChild("Texture"); const CParamNode& outlineNode = paramNode.GetChild("Overlay").GetChild("Outline"); // Save some memory by using interned file paths in these descriptors (almost all actors and // entities have this component, and many use the same textures). if (textureNode.IsOk()) { // textured quad mode (dynamic, for units) m_OverlayDescriptor.m_Type = DYNAMIC_QUAD; m_OverlayDescriptor.m_QuadTexture = CStrIntern(TEXTUREBASEPATH + textureNode.GetChild("MainTexture").ToUTF8()); m_OverlayDescriptor.m_QuadTextureMask = CStrIntern(TEXTUREBASEPATH + textureNode.GetChild("MainTextureMask").ToUTF8()); } else if (outlineNode.IsOk()) { // textured outline mode (static, for buildings) m_OverlayDescriptor.m_Type = STATIC_OUTLINE; m_OverlayDescriptor.m_LineTexture = CStrIntern(TEXTUREBASEPATH + outlineNode.GetChild("LineTexture").ToUTF8()); m_OverlayDescriptor.m_LineTextureMask = CStrIntern(TEXTUREBASEPATH + outlineNode.GetChild("LineTextureMask").ToUTF8()); m_OverlayDescriptor.m_LineThickness = outlineNode.GetChild("LineThickness").ToFloat(); } const CParamNode& shapeNode = paramNode.GetChild("Overlay").GetChild("Shape"); if (shapeNode.IsOk()) { if (shapeNode.GetChild("Square").IsOk()) { m_Shape = SQUARE; m_Width = shapeNode.GetChild("Square").GetChild("@width").ToFixed(); m_Height = shapeNode.GetChild("Square").GetChild("@depth").ToFixed(); } else if (shapeNode.GetChild("Circle").IsOk()) { m_Shape = CIRCLE; m_Width = m_Height = shapeNode.GetChild("Circle").GetChild("@radius").ToFixed(); } else { // Should not happen m_Shape = FOOTPRINT; LOGWARNING("[Selectable] Selected overlay shape is not implemented."); } } else { m_Shape = FOOTPRINT; } m_EnabledInterpolate = false; m_EnabledRenderSubmit = false; UpdateMessageSubscriptions(); } virtual void Deinit() { } virtual void Serialize(ISerializer& UNUSED(serialize)) { // Nothing to do here (the overlay object is not worth saving, it'll get // reconstructed by the GUI soon enough, I think) } virtual void Deserialize(const CParamNode& paramNode, IDeserializer& UNUSED(deserialize)) { // Need to call Init to reload the template properties Init(paramNode); } virtual void HandleMessage(const CMessage& msg, bool UNUSED(global)); virtual void SetSelectionHighlight(const CColor& color, bool selected) { m_Selected = selected; m_Color.r = color.r; m_Color.g = color.g; m_Color.b = color.b; // Always-visible overlays will be desaturated if their parent unit is deselected. if (m_AlwaysVisible && !selected) { float max; // Reduce saturation by one-third, the quick-and-dirty way. if (m_Color.r > m_Color.b) max = (m_Color.r > m_Color.g) ? m_Color.r : m_Color.g; else max = (m_Color.b > m_Color.g) ? m_Color.b : m_Color.g; m_Color.r += (max - m_Color.r) * RGB_DESATURATION; m_Color.g += (max - m_Color.g) * RGB_DESATURATION; m_Color.b += (max - m_Color.b) * RGB_DESATURATION; } SetSelectionHighlightAlpha(color.a); } virtual void SetSelectionHighlightAlpha(float alpha) { alpha = std::max(m_AlphaMin, alpha); // set up fading from the current value (as the baseline) to the target value m_FadeBaselineAlpha = m_Color.a; m_FadeDeltaAlpha = alpha - m_FadeBaselineAlpha; m_FadeProgress = 0.f; UpdateMessageSubscriptions(); } virtual void SetVisibility(bool visible) { m_Visible = visible; UpdateMessageSubscriptions(); } virtual bool IsEditorOnly() const { return m_EditorOnly; } void RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling); /** * Draw a textured line overlay. */ void UpdateTexturedLineOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset); /** * Called from the interpolation handler; responsible for ensuring the dynamic overlay (provided we're * using one) is up-to-date and ready to be submitted to the next rendering run. */ void UpdateDynamicOverlay(float frameOffset); /// Explicitly invalidates the static overlay. void InvalidateStaticOverlay(); /** * Subscribe/unsubscribe to MT_Interpolate, MT_RenderSubmit, depending on * whether we will do any actual work when receiving them. (This is to avoid * the performance cost of receiving messages in the typical case when the * entity is not selected.) * * Must be called after changing m_Visible, m_FadeDeltaAlpha, m_Color.a */ void UpdateMessageSubscriptions(); /** * Set the color of the current owner. */ virtual void UpdateColor(); private: SOverlayDescriptor m_OverlayDescriptor; SOverlayTexturedLine* m_BuildingOverlay; SOverlayQuad* m_UnitOverlay; CBoundingBoxAligned m_UnitOverlayBoundingBox; SOverlayLine* m_DebugBoundingBoxOverlay; SOverlayLine* m_DebugSelectionBoxOverlay; bool m_EnabledInterpolate; bool m_EnabledRenderSubmit; // Whether the selectable will be rendered. bool m_Visible; // Whether the entity is only selectable in Atlas editor bool m_EditorOnly; // Whether the selection overlay is always visible bool m_AlwaysVisible; /// Whether the parent entity is selected (caches GUI's selection state). bool m_Selected; /// Current selection overlay color. Alpha component is subject to fading. CColor m_Color; /// Whether the selectable's player color has been cached for rendering. bool m_Cached; /// Minimum value for current selection overlay alpha. float m_AlphaMin; /// Baseline alpha value to start fading from. Constant during a single fade. float m_FadeBaselineAlpha; /// Delta between target and baseline alpha. Constant during a single fade. Can be positive or negative. float m_FadeDeltaAlpha; /// Linear time progress of the fade, between 0 and m_FadeDuration. float m_FadeProgress; /// Total duration of a single fade, in seconds. Assumed constant for now; feel free to change this into /// a member variable if you need to adjust it per component. static const double FADE_DURATION; static const char* TEXTUREBASEPATH; }; const double CCmpSelectable::FADE_DURATION = 0.3; const char* CCmpSelectable::TEXTUREBASEPATH = "art/textures/selection/"; void CCmpSelectable::HandleMessage(const CMessage& msg, bool UNUSED(global)) { switch (msg.GetType()) { case MT_Interpolate: { PROFILE("Selectable::Interpolate"); const CMessageInterpolate& msgData = static_cast (msg); if (m_FadeDeltaAlpha != 0.f) { m_FadeProgress += msgData.deltaRealTime; if (m_FadeProgress >= FADE_DURATION) { const float targetAlpha = m_FadeBaselineAlpha + m_FadeDeltaAlpha; // stop the fade m_Color.a = targetAlpha; m_FadeBaselineAlpha = targetAlpha; m_FadeDeltaAlpha = 0.f; m_FadeProgress = FADE_DURATION; // will need to be reset to start the next fade again } else { m_Color.a = Ease::QuartOut(m_FadeProgress, m_FadeBaselineAlpha, m_FadeDeltaAlpha, FADE_DURATION); } } // update dynamic overlay only when visible if (m_Color.a > 0) UpdateDynamicOverlay(msgData.offset); UpdateMessageSubscriptions(); break; } case MT_OwnershipChanged: { const CMessageOwnershipChanged& msgData = static_cast (msg); // Ignore newly constructed entities, as they receive their color upon first selection // Ignore deleted entities because they won't be rendered if (msgData.from == INVALID_PLAYER || msgData.to == INVALID_PLAYER) break; UpdateColor(); InvalidateStaticOverlay(); break; } case MT_PlayerColorChanged: { const CMessagePlayerColorChanged& msgData = static_cast (msg); CmpPtr cmpOwnership(GetEntityHandle()); if (!cmpOwnership || msgData.player != cmpOwnership->GetOwner()) break; UpdateColor(); break; } case MT_PositionChanged: case MT_TerrainChanged: case MT_WaterChanged: InvalidateStaticOverlay(); break; case MT_RenderSubmit: { PROFILE("Selectable::RenderSubmit"); const CMessageRenderSubmit& msgData = static_cast (msg); RenderSubmit(msgData.collector, msgData.frustum, msgData.culling); break; } } } void CCmpSelectable::UpdateColor() { CmpPtr cmpOwnership(GetEntityHandle()); CmpPtr cmpPlayerManager(GetSystemEntity()); if (!cmpPlayerManager) return; // Default to white if there's no owner (e.g. decorative, editor-only actors) CColor color(1.0, 1.0, 1.0, 1.0); if (cmpOwnership) { CmpPtr cmpPlayer(GetSimContext(), cmpPlayerManager->GetPlayerByID(cmpOwnership->GetOwner())); if (cmpPlayer) color = cmpPlayer->GetDisplayedColor(); } // Update the highlight color, while keeping the current alpha target value intact // (i.e. baseline + delta), so that any ongoing fades simply continue with the new color. color.a = m_FadeBaselineAlpha + m_FadeDeltaAlpha; SetSelectionHighlight(color, m_Selected); } void CCmpSelectable::UpdateMessageSubscriptions() { bool needInterpolate = false; bool needRenderSubmit = false; if (m_FadeDeltaAlpha != 0.f || m_Color.a > 0) needInterpolate = true; if (m_Visible && m_Color.a > 0) needRenderSubmit = true; if (needInterpolate != m_EnabledInterpolate) { GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_Interpolate, this, needInterpolate); m_EnabledInterpolate = needInterpolate; } if (needRenderSubmit != m_EnabledRenderSubmit) { GetSimContext().GetComponentManager().DynamicSubscriptionNonsync(MT_RenderSubmit, this, needRenderSubmit); m_EnabledRenderSubmit = needRenderSubmit; } } void CCmpSelectable::InvalidateStaticOverlay() { SAFE_DELETE(m_BuildingOverlay); } void CCmpSelectable::UpdateTexturedLineOverlay(const SOverlayDescriptor* overlayDescriptor, SOverlayTexturedLine& overlay, float frameOffset) { if (!CRenderer::IsInitialised()) return; CmpPtr cmpPosition(GetEntityHandle()); if (!cmpPosition || !cmpPosition->IsInWorld()) return; ICmpFootprint::EShape fpShape = ICmpFootprint::CIRCLE; if (m_Shape == FOOTPRINT) { CmpPtr cmpFootprint(GetEntityHandle()); if (!cmpFootprint) return; entity_pos_t h; cmpFootprint->GetShape(fpShape, m_Width, m_Height, h); } float rotY; CVector2D origin; cmpPosition->GetInterpolatedPosition2D(frameOffset, origin.X, origin.Y, rotY); overlay.m_SimContext = &GetSimContext(); overlay.m_Color = m_Color; overlay.CreateOverlayTexture(overlayDescriptor); if (m_Shape == SQUARE || (m_Shape == FOOTPRINT && fpShape == ICmpFootprint::SQUARE)) SimRender::ConstructTexturedLineBox(overlay, origin, cmpPosition->GetRotation(), m_Width.ToFloat(), m_Height.ToFloat()); else SimRender::ConstructTexturedLineCircle(overlay, origin, m_Width.ToFloat()); } void CCmpSelectable::UpdateDynamicOverlay(float frameOffset) { // Dynamic overlay lines are allocated once and never deleted. Since they are expected to change frequently, // they are assumed dirty on every call to this function, and we should therefore use this function more // thoughtfully than calling it right before every frame render. if (m_OverlayDescriptor.m_Type != DYNAMIC_QUAD) return; if (!CRenderer::IsInitialised()) return; CmpPtr cmpPosition(GetEntityHandle()); if (!cmpPosition || !cmpPosition->IsInWorld()) return; ICmpFootprint::EShape fpShape = ICmpFootprint::CIRCLE; if (m_Shape == FOOTPRINT) { CmpPtr cmpFootprint(GetEntityHandle()); if (!cmpFootprint) return; entity_pos_t h; cmpFootprint->GetShape(fpShape, m_Width, m_Height, h); } float rotY; CVector2D position; cmpPosition->GetInterpolatedPosition2D(frameOffset, position.X, position.Y, rotY); CmpPtr cmpWaterManager(GetSystemEntity()); CmpPtr cmpTerrain(GetSystemEntity()); ENSURE(cmpWaterManager && cmpTerrain); CTerrain* terrain = cmpTerrain->GetCTerrain(); ENSURE(terrain); // --------------------------------------------------------------------------------- if (!m_UnitOverlay) { m_UnitOverlay = new SOverlayQuad; // Assuming we don't need the capability of swapping textures on-demand. CTextureProperties texturePropsBase(m_OverlayDescriptor.m_QuadTexture.c_str()); texturePropsBase.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); texturePropsBase.SetMaxAnisotropy(4.f); CTextureProperties texturePropsMask(m_OverlayDescriptor.m_QuadTextureMask.c_str()); texturePropsMask.SetWrap(GL_CLAMP_TO_BORDER, GL_CLAMP_TO_EDGE); texturePropsMask.SetMaxAnisotropy(4.f); m_UnitOverlay->m_Texture = g_Renderer.GetTextureManager().CreateTexture(texturePropsBase); m_UnitOverlay->m_TextureMask = g_Renderer.GetTextureManager().CreateTexture(texturePropsMask); } m_UnitOverlay->m_Color = m_Color; // TODO: some code duplication here :< would be nice to factor out getting the corner points of an // entity based on its footprint sizes (and regardless of whether it's a circle or a square) float s = sinf(-rotY); float c = cosf(-rotY); CVector2D unitX(c, s); CVector2D unitZ(-s, c); float halfSizeX = m_Width.ToFloat(); float halfSizeZ = m_Height.ToFloat(); if (m_Shape == SQUARE || (m_Shape == FOOTPRINT && fpShape == ICmpFootprint::SQUARE)) { halfSizeX /= 2.0f; halfSizeZ /= 2.0f; } std::vector points; points.push_back(CVector2D(position + unitX *(-halfSizeX) + unitZ * halfSizeZ)); // top left points.push_back(CVector2D(position + unitX *(-halfSizeX) + unitZ *(-halfSizeZ))); // bottom left points.push_back(CVector2D(position + unitX * halfSizeX + unitZ *(-halfSizeZ))); // bottom right points.push_back(CVector2D(position + unitX * halfSizeX + unitZ * halfSizeZ)); // top right m_UnitOverlayBoundingBox = CBoundingBoxAligned(); for (size_t i = 0; i < 4; ++i) { float quadY = std::max( terrain->GetExactGroundLevel(points[i].X, points[i].Y), cmpWaterManager->GetExactWaterLevel(points[i].X, points[i].Y) ); m_UnitOverlay->m_Corners[i] = CVector3D(points[i].X, quadY, points[i].Y); m_UnitOverlayBoundingBox += m_UnitOverlay->m_Corners[i]; } } void CCmpSelectable::RenderSubmit(SceneCollector& collector, const CFrustum& frustum, bool culling) { // don't render selection overlay if it's not gonna be visible if (!ICmpSelectable::m_OverrideVisible) return; if (m_Visible && m_Color.a > 0) { if (!m_Cached) { UpdateColor(); m_Cached = true; } switch (m_OverlayDescriptor.m_Type) { case STATIC_OUTLINE: { if (!m_BuildingOverlay) { // Static overlays are allocated once and not updated until they are explicitly deleted again // (see InvalidateStaticOverlay). Since they are expected to change rarely (if ever) during // normal gameplay, this saves us doing all the work below on each frame. m_BuildingOverlay = new SOverlayTexturedLine; UpdateTexturedLineOverlay(&m_OverlayDescriptor, *m_BuildingOverlay, 0); } m_BuildingOverlay->m_Color = m_Color; // done separately so alpha changes don't require a full update call if (culling && !m_BuildingOverlay->IsVisibleInFrustum(frustum)) break; collector.Submit(m_BuildingOverlay); } break; case DYNAMIC_QUAD: { if (culling && !frustum.IsBoxVisible(m_UnitOverlayBoundingBox)) break; if (m_UnitOverlay) collector.Submit(m_UnitOverlay); } break; default: break; } } // Render bounding box debug overlays if we have a positive target alpha value. This ensures // that the debug overlays respond immediately to deselection without delay from fading out. if (m_FadeBaselineAlpha + m_FadeDeltaAlpha > 0) { if (ICmpSelectable::ms_EnableDebugOverlays) { // allocate debug overlays on-demand if (!m_DebugBoundingBoxOverlay) m_DebugBoundingBoxOverlay = new SOverlayLine; if (!m_DebugSelectionBoxOverlay) m_DebugSelectionBoxOverlay = new SOverlayLine; CmpPtr cmpVisual(GetEntityHandle()); if (cmpVisual) { SimRender::ConstructBoxOutline(cmpVisual->GetBounds(), *m_DebugBoundingBoxOverlay); m_DebugBoundingBoxOverlay->m_Thickness = 2; m_DebugBoundingBoxOverlay->m_Color = CColor(1.f, 0.f, 0.f, 1.f); SimRender::ConstructBoxOutline(cmpVisual->GetSelectionBox(), *m_DebugSelectionBoxOverlay); m_DebugSelectionBoxOverlay->m_Thickness = 2; m_DebugSelectionBoxOverlay->m_Color = CColor(0.f, 1.f, 0.f, 1.f); collector.Submit(m_DebugBoundingBoxOverlay); collector.Submit(m_DebugSelectionBoxOverlay); } } else { // reclaim debug overlay line memory when no longer debugging (and make sure to set to zero after deletion) if (m_DebugBoundingBoxOverlay) SAFE_DELETE(m_DebugBoundingBoxOverlay); if (m_DebugSelectionBoxOverlay) SAFE_DELETE(m_DebugSelectionBoxOverlay); } } } REGISTER_COMPONENT_TYPE(Selectable) Index: ps/trunk/source/simulation2/components/ICmpPathfinder.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpPathfinder.h (revision 24226) +++ ps/trunk/source/simulation2/components/ICmpPathfinder.h (revision 24227) @@ -1,214 +1,214 @@ /* 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_ICMPPATHFINDER #define INCLUDED_ICMPPATHFINDER #include "simulation2/system/Interface.h" #include "simulation2/components/ICmpObstruction.h" -#include "simulation2/helpers/PathGoal.h" #include "simulation2/helpers/Pathfinding.h" #include "maths/FixedVector2D.h" #include #include class IObstructionTestFilter; +class PathGoal; template class Grid; // Returned by asynchronous workers, used to send messages in the main thread. struct WaypointPath; struct PathResult { PathResult() = default; PathResult(u32 t, entity_id_t n, WaypointPath p) : ticket(t), notify(n), path(p) {}; u32 ticket; entity_id_t notify; WaypointPath path; }; /** * Pathfinder algorithms. * * There are two different modes: a tile-based pathfinder that works over long distances and * accounts for terrain costs but ignore units, and a 'short' vertex-based pathfinder that * provides precise paths and avoids other units. * * Both use the same concept of a PathGoal: either a point, circle or square. * (If the starting point is inside the goal shape then the path will move outwards * to reach the shape's outline.) * * The output is a list of waypoints. */ class ICmpPathfinder : public IComponent { public: /** * Get the list of all known passability classes. */ virtual void GetPassabilityClasses(std::map& passClasses) const = 0; /** * Get the list of passability classes, separating pathfinding classes and others. */ virtual void GetPassabilityClasses( std::map& nonPathfindingPassClasses, std::map& pathfindingPassClasses) const = 0; /** * Get the tag for a given passability class name. * Logs an error and returns something acceptable if the name is unrecognised. */ virtual pass_class_t GetPassabilityClass(const std::string& name) const = 0; virtual entity_pos_t GetClearance(pass_class_t passClass) const = 0; /** * Get the larger clearance in all passability classes. */ virtual entity_pos_t GetMaximumClearance() const = 0; virtual const Grid& GetPassabilityGrid() = 0; /** * Get the accumulated dirtiness information since the last time the AI accessed and flushed it. */ virtual const GridUpdateInformation& GetAIPathfinderDirtinessInformation() const = 0; virtual void FlushAIPathfinderDirtinessInformation() = 0; /** * Get a grid representing the distance to the shore of the terrain tile. */ virtual Grid ComputeShoreGrid(bool expandOnWater = false) = 0; /** * Asynchronous version of ComputePath. * Request a long path computation, asynchronously. * The result will be sent as CMessagePathResult to 'notify'. * Returns a unique non-zero number, which will match the 'ticket' in the result, * so callers can recognise each individual request they make. */ virtual u32 ComputePathAsync(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, entity_id_t notify) = 0; /* * Request a long-path computation immediately */ virtual void ComputePathImmediate(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass, WaypointPath& ret) const = 0; /** * Request a short path computation, asynchronously. * The result will be sent as CMessagePathResult to 'notify'. * Returns a unique non-zero number, which will match the 'ticket' in the result, * so callers can recognise each individual request they make. */ virtual u32 ComputeShortPathAsync(entity_pos_t x0, entity_pos_t z0, entity_pos_t clearance, entity_pos_t range, const PathGoal& goal, pass_class_t passClass, bool avoidMovingUnits, entity_id_t controller, entity_id_t notify) = 0; /* * Request a short-path computation immediately. */ virtual WaypointPath ComputeShortPathImmediate(const ShortPathRequest& request) const = 0; /** * If the debug overlay is enabled, render the path that will computed by ComputePath. */ virtual void SetDebugPath(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass) = 0; /** * @return true if the goal is reachable from (x0, z0) for the given passClass, false otherwise. * Warning: this is synchronous, somewhat expensive and not should not be called too liberally. */ virtual bool IsGoalReachable(entity_pos_t x0, entity_pos_t z0, const PathGoal& goal, pass_class_t passClass) = 0; /** * Check whether the given movement line is valid and doesn't hit any obstructions * or impassable terrain. * Returns true if the movement is okay. */ virtual bool CheckMovement(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, pass_class_t passClass) const = 0; /** * Check whether a unit placed here is valid and doesn't hit any obstructions * or impassable terrain. * When onlyCenterPoint = true, only check the center tile of the unit * @return ICmpObstruction::FOUNDATION_CHECK_SUCCESS if the placement is okay, else * a value describing the type of failure. */ virtual ICmpObstruction::EFoundationCheck CheckUnitPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, pass_class_t passClass, bool onlyCenterPoint = false) const = 0; /** * Check whether a building placed here is valid and doesn't hit any obstructions * or impassable terrain. * @return ICmpObstruction::FOUNDATION_CHECK_SUCCESS if the placement is okay, else * a value describing the type of failure. */ virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass) const = 0; /** * Check whether a building placed here is valid and doesn't hit any obstructions * or impassable terrain. * when onlyCenterPoint = true, only check the center tile of the building * @return ICmpObstruction::FOUNDATION_CHECK_SUCCESS if the placement is okay, else * a value describing the type of failure. */ virtual ICmpObstruction::EFoundationCheck CheckBuildingPlacement(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, entity_id_t id, pass_class_t passClass, bool onlyCenterPoint) const = 0; /** * Toggle the storage and rendering of debug info. */ virtual void SetDebugOverlay(bool enabled) = 0; /** * Toggle the storage and rendering of debug info for the hierarchical pathfinder. */ virtual void SetHierDebugOverlay(bool enabled) = 0; /** * Finish computing asynchronous path requests and send the CMessagePathResult messages. */ virtual void FetchAsyncResultsAndSendMessages() = 0; /** * Tell asynchronous pathfinder threads that they can begin computing paths. */ virtual void StartProcessingMoves(bool useMax) = 0; /** * Regenerates the grid based on the current obstruction list, if necessary */ virtual void UpdateGrid() = 0; /** * Returns some stats about the last ComputePath. */ virtual void GetDebugData(u32& steps, double& time, Grid& grid) const = 0; /** * Sets up the pathfinder passability overlay in Atlas. */ virtual void SetAtlasOverlay(bool enable, pass_class_t passClass = 0) = 0; DECLARE_INTERFACE_TYPE(Pathfinder) }; #endif // INCLUDED_ICMPPATHFINDER Index: ps/trunk/source/simulation2/components/tests/test_RangeManager.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_RangeManager.h (revision 24226) +++ ps/trunk/source/simulation2/components/tests/test_RangeManager.h (revision 24227) @@ -1,268 +1,269 @@ /* 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 "maths/Matrix3D.h" #include "simulation2/system/ComponentTest.h" #include "simulation2/components/ICmpRangeManager.h" #include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpVision.h" #include #include class MockVisionRgm : public ICmpVision { public: DEFAULT_MOCK_COMPONENT() virtual entity_pos_t GetRange() const { return entity_pos_t::FromInt(66); } virtual bool GetRevealShore() const { return false; } }; class MockPositionRgm : public ICmpPosition { public: DEFAULT_MOCK_COMPONENT() virtual void SetTurretParent(entity_id_t UNUSED(id), const CFixedVector3D& UNUSED(pos)) {} virtual entity_id_t GetTurretParent() const {return INVALID_ENTITY;} virtual void UpdateTurretPosition() {} virtual std::set* GetTurrets() { return NULL; } virtual bool IsInWorld() const { return true; } virtual void MoveOutOfWorld() { } virtual void MoveTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) { } virtual void MoveAndTurnTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z), entity_angle_t UNUSED(a)) { } virtual void JumpTo(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) { } virtual void SetHeightOffset(entity_pos_t UNUSED(dy)) { } virtual entity_pos_t GetHeightOffset() const { return entity_pos_t::Zero(); } virtual void SetHeightFixed(entity_pos_t UNUSED(y)) { } virtual entity_pos_t GetHeightFixed() const { return entity_pos_t::Zero(); } virtual bool IsHeightRelative() const { return true; } virtual void SetHeightRelative(bool UNUSED(relative)) { } virtual bool CanFloat() const { return false; } virtual void SetFloating(bool UNUSED(flag)) { } virtual void SetActorFloating(bool UNUSED(flag)) { } virtual void SetConstructionProgress(fixed UNUSED(progress)) { } virtual CFixedVector3D GetPosition() const { return m_Pos; } virtual CFixedVector2D GetPosition2D() const { return CFixedVector2D(m_Pos.X, m_Pos.Z); } virtual CFixedVector3D GetPreviousPosition() const { return CFixedVector3D(); } virtual CFixedVector2D GetPreviousPosition2D() const { return CFixedVector2D(); } virtual void TurnTo(entity_angle_t UNUSED(y)) { } virtual void SetYRotation(entity_angle_t UNUSED(y)) { } virtual void SetXZRotation(entity_angle_t UNUSED(x), entity_angle_t UNUSED(z)) { } virtual CFixedVector3D GetRotation() const { return CFixedVector3D(); } virtual fixed GetDistanceTravelled() const { return fixed::Zero(); } virtual void GetInterpolatedPosition2D(float UNUSED(frameOffset), float& x, float& z, float& rotY) const { x = z = rotY = 0; } virtual CMatrix3D GetInterpolatedTransform(float UNUSED(frameOffset)) const { return CMatrix3D(); } CFixedVector3D m_Pos; }; class MockObstructionRgm : public ICmpObstruction { public: DEFAULT_MOCK_COMPONENT(); MockObstructionRgm(entity_pos_t s) : m_Size(s) {}; virtual ICmpObstructionManager::tag_t GetObstruction() const { return {}; }; virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare&) const { return false; }; virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare&) const { return false; }; virtual entity_pos_t GetSize() const { return m_Size; }; virtual CFixedVector2D GetStaticSize() const { return {}; }; virtual EObstructionType GetObstructionType() const { return {}; }; virtual void SetUnitClearance(const entity_pos_t&) {}; virtual bool IsControlPersistent() const { return {}; }; virtual bool CheckShorePlacement() const { return {}; }; virtual EFoundationCheck CheckFoundation(const std::string&) const { return {}; }; virtual EFoundationCheck CheckFoundation(const std::string& , bool) const { return {}; }; virtual std::string CheckFoundation_wrapper(const std::string&, bool) const { return {}; }; virtual bool CheckDuplicateFoundation() const { return {}; }; virtual std::vector GetEntitiesByFlags(ICmpObstructionManager::flags_t) const { return {}; }; virtual std::vector GetEntitiesBlockingMovement() const { return {}; }; virtual std::vector GetEntitiesBlockingConstruction() const { return {}; }; virtual std::vector GetEntitiesDeletedUponConstruction() const { return {}; }; virtual void ResolveFoundationCollisions() const {}; virtual void SetActive(bool) {}; virtual void SetMovingFlag(bool) {}; virtual void SetDisableBlockMovementPathfinding(bool, bool, int32_t) {}; virtual bool GetBlockMovementFlag() const { return {}; }; virtual void SetControlGroup(entity_id_t) {}; virtual entity_id_t GetControlGroup() const { return {}; }; virtual void SetControlGroup2(entity_id_t) {}; virtual entity_id_t GetControlGroup2() const { return {}; }; private: entity_pos_t m_Size; }; class TestCmpRangeManager : public CxxTest::TestSuite { public: void setUp() { CXeromyces::Startup(); } void tearDown() { CXeromyces::Terminate(); } // TODO It would be nice to call Verify() with the shore revealing system // but that means testing on an actual map, with water and land. void test_basic() { ComponentTestHelper test(g_ScriptContext); ICmpRangeManager* cmp = test.Add(CID_RangeManager, "", SYSTEM_ENTITY); MockVisionRgm vision; test.AddMock(100, IID_Vision, vision); MockPositionRgm position; test.AddMock(100, IID_Position, position); // This tests that the incremental computation produces the correct result // in various edge cases cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512), 512/TERRAIN_TILE_SIZE + 1); cmp->Verify(); { CMessageCreate msg(100); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessageOwnershipChanged msg(100, -1, 1); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromDouble(257.95), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(247), entity_pos_t::FromInt(253), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_pos_t::FromInt(256), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)+entity_pos_t::Epsilon(), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(256), entity_pos_t::FromInt(256)-entity_pos_t::Epsilon(), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(383), entity_pos_t::FromInt(84), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); { CMessagePositionChanged msg(100, true, entity_pos_t::FromInt(348), entity_pos_t::FromInt(83), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); boost::mt19937 rng; for (size_t i = 0; i < 1024; ++i) { double x = boost::random::uniform_real_distribution(0.0, 512.0)(rng); double z = boost::random::uniform_real_distribution(0.0, 512.0)(rng); { CMessagePositionChanged msg(100, true, entity_pos_t::FromDouble(x), entity_pos_t::FromDouble(z), entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } cmp->Verify(); } // Test OwnershipChange, GetEntitiesByPlayer, GetNonGaiaEntities { player_id_t previousOwner = -1; for (player_id_t newOwner = 0; newOwner < 8; ++newOwner) { CMessageOwnershipChanged msg(100, previousOwner, newOwner); cmp->HandleMessage(msg, false); for (player_id_t i = 0; i < 8; ++i) TS_ASSERT_EQUALS(cmp->GetEntitiesByPlayer(i).size(), i == newOwner ? 1 : 0); TS_ASSERT_EQUALS(cmp->GetNonGaiaEntities().size(), newOwner > 0 ? 1 : 0); previousOwner = newOwner; } } } void test_queries() { ComponentTestHelper test(g_ScriptContext); ICmpRangeManager* cmp = test.Add(CID_RangeManager, "", SYSTEM_ENTITY); MockVisionRgm vision, vision2; MockPositionRgm position, position2; MockObstructionRgm obs(fixed::FromInt(2)), obs2(fixed::Zero()); test.AddMock(100, IID_Vision, vision); test.AddMock(100, IID_Position, position); test.AddMock(100, IID_Obstruction, obs); test.AddMock(101, IID_Vision, vision2); test.AddMock(101, IID_Position, position2); test.AddMock(101, IID_Obstruction, obs2); cmp->SetBounds(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0), entity_pos_t::FromInt(512), entity_pos_t::FromInt(512), 512/TERRAIN_TILE_SIZE + 1); cmp->Verify(); { CMessageCreate msg(100); cmp->HandleMessage(msg, false); } { CMessageCreate msg(101); cmp->HandleMessage(msg, false); } { CMessageOwnershipChanged msg(100, -1, 1); cmp->HandleMessage(msg, false); } { CMessageOwnershipChanged msg(101, -1, 1); cmp->HandleMessage(msg, false); } auto move = [&cmp](entity_id_t ent, MockPositionRgm& pos, fixed x, fixed z) { pos.m_Pos = CFixedVector3D(x, fixed::Zero(), z); { CMessagePositionChanged msg(ent, true, x, z, entity_angle_t::Zero()); cmp->HandleMessage(msg, false); } }; move(100, position, fixed::FromInt(10), fixed::FromInt(10)); move(101, position2, fixed::FromInt(10), fixed::FromInt(20)); std::vector nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{}); nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{101}); move(101, position2, fixed::FromInt(10), fixed::FromInt(10)); nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{101}); nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{}); move(101, position2, fixed::FromInt(10), fixed::FromInt(13)); nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{101}); nearby = cmp->ExecuteQuery(100, fixed::FromInt(4), fixed::FromInt(50), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{}); move(101, position2, fixed::FromInt(10), fixed::FromInt(15)); // In range thanks to self obstruction size. nearby = cmp->ExecuteQuery(100, fixed::FromInt(0), fixed::FromInt(4), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{101}); // In range thanks to target obstruction size. nearby = cmp->ExecuteQuery(101, fixed::FromInt(0), fixed::FromInt(4), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{100}); // Trickier: min-range is closest-to-closest, but rotation may change the real distance. nearby = cmp->ExecuteQuery(100, fixed::FromInt(2), fixed::FromInt(50), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{101}); nearby = cmp->ExecuteQuery(100, fixed::FromInt(5), fixed::FromInt(50), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{101}); nearby = cmp->ExecuteQuery(100, fixed::FromInt(6), fixed::FromInt(50), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{}); nearby = cmp->ExecuteQuery(101, fixed::FromInt(5), fixed::FromInt(50), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{100}); nearby = cmp->ExecuteQuery(101, fixed::FromInt(6), fixed::FromInt(50), {1}, 0); TS_ASSERT_EQUALS(nearby, std::vector{}); } }; Index: ps/trunk/source/simulation2/helpers/Pathfinding.h =================================================================== --- ps/trunk/source/simulation2/helpers/Pathfinding.h (revision 24226) +++ ps/trunk/source/simulation2/helpers/Pathfinding.h (revision 24227) @@ -1,243 +1,243 @@ /* 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_PATHFINDING #define INCLUDED_PATHFINDING #include "graphics/Terrain.h" #include "maths/MathUtil.h" -#include "ps/CLogger.h" #include "simulation2/system/Entity.h" -#include "simulation2/system/ParamNode.h" #include "PathGoal.h" +class CParamNode; + typedef u16 pass_class_t; template class Grid; struct LongPathRequest { u32 ticket; entity_pos_t x0; entity_pos_t z0; PathGoal goal; pass_class_t passClass; entity_id_t notify; }; struct ShortPathRequest { u32 ticket; entity_pos_t x0; entity_pos_t z0; entity_pos_t clearance; entity_pos_t range; PathGoal goal; pass_class_t passClass; bool avoidMovingUnits; entity_id_t group; entity_id_t notify; }; struct Waypoint { entity_pos_t x, z; }; /** * Returned path. * Waypoints are in *reverse* order (the earliest is at the back of the list) */ struct WaypointPath { std::vector m_Waypoints; }; /** * Represents the cost of a path consisting of horizontal/vertical and * diagonal movements over a uniform-cost grid. * Maximum path length before overflow is about 45K steps. */ struct PathCost { PathCost() : data(0) { } /// Construct from a number of horizontal/vertical and diagonal steps PathCost(u16 hv, u16 d) : data(hv * 65536 + d * 92682) // 2^16 * sqrt(2) == 92681.9 { } /// Construct for horizontal/vertical movement of given number of steps static PathCost horizvert(u16 n) { return PathCost(n, 0); } /// Construct for diagonal movement of given number of steps static PathCost diag(u16 n) { return PathCost(0, n); } PathCost operator+(const PathCost& a) const { PathCost c; c.data = data + a.data; return c; } PathCost& operator+=(const PathCost& a) { data += a.data; return *this; } bool operator<=(const PathCost& b) const { return data <= b.data; } bool operator< (const PathCost& b) const { return data < b.data; } bool operator>=(const PathCost& b) const { return data >= b.data; } bool operator>(const PathCost& b) const { return data > b.data; } u32 ToInt() { return data; } private: u32 data; }; static const int PASS_CLASS_BITS = 16; typedef u16 NavcellData; // 1 bit per passability class (up to PASS_CLASS_BITS) #define IS_PASSABLE(item, classmask) (((item) & (classmask)) == 0) #define PASS_CLASS_MASK_FROM_INDEX(id) ((pass_class_t)(1u << id)) #define SPECIAL_PASS_CLASS PASS_CLASS_MASK_FROM_INDEX((PASS_CLASS_BITS-1)) // 16th bit, used for special in-place computations namespace Pathfinding { /** * The long-range pathfinder operates primarily over a navigation grid (a uniform-cost * 2D passability grid, with horizontal/vertical (not diagonal) connectivity). * This is based on the terrain tile passability, plus the rasterized shapes of * obstructions, all expanded outwards by the radius of the units. * Since units are much smaller than terrain tiles, the nav grid should be * higher resolution than the tiles. * We therefore split each terrain tile into NxN "nav cells" (for some integer N, * preferably a power of two). */ const int NAVCELLS_PER_TILE = 4; /** * Size of a navcell in metres ( = TERRAIN_TILE_SIZE / NAVCELLS_PER_TILE) */ const fixed NAVCELL_SIZE = fixed::FromInt((int)TERRAIN_TILE_SIZE) / Pathfinding::NAVCELLS_PER_TILE; const int NAVCELL_SIZE_INT = 1; const int NAVCELL_SIZE_LOG2 = 0; /** * To make sure the long-range pathfinder is more strict than the short-range one, * we need to slightly over-rasterize. So we extend the clearance radius by 1. */ const entity_pos_t CLEARANCE_EXTENSION_RADIUS = fixed::FromInt(1); /** * Compute the navcell indexes on the grid nearest to a given point * w, h are the grid dimensions, i.e. the number of navcells per side */ inline void NearestNavcell(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h) { // Use NAVCELL_SIZE_INT to save the cost of dividing by a fixed i = static_cast(Clamp((x / NAVCELL_SIZE_INT).ToInt_RoundToNegInfinity(), 0, w - 1)); j = static_cast(Clamp((z / NAVCELL_SIZE_INT).ToInt_RoundToNegInfinity(), 0, h - 1)); } /** * Returns the position of the center of the given tile */ inline void TileCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z) { cassert(TERRAIN_TILE_SIZE % 2 == 0); x = entity_pos_t::FromInt(i*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE / 2); z = entity_pos_t::FromInt(j*(int)TERRAIN_TILE_SIZE + (int)TERRAIN_TILE_SIZE / 2); } inline void NavcellCenter(u16 i, u16 j, entity_pos_t& x, entity_pos_t& z) { x = entity_pos_t::FromInt(i * 2 + 1).Multiply(NAVCELL_SIZE / 2); z = entity_pos_t::FromInt(j * 2 + 1).Multiply(NAVCELL_SIZE / 2); } /* * Checks that the line (x0,z0)-(x1,z1) does not intersect any impassable navcells. */ bool CheckLineMovement(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, pass_class_t passClass, const Grid& grid); } /* * For efficient pathfinding we want to try hard to minimise the per-tile search cost, * so we precompute the tile passability flags and movement costs for the various different * types of unit. * We also want to minimise memory usage (there can easily be 100K tiles so we don't want * to store many bytes for each). * * To handle passability efficiently, we have a small number of passability classes * (e.g. "infantry", "ship"). Each unit belongs to a single passability class, and * uses that for all its pathfinding. * Passability is determined by water depth, terrain slope, forestness, buildingness. * We need at least one bit per class per tile to represent passability. * * Not all pass classes are used for actual pathfinding. The pathfinder calls * CCmpObstructionManager's Rasterize() to add shapes onto the passability grid. * Which shapes are rasterized depend on the value of the m_Obstructions of each passability * class. * * Passabilities not used for unit pathfinding should not use the Clearance attribute, and * will get a zero clearance value. */ class PathfinderPassability { public: PathfinderPassability(pass_class_t mask, const CParamNode& node); bool IsPassable(fixed waterdepth, fixed steepness, fixed shoredist) const { return ((m_MinDepth <= waterdepth && waterdepth <= m_MaxDepth) && (steepness < m_MaxSlope) && (m_MinShore <= shoredist && shoredist <= m_MaxShore)); } pass_class_t m_Mask; fixed m_Clearance; // min distance from static obstructions enum ObstructionHandling { NONE, PATHFINDING, FOUNDATION }; ObstructionHandling m_Obstructions; private: fixed m_MinDepth; fixed m_MaxDepth; fixed m_MaxSlope; fixed m_MinShore; fixed m_MaxShore; }; #endif // INCLUDED_PATHFINDING Index: ps/trunk/source/simulation2/helpers/Render.cpp =================================================================== --- ps/trunk/source/simulation2/helpers/Render.cpp (revision 24226) +++ ps/trunk/source/simulation2/helpers/Render.cpp (revision 24227) @@ -1,638 +1,639 @@ /* 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 "Render.h" #include "graphics/Overlay.h" #include "graphics/Terrain.h" #include "maths/BoundingBoxAligned.h" #include "maths/BoundingBoxOriented.h" #include "maths/MathUtil.h" +#include "maths/Matrix3D.h" #include "maths/Quaternion.h" #include "maths/Vector2D.h" #include "ps/Profile.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/components/ICmpWaterManager.h" #include "simulation2/helpers/Geometry.h" void SimRender::ConstructLineOnGround(const CSimContext& context, const std::vector& xz, SOverlayLine& overlay, bool floating, float heightOffset) { overlay.m_Coords.clear(); CmpPtr cmpTerrain(context, SYSTEM_ENTITY); if (!cmpTerrain) return; if (xz.size() < 2) return; float water = 0.f; if (floating) { CmpPtr cmpWaterManager(context, SYSTEM_ENTITY); if (cmpWaterManager) water = cmpWaterManager->GetExactWaterLevel(xz[0], xz[1]); } overlay.m_Coords.reserve(xz.size()/2 * 3); for (size_t i = 0; i < xz.size(); i += 2) { float px = xz[i]; float pz = xz[i+1]; float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } } static void ConstructCircleOrClosedArc( const CSimContext& context, float x, float z, float radius, bool isCircle, float start, float end, SOverlayLine& overlay, bool floating, float heightOffset) { overlay.m_Coords.clear(); CmpPtr cmpTerrain(context, SYSTEM_ENTITY); if (!cmpTerrain) return; float water = 0.f; if (floating) { CmpPtr cmpWaterManager(context, SYSTEM_ENTITY); if (cmpWaterManager) water = cmpWaterManager->GetExactWaterLevel(x, z); } // Adapt the circle resolution to look reasonable for small and largeish radiuses size_t numPoints = Clamp(radius * (end - start), 12, 48); if (!isCircle) overlay.m_Coords.reserve((numPoints + 1 + 2) * 3); else overlay.m_Coords.reserve((numPoints + 1) * 3); float cy; if (!isCircle) { // Start at the center point cy = std::max(water, cmpTerrain->GetExactGroundLevel(x, z)) + heightOffset; overlay.m_Coords.push_back(x); overlay.m_Coords.push_back(cy); overlay.m_Coords.push_back(z); } for (size_t i = 0; i <= numPoints; ++i) // use '<=' so it's a closed loop { float a = start + (float)i * (end - start) / (float)numPoints; float px = x + radius * cosf(a); float pz = z + radius * sinf(a); float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } if (!isCircle) { // Return to the center point overlay.m_Coords.push_back(x); overlay.m_Coords.push_back(cy); overlay.m_Coords.push_back(z); } } void SimRender::ConstructCircleOnGround( const CSimContext& context, float x, float z, float radius, SOverlayLine& overlay, bool floating, float heightOffset) { ConstructCircleOrClosedArc(context, x, z, radius, true, 0.0f, 2.0f*(float)M_PI, overlay, floating, heightOffset); } void SimRender::ConstructClosedArcOnGround( const CSimContext& context, float x, float z, float radius, float start, float end, SOverlayLine& overlay, bool floating, float heightOffset) { ConstructCircleOrClosedArc(context, x, z, radius, false, start, end, overlay, floating, heightOffset); } // This method splits up a straight line into a number of line segments each having a length ~= TERRAIN_TILE_SIZE static void SplitLine(std::vector >& coords, float x1, float y1, float x2, float y2) { float length = sqrtf(SQR(x1 - x2) + SQR(y1 - y2)); size_t pieces = ((int)length) / TERRAIN_TILE_SIZE; if (pieces > 0) { float xPieceLength = (x1 - x2) / (float)pieces; float yPieceLength = (y1 - y2) / (float)pieces; for (size_t i = 1; i <= (pieces - 1); ++i) { coords.emplace_back(x1 - (xPieceLength * (float)i), y1 - (yPieceLength * (float)i)); } } coords.emplace_back(x2, y2); } void SimRender::ConstructSquareOnGround(const CSimContext& context, float x, float z, float w, float h, float a, SOverlayLine& overlay, bool floating, float heightOffset) { overlay.m_Coords.clear(); CmpPtr cmpTerrain(context, SYSTEM_ENTITY); if (!cmpTerrain) return; float water = 0.f; if (floating) { CmpPtr cmpWaterManager(context, SYSTEM_ENTITY); if (cmpWaterManager) water = cmpWaterManager->GetExactWaterLevel(x, z); } float c = cosf(a); float s = sinf(a); std::vector > coords; // Add the first vertex, since SplitLine will be adding only the second end-point of the each line to // the coordinates list. We don't have to worry about the other lines, since the end-point of one line // will be the starting point of the next coords.emplace_back(x - w/2*c + h/2*s, z + w/2*s + h/2*c); SplitLine(coords, x - w/2*c + h/2*s, z + w/2*s + h/2*c, x - w/2*c - h/2*s, z + w/2*s - h/2*c); SplitLine(coords, x - w/2*c - h/2*s, z + w/2*s - h/2*c, x + w/2*c - h/2*s, z - w/2*s - h/2*c); SplitLine(coords, x + w/2*c - h/2*s, z - w/2*s - h/2*c, x + w/2*c + h/2*s, z - w/2*s + h/2*c); SplitLine(coords, x + w/2*c + h/2*s, z - w/2*s + h/2*c, x - w/2*c + h/2*s, z + w/2*s + h/2*c); overlay.m_Coords.reserve(coords.size() * 3); for (size_t i = 0; i < coords.size(); ++i) { float px = coords[i].first; float pz = coords[i].second; float py = std::max(water, cmpTerrain->GetExactGroundLevel(px, pz)) + heightOffset; overlay.m_Coords.push_back(px); overlay.m_Coords.push_back(py); overlay.m_Coords.push_back(pz); } } void SimRender::ConstructBoxOutline(const CBoundingBoxAligned& bound, SOverlayLine& overlayLine) { overlayLine.m_Coords.clear(); if (bound.IsEmpty()) return; const CVector3D& pMin = bound[0]; const CVector3D& pMax = bound[1]; // floor square overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z); overlayLine.PushCoords(pMax.X, pMin.Y, pMin.Z); overlayLine.PushCoords(pMax.X, pMin.Y, pMax.Z); overlayLine.PushCoords(pMin.X, pMin.Y, pMax.Z); overlayLine.PushCoords(pMin.X, pMin.Y, pMin.Z); // roof square overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z); overlayLine.PushCoords(pMax.X, pMax.Y, pMin.Z); overlayLine.PushCoords(pMax.X, pMax.Y, pMax.Z); overlayLine.PushCoords(pMin.X, pMax.Y, pMax.Z); overlayLine.PushCoords(pMin.X, pMax.Y, pMin.Z); } void SimRender::ConstructBoxOutline(const CBoundingBoxOriented& box, SOverlayLine& overlayLine) { overlayLine.m_Coords.clear(); if (box.IsEmpty()) return; CVector3D corners[8]; box.GetCorner(-1, -1, -1, corners[0]); box.GetCorner( 1, -1, -1, corners[1]); box.GetCorner( 1, -1, 1, corners[2]); box.GetCorner(-1, -1, 1, corners[3]); box.GetCorner(-1, 1, -1, corners[4]); box.GetCorner( 1, 1, -1, corners[5]); box.GetCorner( 1, 1, 1, corners[6]); box.GetCorner(-1, 1, 1, corners[7]); overlayLine.PushCoords(corners[0]); overlayLine.PushCoords(corners[1]); overlayLine.PushCoords(corners[2]); overlayLine.PushCoords(corners[3]); overlayLine.PushCoords(corners[0]); overlayLine.PushCoords(corners[4]); overlayLine.PushCoords(corners[5]); overlayLine.PushCoords(corners[6]); overlayLine.PushCoords(corners[7]); overlayLine.PushCoords(corners[4]); } void SimRender::ConstructGimbal(const CVector3D& center, float radius, SOverlayLine& out, size_t numSteps) { ENSURE(numSteps > 0 && numSteps % 4 == 0); // must be a positive multiple of 4 out.m_Coords.clear(); size_t fullCircleSteps = numSteps; const float angleIncrement = 2.f*M_PI/fullCircleSteps; const CVector3D X_UNIT(1, 0, 0); const CVector3D Y_UNIT(0, 1, 0); const CVector3D Z_UNIT(0, 0, 1); CVector3D rotationVector(0, 0, radius); // directional vector based in the center that we will be rotating to get the gimbal points // first draw a quarter of XZ gimbal; then complete the XY gimbal; then continue the XZ gimbal and finally add the YZ gimbal // (that way we can keep a single continuous line) // -- XZ GIMBAL (PART 1/2) ----------------------------------------------- CQuaternion xzRotation; xzRotation.FromAxisAngle(Y_UNIT, angleIncrement); for (size_t i = 0; i < fullCircleSteps/4; ++i) // complete only a quarter of the way { out.PushCoords(center + rotationVector); rotationVector = xzRotation.Rotate(rotationVector); } // -- XY GIMBAL ---------------------------------------------------------- // now complete the XY gimbal while the XZ gimbal is interrupted CQuaternion xyRotation; xyRotation.FromAxisAngle(Z_UNIT, angleIncrement); for (size_t i = 0; i < fullCircleSteps; ++i) // note the <; the last point of the XY gimbal isn't added, because the XZ gimbal will add it { out.PushCoords(center + rotationVector); rotationVector = xyRotation.Rotate(rotationVector); } // -- XZ GIMBAL (PART 2/2) ----------------------------------------------- // resume the XZ gimbal to completion for (size_t i = fullCircleSteps/4; i < fullCircleSteps; ++i) // exclude the last point of the circle so the YZ gimbal can add it { out.PushCoords(center + rotationVector); rotationVector = xzRotation.Rotate(rotationVector); } // -- YZ GIMBAL ---------------------------------------------------------- CQuaternion yzRotation; yzRotation.FromAxisAngle(X_UNIT, angleIncrement); for (size_t i = 0; i <= fullCircleSteps; ++i) { out.PushCoords(center + rotationVector); rotationVector = yzRotation.Rotate(rotationVector); } } void SimRender::ConstructAxesMarker(const CMatrix3D& coordSystem, SOverlayLine& outX, SOverlayLine& outY, SOverlayLine& outZ) { outX.m_Coords.clear(); outY.m_Coords.clear(); outZ.m_Coords.clear(); outX.m_Color = CColor(1, 0, 0, .5f); // X axis; red outY.m_Color = CColor(0, 1, 0, .5f); // Y axis; green outZ.m_Color = CColor(0, 0, 1, .5f); // Z axis; blue outX.m_Thickness = 2; outY.m_Thickness = 2; outZ.m_Thickness = 2; CVector3D origin = coordSystem.GetTranslation(); outX.PushCoords(origin); outY.PushCoords(origin); outZ.PushCoords(origin); outX.PushCoords(origin + CVector3D(coordSystem(0,0), coordSystem(1,0), coordSystem(2,0))); outY.PushCoords(origin + CVector3D(coordSystem(0,1), coordSystem(1,1), coordSystem(2,1))); outZ.PushCoords(origin + CVector3D(coordSystem(0,2), coordSystem(1,2), coordSystem(2,2))); } void SimRender::SmoothPointsAverage(std::vector& points, bool closed) { PROFILE("SmoothPointsAverage"); size_t n = points.size(); if (n < 2) return; // avoid out-of-bounds array accesses, and leave the points unchanged std::vector newPoints; newPoints.resize(points.size()); // Handle the end points appropriately if (closed) { newPoints[0] = (points[n-1] + points[0] + points[1]) / 3.f; newPoints[n-1] = (points[n-2] + points[n-1] + points[0]) / 3.f; } else { newPoints[0] = points[0]; newPoints[n-1] = points[n-1]; } // Average all the intermediate points for (size_t i = 1; i < n-1; ++i) newPoints[i] = (points[i-1] + points[i] + points[i+1]) / 3.f; points.swap(newPoints); } static CVector2D EvaluateSpline(float t, CVector2D a0, CVector2D a1, CVector2D a2, CVector2D a3, float offset) { // Compute position on spline CVector2D p = a0*(t*t*t) + a1*(t*t) + a2*t + a3; // Compute unit-vector direction of spline CVector2D dp = (a0*(3*t*t) + a1*(2*t) + a2).Normalized(); // Offset position perpendicularly return p + CVector2D(dp.Y*-offset, dp.X*offset); } void SimRender::InterpolatePointsRNS(std::vector& points, bool closed, float offset, int segmentSamples /* = 4 */) { PROFILE("InterpolatePointsRNS"); ENSURE(segmentSamples > 0); std::vector newPoints; // (This does some redundant computations for adjacent vertices, // but it's fairly fast (<1ms typically) so we don't worry about it yet) // TODO: Instead of doing a fixed number of line segments between each // control point, it should probably be somewhat adaptive to get a nicer // curve with fewer points size_t n = points.size(); if (closed) { if (n < 1) return; // we need at least a single point to not crash } else { if (n < 2) return; // in non-closed mode, we need at least n=2 to not crash } size_t imax = closed ? n : n-1; newPoints.reserve(imax*segmentSamples); // these are primarily used inside the loop, but for open paths we need them outside the loop once to compute the last point CVector2D a0; CVector2D a1; CVector2D a2; CVector2D a3; for (size_t i = 0; i < imax; ++i) { // Get the relevant points for this spline segment; each step interpolates the segment between p1 and p2; p0 and p3 are the points // before p1 and after p2, respectively; they're needed to compute tangents and whatnot. CVector2D p0; // normally points[(i-1+n)%n], but it's a bit more complicated due to open/closed paths -- see below CVector2D p1 = points[i]; CVector2D p2 = points[(i+1)%n]; CVector2D p3; // normally points[(i+2)%n], but it's a bit more complicated due to open/closed paths -- see below if (!closed && (i == 0)) // p0's point index is out of bounds, and we can't wrap around because we're in non-closed mode -- create an artificial point // that extends p1 -> p0 (i.e. the first segment's direction) p0 = points[0] + (points[0] - points[1]); else // standard wrap-around case p0 = points[(i-1+n)%n]; // careful; don't use (i-1)%n here, as the result is machine-dependent for negative operands (e.g. if i==0, the result could be either -1 or n-1) if (!closed && (i == n-2)) // p3's point index is out of bounds; create an artificial point that extends p_(n-2) -> p_(n-1) (i.e. the last segment's direction) // (note that p2's index should not be out of bounds, because in non-closed mode imax is reduced by 1) p3 = points[n-1] + (points[n-1] - points[n-2]); else // standard wrap-around case p3 = points[(i+2)%n]; // Do the RNS computation (based on GPG4 "Nonuniform Splines") float l1 = (p2 - p1).Length(); // length of spline segment (i)..(i+1) CVector2D s0 = (p1 - p0).Normalized(); // unit vector of spline segment (i-1)..(i) CVector2D s1 = (p2 - p1).Normalized(); // unit vector of spline segment (i)..(i+1) CVector2D s2 = (p3 - p2).Normalized(); // unit vector of spline segment (i+1)..(i+2) CVector2D v1 = (s0 + s1).Normalized() * l1; // spline velocity at i CVector2D v2 = (s1 + s2).Normalized() * l1; // spline velocity at i+1 // Compute standard cubic spline parameters a0 = p1*2 + p2*-2 + v1 + v2; a1 = p1*-3 + p2*3 + v1*-2 + v2*-1; a2 = v1; a3 = p1; // Interpolate at regular points across the interval for (int sample = 0; sample < segmentSamples; sample++) newPoints.push_back(EvaluateSpline(sample/((float) segmentSamples), a0, a1, a2, a3, offset)); } if (!closed) // if the path is open, we should take care to include the last control point // NOTE: we can't just do push_back(points[n-1]) here because that ignores the offset newPoints.push_back(EvaluateSpline(1.f, a0, a1, a2, a3, offset)); points.swap(newPoints); } void SimRender::ConstructDashedLine(const std::vector& keyPoints, SDashedLine& dashedLineOut, const float dashLength, const float blankLength) { // sanity checks if (dashLength <= 0) return; if (blankLength <= 0) return; if (keyPoints.size() < 2) return; dashedLineOut.m_Points.clear(); dashedLineOut.m_StartIndices.clear(); // walk the line, counting the total length so far at each node point. When the length exceeds dashLength, cut the last segment at the // required length and continue for blankLength along the line to start a new dash segment. // TODO: we should probably extend this function to also allow for closed lines. I was thinking of slightly scaling the dash/blank length // so that it fits the length of the curve, but that requires knowing the length of the curve upfront which is sort of expensive to compute // (O(n) and lots of square roots). bool buildingDash = true; // true if we're building a dash, false if a blank float curDashLength = 0; // builds up the current dash/blank's length as we walk through the line nodes CVector2D dashLastPoint = keyPoints[0]; // last point of the current dash/blank being built. // register the first starting node of the first dash dashedLineOut.m_Points.push_back(keyPoints[0]); dashedLineOut.m_StartIndices.push_back(0); // index of the next key point on the path. Must always point to a node that is further along the path than dashLastPoint, so we can // properly take a direction vector along the path. size_t i = 0; while(i < keyPoints.size() - 1) { // get length of this segment CVector2D segmentVector = keyPoints[i + 1] - dashLastPoint; // vector from our current point along the path to nextNode float segmentLength = segmentVector.Length(); float targetLength = (buildingDash ? dashLength : blankLength); if (curDashLength + segmentLength > targetLength) { // segment is longer than the dash length we still have to go, so we'll need to cut it; create a cut point along the segment // line that is of just the required length to complete the dash, then make it the base point for the next dash/blank. float cutLength = targetLength - curDashLength; CVector2D cutPoint = dashLastPoint + (segmentVector.Normalized() * cutLength); // start a new dash or blank in the next iteration curDashLength = 0; buildingDash = !buildingDash; // flip from dash to blank and vice-versa dashLastPoint = cutPoint; // don't increment i, we haven't fully traversed this segment yet so we still need to use the same point to take the // direction vector with in the next iteration // this cut point is either the end of the current dash or the beginning of a new dash; either way, we're gonna need it // in the points array. dashedLineOut.m_Points.push_back(cutPoint); if (buildingDash) { // if we're gonna be building a new dash, then cutPoint is now the base point of that new dash, so let's register its // index as a start index of a dash. dashedLineOut.m_StartIndices.push_back(dashedLineOut.m_Points.size() - 1); } } else { // the segment from lastDashPoint to keyPoints[i+1] doesn't suffice to complete the dash, so we need to add keyPoints[i+1] // to this dash's points and continue from there if (buildingDash) // still building the dash, add it to the output (we don't need to store the blanks) dashedLineOut.m_Points.push_back(keyPoints[i+1]); curDashLength += segmentLength; dashLastPoint = keyPoints[i+1]; i++; } } } // TODO: this serves a similar purpose to SplitLine above, but is more general. Also, SplitLine seems to be implemented more // efficiently, might be nice to take some cues from it void SimRender::SubdividePoints(std::vector& points, float maxSegmentLength, bool closed) { size_t numControlPoints = points.size(); if (numControlPoints < 2) return; ENSURE(maxSegmentLength > 0); size_t endIndex = numControlPoints; if (!closed && numControlPoints > 2) endIndex--; std::vector newPoints; for (size_t i = 0; i < endIndex; i++) { const CVector2D& curPoint = points[i]; const CVector2D& nextPoint = points[(i+1) % numControlPoints]; const CVector2D line(nextPoint - curPoint); CVector2D lineDirection = line.Normalized(); // include control point i + a list of intermediate points between i and i + 1 (excluding i+1 itself) newPoints.push_back(curPoint); // calculate how many intermediate points are needed so that each segment is of length <= maxSegmentLength float lineLength = line.Length(); size_t numSegments = (size_t) ceilf(lineLength / maxSegmentLength); float segmentLength = lineLength / numSegments; for (size_t s = 1; s < numSegments; ++s) // start at one, we already included curPoint { newPoints.push_back(curPoint + lineDirection * (s * segmentLength)); } } points.swap(newPoints); } void SimRender::ConstructTexturedLineBox(SOverlayTexturedLine& overlay, const CVector2D& origin, const CFixedVector3D& rotation, const float sizeX, const float sizeZ) { float s = sinf(-rotation.Y.ToFloat()); float c = cosf(-rotation.Y.ToFloat()); CVector2D unitX(c, s); CVector2D unitZ(-s, c); // Add half the line thickness to the radius so that we get an 'outside' stroke of the footprint shape const float halfSizeX = sizeX / 2.f + overlay.m_Thickness / 2.f; const float halfSizeZ = sizeZ / 2.f + overlay.m_Thickness / 2.f; std::vector points; points.push_back(CVector2D(origin + unitX * halfSizeX + unitZ * (-halfSizeZ))); points.push_back(CVector2D(origin + unitX * (-halfSizeX) + unitZ * (-halfSizeZ))); points.push_back(CVector2D(origin + unitX * (-halfSizeX) + unitZ * halfSizeZ)); points.push_back(CVector2D(origin + unitX * halfSizeX + unitZ * halfSizeZ)); SimRender::SubdividePoints(points, TERRAIN_TILE_SIZE / 3.f, overlay.m_Closed); overlay.PushCoords(points); } void SimRender::ConstructTexturedLineCircle(SOverlayTexturedLine& overlay, const CVector2D& origin, const float overlay_radius) { const float radius = overlay_radius + overlay.m_Thickness / 3.f; size_t numSteps = ceilf(float(2 * M_PI) * radius / (TERRAIN_TILE_SIZE / 3.f)); for (size_t i = 0; i < numSteps; ++i) { float angle = i * float(2 * M_PI) / numSteps; float px = origin.X + radius * sinf(angle); float pz = origin.Y + radius * cosf(angle); overlay.PushCoords(px, pz); } } Index: ps/trunk/source/simulation2/scripting/JSInterface_Simulation.cpp =================================================================== --- ps/trunk/source/simulation2/scripting/JSInterface_Simulation.cpp (revision 24226) +++ ps/trunk/source/simulation2/scripting/JSInterface_Simulation.cpp (revision 24227) @@ -1,219 +1,220 @@ /* Copyright (C) 2020 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 "JSInterface_Simulation.h" #include "graphics/GameView.h" #include "ps/ConfigDB.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Pyrogenesis.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/components/ICmpAIManager.h" #include "simulation2/components/ICmpCommandQueue.h" #include "simulation2/components/ICmpGuiInterface.h" +#include "simulation2/components/ICmpObstruction.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpSelectable.h" #include "simulation2/helpers/Geometry.h" #include "simulation2/helpers/Selection.h" #include "simulation2/Simulation2.h" #include "simulation2/system/Entity.h" #include #include JS::Value JSI_Simulation::GuiInterfaceCall(ScriptInterface::CmptPrivate* pCmptPrivate, const std::wstring& name, JS::HandleValue data) { if (!g_Game) return JS::UndefinedValue(); CSimulation2* sim = g_Game->GetSimulation2(); ENSURE(sim); CmpPtr cmpGuiInterface(*sim, SYSTEM_ENTITY); if (!cmpGuiInterface) return JS::UndefinedValue(); ScriptRequest rqSim(sim->GetScriptInterface()); JS::RootedValue arg(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), data, false)); JS::RootedValue ret(rqSim.cx); cmpGuiInterface->ScriptCall(g_Game->GetViewedPlayerID(), name, arg, &ret); return pCmptPrivate->pScriptInterface->CloneValueFromOtherCompartment(sim->GetScriptInterface(), ret, false); } void JSI_Simulation::PostNetworkCommand(ScriptInterface::CmptPrivate* pCmptPrivate, JS::HandleValue cmd) { if (!g_Game) return; CSimulation2* sim = g_Game->GetSimulation2(); ENSURE(sim); CmpPtr cmpCommandQueue(*sim, SYSTEM_ENTITY); if (!cmpCommandQueue) return; ScriptRequest rqSim(sim->GetScriptInterface()); JS::RootedValue cmd2(rqSim.cx, sim->GetScriptInterface().CloneValueFromOtherCompartment(*(pCmptPrivate->pScriptInterface), cmd, false)); cmpCommandQueue->PostNetworkCommand(cmd2); } void JSI_Simulation::DumpSimState(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) { OsPath path = psLogDir()/"sim_dump.txt"; std::ofstream file (OsString(path).c_str(), std::ofstream::out | std::ofstream::trunc); g_Game->GetSimulation2()->DumpDebugState(file); } entity_id_t JSI_Simulation::PickEntityAtPoint(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), int x, int y) { return EntitySelection::PickEntityAtPoint(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x, y, g_Game->GetViewedPlayerID(), false); } std::vector JSI_Simulation::PickPlayerEntitiesInRect(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), int x0, int y0, int x1, int y1, int player) { return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), x0, y0, x1, y1, player, false); } std::vector JSI_Simulation::PickPlayerEntitiesOnScreen(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), int player) { return EntitySelection::PickEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), 0, 0, g_xres, g_yres, player, false); } std::vector JSI_Simulation::PickNonGaiaEntitiesOnScreen(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) { return EntitySelection::PickNonGaiaEntitiesInRect(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), 0, 0, g_xres, g_yres, false); } std::vector JSI_Simulation::GetEntitiesWithStaticObstructionOnScreen(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) { struct StaticObstructionFilter { bool operator()(IComponent* cmp) { ICmpObstruction* cmpObstruction = static_cast(cmp); return cmpObstruction->GetObstructionType() == ICmpObstruction::STATIC; } }; return EntitySelection::GetEntitiesWithComponentInRect(*g_Game->GetSimulation2(), IID_Obstruction, *g_Game->GetView()->GetCamera(), 0, 0, g_xres, g_yres); } JS::Value JSI_Simulation::GetEdgesOfStaticObstructionsOnScreenNearTo(ScriptInterface::CmptPrivate* pCmptPrivate, entity_pos_t x, entity_pos_t z) { if (!g_Game) return JS::UndefinedValue(); CSimulation2* sim = g_Game->GetSimulation2(); ENSURE(sim); ScriptRequest rq(pCmptPrivate->pScriptInterface); JS::RootedValue edgeList(rq.cx); ScriptInterface::CreateArray(rq, &edgeList); int edgeListIndex = 0; float distanceThreshold = 10.0f; CFG_GET_VAL("gui.session.snaptoedgesdistancethreshold", distanceThreshold); CFixedVector2D entityPos(x, z); std::vector entities = GetEntitiesWithStaticObstructionOnScreen(pCmptPrivate); for (entity_id_t entity : entities) { CmpPtr cmpObstruction(sim->GetSimContext(), entity); if (!cmpObstruction) continue; CmpPtr cmpPosition(sim->GetSimContext(), entity); if (!cmpPosition || !cmpPosition->IsInWorld()) continue; CFixedVector2D halfSize = cmpObstruction->GetStaticSize() / 2; if (halfSize.X.IsZero() || halfSize.Y.IsZero() || std::max(halfSize.X, halfSize.Y) <= fixed::FromInt(2)) continue; std::array corners = { CFixedVector2D(-halfSize.X, -halfSize.Y), CFixedVector2D(-halfSize.X, halfSize.Y), halfSize, CFixedVector2D(halfSize.X, -halfSize.Y) }; fixed angle = cmpPosition->GetRotation().Y; for (CFixedVector2D& corner : corners) corner = corner.Rotate(angle) + cmpPosition->GetPosition2D(); for (size_t i = 0; i < corners.size(); ++i) { JS::RootedValue edge(rq.cx); const CFixedVector2D& corner = corners[i]; const CFixedVector2D& nextCorner = corners[(i + 1) % corners.size()]; fixed distanceToEdge = Geometry::DistanceToSegment(entityPos, corner, nextCorner); if (distanceToEdge.ToFloat() > distanceThreshold) continue; CFixedVector2D normal = -(nextCorner - corner).Perpendicular(); normal.Normalize(); ScriptInterface::CreateObject( rq, &edge, "begin", corner, "end", nextCorner, "angle", angle, "normal", normal, "order", "cw"); pCmptPrivate->pScriptInterface->SetPropertyInt(edgeList, edgeListIndex++, edge); } } return edgeList; } std::vector JSI_Simulation::PickSimilarPlayerEntities(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const std::string& templateName, bool includeOffScreen, bool matchRank, bool allowFoundations) { return EntitySelection::PickSimilarEntities(*g_Game->GetSimulation2(), *g_Game->GetView()->GetCamera(), templateName, g_Game->GetViewedPlayerID(), includeOffScreen, matchRank, false, allowFoundations); } JS::Value JSI_Simulation::GetAIs(ScriptInterface::CmptPrivate* pCmptPrivate) { return ICmpAIManager::GetAIs(*(pCmptPrivate->pScriptInterface)); } void JSI_Simulation::SetBoundingBoxDebugOverlay(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), bool enabled) { ICmpSelectable::ms_EnableDebugOverlays = enabled; } void JSI_Simulation::RegisterScriptFunctions(const ScriptInterface& scriptInterface) { scriptInterface.RegisterFunction("GuiInterfaceCall"); scriptInterface.RegisterFunction("PostNetworkCommand"); scriptInterface.RegisterFunction("DumpSimState"); scriptInterface.RegisterFunction("GetAIs"); scriptInterface.RegisterFunction("PickEntityAtPoint"); scriptInterface.RegisterFunction, int, int, int, int, int, &PickPlayerEntitiesInRect>("PickPlayerEntitiesInRect"); scriptInterface.RegisterFunction, int, &PickPlayerEntitiesOnScreen>("PickPlayerEntitiesOnScreen"); scriptInterface.RegisterFunction, &PickNonGaiaEntitiesOnScreen>("PickNonGaiaEntitiesOnScreen"); scriptInterface.RegisterFunction, &GetEntitiesWithStaticObstructionOnScreen>("GetEntitiesWithStaticObstructionOnScreen"); scriptInterface.RegisterFunction("GetEdgesOfStaticObstructionsOnScreenNearTo"); scriptInterface.RegisterFunction, std::string, bool, bool, bool, &PickSimilarPlayerEntities>("PickSimilarPlayerEntities"); scriptInterface.RegisterFunction("SetBoundingBoxDebugOverlay"); } Index: ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.h =================================================================== --- ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.h (revision 24226) +++ ps/trunk/source/simulation2/helpers/HierarchicalPathfinder.h (revision 24227) @@ -1,334 +1,335 @@ /* Copyright (C) 2020 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_HIERPATHFINDER #define INCLUDED_HIERPATHFINDER #include "Pathfinding.h" +#include "ps/CLogger.h" #include "renderer/TerrainOverlay.h" #include "Render.h" #include "graphics/SColor.h" /** * Hierarchical pathfinder. * * Deals with connectivity (can point A reach point B?). * * The navcell-grid representation of the map is split into fixed-size chunks. * Within a chunk, each maximal set of adjacently-connected passable navcells * is defined as a region. * Each region is a vertex in the hierarchical pathfinder's graph. * When two regions in adjacent chunks are connected by passable navcells, * the graph contains an edge between the corresponding two vertexes. * By design, there can never be an edge between two regions in the same chunk. * * Those fixed-size chunks are used to efficiently compute "global regions" by effectively flood-filling. * Those can then be used to immediately determine if two reachables points are connected. * * The main use of this class is to convert an arbitrary PathGoal to a reachable navcell. * This happens in MakeGoalReachable. * */ #ifdef TEST class TestCmpPathfinder; class TestHierarchicalPathfinder; #endif class HierarchicalOverlay; class SceneCollector; class HierarchicalPathfinder { #ifdef TEST friend class TestCmpPathfinder; friend class TestHierarchicalPathfinder; #endif public: typedef u32 GlobalRegionID; struct RegionID { u8 ci, cj; // chunk ID u16 r; // unique-per-chunk local region ID RegionID(u8 ci, u8 cj, u16 r) : ci(ci), cj(cj), r(r) { } bool operator<(const RegionID& b) const { // Sort by chunk ID, then by per-chunk region ID if (ci < b.ci) return true; if (b.ci < ci) return false; if (cj < b.cj) return true; if (b.cj < cj) return false; return r < b.r; } bool operator==(const RegionID& b) const { return ((ci == b.ci) && (cj == b.cj) && (r == b.r)); } // Returns the distance from the center to the point (i, j) inline u32 DistanceTo(u16 i, u16 j) const { return (ci * CHUNK_SIZE + CHUNK_SIZE/2 - i) * (ci * CHUNK_SIZE + CHUNK_SIZE/2 - i) + (cj * CHUNK_SIZE + CHUNK_SIZE/2 - j) * (cj * CHUNK_SIZE + CHUNK_SIZE/2 - j); } }; HierarchicalPathfinder(); ~HierarchicalPathfinder(); void SetDebugOverlay(bool enabled, const CSimContext* simContext); // Non-pathfinding grids will never be recomputed on calling HierarchicalPathfinder::Update void Recompute(Grid* passabilityGrid, const std::map& nonPathfindingPassClassMasks, const std::map& pathfindingPassClassMasks); void Update(Grid* grid, const Grid& dirtinessGrid); RegionID Get(u16 i, u16 j, pass_class_t passClass) const; GlobalRegionID GetGlobalRegion(u16 i, u16 j, pass_class_t passClass) const; GlobalRegionID GetGlobalRegion(RegionID region, pass_class_t passClass) const; /** * Updates @p goal so that it's guaranteed to be reachable from the navcell * @p i0, @p j0 (which is assumed to be on a passable navcell). * * If the goal is not reachable, it is replaced with a point goal nearest to * the goal center. * * In the case of a non-point reachable goal, it is replaced with a point goal * at the reachable navcell of the goal which is nearest to the starting navcell. * * @returns true if the goal was reachable, false otherwise. */ bool MakeGoalReachable(u16 i0, u16 j0, PathGoal& goal, pass_class_t passClass) const; /** * @return true if the goal is reachable from navcell i0, j0. * (similar to MakeGoalReachable but only checking for reachability). */ bool IsGoalReachable(u16 i0, u16 j0, const PathGoal& goal, pass_class_t passClass) const; /** * Updates @p i, @p j (which is assumed to be an impassable navcell) * to the nearest passable navcell. */ void FindNearestPassableNavcell(u16& i, u16& j, pass_class_t passClass) const; /** * Generates the connectivity grid associated with the given pass_class */ Grid GetConnectivityGrid(pass_class_t passClass) const; pass_class_t GetPassabilityClass(const std::string& name) const { auto it = m_PassClassMasks.find(name); if (it != m_PassClassMasks.end()) return it->second; LOGERROR("Invalid passability class name '%s'", name.c_str()); return 0; } void RenderSubmit(SceneCollector& collector); private: static const u8 CHUNK_SIZE = 96; // number of navcells per side // TODO: figure out best number. Probably 64 < n < 128 struct Chunk { u8 m_ChunkI, m_ChunkJ; // chunk ID std::vector m_RegionsID; // IDs of local regions, 0 (impassable) excluded u16 m_Regions[CHUNK_SIZE][CHUNK_SIZE]; // local region ID per navcell cassert(CHUNK_SIZE*CHUNK_SIZE/2 < 65536); // otherwise we could overflow m_RegionsID with a checkerboard pattern void InitRegions(int ci, int cj, Grid* grid, pass_class_t passClass); RegionID Get(int i, int j) const; void RegionCenter(u16 r, int& i, int& j) const; void RegionNavcellNearest(u16 r, int iGoal, int jGoal, int& iBest, int& jBest, u32& dist2Best) const; bool RegionNearestNavcellInGoal(u16 r, u16 i0, u16 j0, const PathGoal& goal, u16& iOut, u16& jOut, u32& dist2Best) const; #ifdef TEST bool operator==(const Chunk& b) const { return (m_ChunkI == b.m_ChunkI && m_ChunkJ == b.m_ChunkJ && m_RegionsID.size() == b.m_RegionsID.size() && memcmp(&m_Regions, &b.m_Regions, sizeof(u16) * CHUNK_SIZE * CHUNK_SIZE) == 0); } #endif }; const Chunk& GetChunk(u8 ci, u8 cj, pass_class_t passClass) const { return m_Chunks.at(passClass).at(cj * m_ChunksW + ci); } typedef std::map > EdgesMap; void ComputeNeighbors(EdgesMap& edges, Chunk& a, Chunk& b, bool transpose, bool opposite) const; void RecomputeAllEdges(pass_class_t passClass, EdgesMap& edges); void UpdateEdges(u8 ci, u8 cj, pass_class_t passClass, EdgesMap& edges); void UpdateGlobalRegions(const std::map >& needNewGlobalRegionMap); /** * Returns all reachable regions, optionally ordered in a specific manner. */ template void FindReachableRegions(RegionID from, std::set& reachable, pass_class_t passClass) const { // Flood-fill the region graph, starting at 'from', // collecting all the regions that are reachable via edges reachable.insert(from); const EdgesMap& edgeMap = m_Edges.at(passClass); if (edgeMap.find(from) == edgeMap.end()) return; std::vector open; open.reserve(64); open.push_back(from); while (!open.empty()) { RegionID curr = open.back(); open.pop_back(); for (const RegionID& region : edgeMap.at(curr)) // Add to the reachable set; if this is the first time we added // it then also add it to the open list if (reachable.insert(region).second) open.push_back(region); } } struct SortByCenterToPoint { SortByCenterToPoint(u16 i, u16 j): gi(i), gj(j) {}; bool operator()(const HierarchicalPathfinder::RegionID& a, const HierarchicalPathfinder::RegionID& b) const { if (a.DistanceTo(gi, gj) < b.DistanceTo(gi, gj)) return true; if (a.DistanceTo(gi, gj) > b.DistanceTo(gi, gj)) return false; return a.r < b.r; } u16 gi, gj; }; void FindNearestNavcellInRegions(const std::set& regions, u16& iGoal, u16& jGoal, pass_class_t passClass) const; struct InterestingRegion { RegionID region; u16 bestI; u16 bestJ; }; struct SortByBestToPoint { SortByBestToPoint(u16 i, u16 j): gi(i), gj(j) {}; bool operator()(const InterestingRegion& a, const InterestingRegion& b) const { if ((a.bestI - gi) * (a.bestI - gi) + (a.bestJ - gj) * (a.bestJ - gj) < (b.bestI - gi) * (b.bestI - gi) + (b.bestJ - gj) * (b.bestJ - gj)) return true; if ((a.bestI - gi) * (a.bestI - gi) + (a.bestJ - gj) * (a.bestJ - gj) > (b.bestI - gi) * (b.bestI - gi) + (b.bestJ - gj) * (b.bestJ - gj)) return false; return a.region.r < b.region.r; } u16 gi, gj; }; // Returns the region along with the best cell for optimisation. void FindGoalRegionsAndBestNavcells(u16 i0, u16 j0, u16 gi, u16 gj, const PathGoal& goal, std::set& regions, pass_class_t passClass) const; void FillRegionOnGrid(const RegionID& region, pass_class_t passClass, u16 value, Grid& grid) const; u16 m_W, m_H; u8 m_ChunksW, m_ChunksH; std::map > m_Chunks; std::map m_Edges; std::map > m_GlobalRegions; GlobalRegionID m_NextGlobalRegionID; // Passability classes for which grids will be updated when calling Update std::map m_PassClassMasks; void AddDebugEdges(pass_class_t passClass); HierarchicalOverlay* m_DebugOverlay; const CSimContext* m_SimContext; // Used for drawing the debug lines public: std::vector m_DebugOverlayLines; }; class HierarchicalOverlay : public TerrainTextureOverlay { public: HierarchicalPathfinder& m_PathfinderHier; HierarchicalOverlay(HierarchicalPathfinder& pathfinderHier) : TerrainTextureOverlay(Pathfinding::NAVCELLS_PER_TILE), m_PathfinderHier(pathfinderHier) { } virtual void BuildTextureRGBA(u8* data, size_t w, size_t h) { pass_class_t passClass = m_PathfinderHier.GetPassabilityClass("default"); for (size_t j = 0; j < h; ++j) { for (size_t i = 0; i < w; ++i) { SColor4ub color; HierarchicalPathfinder::RegionID rid = m_PathfinderHier.Get(i, j, passClass); if (rid.r == 0) color = SColor4ub(0, 0, 0, 0); else if (rid.r == 0xFFFF) color = SColor4ub(255, 0, 255, 255); else color = GetColor(rid.r + rid.ci*5 + rid.cj*7, 127); *data++ = color.R; *data++ = color.G; *data++ = color.B; *data++ = color.A; } } } }; #endif // INCLUDED_HIERPATHFINDER Index: ps/trunk/source/simulation2/helpers/Rasterize.cpp =================================================================== --- ps/trunk/source/simulation2/helpers/Rasterize.cpp (revision 24226) +++ ps/trunk/source/simulation2/helpers/Rasterize.cpp (revision 24227) @@ -1,125 +1,126 @@ /* Copyright (C) 2015 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 "Rasterize.h" #include "simulation2/helpers/Geometry.h" +#include "simulation2/helpers/Pathfinding.h" void SimRasterize::RasterizeRectWithClearance(Spans& spans, const ICmpObstructionManager::ObstructionSquare& shape, entity_pos_t clearance, entity_pos_t cellSize) { // A long-standing issue with the pathfinding has been that the long-range one // uses a AA navcell grid, while the short-range uses an accurate vector representation. // This means we could get paths accepted by one but not both pathfinders. // Since the new pathfinder, the short-range pathfinder's representation was usually // encompassing the rasterisation of the long-range one for a building. // This means that we could never get quite as close as the long-range pathfinder wanted. // This could mean units tried going through impassable paths. // To fix this, we need to make sure that the short-range pathfinder is always mostly // included in the rasterisation. The easiest way is to rasterise more, thus this variable // Since this is a very complicated subject, check out logs on 31/10/2015 for more detailled info. // or ask wraitii about it. // If the short-range pathfinder is sufficiently changed, this could become unnecessary and thus removed. // A side effect is that the basic clearance has been set to 0.8, so removing this constant should be done // in parallel with setting clearance back to 1 for the default passability class (though this isn't strictly necessary). // Also: the code detecting foundation obstruction in CcmpObstructionManager had to be changed similarly. entity_pos_t rasterClearance = clearance + Pathfinding::CLEARANCE_EXTENSION_RADIUS; // Get the bounds of cells that might possibly be within the shape // (We'll then test each of those cells more precisely) CFixedVector2D shapeHalfSize(CFixedVector2D(shape.hw, shape.hh)); CFixedVector2D halfSize(shape.hw + rasterClearance, shape.hh + rasterClearance); CFixedVector2D halfBound = Geometry::GetHalfBoundingBox(shape.u, shape.v, halfSize); i16 i0 = ((shape.x - halfBound.X) / cellSize).ToInt_RoundToNegInfinity(); i16 j0 = ((shape.z - halfBound.Y) / cellSize).ToInt_RoundToNegInfinity(); i16 i1 = ((shape.x + halfBound.X) / cellSize).ToInt_RoundToInfinity(); i16 j1 = ((shape.z + halfBound.Y) / cellSize).ToInt_RoundToInfinity(); if (j1 <= j0) return; // empty bounds - this shouldn't happen rasterClearance = rasterClearance.Multiply(rasterClearance); spans.reserve(j1 - j0); for (i16 j = j0; j < j1; ++j) { // Find the min/max range of cells that are strictly inside the square+rasterClearance. // (Since the square+rasterClearance is a convex shape, we can just test each // corner of each cell is inside the shape.) // When looping on i, if the previous cell was inside, no need to check again the left corners. // and we can stop the loop when exiting the shape. // Futhermore if one of the right corners of a cell is outside, no need to check the following cell i16 spanI0 = std::numeric_limits::max(); i16 spanI1 = std::numeric_limits::min(); bool previousInside = false; bool skipNextCell = false; for (i16 i = i0; i < i1; ++i) { if (skipNextCell) { skipNextCell = false; continue; } if (Geometry::DistanceToSquareSquared(CFixedVector2D(cellSize*(i+1)-shape.x, cellSize*j-shape.z), shape.u, shape.v, shapeHalfSize, true) > rasterClearance) { if (previousInside) break; skipNextCell = true; continue; } if (Geometry::DistanceToSquareSquared(CFixedVector2D(cellSize*(i+1)-shape.x, cellSize*(j+1)-shape.z), shape.u, shape.v, shapeHalfSize, true) > rasterClearance) { if (previousInside) break; skipNextCell = true; continue; } if (!previousInside) { if (Geometry::DistanceToSquareSquared(CFixedVector2D(cellSize*i-shape.x, cellSize*j-shape.z), shape.u, shape.v, shapeHalfSize, true) > rasterClearance) continue; if (Geometry::DistanceToSquareSquared(CFixedVector2D(cellSize*i-shape.x, cellSize*(j+1)-shape.z), shape.u, shape.v, shapeHalfSize, true) > rasterClearance) continue; previousInside = true; spanI0 = i; } spanI1 = i+1; } // Add non-empty spans onto the list if (spanI0 < spanI1) { Span span = { spanI0, spanI1, j }; spans.push_back(span); } } } Index: ps/trunk/source/simulation2/helpers/Selection.h =================================================================== --- ps/trunk/source/simulation2/helpers/Selection.h (revision 24226) +++ ps/trunk/source/simulation2/helpers/Selection.h (revision 24227) @@ -1,134 +1,134 @@ /* 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_SELECTION #define INCLUDED_SELECTION #include "ps/Profiler2.h" -#include "simulation2/components/ICmpObstruction.h" #include "simulation2/helpers/Player.h" #include "simulation2/Simulation2.h" #include "simulation2/system/Entity.h" +#include "simulation2/system/IComponent.h" #include class CSimulation2; class CCamera; bool CheckEntityInRect(CEntityHandle handle, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, bool allowEditorSelectables); namespace EntitySelection { /** * Finds all selectable entities under the given screen coordinates. * * @param camera use this view to convert screen to world coordinates. * @param screenX,screenY 2D screen coordinates. * @param player player whose LOS will be used when selecting entities. In Atlas * this value is ignored as the whole map is revealed. * @param allowEditorSelectables if true, all entities with the ICmpSelectable interface * will be selected (including decorative actors), else only those selectable ingame. * @param range Approximate range to check for entity in. * * @return ordered list of selected entities with the closest first. */ entity_id_t PickEntityAtPoint(CSimulation2& simulation, const CCamera& camera, int screenX, int screenY, player_id_t player, bool allowEditorSelectables); /** * Finds all selectable entities within the given screen coordinate rectangle, * belonging to the given player. Used for bandboxing. * * @param camera use this view to convert screen to world coordinates. * @param sx0,sy0,sx1,sy1 diagonally opposite corners of the rectangle in 2D screen coordinates. * @param owner player whose entities we are selecting. Ownership is ignored if * INVALID_PLAYER is used. * @param allowEditorSelectables if true, all entities with the ICmpSelectable interface * will be selected (including decorative actors), else only those selectable ingame. * * @return unordered list of selected entities. */ std::vector PickEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, player_id_t owner, bool allowEditorSelectables); /** * Finds all selectable entities within the given screen coordinate rectangle, * belonging to any given player (excluding Gaia). Used for status bars. */ std::vector PickNonGaiaEntitiesInRect(CSimulation2& simulation, const CCamera& camera, int sx0, int sy0, int sx1, int sy1, bool allowEditorSelectables); /** * Finds all entities with a given component belonging to any given player. */ struct DefaultComponentFilter { bool operator()(IComponent* UNUSED(cmp)) { return true; } }; template std::vector GetEntitiesWithComponentInRect(CSimulation2& simulation, int cid, const CCamera& camera, int sx0, int sy0, int sx1, int sy1) { PROFILE2("GetEntitiesWithObstructionInRect"); // Make sure sx0 <= sx1, and sy0 <= sy1 if (sx0 > sx1) std::swap(sx0, sx1); if (sy0 > sy1) std::swap(sy0, sy1); std::vector hitEnts; Filter filter; const CSimulation2::InterfaceListUnordered& entities = simulation.GetEntitiesWithInterfaceUnordered(cid); for (const std::pair& item : entities) { if (!filter(item.second)) continue; if (CheckEntityInRect(item.second->GetEntityHandle(), camera, sx0, sy0, sx1, sy1, false)) hitEnts.push_back(item.first); } return hitEnts; } /** * Finds all entities with the given entity template name, belonging to the given player. * * @param camera use this view to convert screen to world coordinates. * @param templateName the name of the template to match, or the selection group name * for similar matching. * @param owner player whose entities we are selecting. Ownership is ignored if * INVALID_PLAYER is used. * @param includeOffScreen if true, then all entities visible in the world will be selected, * else only entities visible to the camera will be selected. * @param matchRank if true, only entities that exactly match templateName will be selected, * else entities with matching SelectionGroupName will be selected. * @param allowEditorSelectables if true, all entities with the ICmpSelectable interface * will be selected (including decorative actors), else only those selectable in-game. * @param allowFoundations if true, foundations are also included in the results. Only takes * effect when matchRank = true. * * @return unordered list of selected entities. * @see ICmpIdentity */ std::vector PickSimilarEntities(CSimulation2& simulation, const CCamera& camera, const std::string& templateName, player_id_t owner, bool includeOffScreen, bool matchRank, bool allowEditorSelectables, bool allowFoundations); } // namespace #endif // INCLUDED_SELECTION Index: ps/trunk/source/simulation2/system/ComponentTest.h =================================================================== --- ps/trunk/source/simulation2/system/ComponentTest.h (revision 24226) +++ ps/trunk/source/simulation2/system/ComponentTest.h (revision 24227) @@ -1,251 +1,250 @@ /* 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_COMPONENTTEST #define INCLUDED_COMPONENTTEST #include "lib/self_test.h" -#include "maths/Matrix3D.h" #include "maths/Vector3D.h" #include "ps/XML/Xeromyces.h" #include "simulation2/MessageTypes.h" #include "simulation2/system/Component.h" #include "simulation2/components/ICmpTerrain.h" #include "simulation2/serialization/DebugSerializer.h" #include "simulation2/serialization/HashSerializer.h" #include "simulation2/serialization/StdSerializer.h" #include "simulation2/serialization/StdDeserializer.h" #include /** * @file * Various common features for component test cases. */ /** * Class to test a single component. * - Create an instance of this class * - Use AddMock to add mock components that the tested component relies on * - Use Add to add the test component itself, and it returns a component pointer * - Call methods on the component pointer * - Use Roundtrip to test the consistency of serialization */ class ComponentTestHelper { CSimContext m_Context; CComponentManager m_ComponentManager; CParamNode m_Param; IComponent* m_Cmp; EComponentTypeId m_Cid; bool m_isSystemEntityInit = false; public: ComponentTestHelper(shared_ptr scriptContext) : m_Context(), m_ComponentManager(m_Context, scriptContext), m_Cmp(NULL) { m_ComponentManager.LoadComponentTypes(); } const ScriptInterface& GetScriptInterface() { return m_ComponentManager.GetScriptInterface(); } CSimContext& GetSimContext() { return m_Context; } /** * Call this once to initialise the test helper with a component. */ template T* Add(EComponentTypeId cid, const std::string& xml, entity_id_t ent = 10) { TS_ASSERT(m_Cmp == NULL); CEntityHandle handle; if (ent == SYSTEM_ENTITY) { if (!m_isSystemEntityInit) { m_ComponentManager.InitSystemEntity(); m_isSystemEntityInit = true; } handle = m_ComponentManager.GetSystemEntity(); m_Context.SetSystemEntity(handle); } else handle = m_ComponentManager.LookupEntityHandle(ent, true); m_Cid = cid; TS_ASSERT_EQUALS(CParamNode::LoadXMLString(m_Param, ("" + xml + "").c_str()), PSRETURN_OK); TS_ASSERT(m_ComponentManager.AddComponent(handle, m_Cid, m_Param.GetChild("test"))); m_Cmp = m_ComponentManager.QueryInterface(ent, T::GetInterfaceId()); TS_ASSERT(m_Cmp != NULL); return static_cast (m_Cmp); } void AddMock(entity_id_t ent, EInterfaceId iid, IComponent& component) { CEntityHandle handle; if (ent == SYSTEM_ENTITY) { if (!m_isSystemEntityInit) { m_ComponentManager.InitSystemEntity(); m_isSystemEntityInit = true; } handle = m_ComponentManager.GetSystemEntity(); m_Context.SetSystemEntity(handle); } else handle = m_ComponentManager.LookupEntityHandle(ent, true); m_ComponentManager.AddMockComponent(handle, iid, component); } void HandleMessage(IComponent* cmp, const CMessage& msg, bool global) { cmp->HandleMessage(msg, global); } /** * Checks that the object roundtrips through its serialize/deserialize functions correctly. * Computes the debug output, hash, and binary serialization; then deserializes into a new * system and checks the serialization outputs are unchanged. */ void Roundtrip(bool verbose = false) { std::stringstream dbgstr1; CDebugSerializer dbg1(GetScriptInterface(), dbgstr1); m_Cmp->Serialize(dbg1); if (verbose) std::cout << "--------\n" << dbgstr1.str() << "--------\n"; CHashSerializer hash1(GetScriptInterface()); m_Cmp->Serialize(hash1); std::stringstream stdstr1; CStdSerializer std1(GetScriptInterface(), stdstr1); m_Cmp->Serialize(std1); ComponentTestHelper test2(GetScriptInterface().GetContext()); // (We should never need to add any mock objects etc to test2, since deserialization // mustn't depend on other components already existing) CEntityHandle ent = test2.m_ComponentManager.LookupEntityHandle(10, true); CStdDeserializer stdde2(test2.GetScriptInterface(), stdstr1); IComponent* cmp2 = test2.m_ComponentManager.ConstructComponent(ent, m_Cid); cmp2->Deserialize(m_Param.GetChild("test"), stdde2); TS_ASSERT(stdstr1.peek() == EOF); // Deserialize must read whole stream std::stringstream dbgstr2; CDebugSerializer dbg2(test2.GetScriptInterface(), dbgstr2); cmp2->Serialize(dbg2); if (verbose) std::cout << "--------\n" << dbgstr2.str() << "--------\n"; CHashSerializer hash2(test2.GetScriptInterface()); cmp2->Serialize(hash2); std::stringstream stdstr2; CStdSerializer std2(test2.GetScriptInterface(), stdstr2); cmp2->Serialize(std2); TS_ASSERT_EQUALS(dbgstr1.str(), dbgstr2.str()); TS_ASSERT_EQUALS(hash1.GetHashLength(), hash2.GetHashLength()); TS_ASSERT_SAME_DATA(hash1.ComputeHash(), hash2.ComputeHash(), hash1.GetHashLength()); TS_ASSERT_EQUALS(stdstr1.str(), stdstr2.str()); // TODO: need to extend this so callers can run methods on the cloned component // to check that all its data is still correct } }; /** * Simple terrain implementation with constant height of 50. */ class MockTerrain : public ICmpTerrain { public: DEFAULT_MOCK_COMPONENT() virtual bool IsLoaded() const { return true; } virtual CFixedVector3D CalcNormal(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const { return CFixedVector3D(fixed::FromInt(0), fixed::FromInt(1), fixed::FromInt(0)); } virtual CVector3D CalcExactNormal(float UNUSED(x), float UNUSED(z)) const { return CVector3D(0.f, 1.f, 0.f); } virtual entity_pos_t GetGroundLevel(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const { return entity_pos_t::FromInt(50); } virtual float GetExactGroundLevel(float UNUSED(x), float UNUSED(z)) const { return 50.f; } virtual u16 GetTilesPerSide() const { return 16; } virtual u32 GetMapSize() const { return GetTilesPerSide() * TERRAIN_TILE_SIZE; } virtual u16 GetVerticesPerSide() const { return 17; } virtual CTerrain* GetCTerrain() { return NULL; } virtual void MakeDirty(i32 UNUSED(i0), i32 UNUSED(j0), i32 UNUSED(i1), i32 UNUSED(j1)) { } virtual void ReloadTerrain(bool UNUSED(ReloadWater)) { } }; #endif // INCLUDED_COMPONENTTEST Index: ps/trunk/source/graphics/Font.h =================================================================== --- ps/trunk/source/graphics/Font.h (revision 24226) +++ ps/trunk/source/graphics/Font.h (revision 24227) @@ -1,104 +1,103 @@ /* Copyright (C) 2013 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_FONT #define INCLUDED_FONT #include "graphics/Texture.h" #include "lib/res/handle.h" #include -class CStrW; struct UnifontGlyphData; /** * Storage for a bitmap font. Loaded by CFontManager. */ class CFont { friend class CFontManager; CFont() {} public: struct GlyphData { float u0, v0, u1, v1; i16 x0, y0, x1, y1; i16 xadvance; u8 defined; }; /** * Relatively efficient lookup of GlyphData from 16-bit Unicode codepoint. * This is stored as a sparse 2D array, exploiting the knowledge that a font * typically only supports a small number of 256-codepoint blocks, so most * elements of m_Data will be NULL. */ class GlyphMap { NONCOPYABLE(GlyphMap); public: GlyphMap(); ~GlyphMap(); void set(u16 i, const GlyphData& val); const GlyphData* get(u16 i) const { if (!m_Data[i >> 8]) return NULL; if (!m_Data[i >> 8][i & 0xff].defined) return NULL; return &m_Data[i >> 8][i & 0xff]; } private: GlyphData* m_Data[256]; }; bool HasRGB() const { return m_HasRGB; } int GetLineSpacing() const { return m_LineSpacing; } int GetHeight() const { return m_Height; } int GetCharacterWidth(wchar_t c) const; void CalculateStringSize(const wchar_t* string, int& w, int& h) const; void GetGlyphBounds(float& x0, float& y0, float& x1, float& y1) const { x0 = m_BoundsX0; y0 = m_BoundsY0; x1 = m_BoundsX1; y1 = m_BoundsY1; } const GlyphMap& GetGlyphs() const { return m_Glyphs; } CTexturePtr GetTexture() const { return m_Texture; } private: CTexturePtr m_Texture; bool m_HasRGB; // true if RGBA, false if ALPHA GlyphMap m_Glyphs; int m_LineSpacing; int m_Height; // height of a capital letter, roughly // Bounding box of all glyphs float m_BoundsX0; float m_BoundsY0; float m_BoundsX1; float m_BoundsY1; }; #endif // INCLUDED_FONT Index: ps/trunk/source/graphics/HeightMipmap.h =================================================================== --- ps/trunk/source/graphics/HeightMipmap.h (revision 24226) +++ ps/trunk/source/graphics/HeightMipmap.h (revision 24227) @@ -1,79 +1,80 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ /* * Describes ground using heightmap mipmaps * Used for camera movement */ #ifndef INCLUDED_HEIGHTMIPMAP #define INCLUDED_HEIGHTMIPMAP -#include "lib/file/vfs/vfs_path.h" +class Path; +using VfsPath = Path; struct SMipmap { SMipmap() : m_MapSize(0), m_Heightmap(0) { } SMipmap(size_t MapSize, u16* Heightmap) : m_MapSize(MapSize), m_Heightmap(Heightmap) { } size_t m_MapSize; u16* m_Heightmap; }; class CHeightMipmap { NONCOPYABLE(CHeightMipmap); public: CHeightMipmap(); ~CHeightMipmap(); void Initialize(size_t mapSize, const u16* ptr); void ReleaseData(); // update the heightmap mipmaps void Update(const u16* ptr); // update a section of the heightmap mipmaps // (coordinates are heightmap cells, inclusive of lower bounds, // exclusive of upper bounds) void Update(const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); float GetTrilinearGroundLevel(float x, float z, float radius) const; void DumpToDisk(const VfsPath& path) const; private: // get bilinear filtered height from mipmap float BilinearFilter(const SMipmap &mipmap, float x, float z) const; // update rectangle of the output mipmap by bilinear interpolating an input mipmap of exactly twice its size void HalfResizeUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); // update rectangle of the output mipmap by bilinear interpolating the input mipmap void BilinearUpdate(SMipmap &out_mipmap, size_t mapSize, const u16* ptr, size_t left, size_t bottom, size_t right, size_t top); // size of this map in each direction size_t m_MapSize; // mipmap list std::vector m_Mipmap; }; #endif Index: ps/trunk/source/graphics/LOSTexture.h =================================================================== --- ps/trunk/source/graphics/LOSTexture.h (revision 24226) +++ ps/trunk/source/graphics/LOSTexture.h (revision 24227) @@ -1,107 +1,107 @@ /* 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" +#include "graphics/ShaderTechniquePtr.h" - +class CLosQuerier; 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(const 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/graphics/MapReader.h =================================================================== --- ps/trunk/source/graphics/MapReader.h (revision 24226) +++ ps/trunk/source/graphics/MapReader.h (revision 24227) @@ -1,181 +1,182 @@ /* Copyright (C) 2020 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_MAPREADER #define INCLUDED_MAPREADER #include "MapIO.h" + +#include "graphics/LightEnv.h" #include "lib/res/handle.h" #include "ps/CStr.h" -#include "LightEnv.h" #include "ps/FileIo.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/system/Entity.h" class CObjectEntry; class CTerrain; class WaterManager; class SkyManager; class CLightEnv; class CCinemaManager; class CPostprocManager; class CTriggerManager; class CSimulation2; class CSimContext; class CTerrainTextureEntry; class ScriptInterface; class CGameView; class CXMLReader; class CMapGenerator; class CMapReader : public CMapIO { friend class CXMLReader; public: // constructor CMapReader(); ~CMapReader(); // LoadMap: try to load the map from given file; reinitialise the scene to new data if successful void LoadMap(const VfsPath& pathname, const ScriptContext& cx, JS::HandleValue settings, CTerrain*, WaterManager*, SkyManager*, CLightEnv*, CGameView*, CCinemaManager*, CTriggerManager*, CPostprocManager* pPostproc, CSimulation2*, const CSimContext*, int playerID, bool skipEntities); void LoadRandomMap(const CStrW& scriptFile, const ScriptContext& cx, JS::HandleValue settings, CTerrain*, WaterManager*, SkyManager*, CLightEnv*, CGameView*, CCinemaManager*, CTriggerManager*, CPostprocManager* pPostproc_, CSimulation2*, int playerID); private: // Load script settings for use by scripts int LoadScriptSettings(); // load player settings only int LoadPlayerSettings(); // load map settings only int LoadMapSettings(); // UnpackTerrain: unpack the terrain from the input stream int UnpackTerrain(); // UnpackCinema: unpack the cinematic tracks from the input stream int UnpackCinema(); // UnpackMap: unpack the given data from the raw data stream into local variables int UnpackMap(); // ApplyData: take all the input data, and rebuild the scene from it int ApplyData(); int ApplyTerrainData(); // read some misc data from the XML file int ReadXML(); // read entity data from the XML file int ReadXMLEntities(); // Copy random map settings over to sim int LoadRMSettings(); // Generate random map int GenerateMap(); // Parse script data into terrain int ParseTerrain(); // Parse script data into entities int ParseEntities(); // Parse script data into environment int ParseEnvironment(); // Parse script data into camera int ParseCamera(); // size of map ssize_t m_PatchesPerSide; // heightmap for map std::vector m_Heightmap; // list of terrain textures used by map std::vector m_TerrainTextures; // tile descriptions for each tile std::vector m_Tiles; // lightenv stored in file CLightEnv m_LightEnv; // startup script CStrW m_Script; // random map data CStrW m_ScriptFile; JS::PersistentRootedValue m_ScriptSettings; JS::PersistentRootedValue m_MapData; CMapGenerator* m_MapGen; CFileUnpacker unpacker; CTerrain* pTerrain; WaterManager* pWaterMan; SkyManager* pSkyMan; CPostprocManager* pPostproc; CLightEnv* pLightEnv; CGameView* pGameView; CCinemaManager* pCinema; CTriggerManager* pTrigMan; CSimulation2* pSimulation2; const CSimContext* pSimContext; int m_PlayerID; bool m_SkipEntities; VfsPath filename_xml; bool only_xml; u32 file_format_version; entity_id_t m_StartingCameraTarget; CVector3D m_StartingCamera; // UnpackTerrain generator state size_t cur_terrain_tex; size_t num_terrain_tex; CXMLReader* xml_reader; }; /** * A restricted map reader that returns various summary information * for use by scripts (particularly the GUI). */ class CMapSummaryReader { public: /** * Try to load a map file. * @param pathname Path to .pmp or .xml file */ PSRETURN LoadMap(const VfsPath& pathname); /** * Returns a value of the form: * @code * { * "settings": { ... contents of the map's ... } * } * @endcode */ void GetMapSettings(const ScriptInterface& scriptInterface, JS::MutableHandleValue); private: CStr m_ScriptSettings; }; #endif Index: ps/trunk/source/graphics/ModelDef.h =================================================================== --- ps/trunk/source/graphics/ModelDef.h (revision 24226) +++ ps/trunk/source/graphics/ModelDef.h (revision 24227) @@ -1,288 +1,289 @@ /* Copyright (C) 2011 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 . */ /* * Defines a raw 3d model. */ #ifndef INCLUDED_MODELDEF #define INCLUDED_MODELDEF #include "ps/CStr.h" +#include "maths/Matrix3D.h" #include "maths/Vector2D.h" #include "maths/Vector3D.h" #include "maths/Quaternion.h" #include "lib/file/vfs/vfs_path.h" #include "renderer/VertexArray.h" #include #include class CBoneState; /** * Describes the position of a prop point within its parent model. A prop point is the location within a parent model * where the prop's origin will be attached. * * A prop point is specified by its transformation matrix (or separately by its position and rotation), which * can be relative to either the parent model's origin, or one of the parent's bones. If the parent model is boned, * then the @ref m_BoneIndex field may specify a bone to which the transformation matrix is relative (see * @ref CModel::m_BoneMatrices). Otherwise, the transformation matrix is assumed to be relative to the parent model's * origin. * * @see CModel::m_BoneMatrices */ struct SPropPoint { /// Name of the prop point CStr m_Name; /** * Position of the point within the parent model, relative to either the parent model's origin or one of the parent * model's bones if applicable. Also specified as part of @ref m_Transform. * @see m_Transform */ CVector3D m_Position; /** * Rotation of the prop model that will be attached at this point. Also specified as part of @ref m_Transform. * @see m_Transform */ CQuaternion m_Rotation; /** * Object to parent space transformation. Combines both @ref m_Position and @ref m_Rotation in a single * transformation matrix. This transformation is relative to either the parent model's origin, or one of its * bones, depending on whether it is skeletal. If relative to a bone, then the bone in the parent model to * which this transformation is relative may be found by m_BoneIndex. * @see m_Position, m_Rotation */ CMatrix3D m_Transform; /** * Index of parent bone to which this prop point is relative, if any. The value 0xFF specifies that either the parent * model is unboned, or that this prop point is relative to the parent model's origin rather than one if its bones. */ u8 m_BoneIndex; }; /////////////////////////////////////////////////////////////////////////////// // SVertexBlend: structure containing the necessary data for blending vertices // with multiple bones struct SVertexBlend { enum { SIZE = 4 }; // index of the influencing bone, or 0xff if none u8 m_Bone[SIZE]; // weight of the influence; all weights sum to 1 float m_Weight[SIZE]; bool operator==(const SVertexBlend& o) const { return !memcmp(m_Bone, o.m_Bone, sizeof(m_Bone)) && !memcmp(m_Weight, o.m_Weight, sizeof(m_Weight)); } }; /////////////////////////////////////////////////////////////////////////////// // SModelVertex: structure containing per-vertex data struct SModelVertex { // vertex position CVector3D m_Coords; // vertex normal CVector3D m_Norm; // vertex UVs std::vector m_UVs; // vertex blend data SVertexBlend m_Blend; }; /////////////////////////////////////////////////////////////////////////////// // SModelFace: structure containing per-face data struct SModelFace { // indices of the 3 vertices on this face u16 m_Verts[3]; }; //////////////////////////////////////////////////////////////////////////////////////// // CModelDefRPrivate class CModelDefRPrivate { public: CModelDefRPrivate() { } virtual ~CModelDefRPrivate() { } }; //////////////////////////////////////////////////////////////////////////////////////// // CModelDef: a raw 3D model; describes the vertices, faces, skinning and skeletal // information of a model class CModelDef { NONCOPYABLE(CModelDef); public: // current file version given to saved animations enum { FILE_VERSION = 3 }; // supported file read version - files with a version less than this will be rejected enum { FILE_READ_VERSION = 1 }; public: CModelDef(); ~CModelDef(); // model I/O functions static void Save(const VfsPath& filename,const CModelDef* mdef); /** * Loads a PMD file. * @param filename VFS path of .pmd file to load * @param name arbitrary name to give the model for debugging purposes (usually pathname) * @return the model - always non-NULL * @throw PSERROR_File if it can't load the model */ static CModelDef* Load(const VfsPath& filename, const VfsPath& name); public: // accessor: get vertex data size_t GetNumVertices() const { return m_NumVertices; } SModelVertex* GetVertices() const { return m_pVertices; } // accessor: get number of UV sets size_t GetNumUVsPerVertex() const { return m_NumUVsPerVertex; } // accessor: get face data size_t GetNumFaces() const { return m_NumFaces; } SModelFace* GetFaces() const { return m_pFaces; } // accessor: get bone data size_t GetNumBones() const { return m_NumBones; } CBoneState* GetBones() const { return m_Bones; } CMatrix3D* GetInverseBindBoneMatrices() { return m_InverseBindBoneMatrices; } // accessor: get blend data size_t GetNumBlends() const { return m_NumBlends; } SVertexBlend* GetBlends() const { return m_pBlends; } size_t* GetBlendIndices() const { return m_pBlendIndices; } // find and return pointer to prop point matching given name; return // null if no match (case insensitive search) const SPropPoint* FindPropPoint(const char* name) const; /** * Transform the given vertex's position from the bind pose into the new pose. * * @return new world-space vertex coordinates */ static CVector3D SkinPoint(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]); /** * Transform the given vertex's normal from the bind pose into the new pose. * * @return new world-space vertex normal */ static CVector3D SkinNormal(const SModelVertex& vtx, const CMatrix3D newPoseMatrices[]); /** * Transform vertices' positions and normals. * (This is equivalent to looping over SkinPoint and SkinNormal, * but slightly more efficient.) */ static void SkinPointsAndNormals( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]); #if HAVE_SSE /** * SSE-optimised version of SkinPointsAndNormals. */ static void SkinPointsAndNormals_SSE( size_t numVertices, const VertexArrayIterator& Position, const VertexArrayIterator& Normal, const SModelVertex* vertices, const size_t* blendIndices, const CMatrix3D newPoseMatrices[]); #endif /** * Blend bone matrices together to fill bone palette. */ void BlendBoneMatrices(CMatrix3D boneMatrices[]); /** * Register renderer private data. Use the key to * distinguish between private data used by different render paths. * The private data will be managed by this CModelDef object: * It will be deleted when CModelDef is destructed or when private * data is registered using the same key. * * @param key The opaque key that is used to identify the caller. * The given private data can be retrieved by passing key to GetRenderData. * @param data The private data. * * postconditions : data is bound to the lifetime of this CModelDef * object. */ void SetRenderData(const void* key, CModelDefRPrivate* data); // accessor: render data CModelDefRPrivate* GetRenderData(const void* key) const; // accessor: get model name (for debugging) const VfsPath& GetName() const { return m_Name; } public: // vertex data size_t m_NumVertices; SModelVertex* m_pVertices; size_t m_NumUVsPerVertex; // number of UV pairs per vertex // face data size_t m_NumFaces; SModelFace* m_pFaces; // bone data - default model pose size_t m_NumBones; CBoneState* m_Bones; CMatrix3D* m_InverseBindBoneMatrices; // blend data size_t m_NumBlends; SVertexBlend *m_pBlends; size_t* m_pBlendIndices; // prop point data std::vector m_PropPoints; private: VfsPath m_Name; // filename // renderdata shared by models of the same modeldef, // by render path typedef std::map RenderDataMap; RenderDataMap m_RenderData; }; #endif Index: ps/trunk/source/graphics/ShaderTechnique.h =================================================================== --- ps/trunk/source/graphics/ShaderTechnique.h (revision 24226) +++ ps/trunk/source/graphics/ShaderTechnique.h (revision 24227) @@ -1,115 +1,114 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_SHADERTECHNIQUE #define INCLUDED_SHADERTECHNIQUE #include "graphics/ShaderProgramPtr.h" +#include "graphics/ShaderTechniquePtr.h" #include "lib/ogl.h" /** * Implements a render pass consisting of various GL state changes and a shader, * used by CShaderTechnique. */ class CShaderPass { public: CShaderPass(); /** * Set the shader program used for rendering with this pass. */ void SetShader(const CShaderProgramPtr& shader) { m_Shader = shader; } // Add various bits of GL state to the pass: void AlphaFunc(GLenum func, GLclampf ref); void BlendFunc(GLenum src, GLenum dst); void ColorMask(GLboolean r, GLboolean g, GLboolean b, GLboolean a); void DepthMask(GLboolean mask); void DepthFunc(GLenum func); /** * Set up all the GL state that was previously specified on this pass. */ void Bind(); /** * Reset the GL state to the default. */ void Unbind(); const CShaderProgramPtr& GetShader() const { return m_Shader; } private: CShaderProgramPtr m_Shader; bool m_HasAlpha; GLenum m_AlphaFunc; GLclampf m_AlphaRef; bool m_HasBlend; GLenum m_BlendSrc; GLenum m_BlendDst; bool m_HasColorMask; GLboolean m_ColorMaskR; GLboolean m_ColorMaskG; GLboolean m_ColorMaskB; GLboolean m_ColorMaskA; bool m_HasDepthMask; GLboolean m_DepthMask; bool m_HasDepthFunc; GLenum m_DepthFunc; }; /** * Implements a render technique consisting of a sequence of passes. * CShaderManager loads these from shader effect XML files. */ class CShaderTechnique { public: CShaderTechnique(); void AddPass(const CShaderPass& pass); int GetNumPasses() const; void BeginPass(int pass = 0); void EndPass(int pass = 0); const CShaderProgramPtr& GetShader(int pass = 0) const; /** * Whether this technique uses alpha blending that requires objects to be * drawn from furthest to nearest. */ bool GetSortByDistance() const; void SetSortByDistance(bool enable); void Reset(); private: std::vector m_Passes; bool m_SortByDistance; }; -typedef shared_ptr CShaderTechniquePtr; - #endif // INCLUDED_SHADERTECHNIQUE Index: ps/trunk/source/graphics/ShaderTechniquePtr.h =================================================================== --- ps/trunk/source/graphics/ShaderTechniquePtr.h (nonexistent) +++ ps/trunk/source/graphics/ShaderTechniquePtr.h (revision 24227) @@ -0,0 +1,30 @@ +/* Copyright (C) 2020 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_SHADERTECHNIQUEPTR +#define INCLUDED_SHADERTECHNIQUEPTR + +/* + * Forward declaration, to reduce the number of header files that have to pull + * in the whole of ShaderTechnique.h + */ +class CShaderTechnique; +typedef std::shared_ptr CShaderTechniquePtr; + +#endif // INCLUDED_SHADERTECHNIQUEPTR Index: ps/trunk/source/graphics/SkeletonAnimDef.cpp =================================================================== --- ps/trunk/source/graphics/SkeletonAnimDef.cpp (revision 24226) +++ ps/trunk/source/graphics/SkeletonAnimDef.cpp (revision 24227) @@ -1,135 +1,136 @@ /* Copyright (C) 2009 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 . */ /* * Raw description of a skeleton animation */ #include "precompiled.h" #include "SkeletonAnimDef.h" #include "maths/MathUtil.h" +#include "maths/Matrix3D.h" #include "ps/FileIo.h" /////////////////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimDef constructor CSkeletonAnimDef::CSkeletonAnimDef() : m_FrameTime(0), m_NumKeys(0), m_NumFrames(0), m_Keys(0) { } /////////////////////////////////////////////////////////////////////////////////////////// // CSkeletonAnimDef destructor CSkeletonAnimDef::~CSkeletonAnimDef() { delete[] m_Keys; } /////////////////////////////////////////////////////////////////////////////////////////// // BuildBoneMatrices: build matrices for all bones at the given time (in MS) in this // animation void CSkeletonAnimDef::BuildBoneMatrices(float time, CMatrix3D* matrices, bool loop) const { float fstartframe = time/m_FrameTime; size_t startframe = (size_t)(int)(time/m_FrameTime); float deltatime = fstartframe-startframe; startframe %= m_NumFrames; size_t endframe = startframe + 1; endframe %= m_NumFrames; if (!loop && endframe == 0) { // This might be something like a death animation, and interpolating // between the final frame and the initial frame is wrong, because they're // totally different. So if we've looped around to endframe==0, just display // the animation's final frame with no interpolation. for (size_t i = 0; i < m_NumKeys; i++) { const Key& key = GetKey(startframe, i); matrices[i].SetIdentity(); matrices[i].Rotate(key.m_Rotation); matrices[i].Translate(key.m_Translation); } } else { for (size_t i = 0; i < m_NumKeys; i++) { const Key& startkey = GetKey(startframe, i); const Key& endkey = GetKey(endframe, i); CVector3D trans = Interpolate(startkey.m_Translation, endkey.m_Translation, deltatime); // TODO: is slerp the best thing to use here? CQuaternion rot; rot.Slerp(startkey.m_Rotation, endkey.m_Rotation, deltatime); rot.ToMatrix(matrices[i]); matrices[i].Translate(trans); } } } /////////////////////////////////////////////////////////////////////////////////////////// // Load: try to load the anim from given file; return a new anim if successful CSkeletonAnimDef* CSkeletonAnimDef::Load(const VfsPath& filename) { CFileUnpacker unpacker; unpacker.Read(filename,"PSSA"); // check version if (unpacker.GetVersion()m_FrameTime,sizeof(anim->m_FrameTime)); anim->m_NumKeys = unpacker.UnpackSize(); anim->m_NumFrames = unpacker.UnpackSize(); anim->m_Keys=new Key[anim->m_NumKeys*anim->m_NumFrames]; unpacker.UnpackRaw(anim->m_Keys,anim->m_NumKeys*anim->m_NumFrames*sizeof(Key)); } catch (PSERROR_File&) { delete anim; throw; } return anim; } /////////////////////////////////////////////////////////////////////////////////////////// // Save: try to save anim to file void CSkeletonAnimDef::Save(const VfsPath& pathname,const CSkeletonAnimDef* anim) { CFilePacker packer(FILE_VERSION, "PSSA"); // pack up all the data packer.PackString(""); packer.PackRaw(&anim->m_FrameTime,sizeof(anim->m_FrameTime)); const size_t numKeys = anim->m_NumKeys; packer.PackSize(numKeys); const size_t numFrames = anim->m_NumFrames; packer.PackSize(numFrames); packer.PackRaw(anim->m_Keys,numKeys*numFrames*sizeof(Key)); // now write it packer.Write(pathname); } Index: ps/trunk/source/gui/CGUISprite.h =================================================================== --- ps/trunk/source/gui/CGUISprite.h (revision 24226) +++ ps/trunk/source/gui/CGUISprite.h (revision 24227) @@ -1,194 +1,198 @@ /* 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, which is actually a collage of several sprites. */ #ifndef INCLUDED_CGUISPRITE #define INCLUDED_CGUISPRITE #include "gui/GUIRenderer.h" #include "gui/SettingTypes/CGUISize.h" +#include "gui/SettingTypes/CGUIColor.h" +#include "lib/ogl.h" +#include "lib/file/vfs/vfs_path.h" +#include "ps/CStr.h" #include #include #include struct SGUIImageEffects { SGUIImageEffects() : m_Greyscale(false) {} CGUIColor m_AddColor; CGUIColor 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(), m_Border(false), m_DeltaZ(0.f), m_Size(CGUISize::Full()), m_TextureSize(CGUISize::Full()) { } // Filename of the texture VfsPath m_TextureName; // Image placement (relative to object) CGUISize m_Size; // Texture placement (relative to image placement) CGUISize 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) std::shared_ptr m_Effects; // Color CGUIColor m_BackColor; CGUIColor 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; }; // 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: NONCOPYABLE(CGUISpriteInstance); MOVABLE(CGUISpriteInstance); CGUISpriteInstance(); CGUISpriteInstance(const CStr& SpriteName); void Draw(CGUI& pGUI, const CRect& Size, int CellID, std::map& Sprites, float Z) const; /** * Whether this Sprite has no texture name set. */ operator bool() const { return !m_SpriteName.empty(); }; /** * Returns this sprite if it has been set, otherwise the given fallback sprite. */ const CGUISpriteInstance& operator||(const CGUISpriteInstance& fallback) const { if (*this) return *this; return fallback; } /** * Returns the sprite texture name. */ const CStr& GetName() const { return m_SpriteName; } /** * Changes the texture name. * Use as rarely as possible, because it clears the draw cache. */ void SetName(const CStr& 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/gui/GUIRenderer.h =================================================================== --- ps/trunk/source/gui/GUIRenderer.h (revision 24226) +++ ps/trunk/source/gui/GUIRenderer.h (revision 24227) @@ -1,75 +1,77 @@ /* 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_GUIRENDERER #define INCLUDED_GUIRENDERER -#include "graphics/ShaderTechnique.h" +#include "graphics/Color.h" +#include "graphics/ShaderTechniquePtr.h" #include "graphics/Texture.h" -#include "gui/SettingTypes/CGUIColor.h" #include "lib/res/handle.h" -#include "ps/CStr.h" #include "ps/Shapes.h" #include #include +class CGUI; class CGUISprite; +class CStr8; +struct CGUIColor; struct SGUIImage; namespace GUIRenderer { struct SDrawCall { SDrawCall(const SGUIImage* image) : m_Image(image) {} CRect ComputeTexCoords() const; const SGUIImage* m_Image; bool m_HasTexture; CTexturePtr m_Texture; CRect m_ObjectSize; int m_CellID; bool m_EnableBlending; CShaderTechniquePtr m_Shader; CColor m_ShaderColorParameter; CRect m_Vertices; float m_DeltaZ; CGUIColor* m_BorderColor; // == nullptr for no border CGUIColor* m_BackColor; }; class DrawCalls : public std::vector { public: DrawCalls(); // Copy/assignment results in an empty list, not an actual copy DrawCalls(const DrawCalls&); DrawCalls& operator=(const DrawCalls&); }; - void UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr& SpriteName, const CRect& Size, int CellID, std::map& Sprites); + void UpdateDrawCallCache(const CGUI& pGUI, DrawCalls& Calls, const CStr8& SpriteName, const CRect& Size, int CellID, std::map& Sprites); void Draw(DrawCalls& Calls, float Z); } #endif // INCLUDED_GUIRENDERER Index: ps/trunk/source/gui/IGUIScrollBar.h =================================================================== --- ps/trunk/source/gui/IGUIScrollBar.h (revision 24226) +++ ps/trunk/source/gui/IGUIScrollBar.h (revision 24227) @@ -1,437 +1,439 @@ /* 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 Scrollbar, this class doesn't present all functionality to the scrollbar, it just controls the drawing and a wrapper for interaction with it. */ #ifndef INCLUDED_IGUISCROLLBAR #define INCLUDED_IGUISCROLLBAR #include "gui/CGUISprite.h" +#include "ps/CStr.h" +class CGUI; class IGUIScrollBarOwner; struct SGUIMessage; /** * The GUI Scroll-bar style. Tells us how scroll-bars look and feel. * * A scroll-bar style can choose whether to support horizontal, vertical * or both. * * @see IGUIScrollBar */ struct SGUIScrollBarStyle { // CGUISpriteInstance makes this NONCOPYABLE implicitly, make it explicit NONCOPYABLE(SGUIScrollBarStyle); MOVABLE(SGUIScrollBarStyle); SGUIScrollBarStyle() = default; //-------------------------------------------------------- /** @name General Settings */ //-------------------------------------------------------- //@{ /** * Width of bar, also both sides of the edge buttons. */ float m_Width; /** * Scrollable with the wheel. */ bool m_ScrollWheel; /** * How much (in percent, 0.1f = 10%) to scroll each time * the wheel is admitted, or the buttons are pressed. */ float m_ScrollSpeed; /** * Whether or not the edge buttons should appear or not. Sometimes * you actually don't want them, like perhaps in a combo box. */ bool m_ScrollButtons; /** * Sometimes there is *a lot* to scroll, but to prevent the scroll "bar" * from being almost invisible (or ugly), you can set a minimum in pixel * size. */ float m_MinimumBarSize; /** * Sometimes you would like your scroll bar to have a fixed maximum size * so that the texture does not get too stretched, you can set a maximum * in pixels. */ float m_MaximumBarSize; /** * True if you want edge buttons, i.e. buttons that can be pressed in order * to scroll. */ bool m_UseEdgeButtons; //@} //-------------------------------------------------------- /** @name Vertical Sprites */ //-------------------------------------------------------- //@{ CGUISpriteInstance m_SpriteButtonTop; CGUISpriteInstance m_SpriteButtonTopPressed; CGUISpriteInstance m_SpriteButtonTopDisabled; CGUISpriteInstance m_SpriteButtonTopOver; CGUISpriteInstance m_SpriteButtonBottom; CGUISpriteInstance m_SpriteButtonBottomPressed; CGUISpriteInstance m_SpriteButtonBottomDisabled; CGUISpriteInstance m_SpriteButtonBottomOver; CGUISpriteInstance m_SpriteBarVertical; CGUISpriteInstance m_SpriteBarVerticalOver; CGUISpriteInstance m_SpriteBarVerticalPressed; CGUISpriteInstance m_SpriteBackVertical; //@} //-------------------------------------------------------- /** @name Horizontal Sprites */ //-------------------------------------------------------- //@{ CGUISpriteInstance m_SpriteButtonLeft; CGUISpriteInstance m_SpriteButtonLeftPressed; CGUISpriteInstance m_SpriteButtonLeftDisabled; CGUISpriteInstance m_SpriteButtonRight; CGUISpriteInstance m_SpriteButtonRightPressed; CGUISpriteInstance m_SpriteButtonRightDisabled; CGUISpriteInstance m_SpriteBackHorizontal; CGUISpriteInstance m_SpriteBarHorizontal; //@} }; /** * The GUI Scroll-bar, used everywhere there is a scroll-bar in the game. * * To include a scroll-bar to an object, inherent the object from * IGUIScrollBarOwner and call AddScrollBar() to add the scroll-bars. * * It's also important that the scrollbar is located within the parent * object's mouse over area. Otherwise the input won't be sent to the * scroll-bar. * * The class does not provide all functionality to the scroll-bar, many * things the parent of the scroll-bar, must provide. Like a combo-box. */ class IGUIScrollBar { public: NONCOPYABLE(IGUIScrollBar); IGUIScrollBar(CGUI& pGUI); virtual ~IGUIScrollBar(); public: /** * Draw the scroll-bar */ virtual void Draw() = 0; /** * If an object that contains a scrollbar has got messages, send * them to the scroll-bar and it will see if the message regarded * itself. * * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message) = 0; /** * Set m_Pos with g_mouse_x/y input, i.e. when draggin. */ virtual void SetPosFromMousePos(const CPos& mouse) = 0; /** * Hovering the scroll minus button * * @param mouse current mouse position * @return True if mouse positions are hovering the button */ virtual bool HoveringButtonMinus(const CPos& UNUSED(mouse)) { return false; } /** * Hovering the scroll plus button * * @param mouse current mouse position * @return True if mouse positions are hovering the button */ virtual bool HoveringButtonPlus(const CPos& UNUSED(mouse)) { return false; } /** * Get scroll-position */ float GetPos() const { return m_Pos; } /** * Set scroll-position by hand */ virtual void SetPos(float f) { m_Pos = f; UpdatePosBoundaries(); } /** * Get the value of m_Pos that corresponds to the bottom of the scrollable region */ float GetMaxPos() const { return std::max(0.f, m_ScrollRange - m_ScrollSpace); } /** * Scrollbars without height shouldn't be visible */ bool IsVisible() const { return GetMaxPos() != 0.f; } /** * Increase scroll one step */ virtual void ScrollPlus() { m_Pos += 30.f; UpdatePosBoundaries(); } /** * Decrease scroll one step */ virtual void ScrollMinus() { m_Pos -= 30.f; UpdatePosBoundaries(); } /** * Increase scroll three steps */ virtual void ScrollPlusPlenty() { m_Pos += 90.f; UpdatePosBoundaries(); } /** * Decrease scroll three steps */ virtual void ScrollMinusPlenty() { m_Pos -= 90.f; UpdatePosBoundaries(); } /** * Set host object, must be done almost at creation of scroll bar. * @param pOwner Pointer to host object. */ void SetHostObject(IGUIScrollBarOwner* pOwner) { m_pHostObject = pOwner; } /** * Set Width * @param width Width */ void SetWidth(float width) { m_Width = width; } /** * Set X Position * @param x Position in this axis */ void SetX(float x) { m_X = x; } /** * Set Y Position * @param y Position in this axis */ void SetY(float y) { m_Y = y; } /** * Set Z Position * @param z Position in this axis */ void SetZ(float z) { m_Z = z; } /** * Set Length of scroll bar * @param length Length */ void SetLength(float length) { m_Length = length; } /** * Set content length * @param range Maximum scrollable range */ void SetScrollRange(float range) { m_ScrollRange = std::max(range, 1.f); SetupBarSize(); UpdatePosBoundaries(); } /** * Set space that is visible in the scrollable control. * @param space Visible area in the scrollable control. */ void SetScrollSpace(float space) { m_ScrollSpace = space; SetupBarSize(); UpdatePosBoundaries(); } /** * Set bar pressed * @param b True if bar is pressed */ void SetBarPressed(bool b) { m_BarPressed = b; } /** * Set Scroll bar style string * @param style String with scroll bar style reference name */ void SetScrollBarStyle(const CStr& style) { m_ScrollBarStyle = style; } /** * Get style used by the scrollbar * @return Scroll bar style struct. */ const SGUIScrollBarStyle* GetStyle() const; /** * Get the rectangle of the actual BAR. not the whole scroll-bar. * @return Rectangle, CRect */ virtual CRect GetBarRect() const = 0; /** * Get the rectangle of the outline of the scrollbar, every component of the * scroll-bar should be inside this area. * @return Rectangle, CRect */ virtual CRect GetOuterRect() const = 0; protected: /** * Sets up bar size */ void SetupBarSize(); /** * Call every time m_Pos has been updated. */ void UpdatePosBoundaries(); protected: //@} //-------------------------------------------------------- /** @name Settings */ //-------------------------------------------------------- //@{ /** * Width of the scroll bar */ float m_Width; /** * Absolute X Position */ float m_X; /** * Absolute Y Position */ float m_Y; /** * Absolute Z Position */ float m_Z; /** * Total length of scrollbar, including edge buttons. */ float m_Length; /** * Content that can be scrolled, in pixels */ float m_ScrollRange; /** * Content that can be viewed at a time, in pixels */ float m_ScrollSpace; /** * Use input from the scroll-wheel? True or false. */ float m_BarSize; /** * Scroll bar style reference name */ CStr m_ScrollBarStyle; /** * Pointer to scroll bar style used. */ SGUIScrollBarStyle* m_pStyle; /** * Host object, prerequisite! */ IGUIScrollBarOwner* m_pHostObject; /** * Reference to CGUI object, these cannot work stand-alone */ CGUI& m_pGUI; /** * Mouse position when bar was pressed */ CPos m_BarPressedAtPos; //@} //-------------------------------------------------------- /** @name States */ //-------------------------------------------------------- //@{ /** * If the bar is currently being pressed and dragged. */ bool m_BarPressed; /** * Bar being hovered or not */ bool m_BarHovered; /** * Scroll buttons hovered */ bool m_ButtonMinusHovered, m_ButtonPlusHovered; /** * Scroll buttons pressed */ bool m_ButtonMinusPressed, m_ButtonPlusPressed; /** * Position of scroll bar, 0 means scrolled all the way to one side. * It is measured in pixels, it is up to the host to make it actually * apply in pixels. */ float m_Pos; /** * Position from 0.f to 1.f it had when the bar was pressed. */ float m_PosWhenPressed; //@} }; #endif // INCLUDED_IGUISCROLLBAR Index: ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.h =================================================================== --- ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.h (revision 24226) +++ ps/trunk/source/gui/ObjectBases/IGUIButtonBehavior.h (revision 24227) @@ -1,101 +1,102 @@ /* Copyright (C) 2020 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 . */ /* Interface class that enhance the IGUIObject with buttony behavior (click and release to click a button), and the GUI message GUIM_PRESSED. When creating a class with extended settings and buttony behavior, just do a multiple inheritance. */ #ifndef INCLUDED_IGUIBUTTONBEHAVIOR #define INCLUDED_IGUIBUTTONBEHAVIOR #include "gui/ObjectBases/IGUIObject.h" +#include "ps/CStr.h" class CGUISpriteInstance; /** * Appends button behaviours to the IGUIObject. * Can be used with multiple inheritance alongside * IGUISettingsObject and such. */ class IGUIButtonBehavior { NONCOPYABLE(IGUIButtonBehavior); public: IGUIButtonBehavior(IGUIObject& pObject); virtual ~IGUIButtonBehavior(); /** * @see IGUIObject#HandleMessage() */ virtual void HandleMessage(SGUIMessage& Message); /** * This is a function that lets a button being drawn, * it regards if it's over, disabled, pressed and such. * * @param sprite Sprite drawn when not pressed, hovered or disabled * @param sprite_over Sprite drawn when m_MouseHovering is true * @param sprite_pressed Sprite drawn when m_Pressed is true * @param sprite_disabled Sprite drawn when "enabled" is false */ const CGUISpriteInstance& GetButtonSprite(const CGUISpriteInstance& sprite, const CGUISpriteInstance& sprite_over, const CGUISpriteInstance& sprite_pressed, const CGUISpriteInstance& sprite_disabled) const; protected: static const CStr EventNamePress; static const CStr EventNamePressRight; static const CStr EventNamePressRightDisabled; static const CStr EventNameDoublePress; static const CStr EventNameDoublePressRight; static const CStr EventNameRelease; static const CStr EventNameReleaseRight; /** * @see IGUIObject#ResetStates() */ virtual void ResetStates(); /** * Everybody knows how a button works, you don't simply press it, * you have to first press the button, and then release it... * in between those two steps you can actually leave the button * area, as long as you release it within the button area... Anyway * this lets us know we are done with step one (clicking). */ bool m_Pressed; bool m_PressedRight; // Settings CStrW m_SoundDisabled; CStrW m_SoundEnter; CStrW m_SoundLeave; CStrW m_SoundPressed; CStrW m_SoundReleased; private: /** * Reference to the IGUIObject. * Private, because we don't want to inherit it in multiple classes. */ IGUIObject& m_pObject; }; #endif // INCLUDED_IGUIBUTTONBEHAVIOR Index: ps/trunk/source/gui/ObjectTypes/CHotkeyPicker.h =================================================================== --- ps/trunk/source/gui/ObjectTypes/CHotkeyPicker.h (revision 24226) +++ ps/trunk/source/gui/ObjectTypes/CHotkeyPicker.h (revision 24227) @@ -1,81 +1,79 @@ /* Copyright (C) 2020 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_CHOTKEYPICKER #define INCLUDED_CHOTKEYPICKER #include "gui/CGUI.h" #include "lib/external_libraries/libsdl.h" #include "ps/CStr.h" #include class ScriptInterface; /** * When in focus, returns all currently pressed keys. * After a set time without changes, it will trigger a "combination" event. * * Used to create new hotkey combinations in-game. Mostly custom. * This object does not draw anything. - * - * NB: because of how input is handled, mouse clicks */ class CHotkeyPicker : public IGUIObject { GUI_OBJECT(CHotkeyPicker) friend class ScriptInterface; public: CHotkeyPicker(CGUI& pGUI); virtual ~CHotkeyPicker(); // Do nothing. virtual void Draw() {}; // Checks if the timer has passed and we need to fire a "combination" event. virtual void Tick(); // React to blur/focus. virtual void HandleMessage(SGUIMessage& Message); // Pre-empt events: this is our sole purpose. virtual InReaction PreemptEvent(const SDL_Event_* ev); protected: // Fire an event with m_KeysPressed as argument. void FireEvent(const CStr& event); // Time without changes until a "combination" event is sent. float m_TimeToCombination; // Time of the last registered key change. double m_LastKeyChange; // Keep track of which keys we are pressing, and precompute their name for JS code. struct Key { // The scancode is used for fast comparisons. SDL_Scancode code; // This is the name ultimately stored in the config file. CStr scancodeName; }; std::vector m_KeysPressed; static const CStr EventNameCombination; static const CStr EventNameKeyChange; }; #endif // INCLUDED_CHOTKEYPICKER Index: ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp =================================================================== --- ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp (revision 24226) +++ ps/trunk/source/gui/ObjectTypes/CMiniMap.cpp (revision 24227) @@ -1,754 +1,755 @@ /* Copyright (C) 2020 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 "CMiniMap.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MiniPatch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "graphics/TerritoryTexture.h" #include "gui/CGUI.h" #include "gui/GUIManager.h" #include "gui/GUIMatrix.h" #include "lib/bits.h" #include "lib/external_libraries/libsdl.h" #include "lib/ogl.h" #include "lib/timer.h" #include "ps/ConfigDB.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/GameSetup/Config.h" #include "ps/Profile.h" #include "ps/World.h" #include "ps/XML/Xeromyces.h" #include "renderer/Renderer.h" #include "renderer/RenderingOptions.h" #include "renderer/WaterManager.h" #include "scriptinterface/ScriptInterface.h" #include "simulation2/Simulation2.h" #include "simulation2/components/ICmpMinimap.h" +#include "simulation2/components/ICmpRangeManager.h" #include "simulation2/helpers/Los.h" #include "simulation2/system/ParamNode.h" #include extern bool g_GameRestarted; // Set max drawn entities to UINT16_MAX for now, which is more than enough // TODO: we should be cleverer about drawing them to reduce clutter const u16 MAX_ENTITIES_DRAWN = 65535; static unsigned int ScaleColor(unsigned int color, float x) { unsigned int r = unsigned(float(color & 0xff) * x); unsigned int g = unsigned(float((color>>8) & 0xff) * x); unsigned int b = unsigned(float((color>>16) & 0xff) * x); return (0xff000000 | b | g<<8 | r<<16); } const CStr CMiniMap::EventNameWorldClick = "WorldClick"; CMiniMap::CMiniMap(CGUI& pGUI) : IGUIObject(pGUI), m_TerrainTexture(0), m_TerrainData(0), m_MapSize(0), m_Terrain(0), m_TerrainDirty(true), m_MapScale(1.f), m_EntitiesDrawn(0), m_IndexArray(GL_STATIC_DRAW), m_VertexArray(GL_DYNAMIC_DRAW), m_Mask(false), m_NextBlinkTime(0.0), m_PingDuration(25.0), m_BlinkState(false), m_WaterHeight(0.0) { RegisterSetting("mask", m_Mask); m_Clicking = false; m_MouseHovering = false; // Register Relax NG validator CXeromyces::AddValidator(g_VFS, "pathfinder", "simulation/data/pathfinder.rng"); m_ShallowPassageHeight = GetShallowPassageHeight(); m_AttributePos.type = GL_FLOAT; m_AttributePos.elems = 2; m_VertexArray.AddAttribute(&m_AttributePos); m_AttributeColor.type = GL_UNSIGNED_BYTE; m_AttributeColor.elems = 4; m_VertexArray.AddAttribute(&m_AttributeColor); m_VertexArray.SetNumVertices(MAX_ENTITIES_DRAWN); m_VertexArray.Layout(); m_IndexArray.SetNumVertices(MAX_ENTITIES_DRAWN); m_IndexArray.Layout(); VertexArrayIterator index = m_IndexArray.GetIterator(); for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) *index++ = i; m_IndexArray.Upload(); m_IndexArray.FreeBackingStore(); VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); for (u16 i = 0; i < MAX_ENTITIES_DRAWN; ++i) { (*attrColor)[0] = 0; (*attrColor)[1] = 0; (*attrColor)[2] = 0; (*attrColor)[3] = 0; ++attrColor; (*attrPos)[0] = -10000.0f; (*attrPos)[1] = -10000.0f; ++attrPos; } m_VertexArray.Upload(); double blinkDuration = 1.0; // Tests won't have config initialised if (CConfigDB::IsInitialised()) { CFG_GET_VAL("gui.session.minimap.pingduration", m_PingDuration); CFG_GET_VAL("gui.session.minimap.blinkduration", blinkDuration); } m_HalfBlinkDuration = blinkDuration/2; } CMiniMap::~CMiniMap() { Destroy(); } void CMiniMap::HandleMessage(SGUIMessage& Message) { IGUIObject::HandleMessage(Message); switch (Message.type) { case GUIM_MOUSE_PRESS_LEFT: if (m_MouseHovering) { if (!CMiniMap::FireWorldClickEvent(SDL_BUTTON_LEFT, 1)) { SetCameraPos(); m_Clicking = true; } } break; case GUIM_MOUSE_RELEASE_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPos(); m_Clicking = false; break; case GUIM_MOUSE_DBLCLICK_LEFT: if (m_MouseHovering && m_Clicking) SetCameraPos(); m_Clicking = false; break; case GUIM_MOUSE_ENTER: m_MouseHovering = true; break; case GUIM_MOUSE_LEAVE: m_Clicking = false; m_MouseHovering = false; break; case GUIM_MOUSE_RELEASE_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 1); break; case GUIM_MOUSE_DBLCLICK_RIGHT: CMiniMap::FireWorldClickEvent(SDL_BUTTON_RIGHT, 2); break; case GUIM_MOUSE_MOTION: if (m_MouseHovering && m_Clicking) SetCameraPos(); break; case GUIM_MOUSE_WHEEL_DOWN: case GUIM_MOUSE_WHEEL_UP: Message.Skip(); break; default: break; } } bool CMiniMap::IsMouseOver() const { // Get the mouse position. const CPos& mousePos = m_pGUI.GetMousePos(); // Get the position of the center of the minimap. CPos minimapCenter = CPos(m_CachedActualSize.left + m_CachedActualSize.GetWidth() / 2.0, m_CachedActualSize.bottom - m_CachedActualSize.GetHeight() / 2.0); // Take the magnitude of the difference of the mouse position and minimap center. double distFromCenter = sqrt(pow((mousePos.x - minimapCenter.x), 2) + pow((mousePos.y - minimapCenter.y), 2)); // If the distance is less then the radius of the minimap (half the width) the mouse is over the minimap. if (distFromCenter < m_CachedActualSize.GetWidth() / 2.0) return true; else return false; } void CMiniMap::GetMouseWorldCoordinates(float& x, float& z) const { // Determine X and Z according to proportion of mouse position and minimap const CPos& mousePos = m_pGUI.GetMousePos(); float px = (mousePos.x - m_CachedActualSize.left) / m_CachedActualSize.GetWidth(); float py = (m_CachedActualSize.bottom - mousePos.y) / m_CachedActualSize.GetHeight(); float angle = GetAngle(); // Scale world coordinates for shrunken square map x = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(px-0.5) - sin(angle)*(py-0.5)) + 0.5); z = TERRAIN_TILE_SIZE * m_MapSize * (m_MapScale * (cos(angle)*(py-0.5) + sin(angle)*(px-0.5)) + 0.5); } void CMiniMap::SetCameraPos() { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); CVector3D target; GetMouseWorldCoordinates(target.X, target.Z); target.Y = terrain->GetExactGroundLevel(target.X, target.Z); g_Game->GetView()->MoveCameraTarget(target); } float CMiniMap::GetAngle() const { CVector3D cameraIn = m_Camera->GetOrientation().GetIn(); return -atan2(cameraIn.X, cameraIn.Z); } bool CMiniMap::FireWorldClickEvent(int button, int UNUSED(clicks)) { ScriptRequest rq(g_GUI->GetActiveGUI()->GetScriptInterface()); float x, z; GetMouseWorldCoordinates(x, z); JS::RootedValue coords(rq.cx); ScriptInterface::CreateObject(rq, &coords, "x", x, "z", z); JS::RootedValue buttonJs(rq.cx); ScriptInterface::ToJSVal(rq, &buttonJs, button); JS::AutoValueVector paramData(rq.cx); paramData.append(coords); paramData.append(buttonJs); return ScriptEventWithReturn(EventNameWorldClick, paramData); } // This sets up and draws the rectangle on the minimap // which represents the view of the camera in the world. void CMiniMap::DrawViewRect(CMatrix3D transform) const { // Compute the camera frustum intersected with a fixed-height plane. // Use the water height as a fixed base height, which should be the lowest we can go float h = g_Renderer.GetWaterManager()->m_WaterHeight; const float width = m_CachedActualSize.GetWidth(); const float height = m_CachedActualSize.GetHeight(); const float invTileMapSize = 1.0f / float(TERRAIN_TILE_SIZE * m_MapSize); CVector3D hitPt[4]; hitPt[0] = m_Camera->GetWorldCoordinates(0, g_Renderer.GetHeight(), h); hitPt[1] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), g_Renderer.GetHeight(), h); hitPt[2] = m_Camera->GetWorldCoordinates(g_Renderer.GetWidth(), 0, h); hitPt[3] = m_Camera->GetWorldCoordinates(0, 0, h); float ViewRect[4][2]; for (int i = 0; i < 4; ++i) { // convert to minimap space ViewRect[i][0] = (width * hitPt[i].X * invTileMapSize); ViewRect[i][1] = (height * hitPt[i].Z * invTileMapSize); } float viewVerts[] = { ViewRect[0][0], -ViewRect[0][1], ViewRect[1][0], -ViewRect[1][1], ViewRect[2][0], -ViewRect[2][1], ViewRect[3][0], -ViewRect[3][1] }; // Enable Scissoring to restrict the rectangle to only the minimap. glScissor( m_CachedActualSize.left * g_GuiScale, g_Renderer.GetHeight() - m_CachedActualSize.bottom * g_GuiScale, width * g_GuiScale, height * g_GuiScale); glEnable(GL_SCISSOR_TEST); glLineWidth(2.0f); CShaderDefines lineDefines; lineDefines.Add(str_MINIMAP_LINE, str_1); CShaderTechniquePtr tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), lineDefines); tech->BeginPass(); CShaderProgramPtr shader = tech->GetShader(); shader->Uniform(str_transform, transform); shader->Uniform(str_color, 1.0f, 0.3f, 0.3f, 1.0f); shader->VertexPointer(2, GL_FLOAT, 0, viewVerts); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_LINE_LOOP, 0, 4); tech->EndPass(); glLineWidth(1.0f); glDisable(GL_SCISSOR_TEST); } struct MinimapUnitVertex { // This struct is copyable for convenience and because to move is to copy for primitives. u8 r, g, b, a; float x, y; }; // Adds a vertex to the passed VertexArray static void inline addVertex(const MinimapUnitVertex& v, VertexArrayIterator& attrColor, VertexArrayIterator& attrPos) { (*attrColor)[0] = v.r; (*attrColor)[1] = v.g; (*attrColor)[2] = v.b; (*attrColor)[3] = v.a; ++attrColor; (*attrPos)[0] = v.x; (*attrPos)[1] = v.y; ++attrPos; } void CMiniMap::DrawTexture(CShaderProgramPtr shader, float coordMax, float angle, float x, float y, float x2, float y2, float z) const { // Rotate the texture coordinates (0,0)-(coordMax,coordMax) around their center point (m,m) // Scale square maps to fit in circular minimap area const float s = sin(angle) * m_MapScale; const float c = cos(angle) * m_MapScale; const float m = coordMax / 2.f; float quadTex[] = { m*(-c + s + 1.f), m*(-c + -s + 1.f), m*(c + s + 1.f), m*(-c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(c + -s + 1.f), m*(c + s + 1.f), m*(-c + -s + 1.f), m*(c + -s + 1.f), m*(-c + s + 1.f), m*(-c + -s + 1.f) }; float quadVerts[] = { x, y, z, x2, y, z, x2, y2, z, x2, y2, z, x, y2, z, x, y, z }; shader->TexCoordPointer(GL_TEXTURE0, 2, GL_FLOAT, 0, quadTex); shader->VertexPointer(3, GL_FLOAT, 0, quadVerts); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawArrays(GL_TRIANGLES, 0, 6); } // TODO: render the minimap in a framebuffer and just draw the frambuffer texture // most of the time, updating the framebuffer twice a frame. // Here it updates as ping-pong either texture or vertex array each sec to lower gpu stalling // (those operations cause a gpu sync, which slows down the way gpu works) void CMiniMap::Draw() { PROFILE3("render minimap"); // The terrain isn't actually initialized until the map is loaded, which // happens when the game is started, so abort until then. if (!g_Game || !g_Game->IsGameStarted()) return; CSimulation2* sim = g_Game->GetSimulation2(); CmpPtr cmpRangeManager(*sim, SYSTEM_ENTITY); ENSURE(cmpRangeManager); // Set our globals in case they hadn't been set before m_Camera = g_Game->GetView()->GetCamera(); m_Terrain = g_Game->GetWorld()->GetTerrain(); m_Width = (u32)(m_CachedActualSize.right - m_CachedActualSize.left); m_Height = (u32)(m_CachedActualSize.bottom - m_CachedActualSize.top); m_MapSize = m_Terrain->GetVerticesPerSide(); m_TextureSize = (GLsizei)round_up_to_pow2((size_t)m_MapSize); m_MapScale = (cmpRangeManager->GetLosCircular() ? 1.f : 1.414f); if (!m_TerrainTexture || g_GameRestarted) CreateTextures(); // only update 2x / second // (note: since units only move a few pixels per second on the minimap, // we can get away with infrequent updates; this is slow) // TODO: Update all but camera at same speed as simulation static double last_time; const double cur_time = timer_Time(); const bool doUpdate = cur_time - last_time > 0.5; if (doUpdate) { last_time = cur_time; if (m_TerrainDirty || m_WaterHeight != g_Renderer.GetWaterManager()->m_WaterHeight) RebuildTerrainTexture(); } const float x = m_CachedActualSize.left, y = m_CachedActualSize.bottom; const float x2 = m_CachedActualSize.right, y2 = m_CachedActualSize.top; const float z = GetBufferedZ(); const float texCoordMax = (float)(m_MapSize - 1) / (float)m_TextureSize; const float angle = GetAngle(); const float unitScale = (cmpRangeManager->GetLosCircular() ? 1.f : m_MapScale/2.f); CLOSTexture& losTexture = g_Game->GetView()->GetLOSTexture(); // Disable depth updates to prevent apparent z-fighting-related issues // with some drivers causing units to get drawn behind the texture. glDepthMask(0); CShaderProgramPtr shader; CShaderTechniquePtr tech; CShaderDefines baseDefines; baseDefines.Add(str_MINIMAP_BASE, str_1); if (m_Mask) baseDefines.Add(str_MINIMAP_MASK, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), baseDefines); tech->BeginPass(); shader = tech->GetShader(); // Draw the main textured quad shader->BindTexture(str_baseTex, m_TerrainTexture); if (m_Mask) { shader->BindTexture(str_maskTex, losTexture.GetTexture()); CMatrix3D maskTextureTransform = *losTexture.GetMinimapTextureMatrix(); // We need to have texture coordinates in the same coordinate space. const float scale = 1.0f / texCoordMax; maskTextureTransform.Scale(scale, scale, 1.0f); shader->Uniform(str_maskTextureTransform, maskTextureTransform); } const CMatrix3D baseTransform = GetDefaultGuiMatrix(); CMatrix3D baseTextureTransform; baseTextureTransform.SetIdentity(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, baseTextureTransform); if (m_Mask) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } DrawTexture(shader, texCoordMax, angle, x, y, x2, y2, z); if (!m_Mask) { glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); } // Draw territory boundaries CTerritoryTexture& territoryTexture = g_Game->GetView()->GetTerritoryTexture(); shader->BindTexture(str_baseTex, territoryTexture.GetTexture()); if (m_Mask) { shader->BindTexture(str_maskTex, losTexture.GetTexture()); shader->Uniform(str_maskTextureTransform, *losTexture.GetMinimapTextureMatrix()); } const CMatrix3D* territoryTransform = territoryTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, *territoryTransform); DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); tech->EndPass(); // Draw the LOS quad in black, using alpha values from the LOS texture if (!m_Mask) { CShaderDefines losDefines; losDefines.Add(str_MINIMAP_LOS, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), losDefines); tech->BeginPass(); shader = tech->GetShader(); shader->BindTexture(str_baseTex, losTexture.GetTexture()); const CMatrix3D* losTransform = losTexture.GetMinimapTextureMatrix(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_textureTransform, *losTransform); DrawTexture(shader, 1.0f, angle, x, y, x2, y2, z); tech->EndPass(); } glDisable(GL_BLEND); PROFILE_START("minimap units"); CShaderDefines pointDefines; pointDefines.Add(str_MINIMAP_POINT, str_1); tech = g_Renderer.GetShaderManager().LoadEffect(str_minimap, g_Renderer.GetSystemShaderDefines(), pointDefines); tech->BeginPass(); shader = tech->GetShader(); shader->Uniform(str_transform, baseTransform); shader->Uniform(str_pointSize, 3.f); CMatrix3D unitMatrix; unitMatrix.SetIdentity(); // Center the minimap on the origin of the axis of rotation. unitMatrix.Translate(-(x2 - x) / 2.f, -(y2 - y) / 2.f, 0.f); // Rotate the map. unitMatrix.RotateZ(angle); // Scale square maps to fit. unitMatrix.Scale(unitScale, unitScale, 1.f); // Move the minimap back to it's starting position. unitMatrix.Translate((x2 - x) / 2.f, (y2 - y) / 2.f, 0.f); // Move the minimap to it's final location. unitMatrix.Translate(x, y, z); // Apply the gui matrix. unitMatrix *= GetDefaultGuiMatrix(); // Load the transform into the shader. shader->Uniform(str_transform, unitMatrix); const float sx = (float)m_Width / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); const float sy = (float)m_Height / ((m_MapSize - 1) * TERRAIN_TILE_SIZE); CSimulation2::InterfaceList ents = sim->GetEntitiesWithInterface(IID_Minimap); if (doUpdate) { VertexArrayIterator attrPos = m_AttributePos.GetIterator(); VertexArrayIterator attrColor = m_AttributeColor.GetIterator(); m_EntitiesDrawn = 0; MinimapUnitVertex v; std::vector pingingVertices; pingingVertices.reserve(MAX_ENTITIES_DRAWN / 2); if (cur_time > m_NextBlinkTime) { m_BlinkState = !m_BlinkState; m_NextBlinkTime = cur_time + m_HalfBlinkDuration; } entity_pos_t posX, posZ; for (CSimulation2::InterfaceList::const_iterator it = ents.begin(); it != ents.end(); ++it) { ICmpMinimap* cmpMinimap = static_cast(it->second); if (cmpMinimap->GetRenderData(v.r, v.g, v.b, posX, posZ)) { LosVisibility vis = cmpRangeManager->GetLosVisibility(it->first, g_Game->GetSimulation2()->GetSimContext().GetCurrentDisplayedPlayer()); if (vis != LosVisibility::HIDDEN) { v.a = 255; v.x = posX.ToFloat() * sx; v.y = -posZ.ToFloat() * sy; // Check minimap pinging to indicate something if (m_BlinkState && cmpMinimap->CheckPing(cur_time, m_PingDuration)) { v.r = 255; // ping color is white v.g = 255; v.b = 255; pingingVertices.push_back(v); } else { addVertex(v, attrColor, attrPos); ++m_EntitiesDrawn; } } } } // Add the pinged vertices at the end, so they are drawn on top for (size_t v = 0; v < pingingVertices.size(); ++v) { addVertex(pingingVertices[v], attrColor, attrPos); ++m_EntitiesDrawn; } ENSURE(m_EntitiesDrawn < MAX_ENTITIES_DRAWN); m_VertexArray.Upload(); } m_VertexArray.PrepareForRendering(); if (m_EntitiesDrawn > 0) { #if !CONFIG2_GLES if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) glEnable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif u8* indexBase = m_IndexArray.Bind(); u8* base = m_VertexArray.Bind(); const GLsizei stride = (GLsizei)m_VertexArray.GetStride(); shader->VertexPointer(2, GL_FLOAT, stride, base + m_AttributePos.offset); shader->ColorPointer(4, GL_UNSIGNED_BYTE, stride, base + m_AttributeColor.offset); shader->AssertPointersBound(); if (!g_Renderer.m_SkipSubmit) glDrawElements(GL_POINTS, (GLsizei)(m_EntitiesDrawn), GL_UNSIGNED_SHORT, indexBase); g_Renderer.GetStats().m_DrawCalls++; CVertexBuffer::Unbind(); #if !CONFIG2_GLES if (g_RenderingOptions.GetRenderPath() == RenderPath::SHADER) glDisable(GL_VERTEX_PROGRAM_POINT_SIZE); #endif } tech->EndPass(); DrawViewRect(unitMatrix); PROFILE_END("minimap units"); // Reset depth mask glDepthMask(1); } void CMiniMap::CreateTextures() { Destroy(); // Create terrain texture glGenTextures(1, &m_TerrainTexture); g_Renderer.BindTexture(0, m_TerrainTexture); // Initialise texture with solid black, for the areas we don't // overwrite with glTexSubImage2D later u32* texData = new u32[m_TextureSize * m_TextureSize]; for (ssize_t i = 0; i < m_TextureSize * m_TextureSize; ++i) texData[i] = 0xFF000000; glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_TextureSize, m_TextureSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, texData); delete[] texData; m_TerrainData = new u32[(m_MapSize - 1) * (m_MapSize - 1)]; glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); // Rebuild and upload both of them RebuildTerrainTexture(); } void CMiniMap::RebuildTerrainTexture() { u32 x = 0; u32 y = 0; u32 w = m_MapSize - 1; u32 h = m_MapSize - 1; m_WaterHeight = g_Renderer.GetWaterManager()->m_WaterHeight; m_TerrainDirty = false; for (u32 j = 0; j < h; ++j) { u32* dataPtr = m_TerrainData + ((y + j) * (m_MapSize - 1)) + x; for (u32 i = 0; i < w; ++i) { float avgHeight = ( m_Terrain->GetVertexGroundLevel((int)i, (int)j) + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j) + m_Terrain->GetVertexGroundLevel((int)i, (int)j+1) + m_Terrain->GetVertexGroundLevel((int)i+1, (int)j+1) ) / 4.0f; if (avgHeight < m_WaterHeight && avgHeight > m_WaterHeight - m_ShallowPassageHeight) { // shallow water *dataPtr++ = 0xffc09870; } else if (avgHeight < m_WaterHeight) { // Set water as constant color for consistency on different maps *dataPtr++ = 0xffa07850; } else { int hmap = ((int)m_Terrain->GetHeightMap()[(y + j) * m_MapSize + x + i]) >> 8; int val = (hmap / 3) + 170; u32 color = 0xFFFFFFFF; CMiniPatch* mp = m_Terrain->GetTile(x + i, y + j); if (mp) { CTerrainTextureEntry* tex = mp->GetTextureEntry(); if (tex) { // If the texture can't be loaded yet, set the dirty flags // so we'll try regenerating the terrain texture again soon if(!tex->GetTexture()->TryLoad()) m_TerrainDirty = true; color = tex->GetBaseColor(); } } *dataPtr++ = ScaleColor(color, float(val) / 255.0f); } } } // Upload the texture g_Renderer.BindTexture(0, m_TerrainTexture); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, m_MapSize - 1, m_MapSize - 1, GL_RGBA, GL_UNSIGNED_BYTE, m_TerrainData); } void CMiniMap::Destroy() { if (m_TerrainTexture) { glDeleteTextures(1, &m_TerrainTexture); m_TerrainTexture = 0; } SAFE_ARRAY_DELETE(m_TerrainData); } // static 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: ps/trunk/source/gui/SettingTypes/CGUIColor.h =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUIColor.h (revision 24226) +++ ps/trunk/source/gui/SettingTypes/CGUIColor.h (revision 24227) @@ -1,61 +1,61 @@ /* 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_GUICOLOR #define INCLUDED_GUICOLOR #include "graphics/Color.h" -#include "ps/CStr.h" class CGUI; +class CStr8; /** * Same as the CColor class, but this one can also parse colors predefined in the GUI page (such as "yellow"). */ struct CGUIColor : CColor { // Take advantage of compiler warnings if unintentionally copying this NONCOPYABLE(CGUIColor); // Defines move semantics so that the structs using the class can use it. MOVABLE(CGUIColor); CGUIColor() : CColor() {} CGUIColor(float r, float g, float b, float a) : CColor(r, g, b, a) {} /** * Returns this color if it has been set, otherwise the given fallback color. */ const CGUIColor& operator||(const CGUIColor& fallback) const { if (*this) return *this; return fallback; } /** * Load color depending on current GUI page. */ - bool ParseString(const CGUI& pGUI, const CStr& value, int defaultAlpha = 255); + bool ParseString(const CGUI& pGUI, const CStr8& value, int defaultAlpha = 255); /** * Ensure that all users check for predefined colors. */ - bool ParseString(const CStr& value, int defaultAlpha = 255) = delete; + bool ParseString(const CStr8& value, int defaultAlpha = 255) = delete; }; #endif // INCLUDED_GUICOLOR Index: ps/trunk/source/gui/SettingTypes/CGUISize.h =================================================================== --- ps/trunk/source/gui/SettingTypes/CGUISize.h (revision 24226) +++ ps/trunk/source/gui/SettingTypes/CGUISize.h (revision 24227) @@ -1,74 +1,75 @@ /* 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_CGUISIZE #define INCLUDED_CGUISIZE -#include "ps/CStr.h" #include "ps/Shapes.h" -#include "scriptinterface/ScriptInterface.h" +#include "scriptinterface/ScriptForward.h" + +class CStr8; /** * This class represents a rectangle relative to a parent rectangle * The value can be initialized from a string or JS object. */ class CGUISize { public: // COPYABLE, since there are only primitives involved, making move and copy identical, // and since some temporaries cannot be avoided. CGUISize(); CGUISize(const CRect& pixel, const CRect& percent); static CGUISize Full(); /// Pixel modifiers CRect pixel; /// Percent modifiers CRect percent; /** * Get client area rectangle when the parent is given */ CRect GetSize(const CRect& parent) const; /** * The value can be set from a string looking like: * * "0 0 100% 100%" * "50%-10 50%-10 50%+10 50%+10" * * i.e. First percent modifier, then + or - and the pixel modifier. * Although you can use just the percent or the pixel modifier. Notice * though that the percent modifier must always be the first when * both modifiers are inputted. * * @return true if success, otherwise size will remain unchanged. */ - bool FromString(const CStr& Value); + bool FromString(const CStr8& Value); bool operator==(const CGUISize& other) const { return pixel == other.pixel && percent == other.percent; } void ToJSVal(const ScriptRequest& rq, JS::MutableHandleValue ret) const; bool FromJSVal(const ScriptRequest& rq, JS::HandleValue v); }; #endif // INCLUDED_CGUISIZE Index: ps/trunk/source/lib/secure_crt.h =================================================================== --- ps/trunk/source/lib/secure_crt.h (revision 24226) +++ ps/trunk/source/lib/secure_crt.h (revision 24227) @@ -1,122 +1,122 @@ /* Copyright (C) 2015 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. */ /* * partial implementation of VC8's secure CRT functions */ #ifndef INCLUDED_SECURE_CRT #define INCLUDED_SECURE_CRT #include #include "lib/status.h" namespace ERR { const Status STRING_NOT_TERMINATED = -100600; } // if the platform lacks a secure CRT implementation, we'll provide one. #if MSC_VERSION # define EMULATE_SECURE_CRT 0 #else # define EMULATE_SECURE_CRT 1 #endif #if EMULATE_SECURE_CRT // (conflicts with glibc definitions) #if !OS_UNIX || OS_MACOSX || OS_OPENBSD // return length [in characters] of a string, not including the trailing // null character. to protect against access violations, only the // first characters are examined; if the null character is // not encountered by then, is returned. -// strnlen is available on OpenBSD -#if !OS_OPENBSD +// strnlen is available on OpenBSD and MacOS +#if !OS_OPENBSD && !OS_MACOSX extern size_t strnlen(const char* str, size_t max_len); #endif extern size_t wcsnlen(const wchar_t* str, size_t max_len); #endif // copy at most (not including trailing null) from // into , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. // // note: padding with zeroes is not called for by NG1031. extern int strncpy_s(char* dst, size_t max_dst_chars, const char* src, size_t max_src_chars); extern int wcsncpy_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* src, size_t max_src_chars); // copy (including trailing null) into , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. // // note: implemented as tncpy_s(dst, max_dst_chars, src, SIZE_MAX) extern int strcpy_s(char* dst, size_t max_dst_chars, const char* src); extern int wcscpy_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* src); // append at most (not including trailing null) from // to , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. extern int strncat_s(char* dst, size_t max_dst_chars, const char* src, size_t max_src_chars); extern int wcsncat_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* src, size_t max_src_chars); // append to , which must not overlap. // if thereby (including null) would be exceeded, // is set to the empty string and ERANGE returned; otherwise, // 0 is returned to indicate success and that is null-terminated. // // note: implemented as tncat_s(dst, max_dst_chars, src, SIZE_MAX) extern int strcat_s(char* dst, size_t max_dst_chars, const char* src); extern int wcscat_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* src); extern int vsprintf_s(char* dst, size_t max_dst_chars, const char* fmt, va_list ap) VPRINTF_ARGS(3); extern int vswprintf_s(wchar_t* dst, size_t max_dst_chars, const wchar_t* fmt, va_list ap) VWPRINTF_ARGS(3); extern int sprintf_s(char* buf, size_t max_chars, const char* fmt, ...) PRINTF_ARGS(3); extern int swprintf_s(wchar_t* buf, size_t max_chars, const wchar_t* fmt, ...) WPRINTF_ARGS(3); // we'd like to avoid deprecation warnings caused by scanf. selective // 'undeprecation' isn't possible, replacing all stdio declarations with // our own deprecation scheme is a lot of work, suppressing all deprecation // warnings would cause important other warnings to be missed, and avoiding // scanf outright isn't convenient. // the remaining alternative is using scanf_s where available and otherwise // defining it to scanf. note that scanf_s has a different API: // any %s or %c or %[ format specifier's buffer must be followed by a // size parameter. callers must either avoid these, or provide two codepaths // (use scanf #if EMULATE_SECURE_CRT, otherwise scanf_s). #define scanf_s scanf #define wscanf_s wscanf #define fscanf_s fscanf #define fwscanf_s fwscanf #define sscanf_s sscanf #define swscanf_s swscanf #endif // #if EMULATE_SECURE_CRT #endif // #ifndef INCLUDED_SECURE_CRT Index: ps/trunk/source/lib/sysdep/stl.h =================================================================== --- ps/trunk/source/lib/sysdep/stl.h (revision 24226) +++ ps/trunk/source/lib/sysdep/stl.h (revision 24227) @@ -1,81 +1,75 @@ /* Copyright (C) 2013 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. */ /* * fixes for various STL implementations */ #ifndef INCLUDED_STL #define INCLUDED_STL #include "lib/config.h" #include "compiler.h" #include // indirectly pull in bits/c++config.h on Linux, so __GLIBCXX__ is defined // detect STL version // .. Dinkumware #if MSC_VERSION # include // defines _CPPLIB_VER #endif #if defined(_CPPLIB_VER) # define STL_DINKUMWARE _CPPLIB_VER #else # define STL_DINKUMWARE 0 #endif // .. GCC #if defined(__GLIBCPP__) # define STL_GCC __GLIBCPP__ #elif defined(__GLIBCXX__) # define STL_GCC __GLIBCXX__ #else # define STL_GCC 0 #endif // .. ICC #if defined(__INTEL_CXXLIB_ICC) # define STL_ICC __INTEL_CXXLIB_ICC #else # define STL_ICC 0 #endif // disable (slow!) iterator checks in release builds (unless someone already defined this) #if STL_DINKUMWARE && defined(NDEBUG) && !defined(_SECURE_SCL) # define _SECURE_SCL 0 #endif // pass "disable exceptions" setting on to the STL #if CONFIG_DISABLE_EXCEPTIONS # if STL_DINKUMWARE # define _HAS_EXCEPTIONS 0 # else # define STL_NO_EXCEPTIONS # endif #endif - -// OS X - fix some stream template instantiations that break 10.5 compatibility on newer SDKs -#if OS_MACOSX -# include "os/osx/osx_stl_fixes.h" -#endif - #endif // #ifndef INCLUDED_STL Index: ps/trunk/source/maths/Quaternion.cpp =================================================================== --- ps/trunk/source/maths/Quaternion.cpp (revision 24226) +++ ps/trunk/source/maths/Quaternion.cpp (revision 24227) @@ -1,314 +1,316 @@ /* Copyright (C) 2009 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 "Quaternion.h" + #include "MathUtil.h" +#include "Matrix3D.h" const float EPSILON=0.0001f; CQuaternion::CQuaternion() : m_W(1) { } CQuaternion::CQuaternion(float x, float y, float z, float w) : m_V(x, y, z), m_W(w) { } CQuaternion CQuaternion::operator + (const CQuaternion &quat) const { CQuaternion Temp; Temp.m_W = m_W + quat.m_W; Temp.m_V = m_V + quat.m_V; return Temp; } CQuaternion &CQuaternion::operator += (const CQuaternion &quat) { *this = *this + quat; return *this; } CQuaternion CQuaternion::operator - (const CQuaternion &quat) const { CQuaternion Temp; Temp.m_W = m_W - quat.m_W; Temp.m_V = m_V - quat.m_V; return Temp; } CQuaternion &CQuaternion::operator -= (const CQuaternion &quat) { *this = *this - quat; return *this; } CQuaternion CQuaternion::operator * (const CQuaternion &quat) const { CQuaternion Temp; Temp.m_W = (m_W * quat.m_W) - (m_V.Dot(quat.m_V)); Temp.m_V = (m_V.Cross(quat.m_V)) + (quat.m_V * m_W) + (m_V * quat.m_W); return Temp; } CQuaternion &CQuaternion::operator *= (const CQuaternion &quat) { *this = *this * quat; return *this; } CQuaternion CQuaternion::operator * (float factor) const { CQuaternion Temp; Temp.m_W = m_W * factor; Temp.m_V = m_V * factor; return Temp; } float CQuaternion::Dot(const CQuaternion& quat) const { return m_V.X * quat.m_V.X + m_V.Y * quat.m_V.Y + m_V.Z * quat.m_V.Z + m_W * quat.m_W; } void CQuaternion::FromEulerAngles (float x, float y, float z) { float cr, cp, cy; float sr, sp, sy; CQuaternion QRoll, QPitch, QYaw; cr = cosf(x * 0.5f); cp = cosf(y * 0.5f); cy = cosf(z * 0.5f); sr = sinf(x * 0.5f); sp = sinf(y * 0.5f); sy = sinf(z * 0.5f); QRoll.m_V = CVector3D(sr, 0, 0); QRoll.m_W = cr; QPitch.m_V = CVector3D(0, sp, 0); QPitch.m_W = cp; QYaw.m_V = CVector3D(0, 0, sy); QYaw.m_W = cy; (*this) = QYaw * QPitch * QRoll; } CVector3D CQuaternion::ToEulerAngles() { float heading, attitude, bank; float sqw = m_W * m_W; float sqx = m_V.X*m_V.X; float sqy = m_V.Y*m_V.Y; float sqz = m_V.Z*m_V.Z; float unit = sqx + sqy + sqz + sqw; // if normalised is one, otherwise is correction factor float test = m_V.X*m_V.Y + m_V.Z*m_W; if (test > (.5f-EPSILON)*unit) { // singularity at north pole heading = 2 * atan2( m_V.X, m_W); attitude = (float)M_PI/2; bank = 0; } else if (test < (-.5f+EPSILON)*unit) { // singularity at south pole heading = -2 * atan2(m_V.X, m_W); attitude = -(float)M_PI/2; bank = 0; } else { heading = atan2(2.f * (m_V.X*m_V.Y + m_V.Z*m_W),(sqx - sqy - sqz + sqw)); bank = atan2(2.f * (m_V.Y*m_V.Z + m_V.X*m_W),(-sqx - sqy + sqz + sqw)); attitude = asin(-2.f * (m_V.X*m_V.Z - m_V.Y*m_W)); } return CVector3D(bank, attitude, heading); } CMatrix3D CQuaternion::ToMatrix () const { CMatrix3D result; ToMatrix(result); return result; } void CQuaternion::ToMatrix(CMatrix3D& result) const { float wx, wy, wz, xx, xy, xz, yy, yz, zz; // calculate coefficients xx = m_V.X * m_V.X * 2.f; xy = m_V.X * m_V.Y * 2.f; xz = m_V.X * m_V.Z * 2.f; yy = m_V.Y * m_V.Y * 2.f; yz = m_V.Y * m_V.Z * 2.f; zz = m_V.Z * m_V.Z * 2.f; wx = m_W * m_V.X * 2.f; wy = m_W * m_V.Y * 2.f; wz = m_W * m_V.Z * 2.f; result._11 = 1.0f - (yy + zz); result._12 = xy - wz; result._13 = xz + wy; result._14 = 0; result._21 = xy + wz; result._22 = 1.0f - (xx + zz); result._23 = yz - wx; result._24 = 0; result._31 = xz - wy; result._32 = yz + wx; result._33 = 1.0f - (xx + yy); result._34 = 0; result._41 = 0; result._42 = 0; result._43 = 0; result._44 = 1; } void CQuaternion::Slerp(const CQuaternion& from, const CQuaternion& to, float ratio) { float to1[4]; float omega, cosom, sinom, scale0, scale1; // calc cosine cosom = from.Dot(to); // adjust signs (if necessary) if (cosom < 0.0) { cosom = -cosom; to1[0] = -to.m_V.X; to1[1] = -to.m_V.Y; to1[2] = -to.m_V.Z; to1[3] = -to.m_W; } else { to1[0] = to.m_V.X; to1[1] = to.m_V.Y; to1[2] = to.m_V.Z; to1[3] = to.m_W; } // calculate coefficients if ((1.0f - cosom) > EPSILON) { // standard case (slerp) omega = acosf(cosom); sinom = sinf(omega); scale0 = sinf((1.0f - ratio) * omega) / sinom; scale1 = sinf(ratio * omega) / sinom; } else { // "from" and "to" quaternions are very close // ... so we can do a linear interpolation scale0 = 1.0f - ratio; scale1 = ratio; } // calculate final values m_V.X = scale0 * from.m_V.X + scale1 * to1[0]; m_V.Y = scale0 * from.m_V.Y + scale1 * to1[1]; m_V.Z = scale0 * from.m_V.Z + scale1 * to1[2]; m_W = scale0 * from.m_W + scale1 * to1[3]; } void CQuaternion::Nlerp(const CQuaternion& from, const CQuaternion& to, float ratio) { float c = from.Dot(to); if (c < 0.f) *this = from - (to + from) * ratio; else *this = from + (to - from) * ratio; Normalize(); } /////////////////////////////////////////////////////////////////////////////////////////////// // FromAxisAngle: create a quaternion from axis/angle representation of a rotation void CQuaternion::FromAxisAngle(const CVector3D& axis, float angle) { float sinHalfTheta=(float) sin(angle/2); float cosHalfTheta=(float) cos(angle/2); m_V.X=axis.X*sinHalfTheta; m_V.Y=axis.Y*sinHalfTheta; m_V.Z=axis.Z*sinHalfTheta; m_W=cosHalfTheta; } /////////////////////////////////////////////////////////////////////////////////////////////// // ToAxisAngle: convert the quaternion to axis/angle representation of a rotation void CQuaternion::ToAxisAngle(CVector3D& axis, float& angle) { CQuaternion q = *this; q.Normalize(); angle = acosf(q.m_W) * 2.f; float sin_a = sqrtf(1.f - q.m_W * q.m_W); if (fabsf(sin_a) < 0.0005f) sin_a = 1.f; axis.X = q.m_V.X / sin_a; axis.Y = q.m_V.Y / sin_a; axis.Z = q.m_V.Z / sin_a; } /////////////////////////////////////////////////////////////////////////////////////////////// // Normalize: normalize this quaternion void CQuaternion::Normalize() { float lensqrd=SQR(m_V.X)+SQR(m_V.Y)+SQR(m_V.Z)+SQR(m_W); if (lensqrd>0) { float invlen=1.0f/sqrtf(lensqrd); m_V*=invlen; m_W*=invlen; } } /////////////////////////////////////////////////////////////////////////////////////////////// CVector3D CQuaternion::Rotate(const CVector3D& vec) const { // v' = q * v * q^-1 // (where v is the quat. with w=0, xyz=vec) return (*this * CQuaternion(vec.X, vec.Y, vec.Z, 0.f) * GetInverse()).m_V; } CQuaternion CQuaternion::GetInverse() const { // (x,y,z,w)^-1 = (-x/l^2, -y/l^2, -z/l^2, w/l^2) where l^2=x^2+y^2+z^2+w^2 // Since we're only using quaternions for rotation, they should always have unit // length, so assume l=1 return CQuaternion(-m_V.X, -m_V.Y, -m_V.Z, m_W); } Index: ps/trunk/source/maths/Quaternion.h =================================================================== --- ps/trunk/source/maths/Quaternion.h (revision 24226) +++ ps/trunk/source/maths/Quaternion.h (revision 24227) @@ -1,76 +1,77 @@ /* Copyright (C) 2009 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_QUATERNION #define INCLUDED_QUATERNION -#include "Matrix3D.h" #include "Vector3D.h" +class CMatrix3D; + class CQuaternion { public: CVector3D m_V; float m_W; public: CQuaternion(); CQuaternion(float x, float y, float z, float w); CQuaternion operator + (const CQuaternion &quat) const; CQuaternion &operator += (const CQuaternion &quat); CQuaternion operator - (const CQuaternion &quat) const; CQuaternion &operator -= (const CQuaternion &quat); CQuaternion operator * (const CQuaternion &quat) const; CQuaternion &operator *= (const CQuaternion &quat); CQuaternion operator * (float factor) const; float Dot(const CQuaternion& quat) const; void FromEulerAngles (float x, float y, float z); CVector3D ToEulerAngles(); // Convert the quaternion to matrix CMatrix3D ToMatrix() const; void ToMatrix(CMatrix3D& result) const; // Sphere interpolation void Slerp(const CQuaternion& from, const CQuaternion& to, float ratio); // Normalised linear interpolation void Nlerp(const CQuaternion& from, const CQuaternion& to, float ratio); // Create a quaternion from axis/angle representation of a rotation void FromAxisAngle(const CVector3D& axis, float angle); // Convert the quaternion to axis/angle representation of a rotation void ToAxisAngle(CVector3D& axis, float& angle); // Normalize this quaternion void Normalize(); // Rotate a vector by this quaternion. Assumes the quaternion is normalised. CVector3D Rotate(const CVector3D& vec) const; // Calculate q^-1. Assumes the quaternion is normalised. CQuaternion GetInverse() const; }; #endif Index: ps/trunk/source/network/NetServerTurnManager.cpp =================================================================== --- ps/trunk/source/network/NetServerTurnManager.cpp (revision 24226) +++ ps/trunk/source/network/NetServerTurnManager.cpp (revision 24227) @@ -1,201 +1,202 @@ /* 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 "NetMessage.h" #include "NetServerTurnManager.h" #include "NetServer.h" #include "NetSession.h" +#include "lib/utf8.h" #include "ps/CLogger.h" #include "simulation2/system/TurnManager.h" #if 0 #define NETSERVERTURN_LOG(...) debug_printf(__VA_ARGS__) #else #define NETSERVERTURN_LOG(...) #endif CNetServerTurnManager::CNetServerTurnManager(CNetServerWorker& server) : m_NetServer(server), m_ReadyTurn(1), m_TurnLength(DEFAULT_TURN_LENGTH_MP), m_HasSyncError(false) { // The first turn we will actually execute is number 2, // so store dummy values into the saved lengths list m_SavedTurnLengths.push_back(0); m_SavedTurnLengths.push_back(0); } void CNetServerTurnManager::NotifyFinishedClientCommands(CNetServerSession& session, u32 turn) { int client = session.GetHostID(); NETSERVERTURN_LOG("NotifyFinishedClientCommands(client=%d, turn=%d)\n", client, turn); // Must be a client we've already heard of ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end()); // Clients must advance one turn at a time if (turn != m_ClientsReady[client] + 1) { LOGERROR("NotifyFinishedClientCommands: Client %d (%s) is ready for turn %d, but expected %d", client, utf8_from_wstring(session.GetUserName()).c_str(), turn, m_ClientsReady[client] + 1); session.Disconnect(NDR_INCORRECT_READY_TURN_COMMANDS); } m_ClientsReady[client] = turn; // Check whether this was the final client to become ready CheckClientsReady(); } void CNetServerTurnManager::CheckClientsReady() { // See if all clients (including self) are ready for a new turn for (const std::pair& clientReady : m_ClientsReady) { NETSERVERTURN_LOG(" %d: %d <=? %d\n", clientReady.first, clientReady.second, m_ReadyTurn); if (clientReady.second <= m_ReadyTurn) return; // wasn't ready for m_ReadyTurn+1 } ++m_ReadyTurn; NETSERVERTURN_LOG("CheckClientsReady: ready for turn %d\n", m_ReadyTurn); // Tell all clients that the next turn is ready CEndCommandBatchMessage msg; msg.m_TurnLength = m_TurnLength; msg.m_Turn = m_ReadyTurn; m_NetServer.Broadcast(&msg, { NSS_INGAME }); ENSURE(m_SavedTurnLengths.size() == m_ReadyTurn); m_SavedTurnLengths.push_back(m_TurnLength); } void CNetServerTurnManager::NotifyFinishedClientUpdate(CNetServerSession& session, u32 turn, const CStr& hash) { int client = session.GetHostID(); const CStrW& playername = session.GetUserName(); // Clients must advance one turn at a time if (turn != m_ClientsSimulated[client] + 1) { LOGERROR("NotifyFinishedClientUpdate: Client %d (%s) is ready for turn %d, but expected %d", client, utf8_from_wstring(playername).c_str(), turn, m_ClientsReady[client] + 1); session.Disconnect(NDR_INCORRECT_READY_TURN_SIMULATED); } m_ClientsSimulated[client] = turn; // Check for OOS only if in sync if (m_HasSyncError) return; m_ClientPlayernames[client] = playername; m_ClientStateHashes[turn][client] = hash; // Find the newest turn which we know all clients have simulated u32 newest = std::numeric_limits::max(); for (const std::pair& clientSimulated : m_ClientsSimulated) if (clientSimulated.second < newest) newest = clientSimulated.second; // For every set of state hashes that all clients have simulated, check for OOS for (const std::pair>& clientStateHash : m_ClientStateHashes) { if (clientStateHash.first > newest) break; // Assume the host is correct (maybe we should choose the most common instead to help debugging) std::string expected = clientStateHash.second.begin()->second; // Find all players that are OOS on that turn std::vector OOSPlayerNames; for (const std::pair& hashPair : clientStateHash.second) { NETSERVERTURN_LOG("sync check %d: %d = %hs\n", it->first, cit->first, Hexify(cit->second).c_str()); if (hashPair.second != expected) { // Oh no, out of sync m_HasSyncError = true; OOSPlayerNames.push_back(m_ClientPlayernames[hashPair.first]); } } // Tell everyone about it if (m_HasSyncError) { CSyncErrorMessage msg; msg.m_Turn = clientStateHash.first; msg.m_HashExpected = expected; for (const CStrW& playername : OOSPlayerNames) { CSyncErrorMessage::S_m_PlayerNames h; h.m_Name = playername; msg.m_PlayerNames.push_back(h); } m_NetServer.Broadcast(&msg, { NSS_INGAME }); break; } } // Delete the saved hashes for all turns that we've already verified m_ClientStateHashes.erase(m_ClientStateHashes.begin(), m_ClientStateHashes.lower_bound(newest+1)); } void CNetServerTurnManager::InitialiseClient(int client, u32 turn) { NETSERVERTURN_LOG("InitialiseClient(client=%d, turn=%d)\n", client, turn); ENSURE(m_ClientsReady.find(client) == m_ClientsReady.end()); m_ClientsReady[client] = turn + 1; m_ClientsSimulated[client] = turn; } void CNetServerTurnManager::UninitialiseClient(int client) { NETSERVERTURN_LOG("UninitialiseClient(client=%d)\n", client); ENSURE(m_ClientsReady.find(client) != m_ClientsReady.end()); m_ClientsReady.erase(client); m_ClientsSimulated.erase(client); // Check whether we're ready for the next turn now that we're not // waiting for this client any more CheckClientsReady(); } void CNetServerTurnManager::SetTurnLength(u32 msecs) { m_TurnLength = msecs; } u32 CNetServerTurnManager::GetSavedTurnLength(u32 turn) { ENSURE(turn <= m_ReadyTurn); return m_SavedTurnLengths.at(turn); } Index: ps/trunk/source/ps/FileIo.h =================================================================== --- ps/trunk/source/ps/FileIo.h (revision 24226) +++ ps/trunk/source/ps/FileIo.h (revision 24227) @@ -1,149 +1,151 @@ /* Copyright (C) 2009 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 . */ /* * endian-safe binary file IO helpers. */ // the file format has passing similarity to IFF. note however that // "chunks" aren't identified by FOURCCs; only the file header is // so marked. // all > 8-bit integers are stored in little-endian format // (hence the _le suffix). however, the caller is responsible for // swapping their raw data before passing it to PackRaw. a convenience // routine is provided for the common case of storing size_t. #ifndef INCLUDED_FILEPACKER #define INCLUDED_FILEPACKER -#include "CStr.h" #include "lib/file/vfs/vfs_path.h" #include "ps/Filesystem.h" // WriteBuffer #include "ps/Errors.h" + +class CStr8; + ERROR_GROUP(File); ERROR_TYPE(File, OpenFailed); ERROR_TYPE(File, WriteFailed); ERROR_TYPE(File, InvalidType); ERROR_TYPE(File, InvalidVersion); ERROR_TYPE(File, ReadFailed); ERROR_TYPE(File, UnexpectedEOF); /** * helper class for writing binary files. this is basically a * resizable buffer that allows adding raw data and strings; * upon calling Write(), everything is written out to disk. **/ class CFilePacker { public: /** * adds version and signature (i.e. the header) to the buffer. * this means Write() can write the entire buffer to file in one go, * which is simpler and more efficient than writing in pieces. **/ CFilePacker(u32 version, const char magic[4]); ~CFilePacker(); /** * write out to file all packed data added so far. * it's safe to call this multiple times, but typically would * only be done once. **/ void Write(const VfsPath& filename); /** * pack given number of bytes onto the end of the data stream **/ void PackRaw(const void* rawData, size_t rawDataSize); /** * convenience: convert a number (almost always a size type) to * little-endian u32 and pack that. **/ void PackSize(size_t value); /** * pack a string onto the end of the data stream * (encoded as a 32-bit length followed by the characters) **/ - void PackString(const CStr& str); + void PackString(const CStr8& str); private: /** * the output data stream built during pack operations. * contains the header, so we can write this out in one go. **/ WriteBuffer m_writeBuffer; }; /** * helper class for reading binary files **/ class CFileUnpacker { public: CFileUnpacker(); ~CFileUnpacker(); /** * open and read in given file, check magic bits against those given; * throw variety of exceptions if open failed / version incorrect, etc. **/ void Read(const VfsPath& filename, const char magic[4]); /** * @return version number that was stored in the file's header. **/ u32 GetVersion() const { return m_version; } /** * unpack given number of bytes from the input into the given array. * throws PSERROR_File_UnexpectedEOF if the end of the data stream is * reached before the given number of bytes have been read. **/ void UnpackRaw(void* rawData, size_t rawDataSize); /** * use UnpackRaw to retrieve 32-bits; returns their value as size_t * after converting from little endian to native byte order. **/ size_t UnpackSize(); /** * unpack a string from the raw data stream. * @param result is assigned a newly constructed CStr8 holding the * string read from the input stream. **/ void UnpackString(CStr8& result); private: // the data read from file and used during unpack operations shared_ptr m_buf; size_t m_bufSize; size_t m_unpackPos; /// current unpack position in stream u32 m_version; /// version that was stored in the file header }; #endif Index: ps/trunk/source/ps/Filesystem.h =================================================================== --- ps/trunk/source/ps/Filesystem.h (revision 24226) +++ ps/trunk/source/ps/Filesystem.h (revision 24227) @@ -1,108 +1,109 @@ /* Copyright (C) 2015 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_PS_FILESYSTEM #define INCLUDED_PS_FILESYSTEM #include "lib/file/file.h" #include "lib/file/io/io.h" #include "lib/file/io/write_buffer.h" #include "lib/file/vfs/vfs_util.h" -#include "ps/CStr.h" #include "ps/Errors.h" +class CStr8; + extern PIVFS g_VFS; extern bool VfsFileExists(const VfsPath& pathname); extern bool VfsDirectoryExists(const VfsPath& pathname); /** * callback function type for file change notifications */ typedef Status (*FileReloadFunc)(void* param, const VfsPath& path); /** * register a callback function to be called by ReloadChangedFiles */ void RegisterFileReloadFunc(FileReloadFunc func, void* obj); /** * delete a callback function registered with RegisterFileReloadFunc * (removes any with the same func and obj) */ void UnregisterFileReloadFunc(FileReloadFunc func, void* obj); /** * poll for directory change notifications and reload all affected files. * must be called regularly (e.g. once a frame), else notifications * may be lost. * note: polling is much simpler than asynchronous notifications. **/ extern Status ReloadChangedFiles(); /** * Helper function to handle API differences between Boost Filesystem v2 and v3 */ std::wstring GetWstringFromWpath(const fs::wpath& path); ERROR_GROUP(CVFSFile); ERROR_TYPE(CVFSFile, LoadFailed); ERROR_TYPE(CVFSFile, AlreadyLoaded); /** * Reads a file, then gives read-only access to the contents */ class CVFSFile { public: CVFSFile(); ~CVFSFile(); /** * Returns either PSRETURN_OK or PSRETURN_CVFSFile_LoadFailed * @note Dies if the file has already been successfully loaded * @param log Whether to log a failure to load a file */ PSRETURN Load(const PIVFS& vfs, const VfsPath& filename, bool log = true); /** * Returns buffer of this file as a stream of bytes * @note file must have been successfully loaded */ const u8* GetBuffer() const; size_t GetBufferSize() const; /** * Returns contents of file as a string * @note file must have been successfully loaded */ - CStr GetAsString() const; + CStr8 GetAsString() const; /** * Returns contents of a UTF-8 encoded file as a string with optional BOM removed * @note file must have been successfully loaded */ - CStr DecodeUTF8() const; + CStr8 DecodeUTF8() const; private: shared_ptr m_Buffer; size_t m_BufferSize; }; #endif // #ifndef INCLUDED_PS_FILESYSTEM Index: ps/trunk/source/ps/GUID.h =================================================================== --- ps/trunk/source/ps/GUID.h (revision 24226) +++ ps/trunk/source/ps/GUID.h (revision 24227) @@ -1,25 +1,25 @@ /* Copyright (C) 2013 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_GUID #define INCLUDED_GUID -#include "ps/CStr.h" +class CStr8; -CStr ps_generate_guid(void); +CStr8 ps_generate_guid(void); #endif Index: ps/trunk/source/ps/Game.h =================================================================== --- ps/trunk/source/ps/Game.h (revision 24226) +++ ps/trunk/source/ps/Game.h (revision 24227) @@ -1,226 +1,227 @@ /* Copyright (C) 2020 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_GAME #define INCLUDED_GAME #include +#include "ps/CStr.h" #include "ps/Errors.h" #include "ps/Filesystem.h" #include "scriptinterface/ScriptTypes.h" #include "simulation2/helpers/Player.h" class CWorld; class CSimulation2; class CGameView; class CTurnManager; class IReplayLogger; struct CColor; /** * The container that holds the rules, resources and attributes of the game. * The CGame object is responsible for creating a game that is defined by * a set of attributes provided. The CGame object is also responsible for * maintaining the relations between CPlayer and CWorld, CSimulation and CWorld. **/ class CGame { NONCOPYABLE(CGame); /** * pointer to the CWorld object representing the game world. **/ CWorld *m_World; /** * pointer to the CSimulation2 object operating on the game world. **/ CSimulation2 *m_Simulation2; /** * pointer to the CGameView object representing the view into the game world. **/ CGameView *m_GameView; /** * the game has been initialized and ready for use if true. **/ bool m_GameStarted; /** * Timescale multiplier for simulation rate. **/ float m_SimRate; /** * Index assigned to the current player. * 1-8 to control players, 0 for gaia, -1 for observer. */ player_id_t m_PlayerID; /** * Differs from m_PlayerID if a defeated player or observer views another player. */ player_id_t m_ViewedPlayerID; CTurnManager* m_TurnManager; public: CGame(bool replayLog); ~CGame(); /** * the game is paused and no updates will be performed if true. **/ bool m_Paused; void StartGame(JS::MutableHandleValue attribs, const std::string& savedState); PSRETURN ReallyStartGame(); bool StartVisualReplay(const OsPath& replayPath); /** * Periodic heartbeat that controls the process. performs all per-frame updates. * Simulation update is called and game status update is called. * * @param deltaRealTime Elapsed real time since last beat/frame, in seconds. * @param doInterpolate Perform graphics interpolation if true. * @return bool false if it can't keep up with the desired simulation rate * indicating that you might want to render less frequently. */ void Update(const double deltaRealTime, bool doInterpolate = true); void Interpolate(float simFrameLength, float realFrameLength); int GetPlayerID(); void SetPlayerID(player_id_t playerID); int GetViewedPlayerID(); void SetViewedPlayerID(player_id_t playerID); /** * Check if the game is finished by testing if there's a winner. * It is used to end a non visual autostarted game. * * @return true if there's a winner, false otherwise. */ bool IsGameFinished() const; /** * Retrieving player colors from scripts is slow, so this updates an * internal cache of all players' colors. * Call this just before rendering, so it will always have the latest * colors. */ void CachePlayerColors(); CColor GetPlayerColor(player_id_t player) const; /** * Get m_GameStarted. * * @return bool the value of m_GameStarted. **/ inline bool IsGameStarted() const { return m_GameStarted; } /** * Get m_IsVisualReplay. * * @return bool the value of m_IsVisualReplay. **/ inline bool IsVisualReplay() const { return m_IsVisualReplay; } /** * Get the pointer to the game world object. * * @return CWorld * the value of m_World. **/ inline CWorld *GetWorld() { return m_World; } /** * Get the pointer to the game view object. * * @return CGameView * the value of m_GameView. **/ inline CGameView *GetView() { return m_GameView; } /** * Get the pointer to the simulation2 object. * * @return CSimulation2 * the value of m_Simulation2. **/ inline CSimulation2 *GetSimulation2() { return m_Simulation2; } /** * Set the simulation scale multiplier. * * @param simRate Float value to set m_SimRate to. * Because m_SimRate is also used to * scale TimeSinceLastFrame it must be * clamped to 0.0f. **/ inline void SetSimRate(float simRate) { if (isfinite(simRate)) m_SimRate = std::max(simRate, 0.0f); } inline float GetSimRate() const { return m_SimRate; } inline OsPath GetReplayPath() const { return m_ReplayPath; } /** * Replace the current turn manager. * This class will take ownership of the pointer. */ void SetTurnManager(CTurnManager* turnManager); CTurnManager* GetTurnManager() const { return m_TurnManager; } IReplayLogger& GetReplayLogger() const { return *m_ReplayLogger; } private: static const CStr EventNameSimulationUpdate; void RegisterInit(const JS::HandleValue attribs, const std::string& savedState); IReplayLogger* m_ReplayLogger; std::vector m_PlayerColors; int LoadInitialState(); std::string m_InitialSavedState; // valid between RegisterInit and LoadInitialState bool m_IsSavedGame; // true if loading a saved game; false for a new game int LoadVisualReplayData(); OsPath m_ReplayPath; bool m_IsVisualReplay; std::istream* m_ReplayStream; u32 m_FinalReplayTurn; }; extern CGame *g_Game; #endif Index: ps/trunk/source/ps/XML/XMLWriter.h =================================================================== --- ps/trunk/source/ps/XML/XMLWriter.h (revision 24226) +++ ps/trunk/source/ps/XML/XMLWriter.h (revision 24227) @@ -1,126 +1,126 @@ /* 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_XMLWRITER #define INCLUDED_XMLWRITER /* * *System for writing simple XML files, with human-readable formatting. * *Example usage: * * XMLWriter_File exampleFile; * { * XMLWriter_Element scenarioTag (exampleFile,"Scenario"); * { * XMLWriter_Element entitiesTag (exampleFile,"Entities"); * for (...) * { * XMLWriter_Element entityTag (exampleFile,"Entity"); * { * XMLWriter_Element templateTag (exampleFile,"Template"); * templateTag.Text(entity.name); * } * // Or equivalently: * templateTag.Setting("Template", entity.name); * { * XMLWriter_Element positionTag (exampleFile,"Position"); * positionTag.Attribute("x", entity.x); * positionTag.Attribute("y", entity.y); * positionTag.Attribute("z", entity.z); * } * { * XMLWriter_Element orientationTag (exampleFile,"Orientation"); * orientationTag.Attribute("angle", entity.angle); * } * } * } * } * exampleFile.StoreVFS(g_VFS, "/test.xml"); * * In general, "{ XML_Element(name); ... }" means " ... " -- the * scoping braces are important to indicate where an element ends. If you don't put * them the tag won't be closed until the object's destructor is called, usually * when it goes out of scope. * xml_element_.Attribute/xml_element_.Setting are templated. To support more types, alter the * end of XMLWriter.cpp. */ -#include "ps/CStr.h" #include "lib/file/vfs/vfs.h" +class CStr8; class XMBElement; class XMBFile; class XMLWriter_Element; class XMLWriter_File { public: XMLWriter_File(); void SetPrettyPrint(bool enabled) { m_PrettyPrint = enabled; } void Comment(const char* text); void XMB(const XMBFile& file); bool StoreVFS(const PIVFS& vfs, const VfsPath& pathname); - const CStr& GetOutput(); + const CStr8& GetOutput(); private: friend class XMLWriter_Element; void ElementXMB(const XMBFile& file, XMBElement el); void ElementStart(XMLWriter_Element* element, const char* name); void ElementText(const char* text, bool cdata); template void ElementAttribute(const char* name, const T& value, bool newelement); void ElementClose(); void ElementEnd(const char* name, int type); - CStr Indent(); + CStr8 Indent(); bool m_PrettyPrint; - CStr m_Data; + CStr8 m_Data; int m_Indent; XMLWriter_Element* m_LastElement; }; class XMLWriter_Element { public: XMLWriter_Element(XMLWriter_File& file, const char* name); ~XMLWriter_Element(); template void Text(constCharPtr text, bool cdata); template void Attribute(const char* name, T value) { m_File->ElementAttribute(name, value, false); } template void Setting(const char* name, T value) { m_File->ElementAttribute(name, value, true); } void Close(int type); private: friend class XMLWriter_File; XMLWriter_File* m_File; - CStr m_Name; + CStr8 m_Name; int m_Type; }; #endif // INCLUDED_XMLWRITER Index: ps/trunk/source/scriptinterface/ScriptForward.h =================================================================== --- ps/trunk/source/scriptinterface/ScriptForward.h (nonexistent) +++ ps/trunk/source/scriptinterface/ScriptForward.h (revision 24227) @@ -0,0 +1,26 @@ +/* Copyright (C) 2020 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_SCRIPTFORWARD +#define INCLUDED_SCRIPTFORWARD + +#include "js/TypeDecls.h" + +class ScriptInterface; +class ScriptRequest; + +#endif // INCLUDED_SCRIPTFORWARD Index: ps/trunk/source/simulation2/components/ICmpObstructionManager.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstructionManager.h (revision 24226) +++ ps/trunk/source/simulation2/components/ICmpObstructionManager.h (revision 24227) @@ -1,548 +1,550 @@ /* 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_ICMPOBSTRUCTIONMANAGER #define INCLUDED_ICMPOBSTRUCTIONMANAGER #include "simulation2/system/Interface.h" -#include "simulation2/helpers/Pathfinding.h" - #include "maths/FixedVector2D.h" +#include "simulation2/helpers/Position.h" class IObstructionTestFilter; template class Grid; struct GridUpdateInformation; +using NavcellData = u16; +class PathfinderPassability; + /** * Obstruction manager: provides efficient spatial queries over objects in the world. * * The class deals with two types of shape: * "static" shapes, typically representing buildings, which are rectangles with a given * width and height and angle; * and "unit" shapes, representing units that can move around the world, which have a * radius and no rotation. (Units sometimes act as axis-aligned squares, sometimes * as approximately circles, due to the algorithm used by the short pathfinder.) * * Other classes (particularly ICmpObstruction) register shapes with this interface * and keep them updated. * * The @c Test functions provide exact collision tests. * The edge of a shape counts as 'inside' the shape, for the purpose of collisions. * The functions accept an IObstructionTestFilter argument, which can restrict the * set of shapes that are counted as collisions. * * Units can be marked as either moving or stationary, which simply determines whether * certain filters include or exclude them. * * The @c Rasterize function approximates the current set of shapes onto a 2D grid, * for use with tile-based pathfinding. */ class ICmpObstructionManager : public IComponent { public: /** * Standard representation for all types of shapes, for use with geometry processing code. */ struct ObstructionSquare { entity_pos_t x, z; // position of center CFixedVector2D u, v; // 'horizontal' and 'vertical' orthogonal unit vectors, representing orientation entity_pos_t hw, hh; // half width, half height of square }; /** * External identifiers for shapes. * (This is a struct rather than a raw u32 for type-safety.) */ struct tag_t { tag_t() : n(0) {} explicit tag_t(u32 n) : n(n) {} bool valid() const { return n != 0; } u32 n; }; /** * Boolean flags affecting the obstruction behaviour of a shape. */ enum EFlags { FLAG_BLOCK_MOVEMENT = (1 << 0), // prevents units moving through this shape FLAG_BLOCK_FOUNDATION = (1 << 1), // prevents foundations being placed on this shape FLAG_BLOCK_CONSTRUCTION = (1 << 2), // prevents buildings being constructed on this shape FLAG_BLOCK_PATHFINDING = (1 << 3), // prevents the tile pathfinder choosing paths through this shape FLAG_MOVING = (1 << 4), // indicates this unit is currently moving FLAG_DELETE_UPON_CONSTRUCTION = (1 << 5) // this entity is deleted when construction of a building placed on top of this entity starts }; /** * Bitmask of EFlag values. */ typedef u8 flags_t; /** * Set the bounds of the world. * Any point outside the bounds is considered obstructed. * @param x0,z0,x1,z1 Coordinates of the corners of the world */ virtual void SetBounds(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1) = 0; /** * Register a static shape. * * @param ent entity ID associated with this shape (or INVALID_ENTITY if none) * @param x,z coordinates of center, in world space * @param a angle of rotation (clockwise from +Z direction) * @param w width (size along X axis) * @param h height (size along Z axis) * @param flags a set of EFlags values * @param group primary control group of the shape. Must be a valid control group ID. * @param group2 Optional; secondary control group of the shape. Defaults to INVALID_ENTITY. * @return a valid tag for manipulating the shape * @see StaticShape */ virtual tag_t AddStaticShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h, flags_t flags, entity_id_t group, entity_id_t group2 = INVALID_ENTITY) = 0; /** * Register a unit shape. * * @param ent entity ID associated with this shape (or INVALID_ENTITY if none) * @param x,z coordinates of center, in world space * @param clearance pathfinding clearance of the unit (works as a radius) * @param flags a set of EFlags values * @param group control group (typically the owner entity, or a formation controller entity * - units ignore collisions with others in the same group) * @return a valid tag for manipulating the shape * @see UnitShape */ virtual tag_t AddUnitShape(entity_id_t ent, entity_pos_t x, entity_pos_t z, entity_pos_t clearance, flags_t flags, entity_id_t group) = 0; /** * Adjust the position and angle of an existing shape. * @param tag tag of shape (must be valid) * @param x X coordinate of center, in world space * @param z Z coordinate of center, in world space * @param a angle of rotation (clockwise from +Z direction); ignored for unit shapes */ virtual void MoveShape(tag_t tag, entity_pos_t x, entity_pos_t z, entity_angle_t a) = 0; /** * Set whether a unit shape is moving or stationary. * @param tag tag of shape (must be valid and a unit shape) * @param moving whether the unit is currently moving through the world or is stationary */ virtual void SetUnitMovingFlag(tag_t tag, bool moving) = 0; /** * Set the control group of a unit shape. * @param tag tag of shape (must be valid and a unit shape) * @param group control group entity ID */ virtual void SetUnitControlGroup(tag_t tag, entity_id_t group) = 0; /** * Sets the control group of a static shape. * @param tag Tag of the shape to set the control group for. Must be a valid and static shape tag. * @param group Control group entity ID. */ virtual void SetStaticControlGroup(tag_t tag, entity_id_t group, entity_id_t group2) = 0; /** * Remove an existing shape. The tag will be made invalid and must not be used after this. * @param tag tag of shape (must be valid) */ virtual void RemoveShape(tag_t tag) = 0; /** * Returns the distance from the obstruction to the point (px, pz), or -1 if the entity is out of the world. */ virtual fixed DistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const = 0; /** * Calculate the largest straight line distance between the entity and the point. */ virtual fixed MaxDistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const = 0; /** * Calculate the shortest distance between the entity and the target. */ virtual fixed DistanceToTarget(entity_id_t ent, entity_id_t target) const = 0; /** * Calculate the largest straight line distance between the entity and the target. */ virtual fixed MaxDistanceToTarget(entity_id_t ent, entity_id_t target) const = 0; /** * Calculate the shortest straight line distance between the source and the target */ virtual fixed DistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const = 0; /** * Calculate the largest straight line distance between the source and the target */ virtual fixed MaxDistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const = 0; /** * Check if the given entity is in range of the other point given those parameters. * @param maxRange - if -1, treated as infinite. */ virtual bool IsInPointRange(entity_id_t ent, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const = 0; /** * Check if the given entity is in range of the target given those parameters. * @param maxRange - if -1, treated as infinite. */ virtual bool IsInTargetRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const = 0; /** * Check if the given point is in range of the other point given those parameters. * @param maxRange - if -1, treated as infinite. */ virtual bool IsPointInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange) const = 0; /** * Check if the given shape is in range of the target shape given those parameters. * @param maxRange - if -1, treated as infinite. */ virtual bool AreShapesInRange(const ObstructionSquare& source, const ObstructionSquare& target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const = 0; /** * Collision test a flat-ended thick line against the current set of shapes. * The line caps extend by @p r beyond the end points. * Only intersections going from outside to inside a shape are counted. * @param filter filter to restrict the shapes that are counted * @param x0 X coordinate of line's first point * @param z0 Z coordinate of line's first point * @param x1 X coordinate of line's second point * @param z1 Z coordinate of line's second point * @param r radius (half width) of line * @param relaxClearanceForUnits whether unit-unit collisions should be more permissive. * @return true if there is a collision */ virtual bool TestLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r, bool relaxClearanceForUnits) const = 0; /** * Collision test a static square shape against the current set of shapes. * @param filter filter to restrict the shapes that are being tested against * @param x X coordinate of center * @param z Z coordinate of center * @param a angle of rotation (clockwise from +Z direction) * @param w width (size along X axis) * @param h height (size along Z axis) * @param out if non-NULL, all colliding shapes' entities will be added to this list * @return true if there is a collision */ virtual bool TestStaticShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h, std::vector* out) const = 0; /** * Collision test a unit shape against the current set of registered shapes, and optionally writes a list of the colliding * shapes' entities to an output list. * * @param filter filter to restrict the shapes that are being tested against * @param x X coordinate of shape's center * @param z Z coordinate of shape's center * @param clearance clearance of the shape's unit * @param out if non-NULL, all colliding shapes' entities will be added to this list * * @return true if there is a collision */ virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t clearance, std::vector* out) const = 0; /** * Convert the current set of shapes onto a navcell grid, for all passability classes contained in @p passClasses. * If @p fullUpdate is false, the function will only go through dirty shapes. * Shapes are expanded by the @p passClasses clearances, by ORing their masks onto the @p grid. */ virtual void Rasterize(Grid& grid, const std::vector& passClasses, bool fullUpdate) = 0; /** * Gets dirtiness information and resets it afterwards. Then it's the role of CCmpPathfinder * to pass the information to other components if needed. (AIs, etc.) * The return value is false if an update is unnecessary. */ virtual void UpdateInformations(GridUpdateInformation& informations) = 0; /** * Find all the obstructions that are inside (or partially inside) the given range. * @param filter filter to restrict the shapes that are counted * @param x0 X coordinate of left edge of range * @param z0 Z coordinate of bottom edge of range * @param x1 X coordinate of right edge of range * @param z1 Z coordinate of top edge of range * @param squares output list of obstructions */ virtual void GetObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector& squares) const = 0; virtual void GetStaticObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector& squares) const = 0; virtual void GetUnitObstructionsInRange(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, std::vector& squares) const = 0; virtual void GetStaticObstructionsOnObstruction(const ObstructionSquare& square, std::vector& out, const IObstructionTestFilter& filter) const = 0; /** * Returns the entity IDs of all unit shapes that intersect the given * obstruction square, filtering out using the given filter. * @param square the Obstruction squre we want to compare with. * @param out output list of obstructions * @param filter filter for the obstructing units * @param strict whether to be strict in the check or more permissive (ie rasterize more or less). Default false. */ virtual void GetUnitsOnObstruction(const ObstructionSquare& square, std::vector& out, const IObstructionTestFilter& filter, bool strict = false) const = 0; /** * Get the obstruction square representing the given shape. * @param tag tag of shape (must be valid) */ virtual ObstructionSquare GetObstruction(tag_t tag) const = 0; virtual ObstructionSquare GetUnitShapeObstruction(entity_pos_t x, entity_pos_t z, entity_pos_t clearance) const = 0; virtual ObstructionSquare GetStaticShapeObstruction(entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h) const = 0; /** * Set the passability to be restricted to a circular map. */ virtual void SetPassabilityCircular(bool enabled) = 0; virtual bool GetPassabilityCircular() const = 0; /** * Toggle the rendering of debug info. */ virtual void SetDebugOverlay(bool enabled) = 0; DECLARE_INTERFACE_TYPE(ObstructionManager) }; /** * Interface for ICmpObstructionManager @c Test functions to filter out unwanted shapes. */ class IObstructionTestFilter { public: typedef ICmpObstructionManager::tag_t tag_t; typedef ICmpObstructionManager::flags_t flags_t; virtual ~IObstructionTestFilter() {} /** * Return true if the shape with the specified parameters should be tested for collisions. * This is called for all shapes that would collide, and also for some that wouldn't. * * @param tag tag of shape being tested * @param flags set of EFlags for the shape * @param group the control group of the shape (typically the shape's unit, or the unit's formation controller, or 0) * @param group2 an optional secondary control group of the shape, or INVALID_ENTITY if none specified. Currently * exists only for static shapes. */ virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const = 0; }; /** * Obstruction test filter that will test against all shapes. */ class NullObstructionFilter : public IObstructionTestFilter { public: virtual bool TestShape(tag_t UNUSED(tag), flags_t UNUSED(flags), entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const { return true; } }; /** * Obstruction test filter that will test only against stationary (i.e. non-moving) shapes. */ class StationaryOnlyObstructionFilter : public IObstructionTestFilter { public: virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const { return !(flags & ICmpObstructionManager::FLAG_MOVING); } }; /** * Obstruction test filter that reject shapes in a given control group, * and rejects shapes that don't block unit movement, and optionally rejects moving shapes. */ class ControlGroupMovementObstructionFilter : public IObstructionTestFilter { bool m_AvoidMoving; entity_id_t m_Group; public: ControlGroupMovementObstructionFilter(bool avoidMoving, entity_id_t group) : m_AvoidMoving(avoidMoving), m_Group(group) {} virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const { if (group == m_Group || (group2 != INVALID_ENTITY && group2 == m_Group)) return false; if (!(flags & ICmpObstructionManager::FLAG_BLOCK_MOVEMENT)) return false; if ((flags & ICmpObstructionManager::FLAG_MOVING) && !m_AvoidMoving) return false; return true; } }; /** * Obstruction test filter that will test only against shapes that: * - are part of neither one of the specified control groups * - AND, depending on the value of the 'exclude' argument: * - have at least one of the specified flags set. * - OR have none of the specified flags set. * * The first (primary) control group to reject shapes from must be specified and valid. The secondary * control group to reject entities from may be set to INVALID_ENTITY to not use it. * * This filter is useful to e.g. allow foundations within the same control group to be placed and * constructed arbitrarily close together (e.g. for wall pieces that need to link up tightly). */ class SkipControlGroupsRequireFlagObstructionFilter : public IObstructionTestFilter { bool m_Exclude; entity_id_t m_Group; entity_id_t m_Group2; flags_t m_Mask; public: SkipControlGroupsRequireFlagObstructionFilter(bool exclude, entity_id_t group1, entity_id_t group2, flags_t mask) : m_Exclude(exclude), m_Group(group1), m_Group2(group2), m_Mask(mask) { Init(); } SkipControlGroupsRequireFlagObstructionFilter(entity_id_t group1, entity_id_t group2, flags_t mask) : m_Exclude(false), m_Group(group1), m_Group2(group2), m_Mask(mask) { Init(); } virtual bool TestShape(tag_t UNUSED(tag), flags_t flags, entity_id_t group, entity_id_t group2) const { // Don't test shapes that share one or more of our control groups. if (group == m_Group || group == m_Group2 || (group2 != INVALID_ENTITY && (group2 == m_Group || group2 == m_Group2))) return false; // If m_Exclude is true, don't test against shapes that have any of the // obstruction flags specified in m_Mask. if (m_Exclude) return (flags & m_Mask) == 0; // Otherwise, only include shapes that match at least one flag in m_Mask. return (flags & m_Mask) != 0; } private: void Init() { // the primary control group to filter out must be valid ENSURE(m_Group != INVALID_ENTITY); // for simplicity, if m_Group2 is INVALID_ENTITY (i.e. not used), then set it equal to m_Group // so that we have fewer special cases to consider in TestShape(). if (m_Group2 == INVALID_ENTITY) m_Group2 = m_Group; } }; /** * Obstruction test filter that will test only against shapes that: * - are part of both of the specified control groups * - AND have at least one of the specified flags set. * * The first (primary) control group to include shapes from must be specified and valid. * * This filter is useful for preventing entities with identical control groups * from colliding e.g. building a new wall segment on top of an existing wall) * * @todo This filter needs test cases. */ class SkipTagRequireControlGroupsAndFlagObstructionFilter : public IObstructionTestFilter { tag_t m_Tag; entity_id_t m_Group; entity_id_t m_Group2; flags_t m_Mask; public: SkipTagRequireControlGroupsAndFlagObstructionFilter(tag_t tag, entity_id_t group1, entity_id_t group2, flags_t mask) : m_Tag(tag), m_Group(group1), m_Group2(group2), m_Mask(mask) { ENSURE(m_Group != INVALID_ENTITY); } virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t group, entity_id_t group2) const { // To be included in testing, a shape must not have the specified tag, and must // match at least one of the flags in m_Mask, as well as both control groups. return (tag.n != m_Tag.n && (flags & m_Mask) != 0 && ((group == m_Group && group2 == m_Group2) || (group2 == m_Group && group == m_Group2))); } }; /** * Obstruction test filter that will test only against shapes that do not have the specified tag set. */ class SkipTagObstructionFilter : public IObstructionTestFilter { tag_t m_Tag; public: SkipTagObstructionFilter(tag_t tag) : m_Tag(tag) { } virtual bool TestShape(tag_t tag, flags_t UNUSED(flags), entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const { return tag.n != m_Tag.n; } }; /** * Obstruction test filter that will test only against shapes that: * - do not have the specified tag * - AND have at least one of the specified flags set. */ class SkipTagRequireFlagsObstructionFilter : public IObstructionTestFilter { tag_t m_Tag; flags_t m_Mask; public: SkipTagRequireFlagsObstructionFilter(tag_t tag, flags_t mask) : m_Tag(tag), m_Mask(mask) { } virtual bool TestShape(tag_t tag, flags_t flags, entity_id_t UNUSED(group), entity_id_t UNUSED(group2)) const { return (tag.n != m_Tag.n && (flags & m_Mask) != 0); } }; #endif // INCLUDED_ICMPOBSTRUCTIONMANAGER Index: ps/trunk/source/simulation2/components/tests/test_Position.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_Position.h (revision 24226) +++ ps/trunk/source/simulation2/components/tests/test_Position.h (revision 24227) @@ -1,232 +1,233 @@ /* Copyright (C) 2017 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 "simulation2/system/ComponentTest.h" +#include "maths/Matrix3D.h" #include "simulation2/components/ICmpPosition.h" #include "simulation2/components/ICmpWaterManager.h" class MockWater : public ICmpWaterManager { public: DEFAULT_MOCK_COMPONENT() virtual entity_pos_t GetWaterLevel(entity_pos_t UNUSED(x), entity_pos_t UNUSED(z)) const { return entity_pos_t::FromInt(100); } virtual float GetExactWaterLevel(float UNUSED(x), float UNUSED(z)) const { return 100.f; } virtual void RecomputeWaterData() { } virtual void SetWaterLevel(entity_pos_t UNUSED(h)) { } }; class TestCmpPosition : public CxxTest::TestSuite { public: void setUp() { CXeromyces::Startup(); } void tearDown() { CXeromyces::Terminate(); } static CFixedVector3D fixedvec(int x, int y, int z) { return CFixedVector3D(fixed::FromInt(x), fixed::FromInt(y), fixed::FromInt(z)); } void test_basic() { ComponentTestHelper test(g_ScriptContext); MockTerrain terrain; test.AddMock(SYSTEM_ENTITY, IID_Terrain, terrain); ICmpPosition* cmp = test.Add(CID_Position, "upright23false"); // Defaults TS_ASSERT(!cmp->IsInWorld()); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetRotation(), fixedvec(0, 0, 0)); // Change height offset cmp->SetHeightOffset(entity_pos_t::FromInt(10)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(10)); // Move out of world, while currently out of world cmp->MoveOutOfWorld(); TS_ASSERT(!cmp->IsInWorld()); // Jump into world cmp->JumpTo(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0)); TS_ASSERT(cmp->IsInWorld()); // Move out of world, while currently in world cmp->MoveOutOfWorld(); TS_ASSERT(!cmp->IsInWorld()); // Move into world cmp->MoveTo(entity_pos_t::FromInt(100), entity_pos_t::FromInt(200)); TS_ASSERT(cmp->IsInWorld()); // Position computed from move plus terrain TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 60, 200)); // Interpolated position should be constant TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(100, 60, 200)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(100, 60, 200)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(100, 60, 200)); // No TurnStart message, so this move doesn't affect the interpolation cmp->MoveTo(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0)); // Move smoothly to new position cmp->MoveTo(entity_pos_t::FromInt(200), entity_pos_t::FromInt(0)); // Position computed from move plus terrain TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(200, 60, 0)); // Interpolated position should vary, from original move into world to new move TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(100, 60, 200)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(150, 60, 100)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(200, 60, 0)); // Latch new position for interpolation CMessageTurnStart msg; test.HandleMessage(cmp, msg, false); // Move smoothly to new position cmp->MoveTo(entity_pos_t::FromInt(400), entity_pos_t::FromInt(300)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(200, 60, 0)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(300, 60, 150)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(400, 60, 300)); // Jump to new position cmp->JumpTo(entity_pos_t::FromInt(300), entity_pos_t::FromInt(100)); // Position computed from jump plus terrain TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(300, 60, 100)); // Interpolated position should be constant after jump TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(300, 60, 100)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(300, 60, 100)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(300, 60, 100)); // TODO: Test the rotation methods } void test_water() { ComponentTestHelper test(g_ScriptContext); MockTerrain terrain; test.AddMock(SYSTEM_ENTITY, IID_Terrain, terrain); MockWater water; test.AddMock(SYSTEM_ENTITY, IID_WaterManager, water); ICmpPosition* cmp = test.Add(CID_Position, "upright23true1"); // Move into the world, the fixed height uses the water level minus the float depth as a base cmp->JumpTo(entity_pos_t::FromInt(0), entity_pos_t::FromInt(0)); TS_ASSERT(cmp->IsInWorld()); TS_ASSERT(cmp->CanFloat()); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); // Change height offset, the fixed height changes too cmp->SetHeightOffset(entity_pos_t::FromInt(11)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(11)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(110)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(0, 110, 0)); // Move cmp->MoveTo(entity_pos_t::FromInt(100), entity_pos_t::FromInt(200)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.0f).GetTranslation(), CVector3D(0, 122, 0)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(0.5f).GetTranslation(), CVector3D(50, 116, 100)); TS_ASSERT_EQUALS(cmp->GetInterpolatedTransform(1.0f).GetTranslation(), CVector3D(100, 110, 200)); // Change fixed height, the height offset changes too cmp->SetHeightFixed(entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 122, 200)); // The entity can't float anymore, the fixed height is computed from the terrain base cmp->SetFloating(false); TS_ASSERT(!cmp->CanFloat()); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(73)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 73, 200)); // The entity can float again cmp->SetFloating(true); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 122, 200)); // Use non relative height, entity will not follow terrain/water height. TS_ASSERT(cmp->IsHeightRelative()); cmp->SetHeightRelative(false); TS_ASSERT(!cmp->IsHeightRelative()); cmp->SetHeightOffset(entity_pos_t::FromInt(11)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(11)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(110)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 110, 200)); cmp->SetHeightFixed(entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(23)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 122, 200)); // The entity can't float anymore and height is not relative, fixed height doesn't change cmp->SetFloating(false); TS_ASSERT(!cmp->CanFloat()); TS_ASSERT_EQUALS(cmp->GetHeightFixed(), entity_pos_t::FromInt(122)); TS_ASSERT_EQUALS(cmp->GetHeightOffset(), entity_pos_t::FromInt(72)); TS_ASSERT_EQUALS(cmp->GetPosition(), fixedvec(100, 122, 200)); } void test_serialize() { ComponentTestHelper test(g_ScriptContext); MockTerrain terrain; test.AddMock(SYSTEM_ENTITY, IID_Terrain, terrain); ICmpPosition* cmp = test.Add(CID_Position, "upright5false"); test.Roundtrip(); cmp->SetHeightOffset(entity_pos_t::FromInt(20)); cmp->SetXZRotation(entity_angle_t::FromInt(1), entity_angle_t::FromInt(2)); cmp->SetYRotation(entity_angle_t::FromInt(3)); test.Roundtrip(); cmp->JumpTo(entity_pos_t::FromInt(10), entity_pos_t::FromInt(20)); cmp->MoveTo(entity_pos_t::FromInt(123), entity_pos_t::FromInt(456)); test.Roundtrip(); } }; Index: ps/trunk/source/simulation2/helpers/Pathfinding.cpp =================================================================== --- ps/trunk/source/simulation2/helpers/Pathfinding.cpp (revision 24226) +++ ps/trunk/source/simulation2/helpers/Pathfinding.cpp (revision 24227) @@ -1,188 +1,190 @@ /* Copyright (C) 2018 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 "Pathfinding.h" #include "graphics/Terrain.h" -#include "Grid.h" +#include "ps/CLogger.h" +#include "simulation2/helpers/Grid.h" +#include "simulation2/system/ParamNode.h" namespace Pathfinding { bool CheckLineMovement(entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, pass_class_t passClass, const Grid& grid) { // We shouldn't allow lines between diagonally-adjacent navcells. // It doesn't matter whether we allow lines precisely along the edge // of an impassable navcell. // To rasterise the line: // If the line is (e.g.) aiming up-right, then we start at the navcell // containing the start point and the line must either end in that navcell // or else exit along the top edge or the right edge (or through the top-right corner, // which we'll arbitrary treat as the horizontal edge). // So we jump into the adjacent navcell across that edge, and continue. // To handle the special case of units that are stuck on impassable cells, // we allow them to move from an impassable to a passable cell (but not // vice versa). u16 i0, j0, i1, j1; NearestNavcell(x0, z0, i0, j0, grid.m_W, grid.m_H); NearestNavcell(x1, z1, i1, j1, grid.m_W, grid.m_H); // Find which direction the line heads in int di = (i0 < i1 ? +1 : i1 < i0 ? -1 : 0); int dj = (j0 < j1 ? +1 : j1 < j0 ? -1 : 0); u16 i = i0; u16 j = j0; bool currentlyOnImpassable = !IS_PASSABLE(grid.get(i0, j0), passClass); while (true) { // Make sure we are still in the limits ENSURE( ((di > 0 && i0 <= i && i <= i1) || (di < 0 && i1 <= i && i <= i0) || (di == 0 && i == i0)) && ((dj > 0 && j0 <= j && j <= j1) || (dj < 0 && j1 <= j && j <= j0) || (dj == 0 && j == j0))); // Fail if we're moving onto an impassable navcell bool passable = IS_PASSABLE(grid.get(i, j), passClass); if (passable) currentlyOnImpassable = false; else if (!currentlyOnImpassable) return false; // Succeed if we're at the target if (i == i1 && j == j1) return true; // If we can only move horizontally/vertically, then just move in that direction // If we are reaching the limits, we can go straight to the end if (di == 0 || i == i1) { j += dj; continue; } else if (dj == 0 || j == j1) { i += di; continue; } // Otherwise we need to check which cell to move into: // Check whether the line intersects the horizontal (top/bottom) edge of // the current navcell. // Horizontal edge is (i, j + (dj>0?1:0)) .. (i + 1, j + (dj>0?1:0)) // Since we already know the line is moving from this navcell into a different // navcell, we simply need to test that the edge's endpoints are not both on the // same side of the line. // If we are crossing exactly a vertex of the grid, we will get dota or dotb equal // to 0. In that case we arbitrarily choose to move of dj. // This only works because we handle the case (i == i1 || j == j1) beforehand. // Otherwise we could go outside the j limits and never reach the final navcell. entity_pos_t xia = entity_pos_t::FromInt(i).Multiply(Pathfinding::NAVCELL_SIZE); entity_pos_t xib = entity_pos_t::FromInt(i+1).Multiply(Pathfinding::NAVCELL_SIZE); entity_pos_t zj = entity_pos_t::FromInt(j + (dj+1)/2).Multiply(Pathfinding::NAVCELL_SIZE); CFixedVector2D perp = CFixedVector2D(x1 - x0, z1 - z0).Perpendicular(); entity_pos_t dota = (CFixedVector2D(xia, zj) - CFixedVector2D(x0, z0)).Dot(perp); entity_pos_t dotb = (CFixedVector2D(xib, zj) - CFixedVector2D(x0, z0)).Dot(perp); // If the horizontal edge is fully on one side of the line, so the line doesn't // intersect it, we should move across the vertical edge instead if ((dota < entity_pos_t::Zero() && dotb < entity_pos_t::Zero()) || (dota > entity_pos_t::Zero() && dotb > entity_pos_t::Zero())) i += di; else j += dj; } } } PathfinderPassability::PathfinderPassability(pass_class_t mask, const CParamNode& node) : m_Mask(mask) { if (node.GetChild("MinWaterDepth").IsOk()) m_MinDepth = node.GetChild("MinWaterDepth").ToFixed(); else m_MinDepth = std::numeric_limits::min(); if (node.GetChild("MaxWaterDepth").IsOk()) m_MaxDepth = node.GetChild("MaxWaterDepth").ToFixed(); else m_MaxDepth = std::numeric_limits::max(); if (node.GetChild("MaxTerrainSlope").IsOk()) m_MaxSlope = node.GetChild("MaxTerrainSlope").ToFixed(); else m_MaxSlope = std::numeric_limits::max(); if (node.GetChild("MinShoreDistance").IsOk()) m_MinShore = node.GetChild("MinShoreDistance").ToFixed(); else m_MinShore = std::numeric_limits::min(); if (node.GetChild("MaxShoreDistance").IsOk()) m_MaxShore = node.GetChild("MaxShoreDistance").ToFixed(); else m_MaxShore = std::numeric_limits::max(); if (node.GetChild("Clearance").IsOk()) { m_Clearance = node.GetChild("Clearance").ToFixed(); /* According to Philip who designed the original doc (in docs/pathfinder.pdf), * clearance should usually be integer to ensure consistent behavior when rasterizing * the passability map. * This seems doubtful to me and my pathfinder fix makes having a clearance of 0.8 quite convenient * so I comment out this check, but leave it here for the purpose of documentation should a bug arise. if (!(m_Clearance % Pathfinding::NAVCELL_SIZE).IsZero()) { // If clearance isn't an integer number of navcells then we'll // probably get weird behaviour when expanding the navcell grid // by clearance, vs expanding static obstructions by clearance LOGWARNING("Pathfinder passability class has clearance %f, should be multiple of %f", m_Clearance.ToFloat(), Pathfinding::NAVCELL_SIZE.ToFloat()); }*/ } else m_Clearance = fixed::Zero(); if (node.GetChild("Obstructions").IsOk()) { std::wstring obstructions = node.GetChild("Obstructions").ToString(); if (obstructions == L"none") m_Obstructions = NONE; else if (obstructions == L"pathfinding") m_Obstructions = PATHFINDING; else if (obstructions == L"foundation") m_Obstructions = FOUNDATION; else { LOGERROR("Invalid value for Obstructions in pathfinder.xml for pass class %d", mask); m_Obstructions = NONE; } } else m_Obstructions = NONE; } Index: ps/trunk/source/simulation2/helpers/Rasterize.h =================================================================== --- ps/trunk/source/simulation2/helpers/Rasterize.h (revision 24226) +++ ps/trunk/source/simulation2/helpers/Rasterize.h (revision 24227) @@ -1,54 +1,55 @@ /* Copyright (C) 2012 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * 0 A.D. is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with 0 A.D. If not, see . */ #ifndef INCLUDED_HELPER_RASTERIZE #define INCLUDED_HELPER_RASTERIZE /** * @file * Helper functions related to rasterizing geometric shapes to grids. */ #include "simulation2/components/ICmpObstructionManager.h" +#include "simulation2/helpers/Position.h" namespace SimRasterize { /** * Represents the set of cells (i,j) where i0 <= i < i1 */ struct Span { i16 i0; i16 i1; i16 j; }; typedef std::vector Spans; /** * Converts an ObstructionSquare @p shape (a rotated rectangle), * expanded by the given @p clearance, * into a list of spans of cells that are strictly inside the shape. */ void RasterizeRectWithClearance(Spans& spans, const ICmpObstructionManager::ObstructionSquare& shape, entity_pos_t clearance, entity_pos_t cellSize); } #endif // INCLUDED_HELPER_RASTERIZE Index: ps/trunk/source/simulation2/helpers/SimulationCommand.h =================================================================== --- ps/trunk/source/simulation2/helpers/SimulationCommand.h (revision 24226) +++ ps/trunk/source/simulation2/helpers/SimulationCommand.h (revision 24227) @@ -1,52 +1,52 @@ /* Copyright (C) 2015 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_SIMULATIONCOMMAND #define INCLUDED_SIMULATIONCOMMAND -#include "scriptinterface/ScriptInterface.h" +#include "scriptinterface/ScriptTypes.h" #include "simulation2/helpers/Player.h" /** * Simulation command, typically received over the network in multiplayer games. */ struct SimulationCommand { SimulationCommand(player_id_t player, JSContext* cx, JS::HandleValue val) : player(player), data(cx, val) { } SimulationCommand(SimulationCommand&& cmd) : player(cmd.player), data(cmd.data) { } // std::vector::insert requires the move assignment operator at compilation time, // but apparently never uses it (it uses the move constructor). SimulationCommand& operator=(SimulationCommand&& other) { this->player = other.player; this->data = other.data; return *this; } player_id_t player; JS::PersistentRootedValue data; }; #endif // INCLUDED_SIMULATIONCOMMAND Index: ps/trunk/source/simulation2/system/IComponent.h =================================================================== --- ps/trunk/source/simulation2/system/IComponent.h (revision 24226) +++ ps/trunk/source/simulation2/system/IComponent.h (revision 24227) @@ -1,72 +1,71 @@ /* Copyright (C) 2010 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_ICOMPONENT #define INCLUDED_ICOMPONENT #include "Components.h" #include "Message.h" #include "Entity.h" #include "SimContext.h" - -#include "scriptinterface/ScriptTypes.h" +#include "scriptinterface/ScriptForward.h" class CParamNode; class CMessage; class ISerializer; class IDeserializer; class IComponent { public: virtual ~IComponent(); static std::string GetSchema(); virtual void Init(const CParamNode& paramNode) = 0; virtual void Deinit() = 0; virtual void HandleMessage(const CMessage& msg, bool global); CEntityHandle GetEntityHandle() const { return m_EntityHandle; } void SetEntityHandle(CEntityHandle ent) { m_EntityHandle = ent; } entity_id_t GetEntityId() const { return m_EntityHandle.GetId(); } CEntityHandle GetSystemEntity() const { return m_SimContext->GetSystemEntity(); } const CSimContext& GetSimContext() const { return *m_SimContext; } void SetSimContext(const CSimContext& context) { m_SimContext = &context; } static u8 GetSerializationVersion() { return 0; } virtual void Serialize(ISerializer& serialize) = 0; virtual void Deserialize(const CParamNode& paramNode, IDeserializer& deserialize) = 0; /** * Returns false by default, indicating that a scripted wrapper of this IComponent is not supported. * Derrived classes should return true if they implement such a wrapper. */ virtual bool NewJSObject(const ScriptInterface& scriptInterface, JS::MutableHandleObject out) const; virtual JS::Value GetJSInstance() const; virtual int GetComponentTypeId() const = 0; private: CEntityHandle m_EntityHandle; const CSimContext* m_SimContext; }; #endif // INCLUDED_ICOMPONENT Index: ps/trunk/source/tools/atlas/GameInterface/ActorViewer.h =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/ActorViewer.h (revision 24226) +++ ps/trunk/source/tools/atlas/GameInterface/ActorViewer.h (revision 24227) @@ -1,58 +1,59 @@ /* Copyright (C) 2020 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_ACTORVIEWER #define INCLUDED_ACTORVIEWER -#include "ps/CStr.h" #include "simulation2/helpers/Player.h" #include "simulation2/system/Entity.h" struct ActorViewerImpl; struct SColor4ub; class CSimulation2; +class CStr8; +class CStrW; class ActorViewer { NONCOPYABLE(ActorViewer); public: ActorViewer(); ~ActorViewer(); CSimulation2* GetSimulation2(); entity_id_t GetEntity(); - void SetActor(const CStrW& id, const CStr& animation, player_id_t playerID); + void SetActor(const CStrW& id, const CStr8& animation, player_id_t playerID); void SetEnabled(bool enabled); void UnloadObjects(); void SetBackgroundColor(const SColor4ub& color); void SetWalkEnabled(bool enabled); void SetGroundEnabled(bool enabled); void SetWaterEnabled(bool enabled); void SetShadowsEnabled(bool enabled); void SetStatsEnabled(bool enabled); void SetBoundingBoxesEnabled(bool enabled); void SetAxesMarkerEnabled(bool enabled); void SetPropPointsMode(int mode); void Render(); void Update(float simFrameLength, float realFrameLength); private: ActorViewerImpl& m; float GetRepeatTimeByAttackType(const std::string& type) const; }; #endif // INCLUDED_ACTORVIEWER Index: ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp =================================================================== --- ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp (revision 24226) +++ ps/trunk/source/tools/atlas/GameInterface/Handlers/MapHandlers.cpp (revision 24227) @@ -1,637 +1,638 @@ /* Copyright (C) 2020 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 "../GameLoop.h" #include "../MessagePasser.h" #include "graphics/GameView.h" #include "graphics/LOSTexture.h" #include "graphics/MapIO.h" #include "graphics/MapWriter.h" #include "graphics/Patch.h" #include "graphics/Terrain.h" #include "graphics/TerrainTextureEntry.h" #include "graphics/TerrainTextureManager.h" #include "gui/ObjectTypes/CMiniMap.h" #include "lib/bits.h" #include "lib/file/vfs/vfs_path.h" #include "lib/status.h" #include "maths/MathUtil.h" #include "ps/CLogger.h" #include "ps/Filesystem.h" #include "ps/Game.h" #include "ps/GameSetup/GameSetup.h" #include "ps/Loader.h" +#include "ps/Shapes.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/components/ICmpVisual.h" #include "simulation2/system/ParamNode.h" namespace { void InitGame() { if (g_Game) { delete g_Game; g_Game = NULL; } g_Game = new CGame(false); // Default to player 1 for playtesting g_Game->SetPlayerID(1); } void StartGame(JS::MutableHandleValue attrs) { g_Game->StartGame(attrs, ""); // TODO: Non progressive load can fail - need a decent way to handle this LDR_NonprogressiveLoad(); // Disable fog-of-war - this must be done before starting the game, // as visual actors cache their visibility state on first render. CmpPtr cmpRangeManager(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpRangeManager) cmpRangeManager->SetLosRevealAll(-1, true); PSRETURN ret = g_Game->ReallyStartGame(); ENSURE(ret == PSRETURN_OK); } } namespace AtlasMessage { QUERYHANDLER(GenerateMap) { try { InitGame(); // Random map const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); ScriptRequest rq(scriptInterface); JS::RootedValue settings(rq.cx); scriptInterface.ParseJSON(*msg->settings, &settings); scriptInterface.SetProperty(settings, "mapType", "random"); JS::RootedValue attrs(rq.cx); ScriptInterface::CreateObject( rq, &attrs, "mapType", "random", "script", *msg->filename, "settings", settings); StartGame(&attrs); msg->status = 0; } catch (PSERROR_Game_World_MapLoadFailed&) { // Cancel loading LDR_Cancel(); // Since map generation failed and we don't know why, use the blank map as a fallback InitGame(); const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); ScriptRequest rq(scriptInterface); // Set up 8-element array of empty objects to satisfy init JS::RootedValue playerData(rq.cx); ScriptInterface::CreateArray(rq, &playerData); for (int i = 0; i < 8; ++i) { JS::RootedValue player(rq.cx); ScriptInterface::CreateObject(rq, &player); scriptInterface.SetPropertyInt(playerData, i, player); } JS::RootedValue settings(rq.cx); ScriptInterface::CreateObject( rq, &settings, "mapType", "scenario", "PlayerData", playerData); JS::RootedValue attrs(rq.cx); ScriptInterface::CreateObject( rq, &attrs, "mapType", "scenario", "map", "maps/scenarios/_default", "settings", settings); StartGame(&attrs); msg->status = -1; } } MESSAGEHANDLER(LoadMap) { InitGame(); const ScriptInterface& scriptInterface = g_Game->GetSimulation2()->GetScriptInterface(); ScriptRequest rq(scriptInterface); // Scenario CStrW map = *msg->filename; CStrW mapBase = map.BeforeLast(L".pmp"); // strip the file extension, if any JS::RootedValue attrs(rq.cx); ScriptInterface::CreateObject( rq, &attrs, "mapType", "scenario", "map", mapBase); StartGame(&attrs); } MESSAGEHANDLER(ImportHeightmap) { std::vector heightmap_source; if (LoadHeightmapImageOs(*msg->filename, heightmap_source) != INFO::OK) { LOGERROR("Failed to decode heightmap."); return; } // resize terrain to heightmap size // 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(); const ssize_t newSize = (sqrt(heightmap_source.size()) - 1) / PATCH_SIZE; const ssize_t offset = (newSize - terrain->GetPatchesPerSide()) / 2; terrain->ResizeAndOffset(newSize, offset, offset); // copy heightmap data into map u16* heightmap = g_Game->GetWorld()->GetTerrain()->GetHeightMap(); ENSURE(heightmap_source.size() == (std::size_t) SQR(g_Game->GetWorld()->GetTerrain()->GetVerticesPerSide())); std::copy(heightmap_source.begin(), heightmap_source.end(), heightmap); // update simulation CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); g_Game->GetView()->GetLOSTexture().MakeDirty(); } MESSAGEHANDLER(SaveMap) { CMapWriter writer; VfsPath pathname = VfsPath(*msg->filename).ChangeExtension(L".pmp"); writer.SaveMap(pathname, g_Game->GetWorld()->GetTerrain(), g_Renderer.GetWaterManager(), g_Renderer.GetSkyManager(), &g_LightEnv, g_Game->GetView()->GetCamera(), g_Game->GetView()->GetCinema(), &g_Renderer.GetPostprocManager(), g_Game->GetSimulation2()); } QUERYHANDLER(GetMapSettings) { msg->settings = g_Game->GetSimulation2()->GetMapSettingsString(); } BEGIN_COMMAND(SetMapSettings) { std::string m_OldSettings, m_NewSettings; void SetSettings(const std::string& settings) { g_Game->GetSimulation2()->SetMapSettings(settings); } void Do() { m_OldSettings = g_Game->GetSimulation2()->GetMapSettingsString(); m_NewSettings = *msg->settings; SetSettings(m_NewSettings); } // TODO: we need some way to notify the Atlas UI when the settings are changed // externally, otherwise this will have no visible effect void Undo() { // SetSettings(m_OldSettings); } void Redo() { // SetSettings(m_NewSettings); } void MergeIntoPrevious(cSetMapSettings* prev) { prev->m_NewSettings = m_NewSettings; } }; END_COMMAND(SetMapSettings) MESSAGEHANDLER(LoadPlayerSettings) { g_Game->GetSimulation2()->LoadPlayerSettings(msg->newplayers); } QUERYHANDLER(GetMapSizes) { msg->sizes = g_Game->GetSimulation2()->GetMapSizes(); } QUERYHANDLER(RasterizeMinimap) { // TODO: remove the code duplication of the rasterization algorithm, using // CMinimap version. const CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); const ssize_t dimension = terrain->GetVerticesPerSide() - 1; const ssize_t bpp = 24; const ssize_t imageDataSize = dimension * dimension * (bpp / 8); std::vector imageBytes(imageDataSize); 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 = std::move(imageBytes); msg->dimension = dimension; } QUERYHANDLER(GetRMSData) { msg->data = g_Game->GetSimulation2()->GetRMSData(); } QUERYHANDLER(GetCurrentMapSize) { msg->size = g_Game->GetWorld()->GetTerrain()->GetTilesPerSide(); } BEGIN_COMMAND(ResizeMap) { 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; player_id_t owner; CFixedVector3D pos; CFixedVector3D rot; u32 actorSeed; }; 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() { CmpPtr cmpTerrain(*g_Game->GetSimulation2(), SYSTEM_ENTITY); if (cmpTerrain) cmpTerrain->ReloadTerrain(); // The LOS texture won't normally get updated when running Atlas // (since there's no simulation updates), so explicitly dirty it g_Game->GetView()->GetLOSTexture().MakeDirty(); } void ResizeTerrain(ssize_t patches, int offsetX, int offsetY) { CTerrain* terrain = g_Game->GetWorld()->GetTerrain(); terrain->ResizeAndOffset(patches, -offsetX, -offsetY); } void DeleteObjects(const std::vector& deletedObjects) { for (const DeletedObject& deleted : deletedObjects) g_Game->GetSimulation2()->DestroyEntity(deleted.entityId); g_Game->GetSimulation2()->FlushDestroyedEntities(); } void RestoreObjects(const std::vector& deletedObjects) { CSimulation2& sim = *g_Game->GetSimulation2(); 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); CmpPtr cmpVisual(sim, deleted.entityId); if (cmpVisual) cmpVisual->SetActorSeed(deleted.actorSeed); } } } void SetMovedEntitiesPosition(const std::vector>& movedObjects) { for (const std::pair& obj : movedObjects) { const entity_id_t id = obj.first; const CFixedVector3D position = obj.second; CmpPtr cmpPosition(*g_Game->GetSimulation2(), id); ENSURE(cmpPosition); cmpPosition->JumpTo(position.X, position.Z); } } void Do() { CSimulation2& sim = *g_Game->GetSimulation2(); CmpPtr cmpTemplateManager(sim, SYSTEM_ENTITY); ENSURE(cmpTemplateManager); CmpPtr cmpTerrain(sim, SYSTEM_ENTITY); if (!cmpTerrain) { m_OldPatches = m_NewPatches = 0; m_OffsetX = m_OffsetY = 0; } else { 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 (const std::pair& ent : ents) { const entity_id_t entityId = ent.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(); CmpPtr cmpVisual(sim, deleted.entityId); if (cmpVisual) deleted.actorSeed = cmpVisual->GetActorSeed(); m_DeletedObjects.push_back(deleted); } } DeleteObjects(m_DeletedObjects); ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY); SetMovedEntitiesPosition(m_NewPositions); MakeDirty(); } void Undo() { 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() { DeleteObjects(m_DeletedObjects); ResizeTerrain(m_NewPatches, m_OffsetX, m_OffsetY); SetMovedEntitiesPosition(m_NewPositions); MakeDirty(); } }; END_COMMAND(ResizeMap) QUERYHANDLER(VFSFileExists) { msg->exists = VfsFileExists(*msg->path); } QUERYHANDLER(VFSFileRealPath) { VfsPath pathname(*msg->path); if (pathname.empty()) return; OsPath realPathname; if (g_VFS->GetRealPath(pathname, realPathname) == INFO::OK) msg->realPath = realPathname.string(); } static Status AddToFilenames(const VfsPath& pathname, const CFileInfo& UNUSED(fileInfo), const uintptr_t cbData) { std::vector& filenames = *(std::vector*)cbData; filenames.push_back(pathname.string().c_str()); return INFO::OK; } QUERYHANDLER(GetMapList) { #define GET_FILE_LIST(path, list) \ std::vector list; \ vfs::ForEachFile(g_VFS, path, AddToFilenames, (uintptr_t)&list, L"*.xml", vfs::DIR_RECURSIVE); \ msg->list = list; GET_FILE_LIST(L"maps/scenarios/", scenarioFilenames); GET_FILE_LIST(L"maps/skirmishes/", skirmishFilenames); GET_FILE_LIST(L"maps/tutorials/", tutorialFilenames); #undef GET_FILE_LIST } QUERYHANDLER(GetVictoryConditionData) { msg->data = g_Game->GetSimulation2()->GetVictoryConditiondData(); } }