Index: source/simulation2/components/CCmpObstructionManager.cpp =================================================================== --- source/simulation2/components/CCmpObstructionManager.cpp +++ source/simulation2/components/CCmpObstructionManager.cpp @@ -480,7 +480,7 @@ 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; virtual bool AreShapesInRange(const ObstructionSquare& source, const ObstructionSquare& target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const; - 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 = false) const; + virtual bool TestStaticLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r) const; 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; virtual bool TestUnitShape(const IObstructionTestFilter& filter, entity_pos_t x, entity_pos_t z, entity_pos_t r, std::vector* out) const; @@ -856,9 +856,9 @@ (opposite ? MaxDistanceBetweenShapes(source, target) : dist) >= minRange - fixed::FromFloat(0.0001f); } -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, bool relaxClearanceForUnits) const +bool CCmpObstructionManager::TestStaticLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r) const { - PROFILE("TestLine"); + PROFILE("TestStaticLine"); // Check that both end points are within the world (which means the whole line must be) if (!IsInWorld(x0, z0, r) || !IsInWorld(x1, z1, r)) @@ -867,27 +867,6 @@ CFixedVector2D posMin (std::min(x0, x1) - r, std::min(z0, z1) - r); CFixedVector2D posMax (std::max(x0, x1) + r, std::max(z0, z1) + r); - // actual radius used for unit-unit collisions. If relaxClearanceForUnits, will be smaller to allow more overlap. - entity_pos_t unitUnitRadius = r; - if (relaxClearanceForUnits) - unitUnitRadius -= entity_pos_t::FromInt(1)/2; - - std::vector unitShapes; - m_UnitSubdivision.GetInRange(unitShapes, posMin, posMax); - for (const entity_id_t& shape : unitShapes) - { - std::map::const_iterator it = m_UnitShapes.find(shape); - ENSURE(it != m_UnitShapes.end()); - - if (!filter.TestShape(UNIT_INDEX_TO_TAG(it->first), it->second.flags, it->second.group, INVALID_ENTITY)) - continue; - - CFixedVector2D center(it->second.x, it->second.z); - CFixedVector2D halfSize(it->second.clearance + unitUnitRadius, it->second.clearance + unitUnitRadius); - if (Geometry::TestRayAASquare(CFixedVector2D(x0, z0) - center, CFixedVector2D(x1, z1) - center, halfSize)) - return true; - } - std::vector staticShapes; m_StaticSubdivision.GetInRange(staticShapes, posMin, posMax); for (const entity_id_t& shape : staticShapes) Index: source/simulation2/components/CCmpPathfinder.cpp =================================================================== --- source/simulation2/components/CCmpPathfinder.cpp +++ source/simulation2/components/CCmpPathfinder.cpp @@ -898,10 +898,8 @@ PROFILE2_IFSPIKE("Check Movement", 0.001); // Test against obstructions first. filter may discard pathfinding-blocking obstructions. - // Use more permissive version of TestLine to allow unit-unit collisions to overlap slightly. - // This makes movement smoother and more natural for units, overall. CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager || cmpObstructionManager->TestLine(filter, x0, z0, x1, z1, r, true)) + if (!cmpObstructionManager || cmpObstructionManager->TestStaticLine(filter, x0, z0, x1, z1, r)) return false; // Then test against the terrain grid. This should not be necessary Index: source/simulation2/components/CCmpUnitMotionManager.cpp =================================================================== --- source/simulation2/components/CCmpUnitMotionManager.cpp +++ source/simulation2/components/CCmpUnitMotionManager.cpp @@ -102,6 +102,8 @@ void MoveUnits(fixed dt); void MoveFormations(fixed dt); void Move(EntityMap& ents, fixed dt); + + void Push(EntityMap::value_type& a, EntityMap::value_type& b); }; void CCmpUnitMotionManager::Register(entity_id_t ent, bool formationController) @@ -111,6 +113,7 @@ CmpPtr(GetSimContext(), ent), CFixedVector2D(), CFixedVector2D(), + CFixedVector2D(), fixed::Zero(), fixed::Zero(), false, @@ -165,10 +168,51 @@ } for (EntityMap::value_type& data : ents) - data.second.cmpUnitMotion->Move(data.second, dt); + if (data.second.cmpPosition->IsInWorld()) + data.second.cmpUnitMotion->Move(data.second, dt); + + for (EntityMap::value_type& data1 : m_Units) + { + if (!data1.second.cmpPosition->IsInWorld()) + continue; + for (EntityMap::value_type& data2 : m_Units) + if (data1.first < data2.first && data2.second.cmpPosition->IsInWorld()) + Push(data1, data2); + } for (EntityMap::value_type& data : ents) + { + if (data.second.push.CompareLength(fixed::FromInt(2)) >= 0) + { + data.second.wasObstructed = true; + data.second.wentStraight = false; + data.second.pos = data.second.initialPos + (data.second.pos - data.second.initialPos) / 2 + data.second.push / 2; + } + else + { + data.second.pos += data.second.push; + } + data.second.push = CFixedVector2D(); data.second.cmpUnitMotion->PostMove(data.second, dt); + } +} + +void CCmpUnitMotionManager::Push(EntityMap::value_type& a, EntityMap::value_type& b) +{ + CFixedVector2D offset = a.second.pos - b.second.pos; + if (offset.CompareLength(fixed::FromInt(2)) > 0) + return; + CFixedVector2D initial_offset = a.second.initialPos - b.second.initialPos; + if (initial_offset.Dot(offset) < fixed::Zero()) + { + CFixedVector2D delta = offset.Perpendicular(); + a.second.push += delta; + b.second.push -= delta; + return; + } + offset = offset.Multiply(std::max(fixed::Zero(), fixed::FromInt(2) - offset.Length())) / 2; + a.second.push += offset; + b.second.push -= offset; } Index: source/simulation2/components/ICmpObstructionManager.h =================================================================== --- source/simulation2/components/ICmpObstructionManager.h +++ source/simulation2/components/ICmpObstructionManager.h @@ -231,7 +231,7 @@ 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. + * Collision test a flat-ended thick line against the current set of static 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 @@ -240,10 +240,9 @@ * @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; + virtual bool TestStaticLine(const IObstructionTestFilter& filter, entity_pos_t x0, entity_pos_t z0, entity_pos_t x1, entity_pos_t z1, entity_pos_t r) const = 0; /** * Collision test a static square shape against the current set of shapes. Index: source/simulation2/components/ICmpUnitMotionManager.h =================================================================== --- source/simulation2/components/ICmpUnitMotionManager.h +++ source/simulation2/components/ICmpUnitMotionManager.h @@ -38,6 +38,9 @@ // Transient position during the movement. CFixedVector2D pos; + // Accumulated "pushing" from nearby units. + CFixedVector2D push; + fixed initialAngle; fixed angle; Index: source/simulation2/helpers/LongPathfinder.h =================================================================== --- source/simulation2/helpers/LongPathfinder.h +++ source/simulation2/helpers/LongPathfinder.h @@ -260,9 +260,7 @@ /** * Given a path with an arbitrary collection of waypoints, updates the - * waypoints to be nicer. Calls "Testline" between waypoints - * so that bended paths can become straight if there's nothing in between - * (this happens because A* is 8-direction, and the map isn't actually a grid). + * waypoints to be nicer. * If @param maxDist is non-zero, path waypoints will be espaced by at most @param maxDist. * In that case the distance between (x0, z0) and the first waypoint will also be made less than maxDist. */