bool Geometry::TestSquareSquare( const CFixedVector2D& c0, const CFixedVector2D& u0, const CFixedVector2D& v0, const CFixedVector2D& halfSize0, const CFixedVector2D& c1, const CFixedVector2D& u1, const CFixedVector2D& v1, const CFixedVector2D& halfSize1) { // TODO: need to test this carefully CFixedVector2D corner0a = c0 + u0.Multiply(halfSize0.X) + v0.Multiply(halfSize0.Y); CFixedVector2D corner0b = c0 - u0.Multiply(halfSize0.X) - v0.Multiply(halfSize0.Y); CFixedVector2D corner1a = c1 + u1.Multiply(halfSize1.X) + v1.Multiply(halfSize1.Y); CFixedVector2D corner1b = c1 - u1.Multiply(halfSize1.X) - v1.Multiply(halfSize1.Y); // Do a SAT test for each square vs each edge of the other square if (!SquareSAT(corner0a - c1, -u0, u1, v1, halfSize1)) return false; if (!SquareSAT(corner0a - c1, v0, u1, v1, halfSize1)) return false; if (!SquareSAT(corner0b - c1, u0, u1, v1, halfSize1)) return false; if (!SquareSAT(corner0b - c1, -v0, u1, v1, halfSize1)) return false; if (!SquareSAT(corner1a - c0, -u1, u0, v0, halfSize0)) return false; if (!SquareSAT(corner1a - c0, v1, u0, v0, halfSize0)) return false; if (!SquareSAT(corner1b - c0, u1, u0, v0, halfSize0)) return false; if (!SquareSAT(corner1b - c0, -v1, u0, v0, halfSize0)) return false; return true; }
bool Geometry::TestRaySquare(const CFixedVector2D& a, const CFixedVector2D& b, const CFixedVector2D& u, const CFixedVector2D& v, const CFixedVector2D& halfSize) { /* * We only consider collisions to be when the ray goes from outside to inside the shape (and possibly out again). * Various cases to consider: * 'a' inside, 'b' inside -> no collision * 'a' inside, 'b' outside -> no collision * 'a' outside, 'b' inside -> collision * 'a' outside, 'b' outside -> depends; use separating axis theorem: * if the ray's bounding box is outside the square -> no collision * if the whole square is on the same side of the ray -> no collision * otherwise -> collision * (Points on the edge are considered 'inside'.) */ fixed hw = halfSize.X; fixed hh = halfSize.Y; fixed au = a.Dot(u); fixed av = a.Dot(v); if (-hw <= au && au <= hw && -hh <= av && av <= hh) return false; // a is inside fixed bu = b.Dot(u); fixed bv = b.Dot(v); if (-hw <= bu && bu <= hw && -hh <= bv && bv <= hh) // TODO: isn't this subsumed by the next checks? return true; // a is outside, b is inside if ((au < -hw && bu < -hw) || (au > hw && bu > hw) || (av < -hh && bv < -hh) || (av > hh && bv > hh)) return false; // ab is entirely above/below/side the square CFixedVector2D abp = (b - a).Perpendicular(); fixed s0 = abp.Dot((u.Multiply(hw) + v.Multiply(hh)) - a); fixed s1 = abp.Dot((u.Multiply(hw) - v.Multiply(hh)) - a); fixed s2 = abp.Dot((-u.Multiply(hw) - v.Multiply(hh)) - a); fixed s3 = abp.Dot((-u.Multiply(hw) + v.Multiply(hh)) - a); if (s0.IsZero() || s1.IsZero() || s2.IsZero() || s3.IsZero()) return true; // ray intersects the corner bool sign = (s0 < fixed::Zero()); if ((s1 < fixed::Zero()) != sign || (s2 < fixed::Zero()) != sign || (s3 < fixed::Zero()) != sign) return true; // ray cuts through the square return false; }
/** * Separating axis test; returns true if the square defined by u/v/halfSize at the origin * is not entirely on the clockwise side of a line in direction 'axis' passing through 'a' */ static bool SquareSAT(const CFixedVector2D& a, const CFixedVector2D& axis, const CFixedVector2D& u, const CFixedVector2D& v, const CFixedVector2D& halfSize) { fixed hw = halfSize.X; fixed hh = halfSize.Y; CFixedVector2D p = axis.Perpendicular(); if (p.Dot((u.Multiply(hw) + v.Multiply(hh)) - a) <= fixed::Zero()) return true; if (p.Dot((u.Multiply(hw) - v.Multiply(hh)) - a) <= fixed::Zero()) return true; if (p.Dot((-u.Multiply(hw) - v.Multiply(hh)) - a) <= fixed::Zero()) return true; if (p.Dot((-u.Multiply(hw) + v.Multiply(hh)) - a) <= fixed::Zero()) return true; return false; }
virtual CFixedVector3D PickSpawnPoint(entity_id_t spawned) { // Try to find a free space around the building's footprint. // (Note that we use the footprint, not the obstruction shape - this might be a bit dodgy // because the footprint might be inside the obstruction, but it hopefully gives us a nicer // shape.) const CFixedVector3D error(fixed::FromInt(-1), fixed::FromInt(-1), fixed::FromInt(-1)); CmpPtr<ICmpPosition> cmpPosition(GetSimContext(), GetEntityId()); if (!cmpPosition || !cmpPosition->IsInWorld()) return error; CmpPtr<ICmpObstructionManager> cmpObstructionManager(GetSimContext(), SYSTEM_ENTITY); if (!cmpObstructionManager) return error; entity_pos_t spawnedRadius; ICmpObstructionManager::tag_t spawnedTag; CmpPtr<ICmpObstruction> cmpSpawnedObstruction(GetSimContext(), spawned); if (cmpSpawnedObstruction) { spawnedRadius = cmpSpawnedObstruction->GetUnitRadius(); spawnedTag = cmpSpawnedObstruction->GetObstruction(); } // else use zero radius // Get passability class from UnitMotion CmpPtr<ICmpUnitMotion> cmpUnitMotion(GetSimContext(), spawned); if (!cmpUnitMotion) return error; ICmpPathfinder::pass_class_t spawnedPass = cmpUnitMotion->GetPassabilityClass(); CmpPtr<ICmpPathfinder> cmpPathfinder(GetSimContext(), SYSTEM_ENTITY); if (!cmpPathfinder) return error; CFixedVector2D initialPos = cmpPosition->GetPosition2D(); entity_angle_t initialAngle = cmpPosition->GetRotation().Y; // Max spawning distance in tiles const i32 maxSpawningDistance = 4; if (m_Shape == CIRCLE) { // Expand outwards from foundation for (i32 dist = 0; dist <= maxSpawningDistance; ++dist) { // The spawn point should be far enough from this footprint to fit the unit, plus a little gap entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + (int)TERRAIN_TILE_SIZE*dist); entity_pos_t radius = m_Size0 + clearance; // Try equally-spaced points around the circle in alternating directions, starting from the front const i32 numPoints = 31 + 2*dist; for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2] { entity_angle_t angle = initialAngle + (entity_angle_t::Pi()*2).Multiply(entity_angle_t::FromInt(i)/(int)numPoints); fixed s, c; sincos_approx(angle, s, c); CFixedVector3D pos (initialPos.X + s.Multiply(radius), fixed::Zero(), initialPos.Y + c.Multiply(radius)); SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Z, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS) return pos; // this position is okay, so return it } } } else { fixed s, c; sincos_approx(initialAngle, s, c); // Expand outwards from foundation for (i32 dist = 0; dist <= maxSpawningDistance; ++dist) { // The spawn point should be far enough from this footprint to fit the unit, plus a little gap entity_pos_t clearance = spawnedRadius + entity_pos_t::FromInt(2 + (int)TERRAIN_TILE_SIZE*dist); for (i32 edge = 0; edge < 4; ++edge) { // Try equally-spaced points along the edge in alternating directions, starting from the middle const i32 numPoints = 9 + 2*dist; // Compute the direction and length of the current edge CFixedVector2D dir; fixed sx, sy; switch (edge) { case 0: dir = CFixedVector2D(c, -s); sx = m_Size0; sy = m_Size1; break; case 1: dir = CFixedVector2D(-s, -c); sx = m_Size1; sy = m_Size0; break; case 2: dir = CFixedVector2D(s, c); sx = m_Size1; sy = m_Size0; break; case 3: dir = CFixedVector2D(-c, s); sx = m_Size0; sy = m_Size1; break; } CFixedVector2D center = initialPos - dir.Perpendicular().Multiply(sy/2 + clearance); dir = dir.Multiply((sx + clearance*2) / (int)(numPoints-1)); for (i32 i = 0; i < (numPoints+1)/2; i = (i > 0 ? -i : 1-i)) // [0, +1, -1, +2, -2, ... (np-1)/2, -(np-1)/2] { CFixedVector2D pos (center + dir*i); SkipTagObstructionFilter filter(spawnedTag); // ignore collisions with the spawned entity if (cmpPathfinder->CheckUnitPlacement(filter, pos.X, pos.Y, spawnedRadius, spawnedPass) == ICmpObstruction::FOUNDATION_CHECK_SUCCESS) return CFixedVector3D(pos.X, fixed::Zero(), pos.Y); // this position is okay, so return it } } } } return error; }
CFixedVector2D Geometry::NearestPointOnSquare(const CFixedVector2D& point, const CFixedVector2D& u, const CFixedVector2D& v, const CFixedVector2D& halfSize) { /* * Relative to its own coordinate system, we have a square like: * * A : : C * : : * - - #### B #### - - * #\ /# * # \ / # * D --0-- E v * # / \ # ^ * #/ \# | * - - #### G #### - - -->u * : : * F : : H * * where 0 is the center, u and v are unit axes, * and the square is hw*2 by hh*2 units in size. * * Points in the BDEG regions are nearest to the corresponding edge. * Points in the ACFH regions are nearest to the corresponding corner. * * So we just need to check all of the regions to work out which calculations to apply. * */ // du, dv are the location of the point in the square's coordinate system fixed du = point.Dot(u); fixed dv = point.Dot(v); fixed hw = halfSize.X; fixed hh = halfSize.Y; if (-hw < du && du < hw) // regions B, G; or regions D, E inside the square { if (-hh < dv && dv < hh && (du.Absolute() - hw).Absolute() < (dv.Absolute() - hh).Absolute()) // regions D, E { if (du >= fixed::Zero()) // E return u.Multiply(hw) + v.Multiply(dv); else // D return -u.Multiply(hw) + v.Multiply(dv); } else // B, G { if (dv >= fixed::Zero()) // B return v.Multiply(hh) + u.Multiply(du); else // G return -v.Multiply(hh) + u.Multiply(du); } } else if (-hh < dv && dv < hh) // regions D, E outside the square { if (du >= fixed::Zero()) // E return u.Multiply(hw) + v.Multiply(dv); else // D return -u.Multiply(hw) + v.Multiply(dv); } else // regions A, C, F, H { CFixedVector2D corner; if (du < fixed::Zero()) // A, F corner -= u.Multiply(hw); else // C, H corner += u.Multiply(hw); if (dv < fixed::Zero()) // F, H corner -= v.Multiply(hh); else // A, C corner += v.Multiply(hh); return corner; } }