/* 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 . */ #include "precompiled.h" #include "simulation2/system/Component.h" #include "ICmpObstructionManager.h" #include "simulation2/MessageTypes.h" #include "graphics/Terrain.h" #include "maths/FixedVector2D.h" #include "maths/MathUtil.h" #include "ps/Overlay.h" #include "ps/Profile.h" #include "renderer/TerrainOverlay.h" // Externally, tags are opaque non-zero positive integers. // Internally, they are tagged (by shape) indexes into shape lists. // idx must be non-zero. #define TAG_IS_CIRCLE(tag) (((tag) & 1) == 0) #define TAG_IS_SQUARE(tag) (((tag) & 1) == 1) #define CIRCLE_INDEX_TO_TAG(idx) (((idx) << 1) | 0) #define SQUARE_INDEX_TO_TAG(idx) (((idx) << 1) | 1) #define TAG_TO_INDEX(tag) ((tag) >> 1) /** * Internal representation of circle shapes */ struct Circle { entity_pos_t x, z, r; }; /** * Internal representation of square shapes */ struct Square { entity_pos_t x, z; entity_angle_t a; entity_pos_t w, h; }; class CCmpObstructionManager : public ICmpObstructionManager { public: static void ClassInit(CComponentManager& UNUSED(componentManager)) { } DEFAULT_COMPONENT_ALLOCATOR(ObstructionManager) // TODO: using std::map is a bit inefficient; is there a better way to store these? std::map m_Circles; std::map m_Squares; u32 m_CircleNext; // next allocated id u32 m_SquareNext; virtual void Init(const CSimContext& context, const CParamNode& UNUSED(paramNode)) { m_CircleNext = 1; m_SquareNext = 1; m_DirtyID = 1; // init to 1 so default-initialised grids are considered dirty } virtual void Deinit(const CSimContext& UNUSED(context)) { } virtual void Serialize(ISerializer& serialize) { // TODO: do something here // (Do we need to serialise the obstruction state, or is it fine to regenerate it from // the original entities after deserialisation?) } virtual void Deserialize(const CSimContext& context, const CParamNode& paramNode, IDeserializer& deserialize) { Init(context, paramNode); // TODO } virtual tag_t AddCircle(entity_pos_t x, entity_pos_t z, entity_pos_t r) { Circle c = { x, z, r }; size_t id = m_CircleNext++; m_Circles[id] = c; MakeDirty(); return CIRCLE_INDEX_TO_TAG(id); } virtual tag_t AddSquare(entity_pos_t x, entity_pos_t z, entity_angle_t a, entity_pos_t w, entity_pos_t h) { Square s = { x, z, a, w, h }; size_t id = m_SquareNext++; m_Squares[id] = s; MakeDirty(); return SQUARE_INDEX_TO_TAG(id); } virtual void MoveShape(tag_t tag, entity_pos_t x, entity_pos_t z, entity_angle_t a) { debug_assert(tag); if (TAG_IS_CIRCLE(tag)) { Circle& c = m_Circles[TAG_TO_INDEX(tag)]; c.x = x; c.z = z; } else { Square& s = m_Squares[TAG_TO_INDEX(tag)]; s.x = x; s.z = z; s.a = a; } MakeDirty(); } virtual void RemoveShape(tag_t tag) { debug_assert(tag); if (TAG_IS_CIRCLE(tag)) m_Circles.erase(TAG_TO_INDEX(tag)); else m_Squares.erase(TAG_TO_INDEX(tag)); MakeDirty(); } 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); virtual bool TestCircle(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r); virtual bool TestSquare(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h); virtual bool Rasterise(Grid& grid); private: // To support lazy updates of grid rasterisations of obstruction data, // we maintain a DirtyID here and increment it whenever obstructions change; // if a grid has a lower DirtyID then it needs to be updated. size_t m_DirtyID; /** * Mark all previous Rasterise()d grids as dirty */ void MakeDirty() { ++m_DirtyID; } /** * Test whether a Rasterise()d grid is dirty and needs updating */ template bool IsDirty(const Grid& grid) { return grid.m_DirtyID < m_DirtyID; } }; REGISTER_COMPONENT_TYPE(ObstructionManager) // Detect intersection between ray (0,0)-L and circle with center M radius r // (Only counts intersections from the outside to the inside) static bool IntersectRayCircle(CFixedVector2D l, CFixedVector2D m, entity_pos_t r) { // TODO: this should all be checked and tested etc, it's just a rough first attempt for now... // Intersections at (t * l.X - m.X)^2 * (t * l.Y - m.Y) = r^2 // so solve the quadratic for t: #define DOT(u, v) ( ((i64)u.X.GetInternalValue()*(i64)v.X.GetInternalValue()) + ((i64)u.Y.GetInternalValue()*(i64)v.Y.GetInternalValue()) ) i64 a = DOT(l, l); if (a == 0) return false; // avoid divide-by-zero later i64 b = DOT(l, m)*-2; i64 c = DOT(m, m) - r.GetInternalValue()*r.GetInternalValue(); i64 d = b*b - 4*a*c; // TODO: overflow breaks stuff here if (d < 0) // no solutions return false; // Find the time of first intersection (entering the circle) i64 t2a = (-b - isqrt64(d)); // don't divide by 2a explicitly, to avoid rounding errors if ((a > 0 && t2a < 0) || (a < 0 && t2a > 0)) // if t2a/2a < 0 then intersection was before the ray return false; if (t2a >= 2*a) // intersection was after the ray return false; // printf("isct (%f,%f) (%f,%f) %f a=%lld b=%lld c=%lld d=%lld t2a=%lld\n", l.X.ToDouble(), l.Y.ToDouble(), m.X.ToDouble(), m.Y.ToDouble(), r.ToDouble(), a, b, c, d, t2a); return true; } bool CCmpObstructionManager::TestLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r) { PROFILE("TestLine"); // TODO: this is all very inefficient, it should use some kind of spatial data structures // Ray-circle intersections for (std::map::iterator it = m_Circles.begin(); it != m_Circles.end(); ++it) { if (!filter.Allowed(CIRCLE_INDEX_TO_TAG(it->first))) continue; if (IntersectRayCircle(CFixedVector2D(x1 - x0, z1 - z0), CFixedVector2D(it->second.x - x0, it->second.z - z0), it->second.r + r)) return false; } // Ray-square intersections for (std::map::iterator it = m_Squares.begin(); it != m_Squares.end(); ++it) { if (!filter.Allowed(SQUARE_INDEX_TO_TAG(it->first))) continue; // XXX need some kind of square intersection code if (IntersectRayCircle(CFixedVector2D(x1 - x0, z1 - z0), CFixedVector2D(it->second.x - x0, it->second.z - z0), it->second.w/2 + r)) return false; } return true; } bool CCmpObstructionManager::TestCircle(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r) { PROFILE("TestCircle"); // Circle-circle intersections for (std::map::iterator it = m_Circles.begin(); it != m_Circles.end(); ++it) { if (!filter.Allowed(CIRCLE_INDEX_TO_TAG(it->first))) continue; if (CFixedVector2D(it->second.x - x, it->second.z - z).Length() <= it->second.r + r) return false; } // Circle-square intersections for (std::map::iterator it = m_Squares.begin(); it != m_Squares.end(); ++it) { if (!filter.Allowed(SQUARE_INDEX_TO_TAG(it->first))) continue; // XXX need some kind of square intersection code if (CFixedVector2D(it->second.x - x, it->second.z - z).Length() <= it->second.w/2 + r) return false; } return true; } bool CCmpObstructionManager::TestSquare(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t a, entity_pos_t w, entity_pos_t h) { // XXX need to implement this return TestCircle(filter, x, z, w/2); } /** * Compute the tile indexes on the grid nearest to a given point */ static void NearestTile(entity_pos_t x, entity_pos_t z, u16& i, u16& j, u16 w, u16 h) { i = clamp((x / CELL_SIZE).ToInt_RoundToZero(), 0, w-1); j = clamp((z / CELL_SIZE).ToInt_RoundToZero(), 0, h-1); } bool CCmpObstructionManager::Rasterise(Grid& grid) { if (!IsDirty(grid)) return false; grid.m_DirtyID = m_DirtyID; // TODO: this is all hopelessly inefficient // What we should perhaps do is have some kind of quadtree storing Shapes so it's // quick to invalidate and update small numbers of tiles grid.reset(); for (std::map::iterator it = m_Circles.begin(); it != m_Circles.end(); ++it) { // TODO: need to handle larger circles (r != 0) u16 i, j; NearestTile(it->second.x, it->second.z, i, j, grid.m_W, grid.m_H); grid.set(i, j, 1); } for (std::map::iterator it = m_Squares.begin(); it != m_Squares.end(); ++it) { // TODO: need to handle rotations (a != 0) entity_pos_t x0 = it->second.x - it->second.w/2; entity_pos_t z0 = it->second.z - it->second.h/2; entity_pos_t x1 = it->second.x + it->second.w/2; entity_pos_t z1 = it->second.z + it->second.h/2; u16 i0, j0, i1, j1; NearestTile(x0, z0, i0, j0, grid.m_W, grid.m_H); // TODO: should be careful about rounding on edges NearestTile(x1, z1, i1, j1, grid.m_W, grid.m_H); for (u16 j = j0; j <= j1; ++j) for (u16 i = i0; i <= i1; ++i) grid.set(i, j, 1); } return true; }