Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitAI.js @@ -1635,8 +1635,8 @@ "MoveStarted": function(msg) { // Adapt the speed to the one of the target if needed - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - if (cmpUnitMotion.IsInTargetRange(this.isGuardOf, 0, 3*this.guardRange)) + let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + if (cmpObstructionManager.IsInTargetRange(this.entity, this.isGuardOf, 0, 3 * this.guardRange, true)) { var cmpUnitAI = Engine.QueryInterface(this.isGuardOf, IID_UnitAI); if (cmpUnitAI) @@ -4308,8 +4308,8 @@ UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max) { - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return cmpUnitMotion.IsInPointRange(x, z, min, max); + let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInPointRange(this.entity, x, z, min, max, true); }; UnitAI.prototype.CheckTargetRange = function(target, iid, type) @@ -4319,8 +4319,8 @@ return false; var range = cmpRanged.GetRange(type); - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return cmpUnitMotion.IsInTargetRange(target, range.min, range.max); + let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, true); }; /** @@ -4368,14 +4368,14 @@ if (maxRangeSq < 0) return false; - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return cmpUnitMotion.IsInTargetRange(target, range.min, Math.sqrt(maxRangeSq)); + let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, Math.sqrt(maxRangeSq), true); }; UnitAI.prototype.CheckTargetRangeExplicit = function(target, min, max) { - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return cmpUnitMotion.IsInTargetRange(target, min, max); + let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInTargetRange(this.entity, target, min, max, true); }; UnitAI.prototype.CheckGarrisonRange = function(target) @@ -4389,8 +4389,8 @@ if (cmpObstruction) range.max += cmpObstruction.GetUnitRadius()*1.5; // multiply by something larger than sqrt(2) - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return cmpUnitMotion.IsInTargetRange(target, range.min, range.max); + let cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, true); }; /** Index: ps/trunk/binaries/data/mods/public/simulation/components/UnitMotionFlying.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/UnitMotionFlying.js +++ ps/trunk/binaries/data/mods/public/simulation/components/UnitMotionFlying.js @@ -276,29 +276,6 @@ return true; }; -UnitMotionFlying.prototype.IsInPointRange = function(x, y, minRange, maxRange) -{ - var cmpPosition = Engine.QueryInterface(this.entity, IID_Position); - var pos = cmpPosition.GetPosition2D(); - - var distFromTarget = Math.euclidDistance2D(x, y, pos.x, pos.y); - if (minRange <= distFromTarget && distFromTarget <= maxRange) - return true; - - return false; -}; - -UnitMotionFlying.prototype.IsInTargetRange = function(target, minRange, maxRange) -{ - var cmpTargetPosition = Engine.QueryInterface(target, IID_Position); - if (!cmpTargetPosition || !cmpTargetPosition.IsInWorld()) - return false; - - var targetPos = cmpTargetPosition.GetPosition2D(); - - return this.IsInPointRange(targetPos.x, targetPos.y, minRange, maxRange); -}; - UnitMotionFlying.prototype.GetWalkSpeed = function() { return +this.template.MaxSpeed; Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -64,6 +64,9 @@ GetEnemies: function() { return []; }, }); + AddMock(SYSTEM_ENTITY, IID_ObstructionManager, { + "IsInTargetRange": (ent, target, min, max, opposite) => true + }); var unitAI = ConstructComponent(unit, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive" }); @@ -84,12 +87,11 @@ }); AddMock(unit, IID_UnitMotion, { - GetWalkSpeed: function() { return 1; }, - MoveToFormationOffset: function(target, x, z) { }, - IsInTargetRange: function(target, min, max) { return true; }, - MoveToTargetRange: function(target, min, max) { return true; }, - StopMoving: function() { }, - GetPassabilityClassName: function() { return "default"; }, + "GetWalkSpeed": () => 1, + "MoveToFormationOffset": (target, x, z) => {}, + "MoveToTargetRange": (target, min, max) => true, + "StopMoving": () => {}, + "GetPassabilityClassName": () => "default" }); AddMock(unit, IID_Vision, { @@ -207,6 +209,10 @@ GetNumPlayers: function() { return 2; }, }); + AddMock(SYSTEM_ENTITY, IID_ObstructionManager, { + "IsInTargetRange": (ent, target, min, max) => true + }); + AddMock(playerEntity, IID_Player, { IsAlly: function() { return false; }, IsEnemy: function() { return true; }, @@ -237,12 +243,11 @@ }); AddMock(unit + i, IID_UnitMotion, { - GetWalkSpeed: function() { return 1; }, - MoveToFormationOffset: function(target, x, z) { }, - IsInTargetRange: function(target, min, max) { return true; }, - MoveToTargetRange: function(target, min, max) { return true; }, - StopMoving: function() { }, - GetPassabilityClassName: function() { return "default"; }, + "GetWalkSpeed": () => 1, + "MoveToFormationOffset": (target, x, z) => {}, + "MoveToTargetRange": (target, min, max) => true, + "StopMoving": () => {}, + "GetPassabilityClassName": () => "default" }); AddMock(unit + i, IID_Vision, { @@ -283,12 +288,11 @@ }); AddMock(controller, IID_UnitMotion, { - GetWalkSpeed: function() { return 1; }, - SetSpeedMultiplier: function(speed) { }, - MoveToPointRange: function(x, z, minRange, maxRange) { }, - IsInTargetRange: function(target, min, max) { return true; }, - StopMoving: function() { }, - GetPassabilityClassName: function() { return "default"; }, + "GetWalkSpeed": () => 1, + "SetSpeedMultiplier": (speed) => {}, + "MoveToPointRange": (x, z, minRange, maxRange) => {}, + "StopMoving": () => {}, + "GetPassabilityClassName": () => "default" }); AddMock(controller, IID_Attack, { Index: ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitMotionFlying.js =================================================================== --- ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitMotionFlying.js +++ ps/trunk/binaries/data/mods/public/simulation/components/tests/test_UnitMotionFlying.js @@ -53,10 +53,6 @@ "GetPosition2D": () => { return { "x": 100, "y": 200 }; } }); -TS_ASSERT_EQUALS(cmpUnitMotionFlying.IsInTargetRange(target, 10, 112), true); -TS_ASSERT_EQUALS(cmpUnitMotionFlying.IsInTargetRange(target, 50, 111), false); -TS_ASSERT_EQUALS(cmpUnitMotionFlying.IsInTargetRange(target, 112, 200), false); - AddMock(entity, IID_GarrisonHolder, { "AllowGarrisoning": () => {} }); Index: ps/trunk/source/simulation2/components/CCmpObstructionManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpObstructionManager.cpp +++ ps/trunk/source/simulation2/components/CCmpObstructionManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -466,6 +466,15 @@ } virtual fixed DistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const; + virtual fixed MaxDistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const; + virtual fixed DistanceToTarget(entity_id_t ent, entity_id_t target) const; + virtual fixed MaxDistanceToTarget(entity_id_t ent, entity_id_t target) const; + virtual fixed DistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const; + virtual fixed MaxDistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const; + + 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; + virtual bool IsInTargetRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const; + 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 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 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; @@ -657,18 +666,173 @@ REGISTER_COMPONENT_TYPE(ObstructionManager) +/** + * DistanceTo function family, all end up in calculating a vector length, DistanceBetweenShapes or + * MaxDistanceBetweenShapes. The MaxFoo family calculates the opposite edge opposite edge distance. + * When the distance is undefined we return -1. + */ fixed CCmpObstructionManager::DistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const { + ObstructionSquare obstruction; + CmpPtr cmpObstruction(GetSimContext(), ent); + if (cmpObstruction && cmpObstruction->GetObstructionSquare(obstruction)) + { + ObstructionSquare point; + point.x = px; + point.z = pz; + return DistanceBetweenShapes(obstruction, point); + } + CmpPtr cmpPosition(GetSimContext(), ent); if (!cmpPosition || !cmpPosition->IsInWorld()) return fixed::FromInt(-1); - ObstructionSquare s; + return (CFixedVector2D(cmpPosition->GetPosition2D().X, cmpPosition->GetPosition2D().Y) - CFixedVector2D(px, pz)).Length(); +} + +fixed CCmpObstructionManager::MaxDistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const +{ + ObstructionSquare obstruction; + CmpPtr cmpObstruction(GetSimContext(), ent); + if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(obstruction)) + { + ObstructionSquare point; + point.x = px; + point.z = pz; + return MaxDistanceBetweenShapes(obstruction, point); + } + + CmpPtr cmpPosition(GetSimContext(), ent); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return fixed::FromInt(-1); + + return (CFixedVector2D(cmpPosition->GetPosition2D().X, cmpPosition->GetPosition2D().Y) - CFixedVector2D(px, pz)).Length(); +} + +fixed CCmpObstructionManager::DistanceToTarget(entity_id_t ent, entity_id_t target) const +{ + ObstructionSquare obstruction; + CmpPtr cmpObstruction(GetSimContext(), ent); + if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(obstruction)) + { + CmpPtr cmpPosition(GetSimContext(), ent); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return fixed::FromInt(-1); + return DistanceToPoint(target, cmpPosition->GetPosition2D().X, cmpPosition->GetPosition2D().Y); + } + + ObstructionSquare target_obstruction; + CmpPtr cmpObstructionTarget(GetSimContext(), target); + if (!cmpObstructionTarget || !cmpObstructionTarget->GetObstructionSquare(target_obstruction)) + { + CmpPtr cmpPositionTarget(GetSimContext(), target); + if (!cmpPositionTarget || !cmpPositionTarget->IsInWorld()) + return fixed::FromInt(-1); + return DistanceToPoint(ent, cmpPositionTarget->GetPosition2D().X, cmpPositionTarget->GetPosition2D().Y); + } + + return DistanceBetweenShapes(obstruction, target_obstruction); +} + +fixed CCmpObstructionManager::MaxDistanceToTarget(entity_id_t ent, entity_id_t target) const +{ + ObstructionSquare obstruction; CmpPtr cmpObstruction(GetSimContext(), ent); - if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(s)) - return (CFixedVector2D(px, pz) - cmpPosition->GetPosition2D()).Length(); + if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(obstruction)) + { + CmpPtr cmpPosition(GetSimContext(), ent); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return fixed::FromInt(-1); + return MaxDistanceToPoint(target, cmpPosition->GetPosition2D().X, cmpPosition->GetPosition2D().Y); + } + + ObstructionSquare target_obstruction; + CmpPtr cmpObstructionTarget(GetSimContext(), target); + if (!cmpObstructionTarget || !cmpObstructionTarget->GetObstructionSquare(target_obstruction)) + { + CmpPtr cmpPositionTarget(GetSimContext(), target); + if (!cmpPositionTarget || !cmpPositionTarget->IsInWorld()) + return fixed::FromInt(-1); + return MaxDistanceToPoint(ent, cmpPositionTarget->GetPosition2D().X, cmpPositionTarget->GetPosition2D().Y); + } + + return MaxDistanceBetweenShapes(obstruction, target_obstruction); +} - return Geometry::DistanceToSquare(CFixedVector2D(px - s.x, pz - s.z), s.u, s.v, CFixedVector2D(s.hw, s.hh)); +fixed CCmpObstructionManager::DistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const +{ + // Sphere-sphere collision. + if (source.hh == fixed::Zero() && target.hh == fixed::Zero()) + return (CFixedVector2D(target.x, target.z) - CFixedVector2D(source.x, source.z)).Length() - source.hw - target.hw; + + // Square to square. + if (source.hh != fixed::Zero() && target.hh != fixed::Zero()) + return Geometry::DistanceSquareToSquare( + CFixedVector2D(target.x, target.z) - CFixedVector2D(source.x, source.z), + source.u, source.v, CFixedVector2D(source.hw, source.hh), + target.u, target.v, CFixedVector2D(target.hw, target.hh)); + + // To cover both remaining cases, shape a is the square one, shape b is the circular one. + const ObstructionSquare& a = source.hh == fixed::Zero() ? target : source; + const ObstructionSquare& b = source.hh == fixed::Zero() ? source : target; + return Geometry::DistanceToSquare( + CFixedVector2D(b.x, b.z) - CFixedVector2D(a.x, a.z), + a.u, a.v, CFixedVector2D(a.hw, a.hh), true) - b.hw; +} + +fixed CCmpObstructionManager::MaxDistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const +{ + // Sphere-sphere collision. + if (source.hh == fixed::Zero() && target.hh == fixed::Zero()) + return (CFixedVector2D(target.x, target.z) - CFixedVector2D(source.x, source.z)).Length() + source.hw + target.hw; + + // Square to square. + if (source.hh != fixed::Zero() && target.hh != fixed::Zero()) + return Geometry::MaxDistanceSquareToSquare( + CFixedVector2D(target.x, target.z) - CFixedVector2D(source.x, source.z), + source.u, source.v, CFixedVector2D(source.hw, source.hh), + target.u, target.v, CFixedVector2D(target.hw, target.hh)); + + // To cover both remaining cases, shape a is the square one, shape b is the circular one. + const ObstructionSquare& a = source.hh == fixed::Zero() ? target : source; + const ObstructionSquare& b = source.hh == fixed::Zero() ? source : target; + return Geometry::MaxDistanceToSquare( + CFixedVector2D(b.x, b.z) - CFixedVector2D(a.x, a.z), + a.u, a.v, CFixedVector2D(a.hw, a.hh), true) + b.hw; +} + +/** + * IsInRange function family depending on the DistanceTo family. + * + * In range if the edge to edge distance is inferior to maxRange + * and if the opposite edge to opposite edge distance is greater than minRange when the opposite bool is true + * or when the opposite bool is false the edge to edge distance is more than minRange. + * + * Using the opposite egde for minRange means that a unit is in range of a building if it is farther than + * clearance-buildingsize, which is generally going to be negative (and thus this returns true). + * NB: from a game POV, this means units can easily fire on buildings, which is good, + * but it also means that buildings can easily fire on units. Buildings are usually meant + * to fire from the edge, not the opposite edge, so this looks odd. For this reason one can choose + * to set the opposite bool false and use the edge to egde distance. + * + * We don't use squares because the are likely to overflow. + * We use a 0.0001 margin to avoid rounding errors. + */ +bool CCmpObstructionManager::IsInPointRange(entity_id_t ent, entity_pos_t px, entity_pos_t pz, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const +{ + fixed dist = DistanceToPoint(ent, px, pz); + return dist != fixed::FromInt(-1) && dist <= maxRange + fixed::FromFloat(0.0001) && (opposite ? MaxDistanceToPoint(ent, px, pz) : dist) >= minRange - fixed::FromFloat(0.0001); +} + +bool CCmpObstructionManager::IsInTargetRange(entity_id_t ent, entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange, bool opposite) const +{ + fixed dist = DistanceToTarget(ent, target); + return dist != fixed::FromInt(-1) && dist <= maxRange + fixed::FromFloat(0.0001) && (opposite ? MaxDistanceToTarget(ent, target) : dist) >= minRange- fixed::FromFloat(0.0001); +} +bool CCmpObstructionManager::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 +{ + entity_pos_t distance = (CFixedVector2D(x, z) - CFixedVector2D(px, pz)).Length(); + return distance <= maxRange + fixed::FromFloat(0.0001) && distance >= minRange - fixed::FromFloat(0.0001); } 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 Index: ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp +++ ps/trunk/source/simulation2/components/CCmpUnitMotion.cpp @@ -503,9 +503,7 @@ } virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange); - virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) const; virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange); - virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) const; virtual void MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z); virtual void FaceTowardsPoint(entity_pos_t x, entity_pos_t z); @@ -1057,8 +1055,9 @@ else { // check if target was reached in case of a moving target + CmpPtr cmpObstructionManager(GetSystemEntity()); CmpPtr cmpUnitMotion(GetSimContext(), m_TargetEntity); - if (!cmpUnitMotion || cmpUnitMotion->IsInTargetRange(GetEntityId(), m_TargetMinRange, m_TargetMaxRange)) + if (!cmpUnitMotion || cmpObstructionManager->IsInTargetRange(GetEntityId(), m_TargetEntity, m_TargetMinRange, m_TargetMaxRange, false)) { // Not in formation, so just finish moving StopMoving(); @@ -1471,49 +1470,6 @@ return true; } -bool CCmpUnitMotion::IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) const -{ - CmpPtr cmpPosition(GetEntityHandle()); - if (!cmpPosition || !cmpPosition->IsInWorld()) - return false; - - CFixedVector2D pos = cmpPosition->GetPosition2D(); - - bool hasObstruction = false; - CmpPtr cmpObstructionManager(GetSystemEntity()); - ICmpObstructionManager::ObstructionSquare obstruction; -//TODO if (cmpObstructionManager) -// hasObstruction = cmpObstructionManager->FindMostImportantObstruction(GetObstructionFilter(), x, z, m_Radius, obstruction); - - if (minRange.IsZero() && maxRange.IsZero() && hasObstruction) - { - // Handle the non-ranged mode: - CFixedVector2D halfSize(obstruction.hw, obstruction.hh); - entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize); - - // See if we're too close to the target square - if (distance < minRange) - return false; - - // See if we're close enough to the target square - if (maxRange < entity_pos_t::Zero() || distance <= maxRange) - return true; - - return false; - } - else - { - entity_pos_t distance = (pos - CFixedVector2D(x, z)).Length(); - - if (distance < minRange) - return false; - else if (maxRange >= entity_pos_t::Zero() && distance > maxRange) - return false; - else - return true; - } -} - bool CCmpUnitMotion::ShouldTreatTargetAsCircle(entity_pos_t range, entity_pos_t circleRadius) const { // Given a square, plus a target range we should reach, the shape at that distance @@ -1684,76 +1640,6 @@ return true; } -bool CCmpUnitMotion::IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) const -{ - // This function closely mirrors MoveToTargetRange - it needs to return true - // after that Move has completed - - CmpPtr cmpPosition(GetEntityHandle()); - if (!cmpPosition || !cmpPosition->IsInWorld()) - return false; - - CFixedVector2D pos = cmpPosition->GetPosition2D(); - - CmpPtr cmpObstructionManager(GetSystemEntity()); - if (!cmpObstructionManager) - return false; - - bool hasObstruction = false; - ICmpObstructionManager::ObstructionSquare obstruction; - CmpPtr cmpObstruction(GetSimContext(), target); - if (cmpObstruction) - hasObstruction = cmpObstruction->GetObstructionSquare(obstruction); - - if (hasObstruction) - { - CFixedVector2D halfSize(obstruction.hw, obstruction.hh); - entity_pos_t distance = Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize, true); - - // Compare with previous obstruction - ICmpObstructionManager::ObstructionSquare previousObstruction; - cmpObstruction->GetPreviousObstructionSquare(previousObstruction); - entity_pos_t previousDistance = Geometry::DistanceToSquare(pos - CFixedVector2D(previousObstruction.x, previousObstruction.z), obstruction.u, obstruction.v, halfSize, true); - - // See if we're too close to the target square - bool inside = distance.IsZero() && !Geometry::DistanceToSquare(pos - CFixedVector2D(obstruction.x, obstruction.z), obstruction.u, obstruction.v, halfSize).IsZero(); - if ((distance < minRange && previousDistance < minRange) || inside) - return false; - - // See if we're close enough to the target square - if (maxRange < entity_pos_t::Zero() || distance <= maxRange || previousDistance <= maxRange) - return true; - - entity_pos_t circleRadius = halfSize.Length(); - - if (ShouldTreatTargetAsCircle(maxRange, circleRadius)) - { - // The target is small relative to our range, so pretend it's a circle - // and see if we're close enough to that. - // Also check circle around previous position. - entity_pos_t circleDistance = (pos - CFixedVector2D(obstruction.x, obstruction.z)).Length() - circleRadius; - entity_pos_t previousCircleDistance = (pos - CFixedVector2D(previousObstruction.x, previousObstruction.z)).Length() - circleRadius; - - return circleDistance <= maxRange || previousCircleDistance <= maxRange; - } - - // take minimal clearance required in MoveToTargetRange into account, multiplying by 3/2 for diagonals - entity_pos_t maxDist = std::max(maxRange, (m_Clearance + entity_pos_t::FromInt(TERRAIN_TILE_SIZE)/16)*3/2); - return distance <= maxDist || previousDistance <= maxDist; - } - else - { - CmpPtr cmpTargetPosition(GetSimContext(), target); - if (!cmpTargetPosition || !cmpTargetPosition->IsInWorld()) - return false; - - CFixedVector2D targetPos = cmpTargetPosition->GetPreviousPosition2D(); - entity_pos_t distance = (pos - targetPos).Length(); - - return minRange <= distance && (maxRange < entity_pos_t::Zero() || distance <= maxRange); - } -} - void CCmpUnitMotion::MoveToFormationOffset(entity_id_t target, entity_pos_t x, entity_pos_t z) { CmpPtr cmpPosition(GetSimContext(), target); Index: ps/trunk/source/simulation2/components/ICmpObstructionManager.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstructionManager.h +++ ps/trunk/source/simulation2/components/ICmpObstructionManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -54,6 +54,16 @@ { 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.) */ @@ -165,6 +175,46 @@ 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. + */ + 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. + */ + 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. + */ + 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; + + /** * 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. @@ -225,16 +275,6 @@ virtual void UpdateInformations(GridUpdateInformation& informations) = 0; /** - * 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 - }; - - /** * 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 Index: ps/trunk/source/simulation2/components/ICmpObstructionManager.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpObstructionManager.cpp +++ ps/trunk/source/simulation2/components/ICmpObstructionManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -23,6 +23,12 @@ BEGIN_INTERFACE_WRAPPER(ObstructionManager) DEFINE_INTERFACE_METHOD_1("SetPassabilityCircular", void, ICmpObstructionManager, SetPassabilityCircular, bool) -DEFINE_INTERFACE_METHOD_CONST_3("DistanceToPoint", fixed, ICmpObstructionManager, DistanceToPoint, entity_id_t, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_1("SetDebugOverlay", void, ICmpObstructionManager, SetDebugOverlay, bool) +DEFINE_INTERFACE_METHOD_CONST_3("DistanceToPoint", fixed, ICmpObstructionManager, DistanceToPoint, entity_id_t, entity_pos_t, entity_pos_t) +DEFINE_INTERFACE_METHOD_CONST_3("MaxDistanceToPoint", fixed, ICmpObstructionManager, MaxDistanceToPoint, entity_id_t, entity_pos_t, entity_pos_t) +DEFINE_INTERFACE_METHOD_CONST_2("DistanceToTarget", fixed, ICmpObstructionManager, DistanceToTarget, entity_id_t, entity_id_t) +DEFINE_INTERFACE_METHOD_CONST_2("MaxDistanceToTarget", fixed, ICmpObstructionManager, MaxDistanceToTarget, entity_id_t, entity_id_t) +DEFINE_INTERFACE_METHOD_CONST_6("IsInPointRange", bool, ICmpObstructionManager, IsInPointRange, entity_id_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, bool) +DEFINE_INTERFACE_METHOD_CONST_5("IsInTargetRange", bool, ICmpObstructionManager, IsInTargetRange, entity_id_t, entity_id_t, entity_pos_t, entity_pos_t, bool) +DEFINE_INTERFACE_METHOD_CONST_6("IsPointInPointRange", bool, ICmpObstructionManager, IsPointInPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t) END_INTERFACE_WRAPPER(ObstructionManager) Index: ps/trunk/source/simulation2/components/ICmpUnitMotion.h =================================================================== --- ps/trunk/source/simulation2/components/ICmpUnitMotion.h +++ ps/trunk/source/simulation2/components/ICmpUnitMotion.h @@ -45,18 +45,6 @@ virtual bool MoveToPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) = 0; /** - * Determine wether the givven point is within the given range, using the same measurement - * as MoveToPointRange. - */ - virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) const = 0; - - /** - * Determine whether the target is within the given range, using the same measurement - * as MoveToTargetRange. - */ - virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) const = 0; - - /** * Attempt to walk into range of a given target entity, or as close as possible. * The range is measured between approximately the edges of the unit and the target, so that * maxRange=0 is not unreachably close to the target. Index: ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp =================================================================== --- ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp +++ ps/trunk/source/simulation2/components/ICmpUnitMotion.cpp @@ -24,8 +24,6 @@ BEGIN_INTERFACE_WRAPPER(UnitMotion) DEFINE_INTERFACE_METHOD_4("MoveToPointRange", bool, ICmpUnitMotion, MoveToPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t) -DEFINE_INTERFACE_METHOD_CONST_4("IsInPointRange", bool, ICmpUnitMotion, IsInPointRange, entity_pos_t, entity_pos_t, entity_pos_t, entity_pos_t) -DEFINE_INTERFACE_METHOD_CONST_3("IsInTargetRange", bool, ICmpUnitMotion, IsInTargetRange, entity_id_t, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_3("MoveToTargetRange", bool, ICmpUnitMotion, MoveToTargetRange, entity_id_t, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_3("MoveToFormationOffset", void, ICmpUnitMotion, MoveToFormationOffset, entity_id_t, entity_pos_t, entity_pos_t) DEFINE_INTERFACE_METHOD_2("FaceTowardsPoint", void, ICmpUnitMotion, FaceTowardsPoint, entity_pos_t, entity_pos_t) @@ -52,16 +50,6 @@ return m_Script.Call("MoveToPointRange", x, z, minRange, maxRange); } - virtual bool IsInPointRange(entity_pos_t x, entity_pos_t z, entity_pos_t minRange, entity_pos_t maxRange) const - { - return m_Script.Call("IsInPointRange", x, z, minRange, maxRange); - } - - virtual bool IsInTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) const - { - return m_Script.Call("IsInTargetRange", target, minRange, maxRange); - } - virtual bool MoveToTargetRange(entity_id_t target, entity_pos_t minRange, entity_pos_t maxRange) { return m_Script.Call("MoveToTargetRange", target, minRange, maxRange); Index: ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h =================================================================== --- ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h +++ ps/trunk/source/simulation2/components/tests/test_ObstructionManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -18,6 +18,39 @@ #include "simulation2/system/ComponentTest.h" #include "simulation2/components/ICmpObstructionManager.h" +#include "simulation2/components/ICmpObstruction.h" + +class MockObstruction : public ICmpObstruction +{ +public: + DEFAULT_MOCK_COMPONENT() + ICmpObstructionManager::ObstructionSquare obstruction; + + virtual ICmpObstructionManager::tag_t GetObstruction() const { return ICmpObstructionManager::tag_t(); } + virtual bool GetObstructionSquare(ICmpObstructionManager::ObstructionSquare& out) const { out = obstruction; return true; } + virtual bool GetPreviousObstructionSquare(ICmpObstructionManager::ObstructionSquare& UNUSED(out)) const { return true; } + virtual entity_pos_t GetSize() const { return entity_pos_t::Zero(); } + virtual entity_pos_t GetUnitRadius() const { return entity_pos_t::Zero(); } + virtual void SetUnitClearance(const entity_pos_t& UNUSED(clearance)) { } + virtual bool IsControlPersistent() const { return true; } + virtual bool CheckShorePlacement() const { return true; } + virtual EFoundationCheck CheckFoundation(const std::string& UNUSED(className)) const { return EFoundationCheck(); } + virtual EFoundationCheck CheckFoundation(const std::string& UNUSED(className), bool UNUSED(onlyCenterPoint)) const { return EFoundationCheck(); } + virtual std::string CheckFoundation_wrapper(const std::string& UNUSED(className), bool UNUSED(onlyCenterPoint)) const { return std::string(); } + virtual bool CheckDuplicateFoundation() const { return true; } + virtual std::vector GetEntitiesByFlags(ICmpObstructionManager::flags_t UNUSED(flags)) const { return std::vector(); } + virtual std::vector GetEntitiesBlockingConstruction() const { return std::vector(); } + virtual std::vector GetEntitiesDeletedUponConstruction() const { return std::vector(); } + virtual void ResolveFoundationCollisions() const { } + virtual void SetActive(bool UNUSED(active)) { } + virtual void SetMovingFlag(bool UNUSED(enabled)) { } + virtual void SetDisableBlockMovementPathfinding(bool UNUSED(movementDisabled), bool UNUSED(pathfindingDisabled), int32_t UNUSED(shape)) { } + virtual bool GetBlockMovementFlag() const { return true; } + virtual void SetControlGroup(entity_id_t UNUSED(group)) { } + virtual entity_id_t GetControlGroup() const { return INVALID_ENTITY; } + virtual void SetControlGroup2(entity_id_t UNUSED(group2)) { } + virtual entity_id_t GetControlGroup2() const { return INVALID_ENTITY; } +}; class TestCmpObstructionManager : public CxxTest::TestSuite { @@ -474,4 +507,85 @@ TS_ASSERT_EQUALS(obSquare3.u, CFixedVector2D(fixed::FromInt(1), fixed::FromInt(0))); TS_ASSERT_EQUALS(obSquare3.v, CFixedVector2D(fixed::FromInt(0), fixed::FromInt(1))); } + + /** + * Verifies the calculations of distances between shapes. + */ + void test_distance_to() + { + // Create two more entities to have non-zero distances + entity_id_t ent4 = 4, + ent4g1 = ent4, + ent4g2 = INVALID_ENTITY, + ent5 = 5, + ent5g1 = ent5, + ent5g2 = INVALID_ENTITY; + + entity_pos_t ent4a = fixed::Zero(), + ent4w = fixed::FromInt(6), + ent4h = fixed::Zero(), + ent4x = ent1x, + ent4z = fixed::FromInt(20), + ent5a = fixed::Zero(), + ent5w = fixed::FromInt(2), + ent5h = fixed::FromInt(4), + ent5x = fixed::FromInt(20), + ent5z = ent1z; + + tag_t shape4 = cmp->AddStaticShape(ent4, ent4x, ent4z, ent4a, ent4w, ent4h, + ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION | + ICmpObstructionManager::FLAG_BLOCK_MOVEMENT | + ICmpObstructionManager::FLAG_MOVING, ent4g1, ent4g2); + + tag_t shape5 = cmp->AddStaticShape(ent5, ent5x, ent5z, ent5a, ent5w, ent5h, + ICmpObstructionManager::FLAG_BLOCK_CONSTRUCTION | + ICmpObstructionManager::FLAG_BLOCK_MOVEMENT | + ICmpObstructionManager::FLAG_MOVING, ent5g1, ent5g2); + + MockObstruction obstruction1, obstruction2, obstruction3, obstruction4, obstruction5; + testHelper->AddMock(ent1, IID_Obstruction, obstruction1); + testHelper->AddMock(ent2, IID_Obstruction, obstruction2); + testHelper->AddMock(ent3, IID_Obstruction, obstruction3); + testHelper->AddMock(ent4, IID_Obstruction, obstruction4); + testHelper->AddMock(ent5, IID_Obstruction, obstruction5); + obstruction1.obstruction = cmp->GetObstruction(shape1); + obstruction2.obstruction = cmp->GetObstruction(shape2); + obstruction3.obstruction = cmp->GetObstruction(shape3); + obstruction4.obstruction = cmp->GetObstruction(shape4); + obstruction5.obstruction = cmp->GetObstruction(shape5); + + TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent1, ent2)); + TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent2, ent1)); + TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent2, ent3)); + TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent3, ent2)); + + // Due to rounding errors we need to use some leeway + TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(80)), cmp->MaxDistanceToTarget(ent2, ent3), fixed::FromFloat(0.0001)); + TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(80)), cmp->MaxDistanceToTarget(ent3, ent2), fixed::FromFloat(0.0001)); + + TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent1, ent3)); + TS_ASSERT_EQUALS(fixed::Zero(), cmp->DistanceToTarget(ent3, ent1)); + + TS_ASSERT_EQUALS(fixed::FromInt(6), cmp->DistanceToTarget(ent1, ent4)); + TS_ASSERT_EQUALS(fixed::FromInt(6), cmp->DistanceToTarget(ent4, ent1)); + TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(125) + 3), cmp->MaxDistanceToTarget(ent1, ent4), fixed::FromFloat(0.0001)); + TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(125) + 3), cmp->MaxDistanceToTarget(ent4, ent1), fixed::FromFloat(0.0001)); + + TS_ASSERT_EQUALS(fixed::FromInt(7), cmp->DistanceToTarget(ent1, ent5)); + TS_ASSERT_EQUALS(fixed::FromInt(7), cmp->DistanceToTarget(ent5, ent1)); + TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(178)), cmp->MaxDistanceToTarget(ent1, ent5), fixed::FromFloat(0.0001)); + TS_ASSERT_DELTA(fixed::FromFloat(std::sqrt(178)), cmp->MaxDistanceToTarget(ent5, ent1), fixed::FromFloat(0.0001)); + + TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::Zero(), fixed::FromInt(1), true)); + TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::Zero(), fixed::FromInt(1), false)); + TS_ASSERT(cmp->IsInTargetRange(ent1, ent2, fixed::FromInt(1), fixed::FromInt(1), true)); + TS_ASSERT(!cmp->IsInTargetRange(ent1, ent2, fixed::FromInt(1), fixed::FromInt(1), false)); + + TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::Zero(), fixed::FromInt(10), true)); + TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::Zero(), fixed::FromInt(10), false)); + TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(1), fixed::FromInt(10), true)); + TS_ASSERT(!cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(1), fixed::FromInt(5), false)); + TS_ASSERT(!cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(10), fixed::FromInt(10), false)); + TS_ASSERT(cmp->IsInTargetRange(ent1, ent5, fixed::FromInt(10), fixed::FromInt(10), true)); + } }; Index: ps/trunk/source/simulation2/helpers/Geometry.h =================================================================== --- ps/trunk/source/simulation2/helpers/Geometry.h +++ ps/trunk/source/simulation2/helpers/Geometry.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -91,6 +91,32 @@ CFixedVector2D NearestPointOnSquare(const CFixedVector2D& point, const CFixedVector2D& u, const CFixedVector2D& v, const CFixedVector2D& halfSize); +/** + * Returns the shortest distance between two squares. + */ +fixed DistanceSquareToSquare(const CFixedVector2D& relativePos, + const CFixedVector2D& u1, const CFixedVector2D& v1, const CFixedVector2D& halfSize1, + const CFixedVector2D& u2, const CFixedVector2D& v2, const CFixedVector2D& halfSize2); + +/** + * Returns the greatest straight line distance from a point to a square. + * + * If @p countInsideAsZero is true, and the point is inside the rectangle, + * it will return 0. + * If @p countInsideAsZero is false, the greatest (positive) distance to the boundary + * will be returned regardless of where the point is. + */ +fixed MaxDistanceToSquare(const CFixedVector2D& point, + const CFixedVector2D& u, const CFixedVector2D& v, const CFixedVector2D& halfSize, + bool countInsideAsZero = false); + +/** + * Return the greatest straight line distance between two squares. + */ +fixed MaxDistanceSquareToSquare(const CFixedVector2D& relativePos, + const CFixedVector2D& u1, const CFixedVector2D& v1, const CFixedVector2D& halfSize1, + const CFixedVector2D& u2, const CFixedVector2D& v2, const CFixedVector2D& halfSize2); + bool TestRaySquare(const CFixedVector2D& a, const CFixedVector2D& b, const CFixedVector2D& u, const CFixedVector2D& v, const CFixedVector2D& halfSize); bool TestRayAASquare(const CFixedVector2D& a, const CFixedVector2D& b, const CFixedVector2D& halfSize); Index: ps/trunk/source/simulation2/helpers/Geometry.cpp =================================================================== --- ps/trunk/source/simulation2/helpers/Geometry.cpp +++ ps/trunk/source/simulation2/helpers/Geometry.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2018 Wildfire Games. +/* Copyright (C) 2019 Wildfire Games. * This file is part of 0 A.D. * * 0 A.D. is free software: you can redistribute it and/or modify @@ -186,6 +186,73 @@ } } +fixed Geometry::DistanceSquareToSquare(const CFixedVector2D& relativePos, const CFixedVector2D& u1, const CFixedVector2D& v1, const CFixedVector2D& halfSize1, const CFixedVector2D& u2, const CFixedVector2D& v2, const CFixedVector2D& halfSize2) +{ + /* + * The shortest distance between two non colliding squares equals the distance between a corner + * and other square. Thus calculating all 8 those distances and taking the smallest. + * For colliding squares we simply return 0. When one of the points is inside the other square + * we depend on DistanceToSquare's countInsideAsZero. When no point is inside the other square, + * it is enough to check that two adjacent edges of one square does not collide with the other square. + */ + fixed hw1 = halfSize1.X; + fixed hh1 = halfSize1.Y; + fixed hw2 = halfSize2.X; + fixed hh2 = halfSize2.Y; + if (TestRaySquare(relativePos + u1.Multiply(hw1) + v1.Multiply(hh1), relativePos - u1.Multiply(hw1) + v1.Multiply(hh1), u2, v2, halfSize2) || + TestRaySquare(relativePos + u1.Multiply(hw1) + v1.Multiply(hh1), relativePos + u1.Multiply(hw1) - v1.Multiply(hh1), u2, v2, halfSize2)) + return fixed::Zero(); + + return std::min(std::min(std::min( + DistanceToSquare(relativePos + u1.Multiply(hw1) + v1.Multiply(hh1), u2, v2, halfSize2, true), + DistanceToSquare(relativePos + u1.Multiply(hw1) - v1.Multiply(hh1), u2, v2, halfSize2, true)), + std::min( + DistanceToSquare(relativePos - u1.Multiply(hw1) + v1.Multiply(hh1), u2, v2, halfSize2, true), + DistanceToSquare(relativePos - u1.Multiply(hw1) - v1.Multiply(hh1), u2, v2, halfSize2, true))), + std::min(std::min( + DistanceToSquare(relativePos + u2.Multiply(hw2) + v2.Multiply(hh2), u1, v1, halfSize1, true), + DistanceToSquare(relativePos + u2.Multiply(hw2) - v2.Multiply(hh2), u1, v1, halfSize1, true)), + std::min( + DistanceToSquare(relativePos - u2.Multiply(hw2) + v2.Multiply(hh2), u1, v1, halfSize1, true), + DistanceToSquare(relativePos - u2.Multiply(hw2) - v2.Multiply(hh2), u1, v1, halfSize1, true)))); +} + +fixed Geometry::MaxDistanceToSquare(const CFixedVector2D& point, const CFixedVector2D& u, const CFixedVector2D& v, const CFixedVector2D& halfSize, bool countInsideAsZero) +{ + fixed hw = halfSize.X; + fixed hh = halfSize.Y; + + if (point.Dot(u).Absolute() < hw && point.Dot(v).Absolute() < hh && countInsideAsZero) + return fixed::Zero(); + + /* + * The maximum distance from a point to an edge of a square equals the greatest distance + * from the point to the a corner. Thus calculating all and taking the greatest. + */ + return std::max(std::max( + (point + u.Multiply(hw) + v.Multiply(hh)).Length(), + (point + u.Multiply(hw) - v.Multiply(hh)).Length()), + std::max( + (point - u.Multiply(hw) + v.Multiply(hh)).Length(), + (point - u.Multiply(hw) - v.Multiply(hh)).Length())); +} + +fixed Geometry::MaxDistanceSquareToSquare(const CFixedVector2D& relativePos, const CFixedVector2D& u1, const CFixedVector2D& v1, const CFixedVector2D& halfSize1, const CFixedVector2D& u2, const CFixedVector2D& v2, const CFixedVector2D& halfSize2) +{ + /* + * The maximum distance from an edge of a square to the edge of another square + * equals the greatest distance from the any of the 16 corner corner distances. + */ + fixed hw1 = halfSize1.X; + fixed hh1 = halfSize1.Y; + + return std::max(std::max( + MaxDistanceToSquare(relativePos + u1.Multiply(hw1) + v1.Multiply(hh1), u2, v2, halfSize2, true), + MaxDistanceToSquare(relativePos + u1.Multiply(hw1) - v1.Multiply(hh1), u2, v2, halfSize2, true)), + std::max(MaxDistanceToSquare(relativePos - u1.Multiply(hw1) + v1.Multiply(hh1), u2, v2, halfSize2, true), + MaxDistanceToSquare(relativePos - u1.Multiply(hw1) - v1.Multiply(hh1), u2, v2, halfSize2, true))); +} + bool Geometry::TestRaySquare(const CFixedVector2D& a, const CFixedVector2D& b, const CFixedVector2D& u, const CFixedVector2D& v, const CFixedVector2D& halfSize) { /*