Index: binaries/data/mods/public/simulation/components/UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitAI.js +++ binaries/data/mods/public/simulation/components/UnitAI.js @@ -1700,8 +1700,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)) + var 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) @@ -4488,8 +4488,8 @@ UnitAI.prototype.CheckPointRangeExplicit = function(x, z, min, max) { - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return cmpUnitMotion.IsInPointRange(x, z, min, max); + var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInPointRange(this.entity, x, z, min, max, true); }; UnitAI.prototype.CheckTargetRange = function(target, iid, type) @@ -4499,8 +4499,8 @@ return false; var range = cmpRanged.GetRange(type); - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return cmpUnitMotion.IsInTargetRange(target, range.min, range.max); + var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, true); }; /** @@ -4548,14 +4548,14 @@ if (maxRangeSq < 0) return false; - var cmpUnitMotion = Engine.QueryInterface(this.entity, IID_UnitMotion); - return cmpUnitMotion.IsInTargetRange(target, range.min, Math.sqrt(maxRangeSq)); + var 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); + var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInTargetRange(this.entity, target, min, max, true); }; UnitAI.prototype.CheckGarrisonRange = function(target) @@ -4569,8 +4569,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); + var cmpObstructionManager = Engine.QueryInterface(SYSTEM_ENTITY, IID_ObstructionManager); + return cmpObstructionManager.IsInTargetRange(this.entity, target, range.min, range.max, true); }; /** Index: binaries/data/mods/public/simulation/components/UnitMotionFlying.js =================================================================== --- binaries/data/mods/public/simulation/components/UnitMotionFlying.js +++ 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: binaries/data/mods/public/simulation/components/tests/test_UnitAI.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UnitAI.js +++ binaries/data/mods/public/simulation/components/tests/test_UnitAI.js @@ -63,6 +63,9 @@ GetEnemies: function() { return []; }, }); + AddMock(SYSTEM_ENTITY, IID_ObstructionManager, { + IsInTargetRange: function(ent, target, min, max, opposite) { return true; } + }); var unitAI = ConstructComponent(unit, "UnitAI", { "FormationController": "false", "DefaultStance": "aggressive" }); @@ -85,7 +88,6 @@ 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) { }, StopMoving: function() { }, GetPassabilityClassName: function() { return "default"; }, @@ -204,6 +206,10 @@ GetNumPlayers: function() { return 2; }, }); + AddMock(SYSTEM_ENTITY, IID_ObstructionManager, { + IsInTargetRange: function(ent, target, min, max) { return true; } + }); + AddMock(playerEntity, IID_Player, { IsAlly: function() { return false; }, IsEnemy: function() { return true; }, @@ -236,7 +242,6 @@ 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) { }, StopMoving: function() { }, GetPassabilityClassName: function() { return "default"; }, @@ -282,7 +287,6 @@ AddMock(controller, IID_UnitMotion, { SetSpeed: function(speed) { }, MoveToPointRange: function(x, z, minRange, maxRange) { }, - IsInTargetRange: function(target, min, max) { return true; }, GetPassabilityClassName: function() { return "default"; }, }); Index: binaries/data/mods/public/simulation/components/tests/test_UnitMotionFlying.js =================================================================== --- binaries/data/mods/public/simulation/components/tests/test_UnitMotionFlying.js +++ binaries/data/mods/public/simulation/components/tests/test_UnitMotionFlying.js @@ -54,10 +54,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: source/simulation2/components/CCmpObstructionManager.cpp =================================================================== --- source/simulation2/components/CCmpObstructionManager.cpp +++ source/simulation2/components/CCmpObstructionManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* 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 @@ -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; + fixed DistanceBetweenShapes(const ObstructionSquare& source, const ObstructionSquare& target) const; + 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; @@ -656,20 +665,167 @@ 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 { CmpPtr cmpPosition(GetSimContext(), ent); if (!cmpPosition || !cmpPosition->IsInWorld()) return fixed::FromInt(-1); - ObstructionSquare s; + ObstructionSquare obstruction; + CmpPtr cmpObstruction(GetSimContext(), ent); + if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(obstruction)) + return (CFixedVector2D(cmpPosition->GetPosition2D().X, cmpPosition->GetPosition2D().Y) - CFixedVector2D(px, pz)).Length(); + + ObstructionSquare point; + point.x = px; + point.z = pz; + return DistanceBetweenShapes(obstruction, point); +} + +fixed CCmpObstructionManager::MaxDistanceToPoint(entity_id_t ent, entity_pos_t px, entity_pos_t pz) const +{ + CmpPtr cmpPosition(GetSimContext(), ent); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return fixed::FromInt(-1); + + ObstructionSquare obstruction; CmpPtr cmpObstruction(GetSimContext(), ent); - if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(s)) - return (CFixedVector2D(px, pz) - cmpPosition->GetPosition2D()).Length(); + if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(obstruction)) + return (CFixedVector2D(cmpPosition->GetPosition2D().X, cmpPosition->GetPosition2D().Y) - CFixedVector2D(px, pz)).Length(); - return Geometry::DistanceToSquare(CFixedVector2D(px - s.x, pz - s.z), s.u, s.v, CFixedVector2D(s.hw, s.hh)); + ObstructionSquare point; + point.x = px; + point.z = pz; + return MaxDistanceBetweenShapes(obstruction, point); } +fixed CCmpObstructionManager::DistanceToTarget(entity_id_t ent, entity_id_t target) const +{ + CmpPtr cmpPosition(GetSimContext(), ent); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return fixed::FromInt(-1); + + ObstructionSquare obstruction; + CmpPtr cmpObstruction(GetSimContext(), ent); + if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(obstruction)) + return DistanceToPoint(target, cmpPosition->GetPosition2D().X, cmpPosition->GetPosition2D().Y); + + CmpPtr cmpPositionTarget(GetSimContext(), target); + if (!cmpPositionTarget || !cmpPositionTarget->IsInWorld()) + return fixed::FromInt(-1); + + ObstructionSquare target_obstruction; + CmpPtr cmpObstructionTarget(GetSimContext(), target); + if (!cmpObstructionTarget || !cmpObstructionTarget->GetObstructionSquare(target_obstruction)) + 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 +{ + CmpPtr cmpPosition(GetSimContext(), ent); + if (!cmpPosition || !cmpPosition->IsInWorld()) + return fixed::FromInt(-1); + + ObstructionSquare obstruction; + CmpPtr cmpObstruction(GetSimContext(), ent); + if (!cmpObstruction || !cmpObstruction->GetObstructionSquare(obstruction)) + return MaxDistanceToPoint(target, cmpPosition->GetPosition2D().X, cmpPosition->GetPosition2D().Y); + + CmpPtr cmpPositionTarget(GetSimContext(), target); + if (!cmpPositionTarget || !cmpPositionTarget->IsInWorld()) + return fixed::FromInt(-1); + + ObstructionSquare target_obstruction; + CmpPtr cmpObstructionTarget(GetSimContext(), target); + if (!cmpObstructionTarget || !cmpObstructionTarget->GetObstructionSquare(target_obstruction)) + return MaxDistanceToPoint(ent, cmpPositionTarget->GetPosition2D().X, cmpPositionTarget->GetPosition2D().Y); + + return MaxDistanceBetweenShapes(obstruction, target_obstruction); +} + +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 more 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. + */ +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 && (opposite ? MaxDistanceToPoint(ent, px, pz) : dist) > minRange; +} + +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 && (opposite ? MaxDistanceToTarget(ent, target) : dist) > minRange; +} +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 && distance > minRange; +} + + 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 { PROFILE("TestLine"); Index: source/simulation2/components/CCmpUnitMotion.cpp =================================================================== --- source/simulation2/components/CCmpUnitMotion.cpp +++ source/simulation2/components/CCmpUnitMotion.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* 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 @@ -494,9 +494,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); @@ -1455,49 +1453,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 @@ -1668,76 +1623,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: source/simulation2/components/ICmpObstructionManager.h =================================================================== --- source/simulation2/components/ICmpObstructionManager.h +++ source/simulation2/components/ICmpObstructionManager.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* 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 @@ -164,6 +164,36 @@ 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; + + /** + * 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. Index: source/simulation2/components/ICmpObstructionManager.cpp =================================================================== --- source/simulation2/components/ICmpObstructionManager.cpp +++ source/simulation2/components/ICmpObstructionManager.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2010 Wildfire Games. +/* 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 @@ -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: source/simulation2/components/ICmpUnitMotion.h =================================================================== --- source/simulation2/components/ICmpUnitMotion.h +++ source/simulation2/components/ICmpUnitMotion.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* 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 @@ -47,18 +47,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: source/simulation2/components/ICmpUnitMotion.cpp =================================================================== --- source/simulation2/components/ICmpUnitMotion.cpp +++ source/simulation2/components/ICmpUnitMotion.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* 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 @@ -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) @@ -51,16 +49,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: source/simulation2/helpers/Geometry.h =================================================================== --- source/simulation2/helpers/Geometry.h +++ source/simulation2/helpers/Geometry.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* 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 @@ -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: source/simulation2/helpers/Geometry.cpp =================================================================== --- source/simulation2/helpers/Geometry.cpp +++ source/simulation2/helpers/Geometry.cpp @@ -1,4 +1,4 @@ -/* Copyright (C) 2017 Wildfire Games. +/* 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 @@ -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) { /*